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

Problem with mont form inversion #606

Closed
wants to merge 8 commits into from

Conversation

dvdplm
Copy link
Contributor

@dvdplm dvdplm commented Jun 11, 2024

Test to illustrate a potential problem with the Bernstein&Yang code to
invert numbers in Montgomery form. The specific parameters shown here
come from intermittent test failures of homomorphic_mul in the
Synedrion signining library (i.e. parameters are generated with an
unseeded CSPRNG).

Run the test with cargo t inversion_v06_vs_v055.

For convenience, after the test follows a commented out version of the
same that passes with crypto-bigint v0.5.5.

To compare with v0.5.5 do the following:

  1. Checkout the old code: git checkout v0.5.5
  2. Run cargo update -p proc-macro2 to work around a recent compiler
    incompatibility
  3. Paste the commented out v0.5 test in the test module of
    runtime_mod.rs
  4. Run the test with cargo t inversion_v06v_v055
  5. Notice it passes

@tarcieri
Copy link
Member

@dvdplm it looks like this test is making use of internals / private fields. Can you write it entirely in terms of the public API? Otherwise I can't tell if you're miscomputing constants.

It would also be good to generate reference values with e.g. sage, and show your work for that.

@dvdplm
Copy link
Contributor Author

dvdplm commented Jun 12, 2024

Can you write it entirely in terms of the public API?

You mean to instantiate the MontyForm values? Sure, I can try to do that.

I think having known good values would be very good, and I had a look at the Sage docs but couldn't find much info on how to do modular maths with it. I'll try again though.

@dvdplm
Copy link
Contributor Author

dvdplm commented Jun 13, 2024

Can you write it entirely in terms of the public API?

Done.

@tarcieri
Copy link
Member

@dvdplm as an alternative to sage, you could also compare results with num-modular, which is already one of the dev-dependencies

@dvdplm
Copy link
Contributor Author

dvdplm commented Jun 14, 2024

also compare results with num-modular

@tarcieri I added a test using num-modular. It agrees with crypto-bigint v0.5.5.

Add tests for random uints and their inverses for both v06 and v05
@dvdplm
Copy link
Contributor Author

dvdplm commented Jun 20, 2024

@tarcieri @fjarri I think I have found another clue that might help us narrow down what is going wrong here. The problem seems to lie in the modulus, in the size of the modulus. In the latest commit (e0da58b) I have added more tests to illustrate how moduli of sizes U2048 and U4096 seem to break on v0.6. Weirdly enough U1024 moduli seem fine. I did not try U8192 yet.

Not sure what the next steps should be but I wanted to leave as much information as possible here.

