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_02_set_input.py

190 lines
6.8 KiB

"""
UTXOs are sent one by one to Trezor for processing, encoded as MoneroTransactionSourceEntry.
MoneroTransactionSourceEntry contains the actual UTXO to be spent, but also the other decoy/mixin
outputs. So all the outputs are in one list and then the `real_output` index specifies which output
is the real one to be spent.
This step computes spending secret key, key image, tx.vin[i] + HMAC, Pedersen commitment on amount.
If number of inputs is small, in-memory mode is used = alpha, pseudo_outs are kept in the Trezor.
Otherwise pseudo_outs are offloaded with HMAC, alpha is offloaded encrypted under chacha_poly with
key derived for exactly this purpose.
"""
from apps.monero.layout import confirms
from apps.monero.xmr import crypto, monero, serialize
from .state import State
if False:
from apps.monero.xmr.types import Sc25519, Ge25519
from trezor.messages.MoneroTransactionSourceEntry import (
MoneroTransactionSourceEntry,
)
from trezor.messages.MoneroTransactionSetInputAck import (
MoneroTransactionSetInputAck,
)
async def set_input(
state: State, src_entr: MoneroTransactionSourceEntry
) -> MoneroTransactionSetInputAck:
from trezor.messages.MoneroTransactionSetInputAck import (
MoneroTransactionSetInputAck,
)
from apps.monero.xmr.crypto import chacha_poly
from apps.monero.xmr.serialize_messages.tx_prefix import TxinToKey
from apps.monero.signing import offloading_keys
state.current_input_index += 1
await confirms.transaction_step(state, state.STEP_INP, state.current_input_index)
if state.last_step > state.STEP_INP:
raise ValueError("Invalid state transition")
if state.current_input_index >= state.input_count:
raise ValueError("Too many inputs")
# real_output denotes which output in outputs is the real one (ours)
if src_entr.real_output >= len(src_entr.outputs):
raise ValueError(
"real_output index %s bigger than output_keys.size() %s"
% (src_entr.real_output, len(src_entr.outputs))
)
state.summary_inputs_money += src_entr.amount
# Secrets derivation
# the UTXO's one-time address P
out_key = crypto.decodepoint(src_entr.outputs[src_entr.real_output].key.dest)
# the tx_pub of our UTXO stored inside its transaction
tx_key = crypto.decodepoint(src_entr.real_out_tx_key)
additional_tx_pub_key = _get_additional_public_key(src_entr)
"""
Calculates `derivation = Ra`, private spend key `x = H(Ra||i) + b` to be able
to spend the UTXO; and key image `I = x*H(P||i)`
"""
xi, ki, di = monero.generate_tx_spend_and_key_image_and_derivation(
state.creds,
state.subaddresses,
out_key,
tx_key,
additional_tx_pub_key,
src_entr.real_output_in_tx_index,
state.account_idx,
src_entr.subaddr_minor,
)
state.mem_trace(1, True)
# Construct tx.vin
# If multisig is used then ki in vini should be src_entr.multisig_kLRki.ki
vini = TxinToKey(amount=src_entr.amount, k_image=crypto.encodepoint(ki))
vini.key_offsets = _absolute_output_offsets_to_relative(
[x.idx for x in src_entr.outputs]
)
if src_entr.rct:
vini.amount = 0
"""
Serialize `vini` with variant code for TxinToKey (prefix = TxinToKey.VARIANT_CODE).
The binary `vini_bin` is later sent to step 4 and 9 with its hmac,
where it is checked and directly used.
"""
vini_bin = serialize.dump_msg(vini, preallocate=64, prefix=b"\x02")
state.mem_trace(2, True)
# HMAC(T_in,i || vin_i)
hmac_vini = offloading_keys.gen_hmac_vini(
state.key_hmac, src_entr, vini_bin, state.current_input_index
)
state.mem_trace(3, True)
# PseudoOuts commitment, alphas stored to state
alpha, pseudo_out = _gen_commitment(state, src_entr.amount)
pseudo_out = crypto.encodepoint(pseudo_out)
# The alpha is encrypted and passed back for storage
pseudo_out_hmac = crypto.compute_hmac(
offloading_keys.hmac_key_txin_comm(state.key_hmac, state.current_input_index),
pseudo_out,
)
alpha_enc = chacha_poly.encrypt_pack(
offloading_keys.enc_key_txin_alpha(state.key_enc, state.current_input_index),
crypto.encodeint(alpha),
)
spend_enc = chacha_poly.encrypt_pack(
offloading_keys.enc_key_spend(state.key_enc, state.current_input_index),
crypto.encodeint(xi),
)
state.last_step = state.STEP_INP
if state.current_input_index + 1 == state.input_count:
"""
When we finish the inputs processing, we no longer need
the precomputed subaddresses so we clear them to save memory.
"""
state.subaddresses = None
state.input_last_amount = src_entr.amount
return MoneroTransactionSetInputAck(
vini=vini_bin,
vini_hmac=hmac_vini,
pseudo_out=pseudo_out,
pseudo_out_hmac=pseudo_out_hmac,
pseudo_out_alpha=alpha_enc,
spend_key=spend_enc,
)
def _gen_commitment(state: State, in_amount: int) -> tuple[Sc25519, Ge25519]:
"""
Computes Pedersen commitment - pseudo outs
Here is slight deviation from the original protocol.
We want that \\sum Alpha = \\sum A_{i,j} where A_{i,j} is a mask from range proof for output i, bit j.
Previously this was computed in such a way that Alpha_{last} = \\sum A{i,j} - \\sum_{i=0}^{last-1} Alpha
But we would prefer to compute commitment before range proofs so alphas are generated completely randomly
and the last A mask is computed in this special way.
Returns pseudo_out
"""
alpha = crypto.random_scalar()
state.sumpouts_alphas = crypto.sc_add(state.sumpouts_alphas, alpha)
return alpha, crypto.gen_commitment(alpha, in_amount)
def _absolute_output_offsets_to_relative(off: list[int]) -> list[int]:
"""
Mixin outputs are specified in relative numbers. First index is absolute
and the rest is an offset of a previous one.
Helps with varint encoding size.
Example: absolute {7,11,15,20} is converted to {7,4,4,5}
"""
if len(off) == 0:
return off
off.sort()
for i in range(len(off) - 1, 0, -1):
off[i] -= off[i - 1]
return off
def _get_additional_public_key(
src_entr: MoneroTransactionSourceEntry,
) -> Ge25519 | None:
additional_tx_pub_key = None
if len(src_entr.real_out_additional_tx_keys) == 1: # compression
additional_tx_pub_key = crypto.decodepoint(
src_entr.real_out_additional_tx_keys[0]
)
elif src_entr.real_out_additional_tx_keys:
if src_entr.real_output_in_tx_index >= len(
src_entr.real_out_additional_tx_keys
):
raise ValueError("Wrong number of additional derivations")
additional_tx_pub_key = crypto.decodepoint(
src_entr.real_out_additional_tx_keys[src_entr.real_output_in_tx_index]
)
return additional_tx_pub_key