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

Fix overflow when initializing a Sieve with T::MAX #47

Merged
merged 4 commits into from
Oct 9, 2024

Conversation

dvdplm
Copy link
Contributor

@dvdplm dvdplm commented Sep 30, 2024

Avoids a panic when initializing a Sieve with T::MAX.

Copy link

codecov bot commented Sep 30, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 99.33%. Comparing base (4a4696e) to head (20e06b3).
Report is 14 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master      #47   +/-   ##
=======================================
  Coverage   99.32%   99.33%           
=======================================
  Files           9        9           
  Lines        1340     1344    +4     
=======================================
+ Hits         1331     1335    +4     
  Misses          9        9           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@dvdplm dvdplm requested a review from fjarri September 30, 2024 21:32
src/hazmat/sieve.rs Outdated Show resolved Hide resolved
.expect("addition should not overflow by construction");
if self.safe_primes {
num = num.wrapping_shl_vartime(1) | T::one_like(&self.base);
match self.base.checked_add(&self.incr.into()).into_option() {
Copy link
Member

Choose a reason for hiding this comment

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

Seems like here we're branching on something that will never happen unless the private attributes are messed with? Also, instead of returning None for a composite now the method returns None for a composite or if there's an overflow, which can be confusing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This overflow can actually happen without any malicious shenanigans (and is guarded against with the sieve_with_max_start test).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree with you about the odd control flow here though. It gets complex quick:

  • Create a sieve and set base to T::MAX (like the test).
  • Call next()
  • The first call to update_residues() will work fine because incr == 0 (returns true)
  • …but this first call will set last_round to true as a side effect.
  • Now we call maybe_next() which will also work fine because T::MAX is composite (returns None)
  • …but now incr and incr_limit are 2 as a side effect.
  • We call update_residues again, and given that incr is now 2 and less or equal to incr_limit, we match on the first check and return true (note: last_round is true, which would cause returning false)
  • So now we call maybe_next again but now the addition overflows so we return None and the main loop continues
  • Back in update_residues() for the third time, we finally hit the check for last_round, so we return false
  • …which causes the main while-loop in next() to exit and we return None
  • …and the iterator stops.

Pheew!!!

Maybe the Sieve constructor should check if the given start is worth the effort? E.g. check if T::MAX - start contains "enough" primes. Maybe using PNT and check that T::MAX/log(T::MAX) - start/log(start) is larger than some number (1?, 10?)? Or can you think of something smarter?

Copy link
Contributor Author

@dvdplm dvdplm Oct 4, 2024

Choose a reason for hiding this comment

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

…then again, there are a lot of primes, so even if they pick a start that is 99.999999% of T::MAX there are still a LOT of prime candidates left, so maybe we just check that start != T::MAX and that's good enough?

Copy link
Member

Choose a reason for hiding this comment

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

I don't particularly like the idea of heuristic checks and relying on probabilities in this specific case (the sieve), I think if it's possible to do exactly, we should try to do it.

Now we call maybe_next() which will also work fine because T::MAX is composite

Not necessarily, 2^128-1 and 2^8192-1 are primes. (Actually the former can be used as a test)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(Actually the former can be used as a test)

You mean 2^127 -1? Is that the last prime before U128::MAX, probably not right? Seems like a bit of an arbitrary test, or maybe I'm misunderstanding what you're suggesting here.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, my mistake. Any 2^n-1 is indeed composite if n is composite.

src/hazmat/sieve.rs Outdated Show resolved Hide resolved
@dvdplm
Copy link
Contributor Author

dvdplm commented Oct 9, 2024

@fjarri I think this one is ready to go in. I'd prefer to do any bigger refactoring of the sieve in a different PR (if we deem it's worth our time).

@fjarri fjarri merged commit 35ccd95 into master Oct 9, 2024
12 checks passed
fjarri added a commit to fjarri/crypto-primes that referenced this pull request Oct 9, 2024
@fjarri fjarri deleted the dp-fix-overflow-when-sieving-with-max branch October 19, 2024 00:22
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.

2 participants