chore(core): decrease ripple size by 1170 bytes

pull/2633/head
grdddj 2 years ago committed by matejcik
parent 9c0c3852f5
commit f8f3f1bc55

@ -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)

@ -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)

@ -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()

@ -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))

@ -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:
"""

@ -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)
# 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 check_fee(fee: int) -> None:
if fee < helpers.MIN_FEE or fee > helpers.MAX_FEE:
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)
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
"""
msg.flags |= helpers.FLAG_FULLY_CANONICAL
# 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]))
def validate(msg: RippleSignTx) -> None:
if msg.payment.amount > helpers.MAX_ALLOWED_AMOUNT:
raise ProcessError("Amount exceeds maximum allowed amount.")
tx = serialize(msg, source_address, node.public_key(), sig_encoded)
return RippleSignedTx(signature=sig_encoded, serialized_tx=tx)

Loading…
Cancel
Save