async def sign_tx(ctx, received_msg, keychain): state = State(ctx) mods = utils.unimport_begin() # Splitting ctx.call() to write() and read() helps to reduce memory fragmentation # between calls. while True: if __debug__: log.debug(__name__, "#### F: %s, A: %s", gc.mem_free(), gc.mem_alloc()) gc.collect() gc.threshold(gc.mem_free() // 4 + gc.mem_alloc()) result_msg, accept_msgs = await sign_tx_dispatch( state, received_msg, keychain) if accept_msgs is None: break await ctx.write(result_msg) del (result_msg, received_msg) utils.unimport_end(mods) received_msg = await ctx.read(accept_msgs) utils.unimport_end(mods) return result_msg
async def handle_session( iface: WireInterface, session_id: int, is_debug_session: bool = False ) -> None: ctx = Context(iface, session_id, WIRE_BUFFER) next_msg: codec_v1.Message | None = None if __debug__ and is_debug_session: import apps.debug apps.debug.DEBUG_CONTEXT = ctx # Take a mark of modules that are imported at this point, so we can # roll back and un-import any others. modules = utils.unimport_begin() while True: try: if next_msg is None: # If the previous run did not keep an unprocessed message for us, # wait for a new one coming from the wire. try: msg = await ctx.read_from_wire() except codec_v1.CodecError as exc: if __debug__: log.exception(__name__, exc) await ctx.write(failure(exc)) continue else: # Process the message from previous run. msg = next_msg next_msg = None try: next_msg = await _handle_single_message( ctx, msg, use_workflow=not is_debug_session ) except Exception as exc: # Log and ignore. The session handler can only exit explicitly in the # following finally block. if __debug__: log.exception(__name__, exc) finally: if not __debug__ or not is_debug_session: # Unload modules imported by the workflow. Should not raise. # This is not done for the debug session because the snapshot taken # in a debug session would clear modules which are in use by the # workflow running on wire. utils.unimport_end(modules) if next_msg is None and msg.type not in AVOID_RESTARTING_FOR: # Shut down the loop if there is no next message waiting. # Let the session be restarted from `main`. loop.clear() return # pylint: disable=lost-exception except Exception as exc: # Log and try again. The session handler can only exit explicitly via # loop.clear() above. if __debug__: log.exception(__name__, exc)
async def sign_tx(ctx: wire.Context, msg: SignTx, keychain: seed.Keychain, coin: coininfo.CoinInfo) -> TxRequest: if not utils.BITCOIN_ONLY: if coin.decred: signer_class = decred.Decred # type: Type[bitcoin.Bitcoin] elif coin.overwintered: signer_class = zcash.Overwintered elif coin.coin_name not in BITCOIN_NAMES: signer_class = bitcoinlike.Bitcoinlike else: signer_class = bitcoin.Bitcoin else: signer_class = bitcoin.Bitcoin signer = signer_class(msg, keychain, coin).signer() res = None # type: Union[TxAck, bool, None] while True: req = signer.send(res) if isinstance(req, TxRequest): if req.request_type == TXFINISHED: break res = await ctx.call(req, TxAck) elif isinstance(req, helpers.UiConfirmOutput): mods = utils.unimport_begin() res = await layout.confirm_output(ctx, req.output, req.coin) utils.unimport_end(mods) progress.report_init() elif isinstance(req, helpers.UiConfirmTotal): mods = utils.unimport_begin() res = await layout.confirm_total(ctx, req.spending, req.fee, req.coin) utils.unimport_end(mods) progress.report_init() elif isinstance(req, helpers.UiConfirmFeeOverThreshold): mods = utils.unimport_begin() res = await layout.confirm_feeoverthreshold(ctx, req.fee, req.coin) utils.unimport_end(mods) progress.report_init() elif isinstance(req, helpers.UiConfirmNonDefaultLocktime): mods = utils.unimport_begin() res = await layout.confirm_nondefault_locktime(ctx, req.lock_time) utils.unimport_end(mods) progress.report_init() elif isinstance(req, helpers.UiConfirmForeignAddress): mods = utils.unimport_begin() res = await paths.show_path_warning(ctx, req.address_n) utils.unimport_end(mods) progress.report_init() else: raise TypeError("Invalid signing instruction") return req
async def sign_tx(ctx, msg, keychain): signer = signing.sign_tx(msg, keychain) res = None while True: try: req = signer.send(res) except signing.SigningError as e: raise wire.Error(*e.args) except multisig.MultisigError as e: raise wire.Error(*e.args) except addresses.AddressError as e: raise wire.Error(*e.args) except scripts.ScriptsError as e: raise wire.Error(*e.args) except segwit_bip143.Bip143Error as e: raise wire.Error(*e.args) if isinstance(req, TxRequest): if req.request_type == TXFINISHED: break res = await ctx.call(req, TxAck) elif isinstance(req, helpers.UiConfirmOutput): mods = utils.unimport_begin() res = await layout.confirm_output(ctx, req.output, req.coin) utils.unimport_end(mods) progress.report_init() elif isinstance(req, helpers.UiConfirmTotal): mods = utils.unimport_begin() res = await layout.confirm_total(ctx, req.spending, req.fee, req.coin) utils.unimport_end(mods) progress.report_init() elif isinstance(req, helpers.UiConfirmFeeOverThreshold): mods = utils.unimport_begin() res = await layout.confirm_feeoverthreshold(ctx, req.fee, req.coin) utils.unimport_end(mods) progress.report_init() elif isinstance(req, helpers.UiConfirmNonDefaultLocktime): mods = utils.unimport_begin() res = await layout.confirm_nondefault_locktime(ctx, req.lock_time) utils.unimport_end(mods) progress.report_init() elif isinstance(req, helpers.UiConfirmForeignAddress): mods = utils.unimport_begin() res = await paths.show_path_warning(ctx, req.address_n) utils.unimport_end(mods) progress.report_init() else: raise TypeError("Invalid signing instruction") return req
async def session_handler(iface: WireInterface, sid: int) -> None: reader = None ctx = Context(iface, sid) while True: try: # wait for new message, if needed, and find handler if not reader: reader = ctx.make_reader() await reader.aopen() try: handler, args = workflow_handlers[reader.type] except KeyError: handler, args = unexpected_msg, () m = utils.unimport_begin() w = handler(ctx, reader, *args) try: workflow.onstart(w) await w finally: workflow.onclose(w) utils.unimport_end(m) except UnexpectedMessageError as exc: # retry with opened reader from the exception reader = exc.reader continue except Error as exc: # we log wire.Error as warning, not as exception if __debug__: log.warning(__name__, "failure: %s", exc.message) except Exception as exc: # sessions are never closed by raised exceptions if __debug__: log.exception(__name__, exc) # read new message in next iteration reader = None
async def set_output( state: State, dst_entr: MoneroTransactionDestinationEntry, dst_entr_hmac: bytes, rsig_data: MoneroTransactionRsigData, is_offloaded_bp=False, ) -> MoneroTransactionSetOutputAck: state.mem_trace(0, True) mods = utils.unimport_begin() # Progress update only for master message (skip for offloaded BP msg) if not is_offloaded_bp: await layout.transaction_step(state, state.STEP_OUT, state.current_output_index + 1) state.mem_trace(1, True) dst_entr = _validate(state, dst_entr, dst_entr_hmac, is_offloaded_bp) state.mem_trace(2, True) if not state.is_processing_offloaded: # First output - we include the size of the container into the tx prefix hasher if state.current_output_index == 0: state.tx_prefix_hasher.uvarint(state.output_count) state.mem_trace(4, True) state.output_amounts.append(dst_entr.amount) state.summary_outs_money += dst_entr.amount utils.unimport_end(mods) state.mem_trace(5, True) # Compute tx keys and masks if applicable tx_out_key, amount_key, derivation = _compute_tx_keys(state, dst_entr) utils.unimport_end(mods) state.mem_trace(6, True) # Range proof first, memory intensive (fragmentation) rsig_data_new, mask = _range_proof(state, rsig_data) utils.unimport_end(mods) state.mem_trace(7, True) # If det masks & offloading, return as we are handling offloaded BP. if state.is_processing_offloaded: from trezor.messages import MoneroTransactionSetOutputAck return MoneroTransactionSetOutputAck() # Tx header prefix hashing, hmac dst_entr tx_out_bin, hmac_vouti = _set_out_tx_out( state, dst_entr, tx_out_key, derivation, ) del (derivation, ) state.mem_trace(11, True) out_pk_dest, out_pk_commitment, ecdh_info_bin = _get_ecdh_info_and_out_pk( state=state, tx_out_key=tx_out_key, amount=dst_entr.amount, mask=mask, amount_key=amount_key, ) del (dst_entr, mask, amount_key, tx_out_key) state.mem_trace(12, True) # Incremental hashing of the ECDH info. # RctSigBase allows to hash only one of the (ecdh, out_pk) as they are serialized # as whole vectors. We choose to hash ECDH first, because it saves state space. state.full_message_hasher.set_ecdh(ecdh_info_bin) state.mem_trace(13, True) # output_pk_commitment is stored to the state as it is used during the signature and hashed to the # RctSigBase later. No need to store amount, it was already stored. state.output_pk_commitments.append(out_pk_commitment) state.last_step = state.STEP_OUT state.mem_trace(14, True) from trezor.messages import MoneroTransactionSetOutputAck out_pk_bin = bytearray(64) utils.memcpy(out_pk_bin, 0, out_pk_dest, 0, 32) utils.memcpy(out_pk_bin, 32, out_pk_commitment, 0, 32) return MoneroTransactionSetOutputAck( tx_out=tx_out_bin, vouti_hmac=hmac_vouti, rsig_data=rsig_data_new, out_pk=out_pk_bin, ecdh_info=ecdh_info_bin, )
async def handle_session(iface: WireInterface, session_id: int) -> None: ctx = Context(iface, session_id) next_reader = None # type: Optional[codec_v1.Reader] res_msg = None req_reader = None req_type = None req_msg = None while True: try: if next_reader is None: # We are not currently reading a message, so let's wait for one. # If the decoding fails, exception is raised and we try again # (with the same `Reader` instance, it's OK). Even in case of # de-synchronized wire communication, report with a message # header is eventually received, after a couple of tries. req_reader = ctx.make_reader() await req_reader.aopen() if __debug__: log.debug( __name__, "%s:%x receive: %s", iface.iface_num(), session_id, messages.get_type(req_reader.type), ) else: # We have a reader left over from earlier. We should process # this message instead of waiting for new one. req_reader = next_reader next_reader = None # Now we are in a middle of reading a message and we need to decide # what to do with it, based on its type from the message header. # From this point on, we should take care to read it in full and # send a response. # Take a mark of modules that are imported at this point, so we can # roll back and un-import any others. Should not raise. modules = utils.unimport_begin() # We need to find a handler for this message type. Should not # raise. handler = get_workflow_handler(req_reader) if handler is None: # If no handler is found, we can skip decoding and directly # respond with failure, but first, we should read the rest of # the message reports. Should not raise. await read_and_throw_away(req_reader) res_msg = unexpected_message() else: # We found a valid handler for this message type. # Workflow task, declared for the `workflow.on_close` call later. wf_task = None # type: Optional[loop.Task] # Here we make sure we always respond with a Failure response # in case of any errors. try: # Find a protobuf.MessageType subclass that describes this # message. Raises if the type is not found. req_type = messages.get_type(req_reader.type) # Try to decode the message according to schema from # `req_type`. Raises if the message is malformed. req_msg = await protobuf.load_message(req_reader, req_type) # At this point, message reports are all processed and # correctly parsed into `req_msg`. # Create the workflow task. wf_task = handler(ctx, req_msg) # Register the task into the workflow management system. workflow.on_start(wf_task) # Run the workflow task. Workflow can do more on-the-wire # communication inside, but it should eventually return a # response message, or raise an exception (a rather common # thing to do). Exceptions are handled in the code below. res_msg = await wf_task except UnexpectedMessageError as exc: # Workflow was trying to read a message from the wire, and # something unexpected came in. See Context.read() for # example, which expects some particular message and raises # UnexpectedMessageError if another one comes in. # In order not to lose the message, we pass on the reader # to get picked up by the workflow logic in the beginning of # the cycle, which processes it in the usual manner. # TODO: # We might handle only the few common cases here, like # Initialize and Cancel. next_reader = exc.reader res_msg = None except Exception as exc: # Either: # - the first workflow message had a type that has a # registered handler, but does not have a protobuf class # - the first workflow message was not a valid protobuf # - workflow raised some kind of an exception while running if __debug__: if isinstance(exc, ActionCancelled): log.debug(__name__, "cancelled: {}".format(exc.message)) else: log.exception(__name__, exc) res_msg = failure(exc) finally: # De-register the task from the workflow system, if we # registered it before. if wf_task is not None: workflow.on_close(wf_task) # If a default workflow is on, make sure we do not race # against the layout that is inside. # TODO: this is very hacky and complects wire with the ui if workflow.default_task is not None: await ui.wait_until_layout_is_running() if res_msg is not None: # Either the workflow returned a response, or we created one. # Write it on the wire. Possibly, the incoming message haven't # been read in full. We ignore this case here and let the rest # of the reports get processed while waiting for the message # header. # TODO: if the write fails, we do not unimport the loaded modules await ctx.write(res_msg) # Cleanup, so garbage collection triggered after un-importing can # pick up the trash. req_reader = None req_type = None req_msg = None res_msg = None handler = None wf_task = None # Unload modules imported by the workflow. Should not raise. utils.unimport_end(modules) except BaseException as exc: # The session handling should never exit, just log and continue. if __debug__: log.exception(__name__, exc)
async def sign_input( state: State, src_entr: MoneroTransactionSourceEntry, vini_bin: bytes, vini_hmac: bytes, pseudo_out: bytes, pseudo_out_hmac: bytes, pseudo_out_alpha_enc: bytes, spend_enc: bytes, orig_idx: int, ) -> MoneroTransactionSignInputAck: """ :param state: transaction state :param src_entr: Source entry :param vini_bin: tx.vin[i] for the transaction. Contains key image, offsets, amount (usually zero) :param vini_hmac: HMAC for the tx.vin[i] as returned from Trezor :param pseudo_out: Pedersen commitment for the current input, uses pseudo_out_alpha as a mask. Only applicable for RCTTypeSimple. :param pseudo_out_hmac: HMAC for pseudo_out :param pseudo_out_alpha_enc: alpha mask used in pseudo_out, only applicable for RCTTypeSimple. Encrypted. :param spend_enc: one time address spending private key. Encrypted. :param orig_idx: original index of the src_entr before sorting (HMAC check) :return: Generated signature MGs[i] """ await layout.transaction_step(state, state.STEP_SIGN, state.current_input_index + 1) state.current_input_index += 1 if state.last_step not in (state.STEP_ALL_OUT, state.STEP_SIGN): raise ValueError("Invalid state transition") if state.current_input_index >= state.input_count: raise ValueError("Invalid inputs count") if pseudo_out is None: raise ValueError("SimpleRCT requires pseudo_out but none provided") if pseudo_out_alpha_enc is None: raise ValueError( "SimpleRCT requires pseudo_out's mask but none provided") input_position = orig_idx mods = utils.unimport_begin() # Check input's HMAC from apps.monero.signing import offloading_keys vini_hmac_comp = offloading_keys.gen_hmac_vini(state.key_hmac, src_entr, vini_bin, input_position) if not crypto.ct_equals(vini_hmac_comp, vini_hmac): raise ValueError("HMAC is not correct") # Key image sorting check - permutation correctness cur_ki = offloading_keys.get_ki_from_vini(vini_bin) if state.current_input_index > 0 and state.last_ki <= cur_ki: raise ValueError("Key image order invalid") state.last_ki = cur_ki if state.current_input_index < state.input_count else None del (cur_ki, vini_bin, vini_hmac, vini_hmac_comp) gc.collect() state.mem_trace(1, True) from apps.monero.xmr import chacha_poly pseudo_out_alpha = crypto_helpers.decodeint( chacha_poly.decrypt_pack( offloading_keys.enc_key_txin_alpha(state.key_enc, input_position), bytes(pseudo_out_alpha_enc), )) # Last pseudo_out is recomputed so mask sums hold if input_position + 1 == state.input_count: # Recompute the lash alpha so the sum holds state.mem_trace("Correcting alpha") alpha_diff = crypto.sc_sub_into(None, state.sumout, state.sumpouts_alphas) crypto.sc_add_into(pseudo_out_alpha, pseudo_out_alpha, alpha_diff) pseudo_out_c = crypto.gen_commitment_into(None, pseudo_out_alpha, state.input_last_amount) else: if input_position + 1 == state.input_count: utils.ensure( crypto.sc_eq(state.sumpouts_alphas, state.sumout) != 0, "Sum eq error") # both pseudo_out and its mask were offloaded so we need to # validate pseudo_out's HMAC and decrypt the alpha pseudo_out_hmac_comp = crypto_helpers.compute_hmac( offloading_keys.hmac_key_txin_comm(state.key_hmac, input_position), pseudo_out, ) if not crypto.ct_equals(pseudo_out_hmac_comp, pseudo_out_hmac): raise ValueError("HMAC is not correct") pseudo_out_c = crypto_helpers.decodepoint(pseudo_out) state.mem_trace(2, True) # Spending secret spend_key = crypto_helpers.decodeint( chacha_poly.decrypt_pack( offloading_keys.enc_key_spend(state.key_enc, input_position), bytes(spend_enc), )) del ( offloading_keys, chacha_poly, pseudo_out, pseudo_out_hmac, pseudo_out_alpha_enc, spend_enc, ) utils.unimport_end(mods) state.mem_trace(3, True) # Basic setup, sanity check from apps.monero.xmr.serialize_messages.tx_ct_key import CtKey index = src_entr.real_output input_secret_key = CtKey(spend_key, crypto_helpers.decodeint(src_entr.mask)) # Private key correctness test utils.ensure( crypto.point_eq( crypto_helpers.decodepoint( src_entr.outputs[src_entr.real_output].key.dest), crypto.scalarmult_base_into(None, input_secret_key.dest), ), "Real source entry's destination does not equal spend key's", ) utils.ensure( crypto.point_eq( crypto_helpers.decodepoint( src_entr.outputs[src_entr.real_output].key.commitment), crypto.gen_commitment_into(None, input_secret_key.mask, src_entr.amount), ), "Real source entry's mask does not equal spend key's", ) state.mem_trace(4, True) from apps.monero.xmr import clsag mg_buffer = [] ring_pubkeys = [x.key for x in src_entr.outputs if x] utils.ensure(len(ring_pubkeys) == len(src_entr.outputs), "Invalid ring") del src_entr state.mem_trace(5, True) assert state.full_message is not None state.mem_trace("CLSAG") clsag.generate_clsag_simple( state.full_message, ring_pubkeys, input_secret_key, pseudo_out_alpha, pseudo_out_c, index, mg_buffer, ) del (CtKey, input_secret_key, pseudo_out_alpha, clsag, ring_pubkeys) state.mem_trace(6, True) from trezor.messages import MoneroTransactionSignInputAck # Encrypt signature, reveal once protocol finishes OK utils.unimport_end(mods) state.mem_trace(7, True) mg_buffer = _protect_signature(state, mg_buffer) state.mem_trace(8, True) state.last_step = state.STEP_SIGN return MoneroTransactionSignInputAck( signature=mg_buffer, pseudo_out=crypto_helpers.encodepoint(pseudo_out_c))
async def set_output(state: State, dst_entr, dst_entr_hmac, rsig_data): state.mem_trace(0, True) mods = utils.unimport_begin() await confirms.transaction_step(state.ctx, state.STEP_OUT, state.current_output_index + 1, state.output_count) state.mem_trace(1) state.current_output_index += 1 state.mem_trace(2, True) await _validate(state, dst_entr, dst_entr_hmac) # First output - we include the size of the container into the tx prefix hasher if state.current_output_index == 0: state.tx_prefix_hasher.uvarint(state.output_count) state.mem_trace(4, True) state.output_amounts.append(dst_entr.amount) state.summary_outs_money += dst_entr.amount utils.unimport_end(mods) state.mem_trace(5, True) # Range proof first, memory intensive rsig, mask = _range_proof(state, dst_entr.amount, rsig_data) utils.unimport_end(mods) state.mem_trace(6, True) # additional tx key if applicable additional_txkey_priv = _set_out_additional_keys(state, dst_entr) # derivation = a*R or r*A or s*C derivation = _set_out_derivation(state, dst_entr, additional_txkey_priv) # amount key = H_s(derivation || i) amount_key = crypto.derivation_to_scalar(derivation, state.current_output_index) # one-time destination address P = H_s(derivation || i)*G + B tx_out_key = crypto.derive_public_key( derivation, state.current_output_index, crypto.decodepoint(dst_entr.addr.spend_public_key), ) del (derivation, additional_txkey_priv) state.mem_trace(7, True) # Tx header prefix hashing, hmac dst_entr tx_out_bin, hmac_vouti = await _set_out_tx_out(state, dst_entr, tx_out_key) state.mem_trace(11, True) out_pk_dest, out_pk_commitment, ecdh_info_bin = _get_ecdh_info_and_out_pk( state=state, tx_out_key=tx_out_key, amount=dst_entr.amount, mask=mask, amount_key=amount_key, ) del (dst_entr, mask, amount_key, tx_out_key) state.mem_trace(12, True) # Incremental hashing of the ECDH info. # RctSigBase allows to hash only one of the (ecdh, out_pk) as they are serialized # as whole vectors. We choose to hash ECDH first, because it saves state space. state.full_message_hasher.set_ecdh(ecdh_info_bin) state.mem_trace(13, True) # output_pk_commitment is stored to the state as it is used during the signature and hashed to the # RctSigBase later. No need to store amount, it was already stored. state.output_pk_commitments.append(out_pk_commitment) state.mem_trace(14, True) from trezor.messages.MoneroTransactionSetOutputAck import ( MoneroTransactionSetOutputAck, ) out_pk_bin = bytearray(64) utils.memcpy(out_pk_bin, 0, out_pk_dest, 0, 32) utils.memcpy(out_pk_bin, 32, out_pk_commitment, 0, 32) return MoneroTransactionSetOutputAck( tx_out=tx_out_bin, vouti_hmac=hmac_vouti, rsig_data=_return_rsig_data(rsig), out_pk=out_pk_bin, ecdh_info=ecdh_info_bin, )
async def sign_input( state: State, src_entr: MoneroTransactionSourceEntry, vini_bin: bytes, vini_hmac: bytes, pseudo_out: bytes, pseudo_out_hmac: bytes, pseudo_out_alpha_enc: bytes, spend_enc: bytes, ): """ :param state: transaction state :param src_entr: Source entry :param vini_bin: tx.vin[i] for the transaction. Contains key image, offsets, amount (usually zero) :param vini_hmac: HMAC for the tx.vin[i] as returned from Trezor :param pseudo_out: Pedersen commitment for the current input, uses pseudo_out_alpha as a mask. Only applicable for RCTTypeSimple. :param pseudo_out_hmac: HMAC for pseudo_out :param pseudo_out_alpha_enc: alpha mask used in pseudo_out, only applicable for RCTTypeSimple. Encrypted. :param spend_enc: one time address spending private key. Encrypted. :return: Generated signature MGs[i] """ await confirms.transaction_step(state, state.STEP_SIGN, state.current_input_index + 1) state.current_input_index += 1 if state.current_input_index >= state.input_count: raise ValueError("Invalid inputs count") if pseudo_out is None: raise ValueError("SimpleRCT requires pseudo_out but none provided") if pseudo_out_alpha_enc is None: raise ValueError( "SimpleRCT requires pseudo_out's mask but none provided") input_position = state.source_permutation[state.current_input_index] mods = utils.unimport_begin() # Check input's HMAC from apps.monero.signing import offloading_keys vini_hmac_comp = await offloading_keys.gen_hmac_vini( state.key_hmac, src_entr, vini_bin, input_position) if not crypto.ct_equals(vini_hmac_comp, vini_hmac): raise ValueError("HMAC is not correct") gc.collect() state.mem_trace(1, True) from apps.monero.xmr.crypto import chacha_poly pseudo_out_alpha = crypto.decodeint( chacha_poly.decrypt_pack( offloading_keys.enc_key_txin_alpha(state.key_enc, input_position), bytes(pseudo_out_alpha_enc), )) # Last pseud_out is recomputed so mask sums hold if state.is_det_mask() and input_position + 1 == state.input_count: # Recompute the lash alpha so the sum holds state.mem_trace("Correcting alpha") alpha_diff = crypto.sc_sub(state.sumout, state.sumpouts_alphas) crypto.sc_add_into(pseudo_out_alpha, pseudo_out_alpha, alpha_diff) pseudo_out_c = crypto.gen_commitment(pseudo_out_alpha, state.input_last_amount) else: if input_position + 1 == state.input_count: utils.ensure(crypto.sc_eq(state.sumpouts_alphas, state.sumout), "Sum eq error") # both pseudo_out and its mask were offloaded so we need to # validate pseudo_out's HMAC and decrypt the alpha pseudo_out_hmac_comp = crypto.compute_hmac( offloading_keys.hmac_key_txin_comm(state.key_hmac, input_position), pseudo_out, ) if not crypto.ct_equals(pseudo_out_hmac_comp, pseudo_out_hmac): raise ValueError("HMAC is not correct") pseudo_out_c = crypto.decodepoint(pseudo_out) state.mem_trace(2, True) # Spending secret spend_key = crypto.decodeint( chacha_poly.decrypt_pack( offloading_keys.enc_key_spend(state.key_enc, input_position), bytes(spend_enc), )) del ( offloading_keys, chacha_poly, pseudo_out, pseudo_out_hmac, pseudo_out_alpha_enc, spend_enc, ) utils.unimport_end(mods) state.mem_trace(3, True) from apps.monero.xmr.serialize_messages.ct_keys import CtKey # Basic setup, sanity check index = src_entr.real_output input_secret_key = CtKey(dest=spend_key, mask=crypto.decodeint(src_entr.mask)) kLRki = None # for multisig: src_entr.multisig_kLRki # Private key correctness test utils.ensure( crypto.point_eq( crypto.decodepoint( src_entr.outputs[src_entr.real_output].key.dest), crypto.scalarmult_base(input_secret_key.dest), ), "Real source entry's destination does not equal spend key's", ) utils.ensure( crypto.point_eq( crypto.decodepoint( src_entr.outputs[src_entr.real_output].key.commitment), crypto.gen_commitment(input_secret_key.mask, src_entr.amount), ), "Real source entry's mask does not equal spend key's", ) state.mem_trace(4, True) from apps.monero.xmr import mlsag mg_buffer = [] ring_pubkeys = [x.key for x in src_entr.outputs] del src_entr mlsag.generate_mlsag_simple( state.full_message, ring_pubkeys, input_secret_key, pseudo_out_alpha, pseudo_out_c, kLRki, index, mg_buffer, ) del (input_secret_key, pseudo_out_alpha, mlsag, ring_pubkeys) state.mem_trace(5, True) from trezor.messages.MoneroTransactionSignInputAck import ( MoneroTransactionSignInputAck, ) return MoneroTransactionSignInputAck( signature=mg_buffer, pseudo_out=crypto.encodepoint(pseudo_out_c))