def _validate(state: State): from apps.monero.signing import RctType if state.current_output_index + 1 != state.output_count: raise ValueError("Invalid out num") # Test if \sum Alpha == \sum A if state.rct_type == RctType.Simple: utils.ensure(crypto.sc_eq(state.sumout, state.sumpouts_alphas)) # Fee test if state.fee != (state.summary_inputs_money - state.summary_outs_money): raise ValueError( "Fee invalid %s vs %s, out: %s" % ( state.fee, state.summary_inputs_money - state.summary_outs_money, state.summary_outs_money, ) ) if state.summary_outs_money > state.summary_inputs_money: raise ValueError( "Transaction inputs money (%s) less than outputs money (%s)" % (state.summary_inputs_money, state.summary_outs_money) )
def test_hash_to_scalar(self): inp = unhexlify( b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff6405" ) res = crypto.hash_to_scalar(inp) exp = crypto.decodeint( unhexlify( b"9907925b254e12162609fc0dfd0fef2aa4d605b0d10e6507cac253dd31a3ec06" )) self.assertTrue(crypto.sc_eq(res, exp))
async def all_inputs_set(state: State): state.mem_trace(0) await confirms.transaction_step(state.ctx, state.STEP_ALL_IN) from trezor.messages.MoneroTransactionAllInputsSetAck import ( MoneroTransactionAllInputsSetAck, ) from trezor.messages.MoneroTransactionRsigData import MoneroTransactionRsigData # Generate random commitment masks to be used in range proofs. # If SimpleRCT is used the sum of the masks must match the input masks sum. state.sumout = crypto.sc_init(0) for i in range(state.output_count): cur_mask = crypto.new_scalar() # new mask for each output is_last = i + 1 == state.output_count if is_last and state.rct_type == RctType.Simple: # in SimpleRCT the last mask needs to be calculated as an offset of the sum crypto.sc_sub_into(cur_mask, state.sumpouts_alphas, state.sumout) else: crypto.random_scalar(cur_mask) crypto.sc_add_into(state.sumout, state.sumout, cur_mask) state.output_masks.append(cur_mask) if state.rct_type == RctType.Simple: utils.ensure(crypto.sc_eq(state.sumout, state.sumpouts_alphas), "Invalid masks sum") # sum check state.sumout = crypto.sc_init(0) rsig_data = MoneroTransactionRsigData() resp = MoneroTransactionAllInputsSetAck(rsig_data=rsig_data) # If range proofs are being offloaded, we send the masks to the host, which uses them # to create the range proof. If not, we do not send any and we use them in the following step. if state.rsig_offload: tmp_buff = bytearray(32) rsig_data.mask = bytearray(32 * state.output_count) for i in range(state.output_count): crypto.encodeint_into(tmp_buff, state.output_masks[i]) utils.memcpy(rsig_data.mask, 32 * i, tmp_buff, 0, 32) return resp
async def _compute_masks(state: State): """ Output masks computed in advance. Used with client_version=0 && HF9. After HF10 (included) masks are deterministic, computed from the amount_key. After all client update to v1 this code will be removed. In order to preserve client_version=0 compatibility the masks have to be adjusted. """ from trezor.messages.MoneroTransactionRsigData import MoneroTransactionRsigData from apps.monero.signing import offloading_keys rsig_data = MoneroTransactionRsigData() # If range proofs are being offloaded, we send the masks to the host, which uses them # to create the range proof. If not, we do not send any and we use them in the following step. if state.rsig_offload: rsig_data.mask = [] # Deterministic masks, the last one is computed to balance the sums for i in range(state.output_count): if i + 1 == state.output_count: cur_mask = crypto.sc_sub(state.sumpouts_alphas, state.sumout) state.output_last_mask = cur_mask else: cur_mask = offloading_keys.det_comm_masks(state.key_enc, i) crypto.sc_add_into(state.sumout, state.sumout, cur_mask) if state.rsig_offload: rsig_data.mask.append(crypto.encodeint(cur_mask)) if not crypto.sc_eq(state.sumpouts_alphas, state.sumout): raise ValueError("Sum eq error") state.sumout = crypto.sc_init(0) return rsig_data
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))
def verify_clsag(self, msg, ss, sc1, sI, sD, pubs, C_offset): n = len(pubs) c = crypto.Scalar() D_8 = crypto.Point() tmp_bf = bytearray(32) C_offset_bf = crypto_helpers.encodepoint(C_offset) crypto.sc_copy(c, sc1) point_mul8_into(D_8, sD) hsh_P = crypto_helpers.get_keccak() # domain, I, D, P, C, C_offset hsh_C = crypto_helpers.get_keccak() # domain, I, D, P, C, C_offset hsh_P.update(clsag._HASH_KEY_CLSAG_AGG_0) hsh_C.update(clsag._HASH_KEY_CLSAG_AGG_1) def hsh_PC(x): hsh_P.update(x) hsh_C.update(x) for x in pubs: hsh_PC(x.dest) for x in pubs: hsh_PC(x.commitment) hsh_PC(crypto.encodepoint_into(tmp_bf, sI)) hsh_PC(crypto.encodepoint_into(tmp_bf, sD)) hsh_PC(C_offset_bf) mu_P = crypto_helpers.decodeint(hsh_P.digest()) mu_C = crypto_helpers.decodeint(hsh_C.digest()) c_to_hash = crypto_helpers.get_keccak( ) # domain, P, C, C_offset, message, L, R c_to_hash.update(clsag._HASH_KEY_CLSAG_ROUND) for i in range(len(pubs)): c_to_hash.update(pubs[i].dest) for i in range(len(pubs)): c_to_hash.update(pubs[i].commitment) c_to_hash.update(C_offset_bf) c_to_hash.update(msg) c_p = crypto.Scalar() c_c = crypto.Scalar() L = crypto.Point() R = crypto.Point() tmp_pt = crypto.Point() i = 0 while i < n: crypto.sc_mul_into(c_p, mu_P, c) crypto.sc_mul_into(c_c, mu_C, c) C_P = crypto.point_sub_into( None, crypto.decodepoint_into(tmp_pt, pubs[i].commitment), C_offset) crypto.add_keys2_into( L, ss[i], c_p, crypto.decodepoint_into(tmp_pt, pubs[i].dest)) crypto.point_add_into(L, L, crypto.scalarmult_into(tmp_pt, C_P, c_c)) HP = crypto.hash_to_point_into(None, pubs[i].dest) crypto.add_keys3_into(R, ss[i], HP, c_p, sI) crypto.point_add_into(R, R, crypto.scalarmult_into(tmp_pt, D_8, c_c)) chasher = c_to_hash.copy() chasher.update(crypto.encodepoint_into(tmp_bf, L)) chasher.update(crypto.encodepoint_into(tmp_bf, R)) crypto.decodeint_into(c, chasher.digest()) i += 1 res = crypto.sc_sub_into(None, c, sc1) if not crypto.sc_eq(res, crypto.Scalar(0)): raise ValueError("Signature error")
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))