async def get_pre_mlsag_hash(rv): """ Generates final message for the Ring CT signature :param rv: :type rv: RctSig :return: """ from monero_glue.xmr.sub.keccak_hasher import get_keccak_writer from monero_serialize.xmrtypes import RctType kc_master = HashWrapper(crypto.get_keccak()) kc_master.update(rv.message) is_simple = rv.type in [ RctType.Simple, RctType.Bulletproof, RctType.Bulletproof2 ] outputs = len(rv.ecdhInfo) inputs = 0 if rv.type == RctType.Simple: inputs = len(rv.pseudoOuts) elif rv.type in [RctType.Bulletproof, RctType.Bulletproof2]: inputs = len(rv.p.pseudoOuts) kwriter = get_keccak_writer() ar = xmrserialize.Archive(kwriter, True) await rv.serialize_rctsig_base(ar, inputs, outputs) c_hash = kwriter.get_digest() kc_master.update(c_hash) kc = crypto.get_keccak() if rv.type in [RctType.Bulletproof, RctType.Bulletproof2]: for p in rv.p.bulletproofs: kc.update(p.A) kc.update(p.S) kc.update(p.T1) kc.update(p.T2) kc.update(p.taux) kc.update(p.mu) for i in range(len(p.L)): kc.update(p.L[i]) for i in range(len(p.R)): kc.update(p.R[i]) kc.update(p.a) kc.update(p.b) kc.update(p.t) else: for r in rv.p.rangeSigs: for i in range(64): kc.update(r.asig.s0[i]) for i in range(64): kc.update(r.asig.s1[i]) kc.update(r.asig.ee) for i in range(64): kc.update(r.Ci[i]) c_hash = kc.digest() kc_master.update(c_hash) return kc_master.digest()
async def load_keys_data(data, password): """ Loads wallet keys file passed as byte string :param file: :param password: :return: """ reader = xmrserialize.MemoryReaderWriter(bytearray(data)) ar = xmrserialize.Archive(reader, False) msg = xmrtypes.KeysFileData() await ar.message(msg) key = chacha.generate_key(password) buff = bytes(msg.iv + msg.account_data) dec = chacha.decrypt(key, buff) m = re.search(b'(.*)"key_data":"(.+?)",(.*)', dec) key_data = m.group(2) dat = xmrjson.unescape_json_str(key_data) reader = xmrserialize.MemoryReaderWriter(bytearray(dat)) ar = xmrrpc.Archive(reader, False) key_data = {} await ar.root() await ar.section(key_data) rest_json = m.group(1) + m.group(3) wallet_key = json.loads(rest_json) wallet_key["key_data"] = key_data return wallet_key
async def dump_msg(msg, preallocate=None, msg_type=None, prefix=None): writer = xmrserialize.MemoryReaderWriter(preallocate=preallocate) if prefix: writer.write(prefix) ar = xmrserialize.Archive(writer, True) await ar.message(msg, msg_type=msg_type) return writer.get_buffer()
async def get_transaction_prefix_hash(tx): """ Computes transaction prefix in one step """ writer = get_keccak_writer() ar1 = xmrserialize.Archive(writer, True) await ar1.message(tx, msg_type=xmrtypes.TransactionPrefixExtraBlob) return writer.get_digest()
async def serialize_tx(self, tx): """ Serializes transaction :param tx: :return: """ writer = xmrserialize.MemoryReaderWriter() ar1 = xmrserialize.Archive(writer, True) await ar1.message(tx, msg_type=xmrtypes.Transaction) return bytes(writer.get_buffer())
def _ar(self, xser=None): if self.keeping and self.ar: return self.ar if xser: ar = xser.Archive(self.kwriter, True) else: from monero_serialize import xmrserialize ar = xmrserialize.Archive(self.kwriter, True) self.ar = ar if self.keeping else None return ar
async def parse_extra_fields(extra_buff): """ Parses extra buffer to the extra fields vector :param extra_buff: :return: """ extras = [] rw = MemoryReaderWriter(bytes(extra_buff)) ar2 = xmrserialize.Archive(rw, False) while len(rw.get_buffer()) > 0: extras.append(await ar2.variant(elem_type=TxExtraField)) return extras
async def tx_sign_unsigned(self, unsigned_tx, fl=None): """ Tx sign test with given unsigned transaction data :param unsigned_tx: :param fl: :return: """ reader = xmrserialize.MemoryReaderWriter(bytearray(unsigned_tx)) ar = xmrserialize.Archive(reader, False) unsig = xmrtypes.UnsignedTxSet() await ar.message(unsig) await self.tx_sign_unsigned_msg(unsig, fl)
async def remove_field_from_tx_extra(extra, mtype): """ Removes extra field of fiven type from the buffer Reserializes with skipping the given mtype. :param extra: :param mtype: :return: """ if len(extra) == 0: return [] reader = MemoryReaderWriter(extra) writer = MemoryReaderWriter() ar_read = xmrserialize.Archive(reader, False) ar_write = xmrserialize.Archive(writer, True) while len(reader.get_buffer()) > 0: c_extras = await ar_read.variant(elem_type=TxExtraField) if not isinstance(c_extras, mtype): await ar_write.variant(c_extras, elem_type=TxExtraField) return writer.get_buffer()
async def compute_hmac_keys(self, tsx_ctr): """ Generate master key H(TsxData || r || c_tsx) :return: """ writer = monero.get_keccak_writer() ar1 = xmrserialize.Archive(writer, True) await ar1.message(self.tsx_data) await writer.awrite(crypto.encodeint(self.r)) await xmrserialize.dump_uvarint(writer, tsx_ctr) self.key_master = writer.get_digest() self.key_hmac = crypto.keccak_hash(b"hmac" + self.key_master)
async def set_input(self, src_entr): """ :param src_entr: :type src_entr: xmrtypes.TxSourceEntry :return: """ self.inp_idx += 1 if src_entr.real_output >= len(src_entr.outputs): raise ValueError( "real_output index %s bigger than output_keys.size()" % (src_entr.real_output, len(src_entr.outputs)) ) self.summary_inputs_money += src_entr.amount out_key = crypto.decodepoint(src_entr.outputs[src_entr.real_output][1].dest) tx_key = crypto.decodepoint(src_entr.real_out_tx_key) additional_keys = [ crypto.decodepoint(x) for x in src_entr.real_out_additional_tx_keys ] secs = monero.generate_key_image_helper( self.trezor.creds, self.subaddresses, out_key, tx_key, additional_keys, src_entr.real_output_in_tx_index, ) xi, ki, di = secs self.input_secrets.append((xi,)) self.input_rcts.append(src_entr.rct) # Construct tx.vin vini = xmrtypes.TxinToKey( amount=src_entr.amount, k_image=crypto.encodepoint(ki) ) vini.key_offsets = [x[0] for x in src_entr.outputs] vini.key_offsets = monero.absolute_output_offsets_to_relative(vini.key_offsets) self.tx.vin.append(vini) # HMAC(T_in,i || vin_i) kwriter = monero.get_keccak_writer() ar = xmrserialize.Archive(kwriter, True) await ar.message(src_entr, xmrtypes.TxSourceEntry) await ar.message(vini, xmrtypes.TxinToKey) hmac_key_vini = crypto.keccak_hash( self.key_hmac + b"txin" + xmrserialize.dump_uvarint_b(self.inp_idx) ) hmac_vini = crypto.compute_hmac(hmac_key_vini, kwriter.get_digest()) return vini, hmac_vini
async def tx_sign(self, unsigned_tx): """ Tx sign test with given unsigned transaction data :param unsigned_tx: :return: """ reader = xmrserialize.MemoryReaderWriter(bytearray(unsigned_tx)) ar = xmrserialize.Archive(reader, False) unsig = xmrtypes.UnsignedTxSet() await ar.message(unsig) tagent = self.init_agent() await tagent.transfer_unsigned(unsig)
async def tx_sign_unsigned(self, unsigned_tx, fl=None): """ Tx sign test with given unsigned transaction data :param unsigned_tx: :param fl: :return: """ self.skipTest('HP <= 8 not supported anymore') reader = xmrserialize.MemoryReaderWriter(bytearray(unsigned_tx)) ar = xmrserialize.Archive(reader, False, self._get_bc_ver()) unsig = xmrtypes.UnsignedTxSet() await ar.message(unsig) await self.tx_sign_unsigned_msg(unsig, fl)
async def compute_sec_keys(self, tsx_data, tsx_ctr): """ Generate master key H(TsxData || r || c_tsx) :return: """ from monero_glue.xmr.sub.keccak_hasher import get_keccak_writer from monero_serialize import xmrserialize writer = get_keccak_writer() ar1 = xmrserialize.Archive(writer, True) await ar1.message(tsx_data) await writer.awrite(crypto.encodeint(self.r)) await xmrserialize.dump_uvarint(writer, tsx_ctr) self.key_master = crypto.keccak_2hash( writer.get_digest() + crypto.encodeint(crypto.random_scalar()) ) self.key_hmac = crypto.keccak_2hash(b"hmac" + self.key_master) self.key_enc = crypto.keccak_2hash(b"enc" + self.key_master)
async def gen_hmac_tsxdest(self, dst_entr, idx): """ Generates HMAC for TxDestinationEntry[i] :param dst_entr: :param idx: :return: """ from monero_glue.xmr.sub.keccak_hasher import get_keccak_writer from monero_serialize import xmrserialize from monero_serialize.xmrtypes import TxDestinationEntry kwriter = get_keccak_writer() ar = xmrserialize.Archive(kwriter, True) await ar.message(dst_entr, TxDestinationEntry) hmac_key = self.hmac_key_txdst(idx) hmac_tsxdest = crypto.compute_hmac(hmac_key, kwriter.get_digest()) return hmac_tsxdest
async def add_additional_tx_pub_keys_to_extra(tx_extra, additional_pub_keys=None, pub_enc=None): """ Adds all pubkeys to the extra :param tx_extra: :param additional_pub_keys: :param pub_enc: None :return: """ pubs_msg = TxExtraAdditionalPubKeys( data=pub_enc if pub_enc else [crypto.encodepoint(x) for x in additional_pub_keys]) rw = MemoryReaderWriter() ar = xmrserialize.Archive(rw, True) # format: variant_tag (0x4) | array len varint | 32B | 32B | ... await ar.variant(pubs_msg, TxExtraField) tx_extra += bytes(rw.get_buffer()) return tx_extra
async def gen_hmac_vini(self, src_entr, vini, idx): """ Computes hmac (TxSourceEntry[i] || tx.vin[i]) :param src_entr: :param vini: :param idx: :return: """ from monero_glue.xmr.sub.keccak_hasher import get_keccak_writer from monero_serialize import xmrserialize from monero_serialize.xmrtypes import TxSourceEntry from monero_serialize.xmrtypes import TxinToKey kwriter = get_keccak_writer() ar = xmrserialize.Archive(kwriter, True) await ar.message(src_entr, TxSourceEntry) await ar.message(vini, TxinToKey) hmac_key_vini = self.hmac_key_txin(idx) hmac_vini = crypto.compute_hmac(hmac_key_vini, kwriter.get_digest()) return hmac_vini
async def gen_hmac_vouti(self, dst_entr, tx_out, idx): """ Generates HMAC for (TxDestinationEntry[i] || tx.vout[i]) :param dst_entr: :param tx_out: :param idx: :return: """ from monero_glue.xmr.sub.keccak_hasher import get_keccak_writer from monero_serialize import xmrserialize from monero_serialize.xmrtypes import TxDestinationEntry from monero_serialize.xmrtypes import TxOut kwriter = get_keccak_writer() ar = xmrserialize.Archive(kwriter, True) await ar.message(dst_entr, TxDestinationEntry) await ar.message(tx_out, TxOut) hmac_key_vouti = self.hmac_key_txout(idx) hmac_vouti = crypto.compute_hmac(hmac_key_vouti, kwriter.get_digest()) return hmac_vouti
async def gen_keys_file(password, wkeyfile): """ Generates wallet keys file as bytestring :param password: :param wkeyfile: :return: """ key_data = wkeyfile.key_data # type: WalletKeyData js = wkeyfile.to_json() del js["key_data"] # encode wallet key file wth classical json encoder, key data added later with monero encoding. enc = json.dumps(js, cls=xmrjson.AutoJSONEncoder) # key_data KV serialization. Message -> Model. modeler = xmrrpc.Modeler(writing=True, modelize=True) mdl = await modeler.message(msg=key_data) # Model -> binary writer = xmrserialize.MemoryReaderWriter() ar = xmrrpc.Archive(writer, True) await ar.root() await ar.section(mdl) ser = bytes(writer.get_buffer()) ser2 = xmrjson.escape_string_json(ser) enc2 = b'{"key_data":"' + ser2 + b'",' + enc[1:].encode("utf8") key = chacha.generate_key(password) enc_enc = chacha.encrypt(key, enc2) writer = xmrserialize.MemoryReaderWriter() ar = xmrserialize.Archive(writer, True) msg = xmrtypes.KeysFileData() msg.iv = enc_enc[0:8] msg.account_data = enc_enc[8:] await ar.message(msg) return bytes(writer.get_buffer())
async def parse_block(self, bindata): reader = xmrserialize.MemoryReaderWriter(bytearray(bindata)) ar = xmrserialize.Archive(reader, False) return await ar.message(None, xmrtypes.Block)
async def test_node_transaction(self): tx_j = pkg_resources.resource_string( __name__, os.path.join("data", "tsx_01.json")) tx_c = pkg_resources.resource_string( __name__, os.path.join("data", "tsx_01_plain.txt")) tx_u_c = pkg_resources.resource_string( __name__, os.path.join("data", "tsx_01_uns.txt")) tx_js = json.loads(tx_j.decode("utf8")) reader = xmrserialize.MemoryReaderWriter( bytearray(binascii.unhexlify(tx_c))) ar = xmrserialize.Archive(reader, False, self._get_bc_ver()) tx = xmrtypes.Transaction() await ar.message(tx) reader = xmrserialize.MemoryReaderWriter( bytearray(binascii.unhexlify(tx_u_c))) ar = xmrserialize.Archive(reader, False, self._get_bc_ver()) uns = xmrtypes.UnsignedTxSet() await ar.message(uns) # Test message hash computation tx_prefix_hash = await monero.get_transaction_prefix_hash(tx) message = binascii.unhexlify(tx_js["tx_prefix_hash"]) self.assertEqual(tx_prefix_hash, message) # RingCT, range sigs, hash rv = tx.rct_signatures rv.message = message rv.mixRing = self.mixring(tx_js) digest = await monero.get_pre_mlsag_hash(rv) full_message = binascii.unhexlify(tx_js["pre_mlsag_hash"]) self.assertEqual(digest, full_message) # Recompute missing data monero.expand_transaction(tx) # Unmask ECDH data, check range proofs for i in range(len(tx_js["amount_keys"])): ecdh = monero.copy_ecdh(rv.ecdhInfo[i]) monero.recode_ecdh(ecdh, encode=False) ecdh = ring_ct.ecdh_decode(ecdh, derivation=binascii.unhexlify( tx_js["amount_keys"][i])) self.assertEqual(crypto.sc_get64(ecdh.amount), tx_js["outamounts"][i]) self.assertTrue( crypto.sc_eq( ecdh.mask, crypto.decodeint( binascii.unhexlify(tx_js["outSk"][i])[32:]), )) C = crypto.decodepoint(rv.outPk[i].mask) rsig = rv.p.rangeSigs[i] res = ring_ct.ver_range(C, rsig) self.assertTrue(res) res = ring_ct.ver_range( crypto.point_add(C, crypto.scalarmult_base(crypto.sc_init(3))), rsig) self.assertFalse(res) is_simple = len(tx.vin) > 1 monero.recode_rct(rv, encode=False) if is_simple: for index in range(len(rv.p.MGs)): pseudo_out = crypto.decodepoint( binascii.unhexlify( tx_js["tx"]["rct_signatures"]["pseudoOuts"][index])) r = mlsag2.ver_rct_mg_simple(full_message, rv.p.MGs[index], rv.mixRing[index], pseudo_out) self.assertTrue(r) r = mlsag2.ver_rct_mg_simple(full_message, rv.p.MGs[index], rv.mixRing[index - 1], pseudo_out) self.assertFalse(r) else: txn_fee_key = crypto.scalarmult_h(rv.txnFee) r = mlsag2.ver_rct_mg(rv.p.MGs[0], rv.mixRing, rv.outPk, txn_fee_key, digest) self.assertTrue(r) r = mlsag2.ver_rct_mg( rv.p.MGs[0], rv.mixRing, rv.outPk, crypto.scalarmult_h(rv.txnFee - 100), digest, ) self.assertFalse(r)
def __init__(self, ctx=None): from monero_serialize import xmrserialize self.kwriter = get_keccak_writer(ctx=ctx) self.ar = xmrserialize.Archive(self.kwriter, True)
async def test_tx_prefix(self): return url = "http://localhost:48084/json_rpc" req = { "jsonrpc": "2.0", "id": "0", "method": "transfer_unsigned", "params": { "destinations": [ { "amount": 2110000000000, "address": "BZZeyHTQYZ9W9KX2M69WWxWat1Z6JQYsi4LjnZxuVTmCbsNxrUyLFbXiZHRwXgBcaESRz8HtHxTDGSCtgxDdEFpQFrKqXoX", }, { "amount": 2120000000000, "address": "BZg53n1EgLJhYDZNCi3VvxXFMdmmgk6HhhFCvvw9sMf1RQFp7LyjGvrNuF7TzukfaGh7Gsin2bEDpUNRv9oc8qSGMKCnktw", }, { "amount": 2130000000000, "address": "9wviCeWe2D8XS82k2ovp5EUYLzBt9pYNW2LXUFsZiv8S3Mt21FZ5qQaAroko1enzw3eGr9qC7X1D7Geoo2RrAotYPwq9Gm8", }, ], "account_index": 0, "subaddr_indices": [], "priority": 5, "mixin": 4, "unlock_time": 0, # "payment_id": "deadc0dedeadc0d1", "get_tx_keys": True, "do_not_relay": True, "get_tx_hex": True, "get_tx_metadata": True, }, } resp = requests.post(url, json=req) js = resp.json() # Transaction parsing blobs = js["result"]["tx_blob_list"] tx_blob = blobs[0] tx_unsigned = js["result"]["tx_unsigned"] tsx_bin = base64.b16decode(tx_blob, True) reader = xmrserialize.MemoryReaderWriter(bytearray(tsx_bin)) ar = xmrserialize.Archive(reader, False) msg = xmrtypes.Transaction() await ar.message(msg) # Unsigned transaction parsing tsx_unsigned_bin = base64.b16decode(tx_unsigned, True) reader = xmrserialize.MemoryReaderWriter(bytearray(tsx_unsigned_bin)) ar = xmrserialize.Archive(reader, False) unsig = xmrtypes.UnsignedTxSet() await ar.message(unsig) tagent = self.init_agent() txes = await tagent.sign_unsigned_tx(unsig) resp = requests.post( "http://localhost:48081/sendrawtransaction", json={ "tx_as_hex": binascii.hexlify(txes[0]).decode("utf8"), "do_not_relay": False, }, ) print(resp) print("Txblob: \n %s\n" % tx_blob) print("TxUns: \n %s\n" % tx_unsigned) print("TxMeta: \n %s\n" % js["result"]["tx_metadata_list"][0]) print("Done")
async def store_cdata(self, cdata, signed_tx, tx, transfers): """ Stores transaction data for later usage. - cdata.enc_salt1, cdata.enc_salt2, cdata.enc_keys - tx_keys are AEAD protected, key derived from spend key - only token can open. - construction data for further proofs. :param cdata: :param signed_tx: :param tx: :param transfers: :return: """ hash = cdata.tx_prefix_hash prefix = binascii.hexlify(hash[:12]) tx_key_salt = crypto.random_bytes(32) tx_key_inp = hash + crypto.encodeint(self.priv_view) tx_view_key = crypto.pbkdf2(tx_key_inp, tx_key_salt, 2048) unsigned_data = xmrtypes.UnsignedTxSet() unsigned_data.txes = [tx] unsigned_data.transfers = transfers if transfers is not None else [] writer = xmrserialize.MemoryReaderWriter() ar = xmrboost.Archive(writer, True) await ar.root() await ar.message(unsigned_data) unsigned_key = crypto.keccak_2hash(b'unsigned;' + tx_view_key) ciphertext = chacha_poly.encrypt_pack(unsigned_key, bytes(writer.get_buffer())) # Serialize signed transaction writer = xmrserialize.MemoryReaderWriter() ar = xmrserialize.Archive(writer, True) await ar.root() await ar.message(signed_tx) signed_tx_bytes = writer.get_buffer() signed_tx_hmac_key = crypto.keccak_2hash(b'hmac;' + tx_view_key) signed_tx_hmac = crypto.compute_hmac(signed_tx_hmac_key, signed_tx_bytes) try: js = { "time": int(time.time()), "hash": binascii.hexlify(hash).decode("ascii"), "enc_salt1": binascii.hexlify(cdata.enc_salt1).decode("ascii"), "enc_salt2": binascii.hexlify(cdata.enc_salt2).decode("ascii"), "tx_keys": binascii.hexlify(cdata.enc_keys).decode("ascii"), "unsigned_data": binascii.hexlify(ciphertext).decode("ascii"), "tx_salt": binascii.hexlify(tx_key_salt).decode("ascii"), "tx_signed": binascii.hexlify(signed_tx_bytes).decode("ascii"), "tx_signed_hmac": binascii.hexlify(signed_tx_hmac).decode("ascii"), } with open("transaction_%s.json" % prefix.decode("ascii"), "w") as fh: json.dump(js, fh, indent=2) fh.write("\n") except Exception as e: self.trace_logger.log(e) print("Unable to save transaction data for transaction %s" % binascii.hexlify(hash).decode("ascii"))
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 dump_extra_fields(self, extras): writer = xmrserialize.MemoryReaderWriter(preallocate=None) ar = xmrserialize.Archive(writer, True) await ar.container(extras, container_type=TxExtraFields) return writer.get_buffer()
async def verify(self, tx, con_data=None, creds=None): """ Transaction verification :param tx: :param con_data: :param creds: :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) monero.expand_transaction(tx_obj) tx_pub = crypto.decodepoint(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 ) additional_pub_keys = [crypto.decodepoint(x) for x in additional_pub_keys.data] if additional_pub_keys is not None else None # Verify range proofs out_idx = 0 is_bp = tx_obj.rct_signatures.type in [RctType.SimpleBulletproof, RctType.FullBulletproof] if not is_bp: for idx, rsig in enumerate(tx_obj.rct_signatures.p.rangeSigs): out_pk = tx_obj.rct_signatures.outPk[idx] C = crypto.decodepoint(out_pk.mask) rsig = tx_obj.rct_signatures.p.rangeSigs[idx] res = ring_ct.ver_range(C, rsig, use_bulletproof=is_bp) self.assertTrue(res) else: for idx, rsig in enumerate(tx_obj.rct_signatures.p.bulletproofs): rsig_num_outs = min(len(tx_obj.rct_signatures.outPk), 1 << (len(rsig.L) - 6)) outs = tx_obj.rct_signatures.outPk[out_idx : out_idx + rsig_num_outs] rsig.V = [crypto.encodepoint(ring_ct.bp_comm_to_v(crypto.decodepoint(xx.mask))) for xx in outs] res = ring_ct.ver_range(None, rsig, use_bulletproof=is_bp) self.assertTrue(res) # Prefix hash prefix_hash = await monero.get_transaction_prefix_hash(tx_obj) is_simple = len(tx_obj.vin) > 1 or is_bp self.assertEqual(prefix_hash, con_data.tx_prefix_hash) tx_obj.rct_signatures.message = prefix_hash # MLSAG hash mlsag_hash = await monero.get_pre_mlsag_hash(tx_obj.rct_signatures) # Decrypt transaction key tx_key = misc.compute_tx_key(creds.spend_key_private, prefix_hash, salt=con_data.enc_salt1, rand_mult=con_data.enc_salt2)[0] key_buff = chacha_poly.decrypt_pack(tx_key, con_data.enc_keys) tx_priv_keys = [crypto.decodeint(x) for x in common.chunk(key_buff, 32) if x] tx_priv = tx_priv_keys[0] tx_additional_priv = tx_priv_keys[1:] # Verify mlsag signature monero.recode_msg(tx_obj.rct_signatures.p.MGs, encode=False) for idx in range(len(tx_obj.vin)): if is_simple: mix_ring = [MoneroRctKeyPublic(dest=x[1].dest, commitment=x[1].mask) for x in con_data.tx_data.sources[idx].outputs] if is_bp: pseudo_out = crypto.decodepoint(bytes(tx_obj.rct_signatures.p.pseudoOuts[idx])) else: pseudo_out = crypto.decodepoint(bytes(tx_obj.rct_signatures.pseudoOuts[idx])) self.assertTrue(mlsag2.ver_rct_mg_simple( mlsag_hash, tx_obj.rct_signatures.p.MGs[idx], mix_ring, pseudo_out )) else: txn_fee_key = crypto.scalarmult_h(tx_obj.rct_signatures.txnFee) mix_ring = [[MoneroRctKeyPublic(dest=x[1].dest, commitment=x[1].mask)] for x in con_data.tx_data.sources[idx].outputs] self.assertTrue(mlsag2.ver_rct_mg( tx_obj.rct_signatures.p.MGs[idx], mix_ring, tx_obj.rct_signatures.outPk, txn_fee_key, mlsag_hash ))
def __init__(self, ctx=None): self.kwriter = get_keccak_writer(ctx=ctx) self.ar = xmrserialize.Archive(self.kwriter, True)
async def parse_msg(bts, msg): reader = xmrserialize.MemoryReaderWriter(bytearray(bts)) ar = xmrserialize.Archive(reader, False) return await ar.message(msg)
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)