Skip to content

feat: Prague precompiles #1462

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions cairo/ethereum/prague/vm/gas.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ namespace GasConstants {
const GAS_PER_BLOB = 2 ** 17;
const MIN_BLOB_GASPRICE = 1;
const BLOB_BASE_FEE_UPDATE_FRACTION = 3338477;

const GAS_BLS_G1_ADD = 375;
const GAS_BLS_G1_MUL = 12000;
const GAS_BLS_G1_MAP = 5500;
const GAS_BLS_G2_ADD = 600;
const GAS_BLS_G2_MUL = 22500;
const GAS_BLS_G2_MAP = 23800;
}

struct ExtendMemory {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//! Important: the implementations of these precompiles is unsound.
//! TODO: Add rust implementations for the hints.

from starkware.cairo.common.cairo_builtins import BitwiseBuiltin, ModBuiltin, PoseidonBuiltin
from ethereum.prague.vm.evm_impl import Evm, EvmImpl
from ethereum.exceptions import EthereumException
from ethereum.prague.vm.exceptions import InvalidParameter
from ethereum.utils.numeric import divmod
from ethereum.prague.vm.gas import GasConstants, charge_gas
from ethereum_types.numeric import Uint
from ethereum_types.bytes import Bytes
from cairo_core.comparison import is_zero, is_not_zero

// @notice The bls12_381 G1 point addition precompile.
// @dev The implementation is unsound.
func bls12_g1_add{
range_check_ptr,
bitwise_ptr: BitwiseBuiltin*,
keccak_ptr: felt*,
poseidon_ptr: PoseidonBuiltin*,
range_check96_ptr: felt*,
add_mod_ptr: ModBuiltin*,
mul_mod_ptr: ModBuiltin*,
evm: Evm,
}() -> EthereumException* {
alloc_locals;
let data = evm.value.message.value.data;

if (data.value.len != 256) {
tempvar err = new EthereumException(InvalidParameter);
return err;
}

// gas
let err = charge_gas(Uint(GasConstants.GAS_BLS_G1_ADD));
if (cast(err, felt) != 0) {
return err;
}
// Operation
tempvar data = data;
tempvar error: EthereumException*;
tempvar output: Bytes;
%{ bls12_g1_add_hint %}
if (cast(error, felt) != 0) {
return error;
}

EvmImpl.set_output(output);
tempvar ok = cast(0, EthereumException*);
return ok;
}

// @notice The bls12_381 G1 multi-scalar multiplication precompile.
// @dev The implementation is unsound.
func bls12_g1_msm{
range_check_ptr,
bitwise_ptr: BitwiseBuiltin*,
keccak_ptr: felt*,
poseidon_ptr: PoseidonBuiltin*,
range_check96_ptr: felt*,
add_mod_ptr: ModBuiltin*,
mul_mod_ptr: ModBuiltin*,
evm: Evm,
}() -> EthereumException* {
alloc_locals;

let data = evm.value.message.value.data;
let (q, r) = divmod(data.value.len, 160);
let data_multiple_of_160 = is_not_zero(r);
let data_is_zero = is_zero(data.value.len);
let invalid_valid_input = data_multiple_of_160 + data_is_zero;

if (invalid_valid_input != 0) {
tempvar err = new EthereumException(InvalidParameter);
return err;
}

tempvar data = data;
tempvar gas;
%{ bls12_g1_msm_gas_hint %}
let err = charge_gas(Uint(gas));
if (cast(err, felt) != 0) {
return err;
}

tempvar data = evm.value.message.value.data;
tempvar error: EthereumException*;
tempvar output: Bytes;

%{ bls12_g1_msm_hint %}
if (cast(error, felt) != 0) {
return error;
}

EvmImpl.set_output(output);
tempvar ok = cast(0, EthereumException*);
return ok;
}

// @notice Precompile to map field element to G1.
// @dev The implementation is unsound.
func bls12_map_fp_to_g1{
range_check_ptr,
bitwise_ptr: BitwiseBuiltin*,
keccak_ptr: felt*,
poseidon_ptr: PoseidonBuiltin*,
evm: Evm,
}() -> EthereumException* {
alloc_locals;

let data = evm.value.message.value.data;
if (data.value.len != 64) {
tempvar err = new EthereumException(InvalidParameter);
return err;
}

// gas
let err = charge_gas(Uint(GasConstants.GAS_BLS_G1_MAP));
if (cast(err, felt) != 0) {
return err;
}

// operation
tempvar data = data;
tempvar error: EthereumException*;
tempvar output: Bytes;

%{ bls12_map_fp_to_g1_hint %}
if (cast(error, felt) != 0) {
return error;
}

EvmImpl.set_output(output);
tempvar ok = cast(0, EthereumException*);
return ok;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//! Important: the implementations of these precompiles is unsound.
//! TODO: Add rust implementations for the hints.

from starkware.cairo.common.cairo_builtins import BitwiseBuiltin, ModBuiltin, PoseidonBuiltin
from ethereum.prague.vm.evm_impl import Evm, EvmImpl
from ethereum.exceptions import EthereumException
from ethereum.prague.vm.exceptions import InvalidParameter
from ethereum.utils.numeric import divmod
from ethereum.prague.vm.gas import GasConstants, charge_gas
from ethereum_types.numeric import Uint
from ethereum_types.bytes import Bytes
from cairo_core.comparison import is_zero, is_not_zero

// @notice The bls12_381 G2 point addition precompile.
// @dev The implementation is unsound.
func bls12_g2_add{
range_check_ptr,
bitwise_ptr: BitwiseBuiltin*,
keccak_ptr: felt*,
poseidon_ptr: PoseidonBuiltin*,
range_check96_ptr: felt*,
add_mod_ptr: ModBuiltin*,
mul_mod_ptr: ModBuiltin*,
evm: Evm,
}() -> EthereumException* {
alloc_locals;
let data = evm.value.message.value.data;

if (data.value.len != 512) {
tempvar err = new EthereumException(InvalidParameter);
return err;
}

// gas
let err = charge_gas(Uint(GasConstants.GAS_BLS_G2_ADD));
if (cast(err, felt) != 0) {
return err;
}
// Operation
tempvar data = data;
tempvar error: EthereumException*;
tempvar output: Bytes;
%{ bls12_g2_add_hint %}
if (cast(error, felt) != 0) {
return error;
}

EvmImpl.set_output(output);
tempvar ok = cast(0, EthereumException*);
return ok;
}

// @notice The bls12_381 G2 multi-scalar multiplication precompile.
// @dev The implementation is unsound.
func bls12_g2_msm{
range_check_ptr,
bitwise_ptr: BitwiseBuiltin*,
keccak_ptr: felt*,
poseidon_ptr: PoseidonBuiltin*,
range_check96_ptr: felt*,
add_mod_ptr: ModBuiltin*,
mul_mod_ptr: ModBuiltin*,
evm: Evm,
}() -> EthereumException* {
alloc_locals;

let data = evm.value.message.value.data;
let (q, r) = divmod(data.value.len, 288);
let data_multiple_of_288 = is_not_zero(r);
let data_is_zero = is_zero(data.value.len);
let invalid_valid_input = data_multiple_of_288 + data_is_zero;

if (invalid_valid_input != 0) {
tempvar err = new EthereumException(InvalidParameter);
return err;
}

tempvar data = data;
tempvar gas;
%{ bls12_g2_msm_gas_hint %}
let err = charge_gas(Uint(gas));
if (cast(err, felt) != 0) {
return err;
}

tempvar data = evm.value.message.value.data;
tempvar error: EthereumException*;
tempvar output: Bytes;

%{ bls12_g2_msm_hint %}
if (cast(error, felt) != 0) {
return error;
}

EvmImpl.set_output(output);
tempvar ok = cast(0, EthereumException*);
return ok;
}

// @notice Precompile to map field element to G2.
// @dev The implementation is unsound.
func bls12_map_fp2_to_g2{
range_check_ptr,
bitwise_ptr: BitwiseBuiltin*,
keccak_ptr: felt*,
poseidon_ptr: PoseidonBuiltin*,
evm: Evm,
}() -> EthereumException* {
alloc_locals;

let data = evm.value.message.value.data;
if (data.value.len != 64) {
tempvar err = new EthereumException(InvalidParameter);
return err;
}

// gas
let err = charge_gas(Uint(GasConstants.GAS_BLS_G2_MAP));
if (cast(err, felt) != 0) {
return err;
}

// operation
tempvar data = data;
tempvar error: EthereumException*;
tempvar output: Bytes;

%{ bls12_map_fp2_to_g2_hint %}
if (cast(error, felt) != 0) {
return error;
}

EvmImpl.set_output(output);
tempvar ok = cast(0, EthereumException*);
return ok;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//! Important: the implementations of these precompiles is unsound.
//TODO: Add rust implementations for the hints.

from starkware.cairo.common.cairo_builtins import BitwiseBuiltin, ModBuiltin, PoseidonBuiltin
from ethereum.prague.vm.evm_impl import Evm, EvmImpl
from ethereum.exceptions import EthereumException
from ethereum.prague.vm.exceptions import InvalidParameter
from ethereum.utils.numeric import divmod
from ethereum.prague.vm.gas import charge_gas
from ethereum_types.numeric import Uint
from ethereum_types.bytes import Bytes
from cairo_core.comparison import is_zero, is_not_zero

// @notice The bls12_381 pairing precompile.
// @dev The implementation is unsound.
func bls12_pairing{
range_check_ptr,
bitwise_ptr: BitwiseBuiltin*,
keccak_ptr: felt*,
poseidon_ptr: PoseidonBuiltin*,
range_check96_ptr: felt*,
add_mod_ptr: ModBuiltin*,
mul_mod_ptr: ModBuiltin*,
evm: Evm,
}() -> EthereumException* {
alloc_locals;
let data = evm.value.message.value.data;
let (q, r) = divmod(data.value.len, 384);
let data_multiple_of_384 = is_not_zero(r);
let data_is_zero = is_zero(data.value.len);
let invalid_valid_input = data_multiple_of_384 + data_is_zero;

if (invalid_valid_input != 0) {
tempvar err = new EthereumException(InvalidParameter);
return err;
}

// GAS
let gas_cost = Uint(32600 * q + 37700);
let err = charge_gas(gas_cost);
if (cast(err, felt) != 0) {
return err;
}

// OPERATION
tempvar data = evm.value.message.value.data;
tempvar error: EthereumException*;
tempvar output: Bytes;

%{ bls12_pairing_hint %}
if (cast(error, felt) != 0) {
return error;
}

EvmImpl.set_output(output);
tempvar ok = cast(0, EthereumException*);
return ok;
}
29 changes: 27 additions & 2 deletions cairo/ethereum/prague/vm/precompiled_contracts/mapping.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,20 @@ from ethereum.prague.vm.precompiled_contracts.ecrecover import ecrecover
from ethereum.prague.vm.precompiled_contracts.blake2f import blake2f
from ethereum.prague.vm.precompiled_contracts.point_evaluation import point_evaluation
from ethereum.prague.vm.precompiled_contracts.ripemd160 import ripemd160
from ethereum.prague.vm.precompiled_contracts.bls12_381.bls12_381_g1 import (
bls12_g1_add,
bls12_g1_msm,
bls12_map_fp_to_g1,
)
from ethereum.prague.vm.precompiled_contracts.bls12_381.bls12_381_g2 import (
bls12_g2_add,
bls12_g2_msm,
bls12_map_fp2_to_g2,
)
from ethereum.prague.vm.precompiled_contracts.bls12_381.bls12_381_pairing import bls12_pairing
from cairo_core.control_flow import raise
// currently 10 precompiles.
const N_PRECOMPILES = 10;
// currently 17 precompiles.
const N_PRECOMPILES = 17;
const HIGHEST_PRECOMPILE_LEADING_BYTE = 0x0a;

// count 3 steps per index: precompile_address, call, precompile_fn
Expand Down Expand Up @@ -104,6 +115,20 @@ func precompile_table_lookup{range_check_ptr}(address: felt) -> (felt, felt) {
call blake2f; // BLAKE2F
dw 0xa00000000000000000000000000000000000000;
call point_evaluation; // POINT_EVALUATION
dw 0xb00000000000000000000000000000000000000;
call bls12_g1_add; // BLS12_G1ADD
dw 0xc00000000000000000000000000000000000000;
call bls12_g1_msm; // BLS12_G1MSM
dw 0xd00000000000000000000000000000000000000;
call bls12_g2_add; // BLS12_G2ADD
dw 0xe00000000000000000000000000000000000000;
call bls12_g2_msm; // BLS12_G2MSM
dw 0xf00000000000000000000000000000000000000;
call bls12_pairing; // BLS12_PAIRING_CHECK
dw 0x100000000000000000000000000000000000000;
call bls12_map_fp_to_g1; // BLS12_MAP_FP_TO_G1
dw 0x110000000000000000000000000000000000000;
call bls12_map_fp2_to_g2; // BLS12_MAP_FP2_TO_G2
// not reached.
ret;
}
Expand Down
Loading
Loading