Skip to content

Commit

Permalink
Namecoin: Deserialize name scripts.
Browse files Browse the repository at this point in the history
  • Loading branch information
JeremyRand committed Oct 1, 2018
1 parent 5fc9e66 commit d060326
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 23 deletions.
2 changes: 1 addition & 1 deletion electrum_nmc/auxpow.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ def fast_txid(tx):
def stub_parse_output(vds, i):
vds.read_int64() # d['value']
vds.read_bytes(vds.read_compact_size()) # scriptPubKey
return {'type': TYPE_SCRIPT, 'address': None, 'value': 0}
return {'type': TYPE_SCRIPT, 'address': None, 'value': 0, 'name_op': None}

# This is equivalent to tx.deserialize(), but doesn't parse outputs.
def fast_tx_deserialize(tx):
Expand Down
6 changes: 5 additions & 1 deletion electrum_nmc/gui/qt/transaction_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

from electrum_nmc.bitcoin import base_encode
from electrum_nmc.i18n import _
from electrum_nmc.names import format_name_op
from electrum_nmc.plugin import run_hook
from electrum_nmc import simple_config
from electrum_nmc.util import bfh
Expand Down Expand Up @@ -319,11 +320,14 @@ def format_amount(amt):
o_text.setFont(QFont(MONOSPACE_FONT))
o_text.setReadOnly(True)
cursor = o_text.textCursor()
for addr, v in self.tx.get_outputs():
for addr, v, name_op in self.tx.get_outputs():
cursor.insertText(addr, text_format(addr))
if v is not None:
cursor.insertText('\t', ext)
cursor.insertText(format_amount(v), ext)
if name_op is not None:
cursor.insertText('\n', ext)
cursor.insertText(format_name_op(name_op), ext)
cursor.insertBlock()
vbox.addWidget(o_text)

Expand Down
60 changes: 56 additions & 4 deletions electrum_nmc/names.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,76 @@ def split_name_script(decoded):
# NAME_NEW (hash) 2DROP (Bitcoin TxOut)
match = [ OP_NAME_NEW, opcodes.OP_PUSHDATA4, opcodes.OP_2DROP ]
if match_decoded(decoded[:len(match)], match):
return {"name_op": OP_NAME_NEW, "address_scriptPubKey": decoded[len(match):]}
return {"name_op": {"op": OP_NAME_NEW, "hash": decoded[1][1]}, "address_scriptPubKey": decoded[len(match):]}

# name_firstupdate TxOuts look like:
# NAME_FIRSTUPDATE (name) (rand) (value) 2DROP 2DROP (Bitcoin TxOut)
match = [ OP_NAME_FIRSTUPDATE, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_2DROP, opcodes.OP_2DROP ]
if match_decoded(decoded[:len(match)], match):
return {"name_op": OP_NAME_FIRSTUPDATE, "address_scriptPubKey": decoded[len(match):]}
return {"name_op": {"op": OP_NAME_FIRSTUPDATE, "name": decoded[1][1], "rand": decoded[2][1], "value": decoded[3][1]}, "address_scriptPubKey": decoded[len(match):]}

# name_update TxOuts look like:
# NAME_UPDATE (name) (value) 2DROP DROP (Bitcoin TxOut)
match = [ OP_NAME_UPDATE, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_2DROP, opcodes.OP_DROP ]
if match_decoded(decoded[:len(match)], match):
return {"name_op": OP_NAME_UPDATE, "address_scriptPubKey": decoded[len(match):]}
return {"name_op": {"op": OP_NAME_UPDATE, "name": decoded[1][1], "value": decoded[2][1]}, "address_scriptPubKey": decoded[len(match):]}

return {"name_op": None, "address_scriptPubKey": decoded}

def get_name_op_from_output_script(_bytes):
decoded = [x for x in script_GetOp(_bytes)]

from .transaction import match_decoded, opcodes
# Extract the name script if one is present.
return split_name_script(decoded)["name_op"]

