def gen_hmac_vini(key: bytes, src_entr: MoneroTransactionSourceEntry, vini_bin: bytes, idx: int) -> bytes: """ Computes hmac (TxSourceEntry[i] || tx.vin[i]) In src_entr.outputs only src_entr.outputs[src_entr.real_output] is HMACed as it is used across the protocol. Consistency of other values across the protocol is not required as they are used only once and hard to check. I.e., indices in step 2 are uncheckable, decoy keys in step 9 are just random keys. """ from trezor import protobuf from apps.monero.xmr.keccak_hasher import get_keccak_writer kwriter = get_keccak_writer() real_outputs = src_entr.outputs real_additional = src_entr.real_out_additional_tx_keys src_entr.outputs = [src_entr.outputs[src_entr.real_output]] if real_additional and len(real_additional) > 1: src_entr.real_out_additional_tx_keys = [ src_entr.real_out_additional_tx_keys[ src_entr.real_output_in_tx_index] ] kwriter.write(protobuf.dump_message_buffer(src_entr)) src_entr.outputs = real_outputs src_entr.real_out_additional_tx_keys = real_additional kwriter.write(vini_bin) hmac_key_vini = hmac_key_txin(key, idx) hmac_vini = crypto_helpers.compute_hmac(hmac_key_vini, kwriter.get_digest()) return hmac_vini
def compute_enc_key_host(view_key_private: Scalar, tx_prefix_hash: bytes) -> tuple[bytes, bytes]: from trezor.crypto import random from apps.monero.xmr import crypto_helpers salt = random.bytes(32) passwd = crypto_helpers.keccak_2hash( crypto_helpers.encodeint(view_key_private) + tx_prefix_hash) tx_key = crypto_helpers.compute_hmac(salt, passwd) return tx_key, salt
def compute_tx_key( spend_key_private: Scalar, tx_prefix_hash: bytes, salt: bytes, rand_mult_num: Scalar, ) -> bytes: from apps.monero.xmr import crypto, crypto_helpers rand_inp = crypto.sc_add_into(None, spend_key_private, rand_mult_num) passwd = crypto_helpers.keccak_2hash( crypto_helpers.encodeint(rand_inp) + tx_prefix_hash) tx_key = crypto_helpers.compute_hmac(salt, passwd) return tx_key
def gen_hmac_tsxdest(key: bytes, dst_entr: MoneroTransactionDestinationEntry, idx: int) -> bytes: """ Generates HMAC for TxDestinationEntry[i] """ from trezor import protobuf from apps.monero.xmr.keccak_hasher import get_keccak_writer kwriter = get_keccak_writer() kwriter.write(protobuf.dump_message_buffer(dst_entr)) hmac_key = hmac_key_txdst(key, idx) hmac_tsxdest = crypto_helpers.compute_hmac(hmac_key, kwriter.get_digest()) return hmac_tsxdest
def gen_hmac_vouti(key: bytes, dst_entr: MoneroTransactionDestinationEntry, tx_out_bin: bytes, idx: int) -> bytes: """ Generates HMAC for (TxDestinationEntry[i] || tx.vout[i]) """ from trezor import protobuf from apps.monero.xmr.keccak_hasher import get_keccak_writer kwriter = get_keccak_writer() kwriter.write(protobuf.dump_message_buffer(dst_entr)) kwriter.write(tx_out_bin) hmac_key_vouti = hmac_key_txout(key, idx) hmac_vouti = crypto_helpers.compute_hmac(hmac_key_vouti, kwriter.get_digest()) return hmac_vouti
async def sign_input( state: State, src_entr: MoneroTransactionSourceEntry, vini_bin: bytes, vini_hmac: bytes, pseudo_out: bytes, pseudo_out_hmac: bytes, pseudo_out_alpha_enc: bytes, spend_enc: bytes, orig_idx: int, ) -> MoneroTransactionSignInputAck: """ :param state: transaction state :param src_entr: Source entry :param vini_bin: tx.vin[i] for the transaction. Contains key image, offsets, amount (usually zero) :param vini_hmac: HMAC for the tx.vin[i] as returned from Trezor :param pseudo_out: Pedersen commitment for the current input, uses pseudo_out_alpha as a mask. Only applicable for RCTTypeSimple. :param pseudo_out_hmac: HMAC for pseudo_out :param pseudo_out_alpha_enc: alpha mask used in pseudo_out, only applicable for RCTTypeSimple. Encrypted. :param spend_enc: one time address spending private key. Encrypted. :param orig_idx: original index of the src_entr before sorting (HMAC check) :return: Generated signature MGs[i] """ await layout.transaction_step(state, state.STEP_SIGN, state.current_input_index + 1) state.current_input_index += 1 if state.last_step not in (state.STEP_ALL_OUT, state.STEP_SIGN): raise ValueError("Invalid state transition") if state.current_input_index >= state.input_count: raise ValueError("Invalid inputs count") if pseudo_out is None: raise ValueError("SimpleRCT requires pseudo_out but none provided") if pseudo_out_alpha_enc is None: raise ValueError( "SimpleRCT requires pseudo_out's mask but none provided") input_position = orig_idx mods = utils.unimport_begin() # Check input's HMAC from apps.monero.signing import offloading_keys vini_hmac_comp = offloading_keys.gen_hmac_vini(state.key_hmac, src_entr, vini_bin, input_position) if not crypto.ct_equals(vini_hmac_comp, vini_hmac): raise ValueError("HMAC is not correct") # Key image sorting check - permutation correctness cur_ki = offloading_keys.get_ki_from_vini(vini_bin) if state.current_input_index > 0 and state.last_ki <= cur_ki: raise ValueError("Key image order invalid") state.last_ki = cur_ki if state.current_input_index < state.input_count else None del (cur_ki, vini_bin, vini_hmac, vini_hmac_comp) gc.collect() state.mem_trace(1, True) from apps.monero.xmr import chacha_poly pseudo_out_alpha = crypto_helpers.decodeint( chacha_poly.decrypt_pack( offloading_keys.enc_key_txin_alpha(state.key_enc, input_position), bytes(pseudo_out_alpha_enc), )) # Last pseudo_out is recomputed so mask sums hold if input_position + 1 == state.input_count: # Recompute the lash alpha so the sum holds state.mem_trace("Correcting alpha") alpha_diff = crypto.sc_sub_into(None, state.sumout, state.sumpouts_alphas) crypto.sc_add_into(pseudo_out_alpha, pseudo_out_alpha, alpha_diff) pseudo_out_c = crypto.gen_commitment_into(None, pseudo_out_alpha, state.input_last_amount) else: if input_position + 1 == state.input_count: utils.ensure( crypto.sc_eq(state.sumpouts_alphas, state.sumout) != 0, "Sum eq error") # both pseudo_out and its mask were offloaded so we need to # validate pseudo_out's HMAC and decrypt the alpha pseudo_out_hmac_comp = crypto_helpers.compute_hmac( offloading_keys.hmac_key_txin_comm(state.key_hmac, input_position), pseudo_out, ) if not crypto.ct_equals(pseudo_out_hmac_comp, pseudo_out_hmac): raise ValueError("HMAC is not correct") pseudo_out_c = crypto_helpers.decodepoint(pseudo_out) state.mem_trace(2, True) # Spending secret spend_key = crypto_helpers.decodeint( chacha_poly.decrypt_pack( offloading_keys.enc_key_spend(state.key_enc, input_position), bytes(spend_enc), )) del ( offloading_keys, chacha_poly, pseudo_out, pseudo_out_hmac, pseudo_out_alpha_enc, spend_enc, ) utils.unimport_end(mods) state.mem_trace(3, True) # Basic setup, sanity check from apps.monero.xmr.serialize_messages.tx_ct_key import CtKey index = src_entr.real_output input_secret_key = CtKey(spend_key, crypto_helpers.decodeint(src_entr.mask)) # Private key correctness test utils.ensure( crypto.point_eq( crypto_helpers.decodepoint( src_entr.outputs[src_entr.real_output].key.dest), crypto.scalarmult_base_into(None, input_secret_key.dest), ), "Real source entry's destination does not equal spend key's", ) utils.ensure( crypto.point_eq( crypto_helpers.decodepoint( src_entr.outputs[src_entr.real_output].key.commitment), crypto.gen_commitment_into(None, input_secret_key.mask, src_entr.amount), ), "Real source entry's mask does not equal spend key's", ) state.mem_trace(4, True) from apps.monero.xmr import clsag mg_buffer = [] ring_pubkeys = [x.key for x in src_entr.outputs if x] utils.ensure(len(ring_pubkeys) == len(src_entr.outputs), "Invalid ring") del src_entr state.mem_trace(5, True) assert state.full_message is not None state.mem_trace("CLSAG") clsag.generate_clsag_simple( state.full_message, ring_pubkeys, input_secret_key, pseudo_out_alpha, pseudo_out_c, index, mg_buffer, ) del (CtKey, input_secret_key, pseudo_out_alpha, clsag, ring_pubkeys) state.mem_trace(6, True) from trezor.messages import MoneroTransactionSignInputAck # Encrypt signature, reveal once protocol finishes OK utils.unimport_end(mods) state.mem_trace(7, True) mg_buffer = _protect_signature(state, mg_buffer) state.mem_trace(8, True) state.last_step = state.STEP_SIGN return MoneroTransactionSignInputAck( signature=mg_buffer, pseudo_out=crypto_helpers.encodepoint(pseudo_out_c))
async def set_input( state: State, src_entr: MoneroTransactionSourceEntry ) -> MoneroTransactionSetInputAck: from trezor.messages import MoneroTransactionSetInputAck from apps.monero.xmr 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 layout.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( f"real_output index {src_entr.real_output} bigger than output_keys.size() {len(src_entr.outputs)}" ) state.summary_inputs_money += src_entr.amount # Secrets derivation # the UTXO's one-time address P out_key = crypto_helpers.decodepoint( src_entr.outputs[src_entr.real_output].key.dest) # the tx_pub of our UTXO stored inside its transaction tx_key = crypto_helpers.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_helpers.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_helpers.encodepoint(pseudo_out) # The alpha is encrypted and passed back for storage pseudo_out_hmac = crypto_helpers.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_helpers.encodeint(alpha), ) spend_enc = chacha_poly.encrypt_pack( offloading_keys.enc_key_spend(state.key_enc, state.current_input_index), crypto_helpers.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, )