fn v06_inversion_random_uints_u2048_modulus() {
let mut rng = ChaChaRng::from_seed(RANDOM_SEED);
let modulus = U2048::from_be_hex(concat!(
"1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changing this to 1effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff makes the test pass. For this issue it doesn't seem to matter what any other bytes are.

let mut rng = ChaChaRng::from_seed(RANDOM_SEED);
let modulus = U4096::from_be_hex(concat!(
// 0x07… fails (bin 00000111)
"06ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

For U4096 the "failure value" is different.

fn v06_inversion_random_uints_u1024_modulus() {
let mut rng = ChaChaRng::from_seed(RANDOM_SEED);
let modulus = U1024::from_be_hex(concat!(
"1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

U1024 seems to be able to cope with any modulus.

@tarcieri
Copy link
Member

@dvdplm that's a good data point to know re: specific sized integers being impacted

My best guess would be it's a bug in the bernstein_yang_nlimbs! macro which computes the number of 62-bit limbs needed to represent a given-sized value:

/// Calculate the number of 62-bit unsaturated limbs required to represent the given number of bits when performing
/// Bernstein-Yang inversions.
// TODO(tarcieri): replace with `generic_const_exprs` (rust-lang/rust#76560) when stable
macro_rules! bernstein_yang_nlimbs {
($bits:expr) => {
(($bits / 64) + (($bits / 64) * 2).div_ceil(64) + 1)
};
}

That's used here when writing impls of the PrecomputeInverter trait:

{ bernstein_yang_nlimbs!($bits as usize) },

@tarcieri
Copy link
Member

I was able to produce a test failure using the existing test suite by modifying the proptests for modular inversion in tests/monty_form.rs to use U2048 instead of U256.

I then made those same tests pass by modifying bernstein_yang_nlimbs to be the following:

 macro_rules! bernstein_yang_nlimbs { 
     ($bits:expr) => { 
         (($bits / 64) + (($bits / 64) * 2).div_ceil(64) + 2) 
     }; 
 } 

That probably isn't the best way to fix this specific problem, but I believe it does demonstrate that there are currently too few 62-bit limbs for a given sized integer, leading to truncation and thus the miscomputed results.

@dvdplm I haven't tested your specific vectors, but perhaps you could try that locally and see if it corrects the issue?

@@ -38,6 +38,7 @@ num-modular = { version = "0.6", features = ["num-bigint", "num-integer", "num-t
proptest = "1"
rand_core = { version = "0.6", features = ["std"] }
rand_chacha = "0.3"
crypto_bigint_05 = {package = "crypto-bigint", version = "0.5.5"}
Copy link
Member

Choose a reason for hiding this comment

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

Would you mind removing this dependency and the tests that depend on it, leaving only tests against the current codebase with fixed vectors?

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 mean, this PR is just a throw-away thing to tinker with a tricky bug and this was convenient for that purpose. If there's anything we salvage here long term it's perhaps the test vectors, but perhaps we can put those in your other PR?

@tarcieri
Copy link
Member

I'm also a little confused why the current calculation for the number of limbs is insufficient, but I'll have to go back through the paper and find where it's defined.

FWIW, it currently computes 34 limbs for a 2048-bit integer, where 34 * 62 = 2108 bits

@tarcieri
Copy link
Member

Aha, so the issue is for a given UNSAT_LIMBS, we can calculate the maximum allowed size of any modulus or input value in bits as:

(UNSAT_LIMBS * 62) - 64

We currently compute 34 limbs for a 2048-bit integer:

(34 * 62) - 64 = 2044

That's 4-bits too small.

tarcieri added a commit that referenced this pull request Jun 20, 2024
The previous calculation of the number of unsaturated 62-bit limbs
needed to represent an integer of a given size was incorrect, leading to
miscomputed results as seen in #606.

This commit switches to a much simpler calculation based on
`div_ceiling(62)`, and also adds a const assertion that the computed
number of limbs is sufficient to hold a `$bits`-sized integer.
@tarcieri
Copy link
Member

Draft PR with a fix here: #610

@dvdplm I'd appreciate it if you could rebase on that and see if it fixed your issues

@dvdplm
Copy link
Contributor Author

dvdplm commented Jun 20, 2024

That's 4-bits too small.

Awesome sleuthing! I was thiiiiiis close, but you got there first! :)

@dvdplm
Copy link
Contributor Author

dvdplm commented Jun 20, 2024

I'd appreciate it if you could rebase on that and see if it fixed your issues

Yep, will do first thing in the morning.

@dvdplm
Copy link
Contributor Author

dvdplm commented Jun 20, 2024

@dvdplm I haven't tested your specific vectors, but perhaps you could try that locally and see if it corrects the issue?

Changing the + 1 to + 2 fixes the failing tests, so that's a good first indication that your fix is good. I'll test the PR properly as well.

tarcieri added a commit that referenced this pull request Jun 21, 2024
The previous calculation of the number of unsaturated 62-bit limbs
needed to represent an integer of a given size was incorrect, leading to
miscomputed results as seen in #606.

This commit switches to a much simpler calculation based on
`div_ceiling(62)`, and also adds a const assertion that the computed
number of limbs is sufficient to hold a `$bits`-sized integer.
@dvdplm
Copy link
Contributor Author

dvdplm commented Jun 21, 2024

I'd appreciate it if you could rebase on that and see if it fixed your issues

I have checked that #610 indeed fixes all test failures on this branch. 🎉

To check upstream it would be really good to have a new pre-release of crypto-bigint, is that possible?

@tarcieri
Copy link
Member

Yes, I can cut another release soon

@tarcieri
Copy link
Member

Released in v0.6.0-rc.0

jtcoolen pushed a commit to jtcoolen/crypto-bigint that referenced this pull request Jul 29, 2024
The previous calculation of the number of unsaturated 62-bit limbs
needed to represent an integer of a given size was incorrect, leading to
miscomputed results as seen in RustCrypto#606.

This commit switches to a much simpler calculation based on
`div_ceiling(62)`, and also adds a const assertion that the computed
number of limbs is sufficient to hold a `$bits`-sized integer.
@tarcieri
Copy link
Member

Going to close this out, but I'd still be curious in getting some of these tests in if you'd like to resubmit just those

@tarcieri tarcieri closed this Jul 31, 2024
@dvdplm dvdplm mentioned this pull request Aug 5, 2024
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