async def mlsag_prehash_finalize(self): H = None proof = None message = None if self.options & IN_OPTION_MORE_COMMAND: H = self._fetch() self.ctx_h.update(H) self.ctx_commitment.update(H) else: H = self.ctx_commitment.digest() if not common.ct_equal(H, self.C): raise ValueError(SW_SECURITY_COMMITMENT_CHAIN_CONTROL) H = self.ctx_h.digest() message = self._fetch() proof = self._fetch() self.ctx_h = crypto.get_keccak() self.ctx_h.update(message) self.ctx_h.update(H) self.ctx_h.update(proof) self.H = self.ctx_h.digest() self._insert(self.H) return SW_OK
async def hash_vini_pseudo_out( self, vini, inp_idx, pseudo_out=None, pseudo_out_hmac=None ): """ Incremental hasing of tx.vin[i] and pseudo output :param vini: :param inp_idx: :param pseudo_out: :param pseudo_out_hmac: :return: """ # Serialize particular input type from monero_serialize import xmrserialize from monero_serialize.xmrtypes import TxInV self.tx_prefix_hasher.refresh(xser=xmrserialize) await self.tx_prefix_hasher.ar.field(vini, TxInV) # Pseudo_out incremental hashing - applicable only in simple rct if not self.use_simple_rct: return if not self.in_memory(): idx = self.source_permutation[inp_idx] pseudo_out_hmac_comp = crypto.compute_hmac( self.hmac_key_txin_comm(idx), pseudo_out ) if not common.ct_equal(pseudo_out_hmac, pseudo_out_hmac_comp): raise ValueError("HMAC invalid for pseudo outs") else: pseudo_out = self.input_pseudo_outs[inp_idx] await self.full_message_hasher.set_pseudo_out(pseudo_out)
async def mlsag_prehash_update(self): is_subaddress = 0 Aout = None Bout = None C = None v = None k = None changed = 0 is_subaddress = bool(self._fetch_u8()) Aout = crypto.decodepoint(self._fetch()) Bout = crypto.decodepoint(self._fetch()) if self.sig_mode == TRANSACTION_CREATE_REAL: if crypto.point_eq(Aout, self.A) and crypto.point_eq(Bout, self.B): changed = 1 AKout = self._fetch_decrypt() C = self._fetch() k = self._fetch() v = self._fetch() self.ctx_h.update(k) self.ctx_h.update(v) self.ctx_amount.update(AKout) v, k, AKout = self.unblind_int(v, k, AKout) self.ctx_amount.update(crypto.encodeint(k)) self.ctx_amount.update(crypto.encodeint(v)) vH = crypto.scalarmult_h(v) kG = crypto.scalarmult_base(k) k = crypto.point_add(kG, vH) if not crypto.point_eq(k, crypto.decodepoint(C)): raise ValueError(SW_SECURITY_COMMITMENT_CONTROL) self.ctx_commitment.update(C) if self.options & IN_OPTION_MORE_COMMAND == 0: k = self.ctx_amount.digest() if not common.ct_equal(k, self.KV): raise ValueError(SW_SECURITY_AMOUNT_CHAIN_CONTROL) self.C = self.ctx_commitment.digest() self.ctx_commitment = sha256() if self.sig_mode == TRANSACTION_CREATE_REAL: if not changed: await self._req_dst(Aout, Bout, v, is_subaddress) return SW_OK
async def input_vini(self, src_entr, vini, hmac, pseudo_out, pseudo_out_hmac): """ Set tx.vin[i] for incremental tx prefix hash computation. After sorting by key images on host. Hashes pseudo_out to the final_message. :param src_entr: :param vini: tx.vin[i] :param hmac: HMAC of tx.vin[i] :param pseudo_out: pseudo_out for the current entry :param pseudo_out_hmac: hmac of pseudo_out :return: """ from monero_glue.messages.MoneroTransactionInputViniAck import ( MoneroTransactionInputViniAck ) await self.trezor.iface.transaction_step( self.STEP_VINI, self.inp_idx + 1, self.num_inputs() ) if self.in_memory(): return if self.inp_idx >= self.num_inputs(): raise ValueError("Too many inputs") self.state.input_vins() self.inp_idx += 1 # HMAC(T_in,i || vin_i) hmac_vini = await self.gen_hmac_vini( src_entr, vini, self.source_permutation[self.inp_idx] ) if not common.ct_equal(hmac_vini, hmac): raise ValueError("HMAC is not correct") await self.hash_vini_pseudo_out(vini, self.inp_idx, pseudo_out, pseudo_out_hmac) return MoneroTransactionInputViniAck()
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 all_out1_set(self): """ All outputs were set in this phase. Computes additional public keys (if needed), tx.extra and transaction prefix hash. Adds additional public keys to the tx.extra :return: tx.extra, tx_prefix_hash """ self._log_trace(0) self.state.set_output_done() await self.trezor.iface.transaction_step(self.STEP_ALL_OUT) self._log_trace(1) if self.out_idx + 1 != self.num_dests(): raise ValueError("Invalid out num") # Test if \sum Alpha == \sum A if self.use_simple_rct: self.assrt(crypto.sc_eq(self.sumout, self.sumpouts_alphas)) # Fee test if self.fee != (self.summary_inputs_money - self.summary_outs_money): raise ValueError( "Fee invalid %s vs %s, out: %s" % ( self.fee, self.summary_inputs_money - self.summary_outs_money, self.summary_outs_money, ) ) self._log_trace(2) # Set public key to the extra # Not needed to remove - extra is clean await self.all_out1_set_tx_extra() self.additional_tx_public_keys = None gc.collect() self._log_trace(3) if self.summary_outs_money > self.summary_inputs_money: raise ValueError( "Transaction inputs money (%s) less than outputs money (%s)" % (self.summary_inputs_money, self.summary_outs_money) ) # Hashing transaction prefix await self.all_out1_set_tx_prefix() extra_b = self.tx.extra self.tx = None gc.collect() self._log_trace(4) # Txprefix match check for multisig if not common.is_empty(self.exp_tx_prefix_hash) and not common.ct_equal( self.exp_tx_prefix_hash, self.tx_prefix_hash ): self.state.set_fail() raise misc.TrezorTxPrefixHashNotMatchingError("Tx prefix invalid") gc.collect() self._log_trace(5) from monero_glue.messages.MoneroRingCtSig import MoneroRingCtSig from monero_glue.messages.MoneroTransactionAllOutSetAck import ( MoneroTransactionAllOutSetAck ) rv = self.init_rct_sig() rv_pb = MoneroRingCtSig(txn_fee=rv.txnFee, message=rv.message, rv_type=rv.type) return MoneroTransactionAllOutSetAck( extra=extra_b, tx_prefix_hash=self.tx_prefix_hash, rv=rv_pb )
async def set_out1(self, dst_entr, dst_entr_hmac): """ Set destination entry one by one. Computes destination stealth address, amount key, range proof + HMAC, out_pk, ecdh_info. :param dst_entr :type dst_entr: TxDestinationEntry :param dst_entr_hmac :return: """ from monero_serialize import xmrserialize await self.trezor.iface.transaction_step( self.STEP_OUT, self.out_idx + 1, self.num_dests() ) self._log_trace(1) if self.state.is_input_vins() and self.inp_idx + 1 != self.num_inputs(): raise ValueError("Invalid number of inputs") self.state.set_output() self.out_idx += 1 self._log_trace(2) if dst_entr.amount <= 0 and self.tx.version <= 1: raise ValueError("Destination with wrong amount: %s" % dst_entr.amount) # HMAC check of the destination dst_entr_hmac_computed = await self.gen_hmac_tsxdest(dst_entr, self.out_idx) if not common.ct_equal(dst_entr_hmac, dst_entr_hmac_computed): raise ValueError("HMAC invalid") gc.collect() self._log_trace(3) # First output - tx prefix hasher - size of the container self.tx_prefix_hasher.refresh(xser=xmrserialize) if self.out_idx == 0: await self._set_out1_prefix() gc.collect() self._log_trace(4) additional_txkey_priv = await self._set_out1_additional_keys(dst_entr) derivation = await self._set_out1_derivation(dst_entr, additional_txkey_priv) gc.collect() self._log_trace(5) amount_key = crypto.derivation_to_scalar(derivation, self.out_idx) tx_out_key = crypto.derive_public_key( derivation, self.out_idx, crypto.decodepoint(dst_entr.addr.m_spend_public_key), ) from monero_serialize.xmrtypes import TxoutToKey from monero_serialize.xmrtypes import TxOut tk = TxoutToKey(key=crypto.encodepoint(tx_out_key)) tx_out = TxOut(amount=0, target=tk) self.summary_outs_money += dst_entr.amount self._log_trace(6) # Tx header prefix hashing await self.tx_prefix_hasher.ar.field(tx_out, TxOut) gc.collect() # Hmac dest_entr. hmac_vouti = await self.gen_hmac_vouti(dst_entr, tx_out, self.out_idx) gc.collect() self._log_trace(7) # Range proof, out_pk, ecdh_info rsig, out_pk, ecdh_info = await self.range_proof( self.out_idx, dest_pub_key=tk.key, amount=dst_entr.amount, amount_key=amount_key, ) gc.collect() self._log_trace(8) # Incremental hashing of the ECDH info. # RctSigBase allows to hash only one of the (ecdh, out_pk) as they are serialized # as whole vectors. Hashing ECDH info saves state space. await self.full_message_hasher.set_ecdh(ecdh_info) self._log_trace(9) # Output_pk is stored to the state as it is used during the signature and hashed to the # RctSigBase later. self.output_pk.append(out_pk) gc.collect() self._log_trace(10) from monero_glue.messages.MoneroTransactionSetOutputAck import ( MoneroTransactionSetOutputAck ) from monero_serialize.xmrtypes import CtKey return MoneroTransactionSetOutputAck( tx_out=await misc.dump_msg(tx_out, preallocate=34), vouti_hmac=hmac_vouti, rsig=rsig, # rsig is already byte-encoded out_pk=await misc.dump_msg(out_pk, preallocate=64, msg_type=CtKey), ecdh_info=await misc.dump_msg(ecdh_info, preallocate=64), )
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