From dedba65de6c1406b635077ce63a5d1d2c48d153a Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 30 Sep 2024 13:20:23 +0200 Subject: [PATCH 01/16] Add intial minimalfield implementation --- Cargo.lock | 2 + Cargo.toml | 1 + curves/Cargo.toml | 4 +- curves/src/pasta/mod.rs | 1 + curves/src/pasta/wasm_friendly/backend9.rs | 213 ++++++++++++++++ curves/src/pasta/wasm_friendly/bigint32.rs | 52 ++++ .../src/pasta/wasm_friendly/minimal_field.rs | 44 ++++ curves/src/pasta/wasm_friendly/mod.rs | 12 + curves/src/pasta/wasm_friendly/pasta.rs | 33 +++ curves/src/pasta/wasm_friendly/wasm_fp.rs | 236 ++++++++++++++++++ poseidon/benches/poseidon_bench.rs | 47 +++- poseidon/src/permutation.rs | 11 +- poseidon/src/poseidon.rs | 18 +- 13 files changed, 657 insertions(+), 17 deletions(-) create mode 100644 curves/src/pasta/wasm_friendly/backend9.rs create mode 100644 curves/src/pasta/wasm_friendly/bigint32.rs create mode 100644 curves/src/pasta/wasm_friendly/minimal_field.rs create mode 100644 curves/src/pasta/wasm_friendly/mod.rs create mode 100644 curves/src/pasta/wasm_friendly/pasta.rs create mode 100644 curves/src/pasta/wasm_friendly/wasm_fp.rs diff --git a/Cargo.lock b/Cargo.lock index 62d96b58768..60e6c01b799 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2257,8 +2257,10 @@ dependencies = [ "ark-serialize", "ark-std", "ark-test-curves", + "derivative", "num-bigint", "rand", + "wasm-bindgen", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a4303d92371..016cf3312b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ criterion = { version = "0.5", features = [ "cargo_bench_support", "html_reports", ] } +derivative = { version = "2.0", features = ["use_core"] } elf = "0.7.2" env_logger = "0.11.1" getrandom = { version = "0.2.15", features = ["js"] } diff --git a/curves/Cargo.toml b/curves/Cargo.toml index 3c06d1e8f41..316cf889aa3 100644 --- a/curves/Cargo.toml +++ b/curves/Cargo.toml @@ -14,10 +14,12 @@ ark-bn254.workspace = true ark-ec.workspace = true ark-ff.workspace = true num-bigint.workspace = true +ark-serialize.workspace = true +wasm-bindgen.workspace = true +derivative.workspace = true [dev-dependencies] rand.workspace = true ark-test-curves.workspace = true ark-algebra-test-templates.workspace = true -ark-serialize.workspace = true ark-std.workspace = true diff --git a/curves/src/pasta/mod.rs b/curves/src/pasta/mod.rs index abd4207fa3f..4d3eb86cfa5 100644 --- a/curves/src/pasta/mod.rs +++ b/curves/src/pasta/mod.rs @@ -1,5 +1,6 @@ pub mod curves; pub mod fields; +pub mod wasm_friendly; pub use curves::{ pallas::{Pallas, PallasParameters, ProjectivePallas}, diff --git a/curves/src/pasta/wasm_friendly/backend9.rs b/curves/src/pasta/wasm_friendly/backend9.rs new file mode 100644 index 00000000000..9295fa84715 --- /dev/null +++ b/curves/src/pasta/wasm_friendly/backend9.rs @@ -0,0 +1,213 @@ +/** + * Implementation of `FpBackend` for N=9, using 29-bit limbs represented by `u32`s. + */ +use super::bigint32::BigInt; +use super::wasm_fp::{Fp, FpBackend}; + +type B = [u32; 9]; +type B64 = [u64; 9]; + +const SHIFT: u32 = 29; +const MASK: u32 = (1 << SHIFT) - 1; + +const SHIFT64: u64 = SHIFT as u64; +const MASK64: u64 = MASK as u64; + +pub const fn from_64x4(pa: [u64; 4]) -> [u32; 9] { + let mut p = [0u32; 9]; + p[0] = (pa[0] & MASK64) as u32; + p[1] = ((pa[0] >> 29) & MASK64) as u32; + p[2] = (((pa[0] >> 58) | (pa[1] << 6)) & MASK64) as u32; + p[3] = ((pa[1] >> 23) & MASK64) as u32; + p[4] = (((pa[1] >> 52) | (pa[2] << 12)) & MASK64) as u32; + p[5] = ((pa[2] >> 17) & MASK64) as u32; + p[6] = (((pa[2] >> 46) | (pa[3] << 18)) & MASK64) as u32; + p[7] = ((pa[3] >> 11) & MASK64) as u32; + p[8] = (pa[3] >> 40) as u32; + p +} +pub const fn to_64x4(pa: [u32; 9]) -> [u64; 4] { + let mut p = [0u64; 4]; + p[0] = pa[0] as u64; + p[0] |= (pa[1] as u64) << 29; + p[0] |= (pa[2] as u64) << 58; + p[1] = (pa[2] as u64) >> 6; + p[1] |= (pa[3] as u64) << 23; + p[1] |= (pa[4] as u64) << 52; + p[2] = (pa[4] as u64) >> 12; + p[2] |= (pa[5] as u64) << 17; + p[2] |= (pa[6] as u64) << 46; + p[3] = (pa[6] as u64) >> 18; + p[3] |= (pa[7] as u64) << 11; + p[3] |= (pa[8] as u64) << 40; + p +} + +pub trait FpConstants: Send + Sync + 'static + Sized { + const MODULUS: B; + const MODULUS64: B64 = { + let mut modulus64 = [0u64; 9]; + let modulus = Self::MODULUS; + let mut i = 0; + while i < 9 { + modulus64[i] = modulus[i] as u64; + i += 1; + } + modulus64 + }; + + /// montgomery params + /// TODO: compute these + const R: B; // R = 2^261 mod modulus + const R2: B; // R^2 mod modulus + const MINV: u64; // -modulus^(-1) mod 2^29, as a u64 +} + +#[inline] +fn gte_modulus(x: &B) -> bool { + for i in (0..9).rev() { + // don't fix warning -- that makes it 15% slower! + #[allow(clippy::comparison_chain)] + if x[i] > FpC::MODULUS[i] { + return true; + } else if x[i] < FpC::MODULUS[i] { + return false; + } + } + true +} + +// TODO performance ideas to test: +// - unroll loops +// - introduce locals for a[i] instead of accessing memory multiple times +// - only do 1 carry pass at the end, by proving properties of greater-than on uncarried result +// - use cheaper, approximate greater-than check a[8] > Fp::MODULUS[8] +pub fn add_assign(x: &mut B, y: &B) { + let mut tmp: u32; + let mut carry: i32 = 0; + + for i in 0..9 { + tmp = x[i] + y[i] + (carry as u32); + carry = (tmp as i32) >> SHIFT; + x[i] = tmp & MASK; + } + + if gte_modulus::(x) { + carry = 0; + #[allow(clippy::needless_range_loop)] + for i in 0..9 { + tmp = x[i].wrapping_sub(FpC::MODULUS[i]) + (carry as u32); + carry = (tmp as i32) >> SHIFT; + x[i] = tmp & MASK; + } + } +} + +#[inline] +fn conditional_reduce(x: &mut B) { + if gte_modulus::(x) { + #[allow(clippy::needless_range_loop)] + for i in 0..9 { + x[i] = x[i].wrapping_sub(FpC::MODULUS[i]); + } + #[allow(clippy::needless_range_loop)] + for i in 1..9 { + x[i] += ((x[i - 1] as i32) >> SHIFT) as u32; + } + #[allow(clippy::needless_range_loop)] + for i in 0..8 { + x[i] &= MASK; + } + } +} + +/// Montgomery multiplication +pub fn mul_assign(x: &mut B, y: &B) { + // load y[i] into local u64s + // TODO make sure these are locals + let mut y_local = [0u64; 9]; + for i in 0..9 { + y_local[i] = y[i] as u64; + } + + // locals for result + let mut z = [0u64; 8]; + let mut tmp: u64; + + // main loop, without intermediate carries except for z0 + #[allow(clippy::needless_range_loop)] + for i in 0..9 { + let xi = x[i] as u64; + + // compute qi and carry z0 result to z1 before discarding z0 + tmp = xi * y_local[0]; + let qi = ((tmp & MASK64) * FpC::MINV) & MASK64; + z[1] += (tmp + qi * FpC::MODULUS64[0]) >> SHIFT64; + + // compute zi and shift in one step + for j in 1..8 { + z[j - 1] = z[j] + (xi * y_local[j]) + (qi * FpC::MODULUS64[j]); + } + // for j=8 we save an addition since z[8] is never needed + z[7] = xi * y_local[8] + qi * FpC::MODULUS64[8]; + } + + // final carry pass, store result back into x + x[0] = (z[0] & MASK64) as u32; + for i in 1..8 { + x[i] = (((z[i - 1] >> SHIFT64) + z[i]) & MASK64) as u32; + } + x[8] = (z[7] >> SHIFT64) as u32; + + // at this point, x is guaranteed to be less than 2*MODULUS + // conditionally subtract the modulus to bring it back into the canonical range + conditional_reduce::(x); +} + +// implement FpBackend given FpConstants + +pub fn from_bigint_unsafe(x: BigInt<9>) -> Fp { + let mut r = x.0; + // convert to montgomery form + mul_assign::(&mut r, &FpC::R2); + Fp(BigInt(r), Default::default()) +} + +impl FpBackend<9> for FpC { + const MODULUS: BigInt<9> = BigInt(Self::MODULUS); + const ZERO: BigInt<9> = BigInt([0; 9]); + const ONE: BigInt<9> = BigInt(Self::R); + + fn add_assign(x: &mut Fp, y: &Fp) { + add_assign::(&mut x.0 .0, &y.0 .0); + } + + fn mul_assign(x: &mut Fp, y: &Fp) { + mul_assign::(&mut x.0 .0, &y.0 .0); + } + + fn from_bigint(x: BigInt<9>) -> Option> { + if gte_modulus::(&x.0) { + None + } else { + Some(from_bigint_unsafe(x)) + } + } + fn to_bigint(x: Fp) -> BigInt<9> { + let one = [1, 0, 0, 0, 0, 0, 0, 0, 0]; + let mut r = x.0 .0; + // convert back from montgomery form + mul_assign::(&mut r, &one); + BigInt(r) + } + + fn pack(x: Fp) -> Vec { + let x = Self::to_bigint(x).0; + let x64 = to_64x4(x); + let mut res = Vec::with_capacity(4); + for limb in x64.iter() { + res.push(*limb); + } + res + } +} diff --git a/curves/src/pasta/wasm_friendly/bigint32.rs b/curves/src/pasta/wasm_friendly/bigint32.rs new file mode 100644 index 00000000000..f7e23577959 --- /dev/null +++ b/curves/src/pasta/wasm_friendly/bigint32.rs @@ -0,0 +1,52 @@ +/** + * BigInt with 32-bit limbs + * + * Contains everything for wasm_fp which is unrelated to being a field + * + * Code is mostly copied from ark-ff::BigInt + */ +use ark_serialize::{ + CanonicalDeserialize, CanonicalSerialize, Compress, Read, SerializationError, Valid, Validate, + Write, +}; + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +pub struct BigInt(pub [u32; N]); + +impl Default for BigInt { + fn default() -> Self { + Self([0u32; N]) + } +} + +impl CanonicalSerialize for BigInt { + fn serialize_with_mode( + &self, + writer: W, + compress: Compress, + ) -> Result<(), SerializationError> { + self.0.serialize_with_mode(writer, compress) + } + + fn serialized_size(&self, compress: Compress) -> usize { + self.0.serialized_size(compress) + } +} + +impl Valid for BigInt { + fn check(&self) -> Result<(), SerializationError> { + self.0.check() + } +} + +impl CanonicalDeserialize for BigInt { + fn deserialize_with_mode( + reader: R, + compress: Compress, + validate: Validate, + ) -> Result { + Ok(BigInt::(<[u32; N]>::deserialize_with_mode( + reader, compress, validate, + )?)) + } +} diff --git a/curves/src/pasta/wasm_friendly/minimal_field.rs b/curves/src/pasta/wasm_friendly/minimal_field.rs new file mode 100644 index 00000000000..367b82b7406 --- /dev/null +++ b/curves/src/pasta/wasm_friendly/minimal_field.rs @@ -0,0 +1,44 @@ +use ark_ff::{BitIteratorBE, One, Zero}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use std::ops::{Add, AddAssign, Mul, MulAssign}; + +/** + * Minimal Field trait needed to implement Poseidon + */ +pub trait MinimalField: + 'static + + Copy + + Clone + + CanonicalSerialize + + CanonicalDeserialize + + Zero + + One + + for<'a> Add<&'a Self, Output = Self> + + for<'a> Mul<&'a Self, Output = Self> + + for<'a> AddAssign<&'a Self> + + for<'a> MulAssign<&'a Self> +{ + /// Squares `self` in place. + fn square_in_place(&mut self) -> &mut Self; + + /// Returns `self^exp`, where `exp` is an integer represented with `u64` limbs, + /// least significant limb first. + fn pow>(&self, exp: S) -> Self { + let mut res = Self::one(); + + for i in BitIteratorBE::without_leading_zeros(exp) { + res.square_in_place(); + + if i { + res *= self; + } + } + res + } +} + +impl MinimalField for F { + fn square_in_place(&mut self) -> &mut Self { + self.square_in_place() + } +} diff --git a/curves/src/pasta/wasm_friendly/mod.rs b/curves/src/pasta/wasm_friendly/mod.rs new file mode 100644 index 00000000000..15800465ec9 --- /dev/null +++ b/curves/src/pasta/wasm_friendly/mod.rs @@ -0,0 +1,12 @@ +pub mod bigint32; +pub use bigint32::BigInt; + +pub mod minimal_field; +pub use minimal_field::MinimalField; + +pub mod wasm_fp; +pub use wasm_fp::Fp; + +pub mod backend9; +pub mod pasta; +pub use pasta::Fp9; diff --git a/curves/src/pasta/wasm_friendly/pasta.rs b/curves/src/pasta/wasm_friendly/pasta.rs new file mode 100644 index 00000000000..f6cdb802b8d --- /dev/null +++ b/curves/src/pasta/wasm_friendly/pasta.rs @@ -0,0 +1,33 @@ +use super::{backend9, wasm_fp}; +use crate::pasta::Fp; +use ark_ff::PrimeField; + +pub struct Fp9Parameters; + +impl backend9::FpConstants for Fp9Parameters { + const MODULUS: [u32; 9] = [ + 0x1, 0x9698768, 0x133e46e6, 0xd31f812, 0x224, 0x0, 0x0, 0x0, 0x400000, + ]; + const R: [u32; 9] = [ + 0x1fffff81, 0x14a5d367, 0x141ad3c0, 0x1435eec5, 0x1ffeefef, 0x1fffffff, 0x1fffffff, + 0x1fffffff, 0x3fffff, + ]; + const R2: [u32; 9] = [ + 0x3b6a, 0x19c10910, 0x1a6a0188, 0x12a4fd88, 0x634b36d, 0x178792ba, 0x7797a99, 0x1dce5b8a, + 0x3506bd, + ]; + const MINV: u64 = 0x1fffffff; +} +pub type Fp9 = wasm_fp::Fp; + +impl Fp9 { + pub fn from_fp(fp: Fp) -> Self { + backend9::from_bigint_unsafe(super::BigInt(backend9::from_64x4(fp.into_bigint().0))) + } +} + +impl From for Fp9 { + fn from(fp: Fp) -> Self { + Fp9::from_fp(fp) + } +} diff --git a/curves/src/pasta/wasm_friendly/wasm_fp.rs b/curves/src/pasta/wasm_friendly/wasm_fp.rs new file mode 100644 index 00000000000..7c0d45d34ac --- /dev/null +++ b/curves/src/pasta/wasm_friendly/wasm_fp.rs @@ -0,0 +1,236 @@ +/** + * MinimalField trait implementation `Fp` which only depends on an `FpBackend` trait + * + * Most of this code was copied over from ark_ff::Fp + */ +use crate::pasta::wasm_friendly::bigint32::BigInt; +use ark_ff::{One, Zero}; +use ark_serialize::{ + CanonicalDeserialize, CanonicalSerialize, Compress, Read, SerializationError, Valid, Validate, + Write, +}; +use derivative::Derivative; +use num_bigint::BigUint; +use std::{ + marker::PhantomData, + ops::{Add, AddAssign, Mul, MulAssign}, +}; + +use super::minimal_field::MinimalField; + +pub trait FpBackend: Send + Sync + 'static + Sized { + const MODULUS: BigInt; + const ZERO: BigInt; + const ONE: BigInt; + + fn add_assign(a: &mut Fp, b: &Fp); + fn mul_assign(a: &mut Fp, b: &Fp); + + /// Construct a field element from an integer in the range + /// `0..(Self::MODULUS - 1)`. Returns `None` if the integer is outside + /// this range. + fn from_bigint(x: BigInt) -> Option>; + fn to_bigint(x: Fp) -> BigInt; + + fn pack(x: Fp) -> Vec; +} + +/// Represents an element of the prime field F_p, where `p == P::MODULUS`. +/// This type can represent elements in any field of size at most N * 64 bits. +#[derive(Derivative)] +#[derivative( + Default(bound = ""), + Hash(bound = ""), + Copy(bound = ""), + PartialEq(bound = ""), + Eq(bound = ""), + Debug(bound = "") +)] +pub struct Fp, const N: usize>( + pub BigInt, + #[derivative(Debug = "ignore")] + #[doc(hidden)] + pub PhantomData

, +); + +impl, const N: usize> Clone for Fp { + fn clone(&self) -> Self { + *self + } +} + +impl, const N: usize> Fp { + pub fn new(bigint: BigInt) -> Self { + Fp(bigint, Default::default()) + } + + #[inline] + pub fn from_bigint(r: BigInt) -> Option { + P::from_bigint(r) + } + #[inline] + pub fn into_bigint(self) -> BigInt { + P::to_bigint(self) + } + + pub fn to_bytes_le(self) -> Vec { + let chunks = P::pack(self).into_iter().map(|x| x.to_le_bytes()); + let mut bytes = Vec::with_capacity(chunks.len() * 8); + for chunk in chunks { + bytes.extend_from_slice(&chunk); + } + bytes + } +} + +// coerce into Fp from either BigInt or [u32; N] + +impl, const N: usize> From> for Fp { + fn from(val: BigInt) -> Self { + Fp::from_bigint(val).unwrap() + } +} + +impl, const N: usize> From<[u32; N]> for Fp { + fn from(val: [u32; N]) -> Self { + Fp::from_bigint(BigInt(val)).unwrap() + } +} + +// field + +impl, const N: usize> MinimalField for Fp { + fn square_in_place(&mut self) -> &mut Self { + // implemented with mul_assign for now + let self_copy = *self; + self.mul_assign(&self_copy); + self + } +} + +// add, zero + +impl, const N: usize> Zero for Fp { + #[inline] + fn zero() -> Self { + Fp::new(P::ZERO) + } + + #[inline] + fn is_zero(&self) -> bool { + *self == Self::zero() + } +} + +impl<'a, P: FpBackend, const N: usize> AddAssign<&'a Self> for Fp { + #[inline] + fn add_assign(&mut self, other: &Self) { + P::add_assign(self, other) + } +} +impl, const N: usize> Add for Fp { + type Output = Self; + + #[inline] + fn add(mut self, other: Self) -> Self { + self.add_assign(&other); + self + } +} +impl<'a, P: FpBackend, const N: usize> Add<&'a Fp> for Fp { + type Output = Self; + + #[inline] + fn add(mut self, other: &Self) -> Self { + self.add_assign(other); + self + } +} + +// mul, one + +impl, const N: usize> One for Fp { + #[inline] + fn one() -> Self { + Fp::new(P::ONE) + } + + #[inline] + fn is_one(&self) -> bool { + *self == Self::one() + } +} +impl<'a, P: FpBackend, const N: usize> MulAssign<&'a Self> for Fp { + #[inline] + fn mul_assign(&mut self, other: &Self) { + P::mul_assign(self, other) + } +} +impl, const N: usize> Mul for Fp { + type Output = Self; + + #[inline] + fn mul(mut self, other: Self) -> Self { + self.mul_assign(&other); + self + } +} +impl<'a, P: FpBackend, const N: usize> Mul<&'a Fp> for Fp { + type Output = Self; + + #[inline] + fn mul(mut self, other: &Self) -> Self { + self.mul_assign(other); + self + } +} + +// (de)serialization + +impl, const N: usize> CanonicalSerialize for Fp { + #[inline] + fn serialize_with_mode( + &self, + writer: W, + compress: Compress, + ) -> Result<(), SerializationError> { + self.0.serialize_with_mode(writer, compress) + } + + #[inline] + fn serialized_size(&self, compress: Compress) -> usize { + self.0.serialized_size(compress) + } +} + +impl, const N: usize> Valid for Fp { + fn check(&self) -> Result<(), SerializationError> { + Ok(()) + } +} + +impl, const N: usize> CanonicalDeserialize for Fp { + fn deserialize_with_mode( + reader: R, + compress: Compress, + validate: Validate, + ) -> Result { + Self::from_bigint(BigInt::deserialize_with_mode(reader, compress, validate)?) + .ok_or(SerializationError::InvalidData) + } +} + +// display + +impl, const N: usize> From> for BigUint { + #[inline] + fn from(val: Fp) -> BigUint { + BigUint::from_bytes_le(&val.to_bytes_le()) + } +} + +impl, const N: usize> std::fmt::Display for Fp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + BigUint::from(*self).fmt(f) + } +} diff --git a/poseidon/benches/poseidon_bench.rs b/poseidon/benches/poseidon_bench.rs index 81a14d20fb5..4f44ad1f99c 100644 --- a/poseidon/benches/poseidon_bench.rs +++ b/poseidon/benches/poseidon_bench.rs @@ -1,10 +1,12 @@ +use ark_ff::Zero; use criterion::{criterion_group, criterion_main, Criterion}; -use mina_curves::pasta::Fp; +use mina_curves::pasta::{wasm_friendly::Fp9, Fp}; use mina_poseidon::{ constants::PlonkSpongeConstantsKimchi, pasta::fp_kimchi as SpongeParametersKimchi, - poseidon::{ArithmeticSponge as Poseidon, Sponge}, + poseidon::{ArithmeticSponge as Poseidon, ArithmeticSpongeParams, Sponge}, }; +use once_cell::sync::Lazy; pub fn bench_poseidon_kimchi(c: &mut Criterion) { let mut group = c.benchmark_group("Poseidon"); @@ -17,6 +19,23 @@ pub fn bench_poseidon_kimchi(c: &mut Criterion) { SpongeParametersKimchi::static_params(), ); + // poseidon.absorb(&[Fp::zero()]); + // println!("{}", poseidon.squeeze()); + + b.iter(|| { + poseidon.absorb(&[hash]); + hash = poseidon.squeeze(); + }) + }); + + // same as above but with Fp9 + group.bench_function("poseidon_hash_kimchi_fp9", |b| { + let mut hash: Fp9 = Fp9::zero(); + let mut poseidon = Poseidon::::new(fp9_static_params()); + + // poseidon.absorb(&[Fp9::zero()]); + // println!("{}", poseidon.squeeze()); + b.iter(|| { poseidon.absorb(&[hash]); hash = poseidon.squeeze(); @@ -28,3 +47,27 @@ pub fn bench_poseidon_kimchi(c: &mut Criterion) { criterion_group!(benches, bench_poseidon_kimchi); criterion_main!(benches); + +// sponge params for Fp9 + +fn fp9_sponge_params() -> ArithmeticSpongeParams { + let params = SpongeParametersKimchi::params(); + + // leverage .into() to convert from Fp to Fp9 + ArithmeticSpongeParams:: { + round_constants: params + .round_constants + .into_iter() + .map(|x| x.into_iter().map(Fp9::from).collect()) + .collect(), + mds: params + .mds + .into_iter() + .map(|x| x.into_iter().map(Fp9::from).collect()) + .collect(), + } +} +fn fp9_static_params() -> &'static ArithmeticSpongeParams { + static PARAMS: Lazy> = Lazy::new(fp9_sponge_params); + &PARAMS +} diff --git a/poseidon/src/permutation.rs b/poseidon/src/permutation.rs index 208eb37be65..eb780538279 100644 --- a/poseidon/src/permutation.rs +++ b/poseidon/src/permutation.rs @@ -2,14 +2,15 @@ //! used in Poseidon. extern crate alloc; +use mina_curves::pasta::wasm_friendly::minimal_field::MinimalField; + use crate::{ constants::SpongeConstants, poseidon::{sbox, ArithmeticSpongeParams}, }; use alloc::{vec, vec::Vec}; -use ark_ff::Field; -fn apply_mds_matrix( +fn apply_mds_matrix( params: &ArithmeticSpongeParams, state: &[F], ) -> Vec { @@ -40,7 +41,7 @@ fn apply_mds_matrix( /// - Add the round constants to the state. /// /// The function has side-effect and the parameter state is modified. -pub fn full_round( +pub fn full_round( params: &ArithmeticSpongeParams, state: &mut Vec, r: usize, @@ -54,7 +55,7 @@ pub fn full_round( } } -pub fn half_rounds( +pub fn half_rounds( params: &ArithmeticSpongeParams, state: &mut [F], ) { @@ -103,7 +104,7 @@ pub fn half_rounds( } } -pub fn poseidon_block_cipher( +pub fn poseidon_block_cipher( params: &ArithmeticSpongeParams, state: &mut Vec, ) { diff --git a/poseidon/src/poseidon.rs b/poseidon/src/poseidon.rs index dcd2a760f7b..44efa77f8ae 100644 --- a/poseidon/src/poseidon.rs +++ b/poseidon/src/poseidon.rs @@ -6,14 +6,14 @@ use crate::{ permutation::{full_round, poseidon_block_cipher}, }; use alloc::{vec, vec::Vec}; -use ark_ff::Field; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use mina_curves::pasta::wasm_friendly::MinimalField; use serde::{Deserialize, Serialize}; use serde_with::serde_as; /// Cryptographic sponge interface - for hashing an arbitrary amount of /// data into one or more field elements -pub trait Sponge { +pub trait Sponge { /// Create a new cryptographic sponge using arithmetic sponge `params` fn new(params: &'static ArithmeticSpongeParams) -> Self; @@ -27,15 +27,15 @@ pub trait Sponge { fn reset(&mut self); } -pub fn sbox(mut x: F) -> F { +pub fn sbox(mut x: F) -> F { if SC::PERM_SBOX == 7 { // This is much faster than using the generic `pow`. Hard-code to get the ~50% speed-up // that it gives to hashing. let mut square = x; square.square_in_place(); - x *= square; + x *= □ square.square_in_place(); - x *= square; + x *= □ x } else { x.pow([SC::PERM_SBOX as u64]) @@ -50,7 +50,7 @@ pub enum SpongeState { #[serde_as] #[derive(Clone, Serialize, Deserialize, Default, Debug)] -pub struct ArithmeticSpongeParams { +pub struct ArithmeticSpongeParams { #[serde_as(as = "Vec>")] pub round_constants: Vec>, #[serde_as(as = "Vec>")] @@ -58,7 +58,7 @@ pub struct ArithmeticSpongeParams { +pub struct ArithmeticSponge { pub sponge_state: SpongeState, rate: usize, // TODO(mimoo: an array enforcing the width is better no? or at least an assert somewhere) @@ -67,7 +67,7 @@ pub struct ArithmeticSponge { pub constants: core::marker::PhantomData, } -impl ArithmeticSponge { +impl ArithmeticSponge { pub fn full_round(&mut self, r: usize) { full_round::(self.params, &mut self.state, r); } @@ -77,7 +77,7 @@ impl ArithmeticSponge { } } -impl Sponge for ArithmeticSponge { +impl Sponge for ArithmeticSponge { fn new(params: &'static ArithmeticSpongeParams) -> ArithmeticSponge { let capacity = SC::SPONGE_CAPACITY; let rate = SC::SPONGE_RATE; From a60a6be0b4e966d5b0607524c984e963d22aaaaa Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Wed, 20 Nov 2024 15:33:12 +0000 Subject: [PATCH 02/16] Try to stub another minimalfield implementation --- Cargo.lock | 1 + Cargo.toml | 1 + curves/Cargo.toml | 2 + curves/src/pasta/curves/pallas.rs | 42 + curves/src/pasta/fields/fp.rs | 7 + curves/src/pasta/fields/fq.rs | 8 +- curves/src/pasta/wasm_friendly/backend9.rs | 3 +- curves/src/pasta/wasm_friendly/bigint32.rs | 542 +++++++- .../src/pasta/wasm_friendly/minimal_field.rs | 10 +- curves/src/pasta/wasm_friendly/mod.rs | 4 +- curves/src/pasta/wasm_friendly/pasta.rs | 36 +- curves/src/pasta/wasm_friendly/wasm_fp.rs | 793 +++++++++++- .../src/pasta/wasm_friendly/wasm_fp_ported.rs | 1089 +++++++++++++++++ kimchi/src/curve.rs | 37 +- kimchi/src/tests/and.rs | 11 + poseidon/benches/poseidon_bench.rs | 165 ++- poseidon/src/permutation.rs | 10 +- poseidon/src/poseidon.rs | 14 +- 18 files changed, 2737 insertions(+), 38 deletions(-) create mode 100644 curves/src/pasta/wasm_friendly/wasm_fp_ported.rs diff --git a/Cargo.lock b/Cargo.lock index 60e6c01b799..ff94abce772 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2261,6 +2261,7 @@ dependencies = [ "num-bigint", "rand", "wasm-bindgen", + "zeroize", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 016cf3312b3..72457893f74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,6 +91,7 @@ tikv-jemallocator = { version = "0.5" } tinytemplate = "1.1" wasm-bindgen = "=0.2.89" wasm-bindgen-test = ">=0.3.0" +zeroize = "1.7" arkworks = { path = "crates/arkworks" } arrabbiata = { path = "./arrabbiata", version = "0.1.0" } diff --git a/curves/Cargo.toml b/curves/Cargo.toml index 316cf889aa3..23ca7ad7131 100644 --- a/curves/Cargo.toml +++ b/curves/Cargo.toml @@ -11,12 +11,14 @@ license = "Apache-2.0" [dependencies] ark-bn254.workspace = true +ark-std.workspace = true ark-ec.workspace = true ark-ff.workspace = true num-bigint.workspace = true ark-serialize.workspace = true wasm-bindgen.workspace = true derivative.workspace = true +zeroize.workspace = true [dev-dependencies] rand.workspace = true diff --git a/curves/src/pasta/curves/pallas.rs b/curves/src/pasta/curves/pallas.rs index 39813e13bb5..c6349a3baad 100644 --- a/curves/src/pasta/curves/pallas.rs +++ b/curves/src/pasta/curves/pallas.rs @@ -4,6 +4,7 @@ use ark_ec::{ CurveConfig, }; use ark_ff::{MontFp, Zero}; +use std::marker::PhantomData; /// G_GENERATOR_X = /// 1 @@ -73,3 +74,44 @@ impl SWCurveConfig for LegacyPallasParameters { } pub type LegacyPallas = Affine; + +//////////////////////////////////////////////////////////////////////////// +// WASM experimentation +//////////////////////////////////////////////////////////////////////////// + +#[derive(Copy, Clone, Default, PartialEq, Eq)] +pub struct WasmPallasParameters; + +impl CurveConfig for WasmPallasParameters { + type BaseField = crate::pasta::wasm_friendly::Fp9; + + type ScalarField = crate::pasta::wasm_friendly::Fq9; // FIXME must be Fq9 of course + + /// COFACTOR = 1 + const COFACTOR: &'static [u64] = &[0x1]; + + /// COFACTOR_INV = 1 + // FIXME + const COFACTOR_INV: crate::pasta::wasm_friendly::Fq9 = + crate::pasta::wasm_friendly::Fp(crate::pasta::wasm_friendly::BigInt([0; 9]), PhantomData); +} + +pub type WasmPallas = Affine; + +pub type WasmProjectivePallas = Projective; + +impl SWCurveConfig for WasmPallasParameters { + // FIXME + const COEFF_A: Self::BaseField = + crate::pasta::wasm_friendly::Fp(crate::pasta::wasm_friendly::BigInt([0; 9]), PhantomData); + + // FIXME + const COEFF_B: Self::BaseField = + crate::pasta::wasm_friendly::Fp(crate::pasta::wasm_friendly::BigInt([0; 9]), PhantomData); + + // FIXME + const GENERATOR: Affine = Affine::new_unchecked( + crate::pasta::wasm_friendly::Fp(crate::pasta::wasm_friendly::BigInt([0; 9]), PhantomData), + crate::pasta::wasm_friendly::Fp(crate::pasta::wasm_friendly::BigInt([0; 9]), PhantomData), + ); +} diff --git a/curves/src/pasta/fields/fp.rs b/curves/src/pasta/fields/fp.rs index 5672d1abf81..a3c23eefd3d 100644 --- a/curves/src/pasta/fields/fp.rs +++ b/curves/src/pasta/fields/fp.rs @@ -1,5 +1,6 @@ use super::fft::{FftParameters, Fp256Parameters}; use ark_ff::{ + Field, biginteger::BigInteger256 as BigInteger, fields::{MontBackend, MontConfig}, Fp256, @@ -79,3 +80,9 @@ impl super::fft::FpParameters for FpParameters { // -(MODULUS^{-1} mod 2^64) mod 2^64 const INV: u64 = 11037532056220336127; } + +impl crate::pasta::wasm_friendly::MinimalField for Fp { + fn square_in_place(&mut self) -> &mut Self { + ::square_in_place(self) + } +} diff --git a/curves/src/pasta/fields/fq.rs b/curves/src/pasta/fields/fq.rs index b623705750d..c93f4d093ea 100644 --- a/curves/src/pasta/fields/fq.rs +++ b/curves/src/pasta/fields/fq.rs @@ -1,5 +1,5 @@ use super::fft::{FftParameters, Fp256Parameters, FpParameters}; -use ark_ff::{biginteger::BigInteger256 as BigInteger, Fp256}; +use ark_ff::{biginteger::BigInteger256 as BigInteger, Fp256, Field}; pub struct FqParameters; @@ -78,3 +78,9 @@ impl FpParameters for FqParameters { // -(MODULUS^{-1} mod 2^64) mod 2^64 const INV: u64 = 10108024940646105087; } + +impl crate::pasta::wasm_friendly::MinimalField for Fq { + fn square_in_place(&mut self) -> &mut Self { + ::square_in_place(self) + } +} diff --git a/curves/src/pasta/wasm_friendly/backend9.rs b/curves/src/pasta/wasm_friendly/backend9.rs index 9295fa84715..4aeb0163498 100644 --- a/curves/src/pasta/wasm_friendly/backend9.rs +++ b/curves/src/pasta/wasm_friendly/backend9.rs @@ -26,6 +26,7 @@ pub const fn from_64x4(pa: [u64; 4]) -> [u32; 9] { p[8] = (pa[3] >> 40) as u32; p } + pub const fn to_64x4(pa: [u32; 9]) -> [u64; 4] { let mut p = [0u64; 4]; p[0] = pa[0] as u64; @@ -140,7 +141,7 @@ pub fn mul_assign(x: &mut B, y: &B) { let xi = x[i] as u64; // compute qi and carry z0 result to z1 before discarding z0 - tmp = xi * y_local[0]; + tmp = (xi * y_local[0]) + z[0]; let qi = ((tmp & MASK64) * FpC::MINV) & MASK64; z[1] += (tmp + qi * FpC::MODULUS64[0]) >> SHIFT64; diff --git a/curves/src/pasta/wasm_friendly/bigint32.rs b/curves/src/pasta/wasm_friendly/bigint32.rs index f7e23577959..7021846d2d3 100644 --- a/curves/src/pasta/wasm_friendly/bigint32.rs +++ b/curves/src/pasta/wasm_friendly/bigint32.rs @@ -1,18 +1,503 @@ -/** - * BigInt with 32-bit limbs - * - * Contains everything for wasm_fp which is unrelated to being a field - * - * Code is mostly copied from ark-ff::BigInt - */ +//! +//! BigInt with 32-bit limbs +//! +//! Contains everything for wasm_fp which is unrelated to being a field +//! +//! Code is mostly copied from ark-ff::BigInt +//! +use ark_ff::BigInteger; use ark_serialize::{ CanonicalDeserialize, CanonicalSerialize, Compress, Read, SerializationError, Valid, Validate, Write, }; +use ark_std::{ + fmt::Display, + rand::{ + distributions::{Distribution, Standard}, + Rng, + }, +}; +use num_bigint::BigUint; +use zeroize::Zeroize; -#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Zeroize)] pub struct BigInt(pub [u32; N]); +impl BigInt { + pub const fn new(value: [u32; N]) -> Self { + Self(value) + } + + pub const fn zero() -> Self { + Self([0u32; N]) + } + + pub const fn one() -> Self { + let mut one = Self::zero(); + one.0[0] = 1; + one + } + + #[doc(hidden)] + pub const fn const_is_even(&self) -> bool { + self.0[0] % 2 == 0 + } + + #[doc(hidden)] + pub const fn const_is_odd(&self) -> bool { + self.0[0] % 2 == 1 + } + + #[doc(hidden)] + pub const fn mod_4(&self) -> u8 { + todo!() + // To compute n % 4, we need to simply look at the + // 2 least significant bits of n, and check their value mod 4. + //(((self.0[0] << 62) >> 62) % 4) as u8 + } + + /// Compute a right shift of `self` + /// This is equivalent to a (saturating) division by 2. + #[doc(hidden)] + pub const fn const_shr(&self) -> Self { + todo!() + //let mut result = *self; + //let mut t = 0; + //crate::const_for!((i in 0..N) { + // let a = result.0[N - i - 1]; + // let t2 = a << 63; + // result.0[N - i - 1] >>= 1; + // result.0[N - i - 1] |= t; + // t = t2; + //}); + //result + } + + const fn const_geq(&self, other: &Self) -> bool { + todo!() + //const_for!((i in 0..N) { + // let a = self.0[N - i - 1]; + // let b = other.0[N - i - 1]; + // if a < b { + // return false; + // } else if a > b { + // return true; + // } + //}); + //true + } + + /// Compute the smallest odd integer `t` such that `self = 2**s * t + 1` for some + /// integer `s = self.two_adic_valuation()`. + #[doc(hidden)] + pub const fn two_adic_coefficient(mut self) -> Self { + assert!(self.const_is_odd()); + // Since `self` is odd, we can always subtract one + // without a borrow + self.0[0] -= 1; + while self.const_is_even() { + self = self.const_shr(); + } + assert!(self.const_is_odd()); + self + } + + /// Divide `self` by 2, rounding down if necessary. + /// That is, if `self.is_odd()`, compute `(self - 1)/2`. + /// Else, compute `self/2`. + #[doc(hidden)] + pub const fn divide_by_2_round_down(mut self) -> Self { + if self.const_is_odd() { + self.0[0] -= 1; + } + self.const_shr() + } + + /// Find the number of bits in the binary decomposition of `self`. + #[doc(hidden)] + pub const fn const_num_bits(self) -> u32 { + ((N - 1) * 64) as u32 + (64 - self.0[N - 1].leading_zeros()) + } + + #[inline] + pub fn add_nocarry(&mut self, other: &Self) -> bool { + todo!() + //let mut this = self.to_64x4(); + //let other = other.to_64x4(); + + //let mut carry = 0; + //for i in 0..4 { + // this[i] = adc!(this[i], other[i], &mut carry); + //} + //*self = Self::from_64x4(this); + //carry != 0 + } + + #[inline] + pub fn sub_noborrow(&mut self, other: &Self) -> bool { + todo!() + //let mut this = self.to_64x4(); + //let other = other.to_64x4(); + + //let mut borrow = 0; + //for i in 0..4 { + // this[i] = sbb!(this[i], other[i], &mut borrow); + //} + //*self = Self::from_64x4(this); + //borrow != 0 + } +} + +impl BigInteger for BigInt { + const NUM_LIMBS: usize = N; + + #[inline] + fn add_with_carry(&mut self, other: &Self) -> bool { + { + todo!(); + //use arithmetic::adc_for_add_with_carry as adc; + + //let a = &mut self.0; + //let b = &other.0; + //let mut carry = 0; + + //if N >= 1 { + // carry = adc(&mut a[0], b[0], carry); + //} + //if N >= 2 { + // carry = adc(&mut a[1], b[1], carry); + //} + //if N >= 3 { + // carry = adc(&mut a[2], b[2], carry); + //} + //if N >= 4 { + // carry = adc(&mut a[3], b[3], carry); + //} + //if N >= 5 { + // carry = adc(&mut a[4], b[4], carry); + //} + //if N >= 6 { + // carry = adc(&mut a[5], b[5], carry); + //} + //for i in 6..N { + // carry = adc(&mut a[i], b[i], carry); + //} + //carry != 0 + } + } + + #[inline] + fn sub_with_borrow(&mut self, other: &Self) -> bool { + todo!() + //use arithmetic::sbb_for_sub_with_borrow as sbb; + + //let a = &mut self.0; + //let b = &other.0; + //let mut borrow = 0u8; + + //if N >= 1 { + // borrow = sbb(&mut a[0], b[0], borrow); + //} + //if N >= 2 { + // borrow = sbb(&mut a[1], b[1], borrow); + //} + //if N >= 3 { + // borrow = sbb(&mut a[2], b[2], borrow); + //} + //if N >= 4 { + // borrow = sbb(&mut a[3], b[3], borrow); + //} + //if N >= 5 { + // borrow = sbb(&mut a[4], b[4], borrow); + //} + //if N >= 6 { + // borrow = sbb(&mut a[5], b[5], borrow); + //} + //for i in 6..N { + // borrow = sbb(&mut a[i], b[i], borrow); + //} + //borrow != 0 + } + + #[inline] + #[allow(unused)] + fn mul2(&mut self) -> bool { + #[cfg(all(target_arch = "x86_64", feature = "asm"))] + #[allow(unsafe_code)] + { + let mut carry = 0; + + for i in 0..N { + unsafe { + use core::arch::x86_64::_addcarry_u64; + carry = _addcarry_u64(carry, self.0[i], self.0[i], &mut self.0[i]) + }; + } + + carry != 0 + } + + #[cfg(not(all(target_arch = "x86_64", feature = "asm")))] + { + todo!() + //let mut last = 0; + //for i in 0..N { + // let a = &mut self.0[i]; + // let tmp = *a >> 63; + // *a <<= 1; + // *a |= last; + // last = tmp; + //} + //last != 0 + } + } + + #[inline] + fn muln(&mut self, mut n: u32) { + if n >= (64 * N) as u32 { + *self = Self::from(0u64); + return; + } + + while n >= 64 { + let mut t = 0; + for i in 0..N { + core::mem::swap(&mut t, &mut self.0[i]); + } + n -= 64; + } + + if n > 0 { + let mut t = 0; + #[allow(unused)] + for i in 0..N { + let a = &mut self.0[i]; + let t2 = *a >> (64 - n); + *a <<= n; + *a |= t; + t = t2; + } + } + } + + #[inline] + fn div2(&mut self) { + todo!() + //let mut t = 0; + //for i in 0..N { + // let a = &mut self.0[N - i - 1]; + // let t2 = *a << 63; + // *a >>= 1; + // *a |= t; + // t = t2; + //} + } + + #[inline] + fn divn(&mut self, mut n: u32) { + if n >= (64 * N) as u32 { + *self = Self::from(0u64); + return; + } + + while n >= 64 { + let mut t = 0; + for i in 0..N { + core::mem::swap(&mut t, &mut self.0[N - i - 1]); + } + n -= 64; + } + + if n > 0 { + let mut t = 0; + #[allow(unused)] + for i in 0..N { + let a = &mut self.0[N - i - 1]; + let t2 = *a << (64 - n); + *a >>= n; + *a |= t; + t = t2; + } + } + } + + #[inline] + fn is_odd(&self) -> bool { + self.0[0] & 1 == 1 + } + + #[inline] + fn is_even(&self) -> bool { + !self.is_odd() + } + + #[inline] + fn is_zero(&self) -> bool { + self.0.iter().all(|&e| e == 0) + } + + #[inline] + fn num_bits(&self) -> u32 { + let mut ret = N as u32 * 64; + for i in self.0.iter().rev() { + let leading = i.leading_zeros(); + ret -= leading; + if leading != 64 { + break; + } + } + + ret + } + + #[inline] + fn get_bit(&self, i: usize) -> bool { + if i >= 64 * N { + false + } else { + let limb = i / 64; + let bit = i - (64 * limb); + (self.0[limb] & (1 << bit)) != 0 + } + } + + #[inline] + fn from_bits_be(bits: &[bool]) -> Self { + todo!() + //let mut res = Self::default(); + //let mut acc: u64 = 0; + + //let mut bits = bits.to_vec(); + //bits.reverse(); + //for (i, bits64) in bits.chunks(64).enumerate() { + // for bit in bits64.iter().rev() { + // acc <<= 1; + // acc += *bit as u64; + // } + // res.0[i] = acc; + // acc = 0; + //} + //res + } + + fn from_bits_le(bits: &[bool]) -> Self { + todo!() + //let mut res = Self::zero(); + //for (bits64, res_i) in bits.chunks(64).zip(&mut res.0) { + // for (i, bit) in bits64.iter().enumerate() { + // *res_i |= (*bit as u64) << i; + // } + //} + //res + } + + #[inline] + fn to_bytes_be(&self) -> Vec { + let mut le_bytes = self.to_bytes_le(); + le_bytes.reverse(); + le_bytes + } + + #[inline] + fn to_bytes_le(&self) -> Vec { + let array_map = self.0.iter().map(|limb| limb.to_le_bytes()); + let mut res = Vec::with_capacity(N * 8); + for limb in array_map { + res.extend_from_slice(&limb); + } + res + } +} + +impl AsMut<[u64]> for BigInt { + #[inline] + fn as_mut(&mut self) -> &mut [u64] { + todo!() + //&mut self.0 + } +} + +impl AsRef<[u64]> for BigInt { + #[inline] + fn as_ref(&self) -> &[u64] { + todo!() + //&self.0 + } +} + +impl From for BigInt { + #[inline] + fn from(val: u64) -> BigInt { + todo!() + //let mut repr = Self::default(); + //repr.0[0] = val; + //repr + } +} + +impl From for BigInt { + #[inline] + fn from(val: u32) -> BigInt { + let mut repr = Self::default(); + repr.0[0] = u32::from(val); + repr + } +} + +impl From for BigInt { + #[inline] + fn from(val: u16) -> BigInt { + let mut repr = Self::default(); + repr.0[0] = u32::from(val); + repr + } +} + +impl From for BigInt { + #[inline] + fn from(val: u8) -> BigInt { + let mut repr = Self::default(); + repr.0[0] = u32::from(val); + repr + } +} + +impl TryFrom for BigInt { + type Error = (); + + /// Returns `Err(())` if the bit size of `val` is more than `N * 64`. + #[inline] + fn try_from(val: num_bigint::BigUint) -> Result, Self::Error> { + todo!() + //let bytes = val.to_bytes_le(); + + //if bytes.len() > N * 8 { + // Err(()) + //} else { + // let mut limbs = [0u64; N]; + + // bytes + // .chunks(8) + // .into_iter() + // .enumerate() + // .for_each(|(i, chunk)| { + // let mut chunk_padded = [0u8; 8]; + // chunk_padded[..chunk.len()].copy_from_slice(chunk); + // limbs[i] = u64::from_le_bytes(chunk_padded) + // }); + + // Ok(Self(limbs)) + //} + } +} + +impl From> for BigUint { + #[inline] + fn from(val: BigInt) -> num_bigint::BigUint { + BigUint::from_bytes_le(&val.to_bytes_le()) + } +} + impl Default for BigInt { fn default() -> Self { Self([0u32; N]) @@ -50,3 +535,44 @@ impl CanonicalDeserialize for BigInt { )?)) } } + +impl Ord for BigInt { + #[inline] + fn cmp(&self, other: &Self) -> ::core::cmp::Ordering { + use core::cmp::Ordering; + for i in 0..9 { + let a = &self.0[9 - i - 1]; + let b = &other.0[9 - i - 1]; + if a < b { + return Ordering::Less; + } else if a > b { + return Ordering::Greater; + } + } + Ordering::Equal + } +} + +impl PartialOrd for BigInt { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl Display for BigInt { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", BigUint::from(*self)) + } +} + +impl Distribution> for Standard { + fn sample(&self, rng: &mut R) -> BigInt { + todo!() + //let mut res = [0u64; N]; + //for item in res.iter_mut() { + // *item = rng.gen(); + //} + //BigInt::(res) + } +} diff --git a/curves/src/pasta/wasm_friendly/minimal_field.rs b/curves/src/pasta/wasm_friendly/minimal_field.rs index 367b82b7406..17379453a4b 100644 --- a/curves/src/pasta/wasm_friendly/minimal_field.rs +++ b/curves/src/pasta/wasm_friendly/minimal_field.rs @@ -37,8 +37,8 @@ pub trait MinimalField: } } -impl MinimalField for F { - fn square_in_place(&mut self) -> &mut Self { - self.square_in_place() - } -} +//impl MinimalField for F { +// fn square_in_place(&mut self) -> &mut Self { +// self.square_in_place() +// } +//} diff --git a/curves/src/pasta/wasm_friendly/mod.rs b/curves/src/pasta/wasm_friendly/mod.rs index 15800465ec9..2e09509272e 100644 --- a/curves/src/pasta/wasm_friendly/mod.rs +++ b/curves/src/pasta/wasm_friendly/mod.rs @@ -7,6 +7,8 @@ pub use minimal_field::MinimalField; pub mod wasm_fp; pub use wasm_fp::Fp; +//pub mod wasm_fp_ported; + pub mod backend9; pub mod pasta; -pub use pasta::Fp9; +pub use pasta::{Fp9,Fq9}; diff --git a/curves/src/pasta/wasm_friendly/pasta.rs b/curves/src/pasta/wasm_friendly/pasta.rs index f6cdb802b8d..f50c125c098 100644 --- a/curves/src/pasta/wasm_friendly/pasta.rs +++ b/curves/src/pasta/wasm_friendly/pasta.rs @@ -1,5 +1,5 @@ use super::{backend9, wasm_fp}; -use crate::pasta::Fp; +use crate::pasta::{Fp,Fq}; use ark_ff::PrimeField; pub struct Fp9Parameters; @@ -18,6 +18,7 @@ impl backend9::FpConstants for Fp9Parameters { ]; const MINV: u64 = 0x1fffffff; } + pub type Fp9 = wasm_fp::Fp; impl Fp9 { @@ -31,3 +32,36 @@ impl From for Fp9 { Fp9::from_fp(fp) } } + + +pub struct Fq9Parameters; + +impl backend9::FpConstants for Fq9Parameters { + // FIXME @volhovm these are all STUBS and BROKEN, and are for fP9, not fQ9 + const MODULUS: [u32; 9] = [ + 0x1, 0x9698768, 0x133e46e6, 0xd31f812, 0x224, 0x0, 0x0, 0x0, 0x400000, + ]; + const R: [u32; 9] = [ + 0x1fffff81, 0x14a5d367, 0x141ad3c0, 0x1435eec5, 0x1ffeefef, 0x1fffffff, 0x1fffffff, + 0x1fffffff, 0x3fffff, + ]; + const R2: [u32; 9] = [ + 0x3b6a, 0x19c10910, 0x1a6a0188, 0x12a4fd88, 0x634b36d, 0x178792ba, 0x7797a99, 0x1dce5b8a, + 0x3506bd, + ]; + const MINV: u64 = 0x1fffffff; +} + +pub type Fq9 = wasm_fp::Fp; + +impl Fq9 { + pub fn from_fq(fp: Fq) -> Self { + backend9::from_bigint_unsafe(super::BigInt(backend9::from_64x4(fp.into_bigint().0))) + } +} + +impl From for Fq9 { + fn from(fp: Fq) -> Self { + Fq9::from_fq(fp) + } +} diff --git a/curves/src/pasta/wasm_friendly/wasm_fp.rs b/curves/src/pasta/wasm_friendly/wasm_fp.rs index 7c0d45d34ac..0ea549a485a 100644 --- a/curves/src/pasta/wasm_friendly/wasm_fp.rs +++ b/curves/src/pasta/wasm_friendly/wasm_fp.rs @@ -4,16 +4,19 @@ * Most of this code was copied over from ark_ff::Fp */ use crate::pasta::wasm_friendly::bigint32::BigInt; -use ark_ff::{One, Zero}; +use ark_ff::{FftField, Field, One, PrimeField, Zero}; use ark_serialize::{ - CanonicalDeserialize, CanonicalSerialize, Compress, Read, SerializationError, Valid, Validate, - Write, + CanonicalDeserialize, CanonicalDeserializeWithFlags, CanonicalSerialize, + CanonicalSerializeWithFlags, Compress, Flags, Read, SerializationError, Valid, Validate, Write, }; use derivative::Derivative; use num_bigint::BigUint; use std::{ + cmp::Ordering, + iter::{Iterator, Product, Sum}, marker::PhantomData, - ops::{Add, AddAssign, Mul, MulAssign}, + ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}, + str::FromStr, }; use super::minimal_field::MinimalField; @@ -59,6 +62,8 @@ impl, const N: usize> Clone for Fp { } } +impl, const N: usize> zeroize::DefaultIsZeroes for Fp {} + impl, const N: usize> Fp { pub fn new(bigint: BigInt) -> Self { Fp(bigint, Default::default()) @@ -91,6 +96,12 @@ impl, const N: usize> From> for Fp { } } +impl, const N: usize> Into> for Fp { + fn into(self) -> BigInt { + Fp::into_bigint(self) + } +} + impl, const N: usize> From<[u32; N]> for Fp { fn from(val: [u32; N]) -> Self { Fp::from_bigint(BigInt(val)).unwrap() @@ -108,7 +119,22 @@ impl, const N: usize> MinimalField for Fp { } } -// add, zero +// add, zero, neg + +impl, const N: usize> Neg for Fp { + type Output = Self; + + #[must_use] + fn neg(self) -> Self { + if !self.is_zero() { + let mut tmp = P::MODULUS; + tmp.sub_noborrow(&self.0); + Fp(tmp, PhantomData) + } else { + self + } + } +} impl, const N: usize> Zero for Fp { #[inline] @@ -128,6 +154,21 @@ impl<'a, P: FpBackend, const N: usize> AddAssign<&'a Self> for Fp { P::add_assign(self, other) } } + +impl<'a, P: FpBackend, const N: usize> AddAssign<&'a mut Self> for Fp { + #[inline] + fn add_assign(&mut self, other: &mut Self) { + P::add_assign(self, other) + } +} + +impl, const N: usize> AddAssign for Fp { + #[inline] + fn add_assign(&mut self, other: Self) { + P::add_assign(self, &other) + } +} + impl, const N: usize> Add for Fp { type Output = Self; @@ -137,6 +178,7 @@ impl, const N: usize> Add for Fp { self } } + impl<'a, P: FpBackend, const N: usize> Add<&'a Fp> for Fp { type Output = Self; @@ -147,7 +189,19 @@ impl<'a, P: FpBackend, const N: usize> Add<&'a Fp> for Fp { } } -// mul, one +impl<'a, P: FpBackend, const N: usize> Add<&'a mut Fp> for Fp { + type Output = Self; + + #[inline] + fn add(mut self, other: &mut Self) -> Self { + self.add_assign(other); + self + } +} + +//////////////////////////////////////////////////////////////////////////// +// Mul, one +//////////////////////////////////////////////////////////////////////////// impl, const N: usize> One for Fp { #[inline] @@ -160,12 +214,28 @@ impl, const N: usize> One for Fp { *self == Self::one() } } + impl<'a, P: FpBackend, const N: usize> MulAssign<&'a Self> for Fp { #[inline] fn mul_assign(&mut self, other: &Self) { P::mul_assign(self, other) } } + +impl<'a, P: FpBackend, const N: usize> MulAssign<&'a mut Self> for Fp { + #[inline] + fn mul_assign(&mut self, other: &mut Self) { + P::mul_assign(self, other) + } +} + +impl, const N: usize> MulAssign for Fp { + #[inline] + fn mul_assign(&mut self, other: Self) { + P::mul_assign(self, &other) + } +} + impl, const N: usize> Mul for Fp { type Output = Self; @@ -175,6 +245,7 @@ impl, const N: usize> Mul for Fp { self } } + impl<'a, P: FpBackend, const N: usize> Mul<&'a Fp> for Fp { type Output = Self; @@ -185,7 +256,42 @@ impl<'a, P: FpBackend, const N: usize> Mul<&'a Fp> for Fp { } } -// (de)serialization +impl<'a, P: FpBackend, const N: usize> Mul<&'a mut Fp> for Fp { + type Output = Self; + + #[inline] + fn mul(mut self, other: &mut Self) -> Self { + self.mul_assign(other); + self + } +} + +//////////////////////////////////////////////////////////////////////////// +// (De)Serialization +//////////////////////////////////////////////////////////////////////////// + +impl, const N: usize> CanonicalSerializeWithFlags for Fp { + fn serialize_with_flags( + &self, + mut writer: W, + flags: F, + ) -> Result<(), SerializationError> { + todo!() + //if F::BIT_SIZE > 8 { + // return Err(SerializationError::NotEnoughSpace); + //} + //let output_byte_size = buffer_byte_size(C::MODULUS_BITS as usize + F::BIT_SIZE); + //let mut bytes = [0u8; 4 * 8 + 1]; + //self.write(&mut bytes[..4 * 8])?; + //bytes[output_byte_size - 1] |= flags.u8_bitmask(); + //writer.write_all(&bytes[..output_byte_size])?; + //Ok(()) + } + fn serialized_size_with_flags(&self) -> usize { + todo!() + // buffer_byte_size(P::MODULUS_BITS as usize + F::BIT_SIZE) + } +} impl, const N: usize> CanonicalSerialize for Fp { #[inline] @@ -209,6 +315,23 @@ impl, const N: usize> Valid for Fp { } } +impl, const N: usize> CanonicalDeserializeWithFlags for Fp { + fn deserialize_with_flags( + mut reader: R, + ) -> Result<(Self, F), SerializationError> { + todo!() + //if F::BIT_SIZE > 8 { + // return Err(SerializationError::NotEnoughSpace); + //} + //let output_byte_size = buffer_byte_size(C::MODULUS_BITS as usize + F::BIT_SIZE); + //let mut masked_bytes = [0; 4 * 8 + 1]; + //reader.read_exact(&mut masked_bytes[..output_byte_size])?; + //let flags = F::from_u8_remove_flags(&mut masked_bytes[output_byte_size - 1]) + // .ok_or(SerializationError::UnexpectedFlags)?; + //Ok((Self::read(&masked_bytes[..])?, flags)) + } +} + impl, const N: usize> CanonicalDeserialize for Fp { fn deserialize_with_mode( reader: R, @@ -220,8 +343,117 @@ impl, const N: usize> CanonicalDeserialize for Fp { } } +//////////////////////////////////////////////////////////////////////////// +// div +//////////////////////////////////////////////////////////////////////////// + +impl<'a, P: FpBackend, const N: usize> DivAssign<&'a Self> for Fp { + fn div_assign(&mut self, other: &'a Self) { + self.mul_assign(&other.inverse().unwrap()); + } +} + +impl<'a, P: FpBackend, const N: usize> DivAssign<&'a mut Self> for Fp { + fn div_assign(&mut self, other: &'a mut Self) { + self.mul_assign(&other.inverse().unwrap()); + } +} + +impl, const N: usize> DivAssign for Fp { + fn div_assign(&mut self, other: Self) { + self.div_assign(&other) + } +} + +impl, const N: usize> Div for Fp { + type Output = Self; + fn div(mut self, other: Self) -> Self { + self.div_assign(other); + self + } +} + +impl<'a, P: FpBackend, const N: usize> Div<&'a Self> for Fp { + type Output = Self; + fn div(mut self, other: &'a Self) -> Self { + self.div_assign(other); + self + } +} + +impl<'a, P: FpBackend, const N: usize> Div<&'a mut Self> for Fp { + type Output = Self; + fn div(mut self, other: &'a mut Self) -> Self { + self.div_assign(other); + self + } +} + +//////////////////////////////////////////////////////////////////////////// +// sub +//////////////////////////////////////////////////////////////////////////// + +impl, const N: usize> SubAssign for Fp { + fn sub_assign(&mut self, other: Self) { + if other.0 > self.0 { + self.0.add_nocarry(&P::MODULUS); + } + self.0.sub_noborrow(&other.0); + } +} + +impl<'a, P: FpBackend, const N: usize> SubAssign<&'a Self> for Fp { + fn sub_assign(&mut self, other: &'a Self) { + if other.0 > self.0 { + self.0.add_nocarry(&P::MODULUS); + } + self.0.sub_noborrow(&other.0); + } +} + +impl<'a, P: FpBackend, const N: usize> SubAssign<&'a mut Self> for Fp { + fn sub_assign(&mut self, other: &'a mut Self) { + if other.0 > self.0 { + self.0.add_nocarry(&P::MODULUS); + } + self.0.sub_noborrow(&other.0); + } +} + +impl, const N: usize> Sub for Fp { + type Output = Self; + fn sub(mut self, other: Self) -> Self { + self.sub_assign(other); + self + } +} + +impl<'a, P: FpBackend, const N: usize> Sub<&'a Self> for Fp { + type Output = Self; + fn sub(mut self, other: &'a Self) -> Self { + self.sub_assign(other); + self + } +} + +impl<'a, P: FpBackend, const N: usize> Sub<&'a mut Self> for Fp { + type Output = Self; + fn sub(mut self, other: &'a mut Self) -> Self { + self.sub_assign(other); + self + } +} + // display +impl, const N: usize> From for Fp { + #[inline] + fn from(val: BigUint) -> Fp { + Self::from_le_bytes_mod_order(&val.to_bytes_le()) + //BigUint::from_bytes_le(&val.to_bytes_le()) + } +} + impl, const N: usize> From> for BigUint { #[inline] fn from(val: Fp) -> BigUint { @@ -234,3 +466,550 @@ impl, const N: usize> std::fmt::Display for Fp { BigUint::from(*self).fmt(f) } } + +impl, const N: usize> FromStr for Fp { + type Err = (); + /// Interpret a string of numbers as a (congruent) prime field element. + /// Does not accept unnecessary leading zeroes or a blank string. + fn from_str(s: &str) -> Result { + todo!() + //if s.is_empty() { + // return Err(()); + //} + //if s == "0" { + // return Ok(Self::zero()); + //} + //let mut res = Self::zero(); + //let ten = Self::try_from(BigInt::from(10u32)).unwrap(); + //let mut first_digit = true; + //for c in s.chars() { + // match c.to_digit(10) { + // Some(c) => { + // if first_digit { + // if c == 0 { + // return Err(()); + // } + // first_digit = false; + // } + // res.mul_assign(&ten); + // let digit = Self::from(u64::from(c)); + // res.add_assign(&digit); + // } + // None => { + // return Err(()); + // } + // } + //} + //if !res.is_valid() { + // Err(()) + //} else { + // Ok(res) + //} + } +} + +//////////////////////////////////////////////////////////////////////////// +// distribution +//////////////////////////////////////////////////////////////////////////// + +impl, const N: usize> ark_std::rand::distributions::Distribution> + for ark_std::rand::distributions::Standard +{ + #[inline] + fn sample(&self, rng: &mut R) -> Fp { + todo!() + //loop { + // if !(C::REPR_SHAVE_BITS <= 64) { + // panic!("assertion failed: P::REPR_SHAVE_BITS <= 64") + // } + // let mask = if C::REPR_SHAVE_BITS == 64 { + // 0 + // } else { + // core::u64::MAX >> C::REPR_SHAVE_BITS + // }; + // let mut tmp: [u64; 4] = rng.sample(ark_std::rand::distributions::Standard); + // tmp.as_mut().last_mut().map(|val| *val &= mask); + // let is_fp = match C::T.0[0] { + // 0x192d30ed => true, + // 0xc46eb21 => false, + // _ => panic!(), + // }; + // const FP_MODULUS: [u64; 4] = [ + // 0x992d30ed00000001, + // 0x224698fc094cf91b, + // 0x0, + // 0x4000000000000000, + // ]; + // const FQ_MODULUS: [u64; 4] = [ + // 0x8c46eb2100000001, + // 0x224698fc0994a8dd, + // 0x0, + // 0x4000000000000000, + // ]; + // let (modulus, inv) = if is_fp { + // (FP_MODULUS, 11037532056220336127) + // } else { + // (FQ_MODULUS, 10108024940646105087) + // }; + // let is_valid = || { + // for (random, modulus) in tmp.iter().copied().zip(modulus).rev() { + // if random > modulus { + // return false; + // } else if random < modulus { + // return true; + // } + // } + // false + // }; + // if !is_valid() { + // continue; + // } + // let mut r = tmp; + // // Montgomery Reduction + // for i in 0..4 { + // let k = r[i].wrapping_mul(inv); + // let mut carry = 0; + // mac_with_carry!(r[i], k, modulus[0] as _, &mut carry); + // for j in 1..4 { + // r[(j + i) % 4] = mac_with_carry!(r[(j + i) % 4], k, modulus[j], &mut carry); + // } + // r[i % 4] = carry; + // } + // tmp = r; + // return Fp256::::from_repr(BigInteger256::from_64x4(tmp)).unwrap(); + //} + } +} + +//////////////////////////////////////////////////////////////////////////// +// Misc +//////////////////////////////////////////////////////////////////////////// + +impl, const N: usize> Sum for Fp { + fn sum>(iter: I) -> Self { + iter.fold(Self::zero(), core::ops::Add::add) + } +} + +impl, const N: usize> Product for Fp { + fn product>(iter: I) -> Self { + iter.fold(Self::one(), Mul::mul) + } +} + +impl<'a, P: FpBackend, const N: usize> Sum<&'a Self> for Fp { + fn sum>(iter: I) -> Self { + iter.fold(Self::zero(), core::ops::Add::add) + } +} + +impl<'a, P: FpBackend, const N: usize> Product<&'a Self> for Fp { + fn product>(iter: I) -> Self { + iter.fold(Self::one(), Mul::mul) + } +} + +impl, const N: usize> Ord for Fp { + #[inline(always)] + fn cmp(&self, other: &Self) -> Ordering { + self.into_bigint().cmp(&other.into_bigint()) + } +} + +impl, const N: usize> PartialOrd for Fp { + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl, const N: usize> From for Fp { + fn from(other: u128) -> Self { + todo!() + } +} + +impl, const N: usize> From for Fp { + fn from(other: u64) -> Self { + todo!() + } +} + +impl, const N: usize> From for Fp { + fn from(other: u32) -> Self { + todo!() + } +} + +impl, const N: usize> From for Fp { + fn from(other: u16) -> Self { + todo!() + } +} + +impl, const N: usize> From for Fp { + fn from(other: u8) -> Self { + todo!() + } +} + +impl, const N: usize> From for Fp { + fn from(other: bool) -> Self { + todo!() + } +} + +//////////////////////////////////////////////////////////////////////////// +// Field +//////////////////////////////////////////////////////////////////////////// + +// TODO one needs to implement these traits: +// +// + Neg +// + UniformRand +// + Zeroize +// + CanonicalSerializeWithFlags +// + CanonicalDeserializeWithFlags +// + Sub +// + Div +// + AddAssign +// + SubAssign +// + MulAssign +// + DivAssign +// + for<'a> Sub<&'a Self, Output = Self> +// + for<'a> Div<&'a Self, Output = Self> +// + for<'a> SubAssign<&'a Self> +// + for<'a> DivAssign<&'a Self> +// + for<'a> Add<&'a mut Self, Output = Self> +// + for<'a> Sub<&'a mut Self, Output = Self> +// + for<'a> Mul<&'a mut Self, Output = Self> +// + for<'a> Div<&'a mut Self, Output = Self> +// + for<'a> AddAssign<&'a mut Self> +// + for<'a> SubAssign<&'a mut Self> +// + for<'a> MulAssign<&'a mut Self> +// + for<'a> DivAssign<&'a mut Self> +// + core::iter::Sum +// + for<'a> core::iter::Sum<&'a Self> +// + core::iter::Product +// + for<'a> core::iter::Product<&'a Self> + +impl, const N: usize> PrimeField for Fp { + type BigInt = BigInt; + const MODULUS: Self::BigInt = P::MODULUS; + const MODULUS_MINUS_ONE_DIV_TWO: Self::BigInt = P::MODULUS.divide_by_2_round_down(); + const MODULUS_BIT_SIZE: u32 = P::MODULUS.const_num_bits(); + const TRACE: Self::BigInt = P::MODULUS.two_adic_coefficient(); + const TRACE_MINUS_ONE_DIV_TWO: Self::BigInt = Self::TRACE.divide_by_2_round_down(); + + #[inline] + fn from_bigint(r: BigInt) -> Option { + todo!() + //P::from_bigint(r) + } + + fn into_bigint(self) -> BigInt { + todo!() + //P::into_bigint(self) + } +} + +impl, const N: usize> FftField for Fp { + const GENERATOR: Self = Fp(P::MODULUS, PhantomData); + const TWO_ADICITY: u32 = 0; // FIXME!!! // P::TWO_ADICITY; + const TWO_ADIC_ROOT_OF_UNITY: Self = Fp(P::ZERO, PhantomData); // FIXME //P::TWO_ADIC_ROOT_OF_UNITY; + const SMALL_SUBGROUP_BASE: Option = None; //FIXME!! // P::SMALL_SUBGROUP_BASE; + const SMALL_SUBGROUP_BASE_ADICITY: Option = None; // FIXME! // P::SMALL_SUBGROUP_BASE_ADICITY; + const LARGE_SUBGROUP_ROOT_OF_UNITY: Option = None; // FIXME! //P::LARGE_SUBGROUP_ROOT_OF_UNITY; +} + +impl, const N: usize> Field for Fp { + type BasePrimeField = Self; + type BasePrimeFieldIter = core::iter::Once; + + const SQRT_PRECOMP: Option> = None; // FIXME //P::SQRT_PRECOMP; + const ZERO: Self = Fp(P::ZERO, PhantomData); + const ONE: Self = Fp(P::ONE, PhantomData); + + fn extension_degree() -> u64 { + 1 + } + + fn from_base_prime_field(elem: Self::BasePrimeField) -> Self { + elem + } + + fn to_base_prime_field_elements(&self) -> Self::BasePrimeFieldIter { + core::iter::once(*self) + } + + fn from_base_prime_field_elems(elems: &[Self::BasePrimeField]) -> Option { + if elems.len() != (Self::extension_degree() as usize) { + return None; + } + Some(elems[0]) + } + + #[inline] + fn double(&self) -> Self { + let mut temp = *self; + temp.double_in_place(); + temp + } + + #[inline] + fn double_in_place(&mut self) -> &mut Self { + todo!() + //P::double_in_place(self); + //self + } + + #[inline] + fn neg_in_place(&mut self) -> &mut Self { + todo!() + //P::neg_in_place(self); + //self + } + + #[inline] + fn characteristic() -> &'static [u64] { + P::MODULUS.as_ref() + } + + #[inline] + fn sum_of_products(a: &[Self; T], b: &[Self; T]) -> Self { + todo!() + //P::sum_of_products(a, b) + } + + #[inline] + fn from_random_bytes_with_flags(bytes: &[u8]) -> Option<(Self, F)> { + todo!() + //if F::BIT_SIZE > 8 { + // None + //} else { + // let shave_bits = Self::num_bits_to_shave(); + // let mut result_bytes = crate::const_helpers::SerBuffer::::zeroed(); + // // Copy the input into a temporary buffer. + // result_bytes.copy_from_u8_slice(bytes); + // // This mask retains everything in the last limb + // // that is below `P::MODULUS_BIT_SIZE`. + // let last_limb_mask = + // (u64::MAX.checked_shr(shave_bits as u32).unwrap_or(0)).to_le_bytes(); + // let mut last_bytes_mask = [0u8; 9]; + // last_bytes_mask[..8].copy_from_slice(&last_limb_mask); + + // // Length of the buffer containing the field element and the flag. + // let output_byte_size = buffer_byte_size(Self::MODULUS_BIT_SIZE as usize + F::BIT_SIZE); + // // Location of the flag is the last byte of the serialized + // // form of the field element. + // let flag_location = output_byte_size - 1; + + // // At which byte is the flag located in the last limb? + // let flag_location_in_last_limb = flag_location.saturating_sub(8 * (N - 1)); + + // // Take all but the last 9 bytes. + // let last_bytes = result_bytes.last_n_plus_1_bytes_mut(); + + // // The mask only has the last `F::BIT_SIZE` bits set + // let flags_mask = u8::MAX.checked_shl(8 - (F::BIT_SIZE as u32)).unwrap_or(0); + + // // Mask away the remaining bytes, and try to reconstruct the + // // flag + // let mut flags: u8 = 0; + // for (i, (b, m)) in last_bytes.zip(&last_bytes_mask).enumerate() { + // if i == flag_location_in_last_limb { + // flags = *b & flags_mask + // } + // *b &= m; + // } + // Self::deserialize_compressed(&result_bytes.as_slice()[..(N * 8)]) + // .ok() + // .and_then(|f| F::from_u8(flags).map(|flag| (f, flag))) + //} + } + + #[inline] + fn square(&self) -> Self { + todo!() + //let mut temp = *self; + //temp.square_in_place(); + //temp + } + + fn square_in_place(&mut self) -> &mut Self { + todo!() + //P::square_in_place(self); + //self + } + + #[inline] + fn inverse(&self) -> Option { + todo!() + //P::inverse(self) + } + + fn inverse_in_place(&mut self) -> Option<&mut Self> { + if let Some(inverse) = self.inverse() { + *self = inverse; + Some(self) + } else { + None + } + } + + /// The Frobenius map has no effect in a prime field. + #[inline] + fn frobenius_map_in_place(&mut self, _: usize) {} + + #[inline] + fn legendre(&self) -> ark_ff::LegendreSymbol { + todo!() + //use crate::fields::LegendreSymbol::*; + + //// s = self^((MODULUS - 1) // 2) + //let s = self.pow(Self::MODULUS_MINUS_ONE_DIV_TWO); + //if s.is_zero() { + // Zero + //} else if s.is_one() { + // QuadraticResidue + //} else { + // QuadraticNonResidue + //} + } +} + +//impl, const N: usize> Field for Fp { +// type BasePrimeField = Self; +// fn extension_degree() -> u64 { +// 1 +// } +// fn from_base_prime_field_elems(elems: &[Self::BasePrimeField]) -> Option { +// todo!() +// //if elems.len() != (Self::extension_degree() as usize) { +// // return None; +// //} +// //Some(elems[0]) +// } +// #[inline] +// fn double(&self) -> Self { +// todo!() +// //let mut temp = *self; +// //temp.double_in_place(); +// //temp +// } +// #[inline] +// fn double_in_place(&mut self) -> &mut Self { +// todo!() +// //self.0.mul2(); +// //self.reduce(); +// //self +// } +// #[inline] +// fn characteristic() -> [u64; 4] { +// todo!() +// //C::MODULUS.to_64x4() +// } +// #[inline] +// fn from_random_bytes_with_flags(bytes: &[u8]) -> Option<(Self, F)> { +// todo!() +// //if F::BIT_SIZE > 8 { +// // return None; +// //} else { +// // let mut result_bytes = [0u8; 4 * 8 + 1]; +// // result_bytes +// // .iter_mut() +// // .zip(bytes) +// // .for_each(|(result, input)| { +// // *result = *input; +// // }); +// // let last_limb_mask = (u64::MAX >> C::REPR_SHAVE_BITS).to_le_bytes(); +// // let mut last_bytes_mask = [0u8; 9]; +// // last_bytes_mask[..8].copy_from_slice(&last_limb_mask); +// // let output_byte_size = buffer_byte_size(C::MODULUS_BITS as usize + F::BIT_SIZE); +// // let flag_location = output_byte_size - 1; +// // let flag_location_in_last_limb = flag_location - (8 * (4 - 1)); +// // let last_bytes = &mut result_bytes[8 * (4 - 1)..]; +// // let flags_mask = u8::MAX.checked_shl(8 - (F::BIT_SIZE as u32)).unwrap_or(0); +// // let mut flags: u8 = 0; +// // for (i, (b, m)) in last_bytes.iter_mut().zip(&last_bytes_mask).enumerate() { +// // if i == flag_location_in_last_limb { +// // flags = *b & flags_mask; +// // } +// // *b &= m; +// // } +// // Self::deserialize(&result_bytes[..(4 * 8)]) +// // .ok() +// // .and_then(|f| F::from_u8(flags).map(|flag| (f, flag))) +// //} +// } +// #[inline(always)] +// fn square(&self) -> Self { +// todo!() +// //let mut temp = self.clone(); +// //temp.square_in_place(); +// //temp +// } +// #[inline(always)] +// fn square_in_place(&mut self) -> &mut Self { +// todo!() +// //self.const_square(); +// //self +// } +// #[inline] +// fn inverse(&self) -> Option { +// todo!() +// //if self.is_zero() { +// // None +// //} else { +// // let one = BigInteger256::from(1); +// // let mut u = self.0; +// // let mut v = C::MODULUS; +// // let mut b = Self(C::R2, PhantomData); +// // let mut c = Self::zero(); +// // while u != one && v != one { +// // while u.is_even() { +// // u.div2(); +// // if b.0.is_even() { +// // b.0.div2(); +// // } else { +// // b.0.add_nocarry(&C::MODULUS); +// // b.0.div2(); +// // } +// // } +// // while v.is_even() { +// // v.div2(); +// // if c.0.is_even() { +// // c.0.div2(); +// // } else { +// // c.0.add_nocarry(&C::MODULUS); +// // c.0.div2(); +// // } +// // } +// // if v < u { +// // u.sub_noborrow(&v); +// // b.sub_assign(&c); +// // } else { +// // v.sub_noborrow(&u); +// // c.sub_assign(&b); +// // } +// // } +// // if u == one { +// // Some(b) +// // } else { +// // Some(c) +// // } +// //} +// } +// fn inverse_in_place(&mut self) -> Option<&mut Self> { +// if let Some(inverse) = self.inverse() { +// *self = inverse; +// Some(self) +// } else { +// None +// } +// } +// #[inline] +// fn frobenius_map(&mut self, _: usize) { +// todo!() +// } +//} diff --git a/curves/src/pasta/wasm_friendly/wasm_fp_ported.rs b/curves/src/pasta/wasm_friendly/wasm_fp_ported.rs new file mode 100644 index 00000000000..5a879384c24 --- /dev/null +++ b/curves/src/pasta/wasm_friendly/wasm_fp_ported.rs @@ -0,0 +1,1089 @@ +use crate::{ + biginteger::{ + BigInteger as _BigInteger, webnode::BigInteger256, + }, + bytes::{FromBytes, ToBytes}, + fields::{FftField, Field, LegendreSymbol, PrimeField, SquareRootField}, +}; +use ark_serialize::*; +use ark_std::{ + cmp::{Ord, Ordering, PartialOrd}, + fmt::{Display, Formatter, Result as FmtResult}, + io::{Read, Result as IoResult, Write}, + marker::PhantomData, + ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}, + str::FromStr, One, Zero, +}; + +impl Into for Fp256 { + fn into(self) -> BigInteger256 { + self.into_repr() + } +} +impl core::convert::TryFrom for Fp256 { + type Error = crate::fields::arithmetic::InvalidBigInt; + + /// Converts `Self::BigInteger` into `Self` + /// + /// This method returns an error if `int` is larger than `P::MODULUS`. + fn try_from(int: BigInteger256) -> Result { + Self::from_repr(int).ok_or(crate::fields::arithmetic::InvalidBigInt) + } +} + +const SHIFT: u32 = 29; +const MASK: u32 = (1 << SHIFT) - 1; + +const SHIFT64: u64 = SHIFT as u64; +const MASK64: u64 = MASK as u64; + +pub const fn from_64x4(pa: [u64; 4]) -> [u32; 9] { + let mut p = [0u32; 9]; + p[0] = (pa[0] & MASK64) as u32; + p[1] = ((pa[0] >> 29) & MASK64) as u32; + p[2] = (((pa[0] >> 58) | (pa[1] << 6)) & MASK64) as u32; + p[3] = ((pa[1] >> 23) & MASK64) as u32; + p[4] = (((pa[1] >> 52) | (pa[2] << 12)) & MASK64) as u32; + p[5] = ((pa[2] >> 17) & MASK64) as u32; + p[6] = (((pa[2] >> 46) | (pa[3] << 18)) & MASK64) as u32; + p[7] = ((pa[3] >> 11) & MASK64) as u32; + p[8] = (pa[3] >> 40) as u32; + p +} +pub const fn to_64x4(pa: [u32; 9]) -> [u64; 4] { + let mut p = [0u64; 4]; + p[0] = pa[0] as u64; + p[0] |= (pa[1] as u64) << 29; + p[0] |= (pa[2] as u64) << 58; + p[1] = (pa[2] as u64) >> 6; + p[1] |= (pa[3] as u64) << 23; + p[1] |= (pa[4] as u64) << 52; + p[2] = (pa[4] as u64) >> 12; + p[2] |= (pa[5] as u64) << 17; + p[2] |= (pa[6] as u64) << 46; + p[3] = (pa[6] as u64) >> 18; + p[3] |= (pa[7] as u64) << 11; + p[3] |= (pa[8] as u64) << 40; + p +} + +const fn gte_modulus(x: &BigInteger256) -> bool { + let mut i = Fp256::::NLIMBS - 1; + loop { + // don't fix warning -- that makes it 15% slower! + #[allow(clippy::comparison_chain)] + if x.0[i] > C::MODULUS.0[i] { + return true; + } else if x.0[i] < C::MODULUS.0[i] { + return false; + } + if i == 0 { + break; + } + i -= 1; + } + true +} + +#[ark_ff_asm::unroll_for_loops] +#[inline(always)] +const fn conditional_reduce(x: &mut BigInteger256) { + if gte_modulus::(&x) { + for i in 0..9 { + x.0[i] = x.0[i].wrapping_sub(C::MODULUS.0[i]); + } + for i in 1..9 { + x.0[i] += ((x.0[i - 1] as i32) >> SHIFT) as u32; + } + for i in 0..8 { + x.0[i] &= MASK; + } + } +} + +#[ark_ff_asm::unroll_for_loops] +#[inline(always)] +fn add_assign(x: &mut BigInteger256, y: &BigInteger256) { + let y = &y.0; + let mut tmp: u32; + let mut carry: i32 = 0; + + for i in 0..9 { + tmp = x.0[i] + y[i] + (carry as u32); + carry = (tmp as i32) >> SHIFT; + x.0[i] = tmp & MASK; + } + + if gte_modulus::(x) { + carry = 0; + for i in 0..9 { + tmp = x.0[i].wrapping_sub(C::MODULUS.0[i]) + (carry as u32); + carry = (tmp as i32) >> SHIFT; + x.0[i] = tmp & MASK; + } + } +} + +#[derive(Clone, Copy, Default, Eq, PartialEq, Hash)] +pub struct Fp256 (pub BigInteger256, PhantomData); + +/// Note that this implementation of `Ord` compares field elements viewing +/// them as integers in the range 0, 1, ..., P::MODULUS - 1. However, other +/// implementations of `PrimeField` might choose a different ordering, and +/// as such, users should use this `Ord` for applications where +/// any ordering suffices (like in a BTreeMap), and not in applications +/// where a particular ordering is required. +impl Ord for Fp256

{ + #[inline(always)] + fn cmp(&self, other: &Self) -> Ordering { + self.into_repr().cmp(&other.into_repr()) + } +} +/// Note that this implementation of `PartialOrd` compares field elements viewing +/// them as integers in the range 0, 1, ..., `P::MODULUS` - 1. However, other +/// implementations of `PrimeField` might choose a different ordering, and +/// as such, users should use this `PartialOrd` for applications where +/// any ordering suffices (like in a BTreeMap), and not in applications +/// where a particular ordering is required. +impl PartialOrd for Fp256

{ + #[inline(always)] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Display for Fp256 { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + f.write_fmt(format_args!("{:?}", self.0)) + } +} + +impl ark_std::fmt::Debug for Fp256 { + fn fmt(&self, f: &mut ark_std::fmt::Formatter<'_>) -> ark_std::fmt::Result { + use crate::ark_std::string::ToString; + let r: BigInteger256 = self.into_repr(); + let bigint: num_bigint::BigUint = r.into(); + let s = bigint.to_string(); + + let name = match C::T.0[0] { + 0x192d30ed => "Fp", + 0xc46eb21 => "Fq", + _ => panic!(), + }; + + f.write_fmt(format_args!("{}({})", name, s)) + } +} + +impl Fp256 { + const NLIMBS: usize = 9; + + #[inline] + pub const fn new(element: BigInteger256) -> Self { + Self(element, PhantomData) + } + const fn const_is_zero(&self) -> bool { + let mut index = 0; + let mut is_zero = true; + while index < Self::NLIMBS { + is_zero &= self.0.0[index] == 0; + index += 1; + } + is_zero + } + const fn const_neg(self, modulus: BigInteger256) -> Self { + if !self.const_is_zero() { + Self::new(Self::sub_noborrow(&modulus, &self.0)) + } else { + self + } + } + + #[ark_ff_asm::unroll_for_loops] + #[allow(unused_assignments)] + const fn sub_noborrow(a: &BigInteger256, b: &BigInteger256) -> BigInteger256 { + /// Calculate a - b - borrow, returning the result and modifying + /// the borrow value. + macro_rules! sbb { + ($a:expr, $b:expr, &mut $borrow:expr$(,)?) => {{ + let tmp = (1u64 << 32) + ($a as u64) - ($b as u64) - ($borrow as u64); + $borrow = if tmp >> 32 == 0 { 1 } else { 0 }; + tmp as u32 + }}; + } + let mut a = *a; + let mut borrow = 0; + for i in 0..9 { + a.0[i] = sbb!(a.0[i], b.0[i], &mut borrow); + } + a + } + + /// Interpret a string of decimal numbers as a prime field element. + /// Does not accept unnecessary leading zeroes or a blank string. + /// For *internal* use only; please use the `field_new` macro instead + /// of this method + #[doc(hidden)] + pub const fn const_from_str( + limbs: &[u64], + is_positive: bool, + r2: BigInteger256, + modulus: BigInteger256, + inv: u64, + ) -> Self { + let repr = match limbs { + [a, b, c, d] => BigInteger256::from_64x4([*a, *b, *c, *d]), + [a, b, c] => BigInteger256::from_64x4([*a, *b, *c, 0]), + [a, b] => BigInteger256::from_64x4([*a, *b, 0, 0]), + [a] => BigInteger256::from_64x4([*a, 0, 0, 0]), + _ => panic!(), + }; + let res = Self::const_from_repr(repr, r2, modulus, inv as u32); + if is_positive { + res + } else { + res.const_neg(modulus) + } + } + + #[inline] + pub(crate) const fn const_from_repr( + repr: BigInteger256, + r2: BigInteger256, + modulus: BigInteger256, + inv: u32, + ) -> Self { + let mut r = Self::new(repr); + if r.const_is_zero() { + r + } else { + r.const_mul(&Fp256(r2, PhantomData), &modulus, inv); + r + } + } + + const U64_MODULUS: [u64; 9] = { + let mut modulus64 = [0u64; 9]; + let modulus = C::MODULUS; + let mut i = 0; + while i < 9 { + modulus64[i] = modulus.0[i] as u64; + i += 1; + } + modulus64 + }; + + /// Implementation based on https://github.com/o1-labs/proof-systems/pull/2638 + #[ark_ff_asm::unroll_for_loops] + #[inline(always)] + const fn const_mul_without_reduce(&mut self, other: &Self, _modulus: &BigInteger256, _inv: u32) { + let x = &mut self.0.0; + let y = &other.0.0; + + let mut y_local = [0u64; 9]; + for index in 0..9 { + y_local[index] = y[index] as u64; + } + + let mut xy = [0u64; 9]; + + for i in 0..9 { + let xi = x[i] as u64; + + let tmp = (xi * y_local[0]) + xy[0]; + let qi = (MASK64 + 1) - (tmp & MASK64); + let carry = (tmp + (qi * Self::U64_MODULUS[0])) >> SHIFT64; + + for j in 1..8 { + let did_carry = j == 1; + let mut xy_j = xy[j]; + if did_carry { + xy_j += carry; + } + xy[j - 1] = (xy_j + (xi * y_local[j])) + (qi * Self::U64_MODULUS[j]); + } + + let j = Self::NLIMBS - 1; + xy[j - 1] = (xi * y_local[j]) + (qi * Self::U64_MODULUS[j]); + } + + for j in 1..9 { + x[j - 1] = (xy[j - 1] as u32) & MASK; + xy[j] += xy[j - 1] >> SHIFT64; + } + x[Self::NLIMBS - 1] = xy[Self::NLIMBS - 1] as u32; + } + + #[inline(always)] + const fn const_mul(&mut self, other: &Self, modulus: &BigInteger256, inv: u32) { + self.const_mul_without_reduce(other, modulus, inv); + self.const_reduce(modulus); + } + + #[inline(always)] + const fn const_reduce(&mut self, _modulus: &BigInteger256) { + conditional_reduce::(&mut self.0); + } + + // don't fix warning -- that makes it 15% slower! + #[allow(clippy::comparison_chain)] + const fn const_is_valid(&self, _modulus: &BigInteger256) -> bool { + let mut i = Fp256::::NLIMBS - 1; + loop { + if self.0.0[i] > C::MODULUS.0[i] { + return false; + } else if self.0.0[i] < C::MODULUS.0[i] { + return true; + } + if i == 0 { + break; + } + i -= 1; + } + false + } + + /// Implementation based on https://github.com/o1-labs/proof-systems/pull/2638 + #[ark_ff_asm::unroll_for_loops] + #[inline(always)] + const fn const_square(&mut self) { + let mut x = [0u64; 9]; + for i in 0..9 { + x[i] = self.0.0[i] as u64; + } + let mut xy = [0u64; 9]; + for i in 0..9 { + let j = 0; + let tmp = if i == 0 { + x[i] * x[j] + } else { + ((x[i] * x[j]) << 1) + xy[j] + }; + let qi = (MASK64 + 1) - (tmp & MASK64); + let carry = (tmp + (qi * Self::U64_MODULUS[0])) >> SHIFT64; + for j in 1..8 { + let did_carry = j == 1; + let mut xy_j = xy[j]; + if did_carry { + xy_j += carry; + } + if j <= i { + let mut tmp = x[i] * x[j]; + if j < i { + tmp <<= 1; + } + xy_j += tmp; + } + xy[j - 1] = xy_j + (qi * Self::U64_MODULUS[j]); + } + let j = 8; + xy[j - 1] = if i == j { + (x[i] * x[j]) + (qi * Self::U64_MODULUS[j]) + } else { + qi * Self::U64_MODULUS[j] + }; + } + for j in 1..9 { + self.0.0[j - 1] = (xy[j - 1] as u32) & MASK; + xy[j] += xy[j - 1] >> SHIFT64; + } + self.0.0[9 - 1] = xy[9 - 1] as u32; + + self.const_reduce(&C::MODULUS); + } +} + +impl Fp256 { + pub(crate) fn is_valid(&self) -> bool { + self.const_is_valid(&C::MODULUS) + } + fn reduce(&mut self) { + self.const_reduce(&C::MODULUS); + } +} + +impl Zero for Fp256 { + fn zero() -> Self { + Self(BigInteger256([0; 9]), PhantomData) + } + fn is_zero(&self) -> bool { + self.0.0 == [0u32; 9] + } +} + +impl One for Fp256 { + fn one() -> Self { + Self(C::R, PhantomData) + } + fn is_one(&self) -> bool { + self.0 == C::R + } +} + +impl Neg for Fp256 { + type Output = Self; + #[must_use] + fn neg(self) -> Self { + if !self.is_zero() { + let mut tmp = C::MODULUS; + tmp.sub_noborrow(&self.0); + Fp256(tmp, PhantomData) + } else { + self + } + } +} +impl core::ops::DivAssign for Fp256 { + fn div_assign(&mut self, other: Self) { + self.div_assign(&other) + } +} +impl Add for Fp256 { + type Output = Self; + #[inline(always)] + fn add(mut self, other: Self) -> Self { + self.add_assign(other); + self + } +} +impl Sub for Fp256 { + type Output = Self; + fn sub(mut self, other: Self) -> Self { + self.sub_assign(other); + self + } +} +impl Div for Fp256 { + type Output = Self; + fn div(mut self, other: Self) -> Self { + self.div_assign(other); + self + } +} +impl core::ops::AddAssign for Fp256 { + #[inline(always)] + fn add_assign(&mut self, other: Self) { + add_assign::(&mut self.0, &other.0) + } +} +impl Mul for Fp256 { + type Output = Self; + #[inline(always)] + fn mul(mut self, other: Self) -> Self { + self.mul_assign(other); + self + } +} +impl core::ops::MulAssign for Fp256 { + #[inline(always)] + fn mul_assign(&mut self, other: Self) { + self.const_mul(&other, &C::MODULUS, C::INV as u32); + } +} +impl SubAssign for Fp256 { + fn sub_assign(&mut self, other: Self) { + if other.0 > self.0 { + self.0.add_nocarry(&C::MODULUS); + } + self.0.sub_noborrow(&other.0); + } +} +impl core::iter::Sum for Fp256 { + fn sum>(iter: I) -> Self { + iter.fold(Self::zero(), core::ops::Add::add) + } +} +impl core::iter::Product for Fp256 { + fn product>(iter: I) -> Self { + iter.fold(Self::one(), Mul::mul) + } +} + +impl<'a, C: Fp256Parameters> Div<&'a Self> for Fp256 { + type Output = Self; + fn div(mut self, other: &'a Self) -> Self { + self.div_assign(other); + self + } +} +impl<'a, C: Fp256Parameters> DivAssign<&'a Self> for Fp256 { + fn div_assign(&mut self, other: &'a Self) { + self.mul_assign(&other.inverse().unwrap()); + } +} +impl<'a, C: Fp256Parameters> SubAssign<&'a Self> for Fp256 { + fn sub_assign(&mut self, other: &'a Self) { + if other.0 > self.0 { + self.0.add_nocarry(&C::MODULUS); + } + self.0.sub_noborrow(&other.0); + } +} +impl<'a, C: Fp256Parameters> Sub<&'a Self> for Fp256 { + type Output = Self; + fn sub(mut self, other: &'a Self) -> Self { + self.sub_assign(other); + self + } +} +impl<'a, C: Fp256Parameters> core::iter::Product<&'a Self> for Fp256 { + fn product>(iter: I) -> Self { + iter.fold(Self::one(), Mul::mul) + } +} +impl<'a, C: Fp256Parameters> core::iter::Sum<&'a Self> for Fp256 { + fn sum>(iter: I) -> Self { + iter.fold(Self::zero(), core::ops::Add::add) + } +} +impl<'a, C: Fp256Parameters> Add<&'a Self> for Fp256 { + type Output = Self; + #[inline(always)] + fn add(mut self, other: &'a Self) -> Self { + self.add_assign(other); + self + } +} +impl<'a, C: Fp256Parameters> core::ops::AddAssign<&'a Self> for Fp256 { + #[inline(always)] + fn add_assign(&mut self, other: &'a Self) { + add_assign::(&mut self.0, &other.0) + } +} +impl<'a, C: Fp256Parameters> Mul<&'a Self> for Fp256 { + type Output = Self; + #[inline(always)] + fn mul(mut self, other: &'a Self) -> Self { + self.mul_assign(other); + self + } +} +impl<'a, C: Fp256Parameters> core::ops::MulAssign<&'a Self> for Fp256 { + #[inline(always)] + fn mul_assign(&mut self, other: &'a Self) { + self.const_mul(&other, &C::MODULUS, C::INV as u32) + } +} + +impl From for Fp256 { + fn from(value: u128) -> Self { + let hi = (value >> 64) as u64; + let lo = value as u64; + Self::from_repr(BigInteger256(from_64x4([lo, hi, 0, 0]))).unwrap() + } +} +impl From for Fp256 { + fn from(value: u64) -> Self { + Self::from_repr(BigInteger256::from_64x4([value, 0, 0, 0])).unwrap() + } +} +impl From for Fp256 { + fn from(value: u32) -> Self { + Self::from_repr(BigInteger256::from_64x4([value as u64, 0, 0, 0])).unwrap() + } +} +impl From for Fp256 { + fn from(value: i64) -> Self { + let abs = Self::from(value.unsigned_abs()); + if value.is_positive() { + abs + } else { + -abs + } + } +} +impl From for Fp256 { + fn from(value: i32) -> Self { + let abs = Self::from(value.unsigned_abs()); + if value.is_positive() { + abs + } else { + -abs + } + } +} +impl From for Fp256 { + fn from(value: u16) -> Self { + Self::from_repr(BigInteger256::from_64x4([value as u64, 0, 0, 0])).unwrap() + } +} +impl From for Fp256 { + fn from(value: u8) -> Self { + Self::from_repr(BigInteger256::from_64x4([value as u64, 0, 0, 0])).unwrap() + } +} +impl From for Fp256 { + fn from(value: bool) -> Self { + Self::from_repr(BigInteger256::from_64x4([value as u64, 0, 0, 0])).unwrap() + } +} + +impl CanonicalSerializeWithFlags for Fp256 { + fn serialize_with_flags( + &self, + mut writer: W, + flags: F, + ) -> Result<(), SerializationError> { + if F::BIT_SIZE > 8 { + return Err(SerializationError::NotEnoughSpace); + } + let output_byte_size = buffer_byte_size(C::MODULUS_BITS as usize + F::BIT_SIZE); + let mut bytes = [0u8; 4 * 8 + 1]; + self.write(&mut bytes[..4 * 8])?; + bytes[output_byte_size - 1] |= flags.u8_bitmask(); + writer.write_all(&bytes[..output_byte_size])?; + Ok(()) + } + fn serialized_size_with_flags(&self) -> usize { + todo!() + // buffer_byte_size(P::MODULUS_BITS as usize + F::BIT_SIZE) + } +} +impl CanonicalSerialize for Fp256 { + fn serialize(&self, writer: W) -> Result<(), SerializationError> { + self.serialize_with_flags(writer, EmptyFlags) + } + fn serialized_size(&self) -> usize { + self.serialized_size_with_flags::() + } +} +impl CanonicalDeserializeWithFlags for Fp256 { + fn deserialize_with_flags( + mut reader: R, + ) -> Result<(Self, F), SerializationError> { + if F::BIT_SIZE > 8 { + return Err(SerializationError::NotEnoughSpace); + } + let output_byte_size = buffer_byte_size(C::MODULUS_BITS as usize + F::BIT_SIZE); + let mut masked_bytes = [0; 4 * 8 + 1]; + reader.read_exact(&mut masked_bytes[..output_byte_size])?; + let flags = F::from_u8_remove_flags(&mut masked_bytes[output_byte_size - 1]) + .ok_or(SerializationError::UnexpectedFlags)?; + Ok((Self::read(&masked_bytes[..])?, flags)) + } +} +impl CanonicalDeserialize for Fp256 { + fn deserialize(reader: R) -> Result { + Self::deserialize_with_flags::(reader).map(|(r, _)| r) + } +} + +impl PrimeField for Fp256 { + type Params = C; + type BigInt = BigInteger256; + #[inline] + fn from_repr(r: BigInteger256) -> Option { + let mut r = Self(r, PhantomData); + if r.is_zero() { + Some(r) + } else if r.is_valid() { + r *= &Self(C::R2, PhantomData); + Some(r) + } else { + None + } + } + #[inline] + #[allow(clippy::modulo_one)] + fn into_repr(&self) -> BigInteger256 { + let one = BigInteger256([1, 0, 0, 0, 0, 0, 0, 0, 0]); + self.mul(Self(one, PhantomData)).0 + } +} + +impl From for Fp256 { + fn from(val: num_bigint::BigUint) -> Self { + Self::from_le_bytes_mod_order(&val.to_bytes_le()) + } +} +impl Into for Fp256 { + fn into(self) -> num_bigint::BigUint { + self.into_repr().into() + } +} + +impl FromStr for Fp256 { + type Err = (); + /// Interpret a string of numbers as a (congruent) prime field element. + /// Does not accept unnecessary leading zeroes or a blank string. + fn from_str(s: &str) -> Result { + if s.is_empty() { + return Err(()); + } + if s == "0" { + return Ok(Self::zero()); + } + let mut res = Self::zero(); + use core::convert::TryFrom; + let ten = Self::try_from(::BigInt::from(10)).unwrap(); + let mut first_digit = true; + for c in s.chars() { + match c.to_digit(10) { + Some(c) => { + if first_digit { + if c == 0 { + return Err(()); + } + first_digit = false; + } + res.mul_assign(&ten); + let digit = Self::from(u64::from(c)); + res.add_assign(&digit); + }, + None => { + return Err(()); + }, + } + } + if !res.is_valid() { + Err(()) + } else { + Ok(res) + } + } +} + +impl ToBytes for Fp256 { + fn write(&self, writer: W) -> IoResult<()> { + self.into_repr().write(writer) + } +} +impl FromBytes for Fp256 { + fn read(reader: R) -> IoResult { + BigInteger256::read(reader).and_then(|b| match Fp256::from_repr(b) { + Some(f) => Ok(f), + None => Err(crate::error("FromBytes::read failed")), + }) + } +} + +impl Field for Fp256 { + type BasePrimeField = Self; + fn extension_degree() -> u64 { + 1 + } + fn from_base_prime_field_elems(elems: &[Self::BasePrimeField]) -> Option { + if elems.len() != (Self::extension_degree() as usize) { + return None; + } + Some(elems[0]) + } + #[inline] + fn double(&self) -> Self { + let mut temp = *self; + temp.double_in_place(); + temp + } + #[inline] + fn double_in_place(&mut self) -> &mut Self { + self.0.mul2(); + self.reduce(); + self + } + #[inline] + fn characteristic() -> [u64; 4] { + C::MODULUS.to_64x4() + } + #[inline] + fn from_random_bytes_with_flags(bytes: &[u8]) -> Option<(Self, F)> { + if F::BIT_SIZE > 8 { + return None; + } else { + let mut result_bytes = [0u8; 4 * 8 + 1]; + result_bytes + .iter_mut() + .zip(bytes) + .for_each(|(result, input)| { + *result = *input; + }); + let last_limb_mask = (u64::MAX >> C::REPR_SHAVE_BITS).to_le_bytes(); + let mut last_bytes_mask = [0u8; 9]; + last_bytes_mask[..8].copy_from_slice(&last_limb_mask); + let output_byte_size = buffer_byte_size(C::MODULUS_BITS as usize + F::BIT_SIZE); + let flag_location = output_byte_size - 1; + let flag_location_in_last_limb = flag_location - (8 * (4 - 1)); + let last_bytes = &mut result_bytes[8 * (4 - 1)..]; + let flags_mask = u8::MAX.checked_shl(8 - (F::BIT_SIZE as u32)).unwrap_or(0); + let mut flags: u8 = 0; + for (i, (b, m)) in last_bytes.iter_mut().zip(&last_bytes_mask).enumerate() { + if i == flag_location_in_last_limb { + flags = *b & flags_mask; + } + *b &= m; + } + Self::deserialize(&result_bytes[..(4 * 8)]) + .ok() + .and_then(|f| F::from_u8(flags).map(|flag| (f, flag))) + } + } + #[inline(always)] + fn square(&self) -> Self { + let mut temp = self.clone(); + temp.square_in_place(); + temp + } + #[inline(always)] + fn square_in_place(&mut self) -> &mut Self { + self.const_square(); + self + } + #[inline] + fn inverse(&self) -> Option { + if self.is_zero() { + None + } else { + let one = BigInteger256::from(1); + let mut u = self.0; + let mut v = C::MODULUS; + let mut b = Self(C::R2, PhantomData); + let mut c = Self::zero(); + while u != one && v != one { + while u.is_even() { + u.div2(); + if b.0.is_even() { + b.0.div2(); + } else { + b.0.add_nocarry(&C::MODULUS); + b.0.div2(); + } + } + while v.is_even() { + v.div2(); + if c.0.is_even() { + c.0.div2(); + } else { + c.0.add_nocarry(&C::MODULUS); + c.0.div2(); + } + } + if v < u { + u.sub_noborrow(&v); + b.sub_assign(&c); + } else { + v.sub_noborrow(&u); + c.sub_assign(&b); + } + } + if u == one { + Some(b) + } else { + Some(c) + } + } + } + fn inverse_in_place(&mut self) -> Option<&mut Self> { + if let Some(inverse) = self.inverse() { + *self = inverse; + Some(self) + } else { + None + } + } + /// The Frobenius map has no effect in a prime field. + #[inline] + fn frobenius_map(&mut self, _: usize) {} +} + +#[cfg(not(feature = "32x9"))] +impl ark_std::rand::distributions::Distribution> + for ark_std::rand::distributions::Standard +{ + #[inline] + fn sample(&self, rng: &mut R) -> Fp256 { + loop { + if !(C::REPR_SHAVE_BITS <= 64) { + panic!("assertion failed: P::REPR_SHAVE_BITS <= 64") + } + let mask = if C::REPR_SHAVE_BITS == 64 { + 0 + } else { + core::u64::MAX >> C::REPR_SHAVE_BITS + }; + + let mut tmp: [u64; 4] = rng.sample(ark_std::rand::distributions::Standard); + tmp.as_mut().last_mut().map(|val| *val &= mask); + + let tmp = Fp256(BigInteger256::from_64x4(tmp), PhantomData); + if tmp.is_valid() { + return tmp; + } + } + } +} + +// During tests, we want to generate the same fields than on native (to test witness generation etc) +#[cfg(feature = "32x9")] +impl ark_std::rand::distributions::Distribution> + for ark_std::rand::distributions::Standard +{ + #[inline] + fn sample(&self, rng: &mut R) -> Fp256 { + loop { + if !(C::REPR_SHAVE_BITS <= 64) { + panic!("assertion failed: P::REPR_SHAVE_BITS <= 64") + } + let mask = if C::REPR_SHAVE_BITS == 64 { + 0 + } else { + core::u64::MAX >> C::REPR_SHAVE_BITS + }; + let mut tmp: [u64; 4] = rng.sample(ark_std::rand::distributions::Standard); + tmp.as_mut().last_mut().map(|val| *val &= mask); + let is_fp = match C::T.0[0] { + 0x192d30ed => true, + 0xc46eb21 => false, + _ => panic!(), + }; + const FP_MODULUS: [u64; 4] = [ + 0x992d30ed00000001, + 0x224698fc094cf91b, + 0x0, + 0x4000000000000000, + ]; + const FQ_MODULUS: [u64; 4] = [ + 0x8c46eb2100000001, + 0x224698fc0994a8dd, + 0x0, + 0x4000000000000000, + ]; + let (modulus, inv) = if is_fp { + (FP_MODULUS, 11037532056220336127) + } else { + (FQ_MODULUS, 10108024940646105087) + }; + let is_valid = || { + for (random, modulus) in tmp.iter().copied().zip(modulus).rev() { + if random > modulus { + return false; + } else if random < modulus { + return true; + } + } + false + }; + if !is_valid() { + continue; + } + let mut r = tmp; + // Montgomery Reduction + for i in 0..4 { + let k = r[i].wrapping_mul(inv); + let mut carry = 0; + mac_with_carry!(r[i], k, modulus[0] as _, &mut carry); + for j in 1..4 { + r[(j + i) % 4] = mac_with_carry!(r[(j + i) % 4], k, modulus[j], &mut carry); + } + r[i % 4] = carry; + } + tmp = r; + return Fp256::::from_repr(BigInteger256::from_64x4(tmp)).unwrap(); + } + } +} + +pub struct NewFpParameters; + +impl zeroize::DefaultIsZeroes for Fp256 {} + +impl FftField for Fp256 { + type FftParams = C; + fn two_adic_root_of_unity() -> Self { + Fp256::(C::TWO_ADIC_ROOT_OF_UNITY, PhantomData) + } + fn large_subgroup_root_of_unity() -> Option { + Some(Fp256::(C::LARGE_SUBGROUP_ROOT_OF_UNITY?, PhantomData)) + } + fn multiplicative_generator() -> Self { + Fp256::(C::GENERATOR, PhantomData) + } +} + +impl SquareRootField for Fp256 { + #[inline] + fn legendre(&self) -> LegendreSymbol { + use crate::fields::LegendreSymbol::*; + + let modulus_minus_one_div_two = C::MODULUS_MINUS_ONE_DIV_TWO.to_64x4(); + let s = self.pow(modulus_minus_one_div_two); + if s.is_zero() { + Zero + } else if s.is_one() { + QuadraticResidue + } else { + QuadraticNonResidue + } + } + #[inline] + fn sqrt(&self) -> Option { + { + let t_minus_one_div_two = C::T_MINUS_ONE_DIV_TWO.to_64x4(); + + if self.is_zero() { + return Some(Self::zero()); + } + let mut z = Self::qnr_to_t(); + let mut w = self.pow(t_minus_one_div_two); + let mut x = w * self; + let mut b = x * &w; + let mut v = C::TWO_ADICITY as usize; + while !b.is_one() { + let mut k = 0usize; + let mut b2k = b; + while !b2k.is_one() { + b2k.square_in_place(); + k += 1; + } + if k == (C::TWO_ADICITY as usize) { + return None; + } + let j = v - k; + w = z; + for _ in 1..j { + w.square_in_place(); + } + z = w.square(); + b *= &z; + x *= &w; + v = k; + } + if x.square() == *self { + return Some(x); + } else { + #[cfg(debug_assertions)] + { + use crate::fields::LegendreSymbol::*; + if self.legendre() != QuadraticNonResidue { + panic!( + "Input has a square root per its legendre symbol, but it was not found", + ) + } + } + None + } + } + } + fn sqrt_in_place(&mut self) -> Option<&mut Self> { + (*self).sqrt().map(|sqrt| { + *self = sqrt; + self + }) + } +} + +pub trait Fp256Parameters: + crate::FpParameters + + ark_std::fmt::Debug + + Clone + + Copy + + Default + + Eq + + PartialEq + + PartialOrd + + Ord + + core::hash::Hash + + 'static + + Send + + Sync + + Sized +{ +} diff --git a/kimchi/src/curve.rs b/kimchi/src/curve.rs index ebfd08f4132..0d705b1e8c1 100644 --- a/kimchi/src/curve.rs +++ b/kimchi/src/curve.rs @@ -5,7 +5,7 @@ use ark_ec::{short_weierstrass::Affine, AffineRepr, CurveConfig}; use mina_curves::{ named::NamedCurve, pasta::curves::{ - pallas::{LegacyPallasParameters, PallasParameters}, + pallas::{LegacyPallasParameters, PallasParameters, WasmPallasParameters}, vesta::{LegacyVestaParameters, VestaParameters}, }, }; @@ -194,3 +194,38 @@ impl KimchiCurve for Affine { (44u64.into(), 88u64.into()) } } + +//////////////////////////////////////////////////////////////////////////// +// WASM +//////////////////////////////////////////////////////////////////////////// + +impl KimchiCurve for Affine { + const NAME: &'static str = "pallas_wasm"; + + fn sponge_params() -> &'static ArithmeticSpongeParams { + todo!() + //mina_poseidon::pasta::fq_kimchi::static_params() + } + + fn other_curve_sponge_params() -> &'static ArithmeticSpongeParams { + todo!() + //mina_poseidon::pasta::fp_kimchi::static_params() + } + + fn endos() -> &'static (Self::BaseField, Self::ScalarField) { + todo!() + //pallas_endos() + } + + fn other_curve_endo() -> &'static Self::ScalarField { + todo!() + //&vesta_endos().0 + } + + fn other_curve_prime_subgroup_generator() -> (Self::ScalarField, Self::ScalarField) { + todo!() + //Affine::::generator() + // .to_coordinates() + // .unwrap() + } +} diff --git a/kimchi/src/tests/and.rs b/kimchi/src/tests/and.rs index c9e751d8413..728fad94c62 100644 --- a/kimchi/src/tests/and.rs +++ b/kimchi/src/tests/and.rs @@ -176,6 +176,17 @@ fn prove_and_check_serialization_regression(8); prove_and_verify::(8); + + use mina_curves::pasta::{ + curves::pallas::{WasmPallas, WasmPallasParameters}, + wasm_friendly::Fq9, + }; + + prove_and_verify::< + WasmPallas, + DefaultFqSponge, + DefaultFrSponge, + >(8); } #[test] diff --git a/poseidon/benches/poseidon_bench.rs b/poseidon/benches/poseidon_bench.rs index 4f44ad1f99c..74c50400950 100644 --- a/poseidon/benches/poseidon_bench.rs +++ b/poseidon/benches/poseidon_bench.rs @@ -45,7 +45,169 @@ pub fn bench_poseidon_kimchi(c: &mut Criterion) { group.finish(); } -criterion_group!(benches, bench_poseidon_kimchi); +pub fn bench_conversions(c: &mut Criterion) { + let mut group = c.benchmark_group("Conversions"); + + group.bench_function("Conversion: fp_to_fp9", |b| { + b.iter_batched( + || { + let x: Fp = rand::random(); + x + }, + |x| { + let z: Fp9 = x.into(); + z + }, + criterion::BatchSize::SmallInput, + ); + }); + + group.bench_function("Conversion: fp_to_fp9, 2^16 elements", |b| { + b.iter_batched( + || (0..65536).map(|_| rand::random()).collect(), + |hashes_fp: Vec| { + let mut hashes_fp9: Vec = Vec::with_capacity(65536); + for h in hashes_fp.clone().into_iter() { + hashes_fp9.push(h.into()); + } + hashes_fp9 + }, + criterion::BatchSize::SmallInput, + ); + }); +} + +pub fn bench_basic_ops(c: &mut Criterion) { + let mut group = c.benchmark_group("Basic ops"); + + group.bench_function("Native multiplication in Fp (single)", |b| { + b.iter_batched( + || { + let x: Fp = rand::random(); + let y: Fp = rand::random(); + (x, y) + }, + |(x, y)| { + let z: Fp = x * y; + z + }, + criterion::BatchSize::SmallInput, + ); + }); + + group.bench_function("Multiplication in Fp9 (single)", |b| { + b.iter_batched( + || { + let x: Fp = rand::random(); + let y: Fp = rand::random(); + let x_fp9: Fp9 = x.into(); + let y_fp9: Fp9 = y.into(); + (x_fp9, y_fp9) + }, + |(x_fp9, y_fp9)| { + let z_fp9: Fp9 = x_fp9 * y_fp9; + z_fp9 + }, + criterion::BatchSize::SmallInput, + ); + }); + + group.bench_function("Multiplication in Fp9 with a conversion (single)", |b| { + b.iter_batched( + || { + let x: Fp = rand::random(); + let y: Fp = rand::random(); + (x, y) + }, + |(x, y)| { + let x_fp9: Fp9 = From::from(x); + let y_fp9: Fp9 = From::from(y); + let z_fp9: Fp9 = x_fp9 * y_fp9; + z_fp9 + }, + criterion::BatchSize::SmallInput, + ); + }); + + group.bench_function("Native multiplication in Fp (double)", |b| { + b.iter_batched( + || { + let x: Fp = rand::random(); + let y: Fp = rand::random(); + (x, y) + }, + |(x, y)| { + let z: Fp = x * y; + let z: Fp = z * x; + z + }, + criterion::BatchSize::SmallInput, + ); + }); + + group.bench_function("Multiplication in Fp9 with a conversion (double)", |b| { + b.iter_batched( + || { + let x: Fp = rand::random(); + let y: Fp = rand::random(); + (x, y) + }, + |(x, y)| { + let x_fp9: Fp9 = From::from(x); + let y_fp9: Fp9 = From::from(y); + let z_fp9: Fp9 = x_fp9 * y_fp9; + let z_fp9: Fp9 = z_fp9 * x_fp9; + z_fp9 + }, + criterion::BatchSize::SmallInput, + ); + }); + + group.bench_function("Native multiplication in Fp (4 muls)", |b| { + b.iter_batched( + || { + let x: Fp = rand::random(); + let y: Fp = rand::random(); + (x, y) + }, + |(x, y)| { + let z: Fp = x * y; + let z: Fp = z * x; + let z: Fp = z * y; + let z: Fp = z * x; + z + }, + criterion::BatchSize::SmallInput, + ); + }); + + group.bench_function("Multiplication in Fp9 with a conversion (4 muls)", |b| { + b.iter_batched( + || { + let x: Fp = rand::random(); + let y: Fp = rand::random(); + (x, y) + }, + |(x, y)| { + let x_fp9: Fp9 = From::from(x); + let y_fp9: Fp9 = From::from(y); + let z_fp9: Fp9 = x_fp9 * y_fp9; + let z_fp9: Fp9 = z_fp9 * x_fp9; + let z_fp9: Fp9 = z_fp9 * y_fp9; + let z_fp9: Fp9 = z_fp9 * x_fp9; + z_fp9 + }, + criterion::BatchSize::SmallInput, + ); + }); +} + +criterion_group!( + benches, + bench_poseidon_kimchi, + bench_conversions, + bench_basic_ops +); criterion_main!(benches); // sponge params for Fp9 @@ -67,6 +229,7 @@ fn fp9_sponge_params() -> ArithmeticSpongeParams { .collect(), } } + fn fp9_static_params() -> &'static ArithmeticSpongeParams { static PARAMS: Lazy> = Lazy::new(fp9_sponge_params); &PARAMS diff --git a/poseidon/src/permutation.rs b/poseidon/src/permutation.rs index eb780538279..3109ca1e53e 100644 --- a/poseidon/src/permutation.rs +++ b/poseidon/src/permutation.rs @@ -2,7 +2,7 @@ //! used in Poseidon. extern crate alloc; -use mina_curves::pasta::wasm_friendly::minimal_field::MinimalField; +use ark_ff::Field; use crate::{ constants::SpongeConstants, @@ -10,7 +10,7 @@ use crate::{ }; use alloc::{vec, vec::Vec}; -fn apply_mds_matrix( +fn apply_mds_matrix( params: &ArithmeticSpongeParams, state: &[F], ) -> Vec { @@ -41,7 +41,7 @@ fn apply_mds_matrix( /// - Add the round constants to the state. /// /// The function has side-effect and the parameter state is modified. -pub fn full_round( +pub fn full_round( params: &ArithmeticSpongeParams, state: &mut Vec, r: usize, @@ -55,7 +55,7 @@ pub fn full_round( } } -pub fn half_rounds( +pub fn half_rounds( params: &ArithmeticSpongeParams, state: &mut [F], ) { @@ -104,7 +104,7 @@ pub fn half_rounds( } } -pub fn poseidon_block_cipher( +pub fn poseidon_block_cipher( params: &ArithmeticSpongeParams, state: &mut Vec, ) { diff --git a/poseidon/src/poseidon.rs b/poseidon/src/poseidon.rs index 44efa77f8ae..398a4e4e847 100644 --- a/poseidon/src/poseidon.rs +++ b/poseidon/src/poseidon.rs @@ -6,14 +6,14 @@ use crate::{ permutation::{full_round, poseidon_block_cipher}, }; use alloc::{vec, vec::Vec}; +use ark_ff::Field; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use mina_curves::pasta::wasm_friendly::MinimalField; use serde::{Deserialize, Serialize}; use serde_with::serde_as; /// Cryptographic sponge interface - for hashing an arbitrary amount of /// data into one or more field elements -pub trait Sponge { +pub trait Sponge { /// Create a new cryptographic sponge using arithmetic sponge `params` fn new(params: &'static ArithmeticSpongeParams) -> Self; @@ -27,7 +27,7 @@ pub trait Sponge { fn reset(&mut self); } -pub fn sbox(mut x: F) -> F { +pub fn sbox(mut x: F) -> F { if SC::PERM_SBOX == 7 { // This is much faster than using the generic `pow`. Hard-code to get the ~50% speed-up // that it gives to hashing. @@ -50,7 +50,7 @@ pub enum SpongeState { #[serde_as] #[derive(Clone, Serialize, Deserialize, Default, Debug)] -pub struct ArithmeticSpongeParams { +pub struct ArithmeticSpongeParams { #[serde_as(as = "Vec>")] pub round_constants: Vec>, #[serde_as(as = "Vec>")] @@ -58,7 +58,7 @@ pub struct ArithmeticSpongeParams { +pub struct ArithmeticSponge { pub sponge_state: SpongeState, rate: usize, // TODO(mimoo: an array enforcing the width is better no? or at least an assert somewhere) @@ -67,7 +67,7 @@ pub struct ArithmeticSponge { pub constants: core::marker::PhantomData, } -impl ArithmeticSponge { +impl ArithmeticSponge { pub fn full_round(&mut self, r: usize) { full_round::(self.params, &mut self.state, r); } @@ -77,7 +77,7 @@ impl ArithmeticSponge { } } -impl Sponge for ArithmeticSponge { +impl Sponge for ArithmeticSponge { fn new(params: &'static ArithmeticSpongeParams) -> ArithmeticSponge { let capacity = SC::SPONGE_CAPACITY; let rate = SC::SPONGE_RATE; From dd1a839795e7bbd524374de9ba89d95bb590e3f9 Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Fri, 25 Jul 2025 17:16:54 +0100 Subject: [PATCH 03/16] Stubbing WIP --- curves/src/pasta/wasm_friendly/bigint32.rs | 544 ++++++++++++++------- 1 file changed, 375 insertions(+), 169 deletions(-) diff --git a/curves/src/pasta/wasm_friendly/bigint32.rs b/curves/src/pasta/wasm_friendly/bigint32.rs index 7021846d2d3..7cf11cc1be9 100644 --- a/curves/src/pasta/wasm_friendly/bigint32.rs +++ b/curves/src/pasta/wasm_friendly/bigint32.rs @@ -17,7 +17,14 @@ use ark_std::{ Rng, }, }; -use num_bigint::BigUint; +use core::{ + ops::{ + BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Shl, ShlAssign, Shr, + ShrAssign, + }, + str::FromStr, +}; +use num_bigint::{BigUint, ParseBigIntError}; use zeroize::Zeroize; #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Zeroize)] @@ -148,6 +155,358 @@ impl BigInt { } } +impl AsMut<[u64]> for BigInt { + #[inline] + fn as_mut(&mut self) -> &mut [u64] { + todo!() + //&mut self.0 + } +} + +impl AsRef<[u64]> for BigInt { + #[inline] + fn as_ref(&self) -> &[u64] { + todo!() + //&self.0 + } +} + +impl From for BigInt { + #[inline] + fn from(val: u64) -> BigInt { + todo!() + //let mut repr = Self::default(); + //repr.0[0] = val; + //repr + } +} + +impl From for BigInt { + #[inline] + fn from(val: u32) -> BigInt { + let mut repr = Self::default(); + repr.0[0] = u32::from(val); + repr + } +} + +impl From for BigInt { + #[inline] + fn from(val: u16) -> BigInt { + let mut repr = Self::default(); + repr.0[0] = u32::from(val); + repr + } +} + +impl From for BigInt { + #[inline] + fn from(val: u8) -> BigInt { + let mut repr = Self::default(); + repr.0[0] = u32::from(val); + repr + } +} + +impl TryFrom for BigInt { + type Error = (); + + /// Returns `Err(())` if the bit size of `val` is more than `N * 64`. + #[inline] + fn try_from(val: num_bigint::BigUint) -> Result, Self::Error> { + todo!() + //let bytes = val.to_bytes_le(); + + //if bytes.len() > N * 8 { + // Err(()) + //} else { + // let mut limbs = [0u64; N]; + + // bytes + // .chunks(8) + // .into_iter() + // .enumerate() + // .for_each(|(i, chunk)| { + // let mut chunk_padded = [0u8; 8]; + // chunk_padded[..chunk.len()].copy_from_slice(chunk); + // limbs[i] = u64::from_le_bytes(chunk_padded) + // }); + + // Ok(Self(limbs)) + //} + } +} + +impl From> for BigUint { + #[inline] + fn from(val: BigInt) -> num_bigint::BigUint { + BigUint::from_bytes_le(&val.to_bytes_le()) + } +} + +impl Default for BigInt { + fn default() -> Self { + Self([0u32; N]) + } +} + +impl CanonicalSerialize for BigInt { + fn serialize_with_mode( + &self, + writer: W, + compress: Compress, + ) -> Result<(), SerializationError> { + self.0.serialize_with_mode(writer, compress) + } + + fn serialized_size(&self, compress: Compress) -> usize { + self.0.serialized_size(compress) + } +} + +impl Valid for BigInt { + fn check(&self) -> Result<(), SerializationError> { + self.0.check() + } +} + +impl CanonicalDeserialize for BigInt { + fn deserialize_with_mode( + reader: R, + compress: Compress, + validate: Validate, + ) -> Result { + Ok(BigInt::(<[u32; N]>::deserialize_with_mode( + reader, compress, validate, + )?)) + } +} + +impl Ord for BigInt { + #[inline] + fn cmp(&self, other: &Self) -> ::core::cmp::Ordering { + use core::cmp::Ordering; + for i in 0..9 { + let a = &self.0[9 - i - 1]; + let b = &other.0[9 - i - 1]; + if a < b { + return Ordering::Less; + } else if a > b { + return Ordering::Greater; + } + } + Ordering::Equal + } +} + +impl PartialOrd for BigInt { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl Display for BigInt { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", BigUint::from(*self)) + } +} + +impl Distribution> for Standard { + fn sample(&self, rng: &mut R) -> BigInt { + todo!() + //let mut res = [0u64; N]; + //for item in res.iter_mut() { + // *item = rng.gen(); + //} + //BigInt::(res) + } +} + +// do not use forward_ref_ref_binop_commutative! for bitand so that we can +// clone as needed, avoiding over-allocation +impl BitAnd<&BigInt> for &BigInt { + type Output = BigInt; + + #[inline] + fn bitand(self, other: &BigInt) -> BigInt { + todo!() + } +} + +impl BitAnd<&BigInt> for BigInt { + type Output = BigInt; + + #[inline] + fn bitand(mut self, other: &BigInt) -> BigInt { + todo!() + } +} + +impl BitAnd> for &BigInt { + type Output = BigInt; + + #[inline] + fn bitand(self, other: BigInt) -> BigInt { + todo!() + } +} + +impl BitAnd> for BigInt { + type Output = BigInt; + + #[inline] + fn bitand(mut self, other: BigInt) -> BigInt { + todo!() + } +} + +impl BitAndAssign> for BigInt { + fn bitand_assign(&mut self, other: BigInt) { + todo!() + } +} + +impl BitAndAssign<&BigInt> for BigInt { + fn bitand_assign(&mut self, other: &BigInt) { + todo!() + } +} + +impl BitOr> for &BigInt { + type Output = BigInt; + + #[inline] + fn bitor(self, other: BigInt) -> BigInt { + todo!() + } +} + +impl BitOr<&BigInt> for &BigInt { + type Output = BigInt; + + #[inline] + fn bitor(self, other: &BigInt) -> BigInt { + todo!() + } +} + +impl BitOr<&BigInt> for BigInt { + type Output = BigInt; + + #[inline] + fn bitor(mut self, other: &BigInt) -> BigInt { + todo!() + } +} + +impl BitOr> for BigInt { + type Output = BigInt; + + #[inline] + fn bitor(mut self, other: BigInt) -> BigInt { + todo!() + } +} + +impl BitOrAssign> for BigInt { + fn bitor_assign(&mut self, other: BigInt) { + todo!() + } +} + +impl BitOrAssign<&BigInt> for BigInt { + fn bitor_assign(&mut self, other: &BigInt) { + todo!() + } +} + +impl Shl for BigInt { + type Output = BigInt; + + #[inline] + fn shl(self, rhs: u32) -> BigInt { + todo!() + } +} +impl Shl for &BigInt { + type Output = BigInt; + + #[inline] + fn shl(self, rhs: u32) -> BigInt { + todo!() + } +} +impl ShlAssign for BigInt { + #[inline] + fn shl_assign(&mut self, rhs: u32) { + todo!() + } +} + +impl Shr for BigInt { + type Output = BigInt; + + #[inline] + fn shr(self, rhs: u32) -> BigInt { + todo!() + } +} +impl Shr for &BigInt { + type Output = BigInt; + + #[inline] + fn shr(self, rhs: u32) -> BigInt { + todo!() + } +} +impl ShrAssign for BigInt { + #[inline] + fn shr_assign(&mut self, rhs: u32) { + todo!() + } +} + +impl BitXor<&BigInt> for BigInt { + type Output = BigInt; + + #[inline] + fn bitxor(mut self, other: &BigInt) -> BigInt { + self ^= other; + self + } +} + +impl BitXor> for BigInt { + type Output = BigInt; + + #[inline] + fn bitxor(mut self, other: BigInt) -> BigInt { + todo!() + } +} + +impl BitXorAssign> for BigInt { + fn bitxor_assign(&mut self, other: BigInt) { + todo!() + } +} + +impl BitXorAssign<&BigInt> for BigInt { + fn bitxor_assign(&mut self, other: &BigInt) { + todo!() + } +} + +impl FromStr for BigInt { + type Err = ParseBigIntError; + + #[inline] + fn from_str(s: &str) -> Result, ParseBigIntError> { + todo!() + } +} + impl BigInteger for BigInt { const NUM_LIMBS: usize = N; @@ -252,6 +611,21 @@ impl BigInteger for BigInt { } } + #[inline] + fn mul(&self, other: &Self) -> (Self, Self) { + todo!() + } + + #[inline] + fn mul_low(&self, other: &Self) -> Self { + todo!() + } + + #[inline] + fn mul_high(&self, other: &Self) -> Self { + todo!() + } + #[inline] fn muln(&mut self, mut n: u32) { if n >= (64 * N) as u32 { @@ -408,171 +782,3 @@ impl BigInteger for BigInt { res } } - -impl AsMut<[u64]> for BigInt { - #[inline] - fn as_mut(&mut self) -> &mut [u64] { - todo!() - //&mut self.0 - } -} - -impl AsRef<[u64]> for BigInt { - #[inline] - fn as_ref(&self) -> &[u64] { - todo!() - //&self.0 - } -} - -impl From for BigInt { - #[inline] - fn from(val: u64) -> BigInt { - todo!() - //let mut repr = Self::default(); - //repr.0[0] = val; - //repr - } -} - -impl From for BigInt { - #[inline] - fn from(val: u32) -> BigInt { - let mut repr = Self::default(); - repr.0[0] = u32::from(val); - repr - } -} - -impl From for BigInt { - #[inline] - fn from(val: u16) -> BigInt { - let mut repr = Self::default(); - repr.0[0] = u32::from(val); - repr - } -} - -impl From for BigInt { - #[inline] - fn from(val: u8) -> BigInt { - let mut repr = Self::default(); - repr.0[0] = u32::from(val); - repr - } -} - -impl TryFrom for BigInt { - type Error = (); - - /// Returns `Err(())` if the bit size of `val` is more than `N * 64`. - #[inline] - fn try_from(val: num_bigint::BigUint) -> Result, Self::Error> { - todo!() - //let bytes = val.to_bytes_le(); - - //if bytes.len() > N * 8 { - // Err(()) - //} else { - // let mut limbs = [0u64; N]; - - // bytes - // .chunks(8) - // .into_iter() - // .enumerate() - // .for_each(|(i, chunk)| { - // let mut chunk_padded = [0u8; 8]; - // chunk_padded[..chunk.len()].copy_from_slice(chunk); - // limbs[i] = u64::from_le_bytes(chunk_padded) - // }); - - // Ok(Self(limbs)) - //} - } -} - -impl From> for BigUint { - #[inline] - fn from(val: BigInt) -> num_bigint::BigUint { - BigUint::from_bytes_le(&val.to_bytes_le()) - } -} - -impl Default for BigInt { - fn default() -> Self { - Self([0u32; N]) - } -} - -impl CanonicalSerialize for BigInt { - fn serialize_with_mode( - &self, - writer: W, - compress: Compress, - ) -> Result<(), SerializationError> { - self.0.serialize_with_mode(writer, compress) - } - - fn serialized_size(&self, compress: Compress) -> usize { - self.0.serialized_size(compress) - } -} - -impl Valid for BigInt { - fn check(&self) -> Result<(), SerializationError> { - self.0.check() - } -} - -impl CanonicalDeserialize for BigInt { - fn deserialize_with_mode( - reader: R, - compress: Compress, - validate: Validate, - ) -> Result { - Ok(BigInt::(<[u32; N]>::deserialize_with_mode( - reader, compress, validate, - )?)) - } -} - -impl Ord for BigInt { - #[inline] - fn cmp(&self, other: &Self) -> ::core::cmp::Ordering { - use core::cmp::Ordering; - for i in 0..9 { - let a = &self.0[9 - i - 1]; - let b = &other.0[9 - i - 1]; - if a < b { - return Ordering::Less; - } else if a > b { - return Ordering::Greater; - } - } - Ordering::Equal - } -} - -impl PartialOrd for BigInt { - #[inline] - fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> { - Some(self.cmp(other)) - } -} - -impl Display for BigInt { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{}", BigUint::from(*self)) - } -} - -impl Distribution> for Standard { - fn sample(&self, rng: &mut R) -> BigInt { - todo!() - //let mut res = [0u64; N]; - //for item in res.iter_mut() { - // *item = rng.gen(); - //} - //BigInt::(res) - } -} From afec8bfb8cc0ca15a611c75df8aceea1d37755b3 Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Fri, 25 Jul 2025 17:45:48 +0100 Subject: [PATCH 04/16] Make it compile --- curves/src/pasta/wasm_friendly/wasm_fp.rs | 92 ++++++++++++++++------- 1 file changed, 66 insertions(+), 26 deletions(-) diff --git a/curves/src/pasta/wasm_friendly/wasm_fp.rs b/curves/src/pasta/wasm_friendly/wasm_fp.rs index 0ea549a485a..497cdf19bda 100644 --- a/curves/src/pasta/wasm_friendly/wasm_fp.rs +++ b/curves/src/pasta/wasm_friendly/wasm_fp.rs @@ -4,7 +4,7 @@ * Most of this code was copied over from ark_ff::Fp */ use crate::pasta::wasm_friendly::bigint32::BigInt; -use ark_ff::{FftField, Field, One, PrimeField, Zero}; +use ark_ff::{AdditiveGroup, FftField, Field, One, PrimeField, Zero}; use ark_serialize::{ CanonicalDeserialize, CanonicalDeserializeWithFlags, CanonicalSerialize, CanonicalSerializeWithFlags, Compress, Flags, Read, SerializationError, Valid, Validate, Write, @@ -653,6 +653,36 @@ impl, const N: usize> From for Fp { } } +impl, const N: usize> From for Fp { + fn from(other: i128) -> Self { + todo!() + } +} + +impl, const N: usize> From for Fp { + fn from(other: i64) -> Self { + todo!() + } +} + +impl, const N: usize> From for Fp { + fn from(other: i32) -> Self { + todo!() + } +} + +impl, const N: usize> From for Fp { + fn from(other: i16) -> Self { + todo!() + } +} + +impl, const N: usize> From for Fp { + fn from(other: i8) -> Self { + todo!() + } +} + impl, const N: usize> From for Fp { fn from(other: bool) -> Self { todo!() @@ -722,32 +752,9 @@ impl, const N: usize> FftField for Fp { const LARGE_SUBGROUP_ROOT_OF_UNITY: Option = None; // FIXME! //P::LARGE_SUBGROUP_ROOT_OF_UNITY; } -impl, const N: usize> Field for Fp { - type BasePrimeField = Self; - type BasePrimeFieldIter = core::iter::Once; - - const SQRT_PRECOMP: Option> = None; // FIXME //P::SQRT_PRECOMP; +impl, const N: usize> AdditiveGroup for Fp { + type Scalar = Self; const ZERO: Self = Fp(P::ZERO, PhantomData); - const ONE: Self = Fp(P::ONE, PhantomData); - - fn extension_degree() -> u64 { - 1 - } - - fn from_base_prime_field(elem: Self::BasePrimeField) -> Self { - elem - } - - fn to_base_prime_field_elements(&self) -> Self::BasePrimeFieldIter { - core::iter::once(*self) - } - - fn from_base_prime_field_elems(elems: &[Self::BasePrimeField]) -> Option { - if elems.len() != (Self::extension_degree() as usize) { - return None; - } - Some(elems[0]) - } #[inline] fn double(&self) -> Self { @@ -769,6 +776,35 @@ impl, const N: usize> Field for Fp { //P::neg_in_place(self); //self } +} + +impl, const N: usize> Field for Fp { + type BasePrimeField = Self; + + const SQRT_PRECOMP: Option> = None; // FIXME //P::SQRT_PRECOMP; + const ONE: Self = Fp(P::ONE, PhantomData); + + fn extension_degree() -> u64 { + 1 + } + + fn from_base_prime_field(elem: Self::BasePrimeField) -> Self { + elem + } + + fn to_base_prime_field_elements(&self) -> core::iter::Once { + core::iter::once(*self) + } + + fn from_base_prime_field_elems( + elems: impl IntoIterator, + ) -> Option { + todo!() + // if elems.len() != (Self::extension_degree() as usize) { + // return None; + // } + // Some(elems[0]) + } #[inline] fn characteristic() -> &'static [u64] { @@ -876,6 +912,10 @@ impl, const N: usize> Field for Fp { // QuadraticNonResidue //} } + + fn mul_by_base_prime_field(&self, elem: &Self::BasePrimeField) -> Self { + todo!() + } } //impl, const N: usize> Field for Fp { From 2a9d2349fee400444dacd9252b1e49d1ea1fc9f9 Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Mon, 28 Jul 2025 19:07:49 +0100 Subject: [PATCH 05/16] Attempt to use bnum for bigint32 --- Cargo.lock | 7 + Cargo.toml | 1 + curves/Cargo.toml | 1 + curves/src/pasta/curves/pallas.rs | 14 +- curves/src/pasta/wasm_friendly/backend9.rs | 45 +- .../pasta/wasm_friendly/bigint32_attempt2.rs | 578 ++++++++++++++++++ curves/src/pasta/wasm_friendly/mod.rs | 7 +- curves/src/pasta/wasm_friendly/pasta.rs | 11 +- curves/src/pasta/wasm_friendly/wasm_fp.rs | 4 +- 9 files changed, 631 insertions(+), 37 deletions(-) create mode 100644 curves/src/pasta/wasm_friendly/bigint32_attempt2.rs diff --git a/Cargo.lock b/Cargo.lock index ff94abce772..cd20c896865 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -546,6 +546,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bnum" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119771309b95163ec7aaf79810da82f7cd0599c19722d48b9c03894dca833966" + [[package]] name = "bs58" version = "0.5.0" @@ -2257,6 +2263,7 @@ dependencies = [ "ark-serialize", "ark-std", "ark-test-curves", + "bnum", "derivative", "num-bigint", "rand", diff --git a/Cargo.toml b/Cargo.toml index 72457893f74..4e11bd601bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ base64 = "0.21.5" bcs = "0.1.3" bitvec = "1.0.0" blake2 = "0.10.0" +bnum = { version = "0.13" } bs58 = "0.5.0" clap = "4.4.6" command-fds = "0.3" diff --git a/curves/Cargo.toml b/curves/Cargo.toml index 23ca7ad7131..5c369242dac 100644 --- a/curves/Cargo.toml +++ b/curves/Cargo.toml @@ -15,6 +15,7 @@ ark-std.workspace = true ark-ec.workspace = true ark-ff.workspace = true num-bigint.workspace = true +bnum.workspace = true ark-serialize.workspace = true wasm-bindgen.workspace = true derivative.workspace = true diff --git a/curves/src/pasta/curves/pallas.rs b/curves/src/pasta/curves/pallas.rs index c6349a3baad..1677ad999ff 100644 --- a/curves/src/pasta/curves/pallas.rs +++ b/curves/src/pasta/curves/pallas.rs @@ -1,4 +1,4 @@ -use crate::pasta::*; +use crate::pasta::{wasm_friendly::BigInt, *}; use ark_ec::{ models::short_weierstrass::{Affine, Projective, SWCurveConfig}, CurveConfig, @@ -93,7 +93,7 @@ impl CurveConfig for WasmPallasParameters { /// COFACTOR_INV = 1 // FIXME const COFACTOR_INV: crate::pasta::wasm_friendly::Fq9 = - crate::pasta::wasm_friendly::Fp(crate::pasta::wasm_friendly::BigInt([0; 9]), PhantomData); + crate::pasta::wasm_friendly::Fp(BigInt::ZERO, PhantomData); } pub type WasmPallas = Affine; @@ -102,16 +102,14 @@ pub type WasmProjectivePallas = Projective; impl SWCurveConfig for WasmPallasParameters { // FIXME - const COEFF_A: Self::BaseField = - crate::pasta::wasm_friendly::Fp(crate::pasta::wasm_friendly::BigInt([0; 9]), PhantomData); + const COEFF_A: Self::BaseField = crate::pasta::wasm_friendly::Fp(BigInt::ZERO, PhantomData); // FIXME - const COEFF_B: Self::BaseField = - crate::pasta::wasm_friendly::Fp(crate::pasta::wasm_friendly::BigInt([0; 9]), PhantomData); + const COEFF_B: Self::BaseField = crate::pasta::wasm_friendly::Fp(BigInt::ZERO, PhantomData); // FIXME const GENERATOR: Affine = Affine::new_unchecked( - crate::pasta::wasm_friendly::Fp(crate::pasta::wasm_friendly::BigInt([0; 9]), PhantomData), - crate::pasta::wasm_friendly::Fp(crate::pasta::wasm_friendly::BigInt([0; 9]), PhantomData), + crate::pasta::wasm_friendly::Fp(BigInt::ZERO, PhantomData), + crate::pasta::wasm_friendly::Fp(BigInt::ZERO, PhantomData), ); } diff --git a/curves/src/pasta/wasm_friendly/backend9.rs b/curves/src/pasta/wasm_friendly/backend9.rs index 4aeb0163498..4c48fe3ab8c 100644 --- a/curves/src/pasta/wasm_friendly/backend9.rs +++ b/curves/src/pasta/wasm_friendly/backend9.rs @@ -1,7 +1,7 @@ /** * Implementation of `FpBackend` for N=9, using 29-bit limbs represented by `u32`s. */ -use super::bigint32::BigInt; +use super::bigint32_attempt2::BigInt; use super::wasm_fp::{Fp, FpBackend}; type B = [u32; 9]; @@ -168,47 +168,50 @@ pub fn mul_assign(x: &mut B, y: &B) { // implement FpBackend given FpConstants pub fn from_bigint_unsafe(x: BigInt<9>) -> Fp { - let mut r = x.0; + let mut r = Into::<[u32; 9]>::into(x); // convert to montgomery form mul_assign::(&mut r, &FpC::R2); - Fp(BigInt(r), Default::default()) + Fp(BigInt::from_digits(r), Default::default()) } impl FpBackend<9> for FpC { - const MODULUS: BigInt<9> = BigInt(Self::MODULUS); - const ZERO: BigInt<9> = BigInt([0; 9]); - const ONE: BigInt<9> = BigInt(Self::R); + const MODULUS: BigInt<9> = BigInt::from_digits(Self::MODULUS); + const ZERO: BigInt<9> = BigInt::from_digits([0; 9]); + const ONE: BigInt<9> = BigInt::from_digits(Self::R); fn add_assign(x: &mut Fp, y: &Fp) { - add_assign::(&mut x.0 .0, &y.0 .0); + todo!() } fn mul_assign(x: &mut Fp, y: &Fp) { - mul_assign::(&mut x.0 .0, &y.0 .0); + todo!() } fn from_bigint(x: BigInt<9>) -> Option> { - if gte_modulus::(&x.0) { + if gte_modulus::(&Into::<[u32; 9]>::into(x)) { None } else { Some(from_bigint_unsafe(x)) } } + fn to_bigint(x: Fp) -> BigInt<9> { - let one = [1, 0, 0, 0, 0, 0, 0, 0, 0]; - let mut r = x.0 .0; - // convert back from montgomery form - mul_assign::(&mut r, &one); - BigInt(r) + todo!() + //let one = [1, 0, 0, 0, 0, 0, 0, 0, 0]; + //let mut r = x.0 .0; + //// convert back from montgomery form + //mul_assign::(&mut r, &one); + //BigInt::from_digits(r) } fn pack(x: Fp) -> Vec { - let x = Self::to_bigint(x).0; - let x64 = to_64x4(x); - let mut res = Vec::with_capacity(4); - for limb in x64.iter() { - res.push(*limb); - } - res + todo!() + //let x = Self::to_bigint(x).0; + //let x64 = to_64x4(x); + //let mut res = Vec::with_capacity(4); + //for limb in x64.iter() { + // res.push(*limb); + //} + //res } } diff --git a/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs new file mode 100644 index 00000000000..cf64e4a3425 --- /dev/null +++ b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs @@ -0,0 +1,578 @@ +use ark_ff::BigInteger; +use ark_serialize::{ + CanonicalDeserialize, CanonicalSerialize, Compress, Read, SerializationError, Valid, Validate, + Write, +}; +use ark_std::{ + fmt::Display, + rand::{ + distributions::{Distribution, Standard}, + Rng, + }, +}; +use core::{ + ops::{ + BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Shl, ShlAssign, Shr, + ShrAssign, + }, + str::FromStr, +}; +use num_bigint::{BigUint, ParseBigIntError}; +use zeroize::Zeroize; + +use bnum::BUintD32; + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +pub struct BigInt(pub BUintD32); + +impl BigInt { + pub const ZERO: Self = BigInt(BUintD32::ZERO); + + pub const fn from_digits(digits: [u32; N]) -> Self { + BigInt(BUintD32::from_digits(digits)) + } + + pub const fn new(value: [u32; N]) -> Self { + todo!() + } + + pub const fn zero() -> Self { + todo!() + } + + pub const fn one() -> Self { + todo!() + } + + #[doc(hidden)] + pub const fn const_is_even(&self) -> bool { + todo!() + } + + #[doc(hidden)] + pub const fn const_is_odd(&self) -> bool { + todo!() + } + + #[doc(hidden)] + pub const fn mod_4(&self) -> u8 { + todo!() + } + + /// Compute a right shift of `self` + /// This is equivalent to a (saturating) division by 2. + #[doc(hidden)] + pub const fn const_shr(&self) -> Self { + todo!() + } + + const fn const_geq(&self, other: &Self) -> bool { + todo!() + } + + /// Compute the smallest odd integer `t` such that `self = 2**s * t + 1` for some + /// integer `s = self.two_adic_valuation()`. + #[doc(hidden)] + pub const fn two_adic_coefficient(mut self) -> Self { + todo!() + } + + /// Divide `self` by 2, rounding down if necessary. + /// That is, if `self.is_odd()`, compute `(self - 1)/2`. + /// Else, compute `self/2`. + #[doc(hidden)] + pub const fn divide_by_2_round_down(mut self) -> Self { + todo!() + } + + /// Find the number of bits in the binary decomposition of `self`. + #[doc(hidden)] + pub const fn const_num_bits(self) -> u32 { + todo!() + } + + #[inline] + pub fn add_nocarry(&mut self, other: &Self) -> bool { + todo!() + //let mut this = self.to_64x4(); + //let other = other.to_64x4(); + + //let mut carry = 0; + //for i in 0..4 { + // this[i] = adc!(this[i], other[i], &mut carry); + //} + //*self = Self::from_64x4(this); + //carry != 0 + } + + #[inline] + pub fn sub_noborrow(&mut self, other: &Self) -> bool { + todo!() + //let mut this = self.to_64x4(); + //let other = other.to_64x4(); + + //let mut borrow = 0; + //for i in 0..4 { + // this[i] = sbb!(this[i], other[i], &mut borrow); + //} + //*self = Self::from_64x4(this); + //borrow != 0 + } +} + +impl Zeroize for BigInt { + fn zeroize(&mut self) { + self.0 = BUintD32::ZERO; + } +} + +impl AsMut<[u64]> for BigInt { + #[inline] + fn as_mut(&mut self) -> &mut [u64] { + todo!() + } +} + +impl AsRef<[u64]> for BigInt { + #[inline] + fn as_ref(&self) -> &[u64] { + todo!() + } +} + +impl From for BigInt { + #[inline] + fn from(val: u64) -> BigInt { + todo!() + } +} + +impl From for BigInt { + #[inline] + fn from(val: u32) -> BigInt { + todo!() + } +} + +impl From for BigInt { + #[inline] + fn from(val: u16) -> BigInt { + todo!() + } +} + +impl From for BigInt { + #[inline] + fn from(val: u8) -> BigInt { + todo!() + } +} + +impl TryFrom for BigInt { + type Error = (); + + /// Returns `Err(())` if the bit size of `val` is more than `N * 64`. + #[inline] + fn try_from(val: num_bigint::BigUint) -> Result, Self::Error> { + todo!() + //let bytes = val.to_bytes_le(); + + //if bytes.len() > N * 8 { + // Err(()) + //} else { + // let mut limbs = [0u64; N]; + + // bytes + // .chunks(8) + // .into_iter() + // .enumerate() + // .for_each(|(i, chunk)| { + // let mut chunk_padded = [0u8; 8]; + // chunk_padded[..chunk.len()].copy_from_slice(chunk); + // limbs[i] = u64::from_le_bytes(chunk_padded) + // }); + + // Ok(Self(limbs)) + //} + } +} + +impl From> for BigUint { + #[inline] + fn from(val: BigInt) -> num_bigint::BigUint { + BigUint::from_bytes_le(&val.to_bytes_le()) + } +} + +impl Default for BigInt { + fn default() -> Self { + Self(BUintD32::ZERO) + } +} + +impl CanonicalSerialize for BigInt { + fn serialize_with_mode( + &self, + writer: W, + compress: Compress, + ) -> Result<(), SerializationError> { + todo!() + } + + fn serialized_size(&self, compress: Compress) -> usize { + todo!() + } +} + +impl Valid for BigInt { + fn check(&self) -> Result<(), SerializationError> { + todo!() + } +} + +impl CanonicalDeserialize for BigInt { + fn deserialize_with_mode( + reader: R, + compress: Compress, + validate: Validate, + ) -> Result { + todo!() + } +} + +impl Ord for BigInt { + #[inline] + fn cmp(&self, other: &Self) -> ::core::cmp::Ordering { + todo!() + } +} + +impl PartialOrd for BigInt { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl Display for BigInt { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", BigUint::from(*self)) + } +} + +impl Distribution> for Standard { + fn sample(&self, rng: &mut R) -> BigInt { + todo!() + //let mut res = [0u64; N]; + //for item in res.iter_mut() { + // *item = rng.gen(); + //} + //BigInt::(res) + } +} + +// do not use forward_ref_ref_binop_commutative! for bitand so that we can +// clone as needed, avoiding over-allocation +impl BitAnd<&BigInt> for &BigInt { + type Output = BigInt; + + #[inline] + fn bitand(self, other: &BigInt) -> BigInt { + todo!() + } +} + +impl BitAnd<&BigInt> for BigInt { + type Output = BigInt; + + #[inline] + fn bitand(mut self, other: &BigInt) -> BigInt { + todo!() + } +} + +impl BitAnd> for &BigInt { + type Output = BigInt; + + #[inline] + fn bitand(self, other: BigInt) -> BigInt { + todo!() + } +} + +impl BitAnd> for BigInt { + type Output = BigInt; + + #[inline] + fn bitand(mut self, other: BigInt) -> BigInt { + todo!() + } +} + +impl BitAndAssign> for BigInt { + fn bitand_assign(&mut self, other: BigInt) { + todo!() + } +} + +impl BitAndAssign<&BigInt> for BigInt { + fn bitand_assign(&mut self, other: &BigInt) { + todo!() + } +} + +impl BitOr> for &BigInt { + type Output = BigInt; + + #[inline] + fn bitor(self, other: BigInt) -> BigInt { + todo!() + } +} + +impl BitOr<&BigInt> for &BigInt { + type Output = BigInt; + + #[inline] + fn bitor(self, other: &BigInt) -> BigInt { + todo!() + } +} + +impl BitOr<&BigInt> for BigInt { + type Output = BigInt; + + #[inline] + fn bitor(mut self, other: &BigInt) -> BigInt { + todo!() + } +} + +impl BitOr> for BigInt { + type Output = BigInt; + + #[inline] + fn bitor(mut self, other: BigInt) -> BigInt { + todo!() + } +} + +impl BitOrAssign> for BigInt { + fn bitor_assign(&mut self, other: BigInt) { + todo!() + } +} + +impl BitOrAssign<&BigInt> for BigInt { + fn bitor_assign(&mut self, other: &BigInt) { + todo!() + } +} + +impl Shl for BigInt { + type Output = BigInt; + + #[inline] + fn shl(self, rhs: u32) -> BigInt { + todo!() + } +} +impl Shl for &BigInt { + type Output = BigInt; + + #[inline] + fn shl(self, rhs: u32) -> BigInt { + todo!() + } +} +impl ShlAssign for BigInt { + #[inline] + fn shl_assign(&mut self, rhs: u32) { + todo!() + } +} + +impl Shr for BigInt { + type Output = BigInt; + + #[inline] + fn shr(self, rhs: u32) -> BigInt { + todo!() + } +} +impl Shr for &BigInt { + type Output = BigInt; + + #[inline] + fn shr(self, rhs: u32) -> BigInt { + todo!() + } +} +impl ShrAssign for BigInt { + #[inline] + fn shr_assign(&mut self, rhs: u32) { + todo!() + } +} + +impl BitXor<&BigInt> for BigInt { + type Output = BigInt; + + #[inline] + fn bitxor(mut self, other: &BigInt) -> BigInt { + self ^= other; + self + } +} + +impl BitXor> for BigInt { + type Output = BigInt; + + #[inline] + fn bitxor(mut self, other: BigInt) -> BigInt { + todo!() + } +} + +impl BitXorAssign> for BigInt { + fn bitxor_assign(&mut self, other: BigInt) { + todo!() + } +} + +impl BitXorAssign<&BigInt> for BigInt { + fn bitxor_assign(&mut self, other: &BigInt) { + todo!() + } +} + +impl FromStr for BigInt { + type Err = ParseBigIntError; + + #[inline] + fn from_str(s: &str) -> Result, ParseBigIntError> { + todo!() + } +} + +impl BigInteger for BigInt { + const NUM_LIMBS: usize = N; + + #[inline] + fn add_with_carry(&mut self, other: &Self) -> bool { + todo!(); + } + + #[inline] + fn sub_with_borrow(&mut self, other: &Self) -> bool { + todo!() + } + + #[inline] + #[allow(unused)] + fn mul2(&mut self) -> bool { + todo!() + } + + #[inline] + fn mul(&self, other: &Self) -> (Self, Self) { + todo!() + } + + #[inline] + fn mul_low(&self, other: &Self) -> Self { + todo!() + } + + #[inline] + fn mul_high(&self, other: &Self) -> Self { + todo!() + } + + #[inline] + fn muln(&mut self, mut n: u32) { + todo!() + } + + #[inline] + fn div2(&mut self) { + todo!() + } + + #[inline] + fn divn(&mut self, mut n: u32) { + todo!() + } + + #[inline] + fn is_odd(&self) -> bool { + todo!() + } + + #[inline] + fn is_even(&self) -> bool { + todo!() + } + + #[inline] + fn is_zero(&self) -> bool { + todo!() + } + + #[inline] + fn num_bits(&self) -> u32 { + todo!() + } + + #[inline] + fn get_bit(&self, i: usize) -> bool { + todo!() + } + + #[inline] + fn from_bits_be(bits: &[bool]) -> Self { + todo!() + //let mut res = Self::default(); + //let mut acc: u64 = 0; + + //let mut bits = bits.to_vec(); + //bits.reverse(); + //for (i, bits64) in bits.chunks(64).enumerate() { + // for bit in bits64.iter().rev() { + // acc <<= 1; + // acc += *bit as u64; + // } + // res.0[i] = acc; + // acc = 0; + //} + //res + } + + fn from_bits_le(bits: &[bool]) -> Self { + todo!() + //let mut res = Self::zero(); + //for (bits64, res_i) in bits.chunks(64).zip(&mut res.0) { + // for (i, bit) in bits64.iter().enumerate() { + // *res_i |= (*bit as u64) << i; + // } + //} + //res + } + + #[inline] + fn to_bytes_be(&self) -> Vec { + todo!() + } + + #[inline] + fn to_bytes_le(&self) -> Vec { + todo!() + } +} + +impl Into<[u32; N]> for BigInt { + #[inline] + fn into(self) -> [u32; N] { + *self.0.digits() + } +} diff --git a/curves/src/pasta/wasm_friendly/mod.rs b/curves/src/pasta/wasm_friendly/mod.rs index 2e09509272e..57954f211aa 100644 --- a/curves/src/pasta/wasm_friendly/mod.rs +++ b/curves/src/pasta/wasm_friendly/mod.rs @@ -1,5 +1,8 @@ pub mod bigint32; -pub use bigint32::BigInt; +// pub use bigint32::BigInt; + +pub mod bigint32_attempt2; +pub use bigint32_attempt2::BigInt; pub mod minimal_field; pub use minimal_field::MinimalField; @@ -11,4 +14,4 @@ pub use wasm_fp::Fp; pub mod backend9; pub mod pasta; -pub use pasta::{Fp9,Fq9}; +pub use pasta::{Fp9, Fq9}; diff --git a/curves/src/pasta/wasm_friendly/pasta.rs b/curves/src/pasta/wasm_friendly/pasta.rs index f50c125c098..9c85fb32aba 100644 --- a/curves/src/pasta/wasm_friendly/pasta.rs +++ b/curves/src/pasta/wasm_friendly/pasta.rs @@ -1,5 +1,5 @@ use super::{backend9, wasm_fp}; -use crate::pasta::{Fp,Fq}; +use crate::pasta::{Fp, Fq}; use ark_ff::PrimeField; pub struct Fp9Parameters; @@ -23,7 +23,9 @@ pub type Fp9 = wasm_fp::Fp; impl Fp9 { pub fn from_fp(fp: Fp) -> Self { - backend9::from_bigint_unsafe(super::BigInt(backend9::from_64x4(fp.into_bigint().0))) + backend9::from_bigint_unsafe(super::BigInt::from_digits(backend9::from_64x4( + fp.into_bigint().0, + ))) } } @@ -33,7 +35,6 @@ impl From for Fp9 { } } - pub struct Fq9Parameters; impl backend9::FpConstants for Fq9Parameters { @@ -56,7 +57,9 @@ pub type Fq9 = wasm_fp::Fp; impl Fq9 { pub fn from_fq(fp: Fq) -> Self { - backend9::from_bigint_unsafe(super::BigInt(backend9::from_64x4(fp.into_bigint().0))) + backend9::from_bigint_unsafe(super::BigInt::from_digits(backend9::from_64x4( + fp.into_bigint().0, + ))) } } diff --git a/curves/src/pasta/wasm_friendly/wasm_fp.rs b/curves/src/pasta/wasm_friendly/wasm_fp.rs index 497cdf19bda..fc9f5af2344 100644 --- a/curves/src/pasta/wasm_friendly/wasm_fp.rs +++ b/curves/src/pasta/wasm_friendly/wasm_fp.rs @@ -3,7 +3,7 @@ * * Most of this code was copied over from ark_ff::Fp */ -use crate::pasta::wasm_friendly::bigint32::BigInt; +use crate::pasta::wasm_friendly::bigint32_attempt2::BigInt; use ark_ff::{AdditiveGroup, FftField, Field, One, PrimeField, Zero}; use ark_serialize::{ CanonicalDeserialize, CanonicalDeserializeWithFlags, CanonicalSerialize, @@ -104,7 +104,7 @@ impl, const N: usize> Into> for Fp { impl, const N: usize> From<[u32; N]> for Fp { fn from(val: [u32; N]) -> Self { - Fp::from_bigint(BigInt(val)).unwrap() + Fp::from_bigint(BigInt::from_digits(val)).unwrap() } } From 5748a2314721ab767273f6802406b01337db34ef Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Mon, 28 Jul 2025 20:32:00 +0100 Subject: [PATCH 06/16] Remove unused methods --- .../pasta/wasm_friendly/bigint32_attempt2.rs | 38 ------------------- 1 file changed, 38 deletions(-) diff --git a/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs index cf64e4a3425..d7906500f2f 100644 --- a/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs +++ b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs @@ -32,44 +32,6 @@ impl BigInt { BigInt(BUintD32::from_digits(digits)) } - pub const fn new(value: [u32; N]) -> Self { - todo!() - } - - pub const fn zero() -> Self { - todo!() - } - - pub const fn one() -> Self { - todo!() - } - - #[doc(hidden)] - pub const fn const_is_even(&self) -> bool { - todo!() - } - - #[doc(hidden)] - pub const fn const_is_odd(&self) -> bool { - todo!() - } - - #[doc(hidden)] - pub const fn mod_4(&self) -> u8 { - todo!() - } - - /// Compute a right shift of `self` - /// This is equivalent to a (saturating) division by 2. - #[doc(hidden)] - pub const fn const_shr(&self) -> Self { - todo!() - } - - const fn const_geq(&self, other: &Self) -> bool { - todo!() - } - /// Compute the smallest odd integer `t` such that `self = 2**s * t + 1` for some /// integer `s = self.two_adic_valuation()`. #[doc(hidden)] From 18236da38156200d12acfa256c405afd97de994b Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Mon, 28 Jul 2025 20:38:28 +0100 Subject: [PATCH 07/16] Bnum bigint: instantiate wrapper instances --- .../pasta/wasm_friendly/bigint32_attempt2.rs | 69 ++++++++++--------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs index d7906500f2f..ede2d4a9b40 100644 --- a/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs +++ b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs @@ -17,10 +17,10 @@ use core::{ }, str::FromStr, }; -use num_bigint::{BigUint, ParseBigIntError}; +use num_bigint::BigUint; use zeroize::Zeroize; -use bnum::BUintD32; +use bnum::{errors::ParseIntError, BUintD32}; #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct BigInt(pub BUintD32); @@ -240,7 +240,7 @@ impl BitAnd<&BigInt> for &BigInt { #[inline] fn bitand(self, other: &BigInt) -> BigInt { - todo!() + BigInt(self.0.bitand(other.0)) } } @@ -248,8 +248,8 @@ impl BitAnd<&BigInt> for BigInt { type Output = BigInt; #[inline] - fn bitand(mut self, other: &BigInt) -> BigInt { - todo!() + fn bitand(self, other: &BigInt) -> BigInt { + BigInt(self.0.bitand(other.0)) } } @@ -258,7 +258,7 @@ impl BitAnd> for &BigInt { #[inline] fn bitand(self, other: BigInt) -> BigInt { - todo!() + BigInt(self.0.bitand(other.0)) } } @@ -266,20 +266,20 @@ impl BitAnd> for BigInt { type Output = BigInt; #[inline] - fn bitand(mut self, other: BigInt) -> BigInt { - todo!() + fn bitand(self, other: BigInt) -> BigInt { + BigInt(self.0.bitand(other.0)) } } impl BitAndAssign> for BigInt { fn bitand_assign(&mut self, other: BigInt) { - todo!() + self.0.bitand_assign(other.0) } } impl BitAndAssign<&BigInt> for BigInt { fn bitand_assign(&mut self, other: &BigInt) { - todo!() + self.0.bitand_assign(other.0) } } @@ -288,7 +288,7 @@ impl BitOr> for &BigInt { #[inline] fn bitor(self, other: BigInt) -> BigInt { - todo!() + BigInt(self.0.bitor(other.0)) } } @@ -297,7 +297,7 @@ impl BitOr<&BigInt> for &BigInt { #[inline] fn bitor(self, other: &BigInt) -> BigInt { - todo!() + BigInt(self.0.bitor(other.0)) } } @@ -305,8 +305,8 @@ impl BitOr<&BigInt> for BigInt { type Output = BigInt; #[inline] - fn bitor(mut self, other: &BigInt) -> BigInt { - todo!() + fn bitor(self, other: &BigInt) -> BigInt { + BigInt(self.0.bitor(other.0)) } } @@ -314,20 +314,20 @@ impl BitOr> for BigInt { type Output = BigInt; #[inline] - fn bitor(mut self, other: BigInt) -> BigInt { - todo!() + fn bitor(self, other: BigInt) -> BigInt { + BigInt(self.0.bitor(other.0)) } } impl BitOrAssign> for BigInt { fn bitor_assign(&mut self, other: BigInt) { - todo!() + self.0.bitor_assign(other.0) } } impl BitOrAssign<&BigInt> for BigInt { fn bitor_assign(&mut self, other: &BigInt) { - todo!() + self.0.bitor_assign(other.0) } } @@ -336,7 +336,7 @@ impl Shl for BigInt { #[inline] fn shl(self, rhs: u32) -> BigInt { - todo!() + BigInt(self.0.shl(rhs)) } } impl Shl for &BigInt { @@ -344,13 +344,13 @@ impl Shl for &BigInt { #[inline] fn shl(self, rhs: u32) -> BigInt { - todo!() + BigInt(self.0.shl(rhs)) } } impl ShlAssign for BigInt { #[inline] fn shl_assign(&mut self, rhs: u32) { - todo!() + self.0.shl_assign(rhs) } } @@ -359,7 +359,7 @@ impl Shr for BigInt { #[inline] fn shr(self, rhs: u32) -> BigInt { - todo!() + BigInt(self.0.shr(rhs)) } } impl Shr for &BigInt { @@ -367,13 +367,13 @@ impl Shr for &BigInt { #[inline] fn shr(self, rhs: u32) -> BigInt { - todo!() + BigInt(self.0.shr(rhs)) } } impl ShrAssign for BigInt { #[inline] fn shr_assign(&mut self, rhs: u32) { - todo!() + self.0.shr_assign(rhs) } } @@ -381,9 +381,8 @@ impl BitXor<&BigInt> for BigInt { type Output = BigInt; #[inline] - fn bitxor(mut self, other: &BigInt) -> BigInt { - self ^= other; - self + fn bitxor(self, other: &BigInt) -> BigInt { + BigInt(self.0.bitxor(other.0)) } } @@ -391,29 +390,31 @@ impl BitXor> for BigInt { type Output = BigInt; #[inline] - fn bitxor(mut self, other: BigInt) -> BigInt { - todo!() + fn bitxor(self, other: BigInt) -> BigInt { + BigInt(self.0.bitxor(other.0)) } } impl BitXorAssign> for BigInt { fn bitxor_assign(&mut self, other: BigInt) { - todo!() + self.0.bitxor_assign(other.0) } } impl BitXorAssign<&BigInt> for BigInt { fn bitxor_assign(&mut self, other: &BigInt) { - todo!() + self.0.bitxor_assign(other.0) } } impl FromStr for BigInt { - type Err = ParseBigIntError; + type Err = ParseIntError; #[inline] - fn from_str(s: &str) -> Result, ParseBigIntError> { - todo!() + fn from_str(s: &str) -> Result, Self::Err> { + let inner = BUintD32::::from_str(s)?; + // Wrap it in your BigInt newtype + Ok(BigInt(inner)) } } From a4a0062f4b134a138e456aaf0d80c849376c754e Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Mon, 28 Jul 2025 20:41:56 +0100 Subject: [PATCH 08/16] Implement a few more methods --- .../pasta/wasm_friendly/bigint32_attempt2.rs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs index ede2d4a9b40..4e3f3390266 100644 --- a/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs +++ b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs @@ -105,28 +105,28 @@ impl AsRef<[u64]> for BigInt { impl From for BigInt { #[inline] fn from(val: u64) -> BigInt { - todo!() + BigInt(BUintD32::from(val)) } } impl From for BigInt { #[inline] fn from(val: u32) -> BigInt { - todo!() + BigInt(BUintD32::from(val)) } } impl From for BigInt { #[inline] fn from(val: u16) -> BigInt { - todo!() + BigInt(BUintD32::from(val)) } } impl From for BigInt { #[inline] fn from(val: u8) -> BigInt { - todo!() + BigInt(BUintD32::from(val)) } } @@ -205,7 +205,7 @@ impl CanonicalDeserialize for BigInt { impl Ord for BigInt { #[inline] fn cmp(&self, other: &Self) -> ::core::cmp::Ordering { - todo!() + self.0.cmp(&other.0) } } @@ -224,12 +224,11 @@ impl Display for BigInt { impl Distribution> for Standard { fn sample(&self, rng: &mut R) -> BigInt { - todo!() - //let mut res = [0u64; N]; - //for item in res.iter_mut() { - // *item = rng.gen(); - //} - //BigInt::(res) + let mut res = [0u32; N]; + for item in res.iter_mut() { + *item = rng.gen(); + } + BigInt::from_digits(res) } } From a063c4e5ed343a0223e9785f1a371600616e46d3 Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Tue, 29 Jul 2025 18:02:49 +0100 Subject: [PATCH 09/16] Implement more bigint32_attempt2 methods --- Cargo.lock | 1 + Cargo.toml | 2 +- curves/Cargo.toml | 1 + .../pasta/wasm_friendly/bigint32_attempt2.rs | 116 +++++++++++------- 4 files changed, 77 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd20c896865..4a63128ed83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2266,6 +2266,7 @@ dependencies = [ "bnum", "derivative", "num-bigint", + "num-integer", "rand", "wasm-bindgen", "zeroize", diff --git a/Cargo.toml b/Cargo.toml index 4e11bd601bb..98e5213b9a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ base64 = "0.21.5" bcs = "0.1.3" bitvec = "1.0.0" blake2 = "0.10.0" -bnum = { version = "0.13" } +bnum = { version = "0.13.0" } bs58 = "0.5.0" clap = "4.4.6" command-fds = "0.3" diff --git a/curves/Cargo.toml b/curves/Cargo.toml index 5c369242dac..0511d549a2b 100644 --- a/curves/Cargo.toml +++ b/curves/Cargo.toml @@ -15,6 +15,7 @@ ark-std.workspace = true ark-ec.workspace = true ark-ff.workspace = true num-bigint.workspace = true +num-integer.workspace = true bnum.workspace = true ark-serialize.workspace = true wasm-bindgen.workspace = true diff --git a/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs index 4e3f3390266..d4f07248073 100644 --- a/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs +++ b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs @@ -422,113 +422,145 @@ impl BigInteger for BigInt { #[inline] fn add_with_carry(&mut self, other: &Self) -> bool { - todo!(); + let (new, res) = self.0.carrying_add(other.0, false); + self.0 = new; + res } #[inline] fn sub_with_borrow(&mut self, other: &Self) -> bool { - todo!() + let (new, res) = self.0.borrowing_sub(other.0, false); + self.0 = new; + res } #[inline] #[allow(unused)] fn mul2(&mut self) -> bool { - todo!() + self.0.shr_assign(1); + true // FIXME should this be the shifted bit? } #[inline] - fn mul(&self, other: &Self) -> (Self, Self) { - todo!() + fn muln(&mut self, mut n: u32) { + self.0.shr_assign(n); } #[inline] fn mul_low(&self, other: &Self) -> Self { - todo!() + let (low, _) = self.0.widening_mul(other.0); + BigInt(low) } #[inline] fn mul_high(&self, other: &Self) -> Self { - todo!() + let (_, high) = self.0.widening_mul(other.0); + BigInt(high) } #[inline] - fn muln(&mut self, mut n: u32) { - todo!() + fn mul(&self, other: &Self) -> (Self, Self) { + let (low, high) = self.0.widening_mul(other.0); + (BigInt(low), BigInt(high)) } #[inline] fn div2(&mut self) { - todo!() + self.0 /= BUintD32::from(2u32); } #[inline] - fn divn(&mut self, mut n: u32) { - todo!() + fn divn(&mut self, n: u32) { + self.0 /= BUintD32::from(n); } #[inline] fn is_odd(&self) -> bool { - todo!() + !self.0.rem_euclid(BUintD32::from(2u32)).is_zero() } #[inline] fn is_even(&self) -> bool { - todo!() + self.0.rem_euclid(BUintD32::from(2u32)).is_zero() } #[inline] fn is_zero(&self) -> bool { - todo!() + self.0.is_zero() } #[inline] fn num_bits(&self) -> u32 { - todo!() + self.0.bits() } #[inline] fn get_bit(&self, i: usize) -> bool { - todo!() + self.0.bit(i as u32) } #[inline] fn from_bits_be(bits: &[bool]) -> Self { - todo!() - //let mut res = Self::default(); - //let mut acc: u64 = 0; - - //let mut bits = bits.to_vec(); - //bits.reverse(); - //for (i, bits64) in bits.chunks(64).enumerate() { - // for bit in bits64.iter().rev() { - // acc <<= 1; - // acc += *bit as u64; - // } - // res.0[i] = acc; - // acc = 0; - //} - //res + // FIXME check this works + let mut bytes = vec![]; + for chunk in bits.chunks(8) { + let mut byte = 0u8; + for (i, &bit) in chunk.iter().enumerate() { + if bit { + byte |= 1 << (7 - i); // For big-endian, MSB is first + } + } + bytes.push(byte); + } + BigInt(BUintD32::from_be_slice(&bytes).unwrap()) } + #[inline] fn from_bits_le(bits: &[bool]) -> Self { - todo!() - //let mut res = Self::zero(); - //for (bits64, res_i) in bits.chunks(64).zip(&mut res.0) { - // for (i, bit) in bits64.iter().enumerate() { - // *res_i |= (*bit as u64) << i; - // } - //} - //res + // FIXME check this works + let mut bytes = vec![]; + for chunk in bits.chunks(8) { + let mut byte = 0u8; + for (i, &bit) in chunk.iter().enumerate() { + if bit { + byte |= 1 << i; // For little-endian, LSB is first + } + } + bytes.push(byte); + } + BigInt(BUintD32::from_le_slice(&bytes).unwrap()) } #[inline] fn to_bytes_be(&self) -> Vec { - todo!() + // digits() are little endian + let digits: &[u32; N] = self.0.digits(); + let mut bytes = Vec::with_capacity(4 * N); + + for &digit in digits.iter().rev() { + bytes.push((digit >> 24) as u8); + bytes.push((digit >> 16) as u8); + bytes.push((digit >> 8) as u8); + bytes.push(digit as u8); + } + + bytes } #[inline] fn to_bytes_le(&self) -> Vec { - todo!() + // digits() are little endian + let digits: &[u32; N] = self.0.digits(); + let mut bytes = Vec::with_capacity(4 * N); + + for &digit in digits.iter() { + bytes.push(digit as u8); + bytes.push((digit >> 8) as u8); + bytes.push((digit >> 16) as u8); + bytes.push((digit >> 24) as u8); + } + + bytes } } From effaff8e8a207353ddfcb630433dd881eaa44423 Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Tue, 29 Jul 2025 18:59:22 +0100 Subject: [PATCH 10/16] Implement a few more methods --- .../pasta/wasm_friendly/bigint32_attempt2.rs | 97 ++++++++++--------- 1 file changed, 52 insertions(+), 45 deletions(-) diff --git a/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs index d4f07248073..be881228588 100644 --- a/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs +++ b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs @@ -22,63 +22,76 @@ use zeroize::Zeroize; use bnum::{errors::ParseIntError, BUintD32}; +/// Digits inside are stored in little endian #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct BigInt(pub BUintD32); impl BigInt { pub const ZERO: Self = BigInt(BUintD32::ZERO); + /// Returns limbs in little endian pub const fn from_digits(digits: [u32; N]) -> Self { BigInt(BUintD32::from_digits(digits)) } + #[doc(hidden)] + pub const fn const_is_even(&self) -> bool { + self.0.digits()[0] % 2 == 0 + } + + #[doc(hidden)] + pub const fn const_is_odd(&self) -> bool { + self.0.digits()[0] % 2 == 1 + } + + /// Compute a right shift of `self` + /// This is equivalent to a (saturating) division by 2. + #[doc(hidden)] + pub const fn const_shr(&self) -> Self { + BigInt(self.0.unbounded_shr(1)) + } + /// Compute the smallest odd integer `t` such that `self = 2**s * t + 1` for some /// integer `s = self.two_adic_valuation()`. #[doc(hidden)] pub const fn two_adic_coefficient(mut self) -> Self { - todo!() + assert!(self.const_is_odd()); + // Since `self` is odd, we can always subtract one + // without a borrow + self.const_shr(); + while self.const_is_even() { + self = self.const_shr(); + } + assert!(self.const_is_odd()); + self } /// Divide `self` by 2, rounding down if necessary. /// That is, if `self.is_odd()`, compute `(self - 1)/2`. /// Else, compute `self/2`. #[doc(hidden)] - pub const fn divide_by_2_round_down(mut self) -> Self { - todo!() + pub const fn divide_by_2_round_down(self) -> Self { + BigInt(self.0.unbounded_shr(1)) } /// Find the number of bits in the binary decomposition of `self`. #[doc(hidden)] pub const fn const_num_bits(self) -> u32 { - todo!() + self.0.bits() } #[inline] pub fn add_nocarry(&mut self, other: &Self) -> bool { - todo!() - //let mut this = self.to_64x4(); - //let other = other.to_64x4(); - - //let mut carry = 0; - //for i in 0..4 { - // this[i] = adc!(this[i], other[i], &mut carry); - //} - //*self = Self::from_64x4(this); - //carry != 0 + let (new, res) = self.0.carrying_add(other.0, false); + self.0 = new; + res } #[inline] pub fn sub_noborrow(&mut self, other: &Self) -> bool { - todo!() - //let mut this = self.to_64x4(); - //let other = other.to_64x4(); - - //let mut borrow = 0; - //for i in 0..4 { - // this[i] = sbb!(this[i], other[i], &mut borrow); - //} - //*self = Self::from_64x4(this); - //borrow != 0 + let (new, res) = self.0.borrowing_sub(other.0, false); + self.0 = new; + res } } @@ -88,17 +101,30 @@ impl Zeroize for BigInt { } } +// @volhovm: this is incredibly sketchy. The interface of Integer +// itself (in arkworks) does not allow 32-bit integers... what the +// hell. impl AsMut<[u64]> for BigInt { #[inline] fn as_mut(&mut self) -> &mut [u64] { - todo!() + assert!( + N % 2 == 0, + "N must be even to convert u32 array to u64 array" + ); + unsafe { + std::slice::from_raw_parts_mut(self.0.digits_mut().as_mut_ptr() as *mut u64, N / 2) + } } } impl AsRef<[u64]> for BigInt { #[inline] fn as_ref(&self) -> &[u64] { - todo!() + assert!( + N % 2 == 0, + "N must be even to convert u32 array to u64 array" + ); + unsafe { std::slice::from_raw_parts(self.0.digits().as_ptr() as *const u64, N / 2) } } } @@ -137,25 +163,6 @@ impl TryFrom for BigInt { #[inline] fn try_from(val: num_bigint::BigUint) -> Result, Self::Error> { todo!() - //let bytes = val.to_bytes_le(); - - //if bytes.len() > N * 8 { - // Err(()) - //} else { - // let mut limbs = [0u64; N]; - - // bytes - // .chunks(8) - // .into_iter() - // .enumerate() - // .for_each(|(i, chunk)| { - // let mut chunk_padded = [0u8; 8]; - // chunk_padded[..chunk.len()].copy_from_slice(chunk); - // limbs[i] = u64::from_le_bytes(chunk_padded) - // }); - - // Ok(Self(limbs)) - //} } } From 39c227e71e52abee3652ed105293e80608e6151e Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Wed, 30 Jul 2025 11:22:31 +0100 Subject: [PATCH 11/16] Implement some more methods --- curves/src/pasta/wasm_friendly/backend9.rs | 4 +- .../pasta/wasm_friendly/bigint32_attempt2.rs | 9 +- curves/src/pasta/wasm_friendly/wasm_fp.rs | 90 ++++++++++++------- 3 files changed, 68 insertions(+), 35 deletions(-) diff --git a/curves/src/pasta/wasm_friendly/backend9.rs b/curves/src/pasta/wasm_friendly/backend9.rs index 4c48fe3ab8c..ace34eb2d3d 100644 --- a/curves/src/pasta/wasm_friendly/backend9.rs +++ b/curves/src/pasta/wasm_friendly/backend9.rs @@ -180,11 +180,11 @@ impl FpBackend<9> for FpC { const ONE: BigInt<9> = BigInt::from_digits(Self::R); fn add_assign(x: &mut Fp, y: &Fp) { - todo!() + std::ops::AddAssign::add_assign(x, y) } fn mul_assign(x: &mut Fp, y: &Fp) { - todo!() + std::ops::MulAssign::mul_assign(x, y) } fn from_bigint(x: BigInt<9>) -> Option> { diff --git a/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs index be881228588..16a1e75f2f9 100644 --- a/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs +++ b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs @@ -128,6 +128,13 @@ impl AsRef<[u64]> for BigInt { } } +impl From for BigInt { + #[inline] + fn from(val: u128) -> BigInt { + BigInt(BUintD32::from(val)) + } +} + impl From for BigInt { #[inline] fn from(val: u64) -> BigInt { @@ -449,7 +456,7 @@ impl BigInteger for BigInt { } #[inline] - fn muln(&mut self, mut n: u32) { + fn muln(&mut self, n: u32) { self.0.shr_assign(n); } diff --git a/curves/src/pasta/wasm_friendly/wasm_fp.rs b/curves/src/pasta/wasm_friendly/wasm_fp.rs index fc9f5af2344..5a577de091c 100644 --- a/curves/src/pasta/wasm_friendly/wasm_fp.rs +++ b/curves/src/pasta/wasm_friendly/wasm_fp.rs @@ -19,8 +19,6 @@ use std::{ str::FromStr, }; -use super::minimal_field::MinimalField; - pub trait FpBackend: Send + Sync + 'static + Sized { const MODULUS: BigInt; const ZERO: BigInt; @@ -110,14 +108,14 @@ impl, const N: usize> From<[u32; N]> for Fp { // field -impl, const N: usize> MinimalField for Fp { - fn square_in_place(&mut self) -> &mut Self { - // implemented with mul_assign for now - let self_copy = *self; - self.mul_assign(&self_copy); - self - } -} +//impl, const N: usize> MinimalField for Fp { +// fn square_in_place(&mut self) -> &mut Self { +// // implemented with mul_assign for now +// let self_copy = *self; +// self.mul_assign(&self_copy); +// self +// } +//} // add, zero, neg @@ -625,67 +623,97 @@ impl, const N: usize> PartialOrd for Fp { impl, const N: usize> From for Fp { fn from(other: u128) -> Self { - todo!() + Self::new(From::from(other)) } } impl, const N: usize> From for Fp { fn from(other: u64) -> Self { - todo!() + Self::new(From::from(other)) } } impl, const N: usize> From for Fp { fn from(other: u32) -> Self { - todo!() + Self::new(From::from(other)) } } impl, const N: usize> From for Fp { fn from(other: u16) -> Self { - todo!() + Self::new(From::from(other)) } } impl, const N: usize> From for Fp { fn from(other: u8) -> Self { - todo!() + Self::new(From::from(other)) } } impl, const N: usize> From for Fp { fn from(other: i128) -> Self { - todo!() + if other >= 0 { + From::from(other as u128) + } else { + let other_bigint = From::from((-other) as u128); + assert!(P::MODULUS > other_bigint); + Fp::::new(P::MODULUS) - Fp::::new(other_bigint) + } } } impl, const N: usize> From for Fp { fn from(other: i64) -> Self { - todo!() + if other >= 0 { + From::from(other as u64) + } else { + let other_bigint = From::from((-other) as u64); + assert!(P::MODULUS > other_bigint); + Fp::::new(P::MODULUS) - Fp::::new(other_bigint) + } } } impl, const N: usize> From for Fp { fn from(other: i32) -> Self { - todo!() + if other >= 0 { + From::from(other as u32) + } else { + let other_bigint = From::from((-other) as u32); + assert!(P::MODULUS > other_bigint); + Fp::::new(P::MODULUS) - Fp::::new(other_bigint) + } } } impl, const N: usize> From for Fp { fn from(other: i16) -> Self { - todo!() + if other >= 0 { + From::from(other as u16) + } else { + let other_bigint = From::from((-other) as u16); + assert!(P::MODULUS > other_bigint); + Fp::::new(P::MODULUS) - Fp::::new(other_bigint) + } } } impl, const N: usize> From for Fp { fn from(other: i8) -> Self { - todo!() + if other >= 0 { + From::from(other as u8) + } else { + let other_bigint = From::from((-other) as u8); + assert!(P::MODULUS > other_bigint); + Fp::::new(P::MODULUS) - Fp::::new(other_bigint) + } } } impl, const N: usize> From for Fp { fn from(other: bool) -> Self { - todo!() + Self::new(From::from(other as u32)) } } @@ -733,13 +761,11 @@ impl, const N: usize> PrimeField for Fp { #[inline] fn from_bigint(r: BigInt) -> Option { - todo!() - //P::from_bigint(r) + P::from_bigint(r) } fn into_bigint(self) -> BigInt { - todo!() - //P::into_bigint(self) + P::to_bigint(self) } } @@ -866,16 +892,16 @@ impl, const N: usize> Field for Fp { #[inline] fn square(&self) -> Self { - todo!() - //let mut temp = *self; - //temp.square_in_place(); - //temp + let mut temp = *self; + temp.square_in_place(); + temp } fn square_in_place(&mut self) -> &mut Self { - todo!() - //P::square_in_place(self); - //self + // implemented with mul_assign for now + let self_copy = *self; + self.mul_assign(&self_copy); + self } #[inline] From 31c76a1643d4017d103d197356716766834e3f3c Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Wed, 30 Jul 2025 17:33:55 +0100 Subject: [PATCH 12/16] More methods + debugging for montgomery --- curves/src/pasta/curves/pallas.rs | 101 ++++++++++- curves/src/pasta/wasm_friendly/backend9.rs | 171 ++++++++++++++++-- curves/src/pasta/wasm_friendly/bigint32.rs | 125 ++++++------- .../pasta/wasm_friendly/bigint32_attempt2.rs | 7 + curves/src/pasta/wasm_friendly/pasta.rs | 167 ++++++++++++++++- curves/src/pasta/wasm_friendly/wasm_fp.rs | 1 + poseidon/benches/poseidon_bench.rs | 2 +- poseidon/src/poseidon.rs | 53 ++++++ 8 files changed, 541 insertions(+), 86 deletions(-) diff --git a/curves/src/pasta/curves/pallas.rs b/curves/src/pasta/curves/pallas.rs index 1677ad999ff..a4fe08edf9d 100644 --- a/curves/src/pasta/curves/pallas.rs +++ b/curves/src/pasta/curves/pallas.rs @@ -93,23 +93,112 @@ impl CurveConfig for WasmPallasParameters { /// COFACTOR_INV = 1 // FIXME const COFACTOR_INV: crate::pasta::wasm_friendly::Fq9 = - crate::pasta::wasm_friendly::Fp(BigInt::ZERO, PhantomData); + crate::pasta::wasm_friendly::Fp(BigInt::ONE, PhantomData); } pub type WasmPallas = Affine; pub type WasmProjectivePallas = Projective; +//pub const G_GENERATOR_Y: Fp = +// MontFp!("12418654782883325593414442427049395787963493412651469444558597405572177144507");a +// +// BigInt::from_digits([ +// 0x0, 0x1B74B5A3, 0x0A12937C, 0x53DFA9F0, 0x6378EE54, 0x8F655BD4, 0x333D4771, 0x19CF7A23, +// 0xCAED2ABB, +// ]), +pub const G_GENERATOR_Y_WASM: crate::pasta::wasm_friendly::Fp9 = crate::pasta::wasm_friendly::Fp( + BigInt::from_digits(crate::pasta::wasm_friendly::backend9::from_64x4([ + 0xBBA2DEAC32A7FC19, + 0x1774D3334DB556F8, + 0x45EE87360F9AFD35, + 0xC73921A03A5B47B1, + ])), + PhantomData, +); + impl SWCurveConfig for WasmPallasParameters { - // FIXME const COEFF_A: Self::BaseField = crate::pasta::wasm_friendly::Fp(BigInt::ZERO, PhantomData); - // FIXME - const COEFF_B: Self::BaseField = crate::pasta::wasm_friendly::Fp(BigInt::ZERO, PhantomData); + const COEFF_B: Self::BaseField = crate::pasta::wasm_friendly::Fp(BigInt::FIVE, PhantomData); - // FIXME const GENERATOR: Affine = Affine::new_unchecked( - crate::pasta::wasm_friendly::Fp(BigInt::ZERO, PhantomData), + crate::pasta::wasm_friendly::Fp(BigInt::ONE, PhantomData), crate::pasta::wasm_friendly::Fp(BigInt::ZERO, PhantomData), ); } + +#[cfg(test)] +mod tests { + use super::*; + + use crate::pasta::{ + wasm_friendly::{wasm_fp::FpBackend, Fp9}, + Fp, + }; + + #[test] + pub fn test_wasm_curve_basic_ops() { + { + //let x: Fp = rand::random(); + let x: Fp = Fp::from(1u32); + let z: Fp9 = x.into(); + let x2: Fp = z.into(); + println!("x: {:?}", x); + println!("x limbs: {:?}", x.0 .0); + println!("z: {:?}", z); + println!("z limbs: {:?}", FpBackend::pack(z)); + println!("x2: {:?}", x2); + assert!(x2 == x); + } + + { + let x: Fp = rand::random(); + let y: Fp = rand::random(); + let z: Fp = x * y; + } + + assert!(false); + + { + let x: Fp = rand::random(); + let y: Fp = rand::random(); + let x_fp9: Fp9 = x.into(); + let y_fp9: Fp9 = y.into(); + } + + { + let x: Fp = rand::random(); + let y: Fp = rand::random(); + + let x_fp9: Fp9 = From::from(x); + let y_fp9: Fp9 = From::from(y); + let z_fp9: Fp9 = x_fp9 * y_fp9; + } + + { + let x: Fp = rand::random(); + let y: Fp = rand::random(); + let z: Fp = x * y; + let z: Fp = z * x; + } + + { + let x: Fp = rand::random(); + let y: Fp = rand::random(); + let x_fp9: Fp9 = From::from(x); + let y_fp9: Fp9 = From::from(y); + let z_fp9: Fp9 = x_fp9 * y_fp9; + let z_fp9: Fp9 = z_fp9 * x_fp9; + } + + { + let x: Fp = rand::random(); + let y: Fp = rand::random(); + let z: Fp = x * y; + let z: Fp = z * x; + let z: Fp = z * y; + let z: Fp = z * x; + } + } +} diff --git a/curves/src/pasta/wasm_friendly/backend9.rs b/curves/src/pasta/wasm_friendly/backend9.rs index ace34eb2d3d..0e1f75e78f2 100644 --- a/curves/src/pasta/wasm_friendly/backend9.rs +++ b/curves/src/pasta/wasm_friendly/backend9.rs @@ -44,6 +44,31 @@ pub const fn to_64x4(pa: [u32; 9]) -> [u64; 4] { p } +// Converts from "normal" 32 bit bignum limb format to the 32x9 with 29 bit limbs. +pub const fn from_32x8(pa: [u32; 8]) -> [u32; 9] { + let mut p = [0u64; 4]; + p[0] = (pa[0] as u64) | ((pa[1] as u64) << 32); + p[1] = (pa[2] as u64) | ((pa[3] as u64) << 32); + p[2] = (pa[4] as u64) | ((pa[5] as u64) << 32); + p[3] = (pa[6] as u64) | ((pa[7] as u64) << 32); + from_64x4(p) +} + +/// Checks if the number satisfies 32x9 shape (each limb 29 bits). +pub const fn is_32x9_shape(pa: [u32; 9]) -> bool { + let b0 = (pa[0] & MASK) != 0; + let b1 = (pa[1] & MASK) != 0; + let b2 = (pa[2] & MASK) != 0; + let b3 = (pa[3] & MASK) != 0; + let b4 = (pa[4] & MASK) != 0; + let b5 = (pa[5] & MASK) != 0; + let b6 = (pa[6] & MASK) != 0; + let b7 = (pa[7] & MASK) != 0; + let b8 = (pa[8] & MASK) != 0; + + b0 && b1 && b2 && b3 && b4 && b5 && b6 && b7 && b8 +} + pub trait FpConstants: Send + Sync + 'static + Sized { const MODULUS: B; const MODULUS64: B64 = { @@ -83,6 +108,22 @@ fn gte_modulus(x: &B) -> bool { // - introduce locals for a[i] instead of accessing memory multiple times // - only do 1 carry pass at the end, by proving properties of greater-than on uncarried result // - use cheaper, approximate greater-than check a[8] > Fp::MODULUS[8] +/// Performs modular addition: x = (x + y) mod p +/// +/// This function adds two field elements and stores the result in the first operand. +/// The result is reduced to ensure it remains within the canonical range [0, p-1]. +/// +/// # Parameters +/// * `x` - First operand, modified in-place to store the result +/// * `y` - Second operand to add +/// +/// # Type Parameters +/// * `FpC` - Type implementing the FpConstants trait that defines the modulus +/// +/// # Implementation Notes +/// - First performs the addition with carry propagation +/// - Then conditionally subtracts the modulus if the result is greater than or equal to it +/// - Uses 30-bit limbs with SHIFT and MASK constants to handle overflow pub fn add_assign(x: &mut B, y: &B) { let mut tmp: u32; let mut carry: i32 = 0; @@ -122,7 +163,25 @@ fn conditional_reduce(x: &mut B) { } } -/// Montgomery multiplication +/// Performs Montgomery multiplication: x = (x * y * R^-1) mod p +/// +/// This function multiplies two field elements in Montgomery form and stores +/// the result in the first operand. The implementation uses the CIOS (Coarsely +/// Integrated Operand Scanning) method for Montgomery multiplication. +/// +/// # Parameters +/// * `x` - First operand, modified in-place to store the result +/// * `y` - Second operand +/// +/// # Type Parameters +/// * `FpC` - Type implementing the FpConstants trait that defines the modulus +/// and Montgomery reduction parameters +/// +/// # Implementation Notes +/// - Uses a 9-limb representation for intermediate calculations +/// - Performs a conditional reduction at the end to ensure the result is in +/// the canonical range [0, p-1] +/// - Optimized to minimize carry operations in the main loop pub fn mul_assign(x: &mut B, y: &B) { // load y[i] into local u64s // TODO make sure these are locals @@ -168,7 +227,9 @@ pub fn mul_assign(x: &mut B, y: &B) { // implement FpBackend given FpConstants pub fn from_bigint_unsafe(x: BigInt<9>) -> Fp { - let mut r = Into::<[u32; 9]>::into(x); + let r: [u32; 9] = Into::into(x); + assert!(r[8] == 0); + let mut r = from_32x8(r[0..8].try_into().unwrap()); // convert to montgomery form mul_assign::(&mut r, &FpC::R2); Fp(BigInt::from_digits(r), Default::default()) @@ -196,22 +257,100 @@ impl FpBackend<9> for FpC { } fn to_bigint(x: Fp) -> BigInt<9> { - todo!() - //let one = [1, 0, 0, 0, 0, 0, 0, 0, 0]; - //let mut r = x.0 .0; - //// convert back from montgomery form - //mul_assign::(&mut r, &one); - //BigInt::from_digits(r) + let one = [1, 0, 0, 0, 0, 0, 0, 0, 0]; + let mut r = x.0.into_digits(); + // convert back from montgomery form + mul_assign::(&mut r, &one); + BigInt::from_digits(r) } fn pack(x: Fp) -> Vec { - todo!() - //let x = Self::to_bigint(x).0; - //let x64 = to_64x4(x); - //let mut res = Vec::with_capacity(4); - //for limb in x64.iter() { - // res.push(*limb); - //} - //res + let x = Self::to_bigint(x).into_digits(); + let x64 = to_64x4(x); + let mut res = Vec::with_capacity(4); + for limb in x64.iter() { + res.push(*limb); + } + res + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_64_32_conversion_identity() { + // Test with various inputs + let test_cases = [ + [0u64; 4], + [1u64, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], + [u64::MAX, u64::MAX, u64::MAX, u64::MAX], + [ + 0x123456789ABCDEF0, + 0xFEDCBA9876543210, + 0xAAAABBBBCCCCDDDD, + 0x1122334455667788, + ], + ]; + + for input in &test_cases { + // Convert to 9x32-bit representation and back + let intermediate = from_64x4(*input); + let result = to_64x4(intermediate); + + // Check if the round-trip conversion preserves the original value + assert_eq!( + result, *input, + "Conversion failed for input: {:?}, got: {:?}", + input, result + ); + } + + // Test with random inputs + for _ in 0..100 { + let random_input = [ + rand::random::(), + rand::random::(), + rand::random::(), + rand::random::(), + ]; + + let intermediate = from_64x4(random_input); + let result = to_64x4(intermediate); + + assert!( + is_32x9_shape(intermediate), + "from_64x4 does not pass the is_32x9_shape check" + ); + + assert_eq!( + result, random_input, + "Conversion failed for random input: {:?}", + random_input + ); + } + + { + let out_of_shape: [u32; 9] = [ + 1 << 31, + 1 << 31, + 1 << 31, + 1 << 31, + 1 << 31, + 1 << 31, + 1 << 31, + 1 << 31, + 1 << 31, + ]; + + assert!( + !is_32x9_shape(out_of_shape), + "out of shape must NOT pass is_32x9_shape check" + ); + } } } diff --git a/curves/src/pasta/wasm_friendly/bigint32.rs b/curves/src/pasta/wasm_friendly/bigint32.rs index 7cf11cc1be9..0b2716e9938 100644 --- a/curves/src/pasta/wasm_friendly/bigint32.rs +++ b/curves/src/pasta/wasm_friendly/bigint32.rs @@ -80,7 +80,7 @@ impl BigInt { //result } - const fn const_geq(&self, other: &Self) -> bool { + const fn const_geq(&self, _other: &Self) -> bool { todo!() //const_for!((i in 0..N) { // let a = self.0[N - i - 1]; @@ -127,7 +127,7 @@ impl BigInt { } #[inline] - pub fn add_nocarry(&mut self, other: &Self) -> bool { + pub fn add_nocarry(&mut self, _other: &Self) -> bool { todo!() //let mut this = self.to_64x4(); //let other = other.to_64x4(); @@ -141,7 +141,7 @@ impl BigInt { } #[inline] - pub fn sub_noborrow(&mut self, other: &Self) -> bool { + pub fn sub_noborrow(&mut self, _other: &Self) -> bool { todo!() //let mut this = self.to_64x4(); //let other = other.to_64x4(); @@ -173,7 +173,7 @@ impl AsRef<[u64]> for BigInt { impl From for BigInt { #[inline] - fn from(val: u64) -> BigInt { + fn from(_val: u64) -> BigInt { todo!() //let mut repr = Self::default(); //repr.0[0] = val; @@ -213,7 +213,7 @@ impl TryFrom for BigInt { /// Returns `Err(())` if the bit size of `val` is more than `N * 64`. #[inline] - fn try_from(val: num_bigint::BigUint) -> Result, Self::Error> { + fn try_from(_val: num_bigint::BigUint) -> Result, Self::Error> { todo!() //let bytes = val.to_bytes_le(); @@ -329,7 +329,7 @@ impl BitAnd<&BigInt> for &BigInt { type Output = BigInt; #[inline] - fn bitand(self, other: &BigInt) -> BigInt { + fn bitand(self, _other: &BigInt) -> BigInt { todo!() } } @@ -338,7 +338,7 @@ impl BitAnd<&BigInt> for BigInt { type Output = BigInt; #[inline] - fn bitand(mut self, other: &BigInt) -> BigInt { + fn bitand(self, _other: &BigInt) -> BigInt { todo!() } } @@ -347,7 +347,7 @@ impl BitAnd> for &BigInt { type Output = BigInt; #[inline] - fn bitand(self, other: BigInt) -> BigInt { + fn bitand(self, _other: BigInt) -> BigInt { todo!() } } @@ -356,19 +356,19 @@ impl BitAnd> for BigInt { type Output = BigInt; #[inline] - fn bitand(mut self, other: BigInt) -> BigInt { + fn bitand(self, _other: BigInt) -> BigInt { todo!() } } impl BitAndAssign> for BigInt { - fn bitand_assign(&mut self, other: BigInt) { + fn bitand_assign(&mut self, _other: BigInt) { todo!() } } impl BitAndAssign<&BigInt> for BigInt { - fn bitand_assign(&mut self, other: &BigInt) { + fn bitand_assign(&mut self, _other: &BigInt) { todo!() } } @@ -377,7 +377,7 @@ impl BitOr> for &BigInt { type Output = BigInt; #[inline] - fn bitor(self, other: BigInt) -> BigInt { + fn bitor(self, _other: BigInt) -> BigInt { todo!() } } @@ -386,7 +386,7 @@ impl BitOr<&BigInt> for &BigInt { type Output = BigInt; #[inline] - fn bitor(self, other: &BigInt) -> BigInt { + fn bitor(self, _other: &BigInt) -> BigInt { todo!() } } @@ -395,7 +395,7 @@ impl BitOr<&BigInt> for BigInt { type Output = BigInt; #[inline] - fn bitor(mut self, other: &BigInt) -> BigInt { + fn bitor(self, _other: &BigInt) -> BigInt { todo!() } } @@ -404,19 +404,19 @@ impl BitOr> for BigInt { type Output = BigInt; #[inline] - fn bitor(mut self, other: BigInt) -> BigInt { + fn bitor(self, _other: BigInt) -> BigInt { todo!() } } impl BitOrAssign> for BigInt { - fn bitor_assign(&mut self, other: BigInt) { + fn bitor_assign(&mut self, _other: BigInt) { todo!() } } impl BitOrAssign<&BigInt> for BigInt { - fn bitor_assign(&mut self, other: &BigInt) { + fn bitor_assign(&mut self, _other: &BigInt) { todo!() } } @@ -425,7 +425,7 @@ impl Shl for BigInt { type Output = BigInt; #[inline] - fn shl(self, rhs: u32) -> BigInt { + fn shl(self, _rhs: u32) -> BigInt { todo!() } } @@ -433,13 +433,13 @@ impl Shl for &BigInt { type Output = BigInt; #[inline] - fn shl(self, rhs: u32) -> BigInt { + fn shl(self, _rhs: u32) -> BigInt { todo!() } } impl ShlAssign for BigInt { #[inline] - fn shl_assign(&mut self, rhs: u32) { + fn shl_assign(&mut self, _rhs: u32) { todo!() } } @@ -448,7 +448,7 @@ impl Shr for BigInt { type Output = BigInt; #[inline] - fn shr(self, rhs: u32) -> BigInt { + fn shr(self, _rhs: u32) -> BigInt { todo!() } } @@ -456,13 +456,13 @@ impl Shr for &BigInt { type Output = BigInt; #[inline] - fn shr(self, rhs: u32) -> BigInt { + fn shr(self, _rhs: u32) -> BigInt { todo!() } } impl ShrAssign for BigInt { #[inline] - fn shr_assign(&mut self, rhs: u32) { + fn shr_assign(&mut self, _rhs: u32) { todo!() } } @@ -481,19 +481,19 @@ impl BitXor> for BigInt { type Output = BigInt; #[inline] - fn bitxor(mut self, other: BigInt) -> BigInt { + fn bitxor(self, _other: BigInt) -> BigInt { todo!() } } impl BitXorAssign> for BigInt { - fn bitxor_assign(&mut self, other: BigInt) { + fn bitxor_assign(&mut self, _other: BigInt) { todo!() } } impl BitXorAssign<&BigInt> for BigInt { - fn bitxor_assign(&mut self, other: &BigInt) { + fn bitxor_assign(&mut self, _other: &BigInt) { todo!() } } @@ -502,7 +502,7 @@ impl FromStr for BigInt { type Err = ParseBigIntError; #[inline] - fn from_str(s: &str) -> Result, ParseBigIntError> { + fn from_str(_s: &str) -> Result, ParseBigIntError> { todo!() } } @@ -511,7 +511,7 @@ impl BigInteger for BigInt { const NUM_LIMBS: usize = N; #[inline] - fn add_with_carry(&mut self, other: &Self) -> bool { + fn add_with_carry(&mut self, _other: &Self) -> bool { { todo!(); //use arithmetic::adc_for_add_with_carry as adc; @@ -546,7 +546,7 @@ impl BigInteger for BigInt { } #[inline] - fn sub_with_borrow(&mut self, other: &Self) -> bool { + fn sub_with_borrow(&mut self, _other: &Self) -> bool { todo!() //use arithmetic::sbb_for_sub_with_borrow as sbb; @@ -581,48 +581,49 @@ impl BigInteger for BigInt { #[inline] #[allow(unused)] fn mul2(&mut self) -> bool { - #[cfg(all(target_arch = "x86_64", feature = "asm"))] - #[allow(unsafe_code)] - { - let mut carry = 0; - - for i in 0..N { - unsafe { - use core::arch::x86_64::_addcarry_u64; - carry = _addcarry_u64(carry, self.0[i], self.0[i], &mut self.0[i]) - }; - } - - carry != 0 - } - - #[cfg(not(all(target_arch = "x86_64", feature = "asm")))] - { - todo!() - //let mut last = 0; - //for i in 0..N { - // let a = &mut self.0[i]; - // let tmp = *a >> 63; - // *a <<= 1; - // *a |= last; - // last = tmp; - //} - //last != 0 - } + todo!() + // #[cfg(all(target_arch = "x86_64", feature = "asm"))] + // #[allow(unsafe_code)] + // { + // let mut carry = 0; + // + // for i in 0..N { + // unsafe { + // use core::arch::x86_64::_addcarry_u64; + // carry = _addcarry_u64(carry, self.0[i], self.0[i], &mut self.0[i]) + // }; + // } + // + // carry != 0 + // } + // + // #[cfg(not(all(target_arch = "x86_64", feature = "asm")))] + // { + // todo!() + // //let mut last = 0; + // //for i in 0..N { + // // let a = &mut self.0[i]; + // // let tmp = *a >> 63; + // // *a <<= 1; + // // *a |= last; + // // last = tmp; + // //} + // //last != 0 + //} } #[inline] - fn mul(&self, other: &Self) -> (Self, Self) { + fn mul(&self, _other: &Self) -> (Self, Self) { todo!() } #[inline] - fn mul_low(&self, other: &Self) -> Self { + fn mul_low(&self, _other: &Self) -> Self { todo!() } #[inline] - fn mul_high(&self, other: &Self) -> Self { + fn mul_high(&self, _other: &Self) -> Self { todo!() } @@ -736,7 +737,7 @@ impl BigInteger for BigInt { } #[inline] - fn from_bits_be(bits: &[bool]) -> Self { + fn from_bits_be(_bits: &[bool]) -> Self { todo!() //let mut res = Self::default(); //let mut acc: u64 = 0; @@ -754,7 +755,7 @@ impl BigInteger for BigInt { //res } - fn from_bits_le(bits: &[bool]) -> Self { + fn from_bits_le(_bits: &[bool]) -> Self { todo!() //let mut res = Self::zero(); //for (bits64, res_i) in bits.chunks(64).zip(&mut res.0) { diff --git a/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs index 16a1e75f2f9..8717099d9f8 100644 --- a/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs +++ b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs @@ -28,12 +28,19 @@ pub struct BigInt(pub BUintD32); impl BigInt { pub const ZERO: Self = BigInt(BUintD32::ZERO); + pub const ONE: Self = BigInt(BUintD32::ONE); + pub const FIVE: Self = BigInt(BUintD32::FIVE); /// Returns limbs in little endian pub const fn from_digits(digits: [u32; N]) -> Self { BigInt(BUintD32::from_digits(digits)) } + /// Returns limbs in little endian + pub const fn into_digits(self) -> [u32; N] { + *self.0.digits() + } + #[doc(hidden)] pub const fn const_is_even(&self) -> bool { self.0.digits()[0] % 2 == 0 diff --git a/curves/src/pasta/wasm_friendly/pasta.rs b/curves/src/pasta/wasm_friendly/pasta.rs index 9c85fb32aba..101c2c6ed28 100644 --- a/curves/src/pasta/wasm_friendly/pasta.rs +++ b/curves/src/pasta/wasm_friendly/pasta.rs @@ -1,4 +1,7 @@ -use super::{backend9, wasm_fp}; +use super::{ + backend9, + wasm_fp::{self, FpBackend}, +}; use crate::pasta::{Fp, Fq}; use ark_ff::PrimeField; @@ -27,6 +30,10 @@ impl Fp9 { fp.into_bigint().0, ))) } + + pub fn into_fp(self: Fp9) -> Fp { + Fp::from_bigint(ark_ff::BigInt(FpBackend::pack(self).try_into().unwrap())).unwrap() + } } impl From for Fp9 { @@ -35,6 +42,12 @@ impl From for Fp9 { } } +impl Into for Fp9 { + fn into(self) -> Fp { + Fp9::into_fp(self) + } +} + pub struct Fq9Parameters; impl backend9::FpConstants for Fq9Parameters { @@ -68,3 +81,155 @@ impl From for Fq9 { Fq9::from_fq(fp) } } + +#[cfg(test)] +mod tests { + + use crate::pasta::{ + wasm_friendly::{ + backend9::{add_assign, from_32x8, mul_assign, FpConstants}, + bigint32_attempt2::BigInt, + pasta::Fp9Parameters, + wasm_fp::FpBackend, + Fp9, + }, + Fp, + }; + + #[test] + pub fn test_mod_add() { + { + let mut b1: [u32; 9] = BigInt::from(7u32).into_digits(); + let b2: [u32; 9] = BigInt::from(9u32).into_digits(); + add_assign::(&mut b1, &b2); + assert!(b1 == BigInt::from(16u32).into_digits()) + } + { + let mut b1: [u32; 9] = BigInt::from(7u32).into_digits(); + let b2: [u32; 9] = ::MODULUS; + add_assign::(&mut b1, &b2); + assert!(b1 == BigInt::from(7u32).into_digits()) + } + { + let mut b1: [u32; 9] = ::R; + let b2: [u32; 9] = ::MODULUS; + add_assign::(&mut b1, &b2); + assert!(b1 == ::R) + } + { + let mut b1: [u32; 9] = ::R2; + let b2: [u32; 9] = ::MODULUS; + add_assign::(&mut b1, &b2); + assert!(b1 == ::R2) + } + { + let mut b1: [u32; 9] = ::MODULUS; + let b2: [u32; 9] = ::R2; + add_assign::(&mut b1, &b2); + assert!(b1 == ::R2) + } + } + + #[test] + pub fn test_montgomery_mult() { + // 1 * R / R = 1 + { + let mut b1: [u32; 9] = BigInt::from(1u32).into_digits(); + let b2: [u32; 9] = Fp9Parameters::R; + mul_assign::(&mut b1, &b2); + assert!(b1 == BigInt::from(1u32).into_digits()); + } + // 12345 * R / R = 12345 + { + let mut b1: [u32; 9] = BigInt::from(12345u32).into_digits(); + let b2: [u32; 9] = Fp9Parameters::R; + mul_assign::(&mut b1, &b2); + assert!(b1 == BigInt::from(12345u32).into_digits()); + } + // 0 * R / R = 0 + { + let mut b1: [u32; 9] = BigInt::from(0u32).into_digits(); + let b2: [u32; 9] = ::R; + mul_assign::(&mut b1, &b2); + assert!(b1 == BigInt::from(0u32).into_digits()); + } + // R * R / R = R + { + let mut b1: [u32; 9] = Fp9Parameters::R; + let b2: [u32; 9] = Fp9Parameters::R; + mul_assign::(&mut b1, &b2); + println!("b1: {:?}", b1); + println!("R: {:?}", Fp9Parameters::R); + assert!(b1 == Fp9Parameters::R); + } + { + let mut b1: [u32; 9] = from_32x8( + BigInt::<9>::from(1u64 << 40).into_digits()[0..8] + .try_into() + .unwrap(), + ); + let mut b2: [u32; 9] = from_32x8( + BigInt::<9>::from(1u64 << 2).into_digits()[0..8] + .try_into() + .unwrap(), + ); + let b3: [u32; 9] = from_32x8( + BigInt::<9>::from(1u64 << 42).into_digits()[0..8] + .try_into() + .unwrap(), + ); + let b_one: [u32; 9] = BigInt::from(1u32).into_digits(); + println!("b1 {:?}", b1); + println!("b2 {:?}", b2); + println!("b3 {:?}", b3); + println!("b_one {:?}", b_one); + mul_assign::(&mut b1, &::R2); + println!("b1 step 1 {:?}", b1); + mul_assign::(&mut b2, &::R2); + println!("b2 step 2 {:?}", b2); + mul_assign::(&mut b1, &b2); + println!("b1 step 3 {:?}", b1); + mul_assign::(&mut b1, &b_one); + println!("b1 step 4 {:?}", b1); + assert!(b1 == b3); + } + { + let mut b1: [u32; 9] = BigInt::from(1u32).into_digits(); + let b2: [u32; 9] = ::MODULUS; + mul_assign::(&mut b1, &b2); + println!("{:?}", b1); + println!("{:?}", b2); + assert!(b1 == BigInt::from(0u32).into_digits()); + } + { + let mut b1: [u32; 9] = Fp9Parameters::R; + let b2: [u32; 9] = Fp9Parameters::R; + mul_assign::(&mut b1, &b2); + println!("b1: {:?}", b1); + println!("R: {:?}", Fp9Parameters::R); + assert!(b1 == Fp9Parameters::R); + } + } + + #[test] + pub fn test_fp9_to_from_bigint() { + let b: BigInt<9> = BigInt::from(123u32); + let x: Fp9 = Fp9Parameters::from_bigint(b).unwrap(); + let b2 = Fp9Parameters::to_bigint(x); + assert!(b == b2); + } + + #[test] + pub fn test_fp9_fp_conversion() { + //let x: Fp = rand::random(); + let x: Fp = Fp::from(1u32); + let z: Fp9 = x.into(); + let x2: Fp = z.into(); + println!("x: {:?}", x); + println!("x limbs: {:?}", x.0 .0); + println!("z: {:?}", z); + println!("z limbs: {:?}", FpBackend::pack(z)); + println!("x2: {:?}", x2); + assert!(x2 == x); + } +} diff --git a/curves/src/pasta/wasm_friendly/wasm_fp.rs b/curves/src/pasta/wasm_friendly/wasm_fp.rs index 5a577de091c..c599a054d2c 100644 --- a/curves/src/pasta/wasm_friendly/wasm_fp.rs +++ b/curves/src/pasta/wasm_friendly/wasm_fp.rs @@ -71,6 +71,7 @@ impl, const N: usize> Fp { pub fn from_bigint(r: BigInt) -> Option { P::from_bigint(r) } + #[inline] pub fn into_bigint(self) -> BigInt { P::to_bigint(self) diff --git a/poseidon/benches/poseidon_bench.rs b/poseidon/benches/poseidon_bench.rs index 74c50400950..718f571d31e 100644 --- a/poseidon/benches/poseidon_bench.rs +++ b/poseidon/benches/poseidon_bench.rs @@ -30,7 +30,7 @@ pub fn bench_poseidon_kimchi(c: &mut Criterion) { // same as above but with Fp9 group.bench_function("poseidon_hash_kimchi_fp9", |b| { - let mut hash: Fp9 = Fp9::zero(); + let mut hash: Fp9 = Fp9::from(12345u64); let mut poseidon = Poseidon::::new(fp9_static_params()); // poseidon.absorb(&[Fp9::zero()]); diff --git a/poseidon/src/poseidon.rs b/poseidon/src/poseidon.rs index 398a4e4e847..71a05aebfde 100644 --- a/poseidon/src/poseidon.rs +++ b/poseidon/src/poseidon.rs @@ -143,3 +143,56 @@ impl Sponge for ArithmeticSponge { self.sponge_state = SpongeState::Absorbed(0); } } + +#[cfg(test)] +mod tests { + use super::*; + + use crate::{ + constants::PlonkSpongeConstantsKimchi, + pasta::fp_kimchi as SpongeParametersKimchi, + poseidon::{ArithmeticSponge as Poseidon, ArithmeticSpongeParams, Sponge}, + }; + use mina_curves::pasta::{wasm_friendly::Fp9, Fp}; + use once_cell::sync::Lazy; + + // sponge params for Fp9 + + fn fp9_sponge_params() -> ArithmeticSpongeParams { + let params = SpongeParametersKimchi::params(); + + // leverage .into() to convert from Fp to Fp9 + ArithmeticSpongeParams:: { + round_constants: params + .round_constants + .into_iter() + .map(|x| x.into_iter().map(Fp9::from).collect()) + .collect(), + mds: params + .mds + .into_iter() + .map(|x| x.into_iter().map(Fp9::from).collect()) + .collect(), + } + } + + fn fp9_static_params() -> &'static ArithmeticSpongeParams { + static PARAMS: Lazy> = Lazy::new(fp9_sponge_params); + &PARAMS + } + + #[test] + fn test_poseidon_hash_iteration_fp9() { + let mut hash: Fp9 = Fp9::from(12345u64); + let mut poseidon = Poseidon::::new(fp9_static_params()); + + // Run a few iterations to ensure it works + for _ in 0..10 { + poseidon.absorb(&[hash]); + hash = poseidon.squeeze(); + } + + // If we reach here without panicking, the test passes + assert!(true); + } +} From a10063bdc585a8a40f935676c7ca6883fa0575d7 Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Thu, 31 Jul 2025 13:28:04 +0100 Subject: [PATCH 13/16] Debugging: add many tests --- curves/src/pasta/wasm_friendly/backend9.rs | 37 ++- .../pasta/wasm_friendly/bigint32_attempt2.rs | 59 +++- curves/src/pasta/wasm_friendly/pasta.rs | 261 +++++++++++++++++- 3 files changed, 347 insertions(+), 10 deletions(-) diff --git a/curves/src/pasta/wasm_friendly/backend9.rs b/curves/src/pasta/wasm_friendly/backend9.rs index 0e1f75e78f2..6aa93ccc3d9 100644 --- a/curves/src/pasta/wasm_friendly/backend9.rs +++ b/curves/src/pasta/wasm_friendly/backend9.rs @@ -7,11 +7,11 @@ use super::wasm_fp::{Fp, FpBackend}; type B = [u32; 9]; type B64 = [u64; 9]; -const SHIFT: u32 = 29; -const MASK: u32 = (1 << SHIFT) - 1; +pub const SHIFT: u32 = 29; +pub const MASK: u32 = (1 << SHIFT) - 1; -const SHIFT64: u64 = SHIFT as u64; -const MASK64: u64 = MASK as u64; +pub const SHIFT64: u64 = SHIFT as u64; +pub const MASK64: u64 = MASK as u64; pub const fn from_64x4(pa: [u64; 4]) -> [u32; 9] { let mut p = [0u32; 9]; @@ -54,6 +54,25 @@ pub const fn from_32x8(pa: [u32; 8]) -> [u32; 9] { from_64x4(p) } +// Converts from 32x9 with 29 bit limbs back to "normal" 32 bit bignum limb format. +pub fn to_32x8(limbs29: [u32; 9]) -> [u32; 8] { + // First convert to 64x4 format + let limbs64 = to_64x4(limbs29); + + // Then split each 64-bit limb into two 32-bit limbs + let mut result = [0u32; 8]; + result[0] = limbs64[0] as u32; + result[1] = (limbs64[0] >> 32) as u32; + result[2] = limbs64[1] as u32; + result[3] = (limbs64[1] >> 32) as u32; + result[4] = limbs64[2] as u32; + result[5] = (limbs64[2] >> 32) as u32; + result[6] = limbs64[3] as u32; + result[7] = (limbs64[3] >> 32) as u32; + + result +} + /// Checks if the number satisfies 32x9 shape (each limb 29 bits). pub const fn is_32x9_shape(pa: [u32; 9]) -> bool { let b0 = (pa[0] & MASK) != 0; @@ -90,7 +109,7 @@ pub trait FpConstants: Send + Sync + 'static + Sized { } #[inline] -fn gte_modulus(x: &B) -> bool { +pub fn gte_modulus(x: &B) -> bool { for i in (0..9).rev() { // don't fix warning -- that makes it 15% slower! #[allow(clippy::comparison_chain)] @@ -146,7 +165,7 @@ pub fn add_assign(x: &mut B, y: &B) { } #[inline] -fn conditional_reduce(x: &mut B) { +pub fn conditional_reduce(x: &mut B) { if gte_modulus::(x) { #[allow(clippy::needless_range_loop)] for i in 0..9 { @@ -256,12 +275,16 @@ impl FpBackend<9> for FpC { } } + /// Return a "normal" bigint fn to_bigint(x: Fp) -> BigInt<9> { let one = [1, 0, 0, 0, 0, 0, 0, 0, 0]; let mut r = x.0.into_digits(); // convert back from montgomery form mul_assign::(&mut r, &one); - BigInt::from_digits(r) + let repr: [u32; 8] = to_32x8(r); + let mut extended_repr = [0u32; 9]; + extended_repr[..8].copy_from_slice(&repr); + BigInt::from_digits(extended_repr) } fn pack(x: Fp) -> Vec { diff --git a/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs index 8717099d9f8..1c64cb85687 100644 --- a/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs +++ b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs @@ -12,12 +12,13 @@ use ark_std::{ }; use core::{ ops::{ - BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Shl, ShlAssign, Shr, - ShrAssign, + Add, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Mul, Shl, ShlAssign, + Shr, ShrAssign, }, str::FromStr, }; use num_bigint::BigUint; +use std::ops::Rem; use zeroize::Zeroize; use bnum::{errors::ParseIntError, BUintD32}; @@ -438,6 +439,60 @@ impl FromStr for BigInt { } } +impl Add> for BigInt { + type Output = BigInt; + + #[inline] + fn add(self, rhs: BigInt) -> BigInt { + BigInt(self.0 + &rhs.0) + } +} + +impl Add<&BigInt> for BigInt { + type Output = BigInt; + + #[inline] + fn add(self, rhs: &BigInt) -> BigInt { + BigInt(self.0 + &rhs.0) + } +} + +impl Mul> for BigInt { + type Output = BigInt; + + #[inline] + fn mul(self, rhs: BigInt) -> BigInt { + BigInt(self.0 * &rhs.0) + } +} + +impl Mul<&BigInt> for BigInt { + type Output = BigInt; + + #[inline] + fn mul(self, rhs: &BigInt) -> BigInt { + BigInt(self.0 * &rhs.0) + } +} + +impl Rem> for BigInt { + type Output = BigInt; + + #[inline] + fn rem(self, rhs: BigInt) -> BigInt { + BigInt(self.0.rem_euclid(rhs.0)) + } +} + +impl Rem<&BigInt> for BigInt { + type Output = BigInt; + + #[inline] + fn rem(self, rhs: &BigInt) -> BigInt { + BigInt(self.0.rem_euclid(rhs.0)) + } +} + impl BigInteger for BigInt { const NUM_LIMBS: usize = N; diff --git a/curves/src/pasta/wasm_friendly/pasta.rs b/curves/src/pasta/wasm_friendly/pasta.rs index 101c2c6ed28..398eccc7143 100644 --- a/curves/src/pasta/wasm_friendly/pasta.rs +++ b/curves/src/pasta/wasm_friendly/pasta.rs @@ -87,7 +87,10 @@ mod tests { use crate::pasta::{ wasm_friendly::{ - backend9::{add_assign, from_32x8, mul_assign, FpConstants}, + backend9::{ + add_assign, conditional_reduce, from_32x8, gte_modulus, mul_assign, to_32x8, + FpConstants, + }, bigint32_attempt2::BigInt, pasta::Fp9Parameters, wasm_fp::FpBackend, @@ -96,6 +99,258 @@ mod tests { Fp, }; + // move this into bigint crate? + #[test] + fn test_bigint_multiplication_and_modulo() { + use std::str::FromStr; + + // Test 1: Simple multiplication + { + let a = BigInt::<8>::from(123u32); + let b = BigInt::<8>::from(456u32); + let expected = BigInt::<8>::from(123u32 * 456u32); + let result = a * b; + assert_eq!(result, expected, "Simple multiplication failed"); + } + + // Test 2: Multiplication with larger numbers + { + let a = BigInt::<8>::from(0xFFFFFFFFu32); + let b = BigInt::<8>::from(2u32); + let expected = BigInt::<8>::from(0x1FFFFFFFEu64); + let result = a * b; + assert_eq!(result, expected, "Multiplication with carry failed"); + } + + // Test 3: Simple modulo + { + let a = BigInt::<8>::from(10u32); + let b = BigInt::<8>::from(3u32); + let expected = BigInt::<8>::from(1u32); + let result = a % b; + assert_eq!(result, expected, "Simple modulo failed"); + } + + // Test 4: Modulo with larger numbers + { + let a = BigInt::<8>::from(0xFFFFFFFFu32); + let b = BigInt::<8>::from(0x10000000u32); + let expected = BigInt::<8>::from(0xFFFFFFFu32); + let result = a % b; + assert_eq!(result, expected, "Modulo with larger numbers failed"); + } + + // Test 5: Known values from other libraries + { + // These values can be verified with other tools like Python's pow() function + let base = BigInt::<8>::from_str("123456789012345678901234567890").unwrap(); + let modulus = BigInt::<8>::from_str("987654321098765432109876543210").unwrap(); + + // base^2 mod modulus + let base_squared = (base.clone() * base.clone()) % &modulus; + let expected = BigInt::<8>::from_str("958236894095823689409582368940").unwrap(); + assert_eq!( + base_squared, expected, + "Known value test for (base^2 mod modulus) failed" + ); + } + + // Test 6: Verify associativity of multiplication + { + let a = BigInt::<8>::from(123u32); + let b = BigInt::<8>::from(456u32); + let c = BigInt::<8>::from(789u32); + + let result1 = (a.clone() * b.clone()) * c.clone(); + let result2 = a * (b * c); + + assert_eq!(result1, result2, "Multiplication associativity failed"); + } + + // Test 7: Verify distributivity of multiplication over addition + { + let a = BigInt::<8>::from(123u32); + let b = BigInt::<8>::from(456u32); + let c = BigInt::<8>::from(789u32); + + let result1 = a.clone() * (b.clone() + c.clone()); + let result2 = (a.clone() * b) + (a * c); + + assert_eq!(result1, result2, "Multiplication distributivity failed"); + } + + // Test 8: Modular arithmetic identity: (a * b) % m = ((a % m) * (b % m)) % m + { + let a = BigInt::<8>::from(12345u32); + let b = BigInt::<8>::from(67890u32); + let m = BigInt::<8>::from(101u32); + + let result1 = (a.clone() * b.clone()) % &m; + let result2 = ((a % &m) * (b % &m)) % &m; + + assert_eq!(result1, result2, "Modular multiplication identity failed"); + } + + // Test 9: Verify that (a^2) % m works correctly for larger values + { + // This can't be close to the field size, has to be 32x4 bits most, since our Bigints overflow. + let modulus = BigInt::<8>::from_str("11579208923731619542357098500868790").unwrap(); + let mut a = modulus.clone(); + assert!(a.0.digits_mut()[0] >= 1); + a.0.digits_mut()[0] -= 1; // subtract 1 + + let a_squared = (a.clone() * a.clone()) % &modulus; + let expected = BigInt::<8>::from(1u32); + + assert_eq!(a_squared, expected, "Squaring (p-1) mod p should equal 1"); + } + } + + #[test] + fn test_montgomery_constants_consistency() { + // Test 1: Verify R = 2^261 mod MODULUS + { + // Compute 2^261 mod MODULUS + let modulus = + BigInt::<8>::from_digits(to_32x8(::MODULUS)); + + // Start with 1 and repeatedly square and multiply + let mut power_of_2 = BigInt::<8>::ONE; + for _ in 0..261 { + power_of_2 = (power_of_2 + power_of_2) % &modulus; + } + + let r = BigInt::<8>::from_digits(to_32x8(Fp9Parameters::R)); + assert_eq!(r, power_of_2, "R should equal 2^261 mod MODULUS"); + } + + // Test 2: Verify R2 = R^2 mod MODULUS + // needs twice as much limbs to succeed. + { + let extend_array = |arr: [u32; 8]| -> [u32; 16] { + let mut result = [0u32; 16]; + result[..8].copy_from_slice(&arr); + result + }; + + let r = + BigInt::<16>::from_digits(extend_array(to_32x8(::R))); + let modulus = BigInt::<16>::from_digits(extend_array(to_32x8( + ::MODULUS, + ))); + + // Square R and reduce mod MODULUS + let r_squared = (r.clone() * r) % modulus; + + let r2 = BigInt::<16>::from_digits(extend_array(to_32x8( + ::R2, + ))); + assert_eq!(r2, r_squared, "R2 should equal R^2 mod MODULUS"); + } + + // Test 3: Verify MINV is correct: MINV * MODULUS ≡ -1 mod 2^29 + { + let m0 = ::MODULUS[0] as u64; + let minv = ::MINV; + + // Check that (MINV * m0) & ((1 << 29) - 1) == (1 << 29) - 1 + // This is equivalent to MINV * m0 ≡ -1 (mod 2^29) + let result = (minv * m0) & ((1 << 29) - 1); + let expected = (1 << 29) - 1; // -1 mod 2^29 + + assert_eq!( + result, expected, + "MINV should satisfy MINV * MODULUS ≡ -1 (mod 2^29)" + ); + } + } + + #[test] + pub fn test_conditional_reduce() { + // Test 1: Value equal to MODULUS should reduce to 0 + { + let mut x = ::MODULUS; + conditional_reduce::(&mut x); + let expected = [0u32; 9]; + assert_eq!(x, expected, "MODULUS should reduce to 0"); + } + + // Test 2: Value just below MODULUS should not change + { + let mut x = ::MODULUS; + x[0] -= 1; // Just below MODULUS + let expected = x.clone(); + conditional_reduce::(&mut x); + assert_eq!(x, expected, "Value below MODULUS should not change"); + } + + // Test 3: Value just above MODULUS should reduce correctly + { + let mut x = ::MODULUS; + x[0] += 1; // Just above MODULUS + conditional_reduce::(&mut x); + let expected = [1u32, 0, 0, 0, 0, 0, 0, 0, 0]; + assert_eq!(x, expected, "MODULUS+1 should reduce to 1"); + } + + // Test 4: Value at MODULUS + MODULUS - 1 (2*MODULUS - 1) + { + let mut x = ::MODULUS; + for i in 0..9 { + x[i] = x[i].wrapping_add(::MODULUS[i]); + } + x[0] -= 1; // 2*MODULUS - 1 + conditional_reduce::(&mut x); + + // Should reduce to MODULUS - 1 + let mut expected = ::MODULUS; + expected[0] -= 1; + assert_eq!(x, expected, "2*MODULUS-1 should reduce to MODULUS-1"); + } + + // Test 5: Value with carries needed + { + use super::backend9::{MASK, SHIFT}; + + let mut x = [0u32; 9]; + x[0] = MASK + 1; // This will need a carry + x[1] = MASK; // This will overflow after carry + + let expected_before_reduce = [0, 0, 1, 0, 0, 0, 0, 0, 0]; // After normalization + let mut x_normalized = x; + + // Normalize first (simulate what would happen before conditional_reduce) + for i in 1..9 { + x_normalized[i] += ((x_normalized[i - 1] as i32) >> SHIFT) as u32; + x_normalized[i - 1] &= MASK; + } + + assert_eq!(x_normalized, expected_before_reduce, "Normalization check"); + + // Now test conditional_reduce if this value is >= MODULUS + if gte_modulus::(&expected_before_reduce) { + let mut to_reduce = expected_before_reduce; + conditional_reduce::(&mut to_reduce); + + // Calculate expected result manually + let mut expected = expected_before_reduce; + for i in 0..9 { + expected[i] = + expected[i].wrapping_sub(::MODULUS[i]); + } + for i in 1..9 { + expected[i] += ((expected[i - 1] as i32) >> SHIFT) as u32; + expected[i - 1] &= MASK; + } + + assert_eq!( + to_reduce, expected, + "Reduction with carries should work correctly" + ); + } + } + } + #[test] pub fn test_mod_add() { { @@ -132,6 +387,10 @@ mod tests { #[test] pub fn test_montgomery_mult() { + // R < MODULUS + { + assert!(!gte_modulus::(&Fp9Parameters::R)); + } // 1 * R / R = 1 { let mut b1: [u32; 9] = BigInt::from(1u32).into_digits(); From 0ba082e773de551489a265cb66fe0064e172c322 Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Tue, 19 Aug 2025 14:39:09 +0100 Subject: [PATCH 14/16] Add montgomery mul alternative implementation --- curves/src/pasta/wasm_friendly/backend9.rs | 102 ++++++++++++++++--- curves/src/pasta/wasm_friendly/pasta.rs | 108 +++++++++++++++++++-- 2 files changed, 189 insertions(+), 21 deletions(-) diff --git a/curves/src/pasta/wasm_friendly/backend9.rs b/curves/src/pasta/wasm_friendly/backend9.rs index 6aa93ccc3d9..a2c5d875c74 100644 --- a/curves/src/pasta/wasm_friendly/backend9.rs +++ b/curves/src/pasta/wasm_friendly/backend9.rs @@ -51,7 +51,26 @@ pub const fn from_32x8(pa: [u32; 8]) -> [u32; 9] { p[1] = (pa[2] as u64) | ((pa[3] as u64) << 32); p[2] = (pa[4] as u64) | ((pa[5] as u64) << 32); p[3] = (pa[6] as u64) | ((pa[7] as u64) << 32); - from_64x4(p) + let res = from_64x4(p); + assert!(is_32x9_shape(res)); + res +} + +// Converts from "normal" 32 bit bignum limb format to the 32x9 with 29 bit limbs. +pub fn from_32x8_nonconst(pa: [u32; 8]) -> [u32; 9] { + let mut p = [0u64; 4]; + p[0] = (pa[0] as u64) | ((pa[1] as u64) << 32); + p[1] = (pa[2] as u64) | ((pa[3] as u64) << 32); + p[2] = (pa[4] as u64) | ((pa[5] as u64) << 32); + p[3] = (pa[6] as u64) | ((pa[7] as u64) << 32); + let res = from_64x4(p); + if !is_32x9_shape(res) { + println!("pa: {:?}", pa); + println!("p: {:?}", p); + println!("res: {:?}", res); + panic!(); + } + res } // Converts from 32x9 with 29 bit limbs back to "normal" 32 bit bignum limb format. @@ -75,15 +94,15 @@ pub fn to_32x8(limbs29: [u32; 9]) -> [u32; 8] { /// Checks if the number satisfies 32x9 shape (each limb 29 bits). pub const fn is_32x9_shape(pa: [u32; 9]) -> bool { - let b0 = (pa[0] & MASK) != 0; - let b1 = (pa[1] & MASK) != 0; - let b2 = (pa[2] & MASK) != 0; - let b3 = (pa[3] & MASK) != 0; - let b4 = (pa[4] & MASK) != 0; - let b5 = (pa[5] & MASK) != 0; - let b6 = (pa[6] & MASK) != 0; - let b7 = (pa[7] & MASK) != 0; - let b8 = (pa[8] & MASK) != 0; + let b0 = (pa[0] >> 29) == 0; + let b1 = (pa[1] >> 29) == 0; + let b2 = (pa[2] >> 29) == 0; + let b3 = (pa[3] >> 29) == 0; + let b4 = (pa[4] >> 29) == 0; + let b5 = (pa[5] >> 29) == 0; + let b6 = (pa[6] >> 29) == 0; + let b7 = (pa[7] >> 29) == 0; + let b8 = (pa[8] >> 29) == 0; b0 && b1 && b2 && b3 && b4 && b5 && b6 && b7 && b8 } @@ -105,7 +124,7 @@ pub trait FpConstants: Send + Sync + 'static + Sized { /// TODO: compute these const R: B; // R = 2^261 mod modulus const R2: B; // R^2 mod modulus - const MINV: u64; // -modulus^(-1) mod 2^29, as a u64 + const MINV: u64; // - modulus^(-1) mod 2^29, as a u64 } #[inline] @@ -142,7 +161,7 @@ pub fn gte_modulus(x: &B) -> bool { /// # Implementation Notes /// - First performs the addition with carry propagation /// - Then conditionally subtracts the modulus if the result is greater than or equal to it -/// - Uses 30-bit limbs with SHIFT and MASK constants to handle overflow +/// pub fn add_assign(x: &mut B, y: &B) { let mut tmp: u32; let mut carry: i32 = 0; @@ -182,6 +201,7 @@ pub fn conditional_reduce(x: &mut B) { } } +// See: https://github.com/mitschabaude/montgomery/blob/4f1405073aef60b469a0fcd45ff9166f8de4a4bf/src/wasm/multiply-montgomery.ts#L58 /// Performs Montgomery multiplication: x = (x * y * R^-1) mod p /// /// This function multiplies two field elements in Montgomery form and stores @@ -201,7 +221,7 @@ pub fn conditional_reduce(x: &mut B) { /// - Performs a conditional reduction at the end to ensure the result is in /// the canonical range [0, p-1] /// - Optimized to minimize carry operations in the main loop -pub fn mul_assign(x: &mut B, y: &B) { +pub fn mul_assign_orig(x: &mut B, y: &B) { // load y[i] into local u64s // TODO make sure these are locals let mut y_local = [0u64; 9]; @@ -218,19 +238,24 @@ pub fn mul_assign(x: &mut B, y: &B) { for i in 0..9 { let xi = x[i] as u64; + // tmp = x[i] * y[0] + z[0] (mod u64) + // qi = lowest64bits(lowest29bits(x[i] * y[0] + z[0]) * MINV) // compute qi and carry z0 result to z1 before discarding z0 tmp = (xi * y_local[0]) + z[0]; let qi = ((tmp & MASK64) * FpC::MINV) & MASK64; z[1] += (tmp + qi * FpC::MODULUS64[0]) >> SHIFT64; // compute zi and shift in one step - for j in 1..8 { - z[j - 1] = z[j] + (xi * y_local[j]) + (qi * FpC::MODULUS64[j]); + for j in 0..7 { + z[j] = z[j + 1] + (xi * y_local[j + 1]) + (qi * FpC::MODULUS64[j + 1]); } + // for j=8 we save an addition since z[8] is never needed z[7] = xi * y_local[8] + qi * FpC::MODULUS64[8]; } + println!("before carry pass: z = {:?}", z); + // final carry pass, store result back into x x[0] = (z[0] & MASK64) as u32; for i in 1..8 { @@ -238,6 +263,53 @@ pub fn mul_assign(x: &mut B, y: &B) { } x[8] = (z[7] >> SHIFT64) as u32; + println!("before conditional reduce: {:?}", x); + + // at this point, x is guaranteed to be less than 2*MODULUS + // conditionally subtract the modulus to bring it back into the canonical range + conditional_reduce::(x); +} + +// FIXME implementation that uses 9 limbs for z, check it +pub fn mul_assign(x: &mut B, y: &B) { + // load y[i] into local u64s + let mut y_local = [0u64; 9]; + for i in 0..9 { + y_local[i] = y[i] as u64; + } + + let mut z = [0u64; 9]; + + // main loop + for i in 0..9 { + let xi = x[i] as u64; + + // compute qi and first multiplication + let tmp = (xi * y_local[0]) + z[0]; + let qi = ((tmp & MASK64) * FpC::MINV) & MASK64; + + // carry from first step + let mut carry = (tmp + qi * FpC::MODULUS64[0]) >> SHIFT64; + + // compute remaining steps with proper carry propagation + for j in 1..9 { + let t = z[j] + (xi * y_local[j]) + (qi * FpC::MODULUS64[j]) + carry; + z[j - 1] = t & MASK64; + carry = t >> SHIFT64; + } + + // store final carry + z[8] = carry; + } + + // final carry pass, store result back into x + let mut carry = 0u64; + for i in 0..9 { + let t = z[i] + carry; + x[i] = (t & MASK64) as u32; + carry = t >> SHIFT64; + } + // at this point, x is guaranteed to be less than 2*MODULUS // conditionally subtract the modulus to bring it back into the canonical range conditional_reduce::(x); diff --git a/curves/src/pasta/wasm_friendly/pasta.rs b/curves/src/pasta/wasm_friendly/pasta.rs index 398eccc7143..98458a846a0 100644 --- a/curves/src/pasta/wasm_friendly/pasta.rs +++ b/curves/src/pasta/wasm_friendly/pasta.rs @@ -88,8 +88,8 @@ mod tests { use crate::pasta::{ wasm_friendly::{ backend9::{ - add_assign, conditional_reduce, from_32x8, gte_modulus, mul_assign, to_32x8, - FpConstants, + add_assign, conditional_reduce, from_32x8, from_32x8_nonconst, gte_modulus, + is_32x9_shape, mul_assign, to_32x8, FpConstants, }, bigint32_attempt2::BigInt, pasta::Fp9Parameters, @@ -98,12 +98,11 @@ mod tests { }, Fp, }; + use std::str::FromStr; // move this into bigint crate? #[test] fn test_bigint_multiplication_and_modulo() { - use std::str::FromStr; - // Test 1: Simple multiplication { let a = BigInt::<8>::from(123u32); @@ -405,6 +404,100 @@ mod tests { mul_assign::(&mut b1, &b2); assert!(b1 == BigInt::from(12345u32).into_digits()); } + // * R / R = + { + let mut b1 = from_32x8_nonconst(BigInt::<8>::from_str("12345").unwrap().into_digits()); + let b1_exp = b1.clone(); + let b2: [u32; 9] = Fp9Parameters::R; + mul_assign::(&mut b1, &b2); + assert!(b1 == b1_exp); + } + // 2^29 * R / R = 2^29 + { + let mut b1 = + from_32x8_nonconst(BigInt::<8>::from_str("536870912").unwrap().into_digits()); + let b1_exp = b1.clone(); + let b2: [u32; 9] = Fp9Parameters::R; + mul_assign::(&mut b1, &b2); + assert!(b1 == b1_exp); + } + // (2^29-1) * R / R = (2^29-1) + { + let mut b1 = + from_32x8_nonconst(BigInt::<8>::from_str("536870911").unwrap().into_digits()); + let b1_exp = b1.clone(); + let b2: [u32; 9] = Fp9Parameters::R; + mul_assign::(&mut b1, &b2); + assert!(b1 == b1_exp); + } + // (2^29+1) * R / R = (2^29+1) + { + let mut b1 = + from_32x8_nonconst(BigInt::<8>::from_str("536870913").unwrap().into_digits()); + let b1_exp = b1.clone(); + let b2: [u32; 9] = Fp9Parameters::R; + mul_assign::(&mut b1, &b2); + assert!(b1 == b1_exp); + } + + // 2^32 * R / R = 2^32 + { + let mut b1 = + from_32x8_nonconst(BigInt::<8>::from_str("4294967296").unwrap().into_digits()); + let b1_exp = b1.clone(); + let b2: [u32; 9] = Fp9Parameters::R; + mul_assign::(&mut b1, &b2); + assert!(b1 == b1_exp); + } + // (2^32+1) * R / R = (2^32+1) + { + let mut b1 = + from_32x8_nonconst(BigInt::<8>::from_str("4294967297").unwrap().into_digits()); + let b1_exp = b1.clone(); + let b2: [u32; 9] = Fp9Parameters::R; + mul_assign::(&mut b1, &b2); + assert!(b1 == b1_exp); + } + + // (2^64-1) * R / R = (2^64-1) + { + let mut b1 = from_32x8_nonconst( + BigInt::<8>::from_str("18446744073709551615") + .unwrap() + .into_digits(), + ); + let b1_exp = b1.clone(); + let b2: [u32; 9] = Fp9Parameters::R; + mul_assign::(&mut b1, &b2); + assert!(b1 == b1_exp); + } + + // 2^64 * R / R = 2^64 + { + let mut b1 = from_32x8( + BigInt::<8>::from_str("18446744073709551616") + .unwrap() + .into_digits(), + ); + let b1_exp = b1.clone(); + let b2: [u32; 9] = Fp9Parameters::R; + mul_assign::(&mut b1, &b2); + assert!(b1 == b1_exp, "expected {:?} got {:?}", b1_exp, b1); + } + + // (2^64+1) * R / R = (2^64+1) + { + let mut b1 = from_32x8_nonconst( + BigInt::<8>::from_str("18446744073709551617") + .unwrap() + .into_digits(), + ); + let b1_exp = b1.clone(); + let b2: [u32; 9] = Fp9Parameters::R; + mul_assign::(&mut b1, &b2); + assert!(b1 == b1_exp, "expected {:?} got {:?}", b1_exp, b1); + } + // 0 * R / R = 0 { let mut b1: [u32; 9] = BigInt::from(0u32).into_digits(); @@ -416,9 +509,12 @@ mod tests { { let mut b1: [u32; 9] = Fp9Parameters::R; let b2: [u32; 9] = Fp9Parameters::R; + assert!(is_32x9_shape(b1)); + assert!(is_32x9_shape(b2)); mul_assign::(&mut b1, &b2); - println!("b1: {:?}", b1); - println!("R: {:?}", Fp9Parameters::R); + assert!(is_32x9_shape(b1)); + println!("b1 (supposed to be R): {:?}", b1); + println!("R : {:?}", Fp9Parameters::R); assert!(b1 == Fp9Parameters::R); } { From 4631a07a033610370528577033b088ab199ffbc0 Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Wed, 20 Aug 2025 10:38:00 +0100 Subject: [PATCH 15/16] Fix conversion and basic ops --- curves/src/pasta/curves/pallas.rs | 118 +++++++++++++++++- curves/src/pasta/wasm_friendly/backend9.rs | 46 +++++-- .../pasta/wasm_friendly/bigint32_attempt2.rs | 19 +++ curves/src/pasta/wasm_friendly/pasta.rs | 58 ++++++++- .../src/pasta/wasm_friendly/wasm_fp_ported.rs | 41 +++--- 5 files changed, 246 insertions(+), 36 deletions(-) diff --git a/curves/src/pasta/curves/pallas.rs b/curves/src/pasta/curves/pallas.rs index a4fe08edf9d..280278a3724 100644 --- a/curves/src/pasta/curves/pallas.rs +++ b/curves/src/pasta/curves/pallas.rs @@ -139,6 +139,122 @@ mod tests { #[test] pub fn test_wasm_curve_basic_ops() { + // Constants with large bit sizes + let large_a: u32 = 0x1FFFFFFF; // 29 bits (max for a limb) + let large_b: u32 = 0x3FFFFFFF; // 30 bits + + // Test Fp9 operations with large numbers + { + // Test conversion between Fp and Fp9 with large numbers + let x: Fp = Fp::from(large_a); + let x_fp9: Fp9 = x.into(); + let x_back: Fp = x_fp9.into(); + assert_eq!( + x, x_back, + "Conversion between Fp and Fp9 with large numbers failed" + ); + + // Create Fp9 elements with large components + let a: Fp = Fp::from(large_a); + let b: Fp = Fp::from(large_b); + let a_fp9: Fp9 = a.into(); + let b_fp9: Fp9 = b.into(); + + // Test Fp9 addition with large numbers + let sum_fp9 = a_fp9 + b_fp9; + let expected_sum_fp9: Fp9 = (a + b).into(); + assert_eq!( + sum_fp9, expected_sum_fp9, + "Fp9 addition with large numbers failed" + ); + + // Test Fp9 multiplication with large numbers + let prod_fp9 = a_fp9 * b_fp9; + let expected_prod_fp9: Fp9 = (a * b).into(); + assert_eq!( + prod_fp9, expected_prod_fp9, + "Fp9 multiplication with large numbers failed" + ); + + // Test Fp9 squaring with large numbers + let square_fp9 = a_fp9 * a_fp9; + let expected_square_fp9: Fp9 = (a * a).into(); + assert_eq!( + square_fp9, expected_square_fp9, + "Fp9 squaring with large numbers failed" + ); + } + + // Test consistency between Fp and Fp9 operations with large numbers + { + // Create random large Fp elements + let x: Fp = Fp::from(large_a) * Fp::from(0x12345678u64); + let y: Fp = Fp::from(large_b) * Fp::from(0x87654321u64); + + // Compute product in Fp + let z_fp: Fp = x * y; + + // Compute product in Fp9 and convert back + let x_fp9: Fp9 = x.into(); + let y_fp9: Fp9 = y.into(); + let z_fp9: Fp9 = x_fp9 * y_fp9; + let z_fp_from_fp9: Fp = z_fp9.into(); + + // Results should be equal + assert_eq!( + z_fp, z_fp_from_fp9, + "Inconsistency between Fp and Fp9 multiplication with large numbers" + ); + + // Test multiple operations in sequence + let result_fp = ((x * y) + x) * y; + let result_fp9: Fp = (((x_fp9 * y_fp9) + x_fp9) * y_fp9).into(); + assert_eq!( + result_fp, result_fp9, + "Complex operation sequence inconsistent between Fp and Fp9" + ); + } + + // Test with numbers that would span multiple limbs + { + // Create Fp9 with non-trivial structure (not just embedding of Fp) + // This would require knowledge of how to construct a general Fp9 element + + // For now, test with large random values + let r1: Fp = rand::random(); + let r2: Fp = rand::random(); + + // Ensure these are large values by multiplying with our large constants + let large_r1 = r1 * Fp::from(large_a); + let large_r2 = r2 * Fp::from(large_b); + + // Convert to Fp9 + let r1_fp9: Fp9 = large_r1.into(); + let r2_fp9: Fp9 = large_r2.into(); + + // Test operations + let sum_fp9 = r1_fp9 + r2_fp9; + let prod_fp9 = r1_fp9 * r2_fp9; + + // Verify conversion back + let sum_fp: Fp = sum_fp9.into(); + let prod_fp: Fp = prod_fp9.into(); + + assert_eq!( + sum_fp, + large_r1 + large_r2, + "Sum conversion inconsistent with large numbers" + ); + assert_eq!( + prod_fp, + large_r1 * large_r2, + "Product conversion inconsistent with large numbers" + ); + } + } + + #[test] + pub fn test_naive_wasm_curve_basic_ops() { { //let x: Fp = rand::random(); let x: Fp = Fp::from(1u32); @@ -158,8 +274,6 @@ mod tests { let z: Fp = x * y; } - assert!(false); - { let x: Fp = rand::random(); let y: Fp = rand::random(); diff --git a/curves/src/pasta/wasm_friendly/backend9.rs b/curves/src/pasta/wasm_friendly/backend9.rs index a2c5d875c74..068e84f6ae5 100644 --- a/curves/src/pasta/wasm_friendly/backend9.rs +++ b/curves/src/pasta/wasm_friendly/backend9.rs @@ -44,13 +44,31 @@ pub const fn to_64x4(pa: [u32; 9]) -> [u64; 4] { p } -// Converts from "normal" 32 bit bignum limb format to the 32x9 with 29 bit limbs. -pub const fn from_32x8(pa: [u32; 8]) -> [u32; 9] { +pub const fn from_64x4_to_32x8(p: [u64; 4]) -> [u32; 8] { + let mut pa = [0u32; 8]; + pa[0] = p[0] as u32; + pa[1] = (p[0] >> 32) as u32; + pa[2] = p[1] as u32; + pa[3] = (p[1] >> 32) as u32; + pa[4] = p[2] as u32; + pa[5] = (p[2] >> 32) as u32; + pa[6] = p[3] as u32; + pa[7] = (p[3] >> 32) as u32; + pa +} + +pub const fn from_32x8_to_64x4(pa: [u32; 8]) -> [u64; 4] { let mut p = [0u64; 4]; p[0] = (pa[0] as u64) | ((pa[1] as u64) << 32); p[1] = (pa[2] as u64) | ((pa[3] as u64) << 32); p[2] = (pa[4] as u64) | ((pa[5] as u64) << 32); p[3] = (pa[6] as u64) | ((pa[7] as u64) << 32); + p +} + +// Converts from "normal" 32 bit bignum limb format to the 32x9 with 29 bit limbs. +pub const fn from_32x8(pa: [u32; 8]) -> [u32; 9] { + let p = from_32x8_to_64x4(pa); let res = from_64x4(p); assert!(is_32x9_shape(res)); res @@ -58,11 +76,7 @@ pub const fn from_32x8(pa: [u32; 8]) -> [u32; 9] { // Converts from "normal" 32 bit bignum limb format to the 32x9 with 29 bit limbs. pub fn from_32x8_nonconst(pa: [u32; 8]) -> [u32; 9] { - let mut p = [0u64; 4]; - p[0] = (pa[0] as u64) | ((pa[1] as u64) << 32); - p[1] = (pa[2] as u64) | ((pa[3] as u64) << 32); - p[2] = (pa[4] as u64) | ((pa[5] as u64) << 32); - p[3] = (pa[6] as u64) | ((pa[7] as u64) << 32); + let p = from_32x8_to_64x4(pa); let res = from_64x4(p); if !is_32x9_shape(res) { println!("pa: {:?}", pa); @@ -317,12 +331,15 @@ pub fn mul_assign(x: &mut B, y: &B) { // implement FpBackend given FpConstants +/// Converts a bigint of 9 limbs, of 32x8 shape, into a field element. pub fn from_bigint_unsafe(x: BigInt<9>) -> Fp { - let r: [u32; 9] = Into::into(x); - assert!(r[8] == 0); + let r: [u32; 9] = x.into_digits(); + assert!(r[8] == 0, "from_bigint_unsafe: bigint exceeds 256 bits"); let mut r = from_32x8(r[0..8].try_into().unwrap()); + assert!(is_32x9_shape(r)); // convert to montgomery form mul_assign::(&mut r, &FpC::R2); + assert!(is_32x9_shape(r)); Fp(BigInt::from_digits(r), Default::default()) } @@ -332,11 +349,13 @@ impl FpBackend<9> for FpC { const ONE: BigInt<9> = BigInt::from_digits(Self::R); fn add_assign(x: &mut Fp, y: &Fp) { - std::ops::AddAssign::add_assign(x, y) + //panic!("test1"); + //std::ops::AddAssign::add_assign(x, y) + add_assign::(x.0.as_digits_mut(), &y.0.into_digits()) } fn mul_assign(x: &mut Fp, y: &Fp) { - std::ops::MulAssign::mul_assign(x, y) + mul_assign::(x.0.as_digits_mut(), &y.0.into_digits()) } fn from_bigint(x: BigInt<9>) -> Option> { @@ -349,7 +368,8 @@ impl FpBackend<9> for FpC { /// Return a "normal" bigint fn to_bigint(x: Fp) -> BigInt<9> { - let one = [1, 0, 0, 0, 0, 0, 0, 0, 0]; + assert!(is_32x9_shape(x.0.into_digits())); + let one: [u32; 9] = [1, 0, 0, 0, 0, 0, 0, 0, 0]; let mut r = x.0.into_digits(); // convert back from montgomery form mul_assign::(&mut r, &one); @@ -361,7 +381,7 @@ impl FpBackend<9> for FpC { fn pack(x: Fp) -> Vec { let x = Self::to_bigint(x).into_digits(); - let x64 = to_64x4(x); + let x64 = from_32x8_to_64x4(x[0..8].try_into().unwrap()); let mut res = Vec::with_capacity(4); for limb in x64.iter() { res.push(*limb); diff --git a/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs index 1c64cb85687..46161347de6 100644 --- a/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs +++ b/curves/src/pasta/wasm_friendly/bigint32_attempt2.rs @@ -42,6 +42,11 @@ impl BigInt { *self.0.digits() } + #[inline] + pub fn as_digits_mut(&mut self) -> &mut [u32; N] { + self.0.digits_mut() + } + #[doc(hidden)] pub const fn const_is_even(&self) -> bool { self.0.digits()[0] % 2 == 0 @@ -136,6 +141,20 @@ impl AsRef<[u64]> for BigInt { } } +impl AsMut<[u32]> for BigInt { + #[inline] + fn as_mut(&mut self) -> &mut [u32] { + self.0.digits_mut() + } +} + +impl AsRef<[u32]> for BigInt { + #[inline] + fn as_ref(&self) -> &[u32] { + self.0.digits() + } +} + impl From for BigInt { #[inline] fn from(val: u128) -> BigInt { diff --git a/curves/src/pasta/wasm_friendly/pasta.rs b/curves/src/pasta/wasm_friendly/pasta.rs index 98458a846a0..c143b02713e 100644 --- a/curves/src/pasta/wasm_friendly/pasta.rs +++ b/curves/src/pasta/wasm_friendly/pasta.rs @@ -26,9 +26,13 @@ pub type Fp9 = wasm_fp::Fp; impl Fp9 { pub fn from_fp(fp: Fp) -> Self { - backend9::from_bigint_unsafe(super::BigInt::from_digits(backend9::from_64x4( - fp.into_bigint().0, - ))) + let limbs8: [u32; 8] = backend9::from_64x4_to_32x8(fp.into_bigint().0); + let mut limbs9 = [0u32; 9]; + for i in 0..8 { + limbs9[i] = limbs8[i]; + } + println!("from_fp: {:?}, limbs9 {:?}", fp, limbs9); + backend9::from_bigint_unsafe(super::BigInt::from_digits(limbs9)) } pub fn into_fp(self: Fp9) -> Fp { @@ -98,6 +102,7 @@ mod tests { }, Fp, }; + use ark_ff::{One, UniformRand, Zero}; use std::str::FromStr; // move this into bigint crate? @@ -382,6 +387,12 @@ mod tests { add_assign::(&mut b1, &b2); assert!(b1 == ::R2) } + { + let mut b1: [u32; 9] = from_32x8(BigInt::from(0x1FFFFFFFu32).into_digits()); + let b2: [u32; 9] = from_32x8(BigInt::from(0x3FFFFFFFu32).into_digits()); + add_assign::(&mut b1, &b2); + assert!(b1 == from_32x8(BigInt::from(1610612734u32).into_digits())); + } } #[test] @@ -587,4 +598,45 @@ mod tests { println!("x2: {:?}", x2); assert!(x2 == x); } + + #[test] + fn test_fp_to_fp9_roundtrip() { + // Test with zero + let zero = Fp::zero(); + let fp9_zero = Fp9::from_fp(zero); + let back_to_fp = fp9_zero.into_fp(); + assert_eq!(zero, back_to_fp); + + // Test with one + let one = Fp::one(); + let fp9_one = Fp9::from_fp(one); + let back_to_fp = fp9_one.into_fp(); + assert_eq!(one, back_to_fp); + + let v1 = Fp::from(0x1ffffffffffu64); + println!("{:?}", v1.0); + let fp9_v1 = Fp9::from_fp(v1); + println!("{:?}", fp9_v1.0.into_digits()); + let back_to_fp = fp9_v1.into_fp(); + assert_eq!(v1, back_to_fp); + + // Test with random value + let random = Fp::rand(&mut ark_std::test_rng()); + let fp9_random = Fp9::from_fp(random); + let back_to_fp = fp9_random.into_fp(); + assert_eq!(random, back_to_fp); + } + + #[test] + fn test_from_into_traits() { + let value = Fp::rand(&mut ark_std::test_rng()); + + // Test From for Fp9 + let fp9_value: Fp9 = value.into(); + assert_eq!(fp9_value, Fp9::from_fp(value)); + + // Test Into for Fp9 + let fp_value: Fp = fp9_value.into(); + assert_eq!(fp_value, value); + } } diff --git a/curves/src/pasta/wasm_friendly/wasm_fp_ported.rs b/curves/src/pasta/wasm_friendly/wasm_fp_ported.rs index 5a879384c24..b5458d9cf24 100644 --- a/curves/src/pasta/wasm_friendly/wasm_fp_ported.rs +++ b/curves/src/pasta/wasm_friendly/wasm_fp_ported.rs @@ -1,7 +1,5 @@ use crate::{ - biginteger::{ - BigInteger as _BigInteger, webnode::BigInteger256, - }, + biginteger::{webnode::BigInteger256, BigInteger as _BigInteger}, bytes::{FromBytes, ToBytes}, fields::{FftField, Field, LegendreSymbol, PrimeField, SquareRootField}, }; @@ -12,7 +10,8 @@ use ark_std::{ io::{Read, Result as IoResult, Write}, marker::PhantomData, ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}, - str::FromStr, One, Zero, + str::FromStr, + One, Zero, }; impl Into for Fp256 { @@ -104,6 +103,7 @@ const fn conditional_reduce(x: &mut BigInteger256) { #[ark_ff_asm::unroll_for_loops] #[inline(always)] fn add_assign(x: &mut BigInteger256, y: &BigInteger256) { + panic!("whoopsie add_assign"); let y = &y.0; let mut tmp: u32; let mut carry: i32 = 0; @@ -125,7 +125,7 @@ fn add_assign(x: &mut BigInteger256, y: &BigInteger256) { } #[derive(Clone, Copy, Default, Eq, PartialEq, Hash)] -pub struct Fp256 (pub BigInteger256, PhantomData); +pub struct Fp256(pub BigInteger256, PhantomData); /// Note that this implementation of `Ord` compares field elements viewing /// them as integers in the range 0, 1, ..., P::MODULUS - 1. However, other @@ -186,7 +186,7 @@ impl Fp256 { let mut index = 0; let mut is_zero = true; while index < Self::NLIMBS { - is_zero &= self.0.0[index] == 0; + is_zero &= self.0 .0[index] == 0; index += 1; } is_zero @@ -276,9 +276,14 @@ impl Fp256 { /// Implementation based on https://github.com/o1-labs/proof-systems/pull/2638 #[ark_ff_asm::unroll_for_loops] #[inline(always)] - const fn const_mul_without_reduce(&mut self, other: &Self, _modulus: &BigInteger256, _inv: u32) { - let x = &mut self.0.0; - let y = &other.0.0; + const fn const_mul_without_reduce( + &mut self, + other: &Self, + _modulus: &BigInteger256, + _inv: u32, + ) { + let x = &mut self.0 .0; + let y = &other.0 .0; let mut y_local = [0u64; 9]; for index in 0..9 { @@ -330,9 +335,9 @@ impl Fp256 { const fn const_is_valid(&self, _modulus: &BigInteger256) -> bool { let mut i = Fp256::::NLIMBS - 1; loop { - if self.0.0[i] > C::MODULUS.0[i] { + if self.0 .0[i] > C::MODULUS.0[i] { return false; - } else if self.0.0[i] < C::MODULUS.0[i] { + } else if self.0 .0[i] < C::MODULUS.0[i] { return true; } if i == 0 { @@ -349,7 +354,7 @@ impl Fp256 { const fn const_square(&mut self) { let mut x = [0u64; 9]; for i in 0..9 { - x[i] = self.0.0[i] as u64; + x[i] = self.0 .0[i] as u64; } let mut xy = [0u64; 9]; for i in 0..9 { @@ -370,7 +375,7 @@ impl Fp256 { if j <= i { let mut tmp = x[i] * x[j]; if j < i { - tmp <<= 1; + tmp <<= 1; } xy_j += tmp; } @@ -384,10 +389,10 @@ impl Fp256 { }; } for j in 1..9 { - self.0.0[j - 1] = (xy[j - 1] as u32) & MASK; + self.0 .0[j - 1] = (xy[j - 1] as u32) & MASK; xy[j] += xy[j - 1] >> SHIFT64; } - self.0.0[9 - 1] = xy[9 - 1] as u32; + self.0 .0[9 - 1] = xy[9 - 1] as u32; self.const_reduce(&C::MODULUS); } @@ -407,7 +412,7 @@ impl Zero for Fp256 { Self(BigInteger256([0; 9]), PhantomData) } fn is_zero(&self) -> bool { - self.0.0 == [0u32; 9] + self.0 .0 == [0u32; 9] } } @@ -729,10 +734,10 @@ impl FromStr for Fp256 { res.mul_assign(&ten); let digit = Self::from(u64::from(c)); res.add_assign(&digit); - }, + } None => { return Err(()); - }, + } } } if !res.is_valid() { From 5562caa7b1d8445c534ebe2626a5a638d6d0fef8 Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Wed, 20 Aug 2025 13:23:46 +0100 Subject: [PATCH 16/16] Adjust targets/default dependencies to allow for wasm compilation w/o ocaml --- Cargo.toml | 14 ++++++++------ curves/Cargo.toml | 2 +- curves/src/pasta/wasm_friendly/backend9.rs | 9 +++++---- curves/src/pasta/wasm_friendly/pasta.rs | 7 +++---- internal-tracing/Cargo.toml | 6 ++++-- kimchi-stubs/Cargo.toml | 6 ++++-- kimchi/Cargo.toml | 12 +++++++----- poly-commitment/Cargo.toml | 9 ++++++--- poseidon/Cargo.toml | 11 +++++++++-- scripts/bench-wasm.sh | 2 +- 10 files changed, 48 insertions(+), 30 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 98e5213b9a6..7dbbb62ab21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,10 +46,11 @@ bs58 = "0.5.0" clap = "4.4.6" command-fds = "0.3" console_error_panic_hook = { version = "0.1.6" } -criterion = { version = "0.5", features = [ - "cargo_bench_support", - "html_reports", -] } +criterion = { version = "0.5", default-features = false } +#criterion = { version = "0.5", features = [ +# "cargo_bench_support", +# "html_reports", +#] } derivative = { version = "2.0", features = ["use_core"] } elf = "0.7.2" env_logger = "0.11.1" @@ -63,8 +64,8 @@ libflate = "2" log = "0.4.20" num-bigint = { version = "0.4.4", features = ["rand", "serde"] } num-integer = "0.1.45" -ocaml = { version = "0.22.2" } -ocaml-gen = { version = "1.0.0" } +#ocaml = { version = "0.22.2", optional = true } +#ocaml-gen = { version = "1.0.0", optional = true } once_cell = "=1.21.3" os_pipe = { version = "1.1.4", features = ["io_safety"] } paste = "1.0.15" @@ -118,6 +119,7 @@ turshi = { path = "./turshi", version = "0.1.0" } utils = { path = "./utils", version = "0.1.0" } wasm-types = { path = "./crates/wasm-types" } + [profile.release] lto = true panic = 'abort' diff --git a/curves/Cargo.toml b/curves/Cargo.toml index 0511d549a2b..8bca73ec604 100644 --- a/curves/Cargo.toml +++ b/curves/Cargo.toml @@ -26,4 +26,4 @@ zeroize.workspace = true rand.workspace = true ark-test-curves.workspace = true ark-algebra-test-templates.workspace = true -ark-std.workspace = true +ark-std.workspace = true \ No newline at end of file diff --git a/curves/src/pasta/wasm_friendly/backend9.rs b/curves/src/pasta/wasm_friendly/backend9.rs index 068e84f6ae5..617f615024c 100644 --- a/curves/src/pasta/wasm_friendly/backend9.rs +++ b/curves/src/pasta/wasm_friendly/backend9.rs @@ -332,14 +332,15 @@ pub fn mul_assign(x: &mut B, y: &B) { // implement FpBackend given FpConstants /// Converts a bigint of 9 limbs, of 32x8 shape, into a field element. +/// MUST have `x[8] == 0`. pub fn from_bigint_unsafe(x: BigInt<9>) -> Fp { let r: [u32; 9] = x.into_digits(); - assert!(r[8] == 0, "from_bigint_unsafe: bigint exceeds 256 bits"); + //assert!(r[8] == 0, "from_bigint_unsafe: bigint exceeds 256 bits"); let mut r = from_32x8(r[0..8].try_into().unwrap()); - assert!(is_32x9_shape(r)); + //assert!(is_32x9_shape(r)); // convert to montgomery form mul_assign::(&mut r, &FpC::R2); - assert!(is_32x9_shape(r)); + //assert!(is_32x9_shape(r)); Fp(BigInt::from_digits(r), Default::default()) } @@ -368,7 +369,7 @@ impl FpBackend<9> for FpC { /// Return a "normal" bigint fn to_bigint(x: Fp) -> BigInt<9> { - assert!(is_32x9_shape(x.0.into_digits())); + //assert!(is_32x9_shape(x.0.into_digits())); let one: [u32; 9] = [1, 0, 0, 0, 0, 0, 0, 0, 0]; let mut r = x.0.into_digits(); // convert back from montgomery form diff --git a/curves/src/pasta/wasm_friendly/pasta.rs b/curves/src/pasta/wasm_friendly/pasta.rs index c143b02713e..73284868184 100644 --- a/curves/src/pasta/wasm_friendly/pasta.rs +++ b/curves/src/pasta/wasm_friendly/pasta.rs @@ -28,10 +28,9 @@ impl Fp9 { pub fn from_fp(fp: Fp) -> Self { let limbs8: [u32; 8] = backend9::from_64x4_to_32x8(fp.into_bigint().0); let mut limbs9 = [0u32; 9]; - for i in 0..8 { - limbs9[i] = limbs8[i]; - } - println!("from_fp: {:?}, limbs9 {:?}", fp, limbs9); + limbs9[..8].copy_from_slice(&limbs8); + + //println!("from_fp: {:?}, limbs9 {:?}", fp, limbs9); backend9::from_bigint_unsafe(super::BigInt::from_digits(limbs9)) } diff --git a/internal-tracing/Cargo.toml b/internal-tracing/Cargo.toml index bc3d848b97a..5b6d4d12c1f 100644 --- a/internal-tracing/Cargo.toml +++ b/internal-tracing/Cargo.toml @@ -5,8 +5,10 @@ edition = "2021" license = "Apache-2.0" [dependencies] -ocaml = { workspace = true, optional = true } -ocaml-gen = { workspace = true, optional = true } +ocaml = { version = "0.22.2", optional = true } +ocaml-gen = { version = "1.0.0", optional = true } +#ocaml = { workspace = true, optional = true } +#ocaml-gen = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"], optional = true } serde_json = { workspace = true, optional = true } diff --git a/kimchi-stubs/Cargo.toml b/kimchi-stubs/Cargo.toml index e5ae3d69a54..e9bd2f1d9d5 100644 --- a/kimchi-stubs/Cargo.toml +++ b/kimchi-stubs/Cargo.toml @@ -18,8 +18,10 @@ crate-type = ["lib", "staticlib"] libc.workspace = true num-bigint = { workspace = true, features = ["rand", "serde"] } # ocaml-specific -ocaml = { workspace = true, features = ["no-caml-startup"] } -ocaml-gen.workspace = true +#ocaml = { workspace = true, features = ["no-caml-startup"] } +#ocaml-gen.workspace = true +ocaml = { version = "0.22.2", optional = true, features = ["no-caml-startup"] } +ocaml-gen = { version = "1.0.0", optional = true } once_cell.workspace = true paste.workspace = true rand.workspace = true diff --git a/kimchi/Cargo.toml b/kimchi/Cargo.toml index 126ef673977..2df98920f28 100644 --- a/kimchi/Cargo.toml +++ b/kimchi/Cargo.toml @@ -40,19 +40,21 @@ strum.workspace = true strum_macros.workspace = true thiserror.workspace = true -ocaml = { workspace = true, optional = true } -ocaml-gen = { workspace = true, optional = true } +ocaml = { version = "0.22.2", optional = true } +ocaml-gen = { version = "1.0.0", optional = true } +#ocaml = { workspace = true, optional = true } +#ocaml-gen = { workspace = true, optional = true } wasm-bindgen = { workspace = true, optional = true } -internal-tracing.workspace = true +internal-tracing = { workspace = true, default-features = false } # Internal dependencies groupmap.workspace = true mina-curves.workspace = true -mina-poseidon.workspace = true +mina-poseidon = { workspace = true, default-features = false } o1-utils.workspace = true -poly-commitment.workspace = true +poly-commitment = { workspace = true, default-features = false } turshi.workspace = true diff --git a/poly-commitment/Cargo.toml b/poly-commitment/Cargo.toml index 09478e6897f..e5892bb3d0b 100644 --- a/poly-commitment/Cargo.toml +++ b/poly-commitment/Cargo.toml @@ -30,11 +30,14 @@ thiserror.workspace = true groupmap.workspace = true mina-curves.workspace = true -mina-poseidon.workspace = true +mina-poseidon = { workspace = true, default-features = false } o1-utils.workspace = true -ocaml = { workspace = true, optional = true } -ocaml-gen = { workspace = true, optional = true } + +ocaml = { version = "0.22.2", optional = true } +ocaml-gen = { version = "1.0.0", optional = true } +#ocaml = { workspace = true, optional = true } +#ocaml-gen = { workspace = true, optional = true } [dev-dependencies] criterion.workspace = true diff --git a/poseidon/Cargo.toml b/poseidon/Cargo.toml index 1f6fe10788c..9a86120834e 100644 --- a/poseidon/Cargo.toml +++ b/poseidon/Cargo.toml @@ -19,8 +19,10 @@ ark-poly.workspace = true ark-serialize.workspace = true mina-curves.workspace = true o1-utils.workspace = true -ocaml = { workspace = true, optional = true } -ocaml-gen = { workspace = true, optional = true } +#ocaml = { workspace = true, optional = true } +#ocaml-gen = { workspace = true, optional = true } +ocaml = { version = "0.22.2", optional = true } +ocaml-gen = { version = "1.0.0", optional = true } once_cell.workspace = true rand.workspace = true rayon.workspace = true @@ -29,6 +31,11 @@ serde_with.workspace = true [dev-dependencies] criterion.workspace = true +#criterion = { workspace = true, features = [ +# "cargo_bench_support", +# "html_reports", +#] } + hex.workspace = true serde_json.workspace = true diff --git a/scripts/bench-wasm.sh b/scripts/bench-wasm.sh index e47ad9c32c4..fb6eb0dd9e5 100755 --- a/scripts/bench-wasm.sh +++ b/scripts/bench-wasm.sh @@ -91,7 +91,7 @@ cleanup_old_wasm_files # Build the WASM benchmark with cargo-wasi echo "Building benchmark '$BENCH_NAME' with cargo-wasi..." -cargo wasi build --bench="$BENCH_NAME" --release +cargo wasi build --bench="$BENCH_NAME" --release -p mina-poseidon # Function to find the correct WASM file find_wasm_file() {