├── README.md ├── bn128.py ├── bn128_ethereum_api.py ├── ckb_address.py ├── finite_field.py ├── finite_field_primitive_nth_root.py ├── kzg.py ├── kzg_arith_relation.py ├── main.sh ├── polynomial.py ├── polynomial_numpy.py ├── secp256k1.py ├── secp256k1_extract_private_key.py ├── secp256k1_generate_public_key.py ├── secp256k1_jacobian.py ├── secp256k1_schnorr.py ├── secp256k1_sign.py ├── secp256r1.py ├── secp256r1_recovery_pubkey.py └── segwit_addr.py /README.md: -------------------------------------------------------------------------------- 1 | ## cryptography-python 2 | 3 | This is a tutorial project about cryptography in python. Follow the steps and you will fully understand the magic of elliptic curves from scratch. 4 | 5 | - Each file can be run independently! 6 | - There are detailed paper references in the comments of the code 7 | 8 | Based on this project, I implemented the SDKs of three famous blockchain projects: BTC, ETH and CKB. 9 | 10 | - : Early stage of the project. 11 | - : Almost fully supported. 12 | - : Almost fully supported. 13 | 14 | ## secp256k1 15 | 16 | paper: 17 | 18 | - `secp256k1.py`: implement finite field and secp256k1 curve. 19 | - `secp256k1_generate_public_key.py`: calculate bitcoin public key from private key. 20 | - `secp256k1_sign.py`: signature messages and verify it. 21 | - `secp256k1_extract_private_key.py`: extract the private key by two signatures that use the same k. 22 | - `secp256k1_jacobian`: jacobian projective space. 23 | 24 | ## polynomial arith 25 | 26 | - `polynomial_numpy.py`: polynomial operation by numpy. 27 | - `polynomial.py`: polynomial operation by hand writting python. 28 | 29 | ## bn128 30 | 31 | - `bn128.py`: bn128 curve, implements [eip-196](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-196.md), [eip-197](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-197.md). 32 | - `bn128_ethereum_api.py`: bn128 pairing testsuite. 33 | -------------------------------------------------------------------------------- /bn128.py: -------------------------------------------------------------------------------- 1 | import polynomial 2 | 3 | 4 | class Fp: 5 | # Galois field. In mathematics, a finite field or Galois field is a field that contains a finite number of elements. 6 | # As with any field, a finite field is a set on which the operations of multiplication, addition, subtraction and 7 | # division are defined and satisfy certain basic rules. 8 | # 9 | # https://www.cs.miami.edu/home/burt/learning/Csc609.142/ecdsa-cert.pdf 10 | # Don Johnson, Alfred Menezes and Scott Vanstone, The Elliptic Curve Digital Signature Algorithm (ECDSA) 11 | # 3.1 The Finite Field Fp 12 | 13 | p = 0 14 | 15 | def __init__(self, x): 16 | self.x = x % self.p 17 | 18 | def __repr__(self): 19 | return f'Fp(0x{self.x:064x})' 20 | 21 | def __eq__(self, data): 22 | assert self.p == data.p 23 | return self.x == data.x 24 | 25 | def __add__(self, data): 26 | assert self.p == data.p 27 | return self.__class__((self.x + data.x) % self.p) 28 | 29 | def __sub__(self, data): 30 | assert self.p == data.p 31 | return self.__class__((self.x - data.x) % self.p) 32 | 33 | def __mul__(self, data): 34 | assert self.p == data.p 35 | return self.__class__((self.x * data.x) % self.p) 36 | 37 | def __truediv__(self, data): 38 | return self * data ** -1 39 | 40 | def __pow__(self, data): 41 | return self.__class__(pow(self.x, data, self.p)) 42 | 43 | def __pos__(self): 44 | return self 45 | 46 | def __neg__(self): 47 | return self.__class__(self.p - self.x) 48 | 49 | @classmethod 50 | def nil(cls): 51 | return cls(0) 52 | 53 | @classmethod 54 | def one(cls): 55 | return cls(1) 56 | 57 | 58 | if __name__ == '__main__': 59 | Fp.p = 23 60 | assert Fp(12) + Fp(20) == Fp(9) 61 | assert Fp(8) * Fp(9) == Fp(3) 62 | assert Fp(8) ** -1 == Fp(3) 63 | Fp.p = 0 64 | 65 | # Prime of finite field. 66 | P = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 67 | # The order n of G. 68 | N = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 69 | assert pow(2, N, N) == 2 70 | assert (P ** 12 - 1) % N == 0 71 | 72 | 73 | class Fq(Fp): 74 | 75 | p = P 76 | 77 | def __repr__(self): 78 | return f'Fq(0x{self.x:064x})' 79 | 80 | 81 | class Fr(Fp): 82 | 83 | p = N 84 | 85 | def __repr__(self): 86 | return f'Fr(0x{self.x:064x})' 87 | 88 | 89 | if __name__ == '__main__': 90 | Fp.p = 13 91 | assert polynomial.lagrange([Fp(1), Fp(4)], [Fp(6), Fp(2)]) == [Fp(3), Fp(3)] 92 | Fp.p = 0 93 | 94 | 95 | class Pa: 96 | a = None 97 | b = None 98 | i = None 99 | 100 | def __init__(self, x, y): 101 | if x != self.i[0] or y != self.i[1]: 102 | assert y ** 2 == x ** 3 + self.a * x + self.b 103 | self.x = x 104 | self.y = y 105 | 106 | def __repr__(self): 107 | return f'Pa({self.x}, {self.y})' 108 | 109 | def __eq__(self, data): 110 | return self.x == data.x and self.y == data.y 111 | 112 | def __add__(self, data): 113 | # https://www.cs.miami.edu/home/burt/learning/Csc609.142/ecdsa-cert.pdf 114 | # Don Johnson, Alfred Menezes and Scott Vanstone, The Elliptic Curve Digital Signature Algorithm (ECDSA) 115 | # 4.1 Elliptic Curves Over Fp 116 | if self.x == self.i[0] and self.y == self.i[1]: 117 | return data 118 | if data.x == self.i[0] and data.y == self.i[1]: 119 | return self 120 | if self.x == data.x and self.y == -data.y: 121 | return self.__class__(self.i[0], self.i[1]) 122 | x1, x2 = self.x, data.x 123 | y1, y2 = self.y, data.y 124 | if self.y == data.y: 125 | s = (x1 * x1 + x1 * x1 + x1 * x1 + self.a) / (y1 + y1) 126 | else: 127 | s = (y2 - y1) / (x2 - x1) 128 | x3 = s * s - x1 - x2 129 | y3 = s * (x1 - x3) - y1 130 | return self.__class__(x3, y3) 131 | 132 | def __sub__(self, data): 133 | return self + data.__neg__() 134 | 135 | def __mul__(self, k): 136 | # Point multiplication: Double-and-add 137 | # https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication 138 | n = k.x 139 | result = self.__class__(self.i[0], self.i[1]) 140 | addend = self 141 | while n: 142 | b = n & 1 143 | if b == 1: 144 | result += addend 145 | addend = addend + addend 146 | n = n >> 1 147 | return result 148 | 149 | def __pos__(self): 150 | return self 151 | 152 | def __neg__(self): 153 | return self.__class__(self.x, -self.y) 154 | 155 | 156 | class P1(Pa): 157 | a = Fq(0) 158 | b = Fq(3) 159 | i = [ 160 | Fq(0), 161 | Fq(0) 162 | ] 163 | 164 | 165 | G1 = P1(Fq(1), Fq(2)) 166 | I1 = P1(Fq(0), Fq(0)) 167 | 168 | if __name__ == '__main__': 169 | assert G1 * Fr(2) + G1 + G1 == G1 * Fr(4) 170 | assert G1 + G1 != G1 171 | assert G1 * Fr(9) + G1 * Fr(5) == G1 * Fr(12) + G1 * Fr(2) 172 | assert G1 * Fr(N-1) + G1 == I1 173 | 174 | 175 | class Fa: 176 | # A class for elements in polynomial extension fields 177 | 178 | degree = 0 179 | p = [] 180 | 181 | def __init__(self, coeffs): 182 | assert len(coeffs) == self.degree 183 | self.coeffs = coeffs 184 | 185 | def __repr__(self): 186 | return f'Fa({self.coeffs})' 187 | 188 | def __eq__(self, other): 189 | return self.coeffs == other.coeffs 190 | 191 | def __add__(self, other): 192 | return self.__class__(polynomial.ext(polynomial.add(self.coeffs, other.coeffs), self.degree)) 193 | 194 | def __sub__(self, other): 195 | return self.__class__(polynomial.ext(polynomial.sub(self.coeffs, other.coeffs), self.degree)) 196 | 197 | def __mul__(self, other): 198 | mulmod = polynomial.rem(polynomial.mul(self.coeffs, other.coeffs), self.p) 199 | return self.__class__(polynomial.ext(mulmod, self.degree)) 200 | 201 | def __truediv__(self, other): 202 | return self * self.__class__(polynomial.ext(polynomial.inv(other.coeffs, self.p), self.degree)) 203 | 204 | def __pow__(self, data): 205 | result = self.one() 206 | mulend = self 207 | while data: 208 | b = data & 1 209 | if b == 1: 210 | result *= mulend 211 | mulend *= mulend 212 | data = data >> 1 213 | return result 214 | 215 | def __pos__(self): 216 | return self 217 | 218 | def __neg__(self): 219 | return self.__class__([-c for c in self.coeffs]) 220 | 221 | @classmethod 222 | def nil(cls): 223 | return cls([Fq(0) for _ in range(cls.degree)]) 224 | 225 | @classmethod 226 | def one(cls): 227 | return cls([Fq(1)] + [Fq(0) for _ in range(cls.degree - 1)]) 228 | 229 | 230 | class F2(Fa): 231 | degree = 2 232 | p = [Fq(e) for e in [1, 0, 1]] # i² + 1 = 0 233 | 234 | 235 | if __name__ == '__main__': 236 | a = F2([Fq(1), Fq(0)]) 237 | b = F2([Fq(1), Fq(2)]) 238 | assert a + b == F2([Fq(2), Fq(2)]) 239 | assert b / b == F2([Fq(1), Fq(0)]) 240 | assert a / b + a / b == (a + a) / b 241 | assert a * b + a * b == (a + a) * b 242 | assert a ** (P ** 2 - 1) == a 243 | 244 | 245 | class Ft(Fa): 246 | degree = 12 247 | p = [Fq(e) for e in [82, 0, 0, 0, 0, 0, -18, 0, 0, 0, 0, 0, 1]] # w¹² - 18w⁶ + 82 = 0 248 | 249 | 250 | class P2(Pa): 251 | a = F2([Fq(0), Fq(0)]) 252 | b = F2([Fq(3), Fq(0)]) / F2([Fq(9), Fq(1)]) 253 | i = [ 254 | F2([Fq(0), Fq(0)]), 255 | F2([Fq(0), Fq(0)]) 256 | ] 257 | 258 | 259 | class Pt(Pa): 260 | a = Ft([Fq(0) for _ in range(12)]) 261 | b = Ft([Fq(e) for e in [3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]) 262 | i = [ 263 | Ft([Fq(0) for _ in range(12)]), 264 | Ft([Fq(0) for _ in range(12)]) 265 | ] 266 | 267 | 268 | G2 = P2( 269 | F2([Fq(0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed), 270 | Fq(0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2)]), 271 | F2([Fq(0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa), 272 | Fq(0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b)]) 273 | ) 274 | I2 = P2(F2.nil(), F2.nil()) 275 | 276 | if __name__ == '__main__': 277 | assert G2 * Fr(2) + G2 + G2 == G2 * Fr(4) 278 | assert G2 + G2 != G2 279 | assert G2 * Fr(9) + G2 * Fr(5) == G2 * Fr(12) + G2 * Fr(2) 280 | assert G2 * Fr(N-1) + G2 == I2 281 | 282 | 283 | def pairing_twist(p): 284 | if p.x == P2.i[0] and p.y == P2.i[1]: 285 | return Pt(Pt.i[0], Pt.i[1]) 286 | w = Ft([Fq(e) for e in [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]) 287 | xcoeffs = [p.x.coeffs[0] - p.x.coeffs[1] * Fq(9), p.x.coeffs[1]] 288 | ycoeffs = [p.y.coeffs[0] - p.y.coeffs[1] * Fq(9), p.y.coeffs[1]] 289 | nx = Ft([xcoeffs[0], Fq(0), Fq(0), Fq(0), Fq(0), Fq(0), xcoeffs[1], Fq(0), Fq(0), Fq(0), Fq(0), Fq(0)]) 290 | ny = Ft([ycoeffs[0], Fq(0), Fq(0), Fq(0), Fq(0), Fq(0), ycoeffs[1], Fq(0), Fq(0), Fq(0), Fq(0), Fq(0)]) 291 | return Pt(nx * w ** 2, ny * w ** 3) 292 | 293 | 294 | Gt = pairing_twist(G2) 295 | It = Pt(Ft.nil(), Ft.nil()) 296 | 297 | if __name__ == '__main__': 298 | assert Gt * Fr(2) + Gt + Gt == Gt * Fr(4) 299 | assert Gt + Gt != Gt 300 | assert Gt * Fr(9) + Gt * Fr(5) == Gt * Fr(12) + Gt * Fr(2) 301 | assert Gt * Fr(N-1) + Gt == It 302 | 303 | 304 | def pairing_line_function(p, q, r): 305 | # Create a function representing the line between p and q, and evaluate it at r. 306 | # It can be considered as a distance metric between p + q and the second stationary point r. 307 | # 308 | # See https://crypto.stanford.edu/pbc/notes/ep/miller.html 309 | x1, y1 = p.x, p.y 310 | x2, y2 = q.x, q.y 311 | x3, y3 = r.x, r.y 312 | if x1 != x2: 313 | m = (y2 - y1) / (x2 - x1) 314 | return m * (x3 - x1) - (y3 - y1) 315 | if y1 == y2: 316 | # Simplify (3x² + a) / 2y => 3x² / 2y 317 | m = (x1 * x1 + x1 * x1 + x1 * x1) / (y1 + y1) 318 | return m * (x3 - x1) - (y3 - y1) 319 | return x3 - x1 320 | 321 | 322 | if __name__ == '__main__': 323 | x1, x2, x3 = G1, G1 * Fr(2), G1 * Fr(3) 324 | y1, y2, y3 = -x1, -x2, -x3 325 | assert pairing_line_function(x1, x2, x1) == Fq(0) 326 | assert pairing_line_function(x1, x2, x2) == Fq(0) 327 | assert pairing_line_function(x1, x2, x3) != Fq(0) 328 | assert pairing_line_function(x1, x2, y3) == Fq(0) 329 | assert pairing_line_function(x1, y1, x1) == Fq(0) 330 | assert pairing_line_function(x1, y1, y1) == Fq(0) 331 | assert pairing_line_function(x1, y1, x2) != Fq(0) 332 | assert pairing_line_function(x1, x1, x1) == Fq(0) 333 | assert pairing_line_function(x1, x1, x2) != Fq(0) 334 | assert pairing_line_function(x1, x1, y2) == Fq(0) 335 | 336 | 337 | def pairing_miller_loop(q, p): 338 | ate_loop_count = 29793968203157093288 339 | ate_loop_count_log = 63 340 | if (q.x == Pt.i[0] and q.y == Pt.i[1]) or (p.x == Pt.i[0] and p.y == Pt.i[1]): 341 | return Ft.one() 342 | r = q 343 | f = Ft.one() 344 | for i in range(ate_loop_count_log, -1, -1): 345 | f = f * f * pairing_line_function(r, r, p) 346 | r = r + r 347 | if ate_loop_count & (2**i): 348 | f = f * pairing_line_function(r, q, p) 349 | r = r + q 350 | a = Pt(q.x ** P, +q.y ** P) 351 | b = Pt(a.x ** P, -a.y ** P) 352 | f = f * pairing_line_function(r, a, p) 353 | r = r + a 354 | f = f * pairing_line_function(r, b, p) 355 | return f ** ((P ** 12 - 1) // N) 356 | 357 | 358 | def pairing(q, p): 359 | # Pairing computation 360 | r = Pt(Ft.nil(), Ft.nil()) 361 | r.x.coeffs[0] = p.x 362 | r.y.coeffs[0] = p.y 363 | return pairing_miller_loop(pairing_twist(q), r) 364 | 365 | 366 | if __name__ == '__main__': 367 | a = pairing(G2, +G1) 368 | b = pairing(G2, -G1) 369 | assert a * b == Ft.one() 370 | c = pairing(-G2, G1) 371 | assert a * c == Ft.one() 372 | assert b == c 373 | assert a ** N == Ft.one() 374 | d = pairing(G2, G1 * Fr(2)) 375 | assert a * a == d 376 | assert a != d and a != c and d != c 377 | e = pairing(G2 * Fr(2), G1) 378 | assert a * a == e 379 | f = pairing(G2 * Fr(27), G1 * Fr(37)) 380 | g = pairing(G2, G1 * Fr(999)) 381 | assert f == g 382 | h = pairing(G2 * Fr(999), G1) 383 | assert g == h 384 | -------------------------------------------------------------------------------- /bn128_ethereum_api.py: -------------------------------------------------------------------------------- 1 | from bn128 import * 2 | 3 | 4 | def alt_bn128_pairing(s): 5 | r = Ft.one() 6 | n = len(s) // 384 7 | for i in range(n): 8 | p_x = Fq(int(s[i * 384 + 0x00:i * 384 + 0x40], 16)) 9 | p_y = Fq(int(s[i * 384 + 0x40:i * 384 + 0x80], 16)) 10 | p = P1(p_x, p_y) 11 | q_x_y = Fq(int(s[i * 384 + 128:i * 384 + 192], 16)) 12 | q_x_x = Fq(int(s[i * 384 + 192:i * 384 + 256], 16)) 13 | q_y_y = Fq(int(s[i * 384 + 256:i * 384 + 320], 16)) 14 | q_y_x = Fq(int(s[i * 384 + 320:i * 384 + 384], 16)) 15 | q = P2(F2([q_x_x, q_x_y]), F2([q_y_x, q_y_y])) 16 | r = r * pairing(q, p) 17 | if r == Ft.one(): 18 | return 1 19 | return 0 20 | 21 | 22 | if __name__ == '__main__': 23 | # Taking from https://github.com/ethereum/go-ethereum/blob/master/core/vm/testdata/precompiles/bn256Pairing.json 24 | assert alt_bn128_pairing("1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f593034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf704bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a416782bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c2032c61a830e3c17286de9462bf242fca2883585b93870a73853face6a6bf411198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa") == 1 25 | assert alt_bn128_pairing("2eca0c7238bf16e83e7a1e6c5d49540685ff51380f309842a98561558019fc0203d3260361bb8451de5ff5ecd17f010ff22f5c31cdf184e9020b06fa5997db841213d2149b006137fcfb23036606f848d638d576a120ca981b5b1a5f9300b3ee2276cf730cf493cd95d64677bbb75fc42db72513a4c1e387b476d056f80aa75f21ee6226d31426322afcda621464d0611d226783262e21bb3bc86b537e986237096df1f82dff337dd5972e32a8ad43e28a78a96a823ef1cd4debe12b6552ea5f06967a1237ebfeca9aaae0d6d0bab8e28c198c5a339ef8a2407e31cdac516db922160fa257a5fd5b280642ff47b65eca77e626cb685c84fa6d3b6882a283ddd1198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa") == 1 26 | assert alt_bn128_pairing("0f25929bcb43d5a57391564615c9e70a992b10eafa4db109709649cf48c50dd216da2f5cb6be7a0aa72c440c53c9bbdfec6c36c7d515536431b3a865468acbba2e89718ad33c8bed92e210e81d1853435399a271913a6520736a4729cf0d51eb01a9e2ffa2e92599b68e44de5bcf354fa2642bd4f26b259daa6f7ce3ed57aeb314a9a87b789a58af499b314e13c3d65bede56c07ea2d418d6874857b70763713178fb49a2d6cd347dc58973ff49613a20757d0fcc22079f9abd10c3baee245901b9e027bd5cfc2cb5db82d4dc9677ac795ec500ecd47deee3b5da006d6d049b811d7511c78158de484232fc68daf8a45cf217d1c2fae693ff5871e8752d73b21198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa") == 1 27 | assert alt_bn128_pairing("2f2ea0b3da1e8ef11914acf8b2e1b32d99df51f5f4f206fc6b947eae860eddb6068134ddb33dc888ef446b648d72338684d678d2eb2371c61a50734d78da4b7225f83c8b6ab9de74e7da488ef02645c5a16a6652c3c71a15dc37fe3a5dcb7cb122acdedd6308e3bb230d226d16a105295f523a8a02bfc5e8bd2da135ac4c245d065bbad92e7c4e31bf3757f1fe7362a63fbfee50e7dc68da116e67d600d9bf6806d302580dc0661002994e7cd3a7f224e7ddc27802777486bf80f40e4ca3cfdb186bac5188a98c45e6016873d107f5cd131f3a3e339d0375e58bd6219347b008122ae2b09e539e152ec5364e7e2204b03d11d3caa038bfc7cd499f8176aacbee1f39e4e4afc4bc74790a4a028aff2c3d2538731fb755edefd8cb48d6ea589b5e283f150794b6736f670d6a1033f9b46c6f5204f50813eb85c8dc4b59db1c5d39140d97ee4d2b36d99bc49974d18ecca3e7ad51011956051b464d9e27d46cc25e0764bb98575bd466d32db7b15f582b2d5c452b36aa394b789366e5e3ca5aabd415794ab061441e51d01e94640b7e3084a07e02c78cf3103c542bc5b298669f211b88da1679b0b64a63b7e0e7bfe52aae524f73a55be7fe70c7e9bfc94b4cf0da1213d2149b006137fcfb23036606f848d638d576a120ca981b5b1a5f9300b3ee2276cf730cf493cd95d64677bbb75fc42db72513a4c1e387b476d056f80aa75f21ee6226d31426322afcda621464d0611d226783262e21bb3bc86b537e986237096df1f82dff337dd5972e32a8ad43e28a78a96a823ef1cd4debe12b6552ea5f") == 1 28 | assert alt_bn128_pairing("20a754d2071d4d53903e3b31a7e98ad6882d58aec240ef981fdf0a9d22c5926a29c853fcea789887315916bbeb89ca37edb355b4f980c9a12a94f30deeed30211213d2149b006137fcfb23036606f848d638d576a120ca981b5b1a5f9300b3ee2276cf730cf493cd95d64677bbb75fc42db72513a4c1e387b476d056f80aa75f21ee6226d31426322afcda621464d0611d226783262e21bb3bc86b537e986237096df1f82dff337dd5972e32a8ad43e28a78a96a823ef1cd4debe12b6552ea5f1abb4a25eb9379ae96c84fff9f0540abcfc0a0d11aeda02d4f37e4baf74cb0c11073b3ff2cdbb38755f8691ea59e9606696b3ff278acfc098fa8226470d03869217cee0a9ad79a4493b5253e2e4e3a39fc2df38419f230d341f60cb064a0ac290a3d76f140db8418ba512272381446eb73958670f00cf46f1d9e64cba057b53c26f64a8ec70387a13e41430ed3ee4a7db2059cc5fc13c067194bcc0cb49a98552fd72bd9edb657346127da132e5b82ab908f5816c826acb499e22f2412d1a2d70f25929bcb43d5a57391564615c9e70a992b10eafa4db109709649cf48c50dd2198a1f162a73261f112401aa2db79c7dab1533c9935c77290a6ce3b191f2318d198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa") == 1 29 | assert alt_bn128_pairing("1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f593034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf704bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a416782bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c103188585e2364128fe25c70558f1560f4f9350baf3959e603cc91486e110936198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa") == 0 30 | assert alt_bn128_pairing("") == 1 31 | assert alt_bn128_pairing("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa") == 0 32 | assert alt_bn128_pairing("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d") == 1 33 | assert alt_bn128_pairing("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa") == 1 34 | assert alt_bn128_pairing("105456a333e6d636854f987ea7bb713dfd0ae8371a72aea313ae0c32c0bf10160cf031d41b41557f3e7e3ba0c51bebe5da8e6ecd855ec50fc87efcdeac168bcc0476be093a6d2b4bbf907172049874af11e1b6267606e00804d3ff0037ec57fd3010c68cb50161b7d1d96bb71edfec9880171954e56871abf3d93cc94d745fa114c059d74e5b6c4ec14ae5864ebe23a71781d86c29fb8fb6cce94f70d3de7a2101b33461f39d9e887dbb100f170a2345dde3c07e256d1dfa2b657ba5cd030427000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000021a2c3013d2ea92e13c800cde68ef56a294b883f6ac35d25f587c09b1b3c635f7290158a80cd3d66530f74dc94c94adb88f5cdb481acca997b6e60071f08a115f2f997f3dbd66a7afe07fe7862ce239edba9e05c5afff7f8a1259c9733b2dfbb929d1691530ca701b4a106054688728c9972c8512e9789e9567aae23e302ccd75") == 1 35 | assert alt_bn128_pairing("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed275dc4a288d1afb3cbb1ac09187524c7db36395df7be3b99e673b13a075a65ec1d9befcd05a5323e6da4d435f3b617cdb3af83285c2df711ef39c01571827f9d") == 1 36 | assert alt_bn128_pairing("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002203e205db4f19b37b60121b83a7333706db86431c6d835849957ed8c3928ad7927dc7234fd11d3e8c36c59277c3e6f149d5cd3cfa9a62aee49f8130962b4b3b9195e8aa5b7827463722b8c153931579d3505566b4edf48d498e185f0509de15204bb53b8977e5f92a0bc372742c4830944a59b4fe6b1c0466e2a6dad122b5d2e030644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd31a76dae6d3272396d0cbe61fced2bc532edac647851e3ac53ce1cc9c7e645a83198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa") == 1 37 | assert alt_bn128_pairing("105456a333e6d636854f987ea7bb713dfd0ae8371a72aea313ae0c32c0bf10160cf031d41b41557f3e7e3ba0c51bebe5da8e6ecd855ec50fc87efcdeac168bcc0476be093a6d2b4bbf907172049874af11e1b6267606e00804d3ff0037ec57fd3010c68cb50161b7d1d96bb71edfec9880171954e56871abf3d93cc94d745fa114c059d74e5b6c4ec14ae5864ebe23a71781d86c29fb8fb6cce94f70d3de7a2101b33461f39d9e887dbb100f170a2345dde3c07e256d1dfa2b657ba5cd030427000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000021a2c3013d2ea92e13c800cde68ef56a294b883f6ac35d25f587c09b1b3c635f7290158a80cd3d66530f74dc94c94adb88f5cdb481acca997b6e60071f08a115f2f997f3dbd66a7afe07fe7862ce239edba9e05c5afff7f8a1259c9733b2dfbb929d1691530ca701b4a106054688728c9972c8512e9789e9567aae23e302ccd75") == 1 38 | -------------------------------------------------------------------------------- /ckb_address.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | import segwit_addr as sa 4 | import secp256k1 5 | 6 | # Double checked by https://ckb.tools/generator 7 | 8 | prikey = secp256k1.Fr(0xd5d8fe30c6ab6bfd2c6e0a940299a1e01a9ab6b8a8ed407a00b130e6a51435fc) 9 | pubkey = secp256k1.G * prikey 10 | assert pubkey.x.x == 0x97202631ccab00b8669e0b1fcc376f082513f22593c5e99fbf76ab02e8911d2e 11 | assert pubkey.y.x == 0xeae37bf649d45e0cf83c5c057de60d685ece29e9b7e58959a638845d3d0659c6 12 | 13 | if pubkey.y.x & 1 == 0: 14 | pubkey_byte = bytes([0x02]) + pubkey.x.x.to_bytes(32, byteorder='big') 15 | else: 16 | pubkey_byte = bytes([0x03]) + pubkey.x.x.to_bytes(32, byteorder='big') 17 | assert pubkey_byte.hex() == '0297202631ccab00b8669e0b1fcc376f082513f22593c5e99fbf76ab02e8911d2e' 18 | 19 | args = hashlib.blake2b(pubkey_byte, digest_size=32, person=b'ckb-default-hash').digest()[:20] 20 | assert args.hex() == 'e5126d9d897e5d5249607760f9da024119f9e296' 21 | 22 | # https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0021-ckb-address-format/0021-ckb-address-format.md 23 | # https://github.com/rev-chaos/ckb-address-demo/blob/master/ckb_addr_test.py 24 | payload = b'' 25 | payload += b'\x00' 26 | # Append secp256k1 code hash 27 | payload += bytes.fromhex('9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8') 28 | payload += b'\x01' 29 | payload += args 30 | 31 | cktaddr = sa.bech32_encode('ckt', sa.convertbits(payload, 8, 5), sa.Encoding.BECH32M) 32 | assert cktaddr == 'ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq09zfkemzt7t4fyjcrhvrua5qjpr8u799s6se0vv' 33 | -------------------------------------------------------------------------------- /finite_field.py: -------------------------------------------------------------------------------- 1 | class Fp: 2 | # Galois field. In mathematics, a finite field or Galois field is a field that contains a finite number of elements. 3 | # As with any field, a finite field is a set on which the operations of multiplication, addition, subtraction and 4 | # division are defined and satisfy certain basic rules. 5 | # 6 | # https://www.cs.miami.edu/home/burt/learning/Csc609.142/ecdsa-cert.pdf 7 | # Don Johnson, Alfred Menezes and Scott Vanstone, The Elliptic Curve Digital Signature Algorithm (ECDSA) 8 | # 3.1 The Finite Field Fp 9 | 10 | p = 0 11 | 12 | def __init__(self, x): 13 | self.x = x % self.p 14 | 15 | def __repr__(self): 16 | return f'Fp(0x{self.x:064x})' 17 | 18 | def __eq__(self, data): 19 | assert self.p == data.p 20 | return self.x == data.x 21 | 22 | def __add__(self, data): 23 | assert self.p == data.p 24 | return self.__class__((self.x + data.x) % self.p) 25 | 26 | def __sub__(self, data): 27 | assert self.p == data.p 28 | return self.__class__((self.x - data.x) % self.p) 29 | 30 | def __mul__(self, data): 31 | assert self.p == data.p 32 | return self.__class__((self.x * data.x) % self.p) 33 | 34 | def __truediv__(self, data): 35 | return self * data ** -1 36 | 37 | def __pow__(self, data): 38 | return self.__class__(pow(self.x, data, self.p)) 39 | 40 | def __pos__(self): 41 | return self 42 | 43 | def __neg__(self): 44 | return self.__class__(self.p - self.x) 45 | 46 | @classmethod 47 | def nil(cls): 48 | return cls(0) 49 | 50 | @classmethod 51 | def one(cls): 52 | return cls(1) 53 | 54 | 55 | if __name__ == '__main__': 56 | Fp.p = 23 57 | assert Fp(12) + Fp(20) == Fp(9) 58 | assert Fp(8) * Fp(9) == Fp(3) 59 | assert Fp(8) ** -1 == Fp(3) 60 | Fp.p = 0 61 | -------------------------------------------------------------------------------- /finite_field_primitive_nth_root.py: -------------------------------------------------------------------------------- 1 | import finite_field 2 | import random 3 | 4 | 5 | class Fp(finite_field.Fp): 6 | 7 | @classmethod 8 | def primitive_nth_root(cls, n): 9 | # https://crypto.stackexchange.com/questions/63614/finding-the-n-th-root-of-unity-in-a-finite-field 10 | assert (cls.p - 1) % n == 0 11 | while 1: 12 | x = cls(random.randint(1, cls.p - 1)) 13 | g = x ** ((cls.p - 1) // n) 14 | if g ** (n // 2) != Fp(1): 15 | return g 16 | 17 | 18 | if __name__ == '__main__': 19 | Fp.p = 1 + 407 * (1 << 119) 20 | assert Fp.primitive_nth_root(1 << 119) ** (1 << 119) == Fp(1) 21 | assert Fp.primitive_nth_root(1 << 111) ** (1 << 111) == Fp(1) 22 | Fp.p = 0 23 | -------------------------------------------------------------------------------- /kzg.py: -------------------------------------------------------------------------------- 1 | import bn128 2 | import random 3 | import polynomial 4 | 5 | # [1] Polynomial commits: https://cacr.uwaterloo.ca/techreports/2010/cacr2010-10.pdf 6 | # [2] https://www.youtube.com/watch?v=n4eiiCDhTes 7 | # [3] https://www.youtube.com/watch?v=NVvNHe_RGZ8 8 | # [4] https://copper-witch-857.notion.site/Polynomial-KZG-or-Kate-Commitment-DappLearning-Notes-fc426c8cb9a14878840852506865f13b 9 | # [5] https://foresightnews.pro/article/detail/17988 10 | # [6] https://dankradfeist.de/ethereum/2021/10/13/kate-polynomial-commitments-mandarin.html 11 | 12 | 13 | Fr = bn128.Fr 14 | G1 = bn128.G1 15 | I1 = bn128.I1 16 | I2 = bn128.I2 17 | G2 = bn128.G2 18 | pairing = bn128.pairing 19 | 20 | sk = Fr(random.randint(0, Fr.p - 1)) 21 | pk_g1 = [G1 * (sk**i) for i in range(10)] 22 | pk_g2 = [G2 * (sk**i) for i in range(10)] 23 | 24 | ax = [Fr(e) for e in [0, 1, 2, 3]] 25 | ay = [Fr(e) for e in [4, 15, 40, 85]] 26 | fx = polynomial.lagrange(ax, ay) 27 | commit = sum([pk_g1[i] * fx[i] for i in range(len(fx))], start=I1) 28 | 29 | # 单个证明 30 | px = Fr(1) 31 | py = Fr(15) 32 | qx = polynomial.div(polynomial.sub(fx, [py]), [-px, Fr(1)]) 33 | proofs = sum([pk_g1[i] * qx[i] for i in range(len(qx))], start=I1) 34 | lhs = pairing(G2, commit - G1 * py) 35 | rhs = pairing(pk_g2[1] - G2 * px, proofs) 36 | assert lhs == rhs 37 | 38 | # 批量证明 39 | px = [Fr(e) for e in [0, 1]] 40 | py = [Fr(e) for e in [4, 15]] 41 | ix = polynomial.lagrange(px, py) 42 | zx = polynomial.zerofier(px) 43 | qx = polynomial.div(polynomial.sub(fx, ix), zx) 44 | proofs = sum([pk_g1[i] * qx[i] for i in range(len(qx))], start=I1) 45 | ix_sg1 = sum([pk_g1[i] * ix[i] for i in range(len(ix))], start=I1) 46 | zx_sg2 = sum([pk_g2[i] * zx[i] for i in range(len(zx))], start=I2) 47 | lhs = pairing(G2, commit - ix_sg1) 48 | rhs = pairing(zx_sg2, proofs) 49 | assert lhs == rhs 50 | -------------------------------------------------------------------------------- /kzg_arith_relation.py: -------------------------------------------------------------------------------- 1 | import bn128 2 | import hashlib 3 | import random 4 | import polynomial 5 | 6 | Fr = bn128.Fr 7 | G1 = bn128.G1 8 | I1 = bn128.I1 9 | I2 = bn128.I2 10 | G2 = bn128.G2 11 | pairing = bn128.pairing 12 | 13 | 14 | def create_witness(pk, fx, x): 15 | y = polynomial.evaluate(fx, x) 16 | qx = polynomial.div(polynomial.sub(fx, [y]), [-x, Fr(1)]) 17 | proofs = sum([pk[i] * qx[i] for i in range(len(qx))], start=I1) 18 | return x, y, proofs 19 | 20 | 21 | sk = Fr(random.randint(0, Fr.p - 1)) 22 | pk_g1 = [G1 * (sk**i) for i in range(10)] 23 | pk_g2 = [G2 * (sk**i) for i in range(10)] 24 | 25 | # Prove that f(x) + g(x) = h(x) 26 | 27 | fx = [Fr(e) for e in [1, 2, 3]] 28 | gx = [Fr(e) for e in [4, 5, 6]] 29 | hx = [Fr(e) for e in [5, 7, 9]] 30 | fx_commit = sum([pk_g1[i] * fx[i] for i in range(len(fx))], start=I1) 31 | gx_commit = sum([pk_g1[i] * gx[i] for i in range(len(gx))], start=I1) 32 | hx_commit = sum([pk_g1[i] * hx[i] for i in range(len(hx))], start=I1) 33 | m = hashlib.sha256() 34 | m.update(fx_commit.x.x.to_bytes(32, 'little')) 35 | m.update(fx_commit.y.x.to_bytes(32, 'little')) 36 | m.update(gx_commit.x.x.to_bytes(32, 'little')) 37 | m.update(gx_commit.y.x.to_bytes(32, 'little')) 38 | m.update(hx_commit.x.x.to_bytes(32, 'little')) 39 | m.update(hx_commit.y.x.to_bytes(32, 'little')) 40 | z = Fr(int.from_bytes(m.digest(), 'little')) 41 | _, fz, fx_proofs = create_witness(pk_g1, fx, z) 42 | _, gz, gx_proofs = create_witness(pk_g1, gx, z) 43 | _, hz, hx_proofs = create_witness(pk_g1, hx, z) 44 | assert fz + gz == hz 45 | assert pairing(G2, fx_commit - G1 * fz) == pairing(pk_g2[1] - G2 * z, fx_proofs) 46 | assert pairing(G2, gx_commit - G1 * gz) == pairing(pk_g2[1] - G2 * z, gx_proofs) 47 | assert pairing(G2, hx_commit - G1 * hz) == pairing(pk_g2[1] - G2 * z, hx_proofs) 48 | -------------------------------------------------------------------------------- /main.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | for eachfile in $(ls *.py) 4 | do 5 | echo python $eachfile 6 | python $eachfile 7 | done 8 | -------------------------------------------------------------------------------- /polynomial.py: -------------------------------------------------------------------------------- 1 | def clr(c1): 2 | for i in range(len(c1) - 1, -1, -1): 3 | if c1[i] != c1[0].__class__(0): 4 | break 5 | return c1[:i+1] 6 | 7 | 8 | def deg(c1): 9 | d = len(c1) - 1 10 | while c1[d] == c1[0].__class__(0) and d: 11 | d -= 1 12 | return d 13 | 14 | 15 | def ext(c1, sz): 16 | p = [c1[0].__class__(0) for _ in range(sz)] 17 | for i, e in enumerate(c1): 18 | p[i] = e 19 | return p 20 | 21 | 22 | def add(c1, c2): 23 | p = [c1[0].__class__(0) for _ in range(max(len(c1), len(c2)))] 24 | for i, e in enumerate(c1): 25 | p[i] += e 26 | for i, e in enumerate(c2): 27 | p[i] += e 28 | return clr(p) 29 | 30 | 31 | def sub(c1, c2): 32 | p = [c1[0].__class__(0) for _ in range(max(len(c1), len(c2)))] 33 | for i, e in enumerate(c1): 34 | p[i] += e 35 | for i, e in enumerate(c2): 36 | p[i] -= e 37 | return clr(p) 38 | 39 | 40 | def mul(c1, c2): 41 | p = [c1[0].__class__(0) for _ in range(len(c1) + len(c2) - 1)] 42 | for i in range(len(c1)): 43 | for j in range(len(c2)): 44 | p[i+j] += c1[i] * c2[j] 45 | return clr(p) 46 | 47 | 48 | def divrem(c1, c2): 49 | # Algorithm: https://en.wikipedia.org/wiki/Polynomial_long_division 50 | # The code implementation is inspired by numpy.polynomial.polynomial.polydiv 51 | lc1 = len(c1) 52 | lc2 = len(c2) 53 | if c2[-1] == c1[0].__class__(0): 54 | raise ZeroDivisionError() 55 | if lc1 < lc2: 56 | return [c1[0].__class__(0)], c1 57 | if lc2 == 1: 58 | return [e / c2[0] for e in c1], [c1[0].__class__(0)] 59 | dif = lc1 - lc2 60 | scl = c2[-1] 61 | nc1 = c1.copy() 62 | nc2 = [e/scl for e in c2[:-1]] 63 | i = dif 64 | j = lc1 - 1 65 | while i >= 0: 66 | for k in range(lc2 - 1): 67 | nc1[i+k] -= nc2[k]*nc1[j] 68 | i -= 1 69 | j -= 1 70 | return [e/scl for e in nc1[j+1:]], clr(nc1[:j+1]) 71 | 72 | 73 | def div(c1, c2): 74 | return divrem(c1, c2)[0] 75 | 76 | 77 | def rem(c1, c2): 78 | return divrem(c1, c2)[1] 79 | 80 | 81 | def inv(c1, c2): 82 | # Algorithm: https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm 83 | newt, t = [c1[0].__class__(1)], [c1[0].__class__(0)] 84 | newr, r = c1, c2 85 | while deg(newr): 86 | quotient = div(r, clr(newr)) 87 | r, newr = newr, sub(r, mul(newr, quotient)) 88 | t, newt = newt, sub(t, mul(newt, quotient)) 89 | return clr([e/newr[0] for e in newt[:deg(c2)]]) 90 | 91 | 92 | def evaluate(c1, pt): 93 | # Polynomial evaluation on a value. 94 | xi = c1[0].__class__(1) 95 | rt = c1[0].__class__(0) 96 | for c in c1: 97 | rt = rt + xi * c 98 | xi = xi * pt 99 | return rt 100 | 101 | 102 | def lagrange(c1, c2): 103 | # Lagrange interpolation, copied from scipy.interpolate.lagrange. 104 | # Note that unlike scipy's implementation, the results are sequences of coefficients from lowest order term to 105 | # highest. 106 | M = len(c1) 107 | p = [c1[0].__class__(0)] 108 | for j in range(M): 109 | pt = [c2[j]] 110 | for k in range(M): 111 | if k == j: 112 | continue 113 | fac = c1[j]-c1[k] 114 | pt = mul([-c1[k] / fac, c1[0].__class__(1) / fac], pt) 115 | p = add(p, pt) 116 | return p 117 | 118 | 119 | def zerofier(c1): 120 | # See: https://aszepieniec.github.io/stark-anatomy/basic-tools 121 | x = [c1[0].__class__(0), c1[0].__class__(1)] 122 | a = [c1[0].__class__(1)] 123 | for d in c1: 124 | a = mul(a, sub(x, [d])) 125 | return a 126 | 127 | 128 | if __name__ == '__main__': 129 | c1 = [4, -2, 5] 130 | c2 = [2, -5, 2] 131 | assert add(c1, c2) == [6, -7, 7] 132 | assert sub(c1, c2) == [2, 3, 3] 133 | assert mul(c1, c2) == [8, -24, 28, -29, 10] 134 | assert div(c1, c2) == [2.5] 135 | assert rem(c1, c2) == [-1, 10.5] 136 | assert sub(c1, c1) == [0] 137 | # Copied from https://en.wikipedia.org/wiki/Polynomial_long_division#Example 138 | assert div([-4, 0, -2, 1], [-3, 1]) == [3, 1, 1] 139 | assert rem([-4, 0, -2, 1], [-3, 1]) == [5] 140 | assert rem(mul(inv(c1, c2), c1), c2)[0] == 1 141 | assert lagrange([1, 2, 3, 4], [4, 15, 40, 85]) == [1.0, 1.0000000000000284, 1.0, 0.9999999999999982] 142 | assert lagrange([0, 1, 2], [0, 1, 8]) == [0, -2, 3] 143 | assert lagrange([1, 2, 3], [1, 4, 9]) == [0, 0, 1] 144 | assert zerofier([0, 1]) == [0, -1, 1] 145 | assert zerofier([1, 2]) == [2, -3, 1] 146 | -------------------------------------------------------------------------------- /polynomial_numpy.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | 3 | # p(x) = 5x² - 2x + 4 4 | px = [4, -2, 5] 5 | 6 | # q(x) = 2x² - 5x + 2 7 | qx = [2, -5, 2] 8 | 9 | r = numpy.polynomial.polynomial.polyadd(px, qx) 10 | print('polyadd', r) # 7x² - 7x + 6 11 | r = numpy.polynomial.polynomial.polysub(px, qx) 12 | print('polysub', r) # 3x² + 3x + 2 13 | r = numpy.polynomial.polynomial.polymul(px, qx) 14 | print('polymul', r) # 10x⁴ - 29x³ + 28x² - 24x + 8 15 | quo, rem = numpy.polynomial.polynomial.polydiv(px, qx) 16 | print('polyquo', quo) # 2.5 17 | print('polyrem', rem) # 10.5x - 1 18 | -------------------------------------------------------------------------------- /secp256k1.py: -------------------------------------------------------------------------------- 1 | class Fp: 2 | # Galois field. In mathematics, a finite field or Galois field is a field that contains a finite number of elements. 3 | # As with any field, a finite field is a set on which the operations of multiplication, addition, subtraction and 4 | # division are defined and satisfy certain basic rules. 5 | # 6 | # https://www.cs.miami.edu/home/burt/learning/Csc609.142/ecdsa-cert.pdf 7 | # Don Johnson, Alfred Menezes and Scott Vanstone, The Elliptic Curve Digital Signature Algorithm (ECDSA) 8 | # 3.1 The Finite Field Fp 9 | 10 | p = 0 11 | 12 | def __init__(self, x): 13 | self.x = x % self.p 14 | 15 | def __repr__(self): 16 | return f'Fp(0x{self.x:064x})' 17 | 18 | def __eq__(self, data): 19 | assert self.p == data.p 20 | return self.x == data.x 21 | 22 | def __add__(self, data): 23 | assert self.p == data.p 24 | return self.__class__((self.x + data.x) % self.p) 25 | 26 | def __sub__(self, data): 27 | assert self.p == data.p 28 | return self.__class__((self.x - data.x) % self.p) 29 | 30 | def __mul__(self, data): 31 | assert self.p == data.p 32 | return self.__class__((self.x * data.x) % self.p) 33 | 34 | def __truediv__(self, data): 35 | return self * data ** -1 36 | 37 | def __pow__(self, data): 38 | return self.__class__(pow(self.x, data, self.p)) 39 | 40 | def __pos__(self): 41 | return self 42 | 43 | def __neg__(self): 44 | return self.__class__(self.p - self.x) 45 | 46 | @classmethod 47 | def nil(cls): 48 | return cls(0) 49 | 50 | @classmethod 51 | def one(cls): 52 | return cls(1) 53 | 54 | 55 | if __name__ == '__main__': 56 | Fp.p = 23 57 | assert Fp(12) + Fp(20) == Fp(9) 58 | assert Fp(8) * Fp(9) == Fp(3) 59 | assert Fp(8) ** -1 == Fp(3) 60 | Fp.p = 0 61 | 62 | # Prime of finite field. 63 | P = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f 64 | # The order n of G. 65 | N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 66 | 67 | 68 | class Fq(Fp): 69 | 70 | p = P 71 | 72 | def __repr__(self): 73 | return f'Fq(0x{self.x:064x})' 74 | 75 | 76 | class Fr(Fp): 77 | 78 | p = N 79 | 80 | def __repr__(self): 81 | return f'Fr(0x{self.x:064x})' 82 | 83 | 84 | A = Fq(0) 85 | B = Fq(7) 86 | 87 | 88 | class Pt: 89 | 90 | def __init__(self, x, y): 91 | if x != Fq(0) or y != Fq(0): 92 | assert y ** 2 == x ** 3 + A * x + B 93 | self.x = x 94 | self.y = y 95 | 96 | def __repr__(self): 97 | return f'Pt({self.x}, {self.y})' 98 | 99 | def __eq__(self, data): 100 | return self.x == data.x and self.y == data.y 101 | 102 | def __add__(self, data): 103 | # https://www.cs.miami.edu/home/burt/learning/Csc609.142/ecdsa-cert.pdf 104 | # Don Johnson, Alfred Menezes and Scott Vanstone, The Elliptic Curve Digital Signature Algorithm (ECDSA) 105 | # 4.1 Elliptic Curves Over Fp 106 | x1, x2 = self.x, data.x 107 | y1, y2 = self.y, data.y 108 | if x1 == Fq(0) and y1 == Fq(0): 109 | return data 110 | if x2 == Fq(0) and y2 == Fq(0): 111 | return self 112 | if x1 == x2 and y1 == +y2: 113 | sk = (x1 * x1 + x1 * x1 + x1 * x1 + A) / (y1 + y1) 114 | x3 = sk * sk - x1 - x2 115 | y3 = sk * (x1 - x3) - y1 116 | return Pt(x3, y3) 117 | if x1 == x2 and y1 == -y2: 118 | return I 119 | sk = (y2 - y1) / (x2 - x1) 120 | x3 = sk * sk - x1 - x2 121 | y3 = sk * (x1 - x3) - y1 122 | return Pt(x3, y3) 123 | 124 | def __sub__(self, data): 125 | return self + data.__neg__() 126 | 127 | def __mul__(self, k): 128 | # Point multiplication: Double-and-add 129 | # https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication 130 | n = k.x 131 | result = I 132 | addend = self 133 | while n: 134 | b = n & 1 135 | if b == 1: 136 | result += addend 137 | addend = addend + addend 138 | n = n >> 1 139 | return result 140 | 141 | def __truediv__(self, k): 142 | return self.__mul__(k ** -1) 143 | 144 | def __pos__(self): 145 | return self 146 | 147 | def __neg__(self): 148 | return Pt(self.x, -self.y) 149 | 150 | 151 | # Identity element 152 | I = Pt( 153 | Fq(0), 154 | Fq(0), 155 | ) 156 | # Generator point 157 | G = Pt( 158 | Fq(0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798), 159 | Fq(0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8), 160 | ) 161 | 162 | if __name__ == '__main__': 163 | p = G * Fr(42) 164 | q = G * Fr(24) 165 | r = Pt(p.x, -p.y) 166 | assert p + q == G * Fr(66) 167 | assert p + p == G * Fr(84) 168 | assert p - q == G * Fr(18) 169 | assert r == -p 170 | assert p + r == I 171 | assert p + I == p 172 | assert p * Fr(42) == G * Fr(1764) 173 | -------------------------------------------------------------------------------- /secp256k1_extract_private_key.py: -------------------------------------------------------------------------------- 1 | import secp256k1 2 | 3 | # Sony uses a private key, typically stored (in an HSM?) at the company's HQ, to mark their Playstation firmwares as 4 | # valid and unmodified. The PS3 only needs a public key to verify that the signature came from Sony. Normally, this is 5 | # considered safe; but Sony did a rookie mistake in the implementation of their signing algorithm - they used the same 6 | # random number to sign everything. 7 | # 8 | # Recall how the (public parameter) r in the signature is generated from a (secret) random number k, using the formula 9 | # kG = R, r being the x-coordinate of the point R. Given two signatures that use the same k, prove how you can extract 10 | # the private key used for signing. Use the signature formula in the ECDSA section. You'll need pen and paper for this. 11 | 12 | m1 = secp256k1.Fr(0x72a963cdfb01bc37cd283106875ff1f07f02bc9ad6121b75c3d17629df128d4e) 13 | r1 = secp256k1.Fr(0x741a1cc1db8aa02cff2e695905ed866e4e1f1e19b10e2b448bf01d4ef3cbd8ed) 14 | s1 = secp256k1.Fr(0x2222017d7d4b9886a19fe8da9234032e5e8dc5b5b1f27517b03ac8e1dd573c78) 15 | 16 | m2 = secp256k1.Fr(0x059aa1e67abe518ea1e09587f828264119e3cdae0b8fcaedb542d8c287c3d420) 17 | r2 = secp256k1.Fr(0x741a1cc1db8aa02cff2e695905ed866e4e1f1e19b10e2b448bf01d4ef3cbd8ed) 18 | s2 = secp256k1.Fr(0x5c907cdd9ac36fdaf4af60e2ccfb1469c7281c30eb219eca3eddf1f0ad804655) 19 | 20 | # Formula: 21 | # s1 = (m1 + prikey * r1) / k 22 | # s2 = (m2 + prikey * r2) / k 23 | # = (m2 + prikey * r1) / k 24 | # s1 / s2 = (m1 + prikey * r1) / (m2 + prikey * r1) 25 | # prikey = (s1 * m2 - s2 * m1) / (s2 - s1) / r1 26 | 27 | prikey = (s1 * m2 - s2 * m1) / (s2 - s1) / r1 28 | assert prikey.x == 0x5f6717883bef25f45a129c11fcac1567d74bda5a9ad4cbffc8203c0da2a1473c 29 | -------------------------------------------------------------------------------- /secp256k1_generate_public_key.py: -------------------------------------------------------------------------------- 1 | import secp256k1 2 | 3 | prikey = secp256k1.Fr(0x5f6717883bef25f45a129c11fcac1567d74bda5a9ad4cbffc8203c0da2a1473c) 4 | pubkey = secp256k1.G * prikey 5 | assert pubkey.x.x == 0xfb95541bf75e809625f860758a1bc38ac3c1cf120d899096194b94a5e700e891 6 | assert pubkey.y.x == 0xc7b6277d32c52266ab94af215556316e31a9acde79a8b39643c6887544fdf58c 7 | -------------------------------------------------------------------------------- /secp256k1_jacobian.py: -------------------------------------------------------------------------------- 1 | import secp256k1 2 | 3 | # [1] 熠智科技, 椭圆曲线科普, https://download.yeez.tech/doc/ECcurve.pdf, 3.4 Projective Space 4 | # [2] https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates 5 | 6 | 7 | class Pj: 8 | 9 | def __init__(self, x, y, z): 10 | self.x = x 11 | self.y = y 12 | self.z = z 13 | 14 | def __repr__(self): 15 | return f'Pj({self.x}, {self.y}, {self.z})' 16 | 17 | def __eq__(self, data): 18 | if self.x * data.z != data.x * self.z: 19 | return 0 20 | if self.y * data.z != data.y * self.z: 21 | return 0 22 | return 1 23 | 24 | @classmethod 25 | def encode(cls, pt): 26 | if pt == secp256k1.I: 27 | return Pj(secp256k1.Fq(0), secp256k1.Fq(1), secp256k1.Fq(0)) 28 | else: 29 | return Pj(pt.x, pt.y, secp256k1.Fq(1)) 30 | 31 | def decode(self): 32 | if self.z == secp256k1.Fq(0): 33 | return secp256k1.I 34 | else: 35 | return secp256k1.Pt(self.x / self.z, self.y / self.z) 36 | 37 | def __add__(self, data): 38 | x1, y1, z1 = self.x, self.y, self.z 39 | x2, y2, z2 = data.x, data.y, data.z 40 | if z1 == secp256k1.Fq(0): 41 | return data 42 | if z2 == secp256k1.Fq(0): 43 | return self 44 | u1 = y2 * z1 45 | u2 = y1 * z2 46 | v1 = x2 * z1 47 | v2 = x1 * z2 48 | if v1 == v2: 49 | if u1 != u2: 50 | return I 51 | else: 52 | t = secp256k1.A * z1 * z1 + secp256k1.Fq(3) * x1 * x1 53 | u = y1 * z1 54 | v = u * x1 * y1 55 | w = t * t - secp256k1.Fq(8) * v 56 | x3 = secp256k1.Fq(2) * u * w 57 | y3 = t * (secp256k1.Fq(4) * v - w) - secp256k1.Fq(8) * y1 * y1 * u * u 58 | z3 = secp256k1.Fq(8) * u * u * u 59 | return Pj(x3, y3, z3) 60 | else: 61 | u = u1 - u2 62 | v = v1 - v2 63 | w = u * u * z1 * z2 - v * v * v - secp256k1.Fq(2) * v * v * x1 * z2 64 | x3 = v * w 65 | y3 = u * (v * v * x1 * z2 - w) - v * v * v * y1 * z2 66 | z3 = v * v * v * z1 * z2 67 | return Pj(x3, y3, z3) 68 | 69 | def __sub__(self, data): 70 | return self + data.__neg__() 71 | 72 | def __mul__(self, k): 73 | n = k.x 74 | result = I 75 | addend = self 76 | while n: 77 | b = n & 1 78 | if b == 1: 79 | result += addend 80 | addend = addend + addend 81 | n = n >> 1 82 | return result 83 | 84 | def __neg__(self): 85 | return Pj(self.x, -self.y, self.z) 86 | 87 | 88 | G = Pj.encode(secp256k1.G) 89 | I = Pj.encode(secp256k1.I) 90 | 91 | 92 | if __name__ == '__main__': 93 | p1 = secp256k1.G * secp256k1.Fr(42) 94 | p2 = secp256k1.G * secp256k1.Fr(24) 95 | j1 = Pj.encode(p1) 96 | j2 = Pj.encode(p2) 97 | assert p1 == j1.decode() 98 | assert p2 == j2.decode() 99 | assert j1 == G * secp256k1.Fr(42) 100 | assert j2 == G * secp256k1.Fr(24) 101 | assert p1 + p2 == (j1 + j2).decode() 102 | assert p1 + p1 == (j1 + j1).decode() 103 | assert j1 - j1 == I 104 | assert j1 + I == j1 105 | assert j1 * secp256k1.Fr(42) == Pj.encode(p1 * secp256k1.Fr(42)) 106 | -------------------------------------------------------------------------------- /secp256k1_schnorr.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import random 3 | import secp256k1 4 | 5 | # https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki 6 | # 7 | # Schnorr signature variant Elliptic Curve Schnorr signatures for message m and public key P generally involve a point 8 | # R, integers e and s picked by the signer, and the base point G which satisfy e = hash(R || m) and s⋅G = R + e⋅P. Two 9 | # formulations exist, depending on whether the signer reveals e or R: 10 | # 1. .... 11 | # 2. Signatures are pairs (R, s) that satisfy s⋅G = R + hash(R || m)⋅P. This supports batch verification, as there 12 | # are no elliptic curve operations inside the hashes. Batch verification enables significant speedups. 13 | 14 | prikey = secp256k1.Fr(0x5f6717883bef25f45a129c11fcac1567d74bda5a9ad4cbffc8203c0da2a1473c) 15 | pubkey = secp256k1.G * prikey 16 | 17 | # Hash of messages. 18 | with open('./secp256k1.py', 'rb') as f: 19 | m = int.from_bytes(hashlib.sha256(f.read()).digest(), 'little') 20 | m = secp256k1.Fr(m) 21 | print(f'hash={m}') 22 | 23 | # R = k ∗ G 24 | # e = hash(R || m) 25 | # s = k + e ∗ prikey 26 | k = secp256k1.Fr(random.randint(0, secp256k1.N)) 27 | R = secp256k1.G * k 28 | hasher = hashlib.sha256() 29 | hasher.update(R.x.x.to_bytes(32, 'little')) 30 | hasher.update(R.y.x.to_bytes(32, 'little')) 31 | hasher.update(m.x.to_bytes(32, 'little')) 32 | e = secp256k1.Fr(int.from_bytes(hasher.digest(), 'little')) 33 | s = k + e * prikey 34 | print(f'sign=(R={R}, s={s})') 35 | 36 | # s ∗ G =? R + hash(R || m) ∗ P 37 | verify = secp256k1.G * s == R + pubkey * e 38 | print(f'verify={verify}') 39 | -------------------------------------------------------------------------------- /secp256k1_sign.py: -------------------------------------------------------------------------------- 1 | import random 2 | import secp256k1 3 | 4 | # https://www.cs.miami.edu/home/burt/learning/Csc609.142/ecdsa-cert.pdf 5 | # Don Johnson, Alfred Menezes and Scott Vanstone, The Elliptic Curve Digital Signature Algorithm (ECDSA) 6 | # 7 ECDSA Signature Generation and Verification 7 | 8 | prikey = secp256k1.Fr(0x5f6717883bef25f45a129c11fcac1567d74bda5a9ad4cbffc8203c0da2a1473c) 9 | pubkey = secp256k1.G * prikey 10 | 11 | # Hash of messages. Generated by "sha256sum secp256k1.py" 12 | m = secp256k1.Fr(0x72a963cdfb01bc37cd283106875ff1f07f02bc9ad6121b75c3d17629df128d4e) 13 | print(f'hash={m}') 14 | 15 | # Sign 16 | while True: 17 | k = secp256k1.Fr(random.randint(0, secp256k1.N - 1)) 18 | R = secp256k1.G * k 19 | r = secp256k1.Fr(R.x.x) 20 | if r.x == 0: 21 | continue 22 | s = (m + prikey * r) / k 23 | if s.x == 0: 24 | continue 25 | print(f'sigr={r}') 26 | print(f'sigs={s}') 27 | break 28 | 29 | 30 | # Verify 31 | u1 = m / s 32 | u2 = r / s 33 | x = secp256k1.G * u1 + pubkey * u2 34 | assert x != secp256k1.I 35 | v = secp256k1.Fr(x.x.x) 36 | assert v == r 37 | print('pass') 38 | -------------------------------------------------------------------------------- /secp256r1.py: -------------------------------------------------------------------------------- 1 | class Fp: 2 | # Galois field. In mathematics, a finite field or Galois field is a field that contains a finite number of elements. 3 | # As with any field, a finite field is a set on which the operations of multiplication, addition, subtraction and 4 | # division are defined and satisfy certain basic rules. 5 | # 6 | # https://www.cs.miami.edu/home/burt/learning/Csc609.142/ecdsa-cert.pdf 7 | # Don Johnson, Alfred Menezes and Scott Vanstone, The Elliptic Curve Digital Signature Algorithm (ECDSA) 8 | # 3.1 The Finite Field Fp 9 | 10 | p = 0 11 | 12 | def __init__(self, x): 13 | self.x = x % self.p 14 | 15 | def __repr__(self): 16 | return f'Fp(0x{self.x:064x})' 17 | 18 | def __eq__(self, data): 19 | assert self.p == data.p 20 | return self.x == data.x 21 | 22 | def __add__(self, data): 23 | assert self.p == data.p 24 | return self.__class__((self.x + data.x) % self.p) 25 | 26 | def __sub__(self, data): 27 | assert self.p == data.p 28 | return self.__class__((self.x - data.x) % self.p) 29 | 30 | def __mul__(self, data): 31 | assert self.p == data.p 32 | return self.__class__((self.x * data.x) % self.p) 33 | 34 | def __truediv__(self, data): 35 | return self * data ** -1 36 | 37 | def __pow__(self, data): 38 | return self.__class__(pow(self.x, data, self.p)) 39 | 40 | def __pos__(self): 41 | return self 42 | 43 | def __neg__(self): 44 | return self.__class__(self.p - self.x) 45 | 46 | def sqrt(self): 47 | # https://www.staff.uni-mainz.de/pommeren/Cryptology/Asymmetric/5_NTh/SqRprim.pdf, 5.3 48 | # Note source number should be quadratic residued. 49 | if (self.p - 3) % 4 == 0: 50 | m = (self.p - 3) // 4 51 | return self ** (m + 1) 52 | raise Exception('unreachable') 53 | 54 | @classmethod 55 | def nil(cls): 56 | return cls(0) 57 | 58 | @classmethod 59 | def one(cls): 60 | return cls(1) 61 | 62 | 63 | if __name__ == '__main__': 64 | Fp.p = 23 65 | assert Fp(12) + Fp(20) == Fp(9) 66 | assert Fp(8) * Fp(9) == Fp(3) 67 | assert Fp(8) ** -1 == Fp(3) 68 | Fp.p = 0 69 | 70 | # Prime of finite field. 71 | P = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff 72 | # The order n of G. 73 | N = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 74 | 75 | 76 | class Fq(Fp): 77 | 78 | p = P 79 | 80 | def __repr__(self): 81 | return f'Fq(0x{self.x:064x})' 82 | 83 | 84 | class Fr(Fp): 85 | 86 | p = N 87 | 88 | def __repr__(self): 89 | return f'Fr(0x{self.x:064x})' 90 | 91 | 92 | A = Fq(0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc) 93 | B = Fq(0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b) 94 | 95 | 96 | class Pt: 97 | 98 | def __init__(self, x, y): 99 | if x != Fq(0) or y != Fq(0): 100 | assert y ** 2 == x ** 3 + A * x + B 101 | self.x = x 102 | self.y = y 103 | 104 | def __repr__(self): 105 | return f'Pt({self.x}, {self.y})' 106 | 107 | def __eq__(self, data): 108 | return self.x == data.x and self.y == data.y 109 | 110 | def __add__(self, data): 111 | # https://www.cs.miami.edu/home/burt/learning/Csc609.142/ecdsa-cert.pdf 112 | # Don Johnson, Alfred Menezes and Scott Vanstone, The Elliptic Curve Digital Signature Algorithm (ECDSA) 113 | # 4.1 Elliptic Curves Over Fp 114 | x1, x2 = self.x, data.x 115 | y1, y2 = self.y, data.y 116 | if x1 == Fq(0) and y1 == Fq(0): 117 | return data 118 | if x2 == Fq(0) and y2 == Fq(0): 119 | return self 120 | if x1 == x2 and y1 == +y2: 121 | sk = (x1 * x1 + x1 * x1 + x1 * x1 + A) / (y1 + y1) 122 | x3 = sk * sk - x1 - x2 123 | y3 = sk * (x1 - x3) - y1 124 | return Pt(x3, y3) 125 | if x1 == x2 and y1 == -y2: 126 | return I 127 | sk = (y2 - y1) / (x2 - x1) 128 | x3 = sk * sk - x1 - x2 129 | y3 = sk * (x1 - x3) - y1 130 | return Pt(x3, y3) 131 | 132 | def __sub__(self, data): 133 | return self + data.__neg__() 134 | 135 | def __mul__(self, k): 136 | # Point multiplication: Double-and-add 137 | # https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication 138 | n = k.x 139 | result = I 140 | addend = self 141 | while n: 142 | b = n & 1 143 | if b == 1: 144 | result += addend 145 | addend = addend + addend 146 | n = n >> 1 147 | return result 148 | 149 | def __truediv__(self, k): 150 | return self.__mul__(k ** -1) 151 | 152 | def __pos__(self): 153 | return self 154 | 155 | def __neg__(self): 156 | return Pt(self.x, -self.y) 157 | 158 | 159 | # Identity element 160 | I = Pt( 161 | Fq(0), 162 | Fq(0), 163 | ) 164 | # Generator point 165 | G = Pt( 166 | Fq(0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296), 167 | Fq(0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5), 168 | ) 169 | 170 | if __name__ == '__main__': 171 | p = G * Fr(42) 172 | q = G * Fr(24) 173 | r = Pt(p.x, -p.y) 174 | assert p + q == G * Fr(66) 175 | assert p + p == G * Fr(84) 176 | assert p - q == G * Fr(18) 177 | assert r == -p 178 | assert p + r == I 179 | assert p + I == p 180 | assert p * Fr(42) == G * Fr(1764) 181 | 182 | p = G * Fr(0x5f6717883bef25f45a129c11fcac1567d74bda5a9ad4cbffc8203c0da2a1473c) 183 | assert p.x.x == 0x63983e4c8002f443ccb58f7cd8232b75af26c432e30cb7584bed0dbc35bcf86a 184 | assert p.y.x == 0xcdc066239b4c9a967ffd2429d6ffe57850122163413348ba520726e5b08a9d79 185 | 186 | x = Fq(0x660fe3dd941bc58104fff3b424d82cd69658191f91166af80528e65d07cec0c0) 187 | assert x.sqrt() * x.sqrt() == x 188 | -------------------------------------------------------------------------------- /secp256r1_recovery_pubkey.py: -------------------------------------------------------------------------------- 1 | import random 2 | import secp256r1 3 | 4 | prikey = secp256r1.Fr(0x5f6717883bef25f45a129c11fcac1567d74bda5a9ad4cbffc8203c0da2a1473c) 5 | pubkey = secp256r1.G * prikey 6 | 7 | # Hash of messages. Generated by "sha256sum secp256r1.py" 8 | m = secp256r1.Fr(0x72a963cdfb01bc37cd283106875ff1f07f02bc9ad6121b75c3d17629df128d4e) 9 | print(f'hash={m}') 10 | 11 | # Sign 12 | while True: 13 | k = secp256r1.Fr(random.randint(0, secp256r1.N)) 14 | R = secp256r1.G * k 15 | r = secp256r1.Fr(R.x.x) 16 | if r.x == 0: 17 | continue 18 | s = (m + prikey * r) / k 19 | if s.x == 0: 20 | continue 21 | print(f'sigr={r}') 22 | print(f'sigs={s}') 23 | break 24 | 25 | # https://www.secg.org/sec1-v2.pdf, 4.1.6 26 | for j in range(2): 27 | x = r.x + secp256r1.N * j 28 | x = secp256r1.Fq(x) 29 | yy = x ** 3 + secp256r1.A * x + secp256r1.B 30 | y = yy.sqrt() 31 | if y ** 2 != yy: 32 | continue 33 | 34 | r0 = +secp256r1.Pt(x, y) 35 | r1 = -secp256r1.Pt(x, y) 36 | 37 | q0 = (r0 * s - secp256r1.G * m) * (r ** -1) 38 | q1 = (r1 * s - secp256r1.G * m) * (r ** -1) 39 | 40 | if q0 == pubkey or q1 == pubkey: 41 | print(f'nice') 42 | break 43 | -------------------------------------------------------------------------------- /segwit_addr.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017, 2020 Pieter Wuille 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | # 21 | # BIP-0173 https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki 22 | # BIP-0350 https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki 23 | # Copied from https://raw.githubusercontent.com/sipa/bech32/master/ref/python/segwit_addr.py 24 | 25 | """Reference implementation for Bech32/Bech32m and segwit addresses.""" 26 | 27 | 28 | from enum import Enum 29 | 30 | class Encoding(Enum): 31 | """Enumeration type to list the various supported encodings.""" 32 | BECH32 = 1 33 | BECH32M = 2 34 | 35 | CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" 36 | BECH32M_CONST = 0x2bc830a3 37 | 38 | def bech32_polymod(values): 39 | """Internal function that computes the Bech32 checksum.""" 40 | generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] 41 | chk = 1 42 | for value in values: 43 | top = chk >> 25 44 | chk = (chk & 0x1ffffff) << 5 ^ value 45 | for i in range(5): 46 | chk ^= generator[i] if ((top >> i) & 1) else 0 47 | return chk 48 | 49 | 50 | def bech32_hrp_expand(hrp): 51 | """Expand the HRP into values for checksum computation.""" 52 | return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp] 53 | 54 | 55 | def bech32_verify_checksum(hrp, data): 56 | """Verify a checksum given HRP and converted data characters.""" 57 | const = bech32_polymod(bech32_hrp_expand(hrp) + data) 58 | if const == 1: 59 | return Encoding.BECH32 60 | if const == BECH32M_CONST: 61 | return Encoding.BECH32M 62 | return None 63 | 64 | def bech32_create_checksum(hrp, data, spec): 65 | """Compute the checksum values given HRP and data.""" 66 | values = bech32_hrp_expand(hrp) + data 67 | const = BECH32M_CONST if spec == Encoding.BECH32M else 1 68 | polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const 69 | return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)] 70 | 71 | 72 | def bech32_encode(hrp, data, spec): 73 | """Compute a Bech32 string given HRP and data values.""" 74 | combined = data + bech32_create_checksum(hrp, data, spec) 75 | return hrp + '1' + ''.join([CHARSET[d] for d in combined]) 76 | 77 | def bech32_decode(bech): 78 | """Validate a Bech32/Bech32m string, and determine HRP and data.""" 79 | if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or 80 | (bech.lower() != bech and bech.upper() != bech)): 81 | return (None, None, None) 82 | bech = bech.lower() 83 | pos = bech.rfind('1') 84 | if pos < 1 or pos + 7 > len(bech) or len(bech) > 90: 85 | return (None, None, None) 86 | if not all(x in CHARSET for x in bech[pos+1:]): 87 | return (None, None, None) 88 | hrp = bech[:pos] 89 | data = [CHARSET.find(x) for x in bech[pos+1:]] 90 | spec = bech32_verify_checksum(hrp, data) 91 | if spec is None: 92 | return (None, None, None) 93 | return (hrp, data[:-6], spec) 94 | 95 | def convertbits(data, frombits, tobits, pad=True): 96 | """General power-of-2 base conversion.""" 97 | acc = 0 98 | bits = 0 99 | ret = [] 100 | maxv = (1 << tobits) - 1 101 | max_acc = (1 << (frombits + tobits - 1)) - 1 102 | for value in data: 103 | if value < 0 or (value >> frombits): 104 | return None 105 | acc = ((acc << frombits) | value) & max_acc 106 | bits += frombits 107 | while bits >= tobits: 108 | bits -= tobits 109 | ret.append((acc >> bits) & maxv) 110 | if pad: 111 | if bits: 112 | ret.append((acc << (tobits - bits)) & maxv) 113 | elif bits >= frombits or ((acc << (tobits - bits)) & maxv): 114 | return None 115 | return ret 116 | 117 | 118 | def decode(hrp, addr): 119 | """Decode a segwit address.""" 120 | hrpgot, data, spec = bech32_decode(addr) 121 | if hrpgot != hrp: 122 | return (None, None) 123 | decoded = convertbits(data[1:], 5, 8, False) 124 | if decoded is None or len(decoded) < 2 or len(decoded) > 40: 125 | return (None, None) 126 | if data[0] > 16: 127 | return (None, None) 128 | if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32: 129 | return (None, None) 130 | if data[0] == 0 and spec != Encoding.BECH32 or data[0] != 0 and spec != Encoding.BECH32M: 131 | return (None, None) 132 | return (data[0], decoded) 133 | 134 | 135 | def encode(hrp, witver, witprog): 136 | """Encode a segwit address.""" 137 | spec = Encoding.BECH32 if witver == 0 else Encoding.BECH32M 138 | ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5), spec) 139 | if decode(hrp, ret) == (None, None): 140 | return None 141 | return ret 142 | --------------------------------------------------------------------------------