Skip to content

Instantly share code, notes, and snippets.

@HarryR
Last active October 4, 2022 11:50
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save HarryR/fbfd57043d2297885cced12951b80cc4 to your computer and use it in GitHub Desktop.
Save HarryR/fbfd57043d2297885cced12951b80cc4 to your computer and use it in GitHub Desktop.
Implementation of PolyCommit_{DL} from "Constant-Size Commitments to Polynomials and Their Applications" https://www.cypherpunks.ca/~iang/pubs/PolyCommit-AsiaCrypt.pdf
from functools import reduce
import operator
from KZG10 import TrustedSetup, CommitDivision, curve, GF, polynomial, CommitSum
"""
The batch commitment scheme,
using a `gamma` variable randomly chosen by the verifier
We then multiply the commitments by the powers of gamma to
commit to multiple polynomials.
Say, for example we have two polynomials with 3 coefficients each
sage:
phi = lambda k, poly: poly[0] + ((k^1)*poly[1]) + ((k^2)*poly[2])
psi = lambda a, b, poly: ((phi(a, poly) - phi(b, poly)) / (a - b))
poly0 = var(['p0c%d' % _ for _ in range(3)])
poly1 = var(['p1c%d' % _ for _ in range(3)])
poly2 = var(['p2c%d' % _ for _ in range(3)])
x, z, gamma = var('x z gamma')
# Then make witnesses for the two polynomials evaluated at the same point
h_0 = (gamma^1) * psi(x, z, poly0)
h_1 = (gamma^2) * psi(x, z, poly1)
h_2 = (gamma^3) * psi(x, z, poly2)
W = H = h_0 + h_1 + h_2
# Where `H` is equivalent to the batch witness `W`
# We can see the form the batch witness polynomial takes
sage: factor(h_0)
(p0c2*x + p0c2*z + p0c1)*gamma
sage: factor(h_0 + h_1)
(gamma*p1c2*x + gamma*p1c2*z + gamma*p1c1 + p0c2*x + p0c2*z + p0c1)*gamma
sage: factor(h_0 + h_1 + h_2)
(gamma^2*p2c2*x + gamma^2*p2c2*z + gamma^2*p2c1 + gamma*p1c2*x + gamma*p1c2*z + gamma*p1c1 + p0c2*x + p0c2*z + p0c1)*gamma
# Commit to the polynomials
cm_0 = phi(x, poly0)
cm_1 = phi(x, poly1)
cm_2 = phi(x, poly2)
# Then verifier computes the elements F and v
F_0 = (gamma^1) * cm_0
F_1 = (gamma^2) * cm_1
F_2 = (gamma^3) * cm_2
F = F_0 + F_1 + F_2
# For each of the commitments
(gamma^1) * (cm_0 - phi(z, poly0)) == h_0 * (x-z)
(gamma^2) * (cm_1 - phi(z, poly1)) == h_1 * (x-z)
(gamma^3) * (cm_2 - phi(z, poly2)) == h_2 * (x-z)
# Symbolic computations of the evaluations...
s0 = gamma^1 * phi(z, poly0)
s1 = gamma^2 * phi(z, poly1)
s2 = gamma^3 * phi(z, poly2)
v = s0 + s1 + s2
# The pairing equation verifies that
# F-v == W * (x-z)
# Looking at the expansion, we can see they are equivalent
sage: factor(expand(W * (x-z)))
(gamma^2*p2c2*x + gamma^2*p2c2*z + gamma^2*p2c1 + gamma*p1c2*x + gamma*p1c2*z + gamma*p1c1 + p0c2*x + p0c2*z + p0c1)*gamma*(x - z)
sage: factor(expand(F-v))
(gamma^2*p2c2*x + gamma^2*p2c2*z + gamma^2*p2c1 + gamma*p1c2*x + gamma*p1c2*z + gamma*p1c1 + p0c2*x + p0c2*z + p0c1)*gamma*(x - z)
sage: factor((F-v) / (W * (x-z)))
1
"""
def main():
F = GF(curve.curve_order)
n_polynomials = 3
n_coeffs = 3
poly_coeffs = [ [F.random() for _ in range(n_coeffs)] for i in range(n_polynomials) ]
PK = TrustedSetup.generate(F, n_coeffs, True)
# Commit to polynomials, phi(f_i, X)
cm = []
for coeffs_i in poly_coeffs:
cm.append( CommitSum(PK, coeffs_i) )
# Prover creates a random scalar
z = F.random()
# Verifier sends random scalar
gamma = F.random()
# Prover computes the polynomial commitment h(X)
s = []
W = None
for i, coeffs_i in enumerate(poly_coeffs):
s.append(polynomial(z, coeffs_i)) # s_i = f_i(z)
h_i = CommitDivision(PK, z, coeffs_i) # h_i = psi_z(f_i, X)
g1_h_i = curve.multiply(h_i, int(gamma**(i+1))) # y^i * h_i
W = g1_h_i if W is None else curve.add(W, g1_h_i)
# Prover sends W, z and s to verifier
# Verifier computes the element F
g1_F = None
for i, cm_i in enumerate(cm):
F_i = curve.multiply(cm_i, int(gamma**(i+1)))
g1_F = F_i if g1_F is None else curve.add(g1_F, F_i)
# And verifier computes the element `v`
v = reduce(operator.add, [(gamma**(i+1)) * s_i for i, s_i in enumerate(s)])
g1_v = curve.multiply(curve.G1, int(v))
# Then performs the pairing equation
g1_F_minus_v = curve.add(g1_F, curve.neg(g1_v))
g2_z = curve.multiply(curve.G2, int(z))
g2_x_minus_z = curve.add(PK.g2_powers[1], curve.neg(g2_z))
a = curve.pairing(curve.G2, g1_F_minus_v)
b = curve.pairing(g2_x_minus_z, W)
print('a', a)
print('b', b)
c = curve.pairing(curve.neg(g2_x_minus_z), W)
print('a*c', a*c)
if __name__ == "__main__":
main()
from typing import List, NamedTuple, Tuple, Union
from math import ceil, log2
from random import randint
from functools import reduce
import operator
from py_ecc import bn128 as curve
"""
Implementation of PolyCommit_{DL} from:
"Constant-Size Commitments to Polynomials and Their Applications"
- Aniket Kate, Gregory M. Zaverucha, and Ian Goldberg
- Max Planck Institute for Software Systems (MPI-SWS)
- https://www.cypherpunks.ca/~iang/pubs/PolyCommit-AsiaCrypt.pdf
- http://cacr.uwaterloo.ca/techreports/2010/cacr2010-10.pdf (extended version)
Section 3.2
"""
def as_bits_bytes(n):
n_bits = ceil(log2(n))
n_bytes = (n_bits + (8 - (n_bits % 8))) // 8
return n_bits, n_bytes
FIELD_MODULUS = curve.field_modulus
FIELD_MODULUS_bits, FIELD_MODULUS_bytes = as_bits_bytes(FIELD_MODULUS)
CURVE_ORDER = curve.curve_order
CURVE_ORDER_bits, CURVE_ORDER_bytes = as_bits_bytes(CURVE_ORDER)
assert FIELD_MODULUS_bytes == 32
assert CURVE_ORDER_bytes == 32
PointG1 = Tuple[curve.FQ, curve.FQ]
PointG2 = Tuple[curve.FQ2, curve.FQ2]
class Field(object):
def __init__(self, value, modulus: int):
if isinstance(value, Field):
value = value.v
else:
value = value % modulus
self.v = value
self.m = modulus
def __eq__(self, other):
if isinstance(other, int):
return self.v == other % self.m
elif isinstance(other, Field):
return self.v == other.v and self.m == other.m
else:
raise ValueError(f'Cannot compare {self} with {other}')
def __int__(self):
return self.v
def __add__(self, other):
if isinstance(other, Field):
other = other.v
return Field(self.v + other, self.m)
def __neg__(self):
return Field(-self.v, self.m)
def __mul__(self, other):
if isinstance(other, Field):
other = other.v
return Field(self.v * other, self.m)
def __repr__(self):
return f"Field<{self.v}>"
def __sub__(self, other):
if isinstance(other, Field):
other = other.v
return Field(self.v - other, self.m)
def __truediv__(self, other):
return self * other.inverse()
def __pow__(self, other):
assert isinstance(other, int)
return Field(pow(self.v, other, self.m), self.m)
def inverse(self):
return Field(pow(self.v, self.m-2, self.m), self.m)
class GF(object):
def __init__(self, modulus: int):
self.m = modulus
def primitive_root(self, n: int):
"""
Find a primitive n-th root of unity
- http://www.csd.uwo.ca/~moreno//AM583/Lectures/Newton2Hensel.html/node9.html
- https://crypto.stackexchange.com/questions/63614/finding-the-n-th-root-of-unity-in-a-finite-field
"""
assert n >= 2
x = 2
while True:
# x != 0, g = x^(q-1/n)
# if g^(n/2) != 1, then it is a primitive n-th root
g = pow(int(x), (self.m-1)//n, self.m)
if pow(g, n//2, self.m) != 1:
return self(g)
x += 1
def random(self):
return self(randint(0, self.m-1))
def __call__(self, value) -> Field:
return Field(value, self.m)
class TrustedSetup(NamedTuple):
F: GF
t: int
g1_powers: List[PointG1]
g2_powers: List[PointG2]
alpha_powers: List[Field]
@classmethod
def generate(cls, F: GF, t: int, g1andg2: bool):
"""
Simulate the trusted setup
Generate a random `alpha`
Then return `t` powers of `alpha` in G1, and the representation of `alpha` in G2
g, g^a, ... g^{a^t}
"""
alpha = F.random()
alpha_powers = [F(1)]
g1_powers = [curve.G1]
g2_powers = [curve.G2]
for i in range(t+1):
alpha_powers.append(alpha_powers[-1] * alpha)
if g1andg2:
g1_powers.append(curve.multiply(g1_powers[-1], int(alpha)))
g2_powers.append(curve.multiply(g2_powers[-1], int(alpha)))
return cls(F, t, g1_powers, g2_powers, alpha_powers)
def polynomial(x: Field, coeffs: List[Field]):
result = coeffs[0]
for c_i in coeffs[1:]:
result += c_i * x
x = x*x
return result
def CommitProduct(PK: TrustedSetup, coeff: List[Field]):
"""
XXX: unsure if we need this, but it looks useful
Computes commitment 'C' at `a`, where `a` is part of the trusted setup
C = reduce(operator.mul, [(a**j) * c_j for j, c_j in enumerate(coeff)])
For example:
sage: factor((a^0 * phi0) * ((a^1) * phi1) * ((a^2) * phi2) * ((a^3)*phi3) * ((a^4)*phi4))
a^10*(phi0*phi1*phi2*phi3*phi4)
Where the element 'k' is the n-th triangle number
"""
n = len(coeff)
k = (n*(n+1))//2 # equivalent to: reduce(operator.add, range(n))
element = PK.g1_powers[k]
product = PK.F(1)
for c_i in coeffs:
product *= c_i
return curve.multiply(element, product)
def CommitSumTrusted(PK: TrustedSetup, coeff: List[Field]):
return reduce(operator.add, [PK.alpha_powers[i] * c_i for i, c_i in enumerate(coeff)])
def CommitSum(PK: TrustedSetup, coeff: List[Field]):
"""
Copute commitment to the evaluation of a polynomial with coefficients
At `x`, where `x` is part of the trusted setup
"""
return reduce(curve.add, [curve.multiply(PK.g1_powers[i], int(c_i))
for i, c_i in enumerate(coeff)])
def CommitRemainder(PK: TrustedSetup, y: Field, coeff: List[Field]):
# TODO: implement
"""
f(x) = phi(x)
sage: f
x |--> c2*x^2 + c1*x + c0
sage: f.maxima_methods().divide((x-a) * (x-b))[1]
-a*b*c2 + (a*c2 + b*c2 + c1)*x + c0
sage: f.maxima_methods().divide((x-a))[1]
a^2*c2 + a*c1 + c0
sage: f.maxima_methods().divide((x-a) * (x-b) - (x-c))[1]
-a*b*c2 - c*c2 + (a*c2 + b*c2 + c1 + c2)*x + c0
"""
pass
def CommitDivisionTrusted(PK: TrustedSetup, y: Field, coeff: List[Field]):
n = len(coeff)
y_powers = [PK.F(1)]
for i in range(n):
y_powers.append(y_powers[-1] * y)
result = PK.F(0)
for i in range(0, n-1):
for j in range(i, -1, -1):
a = PK.alpha_powers[j]
b = y_powers[i-j]
c = coeff[i+1]
result += a*b*c
return result
"""
return reduce(operator.add, [PK.alpha_powers[j] * (y_powers[i-j] * coeff[i+1])
for j in range(i, -1, -1)
for i in range(0, n-1)])
"""
def CommitDivision(PK: TrustedSetup, y: Field, coeff: List[Field]):
"""
Compute commitment to the division: `(phi(x) - phi(y)) / (x - y)`
Using the trusted setup secret `x`
Example:
sage: poly4 = lambda k: c0 + ((k^1)*c1) + ((k^2)*c2) + ((k^3)*c3)
sage: factor((poly4(x) - poly4(y)) / (x - y))
c3*x^2 + c3*x*y + c3*y^2 + c2*x + c2*y + c1
TODO: number of multiplications can be reduced significantly
number of additions can also be reduced significantly
"""
n = len(coeff)
y_powers = [PK.F(1)]
for i in range(n):
y_powers.append(y_powers[-1] * y)
result = None
for i in range(0, n-1):
for j in range(i, -1, -1):
a = PK.g1_powers[j]
b = y_powers[i-j]
c = coeff[i+1]
term = curve.multiply(a, int(b*c))
result = term if result is None else curve.add(result, term)
return result
def Prove():
F = GF(curve.curve_order)
coeff = [F.random() for _ in range(3)]
PK = TrustedSetup.generate(F, len(coeff), True)
"""
The pairing equation works to verify that the witness and evaluation
match the committed polynomial.
sage:
var('x y c0 c1 c2 c3')
phi = lambda k: c0 + ((k^1)*c1) + ((k^2)*c2)
psi = lambda a, b: ((phi(a) - phi(b)) / (a - b))
psi(x, y) * (x-y) == phi(x) - phi(y)
(psi3(x, y) * (x-y)) + phi(y) == phi(x)
sage: psi(x, y) * (x-y)
c2*x^2 - c2*y^2 + c1*x - c1*y
sage: psi(x, y)
(c2*x^2 - c2*y^2 + c1*x - c1*y)/(x - y)
sage: factor(psi(x, y))
c2*x + c2*y + c1
sage: phi(x) - phi(y)
c2*x^2 - c2*y^2 + c1*x - c1*y
sage: factor(phi(x) - phi(y))
(c2*x + c2*y + c1)*(x - y)
"""
# Verify with trusted information
x = PK.alpha_powers[1]
phi_at_x = polynomial(x, coeff)
assert phi_at_x == CommitSumTrusted(PK, coeff)
i = F(3)
phi_at_i = polynomial(i, coeff)
a = polynomial(x, coeff) - phi_at_i
b = a / (x - i)
psi_i_at_x = CommitDivisionTrusted(PK, i, coeff)
assert psi_i_at_x == b
assert psi_i_at_x * (x-i) == phi_at_x - phi_at_i
# Then make commitment without access to trusted setup secrets
g1_phi_at_x = CommitSum(PK, coeff) # Commit to polynomial
# Commit to an evaluation of the same polynomial at i
i = F.random() # randomly sampled i
phi_at_i = polynomial(i, coeff)
g1_psi_i_at_x = CommitDivision(PK, i, coeff)
# Compute `x - i` in G2
g2_i = curve.multiply(curve.G2, int(i))
g2_x_sub_i = curve.add(PK.g2_powers[1], curve.neg(g2_i)) # x-i
# Verifier
g1_phi_at_i = curve.multiply(curve.G1, int(phi_at_i))
g1_phi_at_x_sub_i = curve.add(g1_phi_at_x, curve.neg(g1_phi_at_i))
a = curve.pairing(g2_x_sub_i, g1_psi_i_at_x)
b = curve.pairing(curve.G2, curve.neg(g1_phi_at_x_sub_i))
ab = a*b
print('ab', ab, ab == curve.FQ12.one())
if __name__ == "__main__":
Prove()
def derp(n):
"""
Example:
sage: poly5 = lambda k: c0 + ((k^1)*c1) + ((k^2)*c2) + ((k^3)*c3) + ((k^4)*c4) + ((k^5)*c5)
sage: poly6 = lambda k: c0 + ((k^1)*c1) + ((k^2)*c2) + ((k^3)*c3) + ((k^4)*c4) + ((k^5)*c5)
sage: poly5 = lambda k: c0 + ((k^1)*c1) + ((k^2)*c2) + ((k^3)*c3) + ((k^4)*c4)
sage: poly4 = lambda k: c0 + ((k^1)*c1) + ((k^2)*c2) + ((k^3)*c3)
sage: poly3 = lambda k: c0 + ((k^1)*c1) + ((k^2)*c2)
sage: poly2 = lambda k: c0 + ((k^1)*c1)
sage: poly2(x) - poly2(y)
c1*x - c1*y
sage: factor(poly2(x) - poly2(y))
c1*(x - y)
sage: factor((poly2(x) - poly2(y)) / (x - y))
c1
sage: factor((poly3(x) - poly3(y)) / (x - y))
c2*x + c2*y + c1
sage: factor((poly4(x) - poly4(y)) / (x - y))
c3*x^2 + c3*x*y + c3*y^2 + c2*x + c2*y + c1
sage: factor((poly5(x) - poly5(y)) / (x - y))
c4*x^3 + c4*x^2*y + c4*x*y^2 + c4*y^3 + c3*x^2 + c3*x*y + c3*y^2 + c2*x + c2*y + c1
sage: factor((poly6(x) - poly6(y)) / (x - y))
c5*x^4 + c5*x^3*y + c5*x^2*y^2 + c5*x*y^3 + c5*y^4 + c4*x^3 + c4*x^2*y + c4*x*y^2 + c4*y^3 + c3*x^2 + c3*x*y + c3*y^2 + c2*x + c2*y + c1
Emit this sequence:
>>> derp(4)
c[1] * x^0 * y^0
c[2] * x^1 * y^0
c[2] * x^0 * y^1
c[3] * x^2 * y^0
c[3] * x^1 * y^1
c[3] * x^0 * y^2
>>> derp(5)
c[1] * x^0 * y^0
c[2] * x^1 * y^0
c[2] * x^0 * y^1
c[3] * x^2 * y^0
c[3] * x^1 * y^1
c[3] * x^0 * y^2
c[4] * x^3 * y^0
c[4] * x^2 * y^1
c[4] * x^1 * y^2
c[4] * x^0 * y^3
"""
for i in range(0, n-1):
for j in range(i, -1, -1):
print(f'c[{i+1}] * x^{j} * y^{i-j}')
from typing import List, NamedTuple, Tuple
from functools import reduce
import operator
from KZG10 import TrustedSetup, CommitDivision, curve, GF, polynomial, CommitSum, Field, PointG1
"""
As per the batch version of KZG10
This opens many polynomials at many points
sage:
phi = lambda k, poly: poly[0] + ((k^1)*poly[1]) + ((k^2)*poly[2])
psi = lambda a, b, poly: ((phi(a, poly) - phi(b, poly)) / (a - b))
# Proving two polynomials, opened at different positions
polynomials = [var(['p%dc%d' % (_, j) for j in range(3)])
for _ in range(2)]
x = var('x')
z1, z2 = var('z1 z2')
s1 = phi(z1, polynomials[0])
s2 = phi(z2, polynomials[1])
cm1 = phi(x, polynomials[0])
cm2 = phi(x, polynomials[1])
gamma1, gamma2 = var('gamma1 gamma2')
W1 = gamma1 * psi(x, z1, polynomials[0])
W2 = gamma2 * psi(x, z2, polynomials[1])
r1, r2 = var('r1 r2')
v1 = (gamma1*s1)
v2 = (gamma2*s2)
F_1 = (gamma1 * cm1) - v1
F_2 = (gamma2 * cm2) - v2
F = (r1*F_1) + (r2*F_2)
lhs = F + ((r1*z1)*W1) + ((r2*z2)*W2)
rhs = ((r1 * W1) + (r2*W2)) * x
sage: factor((r1*W1+r2*W2)*x)
(gamma1*p0c2*r1*x + gamma2*p1c2*r2*x + gamma1*p0c2*r1*z1 + gamma2*p1c2*r2*z2 + gamma1*p0c1*r1 + gamma2*p1c1*r2)*x
sage: factor(expand(lhs))
(gamma1*p0c2*r1*x + gamma2*p1c2*r2*x + gamma1*p0c2*r1*z1 + gamma2*p1c2*r2*z2 + gamma1*p0c1*r1 + gamma2*p1c1*r2)*x
"""
Coefficients = List[Field]
PolynomialGroup = List[Coefficients]
class Commitment(NamedTuple):
c: PolynomialGroup
cm: List[PointG1]
z: Field
s: List[Field]
@classmethod
def create(cls, PK: TrustedSetup, polysys: PolynomialGroup):
z = PK.F.random()
s = [polynomial(z, _) for _ in polysys]
cm = [CommitSum(PK, _) for _ in polysys]
return cls(polysys, cm, z, s)
def witness(self, PK: TrustedSetup, gamma: Field) -> PointG1:
return reduce(curve.add, [curve.multiply(CommitDivision(PK, self.z, coeffs_i), int(gamma**(i+1)))
for i, coeffs_i in enumerate(self.c)])
def prepare_F_and_v(self, gamma: Field) -> Tuple[PointG1, Field]:
g1_F = reduce(curve.add, [curve.multiply(cm_i, int(gamma**(i+1))) for i, cm_i in enumerate(self.cm)])
v = reduce(operator.add, [(gamma**(j+1)) * s_i for j, s_i in enumerate(self.s)])
return g1_F, v
def prepare_F_minus_v(self, gamma: Field) -> PointG1:
g1_F, v = self.prepare_F_and_v(gamma)
g1_v = curve.multiply(curve.G1, int(v))
return curve.add(g1_F, curve.neg(g1_v))
def Step1_Prover(PK: TrustedSetup, many_polynomials: List[PolynomialGroup]) -> List[Commitment]:
"""
Commit to many groups of polynomials
"""
return [Commitment.create(PK, _) for _ in many_polynomials]
def Step2_Verifier(PK: TrustedSetup, step1: List[Commitment]) -> List[Field]:
"""
Verifier challenges prover, by providing random gamma values for each commitment
"""
return [PK.F.random() for _ in range(len(step1))]
def Step3_Prover(PK: TrustedSetup, step1: List[Commitment], step2: List[Field]) -> List[PointG1]:
"""
Prover commits to the groups of polynomials, using the gamma values from the verifier in step2
"""
assert len(step1) == len(step2)
return [_.witness(PK, gamma) for gamma, _ in zip(step2, step1)]
def Step4_Verifier_Individually(PK: TrustedSetup, step1: List[Commitment], step2: List[Field], step3: List[PointG1]):
results = []
for group, gamma, W in zip(step1, step2, step3):
g1_F_minus_v = group.prepare_F_minus_v(gamma)
g2_z = curve.multiply(curve.G2, int(group.z))
g2_x_minus_z = curve.add(PK.g2_powers[1], curve.neg(g2_z))
a = curve.pairing(curve.G2, g1_F_minus_v)
b = curve.pairing(curve.neg(g2_x_minus_z), W)
results.append(a*b == curve.FQ12.one())
return all(results)
def Step4_Verifier(PK: TrustedSetup, step1: List[Commitment], step2: List[Field], step3: List[PointG1]):
lhs_args = list()
rhs_args = list()
effs = list()
for group, gamma, W in zip(step1, step2, step3):
r = PK.F.random()
g1_F_minus_v = group.prepare_F_minus_v(gamma)
effs.append(curve.multiply(g1_F_minus_v, int(r)))
lhs_args.append(curve.multiply(W, int(r*group.z)))
rhs_args.append(curve.multiply(W, int(r)))
lhs_args.insert(0, reduce(curve.add, effs))
lhs = reduce(curve.add, lhs_args)
rhs = reduce(curve.add, rhs_args)
a = curve.pairing(curve.G2, lhs)
b = curve.pairing(PK.g2_powers[1], curve.neg(rhs))
print('a', a)
print('b', b)
return a*b == curve.FQ12.one()
def main():
F = GF(curve.curve_order)
n_polynomials = 3
n_coeffs = 3
n_openings = 2
groups = [[[F.random() for _ in range(n_coeffs)]
for i in range(n_polynomials)]
for j in range(n_openings)]
PK = TrustedSetup.generate(F, n_coeffs, True)
step1 = Step1_Prover(PK, groups)
step2 = Step2_Verifier(PK, step1)
step3 = Step3_Prover(PK, step1, step2)
print('Verifying each opening individually')
print(Step4_Verifier_Individually(PK, step1, step2, step3))
print('Verifying all openings together in a batch')
step4 = Step4_Verifier(PK, step1, step2, step3)
print(step4)
if __name__ == "__main__":
main()
from typing import NamedTuple, Union, List
from functools import reduce
import operator
from KZG10 import PointG1, curve, Field, GF
class DivisionResult(NamedTuple):
q: 'Polynomial'
r: 'Polynomial'
class Polynomial(object):
def __init__(self, F, coeffs=None):
self.F = F
coeffs = coeffs or []
self.coeffs = [F(_) for _ in coeffs]
def eval_g1(self, point_powers: List[PointG1]) -> PointG1:
"""
Evaluate the polynomial at an abstract point
Provide powers of the point, e.g.
[g^0, g^1, g^2...]
returns
sum([(g^0) * c_0, g*c_1, (g^i)*c_i, ...])
"""
assert len(point_powers) >= len(self.coeffs)
result = None
for i, c_i in enumerate(self.coeffs):
x = point_powers[i]
y = curve.multiply(x, c_i)
result = y if result is None else curve.add(result, y)
return result
def eval_scalar(self, point: Field) -> Field:
"""
Evaluate the polynomial at a known scalar point
"""
return reduce(operator.add, [(point**i) * c_i for i, c_i in enumerate(self.coeffs)])
def __repr__(self):
x = [f'{int(c_i)}*x^{i}' for i, c_i in enumerate(self.coeffs) if i != 0]
terms = ', '.join(x)
return f'<Polynomial[{terms}]>'
@classmethod
def _compact(cls, coeffs) -> List[Field]:
"""Removes all trailing zero coefficients"""
new_coeffs = list()
skipping = True
# TODO: use slicing instead of copying
for c_i in coeffs[::-1]:
if skipping:
skipping = c_i == 0
if not skipping:
new_coeffs.append(c_i)
return new_coeffs[::-1]
def compact(self) -> 'Polynomial':
return Polynomial(self.F, self._compact(self.coeffs))
def __call__(self, point: Union[List[PointG1], Field]) -> Union[PointG1, Field]:
"""
Evaluate the polynomial at a specific point
"""
if isinstance(point, list):
assert isinstance(point[0], PointG1)
return self.eval_g1(point)
elif isinstance(point, Field):
return self.eval_scalar(point)
raise TypeError(f"Unknown how to evaluate at {type(point)}: {point}")
def mul_poly(self, other: 'Polynomial') -> 'Polynomial':
"""
From: https://jeremykun.com/tag/division-algorithm/
"""
newCoeffs = [self.F(0) for _ in range(len(self) + len(other) - 1)]
for i,a in enumerate(self.coeffs):
for j,b in enumerate(other.coeffs):
newCoeffs[i+j] += a*b
return Polynomial(self.F, newCoeffs).compact()
def mul_scalar(self, other: Field) -> 'Polynomial':
return Polynomial(self.F, [_ * other for _ in self.coeffs])
def __mul__(self, other: Union[Field, 'Polynomial']) -> 'Polynomial':
if isinstance(other, Polynomial):
return self.mul_poly(other)
elif isinstance(other, Field):
return self.mul_scalar(other)
raise TypeError(f"Unknown how to evaluate at {type(point)}: {point}")
def __add__(self, other: 'Polynomial') -> 'Polynomial':
new_length = max(len(self.coeffs), len(other.coeffs))
result = [self.F(0)] * new_length
for i in range(new_length):
if len(self) > i:
result[i] += self.coeffs[i]
if len(other) > i:
result[i] += other.coeffs[i]
return Polynomial(self.F, result).compact()
def __neg__(self) -> 'Polynomial':
return Polynomial(self.F, [-c_i for c_i in self.coeffs])
def __sub__(self, other: 'Polynomial') -> 'Polynomial':
return self + -other
def is_zero(self):
return len(self.coeffs) == 0 or all([c_i == 0 for c_i in self.coeffs])
@classmethod
def random(cls, F: GF, n: int) -> 'Polynomial':
return cls(F, [F.random() for _ in range(n)])
def inverse(self):
return Polynomial(self.F, [_.inverse() for _ in self.coeffs])
def __iter__(self):
return iter(self.coeffs)
def __len__(self):
return len(self.coeffs)
def __getitem__(self, index: int) -> Field:
return self.coeffs[index]
def degree(self):
return len(self) - 1
def __eq__(self, other: 'Polynomial') -> 'Polynomial':
return self.degree() == other.degree() and all([x==y for x, y in zip(self.coeffs, other.coeffs)])
def leadingCoefficient(self) -> Field:
return self.coeffs[-1]
@classmethod
def zero(cls, F):
return cls(F, [])
def __divmod__(self, other: 'Polynomial') -> 'Polynomial':
# Synthetic division of polynomials
# https://rosettacode.org/wiki/Polynomial_synthetic_division#Python
"""
dividend = list(self.compact().coeffs[::-1])
divisor = list(other.compact().coeffs[::-1])
out = list(dividend)
normalizer = divisor[0].inverse()
for i in range(len(dividend) - (len(divisor)-1)):
out[i] *= normalizer
coef = out[i]
if coef != 0:
for j in range(1, len(divisor)):
out[i + j] += -divisor[j] * coef
separator = -(len(divisor)-1)
q = Polynomial(self.F, out[:separator][::-1]).compact()
r = Polynomial(self.F, out[separator:][::-1]).compact()
return DivisionResult(q, r)
"""
# From: https://jeremykun.com/tag/division-algorithm/
quotient, remainder = self.zero(self.F), self
divisorDeg = other.degree()
divisorLC = other.leadingCoefficient().inverse()
while remainder.degree() >= divisorDeg:
monomialExponent = remainder.degree() - divisorDeg
monomialZeros = [self.F(0) for _ in range(monomialExponent)]
monomialDivisor = Polynomial(self.F, monomialZeros + [remainder.leadingCoefficient() * divisorLC])
quotient += monomialDivisor
remainder -= monomialDivisor * other
return DivisionResult(quotient, remainder)
def __truediv__(self, other: 'Polynomial') -> 'Polynomial':
q, r = divmod(self, other)
return q
def test_division():
Fp = GF(curve.curve_order)
a = Polynomial(Fp, [14, 9, 1])
b = Polynomial(Fp, [7, 1])
q, r = divmod(a, b)
assert q[0] == 2
assert q[1] == 1
assert r.is_zero() == True
# (2x^2 - 5x - 1) / x-3
a = Polynomial(Fp, [-1, -5, 2])
b = Polynomial(Fp, [-3, 1])
q, r = divmod(a, b)
assert len(q) == 2
assert len(r) == 1
assert q[0] == 1
assert q[1] == 2
assert r[0] == 2
# sage: f(x) = 18*x^3 - x^2 - 5*x - 8
# sage: g(x) = x^2 - 3
# sage: f.maxima_methods().divide(g)
# [18*x - 1, 49*x - 11] ... ?
# Instead, just check quotient remainder theorem
a = Polynomial(Fp, [-8, -5, -1, 18])
b = Polynomial(Fp, [3, 1])
q, r = divmod(a, b)
c = (b*q) + r
assert a == c
def test_multiplication():
Fp = GF(curve.curve_order)
a = Polynomial(Fp, [Fp(_) for _ in [-5, 1]])
b = Polynomial(Fp, [Fp(_) for _ in [2, 1]])
c = a * b
assert len(c) == 3
assert c[0] == Fp(-10)
assert c[1] == Fp(-3)
assert c[2] == Fp(1)
def main():
test_division()
test_multiplication()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment