Skip to content

Commit 9eb6238

Browse files
committed
Add implementations of some BLS precompiles
1 parent b069648 commit 9eb6238

File tree

4 files changed

+268
-1
lines changed

4 files changed

+268
-1
lines changed

eth/constants.py

+9
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,15 @@
100100
GAS_ECPAIRING_BASE = 100000
101101
GAS_ECPAIRING_PER_POINT = 80000
102102

103+
GAS_BLS_G1_ADD = 600
104+
GAS_BLS_G1_MUL = 12000
105+
GAS_BLS_G2_ADD = 4500
106+
GAS_BLS_G2_MUL = 55000
107+
GAS_BLS_PAIRING_BASE = 115000
108+
GAS_BLS_PAIRING_PER_PAIR = 115000
109+
GAS_BLS_MAP_FP_TO_G1 = 5500
110+
GAS_BLS_MAP_FP2_TO_G2 = 110000
111+
103112

104113
#
105114
# Gas Limit

eth/precompiles/__init__.py

+11
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,14 @@
77
from .ecmul import ecmul # noqa: F401
88
from .ecpairing import ecpairing # noqa: F401
99
from .blake2 import blake2b_fcompress # noqa: F401
10+
from .bls import ( # noqa: F401
11+
g1_add as bls_g1_add,
12+
g1_mul as bls_g1_mul,
13+
g1_multiexp as bls_g1_multiexp,
14+
g2_add as bls_g2_add,
15+
g2_mul as bls_g2_mul,
16+
g2_multiexp as bls_g2_multiexp,
17+
pairing as bls_pairing,
18+
map_fp_to_g1 as bls_map_fp_to_g1,
19+
map_fp2_to_g2 as bls_map_fp2_to_g2,
20+
)

eth/precompiles/bls.py

