mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-03 03:50:58 +00:00
chore(core): decrease ripple size by 1170 bytes
This commit is contained in:
parent
9c0c3852f5
commit
f8f3f1bc55
@ -6,18 +6,18 @@ from trezor.crypto import base58
|
|||||||
_ripple_alphabet = "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"
|
_ripple_alphabet = "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"
|
||||||
|
|
||||||
|
|
||||||
def encode(data: bytes) -> str:
|
def _encode(data: bytes) -> str:
|
||||||
"""
|
"""
|
||||||
Convert bytes to base58 encoded string.
|
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.
|
Convert base58 encoded string to bytes.
|
||||||
"""
|
"""
|
||||||
return base58.decode(string, alphabet=_ripple_alphabet)
|
return base58.decode(string, _ripple_alphabet)
|
||||||
|
|
||||||
|
|
||||||
def encode_check(
|
def encode_check(
|
||||||
@ -26,7 +26,7 @@ def encode_check(
|
|||||||
"""
|
"""
|
||||||
Convert bytes to base58 encoded string, append checksum.
|
Convert bytes to base58 encoded string, append checksum.
|
||||||
"""
|
"""
|
||||||
return encode(data + digestfunc(data))
|
return _encode(data + digestfunc(data))
|
||||||
|
|
||||||
|
|
||||||
def decode_check(
|
def decode_check(
|
||||||
@ -35,5 +35,5 @@ def decode_check(
|
|||||||
"""
|
"""
|
||||||
Convert base58 encoded string to bytes and verify checksum.
|
Convert base58 encoded string to bytes and verify checksum.
|
||||||
"""
|
"""
|
||||||
data = decode(string)
|
data = _decode(string)
|
||||||
return base58.verify_checksum(data, digestfunc)
|
return base58.verify_checksum(data, digestfunc)
|
||||||
|
@ -1,15 +1,9 @@
|
|||||||
from typing import TYPE_CHECKING
|
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 apps.common.keychain import auto_keychain
|
||||||
|
|
||||||
from .helpers import address_from_public_key
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from trezor.messages import RippleGetAddress
|
from trezor.messages import RippleGetAddress, RippleAddress
|
||||||
from apps.common.keychain import Keychain
|
from apps.common.keychain import Keychain
|
||||||
from trezor.wire import Context
|
from trezor.wire import Context
|
||||||
|
|
||||||
@ -18,6 +12,12 @@ if TYPE_CHECKING:
|
|||||||
async def get_address(
|
async def get_address(
|
||||||
ctx: Context, msg: RippleGetAddress, keychain: Keychain
|
ctx: Context, msg: RippleGetAddress, keychain: Keychain
|
||||||
) -> RippleAddress:
|
) -> 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)
|
await paths.validate_path(ctx, keychain, msg.address_n)
|
||||||
|
|
||||||
node = keychain.derive(msg.address_n)
|
node = keychain.derive(msg.address_n)
|
||||||
@ -26,6 +26,6 @@ async def get_address(
|
|||||||
|
|
||||||
if msg.show_display:
|
if msg.show_display:
|
||||||
title = paths.address_n_to_str(msg.address_n)
|
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)
|
return RippleAddress(address=address)
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
from micropython import const
|
from micropython import const
|
||||||
|
|
||||||
from trezor.crypto.hashlib import ripemd160, sha256
|
|
||||||
|
|
||||||
from . import base58_ripple
|
from . import base58_ripple
|
||||||
|
|
||||||
# HASH_TX_ID = const(0x5458_4E00) # 'TXN'
|
# 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
|
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 = sha256(pubkey).digest()
|
||||||
h = ripemd160(h).digest()
|
h = ripemd160(h).digest()
|
||||||
|
|
||||||
|
@ -3,9 +3,8 @@ from typing import TYPE_CHECKING
|
|||||||
from trezor.enums import ButtonRequestType
|
from trezor.enums import ButtonRequestType
|
||||||
from trezor.strings import format_amount
|
from trezor.strings import format_amount
|
||||||
from trezor.ui.layouts import confirm_metadata
|
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:
|
if TYPE_CHECKING:
|
||||||
from trezor.wire import Context
|
from trezor.wire import Context
|
||||||
@ -15,11 +14,11 @@ async def require_confirm_fee(ctx: Context, fee: int) -> None:
|
|||||||
await confirm_metadata(
|
await confirm_metadata(
|
||||||
ctx,
|
ctx,
|
||||||
"confirm_fee",
|
"confirm_fee",
|
||||||
title="Confirm fee",
|
"Confirm fee",
|
||||||
content="Transaction fee:\n{}",
|
"Transaction fee:\n{}",
|
||||||
param=format_amount(fee, helpers.DECIMALS) + " XRP",
|
format_amount(fee, DECIMALS) + " XRP",
|
||||||
|
ButtonRequestType.ConfirmOutput,
|
||||||
hide_continue=True,
|
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(
|
await confirm_metadata(
|
||||||
ctx,
|
ctx,
|
||||||
"confirm_destination_tag",
|
"confirm_destination_tag",
|
||||||
title="Confirm tag",
|
"Confirm tag",
|
||||||
content="Destination tag:\n{}",
|
"Destination tag:\n{}",
|
||||||
param=str(tag),
|
str(tag),
|
||||||
|
ButtonRequestType.ConfirmOutput,
|
||||||
hide_continue=True,
|
hide_continue=True,
|
||||||
br_code=ButtonRequestType.ConfirmOutput,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def require_confirm_tx(ctx: Context, to: str, value: int) -> None:
|
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 micropython import const
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from . import helpers
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from trezor.messages import RippleSignTx
|
from trezor.messages import RippleSignTx
|
||||||
from trezor.utils import Writer
|
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_INT16 = const(1)
|
||||||
_FIELD_TYPE_INT32 = const(2)
|
_FIELD_TYPE_INT32 = const(2)
|
||||||
_FIELD_TYPE_AMOUNT = const(6)
|
_FIELD_TYPE_AMOUNT = const(6)
|
||||||
_FIELD_TYPE_VL = const(7)
|
_FIELD_TYPE_VL = const(7)
|
||||||
_FIELD_TYPE_ACCOUNT = const(8)
|
_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(
|
def serialize(
|
||||||
msg: RippleSignTx,
|
msg: RippleSignTx,
|
||||||
@ -53,93 +29,98 @@ def serialize(
|
|||||||
pubkey: bytes,
|
pubkey: bytes,
|
||||||
signature: bytes | None = None,
|
signature: bytes | None = None,
|
||||||
) -> bytearray:
|
) -> bytearray:
|
||||||
w = bytearray()
|
|
||||||
# must be sorted numerically first by type and then by name
|
# must be sorted numerically first by type and then by name
|
||||||
write(w, FIELDS_MAP["type"], TRANSACTION_TYPES["Payment"])
|
fields_to_write = ( # field_type, field_key, value
|
||||||
write(w, FIELDS_MAP["flags"], msg.flags)
|
(_FIELD_TYPE_INT16, 2, 0), # payment type is 0
|
||||||
write(w, FIELDS_MAP["sequence"], msg.sequence)
|
(_FIELD_TYPE_INT32, 2, msg.flags), # flags
|
||||||
write(w, FIELDS_MAP["destinationTag"], msg.payment.destination_tag)
|
(_FIELD_TYPE_INT32, 4, msg.sequence), # sequence
|
||||||
write(w, FIELDS_MAP["lastLedgerSequence"], msg.last_ledger_sequence)
|
(_FIELD_TYPE_INT32, 14, msg.payment.destination_tag), # destinationTag
|
||||||
write(w, FIELDS_MAP["amount"], msg.payment.amount)
|
(_FIELD_TYPE_INT32, 27, msg.last_ledger_sequence), # lastLedgerSequence
|
||||||
write(w, FIELDS_MAP["fee"], msg.fee)
|
(_FIELD_TYPE_AMOUNT, 1, msg.payment.amount), # amount
|
||||||
write(w, FIELDS_MAP["signingPubKey"], pubkey)
|
(_FIELD_TYPE_AMOUNT, 8, msg.fee), # fee
|
||||||
write(w, FIELDS_MAP["txnSignature"], signature)
|
(_FIELD_TYPE_VL, 3, pubkey), # signingPubKey
|
||||||
write(w, FIELDS_MAP["account"], source_address)
|
(_FIELD_TYPE_VL, 4, signature), # txnSignature
|
||||||
write(w, FIELDS_MAP["destination"], msg.payment.destination)
|
(_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
|
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:
|
if value is None:
|
||||||
return
|
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)
|
assert isinstance(value, int)
|
||||||
w.extend(value.to_bytes(2, "big"))
|
w.extend(value.to_bytes(2, "big"))
|
||||||
elif field.type == _FIELD_TYPE_INT32:
|
elif field_type == _FIELD_TYPE_INT32:
|
||||||
assert isinstance(value, int)
|
assert isinstance(value, int)
|
||||||
w.extend(value.to_bytes(4, "big"))
|
w.extend(value.to_bytes(4, "big"))
|
||||||
elif field.type == _FIELD_TYPE_AMOUNT:
|
elif field_type == _FIELD_TYPE_AMOUNT:
|
||||||
assert isinstance(value, int)
|
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)
|
assert isinstance(value, str)
|
||||||
write_bytes_varint(w, helpers.decode_address(value))
|
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))
|
assert isinstance(value, (bytes, bytearray))
|
||||||
write_bytes_varint(w, value)
|
write_bytes_varint(w, value)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown field type")
|
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:
|
def write_bytes_varint(w: Writer, value: bytes) -> None:
|
||||||
"""Serialize a variable length bytes."""
|
"""Serialize a variable length bytes."""
|
||||||
write_varint(w, len(value))
|
append = w.append # local_cache_attribute
|
||||||
w.extend(value)
|
|
||||||
|
|
||||||
|
# write_varint
|
||||||
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
|
||||||
Implements variable-length int encoding from Ripple.
|
val = len(value)
|
||||||
See: https://ripple.com/wiki/Binary_Format#Variable_Length_Data_Encoding
|
|
||||||
"""
|
|
||||||
if val < 0:
|
if val < 0:
|
||||||
raise ValueError("Only non-negative integers are supported")
|
raise ValueError("Only non-negative integers are supported")
|
||||||
elif val < 192:
|
elif val < 192:
|
||||||
w.append(val)
|
append(val)
|
||||||
elif val <= 12480:
|
elif val <= 12480:
|
||||||
val -= 193
|
val -= 193
|
||||||
w.append(193 + rshift(val, 8))
|
append(193 + rshift(val, 8))
|
||||||
w.append(val & 0xFF)
|
append(val & 0xFF)
|
||||||
elif val <= 918744:
|
elif val <= 918744:
|
||||||
val -= 12481
|
val -= 12481
|
||||||
w.append(241 + rshift(val, 16))
|
append(241 + rshift(val, 16))
|
||||||
w.append(rshift(val, 8) & 0xFF)
|
append(rshift(val, 8) & 0xFF)
|
||||||
w.append(val & 0xFF)
|
append(val & 0xFF)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Value is too large")
|
raise ValueError("Value is too large")
|
||||||
|
|
||||||
|
w.extend(value)
|
||||||
|
|
||||||
|
|
||||||
def rshift(val: int, n: int) -> int:
|
def rshift(val: int, n: int) -> int:
|
||||||
"""
|
"""
|
||||||
|
@ -1,81 +1,59 @@
|
|||||||
from typing import TYPE_CHECKING
|
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 apps.common.keychain import auto_keychain
|
||||||
|
|
||||||
from . import helpers, layout
|
|
||||||
from .serialize import serialize
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from trezor.messages import RippleSignTx
|
from trezor.messages import RippleSignTx, RippleSignedTx
|
||||||
from apps.common.keychain import Keychain
|
from apps.common.keychain import Keychain
|
||||||
from trezor.wire import Context
|
from trezor.wire import Context
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE: it is one big function because that way it is the most flash-space-efficient
|
||||||
@auto_keychain(__name__)
|
@auto_keychain(__name__)
|
||||||
async def sign_tx(
|
async def sign_tx(
|
||||||
ctx: Context, msg: RippleSignTx, keychain: Keychain
|
ctx: Context, msg: RippleSignTx, keychain: Keychain
|
||||||
) -> RippleSignedTx:
|
) -> 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)
|
await paths.validate_path(ctx, keychain, msg.address_n)
|
||||||
|
|
||||||
node = keychain.derive(msg.address_n)
|
node = keychain.derive(msg.address_n)
|
||||||
source_address = helpers.address_from_public_key(node.public_key())
|
source_address = helpers.address_from_public_key(node.public_key())
|
||||||
|
|
||||||
set_canonical_flag(msg)
|
# Setting canonical flag
|
||||||
tx = serialize(msg, source_address, pubkey=node.public_key())
|
# Our ECDSA implementation already returns fully-canonical signatures,
|
||||||
to_sign = get_network_prefix() + tx
|
# so we're enforcing it in the transaction using the designated flag
|
||||||
|
# - see https://wiki.ripple.com/Transaction_Malleability#Using_Fully-Canonical_Signatures
|
||||||
check_fee(msg.fee)
|
# - see https://github.com/trezor/trezor-crypto/blob/3e8974ff8871263a70b7fbb9a27a1da5b0d810f7/ecdsa.c#L791
|
||||||
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
|
|
||||||
"""
|
|
||||||
msg.flags |= helpers.FLAG_FULLY_CANONICAL
|
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.fee < helpers.MIN_FEE or msg.fee > helpers.MAX_FEE:
|
||||||
if msg.payment.amount > helpers.MAX_ALLOWED_AMOUNT:
|
raise ProcessError("Fee must be in the range of 10 to 10,000 drops")
|
||||||
raise ProcessError("Amount exceeds maximum allowed amount.")
|
|
||||||
|
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)
|
||||||
|
Loading…
Reference in New Issue
Block a user