def precompute_subaddr(self, account, indices): """ Precomputes subaddresses for account (major) and list of indices (minors) Subaddresses have to be stored in encoded form - unique representation. Single point can have multiple extended coordinates representation - would not match during subaddress search. :param account: :param indices: :return: """ monero.compute_subaddresses(self.creds, account, indices, self.subaddresses)
async def init(self, ctx, msg: MoneroKeyImageExportInitRequest): self.ctx = ctx await self.derive_creds(msg) confirmation = await self.iface.confirm_ki_sync(msg, ctx=ctx) if not confirmation: return Failure(code=FailureType.ActionCancelled, message="rejected") self.num = msg.num self.hash = msg.hash self.enc_key = crypto.random_bytes(32) # Sub address precomputation if msg.subs and len(msg.subs) > 0: for sub in msg.subs: monero.compute_subaddresses( self.creds, sub.account, sub.minor_indices, self.subaddresses ) return MoneroKeyImageExportInitAck()
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)
def precompute_subaddr(self, keys, subs, major_cnt=10, minor_cnt=200): if keys is None: return None for i in range(major_cnt): monero.compute_subaddresses(keys, i, list(range(0, minor_cnt)), subs)
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)
async def describe(self, unsigned_txs, keys, key_subs=None): inp = unsigned_txs.txes[0].sources if key_subs is None: key_subs = {} for tx in enumerate(unsigned_txs.txes): monero.compute_subaddresses(keys, tx.subaddr_account, list(tx.subaddr_indices), 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 gen_input(self, value=1, sub_major=None, sub_minor=0, additionals=False): creds = self.src_keys r = self.random_scalar() R = crypto.scalarmult_base(r) additional_keys = [] Additional = None sub_major = sub_major if sub_major is not None else self.account_idx is_sub = sub_major != 0 or sub_minor != 0 if sub_major != self.account_idx: logger.warning( "Generating input with different major subindex, cannot be spent in the resulting " "transaction") kssec = monero.get_subaddress_secret_key(creds.view_key_private, major=sub_major, minor=sub_minor) kssub = crypto.sc_add( kssec, creds.spend_key_private) if is_sub else creds.spend_key_private kvsub = crypto.sc_mul(creds.view_key_private, kssub) if is_sub else creds.view_key_private KSSUB, KVSUB = monero.generate_sub_address_keys( creds.view_key_private, creds.spend_key_public, sub_major, sub_minor) if not crypto.point_eq(KSSUB, crypto.scalarmult_base(kssub)): raise ValueError("Invariant error") oidx = self.prng.randint(0, 12) additionals_cnt = self.prng.randint(oidx + 1, 2 * oidx + 1) deriv = crypto.generate_key_derivation(KVSUB, r) kout = crypto.derive_secret_key(deriv, oidx, kssub) KOUT = crypto.derive_public_key(deriv, oidx, KSSUB) if not crypto.point_eq(KOUT, crypto.scalarmult_base(kout)): raise ValueError("Invariant error") if additionals: if is_sub: Additional = crypto.scalarmult(KSSUB, r) else: Additional = crypto.scalarmult_base(r) else: if is_sub: R = crypto.scalarmult(KSSUB, r) amnt = crypto.sc_init(value) msk = self.random_scalar() # commitment mask C = crypto.add_keys2(msk, amnt, crypto.xmr_H()) ring = [] for i in range(self.ring_size - 1): tk = CtKey( dest=crypto.encodepoint(self.random_pub()), mask=crypto.encodepoint(self.random_pub()), ) ring.append(tk) index = self.prng.randint(0, len(ring)) ring.insert( index, CtKey(dest=crypto.encodepoint(KOUT), mask=crypto.encodepoint(C))) if additionals: additional_keys = [ self.random_pub() for _ in range(additionals_cnt) ] additional_keys[oidx] = Additional src = TxSourceEntry() src.outputs = [(self.random_glob_idx(), x) for x in ring] src.real_output = index src.real_out_tx_key = crypto.encodepoint(R) src.real_out_additional_tx_keys = [ crypto.encodepoint(x) for x in additional_keys ] src.real_output_in_tx_index = oidx src.amount = value src.rct = True src.mask = crypto.encodeint(msk) src.multisig_kLRki = MultisigKLRki(K=crypto.ZERO, L=crypto.ZERO, R=crypto.ZERO, ki=crypto.ZERO) td = TransferDetails() td.m_internal_output_index = oidx td.m_global_output_index = src.outputs[index][0] td.m_mask = src.mask td.m_amount = value td.m_subaddr_index = SubaddressIndex(major=sub_major, minor=sub_minor) td.m_rct = True td.m_txid = self.random_bytes(32) td.m_block_height = self.prng.randint(0, 0xFFFF) td.m_spent = False td.m_spent_height = 0 td.m_key_image_known = True td.m_key_image_requested = False td.m_key_image_partial = False td.m_multisig_k = [] td.m_multisig_info = [] td.m_uses = [] td.m_pk_index = 0 td.m_tx = self.gen_tx_prefix(self.prng.randint(1, 10), additionals_cnt) td.m_tx.vout[oidx].target.key = crypto.encodepoint(KOUT) extras = [] extras.append(TxExtraNonce(nonce=self.random_bytes(8))) extras.append(TxExtraPubKey(pub_key=src.real_out_tx_key)) if src.real_out_additional_tx_keys: extras.append( TxExtraAdditionalPubKeys(data=src.real_out_additional_tx_keys)) td.m_tx.extra = await self.dump_extra_fields(extras) tmpsubs = {} monero.compute_subaddresses(creds, sub_major, [sub_minor], tmpsubs) xi, ki, rderiv = self.check_input(src, creds, tmpsubs) if not crypto.sc_eq(xi, kout): raise ValueError("Invariant error") td.m_key_image = crypto.encodepoint(ki) self.sources.append(src) self.selected_transfers.append(len(self.transfers)) self.transfers.append(td) self.sources_creds.append(creds) if not crypto.point_eq( crypto.decodepoint(src.outputs[src.real_output][1].dest), crypto.scalarmult_base(kout)): raise ValueError("Invariant error") return self