diff --git a/src/apps/monero/protocol/signing/step_06_set_output.py b/src/apps/monero/protocol/signing/step_06_set_output.py index 5ff34a1d9..285f2e075 100644 --- a/src/apps/monero/protocol/signing/step_06_set_output.py +++ b/src/apps/monero/protocol/signing/step_06_set_output.py @@ -150,7 +150,7 @@ def _range_proof(state, amount, rsig_data): The range proof is incrementally hashed to the final_message. """ - from apps.monero.xmr import ring_ct + from apps.monero.xmr import range_signatures mask = state.output_masks[state.current_output_index] provided_rsig = None @@ -179,7 +179,9 @@ def _range_proof(state, amount, rsig_data): state.mem_trace("pre-rproof" if __debug__ else None, collect=True) if state.rsig_type == RsigType.Bulletproof and not state.rsig_offload: """Bulletproof calculation in trezor""" - rsig = ring_ct.prove_range_bp_batch(state.output_amounts, state.output_masks) + rsig = range_signatures.prove_range_bp_batch( + state.output_amounts, state.output_masks + ) state.mem_trace("post-bp" if __debug__ else None, collect=True) # Incremental BP hashing @@ -196,8 +198,8 @@ def _range_proof(state, amount, rsig_data): elif state.rsig_type == RsigType.Borromean and not state.rsig_offload: """Borromean calculation in trezor""" - C, mask, rsig = ring_ct.prove_range_chunked(amount, mask) - del (ring_ct) + C, mask, rsig = range_signatures.prove_range_borromean(amount, mask) + del range_signatures # Incremental hashing state.full_message_hasher.rsig_val(rsig, False, raw=True) @@ -219,10 +221,10 @@ def _range_proof(state, amount, rsig_data): # array sizes compared to the serialized bulletproof format # thus direct serialization cannot be used. state.full_message_hasher.rsig_val(bp_obj, True, raw=False) - res = ring_ct.verify_bp(bp_obj, state.output_amounts, masks) + res = range_signatures.verify_bp(bp_obj, state.output_amounts, masks) state.assrt(res, "BP verification fail") state.mem_trace("BP verified" if __debug__ else None, collect=True) - del (bp_obj, ring_ct) + del (bp_obj, range_signatures) elif state.rsig_type == RsigType.Borromean and state.rsig_offload: """Borromean offloading not supported""" diff --git a/src/apps/monero/protocol/signing/step_09_sign_input.py b/src/apps/monero/protocol/signing/step_09_sign_input.py index f39c2a762..a5fd856bb 100644 --- a/src/apps/monero/protocol/signing/step_09_sign_input.py +++ b/src/apps/monero/protocol/signing/step_09_sign_input.py @@ -129,12 +129,12 @@ async def sign_input( state.mem_trace(4) # RCT signature - from apps.monero.xmr import mlsag2 + from apps.monero.xmr import mlsag if state.rct_type == RctType.Simple: # Simple RingCT mix_ring = [x.key for x in src_entr.outputs] - mg, msc = mlsag2.prove_rct_mg_simple( + mg, msc = mlsag.prove_rct_mg_simple( state.full_message, mix_ring, input_secret_key, @@ -149,7 +149,7 @@ async def sign_input( txn_fee_key = crypto.scalarmult_h(state.fee) mix_ring = [[x.key] for x in src_entr.outputs] - mg, msc = mlsag2.prove_rct_mg( + mg, msc = mlsag.prove_rct_mg( state.full_message, mix_ring, [input_secret_key], diff --git a/src/apps/monero/xmr/crypto.py b/src/apps/monero/xmr/crypto.py index b13233b51..9db4cc163 100644 --- a/src/apps/monero/xmr/crypto.py +++ b/src/apps/monero/xmr/crypto.py @@ -7,9 +7,7 @@ # https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-00#section-4 # https://github.com/monero-project/research-lab -from trezor.crypto import hmac -from trezor.crypto import monero as tcry -from trezor.crypto import random +from trezor.crypto import hmac, monero as tcry, random from trezor.crypto.hashlib import sha3_256 NULL_KEY_ENC = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" diff --git a/src/apps/monero/xmr/mlsag2.py b/src/apps/monero/xmr/mlsag.py similarity index 100% rename from src/apps/monero/xmr/mlsag2.py rename to src/apps/monero/xmr/mlsag.py diff --git a/src/apps/monero/xmr/range_signatures.py b/src/apps/monero/xmr/range_signatures.py new file mode 100644 index 000000000..5968d7656 --- /dev/null +++ b/src/apps/monero/xmr/range_signatures.py @@ -0,0 +1,141 @@ +""" +Computes range signature + +Can compute Borromean range proof or Bulletproof. +Also can verify Bulletproof, in case the computation was offloaded. + +Mostly ported from official Monero client, but also inspired by Mininero. +Author: Dusan Klinec, ph4r05, 2018 +""" + +import gc + +from apps.monero.xmr import crypto + + +def prove_range_bp_batch(amounts, masks): + """Calculates Bulletproof in batches""" + from apps.monero.xmr import bulletproof as bp + + bpi = bp.BulletProofBuilder() + bp_proof = bpi.prove_batch([crypto.sc_init(a) for a in amounts], masks) + del (bpi, bp) + gc.collect() + + return bp_proof + + +def verify_bp(bp_proof, amounts, masks): + """Verifies Bulletproof""" + from apps.monero.xmr import bulletproof as bp + + if amounts: + bp_proof.V = [] + for i in range(len(amounts)): + C = crypto.gen_commitment(masks[i], amounts[i]) + crypto.scalarmult_into(C, C, crypto.sc_inv_eight()) + bp_proof.V.append(crypto.encodepoint(C)) + + bpi = bp.BulletProofBuilder() + res = bpi.verify(bp_proof) + gc.collect() + return res + + +def prove_range_borromean(amount, last_mask): + """Calculates Borromean range proof""" + # The large chunks allocated first to avoid potential memory fragmentation issues. + ai = bytearray(32 * 64) + alphai = bytearray(32 * 64) + Cis = bytearray(32 * 64) + s0s = bytearray(32 * 64) + s1s = bytearray(32 * 64) + buff = bytearray(32) + ee_bin = bytearray(32) + + a = crypto.sc_init(0) + si = crypto.sc_init(0) + c = crypto.sc_init(0) + ee = crypto.sc_init(0) + tmp_ai = crypto.sc_init(0) + tmp_alpha = crypto.sc_init(0) + + C_acc = crypto.identity() + C_h = crypto.xmr_H() + C_tmp = crypto.identity() + L = crypto.identity() + Zero = crypto.identity() + kck = crypto.get_keccak() + + for ii in range(64): + crypto.random_scalar(tmp_ai) + if last_mask is not None and ii == 63: + crypto.sc_sub_into(tmp_ai, last_mask, a) + + crypto.sc_add_into(a, a, tmp_ai) + crypto.random_scalar(tmp_alpha) + + crypto.scalarmult_base_into(L, tmp_alpha) + crypto.scalarmult_base_into(C_tmp, tmp_ai) + + # C_tmp += &Zero if BB(ii) == 0 else &C_h + crypto.point_add_into(C_tmp, C_tmp, Zero if ((amount >> ii) & 1) == 0 else C_h) + crypto.point_add_into(C_acc, C_acc, C_tmp) + + # Set Ci[ii] to sigs + crypto.encodepoint_into(Cis, C_tmp, ii << 5) + crypto.encodeint_into(ai, tmp_ai, ii << 5) + crypto.encodeint_into(alphai, tmp_alpha, ii << 5) + + if ((amount >> ii) & 1) == 0: + crypto.random_scalar(si) + crypto.encodepoint_into(buff, L) + crypto.hash_to_scalar_into(c, buff) + + crypto.point_sub_into(C_tmp, C_tmp, C_h) + crypto.add_keys2_into(L, si, c, C_tmp) + + crypto.encodeint_into(s1s, si, ii << 5) + + crypto.encodepoint_into(buff, L) + kck.update(buff) + + crypto.point_double_into(C_h, C_h) + + # Compute ee + tmp_ee = kck.digest() + crypto.decodeint_into(ee, tmp_ee) + del (tmp_ee, kck) + + C_h = crypto.xmr_H() + gc.collect() + + # Second pass, s0, s1 + for ii in range(64): + crypto.decodeint_into(tmp_alpha, alphai, ii << 5) + crypto.decodeint_into(tmp_ai, ai, ii << 5) + + if ((amount >> ii) & 1) == 0: + crypto.sc_mulsub_into(si, tmp_ai, ee, tmp_alpha) + crypto.encodeint_into(s0s, si, ii << 5) + + else: + crypto.random_scalar(si) + crypto.encodeint_into(s0s, si, ii << 5) + + crypto.decodepoint_into(C_tmp, Cis, ii << 5) + crypto.add_keys2_into(L, si, ee, C_tmp) + crypto.encodepoint_into(buff, L) + crypto.hash_to_scalar_into(c, buff) + + crypto.sc_mulsub_into(si, tmp_ai, c, tmp_alpha) + crypto.encodeint_into(s1s, si, ii << 5) + + crypto.point_double_into(C_h, C_h) + + crypto.encodeint_into(ee_bin, ee) + + del (ai, alphai, buff, tmp_ai, tmp_alpha, si, c, ee, C_tmp, C_h, L, Zero) + gc.collect() + + return C_acc, a, [s0s, s1s, ee_bin, Cis] diff --git a/src/apps/monero/xmr/ring_ct.py b/src/apps/monero/xmr/ring_ct.py index 9fa2b5cf4..a73036bcb 100644 --- a/src/apps/monero/xmr/ring_ct.py +++ b/src/apps/monero/xmr/ring_ct.py @@ -1,170 +1,11 @@ -# Author: https://github.com/monero-project/mininero -# Author: Dusan Klinec, ph4r05, 2018 - -import gc - from apps.monero.xmr import crypto - -def prove_range_bp(amount, last_mask=None): - mask = last_mask if last_mask is not None else crypto.random_scalar() - bp_proof = prove_range_bp_batch([amount], [mask]) - - C = crypto.decodepoint(bp_proof.V[0]) - C = crypto.point_mul8(C) - - gc.collect() - - # Return as struct as the hash(BP_struct) != hash(BP_serialized) - # as the original hashing does not take vector lengths into account which are dynamic - # in the serialization scheme (and thus extraneous) - return C, mask, bp_proof - - -def prove_range_bp_batch(amounts, masks): - from apps.monero.xmr import bulletproof as bp - - bpi = bp.BulletProofBuilder() - bp_proof = bpi.prove_batch([crypto.sc_init(a) for a in amounts], masks) - del (bpi, bp) - gc.collect() - - return bp_proof - - -def verify_bp(bp_proof, amounts=None, masks=None): - from apps.monero.xmr import bulletproof as bp - - if amounts: - bp_proof.V = [] - for i in range(len(amounts)): - C = crypto.gen_commitment(masks[i], amounts[i]) - crypto.scalarmult_into(C, C, crypto.sc_inv_eight()) - bp_proof.V.append(crypto.encodepoint(C)) - - bpi = bp.BulletProofBuilder() - res = bpi.verify(bp_proof) - gc.collect() - - # Return as struct as the hash(BP_struct) != hash(BP_serialized) - # as the original hashing does not take vector lengths into account which are dynamic - # in the serialization scheme (and thus extraneous) - return res - - -def prove_range_chunked(amount, last_mask=None): - # The large chunks allocated first to avoid potential memory fragmentation issues. - ai = bytearray(32 * 64) - alphai = bytearray(32 * 64) - Cis = bytearray(32 * 64) - s0s = bytearray(32 * 64) - s1s = bytearray(32 * 64) - buff = bytearray(32) - ee_bin = bytearray(32) - - a = crypto.sc_init(0) - si = crypto.sc_init(0) - c = crypto.sc_init(0) - ee = crypto.sc_init(0) - tmp_ai = crypto.sc_init(0) - tmp_alpha = crypto.sc_init(0) - - C_acc = crypto.identity() - C_h = crypto.xmr_H() - C_tmp = crypto.identity() - L = crypto.identity() - Zero = crypto.identity() - kck = crypto.get_keccak() - - for ii in range(64): - crypto.random_scalar(tmp_ai) - if last_mask is not None and ii == 63: - crypto.sc_sub_into(tmp_ai, last_mask, a) - - crypto.sc_add_into(a, a, tmp_ai) - crypto.random_scalar(tmp_alpha) - - crypto.scalarmult_base_into(L, tmp_alpha) - crypto.scalarmult_base_into(C_tmp, tmp_ai) - - # C_tmp += &Zero if BB(ii) == 0 else &C_h - crypto.point_add_into(C_tmp, C_tmp, Zero if ((amount >> ii) & 1) == 0 else C_h) - crypto.point_add_into(C_acc, C_acc, C_tmp) - - # Set Ci[ii] to sigs - crypto.encodepoint_into(Cis, C_tmp, ii << 5) - crypto.encodeint_into(ai, tmp_ai, ii << 5) - crypto.encodeint_into(alphai, tmp_alpha, ii << 5) - - if ((amount >> ii) & 1) == 0: - crypto.random_scalar(si) - crypto.encodepoint_into(buff, L) - crypto.hash_to_scalar_into(c, buff) - - crypto.point_sub_into(C_tmp, C_tmp, C_h) - crypto.add_keys2_into(L, si, c, C_tmp) - - crypto.encodeint_into(s1s, si, ii << 5) - - crypto.encodepoint_into(buff, L) - kck.update(buff) - - crypto.point_double_into(C_h, C_h) - - # Compute ee - tmp_ee = kck.digest() - crypto.decodeint_into(ee, tmp_ee) - del (tmp_ee, kck) - - C_h = crypto.xmr_H() - gc.collect() - - # Second pass, s0, s1 - for ii in range(64): - crypto.decodeint_into(tmp_alpha, alphai, ii << 5) - crypto.decodeint_into(tmp_ai, ai, ii << 5) - - if ((amount >> ii) & 1) == 0: - crypto.sc_mulsub_into(si, tmp_ai, ee, tmp_alpha) - crypto.encodeint_into(s0s, si, ii << 5) - - else: - crypto.random_scalar(si) - crypto.encodeint_into(s0s, si, ii << 5) - - crypto.decodepoint_into(C_tmp, Cis, ii << 5) - crypto.add_keys2_into(L, si, ee, C_tmp) - crypto.encodepoint_into(buff, L) - crypto.hash_to_scalar_into(c, buff) - - crypto.sc_mulsub_into(si, tmp_ai, c, tmp_alpha) - crypto.encodeint_into(s1s, si, ii << 5) - - crypto.point_double_into(C_h, C_h) - - crypto.encodeint_into(ee_bin, ee) - - del (ai, alphai, buff, tmp_ai, tmp_alpha, si, c, ee, C_tmp, C_h, L, Zero) - gc.collect() - - return C_acc, a, [s0s, s1s, ee_bin, Cis] - - -# Ring-ct MG sigs -# Prove: -# c.f. http:#eprint.iacr.org/2015/1098 section 4. definition 10. -# This does the MG sig on the "dest" part of the given key matrix, and -# the last row is the sum of input commitments from that column - sum output commitments -# this shows that sum inputs = sum outputs -# Ver: -# verifies the above sig is created corretly +# TODO # # Key image import / export # - - def generate_ring_signature(prefix_hash, image, pubs, sec, sec_idx, test=False): """ Generates ring signature with key image.