use core::mem::{align_of, size_of};
use ark_ff::{BigInt, PrimeField};
use snarkrs_field::{Fq, Fq2, Fr, G1Affine, G2Affine};
pub mod glv;
pub use glv::PackedGlv;
pub const LIMBS: usize = 8;
pub const FR_MODULUS: [u32; LIMBS] = [
0xf000_0001,
0x43e1_f593,
0x79b9_7091,
0x2833_e848,
0x8181_585d,
0xb850_45b6,
0xe131_a029,
0x3064_4e72,
];
pub const FR_N0: u32 = 0xefff_ffff;
pub const FQ_MODULUS: [u32; LIMBS] = [
0xd87c_fd47,
0x3c20_8c16,
0x6871_ca8d,
0x9781_6a91,
0x8181_585d,
0xb850_45b6,
0xe131_a029,
0x3064_4e72,
];
pub const FQ_N0: u32 = 0xe486_6389;
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct PackedFr {
pub v: [u32; LIMBS],
}
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct PackedScalar {
pub v: [u32; LIMBS],
}
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct PackedFq {
pub v: [u32; LIMBS],
}
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct PackedFq2 {
pub c0: PackedFq,
pub c1: PackedFq,
}
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct PackedG1Affine {
pub x: PackedFq,
pub y: PackedFq,
}
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct PackedG2Affine {
pub x: PackedFq2,
pub y: PackedFq2,
}
const _: () = {
assert!(size_of::<PackedFr>() == 32);
assert!(size_of::<PackedScalar>() == 32);
assert!(size_of::<PackedFq>() == 32);
assert!(size_of::<PackedFq2>() == 64);
assert!(size_of::<PackedG1Affine>() == 64);
assert!(size_of::<PackedG2Affine>() == 128);
assert!(align_of::<PackedFr>() == 4);
assert!(align_of::<PackedG1Affine>() == 4);
assert!(align_of::<PackedG2Affine>() == 4);
assert!(size_of::<BigInt<4>>() == 32);
};
#[inline]
fn split(limbs: [u64; 4]) -> [u32; LIMBS] {
let mut out = [0u32; LIMBS];
for (i, w) in limbs.iter().enumerate() {
out[2 * i] = *w as u32;
out[2 * i + 1] = (*w >> 32) as u32;
}
out
}
#[inline]
fn join(v: [u32; LIMBS]) -> [u64; 4] {
let mut out = [0u64; 4];
for (i, w) in out.iter_mut().enumerate() {
*w = u64::from(v[2 * i]) | (u64::from(v[2 * i + 1]) << 32);
}
out
}
impl PackedFr {
pub const ZERO: Self = Self { v: [0; LIMBS] };
#[inline]
pub fn from_fr(x: &Fr) -> Self {
Self { v: split(x.0 .0) }
}
#[inline]
pub fn to_fr(&self) -> Fr {
Fr::new_unchecked(BigInt::new(join(self.v)))
}
pub fn pack_slice(xs: &[Fr]) -> Vec<Self> {
xs.iter().map(Self::from_fr).collect()
}
pub fn pack_into(xs: &[Fr], out: &mut [Self]) {
assert_eq!(xs.len(), out.len(), "packed destination length mismatch");
for (dst, src) in out.iter_mut().zip(xs) {
*dst = Self::from_fr(src);
}
}
pub fn unpack_slice(xs: &[Self]) -> Vec<Fr> {
xs.iter().map(Self::to_fr).collect()
}
}
impl PackedScalar {
pub const ZERO: Self = Self { v: [0; LIMBS] };
#[inline]
pub fn from_fr(x: &Fr) -> Self {
Self {
v: split(x.into_bigint().0),
}
}
#[inline]
pub fn to_fr(&self) -> Option<Fr> {
Fr::from_bigint(BigInt::new(join(self.v)))
}
pub fn pack_slice(xs: &[Fr]) -> Vec<Self> {
xs.iter().map(Self::from_fr).collect()
}
pub fn pack_into(xs: &[Fr], out: &mut [Self]) {
assert_eq!(xs.len(), out.len(), "packed destination length mismatch");
for (dst, src) in out.iter_mut().zip(xs) {
*dst = Self::from_fr(src);
}
}
}
impl PackedFq {
pub const ZERO: Self = Self { v: [0; LIMBS] };
#[inline]
pub fn from_fq(x: &Fq) -> Self {
Self { v: split(x.0 .0) }
}
#[inline]
pub fn to_fq(&self) -> Fq {
Fq::new_unchecked(BigInt::new(join(self.v)))
}
#[inline]
fn is_zero(&self) -> bool {
self.v.iter().fold(0u32, |a, b| a | b) == 0
}
}
impl PackedFq2 {
pub const ZERO: Self = Self {
c0: PackedFq::ZERO,
c1: PackedFq::ZERO,
};
#[inline]
pub fn from_fq2(x: &Fq2) -> Self {
Self {
c0: PackedFq::from_fq(&x.c0),
c1: PackedFq::from_fq(&x.c1),
}
}
#[inline]
pub fn to_fq2(&self) -> Fq2 {
Fq2::new(self.c0.to_fq(), self.c1.to_fq())
}
#[inline]
fn is_zero(&self) -> bool {
self.c0.is_zero() && self.c1.is_zero()
}
}
impl PackedG1Affine {
pub const INFINITY: Self = Self {
x: PackedFq::ZERO,
y: PackedFq::ZERO,
};
#[inline]
pub fn from_affine(p: &G1Affine) -> Self {
if p.infinity {
Self::INFINITY
} else {
Self {
x: PackedFq::from_fq(&p.x),
y: PackedFq::from_fq(&p.y),
}
}
}
#[inline]
pub fn is_infinity(&self) -> bool {
self.x.is_zero() && self.y.is_zero()
}
#[inline]
pub fn to_affine(&self) -> G1Affine {
if self.is_infinity() {
G1Affine::identity()
} else {
G1Affine::new_unchecked(self.x.to_fq(), self.y.to_fq())
}
}
pub fn pack_slice(ps: &[G1Affine]) -> Vec<Self> {
ps.iter().map(Self::from_affine).collect()
}
pub fn pack_into(ps: &[G1Affine], out: &mut [Self]) {
assert_eq!(ps.len(), out.len(), "packed destination length mismatch");
for (dst, src) in out.iter_mut().zip(ps) {
*dst = Self::from_affine(src);
}
}
}
impl PackedG2Affine {
pub const INFINITY: Self = Self {
x: PackedFq2::ZERO,
y: PackedFq2::ZERO,
};
#[inline]
pub fn from_affine(p: &G2Affine) -> Self {
if p.infinity {
Self::INFINITY
} else {
Self {
x: PackedFq2::from_fq2(&p.x),
y: PackedFq2::from_fq2(&p.y),
}
}
}
#[inline]
pub fn is_infinity(&self) -> bool {
self.x.is_zero() && self.y.is_zero()
}
#[inline]
pub fn to_affine(&self) -> G2Affine {
if self.is_infinity() {
G2Affine::identity()
} else {
G2Affine::new_unchecked(self.x.to_fq2(), self.y.to_fq2())
}
}
pub fn pack_slice(ps: &[G2Affine]) -> Vec<Self> {
ps.iter().map(Self::from_affine).collect()
}
pub fn pack_into(ps: &[G2Affine], out: &mut [Self]) {
assert_eq!(ps.len(), out.len(), "packed destination length mismatch");
for (dst, src) in out.iter_mut().zip(ps) {
*dst = Self::from_affine(src);
}
}
}
pub unsafe trait Packed: Copy {}
unsafe impl Packed for PackedFr {}
unsafe impl Packed for PackedScalar {}
unsafe impl Packed for PackedFq {}
unsafe impl Packed for PackedFq2 {}
unsafe impl Packed for PackedG1Affine {}
unsafe impl Packed for PackedG2Affine {}
unsafe impl Packed for u32 {}
pub fn as_bytes<T: Packed>(items: &[T]) -> &[u8] {
unsafe {
core::slice::from_raw_parts(items.as_ptr().cast::<u8>(), size_of::<T>() * items.len())
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use ark_ff::{One, Zero};
use snarkrs_field::{CurveGroup, G1Projective, G2Projective, PrimeGroup};
use crate::testrng::SplitMix64;
const FR_R_MOD_N: [u32; LIMBS] = [
0x4fff_fffb,
0xac96_341c,
0x9f60_cd29,
0x36fc_7695,
0x7879_462e,
0x666e_a36f,
0x9a07_df2f,
0x0e0a_77c1,
];
#[test]
fn montgomery_constants_match_ark() {
for (name, modulus, n0, ark_mod) in [
("Fr", FR_MODULUS, FR_N0, Fr::MODULUS.0),
("Fq", FQ_MODULUS, FQ_N0, Fq::MODULUS.0),
] {
assert_eq!(
join(modulus),
ark_mod,
"{name}: modulus limbs disagree with ark-ff"
);
let low = u64::from(modulus[0]).wrapping_mul(u64::from(n0)) as u32;
assert_eq!(low, u32::MAX, "{name}: N0 is not -m^-1 mod 2^32");
}
assert_eq!(FQ_N0, 3_834_012_553);
}
#[test]
fn fr_round_trips_through_montgomery_limbs() {
let mut rng = SplitMix64(0xC0FF_EE00);
for x in [Fr::zero(), Fr::one(), -Fr::one(), Fr::from(2u64)]
.into_iter()
.chain((0..4096).map(|_| rng.next_fr()))
{
assert_eq!(PackedFr::from_fr(&x).to_fr(), x);
assert_eq!(PackedScalar::from_fr(&x).to_fr(), Some(x));
}
}
#[test]
fn montgomery_and_standard_encodings_are_not_the_same() {
let one = Fr::one();
assert_eq!(PackedScalar::from_fr(&one).v, [1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(PackedFr::from_fr(&one).v, FR_R_MOD_N);
assert_ne!(PackedFr::from_fr(&one).v, PackedScalar::from_fr(&one).v);
}
#[test]
fn a_non_canonical_scalar_is_rejected() {
assert_eq!(PackedScalar { v: FR_MODULUS }.to_fr(), None);
assert_eq!(
PackedScalar {
v: [u32::MAX; LIMBS]
}
.to_fr(),
None
);
}
fn rand_g1(rng: &mut SplitMix64) -> G1Affine {
(G1Projective::generator() * rng.next_fr()).into_affine()
}
fn rand_g2(rng: &mut SplitMix64) -> G2Affine {
(G2Projective::generator() * rng.next_fr()).into_affine()
}
#[test]
fn g1_packs_to_64_bytes_and_round_trips_including_infinity() {
let mut rng = SplitMix64(7);
assert_eq!(as_bytes(&[PackedG1Affine::INFINITY]).len(), 64);
let inf = G1Affine::identity();
assert!(PackedG1Affine::from_affine(&inf).is_infinity());
assert_eq!(PackedG1Affine::from_affine(&inf).to_affine(), inf);
for _ in 0..256 {
let p = rand_g1(&mut rng);
let packed = PackedG1Affine::from_affine(&p);
assert!(!packed.is_infinity(), "a random point is not infinity");
assert_eq!(packed.to_affine(), p);
}
assert!(!G1Affine::new_unchecked(Fq::zero(), Fq::zero()).is_on_curve());
}
#[test]
fn g2_packs_to_128_bytes_and_round_trips_including_infinity() {
let mut rng = SplitMix64(9);
assert_eq!(as_bytes(&[PackedG2Affine::INFINITY]).len(), 128);
let inf = G2Affine::identity();
assert!(PackedG2Affine::from_affine(&inf).is_infinity());
assert_eq!(PackedG2Affine::from_affine(&inf).to_affine(), inf);
for _ in 0..64 {
let p = rand_g2(&mut rng);
let packed = PackedG2Affine::from_affine(&p);
assert!(!packed.is_infinity());
assert_eq!(packed.to_affine(), p);
}
assert!(!G2Affine::new_unchecked(Fq2::zero(), Fq2::zero()).is_on_curve());
}
#[test]
fn ark_affine_strides_are_still_the_ones_documented() {
assert_eq!(size_of::<G1Affine>(), 72);
assert_eq!(size_of::<G2Affine>(), 136);
assert_ne!(size_of::<G1Affine>(), size_of::<PackedG1Affine>());
}
#[test]
fn packed_slices_have_the_declared_stride() {
assert_eq!(as_bytes(&vec![PackedG1Affine::INFINITY; 5]).len(), 5 * 64);
assert_eq!(as_bytes(&vec![PackedG2Affine::INFINITY; 5]).len(), 5 * 128);
assert_eq!(as_bytes(&vec![PackedFr::ZERO; 5]).len(), 5 * 32);
let x = PackedFr::from_fr(&Fr::one());
assert_eq!(&as_bytes(&[x])[..4], &FR_R_MOD_N[0].to_le_bytes());
}
#[test]
fn packing_is_a_pure_function() {
let mut rng = SplitMix64(11);
let xs: Vec<Fr> = (0..64).map(|_| rng.next_fr()).collect();
assert_eq!(PackedFr::pack_slice(&xs), PackedFr::pack_slice(&xs));
let mut out = vec![PackedFr::ZERO; xs.len()];
PackedFr::pack_into(&xs, &mut out);
assert_eq!(out, PackedFr::pack_slice(&xs));
assert_eq!(PackedFr::unpack_slice(&out), xs);
}
}
pub mod testrng {
use snarkrs_field::Fr;
pub struct SplitMix64(pub u64);
impl SplitMix64 {
pub fn next_u64(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
pub fn next_fr(&mut self) -> Fr {
use ark_ff::PrimeField;
let mut b = [0u8; 32];
for c in b.chunks_mut(8) {
c.copy_from_slice(&self.next_u64().to_le_bytes());
}
Fr::from_le_bytes_mod_order(&b)
}
}
}