mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-16 01:22:02 +00:00
feat(xmr): implement bp+, refactor, code cleanup
- implement BulletProof plus verifier and prover - use bulletproof exception to signalize proof generation failed and should be tried again. More robust, fixes bug that was not triggered yet (return tuple did not work properly in all situations) - precomputed 2**i vector is removed as it can be easily computed - BP code cleanup, minor optimizations, comments
This commit is contained in:
parent
6feada2eed
commit
25d32a8144
File diff suppressed because it is too large
Load Diff
@ -376,4 +376,5 @@ def ct_equals(a: bytes, b: bytes) -> bool:
|
||||
"""
|
||||
BP_GI_PRE: bytes
|
||||
BP_HI_PRE: bytes
|
||||
BP_TWO_N: bytes
|
||||
BP_GI_PLUS_PRE: bytes
|
||||
BP_HI_PLUS_PRE: bytes
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -31,3 +31,21 @@ class Bulletproof(MessageType):
|
||||
("b", ECKey),
|
||||
("t", ECKey),
|
||||
)
|
||||
|
||||
|
||||
class BulletproofPlus(MessageType):
|
||||
__slots__ = ("A", "A1", "B", "r1", "s1", "d1", "V", "L", "R")
|
||||
|
||||
@classmethod
|
||||
def f_specs(cls) -> tuple:
|
||||
return (
|
||||
("A", ECKey),
|
||||
("A1", ECKey),
|
||||
("B", ECKey),
|
||||
("r1", ECKey),
|
||||
("s1", ECKey),
|
||||
("d1", ECKey),
|
||||
("V", _KeyV),
|
||||
("L", _KeyV),
|
||||
("R", _KeyV),
|
||||
)
|
||||
|
@ -2,20 +2,151 @@ from common import *
|
||||
|
||||
if not utils.BITCOIN_ONLY:
|
||||
from apps.monero.xmr import bulletproof as bp, crypto, monero
|
||||
from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import Bulletproof
|
||||
from apps.monero.xmr.serialize_messages.tx_rsig_bulletproof import Bulletproof, BulletproofPlus
|
||||
|
||||
|
||||
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
|
||||
class TestMoneroBulletproof(unittest.TestCase):
|
||||
|
||||
def test_1(self):
|
||||
pass
|
||||
def test_square_multiply(self):
|
||||
for x in [2, 3, 16, 17, 31, 32]:
|
||||
ss = crypto.random_scalar()
|
||||
s1 = crypto.sc_copy(None, ss)
|
||||
s2 = crypto.sc_copy(None, ss)
|
||||
for i in range(1, x):
|
||||
crypto.sc_mul_into(s1, s1, ss)
|
||||
|
||||
bp._sc_square_mult(s2, ss, x)
|
||||
self.assertEqual(crypto.encodeint_into(None, s1), crypto.encodeint_into(None, s2))
|
||||
|
||||
def test_dvct_skips(self):
|
||||
z_sq = unhexlify(b'e0408b528e9d35ccb8386b87f39b85c724740644f4db412483a8852cdb3ceb00')
|
||||
d_vct0 = bp.VctD(64, 8, z_sq, raw=True)
|
||||
d_vct1 = bp.VctD(64, 8, z_sq, raw=True)
|
||||
tmp = crypto.Scalar()
|
||||
|
||||
# Linear scan vs jump
|
||||
for i in range(65):
|
||||
tmp = d_vct0[i]
|
||||
self.assertEqual(crypto.encodeint_into(None, tmp), crypto.encodeint_into(None, d_vct1[64]))
|
||||
|
||||
# Jumping around
|
||||
_ = d_vct0[128]
|
||||
self.assertEqual(crypto.encodeint_into(None, d_vct0[64]), crypto.encodeint_into(None, d_vct1[64]))
|
||||
|
||||
# Sync on the same jump
|
||||
self.assertEqual(crypto.encodeint_into(None, d_vct0[65]), crypto.encodeint_into(None, d_vct1[65]))
|
||||
self.assertEqual(crypto.encodeint_into(None, d_vct0[65]), crypto.encodeint_into(None, d_vct1[65]))
|
||||
|
||||
# Jump vs linear again, move_one vs move_more
|
||||
for i in range(1, 10):
|
||||
tmp = d_vct0[65 + i]
|
||||
self.assertEqual(crypto.encodeint_into(None, tmp), crypto.encodeint_into(None, d_vct1[74]))
|
||||
|
||||
_ = d_vct0[85]
|
||||
_ = d_vct1[89] # different jump sizes, internal state management test
|
||||
self.assertEqual(crypto.encodeint_into(None, d_vct0[95]), crypto.encodeint_into(None, d_vct1[95]))
|
||||
|
||||
_ = d_vct0[319] # move_one mults by z_sq then; enforce z component updates
|
||||
self.assertEqual(crypto.encodeint_into(None, d_vct0[320]), crypto.encodeint_into(None, d_vct1[320]))
|
||||
|
||||
tmp = crypto.sc_copy(None, d_vct0[64]) # another jump back and forth
|
||||
_ = d_vct0[127]
|
||||
self.assertEqual(crypto.encodeint_into(None, d_vct0[64]), crypto.encodeint_into(None, tmp))
|
||||
|
||||
_ = d_vct0[0]
|
||||
_ = d_vct1[0]
|
||||
_ = d_vct0[64]
|
||||
self.assertEqual(crypto.encodeint_into(None, d_vct0[5]), crypto.encodeint_into(None, d_vct1[5]))
|
||||
|
||||
def test_pow_back_skips(self):
|
||||
MN = 128
|
||||
y = unhexlify('60421950bee0aab949e63336db1eb9532dba6b4599c5cd9fb1dbde909114100e')
|
||||
y_sc = crypto.decodeint_into(None, y)
|
||||
yinv = bp._invert(None, y)
|
||||
|
||||
y_to_MN_1 = bp._sc_square_mult(None, y_sc, MN - 1)
|
||||
ymax = crypto.sc_mul_into(None, y_to_MN_1, y_sc) ## y**MN
|
||||
ymax2 = bp._sc_square_mult(None, y_sc, MN)
|
||||
self.assertEqual(crypto.encodeint_into(None, ymax), crypto.encodeint_into(None, ymax2))
|
||||
|
||||
size = MN + 1
|
||||
ypow_back = bp.KeyVPowersBackwards(size, y, x_inv=yinv, x_max=ymax, raw=True)
|
||||
self.assertEqual(crypto.encodeint_into(None, ymax), crypto.encodeint_into(None, ypow_back[MN]))
|
||||
|
||||
for i in range(10):
|
||||
_ = ypow_back[MN - i]
|
||||
|
||||
self.assertEqual(crypto.encodeint_into(None, ypow_back[MN - 9]),
|
||||
crypto.encodeint_into(None, bp._sc_square_mult(None, y_sc, MN - 9)))
|
||||
self.assertEqual(crypto.encodeint_into(None, ypow_back[MN - 19]),
|
||||
crypto.encodeint_into(None, bp._sc_square_mult(None, y_sc, MN - 19)))
|
||||
self.assertEqual(crypto.encodeint_into(None, ypow_back[MN - 65]),
|
||||
crypto.encodeint_into(None, bp._sc_square_mult(None, y_sc, MN - 65)))
|
||||
self.assertEqual(crypto.encodeint_into(None, ypow_back[MN - 14]),
|
||||
crypto.encodeint_into(None, bp._sc_square_mult(None, y_sc, MN - 14)))
|
||||
|
||||
tmp = crypto.sc_copy(None, ypow_back[MN - 64]) # another jump back and forth
|
||||
_ = ypow_back[MN - 127]
|
||||
self.assertEqual(crypto.encodeint_into(None, ypow_back[MN - 64]), crypto.encodeint_into(None, tmp))
|
||||
self.assertEqual(crypto.encodeint_into(None, ypow_back[MN - 64]),
|
||||
crypto.encodeint_into(None, bp._sc_square_mult(None, y_sc, MN - 64)))
|
||||
|
||||
def test_bpp_bprime(self):
|
||||
N, M = 64, 4
|
||||
MN = N*M
|
||||
y = unhexlify(b'60421950bee0aab949e63336db1eb9532dba6b4599c5cd9fb1dbde909114100e')
|
||||
z = unhexlify(b'e0408b528e9d35ccb8386b87f39b85c724740644f4db412483a8852cdb3ceb00')
|
||||
zc = crypto.decodeint_into(None, z)
|
||||
z_sq = bp._sc_mul(None, z, z)
|
||||
sv = [1234, 8789, 4455, 6697]
|
||||
sv = [crypto.encodeint_into(None, crypto.Scalar(x)) for x in sv]
|
||||
|
||||
num_inp = len(sv)
|
||||
sc_zero = crypto.decodeint_into_noreduce(None, bp._ZERO)
|
||||
sc_mone = crypto.decodeint_into_noreduce(None, bp._MINUS_ONE)
|
||||
|
||||
def e_xL(idx, d=None):
|
||||
j, i = idx // bp._BP_N, idx % bp._BP_N
|
||||
r = None
|
||||
if j >= num_inp:
|
||||
r = sc_mone
|
||||
elif sv[j][i // 8] & (1 << i % 8):
|
||||
r = sc_zero
|
||||
else:
|
||||
r = sc_mone
|
||||
if d:
|
||||
return crypto.sc_copy(d, r)
|
||||
return r
|
||||
|
||||
aR = bp.KeyVEval(MN, lambda i, d: e_xL(i, d), raw=True)
|
||||
d_vct = bp.VctD(N, M, z_sq, raw=True)
|
||||
ypow_back = bp.KeyVPowersBackwards(MN + 1, y, raw=True)
|
||||
aR1_sc1 = crypto.Scalar()
|
||||
|
||||
def aR1_fnc(i, d):
|
||||
crypto.sc_add_into(aR1_sc1, aR.to(i), zc)
|
||||
crypto.sc_muladd_into(aR1_sc1, d_vct[i], ypow_back[MN - i], aR1_sc1)
|
||||
return crypto.encodeint_into(d, aR1_sc1)
|
||||
|
||||
bprime = bp.KeyVEval(MN, aR1_fnc, raw=False) # aR1
|
||||
b64 = bp._copy_key(None, bprime.to(64))
|
||||
b65 = bp._copy_key(None, bprime.to(65))
|
||||
b128 = bp._copy_key(None, bprime.to(128))
|
||||
b65_2 = bp._copy_key(None, bprime.to(65))
|
||||
b64_2 = bp._copy_key(None, bprime.to(64))
|
||||
_ = bprime[89]
|
||||
b128_2 = bp._copy_key(None, bprime.to(128))
|
||||
|
||||
self.assertEqual(b64, b64_2)
|
||||
self.assertEqual(b65, b65_2)
|
||||
self.assertEqual(b128, b128_2)
|
||||
|
||||
def mask_consistency_check(self, bpi):
|
||||
sv = [crypto.Scalar(123)]
|
||||
gamma = [crypto.Scalar(432)]
|
||||
|
||||
M, logM, aL, aR, V, gamma = bpi.prove_setup(sv, gamma)
|
||||
bpi.prove_setup(sv, gamma)
|
||||
x = bp._ensure_dst_key()
|
||||
y = bp._ensure_dst_key()
|
||||
|
||||
@ -39,8 +170,8 @@ class TestMoneroBulletproof(unittest.TestCase):
|
||||
|
||||
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(bpi.aL, bpi.aR, ve1)
|
||||
bpi.vector_exponent(bpi.aL, bpi.aR, ve2)
|
||||
|
||||
bpi.vector_exponent(sL, sR, ve1)
|
||||
bpi.vector_exponent(sL, sR, ve2)
|
||||
@ -275,6 +406,38 @@ class TestMoneroBulletproof(unittest.TestCase):
|
||||
b=unhexlify(b"dfea0fe39d9a7c5497fd01e92fc7fa8b39cda75b340322f77e0cac15194aa007"),
|
||||
t=unhexlify(b"0de43b393686af8dd0d89f4832a2995cda14e6288de9ecd2b4bf2fa39baba408")
|
||||
)
|
||||
|
||||
def bproof_plus_1(self):
|
||||
return BulletproofPlus(
|
||||
V=[
|
||||
unhexlify(b"e0dae61095ac728a15d4d9754f1f9f956c22d4fa2deee2c0ff1def031b083e02"),
|
||||
unhexlify(b"5b424ecb1f8ea02351d324296a34a0608ecc104610feaad06e6002f61992bfe1"),
|
||||
],
|
||||
A=unhexlify(b"6ae6f16a6b01cf494fb2cf368573365293f76c624cfc11152d648479238e9319"),
|
||||
A1=unhexlify(b"33ad318a44df6f14a945e6d051911ab9a24841457d15d62bd1436fb3edc8a193"),
|
||||
B=unhexlify(b"5f56531cb8e78dbb3450f1d599a6d4c7f5e4c04ee3e7015643c19a528bcbb109"),
|
||||
|
||||
r1=unhexlify(b"40ad8a9c6b3bdd95c7fb8605e50135050e64f1ce29d1c4b37b1271e658354500"),
|
||||
s1=unhexlify(b"aed959c770499134aaa7e099f566dac56ee12959d797b62a3d8d1037b790b806"),
|
||||
d1=unhexlify(b"395a1e8d3df8e90e716fdeaa493090782c8db922337d09a36b50c1f02cd8e100"),
|
||||
|
||||
L=[unhexlify(b"ed2d768bb9c8b5a9fa24c90b5831d3cceb3e78cef45eba90e52f89a2b3c859d2"),
|
||||
unhexlify(b"7f25cc8e211783e9c1b80dd13ee286943da0ec07bd33291536639432758f6927"),
|
||||
unhexlify(b"7bae3d31f4e2a6d78d74d2bcb6d0656e4222161423d635f7ce08805e96cec83e"),
|
||||
unhexlify(b"c87f949f70cf569c4baa332612305733fd19a2262490c55ec88c16a68d7b5e7d"),
|
||||
unhexlify(b"34d06caf0d02129ebcc8bf318da8f6a0ddfaf2c7cb85f4144726561cefc86dcd"),
|
||||
unhexlify(b"ab3effd3a2706591774e013c76f5b8ece9e58abf7efc0a11b479f9d2a89d0c55"),
|
||||
unhexlify(b"ebf8d34e6643533bf73b13d2dd56aeaf2113fb3017d39bc6db6a2f71bc1d53f1"),
|
||||
],
|
||||
R=[unhexlify(b"27e146e61e88944246dcd90ddb4284923c7fdc6fd6a187ed2efa3dcb8c380346"),
|
||||
unhexlify(b"fab99152d48d835b9a01cdbec46301db0f57ca091f6cbaa0b45c8498f18babe1"),
|
||||
unhexlify(b"8467f87acd7be026a27ed798cca6cc1526b0f805ac534a9c5162a9cd75460011"),
|
||||
unhexlify(b"f421fa4bda1dba042ca56c6bdce313dc8d18cee084d722af47447ce54b6ff8df"),
|
||||
unhexlify(b"8dd5dabc0ad67c83f42668e96bf5ee6741bcd8e661eda1e8ce6a23d84cf0b5b5"),
|
||||
unhexlify(b"fcf20a7775699b0456542930b2374b233fb3f8f79e1911428157631a20b3c3ad"),
|
||||
unhexlify(b"66e477bd93dabb184e2738829320bf8e60f6b4b476ca0fbc1013af28e8de34c1"),
|
||||
],
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
def test_masks(self):
|
||||
@ -319,6 +482,31 @@ class TestMoneroBulletproof(unittest.TestCase):
|
||||
with self.assertRaises(Exception):
|
||||
bpi.verify_batch([self.bproof_2_invalid()])
|
||||
|
||||
def test_verify_plus(self):
|
||||
bpi = bp.BulletProofPlusBuilder()
|
||||
bpi.verify_batch([self.bproof_plus_1()])
|
||||
|
||||
def test_prove_plus_(self):
|
||||
bpi = bp.BulletProofPlusBuilder()
|
||||
sv = [crypto.Scalar(123)]
|
||||
gamma = [crypto.Scalar(456)]
|
||||
proof = bpi.prove_batch(sv, gamma)
|
||||
bpi.verify_batch([proof])
|
||||
|
||||
def test_prove_plus_2(self):
|
||||
bpi = bp.BulletProofPlusBuilder()
|
||||
sv = [crypto.Scalar(123), crypto.Scalar(768)]
|
||||
gamma = [crypto.Scalar(456), crypto.Scalar(901)]
|
||||
proof = bpi.prove_batch(sv, gamma)
|
||||
bpi.verify_batch([proof])
|
||||
|
||||
def test_prove_plus_8(self):
|
||||
bpi = bp.BulletProofPlusBuilder()
|
||||
sv = [crypto.Scalar(i*123 + 45) for i in range(8)]
|
||||
gamma = [crypto.Scalar(i*456 * 17) for i in range(8)]
|
||||
proof = bpi.prove_batch(sv, gamma)
|
||||
bpi.verify_batch([proof])
|
||||
|
||||
def test_prove_random_masks(self):
|
||||
bpi = bp.BulletProofBuilder()
|
||||
bpi.use_det_masks = False # trully randomly generated mask vectors
|
||||
|
Loading…
Reference in New Issue
Block a user