def name_op_to_script(name_op):
if name_op is None:
script = ''
elif name_op["op"] == OP_NAME_NEW:
script = '51' # OP_NAME_NEW
script += push_script(bh2u(name_op["hash"]))
script += '6d' # OP_2DROP
elif name_op["op"] == OP_NAME_FIRSTUPDATE:
script = '52' # OP_NAME_FIRSTUPDATE
script += push_script(bh2u(name_op["name"]))
script += push_script(bh2u(name_op["rand"]))
script += push_script(bh2u(name_op["value"]))
script += '6d' # OP_2DROP
script += '6d' # OP_2DROP
elif name_op["op"] == OP_NAME_UPDATE:
script = '53' # OP_NAME_UPDATE
script += push_script(bh2u(name_op["name"]))
script += push_script(bh2u(name_op["value"]))
script += '6d' # OP_2DROP
script += '75' # OP_DROP
else:
raise BitcoinException('unknown name op: {}'.format(name_op))
return script


def format_name_op(name_op):
if name_op is None:
return ''
if "hash" in name_op:
formatted_hash = "Commitment = " + bh2u(name_op["hash"])
if "rand" in name_op:
formatted_rand = "Salt = " + bh2u(name_op["rand"])
if "name" in name_op:
formatted_name = "Name = Hex " + bh2u(name_op["name"])
if "value" in name_op:
formatted_value = "Data = Hex " + bh2u(name_op["value"])

if name_op["op"] == OP_NAME_NEW:
return "\tName Pre-Registration\n\t\t" + formatted_hash
if name_op["op"] == OP_NAME_FIRSTUPDATE:
return "\tName Registration\n\t\t" + formatted_name + "\n\t\t" + formatted_rand + "\n\t\t" + formatted_value
if name_op["op"] == OP_NAME_UPDATE:
return "\tName Update\n\t\t" + formatted_name + "\n\t\t" + formatted_value


from .bitcoin import push_script
from .transaction import match_decoded, opcodes, script_GetOp
from .util import bh2u

OP_NAME_NEW = opcodes.OP_1
OP_NAME_FIRSTUPDATE = opcodes.OP_2
Expand Down
2 changes: 1 addition & 1 deletion electrum_nmc/plugins/hw_wallet/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def get_library_not_available_message(self) -> str:
def is_any_tx_output_on_change_branch(tx):
if not hasattr(tx, 'output_info'):
return False
for _type, address, amount in tx.outputs():
for _type, address, amount, name_op in tx.outputs():
info = tx.output_info.get(address)
if info is not None:
index, xpubs, m = info.address_index, info.sorted_xpubs, info.num_sig
Expand Down
4 changes: 2 additions & 2 deletions electrum_nmc/plugins/ledger/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,9 +378,9 @@ def sign_transaction(self, tx, password):

