The Road to Blockchain System Exploration: Private and Public Key Generation Based on Elliptic Curves

In the first two sections, we discussed the important concept of abstract algebra: finite field, and then studied the weird "+" operation based on points on the elliptic curve. The two seem to be irrelevant, but in fact they are logically closely related. In short, if we take a point G on the elliptic curve, and then let it do the "+" operation with itself, then the set formed by the result will form a finite field.

First of all, we first give a finite field F(103)={0, 1, ... 102}, and recall that the two operations "+" and "*" that act on the finite field are actually corresponding to the remainder Based on ordinary addition and multiplication, when we judge whether a point in a finite field is on a given elliptic curve y ^ 2 = x ^ 3 + 7: first, substitute the x and y coordinates of the point into the elliptic curve equation , and at the same time judge whether the left and right sides are equal based on the remainder, for example, to judge whether the point (17, 64) (where the values ​​of x and y coordinates are obtained from the finite field F(103)) is on the curve, we do the following calculation :
y ^ 2 = (64 ^ 2) % 103 = 79, x ^ 3 + 7 = (17 ^ 3 + 7 ) % 103 = 79
Since the left and right sides take the same value after calculation, the point (17, 64) is on the curve superior.

We combine the "+" and "*" operations of the finite field with the "+" operation on the point on the elliptic curve we mentioned in the previous section to achieve the encryption effect. Here we should pay attention not to combine the two operations Confusion, because their corresponding symbols look the same, but the actual corresponding operations are different.

First of all, let's implement the judgment logic of the finite field point on the elliptic curve mentioned above with code to see:

"""
将有限域的点输入到椭圆曲线,需要注意的是在椭圆曲线里执行+和*两种运算时,它会自动转换为
有限域定义的__add__ 和 __mul__运算,注意到即使运算操作的逻辑变了,但是判断点是否在椭圆曲线上
依然是判断椭圆曲线对应公式的两边是否相等
"""

a = LimitFieldElement(0, 223)
b = LimitFieldElement(7, 223)
x = LimitFieldElement(192, 223)
y = LimitFieldElement(105, 223)
p1 = EllipticPoint(x, y, a, b)
print(f"Elliptc Point over F_223 is {
      
      p1}")

The result after running the above code is:
Elliptc Point over F_223 is EllipticPoint(x:LimitFieldElement_223(192),y:LimitFieldElement_223(105),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))

It should be noted here that when the statement EllipticPoint(x, y, a, b) is executed, its corresponding __init__ function will be executed, which will perform the following operations on the input parameters:

if y ** 2 != x ** 3 + a * x + b:

Here corresponding operations such as "**", "*", "+", "!=" correspond to _ pow_ , _ mul_ , _ add_ , _ ne_ in the finite field object , these overloaded functions.

Now let’s get a little brain-burning. In the previous section, we deduced how to get the third point after they perform the "+" operation for given two points on the elliptic curve. In the algorithm, a series of ordinary addition, subtraction, multiplication and division operations are performed. Now we convert all these operations into the corresponding operations on the finite field, and the obtained results are still valid. For example, given two points P1(x1,y1), P2(x2, y2), to obtain P3 = P1 + P2, we in the above A section performs the following operations:

s = (y2 - y1)/(x2 - x1)
x3 = s ^ 2 - x1 - x3
y3 = s * (x1 - x3) - y1

The above subtraction should correspond to the __sub__ of the LimitFiniteField class, "_truediv_ " and other logic implementations. Here we can realize that after we replace the ordinary addition, subtraction, and multiplication operations with operations with remainder operations, the original conclusions still hold. This is the powerful role of abstract algebra. In the previous chapters, I forgot to implement the __ sub __ and __ rmul __ methods in the LimitFinitField class. Their implementations are as follows:

 def __sub__(self, other):
        if self.order != other.order:
            raise TypeError("不能对两个不同有限域的元素执行减法操作")
        num = (self.num - other.num) % self.order
        return __class__(num, self.order)
   
def __rmul__(self, scalar):
        #实现与常量相乘
        num = (self.num * scalar) % self.order
        return __class__(num, self.order)

With the above foundation, let's test the "+" operation of the point on the elliptic curve based on the finite field, the code is as follows:

