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)
def compute_tx_key(spend_key_private, tx_prefix_hash, salt=None, rand_mult=None): """ :param spend_key_private: :param tx_prefix_hash: :param salt: :param rand_mult: :return: """ if not salt: salt = crypto.random_bytes(32) if not rand_mult: rand_mult_num = crypto.random_scalar() rand_mult = crypto.encodeint(rand_mult_num) else: rand_mult_num = crypto.decodeint(rand_mult) rand_inp = crypto.sc_add(spend_key_private, rand_mult_num) passwd = crypto.keccak_2hash(crypto.encodeint(rand_inp) + tx_prefix_hash) tx_key = crypto.compute_hmac(salt, passwd) # tx_key = crypto.pbkdf2(passwd, salt, count=100) return tx_key, salt, rand_mult
async def set_input(self, src_entr): """ :param src_entr: :type src_entr: xmrtypes.TxSourceEntry :return: """ self.inp_idx += 1 if src_entr.real_output >= len(src_entr.outputs): raise ValueError( "real_output index %s bigger than output_keys.size()" % (src_entr.real_output, len(src_entr.outputs)) ) self.summary_inputs_money += src_entr.amount out_key = crypto.decodepoint(src_entr.outputs[src_entr.real_output][1].dest) tx_key = crypto.decodepoint(src_entr.real_out_tx_key) additional_keys = [ crypto.decodepoint(x) for x in src_entr.real_out_additional_tx_keys ] secs = monero.generate_key_image_helper( self.trezor.creds, self.subaddresses, out_key, tx_key, additional_keys, src_entr.real_output_in_tx_index, ) xi, ki, di = secs self.input_secrets.append((xi,)) self.input_rcts.append(src_entr.rct) # Construct tx.vin vini = xmrtypes.TxinToKey( amount=src_entr.amount, k_image=crypto.encodepoint(ki) ) vini.key_offsets = [x[0] for x in src_entr.outputs] vini.key_offsets = monero.absolute_output_offsets_to_relative(vini.key_offsets) self.tx.vin.append(vini) # HMAC(T_in,i || vin_i) kwriter = monero.get_keccak_writer() ar = xmrserialize.Archive(kwriter, True) await ar.message(src_entr, xmrtypes.TxSourceEntry) await ar.message(vini, xmrtypes.TxinToKey) hmac_key_vini = crypto.keccak_hash( self.key_hmac + b"txin" + xmrserialize.dump_uvarint_b(self.inp_idx) ) hmac_vini = crypto.compute_hmac(hmac_key_vini, kwriter.get_digest()) return vini, hmac_vini
async def gen_hmac_tsxdest(self, dst_entr, idx): """ Generates HMAC for TxDestinationEntry[i] :param dst_entr: :param idx: :return: """ from monero_glue.xmr.sub.keccak_hasher import get_keccak_writer from monero_serialize import xmrserialize from monero_serialize.xmrtypes import TxDestinationEntry kwriter = get_keccak_writer() ar = xmrserialize.Archive(kwriter, True) await ar.message(dst_entr, TxDestinationEntry) hmac_key = self.hmac_key_txdst(idx) hmac_tsxdest = crypto.compute_hmac(hmac_key, kwriter.get_digest()) return hmac_tsxdest
async def gen_hmac_vouti(self, dst_entr, tx_out, idx): """ Generates HMAC for (TxDestinationEntry[i] || tx.vout[i]) :param dst_entr: :param tx_out: :param idx: :return: """ from monero_glue.xmr.sub.keccak_hasher import get_keccak_writer from monero_serialize import xmrserialize from monero_serialize.xmrtypes import TxDestinationEntry from monero_serialize.xmrtypes import TxOut kwriter = get_keccak_writer() ar = xmrserialize.Archive(kwriter, True) await ar.message(dst_entr, TxDestinationEntry) await ar.message(tx_out, TxOut) hmac_key_vouti = self.hmac_key_txout(idx) hmac_vouti = crypto.compute_hmac(hmac_key_vouti, kwriter.get_digest()) return hmac_vouti
async def gen_hmac_vini(self, src_entr, vini, idx): """ Computes hmac (TxSourceEntry[i] || tx.vin[i]) :param src_entr: :param vini: :param idx: :return: """ from monero_glue.xmr.sub.keccak_hasher import get_keccak_writer from monero_serialize import xmrserialize from monero_serialize.xmrtypes import TxSourceEntry from monero_serialize.xmrtypes import TxinToKey kwriter = get_keccak_writer() ar = xmrserialize.Archive(kwriter, True) await ar.message(src_entr, TxSourceEntry) await ar.message(vini, TxinToKey) hmac_key_vini = self.hmac_key_txin(idx) hmac_vini = crypto.compute_hmac(hmac_key_vini, kwriter.get_digest()) return hmac_vini
async def store_cdata(self, cdata, signed_tx, tx, transfers): """ Stores transaction data for later usage. - cdata.enc_salt1, cdata.enc_salt2, cdata.enc_keys - tx_keys are AEAD protected, key derived from spend key - only token can open. - construction data for further proofs. :param cdata: :param signed_tx: :param tx: :param transfers: :return: """ hash = cdata.tx_prefix_hash prefix = binascii.hexlify(hash[:12]) tx_key_salt = crypto.random_bytes(32) tx_key_inp = hash + crypto.encodeint(self.priv_view) tx_view_key = crypto.pbkdf2(tx_key_inp, tx_key_salt, 2048) unsigned_data = xmrtypes.UnsignedTxSet() unsigned_data.txes = [tx] unsigned_data.transfers = transfers if transfers is not None else [] writer = xmrserialize.MemoryReaderWriter() ar = xmrboost.Archive(writer, True) await ar.root() await ar.message(unsigned_data) unsigned_key = crypto.keccak_2hash(b'unsigned;' + tx_view_key) ciphertext = chacha_poly.encrypt_pack(unsigned_key, bytes(writer.get_buffer())) # Serialize signed transaction writer = xmrserialize.MemoryReaderWriter() ar = xmrserialize.Archive(writer, True) await ar.root() await ar.message(signed_tx) signed_tx_bytes = writer.get_buffer() signed_tx_hmac_key = crypto.keccak_2hash(b'hmac;' + tx_view_key) signed_tx_hmac = crypto.compute_hmac(signed_tx_hmac_key, signed_tx_bytes) try: js = { "time": int(time.time()), "hash": binascii.hexlify(hash).decode("ascii"), "enc_salt1": binascii.hexlify(cdata.enc_salt1).decode("ascii"), "enc_salt2": binascii.hexlify(cdata.enc_salt2).decode("ascii"), "tx_keys": binascii.hexlify(cdata.enc_keys).decode("ascii"), "unsigned_data": binascii.hexlify(ciphertext).decode("ascii"), "tx_salt": binascii.hexlify(tx_key_salt).decode("ascii"), "tx_signed": binascii.hexlify(signed_tx_bytes).decode("ascii"), "tx_signed_hmac": binascii.hexlify(signed_tx_hmac).decode("ascii"), } with open("transaction_%s.json" % prefix.decode("ascii"), "w") as fh: json.dump(js, fh, indent=2) fh.write("\n") except Exception as e: self.trace_logger.log(e) print("Unable to save transaction data for transaction %s" % binascii.hexlify(hash).decode("ascii"))
async def set_input(self, src_entr): """ Sets UTXO one by one. Computes spending secret key, key image. tx.vin[i] + HMAC, Pedersen commitment on amount. If number of inputs is small, in-memory mode is used = alpha, pseudo_outs are kept in the Trezor. Otherwise pseudo_outs are offloaded with HMAC, alpha is offloaded encrypted under Chacha20Poly1305() with key derived for exactly this purpose. :param src_entr: :type src_entr: monero_glue.xmr.serialize_messages.tx_construct.TxSourceEntry :return: """ from monero_glue.messages.MoneroTransactionSetInputAck import ( MoneroTransactionSetInputAck ) from monero_glue.xmr.enc import chacha_poly from monero_glue.xmr.sub import tsx_helper from monero_serialize.xmrtypes import TxinToKey self.state.input() self.inp_idx += 1 await self.trezor.iface.transaction_step( self.STEP_INP, self.inp_idx, self.num_inputs() ) if self.inp_idx >= self.num_inputs(): raise ValueError("Too many inputs") if src_entr.real_output >= len(src_entr.outputs): raise ValueError( "real_output index %s bigger than output_keys.size() %s" % (src_entr.real_output, len(src_entr.outputs)) ) self.summary_inputs_money += src_entr.amount # Secrets derivation out_key = crypto.decodepoint(src_entr.outputs[src_entr.real_output][1].dest) tx_key = crypto.decodepoint(src_entr.real_out_tx_key) additional_keys = [ crypto.decodepoint(x) for x in src_entr.real_out_additional_tx_keys ] secs = monero.generate_key_image_helper( self.creds, self.subaddresses, out_key, tx_key, additional_keys, src_entr.real_output_in_tx_index, ) xi, ki, di = secs # Construct tx.vin ki_real = src_entr.multisig_kLRki.ki if self.multi_sig else ki vini = TxinToKey(amount=src_entr.amount, k_image=crypto.encodepoint(ki_real)) vini.key_offsets = [x[0] for x in src_entr.outputs] vini.key_offsets = tsx_helper.absolute_output_offsets_to_relative( vini.key_offsets ) if src_entr.rct: vini.amount = 0 if self.in_memory(): self.tx.vin.append(vini) # HMAC(T_in,i || vin_i) hmac_vini = await self.gen_hmac_vini(src_entr, vini, self.inp_idx) # PseudoOuts commitment, alphas stored to state pseudo_out = None pseudo_out_hmac = None alpha_enc = None spend_enc = None if self.use_simple_rct: alpha, pseudo_out = await self.commitment(src_entr.amount) pseudo_out = crypto.encodepoint(pseudo_out) # In full version the alpha is encrypted and passed back for storage if self.in_memory(): self.input_alphas.append(alpha) self.input_pseudo_outs.append(pseudo_out) else: pseudo_out_hmac = crypto.compute_hmac( self.hmac_key_txin_comm(self.inp_idx), pseudo_out ) alpha_enc = chacha_poly.encrypt_pack( self.enc_key_txin_alpha(self.inp_idx), crypto.encodeint(alpha) ) if self.many_inputs(): spend_enc = chacha_poly.encrypt_pack( self.enc_key_spend(self.inp_idx), crypto.encodeint(xi) ) else: self.input_secrets.append(xi) # All inputs done? if self.inp_idx + 1 == self.num_inputs(): await self.tsx_inputs_done() return MoneroTransactionSetInputAck( vini=await misc.dump_msg(vini, preallocate=64), vini_hmac=hmac_vini, pseudo_out=pseudo_out, pseudo_out_hmac=pseudo_out_hmac, alpha_enc=alpha_enc, spend_enc=spend_enc, )
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, )
def _compute_ki_enc_key_host(view_key_private, prefix, salt): passwd = crypto.keccak_2hash( crypto.encodeint(view_key_private) + prefix) enc_key = crypto.compute_hmac(salt, passwd) return enc_key
def _compute_tx_key_host(view_key_private, tx_prefix_hash, salt): passwd = crypto.keccak_2hash( crypto.encodeint(view_key_private) + tx_prefix_hash) tx_key = crypto.compute_hmac(salt, passwd) return tx_key