def mask_consistency_check(self, bpi): sv = [crypto.sc_init(123)] gamma = [crypto.sc_init(432)] M, logM, aL, aR, V, gamma = bpi.prove_setup(sv, gamma) x = bp._ensure_dst_key() y = bp._ensure_dst_key() sL = bpi.sL_vct(64) sR = bpi.sR_vct(64) self.assertEqual(sL.to(0, x), sL.to(0, y)) self.assertEqual(sL.to(1, x), sL.to(1, y)) self.assertEqual(sL.to(63, x), sL.to(63, y)) self.assertNotEqual(sL.to(1, x), sL.to(0, y)) self.assertNotEqual(sL.to(10, x), sL.to(0, y)) self.assertEqual(sR.to(0, x), sR.to(0, y)) self.assertEqual(sR.to(1, x), sR.to(1, y)) self.assertEqual(sR.to(63, x), sR.to(63, y)) self.assertNotEqual(sR.to(1, x), sR.to(0, y)) self.assertNotEqual(sL.to(0, x), sR.to(0, y)) self.assertNotEqual(sL.to(1, x), sR.to(1, y)) self.assertNotEqual(sL.to(63, x), sR.to(63, y)) ve1 = bp._ensure_dst_key() ve2 = bp._ensure_dst_key() bpi.vector_exponent(aL, aR, ve1) bpi.vector_exponent(aL, aR, ve2) bpi.vector_exponent(sL, sR, ve1) bpi.vector_exponent(sL, sR, ve2) self.assertEqual(ve1, ve2)
def test_prove(self): bpi = bp.BulletProofBuilder() val = crypto.sc_init(123) mask = crypto.sc_init(432) bp_res = bpi.prove(val, mask) bpi.verify(bp_res)
def test_prove_testnet(self): bpi = bp.BulletProofBuilder() val = crypto.sc_init(123) mask = crypto.sc_init(432) bp_res = bpi.prove_testnet(val, mask) bpi.verify_testnet(bp_res) try: bp_res.S[0] += 1 bpi.verify(bp_res) self.fail("Verification should have failed") except: pass
def test_prove_2(self): bpi = bp.BulletProofBuilder() val = crypto.sc_init((1 << 30) - 1 + 16) mask = crypto.random_scalar() bp_res = bpi.prove(val, mask) bpi.verify(bp_res)
def test_clsag_invalid_Cp(self): res = self.gen_clsag_sig(ring_size=11, index=5) msg, scalars, sc1, sI, sD, ring2, Cp = res with self.assertRaises(ValueError): Cp = crypto.point_add(Cp, crypto.scalarmult_base(crypto.sc_init(1))) self.verify_clsag(msg, scalars, sc1, sI, sD, ring2, Cp)
def test_prove_random_masks(self): bpi = bp.BulletProofBuilder() bpi.use_det_masks = False # trully randomly generated mask vectors val = crypto.sc_init((1 << 30) - 1 + 16) mask = crypto.random_scalar() bp_res = bpi.prove(val, mask) bpi.verify(bp_res)
def ctest_multiexp(self): scalars = [0, 1, 2, 3, 4, 99] point_base = [0, 2, 4, 7, 12, 18] scalar_sc = [crypto.sc_init(x) for x in scalars] points = [crypto.scalarmult_base(crypto.sc_init(x)) for x in point_base] muex = bp.MultiExp(scalars=[crypto.encodeint(x) for x in scalar_sc], point_fnc=lambda i, d: crypto.encodepoint(points[i])) self.assertEqual(len(muex), len(scalars)) res = bp.multiexp(None, muex) res2 = bp.vector_exponent_custom( A=bp.KeyVEval(3, lambda i, d: crypto.encodepoint_into(crypto.scalarmult_base(crypto.sc_init(point_base[i])), d)), B=bp.KeyVEval(3, lambda i, d: crypto.encodepoint_into(crypto.scalarmult_base(crypto.sc_init(point_base[3+i])), d)), a=bp.KeyVEval(3, lambda i, d: crypto.encodeint_into(crypto.sc_init(scalars[i]), d),), b=bp.KeyVEval(3, lambda i, d: crypto.encodeint_into(crypto.sc_init(scalars[i+3]), d)), ) self.assertEqual(res, res2)
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 gen_clsag_sig(self, ring_size=11, index=None): msg = random.bytes(32) amnt = crypto.sc_init(random.uniform(0xFFFFFF) + 12) priv = crypto.random_scalar() msk = crypto.random_scalar() alpha = crypto.random_scalar() P = crypto.scalarmult_base(priv) C = crypto.add_keys2(msk, amnt, crypto.xmr_H()) Cp = crypto.add_keys2(alpha, amnt, crypto.xmr_H()) ring = [] for i in range(ring_size - 1): tk = TmpKey( crypto.encodepoint( crypto.scalarmult_base(crypto.random_scalar())), crypto.encodepoint( crypto.scalarmult_base(crypto.random_scalar())), ) ring.append(tk) index = index if index is not None else random.uniform(len(ring)) ring.insert(index, TmpKey(crypto.encodepoint(P), crypto.encodepoint(C))) ring2 = list(ring) mg_buffer = [] self.assertTrue( crypto.point_eq(crypto.scalarmult_base(priv), crypto.decodepoint(ring[index].dest))) self.assertTrue( crypto.point_eq( crypto.scalarmult_base(crypto.sc_sub(msk, alpha)), crypto.point_sub(crypto.decodepoint(ring[index].commitment), Cp), )) mlsag.generate_clsag_simple( msg, ring, CtKey(priv, msk), alpha, Cp, index, mg_buffer, ) sD = crypto.decodepoint(mg_buffer[-1]) sc1 = crypto.decodeint(mg_buffer[-2]) scalars = [crypto.decodeint(x) for x in mg_buffer[1:-2]] H = crypto.new_point() sI = crypto.new_point() crypto.hash_to_point_into(H, crypto.encodepoint(P)) crypto.scalarmult_into(sI, H, priv) # I = p*H return msg, scalars, sc1, sI, sD, ring2, Cp
async def all_inputs_set(state: State): state.mem_trace(0) await confirms.transaction_step(state.ctx, state.STEP_ALL_IN) from trezor.messages.MoneroTransactionAllInputsSetAck import ( MoneroTransactionAllInputsSetAck, ) from trezor.messages.MoneroTransactionRsigData import MoneroTransactionRsigData # Generate random commitment masks to be used in range proofs. # If SimpleRCT is used the sum of the masks must match the input masks sum. state.sumout = crypto.sc_init(0) for i in range(state.output_count): cur_mask = crypto.new_scalar() # new mask for each output is_last = i + 1 == state.output_count if is_last and state.rct_type == RctType.Simple: # in SimpleRCT the last mask needs to be calculated as an offset of the sum crypto.sc_sub_into(cur_mask, state.sumpouts_alphas, state.sumout) else: crypto.random_scalar(cur_mask) crypto.sc_add_into(state.sumout, state.sumout, cur_mask) state.output_masks.append(cur_mask) if state.rct_type == RctType.Simple: utils.ensure(crypto.sc_eq(state.sumout, state.sumpouts_alphas), "Invalid masks sum") # sum check state.sumout = crypto.sc_init(0) rsig_data = MoneroTransactionRsigData() resp = MoneroTransactionAllInputsSetAck(rsig_data=rsig_data) # If range proofs are being offloaded, we send the masks to the host, which uses them # to create the range proof. If not, we do not send any and we use them in the following step. if state.rsig_offload: tmp_buff = bytearray(32) rsig_data.mask = bytearray(32 * state.output_count) for i in range(state.output_count): crypto.encodeint_into(tmp_buff, state.output_masks[i]) utils.memcpy(rsig_data.mask, 32 * i, tmp_buff, 0, 32) return resp
def _ecdh_encode(amount: int, amount_key: bytes) -> EcdhTuple: """ Output recipients decode amounts from EcdhTuple structure. """ from apps.monero.xmr.serialize_messages.tx_ecdh import EcdhTuple ecdh_info = EcdhTuple(mask=crypto.NULL_KEY_ENC, amount=bytearray(32)) amnt = crypto.sc_init(amount) crypto.encodeint_into(ecdh_info.amount, amnt) crypto.xor8(ecdh_info.amount, _ecdh_hash(amount_key)) return ecdh_info
async def all_inputs_set(state: State) -> MoneroTransactionAllInputsSetAck: state.mem_trace(0) await layout.transaction_step(state, state.STEP_ALL_IN) from trezor.messages import MoneroTransactionAllInputsSetAck if state.last_step != state.STEP_VINI: raise ValueError("Invalid state transition") if state.current_input_index != state.input_count - 1: raise ValueError("Invalid input count") # The sum of the masks must match the input masks sum. state.sumout = crypto.sc_init(0) state.last_step = state.STEP_ALL_IN resp = MoneroTransactionAllInputsSetAck() return resp
async def all_inputs_set(state: State): state.mem_trace(0) await confirms.transaction_step(state.ctx, state.STEP_ALL_IN) from trezor.messages.MoneroTransactionAllInputsSetAck import ( MoneroTransactionAllInputsSetAck, ) # Generate random commitment masks to be used in range proofs. # If SimpleRCT is used the sum of the masks must match the input masks sum. state.sumout = crypto.sc_init(0) rsig_data = None # Client 0, HF9. Non-deterministic masks if not state.is_det_mask(): rsig_data = await _compute_masks(state) resp = MoneroTransactionAllInputsSetAck(rsig_data=rsig_data) return resp
def _ecdh_encode(mask, amount, amount_key): """ Output recipients need be able to reconstruct the amount commitments. This means the blinding factor `mask` and `amount` must be communicated to the receiver somehow. The mask and amount are stored as: - mask = mask + Hs(amount_key) - amount = amount + Hs(Hs(amount_key)) Because the receiver can derive the `amount_key` they can easily derive both mask and amount as well. """ from apps.monero.xmr.serialize_messages.tx_ecdh import EcdhTuple ecdh_info = EcdhTuple(mask=mask, amount=crypto.sc_init(amount)) amount_key_hash_single = crypto.hash_to_scalar(amount_key) amount_key_hash_double = crypto.hash_to_scalar( crypto.encodeint(amount_key_hash_single)) ecdh_info.mask = crypto.sc_add(ecdh_info.mask, amount_key_hash_single) ecdh_info.amount = crypto.sc_add(ecdh_info.amount, amount_key_hash_double) return _recode_ecdh(ecdh_info)
def _ecdh_encode(mask, amount, amount_key, v2=False): """ Output recipients need be able to reconstruct the amount commitments. This means the blinding factor `mask` and `amount` must be communicated to the receiver somehow. The mask and amount are stored as: - mask = mask + Hs(amount_key) - amount = amount + Hs(Hs(amount_key)) Because the receiver can derive the `amount_key` they can easily derive both mask and amount as well. """ from apps.monero.xmr.serialize_messages.tx_ecdh import EcdhTuple ecdh_info = EcdhTuple(mask=mask, amount=crypto.sc_init(amount)) if v2: amnt = ecdh_info.amount ecdh_info.mask = crypto.NULL_KEY_ENC ecdh_info.amount = bytearray(32) crypto.encodeint_into(ecdh_info.amount, amnt) crypto.xor8(ecdh_info.amount, _ecdh_hash(amount_key)) return ecdh_info else: amount_key_hash_single = crypto.hash_to_scalar(amount_key) amount_key_hash_double = crypto.hash_to_scalar( crypto.encodeint(amount_key_hash_single)) # Not modifying passed mask, is reused in BP. ecdh_info.mask = crypto.sc_add(ecdh_info.mask, amount_key_hash_single) crypto.sc_add_into(ecdh_info.amount, ecdh_info.amount, amount_key_hash_double) ecdh_info.mask = crypto.encodeint(ecdh_info.mask) ecdh_info.amount = crypto.encodeint(ecdh_info.amount) return ecdh_info
async def _compute_masks(state: State): """ Output masks computed in advance. Used with client_version=0 && HF9. After HF10 (included) masks are deterministic, computed from the amount_key. After all client update to v1 this code will be removed. In order to preserve client_version=0 compatibility the masks have to be adjusted. """ from trezor.messages.MoneroTransactionRsigData import MoneroTransactionRsigData from apps.monero.signing import offloading_keys rsig_data = MoneroTransactionRsigData() # If range proofs are being offloaded, we send the masks to the host, which uses them # to create the range proof. If not, we do not send any and we use them in the following step. if state.rsig_offload: rsig_data.mask = [] # Deterministic masks, the last one is computed to balance the sums for i in range(state.output_count): if i + 1 == state.output_count: cur_mask = crypto.sc_sub(state.sumpouts_alphas, state.sumout) state.output_last_mask = cur_mask else: cur_mask = offloading_keys.det_comm_masks(state.key_enc, i) crypto.sc_add_into(state.sumout, state.sumout, cur_mask) if state.rsig_offload: rsig_data.mask.append(crypto.encodeint(cur_mask)) if not crypto.sc_eq(state.sumpouts_alphas, state.sumout): raise ValueError("Sum eq error") state.sumout = crypto.sc_init(0) return rsig_data
async def diag(ctx, msg, **kwargs): log.debug(__name__, "----diagnostics") gc.collect() if msg.ins == 0: check_mem(0) return retit() elif msg.ins == 1: check_mem(1) micropython.mem_info(1) return retit() elif msg.ins == 2: log.debug(__name__, "_____________________________________________") log.debug(__name__, "_____________________________________________") log.debug(__name__, "_____________________________________________") return retit() elif msg.ins == 3: pass elif msg.ins == 4: total = 0 monero = 0 for k, v in sys.modules.items(): log.info(__name__, "Mod[%s]: %s", k, v) total += 1 if k.startswith("apps.monero"): monero += 1 log.info(__name__, "Total modules: %s, Monero modules: %s", total, monero) return retit() elif msg.ins in [5, 6, 7]: check_mem() from apps.monero.xmr import bulletproof as bp check_mem("BP Imported") from apps.monero.xmr import crypto check_mem("Crypto Imported") bpi = bp.BulletProofBuilder() bpi.gc_fnc = gc.collect bpi.gc_trace = log_trace vals = [crypto.sc_init((1 << 30) - 1 + 16), crypto.sc_init(22222)] masks = [crypto.random_scalar(), crypto.random_scalar()] check_mem("BP pre input") if msg.ins == 5: bp_res = bpi.prove_testnet(vals[0], masks[0]) check_mem("BP post prove") bpi.verify_testnet(bp_res) check_mem("BP post verify") elif msg.ins == 6: bp_res = bpi.prove(vals[0], masks[0]) check_mem("BP post prove") bpi.verify(bp_res) check_mem("BP post verify") elif msg.ins == 7: bp_res = bpi.prove_batch(vals, masks) check_mem("BP post prove") bpi.verify(bp_res) check_mem("BP post verify") return retit() return retit()
def test_prove_batch16(self): bpi = bp.BulletProofBuilder() sv = [crypto.sc_init(137*i) for i in range(16)] gamma = [crypto.sc_init(991*i) for i in range(16)] proof = bpi.prove_batch(sv, gamma) bpi.verify_batch([proof])
def test_prove_batch(self): bpi = bp.BulletProofBuilder() sv = [crypto.sc_init(123), crypto.sc_init(768)] gamma = [crypto.sc_init(456), crypto.sc_init(901)] proof = bpi.prove_batch(sv, gamma) bpi.verify_batch([proof])
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() 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) # if 0: C_tmp += Zero (nothing is added) # if 1: C_tmp += 2^i*H # 2^i*H is already stored in C_h if (amount >> ii) & 1 == 1: crypto.point_add_into(C_tmp, C_tmp, 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) gc.collect() return C_acc, a, [s0s, s1s, ee_bin, Cis]