Test equivalence with py_ecc
This commit is contained in:
parent
cc049a05ca
commit
1e41ed4d36
|
@ -54,6 +54,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
%array_class(fr_t, frArray)
|
%array_class(fr_t, frArray)
|
||||||
|
%array_class(byte, byteArray)
|
||||||
%array_class(g1_t, g1Array)
|
%array_class(g1_t, g1Array)
|
||||||
%array_class(g2_t, g2Array)
|
%array_class(g2_t, g2Array)
|
||||||
%pointer_class(poly, polyp)
|
%pointer_class(poly, polyp)
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
|
||||||
|
from py_ecc import optimized_bls12_381 as b
|
||||||
|
|
||||||
|
def _simple_ft(vals, modulus, roots_of_unity):
|
||||||
|
L = len(roots_of_unity)
|
||||||
|
o = []
|
||||||
|
for i in range(L):
|
||||||
|
last = b.Z1 if type(vals[0]) == tuple else 0
|
||||||
|
for j in range(L):
|
||||||
|
if type(vals[0]) == tuple:
|
||||||
|
last = b.add(last, b.multiply(vals[j], roots_of_unity[(i*j)%L]))
|
||||||
|
else:
|
||||||
|
last += vals[j] * roots_of_unity[(i*j)%L]
|
||||||
|
o.append(last if type(last) == tuple else last % modulus)
|
||||||
|
return o
|
||||||
|
|
||||||
|
def _fft(vals, modulus, roots_of_unity):
|
||||||
|
if len(vals) <= 4 and type(vals[0]) != tuple:
|
||||||
|
#return vals
|
||||||
|
return _simple_ft(vals, modulus, roots_of_unity)
|
||||||
|
elif len(vals) == 1 and type(vals[0]) == tuple:
|
||||||
|
return vals
|
||||||
|
L = _fft(vals[::2], modulus, roots_of_unity[::2])
|
||||||
|
R = _fft(vals[1::2], modulus, roots_of_unity[::2])
|
||||||
|
o = [0 for i in vals]
|
||||||
|
for i, (x, y) in enumerate(zip(L, R)):
|
||||||
|
y_times_root = b.multiply(y, roots_of_unity[i]) if type(y) == tuple else y*roots_of_unity[i]
|
||||||
|
o[i] = b.add(x, y_times_root) if type(x) == tuple else (x+y_times_root) % modulus
|
||||||
|
o[i+len(L)] = b.add(x, b.neg(y_times_root)) if type(x) == tuple else (x-y_times_root) % modulus
|
||||||
|
return o
|
||||||
|
|
||||||
|
def expand_root_of_unity(root_of_unity, modulus):
|
||||||
|
# Build up roots of unity
|
||||||
|
rootz = [1, root_of_unity]
|
||||||
|
while rootz[-1] != 1:
|
||||||
|
rootz.append((rootz[-1] * root_of_unity) % modulus)
|
||||||
|
return rootz
|
||||||
|
|
||||||
|
def fft(vals, modulus, root_of_unity, inv=False):
|
||||||
|
rootz = expand_root_of_unity(root_of_unity, modulus)
|
||||||
|
# Fill in vals with zeroes if needed
|
||||||
|
if len(rootz) > len(vals) + 1:
|
||||||
|
vals = vals + [0] * (len(rootz) - len(vals) - 1)
|
||||||
|
if inv:
|
||||||
|
# Inverse FFT
|
||||||
|
invlen = pow(len(vals), modulus-2, modulus)
|
||||||
|
if type(vals[0]) == tuple:
|
||||||
|
return [b.multiply(x, invlen) for x in
|
||||||
|
_fft(vals, modulus, rootz[:0:-1])]
|
||||||
|
else:
|
||||||
|
return [(x*invlen) % modulus for x in
|
||||||
|
_fft(vals, modulus, rootz[:0:-1])]
|
||||||
|
else:
|
||||||
|
# Regular FFT
|
||||||
|
return _fft(vals, modulus, rootz[:-1])
|
||||||
|
|
||||||
|
# Evaluates f(x) for f in evaluation form
|
||||||
|
def inv_fft_at_point(vals, modulus, root_of_unity, x):
|
||||||
|
if len(vals) == 1:
|
||||||
|
return vals[0]
|
||||||
|
# 1/2 in the field
|
||||||
|
half = (modulus + 1)//2
|
||||||
|
# 1/w
|
||||||
|
inv_root = pow(root_of_unity, len(vals)-1, modulus)
|
||||||
|
# f(-x) in evaluation form
|
||||||
|
f_of_minus_x_vals = vals[len(vals)//2:] + vals[:len(vals)//2]
|
||||||
|
# e(x) = (f(x) + f(-x)) / 2 in evaluation form
|
||||||
|
evens = [(f+g) * half % modulus for f,g in zip(vals, f_of_minus_x_vals)]
|
||||||
|
# o(x) = (f(x) - f(-x)) / 2 in evaluation form
|
||||||
|
odds = [(f-g) * half % modulus for f,g in zip(vals, f_of_minus_x_vals)]
|
||||||
|
# e(x^2) + coordinate * x * o(x^2) in evaluation form
|
||||||
|
comb = [(o * x * inv_root**i + e) % modulus for i, (o, e) in enumerate(zip(odds, evens))]
|
||||||
|
return inv_fft_at_point(comb[:len(comb)//2], modulus, root_of_unity ** 2 % modulus, x**2 % modulus)
|
||||||
|
|
||||||
|
def shift_domain(vals, modulus, root_of_unity, factor):
|
||||||
|
if len(vals) == 1:
|
||||||
|
return vals
|
||||||
|
# 1/2 in the field
|
||||||
|
half = (modulus + 1)//2
|
||||||
|
# 1/w
|
||||||
|
inv_factor = pow(factor, modulus - 2, modulus)
|
||||||
|
half_length = len(vals)//2
|
||||||
|
# f(-x) in evaluation form
|
||||||
|
f_of_minus_x_vals = vals[half_length:] + vals[:half_length]
|
||||||
|
# e(x) = (f(x) + f(-x)) / 2 in evaluation form
|
||||||
|
evens = [(f+g) * half % modulus for f,g in zip(vals, f_of_minus_x_vals)]
|
||||||
|
print('e', evens)
|
||||||
|
# o(x) = (f(x) - f(-x)) / 2 in evaluation form
|
||||||
|
odds = [(f-g) * half % modulus for f,g in zip(vals, f_of_minus_x_vals)]
|
||||||
|
print('o', odds)
|
||||||
|
shifted_evens = shift_domain(evens[:half_length], modulus, root_of_unity ** 2 % modulus, factor ** 2 % modulus)
|
||||||
|
print('se', shifted_evens)
|
||||||
|
shifted_odds = shift_domain(odds[:half_length], modulus, root_of_unity ** 2 % modulus, factor ** 2 % modulus)
|
||||||
|
print('so', shifted_odds)
|
||||||
|
return (
|
||||||
|
[(e + inv_factor * o) % modulus for e, o in zip(shifted_evens, shifted_odds)] +
|
||||||
|
[(e - inv_factor * o) % modulus for e, o in zip(shifted_evens, shifted_odds)]
|
||||||
|
)
|
||||||
|
|
||||||
|
def shift_poly(poly, modulus, factor):
|
||||||
|
factor_power = 1
|
||||||
|
inv_factor = pow(factor, modulus - 2, modulus)
|
||||||
|
o = []
|
||||||
|
for p in poly:
|
||||||
|
o.append(p * factor_power % modulus)
|
||||||
|
factor_power = factor_power * inv_factor % modulus
|
||||||
|
return o
|
||||||
|
|
||||||
|
def mul_polys(a, b, modulus, root_of_unity):
|
||||||
|
rootz = [1, root_of_unity]
|
||||||
|
while rootz[-1] != 1:
|
||||||
|
rootz.append((rootz[-1] * root_of_unity) % modulus)
|
||||||
|
if len(rootz) > len(a) + 1:
|
||||||
|
a = a + [0] * (len(rootz) - len(a) - 1)
|
||||||
|
if len(rootz) > len(b) + 1:
|
||||||
|
b = b + [0] * (len(rootz) - len(b) - 1)
|
||||||
|
x1 = _fft(a, modulus, rootz[:-1])
|
||||||
|
x2 = _fft(b, modulus, rootz[:-1])
|
||||||
|
return _fft([(v1*v2)%modulus for v1,v2 in zip(x1,x2)],
|
||||||
|
modulus, rootz[:0:-1])
|
|
@ -0,0 +1,252 @@
|
||||||
|
from py_ecc import optimized_bls12_381 as b
|
||||||
|
from fft import fft
|
||||||
|
from multicombs import lincomb
|
||||||
|
|
||||||
|
# Generatore for the field
|
||||||
|
PRIMITIVE_ROOT = 5
|
||||||
|
MODULUS = b.curve_order
|
||||||
|
|
||||||
|
assert pow(PRIMITIVE_ROOT, (MODULUS - 1) // 2, MODULUS) != 1
|
||||||
|
assert pow(PRIMITIVE_ROOT, MODULUS - 1, MODULUS) == 1
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
#
|
||||||
|
# Helpers
|
||||||
|
#
|
||||||
|
#########################################################################################
|
||||||
|
|
||||||
|
def is_power_of_two(x):
|
||||||
|
return x > 0 and x & (x-1) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def generate_setup(s, size):
|
||||||
|
"""
|
||||||
|
# Generate trusted setup, in coefficient form.
|
||||||
|
# For data availability we always need to compute the polynomials anyway, so it makes little sense to do things in Lagrange space
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
[b.multiply(b.G1, pow(s, i, MODULUS)) for i in range(size + 1)],
|
||||||
|
[b.multiply(b.G2, pow(s, i, MODULUS)) for i in range(size + 1)],
|
||||||
|
)
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
#
|
||||||
|
# Field operations
|
||||||
|
#
|
||||||
|
#########################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
def get_root_of_unity(order):
|
||||||
|
"""
|
||||||
|
Returns a root of unity of order "order"
|
||||||
|
"""
|
||||||
|
assert (MODULUS - 1) % order == 0
|
||||||
|
return pow(PRIMITIVE_ROOT, (MODULUS - 1) // order, MODULUS)
|
||||||
|
|
||||||
|
def inv(a):
|
||||||
|
"""
|
||||||
|
Modular inverse using eGCD algorithm
|
||||||
|
"""
|
||||||
|
if a == 0:
|
||||||
|
return 0
|
||||||
|
lm, hm = 1, 0
|
||||||
|
low, high = a % MODULUS, MODULUS
|
||||||
|
while low > 1:
|
||||||
|
r = high // low
|
||||||
|
nm, new = hm - lm * r, high - low * r
|
||||||
|
lm, low, hm, high = nm, new, lm, low
|
||||||
|
return lm % MODULUS
|
||||||
|
|
||||||
|
def div(x, y):
|
||||||
|
return x * inv(y) % MODULUS
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
#
|
||||||
|
# Polynomial operations
|
||||||
|
#
|
||||||
|
#########################################################################################
|
||||||
|
|
||||||
|
def eval_poly_at(p, x):
|
||||||
|
"""
|
||||||
|
Evaluate polynomial p (coefficient form) at point x
|
||||||
|
"""
|
||||||
|
y = 0
|
||||||
|
power_of_x = 1
|
||||||
|
for i, p_coeff in enumerate(p):
|
||||||
|
y += power_of_x * p_coeff
|
||||||
|
power_of_x = (power_of_x * x) % MODULUS
|
||||||
|
return y % MODULUS
|
||||||
|
|
||||||
|
def div_polys(a, b):
|
||||||
|
"""
|
||||||
|
Long polynomial difivion for two polynomials in coefficient form
|
||||||
|
"""
|
||||||
|
a = [x for x in a]
|
||||||
|
o = []
|
||||||
|
apos = len(a) - 1
|
||||||
|
bpos = len(b) - 1
|
||||||
|
diff = apos - bpos
|
||||||
|
while diff >= 0:
|
||||||
|
quot = div(a[apos], b[bpos])
|
||||||
|
o.insert(0, quot)
|
||||||
|
for i in range(bpos, -1, -1):
|
||||||
|
a[diff + i] -= b[i] * quot
|
||||||
|
apos -= 1
|
||||||
|
diff -= 1
|
||||||
|
return [x % MODULUS for x in o]
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
#
|
||||||
|
# Utils for reverse bit order
|
||||||
|
#
|
||||||
|
#########################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
def reverse_bit_order(n, order):
|
||||||
|
"""
|
||||||
|
Reverse the bit order of an integer n
|
||||||
|
"""
|
||||||
|
assert is_power_of_two(order)
|
||||||
|
# Convert n to binary with the same number of bits as "order" - 1, then reverse its bit order
|
||||||
|
return int(('{:0' + str(order.bit_length() - 1) + 'b}').format(n)[::-1], 2)
|
||||||
|
|
||||||
|
|
||||||
|
def list_to_reverse_bit_order(l):
|
||||||
|
"""
|
||||||
|
Convert a list between normal and reverse bit order. This operation is idempotent.
|
||||||
|
"""
|
||||||
|
return [l[reverse_bit_order(i, len(l))] for i in range(len(l))]
|
||||||
|
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
#
|
||||||
|
# Converting between polynomials (in coefficient form) and data (in reverse bit order)
|
||||||
|
# and extending data
|
||||||
|
#
|
||||||
|
#########################################################################################
|
||||||
|
|
||||||
|
def get_polynomial(data):
|
||||||
|
"""
|
||||||
|
Interpolate a polynomial (coefficients) from data in reverse bit order
|
||||||
|
"""
|
||||||
|
assert is_power_of_two(len(data))
|
||||||
|
root_of_unity = get_root_of_unity(len(data))
|
||||||
|
return fft(list_to_reverse_bit_order(data), MODULUS, root_of_unity, True)
|
||||||
|
|
||||||
|
def get_data(polynomial):
|
||||||
|
"""
|
||||||
|
Get data (in reverse bit order) from polynomial in coefficient form
|
||||||
|
"""
|
||||||
|
assert is_power_of_two(len(polynomial))
|
||||||
|
root_of_unity = get_root_of_unity(len(polynomial))
|
||||||
|
return list_to_reverse_bit_order(fft(polynomial, MODULUS, root_of_unity, False))
|
||||||
|
|
||||||
|
def get_extended_data(polynomial):
|
||||||
|
"""
|
||||||
|
Get extended data (expanded by 2x, reverse bit order) from polynomial in coefficient form
|
||||||
|
"""
|
||||||
|
assert is_power_of_two(len(polynomial))
|
||||||
|
extended_polynomial = polynomial + [0] * len(polynomial)
|
||||||
|
root_of_unity = get_root_of_unity(len(extended_polynomial))
|
||||||
|
return list_to_reverse_bit_order(fft(extended_polynomial, MODULUS, root_of_unity, False))
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
#
|
||||||
|
# Kate single proofs
|
||||||
|
#
|
||||||
|
#########################################################################################
|
||||||
|
|
||||||
|
def commit_to_poly(polynomial, setup):
|
||||||
|
"""
|
||||||
|
Kate commitment to polynomial in coefficient form
|
||||||
|
"""
|
||||||
|
return lincomb(setup[0][:len(polynomial)], polynomial, b.add, b.Z1)
|
||||||
|
|
||||||
|
def compute_proof_single(polynomial, x, setup):
|
||||||
|
"""
|
||||||
|
Compute Kate proof for polynomial in coefficient form at position x
|
||||||
|
"""
|
||||||
|
quotient_polynomial = div_polys(polynomial, [-x, 1])
|
||||||
|
return lincomb(setup[0][:len(quotient_polynomial)], quotient_polynomial, b.add, b.Z1)
|
||||||
|
|
||||||
|
def check_proof_single(commitment, proof, x, y, setup):
|
||||||
|
"""
|
||||||
|
Check a proof for a Kate commitment for an evaluation f(x) = y
|
||||||
|
"""
|
||||||
|
# Verify the pairing equation
|
||||||
|
#
|
||||||
|
# e([commitment - y], [1]) = e([proof], [s - x])
|
||||||
|
# equivalent to
|
||||||
|
# e([commitment - y]^(-1), [1]) * e([proof], [s - x]) = 1_T
|
||||||
|
#
|
||||||
|
|
||||||
|
s_minus_x = b.add(setup[1][1], b.multiply(b.neg(b.G2), x))
|
||||||
|
commitment_minus_y = b.add(commitment, b.multiply(b.neg(b.G1), y))
|
||||||
|
|
||||||
|
pairing_check = b.pairing(b.G2, b.neg(commitment_minus_y), False)
|
||||||
|
pairing_check *= b.pairing(s_minus_x, proof, False)
|
||||||
|
pairing = b.final_exponentiate(pairing_check)
|
||||||
|
|
||||||
|
return pairing == b.FQ12.one()
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
#
|
||||||
|
# Kate multiproofs on a coset
|
||||||
|
#
|
||||||
|
#########################################################################################
|
||||||
|
|
||||||
|
def compute_proof_multi(polynomial, x, n, setup):
|
||||||
|
"""
|
||||||
|
Compute Kate proof for polynomial in coefficient form at positions x * w^y where w is
|
||||||
|
an n-th root of unity (this is the proof for one data availability sample, which consists
|
||||||
|
of several polynomial evaluations)
|
||||||
|
"""
|
||||||
|
quotient_polynomial = div_polys(polynomial, [-pow(x, n, MODULUS)] + [0] * (n - 1) + [1])
|
||||||
|
return lincomb(setup[0][:len(quotient_polynomial)], quotient_polynomial, b.add, b.Z1)
|
||||||
|
|
||||||
|
def check_proof_multi(commitment, proof, x, ys, setup):
|
||||||
|
"""
|
||||||
|
Check a proof for a Kate commitment for an evaluation f(x w^i) = y_i
|
||||||
|
"""
|
||||||
|
n = len(ys)
|
||||||
|
root_of_unity = get_root_of_unity(n)
|
||||||
|
|
||||||
|
# Interpolate at a coset. Note because it is a coset, not the subgroup, we have to multiply the
|
||||||
|
# polynomial coefficients by x^i
|
||||||
|
interpolation_polynomial = fft(ys, MODULUS, root_of_unity, True)
|
||||||
|
interpolation_polynomial = [div(c, pow(x, i, MODULUS)) for i, c in enumerate(interpolation_polynomial)]
|
||||||
|
|
||||||
|
# Verify the pairing equation
|
||||||
|
#
|
||||||
|
# e([commitment - interpolation_polynomial(s)], [1]) = e([proof], [s^n - x^n])
|
||||||
|
# equivalent to
|
||||||
|
# e([commitment - interpolation_polynomial]^(-1), [1]) * e([proof], [s^n - x^n]) = 1_T
|
||||||
|
#
|
||||||
|
|
||||||
|
xn_minus_yn = b.add(setup[1][n], b.multiply(b.neg(b.G2), pow(x, n, MODULUS)))
|
||||||
|
commitment_minus_interpolation = b.add(commitment, b.neg(lincomb(
|
||||||
|
setup[0][:len(interpolation_polynomial)], interpolation_polynomial, b.add, b.Z1)))
|
||||||
|
pairing_check = b.pairing(b.G2, b.neg(commitment_minus_interpolation), False)
|
||||||
|
pairing_check *= b.pairing(xn_minus_yn, proof, False)
|
||||||
|
pairing = b.final_exponentiate(pairing_check)
|
||||||
|
return pairing == b.FQ12.one()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
polynomial = [1, 2, 3, 4, 7, 7, 7, 7, 13, 13, 13, 13, 13, 13, 13, 13]
|
||||||
|
n = len(polynomial)
|
||||||
|
|
||||||
|
setup = generate_setup(1927409816240961209460912649124, n)
|
||||||
|
|
||||||
|
commitment = commit_to_poly(polynomial, setup)
|
||||||
|
proof = compute_proof_single(polynomial, 17, setup)
|
||||||
|
value = eval_poly_at(polynomial, 17)
|
||||||
|
assert check_proof_single(commitment, proof, 17, value, setup)
|
||||||
|
print("Single point check passed")
|
||||||
|
|
||||||
|
root_of_unity = get_root_of_unity(8)
|
||||||
|
x = 5431
|
||||||
|
coset = [x * pow(root_of_unity, i, MODULUS) for i in range(8)]
|
||||||
|
ys = [eval_poly_at(polynomial, z) for z in coset]
|
||||||
|
proof = compute_proof_multi(polynomial, x, 8, setup)
|
||||||
|
assert check_proof_multi(commitment, proof, x, ys, setup)
|
||||||
|
print("Coset check passed")
|
|
@ -0,0 +1,131 @@
|
||||||
|
import random, time, sys, math
|
||||||
|
|
||||||
|
# For each subset in `subsets` (provided as a list of indices into `numbers`),
|
||||||
|
# compute the sum of that subset of `numbers`. More efficient than the naive method.
|
||||||
|
def multisubset(numbers, subsets, adder=lambda x,y: x+y, zero=0):
|
||||||
|
numbers = numbers[::]
|
||||||
|
subsets = {i: {x for x in subset} for i, subset in enumerate(subsets)}
|
||||||
|
output = [zero for _ in range(len(subsets))]
|
||||||
|
|
||||||
|
for roundcount in range(9999999):
|
||||||
|
# Compute counts of every pair of indices in the subset list
|
||||||
|
pair_count = {}
|
||||||
|
for index, subset in subsets.items():
|
||||||
|
for x in subset:
|
||||||
|
for y in subset:
|
||||||
|
if y > x:
|
||||||
|
pair_count[(x, y)] = pair_count.get((x, y), 0) + 1
|
||||||
|
|
||||||
|
# Determine pairs with highest count. The cutoff parameter [:len(numbers)]
|
||||||
|
# determines a tradeoff between group operation count and other forms of overhead
|
||||||
|
pairs_by_count = sorted([el for el in pair_count.keys()], key=lambda el: pair_count[el], reverse=True)[:len(numbers)*int(math.log(len(numbers)))]
|
||||||
|
|
||||||
|
# Exit condition: all subsets have size 1, no pairs
|
||||||
|
if len(pairs_by_count) == 0:
|
||||||
|
for key, subset in subsets.items():
|
||||||
|
for index in subset:
|
||||||
|
output[key] = adder(output[key], numbers[index])
|
||||||
|
return output
|
||||||
|
|
||||||
|
# In each of the highest-count pairs, take the sum of the numbers at those indices,
|
||||||
|
# and add the result as a new value, and modify `subsets` to include the new value
|
||||||
|
# wherever possible
|
||||||
|
used = set()
|
||||||
|
for maxx, maxy in pairs_by_count:
|
||||||
|
if maxx in used or maxy in used:
|
||||||
|
continue
|
||||||
|
used.add(maxx)
|
||||||
|
used.add(maxy)
|
||||||
|
numbers.append(adder(numbers[maxx], numbers[maxy]))
|
||||||
|
for key, subset in list(subsets.items()):
|
||||||
|
if maxx in subset and maxy in subset:
|
||||||
|
subset.remove(maxx)
|
||||||
|
subset.remove(maxy)
|
||||||
|
if not subset:
|
||||||
|
output[key] = numbers[-1]
|
||||||
|
del subsets[key]
|
||||||
|
else:
|
||||||
|
subset.add(len(numbers)-1)
|
||||||
|
|
||||||
|
# Alternative algorithm. Less optimal than the above, but much lower bit twiddling
|
||||||
|
# overhead and much simpler.
|
||||||
|
def multisubset2(numbers, subsets, adder=lambda x,y: x+y, zero=0):
|
||||||
|
# Split up the numbers into partitions
|
||||||
|
partition_size = 1 + int(math.log(len(subsets) + 1))
|
||||||
|
# Align number count to partition size (for simplicity)
|
||||||
|
numbers = numbers[::]
|
||||||
|
while len(numbers) % partition_size != 0:
|
||||||
|
numbers.append(zero)
|
||||||
|
# Compute power set for each partition (eg. a, b, c -> {0, a, b, a+b, c, a+c, b+c, a+b+c})
|
||||||
|
power_sets = []
|
||||||
|
for i in range(0, len(numbers), partition_size):
|
||||||
|
new_power_set = [zero]
|
||||||
|
for dimension, value in enumerate(numbers[i:i+partition_size]):
|
||||||
|
new_power_set += [adder(n, value) for n in new_power_set]
|
||||||
|
power_sets.append(new_power_set)
|
||||||
|
# Compute subset sums, using elements from power set for each range of values
|
||||||
|
# ie. with a single power set lookup you can get the sum of _all_ elements in
|
||||||
|
# the range partition_size*k...partition_size*(k+1) that are in that subset
|
||||||
|
subset_sums = []
|
||||||
|
for subset in subsets:
|
||||||
|
o = zero
|
||||||
|
for i in range(len(power_sets)):
|
||||||
|
index_in_power_set = 0
|
||||||
|
for j in range(partition_size):
|
||||||
|
if i * partition_size + j in subset:
|
||||||
|
index_in_power_set += 2 ** j
|
||||||
|
o = adder(o, power_sets[i][index_in_power_set])
|
||||||
|
subset_sums.append(o)
|
||||||
|
return subset_sums
|
||||||
|
|
||||||
|
# Reduces a linear combination `numbers[0] * factors[0] + numbers[1] * factors[1] + ...`
|
||||||
|
# into a multi-subset problem, and computes the result efficiently
|
||||||
|
def lincomb(numbers, factors, adder=lambda x,y: x+y, zero=0):
|
||||||
|
# Maximum bit length of a number; how many subsets we need to make
|
||||||
|
maxbitlen = max((len(bin(f))-2 for f in factors), default=0)
|
||||||
|
# Compute the subsets: the ith subset contains the numbers whose corresponding factor
|
||||||
|
# has a 1 at the ith bit
|
||||||
|
subsets = [{i for i in range(len(numbers)) if factors[i] & (1 << j)} for j in range(maxbitlen+1)]
|
||||||
|
subset_sums = multisubset2(numbers, subsets, adder=adder, zero=zero)
|
||||||
|
# For example, suppose a value V has factor 6 (011 in increasing-order binary). Subset 0
|
||||||
|
# will not have V, subset 1 will, and subset 2 will. So if we multiply the output of adding
|
||||||
|
# subset 0 with twice the output of adding subset 1, with four times the output of adding
|
||||||
|
# subset 2, then V will be represented 0 + 2 + 4 = 6 times. This reasoning applies for every
|
||||||
|
# value. So `subset_0_sum + 2 * subset_1_sum + 4 * subset_2_sum` gives us the result we want.
|
||||||
|
# Here, we compute this as `((subset_2_sum * 2) + subset_1_sum) * 2 + subset_0_sum` for
|
||||||
|
# efficiency: an extra `maxbitlen * 2` group operations.
|
||||||
|
o = zero
|
||||||
|
for i in range(len(subsets)-1, -1, -1):
|
||||||
|
o = adder(adder(o, o), subset_sums[i])
|
||||||
|
return o
|
||||||
|
|
||||||
|
# Tests go here
|
||||||
|
def make_mock_adder():
|
||||||
|
counter = [0]
|
||||||
|
def adder(x, y):
|
||||||
|
if x and y:
|
||||||
|
counter[0] += 1
|
||||||
|
return x+y
|
||||||
|
return adder, counter
|
||||||
|
|
||||||
|
def test_multisubset(numcount, setcount):
|
||||||
|
numbers = [random.randrange(10**20) for _ in range(numcount)]
|
||||||
|
subsets = [{i for i in range(numcount) if random.randrange(2)} for i in range(setcount)]
|
||||||
|
adder, counter = make_mock_adder()
|
||||||
|
o = multisubset(numbers, subsets, adder=adder)
|
||||||
|
for output, subset in zip(o, subsets):
|
||||||
|
assert output == sum([numbers[x] for x in subset])
|
||||||
|
|
||||||
|
def test_lincomb(numcount, bitlength=256):
|
||||||
|
numbers = [random.randrange(10**20) for _ in range(numcount)]
|
||||||
|
factors = [random.randrange(2**bitlength) for _ in range(numcount)]
|
||||||
|
adder, counter = make_mock_adder()
|
||||||
|
o = lincomb(numbers, factors, adder=adder)
|
||||||
|
assert o == sum([n*f for n,f in zip(numbers, factors)])
|
||||||
|
total_ones = sum(bin(f).count('1') for f in factors)
|
||||||
|
print("Naive operation count: %d" % (bitlength * numcount + total_ones))
|
||||||
|
print("Optimized operation count: %d" % (bitlength * 2 + counter[0]))
|
||||||
|
print("Optimization factor: %.2f" % ((bitlength * numcount + total_ones) / (bitlength * 2 + counter[0])))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test_lincomb(int(sys.argv[1]) if len(sys.argv) >= 2 else 80)
|
|
@ -0,0 +1,161 @@
|
||||||
|
import atexit
|
||||||
|
import ckzg
|
||||||
|
import kzg_proofs
|
||||||
|
|
||||||
|
def int_from_uint64s(digits):
|
||||||
|
"""Convert a 4-tuple of base64 digits to the int it denotes"""
|
||||||
|
res, mult = 0, 1
|
||||||
|
for x in digits:
|
||||||
|
res += mult * x
|
||||||
|
mult *= 2 ** 64
|
||||||
|
return res
|
||||||
|
|
||||||
|
def fr_from_int(x):
|
||||||
|
r = []
|
||||||
|
while x > 0:
|
||||||
|
r.append(x % 2**64)
|
||||||
|
x //= 2**64
|
||||||
|
assert len(r) <= 4
|
||||||
|
while len(r) < 4:
|
||||||
|
r.append(0)
|
||||||
|
return ckzg.fr_from_uint64s(tuple(r))
|
||||||
|
|
||||||
|
def fr_to_int(fr):
|
||||||
|
return int_from_uint64s(ckzg.fr_to_uint64s(fr))
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
polynomial = [random.randint(0, 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001) for i in range(1024)]
|
||||||
|
n = len(polynomial)
|
||||||
|
|
||||||
|
secret_value = 1927409816240961209460912649124348975783457236447839525141982745761361378419
|
||||||
|
|
||||||
|
cfa = ckzg.frArray(len(polynomial))
|
||||||
|
for i, c in enumerate(polynomial):
|
||||||
|
cfa[i] = fr_from_int(c)
|
||||||
|
|
||||||
|
# Build the polynomial
|
||||||
|
|
||||||
|
ret, p = ckzg.new_poly_with_coeffs(cfa.cast(), len(polynomial))
|
||||||
|
assert ret == ckzg.C_KZG_OK
|
||||||
|
|
||||||
|
# Build a trusted setup with an arbitrary secret s
|
||||||
|
# and max scale 4 (so 16 secret values)
|
||||||
|
|
||||||
|
max_scale = n.bit_length() - 1
|
||||||
|
|
||||||
|
ret, fs = ckzg.new_fft_settings(max_scale)
|
||||||
|
assert ret == 0
|
||||||
|
|
||||||
|
sa = ckzg.byteArray(32)
|
||||||
|
for i, c in enumerate(secret_value.to_bytes(32, "little")):
|
||||||
|
sa[i] = c
|
||||||
|
|
||||||
|
ret, secret_s = ckzg.blst_scalar_from_le_bytes(sa.cast(), 32)
|
||||||
|
assert ret == 1
|
||||||
|
|
||||||
|
test_secret = ckzg.fr_from_scalar(secret_s)
|
||||||
|
assert fr_to_int(test_secret) == secret_value
|
||||||
|
|
||||||
|
|
||||||
|
num_secrets = 2 ** max_scale
|
||||||
|
|
||||||
|
g1s = ckzg.g1Array(num_secrets)
|
||||||
|
g2s = ckzg.g2Array(num_secrets)
|
||||||
|
|
||||||
|
ckzg.generate_trusted_setup(g1s.cast(), g2s.cast(), secret_s, num_secrets)
|
||||||
|
|
||||||
|
ret, ks = ckzg.new_kzg_settings(g1s.cast(), g2s.cast(), num_secrets, fs)
|
||||||
|
assert ret == 0
|
||||||
|
|
||||||
|
# Compute the Lagrange form of our polynomial in this setup
|
||||||
|
|
||||||
|
ret, p_l = ckzg.new_poly_l_from_poly(p, ks)
|
||||||
|
assert ret == 0
|
||||||
|
|
||||||
|
x = 832877253762587406983796272890571809375809175809315245
|
||||||
|
fr_x = fr_from_int(x)
|
||||||
|
assert fr_to_int(fr_x) == x
|
||||||
|
|
||||||
|
ret, y_l = ckzg.eval_poly_l(p_l, fr_x, fs)
|
||||||
|
assert ret == 0
|
||||||
|
|
||||||
|
y = ckzg.eval_poly(p, fr_x)
|
||||||
|
assert ckzg.fr_equal(y, y_l)
|
||||||
|
|
||||||
|
y_p = kzg_proofs.eval_poly_at(polynomial, x)
|
||||||
|
assert fr_to_int(y) == y_p
|
||||||
|
|
||||||
|
# Commit to the polynomial, in both Lagrange and coefficient form
|
||||||
|
# The commitment should be the same
|
||||||
|
|
||||||
|
ret, commitment = ckzg.commit_to_poly(p, ks)
|
||||||
|
assert ret == 0
|
||||||
|
ret, commitment_l = ckzg.commit_to_poly_l(p_l, ks)
|
||||||
|
assert ret == 0
|
||||||
|
assert ckzg.g1_equal(commitment, commitment_l)
|
||||||
|
|
||||||
|
# Compute proof at an arbitrary point (for both forms)
|
||||||
|
|
||||||
|
ret, π = ckzg.compute_proof_single(p, fr_x, ks)
|
||||||
|
assert ret == 0
|
||||||
|
ret, v = ckzg.eval_poly_l(p_l, fr_x, fs)
|
||||||
|
assert ret == 0
|
||||||
|
ret, π_l = ckzg.compute_proof_single_l(p_l, fr_x, v, ks)
|
||||||
|
assert ret == 0
|
||||||
|
|
||||||
|
# Compute proof using py_ecc
|
||||||
|
|
||||||
|
pyecc_setup = kzg_proofs.generate_setup(secret_value, n)
|
||||||
|
|
||||||
|
pyecc_commitment = kzg_proofs.commit_to_poly(polynomial, pyecc_setup)
|
||||||
|
pyecc_proof = kzg_proofs.compute_proof_single(polynomial, x, pyecc_setup)
|
||||||
|
assert kzg_proofs.check_proof_single(pyecc_commitment, pyecc_proof, x, y_p, pyecc_setup)
|
||||||
|
|
||||||
|
from py_ecc.bls.point_compression import compress_G1
|
||||||
|
|
||||||
|
pyecc_commitment_compressed = compress_G1(pyecc_commitment)
|
||||||
|
pyecc_proof_compressed = compress_G1(pyecc_proof)
|
||||||
|
|
||||||
|
commitment_compressed = ckzg.byteArray(48)
|
||||||
|
|
||||||
|
ckzg.blst_p1_compress(commitment_compressed.cast(), commitment)
|
||||||
|
|
||||||
|
commitment_compressed = bytes([commitment_compressed[i] for i in range(48)])
|
||||||
|
assert commitment_compressed == pyecc_commitment_compressed.to_bytes(48, "big")
|
||||||
|
|
||||||
|
|
||||||
|
proof_compressed = ckzg.byteArray(48)
|
||||||
|
|
||||||
|
ckzg.blst_p1_compress(proof_compressed.cast(), π)
|
||||||
|
|
||||||
|
proof_compressed = bytes([proof_compressed[i] for i in range(48)])
|
||||||
|
assert proof_compressed == pyecc_proof_compressed.to_bytes(48, "big")
|
||||||
|
|
||||||
|
# Check the proofs using the commitments
|
||||||
|
|
||||||
|
ret, res = ckzg.check_proof_single(commitment, π, fr_x, v, ks)
|
||||||
|
assert ret == 0
|
||||||
|
assert res
|
||||||
|
|
||||||
|
ret, res = ckzg.check_proof_single(commitment_l, π_l, fr_x, v, ks)
|
||||||
|
assert ret == 0
|
||||||
|
assert res
|
||||||
|
|
||||||
|
# Check the proof fails with the wrong value
|
||||||
|
|
||||||
|
w = ckzg.fr_add(v, ckzg.fr_one)
|
||||||
|
ret, res = ckzg.check_proof_single(commitment_l, π_l, fr_x, w, ks)
|
||||||
|
assert ret == 0
|
||||||
|
assert not res
|
||||||
|
|
||||||
|
print("All tests passed.")
|
||||||
|
|
||||||
|
# We need to manually free the C allocated arrays
|
||||||
|
# Use atexit so this file can be loaded interactively before freeing
|
||||||
|
def cleanup():
|
||||||
|
ckzg.free_poly(p)
|
||||||
|
ckzg.free_poly_l(p_l)
|
||||||
|
ckzg.free_fft_settings(fs)
|
||||||
|
ckzg.free_kzg_settings(ks)
|
||||||
|
atexit.register(cleanup)
|
|
@ -0,0 +1 @@
|
||||||
|
py_ecc
|
Loading…
Reference in New Issue