Skip to content

Commit 8ad15b5

Browse files
perf: Add zkscript utils (#2)
Changes include: * Define utilities to facilitate interaction with zkscript elliptic curve operations * Add classes for gradients to facilitate interaction with zkscript; add documentation
1 parent f582820 commit 8ad15b5

File tree

5 files changed

+278
-93
lines changed

5 files changed

+278
-93
lines changed

elliptic_curves/data_structures/proof.py

Lines changed: 58 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,27 @@
22
from elliptic_curves.models.bilinear_pairings import BilinearPairingCurve
33
from elliptic_curves.data_structures.vk import PreparedVerifyingKey
44
from elliptic_curves.data_structures.zkscript import ZkScriptProof
5+
from elliptic_curves.util.zkscript import (
6+
multi_scalar_multiplication_with_fixed_bases_gradients,
7+
unrolled_multiplication_gradients,
8+
)
59

610

711
class PreparedProof:
12+
"""Prepared proof encapsulating the pre-computed data required to prove that a Groth16 proof is valid.
13+
14+
Args:
15+
proof: The proof.
16+
curve: The curve over which Groth16 is instantiated.
17+
public_statements (list[int]): The public statements for which the proof has been created (w/o the initial 1).
18+
gradients_b: The gradients required to compute proof.b * curve.miller_loop_engine.exp_miller_loop.
19+
gradients_minus_gamma: The gradients required to compute minus_gamma * curve.miller_loop_engine.exp_miller_loop.
20+
gradients_minus_delta: The gradients required to compute minus_delta * curve.miller_loop_engine.exp_miller_loop.
21+
inverse_miller_loop: The inverse of miller_loop([proof.a, sum(vk.gamma_abc), proof.c], [proof.b, -vk.gamma, -vk.delta]).
22+
msm_key: Instance of MsmWithFixedBasesGradients to compute sum_(i=1)^l public_statement[i-1] * vk.gamma_abc[i].
23+
gradient_gamma_abc_zero: The gradient to compute vk.gamma_abc[0] + sum_(i=1)^l public_statement[i-1] * vk.gamma_abc[i].
24+
"""
25+
826
def __init__(
927
self,
1028
proof,
@@ -14,8 +32,8 @@ def __init__(
1432
gradients_minus_gamma,
1533
gradients_minus_delta,
1634
inverse_miller_loop,
17-
gradients_msm,
18-
gradients_public_statements,
35+
msm_key,
36+
gradient_gamma_abc_zero,
1937
):
2038
self.proof = proof
2139
self.curve = curve
@@ -24,12 +42,14 @@ def __init__(
2442
self.gradients_minus_gamma = gradients_minus_gamma
2543
self.gradients_minus_delta = gradients_minus_delta
2644
self.inverse_miller_loop = inverse_miller_loop
27-
self.gradients_msm = gradients_msm
28-
self.gradients_public_statements = gradients_public_statements
45+
self.msm_key = msm_key
46+
self.gradient_gamma_abc_zero = gradient_gamma_abc_zero
2947
return
3048

3149

3250
class Proof:
51+
"""Class encapsulating a Groth16 proof."""
52+
3353
def __init__(self, curve: BilinearPairingCurve, a: G1Point, b: G2Point, c: G1Point):
3454
self.curve = curve
3555
self.a = a
@@ -39,6 +59,7 @@ def __init__(self, curve: BilinearPairingCurve, a: G1Point, b: G2Point, c: G1Poi
3959
def prepare(
4060
self, prepared_vk: PreparedVerifyingKey, public_statements: list[int]
4161
) -> PreparedProof:
62+
"""Turn an instance of `Self` into an instance of `PreparedProof`."""
4263
public_statements_extended = [1, *public_statements]
4364

4465
# Compute \sum_(i=0)^l a_i * gamma_abc[i]
@@ -47,66 +68,46 @@ def prepare(
4768
len(prepared_vk.vk.gamma_abc) == n_pub + 1
4869
), "Wrong number of public inputs"
4970

50-
sum_gamma_abc = prepared_vk.vk.gamma_abc[0]
51-
for i in range(1, n_pub + 1):
71+
sum_gamma_abc = prepared_vk.vk.gamma_abc[1].multiply(
72+
public_statements_extended[1]
73+
)
74+
for i in range(2, n_pub + 1):
5275
sum_gamma_abc += prepared_vk.vk.gamma_abc[i].multiply(
5376
public_statements_extended[i]
5477
)
5578

5679
# Gradients for the pairing
57-
gradients_b = self.b.gradients(self.curve.miller_loop_engine.exp_miller_loop)
80+
gradients_b = unrolled_multiplication_gradients(
81+
self.curve.miller_loop_engine.val_miller_loop,
82+
self.b,
83+
self.curve.miller_loop_engine.exp_miller_loop,
84+
)
5885

5986
# Inverse of the Miller loop output
6087
inverse_miller_loop = self.curve.miller_loop(
61-
[self.a, sum_gamma_abc, self.c],
88+
[self.a, sum_gamma_abc + prepared_vk.vk.gamma_abc[0], self.c],
6289
[self.b, prepared_vk.minus_gamma, prepared_vk.minus_delta],
6390
).invert()
6491

65-
# Compute gradients for partial sums: gradients between a_i * gamma_abc[i] and \sum_(j=0)^(i-1) a_j * gamma_abc[j]
66-
gradients_msm = []
67-
for i in range(n_pub, 0, -1):
68-
sum_gamma_abc -= prepared_vk.vk.gamma_abc[i].multiply(
69-
public_statements_extended[i]
70-
)
71-
if (
72-
sum_gamma_abc.is_infinity()
73-
or prepared_vk.vk.gamma_abc[i]
74-
.multiply(public_statements_extended[i])
75-
.is_infinity()
76-
):
77-
gradients_msm.append([])
78-
else:
79-
gradient = sum_gamma_abc.gradient(
80-
prepared_vk.vk.gamma_abc[i].multiply(public_statements_extended[i])
81-
)
82-
gradients_msm.append(gradient)
83-
84-
# Gradients for multiplications pub[i] * gamma_abc[i]
85-
gradients_public_statements = []
86-
for i in range(1, n_pub + 1):
87-
if public_statements_extended[i] == 0:
88-
gradients_public_statements.append([])
89-
else:
90-
# Binary expansion of pub[i]
91-
exp_pub_i = [
92-
int(bin(public_statements_extended[i])[j])
93-
for j in range(2, len(bin(public_statements_extended[i])))
94-
][::-1]
95-
96-
gradients_public_statements.append(
97-
prepared_vk.vk.gamma_abc[i].gradients(exp_pub_i)
98-
)
92+
# Gradient for addition of gamma_abc[0]
93+
gradient_gamma_abc_zero = sum_gamma_abc.gradient(prepared_vk.vk.gamma_abc[0])
94+
95+
# Gradients for msm
96+
msm_key = multi_scalar_multiplication_with_fixed_bases_gradients(
97+
public_statements,
98+
prepared_vk.vk.gamma_abc[1:],
99+
)
99100

100101
return PreparedProof(
101102
self,
102103
self.curve,
103104
public_statements,
104105
gradients_b,
105-
prepared_vk.gradients_minus_delta,
106+
prepared_vk.gradients_minus_gamma,
106107
prepared_vk.gradients_minus_delta,
107108
inverse_miller_loop,
108-
gradients_msm,
109-
gradients_public_statements,
109+
msm_key,
110+
gradient_gamma_abc_zero,
110111
)
111112

112113
def prepare_for_zkscript(
@@ -115,55 +116,35 @@ def prepare_for_zkscript(
115116
public_statements: list[int],
116117
prepared_proof: PreparedProof | None = None,
117118
) -> ZkScriptProof:
119+
"""Turn an instance of `Self` into an instance of `ZkScriptProof`."""
118120
prepared_proof = (
119121
prepared_proof
120122
if prepared_proof is not None
121123
else self.prepare(prepared_vk, public_statements)
122124
)
123125

124-
gradients_msm = []
125-
for gradient in prepared_proof.gradients_msm:
126-
try:
127-
gradients_msm.append(gradient.to_list())
128-
except Exception as _:
129-
gradients_msm.append([])
130-
131-
gradients_public_statements = []
132-
for gradients in prepared_proof.gradients_public_statements:
133-
try:
134-
gradients_public_statements.append(
135-
[
136-
list(map(lambda s: s.to_list(), gradient))
137-
for gradient in gradients
138-
]
139-
)
140-
except Exception as _:
141-
gradients_public_statements.append([])
126+
gradients_multiplications, gradients_additions = (
127+
prepared_proof.msm_key.as_data()
128+
)
142129

143130
return ZkScriptProof(
144131
self.a.to_list(),
145132
self.b.to_list(),
146133
self.c.to_list(),
147134
public_statements,
148-
[
149-
list(map(lambda s: s.to_list(), gradient))
150-
for gradient in prepared_proof.gradients_b
151-
],
152-
[
153-
list(map(lambda s: s.to_list(), gradient))
154-
for gradient in prepared_vk.gradients_minus_gamma
155-
],
156-
[
157-
list(map(lambda s: s.to_list(), gradient))
158-
for gradient in prepared_vk.gradients_minus_delta
159-
],
135+
prepared_proof.gradients_b.as_data(),
136+
prepared_vk.gradients_minus_gamma.as_data(),
137+
prepared_vk.gradients_minus_delta.as_data(),
160138
prepared_proof.inverse_miller_loop.to_list(),
161-
gradients_msm,
162-
gradients_public_statements,
139+
gradients_multiplications,
140+
gradients_additions,
141+
prepared_proof.gradient_gamma_abc_zero.to_list(),
163142
)
164143

165144

166145
class ProofGeneric:
146+
"""Generic proof class encapsulating the curve over which Groth16 proof is instantiated."""
147+
167148
def __init__(self, curve: BilinearPairingCurve):
168149
self.curve = curve
169150

elliptic_curves/data_structures/vk.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,22 @@
22
from elliptic_curves.models.bilinear_pairings import BilinearPairingCurve
33
from elliptic_curves.data_structures.zkscript import ZkScriptVerifyingKey
44

5+
from elliptic_curves.util.zkscript import unrolled_multiplication_gradients
6+
57

68
class PreparedVerifyingKey:
9+
"""Prepared verifying key encapsulating the pre-computed data needed to verify a Groth16 proof.
10+
11+
Args:
12+
vk: The verifying key
13+
curve (BilinearPairingCurve): The curve over which Groth16 is instantiated.
14+
alpha_beta: The value of pairing(vk.alpha, vk.beta).
15+
minus_gamma: The value of -vk.gamma.
16+
minus_delta: The value of -vk.delta.
17+
gradients_minus_gamma: The gradients required to compute minus_gamma * curve.miller_loop_engine.exp_miller_loop
18+
gradients_minus_delta: The gradients required to compute minus_delta * curve.miller_loop_engine.exp_miller_loop
19+
"""
20+
721
def __init__(
822
self,
923
vk,
@@ -25,6 +39,8 @@ def __init__(
2539

2640

2741
class VerifyingKey:
42+
"""Verifying key encapsulating the data required to verify a Groth16 proof."""
43+
2844
def __init__(
2945
self,
3046
curve: BilinearPairingCurve,
@@ -43,14 +59,19 @@ def __init__(
4359
return
4460

4561
def prepare(self):
62+
"""Turn an instance of `Self` into an instance of `PreparedVerifyingKey`."""
4663
alpha_beta = self.curve.pairing([self.alpha], [self.beta])
4764
minus_gamma = -self.gamma
4865
minus_delta = -self.delta
49-
gradients_minus_gamma = minus_gamma.gradients(
50-
self.curve.miller_loop_engine.exp_miller_loop
66+
gradients_minus_gamma = unrolled_multiplication_gradients(
67+
self.curve.miller_loop_engine.val_miller_loop,
68+
minus_gamma,
69+
self.curve.miller_loop_engine.exp_miller_loop,
5170
)
52-
gradients_minus_delta = minus_delta.gradients(
53-
self.curve.miller_loop_engine.exp_miller_loop
71+
gradients_minus_delta = unrolled_multiplication_gradients(
72+
self.curve.miller_loop_engine.val_miller_loop,
73+
minus_delta,
74+
self.curve.miller_loop_engine.exp_miller_loop,
5475
)
5576

5677
return PreparedVerifyingKey(
@@ -66,25 +87,22 @@ def prepare(self):
6687
def prepare_for_zkscript(
6788
self, prepared_vk: PreparedVerifyingKey | None = None
6889
) -> ZkScriptVerifyingKey:
90+
"""Turn an instance of `Self` into an instance of `ZKScriptVerifyingKey`."""
6991
prepared_vk = prepared_vk if prepared_vk is not None else self.prepare()
7092

7193
return ZkScriptVerifyingKey(
7294
prepared_vk.alpha_beta.to_list(),
7395
prepared_vk.minus_gamma.to_list(),
7496
prepared_vk.minus_delta.to_list(),
7597
[point.to_list() for point in self.gamma_abc],
76-
[
77-
list(map(lambda s: s.to_list(), gradient))
78-
for gradient in prepared_vk.gradients_minus_gamma
79-
],
80-
[
81-
list(map(lambda s: s.to_list(), gradient))
82-
for gradient in prepared_vk.gradients_minus_delta
83-
],
98+
prepared_vk.gradients_minus_gamma.as_data(),
99+
prepared_vk.gradients_minus_delta.as_data(),
84100
)
85101

86102

87103
class VerifyingKeyGeneric:
104+
"""Generic verifying key class encapsulating the curve over which Groth16 proof is instantiated."""
105+
88106
def __init__(self, curve: BilinearPairingCurve):
89107
self.curve = curve
90108
return

0 commit comments

Comments
 (0)