From 70841df689348e7f52382688887ec92e9858a195 Mon Sep 17 00:00:00 2001 From: Jimmy Song Date: Fri, 5 Oct 2018 17:48:52 +0200 Subject: [PATCH] made code-ch05 to code-ch12 consistent --- code-ch04/helper.py | 6 - code-ch05/Programming Bitcoin Chapter 5.ipynb | 2 +- code-ch05/ecc.py | 253 +++++++--------- code-ch05/helper.py | 51 +--- code-ch05/tx.py | 16 + code-ch06/script.py | 89 ------ code-ch07/Programming Bitcoin Chapter 7.ipynb | 2 +- code-ch07/ecc.py | 274 ++++++++---------- code-ch07/helper.py | 90 ++---- code-ch08/ecc.py | 274 ++++++++---------- code-ch08/helper.py | 58 ++-- code-ch09/ecc.py | 274 ++++++++---------- code-ch09/helper.py | 61 ++-- code-ch10/ecc.py | 274 ++++++++---------- code-ch10/helper.py | 69 ++--- code-ch11/block.py | 1 - code-ch11/ecc.py | 274 ++++++++---------- code-ch11/helper.py | 112 ++++--- code-ch11/network.py | 36 ++- code-ch11/tx.py | 26 +- code-ch12/ecc.py | 274 ++++++++---------- code-ch12/helper.py | 114 +++++--- code-ch12/merkleblock.py | 5 +- code-ch12/network.py | 1 + code-ch12/tx.py | 5 +- 25 files changed, 1140 insertions(+), 1501 deletions(-) diff --git a/code-ch04/helper.py b/code-ch04/helper.py index 3ae913d8..12644649 100644 --- a/code-ch04/helper.py +++ b/code-ch04/helper.py @@ -70,12 +70,6 @@ def decode_base58(s): class HelperTest(TestCase): - def test_bytes(self): - b = b'hello world' - s = 'hello world' - self.assertEqual(b, str_to_bytes(s)) - self.assertEqual(s, bytes_to_str(b)) - def test_little_endian_to_int(self): h = bytes.fromhex('99c3980000000000') want = 10011545 diff --git a/code-ch05/Programming Bitcoin Chapter 5.ipynb b/code-ch05/Programming Bitcoin Chapter 5.ipynb index b143da76..409037cb 100644 --- a/code-ch05/Programming Bitcoin Chapter 5.ipynb +++ b/code-ch05/Programming Bitcoin Chapter 5.ipynb @@ -200,7 +200,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.5" + "version": "3.5.2" } }, "nbformat": 4, diff --git a/code-ch05/ecc.py b/code-ch05/ecc.py index d6cd5f9e..912954c7 100644 --- a/code-ch05/ecc.py +++ b/code-ch05/ecc.py @@ -2,18 +2,20 @@ from random import randint from unittest import TestCase +import hashlib + from helper import double_sha256, encode_base58, encode_base58_checksum, hash160 class FieldElement: def __init__(self, num, prime): + if num >= prime or num < 0: + error = 'Num {} not in field range 0 to {}'.format( + num, prime-1) + raise ValueError(error) self.num = num self.prime = prime - if self.num >= self.prime or self.num < 0: - error = 'Num {} not in field range 0 to {}'.format( - self.num, self.prime-1) - raise RuntimeError(error) def __eq__(self, other): if other is None: @@ -21,77 +23,77 @@ def __eq__(self, other): return self.num == other.num and self.prime == other.prime def __ne__(self, other): - if other is None: - return True - return self.num != other.num or self.prime != other.prime + # this should be the inverse of the == operator + return not (self == other) def __repr__(self): return 'FieldElement_{}({})'.format(self.prime, self.num) + def __add__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num + other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) def __sub__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num - other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) def __mul__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num * other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) - def __rmul__(self, coefficient): - num = (self.num * coefficient) % self.prime - return self.__class__(num=num, prime=self.prime) - - def __pow__(self, n): - # remember fermat's little theorem: - # self.num**(p-1) % p == 1 - # you might want to use % operator on n - prime = self.prime - num = pow(self.num, n % (prime-1), prime) - return self.__class__(num, prime) + def __pow__(self, exponent): + n = exponent % (self.prime - 1) + num = pow(self.num, n, self.prime) + return self.__class__(num, self.prime) def __truediv__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num * pow(other.num, self.prime - 2, self.prime)) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # use fermat's little theorem: # self.num**(p-1) % p == 1 # this means: # 1/n == pow(n, p-2, p) # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) + def __rmul__(self, coefficient): + num = (self.num * coefficient) % self.prime + return self.__class__(num=num, prime=self.prime) class FieldElementTest(TestCase): + def test_ne(self): + a = FieldElement(2, 31) + b = FieldElement(2, 31) + c = FieldElement(15, 31) + self.assertEqual(a, b) + self.assertTrue(a != c) + self.assertFalse(a != b) + def test_add(self): a = FieldElement(2, 31) b = FieldElement(15, 31) @@ -136,8 +138,8 @@ def test_div(self): self.assertEqual(a**-4*b, FieldElement(13, 31)) - class Point: + zero = 0 def __init__(self, x, y, a, b): self.a = a @@ -152,16 +154,16 @@ def __init__(self, x, y, a, b): # make sure that the elliptic curve equation is satisfied # y**2 == x**3 + a*x + b if self.y**2 != self.x**3 + a*x + b: - # if not, throw a RuntimeError - raise RuntimeError('({}, {}) is not on the curve'.format(self.x, self.y)) + # if not, throw a ValueError + raise ValueError('({}, {}) is not on the curve'.format(x, y)) def __eq__(self, other): return self.x == other.x and self.y == other.y \ and self.a == other.a and self.b == other.b def __ne__(self, other): - return self.x != other.x or self.y != other.y \ - or self.a != other.a or self.b != other.b + # this should be the inverse of the == operator + return not (self == other) def __repr__(self): if self.x is None: @@ -171,7 +173,7 @@ def __repr__(self): def __add__(self, other): if self.a != other.a or self.b != other.b: - raise RuntimeError('Points {}, {} are not on the same curve'.format(self, other)) + raise TypeError('Points {}, {} are not on the same curve'.format(self, other)) # Case 0.0: self is the point at infinity, return other if self.x is None: return other @@ -212,31 +214,32 @@ def __add__(self, other): # self.__class__(x, y, a, b) return self.__class__(x, y, self.a, self.b) + # Case 4: if we are tangent to the vertical line + if self == other and self.y == self.zero: + return self.__class__(None, None, self.a, self.b) + def __rmul__(self, coefficient): - # rmul calculates coefficient * self - # implement the naive way: - # start product from 0 (point at infinity) - # use: self.__class__(None, None, a, b) - product = self.__class__(None, None, self.a, self.b) - # loop coefficient times - # use: for _ in range(coefficient): - for _ in range(coefficient): - # keep adding self over and over - product += self - # return the product - return product - # Extra Credit: - # a more advanced technique uses point doubling - # find the binary representation of coefficient - # keep doubling the point and if the bit is there for coefficient - # add the current. - # remember to return an instance of the class + coef = coefficient + current = self + result = self.__class__(None, None, self.a, self.b) + while coef: + if coef & 1: + result += current + current += current + coef >>= 1 + return result class PointTest(TestCase): + def test_ne(self): + a = Point(x=3, y=-7, a=5, b=7) + b = Point(x=18, y=77, a=5, b=7) + self.assertTrue(a != b) + self.assertFalse(a != a) + def test_on_curve(self): - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): Point(x=-2, y=4, a=5, b=7) # these should not raise an error Point(x=3, y=-7, a=5, b=7) @@ -276,10 +279,6 @@ def test_on_curve(self): # iterate over valid points for x_raw, y_raw in valid_points: - # Initialize points this way: - # x = FieldElement(x_raw, prime) - # y = FieldElement(y_raw, prime) - # Point(x, y, a, b) x = FieldElement(x_raw, prime) y = FieldElement(y_raw, prime) # Creating the point should not result in an error @@ -287,19 +286,12 @@ def test_on_curve(self): # iterate over invalid points for x_raw, y_raw in invalid_points: - # Initialize points this way: - # x = FieldElement(x_raw, prime) - # y = FieldElement(y_raw, prime) - # Point(x, y, a, b) x = FieldElement(x_raw, prime) y = FieldElement(y_raw, prime) - # check that creating the point results in a RuntimeError - # with self.assertRaises(RuntimeError): - # Point(x, y, a, b) - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): Point(x, y, a, b) - def test_add1(self): + def test_add(self): # tests the following additions on curve y^2=x^3-7 over F_223: # (192,105) + (17,56) # (47,71) + (117,141) @@ -316,16 +308,6 @@ def test_add1(self): ) # iterate over the additions for x1_raw, y1_raw, x2_raw, y2_raw, x3_raw, y3_raw in additions: - # Initialize points this way: - # x1 = FieldElement(x1_raw, prime) - # y1 = FieldElement(y1_raw, prime) - # p1 = Point(x1, y1, a, b) - # x2 = FieldElement(x2_raw, prime) - # y2 = FieldElement(y2_raw, prime) - # p2 = Point(x2, y2, a, b) - # x3 = FieldElement(x3_raw, prime) - # y3 = FieldElement(y3_raw, prime) - # p3 = Point(x3, y3, a, b) x1 = FieldElement(x1_raw, prime) y1 = FieldElement(y1_raw, prime) p1 = Point(x1, y1, a, b) @@ -338,6 +320,7 @@ def test_add1(self): # check that p1 + p2 == p3 self.assertEqual(p1+p2, p3) + def test_rmul(self): # tests the following scalar multiplications # 2*(192,105) @@ -362,17 +345,10 @@ def test_rmul(self): # iterate over the multiplications for s, x1_raw, y1_raw, x2_raw, y2_raw in multiplications: - # Initialize points this way: - # x1 = FieldElement(x1_raw, prime) - # y1 = FieldElement(y1_raw, prime) - # p1 = Point(x1, y1, a, b) x1 = FieldElement(x1_raw, prime) y1 = FieldElement(y1_raw, prime) p1 = Point(x1, y1, a, b) # initialize the second point based on whether it's the point at infinity - # x2 = FieldElement(x2_raw, prime) - # y2 = FieldElement(y2_raw, prime) - # p2 = Point(x2, y2, a, b) if x2_raw is None: p2 = Point(None, None, a, b) else: @@ -406,7 +382,8 @@ def sqrt(self): class S256Point(Point): - bits = 256 + + zero = S256Field(0) def __init__(self, x, y, a=None, b=None): a, b = S256Field(A), S256Field(B) @@ -419,26 +396,20 @@ def __init__(self, x, y, a=None, b=None): def __repr__(self): if self.x is None: - return 'Point(infinity)' + return 'S256Point(infinity)' else: - return 'Point({},{})_{}'.format(self.x.num, self.y.num, self.x.prime) + return 'S256Point({},{})'.format(self.x, self.y) def __rmul__(self, coefficient): - # we want to mod by N to make this simple coef = coefficient % N - # current will undergo binary expansion - current = self - # result is what we return, starts at 0 - result = S256Point(None, None) - # we double 256 times and add where there is a 1 in the binary - # representation of coefficient - for i in range(self.bits): - if coef & 1: - result += current - current += current - # we shift the coefficient to the right - coef >>= 1 - return result + return super().__rmul__(coef) + + def verify(self, z, sig): + s_inv = pow(sig.s, N-2, N) + u = z * s_inv % N + v = sig.r * s_inv % N + total = u*G + v*self + return total.x.num == sig.r def sec(self, compressed=True): # returns the binary version of the sec format, NOT hex @@ -454,36 +425,17 @@ def sec(self, compressed=True): # if non-compressed, starts with b'\x04' followod by self.x and then self.y return b'\x04' + self.x.num.to_bytes(32, 'big') + self.y.num.to_bytes(32, 'big') + def h160(self, compressed=True): + return hash160(self.sec(compressed)) + def address(self, compressed=True, testnet=False): '''Returns the address string''' - # get the sec - sec = self.sec(compressed) - # hash160 the sec - h160 = hash160(sec) - # raw is hash 160 prepended w/ b'\x00' for mainnet, b'\x6f' for testnet + h160 = self.h160(compressed) if testnet: prefix = b'\x6f' else: prefix = b'\x00' - raw = prefix + h160 - # checksum is first 4 bytes of double_sha256 of raw - checksum = double_sha256(raw)[:4] - # encode_base58 the raw + checksum - address = encode_base58(raw+checksum) - # return as a string, you can use .decode('ascii') to do this. - return address.decode('ascii') - - def verify(self, z, sig): - # remember sig.r and sig.s are the main things we're checking - # remember 1/s = pow(s, N-2, N) - s_inv = pow(sig.s, N-2, N) - # u = z / s - u = z * s_inv % N - # v = r / s - v = sig.r * s_inv % N - # u*G + v*P should have as the x coordinate, r - total = u*G + v*self - return total.x.num == sig.r + return encode_base58_checksum(prefix + h160) @classmethod def parse(self, sec_bin): @@ -539,6 +491,19 @@ def test_pubpoint(self): # check that the secret*G is the same as the point self.assertEqual(secret*G, point) + def test_verify(self): + point = S256Point( + 0x887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e4da568744d06c, + 0x61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34) + z = 0xec208baa0fc1c19f708a9ca96fdeff3ac3f230bb4a7ba4aede4942ad003c0f60 + r = 0xac8d1c87e51d0d441be8b3dd5b05c8795b48875dffe00b7ffcfac23010d3a395 + s = 0x68342ceff8935ededd102dd876ffd6ba72d6a427a3edb13d26eb0781cb423c4 + self.assertTrue(point.verify(z, Signature(r, s))) + z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d + r = 0xeff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c + s = 0xc7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6 + self.assertTrue(point.verify(z, Signature(r, s))) + def test_sec(self): coefficient = 999**3 uncompressed = '049d5ca49670cbe4c3bfa84c96a8c87df086c6ea6a24ba6b809c9de234496808d56fa15cc7f3d38cda98dee2419f415b7513dde1301f8643cd9245aea7f3f911f9' @@ -585,19 +550,6 @@ def test_address(self): self.assertEqual( point.address(compressed=False, testnet=True), testnet_address) - def test_verify(self): - point = S256Point( - 0x887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e4da568744d06c, - 0x61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34) - z = 0xec208baa0fc1c19f708a9ca96fdeff3ac3f230bb4a7ba4aede4942ad003c0f60 - r = 0xac8d1c87e51d0d441be8b3dd5b05c8795b48875dffe00b7ffcfac23010d3a395 - s = 0x68342ceff8935ededd102dd876ffd6ba72d6a427a3edb13d26eb0781cb423c4 - self.assertTrue(point.verify(z, Signature(r, s))) - z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d - r = 0xeff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c - s = 0xc7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6 - self.assertTrue(point.verify(z, Signature(r, s))) - class Signature: @@ -672,8 +624,7 @@ def hex(self): return '{:x}'.format(self.secret).zfill(64) def sign(self, z): - # we need a random number k: randint(0, 2**256) - k = randint(0, 2**256) + k = self.deterministic_k(z) # r is the x coordinate of the resulting point k*G r = (k*G).x.num # remember 1/k = pow(k, N-2, N) @@ -686,6 +637,26 @@ def sign(self, z): # Signature(r, s) return Signature(r, s) + def deterministic_k(self, z): + k = b'\x00' * 32 + v = b'\x01' * 32 + if z > N: + z -= N + z_bytes = z.to_bytes(32, 'big') + secret_bytes = self.secret.to_bytes(32, 'big') + s256 = hashlib.sha256 + k = hmac.new(k, v + b'\x00' + secret_bytes + z_bytes, s256).digest() + v = hmac.new(k, v, s256).digest() + k = hmac.new(k, v + b'\x01' + secret_bytes + z_bytes, s256).digest() + v = hmac.new(k, v, s256).digest() + while True: + v = hmac.new(k, v, s256).digest() + candidate = int.from_bytes(v, 'big') + if candidate >= 1 and candidate < N: + return candidate + k = hmac.new(k, v + b'\x00', s256).digest() + v = hmac.new(k, v, s256).digest() + def wif(self, compressed=True, testnet=False): # convert the secret from integer to a 32-bytes in big endian using num.to_bytes(32, 'big') secret_bytes = self.secret.to_bytes(32, 'big') diff --git a/code-ch05/helper.py b/code-ch05/helper.py index 9e7f36fc..d9acbdb8 100644 --- a/code-ch05/helper.py +++ b/code-ch05/helper.py @@ -12,34 +12,6 @@ def run_test(test): TextTestRunner().run(suite) -def bytes_to_str(b, encoding='ascii'): - '''Returns a string version of the bytes''' - return b.decode(encoding) - - -def str_to_bytes(s, encoding='ascii'): - '''Returns a bytes version of the string''' - return s.encode(encoding) - - -def little_endian_to_int(b): - '''little_endian_to_int takes byte sequence as a little-endian number. - Returns an integer''' - # use the from_bytes method of int - return int.from_bytes(b, 'little') - - -def int_to_little_endian(n, length): - '''endian_to_little_endian takes an integer and returns the little-endian - byte sequence of length''' - # use the to_bytes method of n - return n.to_bytes(length, 'little') - - -def hash160(s): - return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest() - - def double_sha256(s): return hashlib.sha256(hashlib.sha256(s).digest()).digest() @@ -59,7 +31,6 @@ def encode_base58(s): while num > 0: num, mod = divmod(num, 58) result.insert(0, BASE58_ALPHABET[mod]) - return prefix + bytes(result) @@ -67,6 +38,22 @@ def encode_base58_checksum(s): return encode_base58(s + double_sha256(s)[:4]).decode('ascii') +def hash160(s): + return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest() + + +def little_endian_to_int(b): + '''little_endian_to_int takes byte sequence as a little-endian number. + Returns an integer''' + return int.from_bytes(b, 'little') + + +def int_to_little_endian(n, length): + '''endian_to_little_endian takes an integer and returns the little-endian + byte sequence of length''' + return n.to_bytes(length, 'little') + + def decode_base58(s): num = 0 for c in s.encode('ascii'): @@ -112,12 +99,6 @@ def encode_varint(i): class HelperTest(TestCase): - def test_bytes(self): - b = b'hello world' - s = 'hello world' - self.assertEqual(b, str_to_bytes(s)) - self.assertEqual(s, bytes_to_str(b)) - def test_little_endian_to_int(self): h = bytes.fromhex('99c3980000000000') want = 10011545 diff --git a/code-ch05/tx.py b/code-ch05/tx.py index c7131b49..cf292900 100644 --- a/code-ch05/tx.py +++ b/code-ch05/tx.py @@ -168,6 +168,22 @@ def parse(cls, s): ''' raise NotImplementedError + def serialize(self): + '''Returns the byte serialization of the transaction input''' + # serialize prev_tx, little endian + result = self.prev_tx[::-1] + # serialize prev_index, 4 bytes, little endian + result += int_to_little_endian(self.prev_index, 4) + # get the scriptSig ready (use self.script_sig.serialize()) + raw_script_sig = self.script_sig.serialize() + # encode_varint on the length of the scriptSig + result += encode_varint(len(raw_script_sig)) + # add the scriptSig + result += raw_script_sig + # serialize sequence, 4 bytes, little endian + result += int_to_little_endian(self.sequence, 4) + return result + class TxTest(TestCase): diff --git a/code-ch06/script.py b/code-ch06/script.py index ea9a589b..de8665c8 100644 --- a/code-ch06/script.py +++ b/code-ch06/script.py @@ -31,44 +31,6 @@ def parse(cls, binary): current = s.read(1) return cls(elements) - def type(self): - '''Some standard pay-to type scripts.''' - if len(self.elements) == 0: - return 'blank' - elif self.elements[0] == 0x76 \ - and self.elements[1] == 0xa9 \ - and type(self.elements[2]) == bytes \ - and len(self.elements[2]) == 0x14 \ - and self.elements[3] == 0x88 \ - and self.elements[4] == 0xac: - # p2pkh: - # OP_DUP OP_HASH160 <20-byte hash> - return 'p2pkh' - elif self.elements[0] == 0xa9 \ - and type(self.elements[1]) == bytes \ - and len(self.elements[1]) == 0x14 \ - and self.elements[-1] == 0x87: - # p2sh: - # OP_HASH160 <20-byte hash> - return 'p2sh' - elif type(self.elements[0]) == bytes \ - and len(self.elements[0]) in (0x47, 0x48, 0x49) \ - and type(self.elements[1]) == bytes \ - and len(self.elements[1]) in (0x21, 0x41): - # p2pkh scriptSig: - # - return 'p2pkh sig' - elif len(self.elements) > 1 \ - and type(self.elements[1]) == bytes \ - and len(self.elements[1]) in (0x47, 0x48, 0x49) \ - and self.elements[-1][-1] == 0xae: - # HACK: assumes p2sh is a multisig - # p2sh multisig: - # ... - return 'p2sh sig' - else: - return 'unknown' - def serialize(self): result = b'' for element in self.elements: @@ -78,57 +40,6 @@ def serialize(self): result += bytes([len(element)]) + element return result - def signature(self, index=0): - '''index isn't used for p2pkh, for p2sh, means one of m sigs''' - sig_type = self.type() - if sig_type == 'p2pkh sig': - return self.elements[0] - elif sig_type == 'p2sh sig': - return self.elements[index+1] - else: - raise RuntimeError('script type needs to be p2pkh sig or p2sh sig') - - def sec_pubkey(self, index=0): - '''index isn't used for p2pkh, for p2sh, means one of n pubkeys''' - sig_type = self.type() - if sig_type == 'p2pkh sig': - return self.elements[1] - elif sig_type == 'p2sh sig': - # HACK: assumes p2sh is a multisig - redeem_script = Script.parse(self.elements[-1]) - return redeem_script.elements[index+1] - - -class ScriptTest(TestCase): - - def test_p2pkh(self): - script_pubkey_raw = bytes.fromhex('76a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac') - script_pubkey = Script.parse(script_pubkey_raw) - self.assertEqual(script_pubkey.type(), 'p2pkh') - self.assertEqual(script_pubkey.serialize(), script_pubkey_raw) - - script_sig_raw = bytes.fromhex('483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278a') - script_sig = Script.parse(script_sig_raw) - self.assertEqual(script_sig.type(), 'p2pkh sig') - self.assertEqual(script_sig.serialize(), script_sig_raw) - self.assertEqual(script_sig.signature(), bytes.fromhex('3045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01')) - self.assertEqual(script_sig.sec_pubkey(), bytes.fromhex('0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278a')) - - def test_p2sh(self): - script_pubkey_raw = bytes.fromhex('a91474d691da1574e6b3c192ecfb52cc8984ee7b6c5687') - script_pubkey = Script.parse(script_pubkey_raw) - self.assertEqual(script_pubkey.type(), 'p2sh') - self.assertEqual(script_pubkey.serialize(), script_pubkey_raw) - - script_sig_raw = bytes.fromhex('00483045022100dc92655fe37036f47756db8102e0d7d5e28b3beb83a8fef4f5dc0559bddfb94e02205a36d4e4e6c7fcd16658c50783e00c341609977aed3ad00937bf4ee942a8993701483045022100da6bee3c93766232079a01639d07fa869598749729ae323eab8eef53577d611b02207bef15429dcadce2121ea07f233115c6f09034c0be68db99980b9a6c5e75402201475221022626e955ea6ea6d98850c994f9107b036b1334f18ca8830bfff1295d21cfdb702103b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7fbdbd4bb7152ae') - script_sig = Script.parse(script_sig_raw) - self.assertEqual(script_sig.type(), 'p2sh sig') - self.assertEqual(script_sig.serialize(), script_sig_raw) - self.assertEqual(script_sig.signature(index=0), bytes.fromhex('3045022100dc92655fe37036f47756db8102e0d7d5e28b3beb83a8fef4f5dc0559bddfb94e02205a36d4e4e6c7fcd16658c50783e00c341609977aed3ad00937bf4ee942a8993701')) - self.assertEqual(script_sig.signature(index=1), bytes.fromhex('3045022100da6bee3c93766232079a01639d07fa869598749729ae323eab8eef53577d611b02207bef15429dcadce2121ea07f233115c6f09034c0be68db99980b9a6c5e75402201')) - self.assertEqual(script_sig.sec_pubkey(index=0), bytes.fromhex('022626e955ea6ea6d98850c994f9107b036b1334f18ca8830bfff1295d21cfdb70')) - self.assertEqual(script_sig.sec_pubkey(index=1), bytes.fromhex('03b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7fbdbd4bb71')) - OP_CODES = { 0: 'OP_0', diff --git a/code-ch07/Programming Bitcoin Chapter 7.ipynb b/code-ch07/Programming Bitcoin Chapter 7.ipynb index 091ced1e..f8c7bf71 100644 --- a/code-ch07/Programming Bitcoin Chapter 7.ipynb +++ b/code-ch07/Programming Bitcoin Chapter 7.ipynb @@ -272,7 +272,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.5" + "version": "3.5.2" } }, "nbformat": 4, diff --git a/code-ch07/ecc.py b/code-ch07/ecc.py index 3ae8a766..208229fe 100644 --- a/code-ch07/ecc.py +++ b/code-ch07/ecc.py @@ -2,18 +2,20 @@ from random import randint from unittest import TestCase +import hashlib + from helper import double_sha256, encode_base58, encode_base58_checksum, hash160 class FieldElement: def __init__(self, num, prime): + if num >= prime or num < 0: + error = 'Num {} not in field range 0 to {}'.format( + num, prime-1) + raise ValueError(error) self.num = num self.prime = prime - if self.num >= self.prime or self.num < 0: - error = 'Num {} not in field range 0 to {}'.format( - self.num, self.prime-1) - raise RuntimeError(error) def __eq__(self, other): if other is None: @@ -21,77 +23,77 @@ def __eq__(self, other): return self.num == other.num and self.prime == other.prime def __ne__(self, other): - if other is None: - return True - return self.num != other.num or self.prime != other.prime + # this should be the inverse of the == operator + return not (self == other) def __repr__(self): return 'FieldElement_{}({})'.format(self.prime, self.num) + def __add__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num + other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) def __sub__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num - other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) def __mul__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num * other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) - def __rmul__(self, coefficient): - num = (self.num * coefficient) % self.prime - return self.__class__(num=num, prime=self.prime) - - def __pow__(self, n): - # remember fermat's little theorem: - # self.num**(p-1) % p == 1 - # you might want to use % operator on n - prime = self.prime - num = pow(self.num, n % (prime-1), prime) - return self.__class__(num, prime) + def __pow__(self, exponent): + n = exponent % (self.prime - 1) + num = pow(self.num, n, self.prime) + return self.__class__(num, self.prime) def __truediv__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num * pow(other.num, self.prime - 2, self.prime)) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # use fermat's little theorem: # self.num**(p-1) % p == 1 # this means: # 1/n == pow(n, p-2, p) # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) + def __rmul__(self, coefficient): + num = (self.num * coefficient) % self.prime + return self.__class__(num=num, prime=self.prime) class FieldElementTest(TestCase): + def test_ne(self): + a = FieldElement(2, 31) + b = FieldElement(2, 31) + c = FieldElement(15, 31) + self.assertEqual(a, b) + self.assertTrue(a != c) + self.assertFalse(a != b) + def test_add(self): a = FieldElement(2, 31) b = FieldElement(15, 31) @@ -136,8 +138,8 @@ def test_div(self): self.assertEqual(a**-4*b, FieldElement(13, 31)) - class Point: + zero = 0 def __init__(self, x, y, a, b): self.a = a @@ -152,16 +154,16 @@ def __init__(self, x, y, a, b): # make sure that the elliptic curve equation is satisfied # y**2 == x**3 + a*x + b if self.y**2 != self.x**3 + a*x + b: - # if not, throw a RuntimeError - raise RuntimeError('({}, {}) is not on the curve'.format(self.x, self.y)) + # if not, throw a ValueError + raise ValueError('({}, {}) is not on the curve'.format(x, y)) def __eq__(self, other): return self.x == other.x and self.y == other.y \ and self.a == other.a and self.b == other.b def __ne__(self, other): - return self.x != other.x or self.y != other.y \ - or self.a != other.a or self.b != other.b + # this should be the inverse of the == operator + return not (self == other) def __repr__(self): if self.x is None: @@ -171,7 +173,7 @@ def __repr__(self): def __add__(self, other): if self.a != other.a or self.b != other.b: - raise RuntimeError('Points {}, {} are not on the same curve'.format(self, other)) + raise TypeError('Points {}, {} are not on the same curve'.format(self, other)) # Case 0.0: self is the point at infinity, return other if self.x is None: return other @@ -185,7 +187,7 @@ def __add__(self, other): # Remember to return an instance of this class: # self.__class__(x, y, a, b) return self.__class__(None, None, self.a, self.b) - + # Case 2: self.x != other.x if self.x != other.x: # Formula (x3,y3)==(x1,y1)+(x2,y2) @@ -212,31 +214,32 @@ def __add__(self, other): # self.__class__(x, y, a, b) return self.__class__(x, y, self.a, self.b) + # Case 4: if we are tangent to the vertical line + if self == other and self.y == self.zero: + return self.__class__(None, None, self.a, self.b) + def __rmul__(self, coefficient): - # rmul calculates coefficient * self - # implement the naive way: - # start product from 0 (point at infinity) - # use: self.__class__(None, None, a, b) - product = self.__class__(None, None, self.a, self.b) - # loop coefficient times - # use: for _ in range(coefficient): - for _ in range(coefficient): - # keep adding self over and over - product += self - # return the product - return product - # Extra Credit: - # a more advanced technique uses point doubling - # find the binary representation of coefficient - # keep doubling the point and if the bit is there for coefficient - # add the current. - # remember to return an instance of the class + coef = coefficient + current = self + result = self.__class__(None, None, self.a, self.b) + while coef: + if coef & 1: + result += current + current += current + coef >>= 1 + return result class PointTest(TestCase): + def test_ne(self): + a = Point(x=3, y=-7, a=5, b=7) + b = Point(x=18, y=77, a=5, b=7) + self.assertTrue(a != b) + self.assertFalse(a != a) + def test_on_curve(self): - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): Point(x=-2, y=4, a=5, b=7) # these should not raise an error Point(x=3, y=-7, a=5, b=7) @@ -249,7 +252,7 @@ def test_add0(self): self.assertEqual(a+b, b) self.assertEqual(b+a, b) self.assertEqual(b+c, a) - + def test_add1(self): a = Point(x=3, y=7, a=5, b=7) b = Point(x=-1, y=-1, a=5, b=7) @@ -270,16 +273,12 @@ def test_on_curve(self): prime = 223 a = FieldElement(0, prime) b = FieldElement(7, prime) - + valid_points = ((192,105), (17,56), (1,193)) invalid_points = ((200,119), (42,99)) - + # iterate over valid points for x_raw, y_raw in valid_points: - # Initialize points this way: - # x = FieldElement(x_raw, prime) - # y = FieldElement(y_raw, prime) - # Point(x, y, a, b) x = FieldElement(x_raw, prime) y = FieldElement(y_raw, prime) # Creating the point should not result in an error @@ -287,19 +286,12 @@ def test_on_curve(self): # iterate over invalid points for x_raw, y_raw in invalid_points: - # Initialize points this way: - # x = FieldElement(x_raw, prime) - # y = FieldElement(y_raw, prime) - # Point(x, y, a, b) x = FieldElement(x_raw, prime) y = FieldElement(y_raw, prime) - # check that creating the point results in a RuntimeError - # with self.assertRaises(RuntimeError): - # Point(x, y, a, b) - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): Point(x, y, a, b) - def test_add1(self): + def test_add(self): # tests the following additions on curve y^2=x^3-7 over F_223: # (192,105) + (17,56) # (47,71) + (117,141) @@ -309,23 +301,13 @@ def test_add1(self): b = FieldElement(7, prime) additions = ( - # (x1, y1, x2, y2, x3, y3) + # (x1, y1, x2, y2, x3, y3) (192, 105, 17, 56, 170, 142), (47, 71, 117, 141, 60, 139), (143, 98, 76, 66, 47, 71), ) # iterate over the additions for x1_raw, y1_raw, x2_raw, y2_raw, x3_raw, y3_raw in additions: - # Initialize points this way: - # x1 = FieldElement(x1_raw, prime) - # y1 = FieldElement(y1_raw, prime) - # p1 = Point(x1, y1, a, b) - # x2 = FieldElement(x2_raw, prime) - # y2 = FieldElement(y2_raw, prime) - # p2 = Point(x2, y2, a, b) - # x3 = FieldElement(x3_raw, prime) - # y3 = FieldElement(y3_raw, prime) - # p3 = Point(x3, y3, a, b) x1 = FieldElement(x1_raw, prime) y1 = FieldElement(y1_raw, prime) p1 = Point(x1, y1, a, b) @@ -338,6 +320,7 @@ def test_add1(self): # check that p1 + p2 == p3 self.assertEqual(p1+p2, p3) + def test_rmul(self): # tests the following scalar multiplications # 2*(192,105) @@ -362,26 +345,19 @@ def test_rmul(self): # iterate over the multiplications for s, x1_raw, y1_raw, x2_raw, y2_raw in multiplications: - # Initialize points this way: - # x1 = FieldElement(x1_raw, prime) - # y1 = FieldElement(y1_raw, prime) - # p1 = Point(x1, y1, a, b) x1 = FieldElement(x1_raw, prime) y1 = FieldElement(y1_raw, prime) p1 = Point(x1, y1, a, b) # initialize the second point based on whether it's the point at infinity - # x2 = FieldElement(x2_raw, prime) - # y2 = FieldElement(y2_raw, prime) - # p2 = Point(x2, y2, a, b) if x2_raw is None: p2 = Point(None, None, a, b) else: x2 = FieldElement(x2_raw, prime) y2 = FieldElement(y2_raw, prime) p2 = Point(x2, y2, a, b) - + # check that the product is equal to the expected point - self.assertEqual(s*p1, p2) + self.assertEqual(s*p1, p2) A = 0 @@ -406,7 +382,8 @@ def sqrt(self): class S256Point(Point): - bits = 256 + + zero = S256Field(0) def __init__(self, x, y, a=None, b=None): a, b = S256Field(A), S256Field(B) @@ -419,26 +396,20 @@ def __init__(self, x, y, a=None, b=None): def __repr__(self): if self.x is None: - return 'Point(infinity)' + return 'S256Point(infinity)' else: - return 'Point({},{})_{}'.format(self.x.num, self.y.num, self.x.prime) + return 'S256Point({},{})'.format(self.x, self.y) def __rmul__(self, coefficient): - # we want to mod by N to make this simple coef = coefficient % N - # current will undergo binary expansion - current = self - # result is what we return, starts at 0 - result = S256Point(None, None) - # we double 256 times and add where there is a 1 in the binary - # representation of coefficient - for i in range(self.bits): - if coef & 1: - result += current - current += current - # we shift the coefficient to the right - coef >>= 1 - return result + return super().__rmul__(coef) + + def verify(self, z, sig): + s_inv = pow(sig.s, N-2, N) + u = z * s_inv % N + v = sig.r * s_inv % N + total = u*G + v*self + return total.x.num == sig.r def sec(self, compressed=True): # returns the binary version of the sec format, NOT hex @@ -454,36 +425,17 @@ def sec(self, compressed=True): # if non-compressed, starts with b'\x04' followod by self.x and then self.y return b'\x04' + self.x.num.to_bytes(32, 'big') + self.y.num.to_bytes(32, 'big') + def h160(self, compressed=True): + return hash160(self.sec(compressed)) + def address(self, compressed=True, testnet=False): '''Returns the address string''' - # get the sec - sec = self.sec(compressed) - # hash160 the sec - h160 = hash160(sec) - # raw is hash 160 prepended w/ b'\x00' for mainnet, b'\x6f' for testnet + h160 = self.h160(compressed) if testnet: prefix = b'\x6f' else: prefix = b'\x00' - raw = prefix + h160 - # checksum is first 4 bytes of double_sha256 of raw - checksum = double_sha256(raw)[:4] - # encode_base58 the raw + checksum - address = encode_base58(raw+checksum) - # return as a string, you can use .decode('ascii') to do this. - return address.decode('ascii') - - def verify(self, z, sig): - # remember sig.r and sig.s are the main things we're checking - # remember 1/s = pow(s, N-2, N) - s_inv = pow(sig.s, N-2, N) - # u = z / s - u = z * s_inv % N - # v = r / s - v = sig.r * s_inv % N - # u*G + v*P should have as the x coordinate, r - total = u*G + v*self - return total.x.num == sig.r + return encode_base58_checksum(prefix + h160) @classmethod def parse(self, sec_bin): @@ -539,6 +491,19 @@ def test_pubpoint(self): # check that the secret*G is the same as the point self.assertEqual(secret*G, point) + def test_verify(self): + point = S256Point( + 0x887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e4da568744d06c, + 0x61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34) + z = 0xec208baa0fc1c19f708a9ca96fdeff3ac3f230bb4a7ba4aede4942ad003c0f60 + r = 0xac8d1c87e51d0d441be8b3dd5b05c8795b48875dffe00b7ffcfac23010d3a395 + s = 0x68342ceff8935ededd102dd876ffd6ba72d6a427a3edb13d26eb0781cb423c4 + self.assertTrue(point.verify(z, Signature(r, s))) + z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d + r = 0xeff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c + s = 0xc7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6 + self.assertTrue(point.verify(z, Signature(r, s))) + def test_sec(self): coefficient = 999**3 uncompressed = '049d5ca49670cbe4c3bfa84c96a8c87df086c6ea6a24ba6b809c9de234496808d56fa15cc7f3d38cda98dee2419f415b7513dde1301f8643cd9245aea7f3f911f9' @@ -585,26 +550,6 @@ def test_address(self): self.assertEqual( point.address(compressed=False, testnet=True), testnet_address) - def test_verify(self): - point = S256Point( - 0x887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e4da568744d06c, - 0x61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34) - z = 0xec208baa0fc1c19f708a9ca96fdeff3ac3f230bb4a7ba4aede4942ad003c0f60 - r = 0xac8d1c87e51d0d441be8b3dd5b05c8795b48875dffe00b7ffcfac23010d3a395 - s = 0x68342ceff8935ededd102dd876ffd6ba72d6a427a3edb13d26eb0781cb423c4 - self.assertTrue(point.verify(z, Signature(r, s))) - z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d - r = 0xeff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c - s = 0xc7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6 - self.assertTrue(point.verify(z, Signature(r, s))) - - def test_parse(self): - sec = bytes.fromhex('0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278a') - point = S256Point.parse(sec) - want = 0xa56c896489c71dfc65701ce25050f542f336893fb8cd15f4e8e5c124dbf58e47 - self.assertEqual(point.y.num, want) - - class Signature: @@ -679,8 +624,7 @@ def hex(self): return '{:x}'.format(self.secret).zfill(64) def sign(self, z): - # we need a random number k: randint(0, 2**256) - k = randint(0, 2**256) + k = self.deterministic_k(z) # r is the x coordinate of the resulting point k*G r = (k*G).x.num # remember 1/k = pow(k, N-2, N) @@ -693,6 +637,26 @@ def sign(self, z): # Signature(r, s) return Signature(r, s) + def deterministic_k(self, z): + k = b'\x00' * 32 + v = b'\x01' * 32 + if z > N: + z -= N + z_bytes = z.to_bytes(32, 'big') + secret_bytes = self.secret.to_bytes(32, 'big') + s256 = hashlib.sha256 + k = hmac.new(k, v + b'\x00' + secret_bytes + z_bytes, s256).digest() + v = hmac.new(k, v, s256).digest() + k = hmac.new(k, v + b'\x01' + secret_bytes + z_bytes, s256).digest() + v = hmac.new(k, v, s256).digest() + while True: + v = hmac.new(k, v, s256).digest() + candidate = int.from_bytes(v, 'big') + if candidate >= 1 and candidate < N: + return candidate + k = hmac.new(k, v + b'\x00', s256).digest() + v = hmac.new(k, v, s256).digest() + def wif(self, compressed=True, testnet=False): # convert the secret from integer to a 32-bytes in big endian using num.to_bytes(32, 'big') secret_bytes = self.secret.to_bytes(32, 'big') diff --git a/code-ch07/helper.py b/code-ch07/helper.py index 3e74a353..bc77a9f1 100644 --- a/code-ch07/helper.py +++ b/code-ch07/helper.py @@ -1,4 +1,3 @@ -from subprocess import check_output from unittest import TestCase, TestSuite, TextTestRunner import hashlib @@ -16,34 +15,6 @@ def run_test(test): TextTestRunner().run(suite) -def bytes_to_str(b, encoding='ascii'): - '''Returns a string version of the bytes''' - return b.decode(encoding) - - -def str_to_bytes(s, encoding='ascii'): - '''Returns a bytes version of the string''' - return s.encode(encoding) - - -def little_endian_to_int(b): - '''little_endian_to_int takes byte sequence as a little-endian number. - Returns an integer''' - # use the from_bytes method of int - return int.from_bytes(b, 'little') - - -def int_to_little_endian(n, length): - '''endian_to_little_endian takes an integer and returns the little-endian - byte sequence of length''' - # use the to_bytes method of n - return n.to_bytes(length, 'little') - - -def hash160(s): - return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest() - - def double_sha256(s): return hashlib.sha256(hashlib.sha256(s).digest()).digest() @@ -63,7 +34,6 @@ def encode_base58(s): while num > 0: num, mod = divmod(num, 58) result.insert(0, BASE58_ALPHABET[mod]) - return prefix + bytes(result) @@ -71,9 +41,20 @@ def encode_base58_checksum(s): return encode_base58(s + double_sha256(s)[:4]).decode('ascii') -def p2pkh_script(h160): - '''Takes a hash160 and returns the scriptPubKey''' - return b'\x76\xa9\x14' + h160 + b'\x88\xac' +def hash160(s): + return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest() + + +def little_endian_to_int(b): + '''little_endian_to_int takes byte sequence as a little-endian number. + Returns an integer''' + return int.from_bytes(b, 'little') + + +def int_to_little_endian(n, length): + '''endian_to_little_endian takes an integer and returns the little-endian + byte sequence of length''' + return n.to_bytes(length, 'little') def decode_base58(s): @@ -119,36 +100,13 @@ def encode_varint(i): raise RuntimeError('integer too large: {}'.format(i)) -def h160_to_p2pkh_address(h160, testnet=False): - '''Takes a byte sequence hash160 and returns a p2pkh address string''' - # p2pkh has a prefix of b'\x00' for mainnet, b'\x6f' for testnet - if testnet: - prefix = b'\x6f' - else: - prefix = b'\x00' - # return the encode_base58_checksum the prefix and h160 - return encode_base58_checksum(prefix + h160) - - -def h160_to_p2sh_address(h160, testnet=False): - '''Takes a byte sequence hash160 and returns a p2sh address string''' - # p2sh has a prefix of b'\x05' for mainnet, b'\xc4' for testnet - if testnet: - prefix = b'\xc4' - else: - prefix = b'\x05' - # return the encode_base58_checksum the prefix and h160 - return encode_base58_checksum(prefix + h160) +def p2pkh_script(h160): + '''Takes a hash160 and returns the scriptPubKey''' + return b'\x76\xa9\x14' + h160 + b'\x88\xac' class HelperTest(TestCase): - def test_bytes(self): - b = b'hello world' - s = 'hello world' - self.assertEqual(b, str_to_bytes(s)) - self.assertEqual(s, bytes_to_str(b)) - def test_little_endian_to_int(self): h = bytes.fromhex('99c3980000000000') want = 10011545 @@ -172,17 +130,3 @@ def test_base58(self): self.assertEqual(h160, want) got = encode_base58_checksum(b'\x6f' + bytes.fromhex(h160)) self.assertEqual(got, addr) - - def test_p2pkh_address(self): - h160 = bytes.fromhex('74d691da1574e6b3c192ecfb52cc8984ee7b6c56') - want = '1BenRpVUFK65JFWcQSuHnJKzc4M8ZP8Eqa' - self.assertEqual(h160_to_p2pkh_address(h160, testnet=False), want) - want = 'mrAjisaT4LXL5MzE81sfcDYKU3wqWSvf9q' - self.assertEqual(h160_to_p2pkh_address(h160, testnet=True), want) - - def test_p2sh_address(self): - h160 = bytes.fromhex('74d691da1574e6b3c192ecfb52cc8984ee7b6c56') - want = '3CLoMMyuoDQTPRD3XYZtCvgvkadrAdvdXh' - self.assertEqual(h160_to_p2sh_address(h160, testnet=False), want) - want = '2N3u1R6uwQfuobCqbCgBkpsgBxvr1tZpe7B' - self.assertEqual(h160_to_p2sh_address(h160, testnet=True), want) diff --git a/code-ch08/ecc.py b/code-ch08/ecc.py index 3ae8a766..208229fe 100644 --- a/code-ch08/ecc.py +++ b/code-ch08/ecc.py @@ -2,18 +2,20 @@ from random import randint from unittest import TestCase +import hashlib + from helper import double_sha256, encode_base58, encode_base58_checksum, hash160 class FieldElement: def __init__(self, num, prime): + if num >= prime or num < 0: + error = 'Num {} not in field range 0 to {}'.format( + num, prime-1) + raise ValueError(error) self.num = num self.prime = prime - if self.num >= self.prime or self.num < 0: - error = 'Num {} not in field range 0 to {}'.format( - self.num, self.prime-1) - raise RuntimeError(error) def __eq__(self, other): if other is None: @@ -21,77 +23,77 @@ def __eq__(self, other): return self.num == other.num and self.prime == other.prime def __ne__(self, other): - if other is None: - return True - return self.num != other.num or self.prime != other.prime + # this should be the inverse of the == operator + return not (self == other) def __repr__(self): return 'FieldElement_{}({})'.format(self.prime, self.num) + def __add__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num + other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) def __sub__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num - other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) def __mul__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num * other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) - def __rmul__(self, coefficient): - num = (self.num * coefficient) % self.prime - return self.__class__(num=num, prime=self.prime) - - def __pow__(self, n): - # remember fermat's little theorem: - # self.num**(p-1) % p == 1 - # you might want to use % operator on n - prime = self.prime - num = pow(self.num, n % (prime-1), prime) - return self.__class__(num, prime) + def __pow__(self, exponent): + n = exponent % (self.prime - 1) + num = pow(self.num, n, self.prime) + return self.__class__(num, self.prime) def __truediv__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num * pow(other.num, self.prime - 2, self.prime)) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # use fermat's little theorem: # self.num**(p-1) % p == 1 # this means: # 1/n == pow(n, p-2, p) # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) + def __rmul__(self, coefficient): + num = (self.num * coefficient) % self.prime + return self.__class__(num=num, prime=self.prime) class FieldElementTest(TestCase): + def test_ne(self): + a = FieldElement(2, 31) + b = FieldElement(2, 31) + c = FieldElement(15, 31) + self.assertEqual(a, b) + self.assertTrue(a != c) + self.assertFalse(a != b) + def test_add(self): a = FieldElement(2, 31) b = FieldElement(15, 31) @@ -136,8 +138,8 @@ def test_div(self): self.assertEqual(a**-4*b, FieldElement(13, 31)) - class Point: + zero = 0 def __init__(self, x, y, a, b): self.a = a @@ -152,16 +154,16 @@ def __init__(self, x, y, a, b): # make sure that the elliptic curve equation is satisfied # y**2 == x**3 + a*x + b if self.y**2 != self.x**3 + a*x + b: - # if not, throw a RuntimeError - raise RuntimeError('({}, {}) is not on the curve'.format(self.x, self.y)) + # if not, throw a ValueError + raise ValueError('({}, {}) is not on the curve'.format(x, y)) def __eq__(self, other): return self.x == other.x and self.y == other.y \ and self.a == other.a and self.b == other.b def __ne__(self, other): - return self.x != other.x or self.y != other.y \ - or self.a != other.a or self.b != other.b + # this should be the inverse of the == operator + return not (self == other) def __repr__(self): if self.x is None: @@ -171,7 +173,7 @@ def __repr__(self): def __add__(self, other): if self.a != other.a or self.b != other.b: - raise RuntimeError('Points {}, {} are not on the same curve'.format(self, other)) + raise TypeError('Points {}, {} are not on the same curve'.format(self, other)) # Case 0.0: self is the point at infinity, return other if self.x is None: return other @@ -185,7 +187,7 @@ def __add__(self, other): # Remember to return an instance of this class: # self.__class__(x, y, a, b) return self.__class__(None, None, self.a, self.b) - + # Case 2: self.x != other.x if self.x != other.x: # Formula (x3,y3)==(x1,y1)+(x2,y2) @@ -212,31 +214,32 @@ def __add__(self, other): # self.__class__(x, y, a, b) return self.__class__(x, y, self.a, self.b) + # Case 4: if we are tangent to the vertical line + if self == other and self.y == self.zero: + return self.__class__(None, None, self.a, self.b) + def __rmul__(self, coefficient): - # rmul calculates coefficient * self - # implement the naive way: - # start product from 0 (point at infinity) - # use: self.__class__(None, None, a, b) - product = self.__class__(None, None, self.a, self.b) - # loop coefficient times - # use: for _ in range(coefficient): - for _ in range(coefficient): - # keep adding self over and over - product += self - # return the product - return product - # Extra Credit: - # a more advanced technique uses point doubling - # find the binary representation of coefficient - # keep doubling the point and if the bit is there for coefficient - # add the current. - # remember to return an instance of the class + coef = coefficient + current = self + result = self.__class__(None, None, self.a, self.b) + while coef: + if coef & 1: + result += current + current += current + coef >>= 1 + return result class PointTest(TestCase): + def test_ne(self): + a = Point(x=3, y=-7, a=5, b=7) + b = Point(x=18, y=77, a=5, b=7) + self.assertTrue(a != b) + self.assertFalse(a != a) + def test_on_curve(self): - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): Point(x=-2, y=4, a=5, b=7) # these should not raise an error Point(x=3, y=-7, a=5, b=7) @@ -249,7 +252,7 @@ def test_add0(self): self.assertEqual(a+b, b) self.assertEqual(b+a, b) self.assertEqual(b+c, a) - + def test_add1(self): a = Point(x=3, y=7, a=5, b=7) b = Point(x=-1, y=-1, a=5, b=7) @@ -270,16 +273,12 @@ def test_on_curve(self): prime = 223 a = FieldElement(0, prime) b = FieldElement(7, prime) - + valid_points = ((192,105), (17,56), (1,193)) invalid_points = ((200,119), (42,99)) - + # iterate over valid points for x_raw, y_raw in valid_points: - # Initialize points this way: - # x = FieldElement(x_raw, prime) - # y = FieldElement(y_raw, prime) - # Point(x, y, a, b) x = FieldElement(x_raw, prime) y = FieldElement(y_raw, prime) # Creating the point should not result in an error @@ -287,19 +286,12 @@ def test_on_curve(self): # iterate over invalid points for x_raw, y_raw in invalid_points: - # Initialize points this way: - # x = FieldElement(x_raw, prime) - # y = FieldElement(y_raw, prime) - # Point(x, y, a, b) x = FieldElement(x_raw, prime) y = FieldElement(y_raw, prime) - # check that creating the point results in a RuntimeError - # with self.assertRaises(RuntimeError): - # Point(x, y, a, b) - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): Point(x, y, a, b) - def test_add1(self): + def test_add(self): # tests the following additions on curve y^2=x^3-7 over F_223: # (192,105) + (17,56) # (47,71) + (117,141) @@ -309,23 +301,13 @@ def test_add1(self): b = FieldElement(7, prime) additions = ( - # (x1, y1, x2, y2, x3, y3) + # (x1, y1, x2, y2, x3, y3) (192, 105, 17, 56, 170, 142), (47, 71, 117, 141, 60, 139), (143, 98, 76, 66, 47, 71), ) # iterate over the additions for x1_raw, y1_raw, x2_raw, y2_raw, x3_raw, y3_raw in additions: - # Initialize points this way: - # x1 = FieldElement(x1_raw, prime) - # y1 = FieldElement(y1_raw, prime) - # p1 = Point(x1, y1, a, b) - # x2 = FieldElement(x2_raw, prime) - # y2 = FieldElement(y2_raw, prime) - # p2 = Point(x2, y2, a, b) - # x3 = FieldElement(x3_raw, prime) - # y3 = FieldElement(y3_raw, prime) - # p3 = Point(x3, y3, a, b) x1 = FieldElement(x1_raw, prime) y1 = FieldElement(y1_raw, prime) p1 = Point(x1, y1, a, b) @@ -338,6 +320,7 @@ def test_add1(self): # check that p1 + p2 == p3 self.assertEqual(p1+p2, p3) + def test_rmul(self): # tests the following scalar multiplications # 2*(192,105) @@ -362,26 +345,19 @@ def test_rmul(self): # iterate over the multiplications for s, x1_raw, y1_raw, x2_raw, y2_raw in multiplications: - # Initialize points this way: - # x1 = FieldElement(x1_raw, prime) - # y1 = FieldElement(y1_raw, prime) - # p1 = Point(x1, y1, a, b) x1 = FieldElement(x1_raw, prime) y1 = FieldElement(y1_raw, prime) p1 = Point(x1, y1, a, b) # initialize the second point based on whether it's the point at infinity - # x2 = FieldElement(x2_raw, prime) - # y2 = FieldElement(y2_raw, prime) - # p2 = Point(x2, y2, a, b) if x2_raw is None: p2 = Point(None, None, a, b) else: x2 = FieldElement(x2_raw, prime) y2 = FieldElement(y2_raw, prime) p2 = Point(x2, y2, a, b) - + # check that the product is equal to the expected point - self.assertEqual(s*p1, p2) + self.assertEqual(s*p1, p2) A = 0 @@ -406,7 +382,8 @@ def sqrt(self): class S256Point(Point): - bits = 256 + + zero = S256Field(0) def __init__(self, x, y, a=None, b=None): a, b = S256Field(A), S256Field(B) @@ -419,26 +396,20 @@ def __init__(self, x, y, a=None, b=None): def __repr__(self): if self.x is None: - return 'Point(infinity)' + return 'S256Point(infinity)' else: - return 'Point({},{})_{}'.format(self.x.num, self.y.num, self.x.prime) + return 'S256Point({},{})'.format(self.x, self.y) def __rmul__(self, coefficient): - # we want to mod by N to make this simple coef = coefficient % N - # current will undergo binary expansion - current = self - # result is what we return, starts at 0 - result = S256Point(None, None) - # we double 256 times and add where there is a 1 in the binary - # representation of coefficient - for i in range(self.bits): - if coef & 1: - result += current - current += current - # we shift the coefficient to the right - coef >>= 1 - return result + return super().__rmul__(coef) + + def verify(self, z, sig): + s_inv = pow(sig.s, N-2, N) + u = z * s_inv % N + v = sig.r * s_inv % N + total = u*G + v*self + return total.x.num == sig.r def sec(self, compressed=True): # returns the binary version of the sec format, NOT hex @@ -454,36 +425,17 @@ def sec(self, compressed=True): # if non-compressed, starts with b'\x04' followod by self.x and then self.y return b'\x04' + self.x.num.to_bytes(32, 'big') + self.y.num.to_bytes(32, 'big') + def h160(self, compressed=True): + return hash160(self.sec(compressed)) + def address(self, compressed=True, testnet=False): '''Returns the address string''' - # get the sec - sec = self.sec(compressed) - # hash160 the sec - h160 = hash160(sec) - # raw is hash 160 prepended w/ b'\x00' for mainnet, b'\x6f' for testnet + h160 = self.h160(compressed) if testnet: prefix = b'\x6f' else: prefix = b'\x00' - raw = prefix + h160 - # checksum is first 4 bytes of double_sha256 of raw - checksum = double_sha256(raw)[:4] - # encode_base58 the raw + checksum - address = encode_base58(raw+checksum) - # return as a string, you can use .decode('ascii') to do this. - return address.decode('ascii') - - def verify(self, z, sig): - # remember sig.r and sig.s are the main things we're checking - # remember 1/s = pow(s, N-2, N) - s_inv = pow(sig.s, N-2, N) - # u = z / s - u = z * s_inv % N - # v = r / s - v = sig.r * s_inv % N - # u*G + v*P should have as the x coordinate, r - total = u*G + v*self - return total.x.num == sig.r + return encode_base58_checksum(prefix + h160) @classmethod def parse(self, sec_bin): @@ -539,6 +491,19 @@ def test_pubpoint(self): # check that the secret*G is the same as the point self.assertEqual(secret*G, point) + def test_verify(self): + point = S256Point( + 0x887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e4da568744d06c, + 0x61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34) + z = 0xec208baa0fc1c19f708a9ca96fdeff3ac3f230bb4a7ba4aede4942ad003c0f60 + r = 0xac8d1c87e51d0d441be8b3dd5b05c8795b48875dffe00b7ffcfac23010d3a395 + s = 0x68342ceff8935ededd102dd876ffd6ba72d6a427a3edb13d26eb0781cb423c4 + self.assertTrue(point.verify(z, Signature(r, s))) + z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d + r = 0xeff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c + s = 0xc7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6 + self.assertTrue(point.verify(z, Signature(r, s))) + def test_sec(self): coefficient = 999**3 uncompressed = '049d5ca49670cbe4c3bfa84c96a8c87df086c6ea6a24ba6b809c9de234496808d56fa15cc7f3d38cda98dee2419f415b7513dde1301f8643cd9245aea7f3f911f9' @@ -585,26 +550,6 @@ def test_address(self): self.assertEqual( point.address(compressed=False, testnet=True), testnet_address) - def test_verify(self): - point = S256Point( - 0x887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e4da568744d06c, - 0x61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34) - z = 0xec208baa0fc1c19f708a9ca96fdeff3ac3f230bb4a7ba4aede4942ad003c0f60 - r = 0xac8d1c87e51d0d441be8b3dd5b05c8795b48875dffe00b7ffcfac23010d3a395 - s = 0x68342ceff8935ededd102dd876ffd6ba72d6a427a3edb13d26eb0781cb423c4 - self.assertTrue(point.verify(z, Signature(r, s))) - z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d - r = 0xeff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c - s = 0xc7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6 - self.assertTrue(point.verify(z, Signature(r, s))) - - def test_parse(self): - sec = bytes.fromhex('0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278a') - point = S256Point.parse(sec) - want = 0xa56c896489c71dfc65701ce25050f542f336893fb8cd15f4e8e5c124dbf58e47 - self.assertEqual(point.y.num, want) - - class Signature: @@ -679,8 +624,7 @@ def hex(self): return '{:x}'.format(self.secret).zfill(64) def sign(self, z): - # we need a random number k: randint(0, 2**256) - k = randint(0, 2**256) + k = self.deterministic_k(z) # r is the x coordinate of the resulting point k*G r = (k*G).x.num # remember 1/k = pow(k, N-2, N) @@ -693,6 +637,26 @@ def sign(self, z): # Signature(r, s) return Signature(r, s) + def deterministic_k(self, z): + k = b'\x00' * 32 + v = b'\x01' * 32 + if z > N: + z -= N + z_bytes = z.to_bytes(32, 'big') + secret_bytes = self.secret.to_bytes(32, 'big') + s256 = hashlib.sha256 + k = hmac.new(k, v + b'\x00' + secret_bytes + z_bytes, s256).digest() + v = hmac.new(k, v, s256).digest() + k = hmac.new(k, v + b'\x01' + secret_bytes + z_bytes, s256).digest() + v = hmac.new(k, v, s256).digest() + while True: + v = hmac.new(k, v, s256).digest() + candidate = int.from_bytes(v, 'big') + if candidate >= 1 and candidate < N: + return candidate + k = hmac.new(k, v + b'\x00', s256).digest() + v = hmac.new(k, v, s256).digest() + def wif(self, compressed=True, testnet=False): # convert the secret from integer to a 32-bytes in big endian using num.to_bytes(32, 'big') secret_bytes = self.secret.to_bytes(32, 'big') diff --git a/code-ch08/helper.py b/code-ch08/helper.py index 85b1a1df..e61033e9 100644 --- a/code-ch08/helper.py +++ b/code-ch08/helper.py @@ -1,4 +1,3 @@ -from subprocess import check_output from unittest import TestCase, TestSuite, TextTestRunner import hashlib @@ -16,34 +15,6 @@ def run_test(test): TextTestRunner().run(suite) -def bytes_to_str(b, encoding='ascii'): - '''Returns a string version of the bytes''' - return b.decode(encoding) - - -def str_to_bytes(s, encoding='ascii'): - '''Returns a bytes version of the string''' - return s.encode(encoding) - - -def little_endian_to_int(b): - '''little_endian_to_int takes byte sequence as a little-endian number. - Returns an integer''' - # use the from_bytes method of int - return int.from_bytes(b, 'little') - - -def int_to_little_endian(n, length): - '''endian_to_little_endian takes an integer and returns the little-endian - byte sequence of length''' - # use the to_bytes method of n - return n.to_bytes(length, 'little') - - -def hash160(s): - return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest() - - def double_sha256(s): return hashlib.sha256(hashlib.sha256(s).digest()).digest() @@ -63,7 +34,6 @@ def encode_base58(s): while num > 0: num, mod = divmod(num, 58) result.insert(0, BASE58_ALPHABET[mod]) - return prefix + bytes(result) @@ -71,9 +41,20 @@ def encode_base58_checksum(s): return encode_base58(s + double_sha256(s)[:4]).decode('ascii') -def p2pkh_script(h160): - '''Takes a hash160 and returns the scriptPubKey''' - return b'\x76\xa9\x14' + h160 + b'\x88\xac' +def hash160(s): + return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest() + + +def little_endian_to_int(b): + '''little_endian_to_int takes byte sequence as a little-endian number. + Returns an integer''' + return int.from_bytes(b, 'little') + + +def int_to_little_endian(n, length): + '''endian_to_little_endian takes an integer and returns the little-endian + byte sequence of length''' + return n.to_bytes(length, 'little') def decode_base58(s): @@ -119,6 +100,11 @@ def encode_varint(i): raise RuntimeError('integer too large: {}'.format(i)) +def p2pkh_script(h160): + '''Takes a hash160 and returns the scriptPubKey''' + return b'\x76\xa9\x14' + h160 + b'\x88\xac' + + def h160_to_p2pkh_address(h160, testnet=False): '''Takes a byte sequence hash160 and returns a p2pkh address string''' raise NotImplementedError @@ -131,12 +117,6 @@ def h160_to_p2sh_address(h160, testnet=False): class HelperTest(TestCase): - def test_bytes(self): - b = b'hello world' - s = 'hello world' - self.assertEqual(b, str_to_bytes(s)) - self.assertEqual(s, bytes_to_str(b)) - def test_little_endian_to_int(self): h = bytes.fromhex('99c3980000000000') want = 10011545 diff --git a/code-ch09/ecc.py b/code-ch09/ecc.py index 3ae8a766..208229fe 100644 --- a/code-ch09/ecc.py +++ b/code-ch09/ecc.py @@ -2,18 +2,20 @@ from random import randint from unittest import TestCase +import hashlib + from helper import double_sha256, encode_base58, encode_base58_checksum, hash160 class FieldElement: def __init__(self, num, prime): + if num >= prime or num < 0: + error = 'Num {} not in field range 0 to {}'.format( + num, prime-1) + raise ValueError(error) self.num = num self.prime = prime - if self.num >= self.prime or self.num < 0: - error = 'Num {} not in field range 0 to {}'.format( - self.num, self.prime-1) - raise RuntimeError(error) def __eq__(self, other): if other is None: @@ -21,77 +23,77 @@ def __eq__(self, other): return self.num == other.num and self.prime == other.prime def __ne__(self, other): - if other is None: - return True - return self.num != other.num or self.prime != other.prime + # this should be the inverse of the == operator + return not (self == other) def __repr__(self): return 'FieldElement_{}({})'.format(self.prime, self.num) + def __add__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num + other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) def __sub__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num - other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) def __mul__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num * other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) - def __rmul__(self, coefficient): - num = (self.num * coefficient) % self.prime - return self.__class__(num=num, prime=self.prime) - - def __pow__(self, n): - # remember fermat's little theorem: - # self.num**(p-1) % p == 1 - # you might want to use % operator on n - prime = self.prime - num = pow(self.num, n % (prime-1), prime) - return self.__class__(num, prime) + def __pow__(self, exponent): + n = exponent % (self.prime - 1) + num = pow(self.num, n, self.prime) + return self.__class__(num, self.prime) def __truediv__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num * pow(other.num, self.prime - 2, self.prime)) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # use fermat's little theorem: # self.num**(p-1) % p == 1 # this means: # 1/n == pow(n, p-2, p) # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) + def __rmul__(self, coefficient): + num = (self.num * coefficient) % self.prime + return self.__class__(num=num, prime=self.prime) class FieldElementTest(TestCase): + def test_ne(self): + a = FieldElement(2, 31) + b = FieldElement(2, 31) + c = FieldElement(15, 31) + self.assertEqual(a, b) + self.assertTrue(a != c) + self.assertFalse(a != b) + def test_add(self): a = FieldElement(2, 31) b = FieldElement(15, 31) @@ -136,8 +138,8 @@ def test_div(self): self.assertEqual(a**-4*b, FieldElement(13, 31)) - class Point: + zero = 0 def __init__(self, x, y, a, b): self.a = a @@ -152,16 +154,16 @@ def __init__(self, x, y, a, b): # make sure that the elliptic curve equation is satisfied # y**2 == x**3 + a*x + b if self.y**2 != self.x**3 + a*x + b: - # if not, throw a RuntimeError - raise RuntimeError('({}, {}) is not on the curve'.format(self.x, self.y)) + # if not, throw a ValueError + raise ValueError('({}, {}) is not on the curve'.format(x, y)) def __eq__(self, other): return self.x == other.x and self.y == other.y \ and self.a == other.a and self.b == other.b def __ne__(self, other): - return self.x != other.x or self.y != other.y \ - or self.a != other.a or self.b != other.b + # this should be the inverse of the == operator + return not (self == other) def __repr__(self): if self.x is None: @@ -171,7 +173,7 @@ def __repr__(self): def __add__(self, other): if self.a != other.a or self.b != other.b: - raise RuntimeError('Points {}, {} are not on the same curve'.format(self, other)) + raise TypeError('Points {}, {} are not on the same curve'.format(self, other)) # Case 0.0: self is the point at infinity, return other if self.x is None: return other @@ -185,7 +187,7 @@ def __add__(self, other): # Remember to return an instance of this class: # self.__class__(x, y, a, b) return self.__class__(None, None, self.a, self.b) - + # Case 2: self.x != other.x if self.x != other.x: # Formula (x3,y3)==(x1,y1)+(x2,y2) @@ -212,31 +214,32 @@ def __add__(self, other): # self.__class__(x, y, a, b) return self.__class__(x, y, self.a, self.b) + # Case 4: if we are tangent to the vertical line + if self == other and self.y == self.zero: + return self.__class__(None, None, self.a, self.b) + def __rmul__(self, coefficient): - # rmul calculates coefficient * self - # implement the naive way: - # start product from 0 (point at infinity) - # use: self.__class__(None, None, a, b) - product = self.__class__(None, None, self.a, self.b) - # loop coefficient times - # use: for _ in range(coefficient): - for _ in range(coefficient): - # keep adding self over and over - product += self - # return the product - return product - # Extra Credit: - # a more advanced technique uses point doubling - # find the binary representation of coefficient - # keep doubling the point and if the bit is there for coefficient - # add the current. - # remember to return an instance of the class + coef = coefficient + current = self + result = self.__class__(None, None, self.a, self.b) + while coef: + if coef & 1: + result += current + current += current + coef >>= 1 + return result class PointTest(TestCase): + def test_ne(self): + a = Point(x=3, y=-7, a=5, b=7) + b = Point(x=18, y=77, a=5, b=7) + self.assertTrue(a != b) + self.assertFalse(a != a) + def test_on_curve(self): - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): Point(x=-2, y=4, a=5, b=7) # these should not raise an error Point(x=3, y=-7, a=5, b=7) @@ -249,7 +252,7 @@ def test_add0(self): self.assertEqual(a+b, b) self.assertEqual(b+a, b) self.assertEqual(b+c, a) - + def test_add1(self): a = Point(x=3, y=7, a=5, b=7) b = Point(x=-1, y=-1, a=5, b=7) @@ -270,16 +273,12 @@ def test_on_curve(self): prime = 223 a = FieldElement(0, prime) b = FieldElement(7, prime) - + valid_points = ((192,105), (17,56), (1,193)) invalid_points = ((200,119), (42,99)) - + # iterate over valid points for x_raw, y_raw in valid_points: - # Initialize points this way: - # x = FieldElement(x_raw, prime) - # y = FieldElement(y_raw, prime) - # Point(x, y, a, b) x = FieldElement(x_raw, prime) y = FieldElement(y_raw, prime) # Creating the point should not result in an error @@ -287,19 +286,12 @@ def test_on_curve(self): # iterate over invalid points for x_raw, y_raw in invalid_points: - # Initialize points this way: - # x = FieldElement(x_raw, prime) - # y = FieldElement(y_raw, prime) - # Point(x, y, a, b) x = FieldElement(x_raw, prime) y = FieldElement(y_raw, prime) - # check that creating the point results in a RuntimeError - # with self.assertRaises(RuntimeError): - # Point(x, y, a, b) - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): Point(x, y, a, b) - def test_add1(self): + def test_add(self): # tests the following additions on curve y^2=x^3-7 over F_223: # (192,105) + (17,56) # (47,71) + (117,141) @@ -309,23 +301,13 @@ def test_add1(self): b = FieldElement(7, prime) additions = ( - # (x1, y1, x2, y2, x3, y3) + # (x1, y1, x2, y2, x3, y3) (192, 105, 17, 56, 170, 142), (47, 71, 117, 141, 60, 139), (143, 98, 76, 66, 47, 71), ) # iterate over the additions for x1_raw, y1_raw, x2_raw, y2_raw, x3_raw, y3_raw in additions: - # Initialize points this way: - # x1 = FieldElement(x1_raw, prime) - # y1 = FieldElement(y1_raw, prime) - # p1 = Point(x1, y1, a, b) - # x2 = FieldElement(x2_raw, prime) - # y2 = FieldElement(y2_raw, prime) - # p2 = Point(x2, y2, a, b) - # x3 = FieldElement(x3_raw, prime) - # y3 = FieldElement(y3_raw, prime) - # p3 = Point(x3, y3, a, b) x1 = FieldElement(x1_raw, prime) y1 = FieldElement(y1_raw, prime) p1 = Point(x1, y1, a, b) @@ -338,6 +320,7 @@ def test_add1(self): # check that p1 + p2 == p3 self.assertEqual(p1+p2, p3) + def test_rmul(self): # tests the following scalar multiplications # 2*(192,105) @@ -362,26 +345,19 @@ def test_rmul(self): # iterate over the multiplications for s, x1_raw, y1_raw, x2_raw, y2_raw in multiplications: - # Initialize points this way: - # x1 = FieldElement(x1_raw, prime) - # y1 = FieldElement(y1_raw, prime) - # p1 = Point(x1, y1, a, b) x1 = FieldElement(x1_raw, prime) y1 = FieldElement(y1_raw, prime) p1 = Point(x1, y1, a, b) # initialize the second point based on whether it's the point at infinity - # x2 = FieldElement(x2_raw, prime) - # y2 = FieldElement(y2_raw, prime) - # p2 = Point(x2, y2, a, b) if x2_raw is None: p2 = Point(None, None, a, b) else: x2 = FieldElement(x2_raw, prime) y2 = FieldElement(y2_raw, prime) p2 = Point(x2, y2, a, b) - + # check that the product is equal to the expected point - self.assertEqual(s*p1, p2) + self.assertEqual(s*p1, p2) A = 0 @@ -406,7 +382,8 @@ def sqrt(self): class S256Point(Point): - bits = 256 + + zero = S256Field(0) def __init__(self, x, y, a=None, b=None): a, b = S256Field(A), S256Field(B) @@ -419,26 +396,20 @@ def __init__(self, x, y, a=None, b=None): def __repr__(self): if self.x is None: - return 'Point(infinity)' + return 'S256Point(infinity)' else: - return 'Point({},{})_{}'.format(self.x.num, self.y.num, self.x.prime) + return 'S256Point({},{})'.format(self.x, self.y) def __rmul__(self, coefficient): - # we want to mod by N to make this simple coef = coefficient % N - # current will undergo binary expansion - current = self - # result is what we return, starts at 0 - result = S256Point(None, None) - # we double 256 times and add where there is a 1 in the binary - # representation of coefficient - for i in range(self.bits): - if coef & 1: - result += current - current += current - # we shift the coefficient to the right - coef >>= 1 - return result + return super().__rmul__(coef) + + def verify(self, z, sig): + s_inv = pow(sig.s, N-2, N) + u = z * s_inv % N + v = sig.r * s_inv % N + total = u*G + v*self + return total.x.num == sig.r def sec(self, compressed=True): # returns the binary version of the sec format, NOT hex @@ -454,36 +425,17 @@ def sec(self, compressed=True): # if non-compressed, starts with b'\x04' followod by self.x and then self.y return b'\x04' + self.x.num.to_bytes(32, 'big') + self.y.num.to_bytes(32, 'big') + def h160(self, compressed=True): + return hash160(self.sec(compressed)) + def address(self, compressed=True, testnet=False): '''Returns the address string''' - # get the sec - sec = self.sec(compressed) - # hash160 the sec - h160 = hash160(sec) - # raw is hash 160 prepended w/ b'\x00' for mainnet, b'\x6f' for testnet + h160 = self.h160(compressed) if testnet: prefix = b'\x6f' else: prefix = b'\x00' - raw = prefix + h160 - # checksum is first 4 bytes of double_sha256 of raw - checksum = double_sha256(raw)[:4] - # encode_base58 the raw + checksum - address = encode_base58(raw+checksum) - # return as a string, you can use .decode('ascii') to do this. - return address.decode('ascii') - - def verify(self, z, sig): - # remember sig.r and sig.s are the main things we're checking - # remember 1/s = pow(s, N-2, N) - s_inv = pow(sig.s, N-2, N) - # u = z / s - u = z * s_inv % N - # v = r / s - v = sig.r * s_inv % N - # u*G + v*P should have as the x coordinate, r - total = u*G + v*self - return total.x.num == sig.r + return encode_base58_checksum(prefix + h160) @classmethod def parse(self, sec_bin): @@ -539,6 +491,19 @@ def test_pubpoint(self): # check that the secret*G is the same as the point self.assertEqual(secret*G, point) + def test_verify(self): + point = S256Point( + 0x887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e4da568744d06c, + 0x61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34) + z = 0xec208baa0fc1c19f708a9ca96fdeff3ac3f230bb4a7ba4aede4942ad003c0f60 + r = 0xac8d1c87e51d0d441be8b3dd5b05c8795b48875dffe00b7ffcfac23010d3a395 + s = 0x68342ceff8935ededd102dd876ffd6ba72d6a427a3edb13d26eb0781cb423c4 + self.assertTrue(point.verify(z, Signature(r, s))) + z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d + r = 0xeff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c + s = 0xc7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6 + self.assertTrue(point.verify(z, Signature(r, s))) + def test_sec(self): coefficient = 999**3 uncompressed = '049d5ca49670cbe4c3bfa84c96a8c87df086c6ea6a24ba6b809c9de234496808d56fa15cc7f3d38cda98dee2419f415b7513dde1301f8643cd9245aea7f3f911f9' @@ -585,26 +550,6 @@ def test_address(self): self.assertEqual( point.address(compressed=False, testnet=True), testnet_address) - def test_verify(self): - point = S256Point( - 0x887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e4da568744d06c, - 0x61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34) - z = 0xec208baa0fc1c19f708a9ca96fdeff3ac3f230bb4a7ba4aede4942ad003c0f60 - r = 0xac8d1c87e51d0d441be8b3dd5b05c8795b48875dffe00b7ffcfac23010d3a395 - s = 0x68342ceff8935ededd102dd876ffd6ba72d6a427a3edb13d26eb0781cb423c4 - self.assertTrue(point.verify(z, Signature(r, s))) - z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d - r = 0xeff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c - s = 0xc7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6 - self.assertTrue(point.verify(z, Signature(r, s))) - - def test_parse(self): - sec = bytes.fromhex('0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278a') - point = S256Point.parse(sec) - want = 0xa56c896489c71dfc65701ce25050f542f336893fb8cd15f4e8e5c124dbf58e47 - self.assertEqual(point.y.num, want) - - class Signature: @@ -679,8 +624,7 @@ def hex(self): return '{:x}'.format(self.secret).zfill(64) def sign(self, z): - # we need a random number k: randint(0, 2**256) - k = randint(0, 2**256) + k = self.deterministic_k(z) # r is the x coordinate of the resulting point k*G r = (k*G).x.num # remember 1/k = pow(k, N-2, N) @@ -693,6 +637,26 @@ def sign(self, z): # Signature(r, s) return Signature(r, s) + def deterministic_k(self, z): + k = b'\x00' * 32 + v = b'\x01' * 32 + if z > N: + z -= N + z_bytes = z.to_bytes(32, 'big') + secret_bytes = self.secret.to_bytes(32, 'big') + s256 = hashlib.sha256 + k = hmac.new(k, v + b'\x00' + secret_bytes + z_bytes, s256).digest() + v = hmac.new(k, v, s256).digest() + k = hmac.new(k, v + b'\x01' + secret_bytes + z_bytes, s256).digest() + v = hmac.new(k, v, s256).digest() + while True: + v = hmac.new(k, v, s256).digest() + candidate = int.from_bytes(v, 'big') + if candidate >= 1 and candidate < N: + return candidate + k = hmac.new(k, v + b'\x00', s256).digest() + v = hmac.new(k, v, s256).digest() + def wif(self, compressed=True, testnet=False): # convert the secret from integer to a 32-bytes in big endian using num.to_bytes(32, 'big') secret_bytes = self.secret.to_bytes(32, 'big') diff --git a/code-ch09/helper.py b/code-ch09/helper.py index 094d88d4..7c4c9169 100644 --- a/code-ch09/helper.py +++ b/code-ch09/helper.py @@ -1,4 +1,3 @@ -from subprocess import check_output from unittest import TestCase, TestSuite, TextTestRunner import hashlib @@ -10,40 +9,13 @@ BASE58_ALPHABET = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' TWO_WEEKS = 60 * 60 * 24 * 14 + def run_test(test): suite = TestSuite() suite.addTest(test) TextTestRunner().run(suite) -def bytes_to_str(b, encoding='ascii'): - '''Returns a string version of the bytes''' - return b.decode(encoding) - - -def str_to_bytes(s, encoding='ascii'): - '''Returns a bytes version of the string''' - return s.encode(encoding) - - -def little_endian_to_int(b): - '''little_endian_to_int takes byte sequence as a little-endian number. - Returns an integer''' - # use the from_bytes method of int - return int.from_bytes(b, 'little') - - -def int_to_little_endian(n, length): - '''endian_to_little_endian takes an integer and returns the little-endian - byte sequence of length''' - # use the to_bytes method of n - return n.to_bytes(length, 'little') - - -def hash160(s): - return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest() - - def double_sha256(s): return hashlib.sha256(hashlib.sha256(s).digest()).digest() @@ -63,7 +35,6 @@ def encode_base58(s): while num > 0: num, mod = divmod(num, 58) result.insert(0, BASE58_ALPHABET[mod]) - return prefix + bytes(result) @@ -71,9 +42,20 @@ def encode_base58_checksum(s): return encode_base58(s + double_sha256(s)[:4]).decode('ascii') -def p2pkh_script(h160): - '''Takes a hash160 and returns the scriptPubKey''' - return b'\x76\xa9\x14' + h160 + b'\x88\xac' +def hash160(s): + return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest() + + +def little_endian_to_int(b): + '''little_endian_to_int takes byte sequence as a little-endian number. + Returns an integer''' + return int.from_bytes(b, 'little') + + +def int_to_little_endian(n, length): + '''endian_to_little_endian takes an integer and returns the little-endian + byte sequence of length''' + return n.to_bytes(length, 'little') def decode_base58(s): @@ -119,6 +101,11 @@ def encode_varint(i): raise RuntimeError('integer too large: {}'.format(i)) +def p2pkh_script(h160): + '''Takes a hash160 and returns the scriptPubKey''' + return b'\x76\xa9\x14' + h160 + b'\x88\xac' + + def h160_to_p2pkh_address(h160, testnet=False): '''Takes a byte sequence hash160 and returns a p2pkh address string''' # p2pkh has a prefix of b'\x00' for mainnet, b'\x6f' for testnet @@ -172,12 +159,6 @@ def calculate_new_bits(previous_bits, time_differential): class HelperTest(TestCase): - def test_bytes(self): - b = b'hello world' - s = 'hello world' - self.assertEqual(b, str_to_bytes(s)) - self.assertEqual(s, bytes_to_str(b)) - def test_little_endian_to_int(self): h = bytes.fromhex('99c3980000000000') want = 10011545 @@ -220,4 +201,4 @@ def test_calculate_new_bits(self): prev_bits = bytes.fromhex('54d80118') time_differential = 302400 want = bytes.fromhex('00157617') - self.assertEqual(calculate_new_bits(prev_bits, time_differential), want) \ No newline at end of file + self.assertEqual(calculate_new_bits(prev_bits, time_differential), want) diff --git a/code-ch10/ecc.py b/code-ch10/ecc.py index 3ae8a766..208229fe 100644 --- a/code-ch10/ecc.py +++ b/code-ch10/ecc.py @@ -2,18 +2,20 @@ from random import randint from unittest import TestCase +import hashlib + from helper import double_sha256, encode_base58, encode_base58_checksum, hash160 class FieldElement: def __init__(self, num, prime): + if num >= prime or num < 0: + error = 'Num {} not in field range 0 to {}'.format( + num, prime-1) + raise ValueError(error) self.num = num self.prime = prime - if self.num >= self.prime or self.num < 0: - error = 'Num {} not in field range 0 to {}'.format( - self.num, self.prime-1) - raise RuntimeError(error) def __eq__(self, other): if other is None: @@ -21,77 +23,77 @@ def __eq__(self, other): return self.num == other.num and self.prime == other.prime def __ne__(self, other): - if other is None: - return True - return self.num != other.num or self.prime != other.prime + # this should be the inverse of the == operator + return not (self == other) def __repr__(self): return 'FieldElement_{}({})'.format(self.prime, self.num) + def __add__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num + other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) def __sub__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num - other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) def __mul__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num * other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) - def __rmul__(self, coefficient): - num = (self.num * coefficient) % self.prime - return self.__class__(num=num, prime=self.prime) - - def __pow__(self, n): - # remember fermat's little theorem: - # self.num**(p-1) % p == 1 - # you might want to use % operator on n - prime = self.prime - num = pow(self.num, n % (prime-1), prime) - return self.__class__(num, prime) + def __pow__(self, exponent): + n = exponent % (self.prime - 1) + num = pow(self.num, n, self.prime) + return self.__class__(num, self.prime) def __truediv__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num * pow(other.num, self.prime - 2, self.prime)) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # use fermat's little theorem: # self.num**(p-1) % p == 1 # this means: # 1/n == pow(n, p-2, p) # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) + def __rmul__(self, coefficient): + num = (self.num * coefficient) % self.prime + return self.__class__(num=num, prime=self.prime) class FieldElementTest(TestCase): + def test_ne(self): + a = FieldElement(2, 31) + b = FieldElement(2, 31) + c = FieldElement(15, 31) + self.assertEqual(a, b) + self.assertTrue(a != c) + self.assertFalse(a != b) + def test_add(self): a = FieldElement(2, 31) b = FieldElement(15, 31) @@ -136,8 +138,8 @@ def test_div(self): self.assertEqual(a**-4*b, FieldElement(13, 31)) - class Point: + zero = 0 def __init__(self, x, y, a, b): self.a = a @@ -152,16 +154,16 @@ def __init__(self, x, y, a, b): # make sure that the elliptic curve equation is satisfied # y**2 == x**3 + a*x + b if self.y**2 != self.x**3 + a*x + b: - # if not, throw a RuntimeError - raise RuntimeError('({}, {}) is not on the curve'.format(self.x, self.y)) + # if not, throw a ValueError + raise ValueError('({}, {}) is not on the curve'.format(x, y)) def __eq__(self, other): return self.x == other.x and self.y == other.y \ and self.a == other.a and self.b == other.b def __ne__(self, other): - return self.x != other.x or self.y != other.y \ - or self.a != other.a or self.b != other.b + # this should be the inverse of the == operator + return not (self == other) def __repr__(self): if self.x is None: @@ -171,7 +173,7 @@ def __repr__(self): def __add__(self, other): if self.a != other.a or self.b != other.b: - raise RuntimeError('Points {}, {} are not on the same curve'.format(self, other)) + raise TypeError('Points {}, {} are not on the same curve'.format(self, other)) # Case 0.0: self is the point at infinity, return other if self.x is None: return other @@ -185,7 +187,7 @@ def __add__(self, other): # Remember to return an instance of this class: # self.__class__(x, y, a, b) return self.__class__(None, None, self.a, self.b) - + # Case 2: self.x != other.x if self.x != other.x: # Formula (x3,y3)==(x1,y1)+(x2,y2) @@ -212,31 +214,32 @@ def __add__(self, other): # self.__class__(x, y, a, b) return self.__class__(x, y, self.a, self.b) + # Case 4: if we are tangent to the vertical line + if self == other and self.y == self.zero: + return self.__class__(None, None, self.a, self.b) + def __rmul__(self, coefficient): - # rmul calculates coefficient * self - # implement the naive way: - # start product from 0 (point at infinity) - # use: self.__class__(None, None, a, b) - product = self.__class__(None, None, self.a, self.b) - # loop coefficient times - # use: for _ in range(coefficient): - for _ in range(coefficient): - # keep adding self over and over - product += self - # return the product - return product - # Extra Credit: - # a more advanced technique uses point doubling - # find the binary representation of coefficient - # keep doubling the point and if the bit is there for coefficient - # add the current. - # remember to return an instance of the class + coef = coefficient + current = self + result = self.__class__(None, None, self.a, self.b) + while coef: + if coef & 1: + result += current + current += current + coef >>= 1 + return result class PointTest(TestCase): + def test_ne(self): + a = Point(x=3, y=-7, a=5, b=7) + b = Point(x=18, y=77, a=5, b=7) + self.assertTrue(a != b) + self.assertFalse(a != a) + def test_on_curve(self): - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): Point(x=-2, y=4, a=5, b=7) # these should not raise an error Point(x=3, y=-7, a=5, b=7) @@ -249,7 +252,7 @@ def test_add0(self): self.assertEqual(a+b, b) self.assertEqual(b+a, b) self.assertEqual(b+c, a) - + def test_add1(self): a = Point(x=3, y=7, a=5, b=7) b = Point(x=-1, y=-1, a=5, b=7) @@ -270,16 +273,12 @@ def test_on_curve(self): prime = 223 a = FieldElement(0, prime) b = FieldElement(7, prime) - + valid_points = ((192,105), (17,56), (1,193)) invalid_points = ((200,119), (42,99)) - + # iterate over valid points for x_raw, y_raw in valid_points: - # Initialize points this way: - # x = FieldElement(x_raw, prime) - # y = FieldElement(y_raw, prime) - # Point(x, y, a, b) x = FieldElement(x_raw, prime) y = FieldElement(y_raw, prime) # Creating the point should not result in an error @@ -287,19 +286,12 @@ def test_on_curve(self): # iterate over invalid points for x_raw, y_raw in invalid_points: - # Initialize points this way: - # x = FieldElement(x_raw, prime) - # y = FieldElement(y_raw, prime) - # Point(x, y, a, b) x = FieldElement(x_raw, prime) y = FieldElement(y_raw, prime) - # check that creating the point results in a RuntimeError - # with self.assertRaises(RuntimeError): - # Point(x, y, a, b) - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): Point(x, y, a, b) - def test_add1(self): + def test_add(self): # tests the following additions on curve y^2=x^3-7 over F_223: # (192,105) + (17,56) # (47,71) + (117,141) @@ -309,23 +301,13 @@ def test_add1(self): b = FieldElement(7, prime) additions = ( - # (x1, y1, x2, y2, x3, y3) + # (x1, y1, x2, y2, x3, y3) (192, 105, 17, 56, 170, 142), (47, 71, 117, 141, 60, 139), (143, 98, 76, 66, 47, 71), ) # iterate over the additions for x1_raw, y1_raw, x2_raw, y2_raw, x3_raw, y3_raw in additions: - # Initialize points this way: - # x1 = FieldElement(x1_raw, prime) - # y1 = FieldElement(y1_raw, prime) - # p1 = Point(x1, y1, a, b) - # x2 = FieldElement(x2_raw, prime) - # y2 = FieldElement(y2_raw, prime) - # p2 = Point(x2, y2, a, b) - # x3 = FieldElement(x3_raw, prime) - # y3 = FieldElement(y3_raw, prime) - # p3 = Point(x3, y3, a, b) x1 = FieldElement(x1_raw, prime) y1 = FieldElement(y1_raw, prime) p1 = Point(x1, y1, a, b) @@ -338,6 +320,7 @@ def test_add1(self): # check that p1 + p2 == p3 self.assertEqual(p1+p2, p3) + def test_rmul(self): # tests the following scalar multiplications # 2*(192,105) @@ -362,26 +345,19 @@ def test_rmul(self): # iterate over the multiplications for s, x1_raw, y1_raw, x2_raw, y2_raw in multiplications: - # Initialize points this way: - # x1 = FieldElement(x1_raw, prime) - # y1 = FieldElement(y1_raw, prime) - # p1 = Point(x1, y1, a, b) x1 = FieldElement(x1_raw, prime) y1 = FieldElement(y1_raw, prime) p1 = Point(x1, y1, a, b) # initialize the second point based on whether it's the point at infinity - # x2 = FieldElement(x2_raw, prime) - # y2 = FieldElement(y2_raw, prime) - # p2 = Point(x2, y2, a, b) if x2_raw is None: p2 = Point(None, None, a, b) else: x2 = FieldElement(x2_raw, prime) y2 = FieldElement(y2_raw, prime) p2 = Point(x2, y2, a, b) - + # check that the product is equal to the expected point - self.assertEqual(s*p1, p2) + self.assertEqual(s*p1, p2) A = 0 @@ -406,7 +382,8 @@ def sqrt(self): class S256Point(Point): - bits = 256 + + zero = S256Field(0) def __init__(self, x, y, a=None, b=None): a, b = S256Field(A), S256Field(B) @@ -419,26 +396,20 @@ def __init__(self, x, y, a=None, b=None): def __repr__(self): if self.x is None: - return 'Point(infinity)' + return 'S256Point(infinity)' else: - return 'Point({},{})_{}'.format(self.x.num, self.y.num, self.x.prime) + return 'S256Point({},{})'.format(self.x, self.y) def __rmul__(self, coefficient): - # we want to mod by N to make this simple coef = coefficient % N - # current will undergo binary expansion - current = self - # result is what we return, starts at 0 - result = S256Point(None, None) - # we double 256 times and add where there is a 1 in the binary - # representation of coefficient - for i in range(self.bits): - if coef & 1: - result += current - current += current - # we shift the coefficient to the right - coef >>= 1 - return result + return super().__rmul__(coef) + + def verify(self, z, sig): + s_inv = pow(sig.s, N-2, N) + u = z * s_inv % N + v = sig.r * s_inv % N + total = u*G + v*self + return total.x.num == sig.r def sec(self, compressed=True): # returns the binary version of the sec format, NOT hex @@ -454,36 +425,17 @@ def sec(self, compressed=True): # if non-compressed, starts with b'\x04' followod by self.x and then self.y return b'\x04' + self.x.num.to_bytes(32, 'big') + self.y.num.to_bytes(32, 'big') + def h160(self, compressed=True): + return hash160(self.sec(compressed)) + def address(self, compressed=True, testnet=False): '''Returns the address string''' - # get the sec - sec = self.sec(compressed) - # hash160 the sec - h160 = hash160(sec) - # raw is hash 160 prepended w/ b'\x00' for mainnet, b'\x6f' for testnet + h160 = self.h160(compressed) if testnet: prefix = b'\x6f' else: prefix = b'\x00' - raw = prefix + h160 - # checksum is first 4 bytes of double_sha256 of raw - checksum = double_sha256(raw)[:4] - # encode_base58 the raw + checksum - address = encode_base58(raw+checksum) - # return as a string, you can use .decode('ascii') to do this. - return address.decode('ascii') - - def verify(self, z, sig): - # remember sig.r and sig.s are the main things we're checking - # remember 1/s = pow(s, N-2, N) - s_inv = pow(sig.s, N-2, N) - # u = z / s - u = z * s_inv % N - # v = r / s - v = sig.r * s_inv % N - # u*G + v*P should have as the x coordinate, r - total = u*G + v*self - return total.x.num == sig.r + return encode_base58_checksum(prefix + h160) @classmethod def parse(self, sec_bin): @@ -539,6 +491,19 @@ def test_pubpoint(self): # check that the secret*G is the same as the point self.assertEqual(secret*G, point) + def test_verify(self): + point = S256Point( + 0x887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e4da568744d06c, + 0x61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34) + z = 0xec208baa0fc1c19f708a9ca96fdeff3ac3f230bb4a7ba4aede4942ad003c0f60 + r = 0xac8d1c87e51d0d441be8b3dd5b05c8795b48875dffe00b7ffcfac23010d3a395 + s = 0x68342ceff8935ededd102dd876ffd6ba72d6a427a3edb13d26eb0781cb423c4 + self.assertTrue(point.verify(z, Signature(r, s))) + z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d + r = 0xeff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c + s = 0xc7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6 + self.assertTrue(point.verify(z, Signature(r, s))) + def test_sec(self): coefficient = 999**3 uncompressed = '049d5ca49670cbe4c3bfa84c96a8c87df086c6ea6a24ba6b809c9de234496808d56fa15cc7f3d38cda98dee2419f415b7513dde1301f8643cd9245aea7f3f911f9' @@ -585,26 +550,6 @@ def test_address(self): self.assertEqual( point.address(compressed=False, testnet=True), testnet_address) - def test_verify(self): - point = S256Point( - 0x887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e4da568744d06c, - 0x61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34) - z = 0xec208baa0fc1c19f708a9ca96fdeff3ac3f230bb4a7ba4aede4942ad003c0f60 - r = 0xac8d1c87e51d0d441be8b3dd5b05c8795b48875dffe00b7ffcfac23010d3a395 - s = 0x68342ceff8935ededd102dd876ffd6ba72d6a427a3edb13d26eb0781cb423c4 - self.assertTrue(point.verify(z, Signature(r, s))) - z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d - r = 0xeff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c - s = 0xc7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6 - self.assertTrue(point.verify(z, Signature(r, s))) - - def test_parse(self): - sec = bytes.fromhex('0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278a') - point = S256Point.parse(sec) - want = 0xa56c896489c71dfc65701ce25050f542f336893fb8cd15f4e8e5c124dbf58e47 - self.assertEqual(point.y.num, want) - - class Signature: @@ -679,8 +624,7 @@ def hex(self): return '{:x}'.format(self.secret).zfill(64) def sign(self, z): - # we need a random number k: randint(0, 2**256) - k = randint(0, 2**256) + k = self.deterministic_k(z) # r is the x coordinate of the resulting point k*G r = (k*G).x.num # remember 1/k = pow(k, N-2, N) @@ -693,6 +637,26 @@ def sign(self, z): # Signature(r, s) return Signature(r, s) + def deterministic_k(self, z): + k = b'\x00' * 32 + v = b'\x01' * 32 + if z > N: + z -= N + z_bytes = z.to_bytes(32, 'big') + secret_bytes = self.secret.to_bytes(32, 'big') + s256 = hashlib.sha256 + k = hmac.new(k, v + b'\x00' + secret_bytes + z_bytes, s256).digest() + v = hmac.new(k, v, s256).digest() + k = hmac.new(k, v + b'\x01' + secret_bytes + z_bytes, s256).digest() + v = hmac.new(k, v, s256).digest() + while True: + v = hmac.new(k, v, s256).digest() + candidate = int.from_bytes(v, 'big') + if candidate >= 1 and candidate < N: + return candidate + k = hmac.new(k, v + b'\x00', s256).digest() + v = hmac.new(k, v, s256).digest() + def wif(self, compressed=True, testnet=False): # convert the secret from integer to a 32-bytes in big endian using num.to_bytes(32, 'big') secret_bytes = self.secret.to_bytes(32, 'big') diff --git a/code-ch10/helper.py b/code-ch10/helper.py index c6a83e70..c680a1f9 100644 --- a/code-ch10/helper.py +++ b/code-ch10/helper.py @@ -1,8 +1,6 @@ -from subprocess import check_output from unittest import TestCase, TestSuite, TextTestRunner import hashlib -import math SIGHASH_ALL = 1 @@ -13,40 +11,13 @@ MAX_TARGET = 0xffff*256**(0x1d-3) + def run_test(test): suite = TestSuite() suite.addTest(test) TextTestRunner().run(suite) -def bytes_to_str(b, encoding='ascii'): - '''Returns a string version of the bytes''' - return b.decode(encoding) - - -def str_to_bytes(s, encoding='ascii'): - '''Returns a bytes version of the string''' - return s.encode(encoding) - - -def little_endian_to_int(b): - '''little_endian_to_int takes byte sequence as a little-endian number. - Returns an integer''' - # use the from_bytes method of int - return int.from_bytes(b, 'little') - - -def int_to_little_endian(n, length): - '''endian_to_little_endian takes an integer and returns the little-endian - byte sequence of length''' - # use the to_bytes method of n - return n.to_bytes(length, 'little') - - -def hash160(s): - return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest() - - def double_sha256(s): return hashlib.sha256(hashlib.sha256(s).digest()).digest() @@ -66,7 +37,6 @@ def encode_base58(s): while num > 0: num, mod = divmod(num, 58) result.insert(0, BASE58_ALPHABET[mod]) - return prefix + bytes(result) @@ -74,9 +44,20 @@ def encode_base58_checksum(s): return encode_base58(s + double_sha256(s)[:4]).decode('ascii') -def p2pkh_script(h160): - '''Takes a hash160 and returns the scriptPubKey''' - return b'\x76\xa9\x14' + h160 + b'\x88\xac' +def hash160(s): + return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest() + + +def little_endian_to_int(b): + '''little_endian_to_int takes byte sequence as a little-endian number. + Returns an integer''' + return int.from_bytes(b, 'little') + + +def int_to_little_endian(n, length): + '''endian_to_little_endian takes an integer and returns the little-endian + byte sequence of length''' + return n.to_bytes(length, 'little') def decode_base58(s): @@ -122,6 +103,11 @@ def encode_varint(i): raise RuntimeError('integer too large: {}'.format(i)) +def p2pkh_script(h160): + '''Takes a hash160 and returns the scriptPubKey''' + return b'\x76\xa9\x14' + h160 + b'\x88\xac' + + def h160_to_p2pkh_address(h160, testnet=False): '''Takes a byte sequence hash160 and returns a p2pkh address string''' # p2pkh has a prefix of b'\x00' for mainnet, b'\x6f' for testnet @@ -141,6 +127,7 @@ def h160_to_p2sh_address(h160, testnet=False): prefix = b'\x05' return encode_base58_checksum(prefix + h160) + def bits_to_target(bits): '''Turns bits into a target (large 256-bit integer)''' # last byte is exponent @@ -151,6 +138,7 @@ def bits_to_target(bits): # coefficient * 256**(exponent-3) return coefficient * 256**(exponent-3) + def target_to_bits(target): '''Turns a target integer back into bits, which is 4 bytes''' raw_bytes = target.to_bytes(32, 'big') @@ -170,6 +158,7 @@ def target_to_bits(target): # we've truncated the number after the first 3 digits of base-256 return new_bits_big_endian[::-1] + def calculate_new_bits(previous_bits, time_differential): '''Calculates the new bits given a 2016-block time differential and the previous bits''' @@ -185,12 +174,6 @@ def calculate_new_bits(previous_bits, time_differential): class HelperTest(TestCase): - def test_bytes(self): - b = b'hello world' - s = 'hello world' - self.assertEqual(b, str_to_bytes(s)) - self.assertEqual(s, bytes_to_str(b)) - def test_little_endian_to_int(self): h = bytes.fromhex('99c3980000000000') want = 10011545 @@ -228,3 +211,9 @@ def test_p2sh_address(self): self.assertEqual(h160_to_p2sh_address(h160, testnet=False), want) want = '2N3u1R6uwQfuobCqbCgBkpsgBxvr1tZpe7B' self.assertEqual(h160_to_p2sh_address(h160, testnet=True), want) + + def test_calculate_new_bits(self): + prev_bits = bytes.fromhex('54d80118') + time_differential = 302400 + want = bytes.fromhex('00157617') + self.assertEqual(calculate_new_bits(prev_bits, time_differential), want) diff --git a/code-ch11/block.py b/code-ch11/block.py index 9e761e9f..c57f377e 100644 --- a/code-ch11/block.py +++ b/code-ch11/block.py @@ -7,7 +7,6 @@ little_endian_to_int, merkle_parent, merkle_parent_level, - merkle_path, merkle_root, ) diff --git a/code-ch11/ecc.py b/code-ch11/ecc.py index 3ae8a766..208229fe 100644 --- a/code-ch11/ecc.py +++ b/code-ch11/ecc.py @@ -2,18 +2,20 @@ from random import randint from unittest import TestCase +import hashlib + from helper import double_sha256, encode_base58, encode_base58_checksum, hash160 class FieldElement: def __init__(self, num, prime): + if num >= prime or num < 0: + error = 'Num {} not in field range 0 to {}'.format( + num, prime-1) + raise ValueError(error) self.num = num self.prime = prime - if self.num >= self.prime or self.num < 0: - error = 'Num {} not in field range 0 to {}'.format( - self.num, self.prime-1) - raise RuntimeError(error) def __eq__(self, other): if other is None: @@ -21,77 +23,77 @@ def __eq__(self, other): return self.num == other.num and self.prime == other.prime def __ne__(self, other): - if other is None: - return True - return self.num != other.num or self.prime != other.prime + # this should be the inverse of the == operator + return not (self == other) def __repr__(self): return 'FieldElement_{}({})'.format(self.prime, self.num) + def __add__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num + other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) def __sub__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num - other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) def __mul__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num * other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) - def __rmul__(self, coefficient): - num = (self.num * coefficient) % self.prime - return self.__class__(num=num, prime=self.prime) - - def __pow__(self, n): - # remember fermat's little theorem: - # self.num**(p-1) % p == 1 - # you might want to use % operator on n - prime = self.prime - num = pow(self.num, n % (prime-1), prime) - return self.__class__(num, prime) + def __pow__(self, exponent): + n = exponent % (self.prime - 1) + num = pow(self.num, n, self.prime) + return self.__class__(num, self.prime) def __truediv__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num * pow(other.num, self.prime - 2, self.prime)) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # use fermat's little theorem: # self.num**(p-1) % p == 1 # this means: # 1/n == pow(n, p-2, p) # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) + def __rmul__(self, coefficient): + num = (self.num * coefficient) % self.prime + return self.__class__(num=num, prime=self.prime) class FieldElementTest(TestCase): + def test_ne(self): + a = FieldElement(2, 31) + b = FieldElement(2, 31) + c = FieldElement(15, 31) + self.assertEqual(a, b) + self.assertTrue(a != c) + self.assertFalse(a != b) + def test_add(self): a = FieldElement(2, 31) b = FieldElement(15, 31) @@ -136,8 +138,8 @@ def test_div(self): self.assertEqual(a**-4*b, FieldElement(13, 31)) - class Point: + zero = 0 def __init__(self, x, y, a, b): self.a = a @@ -152,16 +154,16 @@ def __init__(self, x, y, a, b): # make sure that the elliptic curve equation is satisfied # y**2 == x**3 + a*x + b if self.y**2 != self.x**3 + a*x + b: - # if not, throw a RuntimeError - raise RuntimeError('({}, {}) is not on the curve'.format(self.x, self.y)) + # if not, throw a ValueError + raise ValueError('({}, {}) is not on the curve'.format(x, y)) def __eq__(self, other): return self.x == other.x and self.y == other.y \ and self.a == other.a and self.b == other.b def __ne__(self, other): - return self.x != other.x or self.y != other.y \ - or self.a != other.a or self.b != other.b + # this should be the inverse of the == operator + return not (self == other) def __repr__(self): if self.x is None: @@ -171,7 +173,7 @@ def __repr__(self): def __add__(self, other): if self.a != other.a or self.b != other.b: - raise RuntimeError('Points {}, {} are not on the same curve'.format(self, other)) + raise TypeError('Points {}, {} are not on the same curve'.format(self, other)) # Case 0.0: self is the point at infinity, return other if self.x is None: return other @@ -185,7 +187,7 @@ def __add__(self, other): # Remember to return an instance of this class: # self.__class__(x, y, a, b) return self.__class__(None, None, self.a, self.b) - + # Case 2: self.x != other.x if self.x != other.x: # Formula (x3,y3)==(x1,y1)+(x2,y2) @@ -212,31 +214,32 @@ def __add__(self, other): # self.__class__(x, y, a, b) return self.__class__(x, y, self.a, self.b) + # Case 4: if we are tangent to the vertical line + if self == other and self.y == self.zero: + return self.__class__(None, None, self.a, self.b) + def __rmul__(self, coefficient): - # rmul calculates coefficient * self - # implement the naive way: - # start product from 0 (point at infinity) - # use: self.__class__(None, None, a, b) - product = self.__class__(None, None, self.a, self.b) - # loop coefficient times - # use: for _ in range(coefficient): - for _ in range(coefficient): - # keep adding self over and over - product += self - # return the product - return product - # Extra Credit: - # a more advanced technique uses point doubling - # find the binary representation of coefficient - # keep doubling the point and if the bit is there for coefficient - # add the current. - # remember to return an instance of the class + coef = coefficient + current = self + result = self.__class__(None, None, self.a, self.b) + while coef: + if coef & 1: + result += current + current += current + coef >>= 1 + return result class PointTest(TestCase): + def test_ne(self): + a = Point(x=3, y=-7, a=5, b=7) + b = Point(x=18, y=77, a=5, b=7) + self.assertTrue(a != b) + self.assertFalse(a != a) + def test_on_curve(self): - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): Point(x=-2, y=4, a=5, b=7) # these should not raise an error Point(x=3, y=-7, a=5, b=7) @@ -249,7 +252,7 @@ def test_add0(self): self.assertEqual(a+b, b) self.assertEqual(b+a, b) self.assertEqual(b+c, a) - + def test_add1(self): a = Point(x=3, y=7, a=5, b=7) b = Point(x=-1, y=-1, a=5, b=7) @@ -270,16 +273,12 @@ def test_on_curve(self): prime = 223 a = FieldElement(0, prime) b = FieldElement(7, prime) - + valid_points = ((192,105), (17,56), (1,193)) invalid_points = ((200,119), (42,99)) - + # iterate over valid points for x_raw, y_raw in valid_points: - # Initialize points this way: - # x = FieldElement(x_raw, prime) - # y = FieldElement(y_raw, prime) - # Point(x, y, a, b) x = FieldElement(x_raw, prime) y = FieldElement(y_raw, prime) # Creating the point should not result in an error @@ -287,19 +286,12 @@ def test_on_curve(self): # iterate over invalid points for x_raw, y_raw in invalid_points: - # Initialize points this way: - # x = FieldElement(x_raw, prime) - # y = FieldElement(y_raw, prime) - # Point(x, y, a, b) x = FieldElement(x_raw, prime) y = FieldElement(y_raw, prime) - # check that creating the point results in a RuntimeError - # with self.assertRaises(RuntimeError): - # Point(x, y, a, b) - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): Point(x, y, a, b) - def test_add1(self): + def test_add(self): # tests the following additions on curve y^2=x^3-7 over F_223: # (192,105) + (17,56) # (47,71) + (117,141) @@ -309,23 +301,13 @@ def test_add1(self): b = FieldElement(7, prime) additions = ( - # (x1, y1, x2, y2, x3, y3) + # (x1, y1, x2, y2, x3, y3) (192, 105, 17, 56, 170, 142), (47, 71, 117, 141, 60, 139), (143, 98, 76, 66, 47, 71), ) # iterate over the additions for x1_raw, y1_raw, x2_raw, y2_raw, x3_raw, y3_raw in additions: - # Initialize points this way: - # x1 = FieldElement(x1_raw, prime) - # y1 = FieldElement(y1_raw, prime) - # p1 = Point(x1, y1, a, b) - # x2 = FieldElement(x2_raw, prime) - # y2 = FieldElement(y2_raw, prime) - # p2 = Point(x2, y2, a, b) - # x3 = FieldElement(x3_raw, prime) - # y3 = FieldElement(y3_raw, prime) - # p3 = Point(x3, y3, a, b) x1 = FieldElement(x1_raw, prime) y1 = FieldElement(y1_raw, prime) p1 = Point(x1, y1, a, b) @@ -338,6 +320,7 @@ def test_add1(self): # check that p1 + p2 == p3 self.assertEqual(p1+p2, p3) + def test_rmul(self): # tests the following scalar multiplications # 2*(192,105) @@ -362,26 +345,19 @@ def test_rmul(self): # iterate over the multiplications for s, x1_raw, y1_raw, x2_raw, y2_raw in multiplications: - # Initialize points this way: - # x1 = FieldElement(x1_raw, prime) - # y1 = FieldElement(y1_raw, prime) - # p1 = Point(x1, y1, a, b) x1 = FieldElement(x1_raw, prime) y1 = FieldElement(y1_raw, prime) p1 = Point(x1, y1, a, b) # initialize the second point based on whether it's the point at infinity - # x2 = FieldElement(x2_raw, prime) - # y2 = FieldElement(y2_raw, prime) - # p2 = Point(x2, y2, a, b) if x2_raw is None: p2 = Point(None, None, a, b) else: x2 = FieldElement(x2_raw, prime) y2 = FieldElement(y2_raw, prime) p2 = Point(x2, y2, a, b) - + # check that the product is equal to the expected point - self.assertEqual(s*p1, p2) + self.assertEqual(s*p1, p2) A = 0 @@ -406,7 +382,8 @@ def sqrt(self): class S256Point(Point): - bits = 256 + + zero = S256Field(0) def __init__(self, x, y, a=None, b=None): a, b = S256Field(A), S256Field(B) @@ -419,26 +396,20 @@ def __init__(self, x, y, a=None, b=None): def __repr__(self): if self.x is None: - return 'Point(infinity)' + return 'S256Point(infinity)' else: - return 'Point({},{})_{}'.format(self.x.num, self.y.num, self.x.prime) + return 'S256Point({},{})'.format(self.x, self.y) def __rmul__(self, coefficient): - # we want to mod by N to make this simple coef = coefficient % N - # current will undergo binary expansion - current = self - # result is what we return, starts at 0 - result = S256Point(None, None) - # we double 256 times and add where there is a 1 in the binary - # representation of coefficient - for i in range(self.bits): - if coef & 1: - result += current - current += current - # we shift the coefficient to the right - coef >>= 1 - return result + return super().__rmul__(coef) + + def verify(self, z, sig): + s_inv = pow(sig.s, N-2, N) + u = z * s_inv % N + v = sig.r * s_inv % N + total = u*G + v*self + return total.x.num == sig.r def sec(self, compressed=True): # returns the binary version of the sec format, NOT hex @@ -454,36 +425,17 @@ def sec(self, compressed=True): # if non-compressed, starts with b'\x04' followod by self.x and then self.y return b'\x04' + self.x.num.to_bytes(32, 'big') + self.y.num.to_bytes(32, 'big') + def h160(self, compressed=True): + return hash160(self.sec(compressed)) + def address(self, compressed=True, testnet=False): '''Returns the address string''' - # get the sec - sec = self.sec(compressed) - # hash160 the sec - h160 = hash160(sec) - # raw is hash 160 prepended w/ b'\x00' for mainnet, b'\x6f' for testnet + h160 = self.h160(compressed) if testnet: prefix = b'\x6f' else: prefix = b'\x00' - raw = prefix + h160 - # checksum is first 4 bytes of double_sha256 of raw - checksum = double_sha256(raw)[:4] - # encode_base58 the raw + checksum - address = encode_base58(raw+checksum) - # return as a string, you can use .decode('ascii') to do this. - return address.decode('ascii') - - def verify(self, z, sig): - # remember sig.r and sig.s are the main things we're checking - # remember 1/s = pow(s, N-2, N) - s_inv = pow(sig.s, N-2, N) - # u = z / s - u = z * s_inv % N - # v = r / s - v = sig.r * s_inv % N - # u*G + v*P should have as the x coordinate, r - total = u*G + v*self - return total.x.num == sig.r + return encode_base58_checksum(prefix + h160) @classmethod def parse(self, sec_bin): @@ -539,6 +491,19 @@ def test_pubpoint(self): # check that the secret*G is the same as the point self.assertEqual(secret*G, point) + def test_verify(self): + point = S256Point( + 0x887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e4da568744d06c, + 0x61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34) + z = 0xec208baa0fc1c19f708a9ca96fdeff3ac3f230bb4a7ba4aede4942ad003c0f60 + r = 0xac8d1c87e51d0d441be8b3dd5b05c8795b48875dffe00b7ffcfac23010d3a395 + s = 0x68342ceff8935ededd102dd876ffd6ba72d6a427a3edb13d26eb0781cb423c4 + self.assertTrue(point.verify(z, Signature(r, s))) + z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d + r = 0xeff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c + s = 0xc7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6 + self.assertTrue(point.verify(z, Signature(r, s))) + def test_sec(self): coefficient = 999**3 uncompressed = '049d5ca49670cbe4c3bfa84c96a8c87df086c6ea6a24ba6b809c9de234496808d56fa15cc7f3d38cda98dee2419f415b7513dde1301f8643cd9245aea7f3f911f9' @@ -585,26 +550,6 @@ def test_address(self): self.assertEqual( point.address(compressed=False, testnet=True), testnet_address) - def test_verify(self): - point = S256Point( - 0x887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e4da568744d06c, - 0x61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34) - z = 0xec208baa0fc1c19f708a9ca96fdeff3ac3f230bb4a7ba4aede4942ad003c0f60 - r = 0xac8d1c87e51d0d441be8b3dd5b05c8795b48875dffe00b7ffcfac23010d3a395 - s = 0x68342ceff8935ededd102dd876ffd6ba72d6a427a3edb13d26eb0781cb423c4 - self.assertTrue(point.verify(z, Signature(r, s))) - z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d - r = 0xeff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c - s = 0xc7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6 - self.assertTrue(point.verify(z, Signature(r, s))) - - def test_parse(self): - sec = bytes.fromhex('0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278a') - point = S256Point.parse(sec) - want = 0xa56c896489c71dfc65701ce25050f542f336893fb8cd15f4e8e5c124dbf58e47 - self.assertEqual(point.y.num, want) - - class Signature: @@ -679,8 +624,7 @@ def hex(self): return '{:x}'.format(self.secret).zfill(64) def sign(self, z): - # we need a random number k: randint(0, 2**256) - k = randint(0, 2**256) + k = self.deterministic_k(z) # r is the x coordinate of the resulting point k*G r = (k*G).x.num # remember 1/k = pow(k, N-2, N) @@ -693,6 +637,26 @@ def sign(self, z): # Signature(r, s) return Signature(r, s) + def deterministic_k(self, z): + k = b'\x00' * 32 + v = b'\x01' * 32 + if z > N: + z -= N + z_bytes = z.to_bytes(32, 'big') + secret_bytes = self.secret.to_bytes(32, 'big') + s256 = hashlib.sha256 + k = hmac.new(k, v + b'\x00' + secret_bytes + z_bytes, s256).digest() + v = hmac.new(k, v, s256).digest() + k = hmac.new(k, v + b'\x01' + secret_bytes + z_bytes, s256).digest() + v = hmac.new(k, v, s256).digest() + while True: + v = hmac.new(k, v, s256).digest() + candidate = int.from_bytes(v, 'big') + if candidate >= 1 and candidate < N: + return candidate + k = hmac.new(k, v + b'\x00', s256).digest() + v = hmac.new(k, v, s256).digest() + def wif(self, compressed=True, testnet=False): # convert the secret from integer to a 32-bytes in big endian using num.to_bytes(32, 'big') secret_bytes = self.secret.to_bytes(32, 'big') diff --git a/code-ch11/helper.py b/code-ch11/helper.py index 30e108c1..3a3af9a0 100644 --- a/code-ch11/helper.py +++ b/code-ch11/helper.py @@ -1,14 +1,15 @@ -from subprocess import check_output from unittest import TestCase, TestSuite, TextTestRunner import hashlib -import math SIGHASH_ALL = 1 SIGHASH_NONE = 2 SIGHASH_SINGLE = 3 BASE58_ALPHABET = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' +TWO_WEEKS = 60 * 60 * 24 * 14 +MAX_TARGET = 0xffff*256**(0x1d-3) + def run_test(test): @@ -17,34 +18,6 @@ def run_test(test): TextTestRunner().run(suite) -def bytes_to_str(b, encoding='ascii'): - '''Returns a string version of the bytes''' - return b.decode(encoding) - - -def str_to_bytes(s, encoding='ascii'): - '''Returns a bytes version of the string''' - return s.encode(encoding) - - -def little_endian_to_int(b): - '''little_endian_to_int takes byte sequence as a little-endian number. - Returns an integer''' - # use the from_bytes method of int - return int.from_bytes(b, 'little') - - -def int_to_little_endian(n, length): - '''endian_to_little_endian takes an integer and returns the little-endian - byte sequence of length''' - # use the to_bytes method of n - return n.to_bytes(length, 'little') - - -def hash160(s): - return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest() - - def double_sha256(s): return hashlib.sha256(hashlib.sha256(s).digest()).digest() @@ -64,7 +37,6 @@ def encode_base58(s): while num > 0: num, mod = divmod(num, 58) result.insert(0, BASE58_ALPHABET[mod]) - return prefix + bytes(result) @@ -72,9 +44,20 @@ def encode_base58_checksum(s): return encode_base58(s + double_sha256(s)[:4]).decode('ascii') -def p2pkh_script(h160): - '''Takes a hash160 and returns the scriptPubKey''' - return b'\x76\xa9\x14' + h160 + b'\x88\xac' +def hash160(s): + return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest() + + +def little_endian_to_int(b): + '''little_endian_to_int takes byte sequence as a little-endian number. + Returns an integer''' + return int.from_bytes(b, 'little') + + +def int_to_little_endian(n, length): + '''endian_to_little_endian takes an integer and returns the little-endian + byte sequence of length''' + return n.to_bytes(length, 'little') def decode_base58(s): @@ -120,6 +103,11 @@ def encode_varint(i): raise RuntimeError('integer too large: {}'.format(i)) +def p2pkh_script(h160): + '''Takes a hash160 and returns the scriptPubKey''' + return b'\x76\xa9\x14' + h160 + b'\x88\xac' + + def h160_to_p2pkh_address(h160, testnet=False): '''Takes a byte sequence hash160 and returns a p2pkh address string''' # p2pkh has a prefix of b'\x00' for mainnet, b'\x6f' for testnet @@ -140,6 +128,50 @@ def h160_to_p2sh_address(h160, testnet=False): return encode_base58_checksum(prefix + h160) +def bits_to_target(bits): + '''Turns bits into a target (large 256-bit integer)''' + # last byte is exponent + exponent = bits[-1] + # the first three bytes are the coefficient in little endian + coefficient = little_endian_to_int(bits[:-1]) + # the formula is: + # coefficient * 256**(exponent-3) + return coefficient * 256**(exponent-3) + + +def target_to_bits(target): + '''Turns a target integer back into bits, which is 4 bytes''' + raw_bytes = target.to_bytes(32, 'big') + # get rid of leading 0's + raw_bytes = raw_bytes.lstrip(b'\x00') + if raw_bytes[0] > 0x7f: + # if the first bit is 1, we have to start with 00 + exponent = len(raw_bytes) + 1 + coefficient = b'\x00' + raw_bytes[:2] + else: + # otherwise, we can show the first 3 bytes + # exponent is the number of digits in base-256 + exponent = len(raw_bytes) + # coefficient is the first 3 digits of the base-256 number + coefficient = raw_bytes[:3] + new_bits_big_endian = bytes([exponent]) + coefficient + # we've truncated the number after the first 3 digits of base-256 + return new_bits_big_endian[::-1] + + +def calculate_new_bits(previous_bits, time_differential): + '''Calculates the new bits given + a 2016-block time differential and the previous bits''' + if time_differential > TWO_WEEKS * 4: + time_differential = TWO_WEEKS * 4 + if time_differential < TWO_WEEKS // 4: + time_differential = TWO_WEEKS // 4 + new_target = bits_to_target(previous_bits) * time_differential // TWO_WEEKS + if new_target > MAX_TARGET: + new_target = MAX_TARGET + return target_to_bits(new_target) + + def merkle_parent(hash1, hash2): '''Takes the binary hashes and calculates the double-sha256''' raise NotImplementedError @@ -183,12 +215,6 @@ def bytes_to_bit_field(some_bytes): class HelperTest(TestCase): - def test_bytes(self): - b = b'hello world' - s = 'hello world' - self.assertEqual(b, str_to_bytes(s)) - self.assertEqual(s, bytes_to_str(b)) - def test_little_endian_to_int(self): h = bytes.fromhex('99c3980000000000') want = 10011545 @@ -227,6 +253,12 @@ def test_p2sh_address(self): want = '2N3u1R6uwQfuobCqbCgBkpsgBxvr1tZpe7B' self.assertEqual(h160_to_p2sh_address(h160, testnet=True), want) + def test_calculate_new_bits(self): + prev_bits = bytes.fromhex('54d80118') + time_differential = 302400 + want = bytes.fromhex('00157617') + self.assertEqual(calculate_new_bits(prev_bits, time_differential), want) + def test_merkle_parent(self): tx_hash0 = bytes.fromhex('c117ea8ec828342f4dfb0ad6bd140e03a50720ece40169ee38bdc15d9eb64cf5') tx_hash1 = bytes.fromhex('c131474164b412e3406696da1ee20ab0fc9bf41c8f05fa8ceea7a08d672d7cc5') diff --git a/code-ch11/network.py b/code-ch11/network.py index 404aa0b7..54451b7e 100644 --- a/code-ch11/network.py +++ b/code-ch11/network.py @@ -9,13 +9,18 @@ NETWORK_MAGIC = b'\xf9\xbe\xb4\xd9' +TESTNET_NETWORK_MAGIC = b'\x0b\x11\x09\x07' class NetworkEnvelope: - def __init__(self, command, payload): + def __init__(self, command, payload, testnet=False): self.command = command self.payload = payload + if testnet: + self.magic = TESTNET_NETWORK_MAGIC + else: + self.magic = NETWORK_MAGIC def __repr__(self): return '{}: {}'.format( @@ -24,14 +29,22 @@ def __repr__(self): ) @classmethod - def parse(cls, s): + def parse(cls, s, testnet=False): '''Takes a stream and creates a NetworkEnvelope''' - # check the network magic NETWORK_MAGIC + # check the network magic magic = s.read(4) - if magic != NETWORK_MAGIC: - raise RuntimeError('magic is not right') + if magic == b'': + raise RuntimeError('Connection reset!') + if testnet: + expected_magic = TESTNET_NETWORK_MAGIC + else: + expected_magic = NETWORK_MAGIC + if magic != expected_magic: + raise RuntimeError('magic is not right {} vs {}'.format(magic.hex(), expected_magic.hex())) # command 12 bytes command = s.read(12) + # strip the trailing 0's + command = command.strip(b'\x00') # payload length 4 bytes, little endian payload_length = little_endian_to_int(s.read(4)) # checksum 4 bytes, first four of double-sha256 of payload @@ -42,14 +55,15 @@ def parse(cls, s): calculated_checksum = double_sha256(payload)[:4] if calculated_checksum != checksum: raise RuntimeError('checksum does not match') - return cls(command, payload) + return cls(command, payload, testnet=testnet) def serialize(self): '''Returns the byte serialization of the entire network message''' - # add the network magic NETWORK_MAGIC - result = NETWORK_MAGIC + # add the network magic + result = self.magic # command 12 bytes - result += self.command + # fill with 0's + result += self.command + b'\x00' * (12 - len(self.command)) # payload length 4 bytes, little endian result += int_to_little_endian(len(self.payload), 4) # checksum 4 bytes, first four of double-sha256 of payload @@ -65,12 +79,12 @@ def test_parse(self): msg = bytes.fromhex('f9beb4d976657261636b000000000000000000005df6e0e2') stream = BytesIO(msg) envelope = NetworkEnvelope.parse(stream) - self.assertEqual(envelope.command[:6], b'verack') + self.assertEqual(envelope.command, b'verack') self.assertEqual(envelope.payload, b'') msg = bytes.fromhex('f9beb4d976657273696f6e0000000000650000005f1a69d2721101000100000000000000bc8f5e5400000000010000000000000000000000000000000000ffffc61b6409208d010000000000000000000000000000000000ffffcb0071c0208d128035cbc97953f80f2f5361746f7368693a302e392e332fcf05050001') stream = BytesIO(msg) envelope = NetworkEnvelope.parse(stream) - self.assertEqual(envelope.command[:7], b'version') + self.assertEqual(envelope.command, b'version') self.assertEqual(envelope.payload, msg[24:]) def test_serialize(self): diff --git a/code-ch11/tx.py b/code-ch11/tx.py index 26a405db..0251c511 100644 --- a/code-ch11/tx.py +++ b/code-ch11/tx.py @@ -246,24 +246,32 @@ def serialize(self): result += int_to_little_endian(self.sequence, 4) return result + @classmethod def get_url(cls, testnet=False): if testnet: - return 'https://testnet.blockexplorer.com/api' + return 'http://client:pleasedonthackme@tbtc.programmingblockchain.com:18332' else: - return 'https://blockexplorer.com/api' + return 'http://pbclient:ecdsaisawesome@btc.programmingblockchain.com:8332' def fetch_tx(self, testnet=False): if self.prev_tx not in self.cache: - url = self.get_url(testnet) + '/rawtx/{}'.format(self.prev_tx.hex()) - response = requests.get(url) + url = self.get_url(testnet) + data = { + 'jsonrpc': '2.0', + 'method': 'getrawtransaction', + 'params': [self.prev_tx.hex(),], + 'id': '0', + } + headers = { + 'content-type': 'application/json', + } + response = requests.post(url, headers=headers, json=data) try: - js_response = response.json() - if 'rawtx' not in js_response: - raise RuntimeError('got from server: {}'.format(js_response)) + payload = response.json() except: - raise RuntimeError('got from server: {}'.format(response.text)) - raw = bytes.fromhex(js_response['rawtx']) + raise RuntimeError('Got from server: {}'.format(response)) + raw = bytes.fromhex(payload['result']) stream = BytesIO(raw) tx = Tx.parse(stream) self.cache[self.prev_tx] = tx diff --git a/code-ch12/ecc.py b/code-ch12/ecc.py index 3ae8a766..208229fe 100644 --- a/code-ch12/ecc.py +++ b/code-ch12/ecc.py @@ -2,18 +2,20 @@ from random import randint from unittest import TestCase +import hashlib + from helper import double_sha256, encode_base58, encode_base58_checksum, hash160 class FieldElement: def __init__(self, num, prime): + if num >= prime or num < 0: + error = 'Num {} not in field range 0 to {}'.format( + num, prime-1) + raise ValueError(error) self.num = num self.prime = prime - if self.num >= self.prime or self.num < 0: - error = 'Num {} not in field range 0 to {}'.format( - self.num, self.prime-1) - raise RuntimeError(error) def __eq__(self, other): if other is None: @@ -21,77 +23,77 @@ def __eq__(self, other): return self.num == other.num and self.prime == other.prime def __ne__(self, other): - if other is None: - return True - return self.num != other.num or self.prime != other.prime + # this should be the inverse of the == operator + return not (self == other) def __repr__(self): return 'FieldElement_{}({})'.format(self.prime, self.num) + def __add__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num + other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) def __sub__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num - other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) def __mul__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num * other.num) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) - def __rmul__(self, coefficient): - num = (self.num * coefficient) % self.prime - return self.__class__(num=num, prime=self.prime) - - def __pow__(self, n): - # remember fermat's little theorem: - # self.num**(p-1) % p == 1 - # you might want to use % operator on n - prime = self.prime - num = pow(self.num, n % (prime-1), prime) - return self.__class__(num, prime) + def __pow__(self, exponent): + n = exponent % (self.prime - 1) + num = pow(self.num, n, self.prime) + return self.__class__(num, self.prime) def __truediv__(self, other): if self.prime != other.prime: - raise RuntimeError('Primes must be the same') + raise TypeError('Cannot add two numbers in different Fields') # self.num and other.num are the actual values num = (self.num * pow(other.num, self.prime - 2, self.prime)) % self.prime # self.prime is what you'll need to mod against - prime = self.prime # use fermat's little theorem: # self.num**(p-1) % p == 1 # this means: # 1/n == pow(n, p-2, p) # You need to return an element of the same class # use: self.__class__(num, prime) - return self.__class__(num, prime) + return self.__class__(num, self.prime) + def __rmul__(self, coefficient): + num = (self.num * coefficient) % self.prime + return self.__class__(num=num, prime=self.prime) class FieldElementTest(TestCase): + def test_ne(self): + a = FieldElement(2, 31) + b = FieldElement(2, 31) + c = FieldElement(15, 31) + self.assertEqual(a, b) + self.assertTrue(a != c) + self.assertFalse(a != b) + def test_add(self): a = FieldElement(2, 31) b = FieldElement(15, 31) @@ -136,8 +138,8 @@ def test_div(self): self.assertEqual(a**-4*b, FieldElement(13, 31)) - class Point: + zero = 0 def __init__(self, x, y, a, b): self.a = a @@ -152,16 +154,16 @@ def __init__(self, x, y, a, b): # make sure that the elliptic curve equation is satisfied # y**2 == x**3 + a*x + b if self.y**2 != self.x**3 + a*x + b: - # if not, throw a RuntimeError - raise RuntimeError('({}, {}) is not on the curve'.format(self.x, self.y)) + # if not, throw a ValueError + raise ValueError('({}, {}) is not on the curve'.format(x, y)) def __eq__(self, other): return self.x == other.x and self.y == other.y \ and self.a == other.a and self.b == other.b def __ne__(self, other): - return self.x != other.x or self.y != other.y \ - or self.a != other.a or self.b != other.b + # this should be the inverse of the == operator + return not (self == other) def __repr__(self): if self.x is None: @@ -171,7 +173,7 @@ def __repr__(self): def __add__(self, other): if self.a != other.a or self.b != other.b: - raise RuntimeError('Points {}, {} are not on the same curve'.format(self, other)) + raise TypeError('Points {}, {} are not on the same curve'.format(self, other)) # Case 0.0: self is the point at infinity, return other if self.x is None: return other @@ -185,7 +187,7 @@ def __add__(self, other): # Remember to return an instance of this class: # self.__class__(x, y, a, b) return self.__class__(None, None, self.a, self.b) - + # Case 2: self.x != other.x if self.x != other.x: # Formula (x3,y3)==(x1,y1)+(x2,y2) @@ -212,31 +214,32 @@ def __add__(self, other): # self.__class__(x, y, a, b) return self.__class__(x, y, self.a, self.b) + # Case 4: if we are tangent to the vertical line + if self == other and self.y == self.zero: + return self.__class__(None, None, self.a, self.b) + def __rmul__(self, coefficient): - # rmul calculates coefficient * self - # implement the naive way: - # start product from 0 (point at infinity) - # use: self.__class__(None, None, a, b) - product = self.__class__(None, None, self.a, self.b) - # loop coefficient times - # use: for _ in range(coefficient): - for _ in range(coefficient): - # keep adding self over and over - product += self - # return the product - return product - # Extra Credit: - # a more advanced technique uses point doubling - # find the binary representation of coefficient - # keep doubling the point and if the bit is there for coefficient - # add the current. - # remember to return an instance of the class + coef = coefficient + current = self + result = self.__class__(None, None, self.a, self.b) + while coef: + if coef & 1: + result += current + current += current + coef >>= 1 + return result class PointTest(TestCase): + def test_ne(self): + a = Point(x=3, y=-7, a=5, b=7) + b = Point(x=18, y=77, a=5, b=7) + self.assertTrue(a != b) + self.assertFalse(a != a) + def test_on_curve(self): - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): Point(x=-2, y=4, a=5, b=7) # these should not raise an error Point(x=3, y=-7, a=5, b=7) @@ -249,7 +252,7 @@ def test_add0(self): self.assertEqual(a+b, b) self.assertEqual(b+a, b) self.assertEqual(b+c, a) - + def test_add1(self): a = Point(x=3, y=7, a=5, b=7) b = Point(x=-1, y=-1, a=5, b=7) @@ -270,16 +273,12 @@ def test_on_curve(self): prime = 223 a = FieldElement(0, prime) b = FieldElement(7, prime) - + valid_points = ((192,105), (17,56), (1,193)) invalid_points = ((200,119), (42,99)) - + # iterate over valid points for x_raw, y_raw in valid_points: - # Initialize points this way: - # x = FieldElement(x_raw, prime) - # y = FieldElement(y_raw, prime) - # Point(x, y, a, b) x = FieldElement(x_raw, prime) y = FieldElement(y_raw, prime) # Creating the point should not result in an error @@ -287,19 +286,12 @@ def test_on_curve(self): # iterate over invalid points for x_raw, y_raw in invalid_points: - # Initialize points this way: - # x = FieldElement(x_raw, prime) - # y = FieldElement(y_raw, prime) - # Point(x, y, a, b) x = FieldElement(x_raw, prime) y = FieldElement(y_raw, prime) - # check that creating the point results in a RuntimeError - # with self.assertRaises(RuntimeError): - # Point(x, y, a, b) - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): Point(x, y, a, b) - def test_add1(self): + def test_add(self): # tests the following additions on curve y^2=x^3-7 over F_223: # (192,105) + (17,56) # (47,71) + (117,141) @@ -309,23 +301,13 @@ def test_add1(self): b = FieldElement(7, prime) additions = ( - # (x1, y1, x2, y2, x3, y3) + # (x1, y1, x2, y2, x3, y3) (192, 105, 17, 56, 170, 142), (47, 71, 117, 141, 60, 139), (143, 98, 76, 66, 47, 71), ) # iterate over the additions for x1_raw, y1_raw, x2_raw, y2_raw, x3_raw, y3_raw in additions: - # Initialize points this way: - # x1 = FieldElement(x1_raw, prime) - # y1 = FieldElement(y1_raw, prime) - # p1 = Point(x1, y1, a, b) - # x2 = FieldElement(x2_raw, prime) - # y2 = FieldElement(y2_raw, prime) - # p2 = Point(x2, y2, a, b) - # x3 = FieldElement(x3_raw, prime) - # y3 = FieldElement(y3_raw, prime) - # p3 = Point(x3, y3, a, b) x1 = FieldElement(x1_raw, prime) y1 = FieldElement(y1_raw, prime) p1 = Point(x1, y1, a, b) @@ -338,6 +320,7 @@ def test_add1(self): # check that p1 + p2 == p3 self.assertEqual(p1+p2, p3) + def test_rmul(self): # tests the following scalar multiplications # 2*(192,105) @@ -362,26 +345,19 @@ def test_rmul(self): # iterate over the multiplications for s, x1_raw, y1_raw, x2_raw, y2_raw in multiplications: - # Initialize points this way: - # x1 = FieldElement(x1_raw, prime) - # y1 = FieldElement(y1_raw, prime) - # p1 = Point(x1, y1, a, b) x1 = FieldElement(x1_raw, prime) y1 = FieldElement(y1_raw, prime) p1 = Point(x1, y1, a, b) # initialize the second point based on whether it's the point at infinity - # x2 = FieldElement(x2_raw, prime) - # y2 = FieldElement(y2_raw, prime) - # p2 = Point(x2, y2, a, b) if x2_raw is None: p2 = Point(None, None, a, b) else: x2 = FieldElement(x2_raw, prime) y2 = FieldElement(y2_raw, prime) p2 = Point(x2, y2, a, b) - + # check that the product is equal to the expected point - self.assertEqual(s*p1, p2) + self.assertEqual(s*p1, p2) A = 0 @@ -406,7 +382,8 @@ def sqrt(self): class S256Point(Point): - bits = 256 + + zero = S256Field(0) def __init__(self, x, y, a=None, b=None): a, b = S256Field(A), S256Field(B) @@ -419,26 +396,20 @@ def __init__(self, x, y, a=None, b=None): def __repr__(self): if self.x is None: - return 'Point(infinity)' + return 'S256Point(infinity)' else: - return 'Point({},{})_{}'.format(self.x.num, self.y.num, self.x.prime) + return 'S256Point({},{})'.format(self.x, self.y) def __rmul__(self, coefficient): - # we want to mod by N to make this simple coef = coefficient % N - # current will undergo binary expansion - current = self - # result is what we return, starts at 0 - result = S256Point(None, None) - # we double 256 times and add where there is a 1 in the binary - # representation of coefficient - for i in range(self.bits): - if coef & 1: - result += current - current += current - # we shift the coefficient to the right - coef >>= 1 - return result + return super().__rmul__(coef) + + def verify(self, z, sig): + s_inv = pow(sig.s, N-2, N) + u = z * s_inv % N + v = sig.r * s_inv % N + total = u*G + v*self + return total.x.num == sig.r def sec(self, compressed=True): # returns the binary version of the sec format, NOT hex @@ -454,36 +425,17 @@ def sec(self, compressed=True): # if non-compressed, starts with b'\x04' followod by self.x and then self.y return b'\x04' + self.x.num.to_bytes(32, 'big') + self.y.num.to_bytes(32, 'big') + def h160(self, compressed=True): + return hash160(self.sec(compressed)) + def address(self, compressed=True, testnet=False): '''Returns the address string''' - # get the sec - sec = self.sec(compressed) - # hash160 the sec - h160 = hash160(sec) - # raw is hash 160 prepended w/ b'\x00' for mainnet, b'\x6f' for testnet + h160 = self.h160(compressed) if testnet: prefix = b'\x6f' else: prefix = b'\x00' - raw = prefix + h160 - # checksum is first 4 bytes of double_sha256 of raw - checksum = double_sha256(raw)[:4] - # encode_base58 the raw + checksum - address = encode_base58(raw+checksum) - # return as a string, you can use .decode('ascii') to do this. - return address.decode('ascii') - - def verify(self, z, sig): - # remember sig.r and sig.s are the main things we're checking - # remember 1/s = pow(s, N-2, N) - s_inv = pow(sig.s, N-2, N) - # u = z / s - u = z * s_inv % N - # v = r / s - v = sig.r * s_inv % N - # u*G + v*P should have as the x coordinate, r - total = u*G + v*self - return total.x.num == sig.r + return encode_base58_checksum(prefix + h160) @classmethod def parse(self, sec_bin): @@ -539,6 +491,19 @@ def test_pubpoint(self): # check that the secret*G is the same as the point self.assertEqual(secret*G, point) + def test_verify(self): + point = S256Point( + 0x887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e4da568744d06c, + 0x61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34) + z = 0xec208baa0fc1c19f708a9ca96fdeff3ac3f230bb4a7ba4aede4942ad003c0f60 + r = 0xac8d1c87e51d0d441be8b3dd5b05c8795b48875dffe00b7ffcfac23010d3a395 + s = 0x68342ceff8935ededd102dd876ffd6ba72d6a427a3edb13d26eb0781cb423c4 + self.assertTrue(point.verify(z, Signature(r, s))) + z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d + r = 0xeff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c + s = 0xc7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6 + self.assertTrue(point.verify(z, Signature(r, s))) + def test_sec(self): coefficient = 999**3 uncompressed = '049d5ca49670cbe4c3bfa84c96a8c87df086c6ea6a24ba6b809c9de234496808d56fa15cc7f3d38cda98dee2419f415b7513dde1301f8643cd9245aea7f3f911f9' @@ -585,26 +550,6 @@ def test_address(self): self.assertEqual( point.address(compressed=False, testnet=True), testnet_address) - def test_verify(self): - point = S256Point( - 0x887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e4da568744d06c, - 0x61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34) - z = 0xec208baa0fc1c19f708a9ca96fdeff3ac3f230bb4a7ba4aede4942ad003c0f60 - r = 0xac8d1c87e51d0d441be8b3dd5b05c8795b48875dffe00b7ffcfac23010d3a395 - s = 0x68342ceff8935ededd102dd876ffd6ba72d6a427a3edb13d26eb0781cb423c4 - self.assertTrue(point.verify(z, Signature(r, s))) - z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d - r = 0xeff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c - s = 0xc7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6 - self.assertTrue(point.verify(z, Signature(r, s))) - - def test_parse(self): - sec = bytes.fromhex('0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278a') - point = S256Point.parse(sec) - want = 0xa56c896489c71dfc65701ce25050f542f336893fb8cd15f4e8e5c124dbf58e47 - self.assertEqual(point.y.num, want) - - class Signature: @@ -679,8 +624,7 @@ def hex(self): return '{:x}'.format(self.secret).zfill(64) def sign(self, z): - # we need a random number k: randint(0, 2**256) - k = randint(0, 2**256) + k = self.deterministic_k(z) # r is the x coordinate of the resulting point k*G r = (k*G).x.num # remember 1/k = pow(k, N-2, N) @@ -693,6 +637,26 @@ def sign(self, z): # Signature(r, s) return Signature(r, s) + def deterministic_k(self, z): + k = b'\x00' * 32 + v = b'\x01' * 32 + if z > N: + z -= N + z_bytes = z.to_bytes(32, 'big') + secret_bytes = self.secret.to_bytes(32, 'big') + s256 = hashlib.sha256 + k = hmac.new(k, v + b'\x00' + secret_bytes + z_bytes, s256).digest() + v = hmac.new(k, v, s256).digest() + k = hmac.new(k, v + b'\x01' + secret_bytes + z_bytes, s256).digest() + v = hmac.new(k, v, s256).digest() + while True: + v = hmac.new(k, v, s256).digest() + candidate = int.from_bytes(v, 'big') + if candidate >= 1 and candidate < N: + return candidate + k = hmac.new(k, v + b'\x00', s256).digest() + v = hmac.new(k, v, s256).digest() + def wif(self, compressed=True, testnet=False): # convert the secret from integer to a 32-bytes in big endian using num.to_bytes(32, 'big') secret_bytes = self.secret.to_bytes(32, 'big') diff --git a/code-ch12/helper.py b/code-ch12/helper.py index 50ee0685..b0479d34 100644 --- a/code-ch12/helper.py +++ b/code-ch12/helper.py @@ -1,14 +1,15 @@ -from subprocess import check_output from unittest import TestCase, TestSuite, TextTestRunner import hashlib -import math SIGHASH_ALL = 1 SIGHASH_NONE = 2 SIGHASH_SINGLE = 3 BASE58_ALPHABET = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' +TWO_WEEKS = 60 * 60 * 24 * 14 +MAX_TARGET = 0xffff*256**(0x1d-3) + def run_test(test): @@ -17,34 +18,6 @@ def run_test(test): TextTestRunner().run(suite) -def bytes_to_str(b, encoding='ascii'): - '''Returns a string version of the bytes''' - return b.decode(encoding) - - -def str_to_bytes(s, encoding='ascii'): - '''Returns a bytes version of the string''' - return s.encode(encoding) - - -def little_endian_to_int(b): - '''little_endian_to_int takes byte sequence as a little-endian number. - Returns an integer''' - # use the from_bytes method of int - return int.from_bytes(b, 'little') - - -def int_to_little_endian(n, length): - '''endian_to_little_endian takes an integer and returns the little-endian - byte sequence of length''' - # use the to_bytes method of n - return n.to_bytes(length, 'little') - - -def hash160(s): - return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest() - - def double_sha256(s): return hashlib.sha256(hashlib.sha256(s).digest()).digest() @@ -64,7 +37,6 @@ def encode_base58(s): while num > 0: num, mod = divmod(num, 58) result.insert(0, BASE58_ALPHABET[mod]) - return prefix + bytes(result) @@ -72,9 +44,20 @@ def encode_base58_checksum(s): return encode_base58(s + double_sha256(s)[:4]).decode('ascii') -def p2pkh_script(h160): - '''Takes a hash160 and returns the scriptPubKey''' - return b'\x76\xa9\x14' + h160 + b'\x88\xac' +def hash160(s): + return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest() + + +def little_endian_to_int(b): + '''little_endian_to_int takes byte sequence as a little-endian number. + Returns an integer''' + return int.from_bytes(b, 'little') + + +def int_to_little_endian(n, length): + '''endian_to_little_endian takes an integer and returns the little-endian + byte sequence of length''' + return n.to_bytes(length, 'little') def decode_base58(s): @@ -120,6 +103,11 @@ def encode_varint(i): raise RuntimeError('integer too large: {}'.format(i)) +def p2pkh_script(h160): + '''Takes a hash160 and returns the scriptPubKey''' + return b'\x76\xa9\x14' + h160 + b'\x88\xac' + + def h160_to_p2pkh_address(h160, testnet=False): '''Takes a byte sequence hash160 and returns a p2pkh address string''' # p2pkh has a prefix of b'\x00' for mainnet, b'\x6f' for testnet @@ -140,6 +128,50 @@ def h160_to_p2sh_address(h160, testnet=False): return encode_base58_checksum(prefix + h160) +def bits_to_target(bits): + '''Turns bits into a target (large 256-bit integer)''' + # last byte is exponent + exponent = bits[-1] + # the first three bytes are the coefficient in little endian + coefficient = little_endian_to_int(bits[:-1]) + # the formula is: + # coefficient * 256**(exponent-3) + return coefficient * 256**(exponent-3) + + +def target_to_bits(target): + '''Turns a target integer back into bits, which is 4 bytes''' + raw_bytes = target.to_bytes(32, 'big') + # get rid of leading 0's + raw_bytes = raw_bytes.lstrip(b'\x00') + if raw_bytes[0] > 0x7f: + # if the first bit is 1, we have to start with 00 + exponent = len(raw_bytes) + 1 + coefficient = b'\x00' + raw_bytes[:2] + else: + # otherwise, we can show the first 3 bytes + # exponent is the number of digits in base-256 + exponent = len(raw_bytes) + # coefficient is the first 3 digits of the base-256 number + coefficient = raw_bytes[:3] + new_bits_big_endian = bytes([exponent]) + coefficient + # we've truncated the number after the first 3 digits of base-256 + return new_bits_big_endian[::-1] + + +def calculate_new_bits(previous_bits, time_differential): + '''Calculates the new bits given + a 2016-block time differential and the previous bits''' + if time_differential > TWO_WEEKS * 4: + time_differential = TWO_WEEKS * 4 + if time_differential < TWO_WEEKS // 4: + time_differential = TWO_WEEKS // 4 + new_target = bits_to_target(previous_bits) * time_differential // TWO_WEEKS + if new_target > MAX_TARGET: + new_target = MAX_TARGET + return target_to_bits(new_target) + + def merkle_parent(hash1, hash2): '''Takes the binary hashes and calculates the double-sha256''' # return the double-sha256 of hash1 + hash2 @@ -250,12 +282,6 @@ def murmur3(data, seed=0): class HelperTest(TestCase): - def test_bytes(self): - b = b'hello world' - s = 'hello world' - self.assertEqual(b, str_to_bytes(s)) - self.assertEqual(s, bytes_to_str(b)) - def test_little_endian_to_int(self): h = bytes.fromhex('99c3980000000000') want = 10011545 @@ -294,6 +320,12 @@ def test_p2sh_address(self): want = '2N3u1R6uwQfuobCqbCgBkpsgBxvr1tZpe7B' self.assertEqual(h160_to_p2sh_address(h160, testnet=True), want) + def test_calculate_new_bits(self): + prev_bits = bytes.fromhex('54d80118') + time_differential = 302400 + want = bytes.fromhex('00157617') + self.assertEqual(calculate_new_bits(prev_bits, time_differential), want) + def test_merkle_parent(self): tx_hash0 = bytes.fromhex('c117ea8ec828342f4dfb0ad6bd140e03a50720ece40169ee38bdc15d9eb64cf5') tx_hash1 = bytes.fromhex('c131474164b412e3406696da1ee20ab0fc9bf41c8f05fa8ceea7a08d672d7cc5') @@ -350,4 +382,4 @@ def test_bit_field_to_bytes(self): bit_field = [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0] want = '4000600a080000010940' self.assertEqual(bit_field_to_bytes(bit_field).hex(), want) - self.assertEqual(bytes_to_bit_field(bytes.fromhex(want)), bit_field) \ No newline at end of file + self.assertEqual(bytes_to_bit_field(bytes.fromhex(want)), bit_field) diff --git a/code-ch12/merkleblock.py b/code-ch12/merkleblock.py index 30d4cf95..899411cd 100644 --- a/code-ch12/merkleblock.py +++ b/code-ch12/merkleblock.py @@ -41,7 +41,10 @@ def __repr__(self): result = '' for depth, level in enumerate(self.nodes): for index, h in enumerate(level): - short = '{}...'.format(h.hex()[:8]) + if h is None: + short = 'None' + else: + short = '{}...'.format(h.hex()[:8]) if depth == self.current_depth and index == self.current_index: result += '*{}*, '.format(short[:-2]) else: diff --git a/code-ch12/network.py b/code-ch12/network.py index 9fe21b60..6a197f50 100644 --- a/code-ch12/network.py +++ b/code-ch12/network.py @@ -343,6 +343,7 @@ def wait_for_commands(self, commands): # return the last envelope we got return envelope + class SimpleNodeTest(TestCase): def test_handshake(self): diff --git a/code-ch12/tx.py b/code-ch12/tx.py index c369658e..51608a02 100644 --- a/code-ch12/tx.py +++ b/code-ch12/tx.py @@ -33,14 +33,13 @@ def __repr__(self): tx_outs = '' for tx_out in self.tx_outs: tx_outs += tx_out.__repr__() + '\n' - return 'tx:{}\nversion: {}\ntx_ins:\n{}\ntx_outs:\n{}\nlocktime: {}\n'.format( - self.hash().hex(), + return 'version: {}\ntx_ins:\n{}\ntx_outs:\n{}\nlocktime: {}\n'.format( self.version, tx_ins, tx_outs, self.locktime, ) - + def hash(self): return double_sha256(self.serialize())[::-1]