Skip to content

Commit d0ad61f

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

File tree

4 files changed

+274
-1
lines changed

4 files changed

+274
-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

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