def verify_tx_key(self, tx_priv, tx_pub, all_creds_subs): if crypto.point_eq(tx_pub, crypto.scalarmult_base(tx_priv)): return True for cred_idx, subs in enumerate(all_creds_subs): for ckey in subs: if crypto.point_eq(tx_pub, crypto.scalarmult(ckey, tx_priv)): return True return False
def gen_clsag_sig(self, ring_size=11, index=None): msg = bytearray(random.getrandbits(8) for _ in range(32)) amnt = crypto.sc_init(random.randint(0, 0xFFFFFF) + 12) priv = crypto.random_scalar() msk = crypto.random_scalar() alpha = crypto.random_scalar() P = crypto.scalarmult_base(priv) C = crypto.add_keys2(msk, amnt, crypto.xmr_H()) Cp = crypto.add_keys2(alpha, amnt, crypto.xmr_H()) ring = [] for i in range(ring_size - 1): tk = TmpKey( crypto.encodepoint( crypto.scalarmult_base(crypto.random_scalar())), crypto.encodepoint( crypto.scalarmult_base(crypto.random_scalar())), ) ring.append(tk) index = index if index is not None else random.randint(0, len(ring)) ring.insert(index, TmpKey(crypto.encodepoint(P), crypto.encodepoint(C))) ring2 = list(ring) mg_buffer = [] self.assertTrue( crypto.point_eq(crypto.scalarmult_base(priv), crypto.decodepoint(ring[index].dest))) self.assertTrue( crypto.point_eq( crypto.scalarmult_base(crypto.sc_sub(msk, alpha)), crypto.point_sub(crypto.decodepoint(ring[index].commitment), Cp), )) mlsag2.generate_clsag_simple( msg, ring, CtKey(dest=priv, mask=msk), alpha, Cp, index, mg_buffer, ) sD = crypto.decodepoint(mg_buffer[-1]) sc1 = crypto.decodeint(mg_buffer[-2]) scalars = [crypto.decodeint(x) for x in mg_buffer[1:-2]] H = crypto.new_point() sI = crypto.new_point() crypto.hash_to_point_into(H, crypto.encodepoint(P)) crypto.scalarmult_into(sI, H, priv) # I = p*H return msg, scalars, sc1, sI, sD, ring2, Cp
async def get_tx_key_test(self, agent, con_data, creds, all_creds): salt1 = con_data.enc_salt1 salt2 = con_data.enc_salt2 res = await agent.get_tx_key(salt1, salt2, con_data.enc_keys, con_data.tx_prefix_hash, creds.view_key_private) extras = await monero.parse_extra_fields(list(con_data.tx.extra)) tx_pub = monero.find_tx_extra_field_by_type(extras, xmrtypes.TxExtraPubKey, 0) additional_pub_keys = monero.find_tx_extra_field_by_type( extras, xmrtypes.TxExtraAdditionalPubKeys) # For verification need to build database creds -> pubkeys all_creds_subs = [] for idx, ccred in enumerate(all_creds): subs = {} for accnt in range(10): subs = monero.compute_subaddresses(ccred, accnt, range(20), subs) all_creds_subs.append( [crypto.decodepoint(xx) for xx in subs.keys()]) if not self.verify_tx_key(res[0], crypto.decodepoint(tx_pub.pub_key), all_creds_subs): raise ValueError("Tx pub mismatch") if additional_pub_keys and len( additional_pub_keys.data) != len(res) - 1: raise ValueError("Invalid additional keys count") if additional_pub_keys: for i, ad in enumerate(additional_pub_keys.data): if not self.verify_tx_key(res[i + 1], crypto.decodepoint(ad), all_creds_subs): raise ValueError("Tx additional %s pub mismatch" % i) my_pub = crypto.scalarmult_base(creds.view_key_private) res_der = await agent.get_tx_deriv(salt1, salt2, con_data.enc_keys, con_data.tx_prefix_hash, creds.view_key_private, crypto.encodepoint(my_pub)) if len(res) != len(res_der): raise ValueError("Derivation array mismatch") tmp = crypto.scalarmult(my_pub, res[0]) if not crypto.point_eq(tmp, res_der[0]): raise ValueError("Derivation 0 mismatch") if additional_pub_keys: for i in range(len(additional_pub_keys.data)): tmp = crypto.scalarmult(my_pub, res[i + 1]) if not crypto.point_eq(tmp, res_der[i + 1]): raise ValueError("Tx derivation additional %s mismatch" % i)
async def put_key(self): sec = crypto.decodeint(self._fetch(32)) pub = crypto.decodepoint(self._fetch(32)) if not crypto.point_eq(pub, crypto.scalarmult_base(sec)): return SW_WRONG_DATA self.a = sec sec = crypto.decodeint(self._fetch(32)) pub = crypto.decodepoint(self._fetch(32)) if not crypto.point_eq(pub, crypto.scalarmult_base(sec)): return SW_WRONG_DATA self.b = sec return SW_OK
async def mlsag_prehash_update(self): is_subaddress = 0 Aout = None Bout = None C = None v = None k = None changed = 0 is_subaddress = bool(self._fetch_u8()) Aout = crypto.decodepoint(self._fetch()) Bout = crypto.decodepoint(self._fetch()) if self.sig_mode == TRANSACTION_CREATE_REAL: if crypto.point_eq(Aout, self.A) and crypto.point_eq(Bout, self.B): changed = 1 AKout = self._fetch_decrypt() C = self._fetch() k = self._fetch() v = self._fetch() self.ctx_h.update(k) self.ctx_h.update(v) self.ctx_amount.update(AKout) v, k, AKout = self.unblind_int(v, k, AKout) self.ctx_amount.update(crypto.encodeint(k)) self.ctx_amount.update(crypto.encodeint(v)) vH = crypto.scalarmult_h(v) kG = crypto.scalarmult_base(k) k = crypto.point_add(kG, vH) if not crypto.point_eq(k, crypto.decodepoint(C)): raise ValueError(SW_SECURITY_COMMITMENT_CONTROL) self.ctx_commitment.update(C) if self.options & IN_OPTION_MORE_COMMAND == 0: k = self.ctx_amount.digest() if not common.ct_equal(k, self.KV): raise ValueError(SW_SECURITY_AMOUNT_CHAIN_CONTROL) self.C = self.ctx_commitment.digest() self.ctx_commitment = sha256() if self.sig_mode == TRANSACTION_CREATE_REAL: if not changed: await self._req_dst(Aout, Bout, v, is_subaddress) return SW_OK
def test_pointadd(self): a = crypto.random_scalar() A = crypto.scalarmult_base(a) A2 = crypto.point_add(A, A) A3 = crypto.point_add(A2, A) A4 = crypto.point_add(A3, A) A8 = crypto.scalarmult(A4, crypto.sc_init(2)) A8p = crypto.point_mul8(A) self.assertTrue(crypto.point_eq(A8p, A8)) self.assertTrue( crypto.point_eq(A4, crypto.scalarmult(A, crypto.sc_init(4)))) self.assertTrue( crypto.point_eq(A3, crypto.scalarmult(A, crypto.sc_init(3))))
def ver_range(C=None, rsig=None, use_asnl=False, decode=True): """ Verifies that \sum Ci = C and that each Ci is a commitment to 0 or 2^i :param C: :param rsig: :param use_asnl: use ASNL, used before Borromean, insecure! :param decode: decodes encoded range proof :return: """ n = ATOMS CiH = [None] * n C_tmp = crypto.identity() c_H = crypto.gen_H() if decode: rsig = monero.recode_rangesig(rsig, encode=False, copy=True) for i in range(0, n): CiH[i] = crypto.point_sub(rsig.Ci[i], c_H) C_tmp = crypto.point_add(C_tmp, rsig.Ci[i]) c_H = crypto.point_double(c_H) if C is not None and not crypto.point_eq(C_tmp, C): return 0 if use_asnl: return asnl.ver_asnl(rsig.Ci, CiH, rsig.asig.s0, rsig.asig.s1, rsig.asig.ee) else: return mlsag2.ver_borromean(rsig.Ci, CiH, rsig.asig.s0, rsig.asig.s1, rsig.asig.ee)
def ver_asnl(P1, P2, L1, s2, s): """ Aggregate Schnorr Non-Linkable :param P1: :param P2: :param L1: :param s2: :param s: :return: """ logger.info("Verifying Aggregate Schnorr Non-linkable Ring Signature") n = len(P1) LHS = crypto.scalarmult_base(crypto.sc_0()) RHS = crypto.scalarmult_base(s) for j in range(0, n): c2 = crypto.hash_to_scalar(crypto.encodepoint(L1[j])) L2 = crypto.point_add(crypto.scalarmult_base(s2[j]), crypto.scalarmult(P2[j], c2)) LHS = crypto.point_add(LHS, L1[j]) c1 = crypto.hash_to_scalar(crypto.encodepoint(L2)) RHS = crypto.point_add(RHS, crypto.scalarmult(P1[j], c1)) if crypto.point_eq(LHS, RHS): return 1 else: logger.warning("Didn't verify L1: %s, L1p: %s" % (crypto.encodepoint(LHS), crypto.encodepoint(RHS))) return 0
def ver_range(C=None, rsig=None, use_bulletproof=False, decode=True): """ Verifies that \sum Ci = C and that each Ci is a commitment to 0 or 2^i :param C: :param rsig: :param use_bulletproof: bulletproof :param decode: decodes encoded range proof :return: """ n = ATOMS CiH = [None] * n C_tmp = crypto.identity() c_H = crypto.xmr_H() if decode and not use_bulletproof: rsig = monero.recode_rangesig(rsig, encode=False, copy=True) if not use_bulletproof: for i in range(0, n): CiH[i] = crypto.point_sub(rsig.Ci[i], c_H) C_tmp = crypto.point_add(C_tmp, rsig.Ci[i]) c_H = crypto.point_double(c_H) if C is not None and not crypto.point_eq(C_tmp, C): return 0 if use_bulletproof: bp = bulletproof.BulletProofBuilder() return bp.verify(rsig) return mlsag2.ver_borromean(rsig.Ci, CiH, rsig.asig.s0, rsig.asig.s1, rsig.asig.ee)
def generate_key_image_helper_precomp( ack, out_key, recv_derivation, real_output_index, received_index ): """ Generates UTXO spending key and key image. :param ack: sender credentials :type ack: 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 ack.spend_key_private == 0: raise ValueError("Watch-only wallet not supported") # derive secret key with subaddress - step 1: original CN derivation scalar_step1 = crypto.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 scalar_step2 = 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(scalar_step1, subaddr_sk) # when not in multisig, we know the full spend secret key, so the output pubkey can be obtained by scalarmultBase if len(ack.multisig_keys) == 0: pub_ver = crypto.scalarmult_base(scalar_step2) else: # 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) 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.encodepoint(pub_ver), scalar_step2) return scalar_step2, ki
def test_ge25519_double_scalarmult_vartime(self): for i in range(10): ap = crypto.random_scalar() A = crypto.scalarmult_base(ap) a = crypto.random_scalar() b = crypto.random_scalar() R = crypto.ge_double_scalarmult_base_vartime(a, A, b) R_exp = crypto.point_add(crypto.scalarmult(A, a), crypto.scalarmult_base(b)) self.assertTrue(crypto.point_eq(R, R_exp))
def test_range_proof(self): proof = ring_ct.prove_range(0) res = ring_ct.ver_range(proof[0], proof[2]) self.assertTrue(res) self.assertTrue( crypto.point_eq( proof[0], crypto.point_add(crypto.scalarmult_base(proof[1]), crypto.scalarmult_h(0)), )) proof = ring_ct.prove_range(0, mem_opt=False) res = ring_ct.ver_range(proof[0], proof[2]) self.assertTrue(res) self.assertTrue( crypto.point_eq( proof[0], crypto.point_add(crypto.scalarmult_base(proof[1]), crypto.scalarmult_h(0)), ))
def test_encoding(self): point = unhexlify( b"2486224797d05cae3cba4be043be2db0df381f3f19cfa113f86ab38e3d8d2bd0" ) self.assertEqual(point, crypto.encodepoint(crypto.decodepoint(point))) self.assertTrue( crypto.point_eq( crypto.decodepoint(point), crypto.decodepoint( crypto.encodepoint(crypto.decodepoint(point))), ))
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 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))
async def open_with_keys(self, view_key, address): """ Processess view key private + address :param view_key: :param address: :return: """ self.pub_view = crypto.scalarmult_base(view_key) version, pub_spend, pub_view = monero.decode_addr(address) self.pub_spend = crypto.decodepoint(pub_spend) if not crypto.point_eq(self.pub_view, crypto.decodepoint(pub_view)): raise ValueError( "Computed view public key does not match the one from address")
async def amplify_inputs(self, tx, new_keys=None, new_subs=None): lst = tx.sources[-1] orig_amount = lst.amount partial = orig_amount // (self.args.inputs + 1) amnt_sum = partial lst.amount = partial commitment = crypto.encodepoint(crypto.gen_c(crypto.decodeint(lst.mask), partial)) lst.outputs[lst.real_output][1].mask = commitment for i in range(1, self.args.inputs + 1): is_lst = i >= self.args.inputs new_amnt = orig_amount - amnt_sum if is_lst else partial amnt_sum += partial commitment = crypto.encodepoint(crypto.gen_c(crypto.decodeint(lst.mask), new_amnt)) new_inp = TxSourceEntry(outputs=list(lst.outputs), real_output=lst.real_output, real_out_tx_key=lst.real_out_tx_key, real_out_additional_tx_keys=lst.real_out_additional_tx_keys, real_output_in_tx_index=lst.real_output_in_tx_index, amount=new_amnt, rct=lst.rct, mask=lst.mask, multisig_kLRki=lst.multisig_kLRki) # Amount changed -> update the commitment orig_key = new_inp.outputs[new_inp.real_output][1] new_inp.outputs[new_inp.real_output] = (0, CtKey(dest=orig_key.dest, mask=commitment)) # Randomize mixin values for i in range(new_inp.real_output + 1, len(new_inp.outputs)): new_inp.outputs[i] = (0, CtKey( mask=crypto.encodepoint(self.random_pub()), dest=crypto.encodepoint(self.random_pub()))) if new_inp.real_out_additional_tx_keys: new_inp.real_out_additional_tx_keys[i] = crypto.encodepoint(self.random_pub()) self.check_input(new_inp, new_keys, new_subs) if not crypto.point_eq( crypto.decodepoint(new_inp.outputs[new_inp.real_output][1].mask), crypto.gen_c(crypto.decodeint(new_inp.mask), new_inp.amount), ): raise ValueError("Real source entry's mask does not equal spend key's") tx.sources.append(new_inp)
def ecdh_decode_simple(rv, sk, i): """ Decodes ECDH from the transaction, checks mask (decoding validity). """ if i >= len(rv.ecdhInfo): raise ValueError("Bad index") if len(rv.outPk) != len(rv.ecdhInfo): raise ValueError("outPk vs ecdhInfo mismatch") ecdh_info = rv.ecdhInfo[i] ecdh_info = recode_ecdh(ecdh_info, False) ecdh_info = ring_ct.ecdh_decode(ecdh_info, derivation=crypto.encodeint(sk)) c_tmp = crypto.add_keys2(ecdh_info.mask, ecdh_info.amount, crypto.xmr_H()) if not crypto.point_eq(c_tmp, crypto.decodepoint(rv.outPk[i].mask)): raise ValueError("Amount decoded incorrectly") return ecdh_info.amount, ecdh_info.mask
def test_encoding(self): point = bytes( [ 0x24, 0x86, 0x22, 0x47, 0x97, 0xd0, 0x5c, 0xae, 0x3c, 0xba, 0x4b, 0xe0, 0x43, 0xbe, 0x2d, 0xb0, 0xdf, 0x38, 0x1f, 0x3f, 0x19, 0xcf, 0xa1, 0x13, 0xf8, 0x6a, 0xb3, 0x8e, 0x3d, 0x8d, 0x2b, 0xd0, ] ) self.assertEqual(point, crypto.encodepoint(crypto.decodepoint(point))) self.assertTrue( crypto.point_eq( crypto.decodepoint(point), crypto.decodepoint(crypto.encodepoint(crypto.decodepoint(point))), ) )
def decode_rct(rv, sk, i): """ c.f. http:#eprint.iacr.org/2015/1098 section 5.1.1 Uses the attached ecdh info to find the amounts represented by each output commitment must know the destination private key to find the correct amount, else will return a random number :param rv: :param sk: :param i: :return: """ decodedTuple = ecdh_decode(rv.ecdhInfo[i], sk) mask = decodedTuple.mask amount = decodedTuple.amount C = rv.outPk[i].mask H = crypto.xmr_H() Ctmp = crypto.point_add(crypto.scalarmult_base(mask), crypto.scalarmult(H, amount)) if not crypto.point_eq(crypto.point_sub(C, Ctmp), crypto.identity()): logger.warning("warning, amount decoded incorrectly, will be unable to spend") return amount
async def verify_key(self): priv = self._fetch_decrypt_key() pub = crypto.decodepoint(self._fetch()) computed_pub = crypto.identity() verified = 0 if self.c_p1 == 0: computed_pub = crypto.scalarmult_base(priv) elif self.c_p1 == 1: pub = self.A elif self.c_p1 == 2: pub = self.B else: return SW_WRONG_P1P2 if crypto.point_eq(computed_pub, pub): verified = 1 self._insert_u32(verified) self._insert(self.creds.address) return SW_OK
def test_range_proof2(self): amount = 17 + (1 << 60) proof = ring_ct.prove_range(amount) res = ring_ct.ver_range(proof[0], proof[2]) self.assertTrue(res) self.assertTrue( crypto.point_eq( proof[0], crypto.point_add(crypto.scalarmult_base(proof[1]), crypto.scalarmult_h(amount)), )) proof = ring_ct.prove_range(amount, mem_opt=False, decode=True) res = ring_ct.ver_range(proof[0], proof[2], decode=False) self.assertTrue(res) res = ring_ct.ver_range( crypto.point_add(proof[0], crypto.scalarmult_base(crypto.sc_init(4))), proof[2], decode=False, ) self.assertFalse(res)
async def test_live_refresh(self): if self.should_test_only_tx() or not int( os.getenv("TREZOR_TEST_LIVE_REFRESH", '1')): self.skipTest("Live refresh skipped") creds = self.get_trezor_creds(0) await self.agent.live_refresh_start() for att in range(22): r = crypto.random_scalar() R = crypto.scalarmult_base(r) D = crypto.scalarmult(R, creds.view_key_private) subaddr = 0, att scalar_step1 = crypto.derive_secret_key(D, att, creds.spend_key_private) # step 2: add Hs(SubAddr || a || index_major || index_minor) if subaddr == (0, 0): scalar_step2 = scalar_step1 else: subaddr_sk = monero.get_subaddress_secret_key( creds.view_key_private, major=0, minor=att) scalar_step2 = crypto.sc_add(scalar_step1, subaddr_sk) pub_ver = crypto.scalarmult_base(scalar_step2) ki = monero.generate_key_image(crypto.encodepoint(pub_ver), scalar_step2) ki2 = await self.agent.live_refresh(creds.view_key_private, crypto.encodepoint(pub_ver), crypto.encodepoint(D), att, 0, att) if not crypto.point_eq(ki, ki2): raise ValueError("Key image inconsistent") time.sleep(0.1) await self.agent.live_refresh_final()
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() """ 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.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(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 = crypto.ge_frombytes_vartime(pubs[i]) tmp2 = crypto.ge_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.ge_double_scalarmult_precomp_vartime( sig[i][1], tmp3, sig[i][0], image_pre) 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 signature(self, tx): """ Computes the signature in one pass. Implements RingCT in Python. :param tx: const data :type tx: xmrtypes.TxConstructionData :return: """ amount_in = 0 inamounts = [None] * len(self.source_permutation) index = [None] * len(self.source_permutation) in_sk = [None] * len(self.source_permutation) # type: list[xmrtypes.CtKey] for i in range(len(self.source_permutation)): idx = self.source_permutation[i] src = tx.sources[idx] amount_in += src.amount inamounts[i] = src.amount index[i] = src.real_output in_sk[i] = xmrtypes.CtKey( dest=self.input_secrets[i][0], mask=crypto.decodeint(src.mask) ) # TODO: kLRki # private key correctness test if __debug__: assert crypto.point_eq( crypto.decodepoint(src.outputs[src.real_output][1].dest), crypto.scalarmult_base(in_sk[i].dest), ) assert crypto.point_eq( crypto.decodepoint(src.outputs[src.real_output][1].mask), crypto.gen_c(in_sk[i].mask, inamounts[i]), ) destinations = [] outamounts = [] amount_out = 0 for idx, dst in enumerate(tx.splitted_dsts): destinations.append(crypto.decodepoint(self.tx.vout[idx].target.key)) outamounts.append(self.tx.vout[idx].amount) amount_out += self.tx.vout[idx].amount if self.use_simple_rct: mix_ring = [None] * (self.inp_idx + 1) for i in range(len(self.source_permutation)): src = tx.sources[self.source_permutation[i]] mix_ring[i] = [] for idx2, out in enumerate(src.outputs): mix_ring[i].append(out[1]) else: n_total_outs = len(tx.sources[0].outputs) mix_ring = [None] * n_total_outs for idx in range(n_total_outs): mix_ring[idx] = [] for i in range(len(self.source_permutation)): src = tx.sources[self.source_permutation[i]] mix_ring[idx].append(src.outputs[idx][1]) if not self.use_simple_rct and amount_in > amount_out: outamounts.append(amount_in - amount_out) # Hide amounts self.zero_out_amounts() # Tx prefix hash await self.compute_tx_prefix_hash() # Signature if self.use_simple_rct: rv = await self.gen_rct_simple( in_sk, destinations, inamounts, outamounts, amount_in - amount_out, mix_ring, None, None, index, ) else: rv = await self.gen_rct( in_sk, destinations, outamounts, mix_ring, None, None, tx.sources[0].real_output, ) # Recode for serialization rv = monero.recode_rct(rv, encode=True) self.tx.signatures = [] self.tx.rct_signatures = rv del rv # Serialize response writer = xmrserialize.MemoryReaderWriter() ar1 = xmrserialize.Archive(writer, True) await ar1.message(self.tx, msg_type=xmrtypes.Transaction) return bytes(writer.get_buffer())
async def gen_rct_header(self, destinations, outamounts): """ Initializes RV RctSig structure, processes outputs, computes range proofs, ecdh info masking. Common to gen_rct and gen_rct_simple. :param destinations: :param outamounts: :return: """ rv = xmrtypes.RctSig() rv.p = xmrtypes.RctSigPrunable() rv.message = self.tx_prefix_hash rv.outPk = [None] * len(destinations) if self.use_bulletproof: rv.p.bulletproofs = [None] * len(destinations) else: rv.p.rangeSigs = [None] * len(destinations) rv.ecdhInfo = [None] * len(destinations) # Output processing sumout = crypto.sc_0() out_sk = [None] * len(destinations) for idx in range(len(destinations)): rv.outPk[idx] = xmrtypes.CtKey(dest=crypto.encodepoint(destinations[idx])) C, mask, rsig = None, 0, None # Rangeproof if self.use_bulletproof: raise ValueError("Bulletproof not yet supported") else: C, mask, rsig = ring_ct.prove_range(outamounts[idx]) rv.p.rangeSigs[idx] = rsig if __debug__: assert ring_ct.ver_range(C, rsig) assert crypto.point_eq( C, crypto.point_add( crypto.scalarmult_base(mask), crypto.scalarmult_h(outamounts[idx]), ), ) # Mask sum rv.outPk[idx].mask = crypto.encodepoint(C) sumout = crypto.sc_add(sumout, mask) out_sk[idx] = xmrtypes.CtKey(mask=mask) # ECDH masking amount_key = crypto.encodeint(self.output_secrets[idx][0]) rv.ecdhInfo[idx] = xmrtypes.EcdhTuple( mask=mask, amount=crypto.sc_init(outamounts[idx]) ) rv.ecdhInfo[idx] = ring_ct.ecdh_encode( rv.ecdhInfo[idx], derivation=amount_key ) monero.recode_ecdh(rv.ecdhInfo[idx], encode=True) return rv, sumout, out_sk
async def describe(self, inp, unsigned_txs, keys, key_subs): print("\nInp: %s, #txs: %s, #transfers: %s" % (inp, len(unsigned_txs.txes), len(unsigned_txs.transfers))) for txid, tx in enumerate(unsigned_txs.txes): srcs = tx.sources dsts = tx.splitted_dsts extra = tx.extra change = tx.change_dts account = tx.subaddr_account subs = tx.subaddr_indices mixin = len(srcs[0].outputs) - 1 amnt_in = sum([x.amount for x in srcs]) amnt_out = sum([x.amount for x in dsts]) fee = amnt_in - amnt_out n_inp_additional = sum( [1 for x in srcs if len(x.real_out_additional_tx_keys) > 0] ) change_addr = ( addr.build_address( change.addr.m_spend_public_key, change.addr.m_view_public_key ) if change else None ) out_txs2 = await self.reformat_outs(dsts) num_stdaddresses, num_subaddresses, single_dest_subaddress = addr.classify_subaddresses( out_txs2, change_addr ) print( " tx: %s, #inp: %2d, #inp_add: %2d, #out: %2d, mixin: %2d, acc: %s, subs: %s, " "xmr_in: %10.6f, xmr_out: %10.6f, fee: %10.6f, change: %10.6f, out_clean: %10.6f" % ( txid, len(srcs), n_inp_additional, len(dsts), mixin, account, subs, wallet.conv_disp_amount(amnt_in), wallet.conv_disp_amount(amnt_out), wallet.conv_disp_amount(fee), wallet.conv_disp_amount(change.amount) if change else 0, wallet.conv_disp_amount( (amnt_out - change.amount) if change else amnt_out ), ) ) print( " Out: num_std: %2d, num_sub: %2d, single_dest_sub: %s, total: %s" % ( num_stdaddresses, num_subaddresses, 1 if single_dest_subaddress else 0, len(dsts), ) ) accounts = set() subs = set() for inp in srcs: res = await self.analyze_input(keys, key_subs, inp) accounts.add(res[0]) if res != (0, 0): subs.add(res) print(" Ins: accounts: %s, subs: %s" % (accounts, len(subs))) extras = await monero.parse_extra_fields(extra) extras_val = [] for c in extras: if isinstance(c, TxExtraPubKey): extras_val.append("TxKey") elif isinstance(c, TxExtraNonce): extras_val.append( "Nonce: %s" % binascii.hexlify(c.nonce).decode("ascii") ) elif isinstance(c, TxExtraAdditionalPubKeys): extras_val.append("AdditionalTxKeys: %s" % len(c.data)) else: extras_val.append(str(c)) print(" Extras: %s" % ", ".join(extras_val)) # Final verification for idx, inp in enumerate(tx.sources): self.check_input(inp, keys, key_subs) if not crypto.point_eq( crypto.decodepoint(inp.outputs[inp.real_output][1].mask), crypto.gen_c(crypto.decodeint(inp.mask), inp.amount), ): raise ValueError("Real source entry's mask does not equal spend key's. Inp: %d" % idx)
async def receive(self, tx, all_creds, con_data=None, exp_payment_id=None): """ Test transaction receive with known view/spend keys of destinations. :param tx: :param all_creds: :param con_data: :param exp_payment_id: :return: """ # Unserialize the transaction tx_obj = xmrtypes.Transaction() reader = xmrserialize.MemoryReaderWriter(bytearray(tx)) ar1 = xmrserialize.Archive(reader, False) await ar1.message(tx_obj, msg_type=xmrtypes.Transaction) extras = await monero.parse_extra_fields(tx_obj.extra) tx_pub = monero.find_tx_extra_field_by_type( extras, xmrtypes.TxExtraPubKey ).pub_key additional_pub_keys = monero.find_tx_extra_field_by_type( extras, xmrtypes.TxExtraAdditionalPubKeys ) num_outs = len(tx_obj.vout) num_received = 0 # Try to receive tsx outputs with each account. tx_money_got_in_outs = collections.defaultdict(lambda: 0) outs = [] change_idx = get_change_addr_idx(con_data.tsx_data.outputs, con_data.tsx_data.change_dts) for idx, creds in enumerate(all_creds): wallet_subs = {} for account in range(0, 5): monero.compute_subaddresses(creds, account, range(25), wallet_subs) derivation = crypto.generate_key_derivation( crypto.decodepoint(tx_pub), creds.view_key_private ) additional_derivations = [] if additional_pub_keys and additional_pub_keys.data: for x in additional_pub_keys.data: additional_derivations.append( crypto.generate_key_derivation( crypto.decodepoint(x), creds.view_key_private ) ) for ti, to in enumerate(tx_obj.vout): tx_scan_info = monero.check_acc_out_precomp( to, wallet_subs, derivation, additional_derivations, ti ) if not tx_scan_info.received: continue num_received += 1 tx_scan_info = monero.scan_output( creds, tx_obj, ti, tx_scan_info, tx_money_got_in_outs, outs, False ) # Check spending private key correctness self.assertTrue( crypto.point_eq( crypto.decodepoint(tx_obj.rct_signatures.outPk[ti].mask), crypto.gen_c(tx_scan_info.mask, tx_scan_info.amount), ) ) self.assertTrue( crypto.point_eq( crypto.decodepoint(tx_obj.vout[ti].target.key), crypto.scalarmult_base(tx_scan_info.in_ephemeral), ) ) if exp_payment_id is not None: payment_id = None # Not checking payment id for change transaction if exp_payment_id[0] == 1 and change_idx is not None and ti == change_idx: continue payment_id_type = None extra_nonce = monero.find_tx_extra_field_by_type(extras, xmrtypes.TxExtraNonce) if extra_nonce and monero.has_encrypted_payment_id(extra_nonce.nonce): payment_id_type = 1 payment_id = monero.get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce) payment_id = monero.encrypt_payment_id(payment_id, crypto.decodepoint(tx_pub), creds.view_key_private) elif extra_nonce and monero.has_payment_id(extra_nonce.nonce): payment_id_type = 0 payment_id = monero.get_payment_id_from_tx_extra_nonce(extra_nonce.nonce) self.assertEqual(payment_id_type, exp_payment_id[0]) self.assertEqual(payment_id, exp_payment_id[1]) # All outputs have to be successfully received self.assertEqual(num_outs, num_received)
def rekey_input(self, inp, keys, subs=None, new_keys=None, new_subs=None, mixin_change=None): subs = subs if subs else {} real_out_key = inp.outputs[inp.real_output][1] out_key = crypto.decodepoint(real_out_key.dest) tx_key = crypto.decodepoint(inp.real_out_tx_key) additional_keys = [ crypto.decodepoint(x) for x in inp.real_out_additional_tx_keys ] logger.debug("Current out key: %s" % binascii.hexlify(real_out_key.dest)) secs = monero.generate_key_image_helper( keys, subs, out_key, tx_key, additional_keys, inp.real_output_in_tx_index ) xi, ki, di = secs need_additional = additional_keys is not None and len(additional_keys) > 0 is_dst_sub = self.dest_sub_major != 0 and ( self.args.minors[0] != 0 or len(self.args.minors) > 1 ) logger.debug( "Is dst sub: %s, need additional: %s" % (is_dst_sub, need_additional) ) if is_dst_sub and self.add_additionals: need_additional = True if is_dst_sub: rand_minor = random.choice(self.args.minors) m = monero.get_subaddress_secret_key( new_keys.view_key_private, major=self.dest_sub_major, minor=rand_minor ) M = crypto.scalarmult_base(m) d = crypto.sc_add(m, new_keys.spend_key_private) D = crypto.point_add(new_keys.spend_key_public, M) C = crypto.scalarmult(D, new_keys.view_key_private) if not need_additional and not is_dst_sub: # real_out_key.dst = Hs(R*new_a || idx)G + newB r = crypto.random_scalar() tx_key = crypto.scalarmult_base(r) new_deriv = crypto.generate_key_derivation(new_keys.view_key_public, r) new_out_pr = crypto.derive_secret_key( new_deriv, inp.real_output_in_tx_index, new_keys.spend_key_private ) new_out = crypto.scalarmult_base(new_out_pr) real_out_key.dest = crypto.encodepoint(new_out) elif not need_additional and is_dst_sub: # real_out_key.dst = Hs(r*C || idx)G + newB, R=rD r = crypto.random_scalar() tx_key = crypto.scalarmult(D, r) new_deriv = crypto.generate_key_derivation(C, r) new_out_pr = crypto.derive_secret_key( new_deriv, inp.real_output_in_tx_index, d ) new_out = crypto.scalarmult_base(new_out_pr) real_out_key.dest = crypto.encodepoint(new_out) else: r = crypto.random_scalar() tx_key = crypto.scalarmult_base(r) gen_additionals = min(2, inp.real_output_in_tx_index + 1) if additional_keys is None or len(additional_keys) < gen_additionals: additional_keys = [ crypto.scalarmult_base(crypto.random_scalar()) for _ in range(gen_additionals) ] ri = crypto.random_scalar() if is_dst_sub: add_tx = crypto.scalarmult(D, ri) new_deriv = crypto.generate_key_derivation(C, ri) new_out_pr = crypto.derive_secret_key( new_deriv, inp.real_output_in_tx_index, d ) new_out = crypto.scalarmult_base(new_out_pr) if not crypto.point_eq( new_out, crypto.derive_public_key(new_deriv, inp.real_output_in_tx_index, D), ): raise ValueError("Invalid txout computation") else: add_tx = crypto.scalarmult_base(ri) new_deriv = crypto.generate_key_derivation(new_keys.view_key_public, r) new_out_pr = crypto.derive_secret_key( new_deriv, inp.real_output_in_tx_index, new_keys.spend_key_private ) new_out = crypto.scalarmult_base(new_out_pr) additional_keys[inp.real_output_in_tx_index] = add_tx real_out_key.dest = crypto.encodepoint(new_out) # Increasing the size of the mixin if mixin_change and len(inp.outputs) < mixin_change: for i in range(mixin_change - len(inp.outputs)): inp.outputs.append((0, CtKey( mask=crypto.encodepoint(self.random_pub()), dest=crypto.encodepoint(self.random_pub())))) if additional_keys: additional_keys.append(self.random_pub()) inp.real_out_tx_key = crypto.encodepoint(tx_key) inp.real_out_additional_tx_keys = [ crypto.encodepoint(x) for x in additional_keys ] logger.debug("New pub: %s" % binascii.hexlify(real_out_key.dest)) # Self-check self.check_input(inp, new_keys, new_subs) return inp
def __eq__(self, other): if other == INFINITY: return crypto.point_eq(self._key, crypto.identity()) return crypto.point_eq(self._key, other._key)