You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trezor-firmware/core/src/apps/ripple/sign_tx.py

87 lines
3.1 KiB

from trezor.crypto import der
from trezor.crypto.curve import secp256k1
from trezor.crypto.hashlib import sha512
from trezor.messages.RippleSignedTx import RippleSignedTx
from trezor.messages.RippleSignTx import RippleSignTx
from trezor.wire import ProcessError
from apps.common import paths
from apps.common.keychain import with_slip44_keychain
from . import CURVE, SLIP44_ID, helpers, layout
from .serialize import serialize
@with_slip44_keychain(SLIP44_ID, CURVE, allow_testnet=True)
async def sign_tx(ctx, msg: RippleSignTx, keychain):
validate(msg)
await paths.validate_path(
ctx, helpers.validate_full_path, keychain, msg.address_n, CURVE
)
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):
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():
"""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):
"""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):
"""
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
"""
if msg.flags is None:
msg.flags = 0
msg.flags |= helpers.FLAG_FULLY_CANONICAL
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.")