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.
191 lines
6.9 KiB
191 lines
6.9 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 typing import List, Tuple, Optional
|
|
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,
|
|
) -> Optional[Ge25519]:
|
|
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
|