-
Notifications
You must be signed in to change notification settings - Fork 136
Wasm-friendly Field #2638
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
mitschabaude
wants to merge
19
commits into
develop
Choose a base branch
from
perf/wasm-friendly-field
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Wasm-friendly Field #2638
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
b250a52
minimize assumptions on field trait for poseidon impl
mitschabaude 498aa67
move into src/pasta
mitschabaude a16a19c
reduce minimal field definition to 3 functions and 3 constants
mitschabaude 22b42c1
rename, minor changes
mitschabaude c298437
start implementing one backend
mitschabaude a87bc0a
multiplication, start trying to satisfy backend trait
mitschabaude c5c2ada
make it compile
mitschabaude 0d17ab2
export fp9 type
mitschabaude df262a1
add coercion from fp to fp9
mitschabaude 56dd6e9
add fp9 poseidon benchmark
mitschabaude d99a80f
fix conversion from arkworks fp, and hard-code field constants
mitschabaude 6ed627c
print field elements, poseidon result doesn't match yet
mitschabaude 9a8e7d5
cargo fmt
mitschabaude 3ee9552
fix some clippy warnings, ignore others
mitschabaude a2422ff
fix comments
mitschabaude 9e33755
Add benchmarks for fp9/fp conversion
volhovm bd5d6c6
Improve benchmarks for basic ops
volhovm f73a324
More benchmarks
volhovm 6795ad9
Fix for qi/carry computation for fp9
volhovm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<FpC: FpConstants>(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<FpC: FpConstants>(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::<FpC>(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<FpC: FpConstants>(x: &mut B) { | ||
| if gte_modulus::<FpC>(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<FpC: FpConstants>(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::<FpC>(x); | ||
| } | ||
|
|
||
| // implement FpBackend given FpConstants | ||
|
|
||
| pub fn from_bigint_unsafe<FpC: FpConstants>(x: BigInt<9>) -> Fp<FpC, 9> { | ||
| let mut r = x.0; | ||
| // convert to montgomery form | ||
| mul_assign::<FpC>(&mut r, &FpC::R2); | ||
| Fp(BigInt(r), Default::default()) | ||
| } | ||
|
|
||
| impl<FpC: FpConstants> 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<Self, 9>, y: &Fp<Self, 9>) { | ||
| add_assign::<Self>(&mut x.0 .0, &y.0 .0); | ||
| } | ||
|
|
||
| fn mul_assign(x: &mut Fp<Self, 9>, y: &Fp<Self, 9>) { | ||
| mul_assign::<Self>(&mut x.0 .0, &y.0 .0); | ||
| } | ||
|
|
||
| fn from_bigint(x: BigInt<9>) -> Option<Fp<Self, 9>> { | ||
| if gte_modulus::<Self>(&x.0) { | ||
| None | ||
| } else { | ||
| Some(from_bigint_unsafe(x)) | ||
| } | ||
| } | ||
| fn to_bigint(x: Fp<Self, 9>) -> 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::<Self>(&mut r, &one); | ||
| BigInt(r) | ||
| } | ||
|
|
||
| fn pack(x: Fp<Self, 9>) -> Vec<u64> { | ||
| 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 | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<const N: usize>(pub [u32; N]); | ||
|
|
||
| impl<const N: usize> Default for BigInt<N> { | ||
| fn default() -> Self { | ||
| Self([0u32; N]) | ||
| } | ||
| } | ||
|
|
||
| impl<const N: usize> CanonicalSerialize for BigInt<N> { | ||
| fn serialize_with_mode<W: Write>( | ||
| &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<const N: usize> Valid for BigInt<N> { | ||
| fn check(&self) -> Result<(), SerializationError> { | ||
| self.0.check() | ||
| } | ||
| } | ||
|
|
||
| impl<const N: usize> CanonicalDeserialize for BigInt<N> { | ||
| fn deserialize_with_mode<R: Read>( | ||
| reader: R, | ||
| compress: Compress, | ||
| validate: Validate, | ||
| ) -> Result<Self, SerializationError> { | ||
| Ok(BigInt::<N>(<[u32; N]>::deserialize_with_mode( | ||
| reader, compress, validate, | ||
| )?)) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<S: AsRef<[u64]>>(&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<F: ark_ff::Field> MinimalField for F { | ||
| fn square_in_place(&mut self) -> &mut Self { | ||
| self.square_in_place() | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<Fp9Parameters, 9>; | ||
|
|
||
| impl Fp9 { | ||
| pub fn from_fp(fp: Fp) -> Self { | ||
| backend9::from_bigint_unsafe(super::BigInt(backend9::from_64x4(fp.into_bigint().0))) | ||
| } | ||
| } | ||
|
|
||
| impl From<Fp> for Fp9 { | ||
| fn from(fp: Fp) -> Self { | ||
| Fp9::from_fp(fp) | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.