1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-11-18 05:28:40 +00:00

Merge pull request #442 from ph4r05/ph4r05/xmr-get-txkey

xmr: get_tx_keys, live_refresh
This commit is contained in:
Tomas Susanka 2019-02-13 13:50:30 +01:00 committed by GitHub
commit e81f89288f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 234 additions and 8 deletions

5
Pipfile.lock generated
View File

@ -423,10 +423,9 @@
}, },
"py-cryptonight": { "py-cryptonight": {
"hashes": [ "hashes": [
"sha256:da83570f040bb6c4caad05d28ca7ffc4a0c983a18c60c733366ae8858d587b1f", "sha256:18ef26c56d7b677c527932af37a9bc98f39e4a56a87a003ef9a3a994d8bfe080"
"sha256:fd55a7915b68b7d46794de646d9bff7fb1870b905c83903e36e1b9cb50cc01d2"
], ],
"version": "==0.1.6" "version": "==0.1.8"
}, },
"py-trezor-crypto-ph4": { "py-trezor-crypto-ph4": {
"hashes": [ "hashes": [

View File

@ -15,6 +15,8 @@ def boot():
wire.add( wire.add(
MessageType.MoneroKeyImageExportInitRequest, __name__, "key_image_sync", ns 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"): if __debug__ and hasattr(MessageType, "DebugMoneroDiagRequest"):
wire.add(MessageType.DebugMoneroDiagRequest, __name__, "diag") wire.add(MessageType.DebugMoneroDiagRequest, __name__, "diag")

View 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

View File

@ -21,6 +21,25 @@ async def require_confirm_keyimage_sync(ctx):
return await require_confirm(ctx, content, ButtonRequestType.SignTx) 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): async def require_confirm_transaction(ctx, tsx_data, network_type):
""" """
Ask for confirmation from user. 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 = Text("Syncing", ui.ICON_SEND, icon_color=ui.BLUE)
text.normal("%d/%d" % (current + 1, total_num)) text.normal("%d/%d" % (current + 1, total_num))
text.render() 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()

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

View File

@ -1,5 +1,8 @@
from apps.common import HARDENED from apps.common import HARDENED
if False:
from apps.monero.xmr.types import *
def get_creds(keychain, address_n=None, network_type=None): def get_creds(keychain, address_n=None, network_type=None):
from apps.monero.xmr import crypto, monero 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: if path[2] < HARDENED or path[2] > 1000000 | HARDENED:
return False return False
return True 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

View File

@ -12,6 +12,7 @@ from trezor.messages.MoneroTransactionFinalAck import MoneroTransactionFinalAck
from .state import State from .state import State
from apps.monero import misc
from apps.monero.xmr import crypto from apps.monero.xmr import crypto
from apps.monero.xmr.crypto import chacha_poly 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_num = crypto.random_scalar()
rand_mult = crypto.encodeint(rand_mult_num) rand_mult = crypto.encodeint(rand_mult_num)
rand_inp = crypto.sc_add(spend_key_private, rand_mult_num) tx_key = misc.compute_tx_key(spend_key_private, tx_prefix_hash, salt, rand_mult_num)
passwd = crypto.keccak_2hash(crypto.encodeint(rand_inp) + tx_prefix_hash)
tx_key = crypto.compute_hmac(salt, passwd)
return tx_key, salt, rand_mult return tx_key, salt, rand_mult

View File

@ -40,12 +40,12 @@ def _export_key_image(
xi, ki, recv_derivation = r[:3] xi, ki, recv_derivation = r[:3]
phash = crypto.encodepoint(ki) 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 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. Generates ring signature with key image.
void crypto_ops::generate_ring_signature() void crypto_ops::generate_ring_signature()

View File

@ -16,6 +16,8 @@ cd ..
export EC_BACKEND_FORCE=1 export EC_BACKEND_FORCE=1
export EC_BACKEND=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 python3 -m unittest trezor_monero_test.test_trezor
error=$? error=$?
kill $upy_pid kill $upy_pid