async def _memo(ctx: Context, w: bytearray, msg: StellarSignTx) -> None: writers.write_uint32(w, msg.memo_type) if msg.memo_type == StellarMemoType.NONE: # nothing is serialized memo_confirm_text = "" elif msg.memo_type == StellarMemoType.TEXT: # Text: 4 bytes (size) + up to 28 bytes if msg.memo_text is None: raise DataError("Stellar: Missing memo text") if len(msg.memo_text) > 28: raise ProcessError( "Stellar: max length of a memo text is 28 bytes") writers.write_string(w, msg.memo_text) memo_confirm_text = msg.memo_text elif msg.memo_type == StellarMemoType.ID: # ID: 64 bit unsigned integer if msg.memo_id is None: raise DataError("Stellar: Missing memo id") writers.write_uint64(w, msg.memo_id) memo_confirm_text = str(msg.memo_id) elif msg.memo_type in (StellarMemoType.HASH, StellarMemoType.RETURN): # Hash/Return: 32 byte hash if msg.memo_hash is None: raise DataError("Stellar: Missing memo hash") writers.write_bytes_fixed(w, bytearray(msg.memo_hash), 32) memo_confirm_text = hexlify(msg.memo_hash).decode() else: raise ProcessError("Stellar invalid memo type") await layout.require_confirm_memo(ctx, msg.memo_type, memo_confirm_text)
def validate(msg: NEMSignTx): if msg.transaction is None: raise ProcessError("No common provided") _validate_single_tx(msg) _validate_common(msg.transaction) if msg.multisig: _validate_common(msg.multisig, True) _validate_multisig(msg.multisig, msg.transaction.network) if not msg.multisig and msg.cosigning: raise ProcessError("No multisig transaction to cosign") if msg.transfer: _validate_transfer(msg.transfer, msg.transaction.network) if msg.provision_namespace: _validate_provision_namespace(msg.provision_namespace, msg.transaction.network) if msg.mosaic_creation: _validate_mosaic_creation(msg.mosaic_creation, msg.transaction.network) if msg.supply_change: _validate_supply_change(msg.supply_change) if msg.aggregate_modification: _validate_aggregate_modification( msg.aggregate_modification, msg.multisig is None ) if msg.importance_transfer: _validate_importance_transfer(msg.importance_transfer)
def _validate_common(common: NEMTransactionCommon, inner: bool = False): common.network = validate_network(common.network) err = None if common.timestamp is None: err = "timestamp" if common.fee is None: err = "fee" if common.deadline is None: err = "deadline" if not inner and common.signer: raise ProcessError("Signer not allowed in outer transaction") if inner and common.signer is None: err = "signer" if err: if inner: raise ProcessError("No %s provided in inner transaction" % err) else: raise ProcessError("No %s provided" % err) if common.signer is not None: _validate_public_key( common.signer, "Invalid signer public key in inner transaction" )
async def _memo(ctx, w: bytearray, msg: StellarSignTx): if msg.memo_type is None: msg.memo_type = consts.MEMO_TYPE_NONE writers.write_uint32(w, msg.memo_type) if msg.memo_type == consts.MEMO_TYPE_NONE: # nothing is serialized memo_confirm_text = "" elif msg.memo_type == consts.MEMO_TYPE_TEXT: # Text: 4 bytes (size) + up to 28 bytes if len(msg.memo_text) > 28: raise ProcessError( "Stellar: max length of a memo text is 28 bytes") writers.write_string(w, msg.memo_text) memo_confirm_text = msg.memo_text elif msg.memo_type == consts.MEMO_TYPE_ID: # ID: 64 bit unsigned integer writers.write_uint64(w, msg.memo_id) memo_confirm_text = str(msg.memo_id) elif msg.memo_type in (consts.MEMO_TYPE_HASH, consts.MEMO_TYPE_RETURN): # Hash/Return: 32 byte hash writers.write_bytes_unchecked(w, bytearray(msg.memo_hash)) memo_confirm_text = hexlify(msg.memo_hash).decode() else: raise ProcessError("Stellar invalid memo type") await layout.require_confirm_memo(ctx, msg.memo_type, memo_confirm_text)
def _validate_common(common: NEMTransactionCommon, inner: bool = False): common.network = validate_network(common.network) err = None if common.timestamp is None: err = 'timestamp' if common.fee is None: err = 'fee' if common.deadline is None: err = 'deadline' if not inner and common.signer: raise ProcessError('Signer not allowed in outer transaction') if inner and common.signer is None: err = 'signer' if err: if inner: raise ProcessError('No %s provided in inner transaction' % err) else: raise ProcessError('No %s provided' % err) if common.signer is not None: _validate_public_key(common.signer, 'Invalid signer public key in inner transaction')
def _validate_single_tx(msg: NEMSignTx): # ensure exactly one transaction is provided tx_count = (bool(msg.transfer) + bool(msg.provision_namespace) + bool(msg.mosaic_creation) + bool(msg.supply_change) + bool(msg.aggregate_modification) + bool(msg.importance_transfer)) if tx_count == 0: raise ProcessError("No transaction provided") if tx_count > 1: raise ProcessError("More than one transaction provided")
def _validate_provision_namespace(provision_namespace: NEMProvisionNamespace, network: int): if provision_namespace.namespace is None: raise ProcessError('No namespace provided') if provision_namespace.sink is None: raise ProcessError('No rental sink provided') if provision_namespace.fee is None: raise ProcessError('No rental sink fee provided') if not nem.validate_address(provision_namespace.sink, network): raise ProcessError('Invalid rental sink address')
def _validate_transfer(transfer: NEMTransfer, network: int): if transfer.recipient is None: raise ProcessError("No recipient provided") if transfer.amount is None: raise ProcessError("No amount provided") if transfer.public_key is not None: _validate_public_key(transfer.public_key, "Invalid recipient public key") if transfer.payload is None: raise ProcessError("Public key provided but no payload to encrypt") if transfer.payload: if len(transfer.payload) > NEM_MAX_PLAIN_PAYLOAD_SIZE: raise ProcessError("Payload too large") if ( transfer.public_key and len(transfer.payload) > NEM_MAX_ENCRYPTED_PAYLOAD_SIZE ): raise ProcessError("Payload too large") if not nem.validate_address(transfer.recipient, network): raise ProcessError("Invalid recipient address") for m in transfer.mosaics: if m.namespace is None: raise ProcessError("No mosaic namespace provided") if m.mosaic is None: raise ProcessError("No mosaic name provided") if m.quantity is None: raise ProcessError("No mosaic quantity provided")
def _validate_supply_change(supply_change: NEMMosaicSupplyChange): if supply_change.namespace is None: raise ProcessError('No namespace provided') if supply_change.mosaic is None: raise ProcessError('No mosaic provided') if supply_change.type is None: raise ProcessError('No type provided') elif supply_change.type not in [NEMSupplyChangeType.SupplyChange_Decrease, NEMSupplyChangeType.SupplyChange_Increase]: raise ProcessError('Invalid supply change type') if supply_change.delta is None: raise ProcessError('No delta provided')
def validate(msg: RippleSignTx): if None in (msg.fee, msg.sequence, msg.payment) or ( msg.payment and None in (msg.payment.amount, msg.payment.destination) ): raise ProcessError( "Some of the required fields are missing (fee, sequence, payment.amount, payment.destination)" ) if msg.payment.amount < 0: raise ProcessError("Only non-negative amounts are allowed.") if msg.payment.amount > helpers.MAX_ALLOWED_AMOUNT: raise ProcessError("Amount exceeds maximum allowed amount.")
def _validate_aggregate_modification( aggregate_modification: NEMAggregateModification, creation: bool = False) -> None: if creation and not aggregate_modification.modifications: raise ProcessError("No modifications provided") for m in aggregate_modification.modifications: if creation and m.type == NEMModificationType.CosignatoryModification_Delete: raise ProcessError( "Cannot remove cosignatory when converting account") _validate_public_key(m.public_key, "Invalid cosignatory public key provided")
def write_manage_data_op(w, msg: StellarManageDataOp): if len(msg.key) > 64: raise ProcessError("Stellar: max length of a key is 64 bytes") writers.write_string(w, msg.key) writers.write_bool(w, bool(msg.value)) if msg.value: writers.write_string(w, msg.value)
def validate_network(network: int) -> int: if network is None: return NEM_NETWORK_MAINNET if network not in (NEM_NETWORK_MAINNET, NEM_NETWORK_TESTNET, NEM_NETWORK_MIJIN): raise ProcessError("Invalid NEM network") return network
def validate(msg: RippleSignTx): if None in (msg.fee, msg.sequence, msg.payment) or ( msg.payment and None in (msg.payment.amount, msg.payment.destination)): raise ProcessError( "Some of the required fields are missing (fee, sequence, payment.amount, payment.destination)" )
def _validate_transfer(transfer: NEMTransfer, network: int) -> None: if transfer.public_key is not None: _validate_public_key(transfer.public_key, "Invalid recipient public key") if not transfer.payload: raise ProcessError("Public key provided but no payload to encrypt") if transfer.payload: if len(transfer.payload) > NEM_MAX_PLAIN_PAYLOAD_SIZE: raise ProcessError("Payload too large") if (transfer.public_key and len(transfer.payload) > NEM_MAX_ENCRYPTED_PAYLOAD_SIZE): raise ProcessError("Payload too large") if not nem.validate_address(transfer.recipient, network): raise ProcessError("Invalid recipient address")
async def confirm_set_options_op(ctx: Context, op: StellarSetOptionsOp) -> None: if op.inflation_destination_account: await confirm_address( ctx, "Inflation", op.inflation_destination_account, description="Destination:", br_type="op_inflation", ) if op.clear_flags: t = _format_flags(op.clear_flags) await confirm_text(ctx, "op_set_options", "Clear flags", data=t) if op.set_flags: t = _format_flags(op.set_flags) await confirm_text(ctx, "op_set_options", "Set flags", data=t) thresholds = _format_thresholds(op) if thresholds: await confirm_properties(ctx, "op_thresholds", "Account Thresholds", props=thresholds) if op.home_domain: await confirm_text(ctx, "op_home_domain", "Home Domain", op.home_domain) if op.signer_type is not None: if op.signer_key is None or op.signer_weight is None: raise DataError("Stellar: invalid signer option data.") if op.signer_weight > 0: title = "Add Signer" else: title = "Remove Signer" data: str | bytes = "" if op.signer_type == StellarSignerType.ACCOUNT: description = "Account:" data = helpers.address_from_public_key(op.signer_key) elif op.signer_type == StellarSignerType.PRE_AUTH: description = "Pre-auth transaction:" data = op.signer_key elif op.signer_type == StellarSignerType.HASH: description = "Hash:" data = op.signer_key else: raise ProcessError("Stellar: invalid signer type") await confirm_blob( ctx, "op_signer", title=title, description=description, data=data, )
def _validate_aggregate_modification( aggregate_modification: NEMAggregateModification, creation: bool = False): if creation and not aggregate_modification.modifications: raise ProcessError('No modifications provided') for m in aggregate_modification.modifications: if not m.type: raise ProcessError('No modification type provided') if m.type not in ( NEMModificationType.CosignatoryModification_Add, NEMModificationType.CosignatoryModification_Delete ): raise ProcessError('Unknown aggregate modification') if creation and m.type == NEMModificationType.CosignatoryModification_Delete: raise ProcessError('Cannot remove cosignatory when converting account') _validate_public_key(m.public_key, 'Invalid cosignatory public key provided')
def _format_flags(flags: int) -> tuple: if flags > consts.FLAGS_MAX_SIZE: raise ProcessError("Stellar: invalid flags") text = () if flags & consts.FLAG_AUTH_REQUIRED: text += ("AUTH_REQUIRED", ) if flags & consts.FLAG_AUTH_REVOCABLE: text += ("AUTH_REVOCABLE", ) if flags & consts.FLAG_AUTH_IMMUTABLE: text += ("AUTH_IMMUTABLE", ) return text
def _format_flags(flags: int) -> str: if flags > consts.FLAGS_MAX_SIZE: raise ProcessError("Stellar: invalid flags") flags_set = [] if flags & consts.FLAG_AUTH_REQUIRED: flags_set.append("AUTH_REQUIRED\n") if flags & consts.FLAG_AUTH_REVOCABLE: flags_set.append("AUTH_REVOCABLE\n") if flags & consts.FLAG_AUTH_IMMUTABLE: flags_set.append("AUTH_IMMUTABLE\n") return "".join(flags_set)
def _serialize_asset_code(w, asset_type: int, asset_code: str): code = bytearray(asset_code) if asset_type == consts.ASSET_TYPE_NATIVE: return # nothing is needed elif asset_type == consts.ASSET_TYPE_ALPHANUM4: # pad with zeros to 4 chars writers.write_bytes(w, code + bytearray([0] * (4 - len(code)))) elif asset_type == consts.ASSET_TYPE_ALPHANUM12: # pad with zeros to 12 chars writers.write_bytes(w, code + bytearray([0] * (12 - len(code)))) else: raise ProcessError("Stellar: invalid asset type")
def _validate_common(common: NEMTransactionCommon, inner: bool = False) -> None: validate_network(common.network) err = None if not inner and common.signer: raise ProcessError("Signer not allowed in outer transaction") if inner and common.signer is None: err = "signer" if err: if inner: raise ProcessError(f"No {err} provided in inner transaction") else: raise ProcessError(f"No {err} provided") if common.signer is not None: _validate_public_key(common.signer, "Invalid signer public key in inner transaction")
async def confirm_set_options_op(ctx, op: StellarSetOptionsOp): if op.inflation_destination_account: text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) text.bold("Set Inflation Destination") text.mono(*split(op.inflation_destination_account)) await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) if op.clear_flags: t = _format_flags(op.clear_flags) text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) text.bold("Clear Flags") text.mono(*t) await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) if op.set_flags: t = _format_flags(op.set_flags) text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) text.bold("Set Flags") text.mono(*t) await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) thresholds = _format_thresholds(op) if thresholds: text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) text.bold("Account Thresholds") text.mono(*thresholds) await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) if op.home_domain: text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) text.bold("Home Domain") text.mono(*split(op.home_domain)) await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) if op.signer_type is not None: if op.signer_weight > 0: t = "Add Signer (%s)" else: t = "Remove Signer (%s)" if op.signer_type == consts.SIGN_TYPE_ACCOUNT: text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) text.bold(t % "acc") text.mono(*split(helpers.address_from_public_key(op.signer_key))) await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) elif op.signer_type in (consts.SIGN_TYPE_PRE_AUTH, consts.SIGN_TYPE_HASH): if op.signer_type == consts.SIGN_TYPE_PRE_AUTH: signer_type = "auth" else: signer_type = "hash" text = Text("Confirm operation", ui.ICON_CONFIRM, ui.GREEN) text.bold(t % signer_type) text.mono(*split(hexlify(op.signer_key).decode())) await require_confirm(ctx, text, ButtonRequestType.ConfirmOutput) else: raise ProcessError("Stellar: invalid signer type")
def write_set_options_op(w, msg: StellarSetOptionsOp): # inflation destination if msg.inflation_destination_account is None: writers.write_bool(w, False) else: writers.write_bool(w, True) writers.write_pubkey(w, msg.inflation_destination_account) # clear flags _write_set_options_int(w, msg.clear_flags) # set flags _write_set_options_int(w, msg.set_flags) # account thresholds _write_set_options_int(w, msg.master_weight) _write_set_options_int(w, msg.low_threshold) _write_set_options_int(w, msg.medium_threshold) _write_set_options_int(w, msg.high_threshold) # home domain if msg.home_domain is None: writers.write_bool(w, False) else: writers.write_bool(w, True) if len(msg.home_domain) > 32: raise ProcessError( "Stellar: max length of a home domain is 32 bytes") writers.write_string(w, msg.home_domain) # signer if msg.signer_type is None: writers.write_bool(w, False) elif msg.signer_type in consts.SIGN_TYPES: writers.write_bool(w, True) writers.write_uint32(w, msg.signer_type) writers.write_bytes_fixed(w, msg.signer_key, 32) writers.write_uint32(w, msg.signer_weight) else: raise ProcessError("Stellar: unknown signer type")
async def _init(ctx, w: bytearray, pubkey: bytes, msg: StellarSignTx): network_passphrase_hash = sha256(msg.network_passphrase).digest() writers.write_bytes(w, network_passphrase_hash) writers.write_bytes(w, consts.TX_TYPE) address = helpers.address_from_public_key(pubkey) writers.write_pubkey(w, address) if helpers.public_key_from_address(msg.source_account) != pubkey: raise ProcessError("Stellar: source account does not match address_n") writers.write_uint32(w, msg.fee) writers.write_uint64(w, msg.sequence_number) # confirm init await layout.require_confirm_init(ctx, address, msg.network_passphrase)
def serialize_set_options_op(w, msg: StellarSetOptionsOp): # inflation destination writers.write_bool(w, bool(msg.inflation_destination_account)) if msg.inflation_destination_account: writers.write_pubkey(w, msg.inflation_destination_account) # clear flags writers.write_bool(w, bool(msg.clear_flags)) if msg.clear_flags: writers.write_uint32(w, msg.clear_flags) # set flags writers.write_bool(w, bool(msg.set_flags)) if msg.set_flags: writers.write_uint32(w, msg.set_flags) # account thresholds writers.write_bool(w, bool(msg.master_weight)) if msg.master_weight: writers.write_uint32(w, msg.master_weight) writers.write_bool(w, bool(msg.low_threshold)) if msg.low_threshold: writers.write_uint32(w, msg.low_threshold) writers.write_bool(w, bool(msg.medium_threshold)) if msg.medium_threshold: writers.write_uint32(w, msg.medium_threshold) writers.write_bool(w, bool(msg.high_threshold)) if msg.high_threshold: writers.write_uint32(w, msg.high_threshold) # home domain writers.write_bool(w, bool(msg.home_domain)) if msg.home_domain: if len(msg.home_domain) > 32: raise ProcessError( "Stellar: max length of a home domain is 32 bytes") writers.write_string(w, msg.home_domain) # signer writers.write_bool(w, bool(msg.signer_type)) if msg.signer_type: # signer type writers.write_uint32(w, msg.signer_type) writers.write_bytes(w, msg.signer_key) writers.write_uint32(w, msg.signer_weight)
async def sign_tx(ctx, msg: StellarSignTx): if msg.num_operations == 0: raise ProcessError("Stellar: At least one operation is required") node = await seed.derive_node(ctx, msg.address_n, consts.STELLAR_CURVE) pubkey = seed.remove_ed25519_prefix(node.public_key()) w = bytearray() await _init(ctx, w, pubkey, msg) _timebounds(w, msg.timebounds_start, msg.timebounds_end) await _memo(ctx, w, msg) await _operations(ctx, w, msg.num_operations) await _final(ctx, w, msg) # sign digest = sha256(w).digest() signature = ed25519.sign(node.private_key(), digest) # Add the public key for verification that the right account was used for signing return StellarSignedTx(pubkey, signature)
def _write_asset_code(w: Writer, asset_type: StellarAssetType, asset_code: str | None) -> None: if asset_type == StellarAssetType.NATIVE: return # nothing is needed if asset_code is None: raise DataError("Stellar: invalid asset") code = asset_code.encode() if asset_type == StellarAssetType.ALPHANUM4: if len(code) > 4: raise DataError("Stellar: asset code too long for ALPHANUM4") # pad with zeros to 4 chars writers.write_bytes_fixed(w, code + bytes([0] * (4 - len(code))), 4) elif asset_type == StellarAssetType.ALPHANUM12: if len(code) > 12: raise DataError("Stellar: asset code too long for ALPHANUM12") # pad with zeros to 12 chars writers.write_bytes_fixed(w, code + bytes([0] * (12 - len(code))), 12) else: raise ProcessError("Stellar: invalid asset type")
def write_set_options_op(w: Writer, msg: StellarSetOptionsOp) -> None: # inflation destination if msg.inflation_destination_account is None: writers.write_bool(w, False) else: writers.write_bool(w, True) writers.write_pubkey(w, msg.inflation_destination_account) # clear flags _write_set_options_int(w, msg.clear_flags) # set flags _write_set_options_int(w, msg.set_flags) # account thresholds _write_set_options_int(w, msg.master_weight) _write_set_options_int(w, msg.low_threshold) _write_set_options_int(w, msg.medium_threshold) _write_set_options_int(w, msg.high_threshold) # home domain if msg.home_domain is None: writers.write_bool(w, False) else: writers.write_bool(w, True) if len(msg.home_domain) > 32: raise ProcessError( "Stellar: max length of a home domain is 32 bytes") writers.write_string(w, msg.home_domain) # signer if msg.signer_type is None: writers.write_bool(w, False) else: if msg.signer_key is None or msg.signer_weight is None: raise DataError( "Stellar: signer_type, signer_key, signer_weight must be set together" ) writers.write_bool(w, True) writers.write_uint32(w, msg.signer_type) writers.write_bytes_fixed(w, msg.signer_key, 32) writers.write_uint32(w, msg.signer_weight)
async def sign_tx(ctx, msg: StellarSignTx, keychain): await paths.validate_path(ctx, keychain, msg.address_n) node = keychain.derive(msg.address_n) pubkey = seed.remove_ed25519_prefix(node.public_key()) if msg.num_operations == 0: raise ProcessError("Stellar: At least one operation is required") w = bytearray() await _init(ctx, w, pubkey, msg) await _timebounds(ctx, w, msg.timebounds_start, msg.timebounds_end) await _memo(ctx, w, msg) await _operations(ctx, w, msg.num_operations) await _final(ctx, w, msg) # sign digest = sha256(w).digest() signature = ed25519.sign(node.private_key(), digest) # Add the public key for verification that the right account was used for signing return StellarSignedTx(public_key=pubkey, signature=signature)
def check_fee(fee: int): if fee < helpers.MIN_FEE or fee > helpers.MAX_FEE: raise ProcessError("Fee must be in the range of 10 to 10,000 drops")