use halo2curves_axiom::ff::{Field, PrimeField};
use num_bigint::BigUint;
use num_traits::Num;
use once_cell::sync::Lazy;
use openvm_algebra_circuit::fields::{
blocks_to_field_element, blocks_to_field_element_bls12_381_coordinate, field_element_to_blocks,
field_element_to_blocks_bls12_381_coordinate, FieldType,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CurveType {
K256 = 0,
P256 = 1,
BN254 = 2,
BLS12_381 = 3,
}
const P256_NEG_A: u64 = 3;
fn get_modulus_as_bigint<F: PrimeField>() -> BigUint {
BigUint::from_str_radix(F::MODULUS.trim_start_matches("0x"), 16).unwrap()
}
static K256_COORD_MODULUS: Lazy<BigUint> =
Lazy::new(get_modulus_as_bigint::<halo2curves_axiom::secq256k1::Fq>);
static P256_COORD_MODULUS: Lazy<BigUint> =
Lazy::new(get_modulus_as_bigint::<halo2curves_axiom::secp256r1::Fp>);
static BN254_COORD_MODULUS: Lazy<BigUint> =
Lazy::new(get_modulus_as_bigint::<halo2curves_axiom::bn256::Fq>);
static BLS12_381_COORD_MODULUS: Lazy<BigUint> =
Lazy::new(get_modulus_as_bigint::<halo2curves_axiom::bls12_381::Fq>);
static P256_A_COEFF: Lazy<BigUint> = Lazy::new(|| {
BigUint::from_bytes_le(&(-halo2curves_axiom::secp256r1::Fp::from(P256_NEG_A)).to_bytes())
});
pub(super) fn get_curve_type(modulus: &BigUint, a_coeff: &BigUint) -> Option<CurveType> {
if modulus == &*K256_COORD_MODULUS && a_coeff == &BigUint::ZERO {
return Some(CurveType::K256);
}
if modulus == &*P256_COORD_MODULUS && a_coeff == &*P256_A_COEFF {
return Some(CurveType::P256);
}
if modulus == &*BN254_COORD_MODULUS && a_coeff == &BigUint::ZERO {
return Some(CurveType::BN254);
}
if modulus == &*BLS12_381_COORD_MODULUS && a_coeff == &BigUint::ZERO {
return Some(CurveType::BLS12_381);
}
None
}
#[inline(always)]
pub fn ec_add_ne<const FIELD_TYPE: u8, const BLOCKS: usize, const BLOCK_SIZE: usize>(
input_data: [[[u8; BLOCK_SIZE]; BLOCKS]; 2],
) -> [[u8; BLOCK_SIZE]; BLOCKS] {
match FIELD_TYPE {
x if x == FieldType::K256Coordinate as u8 => {
ec_add_ne_256bit::<halo2curves_axiom::secq256k1::Fq, BLOCKS, BLOCK_SIZE>(input_data)
}
x if x == FieldType::P256Coordinate as u8 => {
ec_add_ne_256bit::<halo2curves_axiom::secp256r1::Fp, BLOCKS, BLOCK_SIZE>(input_data)
}
x if x == FieldType::BN254Coordinate as u8 => {
ec_add_ne_256bit::<halo2curves_axiom::bn256::Fq, BLOCKS, BLOCK_SIZE>(input_data)
}
x if x == FieldType::BLS12_381Coordinate as u8 => {
ec_add_ne_bls12_381::<BLOCKS, BLOCK_SIZE>(input_data)
}
_ => panic!("Unsupported field type: {FIELD_TYPE}"),
}
}
#[inline(always)]
pub fn ec_double<const CURVE_TYPE: u8, const BLOCKS: usize, const BLOCK_SIZE: usize>(
input_data: [[u8; BLOCK_SIZE]; BLOCKS],
) -> [[u8; BLOCK_SIZE]; BLOCKS] {
match CURVE_TYPE {
x if x == CurveType::K256 as u8 => {
ec_double_256bit::<halo2curves_axiom::secq256k1::Fq, 0, BLOCKS, BLOCK_SIZE>(input_data)
}
x if x == CurveType::P256 as u8 => {
ec_double_256bit::<halo2curves_axiom::secp256r1::Fp, P256_NEG_A, BLOCKS, BLOCK_SIZE>(
input_data,
)
}
x if x == CurveType::BN254 as u8 => {
ec_double_256bit::<halo2curves_axiom::bn256::Fq, 0, BLOCKS, BLOCK_SIZE>(input_data)
}
x if x == CurveType::BLS12_381 as u8 => {
ec_double_bls12_381::<BLOCKS, BLOCK_SIZE>(input_data)
}
_ => panic!("Unsupported curve type: {CURVE_TYPE}"),
}
}
#[inline(always)]
fn ec_add_ne_256bit<
F: PrimeField<Repr = [u8; 32]>,
const BLOCKS: usize,
const BLOCK_SIZE: usize,
>(
input_data: [[[u8; BLOCK_SIZE]; BLOCKS]; 2],
) -> [[u8; BLOCK_SIZE]; BLOCKS] {
let x1 = blocks_to_field_element::<F>(input_data[0][..BLOCKS / 2].as_flattened());
let y1 = blocks_to_field_element::<F>(input_data[0][BLOCKS / 2..].as_flattened());
let x2 = blocks_to_field_element::<F>(input_data[1][..BLOCKS / 2].as_flattened());
let y2 = blocks_to_field_element::<F>(input_data[1][BLOCKS / 2..].as_flattened());
let (x3, y3) = ec_add_ne_impl::<F>(x1, y1, x2, y2);
let mut output = [[0u8; BLOCK_SIZE]; BLOCKS];
field_element_to_blocks::<F, BLOCK_SIZE>(&x3, &mut output[..BLOCKS / 2]);
field_element_to_blocks::<F, BLOCK_SIZE>(&y3, &mut output[BLOCKS / 2..]);
output
}
#[inline(always)]
fn ec_double_256bit<
F: PrimeField<Repr = [u8; 32]>,
const NEG_A: u64,
const BLOCKS: usize,
const BLOCK_SIZE: usize,
>(
input_data: [[u8; BLOCK_SIZE]; BLOCKS],
) -> [[u8; BLOCK_SIZE]; BLOCKS] {
let x1 = blocks_to_field_element::<F>(input_data[..BLOCKS / 2].as_flattened());
let y1 = blocks_to_field_element::<F>(input_data[BLOCKS / 2..].as_flattened());
let (x3, y3) = ec_double_impl::<F, NEG_A>(x1, y1);
let mut output = [[0u8; BLOCK_SIZE]; BLOCKS];
field_element_to_blocks::<F, BLOCK_SIZE>(&x3, &mut output[..BLOCKS / 2]);
field_element_to_blocks::<F, BLOCK_SIZE>(&y3, &mut output[BLOCKS / 2..]);
output
}
#[inline(always)]
fn ec_add_ne_bls12_381<const BLOCKS: usize, const BLOCK_SIZE: usize>(
input_data: [[[u8; BLOCK_SIZE]; BLOCKS]; 2],
) -> [[u8; BLOCK_SIZE]; BLOCKS] {
let x1 =
blocks_to_field_element_bls12_381_coordinate(input_data[0][..BLOCKS / 2].as_flattened());
let y1 =
blocks_to_field_element_bls12_381_coordinate(input_data[0][BLOCKS / 2..].as_flattened());
let x2 =
blocks_to_field_element_bls12_381_coordinate(input_data[1][..BLOCKS / 2].as_flattened());
let y2 =
blocks_to_field_element_bls12_381_coordinate(input_data[1][BLOCKS / 2..].as_flattened());
let (x3, y3) = ec_add_ne_impl::<blstrs::Fp>(x1, y1, x2, y2);
let mut output = [[0u8; BLOCK_SIZE]; BLOCKS];
field_element_to_blocks_bls12_381_coordinate(&x3, &mut output[..BLOCKS / 2]);
field_element_to_blocks_bls12_381_coordinate(&y3, &mut output[BLOCKS / 2..]);
output
}
#[inline(always)]
fn ec_double_bls12_381<const BLOCKS: usize, const BLOCK_SIZE: usize>(
input_data: [[u8; BLOCK_SIZE]; BLOCKS],
) -> [[u8; BLOCK_SIZE]; BLOCKS] {
let x1 = blocks_to_field_element_bls12_381_coordinate(input_data[..BLOCKS / 2].as_flattened());
let y1 = blocks_to_field_element_bls12_381_coordinate(input_data[BLOCKS / 2..].as_flattened());
let (x3, y3) = ec_double_impl::<blstrs::Fp, 0>(x1, y1);
let mut output = [[0u8; BLOCK_SIZE]; BLOCKS];
field_element_to_blocks_bls12_381_coordinate(&x3, &mut output[..BLOCKS / 2]);
field_element_to_blocks_bls12_381_coordinate(&y3, &mut output[BLOCKS / 2..]);
output
}
#[inline(always)]
pub fn ec_add_ne_impl<F: Field>(x1: F, y1: F, x2: F, y2: F) -> (F, F) {
let lambda = (y2 - y1) * (x2 - x1).invert().unwrap();
let x3 = lambda.square() - x1 - x2;
let y3 = lambda * (x1 - x3) - y1;
(x3, y3)
}
#[inline(always)]
pub fn ec_double_impl<F: Field + From<u64>, const NEG_A: u64>(x1: F, y1: F) -> (F, F) {
let x1_squared = x1.square();
let three_x1_squared = x1_squared + x1_squared.double();
let two_y1 = y1.double();
let lambda = if NEG_A == 0 {
three_x1_squared * two_y1.invert().unwrap()
} else {
(three_x1_squared - F::from(NEG_A)) * two_y1.invert().unwrap()
};
let x3 = lambda.square() - x1.double();
let y3 = lambda * (x1 - x3) - y1;
(x3, y3)
}