Skip to content

Commit

Permalink
Namecoin: enforce length restrictions in name script serialization
Browse files Browse the repository at this point in the history
Fixes #46
  • Loading branch information
JeremyRand committed Oct 23, 2018
1 parent fb8cad9 commit 897ddb4
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 5 deletions.
7 changes: 6 additions & 1 deletion electrum_nmc/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from . import bitcoin
from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS
from .i18n import _
from .names import build_name_new, name_expires_in, name_identifier_to_scripthash, OP_NAME_FIRSTUPDATE, OP_NAME_UPDATE
from .names import build_name_new, name_expires_in, name_identifier_to_scripthash, OP_NAME_FIRSTUPDATE, OP_NAME_UPDATE, validate_value_length
from .transaction import Transaction, multisig_script, TxOutput
from .paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED
from .synchronizer import Notifier
Expand Down Expand Up @@ -675,6 +675,11 @@ def name_update(self, identifier, value=None, destination=None, amount=0.0, fee=
def name_autoregister(self, identifier, value, destination=None, amount=0.0, fee=None, from_addr=None, change_addr=None, nocheck=False, rbf=None, password=None, locktime=None, allow_existing=False):
"""Creates a name_new transaction, broadcasts it, creates a corresponding name_firstupdate transaction, and queues it. """

# Validate the value before we try to pre-register the name. That way,
# if the value is invalid, we'll be able to cancel the registration
# without losing money in fees.
validate_value_length(value)

# TODO: Don't hardcode the 0.005 name_firstupdate fee
new_result = self.name_new(identifier, amount=amount+0.005, fee=fee, from_addr=from_addr, change_addr=change_addr, nocheck=nocheck, rbf=rbf, password=password, locktime=locktime, allow_existing=allow_existing)
new_txid = new_result["txid"]
Expand Down
9 changes: 7 additions & 2 deletions electrum_nmc/gui/qt/configure_name_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,13 @@ def update_and_broadcast(self, identifier, value, transfer_to):
name_update = self.main_window.console.namespace.get('name_update')
broadcast = self.main_window.console.namespace.get('broadcast')

# TODO: support non-ASCII encodings
tx = name_update(identifier.decode('ascii'), value.decode('ascii'), recipient_address)['hex']
try:
# TODO: support non-ASCII encodings
tx = name_update(identifier.decode('ascii'), value.decode('ascii'), recipient_address)['hex']
except Exception as e:
formatted_name = format_name_identifier(identifier)
self.main_window.show_error(_("Error creating update for ") + formatted_name + ": " + str(e))
return

try:
broadcast(tx)
Expand Down
10 changes: 9 additions & 1 deletion electrum_nmc/gui/qt/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -3358,12 +3358,20 @@ def check_name_availability(self):
name_show = self.console.namespace.get('name_show')

name_exists = True
name_valid = True
try:
name_show(identifier_ascii)
except commands.NameNotFoundError:
name_exists = False
except util.BitcoinException:
# This happens if the name identifier exceeded the 255-byte limit.
name_valid = False

if name_exists:
if not name_valid:
self.buy_names_available_widget.hide()
self.buy_names_already_exists_label.setText(_("That name is invalid (probably exceeded the 255-byte limit) and therefore cannot be registered."))
self.buy_names_already_exists_widget.show()
elif name_exists:
self.buy_names_available_widget.hide()
self.buy_names_already_exists_label.setText(identifier_formatted + _(" is already registered, sorry!"))
self.buy_names_already_exists_widget.show()
Expand Down
49 changes: 48 additions & 1 deletion electrum_nmc/names.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,20 @@ def name_op_to_script(name_op):
if name_op is None:
script = ''
elif name_op["op"] == OP_NAME_NEW:
validate_new_length(name_op)
script = '51' # OP_NAME_NEW
script += push_script(bh2u(name_op["hash"]))
script += '6d' # OP_2DROP
elif name_op["op"] == OP_NAME_FIRSTUPDATE:
validate_firstupdate_length(name_op)
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:
validate_update_length(name_op)
script = '53' # OP_NAME_UPDATE
script += push_script(bh2u(name_op["name"]))
script += push_script(bh2u(name_op["value"]))
Expand All @@ -82,7 +85,51 @@ def name_op_to_script(name_op):
raise BitcoinException('unknown name op: {}'.format(name_op))
return script

def validate_new_length(name_op):
validate_hash_length(name_op["hash"])

def validate_firstupdate_length(name_op):
validate_rand_length(name_op["rand"])
validate_anyupdate_length(name_op)

def validate_update_length(name_op):
validate_anyupdate_length(name_op)

def validate_anyupdate_length(name_op):
validate_identifier_length(name_op["name"])
validate_value_length(name_op["value"])

def validate_hash_length(commitment):
hash_length_requirement = 20

hash_length = len(commitment)
if hash_length != hash_length_requirement:
raise BitcoinException('hash length {} is not equal to requirement of {}'.format(hash_length, hash_length_requirement))

def validate_rand_length(rand):
rand_length_requirement = 20

rand_length = len(rand)
if rand_length != rand_length_requirement:
raise BitcoinException('rand length {} is not equal to requirement of {}'.format(rand_length, rand_length_requirement))

def validate_identifier_length(identifier):
identifier_length_limit = 255

identifier_length = len(identifier)
if identifier_length > identifier_length_limit:
raise BitcoinException('identifier length {} exceeds limit of {}'.format(identifier_length, identifier_length_limit))

def validate_value_length(value):
value_length_limit = 520

value_length = len(value)
if value_length > value_length_limit:
raise BitcoinException('value length {} exceeds limit of {}'.format(value_length, value_length_limit))

def build_name_new(identifier, rand = None):
validate_identifier_length(identifier)

if rand is None:
rand = os.urandom(20)

Expand Down Expand Up @@ -264,7 +311,7 @@ def name_expires_in(name_height, chain_height):
from .bitcoin import push_script, script_to_scripthash
from .crypto import hash_160
from .transaction import MalformedBitcoinScript, match_decoded, opcodes, OPPushDataGeneric, script_GetOp
from .util import bh2u
from .util import bh2u, BitcoinException

OP_NAME_NEW = opcodes.OP_1
OP_NAME_FIRSTUPDATE = opcodes.OP_2
Expand Down

0 comments on commit 897ddb4

Please sign in to comment.