forked from 0xPARC/plonkathon
-
Notifications
You must be signed in to change notification settings - Fork 0
/
setup.py
96 lines (88 loc) · 3.94 KB
/
setup.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
from utils import *
import py_ecc.bn128 as b
from curve import ec_lincomb, G1Point, G2Point
from compiler.program import CommonPreprocessedInput
from verifier import VerificationKey
from dataclasses import dataclass
from poly import Polynomial, Basis
# Recover the trusted setup from a file in the format used in
# https://github.com/iden3/snarkjs#7-prepare-phase-2
SETUP_FILE_G1_STARTPOS = 80
SETUP_FILE_POWERS_POS = 60
@dataclass
class Setup(object):
# ([1]₁, [x]₁, ..., [x^{d-1}]₁)
# = ( G, xG, ..., x^{d-1}G ), where G is a generator of G_2
powers_of_x: list[G1Point]
# [x]₂ = xH, where H is a generator of G_2
X2: G2Point
@classmethod
def from_file(cls, filename):
contents = open(filename, "rb").read()
# Byte 60 gives you the base-2 log of how many powers there are
powers = 2 ** contents[SETUP_FILE_POWERS_POS]
# Extract G1 points, which start at byte 80
values = [
int.from_bytes(contents[i : i + 32], "little")
for i in range(
SETUP_FILE_G1_STARTPOS, SETUP_FILE_G1_STARTPOS + 32 * powers * 2, 32
)
]
assert max(values) < b.field_modulus
# The points are encoded in a weird encoding, where all x and y points
# are multiplied by a factor (for montgomery optimization?). We can
# extract the factor because we know the first point is the generator.
factor = b.FQ(values[0]) / b.G1[0]
values = [b.FQ(x) / factor for x in values]
powers_of_x = [(values[i * 2], values[i * 2 + 1]) for i in range(powers)]
print("Extracted G1 side, X^1 point: {}".format(powers_of_x[1]))
# Search for start of G2 points. We again know that the first point is
# the generator.
pos = SETUP_FILE_G1_STARTPOS + 32 * powers * 2
target = (factor * b.G2[0].coeffs[0]).n
while pos < len(contents):
v = int.from_bytes(contents[pos : pos + 32], "little")
if v == target:
break
pos += 1
print("Detected start of G2 side at byte {}".format(pos))
X2_encoding = contents[pos + 32 * 4 : pos + 32 * 8]
X2_values = [
b.FQ(int.from_bytes(X2_encoding[i : i + 32], "little")) / factor
for i in range(0, 128, 32)
]
X2 = (b.FQ2(X2_values[:2]), b.FQ2(X2_values[2:]))
assert b.is_on_curve(X2, b.b2)
print("Extracted G2 side, X^1 point: {}".format(X2))
# assert b.pairing(b.G2, powers_of_x[1]) == b.pairing(X2, b.G1)
# print("X^1 points checked consistent")
return cls(powers_of_x, X2)
# Encodes the KZG commitment that evaluates to the given values in the group
def commit(self, values: Polynomial) -> G1Point:
# Run inverse FFT to convert values from Lagrange basis to monomial basis
# Optional: Check values size does not exceed maximum power setup can handle
# Compute linear combination of setup with values
assert values.basis == Basis.LAGRANGE
print(values)
coeffs = values.ifft().values
if len(coeffs) > len(self.powers_of_x):
raise Exception("Not enough powers in setup")
return ec_lincomb([(s, x) for s, x in zip(self.powers_of_x, coeffs)])
# Generate the verification key for this program with the given setup
def verification_key(self, pk: CommonPreprocessedInput) -> VerificationKey:
print("Preprocessed group: ", pk.group_order)
print("Preprocessed left selector: ", pk.QM.values)
return VerificationKey(
pk.group_order,
self.commit(pk.QM),
self.commit(pk.QL),
self.commit(pk.QR),
self.commit(pk.QO),
self.commit(pk.QC),
self.commit(pk.S1),
self.commit(pk.S2),
self.commit(pk.S3),
self.X2,
Scalar.root_of_unity(pk.group_order),
)
# Create the appropriate VerificationKey object