Skip to content

Commit f582820

Browse files
Restructuring (#1)
Changes include: * Split fields and curves into two classes: a class for the type, and one for the elements of that type * Define data structures for Proof and Verifying keys * Restructure interface for pairing friendly curves (now it is `BilinearPairingCurve` has a `PairingEngine`, which has a `MillerLoopEngine`) * Add helper functions to prepare the data to be fed to [zkscript](https://github.com/nchain-innovation/zkscript_package) * Add .gitignore
1 parent 5539f96 commit f582820

File tree

25 files changed

+3642
-1663
lines changed

25 files changed

+3642
-1663
lines changed

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
### macOS
2+
.DS_Store
3+
4+
### Pythom
5+
# Byte-compiled / optimized / DLL files
6+
*.py[cod]
7+
*$py.class
8+
**/*.pyc
9+
10+
# Distribution / packaging
11+
*.egg-info/
12+
dist/

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
# Elliptic_curves
22

33
Python library with implementations of:
4-
- [Finite fields](./docs/fields.md) (prime fields: `Fq`, quadratic extensions: `QuadraticExtension`, and cubic extensions: `CubicExtension`)
5-
- [Elliptic curves](./docs/elliptic_curves.md) in Short-Weierstrass form: `EllipticCurve` and `ElliptiCurveProjective`
4+
- [Finite fields](./docs/fields.md) (prime fields: `PrimeField`, quadratic extensions: `QuadraticExtension`, and cubic extensions: `CubicExtension`)
5+
- [Elliptic curves](./docs/elliptic_curves.md) in Short-Weierstrass form: `ShortWeierstrassEllipticCurve`
66
- [Bilinear pairings](./docs/bilinear_pairings.md): `BilinearPairingCurve`
77

8-
## Instantiations currenly implemented:
8+
The structure of the library follows in part that of the [Arkworks](https://github.com/arkworks-rs) library.
9+
10+
## Instantiations currently implemented:
911

1012
The library currently contains instantiations of the following curves:
1113
- BLS12_381

elliptic_curves/data_structures/__init__.py

Whitespace-only changes.
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
from elliptic_curves.models.types import G1Point, G2Point
2+
from elliptic_curves.models.bilinear_pairings import BilinearPairingCurve
3+
from elliptic_curves.data_structures.vk import PreparedVerifyingKey
4+
from elliptic_curves.data_structures.zkscript import ZkScriptProof
5+
6+
7+
class PreparedProof:
8+
def __init__(
9+
self,
10+
proof,
11+
curve: BilinearPairingCurve,
12+
public_statements: list[int],
13+
gradients_b,
14+
gradients_minus_gamma,
15+
gradients_minus_delta,
16+
inverse_miller_loop,
17+
gradients_msm,
18+
gradients_public_statements,
19+
):
20+
self.proof = proof
21+
self.curve = curve
22+
self.public_statements = public_statements
23+
self.gradients_b = gradients_b
24+
self.gradients_minus_gamma = gradients_minus_gamma
25+
self.gradients_minus_delta = gradients_minus_delta
26+
self.inverse_miller_loop = inverse_miller_loop
27+
self.gradients_msm = gradients_msm
28+
self.gradients_public_statements = gradients_public_statements
29+
return
30+
31+
32+
class Proof:
33+
def __init__(self, curve: BilinearPairingCurve, a: G1Point, b: G2Point, c: G1Point):
34+
self.curve = curve
35+
self.a = a
36+
self.b = b
37+
self.c = c
38+
39+
def prepare(
40+
self, prepared_vk: PreparedVerifyingKey, public_statements: list[int]
41+
) -> PreparedProof:
42+
public_statements_extended = [1, *public_statements]
43+
44+
# Compute \sum_(i=0)^l a_i * gamma_abc[i]
45+
n_pub = len(public_statements)
46+
assert (
47+
len(prepared_vk.vk.gamma_abc) == n_pub + 1
48+
), "Wrong number of public inputs"
49+
50+
sum_gamma_abc = prepared_vk.vk.gamma_abc[0]
51+
for i in range(1, n_pub + 1):
52+
sum_gamma_abc += prepared_vk.vk.gamma_abc[i].multiply(
53+
public_statements_extended[i]
54+
)
55+
56+
# Gradients for the pairing
57+
gradients_b = self.b.gradients(self.curve.miller_loop_engine.exp_miller_loop)
58+
59+
# Inverse of the Miller loop output
60+
inverse_miller_loop = self.curve.miller_loop(
61+
[self.a, sum_gamma_abc, self.c],
62+
[self.b, prepared_vk.minus_gamma, prepared_vk.minus_delta],
63+
).invert()
64+
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+
)
99+
100+
return PreparedProof(
101+
self,
102+
self.curve,
103+
public_statements,
104+
gradients_b,
105+
prepared_vk.gradients_minus_delta,
106+
prepared_vk.gradients_minus_delta,
107+
inverse_miller_loop,
108+
gradients_msm,
109+
gradients_public_statements,
110+
)
111+
112+
def prepare_for_zkscript(
113+
self,
114+
prepared_vk: PreparedVerifyingKey,
115+
public_statements: list[int],
116+
prepared_proof: PreparedProof | None = None,
117+
) -> ZkScriptProof:
118+
prepared_proof = (
119+
prepared_proof
120+
if prepared_proof is not None
121+
else self.prepare(prepared_vk, public_statements)
122+
)
123+
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([])
142+
143+
return ZkScriptProof(
144+
self.a.to_list(),
145+
self.b.to_list(),
146+
self.c.to_list(),
147+
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+
],
160+
prepared_proof.inverse_miller_loop.to_list(),
161+
gradients_msm,
162+
gradients_public_statements,
163+
)
164+
165+
166+
class ProofGeneric:
167+
def __init__(self, curve: BilinearPairingCurve):
168+
self.curve = curve
169+
170+
def __call__(self, a, b, c):
171+
return Proof(self.curve, a, b, c)
172+
173+
def deserialise(self, serialised: list[bytes]) -> Proof:
174+
"""Function to deserialise a proof.
175+
176+
This function is based on arkworks unchecked deserialisation of a proof. [https://github.com/arkworks-rs/groth16/blob/master/src/data_structures.rs#L9]
177+
178+
A proof is formed by: A, B, C, and each element is serialised in turn
179+
A, C -> elements in G1
180+
B -> element in G2
181+
"""
182+
length_g1 = (
183+
(self.curve.g1_curve.a.get_modulus().bit_length() + 8)
184+
// 8
185+
* self.curve.g1_field.get_extension_degree_over_prime_field()
186+
)
187+
length_g2 = (
188+
(self.curve.g2_curve.a.get_modulus().bit_length() + 8)
189+
// 8
190+
* self.curve.g2_field.get_extension_degree_over_prime_field()
191+
)
192+
193+
index = 0
194+
a = self.curve.g1_curve.deserialise(
195+
serialised[: index + 2 * length_g1], self.curve.g1_field
196+
)
197+
index += 2 * length_g1
198+
b = self.curve.g2_curve.deserialise(
199+
serialised[index : index + 2 * length_g2], self.curve.g2_field
200+
)
201+
index += 2 * length_g2
202+
c = self.curve.g1_curve.deserialise(
203+
serialised[index : index + 2 * length_g1], self.curve.g1_field
204+
)
205+
206+
return Proof(
207+
self.curve,
208+
a,
209+
b,
210+
c,
211+
)
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
from elliptic_curves.models.types import G1Point, G2Point
2+
from elliptic_curves.models.bilinear_pairings import BilinearPairingCurve
3+
from elliptic_curves.data_structures.zkscript import ZkScriptVerifyingKey
4+
5+
6+
class PreparedVerifyingKey:
7+
def __init__(
8+
self,
9+
vk,
10+
curve: BilinearPairingCurve,
11+
alpha_beta,
12+
minus_gamma,
13+
minus_delta,
14+
gradients_minus_gamma,
15+
gradients_minus_delta,
16+
):
17+
self.vk = vk
18+
self.curve = curve
19+
self.alpha_beta = alpha_beta
20+
self.minus_gamma = minus_gamma
21+
self.minus_delta = minus_delta
22+
self.gradients_minus_gamma = gradients_minus_gamma
23+
self.gradients_minus_delta = gradients_minus_delta
24+
return
25+
26+
27+
class VerifyingKey:
28+
def __init__(
29+
self,
30+
curve: BilinearPairingCurve,
31+
alpha: G1Point,
32+
beta: G2Point,
33+
gamma: G2Point,
34+
delta: G2Point,
35+
gamma_abc: list[G1Point],
36+
):
37+
self.curve = curve
38+
self.alpha = alpha
39+
self.beta = beta
40+
self.gamma = gamma
41+
self.delta = delta
42+
self.gamma_abc = gamma_abc
43+
return
44+
45+
def prepare(self):
46+
alpha_beta = self.curve.pairing([self.alpha], [self.beta])
47+
minus_gamma = -self.gamma
48+
minus_delta = -self.delta
49+
gradients_minus_gamma = minus_gamma.gradients(
50+
self.curve.miller_loop_engine.exp_miller_loop
51+
)
52+
gradients_minus_delta = minus_delta.gradients(
53+
self.curve.miller_loop_engine.exp_miller_loop
54+
)
55+
56+
return PreparedVerifyingKey(
57+
self,
58+
self.curve,
59+
alpha_beta,
60+
minus_gamma,
61+
minus_delta,
62+
gradients_minus_gamma,
63+
gradients_minus_delta,
64+
)
65+
66+
def prepare_for_zkscript(
67+
self, prepared_vk: PreparedVerifyingKey | None = None
68+
) -> ZkScriptVerifyingKey:
69+
prepared_vk = prepared_vk if prepared_vk is not None else self.prepare()
70+
71+
return ZkScriptVerifyingKey(
72+
prepared_vk.alpha_beta.to_list(),
73+
prepared_vk.minus_gamma.to_list(),
74+
prepared_vk.minus_delta.to_list(),
75+
[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+
],
84+
)
85+
86+
87+
class VerifyingKeyGeneric:
88+
def __init__(self, curve: BilinearPairingCurve):
89+
self.curve = curve
90+
return
91+
92+
def __call__(
93+
self,
94+
alpha: G1Point,
95+
beta: G2Point,
96+
gamma: G2Point,
97+
delta: G2Point,
98+
gamma_abc: list[G1Point],
99+
):
100+
return VerifyingKey(self.curve, alpha, beta, gamma, delta, gamma_abc)
101+
102+
def deserialise(self, serialised: list[bytes]) -> VerifyingKey:
103+
"""Deserialise a verifying key.
104+
105+
This function is based on the deserialisation of VK in arkworks. [https://github.com/arkworks-rs/groth16/blob/master/src/data_structures.rs#L32]
106+
107+
vk is a list of: alpha_g1, beta_g2, gamma_g2, delta_g2, gamma_abc_g1, and each element is serialised in turn
108+
alpha_g1 -> element in G1
109+
beta_g2, gamma_g2, delta_g2 -> elements in G2
110+
gamma_abc_g1 -> list of elements in G1 (as it is a vector, is prepended with the length of the list, encoded as an 8-byte little-endian number )
111+
"""
112+
length_g1 = (
113+
(self.curve.g1_curve.a.get_modulus().bit_length() + 8)
114+
// 8
115+
* self.curve.g1_field.get_extension_degree_over_prime_field()
116+
)
117+
length_g2 = (
118+
(self.curve.g2_curve.a.get_modulus().bit_length() + 8)
119+
// 8
120+
* self.curve.g2_field.get_extension_degree_over_prime_field()
121+
)
122+
123+
index = 0
124+
alpha = self.curve.g1_curve.deserialise(
125+
serialised[: index + 2 * length_g1], self.curve.g1_field
126+
)
127+
index += 2 * length_g1
128+
beta = self.curve.g2_curve.deserialise(
129+
serialised[index : index + 2 * length_g2], self.curve.g2_field
130+
)
131+
index += 2 * length_g2
132+
gamma = self.curve.g2_curve.deserialise(
133+
serialised[index : index + 2 * length_g2], self.curve.g2_field
134+
)
135+
index += 2 * length_g2
136+
delta = self.curve.g2_curve.deserialise(
137+
serialised[index : index + 2 * length_g2], self.curve.g2_field
138+
)
139+
index += 2 * length_g2
140+
141+
# Check correct length of gamma_abc
142+
n_abc = int.from_bytes(
143+
bytes=bytearray(serialised[index : index + 8]), byteorder="little"
144+
)
145+
index += 8
146+
147+
gamma_abc = []
148+
for _ in range(n_abc):
149+
gamma_abc.append(
150+
self.curve.g1_curve.deserialise(
151+
serialised[index : index + 2 * length_g1], self.curve.g1_field
152+
)
153+
)
154+
index += 2 * length_g1
155+
156+
assert index == len(serialised)
157+
return VerifyingKey(self.curve, alpha, beta, gamma, delta, gamma_abc)

0 commit comments

Comments
 (0)