-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
[resubmit] Fix bug of FastReducer used in BigInteger.ModPow #55122
Conversation
Tagging subscribers to this area: @dotnet/area-system-numerics Issue Details
This pull request fixes two bugs that existed in 1.There is a bug in the current implementation of
When these conditions are met, the size of the buffers q1 and q2 used in the algorithm becomes insufficient, causing a assertion error on BigIntegerCalculator.FastReducer.cs#L77 in debug build, or
BigInteger.ModPow(BigInteger.One << 1008, 2, BigInteger.One << 992); This PR fixes this bug by changing the allocated size of the buffer. The validity of the change is explained below. Edit: After submitting this PR, I found a bug that is related but has a different cause, so I will fix that as well. See the below comments for details. ExplanationUnless otherwise stated, all variables are non-negative and division is defined as truncated division. Before we begin, we will define a few constants and functions.
First, we will look at the algorithm of the constructor.
Note: The variable The above corresponds to the following code. Lines 26 to 43 in 949fef4
Next, we will look at the algorithm of the Reduce function.
The above corresponds to the following code. Lines 45 to 66 in 949fef4
Here, we will think about upper bound of Same as Now we have shown the upper bound of 2.There is one additional bug in the current BigInteger.ModPow(BigInteger.One << 1008, 2, (BigInteger.One << 992) + 1); The above commit adds a test that covers this case and fixes the bug. Below is an explanation of the cause and fix for this bug. ExplanationIn this explanation, the symbols used in the above explanation are reused.
The above corresponds to the following code. Lines 63 to 65 in 949fef4
Lines 112 to 146 in 949fef4
The reason we divide by
The Furthermore, since
|
@pgovind This PR is assigned to you for follow-up/decision before the RC1 snap. |
I haven't had time to go through the changes here yet. I will have some time later this/next week to play around with the changes here. However, because we're pretty late in the .NET 6 cycle, this will likely miss .NET 6, but we can get it into .NET 7 for sure. .NET 7 will begin accepting changes in ~3 weeks |
@key-moon I wanted to let you know we're still working on some .NET 6.0 RC2 items, so we're a bit delayed on reviewing this. It'll likely be a couple more weeks. Thanks for your contributions and patience! |
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 PR and explanation. This LGTM!
@@ -27,19 +27,19 @@ public FastReducer(uint[] modulus) | |||
{ | |||
Debug.Assert(modulus != null); | |||
|
|||
// Let r = 4^k, with 2^k > m | |||
// Let r = (2^32)^(2k), with (2^32)^k > m |
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.
Could you explain why base
is 2^32
, which I'm interpreting as 4294967296
?
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 formula in the comment changed because the following formula is not correct. It should be v/2^(k-32) * mu
.
Lines 55 to 57 in b759ac9
// Let q1 = v/2^(k-1) * mu | |
int l1 = DivMul(value, length, _mu, _muLength, | |
_q1, _modulus.Length - 1); |
Instead of change (k-1)
to (k-32)
, I choose to change base
to 2^32
from 2
because the base of the number notation in this code is 2^32
. If you write as 2^(k-32)
, I think it is more harder to understand because the reader doesn't sure where 32
came from.
But I can understand why you prefer base 2
. So, what about //Let r = 2^(32*2*k), with 2^(32*2*k) > m
instead of 2^(2k)
or (2^32)^(2k)
?
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 prefer to keep this inline with the paper and just use 2^(k-32)
. We can leave a comment that its 32 because we operate on 32-bits at a time
src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.FastReducer.cs
Outdated
Show resolved
Hide resolved
The logic and math LGTM minus the use of The reduction algorithm is |
@key-moon Because of another PR that got merged, there are now some conflicts. Let us know if you have any trouble resolving the conflicts. Thanks! |
@jeffhandley Should I resolve the conflict by merging the main branch into this branch? |
Yes. If you perform the following steps, it'll set you up to resolve the conflict in your environment:
You'll then see the conflicts in Sorry for the inconvenience with the conflicts. Several folks have been contributing to BigInteger recently. |
Please let us know if you need assistance with fixing the merge conflict. We're happy to do so if needed. |
I apologize for the delay in resolving the conflicts. Because the size of q1 and q2 changes by calculation in the constructor, stack allocation could not be realized only with a read-only struct. To solve this problem, I add a helper structure. I have aligned the definition of k in the comments with the one in Wikipedia and added notes where k is used in similar applications but other definitions. |
Closing and reopening to get CI to reintegrate with main and rerun. If CI is passing, then I'll give this a final review and hopefully get it merged. |
Repeating the process as there are crypto failures that I believe are unrelated. |
@key-moon looks like the crypto tests are still failing. Could you push a merge commit that pulls in latest |
Since uncleared data does not contain any information of private key, it can't be abusable and therefore does not meet the criteria for information leakage in security reports. So I didn't report to MSRC. This problem existed in the PR of |
Update: This failure is reproduced in my local environment. And not occurred on |
This pull request has been automatically marked |
This pull request will now be closed since it had been marked |
This pull request fixes two bugs that existed in
FastReducer
.1.
There is a bug in the current implementation of
FastReducer
that causes a runtime error when certain conditions described below are met.base^{modulus.Length-1}
.When these conditions are met, the size of the buffers q1 and q2 used in the algorithm becomes insufficient, causing a assertion error on BigIntegerCalculator.FastReducer.cs#L77 in debug build, or
IndexOutOfRangeException
on BigIntegerCalculator.PowMod.cs#L401 in Release build.FastReducer
is used only inBigInteger.ModPow
, but the specific condition cause above error. Following code is one of the example.This PR fixes this bug by changing the allocated size of the buffer. The validity of the change is explained below.
Edit: After submitting this PR, I found a bug that is related but has a different cause, so I will fix that as well. See the below comments for details.
Explanation
Unless otherwise stated, all variables are non-negative and division is defined as truncated division.
Before we begin, we will define a few constants and functions.
First, we will look at the algorithm of the constructor.
Note: The variable
k
is implicitly defined and does not appear in the actual code.The above corresponds to the following code.
runtime/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.FastReducer.cs
Lines 26 to 43 in 949fef4
Next, we will look at the algorithm of the Reduce function.
The above corresponds to the following code.
runtime/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.FastReducer.cs
Lines 45 to 66 in 949fef4
Here, we will think about upper bound of
ActualLength(q1)
.Since upper bound of
V
isbase^(2k)
, upper bound ofV / base^(k-1)
isbase^(k+1)
.Since modulus should not be 0,
r
andmu
can't be 0. Somu
is less thanbase^(muLength)
.It is easy to show that an upper bound on the values of both
V
andmu
can be achieved.From these facts, we can see that the upper bound of
q1
isbase^(muLength + k + 1)
.Same as
q1
, we can see that upper bound ofq2
isbase^(muLength + k)
Now we have shown the upper bound of
q1
andq2
. From these, we can see that the upper bound of length of the buffer required forq1
andq2
is:ActualLength(q1) ≦ muLength + k + 1
,ActualLength(q2) ≦ muLength + k
.2.
There is one additional bug in the current
FastReducer
implementation. This bug occurs when the lower half bit of thevalue
is less thanvalue%modulus
.This happens when you call
BigInteger.ModPow
using the FastReducer as follows:The above commit adds a test that covers this case and fixes the bug. Below is an explanation of the cause and fix for this bug.
Explanation
In this explanation, the symbols used in the above explanation are reused.
Barett Reduction is an algorithm that takes advantage of the fact that
V mod m = V - ⌊v/m⌋m
holds. In this algorithm, value ofq2
is⌊v/m⌋m
. Now we will look at the algorithm of the last subtraction.The above corresponds to the following code.
runtime/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.FastReducer.cs
Lines 63 to 65 in 949fef4
runtime/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.FastReducer.cs
Lines 112 to 146 in 949fef4
The reason we divide by
base^(k+1)
first is that we are trying to speed up the process by using the fact that mod is greater thanbase^(k+1)
by the definition of k. This algorithm itself is reasonable and there is no problem.However, the actual implementation process is as follows:
The
SubtractSelf
function, which is performing the subtraction in line 133, does not allow the subtrahend to be larger than minuend. Therefore, this code may result in an assertion error.This can be avoided by using the
SubtractSelf
function which allows overflow. The reason this works is that the overflow inleftLength=k
acts as operation onZ/(base^k)Z
. Note that the overflow occurs only whenleft≧base^k
, sinceleft≧right
holds.Furthermore, since
v%m<m<base^k
holds, we know that the number to dividev
can bebase^k
, notbase^(k+1)
.