#![cfg_attr(not(feature = "std"), no_std)]
use ark_bls12_377::{
g1::Config as G1Config, g2::Config as G2Config, Bls12_377, Fq, Fq2, Fr, G1Affine, G1Projective,
G2Affine, G2Projective,
};
use ark_ec::{
hashing::{curve_maps::wb::WBMap, map_to_curve_hasher::MapToCurve, HashToCurveError},
pairing::Pairing,
AffineRepr, CurveGroup, VariableBaseMSM,
};
use ark_ff::{BigInteger384, PrimeField, Zero};
use ark_std::{ops::Mul, vec::Vec};
use fp_evm::{
ExitError, ExitSucceed, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput,
PrecompileResult,
};
const BLS12377_MULTIEXP_DISCOUNT_TABLE: [u16; 128] = [
1200, 888, 764, 641, 594, 547, 500, 453, 438, 423, 408, 394, 379, 364, 349, 334, 330, 326, 322,
318, 314, 310, 306, 302, 298, 294, 289, 285, 281, 277, 273, 269, 268, 266, 265, 263, 262, 260,
259, 257, 256, 254, 253, 251, 250, 248, 247, 245, 244, 242, 241, 239, 238, 236, 235, 233, 232,
231, 229, 228, 226, 225, 223, 222, 221, 220, 219, 219, 218, 217, 216, 216, 215, 214, 213, 213,
212, 211, 211, 210, 209, 208, 208, 207, 206, 205, 205, 204, 203, 202, 202, 201, 200, 199, 199,
198, 197, 196, 196, 195, 194, 193, 193, 192, 191, 191, 190, 189, 188, 188, 187, 186, 185, 185,
184, 183, 182, 182, 181, 180, 179, 179, 178, 177, 176, 176, 175, 174,
];
fn encode_fq(field: Fq) -> [u8; 64] {
let mut result = [0u8; 64];
let rep = field.into_bigint().0;
result[16..24].copy_from_slice(&rep[5].to_be_bytes());
result[24..32].copy_from_slice(&rep[4].to_be_bytes());
result[32..40].copy_from_slice(&rep[3].to_be_bytes());
result[40..48].copy_from_slice(&rep[2].to_be_bytes());
result[48..56].copy_from_slice(&rep[1].to_be_bytes());
result[56..64].copy_from_slice(&rep[0].to_be_bytes());
result
}
fn encode_g1(g1: G1Affine) -> [u8; 128] {
let mut result = [0u8; 128];
if !g1.is_zero() {
result[0..64].copy_from_slice(&encode_fq(g1.x));
result[64..128].copy_from_slice(&encode_fq(g1.y));
}
result
}
fn encode_g2(g2: G2Affine) -> [u8; 256] {
let mut result = [0u8; 256];
if !g2.is_zero() {
result[0..64].copy_from_slice(&encode_fq(g2.x.c0));
result[64..128].copy_from_slice(&encode_fq(g2.x.c1));
result[128..192].copy_from_slice(&encode_fq(g2.y.c0));
result[192..256].copy_from_slice(&encode_fq(g2.y.c1));
}
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; 32];
read_input(input, &mut bytes, offset);
Fr::from_be_bytes_mod_order(&bytes)
}
fn decode_fq(bytes: [u8; 64]) -> Option<Fq> {
for b in bytes.iter().take(16) {
if b.ne(&0u8) {
return None;
}
}
let mut tmp = BigInteger384::new([0, 0, 0, 0, 0, 0]);
tmp.0[5] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[16..24]).unwrap());
tmp.0[4] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[24..32]).unwrap());
tmp.0[3] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[32..40]).unwrap());
tmp.0[2] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[40..48]).unwrap());
tmp.0[1] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[48..56]).unwrap());
tmp.0[0] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[56..64]).unwrap());
Fq::from_bigint(tmp)
}
fn extract_fq(bytes: [u8; 64]) -> 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 read_fq(input: &[u8], offset: usize) -> Result<Fq, PrecompileFailure> {
let mut buf = [0u8; 64];
read_input(input, &mut buf, offset);
extract_fq(buf)
}
fn read_fq2(input: &[u8], offset: usize) -> Result<Fq2, PrecompileFailure> {
let mut x_buf = [0u8; 64];
let mut y_buf = [0u8; 64];
read_input(input, &mut x_buf, offset);
read_input(input, &mut y_buf, offset + 64);
let px = extract_fq(x_buf)?;
let py = extract_fq(y_buf)?;
Ok(Fq2::new(px, py))
}
fn map_to_curve_g1(fq: Fq) -> Result<G1Affine, HashToCurveError> {
let m2c = WBMap::<G1Config>::new()?;
m2c.map_to_curve(fq)
}
fn map_to_curve_g2(fq2: Fq2) -> Result<G2Affine, HashToCurveError> {
let m2c = WBMap::<G2Config>::new()?;
m2c.map_to_curve(fq2)
}
fn decode_g1(input: &[u8], offset: usize) -> Result<G1Projective, PrecompileFailure> {
let mut px_buf = [0u8; 64];
let mut py_buf = [0u8; 64];
read_input(input, &mut px_buf, offset);
read_input(input, &mut py_buf, offset + 64);
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 px0_buf = [0u8; 64];
let mut px1_buf = [0u8; 64];
let mut py0_buf = [0u8; 64];
let mut py1_buf = [0u8; 64];
read_input(input, &mut px0_buf, offset);
read_input(input, &mut px1_buf, offset + 64);
read_input(input, &mut py0_buf, offset + 128);
read_input(input, &mut py1_buf, offset + 192);
let px0 = extract_fq(px0_buf)?;
let px1 = extract_fq(px1_buf)?;
let py0 = extract_fq(py0_buf)?;
let py1 = extract_fq(py1_buf)?;
let px = Fq2::new(px0, px1);
let py = Fq2::new(py0, py1);
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 Bls12377G1Add;
impl Bls12377G1Add {
const GAS_COST: u64 = 600;
}
impl Precompile for Bls12377G1Add {
fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
handle.record_cost(Bls12377G1Add::GAS_COST)?;
let input = handle.input();
if input.len() != 256 {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("invalid input length".into()),
});
}
let p0 = decode_g1(input, 0)?;
let p1 = decode_g1(input, 128)?;
let r = p0 + p1;
let output = encode_g1(r.into_affine());
Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: output.to_vec(),
})
}
}
pub struct Bls12377G1Mul;
impl Bls12377G1Mul {
const GAS_COST: u64 = 12_000;
}
impl Precompile for Bls12377G1Mul {
fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
handle.record_cost(Bls12377G1Mul::GAS_COST)?;
let input = handle.input();
if input.len() != 160 {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("invalid input length".into()),
});
}
let p = decode_g1(input, 0)?;
let e = decode_fr(input, 128);
let r = p.mul(e);
let output = encode_g1(r.into_affine());
Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: output.to_vec(),
})
}
}
pub struct Bls12377G1MultiExp;
impl Bls12377G1MultiExp {
const MULTIPLIER: u64 = 1_000;
fn calculate_gas_cost(input_len: usize) -> u64 {
let k = input_len / 160;
if k == 0 {
return 0;
}
let d_len = BLS12377_MULTIEXP_DISCOUNT_TABLE.len();
let discount = if k <= d_len {
BLS12377_MULTIEXP_DISCOUNT_TABLE[k - 1]
} else {
BLS12377_MULTIEXP_DISCOUNT_TABLE[d_len - 1]
};
k as u64 * Bls12377G1Mul::GAS_COST * discount as u64 / Bls12377G1MultiExp::MULTIPLIER
}
}
impl Precompile for Bls12377G1MultiExp {
fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
let gas_cost = Bls12377G1MultiExp::calculate_gas_cost(handle.input().len());
handle.record_cost(gas_cost)?;
let k = handle.input().len() / 160;
if handle.input().is_empty() || handle.input().len() % 160 != 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 * 160;
let p = decode_g1(input, offset)?;
let scalar = decode_fr(input, offset + 128);
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 Bls12377G2Add;
impl Bls12377G2Add {
const GAS_COST: u64 = 4_500;
}
impl Precompile for Bls12377G2Add {
fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
handle.record_cost(Bls12377G2Add::GAS_COST)?;
let input = handle.input();
if input.len() != 512 {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("invalid input length".into()),
});
}
let p0 = decode_g2(input, 0)?;
let p1 = decode_g2(input, 256)?;
let r = p0 + p1;
let output = encode_g2(r.into_affine());
Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: output.to_vec(),
})
}
}
pub struct Bls12377G2Mul;
impl Bls12377G2Mul {
const GAS_COST: u64 = 55_000;
}
impl Precompile for Bls12377G2Mul {
fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
handle.record_cost(Bls12377G2Mul::GAS_COST)?;
let input = handle.input();
if input.len() != 288 {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("invalid input length".into()),
});
}
let p = decode_g2(input, 0)?;
let e = decode_fr(input, 256);
let r = p.mul(e);
let output = encode_g2(r.into_affine());
Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: output.to_vec(),
})
}
}
pub struct Bls12377G2MultiExp;
impl Bls12377G2MultiExp {
const MULTIPLIER: u64 = 1_000;
fn calculate_gas_cost(input_len: usize) -> u64 {
let k = input_len / 288;
if k == 0 {
return 0;
}
let d_len = BLS12377_MULTIEXP_DISCOUNT_TABLE.len();
let discount = if k <= d_len {
BLS12377_MULTIEXP_DISCOUNT_TABLE[k - 1]
} else {
BLS12377_MULTIEXP_DISCOUNT_TABLE[d_len - 1]
};
k as u64 * Bls12377G2Mul::GAS_COST * discount as u64 / Bls12377G2MultiExp::MULTIPLIER
}
}
impl Precompile for Bls12377G2MultiExp {
fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
let gas_cost = Bls12377G2MultiExp::calculate_gas_cost(handle.input().len());
handle.record_cost(gas_cost)?;
let k = handle.input().len() / 288;
if handle.input().is_empty() || handle.input().len() % 288 != 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 * 288;
let p = decode_g2(input, offset)?;
let scalar = decode_fr(input, offset + 256);
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 Bls12377Pairing;
impl Bls12377Pairing {
const BASE_GAS: u64 = 65_000;
const PER_PAIR_GAS: u64 = 55_000;
}
impl Precompile for Bls12377Pairing {
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 = Bls12377Pairing::BASE_GAS + (k as u64 * Bls12377Pairing::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 + 128)?;
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 Bls12_377::multi_pairing(a, b).is_zero() {
output[31] = 1;
}
Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: output.to_vec(),
})
}
}
pub struct Bls12377MapG1;
impl Bls12377MapG1 {
const GAS_COST: u64 = 5_500;
}
impl Precompile for Bls12377MapG1 {
fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
handle.record_cost(Bls12377MapG1::GAS_COST)?;
let input = handle.input();
if input.len() != 64 {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("invalid input length".into()),
});
}
let fq = read_fq(input, 0)?;
let g1 = match map_to_curve_g1(fq) {
Ok(point) => point.clear_cofactor(),
Err(_) => {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("map to curve failed".into()),
})
}
};
let output = encode_g1(g1);
Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: output.to_vec(),
})
}
}
pub struct Bls12377MapG2;
impl Bls12377MapG2 {
const GAS_COST: u64 = 75_000;
}
impl Precompile for Bls12377MapG2 {
fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
handle.record_cost(Bls12377MapG2::GAS_COST)?;
let input = handle.input();
if input.len() != 128 {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("invalid input length".into()),
});
}
let fq2 = read_fq2(input, 0)?;
let g2 = match map_to_curve_g2(fq2) {
Ok(point) => point.clear_cofactor(),
Err(_) => {
return Err(PrecompileFailure::Error {
exit_status: ExitError::Other("map to curve failed".into()),
})
}
};
let output = encode_g2(g2);
Ok(PrecompileOutput {
exit_status: ExitSucceed::Returned,
output: output.to_vec(),
})
}
}
#[cfg(test)]
mod tests;