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/src/apps/monero/signing/step_02_set_input.py

172 lines
6.0 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 .state import State
from apps.monero.layout import confirms
from apps.monero.signing import RctType
from apps.monero.xmr import crypto, monero, serialize
if False:
from trezor.messages.MoneroTransactionSourceEntry import (
MoneroTransactionSourceEntry,
)
async def set_input(state: State, src_entr: MoneroTransactionSourceEntry):
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.STEP_INP, state.current_input_index, state.input_count
)
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_keys = [
crypto.decodepoint(x) for x in src_entr.real_out_additional_tx_keys
]
"""
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_keys,
src_entr.real_output_in_tx_index,
)
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 = await 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
pseudo_out = None
pseudo_out_hmac = None
alpha_enc = None
if state.rct_type == RctType.Simple:
alpha, pseudo_out = _gen_commitment(state, src_entr.amount)
pseudo_out = crypto.encodepoint(pseudo_out)
# In full version 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),
)
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
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):
"""
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):
"""
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