async def amplify_inputs(self, tx, new_keys=None, new_subs=None): lst = tx.sources[-1] orig_amount = lst.amount partial = orig_amount // (self.args.inputs + 1) amnt_sum = partial lst.amount = partial commitment = crypto.encodepoint(crypto.gen_c(crypto.decodeint(lst.mask), partial)) lst.outputs[lst.real_output][1].mask = commitment for i in range(1, self.args.inputs + 1): is_lst = i >= self.args.inputs new_amnt = orig_amount - amnt_sum if is_lst else partial amnt_sum += partial commitment = crypto.encodepoint(crypto.gen_c(crypto.decodeint(lst.mask), new_amnt)) new_inp = TxSourceEntry(outputs=list(lst.outputs), real_output=lst.real_output, real_out_tx_key=lst.real_out_tx_key, real_out_additional_tx_keys=lst.real_out_additional_tx_keys, real_output_in_tx_index=lst.real_output_in_tx_index, amount=new_amnt, rct=lst.rct, mask=lst.mask, multisig_kLRki=lst.multisig_kLRki) # Amount changed -> update the commitment orig_key = new_inp.outputs[new_inp.real_output][1] new_inp.outputs[new_inp.real_output] = (0, CtKey(dest=orig_key.dest, mask=commitment)) # Randomize mixin values for i in range(new_inp.real_output + 1, len(new_inp.outputs)): new_inp.outputs[i] = (0, CtKey( mask=crypto.encodepoint(self.random_pub()), dest=crypto.encodepoint(self.random_pub()))) if new_inp.real_out_additional_tx_keys: new_inp.real_out_additional_tx_keys[i] = crypto.encodepoint(self.random_pub()) self.check_input(new_inp, new_keys, new_subs) if not crypto.point_eq( crypto.decodepoint(new_inp.outputs[new_inp.real_output][1].mask), crypto.gen_c(crypto.decodeint(new_inp.mask), new_inp.amount), ): raise ValueError("Real source entry's mask does not equal spend key's") tx.sources.append(new_inp)
async def commitment(self, in_amount): """ Computes Pedersen commitment - pseudo outs Here is slight deviation from the original protocol. We want that \\sum Alpha = \\sum A_{i,j} where A_{i,j} is a mask from range proof for output i, bit j. Previously this was computed in such a way that Alpha_{last} = \\sum A{i,j} - \\sum_{i=0}^{last-1} Alpha But we would prefer to compute commitment before range proofs so alphas are generated completely randomly and the last A mask is computed in this special way. Returns pseudo_out :return: """ alpha = crypto.random_scalar() self.sumpouts_alphas = crypto.sc_add(self.sumpouts_alphas, alpha) return alpha, crypto.gen_c(alpha, in_amount)
async def verify_bp(bp_proof, amounts=None, masks=None): from monero_glue.xmr import bulletproof as bp if amounts: bp_proof.V = [] for i in range(len(amounts)): C = crypto.gen_c(masks[i], amounts[i]) crypto.scalarmult_into(C, C, crypto.sc_inv_eight()) bp_proof.V.append(crypto.encodepoint(C)) bpi = bp.BulletProofBuilder() res = bpi.verify(bp_proof) gc.collect() # Return as struct as the hash(BP_struct) != hash(BP_serialized) # as the original hashing does not take vector lengths into account which are dynamic # in the serialization scheme (and thus extraneous) return res
async def describe(self, inp, unsigned_txs, keys, key_subs): print("\nInp: %s, #txs: %s, #transfers: %s" % (inp, len(unsigned_txs.txes), len(unsigned_txs.transfers))) for txid, tx in enumerate(unsigned_txs.txes): srcs = tx.sources dsts = tx.splitted_dsts extra = tx.extra change = tx.change_dts account = tx.subaddr_account subs = tx.subaddr_indices mixin = len(srcs[0].outputs) - 1 amnt_in = sum([x.amount for x in srcs]) amnt_out = sum([x.amount for x in dsts]) fee = amnt_in - amnt_out n_inp_additional = sum( [1 for x in srcs if len(x.real_out_additional_tx_keys) > 0] ) change_addr = ( addr.build_address( change.addr.m_spend_public_key, change.addr.m_view_public_key ) if change else None ) out_txs2 = await self.reformat_outs(dsts) num_stdaddresses, num_subaddresses, single_dest_subaddress = addr.classify_subaddresses( out_txs2, change_addr ) print( " tx: %s, #inp: %2d, #inp_add: %2d, #out: %2d, mixin: %2d, acc: %s, subs: %s, " "xmr_in: %10.6f, xmr_out: %10.6f, fee: %10.6f, change: %10.6f, out_clean: %10.6f" % ( txid, len(srcs), n_inp_additional, len(dsts), mixin, account, subs, wallet.conv_disp_amount(amnt_in), wallet.conv_disp_amount(amnt_out), wallet.conv_disp_amount(fee), wallet.conv_disp_amount(change.amount) if change else 0, wallet.conv_disp_amount( (amnt_out - change.amount) if change else amnt_out ), ) ) print( " Out: num_std: %2d, num_sub: %2d, single_dest_sub: %s, total: %s" % ( num_stdaddresses, num_subaddresses, 1 if single_dest_subaddress else 0, len(dsts), ) ) accounts = set() subs = set() for inp in srcs: res = await self.analyze_input(keys, key_subs, inp) accounts.add(res[0]) if res != (0, 0): subs.add(res) print(" Ins: accounts: %s, subs: %s" % (accounts, len(subs))) extras = await monero.parse_extra_fields(extra) extras_val = [] for c in extras: if isinstance(c, TxExtraPubKey): extras_val.append("TxKey") elif isinstance(c, TxExtraNonce): extras_val.append( "Nonce: %s" % binascii.hexlify(c.nonce).decode("ascii") ) elif isinstance(c, TxExtraAdditionalPubKeys): extras_val.append("AdditionalTxKeys: %s" % len(c.data)) else: extras_val.append(str(c)) print(" Extras: %s" % ", ".join(extras_val)) # Final verification for idx, inp in enumerate(tx.sources): self.check_input(inp, keys, key_subs) if not crypto.point_eq( crypto.decodepoint(inp.outputs[inp.real_output][1].mask), crypto.gen_c(crypto.decodeint(inp.mask), inp.amount), ): raise ValueError("Real source entry's mask does not equal spend key's. Inp: %d" % idx)
async def gen_rct_simple( self, in_sk, destinations, inamounts, outamounts, txn_fee, mix_ring, kLRki, msout, index, ): """ Generate simple RCT signature. :param in_sk: :param destinations: :param inamounts: :param outamounts: :param txn_fee: :param mix_ring: :param kLRki: :param msout: :param index: :param out_sk: :return: """ if len(inamounts) == 0: raise ValueError("Empty inamounts") if len(inamounts) != len(in_sk): raise ValueError("Different number of inamounts/inSk") if len(outamounts) != len(destinations): raise ValueError("Different number of amounts/destinations") if len(self.output_secrets) != len(destinations): raise ValueError("Different number of amount_keys/destinations") if len(index) != len(in_sk): raise ValueError("Different number of index/inSk") if len(mix_ring) != len(in_sk): raise ValueError("Different number of mixRing/inSk") for idx in range(len(mix_ring)): if index[idx] >= len(mix_ring[idx]): raise ValueError("Bad index into mixRing") rv, sumout, out_sk = await self.gen_rct_header(destinations, outamounts) rv.type = ( xmrtypes.RctType.SimpleBulletproof if self.use_bulletproof else xmrtypes.RctType.Simple ) rv.txnFee = txn_fee rv.mixRing = mix_ring # Pseudooutputs pseudo_outs = [None] * len(inamounts) rv.p.MGs = [None] * len(inamounts) sumpouts = crypto.sc_0() a = [] for idx in range(len(inamounts) - 1): a.append(crypto.random_scalar()) sumpouts = crypto.sc_add(sumpouts, a[idx]) pseudo_outs[idx] = crypto.gen_c(a[idx], inamounts[idx]) a.append(crypto.sc_sub(sumout, sumpouts)) pseudo_outs[-1] = crypto.gen_c(a[-1], inamounts[-1]) if self.use_bulletproof: rv.p.pseudoOuts = [crypto.encodepoint(x) for x in pseudo_outs] else: rv.pseudoOuts = [crypto.encodepoint(x) for x in pseudo_outs] full_message = await monero.get_pre_mlsag_hash(rv) # TODO: msout multisig for i in range(len(inamounts)): rv.p.MGs[i], msc = mlsag2.prove_rct_mg_simple( full_message, rv.mixRing[i], in_sk[i], a[i], pseudo_outs[i], kLRki[i] if kLRki else None, None, index[i], ) if __debug__: assert mlsag2.ver_rct_mg_simple( full_message, rv.p.MGs[i], rv.mixRing[i], pseudo_outs[i] ) return rv
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 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_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, )