```a = LimitFieldElement(0, 223)
b = LimitFieldElement(7, 223)
x = LimitFieldElement(192, 223)
y = LimitFieldElement(105, 223)
p1 = EllipticPoint(x, y, a, b)
#print(f"Elliptc Point over F_223 is {p1}")

#基于有限域上椭圆曲线点对应的"+"操作
x2 = LimitFieldElement(17, 223)
y2 = LimitFieldElement(56, 223)
p2 = EllipticPoint(x2, y2, a, b)

print(f"Elliptic point P1 + P2 is {
      
      p1 + p2}")

The result after running the above code is as follows:

Elliptic point P1 + P2 is EllipticPoint(x:LimitFieldElement_223(170),y:LimitFieldElement_223(142),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))

Next, we need to realize the multiplication of elliptic curve points and constants. This operation will play an important role in elliptic curve encryption. Later, we will select a point G on the elliptic curve, and then select a constant k to calculate k G, where k corresponds to the private key . , and k G corresponds to the public key. The principle here is that even if you know the point G and give you the result of k*G at the same time, there is no way to reversely calculate k in mathematics, so this becomes the principle of elliptic curve encryption.

Next, we use the code to implement a given point G on the elliptic curve, and then calculate k*G, k = 1, 2, ... the results obtained, and note that constant multiplication is essentially adding G to itself for a given number of times:

#计算点G(47, 71)的常量乘法
x = LimitFieldElement(47, 223)
y = LimitFieldElement(71, 223)
G = EllipticPoint(x, y, a, b)
result = G
for k in range(1, 22):
    if k != 1:
        result = result + G
    print(f"{
      
      k} * (47, 71) is {
      
      result}")

The result after running the above code is as follows:

1 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(47),y:LimitFieldElement_223(71),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
2 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(36),y:LimitFieldElement_223(111),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
3 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(15),y:LimitFieldElement_223(137),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
4 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(194),y:LimitFieldElement_223(51),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
5 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(126),y:LimitFieldElement_223(96),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
6 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(139),y:LimitFieldElement_223(137),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
7 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(92),y:LimitFieldElement_223(47),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
8 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(116),y:LimitFieldElement_223(55),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
9 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(69),y:LimitFieldElement_223(86),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
10 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(154),y:LimitFieldElement_223(150),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
11 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(154),y:LimitFieldElement_223(73),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
12 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(69),y:LimitFieldElement_223(137),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
13 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(116),y:LimitFieldElement_223(168),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
14 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(92),y:LimitFieldElement_223(176),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
15 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(139),y:LimitFieldElement_223(86),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
16 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(126),y:LimitFieldElement_223(127),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
17 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(194),y:LimitFieldElement_223(172),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
18 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(15),y:LimitFieldElement_223(86),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
19 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(36),y:LimitFieldElement_223(112),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
20 * (47, 71) is EllipticPoint(x:LimitFieldElement_223(47),y:LimitFieldElement_223(152),a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))
21 * (47, 71) is EllipticPoint(x:None,y:None,a:LimitFieldElement_223(0), b:LimitFieldElement_223(7))

The above operation result has a characteristic, that is, when k increases to 21, the result obtained by k*G is the 0 point of the elliptic curve, and any point on the elliptic curve is selected to perform the above operation to obtain such a result. When k=1, it is always When k=n makes k * G zero, then the set formed by the set {0, G, 2*G, … ,(n-1)*G} is called “group” in mathematics.

Compared with the finite field we mentioned earlier, the elements in the group only correspond to one operation "+" (there are two kinds of finite fields), let's look at some properties of the group: 1, unit 0, that is, the group
must Contains a special element "0", any element performs "+" operation with him, and the result is the element itself, that is, if A is any element in the group, then A "+" 0 = A, 2,
closure , if A and B are two elements in the group, then the result of A "+" B is still an element in the group.
3. Reversibility, if A is an element in the group, then there is another element B in the group, such that A “+” B = 0 4,
Commutativity, if A, B are two elements in the group, then there is A "+" B = B "+" A
5, associativity, (A "+" B) "+" C = A "+" (B "+" C)

"Group" is a crucial concept in abstract algebra, and it is also a pillar concept of cryptography. When we implemented k * G before, the code is to perform k times "+" operation on point G. Now we add constant multiplication to the points of the elliptic curve, and add the code in EllipitcPoint to realize as follows:

 def __rmul__(self, scalar):
        #如果常量是0,那么就返回椭圆曲线"0"点
        result = self.__class__(None, None, self.a, self.b)
        #自加给定次数
        for _ in range(scalar):
            result += self

        return result

We use the following code to test the above implementation, and we can find that the output is the same as before, so we can confirm the correctness of the implementation:

#测试常量乘法:
for k in range(1, 22):
    print(f"{
      
      k} * (47, 71) is {
      
      k * G}")

Next, let's look at the realization of Bitcoin's corresponding elliptic curve. For Bitcoin, its corresponding elliptic curve is to set a = 0, b = 7, so its corresponding curve formula is y ^ 2 = x ^ 3 + 7, For Bitcoin, the number of elements in the finite field it defines is p = 2256 – 232 – 977, and the x component of point G is G(x) = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 G(y) = 0x483ada7726 a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8, when k =
0xfffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
, k * G = 0.

For the elliptic curve used by Bitcoin, it has several characteristics. First, the parameters a and b are very simple. Secondly, it corresponds to the number of elements in the finite field, which is close to 2^256, so the size of the two components of the elliptic curve G point They are all close to 256bit, that is, 32 bytes. This value is almost close to the total number of atoms in the universe.

Let's use the code to see if point G is on the Bitcoin curve:

#测试G点是否在曲线上
gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
p = 2 ** 256 - 2 ** 32 - 977
print(gy ** 2 % p == (gx ** 3 + 7) % p) #True

The above code returns True after running. Next, we try to convert the two components of point G into points on the finite field, and at the same time test whether the result of k * G is 0 point on the elliptic curve. The corresponding code is as follows:

k = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
x = LimitFieldElement(gx, p)
y = LimitFieldElement(gy, p)
a = LimitFieldElement(0, p)
b = LimitFieldElement(7, p)
G = EllipticPoint(x, y, a, b)
print(f'result of n * G is {
      
      n * G}')

If you directly execute the above code, you will find that the program will be stuck, because the value of k is too large. When we implement the constant multiplication of the EllipticPoint class (__ rmul __), we use the loop k additions, but due to the value of k is too large, so the loop cannot complete in a short time.

One solution is to use binary expansion to optimize. Let's look at a specific example. Assume that the constant value k has a value of 36, which is 100100 when converted to binary, so k * G = (2 ^5 + 2 ^ 2) * G = 2 ^ 5 * G + 2 ^ 2 * G, here we can see that we converted the original 36 additions into 2 multiplications and 1 addition, and the amount of calculation required does not exceed lg(k). Note that in the calculation (0b100100) * When G, we traverse from the rightmost bit, if the bit is 0, then we only need to calculate 2 ^ k (k represents the position of the currently traversed bit in binary), if the traversed bit is 1, then an addition is performed, so we optimize the constant multiplication as follows:

    def __rmul__(self, scalar):
        #二进制扩展
        coef = scalar
        current = self
        result = self.__class__(None, None, self.a, self.b)
        while coef:
            if coef & 1: #如果当前比特位是1,那么执行加法
                result += current
            current += current  #如果上次比特位的位置在k,那么下次比特位的位置变成k+1,2^(k+1)*G 等价于2^k*G + 2^k * G
            coef >>= 1

        return result

Execute the previous code after the above optimization, the result is as follows:

result of k * G is EllipticPoint(x:None,y:None,a:LimitFieldElement_115792089237316195423570985008687907853269984665640564039457584007908834671663(0), b:LimitFieldElement_115792089237316195423570985008687907853269984665640564039457584007908834671663(7))

It can be seen that the result of k * G is indeed a zero point on the elliptic curve. Since the elliptic curve corresponding to Bitcoin is called secp256k1, the a and b parameters of the curve have been determined, and the number of finite field elements has also been determined, so we make corresponding subclasses on the basis of LimitFinitField and EllipticPoint, and write the corresponding parameters to death ,code show as below:


P = 2**256 - 2**32 - 977


class S256Field(LimitFieldElement):
    def __init__(self, num, order=None):
        # 参数写死即可
        super().__init__(num, P)

    def __repr__(self):
        return '{:x}'.format(self.num).zfill(64)


N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141


class S256Point(EllipticPoint):
    def __init__(self, x, y, a=None, b=None):
        a, b = S256Field(0), S256Field(7)
        if type(x) == int:
            super().__init__(S256Field(x), S256Field(y), a, b)
        else:
            # 如果x,y 是None,那么直接初始化椭圆曲线的0点
            super().__init__(x, y, a, b)

    def __repr__(self):
        if self.x is None:
            return 'S256Point(infinity)'

        return f'S256Point({
      
      self.x}, {
      
      self.y})'

    def __rmul__(self, k):
        k = k % N
        return super().__rmul__(k)


G = S256Point(0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
              0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8)


print(N * G)

The output after running the above code is:

S256Point(infinity)

With the above foundation, we can generate public key and private key through elliptic curve. The private key is very simple. We only need to take a value e in the range of [1, N], and then the public key is P = e * G, with the public key, we can construct the address of the bitcoin wallet.

For more content, please search for coding Disney at Station B. The code download address for this section is:

Link: https://pan.baidu.com/s/1SIVPmmVXYnA0pfKh4cfuEQ Extraction code: b1fe

Guess you like

Origin blog.csdn.net/tyler_download/article/details/130444233