async def get_address(ctx, msg, keychain): await paths.validate_path(ctx, misc.validate_full_path, keychain, msg.address_n, CURVE) creds = misc.get_creds(keychain, msg.address_n, msg.network_type) addr = creds.address if msg.payment_id: if len(msg.payment_id) != 8: raise ValueError("Invalid payment ID length") addr = addresses.encode_addr( net_version(msg.network_type, False, True), crypto.encodepoint(creds.spend_key_public), crypto.encodepoint(creds.view_key_public), msg.payment_id, ) if msg.account or msg.minor: if msg.payment_id: raise ValueError("Subaddress cannot be integrated") 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.encodepoint(pub_spend), crypto.encodepoint(pub_view), ) if msg.show_display: desc = address_n_to_str(msg.address_n) while True: if await confirms.show_address(ctx, addr.decode(), desc=desc): break if await show_qr(ctx, "monero:" + addr.decode(), desc=desc): break return MoneroAddress(address=addr)
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)) state.sumout = crypto.sc_add(state.sumout, mask) state.output_sk_masks.append(mask) # masking of mask and amount ecdh_info = _ecdh_encode(mask, amount, crypto.encodeint(amount_key)) # Manual ECDH info serialization ecdh_info_bin = bytearray(64) utils.memcpy(ecdh_info_bin, 0, ecdh_info.mask, 0, 32) utils.memcpy(ecdh_info_bin, 32, ecdh_info.amount, 0, 32) gc.collect() return out_pk_dest, out_pk_commitment, ecdh_info_bin
def test_scalarmult(self): priv = unhexlify( b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a" ) pub = unhexlify( b"2486224797d05cae3cba4be043be2db0df381f3f19cfa113f86ab38e3d8d2bd0" ) exp = unhexlify( b"adcd1f5881f46f254900a03c654e71950a88a0236fa0a3a946c9b8daed6ef43d" ) res = crypto.scalarmult(crypto.decodepoint(pub), crypto.decodeint(priv)) self.assertEqual(exp, crypto.encodepoint(res)) self.assertTrue(crypto.point_eq(crypto.decodepoint(exp), res))
def _recode_msg(mgs): """ Recodes MGs signatures from raw forms to bytearrays so it works with serialization """ for idx in range(len(mgs)): mgs[idx].cc = crypto.encodeint(mgs[idx].cc) if hasattr(mgs[idx], "II") and mgs[idx].II: for i in range(len(mgs[idx].II)): mgs[idx].II[i] = crypto.encodepoint(mgs[idx].II[i]) for i in range(len(mgs[idx].ss)): for j in range(len(mgs[idx].ss[i])): mgs[idx].ss[i][j] = crypto.encodeint(mgs[idx].ss[i][j]) return mgs
def test_public_spend(self): derivation = unhexlify( b"e720a09f2e3a0bbf4e4ba7ad93653bb296885510121f806acb2a5f9168fafa01" ) base = unhexlify( b"7d996b0f2db6dbb5f2a086211f2399a4a7479b2c911af307fdc3f7f61a88cb0e" ) pkey_ex = unhexlify( b"0846cae7405077b6b7800f0b932c10a186448370b6db318f8c9e13f781dab546" ) pkey_comp = crypto.derive_public_key(crypto.decodepoint(derivation), 0, crypto.decodepoint(base)) self.assertEqual(pkey_ex, crypto.encodepoint(pkey_comp))
def compute_subaddresses(creds, account: int, indices, subaddresses=None): """ 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.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.encodepoint(pub) subaddresses[pub] = (account, idx) return subaddresses
def verify_bp(bp_proof, amounts, masks): """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(masks[i], amounts[i]) crypto.scalarmult_into(C, C, crypto.sc_inv_eight()) bp_proof.V.append(crypto.encodepoint(C)) bpi = bp.BulletProofBuilder() res = bpi.verify(bp_proof) gc.collect() return res
def test_scalarmult_base(self): scalar = crypto.decodeint( unhexlify( b"a0eea49140a3b036da30eacf64bd9d56ce3ef68ba82ef13571ec511edbcf8303" )) exp = unhexlify( b"16bb4a3c44e2ced511fc0d4cd86b13b3af21efc99fb0356199fac489f2544c09" ) res = crypto.scalarmult_base(scalar) self.assertEqual(exp, crypto.encodepoint(res)) self.assertTrue(crypto.point_eq(crypto.decodepoint(exp), res)) scalar = crypto.decodeint( unhexlify( b"fd290dce39f781aebbdbd24584ed6d48bd300de19d9c3decfda0a6e2c6751d0f" )) exp = unhexlify( b"123daf90fc26f13c6529e6b49bfed498995ac383ef19c0db6771143f24ba8dd5" ) res = crypto.scalarmult_base(scalar) self.assertEqual(exp, crypto.encodepoint(res)) self.assertTrue(crypto.point_eq(crypto.decodepoint(exp), res))
def generate_mlsag_simple( message: bytes, pubs: list[MoneroRctKeyPublic], in_sk: CtKey, a: Sc25519, cout: Ge25519, index: int, mg_buff: list[bytes], ) -> list[bytes]: """ MLSAG for RctType.Simple :param message: the full message to be signed (actually its hash) :param pubs: vector of MoneroRctKey; this forms the ring; point values in encoded form; (dest, mask) = (P, C) :param in_sk: CtKey; spending private key with input commitment mask (original); better_name: input_secret_key :param a: mask from the pseudo output commitment; better name: pseudo_out_alpha :param cout: pseudo output commitment; point, decoded; better name: pseudo_out_c :param index: specifies corresponding public key to the `in_sk` in the pubs array :param mg_buff: buffer to store the signature to """ # Monero signs inputs separately, so `rows` always equals 2 (pubkey, commitment) # and `dsRows` is always 1 (denotes where the pubkeys "end") rows = 2 dsRows = 1 cols = len(pubs) if cols == 0: raise ValueError("Empty pubs") sk = _key_vector(rows) M = _key_matrix(rows, cols) sk[0] = in_sk.dest sk[1] = crypto.sc_sub(in_sk.mask, a) tmp_pt = crypto.new_point() for i in range(cols): crypto.point_sub_into( tmp_pt, crypto.decodepoint_into(tmp_pt, pubs[i].commitment), cout ) M[i][0] = pubs[i].dest M[i][1] = crypto.encodepoint(tmp_pt) pubs[i] = None del pubs gc.collect() return generate_mlsag(message, M, sk, index, dsRows, mg_buff)
def test_derive_subaddress_public_key(self): out_key = crypto.decodepoint( unhexlify( b"f4efc29da4ccd6bc6e81f52a6f47b2952966442a7efb49901cce06a7a3bef3e5" )) deriv = crypto.decodepoint( unhexlify( b"259ef2aba8feb473cf39058a0fe30b9ff6d245b42b6826687ebd6b63128aff64" )) res = crypto.encodepoint( monero.derive_subaddress_public_key(out_key, deriv, 5)) self.assertEqual( res, unhexlify( b"5a10cca900ee47a7f412cd661b29f5ab356d6a1951884593bb170b5ec8b6f2e8" ), )
def ctest_multiexp(self): scalars = [0, 1, 2, 3, 4, 99] point_base = [0, 2, 4, 7, 12, 18] scalar_sc = [crypto.sc_init(x) for x in scalars] points = [crypto.scalarmult_base(crypto.sc_init(x)) for x in point_base] muex = bp.MultiExp(scalars=[crypto.encodeint(x) for x in scalar_sc], point_fnc=lambda i, d: crypto.encodepoint(points[i])) self.assertEqual(len(muex), len(scalars)) res = bp.multiexp(None, muex) res2 = bp.vector_exponent_custom( A=bp.KeyVEval(3, lambda i, d: crypto.encodepoint_into(crypto.scalarmult_base(crypto.sc_init(point_base[i])), d)), B=bp.KeyVEval(3, lambda i, d: crypto.encodepoint_into(crypto.scalarmult_base(crypto.sc_init(point_base[3+i])), d)), a=bp.KeyVEval(3, lambda i, d: crypto.encodeint_into(crypto.sc_init(scalars[i]), d),), b=bp.KeyVEval(3, lambda i, d: crypto.encodeint_into(crypto.sc_init(scalars[i+3]), d)), ) self.assertEqual(res, res2)
def _export_key_image(creds, subaddresses, pkey, tx_pub_key, additional_tx_pub_keys, out_idx, test=True): """ 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_keys, out_idx) xi, ki, recv_derivation = r[:3] phash = crypto.encodepoint(ki) sig = generate_ring_signature(phash, ki, [pkey], xi, 0, test) return ki, sig
def test_generate_key_derivation(self): key_pub = crypto.decodepoint( unhexlify( b"7739c95d3298e2f87362dba9e0e0b3980a692ae8e2f16796b0e382098cd6bd83" )) key_priv = crypto.decodeint( unhexlify( b"3482fb9735ef879fcae5ec7721b5d3646e155c4fb58d6cc11c732c9c9b76620a" )) deriv_exp = unhexlify( b"fa188a45a0e4daccc0e6d4f6f6858fd46392104be74183ec0047e7e9f4eaf739" ) self.assertEqual( deriv_exp, crypto.encodepoint( crypto.generate_key_derivation(key_pub, key_priv)), )
def check_ring_singature(prefix_hash, image, pubs, sig): """ Checks ring signature generated with generate_ring_signature :param prefix_hash: :param image: :param pubs: :param sig: :return: """ from apps.monero.xmr.common import memcpy image_unp = crypto.ge_frombytes_vartime(image) image_pre = crypto.ge_dsm_precomp(image_unp) 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.sc_0() for i in range(len(pubs)): if crypto.sc_check(sig[i][0]) != 0 or crypto.sc_check(sig[i][1]) != 0: return False tmp3 = crypto.ge_frombytes_vartime(pubs[i]) tmp2 = crypto.ge_double_scalarmult_base_vartime( sig[i][0], tmp3, sig[i][1]) crypto.encodepoint_into(tmp2, mvbuff[buff_off:buff_off + 32]) buff_off += 32 tmp3 = crypto.hash_to_ec(crypto.encodepoint(pubs[i])) tmp2 = crypto.ge_double_scalarmult_precomp_vartime( sig[i][1], tmp3, sig[i][0], image_pre) crypto.encodepoint_into(tmp2, mvbuff[buff_off:buff_off + 32]) buff_off += 32 sum = crypto.sc_add(sum, sig[i][0]) h = crypto.hash_to_scalar(buff) h = crypto.sc_sub(h, sum) return crypto.sc_isnonzero(h) == 0
async def _refresh_step(s: LiveRefreshState, ctx, msg: MoneroLiveRefreshStepRequest): buff = bytearray(32 * 3) buff_mv = memoryview(buff) await confirms.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.decodepoint(msg.out_key) recv_deriv = crypto.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.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 ecdh_encode(unmasked, receiver_pk=None, derivation=None): """ Elliptic Curve Diffie-Helman: encodes and decodes the amount b and mask a where C= aG + bH :param unmasked: :param receiver_pk: :param derivation: :return: """ from apps.monero.xmr.serialize_messages.tx_ecdh import EcdhTuple rv = EcdhTuple() if derivation is None: esk = crypto.random_scalar() rv.senderPk = crypto.scalarmult_base(esk) derivation = crypto.encodepoint(crypto.scalarmult(receiver_pk, esk)) sharedSec1 = crypto.hash_to_scalar(derivation) sharedSec2 = crypto.hash_to_scalar(crypto.encodeint(sharedSec1)) rv.mask = crypto.sc_add(unmasked.mask, sharedSec1) rv.amount = crypto.sc_add(unmasked.amount, sharedSec2) return rv
def _set_out_additional_keys(state: State, dst_entr): """ 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.scalarmult( crypto.decodepoint(dst_entr.addr.spend_public_key), additional_txkey_priv) else: # R=r*G additional_txkey = crypto.scalarmult_base(additional_txkey_priv) state.additional_tx_public_keys.append( crypto.encodepoint(additional_txkey)) state.additional_tx_private_keys.append(additional_txkey_priv) return additional_txkey_priv
def export_key_image( creds, subaddresses, pkey, tx_pub_key, additional_tx_pub_keys, out_idx, test=True, verify=True, ): """ Generates key image for the TXO + signature for the key image :param creds: :param subaddresses: :param pkey: :param tx_pub_key: :param additional_tx_pub_keys: :param out_idx: :param test: :param verify: :return: """ from apps.monero.xmr import monero r = monero.generate_key_image_helper( creds, subaddresses, pkey, tx_pub_key, additional_tx_pub_keys, out_idx ) xi, ki, recv_derivation = r[:3] phash = crypto.encodepoint(ki) sig = generate_ring_signature(phash, ki, [pkey], xi, 0, test) if verify: if check_ring_singature(phash, ki, [pkey], sig) != 1: raise ValueError("Signature error") return ki, sig
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 test_h(self): H = unhexlify( b"8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94" ) self.assertEqual(crypto.encodepoint(crypto.xmr_H()), H)
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 confirms.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 = (state.source_permutation[state.current_input_index] if state.client_version <= 1 else orig_idx) 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") # 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.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 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(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) # 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.decodeint(src_entr.mask)) # 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 if x] utils.ensure(len(ring_pubkeys) == len(src_entr.outputs), "Invalid ring") del src_entr state.mem_trace(5, True) if state.hard_fork and state.hard_fork >= 13: state.mem_trace("CLSAG") mlsag.generate_clsag_simple( state.full_message, ring_pubkeys, input_secret_key, pseudo_out_alpha, pseudo_out_c, index, mg_buffer, ) else: mlsag.generate_mlsag_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, mlsag, ring_pubkeys) state.mem_trace(6, True) from trezor.messages.MoneroTransactionSignInputAck import ( MoneroTransactionSignInputAck, ) # Encrypt signature, reveal once protocol finishes OK if state.client_version >= 3: 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.encodepoint(pseudo_out_c))
def generate_first_c_and_key_images(message, rv, pk, xx, kLRki, index, dsRows, rows, cols): """ MLSAG computation - the part with secret keys :param message: the full message to be signed (actually its hash) :param rv: MgSig :param pk: matrix of public keys and commitments :param xx: input secret array composed of a private key and commitment mask :param kLRki: used only in multisig, currently not implemented :param index: specifies corresponding public key to the `xx`'s private key in the `pk` array :param dsRows: row number where the pubkeys "end" (and commitments follow) :param rows: total number of rows :param cols: size of ring """ Ip = _key_vector(dsRows) rv.II = _key_vector(dsRows) alpha = _key_vector(rows) rv.ss = _key_matrix(rows, cols) tmp_buff = bytearray(32) hasher = _hasher_message(message) for i in range(dsRows): # this is somewhat extra as compared to the Ring Confidential Tx paper # see footnote in From Zero to Monero section 3.3 hasher.update(crypto.encodepoint(pk[index][i])) if kLRki: raise NotImplementedError("Multisig not implemented") # alpha[i] = kLRki.k # rv.II[i] = kLRki.ki # hash_point(hasher, kLRki.L, tmp_buff) # hash_point(hasher, kLRki.R, tmp_buff) else: Hi = crypto.hash_to_point(crypto.encodepoint(pk[index][i])) alpha[i] = crypto.random_scalar() # L = alpha_i * G aGi = crypto.scalarmult_base(alpha[i]) # Ri = alpha_i * H(P_i) aHPi = crypto.scalarmult(Hi, alpha[i]) # key image rv.II[i] = crypto.scalarmult(Hi, xx[i]) _hash_point(hasher, aGi, tmp_buff) _hash_point(hasher, aHPi, tmp_buff) Ip[i] = rv.II[i] for i in range(dsRows, rows): alpha[i] = crypto.random_scalar() # L = alpha_i * G aGi = crypto.scalarmult_base(alpha[i]) # for some reasons we omit calculating R here, which seems # contrary to the paper, but it is in the Monero official client # see https://github.com/monero-project/monero/blob/636153b2050aa0642ba86842c69ac55a5d81618d/src/ringct/rctSigs.cpp#L191 _hash_point(hasher, pk[index][i], tmp_buff) _hash_point(hasher, aGi, tmp_buff) # the first c c_old = hasher.digest() c_old = crypto.decodeint(c_old) return c_old, Ip, alpha
def generate_ring_signature( prefix_hash: bytes, image: Ge25519, pubs: list[Ge25519], sec: Sc25519, 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(sec) if not crypto.point_eq(t, pubs[sec_idx]): raise ValueError("Invalid sec key") k_i = monero.generate_key_image(crypto.encodepoint(pubs[sec_idx]), sec) if not crypto.point_eq(k_i, image): raise ValueError("Key image invalid") for k in pubs: crypto.check_ed25519point(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.sc_0() k = crypto.sc_0() sig = [] for _ in range(len(pubs)): sig.append([crypto.sc_0(), crypto.sc_0()]) # c, r for i in range(len(pubs)): if i == sec_idx: k = crypto.random_scalar() tmp3 = crypto.scalarmult_base(k) crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp3) buff_off += 32 tmp3 = crypto.hash_to_point(crypto.encodepoint(pubs[i])) tmp2 = crypto.scalarmult(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_base_vartime( sig[i][0], tmp3, sig[i][1]) crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp2) buff_off += 32 tmp3 = crypto.hash_to_point(crypto.encodepoint(tmp3)) tmp2 = crypto.ge25519_double_scalarmult_vartime2( sig[i][1], tmp3, sig[i][0], image) crypto.encodepoint_into(mvbuff[buff_off:buff_off + 32], tmp2) buff_off += 32 sum = crypto.sc_add(sum, sig[i][0]) h = crypto.hash_to_scalar(buff) sig[sec_idx][0] = crypto.sc_sub(h, sum) sig[sec_idx][1] = crypto.sc_mulsub(sig[sec_idx][0], sec, k) return sig
async def set_input( state: State, src_entr: MoneroTransactionSourceEntry ) -> MoneroTransactionSetInputAck: from trezor.messages import MoneroTransactionSetInputAck from apps.monero.xmr.crypto 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.decodepoint( src_entr.outputs[src_entr.real_output].key.dest) # the tx_pub of our UTXO stored inside its transaction tx_key = crypto.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.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.encodepoint(pseudo_out) # The alpha is encrypted and passed back for storage pseudo_out_hmac = crypto.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.encodeint(alpha), ) spend_enc = chacha_poly.encrypt_pack( offloading_keys.enc_key_spend(state.key_enc, state.current_input_index), crypto.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_ring_signature(prefix_hash, image, pubs, sec, sec_idx, test=False): """ Generates ring signature with key image. void crypto_ops::generate_ring_signature() :param prefix_hash: :param image: :param pubs: :param sec: :param sec_idx: :param test: :return: """ from apps.monero.xmr.common import memcpy if test: from apps.monero.xmr import monero t = crypto.scalarmult_base(sec) if not crypto.point_eq(t, pubs[sec_idx]): raise ValueError("Invalid sec key") k_i = monero.generate_key_image(crypto.encodepoint(pubs[sec_idx]), sec) if not crypto.point_eq(k_i, image): raise ValueError("Key image invalid") for k in pubs: crypto.ge_frombytes_vartime_check(k) image_unp = crypto.ge_frombytes_vartime(image) image_pre = crypto.ge_dsm_precomp(image_unp) 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.sc_0() k = crypto.sc_0() sig = [] for i in range(len(pubs)): sig.append([crypto.sc_0(), crypto.sc_0()]) # c, r for i in range(len(pubs)): if i == sec_idx: k = crypto.random_scalar() tmp3 = crypto.scalarmult_base(k) crypto.encodepoint_into(tmp3, mvbuff[buff_off:buff_off + 32]) buff_off += 32 tmp3 = crypto.hash_to_ec(crypto.encodepoint(pubs[i])) tmp2 = crypto.scalarmult(tmp3, k) crypto.encodepoint_into(tmp2, mvbuff[buff_off:buff_off + 32]) buff_off += 32 else: sig[i] = [crypto.random_scalar(), crypto.random_scalar()] tmp3 = crypto.ge_frombytes_vartime(pubs[i]) tmp2 = crypto.ge_double_scalarmult_base_vartime( sig[i][0], tmp3, sig[i][1]) crypto.encodepoint_into(tmp2, mvbuff[buff_off:buff_off + 32]) buff_off += 32 tmp3 = crypto.hash_to_ec(crypto.encodepoint(tmp3)) tmp2 = crypto.ge_double_scalarmult_precomp_vartime( sig[i][1], tmp3, sig[i][0], image_pre) crypto.encodepoint_into(tmp2, mvbuff[buff_off:buff_off + 32]) buff_off += 32 sum = crypto.sc_add(sum, sig[i][0]) h = crypto.hash_to_scalar(buff) sig[sec_idx][0] = crypto.sc_sub(h, sum) sig[sec_idx][1] = crypto.sc_mulsub(sig[sec_idx][0], sec, k) return sig
def _generate_clsag( message: bytes, P: List[bytes], p: Sc25519, C_nonzero: List[bytes], z: Sc25519, Cout: Ge25519, index: int, mg_buff: List[bytes], ) -> List[bytes]: sI = crypto.new_point() # sig.I sD = crypto.new_point() # sig.D sc1 = crypto.new_scalar() # sig.c1 a = crypto.random_scalar() H = crypto.new_point() D = crypto.new_point() Cout_bf = crypto.encodepoint(Cout) tmp_sc = crypto.new_scalar() tmp = crypto.new_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.sc_inv_eight()) # 1/8*z crypto.scalarmult_into(sD, H, tmp_sc) # sig.D = 1/8*z*H sD = crypto.encodepoint(sD) hsh_P = crypto.get_keccak() # domain, I, D, P, C, C_offset hsh_C = crypto.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.decodeint(hsh_P.digest()) mu_C = crypto.decodeint(hsh_C.digest()) del (hsh_PC, hsh_P, hsh_C) c_to_hash = crypto.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.decodeint(chasher.digest()) del (chasher, H) L = crypto.new_point() R = crypto.new_point() c_p = crypto.new_scalar() c_c = crypto.new_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 C_nonzero[i] = None 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) mg_buff.append(crypto.encodeint(sc1)) mg_buff.append(sD) return mg_buff
def prove_range_mem(amount, last_mask=None): """ Memory optimized range proof. Gives C, and mask such that \sumCi = C c.f. http:#eprint.iacr.org/2015/1098 section 5.1 Ci is a commitment to either 0 or 2^i, i=0,...,63 thus this proves that "amount" is in [0, 2^ATOMS] mask is a such that C = aG + bH, and b = amount :param amount: :param last_mask: ai[ATOMS-1] will be computed as \sum_{i=0}^{ATOMS-2} a_i - last_mask :param use_asnl: use ASNL, used before Borromean :return: sumCi, mask, RangeSig. sumCi is Pedersen commitment on the amount value. sumCi = aG + amount*H mask is "a" from the Pedersent commitment above. """ res = bytearray(32 * (64 + 64 + 64 + 1)) mv = memoryview(res) gc.collect() def as0(mv, x, i): crypto.encodeint_into(x, mv[32 * i:]) def as1(mv, x, i): crypto.encodeint_into(x, mv[32 * 64 + 32 * i:]) def aci(mv, x, i): crypto.encodepoint_into(x, mv[32 * 64 * 2 + 32 + 32 * i:]) n = 64 bb = d2b(amount, n) # gives binary form of bb in "digits" binary digits ai = key_zero_vector(n) a = crypto.sc_0() C = crypto.identity() alpha = key_zero_vector(n) c_H = crypto.gen_H() kck = crypto.get_keccak() # ee computation # First pass, generates: ai, alpha, Ci, ee, s1 for ii in range(n): ai[ii] = crypto.random_scalar() if last_mask is not None and ii == 64 - 1: ai[ii] = crypto.sc_sub(last_mask, a) a = crypto.sc_add( a, ai[ii] ) # creating the total mask since you have to pass this to receiver... alpha[ii] = crypto.random_scalar() L = crypto.scalarmult_base(alpha[ii]) if bb[ii] == 0: Ctmp = crypto.scalarmult_base(ai[ii]) else: Ctmp = crypto.point_add(crypto.scalarmult_base(ai[ii]), c_H) C = crypto.point_add(C, Ctmp) aci(mv, Ctmp, ii) if bb[ii] == 0: si = crypto.random_scalar() c = crypto.hash_to_scalar(crypto.encodepoint(L)) L = crypto.add_keys2(si, c, crypto.point_sub(Ctmp, c_H)) kck.update(crypto.encodepoint(L)) as1(mv, si, ii) else: kck.update(crypto.encodepoint(L)) c_H = crypto.point_double(c_H) # Compute ee, memory cleanup ee = crypto.sc_reduce32(crypto.decodeint(kck.digest())) crypto.encodeint_into(ee, mv[64 * 32 * 2:]) del kck gc.collect() # Second phase computes: s0, s1 c_H = crypto.gen_H() for jj in range(n): if not bb[jj]: s0 = crypto.sc_mulsub(ai[jj], ee, alpha[jj]) else: s0 = crypto.random_scalar() Ctmp = crypto.decodepoint( mv[32 * 64 * 2 + 32 + 32 * jj:32 * 64 * 2 + 32 + 32 * jj + 32]) LL = crypto.add_keys2(s0, ee, Ctmp) cc = crypto.hash_to_scalar(crypto.encodepoint(LL)) si = crypto.sc_mulsub(ai[jj], cc, alpha[jj]) as1(mv, si, jj) as0(mv, s0, jj) c_H = crypto.point_double(c_H) gc.collect() return C, a, res
def verify_batch(self, proofs, single_optim=True, proof_v8=False): """ BP batch verification :param proofs: :param single_optim: single proof memory optimization :param proof_v8: previous testnet version :return: """ max_length = 0 for proof in proofs: utils.ensure(is_reduced(proof.taux), "Input scalar not in range") utils.ensure(is_reduced(proof.mu), "Input scalar not in range") utils.ensure(is_reduced(proof.a), "Input scalar not in range") utils.ensure(is_reduced(proof.b), "Input scalar not in range") utils.ensure(is_reduced(proof.t), "Input scalar not in range") utils.ensure(len(proof.V) >= 1, "V does not have at least one element") utils.ensure(len(proof.L) == len(proof.R), "|L| != |R|") utils.ensure(len(proof.L) > 0, "Empty proof") max_length = max(max_length, len(proof.L)) utils.ensure(max_length < 32, "At least one proof is too large") maxMN = 1 << max_length logN = 6 N = 1 << logN tmp = _ensure_dst_key() # setup weighted aggregates is_single = len(proofs) == 1 and single_optim # ph4 z1 = init_key(_ZERO) z3 = init_key(_ZERO) m_z4 = vector_dup(_ZERO, maxMN) if not is_single else None m_z5 = vector_dup(_ZERO, maxMN) if not is_single else None m_y0 = init_key(_ZERO) y1 = init_key(_ZERO) muex_acc = init_key(_ONE) Gprec = self._gprec_aux(maxMN) Hprec = self._hprec_aux(maxMN) for proof in proofs: M = 1 logM = 0 while M <= _BP_M and M < len(proof.V): logM += 1 M = 1 << logM utils.ensure(len(proof.L) == 6 + logM, "Proof is not the expected size") MN = M * N weight_y = crypto.encodeint(crypto.random_scalar()) weight_z = crypto.encodeint(crypto.random_scalar()) # Reconstruct the challenges hash_cache = hash_vct_to_scalar(None, proof.V) y = hash_cache_mash(None, hash_cache, proof.A, proof.S) utils.ensure(y != _ZERO, "y == 0") z = hash_to_scalar(None, y) copy_key(hash_cache, z) utils.ensure(z != _ZERO, "z == 0") x = hash_cache_mash(None, hash_cache, z, proof.T1, proof.T2) utils.ensure(x != _ZERO, "x == 0") x_ip = hash_cache_mash(None, hash_cache, x, proof.taux, proof.mu, proof.t) utils.ensure(x_ip != _ZERO, "x_ip == 0") # PAPER LINE 61 sc_mulsub(m_y0, proof.taux, weight_y, m_y0) zpow = vector_powers(z, M + 3) k = _ensure_dst_key() ip1y = vector_power_sum(y, MN) sc_mulsub(k, zpow[2], ip1y, _ZERO) for j in range(1, M + 1): utils.ensure(j + 2 < len(zpow), "invalid zpow index") sc_mulsub(k, zpow.to(j + 2), _BP_IP12, k) # VERIFY_line_61rl_new sc_muladd(tmp, z, ip1y, k) sc_sub(tmp, proof.t, tmp) sc_muladd(y1, tmp, weight_y, y1) weight_y8 = init_key(weight_y) if not proof_v8: weight_y8 = sc_mul(None, weight_y, _EIGHT) muex = MultiExpSequential(points=[pt for pt in proof.V]) for j in range(len(proof.V)): sc_mul(tmp, zpow[j + 2], weight_y8) muex.add_scalar(init_key(tmp)) sc_mul(tmp, x, weight_y8) muex.add_pair(init_key(tmp), proof.T1) xsq = _ensure_dst_key() sc_mul(xsq, x, x) sc_mul(tmp, xsq, weight_y8) muex.add_pair(init_key(tmp), proof.T2) weight_z8 = init_key(weight_z) if not proof_v8: weight_z8 = sc_mul(None, weight_z, _EIGHT) muex.add_pair(weight_z8, proof.A) sc_mul(tmp, x, weight_z8) muex.add_pair(init_key(tmp), proof.S) multiexp(tmp, muex, False) add_keys(muex_acc, muex_acc, tmp) del muex # Compute the number of rounds for the inner product rounds = logM + logN utils.ensure(rounds > 0, "Zero rounds") # PAPER LINES 21-22 # The inner product challenges are computed per round w = _ensure_dst_keyvect(None, rounds) for i in range(rounds): hash_cache_mash(tmp_bf_0, hash_cache, proof.L[i], proof.R[i]) w.read(i, tmp_bf_0) utils.ensure(w[i] != _ZERO, "w[i] == 0") # Basically PAPER LINES 24-25 # Compute the curvepoints from G[i] and H[i] yinvpow = init_key(_ONE) ypow = init_key(_ONE) yinv = invert(None, y) self.gc(61) winv = _ensure_dst_keyvect(None, rounds) for i in range(rounds): invert(tmp_bf_0, w.to(i)) winv.read(i, tmp_bf_0) self.gc(62) g_scalar = _ensure_dst_key() h_scalar = _ensure_dst_key() twoN = self._two_aux(N) for i in range(MN): copy_key(g_scalar, proof.a) sc_mul(h_scalar, proof.b, yinvpow) for j in range(rounds - 1, -1, -1): J = len(w) - j - 1 if (i & (1 << j)) == 0: sc_mul(g_scalar, g_scalar, winv.to(J)) sc_mul(h_scalar, h_scalar, w.to(J)) else: sc_mul(g_scalar, g_scalar, w.to(J)) sc_mul(h_scalar, h_scalar, winv.to(J)) # Adjust the scalars using the exponents from PAPER LINE 62 sc_add(g_scalar, g_scalar, z) utils.ensure(2 + i // N < len(zpow), "invalid zpow index") utils.ensure(i % N < len(twoN), "invalid twoN index") sc_mul(tmp, zpow.to(2 + i // N), twoN.to(i % N)) sc_muladd(tmp, z, ypow, tmp) sc_mulsub(h_scalar, tmp, yinvpow, h_scalar) if not is_single: # ph4 sc_mulsub(m_z4[i], g_scalar, weight_z, m_z4[i]) sc_mulsub(m_z5[i], h_scalar, weight_z, m_z5[i]) else: sc_mul(tmp, g_scalar, weight_z) sub_keys(muex_acc, muex_acc, scalarmult_key(tmp, Gprec.to(i), tmp)) sc_mul(tmp, h_scalar, weight_z) sub_keys(muex_acc, muex_acc, scalarmult_key(tmp, Hprec.to(i), tmp)) if i != MN - 1: sc_mul(yinvpow, yinvpow, yinv) sc_mul(ypow, ypow, y) if i & 15 == 0: self.gc(62) del (g_scalar, h_scalar, twoN) self.gc(63) sc_muladd(z1, proof.mu, weight_z, z1) muex = MultiExpSequential( point_fnc=lambda i, d: proof.L[i // 2] if i & 1 == 0 else proof.R[i // 2] ) for i in range(rounds): sc_mul(tmp, w[i], w[i]) sc_mul(tmp, tmp, weight_z8) muex.add_scalar(tmp) sc_mul(tmp, winv[i], winv[i]) sc_mul(tmp, tmp, weight_z8) muex.add_scalar(tmp) acc = multiexp(None, muex, False) add_keys(muex_acc, muex_acc, acc) sc_mulsub(tmp, proof.a, proof.b, proof.t) sc_mul(tmp, tmp, x_ip) sc_muladd(z3, tmp, weight_z, z3) sc_sub(tmp, m_y0, z1) z3p = sc_sub(None, z3, y1) check2 = crypto.encodepoint( crypto.ge25519_double_scalarmult_base_vartime( crypto.decodeint(z3p), crypto.xmr_H(), crypto.decodeint(tmp) ) ) add_keys(muex_acc, muex_acc, check2) if not is_single: # ph4 muex = MultiExpSequential( point_fnc=lambda i, d: Gprec.to(i // 2) if i & 1 == 0 else Hprec.to(i // 2) ) for i in range(maxMN): muex.add_scalar(m_z4[i]) muex.add_scalar(m_z5[i]) add_keys(muex_acc, muex_acc, multiexp(None, muex, True)) if muex_acc != _ONE: raise ValueError("Verification failure at step 2") return True
def verify_clsag(self, msg, ss, sc1, sI, sD, pubs, C_offset): n = len(pubs) c = crypto.new_scalar() D_8 = crypto.new_point() tmp_bf = bytearray(32) C_offset_bf = crypto.encodepoint(C_offset) crypto.sc_copy(c, sc1) crypto.point_mul8_into(D_8, sD) hsh_P = crypto.get_keccak() # domain, I, D, P, C, C_offset hsh_C = crypto.get_keccak() # domain, I, D, P, C, C_offset hsh_P.update(mlsag._HASH_KEY_CLSAG_AGG_0) hsh_C.update(mlsag._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.decodeint(hsh_P.digest()) mu_C = crypto.decodeint(hsh_C.digest()) c_to_hash = crypto.get_keccak( ) # domain, P, C, C_offset, message, L, R c_to_hash.update(mlsag._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.new_scalar() c_c = crypto.new_scalar() L = crypto.new_point() R = crypto.new_point() tmp_pt = crypto.new_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( 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(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(c, sc1) if not crypto.sc_eq(res, crypto.sc_0()): raise ValueError("Signature error")