-
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 for Cryptographic Applications #495
Conversation
✅ Deploy Preview for contracts-stylus canceled.
|
Codecov ReportAttention: Patch coverage is
Additional details and impacted files
|
Cargo.toml
Outdated
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.
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.
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 was about to drop it. It is just for comparison, when I was optimizing it
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.
Left some comments + many warnings from CI.
Overall great progress @qalisander!
lib/crypto/src/arithmetic/mod.rs
Outdated
const_for!((i in 0..N) { | ||
let a = self.limbs[N - i - 1]; | ||
let b = other.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.
- Can't you have jus
return a < b
? - What 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 they're equal go to next. As I remember it is code for geq
comparison
lib/crypto/src/arithmetic/mod.rs
Outdated
while self.const_is_even() { | ||
self = self.const_shr(); | ||
} | ||
assert!(self.const_is_odd()); |
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 do not like these asserts but I believe it is the only way to assure proper execution :(
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 I've already dropped this code:D
lib/crypto/src/arithmetic/mod.rs
Outdated
} | ||
} | ||
|
||
// TODO#q: rename to checked_add? |
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 you are about checked_add
you should implement the proper trait then.
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've thought about about names, and constant ct_checked_add
and checked_add_assign
that returns boolean seems rational to me
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.
Fuzzing here is super needed 😅 @0xNeshi
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.
Proptests would be a bare minimum I'd say 😃
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.
Yeah. We will try to add more proptests, but currently: the best warranty gives poseidon, that computes valid hash on each instance
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.
Just one wrong addition/multiplication or messed up index and hash will be totally different from the other 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.
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)
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
lib/crypto/src/field/fp.rs
Outdated
/// 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
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
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 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
} | ||
} | ||
|
||
/// 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.
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.
Increased binary size is also an impact of aggressive inlines #[inline(always)]
. It is usually a tradeoff
Yeah, measured. Impact with inlines seems significant
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've checked most of them and also #[inline(always)]
vs normal #[inline]
. The first one gives more impact sometimes
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.
Did you measure in Release mode or in Debug mode?
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.
Looks good to me!
318781f
to
97063af
Compare
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.
*clicks tongue* Noice!
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