def compute_subaddresses( creds: AccountCreds, account: int, indices: list[int], subaddresses: Subaddresses | None = None, ) -> Subaddresses: """ Computes subaddress public spend key for receiving transactions. :param creds: credentials :param account: major index :param indices: array of minor indices :param subaddresses: subaddress dict. optional. :return: """ if subaddresses is None: subaddresses = {} for idx in indices: if account == 0 and idx == 0: subaddresses[crypto_helpers.encodepoint( creds.spend_key_public)] = (0, 0) continue pub = get_subaddress_spend_public_key(creds.view_key_private, creds.spend_key_public, major=account, minor=idx) pub = crypto_helpers.encodepoint(pub) subaddresses[pub] = (account, idx) return subaddresses
def is_out_to_account( subaddresses: Subaddresses, out_key: crypto.Point, derivation: crypto.Point, additional_derivation: crypto.Point | None, output_index: int, creds: AccountCreds | None, sub_addr_major: int | None, sub_addr_minor: int | None, ) -> tuple[tuple[int, int], crypto.Point] | None: """ Checks whether the given transaction is sent to the account. Searches subaddresses for the computed subaddress_spendkey. Corresponds to is_out_to_acc_precomp() in the Monero codebase. If found, returns (major, minor), derivation, otherwise None. If (creds, sub_addr_major, sub_addr_minor) are specified, subaddress is checked directly (avoids the need to store large subaddresses dicts). """ subaddress_spendkey_obj = derive_subaddress_public_key( out_key, derivation, output_index) sub_pub_key = None if creds and sub_addr_major is not None and sub_addr_minor is not None: sub_pub_key = get_subaddress_spend_public_key( creds.view_key_private, creds.spend_key_public, sub_addr_major, sub_addr_minor, ) if crypto.point_eq(subaddress_spendkey_obj, sub_pub_key): return (sub_addr_major, sub_addr_minor), derivation if subaddresses: subaddress_spendkey = crypto_helpers.encodepoint( subaddress_spendkey_obj) if subaddress_spendkey in subaddresses: return subaddresses[subaddress_spendkey], derivation if additional_derivation: subaddress_spendkey_obj = derive_subaddress_public_key( out_key, additional_derivation, output_index) if sub_pub_key and crypto.point_eq(subaddress_spendkey_obj, sub_pub_key): # sub_pub_key is only set if sub_addr_{major, minor} are set assert sub_addr_major is not None and sub_addr_minor is not None return (sub_addr_major, sub_addr_minor), additional_derivation if subaddresses: subaddress_spendkey = crypto_helpers.encodepoint( subaddress_spendkey_obj) if subaddress_spendkey in subaddresses: return subaddresses[subaddress_spendkey], additional_derivation return None
def _get_primary_change_address(state: State) -> MoneroAccountPublicAddress: """ Computes primary change address for the current account index """ from trezor.messages import MoneroAccountPublicAddress D, C = monero.generate_sub_address_keys(state.creds.view_key_private, state.creds.spend_key_public, state.account_idx, 0) return MoneroAccountPublicAddress( view_public_key=crypto_helpers.encodepoint(C), spend_public_key=crypto_helpers.encodepoint(D), )
async def get_address(ctx: wire.Context, msg: MoneroGetAddress, keychain: Keychain) -> MoneroAddress: await paths.validate_path(ctx, keychain, msg.address_n) creds = misc.get_creds(keychain, msg.address_n, msg.network_type) addr = creds.address have_subaddress = msg.account is not None and msg.minor is not None have_payment_id = msg.payment_id is not None if (msg.account is None) != (msg.minor is None): raise wire.ProcessError("Invalid subaddress indexes") if have_payment_id and have_subaddress: raise wire.DataError("Subaddress cannot be integrated") if have_payment_id: assert msg.payment_id is not None if len(msg.payment_id) != 8: raise ValueError("Invalid payment ID length") addr = addresses.encode_addr( net_version(msg.network_type, False, True), crypto_helpers.encodepoint(creds.spend_key_public), crypto_helpers.encodepoint(creds.view_key_public), msg.payment_id, ) if have_subaddress: assert msg.account is not None assert msg.minor is not None pub_spend, pub_view = monero.generate_sub_address_keys( creds.view_key_private, creds.spend_key_public, msg.account, msg.minor) addr = addresses.encode_addr( net_version(msg.network_type, True, False), crypto_helpers.encodepoint(pub_spend), crypto_helpers.encodepoint(pub_view), ) if msg.show_display: title = paths.address_n_to_str(msg.address_n) await show_address( ctx, address=addr, address_qr="monero:" + addr, title=title, ) return MoneroAddress(address=addr.encode())
def test_encoding(self): point = unhexlify( b"2486224797d05cae3cba4be043be2db0df381f3f19cfa113f86ab38e3d8d2bd0" ) self.assertEqual( point, crypto_helpers.encodepoint(crypto_helpers.decodepoint(point))) self.assertTrue( crypto.point_eq( crypto_helpers.decodepoint(point), crypto_helpers.decodepoint( crypto_helpers.encodepoint( crypto_helpers.decodepoint(point))), ))
def generate_tx_spend_and_key_image( ack: AccountCreds, out_key: crypto.Point, recv_derivation: crypto.Point, real_output_index: int, received_index: tuple[int, int], ) -> tuple[crypto.Scalar, crypto.Point]: """ Generates UTXO spending key and key image. Corresponds to generate_key_image_helper_precomp() in the Monero codebase. :param ack: sender credentials :type ack: apps.monero.xmr.credentials.AccountCreds :param out_key: real output (from input RCT) destination key :param recv_derivation: :param real_output_index: :param received_index: subaddress index this payment was received to :return: """ if crypto.sc_iszero(ack.spend_key_private): raise ValueError("Watch-only wallet not supported") # derive secret key with subaddress - step 1: original CN derivation scalar_step1 = crypto_helpers.derive_secret_key(recv_derivation, real_output_index, ack.spend_key_private) # step 2: add Hs(SubAddr || a || index_major || index_minor) subaddr_sk = None if received_index == (0, 0): scalar_step2 = scalar_step1 else: subaddr_sk = get_subaddress_secret_key(ack.view_key_private, major=received_index[0], minor=received_index[1]) scalar_step2 = crypto.sc_add_into(None, scalar_step1, subaddr_sk) # When not in multisig, we know the full spend secret key, so the output pubkey can be obtained by scalarmultBase pub_ver = crypto.scalarmult_base_into(None, scalar_step2) # <Multisig>, branch deactivated until implemented # # When in multisig, we only know the partial spend secret key. But we do know the full spend public key, # # so the output pubkey can be obtained by using the standard CN key derivation. # pub_ver = crypto.derive_public_key( # recv_derivation, real_output_index, ack.spend_key_public # ) # # # Add the contribution from the subaddress part # if received_index != (0, 0): # subaddr_pk = crypto.scalarmult_base(subaddr_sk) # pub_ver = crypto.point_add(pub_ver, subaddr_pk) # </Multisig> if not crypto.point_eq(pub_ver, out_key): raise ValueError( "key image helper precomp: given output pubkey doesn't match the derived one" ) ki = generate_key_image(crypto_helpers.encodepoint(pub_ver), scalar_step2) return scalar_step2, ki
def _set_out_additional_keys( state: State, dst_entr: MoneroTransactionDestinationEntry) -> crypto.Scalar: """ If needed (decided in step 1), additional tx keys are calculated for this particular output. """ if not state.need_additional_txkeys: return None additional_txkey_priv = crypto.random_scalar() if dst_entr.is_subaddress: # R=r*D additional_txkey = crypto_helpers.decodepoint( dst_entr.addr.spend_public_key) crypto.scalarmult_into(additional_txkey, additional_txkey, additional_txkey_priv) else: # R=r*G additional_txkey = crypto.scalarmult_base_into(None, additional_txkey_priv) state.additional_tx_public_keys.append( crypto_helpers.encodepoint(additional_txkey)) state.additional_tx_private_keys.append(additional_txkey_priv) return additional_txkey_priv
def _export_key_image( creds: AccountCreds, subaddresses: Subaddresses, pkey: crypto.Point, tx_pub_key: crypto.Point, additional_tx_pub_key: crypto.Point | None, out_idx: int, test: bool = True, sub_addr_major: int | None = None, sub_addr_minor: int | None = None, ) -> tuple[crypto.Point, Sig]: """ Generates key image for the TXO + signature for the key image """ r = monero.generate_tx_spend_and_key_image_and_derivation( creds, subaddresses, pkey, tx_pub_key, additional_tx_pub_key, out_idx, sub_addr_major, sub_addr_minor, ) xi, ki, _ = r[:3] phash = crypto_helpers.encodepoint(ki) sig = generate_ring_signature(phash, ki, [pkey], xi, 0, test) return ki, sig
def test_clsag_invalid_P(self): res = self.gen_clsag_sig(ring_size=11, index=5) msg, scalars, sc1, sI, sD, ring2, Cp = res with self.assertRaises(ValueError): ring2[5].commitment = crypto_helpers.encodepoint( point_mul8_into(None, crypto_helpers.decodepoint(ring2[5].dest))) self.verify_clsag(msg, scalars, sc1, sI, sD, ring2, Cp)
def new_wallet( cls, priv_view_key: crypto.Scalar, priv_spend_key: crypto.Scalar, network_type: MoneroNetworkType = MoneroNetworkType.MAINNET, ) -> "AccountCreds": pub_view_key = crypto.scalarmult_base_into(None, priv_view_key) pub_spend_key = crypto.scalarmult_base_into(None, priv_spend_key) addr = encode_addr( net_version(network_type), crypto_helpers.encodepoint(pub_spend_key), crypto_helpers.encodepoint(pub_view_key), ) return cls( view_key_private=priv_view_key, spend_key_private=priv_spend_key, view_key_public=pub_view_key, spend_key_public=pub_spend_key, address=addr, network_type=network_type, )
def test_hash_to_point(self): data = unhexlify( b"42f6835bf83114a1f5f6076fe79bdfa0bd67c74b88f127d54572d3910dd09201" ) res = crypto.hash_to_point_into(None, data) res_p = crypto_helpers.encodepoint(res) self.assertEqual( res_p, unhexlify( b"54863a0464c008acc99cffb179bc6cf34eb1bbdf6c29f7a070a7c6376ae30ab5" ), )
def test_scalarmult_base(self): scalar = crypto_helpers.decodeint( unhexlify( b"a0eea49140a3b036da30eacf64bd9d56ce3ef68ba82ef13571ec511edbcf8303" )) exp = unhexlify( b"16bb4a3c44e2ced511fc0d4cd86b13b3af21efc99fb0356199fac489f2544c09" ) res = crypto.scalarmult_base_into(None, scalar) self.assertEqual(exp, crypto_helpers.encodepoint(res)) self.assertTrue(crypto.point_eq(crypto_helpers.decodepoint(exp), res)) scalar = crypto_helpers.decodeint( unhexlify( b"fd290dce39f781aebbdbd24584ed6d48bd300de19d9c3decfda0a6e2c6751d0f" )) exp = unhexlify( b"123daf90fc26f13c6529e6b49bfed498995ac383ef19c0db6771143f24ba8dd5" ) res = crypto.scalarmult_base_into(None, scalar) self.assertEqual(exp, crypto_helpers.encodepoint(res)) self.assertTrue(crypto.point_eq(crypto_helpers.decodepoint(exp), res))
def test_public_spend(self): derivation = unhexlify( b"e720a09f2e3a0bbf4e4ba7ad93653bb296885510121f806acb2a5f9168fafa01" ) base = unhexlify( b"7d996b0f2db6dbb5f2a086211f2399a4a7479b2c911af307fdc3f7f61a88cb0e" ) pkey_ex = unhexlify( b"0846cae7405077b6b7800f0b932c10a186448370b6db318f8c9e13f781dab546" ) pkey_comp = crypto_helpers.derive_public_key( crypto_helpers.decodepoint(derivation), 0, crypto_helpers.decodepoint(base)) self.assertEqual(pkey_ex, crypto_helpers.encodepoint(pkey_comp))
def test_scalarmult(self): priv = unhexlify( b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a" ) pub = unhexlify( b"2486224797d05cae3cba4be043be2db0df381f3f19cfa113f86ab38e3d8d2bd0" ) exp = unhexlify( b"adcd1f5881f46f254900a03c654e71950a88a0236fa0a3a946c9b8daed6ef43d" ) res = crypto.scalarmult_into(None, crypto_helpers.decodepoint(pub), crypto_helpers.decodeint(priv)) self.assertEqual(exp, crypto_helpers.encodepoint(res)) self.assertTrue(crypto.point_eq(crypto_helpers.decodepoint(exp), res))
def _get_ecdh_info_and_out_pk( state: State, tx_out_key: crypto.Point, amount: int, mask: crypto.Scalar, amount_key: crypto.Scalar, ) -> tuple[bytes, bytes, bytes]: """ Calculates the Pedersen commitment C = aG + bH and returns it as CtKey. Also encodes the two items - `mask` and `amount` - into ecdh info, so the recipient is able to reconstruct the commitment. """ out_pk_dest = crypto_helpers.encodepoint(tx_out_key) out_pk_commitment = crypto.gen_commitment_into(None, mask, amount) out_pk_commitment = crypto_helpers.encodepoint(out_pk_commitment) crypto.sc_add_into(state.sumout, state.sumout, mask) ecdh_info = _ecdh_encode(amount, crypto_helpers.encodeint(amount_key)) # Manual ECDH info serialization ecdh_info_bin = _serialize_ecdh(ecdh_info) gc.collect() return out_pk_dest, out_pk_commitment, ecdh_info_bin
def test_derive_subaddress_public_key(self): out_key = crypto_helpers.decodepoint( unhexlify( b"f4efc29da4ccd6bc6e81f52a6f47b2952966442a7efb49901cce06a7a3bef3e5" )) deriv = crypto_helpers.decodepoint( unhexlify( b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff64" )) res = crypto_helpers.encodepoint( monero.derive_subaddress_public_key(out_key, deriv, 5)) self.assertEqual( res, unhexlify( b"5a10cca900ee47a7f412cd661b29f5ab356d6a1951884593bb170b5ec8b6f2e8" ), )
def test_generate_key_derivation(self): key_pub = crypto_helpers.decodepoint( unhexlify( b"7739c95d3298e2f87362dba9e0e0b3980a692ae8e2f16796b0e382098cd6bd83" )) key_priv = crypto_helpers.decodeint( unhexlify( b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a" )) deriv_exp = unhexlify( b"fa188a45a0e4daccc0e6d4f6f6858fd46392104be74183ec0047e7e9f4eaf739" ) self.assertEqual( deriv_exp, crypto_helpers.encodepoint( crypto_helpers.generate_key_derivation(key_pub, key_priv)), )
async def _refresh_step( s: LiveRefreshState, ctx: Context, msg: MoneroLiveRefreshStepRequest) -> MoneroLiveRefreshStepAck: assert s.creds is not None buff = bytearray(32 * 3) buff_mv = memoryview(buff) await layout.live_refresh_step(ctx, s.current_output) s.current_output += 1 if __debug__: log.debug(__name__, "refresh, step i: %d", s.current_output) # Compute spending secret key and the key image # spend_priv = Hs(recv_deriv || real_out_idx) + spend_key_private # If subaddr: # spend_priv += Hs("SubAddr" || view_key_private || major || minor) # out_key = spend_priv * G, KI: spend_priv * Hp(out_key) out_key = crypto_helpers.decodepoint(msg.out_key) recv_deriv = crypto_helpers.decodepoint(msg.recv_deriv) received_index = msg.sub_addr_major, msg.sub_addr_minor spend_priv, ki = monero.generate_tx_spend_and_key_image( s.creds, out_key, recv_deriv, msg.real_out_idx, received_index) ki_enc = crypto_helpers.encodepoint(ki) sig = key_image.generate_ring_signature(ki_enc, ki, [out_key], spend_priv, 0, False) del spend_priv # spend_priv never leaves the device # Serialize into buff buff[0:32] = ki_enc crypto.encodeint_into(buff_mv[32:64], sig[0][0]) crypto.encodeint_into(buff_mv[64:], sig[0][1]) # Encrypt with view key private based key - so host can decrypt and verify HMAC enc_key, salt = misc.compute_enc_key_host(s.creds.view_key_private, msg.out_key) resp = chacha_poly.encrypt_pack(enc_key, buff) return MoneroLiveRefreshStepAck(salt=salt, key_image=resp)
def verify_bp( bp_proof: Bulletproof | BulletproofPlus, amounts: list[int], masks: list[crypto.Scalar], ) -> bool: """Verifies Bulletproof""" from apps.monero.xmr import bulletproof as bp if amounts: bp_proof.V = [] for i in range(len(amounts)): C = crypto.gen_commitment_into(None, masks[i], amounts[i]) crypto.scalarmult_into(C, C, crypto_helpers.INV_EIGHT_SC) bp_proof.V.append(crypto_helpers.encodepoint(C)) from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import BulletproofPlus bpi = (bp.BulletProofPlusBuilder() if isinstance(bp_proof, BulletproofPlus) else bp.BulletProofBuilder()) res = bpi.verify(bp_proof) gc.collect() return res
def _generate_clsag( message: bytes, P: list[bytes], p: crypto.Scalar, C_nonzero: list[bytes], z: crypto.Scalar, Cout: crypto.Point, index: int, mg_buff: list[bytearray], ) -> list[bytes]: sI = crypto.Point() # sig.I sD = crypto.Point() # sig.D sc1 = crypto.Scalar() # sig.c1 a = crypto.random_scalar() H = crypto.Point() D = crypto.Point() Cout_bf = crypto_helpers.encodepoint(Cout) tmp_sc = crypto.Scalar() tmp = crypto.Point() tmp_bf = bytearray(32) crypto.hash_to_point_into(H, P[index]) crypto.scalarmult_into(sI, H, p) # I = p*H crypto.scalarmult_into(D, H, z) # D = z*H crypto.sc_mul_into(tmp_sc, z, crypto_helpers.INV_EIGHT_SC) # 1/8*z crypto.scalarmult_into(sD, H, tmp_sc) # sig.D = 1/8*z*H sD = crypto_helpers.encodepoint(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(_HASH_KEY_CLSAG_AGG_0) hsh_C.update(_HASH_KEY_CLSAG_AGG_1) def hsh_PC(x): nonlocal hsh_P, hsh_C hsh_P.update(x) hsh_C.update(x) for x in P: hsh_PC(x) for x in C_nonzero: hsh_PC(x) hsh_PC(crypto.encodepoint_into(tmp_bf, sI)) hsh_PC(sD) hsh_PC(Cout_bf) mu_P = crypto_helpers.decodeint(hsh_P.digest()) mu_C = crypto_helpers.decodeint(hsh_C.digest()) del (hsh_PC, hsh_P, hsh_C) c_to_hash = crypto_helpers.get_keccak() # domain, P, C, C_offset, message, aG, aH c_to_hash.update(_HASH_KEY_CLSAG_ROUND) for i in range(len(P)): c_to_hash.update(P[i]) for i in range(len(P)): c_to_hash.update(C_nonzero[i]) c_to_hash.update(Cout_bf) c_to_hash.update(message) chasher = c_to_hash.copy() crypto.scalarmult_base_into(tmp, a) chasher.update(crypto.encodepoint_into(tmp_bf, tmp)) # aG crypto.scalarmult_into(tmp, H, a) chasher.update(crypto.encodepoint_into(tmp_bf, tmp)) # aH c = crypto_helpers.decodeint(chasher.digest()) del (chasher, H) L = crypto.Point() R = crypto.Point() c_p = crypto.Scalar() c_c = crypto.Scalar() i = (index + 1) % len(P) if i == 0: crypto.sc_copy(sc1, c) mg_buff.append(int_serialize.dump_uvarint_b(len(P))) for _ in range(len(P)): mg_buff.append(bytearray(32)) while i != index: crypto.random_scalar(tmp_sc) crypto.encodeint_into(mg_buff[i + 1], tmp_sc) crypto.sc_mul_into(c_p, mu_P, c) crypto.sc_mul_into(c_c, mu_C, c) # L = tmp_sc * G + c_P * P[i] + c_c * C[i] crypto.add_keys2_into(L, tmp_sc, c_p, crypto.decodepoint_into(tmp, P[i])) crypto.decodepoint_into(tmp, C_nonzero[i]) # C = C_nonzero - Cout crypto.point_sub_into(tmp, tmp, Cout) crypto.scalarmult_into(tmp, tmp, c_c) crypto.point_add_into(L, L, tmp) # R = tmp_sc * HP + c_p * I + c_c * D crypto.hash_to_point_into(tmp, P[i]) crypto.add_keys3_into(R, tmp_sc, tmp, c_p, sI) crypto.point_add_into(R, R, crypto.scalarmult_into(tmp, D, 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()) P[i] = None # type: ignore C_nonzero[i] = None # type: ignore i = (i + 1) % len(P) if i == 0: crypto.sc_copy(sc1, c) if i & 3 == 0: gc.collect() # Final scalar = a - c * (mu_P * p + mu_c * Z) crypto.sc_mul_into(tmp_sc, mu_P, p) crypto.sc_muladd_into(tmp_sc, mu_C, z, tmp_sc) crypto.sc_mulsub_into(tmp_sc, c, tmp_sc, a) crypto.encodeint_into(mg_buff[index + 1], tmp_sc) if TYPE_CHECKING: assert list_of_type(mg_buff, bytes) mg_buff.append(crypto_helpers.encodeint(sc1)) mg_buff.append(sD) return mg_buff
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, )
def gen_clsag_sig(self, ring_size=11, index=None): msg = random.bytes(32) amnt = crypto.Scalar(random.uniform(0xFFFFFF) + 12) priv = crypto.random_scalar() msk = crypto.random_scalar() alpha = crypto.random_scalar() P = crypto.scalarmult_base_into(None, priv) C = crypto.add_keys2_into(None, msk, amnt, crypto.xmr_H()) Cp = crypto.add_keys2_into(None, alpha, amnt, crypto.xmr_H()) ring = [] for i in range(ring_size - 1): tk = TmpKey( crypto_helpers.encodepoint( crypto.scalarmult_base_into(None, crypto.random_scalar())), crypto_helpers.encodepoint( crypto.scalarmult_base_into(None, crypto.random_scalar())), ) ring.append(tk) index = index if index is not None else random.uniform(len(ring)) ring.insert( index, TmpKey(crypto_helpers.encodepoint(P), crypto_helpers.encodepoint(C))) ring2 = list(ring) mg_buffer = [] self.assertTrue( crypto.point_eq( crypto.scalarmult_base_into(None, priv), crypto_helpers.decodepoint(ring[index].dest), )) self.assertTrue( crypto.point_eq( crypto.scalarmult_base_into( None, crypto.sc_sub_into(None, msk, alpha)), crypto.point_sub_into( None, crypto_helpers.decodepoint(ring[index].commitment), Cp), )) clsag.generate_clsag_simple( msg, ring, CtKey(priv, msk), alpha, Cp, index, mg_buffer, ) sD = crypto_helpers.decodepoint(mg_buffer[-1]) sc1 = crypto_helpers.decodeint(mg_buffer[-2]) scalars = [crypto_helpers.decodeint(x) for x in mg_buffer[1:-2]] H = crypto.Point() sI = crypto.Point() crypto.hash_to_point_into(H, crypto_helpers.encodepoint(P)) crypto.scalarmult_into(sI, H, priv) # I = p*H return msg, scalars, sc1, sI, sD, ring2, Cp
def test_h(self): H = unhexlify( b"8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94" ) self.assertEqual(crypto_helpers.encodepoint(crypto.xmr_H()), H)
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")
def generate_ring_signature( prefix_hash: bytes, image: crypto.Point, pubs: list[crypto.Point], sec: crypto.Scalar, sec_idx: int, test: bool = False, ) -> Sig: """ Generates ring signature with key image. void crypto_ops::generate_ring_signature() """ from trezor.utils import memcpy if test: t = crypto.scalarmult_base_into(None, sec) if not crypto.point_eq(t, pubs[sec_idx]): raise ValueError("Invalid sec key") k_i = monero.generate_key_image( crypto_helpers.encodepoint(pubs[sec_idx]), sec) if not crypto.point_eq(k_i, image): raise ValueError("Key image invalid") for k in pubs: crypto.ge25519_check(k) buff_off = len(prefix_hash) buff = bytearray(buff_off + 2 * 32 * len(pubs)) memcpy(buff, 0, prefix_hash, 0, buff_off) mvbuff = memoryview(buff) sum = crypto.Scalar(0) k = crypto.Scalar(0) sig = [] for _ in range(len(pubs)): sig.append([crypto.Scalar(0), crypto.Scalar(0)]) # c, r for i in range(len(pubs)): if i == sec_idx: k = crypto.random_scalar() tmp3 = crypto.scalarmult_base_into(None, k) crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp3) buff_off += 32 tmp3 = crypto.hash_to_point_into( None, crypto_helpers.encodepoint(pubs[i])) tmp2 = crypto.scalarmult_into(None, tmp3, k) crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp2) buff_off += 32 else: sig[i] = [crypto.random_scalar(), crypto.random_scalar()] tmp3 = pubs[i] tmp2 = crypto.ge25519_double_scalarmult_vartime_into( None, tmp3, sig[i][0], sig[i][1]) crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp2) buff_off += 32 tmp3 = crypto.hash_to_point_into(None, crypto_helpers.encodepoint(tmp3)) tmp2 = crypto.add_keys3_into(None, sig[i][1], tmp3, sig[i][0], image) crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp2) buff_off += 32 crypto.sc_add_into(sum, sum, sig[i][0]) h = crypto.hash_to_scalar_into(None, buff) sig[sec_idx][0] = crypto.sc_sub_into(None, h, sum) sig[sec_idx][1] = crypto.sc_mulsub_into(None, sig[sec_idx][0], sec, k) return sig