From f8f3f1bc552024fd13817d735a35568c1f171d04 Mon Sep 17 00:00:00 2001 From: grdddj Date: Fri, 16 Sep 2022 12:06:56 +0200 Subject: [PATCH] chore(core): decrease ripple size by 1170 bytes --- core/src/apps/ripple/base58_ripple.py | 12 +-- core/src/apps/ripple/get_address.py | 16 +-- core/src/apps/ripple/helpers.py | 5 +- core/src/apps/ripple/layout.py | 24 +++-- core/src/apps/ripple/serialize.py | 139 +++++++++++--------------- core/src/apps/ripple/sign_tx.py | 98 +++++++----------- 6 files changed, 128 insertions(+), 166 deletions(-) diff --git a/core/src/apps/ripple/base58_ripple.py b/core/src/apps/ripple/base58_ripple.py index d9685648c6..5e2f99adb3 100644 --- a/core/src/apps/ripple/base58_ripple.py +++ b/core/src/apps/ripple/base58_ripple.py @@ -6,18 +6,18 @@ from trezor.crypto import base58 _ripple_alphabet = "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz" -def encode(data: bytes) -> str: +def _encode(data: bytes) -> str: """ Convert bytes to base58 encoded string. """ - return base58.encode(data, alphabet=_ripple_alphabet) + return base58.encode(data, _ripple_alphabet) -def decode(string: str) -> bytes: +def _decode(string: str) -> bytes: """ Convert base58 encoded string to bytes. """ - return base58.decode(string, alphabet=_ripple_alphabet) + return base58.decode(string, _ripple_alphabet) def encode_check( @@ -26,7 +26,7 @@ def encode_check( """ Convert bytes to base58 encoded string, append checksum. """ - return encode(data + digestfunc(data)) + return _encode(data + digestfunc(data)) def decode_check( @@ -35,5 +35,5 @@ def decode_check( """ Convert base58 encoded string to bytes and verify checksum. """ - data = decode(string) + data = _decode(string) return base58.verify_checksum(data, digestfunc) diff --git a/core/src/apps/ripple/get_address.py b/core/src/apps/ripple/get_address.py index 7b80f80189..b1f9e1d284 100644 --- a/core/src/apps/ripple/get_address.py +++ b/core/src/apps/ripple/get_address.py @@ -1,15 +1,9 @@ from typing import TYPE_CHECKING -from trezor.messages import RippleAddress -from trezor.ui.layouts import show_address - -from apps.common import paths from apps.common.keychain import auto_keychain -from .helpers import address_from_public_key - if TYPE_CHECKING: - from trezor.messages import RippleGetAddress + from trezor.messages import RippleGetAddress, RippleAddress from apps.common.keychain import Keychain from trezor.wire import Context @@ -18,6 +12,12 @@ if TYPE_CHECKING: async def get_address( ctx: Context, msg: RippleGetAddress, keychain: Keychain ) -> RippleAddress: + # NOTE: local imports here saves 20 bytes + from trezor.messages import RippleAddress + from trezor.ui.layouts import show_address + from apps.common import paths + from .helpers import address_from_public_key + await paths.validate_path(ctx, keychain, msg.address_n) node = keychain.derive(msg.address_n) @@ -26,6 +26,6 @@ async def get_address( if msg.show_display: title = paths.address_n_to_str(msg.address_n) - await show_address(ctx, address=address, title=title) + await show_address(ctx, address, title=title) return RippleAddress(address=address) diff --git a/core/src/apps/ripple/helpers.py b/core/src/apps/ripple/helpers.py index 67488143a9..8809d59212 100644 --- a/core/src/apps/ripple/helpers.py +++ b/core/src/apps/ripple/helpers.py @@ -1,7 +1,5 @@ from micropython import const -from trezor.crypto.hashlib import ripemd160, sha256 - from . import base58_ripple # HASH_TX_ID = const(0x5458_4E00) # 'TXN' @@ -36,6 +34,9 @@ def address_from_public_key(pubkey: bytes) -> str: Returns the Ripple address created using base58 """ + # NOTE: local imports here saves 8 bytes + from trezor.crypto.hashlib import ripemd160, sha256 + h = sha256(pubkey).digest() h = ripemd160(h).digest() diff --git a/core/src/apps/ripple/layout.py b/core/src/apps/ripple/layout.py index 430d5b92bd..3443f3241d 100644 --- a/core/src/apps/ripple/layout.py +++ b/core/src/apps/ripple/layout.py @@ -3,9 +3,8 @@ from typing import TYPE_CHECKING from trezor.enums import ButtonRequestType from trezor.strings import format_amount from trezor.ui.layouts import confirm_metadata -from trezor.ui.layouts.altcoin import confirm_total_ripple -from . import helpers +from .helpers import DECIMALS if TYPE_CHECKING: from trezor.wire import Context @@ -15,11 +14,11 @@ async def require_confirm_fee(ctx: Context, fee: int) -> None: await confirm_metadata( ctx, "confirm_fee", - title="Confirm fee", - content="Transaction fee:\n{}", - param=format_amount(fee, helpers.DECIMALS) + " XRP", + "Confirm fee", + "Transaction fee:\n{}", + format_amount(fee, DECIMALS) + " XRP", + ButtonRequestType.ConfirmOutput, hide_continue=True, - br_code=ButtonRequestType.ConfirmOutput, ) @@ -27,13 +26,16 @@ async def require_confirm_destination_tag(ctx: Context, tag: int) -> None: await confirm_metadata( ctx, "confirm_destination_tag", - title="Confirm tag", - content="Destination tag:\n{}", - param=str(tag), + "Confirm tag", + "Destination tag:\n{}", + str(tag), + ButtonRequestType.ConfirmOutput, hide_continue=True, - br_code=ButtonRequestType.ConfirmOutput, ) async def require_confirm_tx(ctx: Context, to: str, value: int) -> None: - await confirm_total_ripple(ctx, to, format_amount(value, helpers.DECIMALS)) + # NOTE: local imports here saves 4 bytes + from trezor.ui.layouts.altcoin import confirm_total_ripple + + await confirm_total_ripple(ctx, to, format_amount(value, DECIMALS)) diff --git a/core/src/apps/ripple/serialize.py b/core/src/apps/ripple/serialize.py index 47a2f69ac4..d408562757 100644 --- a/core/src/apps/ripple/serialize.py +++ b/core/src/apps/ripple/serialize.py @@ -11,41 +11,17 @@ from micropython import const from typing import TYPE_CHECKING -from . import helpers - if TYPE_CHECKING: from trezor.messages import RippleSignTx from trezor.utils import Writer -class RippleField: - def __init__(self, type: int, key: int) -> None: - self.type: int = type - self.key: int = key - - _FIELD_TYPE_INT16 = const(1) _FIELD_TYPE_INT32 = const(2) _FIELD_TYPE_AMOUNT = const(6) _FIELD_TYPE_VL = const(7) _FIELD_TYPE_ACCOUNT = const(8) -FIELDS_MAP: dict[str, RippleField] = { - "account": RippleField(type=_FIELD_TYPE_ACCOUNT, key=1), - "amount": RippleField(type=_FIELD_TYPE_AMOUNT, key=1), - "destination": RippleField(type=_FIELD_TYPE_ACCOUNT, key=3), - "fee": RippleField(type=_FIELD_TYPE_AMOUNT, key=8), - "sequence": RippleField(type=_FIELD_TYPE_INT32, key=4), - "type": RippleField(type=_FIELD_TYPE_INT16, key=2), - "signingPubKey": RippleField(type=_FIELD_TYPE_VL, key=3), - "flags": RippleField(type=_FIELD_TYPE_INT32, key=2), - "txnSignature": RippleField(type=_FIELD_TYPE_VL, key=4), - "lastLedgerSequence": RippleField(type=_FIELD_TYPE_INT32, key=27), - "destinationTag": RippleField(type=_FIELD_TYPE_INT32, key=14), -} - -TRANSACTION_TYPES = {"Payment": 0} - def serialize( msg: RippleSignTx, @@ -53,93 +29,98 @@ def serialize( pubkey: bytes, signature: bytes | None = None, ) -> bytearray: - w = bytearray() # must be sorted numerically first by type and then by name - write(w, FIELDS_MAP["type"], TRANSACTION_TYPES["Payment"]) - write(w, FIELDS_MAP["flags"], msg.flags) - write(w, FIELDS_MAP["sequence"], msg.sequence) - write(w, FIELDS_MAP["destinationTag"], msg.payment.destination_tag) - write(w, FIELDS_MAP["lastLedgerSequence"], msg.last_ledger_sequence) - write(w, FIELDS_MAP["amount"], msg.payment.amount) - write(w, FIELDS_MAP["fee"], msg.fee) - write(w, FIELDS_MAP["signingPubKey"], pubkey) - write(w, FIELDS_MAP["txnSignature"], signature) - write(w, FIELDS_MAP["account"], source_address) - write(w, FIELDS_MAP["destination"], msg.payment.destination) + fields_to_write = ( # field_type, field_key, value + (_FIELD_TYPE_INT16, 2, 0), # payment type is 0 + (_FIELD_TYPE_INT32, 2, msg.flags), # flags + (_FIELD_TYPE_INT32, 4, msg.sequence), # sequence + (_FIELD_TYPE_INT32, 14, msg.payment.destination_tag), # destinationTag + (_FIELD_TYPE_INT32, 27, msg.last_ledger_sequence), # lastLedgerSequence + (_FIELD_TYPE_AMOUNT, 1, msg.payment.amount), # amount + (_FIELD_TYPE_AMOUNT, 8, msg.fee), # fee + (_FIELD_TYPE_VL, 3, pubkey), # signingPubKey + (_FIELD_TYPE_VL, 4, signature), # txnSignature + (_FIELD_TYPE_ACCOUNT, 1, source_address), # account + (_FIELD_TYPE_ACCOUNT, 3, msg.payment.destination), # destination + ) + + w = bytearray() + for field_type, field_key, value in fields_to_write: + _write(w, field_type, field_key, value) return w -def write(w: Writer, field: RippleField, value: int | bytes | str | None) -> None: +def _write( + w: Writer, field_type: int, field_key: int, value: int | bytes | str | None +) -> None: + from . import helpers + if value is None: return - write_type(w, field) - if field.type == _FIELD_TYPE_INT16: + + # write_type + if field_key <= 0xF: + w.append((field_type << 4) | field_key) + else: + # this concerns two-bytes fields such as lastLedgerSequence + w.append(field_type << 4) + w.append(field_key) + + if field_type == _FIELD_TYPE_INT16: assert isinstance(value, int) w.extend(value.to_bytes(2, "big")) - elif field.type == _FIELD_TYPE_INT32: + elif field_type == _FIELD_TYPE_INT32: assert isinstance(value, int) w.extend(value.to_bytes(4, "big")) - elif field.type == _FIELD_TYPE_AMOUNT: + elif field_type == _FIELD_TYPE_AMOUNT: assert isinstance(value, int) - w.extend(serialize_amount(value)) - elif field.type == _FIELD_TYPE_ACCOUNT: + + # serialize_amount + if value < 0: + raise ValueError("Only non-negative integers are supported") + if value > helpers.MAX_ALLOWED_AMOUNT: + raise ValueError("Value is too large") + serialized_amount = bytearray(value.to_bytes(8, "big")) + serialized_amount[0] &= 0x7F # clear first bit to indicate XRP + serialized_amount[0] |= 0x40 # set second bit to indicate positive number + + w.extend(serialized_amount) + elif field_type == _FIELD_TYPE_ACCOUNT: assert isinstance(value, str) write_bytes_varint(w, helpers.decode_address(value)) - elif field.type == _FIELD_TYPE_VL: + elif field_type == _FIELD_TYPE_VL: assert isinstance(value, (bytes, bytearray)) write_bytes_varint(w, value) else: raise ValueError("Unknown field type") -def write_type(w: Writer, field: RippleField) -> None: - if field.key <= 0xF: - w.append((field.type << 4) | field.key) - else: - # this concerns two-bytes fields such as lastLedgerSequence - w.append(field.type << 4) - w.append(field.key) - - -def serialize_amount(value: int) -> bytearray: - if value < 0: - raise ValueError("Only non-negative integers are supported") - if value > helpers.MAX_ALLOWED_AMOUNT: - raise ValueError("Value is too large") - - b = bytearray(value.to_bytes(8, "big")) - b[0] &= 0x7F # clear first bit to indicate XRP - b[0] |= 0x40 # set second bit to indicate positive number - return b - - def write_bytes_varint(w: Writer, value: bytes) -> None: """Serialize a variable length bytes.""" - write_varint(w, len(value)) - w.extend(value) + append = w.append # local_cache_attribute - -def write_varint(w: Writer, val: int) -> None: - """ - Implements variable-length int encoding from Ripple. - See: https://ripple.com/wiki/Binary_Format#Variable_Length_Data_Encoding - """ + # write_varint + # Implements variable-length int encoding from Ripple. + # See: https://ripple.com/wiki/Binary_Format#Variable_Length_Data_Encoding + val = len(value) if val < 0: raise ValueError("Only non-negative integers are supported") elif val < 192: - w.append(val) + append(val) elif val <= 12480: val -= 193 - w.append(193 + rshift(val, 8)) - w.append(val & 0xFF) + append(193 + rshift(val, 8)) + append(val & 0xFF) elif val <= 918744: val -= 12481 - w.append(241 + rshift(val, 16)) - w.append(rshift(val, 8) & 0xFF) - w.append(val & 0xFF) + append(241 + rshift(val, 16)) + append(rshift(val, 8) & 0xFF) + append(val & 0xFF) else: raise ValueError("Value is too large") + w.extend(value) + def rshift(val: int, n: int) -> int: """ diff --git a/core/src/apps/ripple/sign_tx.py b/core/src/apps/ripple/sign_tx.py index 7c5188724a..742bc7fdae 100644 --- a/core/src/apps/ripple/sign_tx.py +++ b/core/src/apps/ripple/sign_tx.py @@ -1,81 +1,59 @@ from typing import TYPE_CHECKING -from trezor.crypto import der -from trezor.crypto.curve import secp256k1 -from trezor.crypto.hashlib import sha512 -from trezor.messages import RippleSignedTx -from trezor.wire import ProcessError - -from apps.common import paths from apps.common.keychain import auto_keychain -from . import helpers, layout -from .serialize import serialize - if TYPE_CHECKING: - from trezor.messages import RippleSignTx + from trezor.messages import RippleSignTx, RippleSignedTx from apps.common.keychain import Keychain from trezor.wire import Context +# NOTE: it is one big function because that way it is the most flash-space-efficient @auto_keychain(__name__) async def sign_tx( ctx: Context, msg: RippleSignTx, keychain: Keychain ) -> RippleSignedTx: - validate(msg) + from trezor.crypto import der + from trezor.crypto.curve import secp256k1 + from trezor.crypto.hashlib import sha512 + from trezor.messages import RippleSignedTx + from trezor.wire import ProcessError + from apps.common import paths + from . import helpers, layout + from .serialize import serialize + + payment = msg.payment # local_cache_attribute + + if payment.amount > helpers.MAX_ALLOWED_AMOUNT: + raise ProcessError("Amount exceeds maximum allowed amount.") await paths.validate_path(ctx, keychain, msg.address_n) node = keychain.derive(msg.address_n) source_address = helpers.address_from_public_key(node.public_key()) - set_canonical_flag(msg) - tx = serialize(msg, source_address, pubkey=node.public_key()) - to_sign = get_network_prefix() + tx - - check_fee(msg.fee) - if msg.payment.destination_tag is not None: - await layout.require_confirm_destination_tag(ctx, msg.payment.destination_tag) - await layout.require_confirm_fee(ctx, msg.fee) - await layout.require_confirm_tx(ctx, msg.payment.destination, msg.payment.amount) - - signature = ecdsa_sign(node.private_key(), first_half_of_sha512(to_sign)) - tx = serialize(msg, source_address, pubkey=node.public_key(), signature=signature) - return RippleSignedTx(signature=signature, serialized_tx=tx) - - -def check_fee(fee: int) -> None: - if fee < helpers.MIN_FEE or fee > helpers.MAX_FEE: - raise ProcessError("Fee must be in the range of 10 to 10,000 drops") - - -def get_network_prefix() -> bytes: - """Network prefix is prepended before the transaction and public key is included""" - return helpers.HASH_TX_SIGN.to_bytes(4, "big") - - -def first_half_of_sha512(b: bytes) -> bytes: - """First half of SHA512, which Ripple uses""" - hash = sha512(b) - return hash.digest()[:32] - - -def ecdsa_sign(private_key: bytes, digest: bytes) -> bytes: - """Signs and encodes signature into DER format""" - signature = secp256k1.sign(private_key, digest) - sig_der = der.encode_seq((signature[1:33], signature[33:65])) - return sig_der - - -def set_canonical_flag(msg: RippleSignTx) -> None: - """ - Our ECDSA implementation already returns fully-canonical signatures, - so we're enforcing it in the transaction using the designated flag - - see https://wiki.ripple.com/Transaction_Malleability#Using_Fully-Canonical_Signatures - - see https://github.com/trezor/trezor-crypto/blob/3e8974ff8871263a70b7fbb9a27a1da5b0d810f7/ecdsa.c#L791 - """ + # Setting canonical flag + # Our ECDSA implementation already returns fully-canonical signatures, + # so we're enforcing it in the transaction using the designated flag + # - see https://wiki.ripple.com/Transaction_Malleability#Using_Fully-Canonical_Signatures + # - see https://github.com/trezor/trezor-crypto/blob/3e8974ff8871263a70b7fbb9a27a1da5b0d810f7/ecdsa.c#L791 msg.flags |= helpers.FLAG_FULLY_CANONICAL + tx = serialize(msg, source_address, node.public_key()) + network_prefix = helpers.HASH_TX_SIGN.to_bytes(4, "big") + to_sign = network_prefix + tx -def validate(msg: RippleSignTx) -> None: - if msg.payment.amount > helpers.MAX_ALLOWED_AMOUNT: - raise ProcessError("Amount exceeds maximum allowed amount.") + if msg.fee < helpers.MIN_FEE or msg.fee > helpers.MAX_FEE: + raise ProcessError("Fee must be in the range of 10 to 10,000 drops") + + if payment.destination_tag is not None: + await layout.require_confirm_destination_tag(ctx, payment.destination_tag) + await layout.require_confirm_fee(ctx, msg.fee) + await layout.require_confirm_tx(ctx, payment.destination, payment.amount) + + # Signs and encodes signature into DER format + first_half_of_sha512 = sha512(to_sign).digest()[:32] + sig = secp256k1.sign(node.private_key(), first_half_of_sha512) + sig_encoded = der.encode_seq((sig[1:33], sig[33:65])) + + tx = serialize(msg, source_address, node.public_key(), sig_encoded) + return RippleSignedTx(signature=sig_encoded, serialized_tx=tx)