Skip to content

Commit a6cacd1

Browse files
committed
feat: bls12_pairing
1 parent 61a8c03 commit a6cacd1

File tree

3 files changed

+154
-0
lines changed

3 files changed

+154
-0
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from starkware.cairo.common.cairo_builtins import BitwiseBuiltin, ModBuiltin, PoseidonBuiltin
2+
from ethereum.prague.vm.evm_impl import Evm, EvmImpl
3+
from ethereum.exceptions import EthereumException
4+
from ethereum.prague.vm.exceptions import InvalidParameter
5+
from ethereum.utils.numeric import divmod
6+
from ethereum.prague.vm.gas import charge_gas
7+
from ethereum_types.numeric import Uint
8+
from ethereum_types.bytes import Bytes
9+
from cairo_core.comparison import is_zero, is_not_zero
10+
11+
func bls12_pairing{
12+
range_check_ptr,
13+
bitwise_ptr: BitwiseBuiltin*,
14+
keccak_ptr: felt*,
15+
poseidon_ptr: PoseidonBuiltin*,
16+
range_check96_ptr: felt*,
17+
add_mod_ptr: ModBuiltin*,
18+
mul_mod_ptr: ModBuiltin*,
19+
evm: Evm,
20+
}() -> EthereumException* {
21+
alloc_locals;
22+
let data = evm.value.message.value.data;
23+
let (q, r) = divmod(data.value.len, 384);
24+
let data_multiple_of_384 = is_not_zero(r);
25+
let data_is_zero = is_zero(data.value.len);
26+
let invalid_valid_input = data_multiple_of_384 + data_is_zero;
27+
28+
if (invalid_valid_input != 0) {
29+
tempvar err = new EthereumException(InvalidParameter);
30+
return err;
31+
}
32+
33+
// GAS
34+
let gas_cost = Uint(32600 * q + 37700);
35+
let err = charge_gas(gas_cost);
36+
if (cast(err, felt) != 0) {
37+
return err;
38+
}
39+
40+
// OPERATION
41+
tempvar data = evm.value.message.value.data;
42+
tempvar error: EthereumException*;
43+
tempvar output: Bytes;
44+
45+
%{ bls12_pairing_hint %}
46+
if (cast(error, felt) != 0) {
47+
return error;
48+
}
49+
50+
EvmImpl.set_output(output);
51+
tempvar ok = cast(0, EthereumException*);
52+
return ok;
53+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from ethereum.prague.vm import Evm
2+
from ethereum.prague.vm.precompiled_contracts.bls12_381.bls12_381_g1 import G1_to_bytes
3+
from ethereum.prague.vm.precompiled_contracts.bls12_381.bls12_381_g2 import G2_to_bytes
4+
from ethereum.prague.vm.precompiled_contracts.bls12_381.bls12_381_pairing import (
5+
bls12_pairing,
6+
)
7+
from ethereum_types.bytes import Bytes
8+
from hypothesis import given
9+
from hypothesis import strategies as st
10+
from py_ecc.fields import optimized_bls12_381_FQ2 as FQ2
11+
from py_ecc.optimized_bls12_381.optimized_curve import Z1, Z2
12+
13+
from cairo_addons.testing.errors import strict_raises
14+
from tests.utils.evm_builder import EvmBuilder
15+
from tests.utils.strategies import blsp2_strategy, blsp_strategy
16+
17+
18+
@st.composite
19+
def bls12_381_pairing_data(draw):
20+
num_points = draw(st.integers(min_value=1, max_value=10))
21+
g1_points = draw(st.lists(blsp_strategy, min_size=num_points, max_size=num_points))
22+
g2_points = draw(st.lists(blsp2_strategy, min_size=num_points, max_size=num_points))
23+
result = Bytes(b"")
24+
for i in range(num_points):
25+
assert g1_points[i][2] == 0 if g1_points[i] == Z1 else g1_points[i][2] == 1
26+
assert (
27+
g2_points[i][2] == FQ2.zero()
28+
if g2_points[i] == Z2
29+
else g2_points[i][2] == FQ2.one()
30+
)
31+
result += G1_to_bytes(g1_points[i][:2]) + G2_to_bytes(g2_points[i][:2])
32+
return result
33+
34+
35+
@given(
36+
evm=EvmBuilder().with_gas_left().with_message().build(),
37+
data=bls12_381_pairing_data(),
38+
)
39+
def test_bls12_381_pairing(cairo_run, evm: Evm, data: Bytes):
40+
evm.message.data = data
41+
try:
42+
evm_cairo = cairo_run("bls12_pairing", evm, data)
43+
except Exception as e:
44+
with strict_raises(type(e)):
45+
bls12_pairing(evm)
46+
return
47+
bls12_pairing(evm)
48+
assert evm_cairo == evm

python/cairo-addons/src/cairo_addons/hints/precompiles.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,3 +342,56 @@ def inner():
342342
write_error(memory, ap, segments, e)
343343

344344
inner()
345+
346+
347+
@register_hint
348+
def bls12_pairing_hint(
349+
ids: VmConsts,
350+
segments: MemorySegmentManager,
351+
memory: MemoryDict,
352+
ap: RelocatableValue,
353+
):
354+
from ethereum.prague.vm.exceptions import InvalidParameter
355+
from ethereum.prague.vm.memory import buffer_read
356+
from ethereum.prague.vm.precompiled_contracts.bls12_381.bls12_381_g1 import (
357+
bytes_to_G1,
358+
)
359+
from ethereum.prague.vm.precompiled_contracts.bls12_381.bls12_381_g2 import (
360+
bytes_to_G2,
361+
)
362+
from ethereum_types.numeric import U256, Uint
363+
from py_ecc.bls12_381.bls12_381_curve import FQ12, curve_order, multiply
364+
from py_ecc.bls12_381.bls12_381_pairing import pairing
365+
366+
from cairo_addons.hints.precompiles import write_error, write_output
367+
368+
def inner():
369+
data = bytes(
370+
[memory[ids.data.value.data + i] for i in range(ids.data.value.len)]
371+
)
372+
try:
373+
result = FQ12.one()
374+
k = len(data) // 384
375+
for i in range(k):
376+
g1_start = Uint(384 * i)
377+
g2_start = Uint(384 * i + 128)
378+
379+
g1_point = bytes_to_G1(buffer_read(data, U256(g1_start), U256(128)))
380+
if multiply(g1_point, curve_order) is not None:
381+
raise InvalidParameter("Sub-group check failed.")
382+
383+
g2_point = bytes_to_G2(buffer_read(data, U256(g2_start), U256(256)))
384+
if multiply(g2_point, curve_order) is not None:
385+
raise InvalidParameter("Sub-group check failed.")
386+
387+
result *= pairing(g2_point, g1_point)
388+
if result == FQ12.one():
389+
output = b"\x00" * 31 + b"\x01"
390+
else:
391+
output = b"\x00" * 32
392+
memory[ap - 2] = 0
393+
write_output(memory, ap, segments, output)
394+
except Exception as e:
395+
write_error(memory, ap, segments, e)
396+
397+
inner()

0 commit comments

Comments
 (0)