async def input_vini( state: State, src_entr: MoneroTransactionSourceEntry, vini_bin: bytes, vini_hmac: bytes, ): from trezor.messages.MoneroTransactionInputViniAck import ( MoneroTransactionInputViniAck, ) await confirms.transaction_step( state, state.STEP_VINI, state.current_input_index + 1 ) if state.current_input_index >= state.input_count: raise ValueError("Too many inputs") state.current_input_index += 1 # HMAC(T_in,i || vin_i) hmac_vini_comp = await offloading_keys.gen_hmac_vini( state.key_hmac, src_entr, vini_bin, state.source_permutation[state.current_input_index], ) if not crypto.ct_equals(hmac_vini_comp, vini_hmac): raise ValueError("HMAC is not correct") """ Incremental hasing of tx.vin[i] """ state.tx_prefix_hasher.buffer(vini_bin) return MoneroTransactionInputViniAck()
def _hash_vini_pseudo_out(state: State, pseudo_out: bytes, pseudo_out_hmac: bytes): """ Incremental hasing of pseudo output. Only applicable for simple rct. """ idx = state.source_permutation[state.current_input_index] pseudo_out_hmac_comp = crypto.compute_hmac( offloading_keys.hmac_key_txin_comm(state.key_hmac, idx), pseudo_out) if not crypto.ct_equals(pseudo_out_hmac, pseudo_out_hmac_comp): raise ValueError("HMAC invalid for pseudo outs") state.full_message_hasher.set_pseudo_out(pseudo_out)
async def _validate( state: State, dst_entr: MoneroTransactionDestinationEntry, dst_entr_hmac: bytes, is_offloaded_bp: bool, ) -> MoneroTransactionDestinationEntry: if state.last_step not in (state.STEP_ALL_IN, state.STEP_OUT): raise ValueError("Invalid state transition") if is_offloaded_bp and (not state.rsig_offload): raise ValueError("Extraneous offloaded msg") if state.rsig_offload: bidx = _get_rsig_batch(state, state.current_output_index) last_in_batch = _is_last_in_batch(state, state.current_output_index, bidx) utils.ensure( not last_in_batch or state.is_processing_offloaded != is_offloaded_bp, "Offloaded BP out of order", ) state.is_processing_offloaded = is_offloaded_bp if not state.is_processing_offloaded: state.current_output_index += 1 utils.ensure( not dst_entr or dst_entr.amount >= 0, "Destination with negative amount" ) utils.ensure( state.current_input_index + 1 == state.input_count, "Invalid number of inputs" ) utils.ensure( state.current_output_index < state.output_count, "Invalid output index" ) if not state.is_processing_offloaded: # HMAC check of the destination dst_entr_hmac_computed = await offloading_keys.gen_hmac_tsxdest( state.key_hmac, dst_entr, state.current_output_index ) utils.ensure( crypto.ct_equals(dst_entr_hmac, dst_entr_hmac_computed), "HMAC failed" ) del dst_entr_hmac_computed else: dst_entr = None del dst_entr_hmac state.mem_trace(3, True) return dst_entr
async def _validate(state: State, dst_entr, dst_entr_hmac, is_offloaded_bp): # If offloading flag then it has to be det_masks and offloading enabled. # Using IF as it is easier to read. if is_offloaded_bp and (not state.rsig_offload or not state.is_det_mask()): raise ValueError("Extraneous offloaded msg") # State change according to the det-mask BP offloading. if state.is_det_mask() and state.rsig_offload: bidx = _get_rsig_batch(state, state.current_output_index) last_in_batch = _is_last_in_batch(state, state.current_output_index, bidx) utils.ensure( not last_in_batch or state.is_processing_offloaded != is_offloaded_bp, "Offloaded BP out of order", ) state.is_processing_offloaded = is_offloaded_bp if not state.is_processing_offloaded: state.current_output_index += 1 utils.ensure(not dst_entr or dst_entr.amount >= 0, "Destination with negative amount") utils.ensure(state.current_input_index + 1 == state.input_count, "Invalid number of inputs") utils.ensure(state.current_output_index < state.output_count, "Invalid output index") utils.ensure( state.is_det_mask() or not state.is_processing_offloaded, "Offloaded extra msg while not using det masks", ) if not state.is_processing_offloaded: # HMAC check of the destination dst_entr_hmac_computed = await offloading_keys.gen_hmac_tsxdest( state.key_hmac, dst_entr, state.current_output_index) utils.ensure(crypto.ct_equals(dst_entr_hmac, dst_entr_hmac_computed), "HMAC failed") del (dst_entr_hmac_computed) else: dst_entr = None del (dst_entr_hmac) state.mem_trace(3, True) return dst_entr
async def _validate(state: State, dst_entr, dst_entr_hmac): if state.current_input_index + 1 != state.input_count: raise ValueError("Invalid number of inputs") if state.current_output_index >= state.output_count: raise ValueError("Invalid output index") if dst_entr.amount < 0: raise ValueError("Destination with wrong amount: %s" % dst_entr.amount) # HMAC check of the destination dst_entr_hmac_computed = await offloading_keys.gen_hmac_tsxdest( state.key_hmac, dst_entr, state.current_output_index) if not crypto.ct_equals(dst_entr_hmac, dst_entr_hmac_computed): raise ValueError("HMAC invalid") del (dst_entr_hmac, dst_entr_hmac_computed) state.mem_trace(3, True)
async def input_vini( state: State, src_entr: MoneroTransactionSourceEntry, vini_bin: bytes, vini_hmac: bytes, orig_idx: int, ) -> MoneroTransactionInputViniAck: from trezor.messages.MoneroTransactionInputViniAck import ( MoneroTransactionInputViniAck, ) await confirms.transaction_step(state, state.STEP_VINI, state.current_input_index + 1) if state.last_step not in (state.STEP_INP, state.STEP_PERM, state.STEP_VINI): raise ValueError("Invalid state transition") if state.current_input_index >= state.input_count: raise ValueError("Too many inputs") if state.client_version >= 2 and state.last_step < state.STEP_VINI: state.current_input_index = -1 state.last_ki = None state.current_input_index += 1 # HMAC(T_in,i || vin_i) hmac_vini_comp = await offloading_keys.gen_hmac_vini( state.key_hmac, src_entr, vini_bin, state.source_permutation[state.current_input_index] if state.client_version <= 1 else orig_idx, ) if not crypto.ct_equals(hmac_vini_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") """ Incremental hasing of tx.vin[i] """ state.tx_prefix_hasher.buffer(vini_bin) state.last_step = state.STEP_VINI state.last_ki = cur_ki if state.current_input_index < state.input_count else None return MoneroTransactionInputViniAck()
async def input_vini( state: State, src_entr: MoneroTransactionSourceEntry, vini_bin: bytes, vini_hmac: bytes, pseudo_out: bytes, pseudo_out_hmac: bytes, ): from trezor.messages.MoneroTransactionInputViniAck import ( MoneroTransactionInputViniAck, ) await confirms.transaction_step(state.ctx, state.STEP_VINI, state.current_input_index + 1, state.input_count) if state.current_input_index >= state.input_count: raise ValueError("Too many inputs") state.current_input_index += 1 # HMAC(T_in,i || vin_i) hmac_vini_comp = await offloading_keys.gen_hmac_vini( state.key_hmac, src_entr, vini_bin, state.source_permutation[state.current_input_index], ) if not crypto.ct_equals(hmac_vini_comp, vini_hmac): raise ValueError("HMAC is not correct") """ Incremental hasing of tx.vin[i] """ state.tx_prefix_hasher.buffer(vini_bin) # in monero version >= 8 pseudo outs were moved to a different place # bulletproofs imply version >= 8 if state.rct_type == RctType.Simple and state.rsig_type != RsigType.Bulletproof: _hash_vini_pseudo_out(state, pseudo_out, pseudo_out_hmac) return MoneroTransactionInputViniAck()
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 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, ): """ :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. :return: Generated signature MGs[i] """ await confirms.transaction_step(state, state.STEP_SIGN, state.current_input_index + 1) state.current_input_index += 1 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 = state.source_permutation[state.current_input_index] mods = utils.unimport_begin() # Check input's HMAC from apps.monero.signing import offloading_keys vini_hmac_comp = await 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") gc.collect() state.mem_trace(1, True) from apps.monero.xmr.crypto import chacha_poly pseudo_out_alpha = crypto.decodeint( chacha_poly.decrypt_pack( offloading_keys.enc_key_txin_alpha(state.key_enc, input_position), bytes(pseudo_out_alpha_enc), )) # Last pseud_out is recomputed so mask sums hold if state.is_det_mask() and input_position + 1 == state.input_count: # Recompute the lash alpha so the sum holds state.mem_trace("Correcting alpha") alpha_diff = crypto.sc_sub(state.sumout, state.sumpouts_alphas) crypto.sc_add_into(pseudo_out_alpha, pseudo_out_alpha, alpha_diff) pseudo_out_c = crypto.gen_commitment(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), "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.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.decodepoint(pseudo_out) state.mem_trace(2, True) # Spending secret spend_key = crypto.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) from apps.monero.xmr.serialize_messages.ct_keys import CtKey # Basic setup, sanity check index = src_entr.real_output input_secret_key = CtKey(dest=spend_key, mask=crypto.decodeint(src_entr.mask)) kLRki = None # for multisig: src_entr.multisig_kLRki # Private key correctness test utils.ensure( crypto.point_eq( crypto.decodepoint( src_entr.outputs[src_entr.real_output].key.dest), crypto.scalarmult_base(input_secret_key.dest), ), "Real source entry's destination does not equal spend key's", ) utils.ensure( crypto.point_eq( crypto.decodepoint( src_entr.outputs[src_entr.real_output].key.commitment), crypto.gen_commitment(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 mlsag mg_buffer = [] ring_pubkeys = [x.key for x in src_entr.outputs] del src_entr mlsag.generate_mlsag_simple( state.full_message, ring_pubkeys, input_secret_key, pseudo_out_alpha, pseudo_out_c, kLRki, index, mg_buffer, ) del (input_secret_key, pseudo_out_alpha, mlsag, ring_pubkeys) state.mem_trace(5, True) from trezor.messages.MoneroTransactionSignInputAck import ( MoneroTransactionSignInputAck, ) return MoneroTransactionSignInputAck( signature=mg_buffer, pseudo_out=crypto.encodepoint(pseudo_out_c))
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, ): """ :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. :return: Generated signature MGs[i] """ from apps.monero.signing import offloading_keys await confirms.transaction_step(state.ctx, state.STEP_SIGN, state.current_input_index + 1, state.input_count) state.current_input_index += 1 if state.current_input_index >= state.input_count: raise ValueError("Invalid inputs count") if state.rct_type == RctType.Simple and pseudo_out is None: raise ValueError("SimpleRCT requires pseudo_out but none provided") if state.rct_type == RctType.Simple and pseudo_out_alpha_enc is None: raise ValueError( "SimpleRCT requires pseudo_out's mask but none provided") if state.current_input_index >= 1 and not state.rct_type == RctType.Simple: raise ValueError("Two and more inputs must imply SimpleRCT") input_position = state.source_permutation[state.current_input_index] # Check input's HMAC vini_hmac_comp = await 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") gc.collect() state.mem_trace(1) if state.rct_type == RctType.Simple: # 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.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") gc.collect() state.mem_trace(2) from apps.monero.xmr.crypto import chacha_poly pseudo_out_alpha = crypto.decodeint( chacha_poly.decrypt_pack( offloading_keys.enc_key_txin_alpha(state.key_enc, input_position), bytes(pseudo_out_alpha_enc), )) pseudo_out_c = crypto.decodepoint(pseudo_out) # Spending secret from apps.monero.xmr.crypto import chacha_poly from apps.monero.xmr.serialize_messages.ct_keys import CtKey spend_key = crypto.decodeint( chacha_poly.decrypt_pack( offloading_keys.enc_key_spend(state.key_enc, input_position), bytes(spend_enc), )) gc.collect() state.mem_trace(3) # Basic setup, sanity check index = src_entr.real_output input_secret_key = CtKey(dest=spend_key, mask=crypto.decodeint(src_entr.mask)) kLRki = None # for multisig: src_entr.multisig_kLRki # Private key correctness test utils.ensure( crypto.point_eq( crypto.decodepoint( src_entr.outputs[src_entr.real_output].key.dest), crypto.scalarmult_base(input_secret_key.dest), ), "Real source entry's destination does not equal spend key's", ) utils.ensure( crypto.point_eq( crypto.decodepoint( src_entr.outputs[src_entr.real_output].key.commitment), crypto.gen_commitment(input_secret_key.mask, src_entr.amount), ), "Real source entry's mask does not equal spend key's", ) gc.collect() state.mem_trace(4) from apps.monero.xmr import mlsag if state.rct_type == RctType.Simple: ring_pubkeys = [x.key for x in src_entr.outputs] mg = mlsag.generate_mlsag_simple( state.full_message, ring_pubkeys, input_secret_key, pseudo_out_alpha, pseudo_out_c, kLRki, index, ) else: # Full RingCt, only one input txn_fee_key = crypto.scalarmult_h(state.fee) ring_pubkeys = [[x.key] for x in src_entr.outputs] mg = mlsag.generate_mlsag_full( state.full_message, ring_pubkeys, [input_secret_key], state.output_sk_masks, state.output_pk_commitments, kLRki, index, txn_fee_key, ) gc.collect() state.mem_trace(5) # Encode mgs = _recode_msg([mg]) gc.collect() state.mem_trace(6) from trezor.messages.MoneroTransactionSignInputAck import ( MoneroTransactionSignInputAck, ) return MoneroTransactionSignInputAck( signature=serialize.dump_msg_gc(mgs[0], preallocate=488))