async def tsx_inputs_done(self): """ All inputs set :return: """ # Sort tx.in by key image self.source_permutation = list(range(self.inp_idx + 1)) self.source_permutation.sort(key=lambda x: self.tx.vin[x].k_image) def swapper(x, y): self.tx.vin[x], self.tx.vin[y] = self.tx.vin[y], self.tx.vin[x] self.input_secrets[x], self.input_secrets[y] = ( self.input_secrets[y], self.input_secrets[x], ) self.input_rcts[x], self.input_rcts[y] = ( self.input_rcts[y], self.input_rcts[x], ) common.apply_permutation(self.source_permutation, swapper) # Set public key to the extra self.tx.extra = monero.add_tx_pub_key_to_extra(self.tx.extra, self.r_pub) self.use_simple_rct = self.inp_idx > 0
async def _tsx_inputs_permutation(self, permutation): """ Set permutation on the inputs - sorted by key image on host. :param permutation: :return: """ self.state.input_permutation() self.source_permutation = permutation def swapper(x, y): if not self.many_inputs(): self.input_secrets[x], self.input_secrets[y] = ( self.input_secrets[y], self.input_secrets[x], ) if self.in_memory() and self.use_simple_rct: self.input_alphas[x], self.input_alphas[y] = ( self.input_alphas[y], self.input_alphas[x], ) self.input_pseudo_outs[x], self.input_pseudo_outs[y] = ( self.input_pseudo_outs[y], self.input_pseudo_outs[x], ) if self.in_memory(): self.tx.vin[x], self.tx.vin[y] = self.tx.vin[y], self.tx.vin[x] common.apply_permutation(self.source_permutation, swapper) self.inp_idx = -1 # Incremental hashing if self.in_memory(): for idx in range(self.num_inputs()): await self.hash_vini_pseudo_out(self.tx.vin[idx], idx)
async def shuffle_outs(self, tx, change_idx=None): change_idx = await self.find_change_idx(tx, change_idx) permutation = list(range(len(tx.splitted_dsts))) random.shuffle(permutation) def swapper(x, y): tx.splitted_dsts[x], tx.splitted_dsts[y] = tx.splitted_dsts[y], tx.splitted_dsts[x] common.apply_permutation(permutation, swapper) new_change = permutation.index(change_idx) if change_idx is not None else None logger.debug('Outputs shuffled, change tsx idx: %d' % new_change) return new_change
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 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