#![cfg_attr(not(feature = "std"), no_std)]
use ark_bw6_761::{Fq, Fr, G1Affine, G1Projective, G2Affine, G2Projective, BW6_761};
use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup, VariableBaseMSM};
use ark_ff::{BigInteger768, PrimeField, Zero};
use ark_std::{ops::Mul, vec::Vec};
use fp_evm::{
ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput,
PrecompileResult,
};
const BW6761_MULTIEXP_DISCOUNT_TABLE: [u16; 128] = [
1266, 733, 561, 474, 422, 387, 362, 344, 329, 318, 308, 300, 296, 289, 283, 279, 275, 272, 269,
266, 265, 260, 259, 256, 255, 254, 252, 251, 250, 249, 249, 220, 228, 225, 223, 219, 216, 214,
212, 209, 209, 205, 203, 202, 200, 198, 196, 199, 195, 192, 192, 191, 190, 187, 186, 185, 184,
184, 181, 181, 181, 180, 178, 179, 176, 177, 176, 175, 174, 173, 171, 171, 170, 170, 169, 168,
168, 167, 167, 166, 165, 167, 166, 166, 165, 165, 164, 164, 163, 163, 162, 162, 160, 163, 159,
162, 159, 160, 159, 159, 158, 158, 158, 158, 157, 157, 156, 155, 155, 156, 155, 155, 154, 155,
154, 153, 153, 153, 152, 152, 152, 152, 151, 151, 151, 151, 151, 150,
];
fn encode_fq(field: Fq) -> [u8; 96] {
let mut result = [0u8; 96];
let rep = field.into_bigint().0;
result[0..8].copy_from_slice(&rep[11].to_be_bytes());
result[8..16].copy_from_slice(&rep[10].to_be_bytes());
result[16..24].copy_from_slice(&rep[9].to_be_bytes());
result[24..32].copy_from_slice(&rep[8].to_be_bytes());
result[32..40].copy_from_slice(&rep[7].to_be_bytes());
result[40..48].copy_from_slice(&rep[6].to_be_bytes());
result[48..56].copy_from_slice(&rep[5].to_be_bytes());
result[56..64].copy_from_slice(&rep[4].to_be_bytes());
result[64..72].copy_from_slice(&rep[3].to_be_bytes());
result[72..80].copy_from_slice(&rep[2].to_be_bytes());
result[80..88].copy_from_slice(&rep[1].to_be_bytes());
result[88..96].copy_from_slice(&rep[0].to_be_bytes());
result
}
fn encode_g1(g1: G1Affine) -> [u8; 192] {
let mut result = [0u8; 192];
if !g1.is_zero() {
result[0..96].copy_from_slice(&encode_fq(g1.x));
result[96..192].copy_from_slice(&encode_fq(g1.y));
}
result
}
fn encode_g2(g2: G2Affine) -> [u8; 192] {
let mut result = [0u8; 192];
if !g2.is_zero() {
result[0..96].copy_from_slice(&encode_fq(g2.x));
result[96..192].copy_from_slice(&encode_fq(g2.y));
}
result
}
fn read_input(source: &[u8], target: &mut [u8], offset: usize) {
let len = target.len();
target[..len].copy_from_slice(&source[offset..][..len]);
}
fn decode_fr(input: &[u8], offset: usize) -> Fr {
let mut bytes = [0u8; 64];
read_input(input, &mut bytes, offset);
Fr::from_be_bytes_mod_order(&bytes)
}
fn decode_fq(bytes: [u8; 96]) -> Option<Fq> {
let mut tmp = BigInteger768::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
tmp.0[11] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[0..8]).unwrap());
tmp.0[10] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[8..16]).unwrap());
tmp.0[9] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[16..24]).unwrap());
tmp.0[8] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[24..32]).unwrap());
tmp.0[7] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[32..40]).unwrap());
tmp.0[6] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[40..48]).unwrap());
tmp.0[5] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[48..56]).unwrap());
tmp.0[4] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[56..64]).unwrap());
tmp.0[3] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[64..72]).unwrap());
tmp.0[2] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[72..80]).unwrap());
tmp.0[1] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[80..88]).unwrap());
tmp.0[0] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[88..96]).unwrap());
Fq::from_bigint(tmp)
}
fn extract_fq(bytes: [u8; 96]) -> Result<Fq, PrecompileFailure> {
let fq = decode_fq(bytes);
match fq {
None => Err(PrecompileFailure::Error {
exit_status: ExitError::Other("invalid Fq".into()),
}),
Some(c) => Ok(c),
}
}
fn decode_g1(input: &[u8], offset: usize) -> Result<G1Projective, PrecompileFailure> {
let mut px_buf = [0u8; 96];
let mut py_buf = [0u8; 96];
read_input(input, &mut px_buf, offset);
read_input(input, &mut py_buf, offset + 96);
let px = extract_fq(px_buf)?;
let py = extract_fq(py_buf)?;
if px.is_zero() && py.is_zero() {
Ok(G1Projective::zero())
} else {
let g1 = G1Affine::new_unchecked(px, py);
if !g1.is_on_curve() {
Err(PrecompileFailure::Error {
exit_status: ExitError::Other("point is not on curve".into()),
})
} else {
Ok(g1.into())
}
}
}
fn decode_g2(input: &[u8], offset: usize) -> Result<G2Projective, PrecompileFailure> {
let mut px_buf = [0u8; 96];
let mut py_buf = [0u8; 96];
read_input(input, &mut px_buf, offset);
read_input(input, &mut py_buf, offset + 96);
let px = extract_fq(px_buf)?;
let py = extract_fq(py_buf)?;
if px.is_zero() && py.is_zero() {
Ok(G2Projective::zero())
} else {
let g2 = G2Affine::new_unchecked(px, py);
if !g2.is_on_curve() {
Err(PrecompileFailure::Error {
exit_status: ExitError::Other("point is not on curve".into()),
})
} else {
Ok(g2.into())
}
}
}
pub struct Bw6761G1Add;
impl Bw6761G1Add {
const GAS_COST: u64 = 180;
}
impl Precompile for Bw6761G1Add {
fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
handle.record_cost(Bw6761G1Add::GAS_COST)?;
let input = handle.input();
if input.len() != 384 {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("invalid input length".into()),
});
}
let p0 = decode_g1(input, 0)?;
let p1 = decode_g1(input, 192)?;
let r = p0 + p1;
let output = encode_g1(r.into_affine());
Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: output.to_vec(),
})
}
}
pub struct Bw6761G1Mul;
impl Bw6761G1Mul {
const GAS_COST: u64 = 64_000;
}
impl Precompile for Bw6761G1Mul {
fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
handle.record_cost(Bw6761G1Mul::GAS_COST)?;
let input = handle.input();
if input.len() != 256 {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("invalid input length".into()),
});
}
let p = decode_g1(input, 0)?;
let e = decode_fr(input, 192);
let r = p.mul(e);
let output = encode_g1(r.into_affine());
Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: output.to_vec(),
})
}
}
pub struct Bw6761G1MultiExp;
impl Bw6761G1MultiExp {
const MULTIPLIER: u64 = 1_000;
fn calculate_gas_cost(input_len: usize) -> u64 {
let k = input_len / 256;
if k == 0 {
return 0;
}
let d_len = BW6761_MULTIEXP_DISCOUNT_TABLE.len();
let discount = if k <= d_len {
BW6761_MULTIEXP_DISCOUNT_TABLE[k - 1]
} else {
BW6761_MULTIEXP_DISCOUNT_TABLE[d_len - 1]
};
k as u64 * Bw6761G1Mul::GAS_COST * discount as u64 / Bw6761G1MultiExp::MULTIPLIER
}
}
impl Precompile for Bw6761G1MultiExp {
fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
let gas_cost = Bw6761G1MultiExp::calculate_gas_cost(handle.input().len());
handle.record_cost(gas_cost)?;
let k = handle.input().len() / 256;
if handle.input().is_empty() || handle.input().len() % 256 != 0 {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("invalid input length".into()),
});
}
let input = handle.input();
let mut points = Vec::new();
let mut scalars = Vec::new();
for idx in 0..k {
let offset = idx * 256;
let p = decode_g1(input, offset)?;
let scalar = decode_fr(input, offset + 192);
points.push(p.into_affine());
scalars.push(scalar);
}
let r = G1Projective::msm(&points.to_vec(), &scalars.to_vec()).map_err(|_| {
PrecompileFailure::Error {
exit_status: ExitError::Other("MSM failed".into()),
}
})?;
let output = encode_g1(r.into_affine());
Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: output.to_vec(),
})
}
}
pub struct Bw6761G2Add;
impl Bw6761G2Add {
const GAS_COST: u64 = 180;
}
impl Precompile for Bw6761G2Add {
fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
handle.record_cost(Bw6761G2Add::GAS_COST)?;
let input = handle.input();
if input.len() != 384 {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("invalid input length".into()),
});
}
let p0 = decode_g2(input, 0)?;
let p1 = decode_g2(input, 192)?;
let r = p0 + p1;
let output = encode_g2(r.into_affine());
Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: output.to_vec(),
})
}
}
pub struct Bw6761G2Mul;
impl Bw6761G2Mul {
const GAS_COST: u64 = 64_000;
}
impl Precompile for Bw6761G2Mul {
fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
handle.record_cost(Bw6761G2Mul::GAS_COST)?;
let input = handle.input();
if input.len() != 256 {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("invalid input length".into()),
});
}
let p = decode_g2(input, 0)?;
let e = decode_fr(input, 192);
let r = p.mul(e);
let output = encode_g2(r.into_affine());
Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: output.to_vec(),
})
}
}
pub struct Bw6761G2MultiExp;
impl Bw6761G2MultiExp {
const MULTIPLIER: u64 = 1_000;
fn calculate_gas_cost(input_len: usize) -> u64 {
let k = input_len / 256;
if k == 0 {
return 0;
}
let d_len = BW6761_MULTIEXP_DISCOUNT_TABLE.len();
let discount = if k <= d_len {
BW6761_MULTIEXP_DISCOUNT_TABLE[k - 1]
} else {
BW6761_MULTIEXP_DISCOUNT_TABLE[d_len - 1]
};
k as u64 * Bw6761G2Mul::GAS_COST * discount as u64 / Bw6761G2MultiExp::MULTIPLIER
}
}
impl Precompile for Bw6761G2MultiExp {
fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
let gas_cost = Bw6761G2MultiExp::calculate_gas_cost(handle.input().len());
handle.record_cost(gas_cost)?;
let k = handle.input().len() / 256;
if handle.input().is_empty() || handle.input().len() % 256 != 0 {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("invalid input length".into()),
});
}
let input = handle.input();
let mut points = Vec::new();
let mut scalars = Vec::new();
for idx in 0..k {
let offset = idx * 256;
let p = decode_g2(input, offset)?;
let scalar = decode_fr(input, offset + 192);
points.push(p.into_affine());
scalars.push(scalar);
}
let r = G2Projective::msm(&points.to_vec(), &scalars.to_vec()).map_err(|_| {
PrecompileFailure::Error {
exit_status: ExitError::Other("MSM failed".into()),
}
})?;
let output = encode_g2(r.into_affine());
Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: output.to_vec(),
})
}
}
pub struct Bw6761Pairing;
impl Bw6761Pairing {
const BASE_GAS: u64 = 120_000;
const PER_PAIR_GAS: u64 = 320_000;
}
impl Precompile for Bw6761Pairing {
fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
if handle.input().is_empty() || handle.input().len() % 384 != 0 {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("invalid input length".into()),
});
}
let k = handle.input().len() / 384;
let gas_cost: u64 = Bw6761Pairing::BASE_GAS + (k as u64 * Bw6761Pairing::PER_PAIR_GAS);
handle.record_cost(gas_cost)?;
let input = handle.input();
let mut a = Vec::new();
let mut b = Vec::new();
for idx in 0..k {
let offset = idx * 384;
let g1 = decode_g1(input, offset)?;
let g2 = decode_g2(input, offset + 192)?;
if !g1.into_affine().is_in_correct_subgroup_assuming_on_curve() {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("g1 point is not on correct subgroup".into()),
});
}
if !g2.into_affine().is_in_correct_subgroup_assuming_on_curve() {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("g2 point is not on correct subgroup".into()),
});
}
a.push(g1);
b.push(g2);
}
let mut output = [0u8; 32];
if BW6_761::multi_pairing(a, b).is_zero() {
output[31] = 1;
}
Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: output.to_vec(),
})
}
}
#[cfg(test)]
mod tests;