def _compute_sec_keys(state: State, tsx_data: MoneroTransactionData) -> None: """ Generate master key H( H(TsxData || tx_priv) || rand ) """ from trezor import protobuf from apps.monero.xmr.keccak_hasher import get_keccak_writer writer = get_keccak_writer() writer.write(protobuf.dump_message_buffer(tsx_data)) writer.write(crypto_helpers.encodeint(state.tx_priv)) master_key = crypto_helpers.keccak_2hash( writer.get_digest() + crypto_helpers.encodeint(crypto.random_scalar())) state.key_hmac = crypto_helpers.keccak_2hash(b"hmac" + master_key) state.key_enc = crypto_helpers.keccak_2hash(b"enc" + master_key)
def _compute_tx_keys( state: State, dst_entr: MoneroTransactionDestinationEntry ) -> tuple[crypto.Point, crypto.Scalar, crypto.Point]: """Computes tx_out_key, amount_key""" if state.is_processing_offloaded: return None, None, None # no need to recompute # additional tx key if applicable additional_txkey_priv = _set_out_additional_keys(state, dst_entr) # derivation = a*R or r*A or s*C derivation = _set_out_derivation(state, dst_entr, additional_txkey_priv) # amount key = H_s(derivation || i) amount_key = crypto_helpers.derivation_to_scalar( derivation, state.current_output_index) # one-time destination address P = H_s(derivation || i)*G + B tx_out_key = crypto_helpers.derive_public_key( derivation, state.current_output_index, crypto_helpers.decodepoint(dst_entr.addr.spend_public_key), ) del (additional_txkey_priv, ) from apps.monero.xmr import monero mask = monero.commitment_mask(crypto_helpers.encodeint(amount_key)) state.output_masks.append(mask) return tx_out_key, amount_key, derivation
def _compute_tx_key(spend_key_private: crypto.Scalar, tx_prefix_hash: bytes) -> tuple[bytes, bytes, bytes]: salt = random.bytes(32) rand_mult_num = crypto.random_scalar() rand_mult = crypto_helpers.encodeint(rand_mult_num) tx_key = misc.compute_tx_key(spend_key_private, tx_prefix_hash, salt, rand_mult_num) return tx_key, salt, rand_mult
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 test_derivation_to_scalar(self): derivation = unhexlify( b"e720a09f2e3a0bbf4e4ba7ad93653bb296885510121f806acb2a5f9168fafa01" ) scalar = unhexlify( b"25d08763414c379aa9cf989cdcb3cadd36bd5193b500107d6bf5f921f18e470e" ) sc_int = crypto_helpers.derivation_to_scalar( crypto_helpers.decodepoint(derivation), 0) self.assertEqual(scalar, crypto_helpers.encodeint(sc_int))
def test_get_subaddress_secret_key(self): a = crypto_helpers.decodeint( unhexlify( b"4ce88c168e0f5f8d6524f712d5f8d7d83233b1e7a2a60b5aba5206cc0ea2bc08" )) m = monero.get_subaddress_secret_key(secret_key=a, major=0, minor=1) self.assertEqual( crypto_helpers.encodeint(m), unhexlify( b"b6ff4d689b95e3310efbf683850c075bcde46361923054e42ef30016b287ff0c" ), )
def test_sc_inversion(self): res = crypto.Scalar() inp = crypto_helpers.decodeint( unhexlify( b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a" )) crypto.sc_inv_into(res, inp) self.assertEqual( hexlify(crypto_helpers.encodeint(res)), b"bcf365a551e6358f3f281a6241d4a25eded60230b60a1d48c67b51a85e33d70e", )
async def get_watch_only( ctx: Context, msg: MoneroGetWatchKey, keychain: Keychain ) -> MoneroWatchKey: await paths.validate_path(ctx, keychain, msg.address_n) await layout.require_confirm_watchkey(ctx) creds = misc.get_creds(keychain, msg.address_n, msg.network_type) address = creds.address watch_key = crypto_helpers.encodeint(creds.view_key_private) return MoneroWatchKey(watch_key=watch_key, address=address.encode())
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 generate_monero_keys( seed: bytes, ) -> tuple[crypto.Scalar, crypto.Point, crypto.Scalar, crypto.Point]: """ Generates spend key / view key from the seed in the same manner as Monero code does. account.cpp: crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random). """ spend_sec, spend_pub = generate_keys(crypto_helpers.decodeint(seed)) hash = crypto.fast_hash_into(None, crypto_helpers.encodeint(spend_sec)) view_sec, view_pub = generate_keys(crypto_helpers.decodeint(hash)) return spend_sec, spend_pub, view_sec, view_pub
def final_msg(state: State) -> MoneroTransactionFinalAck: if state.last_step != state.STEP_SIGN: raise ValueError("Invalid state transition") if state.current_input_index != state.input_count - 1: raise ValueError("Invalid input count") tx_key, salt, rand_mult = _compute_tx_key(state.creds.spend_key_private, state.tx_prefix_hash) key_buff = crypto_helpers.encodeint(state.tx_priv) + b"".join([ crypto_helpers.encodeint(x) for x in state.additional_tx_private_keys ]) tx_enc_keys = chacha_poly.encrypt_pack(tx_key, key_buff) state.last_step = None return MoneroTransactionFinalAck( cout_key=None, salt=salt, rand_mult=rand_mult, tx_enc_keys=tx_enc_keys, opening_key=state.opening_key, )
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 _range_proof( state: State, rsig_data: MoneroTransactionRsigData ) -> tuple[MoneroTransactionRsigData, crypto.Scalar]: """ Computes rangeproof and handles range proof offloading logic. Since HF10 the commitments are deterministic. The range proof is incrementally hashed to the final_message. """ provided_rsig = None if rsig_data and rsig_data.rsig and len(rsig_data.rsig) > 0: provided_rsig = rsig_data.rsig if not state.rsig_offload and provided_rsig: raise signing.Error("Provided unexpected rsig") # Batching & validation bidx = _get_rsig_batch(state, state.current_output_index) last_in_batch = _is_last_in_batch(state, state.current_output_index, bidx) if state.rsig_offload and provided_rsig and not last_in_batch: raise signing.Error("Provided rsig too early") if (state.rsig_offload and last_in_batch and not provided_rsig and state.is_processing_offloaded): raise signing.Error("Rsig expected, not provided") # Batch not finished, skip range sig generation now mask = state.output_masks[-1] if not state.is_processing_offloaded else None offload_mask = mask and state.rsig_offload # If not last, do not proceed to the BP processing. if not last_in_batch: rsig_data_new = (_return_rsig_data( mask=crypto_helpers.encodeint(mask)) if offload_mask else None) return rsig_data_new, mask # Rangeproof # Pedersen commitment on the value, mask from the commitment, range signature. rsig = None state.mem_trace("pre-rproof" if __debug__ else None, collect=True) if not state.rsig_offload: # Bulletproof calculation in Trezor rsig = _rsig_bp(state) elif not state.is_processing_offloaded: # Bulletproof offloaded to the host, deterministic masks. Nothing here, waiting for offloaded BP. pass else: # Bulletproof offloaded to the host, check BP, hash it. _rsig_process_bp(state, rsig_data) state.mem_trace("rproof" if __debug__ else None, collect=True) # Construct new rsig data to send back to the host. rsig_data_new = _return_rsig_data( rsig, crypto_helpers.encodeint(mask) if offload_mask else None) if state.current_output_index + 1 == state.output_count and ( not state.rsig_offload or state.is_processing_offloaded): # output masks and amounts are not needed anymore state.output_amounts = None state.output_masks = None return rsig_data_new, mask
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 _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