use alloc::{borrow::Cow, vec::Vec};
use bn::{AffineG1, AffineG2, Fq, Fq2, G1, G2, Group, Gt};
use evm::{
GasMutState,
interpreter::{ExitError, ExitException, ExitResult, ExitSucceed, utils::u256_to_h256},
};
use primitive_types::U256;
use crate::PurePrecompile;
pub struct Bn128AddByzantium;
impl<G: GasMutState> PurePrecompile<G> for Bn128AddByzantium {
fn execute(&self, input: &[u8], gasometer: &mut G) -> (ExitResult, Vec<u8>) {
const BYZANTIUM_ADD_GAS_COST: u64 = 500;
match run_add(input, BYZANTIUM_ADD_GAS_COST, gasometer) {
Ok((res, out)) => (Ok(res), out),
Err(err) => (Err(err), Vec::new()),
}
}
}
pub struct Bn128AddIstanbul;
impl<G: GasMutState> PurePrecompile<G> for Bn128AddIstanbul {
fn execute(&self, input: &[u8], gasometer: &mut G) -> (ExitResult, Vec<u8>) {
const ISTANBUL_ADD_GAS_COST: u64 = 150;
match run_add(input, ISTANBUL_ADD_GAS_COST, gasometer) {
Ok((res, out)) => (Ok(res), out),
Err(err) => (Err(err), Vec::new()),
}
}
}
pub struct Bn128MulByzantium;
impl<G: GasMutState> PurePrecompile<G> for Bn128MulByzantium {
fn execute(&self, input: &[u8], gasometer: &mut G) -> (ExitResult, Vec<u8>) {
const BYZANTIUM_MUL_GAS_COST: u64 = 40_000;
match run_mul(input, BYZANTIUM_MUL_GAS_COST, gasometer) {
Ok((res, out)) => (Ok(res), out),
Err(err) => (Err(err), Vec::new()),
}
}
}
pub struct Bn128MulIstanbul;
impl<G: GasMutState> PurePrecompile<G> for Bn128MulIstanbul {
fn execute(&self, input: &[u8], gasometer: &mut G) -> (ExitResult, Vec<u8>) {
const ISTANBUL_MUL_GAS_COST: u64 = 6_000;
match run_mul(input, ISTANBUL_MUL_GAS_COST, gasometer) {
Ok((res, out)) => (Ok(res), out),
Err(err) => (Err(err), Vec::new()),
}
}
}
pub struct Bn128PairingByzantium;
impl<G: GasMutState> PurePrecompile<G> for Bn128PairingByzantium {
fn execute(&self, input: &[u8], gasometer: &mut G) -> (ExitResult, Vec<u8>) {
const BYZANTIUM_PAIR_PER_POINT: u64 = 80_000;
const BYZANTIUM_PAIR_BASE: u64 = 100_000;
match run_pair(
input,
BYZANTIUM_PAIR_PER_POINT,
BYZANTIUM_PAIR_BASE,
gasometer,
) {
Ok((res, out)) => (Ok(res), out),
Err(err) => (Err(err), Vec::new()),
}
}
}
pub struct Bn128PairingIstanbul;
impl<G: GasMutState> PurePrecompile<G> for Bn128PairingIstanbul {
fn execute(&self, input: &[u8], gasometer: &mut G) -> (ExitResult, Vec<u8>) {
const ISTANBUL_PAIR_PER_POINT: u64 = 34_000;
const ISTANBUL_PAIR_BASE: u64 = 45_000;
match run_pair(
input,
ISTANBUL_PAIR_PER_POINT,
ISTANBUL_PAIR_BASE,
gasometer,
) {
Ok((res, out)) => (Ok(res), out),
Err(err) => (Err(err), Vec::new()),
}
}
}
const FQ_LEN: usize = 32;
const SCALAR_LEN: usize = 32;
const FQ2_LEN: usize = 2 * FQ_LEN;
const G1_LEN: usize = 2 * FQ_LEN;
const G2_LEN: usize = 2 * FQ2_LEN;
pub const ADD_INPUT_LEN: usize = 2 * G1_LEN;
pub const MUL_INPUT_LEN: usize = G1_LEN + SCALAR_LEN;
pub const PAIR_ELEMENT_LEN: usize = G1_LEN + G2_LEN;
pub fn run_add<G: GasMutState>(
input: &[u8],
gas_cost: u64,
gasometer: &mut G,
) -> Result<(ExitSucceed, Vec<u8>), ExitError> {
gasometer.record_gas(gas_cost.into())?;
let input = right_pad::<ADD_INPUT_LEN>(input);
let p1_bytes = &input[..G1_LEN];
let p2_bytes = &input[G1_LEN..];
let output = g1_point_add(p1_bytes, p2_bytes)?;
Ok((ExitSucceed::Returned, output.into()))
}
pub fn run_mul<G: GasMutState>(
input: &[u8],
gas_cost: u64,
gasometer: &mut G,
) -> Result<(ExitSucceed, Vec<u8>), ExitError> {
gasometer.record_gas(gas_cost.into())?;
let input = right_pad::<MUL_INPUT_LEN>(input);
let point_bytes = &input[..G1_LEN];
let scalar_bytes = &input[G1_LEN..G1_LEN + SCALAR_LEN];
let output = g1_point_mul(point_bytes, scalar_bytes)?;
Ok((ExitSucceed::Returned, output.into()))
}
pub fn run_pair<G: GasMutState>(
input: &[u8],
pair_per_point_cost: u64,
pair_base_cost: u64,
gasometer: &mut G,
) -> Result<(ExitSucceed, Vec<u8>), ExitError> {
let gas_used = (input.len() / PAIR_ELEMENT_LEN) as u64 * pair_per_point_cost + pair_base_cost;
gasometer.record_gas(gas_used.into())?;
if !input.len().is_multiple_of(PAIR_ELEMENT_LEN) {
return Err(ExitException::OutOfGas.into());
}
let elements = input.len() / PAIR_ELEMENT_LEN;
let mut points = Vec::with_capacity(elements);
for idx in 0..elements {
let start = idx * PAIR_ELEMENT_LEN;
let g1_start = start;
let g2_start = start + G1_LEN;
let encoded_g1_element = &input[g1_start..g2_start];
let encoded_g2_element = &input[g2_start..g2_start + G2_LEN];
points.push((encoded_g1_element, encoded_g2_element));
}
let pairing_result = pairing_check(&points)?;
Ok((
ExitSucceed::Returned,
u256_to_h256(bool_to_u256(pairing_result)).0.into(),
))
}
#[inline]
fn read_fq(input: &[u8]) -> Result<Fq, ExitError> {
Ok(Fq::from_slice(&input[..FQ_LEN]).map_err(|_| ExitException::OutOfGas)?)
}
#[inline]
fn read_fq2(input: &[u8]) -> Result<Fq2, ExitError> {
let y = read_fq(&input[..FQ_LEN])?;
let x = read_fq(&input[FQ_LEN..2 * FQ_LEN])?;
Ok(Fq2::new(x, y))
}
#[inline]
fn new_g1_point(px: Fq, py: Fq) -> Result<G1, ExitError> {
if px == Fq::zero() && py == Fq::zero() {
Ok(G1::zero())
} else {
Ok(AffineG1::new(px, py)
.map(Into::into)
.map_err(|_| ExitException::OutOfGas)?)
}
}
#[inline]
fn new_g2_point(x: Fq2, y: Fq2) -> Result<G2, ExitError> {
let point = if x.is_zero() && y.is_zero() {
G2::zero()
} else {
G2::from(AffineG2::new(x, y).map_err(|_| ExitException::OutOfGas)?)
};
Ok(point)
}
#[inline]
pub fn read_g1_point(input: &[u8]) -> Result<G1, ExitError> {
let px = read_fq(&input[0..FQ_LEN])?;
let py = read_fq(&input[FQ_LEN..2 * FQ_LEN])?;
new_g1_point(px, py)
}
#[inline]
pub fn encode_g1_point(point: G1) -> [u8; G1_LEN] {
let mut output = [0u8; G1_LEN];
if let Some(point_affine) = AffineG1::from_jacobian(point) {
point_affine
.x()
.to_big_endian(&mut output[..FQ_LEN])
.unwrap();
point_affine
.y()
.to_big_endian(&mut output[FQ_LEN..])
.unwrap();
}
output
}
#[inline]
pub fn read_g2_point(input: &[u8]) -> Result<G2, ExitError> {
let ba = read_fq2(&input[0..FQ2_LEN])?;
let bb = read_fq2(&input[FQ2_LEN..2 * FQ2_LEN])?;
new_g2_point(ba, bb)
}
#[inline]
pub fn read_scalar(input: &[u8]) -> bn::Fr {
assert_eq!(
input.len(),
SCALAR_LEN,
"unexpected scalar length. got {}, expected {SCALAR_LEN}",
input.len()
);
bn::Fr::from_slice(input).unwrap()
}
#[inline]
fn g1_point_add(p1_bytes: &[u8], p2_bytes: &[u8]) -> Result<[u8; 64], ExitError> {
let p1 = read_g1_point(p1_bytes)?;
let p2 = read_g1_point(p2_bytes)?;
let result = p1 + p2;
Ok(encode_g1_point(result))
}
#[inline]
fn g1_point_mul(point_bytes: &[u8], fr_bytes: &[u8]) -> Result<[u8; 64], ExitError> {
let p = read_g1_point(point_bytes)?;
let fr = read_scalar(fr_bytes);
let result = p * fr;
Ok(encode_g1_point(result))
}
#[inline]
fn pairing_check(pairs: &[(&[u8], &[u8])]) -> Result<bool, ExitError> {
let mut parsed_pairs = Vec::with_capacity(pairs.len());
for (g1_bytes, g2_bytes) in pairs {
let g1 = read_g1_point(g1_bytes)?;
let g2 = read_g2_point(g2_bytes)?;
if !g1.is_zero() && !g2.is_zero() {
parsed_pairs.push((g1, g2));
}
}
if parsed_pairs.is_empty() {
return Ok(true);
}
Ok(bn::pairing_batch(&parsed_pairs) == Gt::one())
}
#[inline]
fn right_pad<const LEN: usize>(data: &[u8]) -> Cow<'_, [u8; LEN]> {
if let Some(data) = data.get(..LEN) {
Cow::Borrowed(data.try_into().unwrap())
} else {
let mut padded = [0; LEN];
padded[..data.len()].copy_from_slice(data);
Cow::Owned(padded)
}
}
#[inline]
pub const fn bool_to_u256(value: bool) -> U256 {
if value { U256::one() } else { U256::zero() }
}