-
Notifications
You must be signed in to change notification settings - Fork 34
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
feat: Big Integer implementation #495
base: main
Are you sure you want to change the base?
Conversation
lib/crypto/src/arithmetic/uint.rs
Outdated
!self.ct_is_odd() | ||
} | ||
|
||
const fn ct_geq(&self, rhs: &Self) -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The whole point of ct_*
prefix is to distinguish constant function from runtime function.
E.g. we can have geq
for runtime and ct_geq
for compile time.
Also same convention applied to ct_for!
macro, that adds convenience of for
but in const context.
p.s. for
cycle is not available in const context
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great progress, left some comments.
let (_, mut carry) = arithmetic::limb::mac(lo.limbs[i], tmp, P::MODULUS.limbs[0]); | ||
|
||
ct_for_unroll6!((j in 1..N) { | ||
let k = i + j; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I do not like k
, i
, l
for loop iterations, prefer more self-descriptive names to not mess them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this (complex) case I have to agree
ct_for!((i in 0..N) { | ||
let a = self.limbs[N - i - 1]; | ||
let b = rhs.limbs[N - i - 1]; | ||
if a < b { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if a != b { return a > b }
I believe it reduces some condition calculation in if
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this is more optimal 🤔 It requires 2 comparisons every time, whereas the current impl can sometimes be only 1 comparison, so it should be more performant in general.
We'd have to measure to know for sure.
lib/crypto/src/arithmetic/uint.rs
Outdated
#[must_use] | ||
pub const fn ct_ge(&self, rhs: &Self) -> bool { | ||
ct_for!((i in 0..N) { | ||
if self.limbs[i] < rhs.limbs[i] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if a != b { return a > b }
I believe it reduces some condition calculation in if
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I like your way:) Thnx!
break; | ||
} | ||
|
||
if index == 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I would merge this condition with the previous if
.
/// reduction for efficient implementation. | ||
#[inline(always)] | ||
fn mul_assign(a: &mut Fp<Self, N>, b: &Fp<Self, N>) { | ||
// Implements CIOS. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Attach link
#[inline] | ||
fn cmp(&self, rhs: &Self) -> Ordering { | ||
ct_for_unroll6!((i in 0..N) { | ||
let a = &self.limbs[N - i - 1]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add reversed const for
cycle
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only few comments left.
Please:
- update CHANGELOG.md
- add GH Issue describing how we should add prop tests and fuzzing to this Big Integer implementation
/// SPDX-License-Identifier: MIT | ||
pragma solidity >=0.7.0; | ||
|
||
library PoseidonT3 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe you should mention the author/repository of this Solidity implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this file as it is the same as in poseidon_asm_sol.rs
bench?
} | ||
} | ||
|
||
// TODO#q: Document ShrAssign and ShlAssign implementations |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My one big critique is terse/missing doc comments.
I think for the sake of our future selves, and for the sake of our audit team, we should have clear doc comments for each functions.
This is especially important if we plan to make this bigint implementation into a separate crate that we want to share with the world
true | ||
} | ||
|
||
/// Compute a right shift of `self` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Compute a right shift of `self` | |
/// Compute a right shift of `self`. |
let leading = self.limbs[index].leading_zeros() as usize; | ||
num_bits -= leading; | ||
|
||
// If the limb is not empty, stop processing other limbs, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// If the limb is not empty, stop processing other limbs, | |
// If the limb is not empty, stop processing other limbs. |
other comments use a dot (comma makes little sense anyway).
(self.limbs[limb] & mask) != 0 | ||
} | ||
|
||
/// Multiply `self` by `2`, assign the result to `self` and return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Multiply `self` by `2`, assign the result to `self` and return | |
/// Multiplies `self` by `2` in-place, returning whether overflow occurred. | |
/// | |
/// Returns `true` if the operation overflowed, `false` otherwise. |
Wdyt about this comment style for all functions? Seems a bit clearer to me
} | ||
|
||
#[inline] | ||
pub(crate) const fn ct_checked_sub(mut self, rhs: &Self) -> (Self, bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing docs. Even though this is internal to the crate, we as maintainers will need to know what each function does, especially months/years from now
#[inline] | ||
#[allow(dead_code)] | ||
pub(crate) const fn ct_checked_add(mut self, rhs: &Self) -> (Self, bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this if it isn't used?
/// Compute `a^{-1}` if `a` is not zero. | ||
/// | ||
/// Guajardo, Kumar, Paar, Pelzl. | ||
/// Efficient Software-Implementation of Finite Fields with Applications to | ||
/// Cryptography [reference]. | ||
/// Algorithm 16 (BEA for Inversion in Fp). | ||
/// | ||
/// [reference]: https://www.sandeep.de/my/papers/2006_ActaApplMath_EfficientSoftFiniteF.pdf |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reference looks weird as-is (like it's part of text).
This is what Claude spit out, and it looks really clean:
/// Compute `a^{-1}` if `a` is not zero. | |
/// | |
/// Guajardo, Kumar, Paar, Pelzl. | |
/// Efficient Software-Implementation of Finite Fields with Applications to | |
/// Cryptography [reference]. | |
/// Algorithm 16 (BEA for Inversion in Fp). | |
/// | |
/// [reference]: https://www.sandeep.de/my/papers/2006_ActaApplMath_EfficientSoftFiniteF.pdf | |
/// Computes the multiplicative inverse `a^{-1}` if `a` is non-zero. | |
/// | |
/// Uses the Binary Extended Algorithm (BEA) for inversion in Fp as | |
/// described in [Guajardo et al., 2006][paper]. | |
/// | |
/// [paper]: https://www.sandeep.de/my/papers/2006_ActaApplMath_EfficientSoftFiniteF.pdf "Efficient Software-Implementation of Finite Fields with Applications to Cryptography" |
It added paper title as hover text in the link definition
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could really utilize LLMs for comments too, they generally create very clean & clear docs
} | ||
} | ||
|
||
/// Compute `-M^{-1} mod 2^64`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a very terse doc comment.
See LLM's suggestion
/// Compute `-M^{-1} mod 2^64`. | |
/// Computes the Montgomery modular inverse `-MODULUS^{-1} mod 2^64` for the field modulus. | |
/// | |
/// This is a constant function that computes the modular multiplicative inverse of | |
/// the negative field modulus, modulo 2^64. This value is crucial for Montgomery arithmetic | |
/// operations in the field. | |
/// | |
/// The calculation uses the fact that for a 64-bit value: | |
/// - We only need the lowest limb of the modulus | |
/// - The Euler totient φ(2^64) = 2^63 | |
/// - Therefore we can compute the inverse by raising to power (2^63 - 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the audit team will thank us
let (_, mut carry) = arithmetic::limb::mac(lo.limbs[i], tmp, P::MODULUS.limbs[0]); | ||
|
||
ct_for_unroll6!((j in 1..N) { | ||
let k = i + j; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this (complex) case I have to agree
/// Algorithm 14.32 in Handbook of Applied Cryptography [reference]. | ||
/// | ||
/// [reference]: https://cacr.uwaterloo.ca/hac/about/chap14.pdf |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See previous comment on how to link to a paper in a cleaner way
@@ -42,6 +42,7 @@ impl<P: PoseidonParams<F>, F: PrimeField> Default for Poseidon2<P, F> { | |||
impl<P: PoseidonParams<F>, F: PrimeField> Poseidon2<P, F> { | |||
/// Create a new Poseidon sponge. | |||
#[must_use] | |||
#[inline] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inlining everything is the way to reducing gas cost in all cases? 🤔
I just noticed that many lib/crypto functions are inlined, but not all. Shouldn't we then be inlining everything?
Did you measure the impact of not using #[inline]
?
Shouldn't link-time optimizations perform the same optimization? Stylus projects have this option ON by default, and so do we.
Resolves #481
PR Checklist