 quotient. It might have zero divisors. It doesn't matter. This algorithm is going to work regardless. And I'll use x-bar just to be very careful here. I'll use x-bar to denote the image of x in the quotient morphism that sends f cubed back at x to f cubed back at x mod h. And then I want to exponentiate inside the quotient ring. And I'm going to use binary exponentiation to do that. What that means is you start with x-bar and then you square it and you check. And then you reduce that square mod h. If h has degree 3, nothing will happen. Then you square it again. You're going to get something, say, of degree 4. And if h has degree 3, when you reduce mod h, you're going to get a polynomial of lower degree. And you're going to keep going. Of course, you're not always going to be squaring. Sometimes you're going to be squaring and multiplying. We're doing a binary exponentiation. But you're never going to have to work with polynomials of degree larger than twice the degree of h. Every time you do a square or a multiply, you double the degree and then you reduce mod h. And now it's back below the degree of h. And because we're doing a binary exponentiation, we're only going to do log q of these operations. And for us, h has degree o of 1. It's a cubic. It has degree 3 at most. So effectively, the cost of doing this is no worse than up to constant factors than just doing an exponentiation in f cubed. And once you've done that, we should take our polynomial, the image of x to the q minus x in our quotient ring that we've computed by just subtracting x bar from x bar to the q, and then lift it back up to fq to the x, where we're now going to get a polynomial of degree less than h that is congruent to x to the q minus x mod h. And from facts we know about the GCDs, we can plug that polynomial directly to the GCD where we had x to the q minus x. And we're going to get exactly the same answer. And so this root-finding algorithm, just as a subroutine, has quite good complexity. I realize there's a type of there. The 2 should be on the log. It's or a parentheses around the log. It's quantity log q squared times log log q. Time and o of log q space, assuming h has degree o of 1. Things get more interesting when you start thinking about polynomials of larger degree, and there's a lot of interesting literature on that and what the optimal algorithm is. But for our case, this simple algorithm is the optimal algorithm, and it has precisely the complexity stated here. OK. Let's go ahead and take a look at this, how this is implemented. And I think this is maybe the first example where you can really appreciate the power of working with a computer algebra system rather than just a low-level programming language. Because you can imagine that it might take some work for you to write that algorithm, to do all the GCD computations, to actually compute in this quotient ring. If you were, say, writing low-level C code, or even if you were writing Python code from scratch without Sage to help you, you would have to write a fair amount of code. Whereas in all of these systems, this is going to be essentially a one-line algorithm. The top first two lines are just set up. So what are the first two lines? First I'm finding out what's the parent of h. That's the polynomial ring that h lives in. I'm getting the cardinality of its base ring, which is fq, because I just need to know what the q is, because we're trying to compute x to the q minus x. And then this second line, s, angle bracket z, is creating the quotient of r, which is fq bracket x, modulo the polynomial h. And it's calling that s. And z, the angle bracket z says z represents the image of whatever the generator for the parent of h is, what we called x. z is the image of x in this quotient ring, s. I decided to call it z rather than x lot, just because, or x barb, just because z is easier to type. And then we just do exactly what I said. We compute x z to the q minus c, but that computation is automatically taking place in the quotient ring, because we told magma, z is the way we're labeling or identifying the generator of our quotient ring. And we're then going to lift. To lift, we just are, that r bang is saying, okay, I know I told you this is an quotient ring, but now I want you to think of it as an element of the ring r, which we took a quotient of. And magma does have an intrinsic called lift, and it unfortunately doesn't do what it should in this case. So it would be nice if you could just type lift, and I think in stage you can, but in magma you need to do an r bang. And then so we lift and we take the GCD and we return the degree of it. And so let's just check that this works. And so the code here was just computing roots of a bunch of randomly generated polynomials of degree at most three. And I was checking the answer against magma's computation. So magma has a function roots h, which doesn't just count the roots, it actually imputes the roots. Notice that we're not computing the roots. We don't know what the roots are. And nobody knows a polynomial time deterministic algorithm that can actually do that. But our algorithm is deterministic polynomial time. There's no randomness in our algorithm, we're just we're just taking a GCD. The randomness comes in, what effect, what effect GCD has degree three say? So h has three rational roots. But we don't know what they are. And we don't know of any efficient way to find them without using randomness or assuming GRH. All right, let's come back here. And so now that we've got our root counting algorithm, we can use it to implement a more efficient version, a less naive version of our pointing counting for plane projective curves. And the algorithm is essentially the same. The only thing that's changed, let me scroll this up a bit, is that now rather than iterating over x not and y not, we're just going to check for each y not, we're going to check, we're going to plug in y not in one into G and it's still going to be, it's going to be a univariate polynomial in x. And we want to add, we want to, sorry, that should be an r. We want to count the numbers of r, the number of r and fq such that G of r y to zero one is equal to zero. Then we're going to count the number of roots of the polynomial we get when we plug in one and zero and just leave this variable free. And then finally we'll add the last zero or one that we need to. And I'm realizing it'll be, I think it'll be easier to understand this algorithm if I actually show you the code. So let me go ahead and do that. Sorry, I don't, the link is not on. This is the one I want. Okay. All right. So all I've done here is I copied the count roots function. This is just a cut and paste of the other one. You know, in general, you probably want to write this out to a file and load it instead rather than copying it, but it's so short I decided to copy it. And here's the less naive point counting algorithm. And you can see it starts out the same. It's checking, figuring out what the base ring is that we're working with. It's going to get and construct the polynomial ring over fq in one variable, which I'm going to call t. And then for each a and fq, it's going to evaluate our polynomial f and three variables at t, a and one. So t is an indeterminate a and one are constants. And so this is going to produce a univariate polynomial of degree at most three. And then we're going to count its roots using our count roots function. And we're going to sum that over all the a's and fq. So notice I managed again to use a list comprehension rather than writing a loop. And then we'll do it again to just count the roots of the single polynomial we get when we plug one in for y and zero in for z. And then finally, we add the final check for the potentially one or zero rational points when we plug in one zero zero. We can again check that this works. And we can compare its performance to our naive approach. Our less naive approach is a lot faster. We could even go up to say 100,000. It takes about a second. Again, you're never going to use this algorithm when q is that big. You'd use other better algorithms that we're going to get to in a moment. I just want to show you something for comparison. Yeah, question. Why are you using over y not? If you're making an assumption that I don't want to make, though, you're checking, assuming you're thinking in your head, oh, my polynomial is really y squared equals f of x. But what if you're in characteristic two? So my algorithm will still work. Another reason is notice the space complexity. So the space complexity of the algorithm of our less naive point algorithm, or the time complexity, again, there's parentheses missing. This is of log q quantity squared log. And the space is all log q. It's only storing a constant number of elements of fq when it does this computation. Whereas there's two different approaches to what you're, I guess, yeah, there's two ways to do what you're suggesting. The most efficient way would be to build a table of squares and just do a lookup. Alternatively, you could do a Legendre computation to check whether it's a square. But that's not going to be asymptotically any faster than what we're doing. Yeah, sorry. Maybe I misinterpreted what you were suggesting the first time. Any other questions? Yeah, this general trick of any time you have a polynomial in many variables that you can separate where you get all the variables on one side of equality and all the variables on the other side of equality sets you up to do some optimizations, like the one that Sam was suggesting, that aren't always possible if you have a polynomial that you can't separate. In fact, you can do, as you'll see, we'll see there's also many opportunities to take advantage of birthday paradox algorithms that will even give you a square root speedup. And that's what we're going to look at in the next algorithm we consider. Other questions? Okay. All right. And oh, I should just note in the remark that the algorithm for counting roots, if we actually wanted to know what the rational roots are, there's an algorithm due to Raybin, the probabilistic algorithm due to Raybin, which is implemented in all of these computer algebra systems and is very fast. And there's a generalization of that which is due to Cantor and Zassenhaus that will allow you to factor polynomials over finite fields using a probabilistic algorithm of Las Vegas type, meaning it always gives you the right answer. The probability is only impacting potentially its running time, but its expected running time is still going to be quite good. Okay. And if the degree of H is O of one, the expected running time is going to be exactly O of log Q squared and I made a whole hour long YouTube video on this topic a few years ago and during the pandemic. So if you want to learn more about that, I invite you to watch the video, but don't do it now. Watch the live version. Okay. All right. So now we come to a not so naive algorithm. Due to Mestra, although I think this algorithm was to a large extent popularized by Rene Schoff, who wrote about it in his article on a deterministic polynomial time algorithm, which is now called Schoff's algorithm, which we will be talking about later in the week. But he also gives a very nice presentation of Mestra's algorithm in his paper on counting points on elliptic curves over finite fields. But I'll just quickly summarize the algorithm. We're going to use a minor variant of it that is not necessarily faster, but it's simpler. Okay. So let's just talk through the algorithm first. So up to now, the algorithms we gave work for any plane projective curve, we're not even using the fact that we have a group law on our elliptic curve. So now we want to take advantage of that. And we also want to take advantage of the fact that we know the order of our of the number of fq rational points on E isn't just some random integer. It's some integer that lies in the Haas interval between q plus one minus two square root of q and q plus one plus two square root of q. And this is because we know the trace of Frobenius has absolute value at most two times the square root of q. And so the strategy, Mestra strategy, is to determine the cardinality of this finite abelian group by finding a divisor of the exponent of the group. The exponent of a finite abelian group is just the least common multiple of all the orders of all of its elements. And that is an integer that always divides the order of the group. And it's an integer you can compute or at least would be fairly confident you've computed just by sampling random group elements and taking the least common multiple of their orders. It's a fun exercise to show that I think I maybe even I stated this down here. In fact, even just two group elements for any finite abelian group not necessarily coming from an elliptic curve. So it could have a rank much larger than two. If you just sample two random elements and take the LCM of their orders, you're likely to get the exponent on the nose with probability greater than one half. In fact, you can show the probability is greater than 6 over pi squared. And you probably have some guess as to where the 6 over pi squared comes from. And that's why this is a fun exercise. But we're not going to do it right now. But so the strategy is to find a divisor of the exponent. It doesn't have to, we don't have to hit the exponent on the nose as long as we find a divisor of the exponent, which is also going to be a divisor of the number of points on the elliptic curve that has a unique multiple in the Haas interval. Because we know that the cardinality, the number of fq rational points on our elliptic curve is a multiple of the exponent. And it lies in the Haas interval. And if we show there's a unique integer with that property, we're done. It must be exactly the same as the number of fq rational points. So that's that's the idea. Unfortunately, it doesn't work. It works on a lot of cases, but sometimes it just doesn't work because it can happen that the exponent is smaller than the width of the Haas interval. And it could have multiple, there could be more than one multiple in the Haas interval. And if that happens, no matter how many random points you sample, you're never going to finish. You're never going to be able to say you found a unique answer. However, I'll just mention it as a side. The exponent can't be too small because we know that the structure of the finite abelian group coming from an elliptic curve or a finite field is generated by two elements. So it's a product of two cyclic groups. And that means the exponent can't really be smaller, much smaller. It can't be any smaller than the square root of q plus 1 minus 2 square root of q or roughly square root of q, but there's still room for it to be smaller than 4 square root of q. But master observed that in this situation, subject to some constraints on q we'll talk about in a minute, what you should do is you should instead of trying to count points on e, you should instead count points on a quadratic twist of e. So it's a quadratic twist. It's just in another elliptic curve over fq that is isomorphic to e over a quadratic extension. And in general, whenever people refer to a quadratic twist of an elliptic curve over a finite field, they mean any quadratic twist in the isomorphism class of us quadratic twists that's not isomorphic to the curve that you have. So in general, if you give somebody gives you an elliptic curve over fq, up to fq isomorphism, there is a unique non-isomorphic quadratic twist and that's the one you're interested in. And the reason you're interested in is because we know that when we switch to the non-isomorphic quadratic twist, all we're doing is negating the trace of Frobenius. And so if we know, so the quadratic twist, the number points on the quadratic twist is going to be q plus one plus a sub q, where a sub q is the trace of Frobenius of the elliptic curve we started with. Because the elliptic curve we started with had the number points was q plus one minus a sub q. And once we know, if we know either of these two integers, we can compute the other because we know they sum to two q plus two. So that's just a very useful fact. And it turns out with a bit of work, you can show that this is guaranteed to work for suitable q. And suitable q includes all primes greater than 229, which is great. I mean, I think that in sort of the early days when people were really thinking hard about point counting, they were were really thinking hard about wanting to know the number of F, of Fp rational points on elliptic curve or Fp that they were going to use for discrete log based cryptography. And in that situation, just knowing you have something that works for all primes greater than 229 is good enough. Unfortunately, there are counter examples or problem cases that come up when q is the square of a prime and these arise from which you might guess, they arise from super singular curves where negating the trace of Frobenius doesn't help you really because it might be zero and you just negated and got zero again. But it turns out there is one can sharpen this result. And so there's another result due to on Cromona myself that sort of extends the theorem of Messer and Scope that gives you a slightly different statement but one that is sufficient for the algorithm to work and is guaranteed to work for every q bigger than 49. And so what is the precise statement is that there is a unique integer t with absolute value at most 2 root q such that the exponent of E divides q plus 1 minus t and the exponent of the quadratic twist divides q minus plus 1 plus t. And that's really all the algorithm is depending on. It's not depending on one of these having a unique multiple, it's enough to know that there's just a unique t for which the both of these statements hold and that's an easier condition to satisfy. And so our strategy is to use to use Messer's algorithm and if q is smaller than 49 we'll just call our less naive point counting algorithm and it can easily handle any q up to 49 without breaking a sweat. Okay. How are we doing? All right. So before I get into describing Messer's algorithm I want to describe the subroutine that it's going to rely on because we're going to do two different implementations of the subroutine and what the subroutine is going to do is in the sort of the standard Messer's algorithm what the subroutine would do is given a random point it would compute the order of that point. What's the least integer such that when I multiply m such when I multiply p by m I get the zero point on the elliptic curve. But we're going to do something that is both easier and sufficient which is we're either going to do what you wanted us to do which is what the caller really wanted us to do which was to compute the order of the point p that we're given or we're going to report that there is a unique multiple of the order of the point p in the Haas interval and you should be happy with that answer because you now know how many points your elliptic curve has and stop asking us to compute orders of points. So how does this work? Well so we're not going to do the we're going to assume that this algorithm is actually given a point so there's no randomness in this algorithm this is a deterministic algorithm but the caller will generate random points. Okay and so what we're going to do is we're simply going to march through the Haas interval. We know there is a multiple of the order of that point in the Haas interval because we know that the order number of fq rational points the order of the group it lives in is an integer in that interval. And so we're going to start with by computing the first multiple of p that is by the least integer in the Haas interval that's q plus 1 minus 2 square root of p. And while that point is not zero and here zero means the zero point on our elliptic curve we're going to just keep adding p to it and we're going to keep track of what multiple we have. We're going to add p to q and add 1 to m. And then when we find a multiple eventually this is guaranteed to stop that's why there's no upper bound it is guaranteed mathematically this will happen we will hit a multiple that kills it and it will happen before we get to the end of the Haas interval. When it happens you might think oh we should be happy and celebrate we're done but no we don't know the order of the point yet and we don't know that we have a it has a unique multiple of its order in the Haas interval so we're just going to record the first answer we got which is m we'll call it m sub zero and then we're going to keep going keep marching along the Haas interval until one of two things happens either we hit the end of the Haas interval and then we know that m zero was the unique multiple of the order or we hit another multiple of the order call it m one and we know that the order of p is exactly the difference because we checked every integer in between so the advantage of this algorithm is we don't if somebody just tells you an element of a group and here's a multiple of its order to get the exact order you'd have to factor that multiple and try to pull out figure out what's the least divisor of that multiple that exactly kills the point p but with this approach we completely avoid having to do any factorization or even thinking about it now it turns out the factorization isn't going to change the complexity of the algorithm so there's nothing wrong with doing the factorization but the advantage of this algorithm is we don't have to think about it and it's actually is easier to code this way and so how long does this algorithm take well the house the width of the house interval is four square root of q and at each what are we doing at each step we're doing a group operation on the elliptic curve and that works out to be some a finite number of field operations over fq now one of those field operations is actually an inversion if you work in affine coordinates but for this algorithm you could work in projective coordinates because all we're ever doing is checking whether a point is zero or not and that's something you can check in projective coordinates you don't need a unique representative and so in projective coordinates you're just using ring operations multiplications and additions and in if we did want to use affine coordinates which we will in the next algorithm what we should do instead is batch the inversions so the reason we want to avoid inversions is because it'll induce an extra log an extra log log factor into the complexity bound that we'd rather not have in there and but if you do a bunch of inversions and there's a way to do it a bunch of inversions in parallel that I don't want to spend the time to explain now but I'll put it in notes that allows you to effectively perform an inversion for an amortized cost of three multiplications you can perform M inversions for a cost of 3M multiplications and 1 inversion and if you just make M big enough it'll over the 3M will overwhelm the 1 inversion alright so let's take a look at this algorithm and again I hope you're appreciating the fact that in some sense at least if you if you can read the code the code is actually easier to understand I think than the mathematical description on the previous slide or at least my goal is to get you to the point where you feel that way even if you don't feel that way yet but that is really the beauty of working in these sort of high level computer algebra systems is that you can express mathematics in many cases at least at this level of sophistication in almost a one to one way okay so the first thing we need to do as in all of these algorithms I don't want to pass in a whole bunch of parameters so we're just passing in the point P but the point P knows where it lives it knows what its coordinates are and what universe those coordinates live in they live in FQ and so this is just a way of finding out the cardinality of the finite field over which the