parent
4dc8110b31
commit
ab9ab25355
@ -0,0 +1,77 @@
|
||||
"""
|
||||
This `get_tx_key` command supports retrieval of private tx keys (not spend keys,
|
||||
just random transaction privates `r` and additional private keys if applicable)
|
||||
required by users to check the transaction or when resolving disputes with
|
||||
the recipient.
|
||||
|
||||
It supports returning transaction derivations = private tx key * public view key.
|
||||
This enables to compute the tx_proof for outgoing transactions which are also
|
||||
a nice tool when resolving disputes, provides better protection as tx private
|
||||
key are not exported in this case.
|
||||
|
||||
This is related to singing/step10 where we send `tx_enc_keys` to the host
|
||||
encrypted using the private spend key. Here the host sends it back
|
||||
in `MoneroGetTxKeyRequest.tx_enc_keys` to be decrypted and yet again encrypted
|
||||
using the view key, which the host possess.
|
||||
"""
|
||||
|
||||
from trezor import utils
|
||||
from trezor.messages.MoneroGetTxKeyAck import MoneroGetTxKeyAck
|
||||
from trezor.messages.MoneroGetTxKeyRequest import MoneroGetTxKeyRequest
|
||||
|
||||
from apps.common import paths
|
||||
from apps.monero import misc
|
||||
from apps.monero.layout import confirms
|
||||
from apps.monero.xmr import crypto
|
||||
from apps.monero.xmr.crypto import chacha_poly
|
||||
|
||||
_GET_TX_KEY_REASON_TX_KEY = 0
|
||||
_GET_TX_KEY_REASON_TX_DERIVATION = 1
|
||||
|
||||
|
||||
async def get_tx_keys(ctx, msg: MoneroGetTxKeyRequest, keychain):
|
||||
await paths.validate_path(ctx, misc.validate_full_path, path=msg.address_n)
|
||||
|
||||
do_deriv = msg.reason == _GET_TX_KEY_REASON_TX_DERIVATION
|
||||
await confirms.require_confirm_tx_key(ctx, export_key=not do_deriv)
|
||||
|
||||
creds = misc.get_creds(keychain, msg.address_n, msg.network_type)
|
||||
|
||||
tx_enc_key = misc.compute_tx_key(
|
||||
creds.spend_key_private,
|
||||
msg.tx_prefix_hash,
|
||||
msg.salt1,
|
||||
crypto.decodeint(msg.salt2),
|
||||
)
|
||||
|
||||
# the plain_buff first stores the tx_priv_keys as decrypted here
|
||||
# and then is used to store the derivations if applicable
|
||||
plain_buff = chacha_poly.decrypt_pack(tx_enc_key, msg.tx_enc_keys)
|
||||
utils.ensure(len(plain_buff) % 32 == 0, "Tx key buffer has invalid size")
|
||||
del msg.tx_enc_keys
|
||||
|
||||
# If return only derivations do tx_priv * view_pub
|
||||
if do_deriv:
|
||||
plain_buff = bytearray(plain_buff)
|
||||
view_pub = crypto.decodepoint(msg.view_public_key)
|
||||
tx_priv = crypto.new_scalar()
|
||||
derivation = crypto.new_point()
|
||||
n_keys = len(plain_buff) // 32
|
||||
for c in range(n_keys):
|
||||
crypto.decodeint_into(tx_priv, plain_buff, 32 * c)
|
||||
crypto.scalarmult_into(derivation, view_pub, tx_priv)
|
||||
crypto.encodepoint_into(plain_buff, derivation, 32 * c)
|
||||
|
||||
# Encrypt by view-key based password.
|
||||
tx_enc_key_host, salt = misc.compute_enc_key_host(
|
||||
creds.view_key_private, msg.tx_prefix_hash
|
||||
)
|
||||
|
||||
res = chacha_poly.encrypt_pack(tx_enc_key_host, plain_buff)
|
||||
res_msg = MoneroGetTxKeyAck(salt=salt)
|
||||
if do_deriv:
|
||||
res_msg.tx_derivations = res
|
||||
return res_msg
|
||||
|
||||
res_msg.tx_keys = res
|
||||
return res_msg
|
@ -0,0 +1,91 @@
|
||||
import gc
|
||||
|
||||
from trezor import log
|
||||
from trezor.messages import MessageType
|
||||
from trezor.messages.MoneroLiveRefreshFinalAck import MoneroLiveRefreshFinalAck
|
||||
from trezor.messages.MoneroLiveRefreshStartAck import MoneroLiveRefreshStartAck
|
||||
from trezor.messages.MoneroLiveRefreshStartRequest import MoneroLiveRefreshStartRequest
|
||||
from trezor.messages.MoneroLiveRefreshStepAck import MoneroLiveRefreshStepAck
|
||||
from trezor.messages.MoneroLiveRefreshStepRequest import MoneroLiveRefreshStepRequest
|
||||
|
||||
from apps.common import paths
|
||||
from apps.monero import misc
|
||||
from apps.monero.layout import confirms
|
||||
from apps.monero.xmr import crypto, key_image, monero
|
||||
from apps.monero.xmr.crypto import chacha_poly
|
||||
|
||||
|
||||
async def live_refresh(ctx, msg: MoneroLiveRefreshStartRequest, keychain):
|
||||
state = LiveRefreshState()
|
||||
|
||||
res = await _init_step(state, ctx, msg, keychain)
|
||||
while True:
|
||||
msg = await ctx.call(
|
||||
res,
|
||||
MessageType.MoneroLiveRefreshStepRequest,
|
||||
MessageType.MoneroLiveRefreshFinalRequest,
|
||||
)
|
||||
del res
|
||||
if msg.MESSAGE_WIRE_TYPE == MessageType.MoneroLiveRefreshStepRequest:
|
||||
res = await _refresh_step(state, ctx, msg)
|
||||
else:
|
||||
return MoneroLiveRefreshFinalAck()
|
||||
gc.collect()
|
||||
|
||||
return res
|
||||
|
||||
|
||||
class LiveRefreshState:
|
||||
def __init__(self):
|
||||
self.current_output = -1
|
||||
self.creds = None
|
||||
|
||||
|
||||
async def _init_step(
|
||||
s: LiveRefreshState, ctx, msg: MoneroLiveRefreshStartRequest, keychain
|
||||
):
|
||||
await paths.validate_path(ctx, misc.validate_full_path, path=msg.address_n)
|
||||
|
||||
await confirms.require_confirm_live_refresh(ctx)
|
||||
|
||||
s.creds = misc.get_creds(keychain, msg.address_n, msg.network_type)
|
||||
|
||||
return MoneroLiveRefreshStartAck()
|
||||
|
||||
|
||||
async def _refresh_step(s: LiveRefreshState, ctx, msg: MoneroLiveRefreshStepRequest):
|
||||
buff = bytearray(32 * 3)
|
||||
buff_mv = memoryview(buff)
|
||||
|
||||
await confirms.live_refresh_step(ctx, s.current_output)
|
||||
s.current_output += 1
|
||||
|
||||
if __debug__:
|
||||
log.debug(__name__, "refresh, step i: %d", s.current_output)
|
||||
|
||||
# Compute spending secret key and the key image
|
||||
# spend_priv = Hs(recv_deriv || real_out_idx) + spend_key_private
|
||||
# If subaddr:
|
||||
# spend_priv += Hs("SubAddr" || view_key_private || major || minor)
|
||||
# out_key = spend_priv * G, KI: spend_priv * Hp(out_key)
|
||||
out_key = crypto.decodepoint(msg.out_key)
|
||||
recv_deriv = crypto.decodepoint(msg.recv_deriv)
|
||||
received_index = msg.sub_addr_major, msg.sub_addr_minor
|
||||
spend_priv, ki = monero.generate_tx_spend_and_key_image(
|
||||
s.creds, out_key, recv_deriv, msg.real_out_idx, received_index
|
||||
)
|
||||
|
||||
ki_enc = crypto.encodepoint(ki)
|
||||
sig = key_image.generate_ring_signature(ki_enc, ki, [out_key], spend_priv, 0, False)
|
||||
del spend_priv # spend_priv never leaves the device
|
||||
|
||||
# Serialize into buff
|
||||
buff[0:32] = ki_enc
|
||||
crypto.encodeint_into(buff_mv[32:64], sig[0][0])
|
||||
crypto.encodeint_into(buff_mv[64:], sig[0][1])
|
||||
|
||||
# Encrypt with view key private based key - so host can decrypt and verify HMAC
|
||||
enc_key, salt = misc.compute_enc_key_host(s.creds.view_key_private, msg.out_key)
|
||||
resp = chacha_poly.encrypt_pack(enc_key, buff)
|
||||
|
||||
return MoneroLiveRefreshStepAck(salt=salt, key_image=resp)
|
Loading…
Reference in new issue