xmr: get_tx_keys, live_refresh

pull/25/head
Dusan Klinec 5 years ago
parent 4dc8110b31
commit ab9ab25355
No known key found for this signature in database
GPG Key ID: 6337E118CCBCE103

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")

@ -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()

@ -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…
Cancel
Save