use crate::{
precompiles::{BuiltinAddressMatcher, Error, Ext, PrimitivePrecompile},
wasm::RuntimeCosts,
Config,
};
use alloc::vec::Vec;
use bn::{pairing_batch, AffineG1, AffineG2, Fq, Fq2, Group, Gt, G1, G2};
use core::{marker::PhantomData, num::NonZero};
use sp_core::U256;
use sp_runtime::DispatchError;
pub struct Bn128Add<T>(PhantomData<T>);
impl<T: Config> PrimitivePrecompile for Bn128Add<T> {
type T = T;
const MATCHER: BuiltinAddressMatcher = BuiltinAddressMatcher::Fixed(NonZero::new(6).unwrap());
const HAS_CONTRACT_INFO: bool = false;
fn call(
_address: &[u8; 20],
input: Vec<u8>,
env: &mut impl Ext<T = Self::T>,
) -> Result<Vec<u8>, Error> {
env.gas_meter_mut().charge(RuntimeCosts::Bn128Add)?;
let p1 = read_point(&input, 0)?;
let p2 = read_point(&input, 64)?;
let mut buf = [0u8; 64];
if let Some(sum) = AffineG1::from_jacobian(p1 + p2) {
sum.x().to_big_endian(&mut buf[0..32]).expect("0..32 is 32-byte length; qed");
sum.y().to_big_endian(&mut buf[32..64]).expect("32..64 is 32-byte length; qed");
}
Ok(buf.to_vec())
}
}
pub struct Bn128Mul<T>(PhantomData<T>);
impl<T: Config> PrimitivePrecompile for Bn128Mul<T> {
type T = T;
const MATCHER: BuiltinAddressMatcher = BuiltinAddressMatcher::Fixed(NonZero::new(7).unwrap());
const HAS_CONTRACT_INFO: bool = false;
fn call(
_address: &[u8; 20],
input: Vec<u8>,
env: &mut impl Ext<T = Self::T>,
) -> Result<Vec<u8>, Error> {
env.gas_meter_mut().charge(RuntimeCosts::Bn128Mul)?;
let p = read_point(&input, 0)?;
let fr = read_fr(&input, 64)?;
let mut buf = [0u8; 64];
if let Some(sum) = AffineG1::from_jacobian(p * fr) {
sum.x().to_big_endian(&mut buf[0..32]).expect("0..32 is 32-byte length; qed");
sum.y().to_big_endian(&mut buf[32..64]).expect("32..64 is 32-byte length; qed");
}
Ok(buf.to_vec())
}
}
pub struct Bn128Pairing<T>(PhantomData<T>);
impl<T: Config> PrimitivePrecompile for Bn128Pairing<T> {
type T = T;
const MATCHER: BuiltinAddressMatcher = BuiltinAddressMatcher::Fixed(NonZero::new(8).unwrap());
const HAS_CONTRACT_INFO: bool = false;
fn call(
_address: &[u8; 20],
input: Vec<u8>,
env: &mut impl Ext<T = Self::T>,
) -> Result<Vec<u8>, Error> {
if input.len() % 192 != 0 {
Err(DispatchError::from("invalid input length"))?;
}
let ret_val = if input.is_empty() {
env.gas_meter_mut().charge(RuntimeCosts::Bn128Pairing(0))?;
U256::one()
} else {
let elements = input.len() / 192;
env.gas_meter_mut().charge(RuntimeCosts::Bn128Pairing(elements as u32))?;
let mut vals = Vec::new();
for i in 0..elements {
let offset = i * 192;
let a_x = Fq::from_slice(&input[offset..offset + 32])
.map_err(|_| DispatchError::from("Invalid a argument x coordinate"))?;
let a_y = Fq::from_slice(&input[offset + 32..offset + 64])
.map_err(|_| DispatchError::from("Invalid a argument y coordinate"))?;
let b_a_y = Fq::from_slice(&input[offset + 64..offset + 96]).map_err(|_| {
DispatchError::from("Invalid b argument imaginary coeff x coordinate")
})?;
let b_a_x = Fq::from_slice(&input[offset + 96..offset + 128]).map_err(|_| {
DispatchError::from("Invalid b argument imaginary coeff y coordinate")
})?;
let b_b_y = Fq::from_slice(&input[offset + 128..offset + 160]).map_err(|_| {
DispatchError::from("Invalid b argument real coeff x coordinate")
})?;
let b_b_x = Fq::from_slice(&input[offset + 160..offset + 192]).map_err(|_| {
DispatchError::from("Invalid b argument real coeff y coordinate")
})?;
let b_a = Fq2::new(b_a_x, b_a_y);
let b_b = Fq2::new(b_b_x, b_b_y);
let b =
if b_a.is_zero() && b_b.is_zero() {
G2::zero()
} else {
G2::from(AffineG2::new(b_a, b_b).map_err(|_| {
DispatchError::from("Invalid b argument - not on curve")
})?)
};
let a =
if a_x.is_zero() && a_y.is_zero() {
G1::zero()
} else {
G1::from(AffineG1::new(a_x, a_y).map_err(|_| {
DispatchError::from("Invalid a argument - not on curve")
})?)
};
vals.push((a, b));
}
let mul = pairing_batch(&vals);
if mul == Gt::one() {
U256::one()
} else {
U256::zero()
}
};
let buf = ret_val.to_big_endian();
Ok(buf.to_vec())
}
}
fn read_point(input: &[u8], start_inx: usize) -> Result<bn::G1, DispatchError> {
let mut px_buf = [0u8; 32];
let mut py_buf = [0u8; 32];
read_input(input, &mut px_buf, start_inx);
read_input(input, &mut py_buf, start_inx + 32);
let px = Fq::from_slice(&px_buf).map_err(|_| "Invalid point x coordinate")?;
let py = Fq::from_slice(&py_buf).map_err(|_| "Invalid point y coordinate")?;
Ok(if px == Fq::zero() && py == Fq::zero() {
G1::zero()
} else {
AffineG1::new(px, py).map_err(|_| "Invalid curve point")?.into()
})
}
fn read_fr(input: &[u8], start_inx: usize) -> Result<bn::Fr, DispatchError> {
let mut buf = [0u8; 32];
read_input(input, &mut buf, start_inx);
let r = bn::Fr::from_slice(&buf).map_err(|_| "Invalid field element")?;
Ok(r)
}
fn read_input(source: &[u8], target: &mut [u8], offset: usize) {
if source.len() <= offset {
return;
}
let len = core::cmp::min(target.len(), source.len() - offset);
target[..len].copy_from_slice(&source[offset..][..len]);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
precompiles::tests::{run_failure_test_vectors, run_test_vectors},
tests::Test,
};
#[test]
fn test_bn128add() {
run_test_vectors::<Bn128Add<Test>>(include_str!("./testdata/6-bn128add.json"));
run_failure_test_vectors::<Bn128Add<Test>>(include_str!(
"./testdata/6-bn128add-failure.json"
));
}
#[test]
fn test_bn128mul() {
run_test_vectors::<Bn128Mul<Test>>(include_str!("./testdata/7-bn128mul.json"));
}
#[test]
fn test_bn128pairing() {
run_test_vectors::<Bn128Pairing<Test>>(include_str!("./testdata/8-bn128pairing.json"));
}
}