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 for Cryptographic Applications #495

Merged
merged 138 commits into from
Jan 30, 2025

Conversation

qalisander
Copy link
Member

@qalisander qalisander commented Jan 15, 2025

This pr adds Uint<..> bigintegers, and implementation of basic arithmetic operations on integers in Montgomery form. That introduces significant gas cost cut, for finite field arithmetic.
Poseidon hash computation got around 8 times cheaper compare to what was before.
And cheaper than Solidity Assembly poseidon hash.

Resolves #481

PR Checklist

  • Tests
  • Documentation
  • Changelog

Copy link

netlify bot commented Jan 15, 2025

Deploy Preview for contracts-stylus canceled.

Name Link
🔨 Latest commit e43eb87
🔍 Latest deploy log https://app.netlify.com/sites/contracts-stylus/deploys/679bbb726f40b2000852e4c4

Copy link

codecov bot commented Jan 15, 2025

Codecov Report

Attention: Patch coverage is 70.25393% with 246 lines in your changes missing coverage. Please review.

Project coverage is 84.1%. Comparing base (5d612e6) to head (e43eb87).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
lib/crypto/src/arithmetic/uint.rs 66.2% 182 Missing ⚠️
lib/crypto/src/field/fp.rs 69.5% 64 Missing ⚠️
Additional details and impacted files
Files with missing lines Coverage Δ
lib/crypto/src/arithmetic/limb.rs 100.0% <100.0%> (ø)
lib/crypto/src/field/mod.rs 100.0% <ø> (ø)
lib/crypto/src/field/prime.rs 0.0% <ø> (ø)
lib/crypto/src/poseidon2/mod.rs 93.2% <ø> (ø)
lib/crypto/src/field/fp.rs 59.6% <69.5%> (+10.9%) ⬆️
lib/crypto/src/arithmetic/uint.rs 66.2% <66.2%> (ø)

Cargo.toml Outdated
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's great you have done these benchmarks but I am not a fan of maintaining akr-ff or poseidon-renegades. We may want. to have just a separated branch with these benchmarks.

Copy link
Member Author

Choose a reason for hiding this comment

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

I was about to drop it. It is just for comparison, when I was optimizing it

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.

Left some comments + many warnings from CI.
Overall great progress @qalisander!

benches/src/main.rs Outdated Show resolved Hide resolved
lib/crypto/src/field/mod.rs Outdated Show resolved Hide resolved
examples/oz-crypto/Cargo.toml Outdated Show resolved Hide resolved
lib/crypto/Cargo.toml Outdated Show resolved Hide resolved
benches/src/oz_crypto.rs Outdated Show resolved Hide resolved
const_for!((i in 0..N) {
let a = self.limbs[N - i - 1];
let b = other.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.

  1. Can't you have jus return a < b?
  2. What if a == b?

Copy link
Member Author

Choose a reason for hiding this comment

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

If they're equal go to next. As I remember it is code for geq comparison

while self.const_is_even() {
self = self.const_shr();
}
assert!(self.const_is_odd());
Copy link
Collaborator

Choose a reason for hiding this comment

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

I do not like these asserts but I believe it is the only way to assure proper execution :(

Copy link
Member Author

Choose a reason for hiding this comment

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

I think I've already dropped this code:D

}
}

// TODO#q: rename to checked_add?
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you are about checked_add you should implement the proper trait then.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've thought about about names, and constant ct_checked_add and checked_add_assign that returns boolean seems rational to me

lib/crypto/src/arithmetic/mod.rs Outdated Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

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

Fuzzing here is super needed 😅 @0xNeshi

Copy link
Collaborator

Choose a reason for hiding this comment

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

Proptests would be a bare minimum I'd say 😃

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah. We will try to add more proptests, but currently: the best warranty gives poseidon, that computes valid hash on each instance

Copy link
Member Author

Choose a reason for hiding this comment

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

Just one wrong addition/multiplication or messed up index and hash will be totally different from the other implementations

Copy link
Collaborator

@0xNeshi 0xNeshi Jan 30, 2025

Choose a reason for hiding this comment

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

I'd say there's still value in testing the primitives, not just the upper-layer stuff. There can always be an edge case that might cause an issue, but would rarely pop up in the upper-layer test (e.g. for poseidon)

@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
lib/crypto/src/arithmetic/uint.rs Outdated Show resolved Hide resolved
lib/crypto/src/arithmetic/uint.rs Outdated Show resolved Hide resolved
lib/crypto/src/arithmetic/uint.rs Show resolved Hide resolved
/// 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

@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

benches/src/PoseidonT3_asm.sol Outdated Show resolved Hide resolved
benches/src/PoseidonT3_asm.sol Outdated Show resolved Hide resolved
benches/src/report.rs Show resolved Hide resolved
lib/crypto/src/arithmetic/limb.rs Show resolved Hide resolved
lib/crypto/src/arithmetic/uint.rs Outdated Show resolved Hide resolved
@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 function.

This is especially important if we plan to make this bigint implementation into a separate crate that we want to share with the world

lib/crypto/src/arithmetic/uint.rs Outdated Show resolved Hide resolved
lib/crypto/src/arithmetic/uint.rs Outdated Show resolved Hide resolved
lib/crypto/src/arithmetic/uint.rs Outdated Show resolved Hide resolved
lib/crypto/src/arithmetic/uint.rs Outdated Show resolved Hide resolved
lib/crypto/src/arithmetic/uint.rs Outdated Show resolved Hide resolved
lib/crypto/src/field/fp.rs Show resolved Hide resolved
}
}

/// 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 +440
/// 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.

Copy link
Member Author

Choose a reason for hiding this comment

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

Increased binary size is also an impact of aggressive inlines #[inline(always)]. It is usually a tradeoff
Yeah, measured. Impact with inlines seems significant

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've checked most of them and also #[inline(always)] vs normal #[inline]. The first one gives more impact sometimes

Copy link
Collaborator

Choose a reason for hiding this comment

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

Did you measure in Release mode or in Debug mode?

@qalisander qalisander changed the title feat: Big Integer implementation feat: Big Integer Implementation for Cryptographic Applications Jan 30, 2025
@qalisander
Copy link
Member Author

@bidzyyys this issue for proptests

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.

Looks good to me!

@qalisander qalisander force-pushed the benchmark-experiments/biginteger branch from 318781f to 97063af Compare January 30, 2025 17:45
@qalisander qalisander requested a review from 0xNeshi January 30, 2025 18:14
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.

*clicks tongue* Noice!

@0xNeshi 0xNeshi merged commit d142e88 into main Jan 30, 2025
26 checks passed
@0xNeshi 0xNeshi deleted the benchmark-experiments/biginteger branch January 30, 2025 19:04
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