def sc_add(dst, a, b): dst = _ensure_dst_key(dst) crypto.decodeint_into_noreduce(tmp_sc_1, a) crypto.decodeint_into_noreduce(tmp_sc_2, b) crypto.sc_add_into(tmp_sc_3, tmp_sc_1, tmp_sc_2) crypto.encodeint_into(dst, tmp_sc_3) return dst
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 _get_ecdh_info_and_out_pk( state: State, tx_out_key: Ge25519, amount: int, mask: Sc25519, amount_key: Sc25519) -> 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.encodepoint(tx_out_key) out_pk_commitment = crypto.encodepoint(crypto.gen_commitment(mask, amount)) crypto.sc_add_into(state.sumout, state.sumout, mask) ecdh_info = _ecdh_encode(amount, crypto.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
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
def _get_ecdh_info_and_out_pk(state: State, tx_out_key, amount, mask, amount_key): """ 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.encodepoint(tx_out_key) out_pk_commitment = crypto.encodepoint(crypto.gen_commitment(mask, amount)) crypto.sc_add_into(state.sumout, state.sumout, mask) # masking of mask and amount ecdh_info = _ecdh_encode(mask, amount, crypto.encodeint(amount_key), state.is_bulletproof_v2()) # Manual ECDH info serialization ecdh_info_bin = _serialize_ecdh(ecdh_info, state.is_bulletproof_v2()) gc.collect() return out_pk_dest, out_pk_commitment, ecdh_info_bin
def scalar_fold(v, a, b, into=None, into_offset=0): """ ln = len(v); h = ln // 2 v[i] = v[i] * a + v[h+i] * b) """ h = len(v) // 2 crypto.decodeint_into_noreduce(tmp_sc_1, a) crypto.decodeint_into_noreduce(tmp_sc_2, b) into = into if into else v for i in range(h): crypto.decodeint_into_noreduce(tmp_sc_3, v.to(i)) crypto.decodeint_into_noreduce(tmp_sc_4, v.to(h + i)) crypto.sc_mul_into(tmp_sc_3, tmp_sc_3, tmp_sc_1) crypto.sc_mul_into(tmp_sc_4, tmp_sc_4, tmp_sc_2) crypto.sc_add_into(tmp_sc_3, tmp_sc_3, tmp_sc_4) crypto.encodeint_into(tmp_bf_0, tmp_sc_3) into.read(i + into_offset, tmp_bf_0) gc_iter(i) return into
def cross_inner_product(l0, r0, l1, r1): """ t1_1 = l0 . r1, t1_2 = l1 . r0 t1 = t1_1 + t1_2, t2 = l1 . r1 """ sc_t1_1, sc_t1_2, sc_t2 = alloc_scalars(3) cl0, cr0, cl1, cr1 = alloc_scalars(4) for i in range(len(l0)): crypto.decodeint_into_noreduce(cl0, l0.to(i)) crypto.decodeint_into_noreduce(cr0, r0.to(i)) crypto.decodeint_into_noreduce(cl1, l1.to(i)) crypto.decodeint_into_noreduce(cr1, r1.to(i)) crypto.sc_muladd_into(sc_t1_1, cl0, cr1, sc_t1_1) crypto.sc_muladd_into(sc_t1_2, cl1, cr0, sc_t1_2) crypto.sc_muladd_into(sc_t2, cl1, cr1, sc_t2) gc_iter(i) crypto.sc_add_into(sc_t1_1, sc_t1_1, sc_t1_2) return crypto.encodeint(sc_t1_1), crypto.encodeint(sc_t2)
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
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
def _ecdh_encode(mask, amount, amount_key, v2=False): """ Output recipients need be able to reconstruct the amount commitments. This means the blinding factor `mask` and `amount` must be communicated to the receiver somehow. The mask and amount are stored as: - mask = mask + Hs(amount_key) - amount = amount + Hs(Hs(amount_key)) Because the receiver can derive the `amount_key` they can easily derive both mask and amount as well. """ from apps.monero.xmr.serialize_messages.tx_ecdh import EcdhTuple ecdh_info = EcdhTuple(mask=mask, amount=crypto.sc_init(amount)) if v2: amnt = ecdh_info.amount ecdh_info.mask = crypto.NULL_KEY_ENC ecdh_info.amount = bytearray(32) crypto.encodeint_into(ecdh_info.amount, amnt) crypto.xor8(ecdh_info.amount, _ecdh_hash(amount_key)) return ecdh_info else: amount_key_hash_single = crypto.hash_to_scalar(amount_key) amount_key_hash_double = crypto.hash_to_scalar( crypto.encodeint(amount_key_hash_single)) # Not modifying passed mask, is reused in BP. ecdh_info.mask = crypto.sc_add(ecdh_info.mask, amount_key_hash_single) crypto.sc_add_into(ecdh_info.amount, ecdh_info.amount, amount_key_hash_double) ecdh_info.mask = crypto.encodeint(ecdh_info.mask) ecdh_info.amount = crypto.encodeint(ecdh_info.amount) return ecdh_info
def _gen_commitment(state: State, in_amount: int) -> tuple[crypto.Scalar, crypto.Point]: """ Computes Pedersen commitment - pseudo outs Here is slight deviation from the original protocol. We want that \\sum Alpha = \\sum A_{i,j} where A_{i,j} is a mask from range proof for output i, bit j. Previously this was computed in such a way that Alpha_{last} = \\sum A{i,j} - \\sum_{i=0}^{last-1} Alpha But we would prefer to compute commitment before range proofs so alphas are generated completely randomly and the last A mask is computed in this special way. Returns pseudo_out """ alpha = crypto.random_scalar() state.sumpouts_alphas = crypto.sc_add_into(None, state.sumpouts_alphas, alpha) return alpha, crypto.gen_commitment_into(None, alpha, in_amount)
def _prove_batch_main( self, V, gamma, aL, aR, hash_cache, logM, logN, M, N, proof_v8=False ): logMN = logM + logN MN = M * N hash_vct_to_scalar(hash_cache, V) # Extended precomputed GiHi Gprec = self._gprec_aux(MN) Hprec = self._hprec_aux(MN) # PAPER LINES 38-39 alpha = sc_gen() ve = _ensure_dst_key() A = _ensure_dst_key() vector_exponent_custom(Gprec, Hprec, aL, aR, ve) add_keys(A, ve, scalarmult_base(tmp_bf_1, alpha)) if not proof_v8: scalarmult_key(A, A, _INV_EIGHT) self.gc(11) # PAPER LINES 40-42 sL = self.sL_vct(MN) sR = self.sR_vct(MN) rho = sc_gen() vector_exponent_custom(Gprec, Hprec, sL, sR, ve) S = _ensure_dst_key() add_keys(S, ve, scalarmult_base(tmp_bf_1, rho)) if not proof_v8: scalarmult_key(S, S, _INV_EIGHT) del ve self.gc(12) # PAPER LINES 43-45 y = _ensure_dst_key() hash_cache_mash(y, hash_cache, A, S) if y == _ZERO: return (0,) z = _ensure_dst_key() hash_to_scalar(hash_cache, y) copy_key(z, hash_cache) if z == _ZERO: return (0,) # Polynomial construction by coefficients zMN = const_vector(z, MN) l0 = _ensure_dst_keyvect(None, MN) vector_subtract(aL, zMN, l0) l1 = sL self.gc(13) # This computes the ugly sum/concatenation from PAPER LINE 65 # r0 = aR + z r0 = vector_add(aR, zMN) del zMN self.gc(14) # r0 = r0 \odot yMN => r0[i] = r0[i] * y^i # r1 = sR \odot yMN => r1[i] = sR[i] * y^i yMN = vector_powers(y, MN, dynamic=False) hadamard(r0, yMN, dst=r0) self.gc(15) # r0 = r0 + zero_twos zpow = vector_powers(z, M + 2) twoN = self._two_aux(MN) zero_twos = vector_z_two(N, logN, M, zpow, twoN, dynamic=True, raw=True) vector_gen( r0, len(r0), lambda i, d: crypto.encodeint_into( d, crypto.sc_add_into( tmp_sc_1, zero_twos[i], # noqa: F821 crypto.decodeint_into_noreduce(tmp_sc_2, r0.to(i)), # noqa: F821 ), ), ) del (zero_twos, twoN) self.gc(15) # Polynomial construction before PAPER LINE 46 # r1 = KeyVEval(MN, lambda i, d: sc_mul(d, yMN[i], sR[i])) # r1 optimization possible, but has clashing sc registers. # Moreover, max memory complexity is 4MN as below (while loop). r1 = hadamard(yMN, sR, yMN) # re-use yMN vector for r1 del (yMN, sR) self.gc(16) # Inner products # l0 = aL - z r0 = ((aR + z) \cdot ypow) + zt # l1 = sL r1 = sR \cdot ypow # t1_1 = l0 . r1, t1_2 = l1 . r0 # t1 = t1_1 + t1_2, t2 = l1 . r1 # l = l0 \odot x*l1 r = r0 \odot x*r1 t1, t2 = cross_inner_product(l0, r0, l1, r1) self.gc(17) # PAPER LINES 47-48 tau1, tau2 = sc_gen(), sc_gen() T1, T2 = _ensure_dst_key(), _ensure_dst_key() add_keys(T1, scalarmultH(tmp_bf_1, t1), scalarmult_base(tmp_bf_2, tau1)) if not proof_v8: scalarmult_key(T1, T1, _INV_EIGHT) add_keys(T2, scalarmultH(tmp_bf_1, t2), scalarmult_base(tmp_bf_2, tau2)) if not proof_v8: scalarmult_key(T2, T2, _INV_EIGHT) del (t1, t2) self.gc(17) # PAPER LINES 49-51 x = _ensure_dst_key() hash_cache_mash(x, hash_cache, z, T1, T2) if x == _ZERO: return (0,) # PAPER LINES 52-53 taux = _ensure_dst_key() copy_key(taux, _ZERO) sc_mul(taux, tau1, x) xsq = _ensure_dst_key() sc_mul(xsq, x, x) sc_muladd(taux, tau2, xsq, taux) del (xsq, tau1, tau2) for j in range(1, len(V) + 1): sc_muladd(taux, zpow.to(j + 1), gamma[j - 1], taux) del zpow self.gc(18) mu = _ensure_dst_key() sc_muladd(mu, x, rho, alpha) del (rho, alpha) # PAPER LINES 54-57 # l = l0 \odot x*l1, has to evaluated as it becomes aprime in the loop l = vector_gen( l0, len(l0), lambda i, d: sc_add(d, d, sc_mul(tmp_bf_1, l1.to(i), x)), # noqa: F821 ) del (l0, l1, sL) self.gc(19) # r = r0 \odot x*r1, has to evaluated as it becomes bprime in the loop r = vector_gen( r0, len(r0), lambda i, d: sc_add(d, d, sc_mul(tmp_bf_1, r1.to(i), x)), # noqa: F821 ) t = inner_product(l, r) del (r1, r0) self.gc(19) # PAPER LINES 32-33 x_ip = hash_cache_mash(None, hash_cache, x, taux, mu, t) if x_ip == _ZERO: return 0, None # PHASE 2 # These are used in the inner product rounds nprime = MN Gprime = _ensure_dst_keyvect(None, MN) Hprime = _ensure_dst_keyvect(None, MN) aprime = l bprime = r yinv = invert(None, y) yinvpow = init_key(_ONE) self.gc(20) for i in range(0, MN): Gprime.read(i, Gprec.to(i)) scalarmult_key(tmp_bf_0, Hprec.to(i), yinvpow) Hprime.read(i, tmp_bf_0) sc_mul(yinvpow, yinvpow, yinv) gc_iter(i) self.gc(21) L = _ensure_dst_keyvect(None, logMN) R = _ensure_dst_keyvect(None, logMN) cL = _ensure_dst_key() cR = _ensure_dst_key() winv = _ensure_dst_key() w_round = _ensure_dst_key() tmp = _ensure_dst_key() round = 0 _tmp_k_1 = _ensure_dst_key() # PAPER LINE 13 while nprime > 1: # PAPER LINE 15 npr2 = nprime nprime >>= 1 self.gc(22) # PAPER LINES 16-17 inner_product( aprime.slice_view(0, nprime), bprime.slice_view(nprime, npr2), cL ) inner_product( aprime.slice_view(nprime, npr2), bprime.slice_view(0, nprime), cR ) self.gc(23) # PAPER LINES 18-19 vector_exponent_custom( Gprime.slice_view(nprime, npr2), Hprime.slice_view(0, nprime), aprime.slice_view(0, nprime), bprime.slice_view(nprime, npr2), tmp_bf_0, ) sc_mul(tmp, cL, x_ip) add_keys(tmp_bf_0, tmp_bf_0, scalarmultH(_tmp_k_1, tmp)) if not proof_v8: scalarmult_key(tmp_bf_0, tmp_bf_0, _INV_EIGHT) L.read(round, tmp_bf_0) self.gc(24) vector_exponent_custom( Gprime.slice_view(0, nprime), Hprime.slice_view(nprime, npr2), aprime.slice_view(nprime, npr2), bprime.slice_view(0, nprime), tmp_bf_0, ) sc_mul(tmp, cR, x_ip) add_keys(tmp_bf_0, tmp_bf_0, scalarmultH(_tmp_k_1, tmp)) if not proof_v8: scalarmult_key(tmp_bf_0, tmp_bf_0, _INV_EIGHT) R.read(round, tmp_bf_0) self.gc(25) # PAPER LINES 21-22 hash_cache_mash(w_round, hash_cache, L.to(round), R.to(round)) if w_round == _ZERO: return (0,) # PAPER LINES 24-25 invert(winv, w_round) self.gc(26) hadamard_fold(Gprime, winv, w_round) self.gc(27) hadamard_fold(Hprime, w_round, winv, Gprime, nprime) Hprime.realloc_init_from(nprime, Gprime, nprime, round < 2) self.gc(28) # PAPER LINES 28-29 scalar_fold(aprime, w_round, winv, Gprime, nprime) aprime.realloc_init_from(nprime, Gprime, nprime, round < 2) self.gc(29) scalar_fold(bprime, winv, w_round, Gprime, nprime) bprime.realloc_init_from(nprime, Gprime, nprime, round < 2) self.gc(30) # Finally resize Gprime which was buffer for all ops Gprime.resize(nprime, realloc=True) round += 1 from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import Bulletproof return ( 1, Bulletproof( V=V, A=A, S=S, T1=T1, T2=T2, taux=taux, mu=mu, L=L, R=R, a=aprime.to(0), b=bprime.to(0), t=t, ), )
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 aR1_fnc(i, d): crypto.sc_add_into(aR1_sc1, aR.to(i), zc) crypto.sc_muladd_into(aR1_sc1, d_vct[i], ypow_back[MN - i], aR1_sc1) return crypto.encodeint_into(d, aR1_sc1)
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))
def prove_range_borromean(amount, last_mask): """Calculates Borromean range proof""" # The large chunks allocated first to avoid potential memory fragmentation issues. ai = bytearray(32 * 64) alphai = bytearray(32 * 64) Cis = bytearray(32 * 64) s0s = bytearray(32 * 64) s1s = bytearray(32 * 64) buff = bytearray(32) ee_bin = bytearray(32) a = crypto.sc_init(0) si = crypto.sc_init(0) c = crypto.sc_init(0) ee = crypto.sc_init(0) tmp_ai = crypto.sc_init(0) tmp_alpha = crypto.sc_init(0) C_acc = crypto.identity() C_h = crypto.xmr_H() C_tmp = crypto.identity() L = crypto.identity() kck = crypto.get_keccak() for ii in range(64): crypto.random_scalar(tmp_ai) if last_mask is not None and ii == 63: crypto.sc_sub_into(tmp_ai, last_mask, a) crypto.sc_add_into(a, a, tmp_ai) crypto.random_scalar(tmp_alpha) crypto.scalarmult_base_into(L, tmp_alpha) crypto.scalarmult_base_into(C_tmp, tmp_ai) # if 0: C_tmp += Zero (nothing is added) # if 1: C_tmp += 2^i*H # 2^i*H is already stored in C_h if (amount >> ii) & 1 == 1: crypto.point_add_into(C_tmp, C_tmp, C_h) crypto.point_add_into(C_acc, C_acc, C_tmp) # Set Ci[ii] to sigs crypto.encodepoint_into(Cis, C_tmp, ii << 5) crypto.encodeint_into(ai, tmp_ai, ii << 5) crypto.encodeint_into(alphai, tmp_alpha, ii << 5) if ((amount >> ii) & 1) == 0: crypto.random_scalar(si) crypto.encodepoint_into(buff, L) crypto.hash_to_scalar_into(c, buff) crypto.point_sub_into(C_tmp, C_tmp, C_h) crypto.add_keys2_into(L, si, c, C_tmp) crypto.encodeint_into(s1s, si, ii << 5) crypto.encodepoint_into(buff, L) kck.update(buff) crypto.point_double_into(C_h, C_h) # Compute ee tmp_ee = kck.digest() crypto.decodeint_into(ee, tmp_ee) del (tmp_ee, kck) C_h = crypto.xmr_H() gc.collect() # Second pass, s0, s1 for ii in range(64): crypto.decodeint_into(tmp_alpha, alphai, ii << 5) crypto.decodeint_into(tmp_ai, ai, ii << 5) if ((amount >> ii) & 1) == 0: crypto.sc_mulsub_into(si, tmp_ai, ee, tmp_alpha) crypto.encodeint_into(s0s, si, ii << 5) else: crypto.random_scalar(si) crypto.encodeint_into(s0s, si, ii << 5) crypto.decodepoint_into(C_tmp, Cis, ii << 5) crypto.add_keys2_into(L, si, ee, C_tmp) crypto.encodepoint_into(buff, L) crypto.hash_to_scalar_into(c, buff) crypto.sc_mulsub_into(si, tmp_ai, c, tmp_alpha) crypto.encodeint_into(s1s, si, ii << 5) crypto.point_double_into(C_h, C_h) crypto.encodeint_into(ee_bin, ee) del (ai, alphai, buff, tmp_ai, tmp_alpha, si, c, ee, C_tmp, C_h, L) gc.collect() return C_acc, a, [s0s, s1s, ee_bin, Cis]
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