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/lisk/sign_tx.py

151 lines
4.6 KiB

import ustruct
from trezor import wire
from trezor.crypto.curve import ed25519
from trezor.crypto.hashlib import sha256
from trezor.enums import LiskTransactionType
from trezor.messages import LiskSignedTx
from trezor.utils import HashWriter
from apps.common import paths
from apps.common.keychain import auto_keychain
from . import layout
from .helpers import get_address_from_public_key
@auto_keychain(__name__)
async def sign_tx(ctx, msg, keychain):
await paths.validate_path(ctx, keychain, msg.address_n)
pubkey, seckey = _get_keys(keychain, msg)
transaction = _update_raw_tx(msg.transaction, pubkey)
try:
await _require_confirm_by_type(ctx, transaction)
except AttributeError:
raise wire.DataError("The transaction has invalid asset data field")
await layout.require_confirm_fee(ctx, transaction.amount, transaction.fee)
txbytes = _get_transaction_bytes(transaction)
txhash = HashWriter(sha256())
for field in txbytes:
txhash.extend(field)
digest = txhash.get_digest()
signature = ed25519.sign(seckey, digest)
return LiskSignedTx(signature=signature)
def _get_keys(keychain, msg):
node = keychain.derive(msg.address_n)
seckey = node.private_key()
pubkey = node.public_key()
pubkey = pubkey[1:] # skip ed25519 pubkey marker
return pubkey, seckey
def _update_raw_tx(transaction, pubkey):
# If device is using for second signature sender_public_key must be exist in transaction
if not transaction.sender_public_key:
transaction.sender_public_key = pubkey
# For CastVotes transactions, recipientId should be equal to transaction
# creator address.
if transaction.type == LiskTransactionType.CastVotes:
if not transaction.recipient_id:
transaction.recipient_id = get_address_from_public_key(pubkey)
return transaction
async def _require_confirm_by_type(ctx, transaction):
if transaction.type == LiskTransactionType.Transfer:
return await layout.require_confirm_tx(
ctx, transaction.recipient_id, transaction.amount
)
if transaction.type == LiskTransactionType.RegisterDelegate:
return await layout.require_confirm_delegate_registration(
ctx, transaction.asset.delegate.username
)
if transaction.type == LiskTransactionType.CastVotes:
return await layout.require_confirm_vote_tx(ctx, transaction.asset.votes)
if transaction.type == LiskTransactionType.RegisterSecondPassphrase:
return await layout.require_confirm_public_key(
ctx, transaction.asset.signature.public_key
)
if transaction.type == LiskTransactionType.RegisterMultisignatureAccount:
return await layout.require_confirm_multisig(
ctx, transaction.asset.multisignature
)
raise wire.DataError("Invalid transaction type")
def _get_transaction_bytes(tx):
# Required transaction parameters
t_type = ustruct.pack("<b", tx.type)
t_timestamp = ustruct.pack("<i", tx.timestamp)
t_sender_public_key = tx.sender_public_key
t_requester_public_key = tx.requester_public_key or b""
if not tx.recipient_id:
# Value can be empty string
t_recipient_id = ustruct.pack(">Q", 0)
else:
# Lisk uses big-endian for recipient_id, string -> int -> bytes
t_recipient_id = ustruct.pack(">Q", int(tx.recipient_id[:-1]))
t_amount = ustruct.pack("<Q", tx.amount)
t_asset = _get_asset_data_bytes(tx)
t_signature = tx.signature or b""
return (
t_type,
t_timestamp,
t_sender_public_key,
t_requester_public_key,
t_recipient_id,
t_amount,
t_asset,
t_signature,
)
def _get_asset_data_bytes(msg):
if msg.type == LiskTransactionType.Transfer:
# Transfer transaction have optional data field
if msg.asset.data is not None:
return msg.asset.data.encode()
else:
return b""
if msg.type == LiskTransactionType.RegisterDelegate:
return msg.asset.delegate.username.encode()
if msg.type == LiskTransactionType.CastVotes:
return ("".join(msg.asset.votes)).encode()
if msg.type == LiskTransactionType.RegisterSecondPassphrase:
return msg.asset.signature.public_key
if msg.type == LiskTransactionType.RegisterMultisignatureAccount:
data = b""
data += ustruct.pack("<b", msg.asset.multisignature.min)
data += ustruct.pack("<b", msg.asset.multisignature.life_time)
data += ("".join(msg.asset.multisignature.keys_group)).encode()
return data
raise wire.DataError("Invalid transaction type")