Issue with S256Point.__rmul__
Hi Jimmy,
In Chapter 4 you have introduced a subclass S256Point of the Point class (that uses the S256Field as the underlying field). You have also overridden the __rmul__ method as follows:
def __rmul__(self, coefficient):
coef = coefficient % N
return super().__rmul__(coef)
where N is the order of the group generated by G (as defined in secp256k1).
The problem with the optimization: coef = coefficient % N is that it assumes that the S256Point is of the form k * G for some k. However, it is (in theory) possible to initialize the S256Point with a point that is not of the form k * G, in which case the corresponding order of such generator might be different from N.
In the book, we are only using points of the form k * G, so this is not an issue. However, it would be great to add a comment that this is implicitly assumed.
Cheers,
Peter
I was looking for another issue that might relate to these (edit: ~ch04~) ch03 classes and saw this. I think I understand your comment, but also don't think it's an either or, per se. As in:
- a
Psuch thatP != k*GyetP.y**2 % p = (P.x**3 + 7) % pis still a validS256Point - however, ECC (at least with respect to bitcoin) always starts at
Gand makeskhops to a new point,P = k*G
So, I can see it both ways. Given this is for specifically bitcoin, one could enforce the latter point more explicitly, but I also felt this was made clear by the following (p. 58):
An elliptic curve for public key cryptography is defined with the following parameters:
- ...
- we specify the x and y coordinates of the generator point, G
- we specify the order of the group generated by G, n
A field is defined by it's order, so if stating that n is the order is not sufficient, I also thought this reinforced the idea (p. 61):
At last, we have all the tools we need to do public key cryptography operations. They key operation that we need is P = eG, which is an asymmetric operation.
As well as how the classes are defined:
A = 0
B = 7
P = 2**256 - 2**32 - 977
N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
class S256Field(FieldElement):
def __init__(self, num, prime=None):
super().__init__(num=num, prime=P)
class S256Point(Point):
def __init__(self, x, y, a=None, b=None):
a, b = S256Field(A), S256Field(B)
if type(x) == int:
super().__init__(x=S256Field(x), y=S256Field(y), a=a, b=b)
else:
super().__init__(x=x, y=y, a=a, b=b) # <1>
G = S256Point(
0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8)
I took this as A, B, P, and G all being constrained, with N thus being an additional deterministic parameter given those seed values. So, points where all else holds except P != kG are still valid points on secp256k1, it's just that one isn't "programming bitcoin" anymore.
As an aside, what I came to post about was why one would define constants outside of the class. I felt that A, B, P, and N should be defined within the class vs. as global parameters... My intuition was similar to yours in that we know these classes are "for" bitcoin, specifically, so why leave these values outside of the class?
Consider me taking the devil's advocate approach above with your post :) I'm tacking on my question because I think the root intuition is the same: should one allow operating outside of the intended way, or force it since we know what we're trying to do?
Edit: further reading in ch03 confirms my leaning (p 63)
The inscribing of the target depends on the signature algorithm, and in our case that algorithm is called the Elliptic Curve Digital Signature Algorithm, or ECDSA for short.
The secret in our case is e satisfying the following: eG = P
So, again, I think finding other P's that don't satisfy this is perfectly legit with respect to elliptic curve math and these interesting sets, but it just isn't how bitcoin is implemented. The consistent return to this as how the magic is done seems sufficient to clarify, at least to my reading.