txOutput = var_int(len(tx.outputs()))
for txout in tx.outputs():
output_type, addr, amount = txout
output_type, addr, amount, name_op = txout
txOutput += int_to_hex(amount, 8)
script = tx.pay_script(output_type, addr)
script = tx.pay_script(output_type, addr, name_op)
txOutput += var_int(len(script)//2)
txOutput += script
txOutput = bfh(txOutput)
Expand Down
37 changes: 24 additions & 13 deletions electrum_nmc/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class MalformedBitcoinScript(Exception):
pass


TxOutput = NamedTuple("TxOutput", [('type', int), ('address', str), ('value', Union[int, str])])
TxOutput = NamedTuple("TxOutput", [('type', int), ('address', str), ('value', Union[int, str]), ("name_op", dict)])
# ^ value is str when the output is set to max: '!'


Expand Down Expand Up @@ -571,6 +571,12 @@ def parse_output(vds, i):
raise SerializationError('invalid output amount (negative)')
scriptPubKey = vds.read_bytes(vds.read_compact_size())
d['type'], d['address'] = get_address_from_output_script(scriptPubKey)
d['name_op'] = get_name_op_from_output_script(scriptPubKey)
if d['name_op'] is not None:
# Subtract the 0.01 NMC that's permanently locked in the name
d['value'] = d['value'] - COIN // 100
if d['value'] < 0:
raise SerializationError('invalid output amount (insufficient for name output)')
d['scriptPubKey'] = bh2u(scriptPubKey)
d['prevout_n'] = i
return d
Expand Down Expand Up @@ -764,7 +770,7 @@ def deserialize(self, force_full_parse=False):
else:
d = deserialize(self.raw, force_full_parse, raw_bytes=self.raw_bytes, start_position=self.start_position)
self._inputs = d['inputs']
self._outputs = [TxOutput(x['type'], x['address'], x['value']) for x in d['outputs']]
self._outputs = [TxOutput(x['type'], x['address'], x['value'], x['name_op']) for x in d['outputs']]
self.locktime = d['lockTime']
self.version = d['version']
self.is_partial_originally = d['partial']
Expand Down Expand Up @@ -796,13 +802,15 @@ def from_io(klass, inputs, outputs, locktime=0):
return self

@classmethod
def pay_script(self, output_type, addr):
def pay_script(self, output_type, addr, name_op = None):
name_prefix = name_op_to_script(name_op)

if output_type == TYPE_SCRIPT:
return addr
return name_prefix + addr
elif output_type == TYPE_ADDRESS:
return bitcoin.address_to_script(addr)
return name_prefix + bitcoin.address_to_script(addr)
elif output_type == TYPE_PUBKEY:
return bitcoin.public_key_to_p2pk_script(addr)
return name_prefix + bitcoin.public_key_to_p2pk_script(addr)
else:
raise TypeError('Unknown output type')

Expand Down Expand Up @@ -1031,12 +1039,15 @@ def BIP69_sort(self, inputs=True, outputs=True):
if inputs:
self._inputs.sort(key = lambda i: (i['prevout_hash'], i['prevout_n']))
if outputs:
self._outputs.sort(key = lambda o: (o[2], self.pay_script(o[0], o[1])))
self._outputs.sort(key = lambda o: (o[2], self.pay_script(o[0], o[1], o[3])))

def serialize_output(self, output):
output_type, addr, amount = output
output_type, addr, amount, name_op = output
if name_op is not None:
# Add the 0.01 NMC that's permanently locked in the name
amount = amount + COIN // 100
s = int_to_hex(amount, 8)
script = self.pay_script(output_type, addr)
script = self.pay_script(output_type, addr, name_op)
s += var_int(len(script)//2)
s += script
return s
Expand Down Expand Up @@ -1128,7 +1139,7 @@ def input_value(self):
return sum(x['value'] for x in self.inputs())

def output_value(self):
return sum(val for tp, addr, val in self.outputs())
return sum(val for tp, addr, val, name_op in self.outputs())

def get_fee(self):
return self.input_value() - self.output_value()
Expand Down Expand Up @@ -1250,11 +1261,11 @@ def get_outputs(self):
addr = bitcoin.public_key_to_p2pkh(bfh(o.address))
else:
addr = 'SCRIPT ' + o.address
outputs.append((addr, o.value)) # consider using yield (addr, v)
outputs.append((addr, o.value, o.name_op)) # consider using yield (addr, v)
return outputs

def get_output_addresses(self):
return [addr for addr, val in self.get_outputs()]
return [addr for addr, val, name_op in self.get_outputs()]


def has_address(self, addr):
Expand Down Expand Up @@ -1290,5 +1301,5 @@ def tx_from_str(txt):
return tx_dict["hex"]


from .names import split_name_script
from .names import get_name_op_from_output_script, name_op_to_script, split_name_script

2 changes: 1 addition & 1 deletion electrum_nmc/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -787,7 +787,7 @@ def add_hw_info(self, tx):
info = {}
xpubs = self.get_master_public_keys()
for txout in tx.outputs():
_type, addr, amount = txout
_type, addr, amount, name_op = txout
if self.is_mine(addr):
index = self.get_address_index(addr)
pubkeys = self.get_public_keys(addr)
Expand Down

0 comments on commit d060326

Please sign in to comment.