+226
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
from typing import Tuple
2+
3+
from eth_utils import (
4+
ValidationError,
5+
big_endian_to_int,
6+
)
7+
from py_ecc import (
8+
optimized_bls12_381 as bls12_381,
9+
bls
10+
)
11+
12+
from eth import constants
13+
from eth.exceptions import (
14+
VMError,
15+
)
16+
17+
from eth.vm.computation import (
18+
BaseComputation,
19+
)
20+
21+
22+
FP2_SIZE_IN_BYTES = 128
23+
G1_SIZE_IN_BYTES = 128
24+
G2_SIZE_IN_BYTES = 256
25+
26+
G1Point = Tuple[bls12_381.FQ, bls12_381.FQ]
27+
G2Point = Tuple[bls12_381.FQ2, bls12_381.FQ2]
28+
29+
30+
def _parse_g1_point(data: bytes) -> G1Point:
31+
if len(data) != G1_SIZE_IN_BYTES:
32+
raise ValidationError("invalid size of G1 input")
33+
34+
x = bls12_381.FQ(int.from_bytes(data[0:64], byteorder="big"))
35+
y = bls12_381.FQ(int.from_bytes(data[64:128], byteorder="big"))
36+
point = (x, y)
37+
38+
if not bls12_381.is_on_curve((x, y, bls12_381.FQ.one()), bls12_381.b):
39+
raise ValidationError("invalid G1 point not on curve")
40+
41+
return point
42+
43+
44+
def g1_add(computation: BaseComputation,
45+
gas_cost: int = constants.GAS_BLS_G1_ADD) -> BaseComputation:
46+
raise NotImplementedError()
47+
48+
49+
def g1_mul(computation: BaseComputation,
50+
gas_cost: int = constants.GAS_BLS_G1_MUL) -> BaseComputation:
51+
raise NotImplementedError()
52+
53+
54+
def g1_multiexp(computation: BaseComputation) -> BaseComputation:
55+
# NOTE: gas cost involves a discount based on the number of points involved
56+
# TODO load discount table and compute gas cost based on number of inputs
57+
raise NotImplementedError()
58+
59+
60+
def _parse_g2_point(data: bytes) -> G2Point:
61+
if len(data) != G2_SIZE_IN_BYTES:
62+
raise ValidationError("invalid size of G2 input")
63+
64+
x = bls12_381.FQ2(
65+
(
66+
int.from_bytes(data[0:64], byteorder="big"),
67+
int.from_bytes(data[64:128], byteorder="big")
68+
)
69+
)
70+
y = bls12_381.FQ2(
71+
(
72+
int.from_bytes(data[128:192], byteorder="big"),
73+
int.from_bytes(data[192:256], byteorder="big")
74+
)
75+
)
76+
point = (x, y)
77+
78+
if not bls12_381.is_on_curve((x, y, bls12_381.FQ2.one()), bls12_381.b2):
79+
raise ValidationError("invalid G2 point not on curve")
80+
81+
return point
82+
83+
84+
def _serialize_g2(result: G2Point) -> bytes:
85+
return b"".join(
86+
(
87+
result[0].coeffs[0].to_bytes(64, byteorder="big"),
88+
result[0].coeffs[1].to_bytes(64, byteorder="big"),
89+
result[1].coeffs[0].to_bytes(64, byteorder="big"),
90+
result[1].coeffs[1].to_bytes(64, byteorder="big"),
91+
)
92+
)
93+
94+
95+
def _g2_add(x: G2Point, y: G2Point) -> G2Point:
96+
result = bls12_381.add((x[0], x[1], bls12_381.FQ2.one()), (y[0], y[1], bls12_381.FQ2.one()))
97+
return bls12_381.normalize(result)
98+
99+
100+
def g2_add(computation: BaseComputation,
101+
gas_cost: int = constants.GAS_BLS_G2_ADD) -> BaseComputation:
102+
computation.consume_gas(gas_cost, reason='BLS_G2_ADD Precompile')
103+
104+
try:
105+
input_data = computation.msg.data_as_bytes
106+
x = _parse_g2_point(input_data[:G2_SIZE_IN_BYTES])
107+
y = _parse_g2_point(input_data[G2_SIZE_IN_BYTES:])
108+
result = _g2_add(x, y)
109+
except ValidationError:
110+
raise VMError("Invalid BLS_G2_ADD parameters")
111+
112+
computation.output = _serialize_g2(result)
113+
return computation
114+
115+
116+
def _g2_mul(x: G2Point, k: int) -> G2Point:
117+
result = bls12_381.multiply((x[0], x[1], bls12_381.FQ2.one()), k)
118+
return bls12_381.normalize(result)
119+
120+
121+
def _parse_scalar(data: bytes) -> int:
122+
if len(data) != 32:
123+
raise ValidationError("invalid size of scalar input")
124+
125+
return big_endian_to_int(data)
126+
127+
128+
def g2_mul(computation: BaseComputation,
129+
gas_cost: int = constants.GAS_BLS_G2_MUL) -> BaseComputation:
130+
computation.consume_gas(gas_cost, reason='BLS_G2_MUL Precompile')
131+
132+
try:
133+
input_data = computation.msg.data_as_bytes
134+
x = _parse_g2_point(input_data[:G2_SIZE_IN_BYTES])
135+
k = _parse_scalar(input_data[G2_SIZE_IN_BYTES:])
136+
result = _g2_mul(x, k)
137+
except ValidationError:
138+
raise VMError("Invalid BLS_G2_MUL parameters")
139+
140+
computation.output = _serialize_g2(result)
141+
return computation
142+
143+
144+
def g2_multiexp(computation: BaseComputation) -> BaseComputation:
145+
# NOTE: gas cost involves a discount based on the number of points involved
146+
# TODO load discount table and compute gas cost based on number of inputs
147+
raise NotImplementedError()
148+
149+
150+
def _pairing(input_data: bytes) -> bool:
151+
field_element = bls12_381.FQ12.one()
152+
g1_to_g2_offset = G1_SIZE_IN_BYTES + G2_SIZE_IN_BYTES
153+
for next_index in range(0, len(input_data), 384):
154+
p = _parse_g1_point(input_data[next_index:next_index + G1_SIZE_IN_BYTES])
155+
156+
q = _parse_g2_point(
157+
input_data[next_index + G1_SIZE_IN_BYTES:next_index + g1_to_g2_offset]
158+
)
159+
projective_p = (p[0], p[1], bls12_381.FQ.one())
160+
projective_q = (q[0], q[1], bls12_381.FQ2.one())
161+
field_element *= bls12_381.pairing(projective_q, projective_p, final_exponentiate=False)
162+
163+
return bls12_381.final_exponentiate(field_element) == bls12_381.FQ12.one()
164+
165+
166+
def _serialize_boolean(value: bool) -> bytes:
167+
return int(value).to_bytes(32, byteorder="big")
168+
169+
170+
def pairing(computation: BaseComputation,
171+
gas_cost_base: int = constants.GAS_BLS_PAIRING_BASE,
172+
gas_cost_per_pair: int = constants.GAS_BLS_PAIRING_PER_PAIR) -> BaseComputation:
173+
input_data = computation.msg.data_as_bytes
174+
if len(input_data) % 384:
175+
# data length must be an exact multiple of 384
176+
raise VMError("Invalid BLS_PAIRING parameters")
177+
178+
num_points = len(input_data) // 384
179+
gas_cost = gas_cost_base + num_points * gas_cost_per_pair
180+
181+
computation.consume_gas(gas_cost, reason='BLS_PAIRING Precompile')
182+
183+
try:
184+
result = _pairing(input_data)
185+
except ValidationError:
186+
raise VMError("Invalid BLS_PAIRING parameters")
187+
188+
computation.output = _serialize_boolean(result)
189+
return computation
190+
191+
192+
def map_fp_to_g1(computation: BaseComputation,
193+
gas_cost: int = constants.GAS_BLS_MAP_FP_TO_G1) -> BaseComputation:
194+
raise NotImplementedError()
195+
196+
197+
def _parse_fp2_element(data: bytes) -> bls12_381.FQ2:
198+
if len(data) != FP2_SIZE_IN_BYTES:
199+
raise ValidationError("invalid size of FP2 input")
200+
201+
return bls12_381.FQ2(
202+
(
203+
int.from_bytes(data[:64], byteorder="big"),
204+
int.from_bytes(data[64:], byteorder="big")
205+
)
206+
)
207+
208+
209+
def _map_fp2_to_g2(x: bls12_381.FQ2) -> G2Point:
210+
point = bls.hash_to_curve.map_to_curve_G2(x)
211+
return bls12_381.normalize(point)
212+
213+
214+
def map_fp2_to_g2(computation: BaseComputation,
215+
gas_cost: int = constants.GAS_BLS_MAP_FP2_TO_G2) -> BaseComputation:
216+
computation.consume_gas(gas_cost, reason='BLS_MAP_FP2_TO_G2 Precompile')
217+
218+
try:
219+
input_data = computation.msg.data_as_bytes
220+
x = _parse_fp2_element(input_data[:FP2_SIZE_IN_BYTES])
221+
result = _map_fp2_to_g2(x)
222+
except ValidationError:
223+
raise VMError("Invalid BLS_MAP_FP2_TO_G2 parameters")
224+
225+
computation.output = _serialize_g2(result)
226+
return computation

