-
-
Notifications
You must be signed in to change notification settings - Fork 487
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
Implement EllipticCurve_with_prime_order() constructor #38341
Conversation
Documentation preview for this PR (built with commit b60e81f; changes) is ready! 🎉 |
From my perspective the work was done during a Sage days and most probably can be improved but I would be interested in tracing that over flow error... I thought we tested it for larger values and it was fine and fast enough. Don't know... |
Now that I read my comment again, I might have wrongfully expressed myself. I wanted to argue why this code that would intuitively fit inside your work is written separately. |
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 don't think I'm familiar enough with Sage or what you're doing to approve this, but hopefully this feedback helps make the review easier for someone who is more familiar.
# of elements of S than using a powerset. Here many multiplications are | ||
# done multiple times. | ||
for e in powerset(S): | ||
D = product(e) |
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 the paper, the algorithm takes a product of the p^*
terms, not the p
terms. Should there be a computation of the p^*
terms somewhere? I also don't see where you do the square root mod N
part of the algorithm.
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 square roots are used to solve the diophantine equation for 4 * N
and BinaryQF
does it for us.
The p*
are defined as p* = (-1)^(p-1 / 2) * p
. Basically, it checks the parity of p-1 / 2
to negate or not p
. This is a shady part for me because this wouldn't work and it kept yielding non-discriminants for the Hilbert polynomial. I found a hack around that seems to work but maybe I'm wrong here.
I disagree, I mean CM method inherently generates multiple curves, so it makes sense to return an iterator. If you want a single curve, just use the (builtin) |
Also I will have to look closer at the paper and the implementation, but why don't use you just use |
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, but someone who better understands the algorithm and paper you're working off of should take a look too.
Agreed, I believe this work is fine but the ways around for the discriminant computation would require second, maybe deeper review. Thanks a lot for the good review so far! |
7ffc6ec
to
2e7c01c
Compare
I have tried to comply 100% with the algorithm from the paper Constructing Elliptic Curves of prime order by Bröker and Stevenhagen i.e.
I can't explain why the actual implementation works while the attempts at strictly following the algorithm don't. |
Hi, I'm ready to review this. As @vincentmacri mentioned, you implemented the algorithm incorrectly. I am able to get an algorithm working using And also as I said above, please return an iterator instead of a single curve, just like many other algorithms do. sage: def curve_has_order(E, N):
....: if any(E.random_point() * N != 0 for _ in range(20)):
....: return False
....: try:
....: E.set_order(N)
....: return True
....: except ValueError:
....: return False
....:
....: def f(N):
....: assert is_prime(N)
....:
....: r = 0
....: while r < 1:
....: primes = []
....: for p in prime_range(max(3, floor(r * log(N))), floor((r + 1) * log(N))):
....: if legendre_symbol(N, p) == 1:
....: p_ = p if p % 4 == 1 else -p
....: primes.append((p_, GF(N)(p_).sqrt()))
....: for subset in powerset(primes):
....: D, sqrtD = prod(u[0] for u in subset), prod(u[1] for u in subset)
....: if D % 8 != 5 or D >= 0:
....: continue
....: Q = BinaryQF([1, 0, -D])
....: sol = Q.solve_integer(4 * N, algorithm="cornacchia")
....: if sol is None:
....: continue
....: x, y = sol
....: assert x**2 - D * y**2 == 4 * N
....: for p in [N + 1 + x, N + 1 - x]:
....: if not is_prime(p):
....: continue
....: h = hilbert_class_polynomial(D)
....: K = GF(p)
....: for j in h.roots(ring=K, multiplicities=False):
....: E = EllipticCurve(K, j=j)
....: for E_ in E.twists():
....: if curve_has_order(E_, N):
....: yield E_
....: r += 1
....:
....: N = 123456789012345678901234567890123456789012345678901234568197
....: it = f(N)
....: [next(it) for _ in range(5)]
[Elliptic Curve defined by y^2 = x^3 + 70969083882016979692051040054897244304487459580456198765042*x + 23963839073844928337866754804687093774080822288916599802630 over Finite Field of size 123456789012345678901234567890654833374525085966737125236501,
Elliptic Curve defined by y^2 = x^3 + 32301896591105181383197983392011966935739308240089212446888*x + 20440933360506659987728474271841544365948756313127780399770 over Finite Field of size 123456789012345678901234567890654833374525085966737125236501,
Elliptic Curve defined by y^2 = x^3 + 49640879391385567455601737809134376710338058924528599296877*x + 57903173466042191227456380331494397476264372226969014648600 over Finite Field of size 123456789012345678901234567890654833374525085966737125236501,
Elliptic Curve defined by y^2 = x^3 + 113468659391060172590889752354736732425162987733160523980142*x + 72243181080242569121408088391312469724962221744363796902853 over Finite Field of size 123456789012345678901234567890654833374525085966737125236501,
Elliptic Curve defined by y^2 = x^3 + 75763109110881858499138647331042804275382035844950124504205*x + 24123673357232490706020024337399069487889990527522975347683 over Finite Field of size 123456789012345678901234567890654833374525085966737125236501]``` |
Thanks for the review! From your comment and code and my notes I was able to propose commit db67898 complying entirely with Algorithm 2.2 from the paper. Few things I'm not convinced about:
|
I don't have a strong opinion either way, but unless there's some kind of canonical curve with the given prime order, what's wrong with an iterator? If a user is fine with an arbitrary one, they could use |
There isn't. As @grhkm21 mentionned: "CM method inherently generates multiple curves" and that makes it more logical to return an iterator on a theoretical point of view. My point is on a programming and software point of view: we are defining a special constructor of Maybe we could satisfy everybody by introducing a parameter Open debate, please take part :) |
We should also discuss how to efficiently check the order of the curve candidates. def curve_has_order(E, N):
if any(E.random_point() * N != 0 for _ in range(20)):
return False
try:
E.set_order(N)
return True
except ValueError:
return False However, grhkm found out while discussing this with me that it was deterministically incorrect in some rare cases (see #38617). So I think it would be nice to discuss which method to adopt for this case (and given how the issue is addressed, maybe implement |
I agree a constructor shouldn't return an iterator. But why does this need to be a constructor? We already have |
Maybe not that specific name since it seems |
This should definitely be viewed as an utility function rather than a constructor. I still don't see any valid argument for returning a curve rather than an iterator. |
Co-authored-by: Vincent Macri <vincent.macri@ucalgary.ca>
…to ecc-prime-order-BS2007
Hey @vincentmacri , I added a few changes to |
This PR fixes #38342 as well after reversing the order of searching |
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 it looks fine otherwise, but I'll take another look tomorrow or Friday before approving.
to be fast for many purposes, and for most `N` we tested we are able to | ||
find a suitable small `D` without increasing the size of `S`. | ||
|
||
<<<<<<< HEAD |
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.
<<<<<<< HEAD |
Guessing this made it in when doing some git operations?
(-D)y^2 = 4N` with `D < 0` and `N` prime, we actually need `|D| \leq 4N`, | ||
so we terminate the algorithm when the primes in the table are larger than | ||
that bound. This makes the iterator return all curves it can find in finite | ||
time :) |
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.
time :) | |
time. |
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.
As much as I wish software documentation had more emojis, I don't think that's how Sage docs are written.
for Et in E.twists(): | ||
# `num_checks=1` is sufficient for prime order. | ||
if Et.has_order(N, num_checks=1): | ||
Et.set_order(N, check=False) |
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.
Do you think it's worth calling set_order
in has_order
, at least when you're in the situation where you know has_order
will return the correct result?
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 can do that, I was worried because there's a bug report open right now, but I think it'll be fine...
|
|
||
- Gareth Ma (2024-01-21): Fix bug for small curves | ||
""" | ||
# This method does *not* use the cached value of `_order` even when |
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.
Why not?
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 that's the point of the method, to check the order "independently" in case the order is wrong (?) I don't know if it makes sense. If you think it makes more sense to use the cached order (when available) I can change 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.
My understanding of the point of this method is it's a faster version of E.order() == N
meant to be used when E.order()
would take too long to compute. So if we know the order then we should use it. The value of _order
shouldn't be allowed to be wrong (unless the user has used proof=False
or something in which case they should know to be aware of wrong answers).
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 change it to use _order
you might need to tweak the test showcasing the bug.
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.
done
@@ -1436,6 +1436,7 @@ def has_order(self, value, num_checks=8): | |||
if not (value * G).is_zero(): | |||
return False | |||
|
|||
self._order = value |
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 to make sure, there is no path to this line that is impacted by the false positive bug? I.e. if this line is reached, the order is 100% equal to value
?
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.
No, it's the other way around I think. It's possible for the code to reach this line with a wrong value.
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 this can be reached in the bugged case. I think you were right before, let's not set it here for now until the issues are sorted out (sorry for changing my mind).
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.
No, it's the other way around I think. It's possible for the code to reach this line with a wrong value.
Let's not set order
here then, but maybe add a TODO
comment to do so in the future when/if the bugs are sorted out.
You can put the |
Not a fan at all to address multiple problems (#38342) in a single PR. |
I don't see problems if they fit in the PR naturally, as it was required to create the example of failing |
It's an incredibly minor change and I've already reviewed it. Might not be the best example of best practices but splitting it up at this point seems like it would be a waste of time. |
# This method does *not* use the cached value of `_order` even when | ||
# available. |
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 method does *not* use the cached value of `_order` even when | |
# available. |
It does use it now.
|
||
- ``num_checks``-- integer (default: `8`); the number of times to check | ||
whether ``value`` times a random point on this curve equals the | ||
identity |
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.
identity | |
identity. If the order of the base field is prime, it is sufficient | |
to pass in ``num_checks = 1`` because of the Hass bound. |
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.
Correct me if I'm wrong about why num_checks = 1
is sufficient here. Feel free to reword.
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.
Hmm, not quite, it's if the order of the curve is prime. I will edit 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.
Sure, the commented out code I asked to remove was checking if value
is prime.
|
||
# This might be slow | ||
# if value.is_prime(): | ||
# num_checks = 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.
# This might be slow | |
# if value.is_prime(): | |
# num_checks = 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 always keep these comments because they're valuable for contributors later.
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 it's redundant with the added explanation but if you still think it's valuable that's fine.
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.
LGTM. I don't have permission to add the positive review label so feel free to do that yourself.
You don't happen to know what the process is to get that permission, do you?
I think I can get someone to add you to the group. Thanks for the review. |
That would be great. Thanks for the contribution! |
Me and @grhkm21 suggest this diff against develop that implements the
EllipticCurve_with_prime_order(N)
constructor. Using the prime orderN
in input, this method finds another primep
and constructs an elliptic curveE/Fp
with#E(Fp) = N
.It follows Algorithm 2.2 of the paper Constructing Elliptic Curves of prime order by Bröker and Stevenhagen. The running time is quite random depending on the input parameter but can turn out to be fast for some larger values (≃ 256 bits primes). It's also worth noticing that some values will make this function run for a very long time.
There had been a PR by @grhkm21 and @GiacomoPope that implements the
EllipticCurve_with_order()
method. This PR would intuitively fit nice into their work but I felt uncomfortable with it returning an iterator. I felt like returning a single curve was more handy so I implemented this method in a separate function that does so but I'm open to suggestions if this is of any interest to the community.Fixes #38342
📝 Checklist
⌛ Dependencies