def copy_ct_key(ct): """ Ct key copy :param ct: :return: """ return xmrtypes.CtKey(mask=ct.mask, dest=ct.dest)
def mixring(self, js): mxr = [] mx = js["mixRing"] for i in range(len(mx)): mxr.append([]) for j in range(len(mx[i])): dt = binascii.unhexlify(mx[i][j]) mxr[i].append(xmrtypes.CtKey(dest=dt[:32], mask=dt[32:])) return mxr
async def _on_set_outputs_ack(self, idx, dst, 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_pk.append(await tmisc.parse_msg(t_res.out_pk, xmrtypes.CtKey())) # hf10 encodes only 8B amount if self.hf == 9: self.ct.tx_out_ecdh.append(await tmisc.parse_msg(t_res.ecdh_info, xmrtypes.EcdhTuple())) else: cur_ecdh = xmrtypes.EcdhTuple(mask=crypto.NULL_KEY_ENC, amount=bytearray( crypto.NULL_KEY_ENC)) cur_ecdh.amount[0:8] = bytearray(t_res.ecdh_info) self.ct.tx_out_ecdh.append(cur_ecdh) rsig_buff = None if t_res.rsig_data: tsig_data = t_res.rsig_data if tsig_data.rsig and len(tsig_data.rsig) > 0: rsig_buff = tsig_data.rsig elif tsig_data.rsig_parts and len(tsig_data.rsig_parts) > 0: rsig_buff = b"".join(tsig_data.rsig_parts) if self.client_version >= 1 and tsig_data.mask: logger.debug("MASK received") self.ct.rsig_gamma.append(tsig_data.mask) # Client1: send generated BP if offloading if self.client_version >= 1 and self._is_offloading(): proc = await self._v1_offloading(idx, dst, t_res) if not proc: return # no state advance else: rsig = None if rsig_buff and not self._is_req_bulletproof(): rsig = await tmisc.parse_msg(rsig_buff, xmrtypes.RangeSig()) elif rsig_buff: rsig = await tmisc.parse_msg(rsig_buff, xmrtypes.Bulletproof()) else: return # no state change await self._out_proc_range_proof(rsig) # batch state advance self.ct.cur_batch_idx += 1 self.ct.cur_output_in_batch_idx = 0
async def _on_set_outputs_ack(self, 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_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_buff = None if t_res.rsig_data: tsig_data = t_res.rsig_data if tsig_data.rsig and len(tsig_data.rsig) > 0: rsig_buff = tsig_data.rsig elif tsig_data.rsig_parts and len(tsig_data.rsig_parts) > 0: rsig_buff = b"".join(tsig_data.rsig_parts) if rsig_buff and not self._is_req_bulletproof(): rsig = await tmisc.parse_msg(rsig_buff, xmrtypes.RangeSig()) elif rsig_buff: rsig = await tmisc.parse_msg(rsig_buff, xmrtypes.Bulletproof()) else: return if self._is_req_bulletproof(): rsig.V = [] batch_size = self.ct.rsig_batches[self.ct.cur_batch_idx] for i in range(batch_size): commitment = self.ct.tx_out_pk[1 + self.ct.cur_output_idx - batch_size + i].mask commitment = crypto.scalarmult(crypto.decodepoint(commitment), crypto.sc_inv_eight()) rsig.V.append(crypto.encodepoint(commitment)) self.ct.tx_out_rsigs.append(rsig) # Rsig verification try: if not ring_ct.ver_range( C=crypto.decodepoint(self.ct.tx_out_pk[-1].mask), rsig=rsig, use_bulletproof=self._is_req_bulletproof(), ): logger.warning("Rsing not valid") except Exception as e: logger.error("Exception rsig: %s" % e) traceback.print_exc() self.ct.cur_batch_idx += 1 self.ct.cur_output_in_batch_idx = 0
async def gen_rct_header(self, destinations, outamounts): """ Initializes RV RctSig structure, processes outputs, computes range proofs, ecdh info masking. Common to gen_rct and gen_rct_simple. :param destinations: :param outamounts: :return: """ rv = xmrtypes.RctSig() rv.p = xmrtypes.RctSigPrunable() rv.message = self.tx_prefix_hash rv.outPk = [None] * len(destinations) if self.use_bulletproof: rv.p.bulletproofs = [None] * len(destinations) else: rv.p.rangeSigs = [None] * len(destinations) rv.ecdhInfo = [None] * len(destinations) # Output processing sumout = crypto.sc_0() out_sk = [None] * len(destinations) for idx in range(len(destinations)): rv.outPk[idx] = xmrtypes.CtKey(dest=crypto.encodepoint(destinations[idx])) C, mask, rsig = None, 0, None # Rangeproof if self.use_bulletproof: raise ValueError("Bulletproof not yet supported") else: C, mask, rsig = ring_ct.prove_range(outamounts[idx]) rv.p.rangeSigs[idx] = rsig if __debug__: assert ring_ct.ver_range(C, rsig) assert crypto.point_eq( C, crypto.point_add( crypto.scalarmult_base(mask), crypto.scalarmult_h(outamounts[idx]), ), ) # Mask sum rv.outPk[idx].mask = crypto.encodepoint(C) sumout = crypto.sc_add(sumout, mask) out_sk[idx] = xmrtypes.CtKey(mask=mask) # ECDH masking amount_key = crypto.encodeint(self.output_secrets[idx][0]) rv.ecdhInfo[idx] = xmrtypes.EcdhTuple( mask=mask, amount=crypto.sc_init(outamounts[idx]) ) rv.ecdhInfo[idx] = ring_ct.ecdh_encode( rv.ecdhInfo[idx], derivation=amount_key ) monero.recode_ecdh(rv.ecdhInfo[idx], encode=True) return rv, sumout, out_sk
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 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