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/monero/signing/step_07_all_outputs_set.py

180 lines
5.4 KiB

"""
All outputs were set in this phase. This step serializes tx pub keys
into the tx extra field and then hashes it into the prefix hash.
The prefix hash is then complete.
"""
import gc
from trezor import utils
from apps.monero.layout import confirms
from apps.monero.signing import RctType
from apps.monero.xmr import crypto
from .state import State
if False:
from trezor.messages.MoneroTransactionAllOutSetAck import (
MoneroTransactionAllOutSetAck,
)
async def all_outputs_set(state: State) -> MoneroTransactionAllOutSetAck:
state.mem_trace(0)
await confirms.transaction_step(state, state.STEP_ALL_OUT)
state.mem_trace(1)
_validate(state)
state.is_processing_offloaded = False
state.mem_trace(2)
extra_b = _set_tx_extra(state)
# tx public keys not needed anymore
state.additional_tx_public_keys = None
state.tx_pub = None
state.rsig_grouping = None
state.rsig_offload = None
gc.collect()
state.mem_trace(3)
# Completes the transaction prefix hash by including extra
_set_tx_prefix(state, extra_b)
state.output_change = None
gc.collect()
state.mem_trace(4)
# In the multisig mode here needs to be a check whether currently computed
# transaction prefix matches expected transaction prefix sent in the
# init step.
from trezor.messages.MoneroRingCtSig import MoneroRingCtSig
from trezor.messages.MoneroTransactionAllOutSetAck import (
MoneroTransactionAllOutSetAck,
)
# Initializes RCTsig structure (fee, tx prefix hash, type)
rv_pb = MoneroRingCtSig(
txn_fee=state.fee, message=state.tx_prefix_hash, rv_type=RctType.Bulletproof2,
)
_out_pk(state)
state.full_message_hasher.rctsig_base_done()
state.current_output_index = None
state.current_input_index = -1
state.full_message = state.full_message_hasher.get_digest()
state.full_message_hasher = None
state.output_pk_commitments = None
state.summary_outs_money = None
state.summary_inputs_money = None
state.fee = None
state.last_ki = None
state.last_step = state.STEP_ALL_OUT
return MoneroTransactionAllOutSetAck(
extra=extra_b,
tx_prefix_hash=state.tx_prefix_hash,
rv=rv_pb,
full_message_hash=state.full_message,
)
def _validate(state: State):
if state.last_step != state.STEP_OUT:
raise ValueError("Invalid state transition")
if state.current_output_index + 1 != state.output_count:
raise ValueError("Invalid out num")
# Fee test
if state.fee != (state.summary_inputs_money - state.summary_outs_money):
raise ValueError(
"Fee invalid %s vs %s, out: %s"
% (
state.fee,
state.summary_inputs_money - state.summary_outs_money,
state.summary_outs_money,
)
)
if state.summary_outs_money > state.summary_inputs_money:
raise ValueError(
"Transaction inputs money (%s) less than outputs money (%s)"
% (state.summary_inputs_money, state.summary_outs_money)
)
def _set_tx_extra(state: State) -> bytes:
"""
Sets tx public keys into transaction's extra.
Extra field is supposed to be sorted (by sort_tx_extra() in the Monero)
Tag ordering: TX_EXTRA_TAG_PUBKEY, TX_EXTRA_TAG_ADDITIONAL_PUBKEYS, TX_EXTRA_NONCE
"""
from apps.monero.xmr.serialize import int_serialize
# Extra buffer length computation
# TX_EXTRA_TAG_PUBKEY (1B) | tx_pub_key (32B)
extra_size = 33
offset = 0
num_keys = 0
len_size = 0
if state.need_additional_txkeys:
num_keys = len(state.additional_tx_public_keys)
len_size = int_serialize.uvarint_size(num_keys)
# TX_EXTRA_TAG_ADDITIONAL_PUBKEYS (1B) | varint | keys
extra_size += 1 + len_size + 32 * num_keys
if state.extra_nonce:
extra_size += len(state.extra_nonce)
extra = bytearray(extra_size)
extra[0] = 1 # TX_EXTRA_TAG_PUBKEY
crypto.encodepoint_into(memoryview(extra)[1:], state.tx_pub)
offset += 33
if state.need_additional_txkeys:
extra[offset] = 0x4 # TX_EXTRA_TAG_ADDITIONAL_PUBKEYS
int_serialize.dump_uvarint_b_into(num_keys, extra, offset + 1)
offset += 1 + len_size
for idx in range(num_keys):
extra[offset : offset + 32] = state.additional_tx_public_keys[idx]
offset += 32
if state.extra_nonce:
utils.memcpy(extra, offset, state.extra_nonce, 0, len(state.extra_nonce))
state.extra_nonce = None
return extra
def _set_tx_prefix(state: State, extra: bytes):
"""
Adds `extra` to the tx_prefix_hash, which is the last needed item,
so the tx_prefix_hash is now complete and can be incorporated
into full_message_hash.
"""
# Serializing "extra" type as BlobType.
# uvarint(len(extra)) || extra
state.tx_prefix_hasher.uvarint(len(extra))
state.tx_prefix_hasher.buffer(extra)
state.tx_prefix_hash = state.tx_prefix_hasher.get_digest()
state.tx_prefix_hasher = None
state.full_message_hasher.set_message(state.tx_prefix_hash)
def _out_pk(state: State):
"""
Hashes out_pk into the full message.
"""
if state.output_count != len(state.output_pk_commitments):
raise ValueError("Invalid number of ecdh")
for out in state.output_pk_commitments:
state.full_message_hasher.set_out_pk_commitment(out)