 OK. So here's the algorithm. We take as input an elliptic curve of the form y squared equals f of x, and in the code, I'll even assume that it's in short virus stress form. We already dealt with l equals 2, remember, so we know l is odd. And our goal is to compute the trace-for-meanus mod l. So step one is to compute the L-th division polynomial. That was our h. And there's standard recursion formulas for doing that that you can find in Silverman's book or any book on elliptic curves. The recursion formulas you need to compute these division polynomials. This is a very straightforward thing. Step two is to compute the Frobenius endomorphism restricted to the L-torsion, which we already saw to do that, but just x to the q mod h. Of course, we don't actually compute x to the q and then reduce mod h. We think of x as living in our univariate polynomial ring mod h, and we do binary exponentiation there, just like we did when we were finding roots of polynomials. So that when we're computing x to the q, we never see a polynomial of degree more than twice the degree of h, because we're always reducing mod h at every step during the exponentiation. Similar, we exponentiate our cubic polynomial f to the q minus 1 over 2 power, again, doing all the computations mod h as we go. And then once we're done, we use our multiplication law for the anamorphism ring, our composition law, to compute pi l squared. So we don't even need to do raise to the q power twice. It's better. We might as well just use the addition law or the multiplication law in our anamorphism ring. The next step is to use scalar multiplication with a binary double and add approach to take the one scalar multiplication we know, multiplication by 1, and turn it into multiplication by q. And then we'll compute one side of the equation we're trying to solve, which is pi l squared plus q sub l. Those are the things we know. And then we're going to iteratively compute 0, pi sub l, 2 times pi sub l, et cetera. Each of those coefficients corresponds to some c. C is going through 0, 1, 2, 3. And at some point, guaranteed to happen before we hit l, we will find a c that works, that's such that c times pi sub l is equal on the nose to pi l squared plus q sub l, meaning the a's and b's in our representation of anamorphisms match on the nose. Now, it could happen that during that step, or even during the scalar multiplication of one step, it could happen that we hit a non-invertible denominator. If that arises, just update, replace h with our better h, and go to step 2 and start over. And if we ever get down to the point where, I mean, eventually, we're going to get down to some irreducible factor of h. So this is that we can't just keep restarting forever. Because every time we have run into a problem, we have a proper divisor of h. And then at the end, we output our trace-verbenius model. So let's go ahead and pop into the code. And yeah, maybe we'll look at the sage version, even though it's a little longer. One of the reasons I've been tending to show the magma code is because you'll see the same function in magma tends to be a lot longer in sage, just somewhat because of the way the spacing works. But I know sages and Python are familiar to more people in the audience than magma. And I really want you to understand and appreciate this algorithm. So I'm going to go for the setting that I think is the most familiar to everyone. OK, so our first step, the first cell in this notebook, which I encourage you to check out yourself when you have a chance, is just implementing all the operations in our endomorphism. We need to know how to add and multiply. And we need to implement a scalar multiplication explicitly doing a double and add. So the addition is the first step. And so what does the addition function take in? It takes a p and a q, which you could think of as, quote, points on our elliptic curve over this weird ring. And so what are p and q? They're each a pair of polynomials, the a and the b, that represent that endomorphism. The y is implicit, so we're not passing in any y's. A is the a in our curve equation. y squared equals xq plus ax plus b. Because if you recall from the formulas for the group law when you're doubling a point, what a is. And then f is the cubic polynomial that defines our equation, y squared equals f of x. Because there are also situations when you're applying the group law where you need to know what f is as well. OK. And in particular, f showed up in our r. So the first thing we do is we just pull out the components of p and q. So a1 comma b1, a, these are the univariate polynomials mod h, a1 and b1 that correspond to p, and a2 and b2 which correspond to q. If a1 and a2 are the same, meaning we're thinking of this, we have endomorphisms that do the same thing to the x-coordinate. Well, then we want to check and see what happens to the y-coordinate. There's only two things that could happen, either they're the same or they're opposite. Because our elliptic curve looks like y squared equals f of x. If I have the same thing on the right-hand side, there's only two possibilities on the left. If they're the same, we should call our doubling routine, which has a slightly different operation, which we'll see in a moment. But if they're different, we get 0. We're adding an endomorphism to its negation, and we get the 0 endomorphism. And we'll represent the 0 endomorphism as just an empty tuple. And so you'll see at the very first line in the addition law, I had an if not p or not q. That's just checking to see if p or q is 0. And if it is, just return the other one. We know how to add 0 to anything. OK. So now we've dealt with all the special cases. Now we're going to get to the actual addition. The first thing we need to do is compute this m that appeared in the group law. This is the slope of the line between p and q on our elliptic curve. Now that a2 minus a1, that's some polynomial mod h. And it's possible that it's not invertible mod h. So that's why that line is inside a try accept clause in Python. This is how you catch an error. So the sage, I should say this is not just Python. This is sage, we'll raise a division by 0 error if a2 minus a1 has a non-trivial GCD with h. And if it does, we're going to capture the thing that gave us a problem, a2 minus a1. And then we're going to raise the error again. And this is going to go all the way back up to the collar. And we'll see how the collar handles that error when we get to it. OK, but from the point of view of the addition operation, we're done. We're like, yeah, we ran into a problem. We know we need to reset and start over with a new h. So we're never going to add these points. Let's just get out of here. And so that's what that raise does. The raise, x, it's the function. Assuming we are able to invert a2 minus a1, then we compute a3 and b3 using exactly the formulas I showed on the slide. And we return the pair a3 comma b3. Notice the computation of a3 and b3 only involve addition and multiplication. We don't have to worry about any errors there. It was only the computation of m that was potentially problematic. This is all happening in the quotient ring. A and B are elements of fq bracket x mod h. I'm not modding out by the curve equation here because we've now sort of made the y implicit and we're going to explicitly deal with any situation where we get a power of y greater than 1. So we're actually the algorithm, the implementation. Mathematically, we're working in this two variable ring modulo, this ideal with two generators. But in the algorithm itself, we're always working in a quotient of a univariate polynomial ring. Let's take a quick look at the double, which is very similar. If we're doubling 0, we know what to do. That's 0. Otherwise, we take the coordinates and we try to compute m again. This is the slope of the tangent line. And again, we might find something that's not invertible. And if it is, we catch the error, we record the polynomial that was uninvertible, and we exit out. We raise the error again and it'll get caught further up the call stack. But if things are in good shape, we apply the formulas to compute a3 and b3 and we just return a3 and b3. Now that we know how to add and to double, we can implement our scalar multiplication, our double and add scalar multiplication. And this function here should look completely familiar to you if you've ever implemented binary exponentiation. It's exactly the same algorithm. I'm doing right to left exponentiation, I guess, working from the most significant bit down. But it's just executing a double and add. And so you can see the scalar multiplication itself is completely generic. All the work is being done in these steps. In the double and in the add. I should scroll it up a bit so it's easier for people to see. And then lastly, we need to know how to multiply an anamorphism ring. And I promised you this would be one line of code and here it is. And this is just implementing the composition of the a1, b1, and a2 in exactly the form that we need. The lifts are just there because we want to do the composition working in our univariate polynomial ring and then we reduce back down to our quotient. So all of the composition and the reduction mod h, all that's being magically handled by our computer algebra system. And this is what I meant when I said that Scopes algorithm is hard to implement if you need to do it with your bare hands. But if you've got a computer algebra system that your back can call, it's quite straightforward. So we've now implemented arithmetic in our anamorphism ring, our L-torsion anamorphism ring. And actually, these functions have other applications, not just in Scopes algorithm. If you're ever working with isogenes or there are a lot of scenarios where you might want to use these exact same sort of things. But now let's move on to the heart of Scopes algorithm, which is computing the trace mod L. OK, so we take as input an elliptic curve and an odd prime L. We're trying to, and we're going to return the trace of our binius mod L. Step one is let's just figure out what ring, what finite field we're working in. That's what ff equals e dot base ring is doing. And this assert here is just making sure that the caller didn't violate the assumptions that we said we were making. The J invariance, not 0, 7, 10, 28. And we want the characteristic to be greater than 3, just so we can fit our curve in the form y squared equals xq plus ax plus b. Those are not necessary. You could make the implementation a little more complicated and handle these cases. Then we're going to pull out q, the cardinality of our finite field. We're going to form the polynomial ring in one variable t over our finite field. We're going to grab the a and b from our elliptic curve equation. And if L is 2, we're going to implement the algorithm we already agreed was very straightforward. We just checked to see if f are x cubed plus ax plus b. And now we're working in this polynomial ring over the finite field. And so I've called the variable t. If t cubed plus at plus b is irreducible, we know the trace of hermeneus is odd. And otherwise, it's even. OK, now we're in the case where we have an odd L. And we're going to compute the Lth division polynomial. And we're going to make it monic. I mean, the definition of the division polynomial, there was going to be an L squared out. And the numerator L doesn't divide the characteristic of our field, though, so we can make it monic by dividing through by the leading coefficient. And Sage has a built-in method for doing that. All right, so now we've done all the setup work. We're now ready to enter the main body. And in order to handle the fact that we may need to restart, I've encapsulated the whole algorithm inside in what looks like an infinite loop, while true. OK, at some point when we succeed, we're going to return our value out of that loop. But we want the while true there to make sure that it will automatically restart when we get into trouble. And we're also going to keep everything inside a tri block because we need to catch the error that was re-raised in either, it might have been caught by the add and double and then re-raised to report the non-invertible polynomial mod H. OK, but let's, for a first pass, let's just assume everything goes as planned, and most of the time it will. So the first thing we do is we compute our quotient ring mod H. That's what this step is doing.