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
pull/2227/head
Dusan Klinec 2 years ago committed by matejcik
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…
Cancel
Save