mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-23 23:08:14 +00:00
xmr: get_tx_keys, live_refresh
This commit is contained in:
parent
4dc8110b31
commit
ab9ab25355
5
Pipfile.lock
generated
5
Pipfile.lock
generated
@ -423,10 +423,9 @@
|
||||
},
|
||||
"py-cryptonight": {
|
||||
"hashes": [
|
||||
"sha256:da83570f040bb6c4caad05d28ca7ffc4a0c983a18c60c733366ae8858d587b1f",
|
||||
"sha256:fd55a7915b68b7d46794de646d9bff7fb1870b905c83903e36e1b9cb50cc01d2"
|
||||
"sha256:18ef26c56d7b677c527932af37a9bc98f39e4a56a87a003ef9a3a994d8bfe080"
|
||||
],
|
||||
"version": "==0.1.6"
|
||||
"version": "==0.1.8"
|
||||
},
|
||||
"py-trezor-crypto-ph4": {
|
||||
"hashes": [
|
||||
|
@ -15,6 +15,8 @@ def boot():
|
||||
wire.add(
|
||||
MessageType.MoneroKeyImageExportInitRequest, __name__, "key_image_sync", ns
|
||||
)
|
||||
wire.add(MessageType.MoneroGetTxKeyRequest, __name__, "get_tx_keys", ns)
|
||||
wire.add(MessageType.MoneroLiveRefreshStartRequest, __name__, "live_refresh", ns)
|
||||
|
||||
if __debug__ and hasattr(MessageType, "DebugMoneroDiagRequest"):
|
||||
wire.add(MessageType.DebugMoneroDiagRequest, __name__, "diag")
|
||||
|
77
src/apps/monero/get_tx_keys.py
Normal file
77
src/apps/monero/get_tx_keys.py
Normal file
@ -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
|
@ -21,6 +21,25 @@ async def require_confirm_keyimage_sync(ctx):
|
||||
return await require_confirm(ctx, content, ButtonRequestType.SignTx)
|
||||
|
||||
|
||||
async def require_confirm_live_refresh(ctx):
|
||||
content = Text("Confirm ki sync", ui.ICON_SEND, icon_color=ui.GREEN)
|
||||
content.normal("Do you really want to", "start refresh?")
|
||||
return await require_confirm(ctx, content, ButtonRequestType.SignTx)
|
||||
|
||||
|
||||
async def require_confirm_tx_key(ctx, export_key=False):
|
||||
content = Text("Confirm export", ui.ICON_SEND, icon_color=ui.GREEN)
|
||||
txt = ["Do you really want to"]
|
||||
if export_key:
|
||||
txt.append("export tx_key?")
|
||||
else:
|
||||
txt.append("export tx_der")
|
||||
txt.append("for tx_proof?")
|
||||
|
||||
content.normal(*txt)
|
||||
return await require_confirm(ctx, content, ButtonRequestType.SignTx)
|
||||
|
||||
|
||||
async def require_confirm_transaction(ctx, tsx_data, network_type):
|
||||
"""
|
||||
Ask for confirmation from user.
|
||||
@ -132,3 +151,12 @@ async def keyimage_sync_step(ctx, current, total_num):
|
||||
text = Text("Syncing", ui.ICON_SEND, icon_color=ui.BLUE)
|
||||
text.normal("%d/%d" % (current + 1, total_num))
|
||||
text.render()
|
||||
|
||||
|
||||
@ui.layout
|
||||
async def live_refresh_step(ctx, current):
|
||||
if current is None:
|
||||
return
|
||||
text = Text("Refreshing", ui.ICON_SEND, icon_color=ui.BLUE)
|
||||
text.normal("%d" % current)
|
||||
text.render()
|
||||
|
91
src/apps/monero/live_refresh.py
Normal file
91
src/apps/monero/live_refresh.py
Normal file
@ -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)
|
@ -1,5 +1,8 @@
|
||||
from apps.common import HARDENED
|
||||
|
||||
if False:
|
||||
from apps.monero.xmr.types import *
|
||||
|
||||
|
||||
def get_creds(keychain, address_n=None, network_type=None):
|
||||
from apps.monero.xmr import crypto, monero
|
||||
@ -37,3 +40,28 @@ def validate_full_path(path: list) -> bool:
|
||||
if path[2] < HARDENED or path[2] > 1000000 | HARDENED:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def compute_tx_key(
|
||||
spend_key_private: Sc25519,
|
||||
tx_prefix_hash: bytes,
|
||||
salt: bytes,
|
||||
rand_mult_num: Sc25519,
|
||||
) -> bytes:
|
||||
from apps.monero.xmr import crypto
|
||||
|
||||
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)
|
||||
return tx_key
|
||||
|
||||
|
||||
def compute_enc_key_host(
|
||||
view_key_private: Sc25519, tx_prefix_hash: bytes
|
||||
) -> Tuple[bytes, bytes]:
|
||||
from apps.monero.xmr import crypto
|
||||
|
||||
salt = crypto.random_bytes(32)
|
||||
passwd = crypto.keccak_2hash(crypto.encodeint(view_key_private) + tx_prefix_hash)
|
||||
tx_key = crypto.compute_hmac(salt, passwd)
|
||||
return tx_key, salt
|
||||
|
@ -12,6 +12,7 @@ from trezor.messages.MoneroTransactionFinalAck import MoneroTransactionFinalAck
|
||||
|
||||
from .state import State
|
||||
|
||||
from apps.monero import misc
|
||||
from apps.monero.xmr import crypto
|
||||
from apps.monero.xmr.crypto import chacha_poly
|
||||
|
||||
@ -37,7 +38,5 @@ def _compute_tx_key(spend_key_private, tx_prefix_hash):
|
||||
rand_mult_num = crypto.random_scalar()
|
||||
rand_mult = crypto.encodeint(rand_mult_num)
|
||||
|
||||
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 = misc.compute_tx_key(spend_key_private, tx_prefix_hash, salt, rand_mult_num)
|
||||
return tx_key, salt, rand_mult
|
||||
|
@ -40,12 +40,12 @@ def _export_key_image(
|
||||
xi, ki, recv_derivation = r[:3]
|
||||
|
||||
phash = crypto.encodepoint(ki)
|
||||
sig = _generate_ring_signature(phash, ki, [pkey], xi, 0, test)
|
||||
sig = generate_ring_signature(phash, ki, [pkey], xi, 0, test)
|
||||
|
||||
return ki, sig
|
||||
|
||||
|
||||
def _generate_ring_signature(prefix_hash, image, pubs, sec, sec_idx, test=False):
|
||||
def generate_ring_signature(prefix_hash, image, pubs, sec, sec_idx, test=False):
|
||||
"""
|
||||
Generates ring signature with key image.
|
||||
void crypto_ops::generate_ring_signature()
|
||||
|
@ -16,6 +16,8 @@ cd ..
|
||||
|
||||
export EC_BACKEND_FORCE=1
|
||||
export EC_BACKEND=1
|
||||
export TREZOR_TEST_GET_TX=1
|
||||
export TREZOR_TEST_LIVE_REFRESH=1
|
||||
python3 -m unittest trezor_monero_test.test_trezor
|
||||
error=$?
|
||||
kill $upy_pid
|
||||
|
Loading…
Reference in New Issue
Block a user