async def sign_transaction_data(self, tx, multisig=False, exp_tx_prefix_hash=None, use_tx_keys=None): """ Uses Trezor to sign the transaction :param tx: :type tx: xmrtypes.TxConstructionData :param multisig: :param exp_tx_prefix_hash: :param use_tx_keys: :return: """ self.ct = TData() self.ct.tx_data = tx payment_id = [] extras = await monero.parse_extra_fields(tx.extra) 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 = monero.get_encrypted_payment_id_from_tx_extra_nonce( extra_nonce.nonce) elif extra_nonce and monero.has_payment_id(extra_nonce.nonce): payment_id = monero.get_payment_id_from_tx_extra_nonce( extra_nonce.nonce) # Init transaction tsx_data = TsxData() tsx_data.version = 1 tsx_data.payment_id = payment_id tsx_data.unlock_time = tx.unlock_time tsx_data.outputs = tx.splitted_dsts tsx_data.change_dts = tx.change_dts tsx_data.num_inputs = len(tx.sources) tsx_data.mixin = len(tx.sources[0].outputs) tsx_data.fee = sum([x.amount for x in tx.sources]) - sum( [x.amount for x in tx.splitted_dsts]) tsx_data.account = tx.subaddr_account tsx_data.minor_indices = tx.subaddr_indices tsx_data.is_multisig = multisig tsx_data.is_bulletproof = False tsx_data.exp_tx_prefix_hash = common.defval(exp_tx_prefix_hash, b"") tsx_data.use_tx_keys = common.defval(use_tx_keys, []) self.ct.tx.unlock_time = tx.unlock_time self.ct.tsx_data = tsx_data tsx_data_pb = await tmisc.translate_tsx_data_pb(tsx_data) init_msg = MoneroTransactionInitRequest( version=0, address_n=self.address_n, network_type=self.network_type, tsx_data=tsx_data_pb, ) t_res = await self.trezor.tsx_sign( MoneroTransactionSignRequest(init=init_msg) ) # type: MoneroTransactionInitAck self.handle_error(t_res) in_memory = t_res.in_memory self.ct.tx_out_entr_hmacs = t_res.hmacs # Set transaction inputs for idx, src in enumerate(tx.sources): src_bin = await tmisc.dump_msg(src) msg = MoneroTransactionSetInputRequest(src_entr=src_bin) t_res = await self.trezor.tsx_sign( MoneroTransactionSignRequest(set_input=msg) ) # type: MoneroTransactionSetInputAck self.handle_error(t_res) vini = await tmisc.parse_msg(t_res.vini, xmrtypes.TxinToKey()) self.ct.tx.vin.append(vini) self.ct.tx_in_hmacs.append(t_res.vini_hmac) self.ct.pseudo_outs.append( (t_res.pseudo_out, t_res.pseudo_out_hmac)) self.ct.alphas.append(t_res.alpha_enc) self.ct.spend_encs.append(t_res.spend_enc) # Sort key image self.ct.source_permutation = list(range(len(tx.sources))) self.ct.source_permutation.sort( key=lambda x: self.ct.tx.vin[x].k_image, reverse=True) def swapper(x, y): self.ct.tx.vin[x], self.ct.tx.vin[y] = self.ct.tx.vin[ y], self.ct.tx.vin[x] self.ct.tx_in_hmacs[x], self.ct.tx_in_hmacs[y] = ( self.ct.tx_in_hmacs[y], self.ct.tx_in_hmacs[x], ) self.ct.pseudo_outs[x], self.ct.pseudo_outs[y] = ( self.ct.pseudo_outs[y], self.ct.pseudo_outs[x], ) self.ct.alphas[x], self.ct.alphas[y] = self.ct.alphas[ y], self.ct.alphas[x] self.ct.spend_encs[x], self.ct.spend_encs[y] = ( self.ct.spend_encs[y], self.ct.spend_encs[x], ) tx.sources[x], tx.sources[y] = tx.sources[y], tx.sources[x] common.apply_permutation(self.ct.source_permutation, swapper) if not in_memory: msg = MoneroTransactionInputsPermutationRequest( perm=self.ct.source_permutation) t_res = await self.trezor.tsx_sign( MoneroTransactionSignRequest(input_permutation=msg)) self.handle_error(t_res) # Set vin_i back - tx prefix hashing # Done only if not in-memory. if not in_memory: for idx in range(len(self.ct.tx.vin)): msg = MoneroTransactionInputViniRequest( src_entr=await tmisc.dump_msg(tx.sources[idx]), vini=await tmisc.dump_msg(self.ct.tx.vin[idx]), vini_hmac=self.ct.tx_in_hmacs[idx], pseudo_out=self.ct.pseudo_outs[idx][0] if not in_memory else None, pseudo_out_hmac=self.ct.pseudo_outs[idx][1] if not in_memory else None, ) t_res = await self.trezor.tsx_sign( MoneroTransactionSignRequest(input_vini=msg)) self.handle_error(t_res) # Set transaction outputs for idx, dst in enumerate(tx.splitted_dsts): msg = MoneroTransactionSetOutputRequest( dst_entr=await tmisc.dump_msg(dst), dst_entr_hmac=self.ct.tx_out_entr_hmacs[idx], ) t_res = await self.trezor.tsx_sign( MoneroTransactionSignRequest(set_output=msg) ) # type: MoneroTransactionSetOutputAck self.handle_error(t_res) self.ct.tx.vout.append(await tmisc.parse_msg(t_res.tx_out, xmrtypes.TxOut())) self.ct.tx_out_hmacs.append(t_res.vouti_hmac) self.ct.tx_out_rsigs.append(await tmisc.parse_msg( t_res.rsig, xmrtypes.RangeSig())) self.ct.tx_out_pk.append(await tmisc.parse_msg(t_res.out_pk, xmrtypes.CtKey())) self.ct.tx_out_ecdh.append(await tmisc.parse_msg(t_res.ecdh_info, xmrtypes.EcdhTuple())) # Rsig verification try: rsig = self.ct.tx_out_rsigs[-1] if not ring_ct.ver_range(C=None, rsig=rsig): logger.warning("Rsing not valid") except Exception as e: logger.error("Exception rsig: %s" % e) traceback.print_exc() t_res = await self.trezor.tsx_sign( MoneroTransactionSignRequest( all_out_set=MoneroTransactionAllOutSetRequest()) ) # type: MoneroTransactionAllOutSetAck self.handle_error(t_res) rv = xmrtypes.RctSig() rv.p = xmrtypes.RctSigPrunable() rv.txnFee = t_res.rv.txn_fee rv.message = t_res.rv.message rv.type = t_res.rv.rv_type self.ct.tx.extra = list(bytearray(t_res.extra)) # Verify transaction prefix hash correctness, tx hash in one pass self.ct.tx_prefix_hash = await monero.get_transaction_prefix_hash( self.ct.tx) if not common.ct_equal(t_res.tx_prefix_hash, self.ct.tx_prefix_hash): raise ValueError("Transaction prefix has does not match") # RctSig if self.is_simple(rv): if self.is_bulletproof(rv): rv.p.pseudoOuts = [x[0] for x in self.ct.pseudo_outs] else: rv.pseudoOuts = [x[0] for x in self.ct.pseudo_outs] # Range proof rv.p.rangeSigs = [] rv.outPk = [] rv.ecdhInfo = [] for idx in range(len(self.ct.tx_out_rsigs)): rv.p.rangeSigs.append(self.ct.tx_out_rsigs[idx]) rv.outPk.append(self.ct.tx_out_pk[idx]) rv.ecdhInfo.append(self.ct.tx_out_ecdh[idx]) # MLSAG message check t_res = await self.trezor.tsx_sign( MoneroTransactionSignRequest( mlsag_done=MoneroTransactionMlsagDoneRequest()) ) # type: MoneroTransactionMlsagDoneAck self.handle_error(t_res) mlsag_hash = t_res.full_message_hash mlsag_hash_computed = await monero.get_pre_mlsag_hash(rv) if not common.ct_equal(mlsag_hash, mlsag_hash_computed): raise ValueError("Pre MLSAG hash has does not match") # Sign each input couts = [] rv.p.MGs = [] for idx, src in enumerate(tx.sources): msg = MoneroTransactionSignInputRequest( await tmisc.dump_msg(src), await tmisc.dump_msg(self.ct.tx.vin[idx]), self.ct.tx_in_hmacs[idx], self.ct.pseudo_outs[idx][0] if not in_memory else None, self.ct.pseudo_outs[idx][1] if not in_memory else None, self.ct.alphas[idx], self.ct.spend_encs[idx], ) t_res = await self.trezor.tsx_sign( MoneroTransactionSignRequest(sign_input=msg) ) # type: MoneroTransactionSignInputAck self.handle_error(t_res) mg = await tmisc.parse_msg(t_res.signature, xmrtypes.MgSig()) rv.p.MGs.append(mg) couts.append(t_res.cout) self.ct.tx.signatures = [] self.ct.tx.rct_signatures = rv t_res = await self.trezor.tsx_sign( MoneroTransactionSignRequest( final_msg=MoneroTransactionFinalRequest()) ) # type: MoneroTransactionFinalAck self.handle_error(t_res) if multisig: cout_key = t_res.cout_key for ccout in couts: self.ct.couts.append( chacha_poly.decrypt(cout_key, ccout[0], ccout[1], ccout[2])) self.ct.enc_salt1, self.ct.enc_salt2 = t_res.salt, t_res.rand_mult self.ct.enc_keys = t_res.tx_enc_keys return self.ct.tx
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 sign_transaction_data( self, tx, multisig=False, exp_tx_prefix_hash=None, use_tx_keys=None, aux_data=None, ): """ Uses Trezor to sign the transaction :param tx: :type tx: xmrtypes.TxConstructionData :param multisig: :param exp_tx_prefix_hash: :param use_tx_keys: :param aux_data: :return: """ self.ct = TData() self.ct.tx_data = tx payment_id = [] extras = await monero.parse_extra_fields(tx.extra) 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 = monero.get_encrypted_payment_id_from_tx_extra_nonce( extra_nonce.nonce) elif extra_nonce and monero.has_payment_id(extra_nonce.nonce): payment_id = monero.get_payment_id_from_tx_extra_nonce( extra_nonce.nonce) # Init transaction tsx_data = MoneroTransactionData() tsx_data.version = 1 tsx_data.client_version = self.client_version tsx_data.payment_id = payment_id tsx_data.unlock_time = tx.unlock_time tsx_data.outputs = [ tmisc.translate_monero_dest_entry_pb(x) for x in tx.splitted_dsts ] tsx_data.change_dts = tmisc.translate_monero_dest_entry_pb( tx.change_dts) tsx_data.num_inputs = len(tx.sources) tsx_data.mixin = len(tx.sources[0].outputs) tsx_data.fee = sum([x.amount for x in tx.sources]) - sum( [x.amount for x in tx.splitted_dsts]) tsx_data.account = tx.subaddr_account tsx_data.minor_indices = tx.subaddr_indices tsx_data.integrated_indices = self._get_integrated_idx( tsx_data.outputs, aux_data) self.ct.tx.unlock_time = tx.unlock_time # Rsig num_outputs = len(tsx_data.outputs) use_bp = common.defattr(tx, "use_bulletproofs", False) self.ct.rsig_type = get_rsig_type(use_bp, num_outputs) self.ct.rsig_batches = generate_rsig_batch_sizes( self.ct.rsig_type, num_outputs) rsig_data = MoneroTransactionRsigData(rsig_type=self.ct.rsig_type, grouping=self.ct.rsig_batches) tsx_data.rsig_data = rsig_data if self.hf >= 10: tsx_data.rsig_data.bp_version = 2 self.ct.tsx_data = tsx_data init_msg = MoneroTransactionInitRequest( version=0, address_n=self.address_n, network_type=self.network_type, tsx_data=tsx_data, ) t_res = await self.trezor.tsx_sign(init_msg ) # type: MoneroTransactionInitAck self.handle_error(t_res) self.ct.tx_out_entr_hmacs = t_res.hmacs self.ct.rsig_param = t_res.rsig_data # Set transaction inputs for idx, src in enumerate(tx.sources): src_pb = tmisc.translate_monero_src_entry_pb(src) msg = MoneroTransactionSetInputRequest(src_entr=src_pb) t_res = await self.trezor.tsx_sign( msg) # type: MoneroTransactionSetInputAck self.handle_error(t_res) vini_p = await tmisc.parse_msg(t_res.vini, xmrtypes.TxInV()) vini = vini_p.txin_to_key self.ct.tx.vin.append(vini) self.ct.tx_in_hmacs.append(t_res.vini_hmac) self.ct.pseudo_outs.append( (t_res.pseudo_out, t_res.pseudo_out_hmac)) self.ct.alphas.append(t_res.pseudo_out_alpha) self.ct.spend_encs.append(t_res.spend_key) # Sort key image self.ct.source_permutation = list(range(len(tx.sources))) self.ct.source_permutation.sort( key=lambda x: self.ct.tx.vin[x].k_image, reverse=True) def swapper(x, y): self.ct.tx.vin[x], self.ct.tx.vin[y] = self.ct.tx.vin[ y], self.ct.tx.vin[x] self.ct.tx_in_hmacs[x], self.ct.tx_in_hmacs[y] = ( self.ct.tx_in_hmacs[y], self.ct.tx_in_hmacs[x], ) self.ct.pseudo_outs[x], self.ct.pseudo_outs[y] = ( self.ct.pseudo_outs[y], self.ct.pseudo_outs[x], ) self.ct.alphas[x], self.ct.alphas[y] = self.ct.alphas[ y], self.ct.alphas[x] self.ct.spend_encs[x], self.ct.spend_encs[y] = ( self.ct.spend_encs[y], self.ct.spend_encs[x], ) tx.sources[x], tx.sources[y] = tx.sources[y], tx.sources[x] common.apply_permutation(self.ct.source_permutation, swapper) msg = MoneroTransactionInputsPermutationRequest( perm=self.ct.source_permutation) t_res = await self.trezor.tsx_sign(msg) self.handle_error(t_res) # Set vin_i back - tx prefix hashing for idx in range(len(self.ct.tx.vin)): src_pb = tmisc.translate_monero_src_entry_pb(tx.sources[idx]) msg = MoneroTransactionInputViniRequest( src_entr=src_pb, vini=await tmisc.dump_msg(self.ct.tx.vin[idx], prefix=b"\x02"), vini_hmac=self.ct.tx_in_hmacs[idx], orig_idx=self.ct.source_permutation[idx], ) t_res = await self.trezor.tsx_sign(msg) self.handle_error(t_res) # All inputs set t_res = await self.trezor.tsx_sign( MoneroTransactionAllInputsSetRequest() ) # type: MoneroTransactionAllInputsSetAck self.handle_error(t_res) await self._on_all_input_set(t_res) # Set transaction outputs for idx, dst in enumerate(tx.splitted_dsts): msg = await self._step_set_outputs(idx, dst) t_res = await self.trezor.tsx_sign( msg) # type: MoneroTransactionSetOutputAck self.handle_error(t_res) await self._on_set_outputs_ack(idx, dst, t_res) t_res = await self.trezor.tsx_sign( MoneroTransactionAllOutSetRequest() ) # type: MoneroTransactionAllOutSetAck self.handle_error(t_res) rv = xmrtypes.RctSig() rv.p = xmrtypes.RctSigPrunable() rv.txnFee = t_res.rv.txn_fee rv.message = t_res.rv.message rv.type = t_res.rv.rv_type self.ct.tx.extra = list(bytearray(t_res.extra)) # Verify transaction prefix hash correctness, tx hash in one pass self.ct.tx_prefix_hash = await monero.get_transaction_prefix_hash( self.ct.tx) if not common.ct_equal(t_res.tx_prefix_hash, self.ct.tx_prefix_hash): raise ValueError("Transaction prefix has does not match") # RctSig if self.is_simple(rv): if self.is_bulletproof(rv): rv.p.pseudoOuts = [x[0] for x in self.ct.pseudo_outs] else: rv.pseudoOuts = [x[0] for x in self.ct.pseudo_outs] # Range proof rv.p.rangeSigs = [] rv.p.bulletproofs = [] rv.outPk = [] rv.ecdhInfo = [] for idx in range(len(self.ct.tx_out_pk)): rv.outPk.append(self.ct.tx_out_pk[idx]) rv.ecdhInfo.append(self.ct.tx_out_ecdh[idx]) for idx in range(len(self.ct.tx_out_rsigs)): if self.is_bulletproof(rv): rv.p.bulletproofs.append(self.ct.tx_out_rsigs[idx]) else: rv.p.rangeSigs.append(self.ct.tx_out_rsigs[idx]) mlsag_hash = t_res.full_message_hash mlsag_hash_computed = await monero.get_pre_mlsag_hash(rv) if not common.ct_equal(mlsag_hash, mlsag_hash_computed): raise ValueError("Pre MLSAG hash has does not match") # Sign each input couts = [] rv.p.MGs = [] for idx, src in enumerate(tx.sources): msg = MoneroTransactionSignInputRequest( src_entr=tmisc.translate_monero_src_entry_pb(src), vini=await tmisc.dump_msg(self.ct.tx.vin[idx], prefix=b"\x02"), vini_hmac=self.ct.tx_in_hmacs[idx], pseudo_out=self.ct.pseudo_outs[idx][0], pseudo_out_hmac=self.ct.pseudo_outs[idx][1], pseudo_out_alpha=self.ct.alphas[idx], spend_key=self.ct.spend_encs[idx], orig_idx=self.ct.source_permutation[idx], ) t_res = await self.trezor.tsx_sign( msg) # type: MoneroTransactionSignInputAck self.handle_error(t_res) if self.client_version > 0 and t_res.pseudo_out: self.ct.pseudo_outs[ idx] = t_res.pseudo_out, self.ct.pseudo_outs[idx][1] if self.is_bulletproof(rv): rv.p.pseudoOuts[idx] = t_res.pseudo_out else: rv.pseudoOuts[idx] = t_res.pseudo_out mg = await tmisc.parse_msg(t_res.signature, xmrtypes.MgSig()) rv.p.MGs.append(mg) couts.append(None) self.ct.tx.signatures = [] self.ct.tx.rct_signatures = rv t_res = await self.trezor.tsx_sign(MoneroTransactionFinalRequest() ) # type: MoneroTransactionFinalAck self.handle_error(t_res) if multisig: cout_key = t_res.cout_key for ccout in couts: self.ct.couts.append(chacha_poly.decrypt_pack(cout_key, ccout)) self.ct.enc_salt1, self.ct.enc_salt2 = t_res.salt, t_res.rand_mult self.ct.enc_keys = t_res.tx_enc_keys return self.ct.tx