Skip to content
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

Open
wants to merge 127 commits into
base: main
Choose a base branch
from

Conversation

qalisander
Copy link
Member

@qalisander qalisander commented Jan 15, 2025

Resolves #481

PR Checklist

  • Tests
  • Documentation
  • Changelog

@bidzyyys bidzyyys self-requested a review January 29, 2025 08:33
@qalisander qalisander requested a review from 0xNeshi January 29, 2025 08:57
!self.ct_is_odd()
}

const fn ct_geq(&self, rhs: &Self) -> bool {
Copy link
Member Author

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

Copy link
Collaborator

@bidzyyys bidzyyys left a 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.

benches/src/report.rs Show resolved Hide resolved
examples/oz-crypto/src/lib.rs Outdated Show resolved Hide resolved
lib/crypto/src/arithmetic/limb.rs Show resolved Hide resolved
lib/crypto/src/arithmetic/mod.rs Outdated Show resolved Hide resolved
lib/crypto/src/arithmetic/mod.rs Outdated Show resolved Hide resolved
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;
Copy link
Collaborator

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.

Copy link
Collaborator

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

lib/crypto/src/arithmetic/uint.rs Outdated Show resolved Hide resolved
ct_for!((i in 0..N) {
let a = self.limbs[N - i - 1];
let b = rhs.limbs[N - i - 1];
if a < b {
Copy link
Collaborator

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

Copy link
Collaborator

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.

#[must_use]
pub const fn ct_ge(&self, rhs: &Self) -> bool {
ct_for!((i in 0..N) {
if self.limbs[i] < rhs.limbs[i] {
Copy link
Collaborator

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

Copy link
Member Author

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 {
Copy link
Collaborator

@bidzyyys bidzyyys Jan 29, 2025

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.
Copy link
Member Author

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];
Copy link
Member Author

@qalisander qalisander Jan 29, 2025

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

@qalisander qalisander marked this pull request as ready for review January 29, 2025 13:08
Copy link
Collaborator

@bidzyyys bidzyyys left a 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 {
Copy link
Collaborator

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.

Copy link
Collaborator

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?

benches/src/report.rs Show resolved Hide resolved
lib/crypto/src/arithmetic/limb.rs Show resolved Hide resolved
}
}

// TODO#q: Document ShrAssign and ShlAssign implementations
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO

@bidzyyys bidzyyys changed the title feat: biginteger implementation feat: Big Integer implementation Jan 29, 2025
Copy link
Collaborator

@0xNeshi 0xNeshi left a 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`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// 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,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// 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) {
Copy link
Collaborator

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

Comment on lines +241 to +243
#[inline]
#[allow(dead_code)]
pub(crate) const fn ct_checked_add(mut self, rhs: &Self) -> (Self, bool) {
Copy link
Collaborator

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?

Comment on lines 142 to +149
/// 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
Copy link
Collaborator

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:

Suggested change
/// 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

Copy link
Collaborator

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`.
Copy link
Collaborator

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

Suggested change
/// 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)

Copy link
Collaborator

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;
Copy link
Collaborator

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

Comment on lines +441 to +443
/// Algorithm 14.32 in Handbook of Applied Cryptography [reference].
///
/// [reference]: https://cacr.uwaterloo.ca/hac/about/chap14.pdf
Copy link
Collaborator

@0xNeshi 0xNeshi Jan 29, 2025

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]
Copy link
Collaborator

@0xNeshi 0xNeshi Jan 29, 2025

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Feature]: BigInteger implementation
3 participants