eth/vm/forks/berlin/computation.py

+22-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
from eth_utils.toolz import (
2+
merge,
3+
)
4+
5+
from eth import precompiles
6+
from eth._utils.address import (
7+
force_bytes_to_address,
8+
)
19
from eth.vm.forks.muir_glacier.computation import (
210
MUIR_GLACIER_PRECOMPILES
311
)
@@ -7,7 +15,20 @@
715

816
from .opcodes import BERLIN_OPCODES
917

10-
BERLIN_PRECOMPILES = MUIR_GLACIER_PRECOMPILES
18+
BERLIN_PRECOMPILES = merge(
19+
MUIR_GLACIER_PRECOMPILES,
20+
{
21+
force_bytes_to_address(b'\x0a'): precompiles.bls_g1_add,
22+
force_bytes_to_address(b'\x0b'): precompiles.bls_g1_mul,
23+
force_bytes_to_address(b'\x0c'): precompiles.bls_g1_multiexp,
24+
force_bytes_to_address(b'\x0d'): precompiles.bls_g2_add,
25+
force_bytes_to_address(b'\x0e'): precompiles.bls_g2_mul,
26+
force_bytes_to_address(b'\x0f'): precompiles.bls_g2_multiexp,
27+
force_bytes_to_address(b'\x10'): precompiles.bls_pairing,
28+
force_bytes_to_address(b'\x11'): precompiles.bls_map_fp_to_g1,
29+
force_bytes_to_address(b'\x12'): precompiles.bls_map_fp2_to_g2,
30+
}
31+
)
1132

1233

1334
class BerlinComputation(MuirGlacierComputation):

0 commit comments

Comments
 (0)