Skip to content
This repository has been archived by the owner on May 28, 2019. It is now read-only.

Commit

Permalink
xmr: step 02 review
Browse files Browse the repository at this point in the history
  • Loading branch information
tsusanka authored and ph4r05 committed Oct 3, 2018
1 parent 626dd5f commit daf7b7d
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 120 deletions.
4 changes: 2 additions & 2 deletions src/apps/monero/protocol/signing/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ def __init__(self, ctx):

self.additional_tx_private_keys = []
self.additional_tx_public_keys = []
self.inp_idx = -1
self.out_idx = -1
self.current_input_index = -1
self.current_output_index = -1
self.summary_inputs_money = 0
self.summary_outs_money = 0
self.input_secrets = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ async def init_transaction(
_check_subaddresses(state, tsx_data.outputs)

# Extra processing, payment id
state.tx.version = 2 # current Monero transaction format
state.tx.version = 2 # current Monero transaction format (RingCT = 2)
state.tx.unlock_time = tsx_data.unlock_time
_process_payment_id(state, tsx_data)
await _compute_sec_keys(state, tsx_data)
Expand Down
104 changes: 63 additions & 41 deletions src/apps/monero/protocol/signing/step_02_set_input.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,45 @@
"""
Sets UTXO one by one.
Computes spending secret key, key image. tx.vin[i] + HMAC, Pedersen commitment on amount.
UTXOs are sent one by one to Trezor for processing, encoded as MoneroTransactionSourceEntry.
MoneroTransactionSourceEntry contains the actual UTXO to be spent, but also the other decoy/mixin
outputs. So all the outputs are in one list and then the `real_output` index specifies which output
is the real one to be spent.
This step computes spending secret key, key image, tx.vin[i] + HMAC, Pedersen commitment on amount.
If number of inputs is small, in-memory mode is used = alpha, pseudo_outs are kept in the Trezor.
Otherwise pseudo_outs are offloaded with HMAC, alpha is offloaded encrypted under AES-GCM() with
Otherwise pseudo_outs are offloaded with HMAC, alpha is offloaded encrypted under chacha_poly with
key derived for exactly this purpose.
"""

from trezor.messages.MoneroTransactionSourceEntry import MoneroTransactionSourceEntry

from .state import State

from apps.monero.controller import misc
from apps.monero.layout.confirms import transaction_step
from apps.monero.layout import confirms
from apps.monero.xmr import crypto, monero

if False:
from trezor.messages.MoneroTransactionSourceEntry import (
MoneroTransactionSourceEntry
)

async def set_input(state: State, src_entr: MoneroTransactionSourceEntry):
"""
Sets UTXO one by one.
Computes spending secret key, key image. tx.vin[i] + HMAC, Pedersen commitment on amount.

If number of inputs is small, in-memory mode is used = alpha, pseudo_outs are kept in the Trezor.
Otherwise pseudo_outs are offloaded with HMAC, alpha is offloaded encrypted under Chacha20Poly1305()
with key derived for exactly this purpose.
"""
async def set_input(state: State, src_entr: MoneroTransactionSourceEntry):
from trezor.messages.MoneroTransactionSetInputAck import (
MoneroTransactionSetInputAck
)
from apps.monero.xmr.enc import chacha_poly
from apps.monero.xmr.sub import tsx_helper
from apps.monero.xmr.serialize_messages.tx_prefix import TxinToKey
from apps.monero.protocol import hmac_encryption_keys

state.inp_idx += 1
state.current_input_index += 1

await transaction_step(state.STEP_INP, state.inp_idx, state.input_count)
await confirms.transaction_step(
state.STEP_INP, state.current_input_index, state.input_count
)

if state.inp_idx >= state.input_count:
if state.current_input_index >= state.input_count:
raise ValueError("Too many inputs")
# real_output denotes which output in outputs is the real one (ours)
if src_entr.real_output >= len(src_entr.outputs):
raise ValueError(
"real_output index %s bigger than output_keys.size() %s"
Expand All @@ -47,40 +48,49 @@ async def set_input(state: State, src_entr: MoneroTransactionSourceEntry):
state.summary_inputs_money += src_entr.amount

# Secrets derivation
# the UTXO's one-time address P
out_key = crypto.decodepoint(src_entr.outputs[src_entr.real_output].key.dest)
# the tx_pub of our UTXO stored inside its transaction
tx_key = crypto.decodepoint(src_entr.real_out_tx_key)
additional_keys = [
crypto.decodepoint(x) for x in src_entr.real_out_additional_tx_keys
]

secs = monero.generate_tx_spend_and_key_image_and_derivation(
"""
Calculates `derivation = Ra`, private spend key `x = H(Ra||i) + b` to be able
to spend the UTXO; and key image `I = x*H(P||i)`
"""
xi, ki, di = monero.generate_tx_spend_and_key_image_and_derivation(
state.creds,
state.subaddresses,
out_key,
tx_key,
additional_keys,
src_entr.real_output_in_tx_index,
)
xi, ki, di = secs
state.mem_trace(1, True)

# Construct tx.vin
ki_real = src_entr.multisig_kLRki.ki if state.multi_sig else ki
vini = TxinToKey(amount=src_entr.amount, k_image=crypto.encodepoint(ki_real))
vini.key_offsets = tsx_helper.absolute_output_offsets_to_relative(
vini.key_offsets = _absolute_output_offsets_to_relative(
[x.idx for x in src_entr.outputs]
)

if src_entr.rct:
vini.amount = 0

# Serialize with variant code for TxinToKey
"""
Serialize `vini` with variant code for TxinToKey (prefix = TxinToKey.VARIANT_CODE).
The binary `vini_bin` is later sent to step 4 and 9 with its hmac,
where it is checked and directly used.
"""
vini_bin = misc.dump_msg(vini, preallocate=64, prefix=b"\x02")
state.mem_trace(2, True)

# HMAC(T_in,i || vin_i)
hmac_vini = await hmac_encryption_keys.gen_hmac_vini(
state.key_hmac, src_entr, vini_bin, state.inp_idx
state.key_hmac, src_entr, vini_bin, state.current_input_index
)
state.mem_trace(3, True)

Expand All @@ -95,22 +105,29 @@ async def set_input(state: State, src_entr: MoneroTransactionSourceEntry):

# In full version the alpha is encrypted and passed back for storage
pseudo_out_hmac = crypto.compute_hmac(
hmac_encryption_keys.hmac_key_txin_comm(state.key_hmac, state.inp_idx),
hmac_encryption_keys.hmac_key_txin_comm(
state.key_hmac, state.current_input_index
),
pseudo_out,
)
alpha_enc = chacha_poly.encrypt_pack(
hmac_encryption_keys.enc_key_txin_alpha(state.key_enc, state.inp_idx),
hmac_encryption_keys.enc_key_txin_alpha(
state.key_enc, state.current_input_index
),
crypto.encodeint(alpha),
)

spend_enc = chacha_poly.encrypt_pack(
hmac_encryption_keys.enc_key_spend(state.key_enc, state.inp_idx),
hmac_encryption_keys.enc_key_spend(state.key_enc, state.current_input_index),
crypto.encodeint(xi),
)

# All inputs done?
if state.inp_idx + 1 == state.input_count:
tsx_inputs_done(state)
if state.current_input_index + 1 == state.input_count:
"""
When we finish the inputs processing, we no longer need
the precomputed subaddresses so we clear them to save memory.
"""
state.subaddresses = None

return MoneroTransactionSetInputAck(
vini=vini_bin,
Expand All @@ -122,17 +139,6 @@ async def set_input(state: State, src_entr: MoneroTransactionSourceEntry):
)


def tsx_inputs_done(state: State):
"""
All inputs set
"""
# self.state.input_done()
state.subaddresses = None # TODO why? remove this?

if state.inp_idx + 1 != state.input_count:
raise ValueError("Input count mismatch")


def _gen_commitment(state: State, in_amount):
"""
Computes Pedersen commitment - pseudo outs
Expand All @@ -147,3 +153,19 @@ def _gen_commitment(state: State, in_amount):
alpha = crypto.random_scalar()
state.sumpouts_alphas = crypto.sc_add(state.sumpouts_alphas, alpha)
return alpha, crypto.gen_commitment(alpha, in_amount)


def _absolute_output_offsets_to_relative(off):
"""
Mixin outputs are specified in relative numbers. First index is absolute
and the rest is an offset of a previous one.
Helps with varint encoding size.
Example: absolute {7,11,15,20} is converted to {7,4,4,5}
"""
if len(off) == 0:
return off
off.sort()
for i in range(len(off) - 1, 0, -1):
off[i] -= off[i - 1]
return off
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ def _tsx_inputs_permutation(state: State, permutation):
"""
state.source_permutation = permutation
common.check_permutation(permutation)
state.inp_idx = -1
state.current_input_index = -1
15 changes: 10 additions & 5 deletions src/apps/monero/protocol/signing/step_04_input_vini.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,27 @@ async def input_vini(
)

await confirms.transaction_step(
state.ctx, state.STEP_VINI, state.inp_idx + 1, state.input_count
state.ctx, state.STEP_VINI, state.current_input_index + 1, state.input_count
)

if state.inp_idx >= state.input_count:
if state.current_input_index >= state.input_count:
raise ValueError("Too many inputs")

state.inp_idx += 1
state.current_input_index += 1

# HMAC(T_in,i || vin_i)
hmac_vini = await hmac_encryption_keys.gen_hmac_vini(
state.key_hmac, src_entr, vini_bin, state.source_permutation[state.inp_idx]
state.key_hmac,
src_entr,
vini_bin,
state.source_permutation[state.current_input_index],
)
if not common.ct_equal(hmac_vini, hmac):
raise ValueError("HMAC is not correct")

hash_vini_pseudo_out(state, vini_bin, state.inp_idx, pseudo_out, pseudo_out_hmac)
hash_vini_pseudo_out(
state, vini_bin, state.current_input_index, pseudo_out, pseudo_out_hmac
)

# TODO check input count?
return MoneroTransactionInputViniAck()
Expand Down
22 changes: 13 additions & 9 deletions src/apps/monero/protocol/signing/step_06_set_out1.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,32 @@ async def set_out1(state: State, dst_entr, dst_entr_hmac, rsig_data=None):
mods = utils.unimport_begin()

await confirms.transaction_step(
state.ctx, state.STEP_OUT, state.out_idx + 1, state.output_count
state.ctx, state.STEP_OUT, state.current_output_index + 1, state.output_count
)
state.mem_trace(1)

if (
state.inp_idx + 1 != state.input_count
state.current_input_index + 1 != state.input_count
): # todo check state.state.is_input_vins() - needed?
raise ValueError("Invalid number of inputs")

state.out_idx += 1
state.current_output_index += 1
state.mem_trace(2, True)

if dst_entr.amount <= 0 and state.tx.version <= 1:
raise ValueError("Destination with wrong amount: %s" % dst_entr.amount)

# HMAC check of the destination
dst_entr_hmac_computed = await hmac_encryption_keys.gen_hmac_tsxdest(
state.key_hmac, dst_entr, state.out_idx
state.key_hmac, dst_entr, state.current_output_index
)
if not common.ct_equal(dst_entr_hmac, dst_entr_hmac_computed):
raise ValueError("HMAC invalid")
del (dst_entr_hmac, dst_entr_hmac_computed)
state.mem_trace(3, True)

# First output - tx prefix hasher - size of the container
if state.out_idx == 0:
if state.current_output_index == 0:
state.tx_prefix_hasher.uvarint(state.output_count)
state.mem_trace(4, True)

Expand All @@ -53,16 +53,20 @@ async def set_out1(state: State, dst_entr, dst_entr_hmac, rsig_data=None):
state.mem_trace(5, True)

# Range proof first, memory intensive
rsig, mask = _range_proof(state, state.out_idx, dst_entr.amount, rsig_data)
rsig, mask = _range_proof(
state, state.current_output_index, dst_entr.amount, rsig_data
)
utils.unimport_end(mods)
state.mem_trace(6, True)

# Amount key, tx out key
additional_txkey_priv = _set_out1_additional_keys(state, dst_entr)
derivation = _set_out1_derivation(state, dst_entr, additional_txkey_priv)
amount_key = crypto.derivation_to_scalar(derivation, state.out_idx)
amount_key = crypto.derivation_to_scalar(derivation, state.current_output_index)
tx_out_key = crypto.derive_public_key(
derivation, state.out_idx, crypto.decodepoint(dst_entr.addr.spend_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)
Expand Down Expand Up @@ -124,7 +128,7 @@ async def _set_out1_tx_out(state: State, dst_entr, tx_out_key):

# Hmac dest_entr.
hmac_vouti = await hmac_encryption_keys.gen_hmac_vouti(
state.key_hmac, dst_entr, tx_out_bin, state.out_idx
state.key_hmac, dst_entr, tx_out_bin, state.current_output_index
)
state.mem_trace(10, True)
return tx_out_bin, hmac_vouti
Expand Down
41 changes: 36 additions & 5 deletions src/apps/monero/protocol/signing/step_07_all_out1_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ async def all_out1_set(state: State):
await confirms.transaction_step(state.ctx, state.STEP_ALL_OUT)
state.mem_trace(1)

if state.out_idx + 1 != state.output_count:
if state.current_output_index + 1 != state.output_count:
raise ValueError("Invalid out num")

# Test if \sum Alpha == \sum A
Expand Down Expand Up @@ -89,12 +89,10 @@ async def all_out1_set(state: State):


def _set_tx_extra(state: State):
from apps.monero.xmr.sub import tsx_helper

state.tx.extra = tsx_helper.add_tx_pub_key_to_extra(state.tx.extra, state.tx_pub)
state.tx.extra = _add_tx_pub_key_to_extra(state.tx.extra, state.tx_pub)

if state.need_additional_txkeys:
state.tx.extra = tsx_helper.add_additional_tx_pub_keys_to_extra(
state.tx.extra = _add_additional_tx_pub_keys_to_extra(
state.tx.extra, state.additional_tx_public_keys
)

Expand All @@ -110,3 +108,36 @@ def _set_tx_prefix(state: State):

# Hash message to the final_message
state.full_message_hasher.set_message(state.tx_prefix_hash)


def _add_tx_pub_key_to_extra(tx_extra, pub_key):
"""
Adds public key to the extra
"""
to_add = bytearray(33)
to_add[0] = 1 # TX_EXTRA_TAG_PUBKEY
crypto.encodepoint_into(memoryview(to_add)[1:], pub_key)
return tx_extra + to_add


def _add_additional_tx_pub_keys_to_extra(tx_extra, pub_enc):
"""
Adds all additional tx public keys to the extra buffer
"""
from apps.monero.xmr.serialize import int_serialize

# format: variant_tag (0x4) | array len varint | 32B | 32B | ...
num_keys = len(pub_enc)
len_size = int_serialize.uvarint_size(num_keys)
buffer = bytearray(1 + len_size + 32 * num_keys)

buffer[0] = 0x4 # TX_EXTRA_TAG_ADDITIONAL_PUBKEYS
int_serialize.dump_uvarint_b_into(num_keys, buffer, 1) # uvarint(num_keys)
offset = 1 + len_size

for idx in range(num_keys):
buffer[offset : offset + 32] = pub_enc[idx]
offset += 32

tx_extra += buffer
return tx_extra
4 changes: 2 additions & 2 deletions src/apps/monero/protocol/signing/step_08_mlsag_done.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ async def mlsag_done(state: State):
_ecdh_info(state)
_out_pk(state)
state.full_message_hasher.rctsig_base_done()
state.out_idx = -1
state.inp_idx = -1
state.current_output_index = -1
state.current_input_index = -1

state.full_message = state.full_message_hasher.get_digest()
state.full_message_hasher = None
Expand Down
Loading

0 comments on commit daf7b7d

Please sign in to comment.