async def gen_rct( self, in_sk, destinations, amounts, mix_ring, kLRki, msout, index ): """ Full ring CT signature. Used when there is only one input transaction to spend. :param in_sk: :param destinations: :param amounts: :param mix_ring: :param kLRki: :param msout: :param index: :param out_sk: :return: """ if len(amounts) != len(destinations) and len(amounts) != len(destinations) + 1: raise ValueError("Different number of amounts/destinations") if len(self.output_secrets) != len(destinations): raise ValueError("Different number of amount_keys/destinations") if index >= len(mix_ring): raise ValueError("Bad index into mix ring") for n in range(len(mix_ring)): if len(mix_ring[n]) != len(in_sk): raise ValueError("Bad mixring size") if (not kLRki or not msout) and (kLRki or msout): raise ValueError("Only one of kLRki/mscout is present") rv, sumout, out_sk = await self.gen_rct_header(destinations, amounts) rv.type = ( xmrtypes.RctType.FullBulletproof if self.use_bulletproof else xmrtypes.RctType.Full ) if len(amounts) > len(destinations): rv.txnFee = amounts[len(destinations)] else: rv.txnFee = 0 txn_fee_key = crypto.scalarmult_h(rv.txnFee) rv.mixRing = mix_ring # TODO: msout multisig full_message = await monero.get_pre_mlsag_hash(rv) mg, msc = mlsag2.prove_rct_mg( full_message, rv.mixRing, in_sk, out_sk, rv.outPk, kLRki, None, index, txn_fee_key, ) rv.p.MGs = [mg] if __debug__: assert mlsag2.ver_rct_mg( rv.p.MGs[0], rv.mixRing, rv.outPk, txn_fee_key, full_message ) return rv
async def sign_input( self, src_entr, vini, hmac_vini, pseudo_out, pseudo_out_hmac, alpha_enc, spend_enc, ): """ Generates a signature for one input. :param src_entr: Source entry :type src_entr: monero_glue.xmr.serialize_messages.tx_construct.TxSourceEntry :param vini: tx.vin[i] for the transaction. Contains key image, offsets, amount (usually zero) :param hmac_vini: HMAC for the tx.vin[i] as returned from Trezor :param pseudo_out: pedersen commitment for the current input, uses alpha as the mask. Only in memory offloaded scenario. Tuple containing HMAC, as returned from the Trezor. :param pseudo_out_hmac: :param alpha_enc: alpha mask for the current input. Only in memory offloaded scenario, tuple as returned from the Trezor :param spend_enc: :return: Generated signature MGs[i] """ self.state.set_signature() await self.trezor.iface.transaction_step( self.STEP_SIGN, self.inp_idx + 1, self.num_inputs() ) self.inp_idx += 1 if self.inp_idx >= self.num_inputs(): raise ValueError("Invalid ins") if self.use_simple_rct and (not self.in_memory() and alpha_enc is None): raise ValueError("Inconsistent1") if self.use_simple_rct and (not self.in_memory() and pseudo_out is None): raise ValueError("Inconsistent2") if self.inp_idx >= 1 and not self.use_simple_rct: raise ValueError("Inconsistent3") inv_idx = self.source_permutation[self.inp_idx] # Check HMAC of all inputs hmac_vini_comp = await self.gen_hmac_vini(src_entr, vini, inv_idx) if not common.ct_equal(hmac_vini_comp, hmac_vini): raise ValueError("HMAC is not correct") gc.collect() self._log_trace(1) if self.use_simple_rct and not self.in_memory(): pseudo_out_hmac_comp = crypto.compute_hmac( self.hmac_key_txin_comm(inv_idx), pseudo_out ) if not common.ct_equal(pseudo_out_hmac_comp, pseudo_out_hmac): raise ValueError("HMAC is not correct") gc.collect() self._log_trace(2) from monero_glue.xmr.enc import chacha_poly alpha_c = crypto.decodeint( chacha_poly.decrypt_pack( self.enc_key_txin_alpha(inv_idx), bytes(alpha_enc) ) ) pseudo_out_c = crypto.decodepoint(pseudo_out) elif self.use_simple_rct: alpha_c = self.input_alphas[self.inp_idx] pseudo_out_c = crypto.decodepoint(self.input_pseudo_outs[self.inp_idx]) else: alpha_c = None pseudo_out_c = None # Spending secret if self.many_inputs(): from monero_glue.xmr.enc import chacha_poly input_secret = crypto.decodeint( chacha_poly.decrypt_pack(self.enc_key_spend(inv_idx), bytes(spend_enc)) ) else: input_secret = self.input_secrets[self.inp_idx] gc.collect() self._log_trace(3) # Basic setup, sanity check index = src_entr.real_output in_sk = misc.StdObj(dest=input_secret, mask=crypto.decodeint(src_entr.mask)) kLRki = src_entr.multisig_kLRki if self.multi_sig else None # Private key correctness test self.assrt( crypto.point_eq( crypto.decodepoint(src_entr.outputs[src_entr.real_output][1].dest), crypto.scalarmult_base(in_sk.dest), ), "a1", ) self.assrt( crypto.point_eq( crypto.decodepoint(src_entr.outputs[src_entr.real_output][1].mask), crypto.gen_c(in_sk.mask, src_entr.amount), ), "a2", ) gc.collect() self._log_trace(4) # RCT signature gc.collect() from monero_glue.xmr import mlsag2 mg = None if self.use_simple_rct: # Simple RingCT mix_ring = [x[1] for x in src_entr.outputs] mg, msc = mlsag2.prove_rct_mg_simple( self.full_message, mix_ring, in_sk, alpha_c, pseudo_out_c, kLRki, None, index, ) if __debug__: self.assrt( mlsag2.ver_rct_mg_simple( self.full_message, mg, mix_ring, pseudo_out_c ) ) else: # Full RingCt, only one input txn_fee_key = crypto.scalarmult_h(self.get_fee()) mix_ring = [[x[1]] for x in src_entr.outputs] mg, msc = mlsag2.prove_rct_mg( self.full_message, mix_ring, [in_sk], self.output_sk, self.output_pk, kLRki, None, index, txn_fee_key, ) if __debug__: self.assrt( mlsag2.ver_rct_mg( mg, mix_ring, self.output_pk, txn_fee_key, self.full_message ) ) gc.collect() self._log_trace(5) # Encode from monero_glue.xmr.sub.recode import recode_msg mgs = recode_msg([mg]) cout = None gc.collect() self._log_trace(6) # Multisig values returned encrypted, keys returned after finished successfully. if self.multi_sig: from monero_glue.xmr.enc import chacha_poly cout = chacha_poly.encrypt_pack(self.enc_key_cout(), crypto.encodeint(msc)) # Final state transition if self.inp_idx + 1 == self.num_inputs(): self.state.set_signature_done() await self.trezor.iface.transaction_signed() gc.collect() self._log_trace() from monero_glue.messages.MoneroTransactionSignInputAck import ( MoneroTransactionSignInputAck ) return MoneroTransactionSignInputAck( signature=await misc.dump_msg_gc(mgs[0], preallocate=488, del_msg=True), cout=cout, )
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 ))
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)