Jimmy Song is a developer with over 20 years' experience who started his career by reading the second edition of Programming Perl, published by the very same publisher as this book. He's been involved in way too many startups and got into Bitcoin full time in 2014. He's contributed to many different Bitcoin open source projects over the years, including Armory, Bitcoin Core, btcd, and pycoin.
4 |
If you ever meet Jimmy and want him to rant, ask him about Bitcoin, sound money, how fiat money makes everything worse, fasting, carnivory, powerlifting, raising children, or cowboy hats.
11 | ++++
12 |
13 | # end::exercise1[]
14 | # tag::answer1[]
15 | >>> def on_curve(x, y):
16 | ... return y**2 == x**3 + 5*x + 7
17 | >>> print(on_curve(2,4))
18 | False
19 | >>> print(on_curve(-1,-1))
20 | True
21 | >>> print(on_curve(18,77))
22 | True
23 | >>> print(on_curve(5,7))
24 | False
25 |
26 | # end::answer1[]
27 | # tag::exercise4[]
28 | ==== Exercise 4
29 |
30 | For the curve __y__^2^ = __x__^3^ + 5__x__ + 7, what is (2,5) + (–1,–1)?
31 | # end::exercise4[]
32 | # tag::answer4[]
33 | >>> x1, y1 = 2, 5
34 | >>> x2, y2 = -1, -1
35 | >>> s = (y2 - y1) / (x2 - x1)
36 | >>> x3 = s**2 - x1 - x2
37 | >>> y3 = s * (x1 - x3) - y1
38 | >>> print(x3, y3)
39 | 3.0 -7.0
40 |
41 | # end::answer4[]
42 | # tag::exercise6[]
43 | ==== Exercise 6
44 |
45 | For the curve __y__^2^ = __x__^3^ + 5__x__ + 7, what is (–1,–1) + (–1,–1)?
46 | # end::exercise6[]
47 | # tag::answer6[]
48 | >>> a, x1, y1 = 5, -1, -1
49 | >>> s = (3 * x1**2 + a) / (2 * y1)
50 | >>> x3 = s**2 - 2*x1
51 | >>> y3 = s*(x1-x3)-y1
52 | >>> print(x3,y3)
53 | 18.0 77.0
54 |
55 | # end::answer6[]
56 | '''
57 |
58 |
59 | from unittest import TestCase
60 |
61 | from ecc import Point
62 |
63 |
64 | '''
65 | # tag::exercise2[]
66 | ==== Exercise 2
67 |
68 | Write the `__ne__` method for `Point`.
69 | # end::exercise2[]
70 | '''
71 |
72 |
73 | # tag::answer2[]
74 | def __ne__(self, other):
75 | return not (self == other)
76 | # end::answer2[]
77 |
78 |
79 | '''
80 | # tag::exercise3[]
81 | ==== Exercise 3
82 |
83 | Handle the case where the two points are additive inverses (that is, they have the same `x` but a different `y`, causing a vertical line). This should return the point at infinity.
84 | # end::exercise3[]
85 | # tag::exercise5[]
86 | ==== Exercise 5
87 |
88 | Write the `__add__` method where __x__~1~ ≠ __x__~2~.
89 | # end::exercise5[]
90 | # tag::exercise7[]
91 | ==== Exercise 7
92 |
93 | Write the `__add__` method when __P__~1~ = __P__~2~.
94 | # end::exercise7[]
95 | '''
96 |
97 |
98 | def __add__(self, other):
99 | if self.a != other.a or self.b != other.b:
100 | raise TypeError
101 | if self.x is None:
102 | return other
103 | if other.x is None:
104 | return self
105 | # tag::answer3[]
106 | if self.x == other.x and self.y != other.y:
107 | return self.__class__(None, None, self.a, self.b)
108 | # end::answer3[]
109 | # tag::answer5[]
110 | if self.x != other.x:
111 | s = (other.y - self.y) / (other.x - self.x)
112 | x = s**2 - self.x - other.x
113 | y = s * (self.x - x) - self.y
114 | return self.__class__(x, y, self.a, self.b)
115 | # end::answer5[]
116 | if self == other and self.y == 0 * self.x:
117 | return self.__class__(None, None, self.a, self.b)
118 | # tag::answer7[]
119 | if self == other:
120 | s = (3 * self.x**2 + self.a) / (2 * self.y)
121 | x = s**2 - 2 * self.x
122 | y = s * (self.x - x) - self.y
123 | return self.__class__(x, y, self.a, self.b)
124 | # end::answer7[]
125 |
126 |
127 | class ChapterTest(TestCase):
128 |
129 | def test_apply(self):
130 | Point.__ne__ = __ne__
131 | Point.__add__ = __add__
132 |
--------------------------------------------------------------------------------
/code-ch02/examples.py:
--------------------------------------------------------------------------------
1 | """
2 | # tag::example1[]
3 | >>> from ecc import Point
4 | >>> p1 = Point(-1, -1, 5, 7)
5 | >>> p2 = Point(-1, -2, 5, 7)
6 | Traceback (most recent call last):
7 | File "", line 1, in
8 | File "ecc.py", line 143, in __init__
9 | raise ValueError('({}, {}) is not on the curve'.format(self.x, self.y))
10 | ValueError: (-1, -2) is not on the curve
11 |
12 | # end::example1[]
13 | # tag::example2[]
14 | >>> from ecc import Point
15 | >>> p1 = Point(-1, -1, 5, 7)
16 | >>> p2 = Point(-1, 1, 5, 7)
17 | >>> inf = Point(None, None, 5, 7)
18 | >>> print(p1 + inf)
19 | Point(-1,-1)_5_7
20 | >>> print(inf + p2)
21 | Point(-1,1)_5_7
22 | >>> print(p1 + p2)
23 | Point(infinity)
24 |
25 | # end::example2[]
26 | """
27 |
--------------------------------------------------------------------------------
/code-ch02/helper.py:
--------------------------------------------------------------------------------
1 | from unittest import TestSuite, TextTestRunner
2 |
3 |
4 | def run(test):
5 | suite = TestSuite()
6 | suite.addTest(test)
7 | TextTestRunner().run(suite)
8 |
--------------------------------------------------------------------------------
/code-ch02/jupyter.txt:
--------------------------------------------------------------------------------
1 | import ecc
2 | import helper
3 |
4 | from ecc import Point
5 | ---
6 | example1
7 | ---
8 | exercise1:
9 | # (2,4), (-1,-1), (18,77), (5,7)
10 | # equation in python is: y**2 == x**3 + 5*x + 7
11 | ---
12 | exercise2:ecc:PointTest:test_ne
13 | ---
14 | example2
15 | ---
16 | exercise3:ecc:PointTest:test_add0
17 | ---
18 | exercise4:
19 | from ecc import Point
20 |
21 | a = 5
22 | b = 7
23 | x1, y1 = 2, 5
24 | x2, y2 = -1, -1
25 |
26 | # (x1,y1) + (x2,y2)
27 | ---
28 | exercise5:ecc:PointTest:test_add1
29 | ---
30 | exercise6:
31 | from ecc import Point
32 |
33 | a = 5
34 | b = 7
35 | x1, y1 = -1, -1
36 | # (-1,-1) + (-1,-1)
37 | ---
38 | exercise7:ecc:PointTest:test_add2
39 |
--------------------------------------------------------------------------------
/code-ch03/examples.py:
--------------------------------------------------------------------------------
1 | """
2 | # tag::example1[]
3 | >>> from ecc import FieldElement, Point
4 | >>> a = FieldElement(num=0, prime=223)
5 | >>> b = FieldElement(num=7, prime=223)
6 | >>> x = FieldElement(num=192, prime=223)
7 | >>> y = FieldElement(num=105, prime=223)
8 | >>> p1 = Point(x, y, a, b)
9 | >>> print(p1)
10 | Point(192,105)_0_7 FieldElement(223)
11 |
12 | # end::example1[]
13 | # tag::example3[]
14 | >>> from ecc import FieldElement, Point
15 | >>> prime = 223
16 | >>> a = FieldElement(num=0, prime=prime)
17 | >>> b = FieldElement(num=7, prime=prime)
18 | >>> x1 = FieldElement(num=192, prime=prime)
19 | >>> y1 = FieldElement(num=105, prime=prime)
20 | >>> x2 = FieldElement(num=17, prime=prime)
21 | >>> y2 = FieldElement(num=56, prime=prime)
22 | >>> p1 = Point(x1, y1, a, b)
23 | >>> p2 = Point(x2, y2, a, b)
24 | >>> print(p1+p2)
25 | Point(170,142)_0_7 FieldElement(223)
26 |
27 | # end::example3[]
28 | # tag::example4[]
29 | >>> from ecc import FieldElement, Point
30 | >>> prime = 223
31 | >>> a = FieldElement(0, prime)
32 | >>> b = FieldElement(7, prime)
33 | >>> x = FieldElement(47, prime)
34 | >>> y = FieldElement(71, prime)
35 | >>> p = Point(x, y, a, b)
36 | >>> for s in range(1,21):
37 | ... result = s*p
38 | ... print('{}*(47,71)=({},{})'.format(s,result.x.num,result.y.num))
39 | 1*(47,71)=(47,71)
40 | 2*(47,71)=(36,111)
41 | 3*(47,71)=(15,137)
42 | 4*(47,71)=(194,51)
43 | 5*(47,71)=(126,96)
44 | 6*(47,71)=(139,137)
45 | 7*(47,71)=(92,47)
46 | 8*(47,71)=(116,55)
47 | 9*(47,71)=(69,86)
48 | 10*(47,71)=(154,150)
49 | 11*(47,71)=(154,73)
50 | 12*(47,71)=(69,137)
51 | 13*(47,71)=(116,168)
52 | 14*(47,71)=(92,176)
53 | 15*(47,71)=(139,86)
54 | 16*(47,71)=(126,127)
55 | 17*(47,71)=(194,172)
56 | 18*(47,71)=(15,86)
57 | 19*(47,71)=(36,112)
58 | 20*(47,71)=(47,152)
59 |
60 | # end::example4[]
61 | # tag::example5[]
62 | >>> from ecc import FieldElement, Point
63 | >>> prime = 223
64 | >>> a = FieldElement(0, prime)
65 | >>> b = FieldElement(7, prime)
66 | >>> x = FieldElement(15, prime)
67 | >>> y = FieldElement(86, prime)
68 | >>> p = Point(x, y, a, b)
69 | >>> print(7*p)
70 | Point(infinity)
71 |
72 | # end::example5[]
73 | # tag::example6[]
74 | >>> gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
75 | >>> gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
76 | >>> p = 2**256 - 2**32 - 977
77 | >>> print(gy**2 % p == (gx**3 + 7) % p)
78 | True
79 |
80 | # end::example6[]
81 | # tag::example7[]
82 | >>> from ecc import FieldElement, Point
83 | >>> gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
84 | >>> gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
85 | >>> p = 2**256 - 2**32 - 977
86 | >>> n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
87 | >>> x = FieldElement(gx, p)
88 | >>> y = FieldElement(gy, p)
89 | >>> seven = FieldElement(7, p)
90 | >>> zero = FieldElement(0, p)
91 | >>> G = Point(x, y, zero, seven)
92 | >>> print(n*G)
93 | Point(infinity)
94 |
95 | # end::example7[]
96 | # tag::example8[]
97 | >>> from ecc import G, N
98 | >>> print(N*G)
99 | S256Point(infinity)
100 |
101 | # end::example8[]
102 | # tag::example9[]
103 | >>> from ecc import S256Point, G, N
104 | >>> z = 0xbc62d4b80d9e36da29c16c5d4d9f11731f36052c72401a76c23c0fb5a9b74423
105 | >>> r = 0x37206a0610995c58074999cb9767b87af4c4978db68c06e8e6e81d282047a7c6
106 | >>> s = 0x8ca63759c1157ebeaec0d03cecca119fc9a75bf8e6d0fa65c841c8e2738cdaec
107 | >>> px = 0x04519fac3d910ca7e7138f7013706f619fa8f033e6ec6e09370ea38cee6a7574
108 | >>> py = 0x82b51eab8c27c66e26c858a079bcdf4f1ada34cec420cafc7eac1a42216fb6c4
109 | >>> point = S256Point(px, py)
110 | >>> s_inv = pow(s, N-2, N) # <1>
111 | >>> u = z * s_inv % N # <2>
112 | >>> v = r * s_inv % N # <3>
113 | >>> print((u*G + v*point).x.num == r) # <4>
114 | True
115 |
116 | # end::example9[]
117 | # tag::example10[]
118 | >>> from ecc import S256Point, G, N
119 | >>> from helper import hash256
120 | >>> e = int.from_bytes(hash256(b'my secret'), 'big') # <1>
121 | >>> z = int.from_bytes(hash256(b'my message'), 'big') # <2>
122 | >>> k = 1234567890 # <3>
123 | >>> r = (k*G).x.num # <4>
124 | >>> k_inv = pow(k, N-2, N)
125 | >>> s = (z+r*e) * k_inv % N # <5>
126 | >>> point = e*G # <6>
127 | >>> print(point)
128 | S256Point(028d003eab2e428d11983f3e97c3fa0addf3b42740df0d211795ffb3be2f6c52, \
129 | 0ae987b9ec6ea159c78cb2a937ed89096fb218d9e7594f02b547526d8cd309e2)
130 | >>> print(hex(z))
131 | 0x231c6f3d980a6b0fb7152f85cee7eb52bf92433d9919b9c5218cb08e79cce78
132 | >>> print(hex(r))
133 | 0x2b698a0f0a4041b77e63488ad48c23e8e8838dd1fb7520408b121697b782ef22
134 | >>> print(hex(s))
135 | 0xbb14e602ef9e3f872e25fad328466b34e6734b7a0fcd58b1eb635447ffae8cb9
136 |
137 | # end::example10[]
138 | """
139 |
--------------------------------------------------------------------------------
/code-ch03/helper.py:
--------------------------------------------------------------------------------
1 | from unittest import TestSuite, TextTestRunner
2 |
3 | import hashlib
4 |
5 |
6 | def run(test):
7 | suite = TestSuite()
8 | suite.addTest(test)
9 | TextTestRunner().run(suite)
10 |
11 |
12 | def hash256(s):
13 | '''two rounds of sha256'''
14 | return hashlib.sha256(hashlib.sha256(s).digest()).digest()
15 |
--------------------------------------------------------------------------------
/code-ch03/jupyter.txt:
--------------------------------------------------------------------------------
1 | import ecc
2 | import helper
3 |
4 | from ecc import FieldElement, Point
5 | ---
6 | exercise1:
7 | prime = 223
8 | a = FieldElement(0, prime)
9 | b = FieldElement(7, prime)
10 |
11 | # (192,105), (17,56), (200,119), (1,193), (42,99)
12 | ---
13 | example1
14 | ---
15 | example3
16 | ---
17 | exercise2:
18 | prime = 223
19 | a = FieldElement(0, prime)
20 | b = FieldElement(7, prime)
21 |
22 | # (170,142) + (60,139)
23 | # (47,71) + (17,56)
24 | # (143,98) + (76,66)
25 | ---
26 | exercise3:ecc:ECCTest:test_add
27 | ---
28 | exercise4:
29 | prime = 223
30 | a = FieldElement(0, prime)
31 | b = FieldElement(7, prime)
32 |
33 | # 2*(192, 105)
34 | # 2*(143, 98)
35 | # 2*(47, 71)
36 | # 4*(47, 71)
37 | # 8*(47, 71)
38 | # 21*(47, 71)
39 |
40 | # create a product variable
41 | # add the point to the product n times
42 | # print the product
43 | ---
44 | example4
45 | ---
46 | exercise5:
47 | prime = 223
48 | a = FieldElement(0, prime)
49 | b = FieldElement(7, prime)
50 | x = FieldElement(15, prime)
51 | y = FieldElement(86, prime)
52 | p = Point(x, y, a, b)
53 | inf = Point(None, None, a, b)
54 |
55 | # create a product variable
56 | # create a counter variable
57 | # loop until the product is the point at infinity
58 | # add the point to the product and increment counter
59 | # print the counter when exited from loop
60 | ---
61 | example5
62 | ---
63 | example6
64 | ---
65 | example7
66 | ---
67 | example8
68 | ---
69 | example9
70 | ---
71 | exercise6:
72 | point = S256Point(
73 | 0x887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e4da568744d06c,
74 | 0x61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34)
75 | # signature 1
76 | z = 0xec208baa0fc1c19f708a9ca96fdeff3ac3f230bb4a7ba4aede4942ad003c0f60
77 | r = 0xac8d1c87e51d0d441be8b3dd5b05c8795b48875dffe00b7ffcfac23010d3a395
78 | s = 0x68342ceff8935ededd102dd876ffd6ba72d6a427a3edb13d26eb0781cb423c4
79 | # signature 2
80 | z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d
81 | r = 0xeff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c
82 | s = 0xc7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6
83 | ---
84 | example10
85 | ---
86 | exercise7:
87 | # Exercise 7
88 |
89 | e = 12345
90 | z = int.from_bytes(hash256(b'Programming Bitcoin!'), 'big')
91 |
92 | # choose a random k
93 | # calculate r (kG's x-coordinate)
94 | # calculate s ((z+re)/k)
95 | # print the point, z, r and s
96 |
--------------------------------------------------------------------------------
/code-ch04/examples.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/code-ch04/examples.py
--------------------------------------------------------------------------------
/code-ch04/helper.py:
--------------------------------------------------------------------------------
1 | from unittest import TestCase, TestSuite, TextTestRunner
2 |
3 | import hashlib
4 |
5 |
6 | # tag::source1[]
7 | BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
8 | # end::source1[]
9 |
10 |
11 | def run(test):
12 | suite = TestSuite()
13 | suite.addTest(test)
14 | TextTestRunner().run(suite)
15 |
16 |
17 | # tag::source4[]
18 | def hash160(s):
19 | '''sha256 followed by ripemd160'''
20 | return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest() # <1>
21 | # end::source4[]
22 |
23 |
24 | def hash256(s):
25 | '''two rounds of sha256'''
26 | return hashlib.sha256(hashlib.sha256(s).digest()).digest()
27 |
28 |
29 | # tag::source2[]
30 | def encode_base58(s):
31 | count = 0
32 | for c in s: # <1>
33 | if c == 0:
34 | count += 1
35 | else:
36 | break
37 | num = int.from_bytes(s, 'big')
38 | prefix = '1' * count
39 | result = ''
40 | while num > 0: # <2>
41 | num, mod = divmod(num, 58)
42 | result = BASE58_ALPHABET[mod] + result
43 | return prefix + result # <3>
44 | # end::source2[]
45 |
46 |
47 | # tag::source3[]
48 | def encode_base58_checksum(b):
49 | return encode_base58(b + hash256(b)[:4])
50 | # end::source3[]
51 |
52 |
53 | def decode_base58(s):
54 | num = 0
55 | for c in s:
56 | num *= 58
57 | num += BASE58_ALPHABET.index(c)
58 | combined = num.to_bytes(25, byteorder='big')
59 | checksum = combined[-4:]
60 | if hash256(combined[:-4])[:4] != checksum:
61 | raise ValueError('bad address: {} {}'.format(checksum, hash256(combined[:-4])[:4]))
62 | return combined[1:-4]
63 |
64 |
65 | def little_endian_to_int(b):
66 | '''little_endian_to_int takes byte sequence as a little-endian number.
67 | Returns an integer'''
68 | # use int.from_bytes()
69 | raise NotImplementedError
70 |
71 |
72 | def int_to_little_endian(n, length):
73 | '''endian_to_little_endian takes an integer and returns the little-endian
74 | byte sequence of length'''
75 | # use n.to_bytes()
76 | raise NotImplementedError
77 |
78 |
79 | class HelperTest(TestCase):
80 |
81 | def test_little_endian_to_int(self):
82 | h = bytes.fromhex('99c3980000000000')
83 | want = 10011545
84 | self.assertEqual(little_endian_to_int(h), want)
85 | h = bytes.fromhex('a135ef0100000000')
86 | want = 32454049
87 | self.assertEqual(little_endian_to_int(h), want)
88 |
89 | def test_int_to_little_endian(self):
90 | n = 1
91 | want = b'\x01\x00\x00\x00'
92 | self.assertEqual(int_to_little_endian(n, 4), want)
93 | n = 10011545
94 | want = b'\x99\xc3\x98\x00\x00\x00\x00\x00'
95 | self.assertEqual(int_to_little_endian(n, 8), want)
96 |
--------------------------------------------------------------------------------
/code-ch04/jupyter.txt:
--------------------------------------------------------------------------------
1 | import ecc
2 | import helper
3 | ---
4 | exercise1:
5 | from ecc import PrivateKey
6 |
7 | # 5000
8 | # 2018**5
9 | # 0xdeadbeef12345
10 | # privatekey.point is the public key for a private key
11 | ---
12 | exercise2:
13 | from ecc import PrivateKey
14 |
15 | # 5001
16 | # 2019**5
17 | # 0xdeadbeef54321
18 | ---
19 | exercise3:
20 | from ecc import Signature
21 |
22 | r = 0x37206a0610995c58074999cb9767b87af4c4978db68c06e8e6e81d282047a7c6
23 | s = 0x8ca63759c1157ebeaec0d03cecca119fc9a75bf8e6d0fa65c841c8e2738cdaec
24 | ---
25 | exercise4:
26 | from helper import encode_base58
27 |
28 | # 7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d
29 | # eff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c
30 | # c7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6
31 | ---
32 | exercise5:
33 | from ecc import PrivateKey
34 |
35 | # 5002 (use uncompressed SEC, on testnet)
36 | # 2020**5 (use compressed SEC, on testnet)
37 | # 0x12345deadbeef (use compressed SEC on mainnet)
38 | ---
39 | exercise6:
40 | from ecc import PrivateKey
41 |
42 | # 5003
43 | # 2021**5
44 | # 0x54321deadbeef
45 | ---
46 | exercise7:helper:HelperTest:test_little_endian_to_int
47 | ---
48 | exercise8:helper:HelperTest:test_int_to_little_endian
49 | ---
50 | exercise9:
51 | from ecc import PrivateKey
52 | from helper import hash256, little_endian_to_int
53 |
54 | # select a passphrase here, add your email address into the passphrase for security
55 | # passphrase = b'your@email.address some secret only you know'
56 | # secret = little_endian_to_int(hash256(passphrase))
57 | # create a private key using your secret
58 | # print an address from the public point of the private key with testnet=True
59 |
--------------------------------------------------------------------------------
/code-ch05/examples.py:
--------------------------------------------------------------------------------
1 | """
2 | # tag::example1[]
3 | >>> from io import BytesIO
4 | >>> from script import Script # <1>
5 | >>> script_hex = ('6b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccf\
6 | cf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8\
7 | e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278\
8 | a')
9 | >>> stream = BytesIO(bytes.fromhex(script_hex))
10 | >>> script_sig = Script.parse(stream)
11 | >>> print(script_sig)
12 | 3045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f0220\
13 | 7a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01 0349fc4e631\
14 | e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278a
15 |
16 | # end::example1[]
17 | """
18 |
--------------------------------------------------------------------------------
/code-ch05/helper.py:
--------------------------------------------------------------------------------
1 | from unittest import TestCase, TestSuite, TextTestRunner
2 |
3 | import hashlib
4 |
5 |
6 | BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
7 |
8 |
9 | def run(test):
10 | suite = TestSuite()
11 | suite.addTest(test)
12 | TextTestRunner().run(suite)
13 |
14 |
15 | def hash160(s):
16 | '''sha256 followed by ripemd160'''
17 | return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest()
18 |
19 |
20 | def hash256(s):
21 | '''two rounds of sha256'''
22 | return hashlib.sha256(hashlib.sha256(s).digest()).digest()
23 |
24 |
25 | def encode_base58(s):
26 | # determine how many 0 bytes (b'\x00') s starts with
27 | count = 0
28 | for c in s:
29 | if c == 0:
30 | count += 1
31 | else:
32 | break
33 | # convert to big endian integer
34 | num = int.from_bytes(s, 'big')
35 | prefix = '1' * count
36 | result = ''
37 | while num > 0:
38 | num, mod = divmod(num, 58)
39 | result = BASE58_ALPHABET[mod] + result
40 | return prefix + result
41 |
42 |
43 | def encode_base58_checksum(s):
44 | return encode_base58(s + hash256(s)[:4])
45 |
46 |
47 | def decode_base58(s):
48 | num = 0
49 | for c in s:
50 | num *= 58
51 | num += BASE58_ALPHABET.index(c)
52 | combined = num.to_bytes(25, byteorder='big')
53 | checksum = combined[-4:]
54 | if hash256(combined[:-4])[:4] != checksum:
55 | raise ValueError('bad address: {} {}'.format(checksum, hash256(combined[:-4])[:4]))
56 | return combined[1:-4]
57 |
58 |
59 | def little_endian_to_int(b):
60 | '''little_endian_to_int takes byte sequence as a little-endian number.
61 | Returns an integer'''
62 | return int.from_bytes(b, 'little')
63 |
64 |
65 | def int_to_little_endian(n, length):
66 | '''endian_to_little_endian takes an integer and returns the little-endian
67 | byte sequence of length'''
68 | return n.to_bytes(length, 'little')
69 |
70 |
71 | # tag::source1[]
72 | def read_varint(s):
73 | '''read_varint reads a variable integer from a stream'''
74 | i = s.read(1)[0]
75 | if i == 0xfd:
76 | # 0xfd means the next two bytes are the number
77 | return little_endian_to_int(s.read(2))
78 | elif i == 0xfe:
79 | # 0xfe means the next four bytes are the number
80 | return little_endian_to_int(s.read(4))
81 | elif i == 0xff:
82 | # 0xff means the next eight bytes are the number
83 | return little_endian_to_int(s.read(8))
84 | else:
85 | # anything else is just the integer
86 | return i
87 |
88 |
89 | def encode_varint(i):
90 | '''encodes an integer as a varint'''
91 | if i < 0xfd:
92 | return bytes([i])
93 | elif i < 0x10000:
94 | return b'\xfd' + int_to_little_endian(i, 2)
95 | elif i < 0x100000000:
96 | return b'\xfe' + int_to_little_endian(i, 4)
97 | elif i < 0x10000000000000000:
98 | return b'\xff' + int_to_little_endian(i, 8)
99 | else:
100 | raise ValueError('integer too large: {}'.format(i))
101 | # end::source1[]
102 |
103 |
104 | class HelperTest(TestCase):
105 |
106 | def test_little_endian_to_int(self):
107 | h = bytes.fromhex('99c3980000000000')
108 | want = 10011545
109 | self.assertEqual(little_endian_to_int(h), want)
110 | h = bytes.fromhex('a135ef0100000000')
111 | want = 32454049
112 | self.assertEqual(little_endian_to_int(h), want)
113 |
114 | def test_int_to_little_endian(self):
115 | n = 1
116 | want = b'\x01\x00\x00\x00'
117 | self.assertEqual(int_to_little_endian(n, 4), want)
118 | n = 10011545
119 | want = b'\x99\xc3\x98\x00\x00\x00\x00\x00'
120 | self.assertEqual(int_to_little_endian(n, 8), want)
121 |
--------------------------------------------------------------------------------
/code-ch05/jupyter.txt:
--------------------------------------------------------------------------------
1 | import ecc
2 | import helper
3 | import script
4 | import tx
5 | ---
6 | exercise1:tx:TxTest:test_parse_version
7 | ---
8 | example1
9 | ---
10 | exercise2:tx:TxTest:test_parse_inputs
11 | ---
12 | exercise3:tx:TxTest:test_parse_outputs
13 | ---
14 | exercise4:tx:TxTest:test_parse_locktime
15 | ---
16 | exercise5:
17 | from io import BytesIO
18 | from tx import Tx
19 |
20 | hex_transaction = '010000000456919960ac691763688d3d3bcea9ad6ecaf875df5339e148a1fc61c6ed7a069e010000006a47304402204585bcdef85e6b1c6af5c2669d4830ff86e42dd205c0e089bc2a821657e951c002201024a10366077f87d6bce1f7100ad8cfa8a064b39d4e8fe4ea13a7b71aa8180f012102f0da57e85eec2934a82a585ea337ce2f4998b50ae699dd79f5880e253dafafb7feffffffeb8f51f4038dc17e6313cf831d4f02281c2a468bde0fafd37f1bf882729e7fd3000000006a47304402207899531a52d59a6de200179928ca900254a36b8dff8bb75f5f5d71b1cdc26125022008b422690b8461cb52c3cc30330b23d574351872b7c361e9aae3649071c1a7160121035d5c93d9ac96881f19ba1f686f15f009ded7c62efe85a872e6a19b43c15a2937feffffff567bf40595119d1bb8a3037c356efd56170b64cbcc160fb028fa10704b45d775000000006a47304402204c7c7818424c7f7911da6cddc59655a70af1cb5eaf17c69dadbfc74ffa0b662f02207599e08bc8023693ad4e9527dc42c34210f7a7d1d1ddfc8492b654a11e7620a0012102158b46fbdff65d0172b7989aec8850aa0dae49abfb84c81ae6e5b251a58ace5cfeffffffd63a5e6c16e620f86f375925b21cabaf736c779f88fd04dcad51d26690f7f345010000006a47304402200633ea0d3314bea0d95b3cd8dadb2ef79ea8331ffe1e61f762c0f6daea0fabde022029f23b3e9c30f080446150b23852028751635dcee2be669c2a1686a4b5edf304012103ffd6f4a67e94aba353a00882e563ff2722eb4cff0ad6006e86ee20dfe7520d55feffffff0251430f00000000001976a914ab0c0b2e98b1ab6dbf67d4750b0a56244948a87988ac005a6202000000001976a9143c82d7df364eb6c75be8c80df2b3eda8db57397088ac46430600'
21 |
22 | # convert the hex_transaction to binary
23 | # create a stream using BytesIO
24 | # use Tx.parse to get the transaction object.
25 | # ScriptSig from second input
26 | # ScriptPubKey from first output
27 | # Amount from second output
28 | ---
29 | exercise6:tx:TxTest:test_fee
30 |
--------------------------------------------------------------------------------
/code-ch06/Chapter6.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "############## PLEASE RUN THIS CELL FIRST! ###################\n",
10 | "\n",
11 | "# import everything and define a test runner function\n",
12 | "from importlib import reload\n",
13 | "from helper import run\n",
14 | "import op\n",
15 | "import script"
16 | ]
17 | },
18 | {
19 | "cell_type": "markdown",
20 | "metadata": {},
21 | "source": [
22 | "### Exercise 1\n",
23 | "\n",
24 | "Write the `op_hash160` function.\n",
25 | "\n",
26 | "#### Make [this test](/edit/code-ch06/op.py) pass: `op.py:OpTest:test_op_hash160`"
27 | ]
28 | },
29 | {
30 | "cell_type": "code",
31 | "execution_count": null,
32 | "metadata": {},
33 | "outputs": [],
34 | "source": [
35 | "# Exercise 1\n",
36 | "\n",
37 | "reload(op)\n",
38 | "run(op.OpTest(\"test_op_hash160\"))"
39 | ]
40 | },
41 | {
42 | "cell_type": "code",
43 | "execution_count": null,
44 | "metadata": {},
45 | "outputs": [],
46 | "source": [
47 | "from script import Script\n",
48 | "z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d\n",
49 | "sec = bytes.fromhex('04887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e4da568744d06c61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34')\n",
50 | "sig = bytes.fromhex('3045022000eff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c022100c7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab601')\n",
51 | "script_pubkey = Script([sec, 0xac])\n",
52 | "script_sig = Script([sig])\n",
53 | "combined_script = script_sig + script_pubkey\n",
54 | "print(combined_script.evaluate(z))"
55 | ]
56 | },
57 | {
58 | "cell_type": "markdown",
59 | "metadata": {},
60 | "source": [
61 | "### Exercise 2\n",
62 | "\n",
63 | "Write the `op_checksig` function in `op.py`\n",
64 | "\n",
65 | "#### Make [this test](/edit/code-ch06/op.py) pass: `op.py:OpTest:test_op_checksig`"
66 | ]
67 | },
68 | {
69 | "cell_type": "code",
70 | "execution_count": null,
71 | "metadata": {},
72 | "outputs": [],
73 | "source": [
74 | "# Exercise 2\n",
75 | "\n",
76 | "reload(op)\n",
77 | "run(op.OpTest(\"test_op_checksig\"))"
78 | ]
79 | },
80 | {
81 | "cell_type": "markdown",
82 | "metadata": {},
83 | "source": [
84 | "### Exercise 3\n",
85 | "\n",
86 | "Create a ScriptSig that can unlock this ScriptPubKey. Note `OP_MUL` multiplies the top two elements of the stack.\n",
87 | "\n",
88 | "`767695935687`\n",
89 | "\n",
90 | "* `56 = OP_6`\n",
91 | "* `76 = OP_DUP`\n",
92 | "* `87 = OP_EQUAL`\n",
93 | "* `93 = OP_ADD`\n",
94 | "* `95 = OP_MUL`"
95 | ]
96 | },
97 | {
98 | "cell_type": "code",
99 | "execution_count": null,
100 | "metadata": {},
101 | "outputs": [],
102 | "source": [
103 | "# Exercise 3\n",
104 | "\n",
105 | "from script import Script\n",
106 | "\n",
107 | "script_pubkey = Script([0x76, 0x76, 0x95, 0x93, 0x56, 0x87])\n",
108 | "script_sig = Script([]) # FILL THIS IN\n",
109 | "combined_script = script_sig + script_pubkey\n",
110 | "print(combined_script.evaluate(0))"
111 | ]
112 | },
113 | {
114 | "cell_type": "markdown",
115 | "metadata": {},
116 | "source": [
117 | "### Exercise 4\n",
118 | "\n",
119 | "Figure out what this Script is doing:\n",
120 | "\n",
121 | "`6e879169a77ca787`\n",
122 | "\n",
123 | "* `69 = OP_VERIFY`\n",
124 | "* `6e = OP_2DUP`\n",
125 | "* `7c = OP_SWAP`\n",
126 | "* `87 = OP_EQUAL`\n",
127 | "* `91 = OP_NOT`\n",
128 | "* `a7 = OP_SHA1`\n",
129 | "\n",
130 | "Use the `Script.parse` method and look up what various opcodes do at https://en.bitcoin.it/wiki/Script"
131 | ]
132 | },
133 | {
134 | "cell_type": "code",
135 | "execution_count": null,
136 | "metadata": {},
137 | "outputs": [],
138 | "source": [
139 | "# Exercise 4\n",
140 | "\n",
141 | "from script import Script\n",
142 | "\n",
143 | "script_pubkey = Script([0x6e, 0x87, 0x91, 0x69, 0xa7, 0x7c, 0xa7, 0x87])\n",
144 | "script_sig = Script([]) # FILL THIS IN\n",
145 | "combined_script = script_sig + script_pubkey\n",
146 | "print(combined_script.evaluate(0))"
147 | ]
148 | }
149 | ],
150 | "metadata": {},
151 | "nbformat": 4,
152 | "nbformat_minor": 2
153 | }
154 |
--------------------------------------------------------------------------------
/code-ch06/answers.py:
--------------------------------------------------------------------------------
1 | '''
2 | # tag::exercise3[]
3 | ==== Exercise 3
4 |
5 | Create a ScriptSig that can unlock this ScriptPubKey:
6 |
7 | ----
8 | 767695935687
9 | ----
10 |
11 | Note that `OP_MUL` multiplies the top two elements of the stack.
12 |
13 | * `56 = OP_6`
14 | * `76 = OP_DUP`
15 | * `87 = OP_EQUAL`
16 | * `93 = OP_ADD`
17 | * `95 = OP_MUL`
18 | # end::exercise3[]
19 | # tag::answer3[]
20 | >>> from script import Script
21 | >>> script_pubkey = Script([0x76, 0x76, 0x95, 0x93, 0x56, 0x87])
22 | >>> script_sig = Script([0x52])
23 | >>> combined_script = script_sig + script_pubkey
24 | >>> print(combined_script.evaluate(0))
25 | True
26 |
27 | # end::answer3[]
28 | # tag::exercise4[]
29 | ==== Exercise 4
30 |
31 | Figure out what this script is doing:
32 |
33 | ----
34 | 6e879169a77ca787
35 | ----
36 |
37 | * `69 = OP_VERIFY`
38 | * `6e = OP_2DUP`
39 | * `7c = OP_SWAP`
40 | * `87 = OP_EQUAL`
41 | * `91 = OP_NOT`
42 | * `a7 = OP_SHA1`
43 |
44 | Use the `Script.parse` method and look up what various opcodes do at https://en.bitcoin.it/wiki/Script[].
45 |
46 | # end::exercise4[]
47 | # tag::answer4[]
48 | >>> from script import Script
49 | >>> script_pubkey = Script([0x6e, 0x87, 0x91, 0x69, 0xa7, 0x7c, 0xa7, 0x87])
50 | >>> c1 = '255044462d312e330a25e2e3cfd30a0a0a312030206f626a0a3c3c2f576964746820\
51 | 32203020522f4865696768742033203020522f547970652034203020522f537562747970652035\
52 | 203020522f46696c7465722036203020522f436f6c6f7253706163652037203020522f4c656e67\
53 | 74682038203020522f42697473506572436f6d706f6e656e7420383e3e0a73747265616d0affd8\
54 | fffe00245348412d3120697320646561642121212121852fec092339759c39b1a1c63c4c97e1ff\
55 | fe017f46dc93a6b67e013b029aaa1db2560b45ca67d688c7f84b8c4c791fe02b3df614f86db169\
56 | 0901c56b45c1530afedfb76038e972722fe7ad728f0e4904e046c230570fe9d41398abe12ef5bc\
57 | 942be33542a4802d98b5d70f2a332ec37fac3514e74ddc0f2cc1a874cd0c78305a215664613097\
58 | 89606bd0bf3f98cda8044629a1'
59 | >>> c2 = '255044462d312e330a25e2e3cfd30a0a0a312030206f626a0a3c3c2f576964746820\
60 | 32203020522f4865696768742033203020522f547970652034203020522f537562747970652035\
61 | 203020522f46696c7465722036203020522f436f6c6f7253706163652037203020522f4c656e67\
62 | 74682038203020522f42697473506572436f6d706f6e656e7420383e3e0a73747265616d0affd8\
63 | fffe00245348412d3120697320646561642121212121852fec092339759c39b1a1c63c4c97e1ff\
64 | fe017346dc9166b67e118f029ab621b2560ff9ca67cca8c7f85ba84c79030c2b3de218f86db3a9\
65 | 0901d5df45c14f26fedfb3dc38e96ac22fe7bd728f0e45bce046d23c570feb141398bb552ef5a0\
66 | a82be331fea48037b8b5d71f0e332edf93ac3500eb4ddc0decc1a864790c782c76215660dd3097\
67 | 91d06bd0af3f98cda4bc4629b1'
68 | >>> collision1 = bytes.fromhex(c1) # <1>
69 | >>> collision2 = bytes.fromhex(c2)
70 | >>> script_sig = Script([collision1, collision2])
71 | >>> combined_script = script_sig + script_pubkey
72 | >>> print(combined_script.evaluate(0))
73 | True
74 |
75 | # end::answer4[]
76 | '''
77 |
78 |
79 | from unittest import TestCase
80 |
81 | import op
82 |
83 | from ecc import S256Point, Signature
84 | from helper import hash160
85 | from op import encode_num
86 |
87 |
88 | '''
89 | # tag::exercise1[]
90 | ==== Exercise 1
91 |
92 | Write the `op_hash160` function.
93 | # end::exercise1[]
94 | '''
95 |
96 |
97 | # tag::answer1[]
98 | def op_hash160(stack):
99 | if len(stack) < 1:
100 | return False
101 | element = stack.pop()
102 | h160 = hash160(element)
103 | stack.append(h160)
104 | return True
105 | # end::answer1[]
106 |
107 |
108 | '''
109 | # tag::exercise2[]
110 | ==== Exercise 2
111 |
112 | Write the `op_checksig` function in _op.py_.
113 | # end::exercise2[]
114 | '''
115 |
116 |
117 | # tag::answer2[]
118 | def op_checksig(stack, z):
119 | if len(stack) < 2:
120 | return False
121 | sec_pubkey = stack.pop()
122 | der_signature = stack.pop()[:-1]
123 | try:
124 | point = S256Point.parse(sec_pubkey)
125 | sig = Signature.parse(der_signature)
126 | except (ValueError, SyntaxError) as e:
127 | return False
128 | if point.verify(z, sig):
129 | stack.append(encode_num(1))
130 | else:
131 | stack.append(encode_num(0))
132 | return True
133 | # end::answer2[]
134 |
135 |
136 | class ChapterTest(TestCase):
137 |
138 | def test_apply(self):
139 |
140 | op.op_hash160 = op_hash160
141 | op.op_checksig = op_checksig
142 | op.OP_CODE_FUNCTIONS[0xa9] = op_hash160
143 | op.OP_CODE_FUNCTIONS[0xac] = op_checksig
144 |
--------------------------------------------------------------------------------
/code-ch06/examples.py:
--------------------------------------------------------------------------------
1 | """
2 | # tag::example1[]
3 | >>> from script import Script
4 | >>> z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d
5 | >>> sec = bytes.fromhex('04887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e\
6 | 4da568744d06c61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34')
7 | >>> sig = bytes.fromhex('3045022000eff69ef2b1bd93a66ed5219add4fb51e11a840f4048\
8 | 76325a1e8ffe0529a2c022100c7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fd\
9 | dbdce6feab601')
10 | >>> script_pubkey = Script([sec, 0xac]) # <1>
11 | >>> script_sig = Script([sig])
12 | >>> combined_script = script_sig + script_pubkey # <2>
13 | >>> print(combined_script.evaluate(z)) # <3>
14 | True
15 |
16 | # end::example1[]
17 | """
18 |
--------------------------------------------------------------------------------
/code-ch06/helper.py:
--------------------------------------------------------------------------------
1 | from unittest import TestCase, TestSuite, TextTestRunner
2 |
3 | import hashlib
4 |
5 |
6 | SIGHASH_ALL = 1
7 | SIGHASH_NONE = 2
8 | SIGHASH_SINGLE = 3
9 | BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
10 |
11 |
12 | def run(test):
13 | suite = TestSuite()
14 | suite.addTest(test)
15 | TextTestRunner().run(suite)
16 |
17 |
18 | def hash160(s):
19 | '''sha256 followed by ripemd160'''
20 | return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest()
21 |
22 |
23 | def hash256(s):
24 | '''two rounds of sha256'''
25 | return hashlib.sha256(hashlib.sha256(s).digest()).digest()
26 |
27 |
28 | def encode_base58(s):
29 | # determine how many 0 bytes (b'\x00') s starts with
30 | count = 0
31 | for c in s:
32 | if c == 0:
33 | count += 1
34 | else:
35 | break
36 | # convert to big endian integer
37 | num = int.from_bytes(s, 'big')
38 | prefix = '1' * count
39 | result = ''
40 | while num > 0:
41 | num, mod = divmod(num, 58)
42 | result = BASE58_ALPHABET[mod] + result
43 | return prefix + result
44 |
45 |
46 | def encode_base58_checksum(s):
47 | return encode_base58(s + hash256(s)[:4])
48 |
49 |
50 | def decode_base58(s):
51 | num = 0
52 | for c in s:
53 | num *= 58
54 | num += BASE58_ALPHABET.index(c)
55 | combined = num.to_bytes(25, byteorder='big')
56 | checksum = combined[-4:]
57 | if hash256(combined[:-4])[:4] != checksum:
58 | raise ValueError('bad address: {} {}'.format(checksum, hash256(combined[:-4])[:4]))
59 | return combined[1:-4]
60 |
61 |
62 | def little_endian_to_int(b):
63 | '''little_endian_to_int takes byte sequence as a little-endian number.
64 | Returns an integer'''
65 | return int.from_bytes(b, 'little')
66 |
67 |
68 | def int_to_little_endian(n, length):
69 | '''endian_to_little_endian takes an integer and returns the little-endian
70 | byte sequence of length'''
71 | return n.to_bytes(length, 'little')
72 |
73 |
74 | def read_varint(s):
75 | '''read_varint reads a variable integer from a stream'''
76 | i = s.read(1)[0]
77 | if i == 0xfd:
78 | # 0xfd means the next two bytes are the number
79 | return little_endian_to_int(s.read(2))
80 | elif i == 0xfe:
81 | # 0xfe means the next four bytes are the number
82 | return little_endian_to_int(s.read(4))
83 | elif i == 0xff:
84 | # 0xff means the next eight bytes are the number
85 | return little_endian_to_int(s.read(8))
86 | else:
87 | # anything else is just the integer
88 | return i
89 |
90 |
91 | def encode_varint(i):
92 | '''encodes an integer as a varint'''
93 | if i < 0xfd:
94 | return bytes([i])
95 | elif i < 0x10000:
96 | return b'\xfd' + int_to_little_endian(i, 2)
97 | elif i < 0x100000000:
98 | return b'\xfe' + int_to_little_endian(i, 4)
99 | elif i < 0x10000000000000000:
100 | return b'\xff' + int_to_little_endian(i, 8)
101 | else:
102 | raise ValueError('integer too large: {}'.format(i))
103 |
104 |
105 | class HelperTest(TestCase):
106 |
107 | def test_little_endian_to_int(self):
108 | h = bytes.fromhex('99c3980000000000')
109 | want = 10011545
110 | self.assertEqual(little_endian_to_int(h), want)
111 | h = bytes.fromhex('a135ef0100000000')
112 | want = 32454049
113 | self.assertEqual(little_endian_to_int(h), want)
114 |
115 | def test_int_to_little_endian(self):
116 | n = 1
117 | want = b'\x01\x00\x00\x00'
118 | self.assertEqual(int_to_little_endian(n, 4), want)
119 | n = 10011545
120 | want = b'\x99\xc3\x98\x00\x00\x00\x00\x00'
121 | self.assertEqual(int_to_little_endian(n, 8), want)
122 |
--------------------------------------------------------------------------------
/code-ch06/jupyter.txt:
--------------------------------------------------------------------------------
1 | import op
2 | import script
3 | ---
4 | exercise1:op:OpTest:test_op_hash160
5 | ---
6 | example1
7 | ---
8 | exercise2:op:OpTest:test_op_checksig
9 | ---
10 | exercise3:
11 | from script import Script
12 |
13 | script_pubkey = Script(0x76, 0x76, 0x95, 0x93, 0x56, 0x87)
14 | script_sig = Script([]) # FILL THIS IN
15 | combined_script = script_sig + script_pubkey
16 | print(combined_script.evaluate(0))
17 | ---
18 | exercise4:
19 | from script import Script
20 |
21 | script_pubkey = Script(0x6e, 0x87, 0x91, 0x91, 0x69, 0xa7, 0x7c, 0xa7, 0x87)
22 | script_sig = Script([]) # FILL THIS IN
23 | combined_script = script_sig + script_pubkey
24 | print(combined_script.evaluate(0))
25 |
--------------------------------------------------------------------------------
/code-ch06/script.py:
--------------------------------------------------------------------------------
1 | from io import BytesIO
2 | from logging import getLogger
3 | from unittest import TestCase
4 |
5 | from helper import (
6 | encode_varint,
7 | int_to_little_endian,
8 | little_endian_to_int,
9 | read_varint,
10 | )
11 | from op import (
12 | OP_CODE_FUNCTIONS,
13 | OP_CODE_NAMES,
14 | )
15 |
16 |
17 | LOGGER = getLogger(__name__)
18 |
19 |
20 | # tag::source1[]
21 | class Script:
22 |
23 | def __init__(self, cmds=None):
24 | if cmds is None:
25 | self.cmds = []
26 | else:
27 | self.cmds = cmds # <1>
28 | # end::source1[]
29 |
30 | def __repr__(self):
31 | result = []
32 | for cmd in self.cmds:
33 | if type(cmd) == int:
34 | if OP_CODE_NAMES.get(cmd):
35 | name = OP_CODE_NAMES.get(cmd)
36 | else:
37 | name = 'OP_[{}]'.format(cmd)
38 | result.append(name)
39 | else:
40 | result.append(cmd.hex())
41 | return ' '.join(result)
42 |
43 | # tag::source4[]
44 | def __add__(self, other):
45 | return Script(self.cmds + other.cmds) # <1>
46 | # end::source4[]
47 |
48 | # tag::source2[]
49 | @classmethod
50 | def parse(cls, s):
51 | length = read_varint(s) # <2>
52 | cmds = []
53 | count = 0
54 | while count < length: # <3>
55 | current = s.read(1) # <4>
56 | count += 1
57 | current_byte = current[0] # <5>
58 | if current_byte >= 1 and current_byte <= 75: # <6>
59 | n = current_byte
60 | cmds.append(s.read(n))
61 | count += n
62 | elif current_byte == 76: # <7>
63 | data_length = little_endian_to_int(s.read(1))
64 | cmds.append(s.read(data_length))
65 | count += data_length + 1
66 | elif current_byte == 77: # <8>
67 | data_length = little_endian_to_int(s.read(2))
68 | cmds.append(s.read(data_length))
69 | count += data_length + 2
70 | else: # <9>
71 | op_code = current_byte
72 | cmds.append(op_code)
73 | if count != length: # <10>
74 | raise SyntaxError('parsing script failed')
75 | return cls(cmds)
76 | # end::source2[]
77 |
78 | # tag::source3[]
79 | def raw_serialize(self):
80 | result = b''
81 | for cmd in self.cmds:
82 | if type(cmd) == int: # <1>
83 | result += int_to_little_endian(cmd, 1)
84 | else:
85 | length = len(cmd)
86 | if length < 75: # <2>
87 | result += int_to_little_endian(length, 1)
88 | elif length > 75 and length < 0x100: # <3>
89 | result += int_to_little_endian(76, 1)
90 | result += int_to_little_endian(length, 1)
91 | elif length >= 0x100 and length <= 520: # <4>
92 | result += int_to_little_endian(77, 1)
93 | result += int_to_little_endian(length, 2)
94 | else: # <5>
95 | raise ValueError('too long an cmd')
96 | result += cmd
97 | return result
98 |
99 | def serialize(self):
100 | result = self.raw_serialize()
101 | total = len(result)
102 | return encode_varint(total) + result # <6>
103 | # end::source3[]
104 |
105 | # tag::source5[]
106 | def evaluate(self, z):
107 | cmds = self.cmds[:] # <1>
108 | stack = []
109 | altstack = []
110 | while len(cmds) > 0: # <2>
111 | cmd = cmds.pop(0)
112 | if type(cmd) == int:
113 | operation = OP_CODE_FUNCTIONS[cmd] # <3>
114 | if cmd in (99, 100): # <4>
115 | if not operation(stack, cmds):
116 | LOGGER.info('bad op: {}'.format(OP_CODE_NAMES[cmd]))
117 | return False
118 | elif cmd in (107, 108): # <5>
119 | if not operation(stack, altstack):
120 | LOGGER.info('bad op: {}'.format(OP_CODE_NAMES[cmd]))
121 | return False
122 | elif cmd in (172, 173, 174, 175): # <6>
123 | if not operation(stack, z):
124 | LOGGER.info('bad op: {}'.format(OP_CODE_NAMES[cmd]))
125 | return False
126 | else:
127 | if not operation(stack):
128 | LOGGER.info('bad op: {}'.format(OP_CODE_NAMES[cmd]))
129 | return False
130 | else:
131 | stack.append(cmd) # <7>
132 | if len(stack) == 0:
133 | return False # <8>
134 | if stack.pop() == b'':
135 | return False # <9>
136 | return True # <10>
137 | # end::source5[]
138 |
139 |
140 | class ScriptTest(TestCase):
141 |
142 | def test_parse(self):
143 | script_pubkey = BytesIO(bytes.fromhex('6a47304402207899531a52d59a6de200179928ca900254a36b8dff8bb75f5f5d71b1cdc26125022008b422690b8461cb52c3cc30330b23d574351872b7c361e9aae3649071c1a7160121035d5c93d9ac96881f19ba1f686f15f009ded7c62efe85a872e6a19b43c15a2937'))
144 | script = Script.parse(script_pubkey)
145 | want = bytes.fromhex('304402207899531a52d59a6de200179928ca900254a36b8dff8bb75f5f5d71b1cdc26125022008b422690b8461cb52c3cc30330b23d574351872b7c361e9aae3649071c1a71601')
146 | self.assertEqual(script.cmds[0].hex(), want.hex())
147 | want = bytes.fromhex('035d5c93d9ac96881f19ba1f686f15f009ded7c62efe85a872e6a19b43c15a2937')
148 | self.assertEqual(script.cmds[1], want)
149 |
150 | def test_serialize(self):
151 | want = '6a47304402207899531a52d59a6de200179928ca900254a36b8dff8bb75f5f5d71b1cdc26125022008b422690b8461cb52c3cc30330b23d574351872b7c361e9aae3649071c1a7160121035d5c93d9ac96881f19ba1f686f15f009ded7c62efe85a872e6a19b43c15a2937'
152 | script_pubkey = BytesIO(bytes.fromhex(want))
153 | script = Script.parse(script_pubkey)
154 | self.assertEqual(script.serialize().hex(), want)
155 |
--------------------------------------------------------------------------------
/code-ch07/examples.py:
--------------------------------------------------------------------------------
1 | """
2 | # tag::example1[]
3 | >>> from tx import Tx
4 | >>> from io import BytesIO
5 | >>> raw_tx = ('0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf830\
6 | 3c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccf\
7 | cf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8\
8 | e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278\
9 | afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88a\
10 | c99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600')
11 | >>> stream = BytesIO(bytes.fromhex(raw_tx))
12 | >>> transaction = Tx.parse(stream)
13 | >>> print(transaction.fee() >= 0) # <1>
14 | True
15 |
16 | # end::example1[]
17 | # tag::example2[]
18 | >>> from ecc import S256Point, Signature
19 | >>> sec = bytes.fromhex('0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e\
20 | 213bf016b278a')
21 | >>> der = bytes.fromhex('3045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031c\
22 | cfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9\
23 | c8e10615bed')
24 | >>> z = 0x27e0c5994dec7824e56dec6b2fcb342eb7cdb0d0957c2fce9882f715e85d81a6
25 | >>> point = S256Point.parse(sec)
26 | >>> signature = Signature.parse(der)
27 | >>> print(point.verify(z, signature))
28 | True
29 |
30 | # end::example2[]
31 | # tag::example3[]
32 | >>> from helper import hash256
33 | >>> modified_tx = bytes.fromhex('0100000001813f79011acb80925dfe69b3def355fe914\
34 | bd1d96a3f5f71bf8303c6a989c7d1000000001976a914a802fc56c704ce87c42d7c92eb75e7896\
35 | bdc41ae88acfeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02\
36 | e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288a\
37 | c1943060001000000')
38 | >>> h256 = hash256(modified_tx)
39 | >>> z = int.from_bytes(h256, 'big')
40 | >>> print(hex(z))
41 | 0x27e0c5994dec7824e56dec6b2fcb342eb7cdb0d0957c2fce9882f715e85d81a6
42 |
43 | # end::example3[]
44 | # tag::example4[]
45 | >>> from ecc import S256Point, Signature
46 | >>> sec = bytes.fromhex('0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e\
47 | 213bf016b278a')
48 | >>> der = bytes.fromhex('3045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031c\
49 | cfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9\
50 | c8e10615bed')
51 | >>> z = 0x27e0c5994dec7824e56dec6b2fcb342eb7cdb0d0957c2fce9882f715e85d81a6
52 | >>> point = S256Point.parse(sec)
53 | >>> signature = Signature.parse(der)
54 | >>> point.verify(z, signature)
55 | True
56 |
57 | # end::example4[]
58 | # tag::example5[]
59 | >>> from helper import decode_base58, SIGHASH_ALL
60 | >>> from script import p2pkh_script, Script
61 | >>> from tx import TxIn, TxOut, Tx
62 | >>> prev_tx = bytes.fromhex('0d6fe5213c0b3291f208cba8bfb59b7476dffacc4e5cb66f6\
63 | eb20a080843a299')
64 | >>> prev_index = 13
65 | >>> tx_in = TxIn(prev_tx, prev_index)
66 | >>> tx_outs = []
67 | >>> change_amount = int(0.33*100000000) # <1>
68 | >>> change_h160 = decode_base58('mzx5YhAH9kNHtcN481u6WkjeHjYtVeKVh2')
69 | >>> change_script = p2pkh_script(change_h160)
70 | >>> change_output = TxOut(amount=change_amount, script_pubkey=change_script)
71 | >>> target_amount = int(0.1*100000000) # <1>
72 | >>> target_h160 = decode_base58('mnrVtF8DWjMu839VW3rBfgYaAfKk8983Xf')
73 | >>> target_script = p2pkh_script(target_h160)
74 | >>> target_output = TxOut(amount=target_amount, script_pubkey=target_script)
75 | >>> tx_obj = Tx(1, [tx_in], [change_output, target_output], 0, True) # <2>
76 | >>> print(tx_obj)
77 | tx: cd30a8da777d28ef0e61efe68a9f7c559c1d3e5bcd7b265c850ccb4068598d11
78 | version: 1
79 | tx_ins:
80 | 0d6fe5213c0b3291f208cba8bfb59b7476dffacc4e5cb66f6eb20a080843a299:13
81 | tx_outs:
82 | 33000000:OP_DUP OP_HASH160 d52ad7ca9b3d096a38e752c2018e6fbc40cdf26f OP_EQUALVE\
83 | RIFY OP_CHECKSIG
84 | 10000000:OP_DUP OP_HASH160 507b27411ccf7f16f10297de6cef3f291623eddf OP_EQUALVE\
85 | RIFY OP_CHECKSIG
86 | locktime: 0
87 |
88 | # end::example5[]
89 | # tag::example6[]
90 | >>> from ecc import PrivateKey
91 | >>> from helper import SIGHASH_ALL
92 | >>> z = transaction.sig_hash(0) # <1>
93 | >>> private_key = PrivateKey(secret=8675309)
94 | >>> der = private_key.sign(z).der()
95 | >>> sig = der + SIGHASH_ALL.to_bytes(1, 'big') # <2>
96 | >>> sec = private_key.point.sec()
97 | >>> script_sig = Script([sig, sec]) # <3>
98 | >>> transaction.tx_ins[0].script_sig = script_sig # <4>
99 | >>> print(transaction.serialize().hex())
100 | 0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d10000\
101 | 00006a47304402207db2402a3311a3b845b038885e3dd889c08126a8570f26a844e3e4049c482a\
102 | 11022010178cdca4129eacbeab7c44648bf5ac1f9cac217cd609d216ec2ebc8d242c0a01210393\
103 | 5581e52c354cd2f484fe8ed83af7a3097005b2f9c60bff71d35bd795f54b67feffffff02a135ef\
104 | 01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c3980000000000\
105 | 1976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600
106 |
107 | # end::example6[]
108 | # tag::example7[]
109 | >>> from ecc import PrivateKey
110 | >>> from helper import hash256, little_endian_to_int
111 | >>> secret = little_endian_to_int(hash256(b'Jimmy Song secret')) # <1>
112 | >>> private_key = PrivateKey(secret)
113 | >>> print(private_key.point.address(testnet=True))
114 | mn81594PzKZa9K3Jyy1ushpuEzrnTnxhVg
115 |
116 | # end::example7[]
117 | """
118 |
--------------------------------------------------------------------------------
/code-ch07/helper.py:
--------------------------------------------------------------------------------
1 | from unittest import TestCase, TestSuite, TextTestRunner
2 |
3 | import hashlib
4 |
5 |
6 | SIGHASH_ALL = 1
7 | SIGHASH_NONE = 2
8 | SIGHASH_SINGLE = 3
9 | BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
10 |
11 |
12 | def run(test):
13 | suite = TestSuite()
14 | suite.addTest(test)
15 | TextTestRunner().run(suite)
16 |
17 |
18 | def hash160(s):
19 | '''sha256 followed by ripemd160'''
20 | return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest()
21 |
22 |
23 | def hash256(s):
24 | '''two rounds of sha256'''
25 | return hashlib.sha256(hashlib.sha256(s).digest()).digest()
26 |
27 |
28 | def encode_base58(s):
29 | # determine how many 0 bytes (b'\x00') s starts with
30 | count = 0
31 | for c in s:
32 | if c == 0:
33 | count += 1
34 | else:
35 | break
36 | # convert to big endian integer
37 | num = int.from_bytes(s, 'big')
38 | prefix = '1' * count
39 | result = ''
40 | while num > 0:
41 | num, mod = divmod(num, 58)
42 | result = BASE58_ALPHABET[mod] + result
43 | return prefix + result
44 |
45 |
46 | def encode_base58_checksum(s):
47 | return encode_base58(s + hash256(s)[:4])
48 |
49 |
50 | # tag::source1[]
51 | def decode_base58(s):
52 | num = 0
53 | for c in s: # <1>
54 | num *= 58
55 | num += BASE58_ALPHABET.index(c)
56 | combined = num.to_bytes(25, byteorder='big') # <2>
57 | checksum = combined[-4:]
58 | if hash256(combined[:-4])[:4] != checksum:
59 | raise ValueError('bad address: {} {}'.format(checksum,
60 | hash256(combined[:-4])[:4]))
61 | return combined[1:-4] # <3>
62 | # end::source1[]
63 |
64 |
65 | def little_endian_to_int(b):
66 | '''little_endian_to_int takes byte sequence as a little-endian number.
67 | Returns an integer'''
68 | return int.from_bytes(b, 'little')
69 |
70 |
71 | def int_to_little_endian(n, length):
72 | '''endian_to_little_endian takes an integer and returns the little-endian
73 | byte sequence of length'''
74 | return n.to_bytes(length, 'little')
75 |
76 |
77 | def read_varint(s):
78 | '''read_varint reads a variable integer from a stream'''
79 | i = s.read(1)[0]
80 | if i == 0xfd:
81 | # 0xfd means the next two bytes are the number
82 | return little_endian_to_int(s.read(2))
83 | elif i == 0xfe:
84 | # 0xfe means the next four bytes are the number
85 | return little_endian_to_int(s.read(4))
86 | elif i == 0xff:
87 | # 0xff means the next eight bytes are the number
88 | return little_endian_to_int(s.read(8))
89 | else:
90 | # anything else is just the integer
91 | return i
92 |
93 |
94 | def encode_varint(i):
95 | '''encodes an integer as a varint'''
96 | if i < 0xfd:
97 | return bytes([i])
98 | elif i < 0x10000:
99 | return b'\xfd' + int_to_little_endian(i, 2)
100 | elif i < 0x100000000:
101 | return b'\xfe' + int_to_little_endian(i, 4)
102 | elif i < 0x10000000000000000:
103 | return b'\xff' + int_to_little_endian(i, 8)
104 | else:
105 | raise ValueError('integer too large: {}'.format(i))
106 |
107 |
108 | class HelperTest(TestCase):
109 |
110 | def test_little_endian_to_int(self):
111 | h = bytes.fromhex('99c3980000000000')
112 | want = 10011545
113 | self.assertEqual(little_endian_to_int(h), want)
114 | h = bytes.fromhex('a135ef0100000000')
115 | want = 32454049
116 | self.assertEqual(little_endian_to_int(h), want)
117 |
118 | def test_int_to_little_endian(self):
119 | n = 1
120 | want = b'\x01\x00\x00\x00'
121 | self.assertEqual(int_to_little_endian(n, 4), want)
122 | n = 10011545
123 | want = b'\x99\xc3\x98\x00\x00\x00\x00\x00'
124 | self.assertEqual(int_to_little_endian(n, 8), want)
125 |
126 | def test_base58(self):
127 | addr = 'mnrVtF8DWjMu839VW3rBfgYaAfKk8983Xf'
128 | h160 = decode_base58(addr).hex()
129 | want = '507b27411ccf7f16f10297de6cef3f291623eddf'
130 | self.assertEqual(h160, want)
131 | got = encode_base58_checksum(b'\x6f' + bytes.fromhex(h160))
132 | self.assertEqual(got, addr)
133 |
--------------------------------------------------------------------------------
/code-ch07/jupyter.txt:
--------------------------------------------------------------------------------
1 | import ecc
2 | import helper
3 | import tx
4 | import script
5 | ---
6 | example1
7 | ---
8 | example2
9 | ---
10 | example3
11 | ---
12 | example4
13 | ---
14 | exercise1:tx:TxTest:test_sig_hash
15 | ---
16 | exercise2:tx:TxTest:test_verify_p2pkh
17 | ---
18 | example5
19 | ---
20 | example6
21 | ---
22 | example7
23 | ---
24 | exercise3:tx:TxTest:test_sign_input
25 | ---
26 | exercise4:
27 | from ecc import PrivateKey
28 | from helper import decode_base58, SIGHASH_ALL
29 | from script import p2pkh_script, Script
30 | from tx import TxIn, TxOut, Tx
31 |
32 | # create 1 TxIn and 2 TxOuts
33 | # 1 of the TxOuts should be back to your address
34 | # the other TxOut should be to this address
35 | target_address = 'mwJn1YPMq7y5F8J3LkC5Hxg9PHyZ5K4cFv'
36 |
37 | # get the private key from the exercise in Chapter 4
38 | # change address should be the address generated from Chapter 4
39 |
40 | # get the prev_tx and prev_index from the transaction where you got
41 | # some testnet coins
42 | # create a transaction input for the previous transaction with
43 | # the default ScriptSig and sequence
44 |
45 | # target amount should be 60% of the output amount
46 | # set the fee to some reasonable amount
47 | # change amount = amount from the prev tx - target amount - fee
48 |
49 | # create a transaction output for the target amount and address
50 | # create a transaction output for the change amount and address
51 | # create the transaction object
52 |
53 | # sign the one input in the transaction object using the private key
54 | # print the transaction's serialization in hex
55 | # broadcast at http://testnet.blockchain.info/pushtx
56 | ---
57 | exercise5:
58 | from ecc import PrivateKey
59 | from helper import decode_base58, SIGHASH_ALL
60 | from script import p2pkh_script, Script
61 | from tx import TxIn, TxOut, Tx
62 |
63 | # Create 2 TxIns, 1 from the Exercise 4 and 1 from a testnet faucet
64 | # Creat 1 TxOut to the address above
65 | target_address = 'mwJn1YPMq7y5F8J3LkC5Hxg9PHyZ5K4cFv'
66 |
67 | # get the private key from the exercise in Chapter 4
68 |
69 | # get the prev_tx and prev_index from the transaction where you got
70 | # some testnet coins
71 | # create the first transaction input with the default ScriptSig and
72 | # sequence
73 | # get the prev_tx and prev_index from the transaction in Exercise 4
74 | # create the second transaction input with the default ScriptSig and
75 | # sequence
76 |
77 | # set the fee to some reasonable amount
78 | # target amount should be the sum of the inputs - fee
79 |
80 | # create a transaction output for the amount and address
81 |
82 | # sign the first input using the private key
83 | # sign the second input using the private key
84 | # print the transaction's serialization in hex
85 | # broadcast at http://testnet.blockchain.info/pushtx
86 |
--------------------------------------------------------------------------------
/code-ch08/examples.py:
--------------------------------------------------------------------------------
1 | '''
2 | # tag::example1[]
3 | >>> from helper import encode_base58_checksum
4 | >>> h160 = bytes.fromhex('74d691da1574e6b3c192ecfb52cc8984ee7b6c56')
5 | >>> print(encode_base58_checksum(b'\x05' + h160))
6 | 3CLoMMyuoDQTPRD3XYZtCvgvkadrAdvdXh
7 |
8 | # end::example1[]
9 | # tag::example2[]
10 | >>> from helper import hash256
11 | >>> modified_tx = bytes.fromhex('0100000001868278ed6ddfb6c1ed3ad5f8181eb0c7a38\
12 | 5aa0836f01d5e4789e6bd304d87221a000000475221022626e955ea6ea6d98850c994f9107b036\
13 | b1334f18ca8830bfff1295d21cfdb702103b287eaf122eea69030a0e9feed096bed8045c8b98be\
14 | c453e1ffac7fbdbd4bb7152aeffffffff04d3b11400000000001976a914904a49878c0adfc3aa0\
15 | 5de7afad2cc15f483a56a88ac7f400900000000001976a914418327e3f3dda4cf5b9089325a4b9\
16 | 5abdfa0334088ac722c0c00000000001976a914ba35042cfe9fc66fd35ac2224eebdafd1028ad2\
17 | 788acdc4ace020000000017a91474d691da1574e6b3c192ecfb52cc8984ee7b6c5687000000000\
18 | 1000000')
19 | >>> s256 = hash256(modified_tx)
20 | >>> z = int.from_bytes(s256, 'big')
21 | >>> print(hex(z))
22 | 0xe71bfa115715d6fd33796948126f40a8cdd39f187e4afb03896795189fe1423c
23 |
24 | # end::example2[]
25 | # tag::example3[]
26 | >>> from ecc import S256Point, Signature
27 | >>> from helper import hash256
28 | >>> modified_tx = bytes.fromhex('0100000001868278ed6ddfb6c1ed3ad5f8181eb0c7a38\
29 | 5aa0836f01d5e4789e6bd304d87221a000000475221022626e955ea6ea6d98850c994f9107b036\
30 | b1334f18ca8830bfff1295d21cfdb702103b287eaf122eea69030a0e9feed096bed8045c8b98be\
31 | c453e1ffac7fbdbd4bb7152aeffffffff04d3b11400000000001976a914904a49878c0adfc3aa0\
32 | 5de7afad2cc15f483a56a88ac7f400900000000001976a914418327e3f3dda4cf5b9089325a4b9\
33 | 5abdfa0334088ac722c0c00000000001976a914ba35042cfe9fc66fd35ac2224eebdafd1028ad2\
34 | 788acdc4ace020000000017a91474d691da1574e6b3c192ecfb52cc8984ee7b6c5687000000000\
35 | 1000000')
36 | >>> h256 = hash256(modified_tx)
37 | >>> z = int.from_bytes(h256, 'big') # <1>
38 | >>> sec = bytes.fromhex('022626e955ea6ea6d98850c994f9107b036b1334f18ca8830bfff\
39 | 1295d21cfdb70')
40 | >>> der = bytes.fromhex('3045022100dc92655fe37036f47756db8102e0d7d5e28b3beb83a\
41 | 8fef4f5dc0559bddfb94e02205a36d4e4e6c7fcd16658c50783e00c341609977aed3ad00937bf4\
42 | ee942a89937')
43 | >>> point = S256Point.parse(sec)
44 | >>> sig = Signature.parse(der)
45 | >>> print(point.verify(z, sig))
46 | True
47 |
48 | # end::example3[]
49 | '''
50 |
--------------------------------------------------------------------------------
/code-ch08/helper.py:
--------------------------------------------------------------------------------
1 | from unittest import TestCase, TestSuite, TextTestRunner
2 |
3 | import hashlib
4 |
5 |
6 | SIGHASH_ALL = 1
7 | SIGHASH_NONE = 2
8 | SIGHASH_SINGLE = 3
9 | BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
10 |
11 |
12 | def run(test):
13 | suite = TestSuite()
14 | suite.addTest(test)
15 | TextTestRunner().run(suite)
16 |
17 |
18 | def hash160(s):
19 | '''sha256 followed by ripemd160'''
20 | return hashlib.new('ripemd160', hashlib.sha256(s).digest()).digest()
21 |
22 |
23 | def hash256(s):
24 | '''two rounds of sha256'''
25 | return hashlib.sha256(hashlib.sha256(s).digest()).digest()
26 |
27 |
28 | def encode_base58(s):
29 | # determine how many 0 bytes (b'\x00') s starts with
30 | count = 0
31 | for c in s:
32 | if c == 0:
33 | count += 1
34 | else:
35 | break
36 | # convert to big endian integer
37 | num = int.from_bytes(s, 'big')
38 | prefix = '1' * count
39 | result = ''
40 | while num > 0:
41 | num, mod = divmod(num, 58)
42 | result = BASE58_ALPHABET[mod] + result
43 | return prefix + result
44 |
45 |
46 | def encode_base58_checksum(s):
47 | return encode_base58(s + hash256(s)[:4])
48 |
49 |
50 | def decode_base58(s):
51 | num = 0
52 | for c in s:
53 | num *= 58
54 | num += BASE58_ALPHABET.index(c)
55 | combined = num.to_bytes(25, byteorder='big')
56 | checksum = combined[-4:]
57 | if hash256(combined[:-4])[:4] != checksum:
58 | raise ValueError('bad address: {} {}'.format(checksum, hash256(combined[:-4])[:4]))
59 | return combined[1:-4]
60 |
61 |
62 | def little_endian_to_int(b):
63 | '''little_endian_to_int takes byte sequence as a little-endian number.
64 | Returns an integer'''
65 | return int.from_bytes(b, 'little')
66 |
67 |
68 | def int_to_little_endian(n, length):
69 | '''endian_to_little_endian takes an integer and returns the little-endian
70 | byte sequence of length'''
71 | return n.to_bytes(length, 'little')
72 |
73 |
74 | def read_varint(s):
75 | '''read_varint reads a variable integer from a stream'''
76 | i = s.read(1)[0]
77 | if i == 0xfd:
78 | # 0xfd means the next two bytes are the number
79 | return little_endian_to_int(s.read(2))
80 | elif i == 0xfe:
81 | # 0xfe means the next four bytes are the number
82 | return little_endian_to_int(s.read(4))
83 | elif i == 0xff:
84 | # 0xff means the next eight bytes are the number
85 | return little_endian_to_int(s.read(8))
86 | else:
87 | # anything else is just the integer
88 | return i
89 |
90 |
91 | def encode_varint(i):
92 | '''encodes an integer as a varint'''
93 | if i < 0xfd:
94 | return bytes([i])
95 | elif i < 0x10000:
96 | return b'\xfd' + int_to_little_endian(i, 2)
97 | elif i < 0x100000000:
98 | return b'\xfe' + int_to_little_endian(i, 4)
99 | elif i < 0x10000000000000000:
100 | return b'\xff' + int_to_little_endian(i, 8)
101 | else:
102 | raise ValueError('integer too large: {}'.format(i))
103 |
104 |
105 | def h160_to_p2pkh_address(h160, testnet=False):
106 | '''Takes a byte sequence hash160 and returns a p2pkh address string'''
107 | # p2pkh has a prefix of b'\x00' for mainnet, b'\x6f' for testnet
108 | # use encode_base58_checksum to get the address
109 | raise NotImplementedError
110 |
111 |
112 | def h160_to_p2sh_address(h160, testnet=False):
113 | '''Takes a byte sequence hash160 and returns a p2sh address string'''
114 | # p2sh has a prefix of b'\x05' for mainnet, b'\xc4' for testnet
115 | # use encode_base58_checksum to get the address
116 | raise NotImplementedError
117 |
118 |
119 | class HelperTest(TestCase):
120 |
121 | def test_little_endian_to_int(self):
122 | h = bytes.fromhex('99c3980000000000')
123 | want = 10011545
124 | self.assertEqual(little_endian_to_int(h), want)
125 | h = bytes.fromhex('a135ef0100000000')
126 | want = 32454049
127 | self.assertEqual(little_endian_to_int(h), want)
128 |
129 | def test_int_to_little_endian(self):
130 | n = 1
131 | want = b'\x01\x00\x00\x00'
132 | self.assertEqual(int_to_little_endian(n, 4), want)
133 | n = 10011545
134 | want = b'\x99\xc3\x98\x00\x00\x00\x00\x00'
135 | self.assertEqual(int_to_little_endian(n, 8), want)
136 |
137 | def test_base58(self):
138 | addr = 'mnrVtF8DWjMu839VW3rBfgYaAfKk8983Xf'
139 | h160 = decode_base58(addr).hex()
140 | want = '507b27411ccf7f16f10297de6cef3f291623eddf'
141 | self.assertEqual(h160, want)
142 | got = encode_base58_checksum(b'\x6f' + bytes.fromhex(h160))
143 | self.assertEqual(got, addr)
144 |
145 | def test_p2pkh_address(self):
146 | h160 = bytes.fromhex('74d691da1574e6b3c192ecfb52cc8984ee7b6c56')
147 | want = '1BenRpVUFK65JFWcQSuHnJKzc4M8ZP8Eqa'
148 | self.assertEqual(h160_to_p2pkh_address(h160, testnet=False), want)
149 | want = 'mrAjisaT4LXL5MzE81sfcDYKU3wqWSvf9q'
150 | self.assertEqual(h160_to_p2pkh_address(h160, testnet=True), want)
151 |
152 | def test_p2sh_address(self):
153 | h160 = bytes.fromhex('74d691da1574e6b3c192ecfb52cc8984ee7b6c56')
154 | want = '3CLoMMyuoDQTPRD3XYZtCvgvkadrAdvdXh'
155 | self.assertEqual(h160_to_p2sh_address(h160, testnet=False), want)
156 | want = '2N3u1R6uwQfuobCqbCgBkpsgBxvr1tZpe7B'
157 | self.assertEqual(h160_to_p2sh_address(h160, testnet=True), want)
158 |
--------------------------------------------------------------------------------
/code-ch08/jupyter.txt:
--------------------------------------------------------------------------------
1 | import ecc
2 | import helper
3 | import op
4 | import script
5 | import tx
6 | ---
7 | exercise1:op:OpTest:test_op_checkmultisig
8 | ---
9 | example1
10 | ---
11 | exercise2:helper.HelperTest::test_p2pkh_address
12 | ---
13 | exercise3:helper.HelperTest::test_p2sh_address
14 | ---
15 | example2
16 | ---
17 | example3
18 | ---
19 | exercise4:
20 | from io import BytesIO
21 | from ecc import S256Point, Signature
22 | from helper import encode_varint, hash256, int_to_little_endian
23 | from script import Script
24 | from tx import Tx, SIGHASH_ALL
25 |
26 | hex_tx = '0100000001868278ed6ddfb6c1ed3ad5f8181eb0c7a385aa0836f01d5e4789e6bd304d87221a000000db00483045022100dc92655fe37036f47756db8102e0d7d5e28b3beb83a8fef4f5dc0559bddfb94e02205a36d4e4e6c7fcd16658c50783e00c341609977aed3ad00937bf4ee942a8993701483045022100da6bee3c93766232079a01639d07fa869598749729ae323eab8eef53577d611b02207bef15429dcadce2121ea07f233115c6f09034c0be68db99980b9a6c5e75402201475221022626e955ea6ea6d98850c994f9107b036b1334f18ca8830bfff1295d21cfdb702103b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7fbdbd4bb7152aeffffffff04d3b11400000000001976a914904a49878c0adfc3aa05de7afad2cc15f483a56a88ac7f400900000000001976a914418327e3f3dda4cf5b9089325a4b95abdfa0334088ac722c0c00000000001976a914ba35042cfe9fc66fd35ac2224eebdafd1028ad2788acdc4ace020000000017a91474d691da1574e6b3c192ecfb52cc8984ee7b6c568700000000'
27 | hex_sec = '03b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7fbdbd4bb71'
28 | hex_der = '3045022100da6bee3c93766232079a01639d07fa869598749729ae323eab8eef53577d611b02207bef15429dcadce2121ea07f233115c6f09034c0be68db99980b9a6c5e754022'
29 | hex_redeem_script = '475221022626e955ea6ea6d98850c994f9107b036b1334f18ca8830bfff1295d21cfdb702103b287eaf122eea69030a0e9feed096bed8045c8b98bec453e1ffac7fbdbd4bb7152ae'
30 | sec = bytes.fromhex(hex_sec)
31 | der = bytes.fromhex(hex_der)
32 | redeem_script = Script.parse(BytesIO(bytes.fromhex(hex_redeem_script)))
33 | stream = BytesIO(bytes.fromhex(hex_tx))
34 |
35 | # modify the transaction
36 | # start with version
37 | # add number of inputs
38 | # modify the single TxIn to have the ScriptSig to be the RedeemScript
39 | # add the number of outputs
40 | # add each output serialization
41 | # add the locktime
42 | # add the SIGHASH_ALL
43 | # hash256 the result
44 | # interpret as a Big-Endian number
45 | # parse the S256Point
46 | # parse the Signature
47 | # verify that the point, z and signature work
48 | ---
49 | exercise5:tx:TxTest:test_verify_p2sh
50 |
--------------------------------------------------------------------------------
/code-ch09/examples.py:
--------------------------------------------------------------------------------
1 | '''
2 | # tag::example1[]
3 | >>> from io import BytesIO
4 | >>> from script import Script
5 | >>> stream = BytesIO(bytes.fromhex('4d04ffff001d0104455468652054696d6573203033\
6 | 2f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64\
7 | 206261696c6f757420666f722062616e6b73'))
8 | >>> s = Script.parse(stream)
9 | >>> print(s.cmds[2])
10 | b'The Times 03/Jan/2009 Chancellor on brink of second bailout for banks'
11 |
12 | # end::example1[]
13 | # tag::example2[]
14 | >>> from io import BytesIO
15 | >>> from script import Script
16 | >>> from helper import little_endian_to_int
17 | >>> stream = BytesIO(bytes.fromhex('5e03d71b07254d696e656420627920416e74506f6f\
18 | 6c20626a31312f4542312f4144362f43205914293101fabe6d6d678e2c8c34afc36896e7d94028\
19 | 24ed38e856676ee94bfdb0c6c4bcd8b2e5666a0400000000000000c7270000a5e00e00'))
20 | >>> script_sig = Script.parse(stream)
21 | >>> print(little_endian_to_int(script_sig.cmds[0]))
22 | 465879
23 |
24 | # end::example2[]
25 | # tag::example3[]
26 | >>> from helper import hash256
27 | >>> block_hash = hash256(bytes.fromhex('020000208ec39428b17323fa0ddec8e887b4a7\
28 | c53b8c0a0a220cfd0000000000000000005b0750fce0a889502d40508d39576821155e9c9e3f5c\
29 | 3157f961db38fd8b25be1e77a759e93c0118a4ffd71d'))[::-1]
30 | >>> block_id = block_hash.hex()
31 | >>> print(block_id)
32 | 0000000000000000007e9e4c586439b0cdbe13b1370bdd9435d76a644d047523
33 |
34 | # end::example3[]
35 | # tag::example4[]
36 | >>> from io import BytesIO
37 | >>> from block import Block
38 | >>> b = Block.parse(BytesIO(bytes.fromhex('020000208ec39428b17323fa0ddec8e887b\
39 | 4a7c53b8c0a0a220cfd0000000000000000005b0750fce0a889502d40508d39576821155e9c9e3\
40 | f5c3157f961db38fd8b25be1e77a759e93c0118a4ffd71d')))
41 | >>> print('BIP9: {}'.format(b.version >> 29 == 0b001)) # <1>
42 | BIP9: True
43 | >>> print('BIP91: {}'.format(b.version >> 4 & 1 == 1)) # <2>
44 | BIP91: False
45 | >>> print('BIP141: {}'.format(b.version >> 1 & 1 == 1)) # <3>
46 | BIP141: True
47 |
48 | # end::example4[]
49 | # tag::example5[]
50 | >>> from helper import hash256
51 | >>> block_id = hash256(bytes.fromhex('020000208ec39428b17323fa0ddec8e887b4a7c5\
52 | 3b8c0a0a220cfd0000000000000000005b0750fce0a889502d40508d39576821155e9c9e3f5c31\
53 | 57f961db38fd8b25be1e77a759e93c0118a4ffd71d'))[::-1]
54 | >>> print('{}'.format(block_id.hex()).zfill(64)) # <1>
55 | 0000000000000000007e9e4c586439b0cdbe13b1370bdd9435d76a644d047523
56 |
57 | # end::example5[]
58 | # tag::example6[]
59 | >>> from helper import little_endian_to_int
60 | >>> bits = bytes.fromhex('e93c0118')
61 | >>> exponent = bits[-1]
62 | >>> coefficient = little_endian_to_int(bits[:-1])
63 | >>> target = coefficient * 256**(exponent - 3)
64 | >>> print('{:x}'.format(target).zfill(64)) # <1>
65 | 0000000000000000013ce9000000000000000000000000000000000000000000
66 |
67 | # end::example6[]
68 | # tag::example7[]
69 | >>> from helper import little_endian_to_int
70 | >>> proof = little_endian_to_int(hash256(bytes.fromhex('020000208ec39428b17323\
71 | fa0ddec8e887b4a7c53b8c0a0a220cfd0000000000000000005b0750fce0a889502d40508d3957\
72 | 6821155e9c9e3f5c3157f961db38fd8b25be1e77a759e93c0118a4ffd71d')))
73 | >>> print(proof < target) # <1>
74 | True
75 |
76 | # end::example7[]
77 | # tag::example8[]
78 | >>> from helper import little_endian_to_int
79 | >>> bits = bytes.fromhex('e93c0118')
80 | >>> exponent = bits[-1]
81 | >>> coefficient = little_endian_to_int(bits[:-1])
82 | >>> target = coefficient*256**(exponent-3)
83 | >>> difficulty = 0xffff * 256**(0x1d-3) / target
84 | >>> print(difficulty)
85 | 888171856257.3206
86 |
87 | # end::example8[]
88 | # tag::example9[]
89 | >>> from block import Block
90 | >>> from helper import TWO_WEEKS # <1>
91 | >>> last_block = Block.parse(BytesIO(bytes.fromhex('00000020fdf740b0e49cf75bb3\
92 | d5168fb3586f7613dcc5cd89675b0100000000000000002e37b144c0baced07eb7e7b64da916cd\
93 | 3121f2427005551aeb0ec6a6402ac7d7f0e4235954d801187f5da9f5')))
94 | >>> first_block = Block.parse(BytesIO(bytes.fromhex('000000201ecd89664fd205a37\
95 | 566e694269ed76e425803003628ab010000000000000000bfcade29d080d9aae8fd461254b0418\
96 | 05ae442749f2a40100440fc0e3d5868e55019345954d80118a1721b2e')))
97 | >>> time_differential = last_block.timestamp - first_block.timestamp
98 | >>> if time_differential > TWO_WEEKS * 4: # <2>
99 | ... time_differential = TWO_WEEKS * 4
100 | >>> if time_differential < TWO_WEEKS // 4: # <3>
101 | ... time_differential = TWO_WEEKS // 4
102 | >>> new_target = last_block.target() * time_differential // TWO_WEEKS
103 | >>> print('{:x}'.format(new_target).zfill(64))
104 | 0000000000000000007615000000000000000000000000000000000000000000
105 |
106 | # end::example9[]
107 | '''
108 |
--------------------------------------------------------------------------------
/code-ch09/jupyter.txt:
--------------------------------------------------------------------------------
1 | import block
2 | import ecc
3 | import helper
4 | import script
5 | import tx
6 | ---
7 | exercise1:tx:TxTest:test_is_coinbase
8 | ---
9 | example1
10 | ---
11 | example2
12 | ---
13 | exercise2:tx:TxTest:test_coinbase_height
14 | ---
15 | example3
16 | ---
17 | exercise3:block:BlockTest:test_parse
18 | ---
19 | exercise4:block:BlockTest:test_serialize
20 | ---
21 | exercise5:block:BlockTest:test_hash
22 | ---
23 | example4
24 | ---
25 | exercise6:block:BlockTest:test_bip9
26 | ---
27 | exercise7:block:BlockTest:test_bip91
28 | ---
29 | exercise8:block:BlockTest:test_bip141
30 | ---
31 | example5
32 | ---
33 | example6
34 | ---
35 | example7
36 | ---
37 | exercise9:block:BlockTest:test_target
38 | ---
39 | example8
40 | ---
41 | exercise10:block:BlockTest:test_difficulty
42 | ---
43 | exercise11:block:BlockTest:test_check_pow
44 | ---
45 | example9
46 | ---
47 | exercise12:
48 | from block import Block, TWO_WEEKS
49 | from helper import target_to_bits
50 |
51 | block1_hex = '000000203471101bbda3fe307664b3283a9ef0e97d9a38a7eacd8800000000000000000010c8aba8479bbaa5e0848152fd3c2289ca50e1c3e58c9a4faaafbdf5803c5448ddb845597e8b0118e43a81d3'
52 | block2_hex = '02000020f1472d9db4b563c35f97c428ac903f23b7fc055d1cfc26000000000000000000b3f449fcbe1bc4cfbcb8283a0d2c037f961a3fdf2b8bedc144973735eea707e1264258597e8b0118e5f00474'
53 |
54 | # parse both blocks
55 | # get the time differential
56 | # if the differential > 8 weeks, set to 8 weeks
57 | # if the differential < 1/2 week, set to 1/2 week
58 | # new target is last target * differential / 2 weeks
59 | # convert new target to bits
60 | # print the new bits hex
61 | ---
62 | exercise13:helper:HelperTest:test_calculate_new_bits
63 |
--------------------------------------------------------------------------------
/code-ch10/answers.py:
--------------------------------------------------------------------------------
1 | '''
2 | # tag::exercise2[]
3 | ==== Exercise 2
4 |
5 | Determine what this network message is:
6 |
7 | ----
8 | f9beb4d976657261636b000000000000000000005df6e0e2
9 | ----
10 | # end::exercise2[]
11 | # tag::answer2[]
12 | >>> from network import NetworkEnvelope
13 | >>> from io import BytesIO
14 | >>> message_hex = 'f9beb4d976657261636b000000000000000000005df6e0e2'
15 | >>> stream = BytesIO(bytes.fromhex(message_hex))
16 | >>> envelope = NetworkEnvelope.parse(stream)
17 | >>> print(envelope.command)
18 | b'verack'
19 | >>> print(envelope.payload)
20 | b''
21 |
22 | # end::answer2[]
23 | '''
24 |
25 |
26 | from unittest import TestCase
27 |
28 | from helper import (
29 | encode_varint,
30 | hash256,
31 | int_to_little_endian,
32 | little_endian_to_int,
33 | )
34 | from network import (
35 | GetHeadersMessage,
36 | NetworkEnvelope,
37 | SimpleNode,
38 | VersionMessage,
39 | VerAckMessage,
40 | NETWORK_MAGIC,
41 | TESTNET_NETWORK_MAGIC,
42 | )
43 |
44 |
45 | methods = []
46 |
47 |
48 | '''
49 | # tag::exercise1[]
50 | ==== Exercise 1
51 |
52 | Write the `parse` method for `NetworkEnvelope`.
53 | # end::exercise1[]
54 | '''
55 |
56 |
57 | # tag::answer1[]
58 | @classmethod
59 | def parse(cls, s, testnet=False):
60 | magic = s.read(4)
61 | if magic == b'':
62 | raise IOError('Connection reset!')
63 | if testnet:
64 | expected_magic = TESTNET_NETWORK_MAGIC
65 | else:
66 | expected_magic = NETWORK_MAGIC
67 | if magic != expected_magic:
68 | raise SyntaxError('magic is not right {} vs {}'.format(magic.hex(),
69 | expected_magic.hex()))
70 | command = s.read(12)
71 | command = command.strip(b'\x00')
72 | payload_length = little_endian_to_int(s.read(4))
73 | checksum = s.read(4)
74 | payload = s.read(payload_length)
75 | calculated_checksum = hash256(payload)[:4]
76 | if calculated_checksum != checksum:
77 | raise IOError('checksum does not match')
78 | return cls(command, payload, testnet=testnet)
79 | # end::answer1[]
80 |
81 |
82 | '''
83 | # tag::exercise3[]
84 | ==== Exercise 3
85 |
86 | Write the `serialize` method for `NetworkEnvelope`.
87 | # end::exercise3[]
88 | '''
89 |
90 |
91 | # tag::answer3[]
92 | def serialize(self):
93 | result = self.magic
94 | result += self.command + b'\x00' * (12 - len(self.command))
95 | result += int_to_little_endian(len(self.payload), 4)
96 | result += hash256(self.payload)[:4]
97 | result += self.payload
98 | return result
99 | # end::answer3[]
100 |
101 |
102 | methods.append(serialize)
103 |
104 |
105 | '''
106 | # tag::exercise4[]
107 | ==== Exercise 4
108 |
109 | Write the `serialize` method for `VersionMessage`.
110 | # end::exercise4[]
111 | '''
112 |
113 |
114 | # tag::answer4[]
115 | def serialize(self):
116 | result = int_to_little_endian(self.version, 4)
117 | result += int_to_little_endian(self.services, 8)
118 | result += int_to_little_endian(self.timestamp, 8)
119 | result += int_to_little_endian(self.receiver_services, 8)
120 | result += b'\x00' * 10 + b'\xff\xff' + self.receiver_ip
121 | result += self.receiver_port.to_bytes(2, 'big')
122 | result += int_to_little_endian(self.sender_services, 8)
123 | result += b'\x00' * 10 + b'\xff\xff' + self.sender_ip
124 | result += self.sender_port.to_bytes(2, 'big')
125 | result += self.nonce
126 | result += encode_varint(len(self.user_agent))
127 | result += self.user_agent
128 | result += int_to_little_endian(self.latest_block, 4)
129 | if self.relay:
130 | result += b'\x01'
131 | else:
132 | result += b'\x00'
133 | return result
134 | # end::answer4[]
135 |
136 |
137 | methods.append(serialize)
138 |
139 |
140 | '''
141 | # tag::exercise5[]
142 | ==== Exercise 5
143 |
144 | Write the `handshake` method for `SimpleNode`.
145 | # end::exercise5[]
146 | '''
147 |
148 |
149 | # tag::answer5[]
150 | def handshake(self):
151 | version = VersionMessage()
152 | self.send(version)
153 | self.wait_for(VerAckMessage)
154 | # end::answer5[]
155 |
156 |
157 | '''
158 | # tag::exercise6[]
159 | ==== Exercise 6
160 |
161 | Write the `serialize` method for `GetHeadersMessage`.
162 | # end::exercise6[]
163 | '''
164 |
165 |
166 | # tag::answer6[]
167 | def serialize(self):
168 | result = int_to_little_endian(self.version, 4)
169 | result += encode_varint(self.num_hashes)
170 | result += self.start_block[::-1]
171 | result += self.end_block[::-1]
172 | return result
173 | # end::answer6[]
174 |
175 |
176 | methods.append(serialize)
177 |
178 |
179 | class ChapterTest(TestCase):
180 |
181 | def test_apply(self):
182 | NetworkEnvelope.parse = parse
183 | NetworkEnvelope.serialize = methods[0]
184 | VersionMessage.serialize = methods[1]
185 | SimpleNode.handshake = handshake
186 | GetHeadersMessage.serialize = methods[2]
187 |
--------------------------------------------------------------------------------
/code-ch10/examples.py:
--------------------------------------------------------------------------------
1 | '''
2 | # tag::example1[]
3 | >>> from io import BytesIO
4 | >>> from network import SimpleNode, GetHeadersMessage, HeadersMessage
5 | >>> from block import Block, GENESIS_BLOCK, LOWEST_BITS
6 | >>> from helper import calculate_new_bits
7 | >>> previous = Block.parse(BytesIO(GENESIS_BLOCK))
8 | >>> first_epoch_timestamp = previous.timestamp
9 | >>> expected_bits = LOWEST_BITS
10 | >>> count = 1
11 | >>> node = SimpleNode('mainnet.programmingbitcoin.com', testnet=False)
12 | >>> node.handshake()
13 | >>> for _ in range(19):
14 | ... getheaders = GetHeadersMessage(start_block=previous.hash())
15 | ... node.send(getheaders)
16 | ... headers = node.wait_for(HeadersMessage)
17 | ... for header in headers.blocks:
18 | ... if not header.check_pow(): # <1>
19 | ... raise RuntimeError('bad PoW at block {}'.format(count))
20 | ... if header.prev_block != previous.hash(): # <2>
21 | ... raise RuntimeError('discontinuous block at {}'.format(count))
22 | ... if count % 2016 == 0:
23 | ... time_diff = previous.timestamp - first_epoch_timestamp
24 | ... expected_bits = calculate_new_bits(previous.bits, time_diff) # <4>
25 | ... print(expected_bits.hex())
26 | ... first_epoch_timestamp = header.timestamp # <5>
27 | ... if header.bits != expected_bits: # <3>
28 | ... raise RuntimeError('bad bits at block {}'.format(count))
29 | ... previous = header
30 | ... count += 1
31 | ffff001d
32 | ffff001d
33 | ffff001d
34 | ffff001d
35 | ffff001d
36 | ffff001d
37 | ffff001d
38 | ffff001d
39 | ffff001d
40 | ffff001d
41 | ffff001d
42 | ffff001d
43 | ffff001d
44 | ffff001d
45 | ffff001d
46 | 6ad8001d
47 | 28c4001d
48 | 71be001d
49 |
50 | # end::example1[]
51 | '''
52 |
--------------------------------------------------------------------------------
/code-ch10/jupyter.txt:
--------------------------------------------------------------------------------
1 | import network
2 |
3 | from block import GENESIS_BLOCK
4 | from helper import calculate_new_bits
5 | from network import (
6 | NetworkEnvelope,
7 | VersionMessage,
8 | )
9 | ---
10 | exercise1:
11 | message_hex = 'f9beb4d976657261636b000000000000000000005df6e0e2'
12 |
13 | # convert to binary
14 | # see what the bytes 4 through 16 aregi
15 | ---
16 | exercise2:network:NetworkEnvelopeTest:test_parse
17 | ---
18 | exercise3:network:NetworkEnvelopeTest:test_serialize
19 | ---
20 | exercise4:network:VersionMessageTest:test_serialize
21 | ---
22 | exercise5:network:SimpleNodeTest:test_handshake
23 | ---
24 | exercise6:network:GetHeadersMessageTest:test_serialize
25 | ---
26 | example1
27 |
28 |
--------------------------------------------------------------------------------
/code-ch11/answers.py:
--------------------------------------------------------------------------------
1 | '''
2 | # tag::answer5[]
3 | >>> import math
4 | >>> total = 27
5 | >>> max_depth = math.ceil(math.log(total, 2))
6 | >>> merkle_tree = []
7 | >>> for depth in range(max_depth + 1):
8 | ... num_items = math.ceil(total / 2**(max_depth - depth))
9 | ... level_hashes = [None] * num_items
10 | ... merkle_tree.append(level_hashes)
11 | >>> for level in merkle_tree:
12 | ... print(level)
13 | [None]
14 | [None, None]
15 | [None, None, None, None]
16 | [None, None, None, None, None, None, None]
17 | [None, None, None, None, None, None, None, None, None, None, None, None, None,\
18 | None]
19 | [None, None, None, None, None, None, None, None, None, None, None, None, None,\
20 | None, None, None, None, None, None, None, None, None, None, None, None, None,\
21 | None]
22 |
23 | # end::answer5[]
24 | '''
25 |
26 |
27 | from unittest import TestCase
28 |
29 | import helper
30 | import merkleblock
31 |
32 | from block import Block
33 | from helper import (
34 | bytes_to_bit_field,
35 | hash256,
36 | little_endian_to_int,
37 | read_varint,
38 | )
39 | from merkleblock import (
40 | MerkleBlock,
41 | MerkleTree,
42 | )
43 |
44 |
45 | '''
46 | # tag::exercise1[]
47 | ==== Exercise 1
48 |
49 | Write the `merkle_parent` function.
50 | # end::exercise1[]
51 | '''
52 |
53 |
54 | # tag::answer1[]
55 | def merkle_parent(hash1, hash2):
56 | '''Takes the binary hashes and calculates the hash256'''
57 | return hash256(hash1 + hash2)
58 | # end::answer1[]
59 |
60 |
61 | '''
62 | # tag::exercise2[]
63 | ==== Exercise 2
64 |
65 | Write the `merkle_parent_level` function.
66 | # end::exercise2[]
67 | '''
68 |
69 |
70 | # tag::answer2[]
71 | def merkle_parent_level(hashes):
72 | '''Takes a list of binary hashes and returns a list that's half
73 | the length'''
74 | if len(hashes) == 1:
75 | raise RuntimeError('Cannot take a parent level with only 1 item')
76 | if len(hashes) % 2 == 1:
77 | hashes.append(hashes[-1])
78 | parent_level = []
79 | for i in range(0, len(hashes), 2):
80 | parent = merkle_parent(hashes[i], hashes[i + 1])
81 | parent_level.append(parent)
82 | return parent_level
83 | # end::answer2[]
84 |
85 |
86 | '''
87 | # tag::exercise3[]
88 | ==== Exercise 3
89 |
90 | Write the `merkle_root` function.
91 | # end::exercise3[]
92 | '''
93 |
94 |
95 | # tag::answer3[]
96 | def merkle_root(hashes):
97 | '''Takes a list of binary hashes and returns the merkle root
98 | '''
99 | current_level = hashes
100 | while len(current_level) > 1:
101 | current_level = merkle_parent_level(current_level)
102 | return current_level[0]
103 | # end::answer3[]
104 |
105 |
106 | '''
107 | # tag::exercise4[]
108 | ==== Exercise 4
109 |
110 | Write the `validate_merkle_root` method for `Block`.
111 | # end::exercise4[]
112 | '''
113 |
114 |
115 | # tag::answer4[]
116 | def validate_merkle_root(self):
117 | hashes = [h[::-1] for h in self.tx_hashes]
118 | root = merkle_root(hashes)
119 | return root[::-1] == self.merkle_root
120 | # end::answer4[]
121 |
122 |
123 | '''
124 | # tag::exercise5[]
125 | ==== Exercise 5
126 |
127 | Create an empty Merkle Tree with 27 items and print each level.
128 | # end::exercise5[]
129 | '''
130 | '''
131 | # tag::exercise6[]
132 | ==== Exercise 6
133 |
134 | Write the `parse` method for `MerkleBlock`.
135 | # end::exercise6[]
136 | '''
137 |
138 |
139 | # tag::answer6[]
140 | @classmethod
141 | def parse(cls, s):
142 | version = little_endian_to_int(s.read(4))
143 | prev_block = s.read(32)[::-1]
144 | merkle_root = s.read(32)[::-1]
145 | timestamp = little_endian_to_int(s.read(4))
146 | bits = s.read(4)
147 | nonce = s.read(4)
148 | total = little_endian_to_int(s.read(4))
149 | num_hashes = read_varint(s)
150 | hashes = []
151 | for _ in range(num_hashes):
152 | hashes.append(s.read(32)[::-1])
153 | flags_length = read_varint(s)
154 | flags = s.read(flags_length)
155 | return cls(version, prev_block, merkle_root, timestamp, bits,
156 | nonce, total, hashes, flags)
157 | # end::answer6[]
158 |
159 |
160 | '''
161 | # tag::exercise7[]
162 | ==== Exercise 7
163 |
164 | Write the `is_valid` method for `MerkleBlock`.
165 | # end::exercise7[]
166 | '''
167 |
168 |
169 | # tag::answer7[]
170 | def is_valid(self):
171 | flag_bits = bytes_to_bit_field(self.flags)
172 | hashes = [h[::-1] for h in self.hashes]
173 | merkle_tree = MerkleTree(self.total)
174 | merkle_tree.populate_tree(flag_bits, hashes)
175 | return merkle_tree.root()[::-1] == self.merkle_root
176 | # end::answer7[]
177 |
178 |
179 | class ChapterTest(TestCase):
180 |
181 | def test_apply(self):
182 | helper.merkle_parent = merkle_parent
183 | merkleblock.merkle_parent = merkle_parent
184 | helper.merkle_parent_level = merkle_parent_level
185 | helper.merkle_root = merkle_root
186 | Block.validate_merkle_root = validate_merkle_root
187 | MerkleBlock.parse = parse
188 | MerkleBlock.is_valid = is_valid
189 |
--------------------------------------------------------------------------------
/code-ch11/jupyter.txt:
--------------------------------------------------------------------------------
1 | import block
2 | import ecc
3 | import helper
4 | import network
5 | import script
6 | import tx
7 | ---
8 | example1
9 | ---
10 | exercise1:helper:HelperTest:test_merkle_parent
11 | ---
12 | example2
13 | ---
14 | exercise2:helper:HelperTest:test_merkle_parent_level
15 | ---
16 | example3
17 | ---
18 | exercise3:helper:HelperTest:test_merkle_root
19 | ---
20 | example4
21 | ---
22 | exercise4:block:BlockTest:test_validate_merkle_root
23 | ---
24 | example5
25 | ---
26 | exercise5:
27 | import math
28 |
29 | total = 27
30 | # use math.ceil(math.log(total, 2)) to get the max depth
31 | # create an array of arrays for the tree
32 | # loop through all possible depths
33 | # get how many items at this depth
34 | # use math.ceil(total / 2**(max depth - current depth))
35 | # create an empty array for this level
36 | # append the level to the tree
37 | # print the tree
38 | ---
39 | example6
40 | ---
41 | example7
42 | ---
43 | example8
44 | ---
45 | exercise6:merkleblock:MerkleBlockTest:test_parse
46 | ---
47 | exercise7:merkleblock:MerkleBlockTest:test_is_valid
48 |
--------------------------------------------------------------------------------
/code-ch12/bloomfilter.py:
--------------------------------------------------------------------------------
1 | from unittest import TestCase
2 |
3 | from helper import (
4 | bit_field_to_bytes,
5 | encode_varint,
6 | int_to_little_endian,
7 | murmur3,
8 | )
9 | from network import GenericMessage
10 |
11 |
12 | BIP37_CONSTANT = 0xfba4c795
13 |
14 |
15 | # tag::source1[]
16 | class BloomFilter:
17 |
18 | def __init__(self, size, function_count, tweak):
19 | self.size = size
20 | self.bit_field = [0] * (size * 8)
21 | self.function_count = function_count
22 | self.tweak = tweak
23 | # end::source1[]
24 |
25 | def add(self, item):
26 | '''Add an item to the filter'''
27 | # iterate self.function_count number of times
28 | # BIP0037 spec seed is i*BIP37_CONSTANT + self.tweak
29 | # get the murmur3 hash given that seed
30 | # set the bit at the hash mod the bitfield size (self.size*8)
31 | # set the bit field at bit to be 1
32 | raise NotImplementedError
33 |
34 | def filter_bytes(self):
35 | return bit_field_to_bytes(self.bit_field)
36 |
37 | def filterload(self, flag=1):
38 | '''Return the filterload message'''
39 | # start the payload with the size of the filter in bytes
40 | # next add the bit field using self.filter_bytes()
41 | # function count is 4 bytes little endian
42 | # tweak is 4 bytes little endian
43 | # flag is 1 byte little endian
44 | # return a GenericMessage whose command is b'filterload'
45 | # and payload is what we've calculated
46 | raise NotImplementedError
47 |
48 |
49 | class BloomFilterTest(TestCase):
50 |
51 | def test_add(self):
52 | bf = BloomFilter(10, 5, 99)
53 | item = b'Hello World'
54 | bf.add(item)
55 | expected = '0000000a080000000140'
56 | self.assertEqual(bf.filter_bytes().hex(), expected)
57 | item = b'Goodbye!'
58 | bf.add(item)
59 | expected = '4000600a080000010940'
60 | self.assertEqual(bf.filter_bytes().hex(), expected)
61 |
62 | def test_filterload(self):
63 | bf = BloomFilter(10, 5, 99)
64 | item = b'Hello World'
65 | bf.add(item)
66 | item = b'Goodbye!'
67 | bf.add(item)
68 | expected = '0a4000600a080000010940050000006300000001'
69 | self.assertEqual(bf.filterload().serialize().hex(), expected)
70 |
--------------------------------------------------------------------------------
/code-ch12/examples.py:
--------------------------------------------------------------------------------
1 | '''
2 | # tag::example1[]
3 | >>> from helper import hash256
4 | >>> bit_field_size = 10 # <1>
5 | >>> bit_field = [0] * bit_field_size
6 | >>> h = hash256(b'hello world') # <2>
7 | >>> bit = int.from_bytes(h, 'big') % bit_field_size # <3>
8 | >>> bit_field[bit] = 1 # <4>
9 | >>> print(bit_field)
10 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
11 |
12 | # end::example1[]
13 | # tag::example2[]
14 | >>> from helper import hash256
15 | >>> bit_field_size = 10
16 | >>> bit_field = [0] * bit_field_size
17 | >>> for item in (b'hello world', b'goodbye'): # <1>
18 | ... h = hash256(item)
19 | ... bit = int.from_bytes(h, 'big') % bit_field_size
20 | ... bit_field[bit] = 1
21 | >>> print(bit_field)
22 | [0, 0, 1, 0, 0, 0, 0, 0, 0, 1]
23 |
24 | # end::example2[]
25 | # tag::example3[]
26 | >>> from helper import hash256, hash160
27 | >>> bit_field_size = 10
28 | >>> bit_field = [0] * bit_field_size
29 | >>> for item in (b'hello world', b'goodbye'):
30 | ... for hash_function in (hash256, hash160): # <1>
31 | ... h = hash_function(item)
32 | ... bit = int.from_bytes(h, 'big') % bit_field_size
33 | ... bit_field[bit] = 1
34 | >>> print(bit_field)
35 | [1, 1, 1, 0, 0, 0, 0, 0, 0, 1]
36 |
37 | # end::example3[]
38 | # tag::example4[]
39 | >>> from helper import murmur3 # <1>
40 | >>> from bloomfilter import BIP37_CONSTANT # <2>
41 | >>> field_size = 2
42 | >>> num_functions = 2
43 | >>> tweak = 42
44 | >>> bit_field_size = field_size * 8
45 | >>> bit_field = [0] * bit_field_size
46 | >>> for phrase in (b'hello world', b'goodbye'): # <3>
47 | ... for i in range(num_functions): # <4>
48 | ... seed = i * BIP37_CONSTANT + tweak # <5>
49 | ... h = murmur3(phrase, seed=seed) # <6>
50 | ... bit = h % bit_field_size
51 | ... bit_field[bit] = 1
52 | >>> print(bit_field)
53 | [0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0]
54 |
55 | # end::example4[]
56 | # tag::example5[]
57 | >>> from bloomfilter import BloomFilter
58 | >>> from helper import decode_base58
59 | >>> from merkleblock import MerkleBlock
60 | >>> from network import FILTERED_BLOCK_DATA_TYPE, GetHeadersMessage, GetDataMe\
61 | ssage, HeadersMessage, SimpleNode
62 | >>> from tx import Tx
63 | >>> last_block_hex = '00000000000538d5c2246336644f9a4956551afb44ba47278759ec55\
64 | ea912e19'
65 | >>> address = 'mwJn1YPMq7y5F8J3LkC5Hxg9PHyZ5K4cFv'
66 | >>> h160 = decode_base58(address)
67 | >>> node = SimpleNode('testnet.programmingbitcoin.com', testnet=True, logging=\
68 | False)
69 | >>> bf = BloomFilter(size=30, function_count=5, tweak=90210) # <1>
70 | >>> bf.add(h160) # <2>
71 | >>> node.handshake()
72 | >>> node.send(bf.filterload()) # <3>
73 | >>> start_block = bytes.fromhex(last_block_hex)
74 | >>> getheaders = GetHeadersMessage(start_block=start_block)
75 | >>> node.send(getheaders) # <4>
76 | >>> headers = node.wait_for(HeadersMessage)
77 | >>> getdata = GetDataMessage() # <5>
78 | >>> for b in headers.blocks:
79 | ... if not b.check_pow():
80 | ... raise RuntimeError('proof of work is invalid')
81 | ... getdata.add_data(FILTERED_BLOCK_DATA_TYPE, b.hash()) # <6>
82 | >>> node.send(getdata) # <7>
83 | >>> found = False
84 | >>> while not found:
85 | ... message = node.wait_for(MerkleBlock, Tx) # <8>
86 | ... if message.command == b'merkleblock':
87 | ... if not message.is_valid(): # <9>
88 | ... raise RuntimeError('invalid merkle proof')
89 | ... else:
90 | ... for i, tx_out in enumerate(message.tx_outs):
91 | ... if tx_out.script_pubkey.address(testnet=True) == address: # <10>
92 | ... print('found: {}:{}'.format(message.id(), i))
93 | ... found = True
94 | ... break
95 | found: e3930e1e566ca9b75d53b0eb9acb7607f547e1182d1d22bd4b661cfe18dcddf1:0
96 |
97 | # end::example5[]
98 | '''
99 |
--------------------------------------------------------------------------------
/code-ch12/jupyter.txt:
--------------------------------------------------------------------------------
1 | import bloomfilter
2 | import block
3 | import ecc
4 | import helper
5 | import merkleblock
6 | import network
7 | import script
8 | import tx
9 | ---
10 | example1
11 | ---
12 | example2
13 | ---
14 | exercise1:
15 | from helper import hash160
16 |
17 | bit_field_size = 10
18 | bit_field = [0] * bit_field_size
19 | items = (b'hello world', b'goodbye')
20 | # loop through each item
21 | # hash160 the item
22 | # interpret hash as a Big-Endian integer and mod by bit_field_size
23 | # set that bit in bit_field to 1
24 | # print the bit_field
25 | ---
26 | example3
27 | ---
28 | example4
29 | ---
30 | exercise2:
31 | from bloomfilter import BloomFilter, BIP37_CONSTANT
32 | from helper import bit_field_to_bytes, murmur3
33 |
34 | field_size = 10
35 | function_count = 5
36 | tweak = 99
37 | items = (b'Hello World', b'Goodbye!')
38 | # calculate the bitfield size
39 | # create an empty bit field
40 | # loop through items
41 | # loop through function count
42 | # calculate the seed
43 | # get the murmur3 hash of the item using the seed
44 | # mod by the bitfield size
45 | # set the bit
46 | # convert the bit field to bytes
47 | # print the bytes in hex
48 | ---
49 | exercise3:bloomfilter:BloomFilterTest:test_add
50 | ---
51 | exercise4:bloomfilter:BloomFilterTest:test_filterload
52 | ---
53 | exercise5:network:GetDataMessageTest:test_serialize
54 | ---
55 | example5
56 | ---
57 | exercise6:
58 | import time
59 |
60 | from block import Block
61 | from bloomfilter import BloomFilter
62 | from ecc import PrivateKey
63 | from helper import hash256, little_endian_to_int, encode_varint, read_varint, decode_base58, SIGHASH_ALL
64 | from merkleblock import MerkleBlock
65 | from network import (
66 | GetDataMessage,
67 | GetHeadersMessage,
68 | HeadersMessage,
69 | NetworkEnvelope,
70 | SimpleNode,
71 | TX_DATA_TYPE,
72 | FILTERED_BLOCK_DATA_TYPE,
73 | )
74 | from script import p2pkh_script, Script
75 | from tx import Tx, TxIn, TxOut
76 |
77 | last_block_hex = '' # FILL THIS IN
78 |
79 | secret = little_endian_to_int(hash256(b'')) # FILL THIS IN
80 | private_key = PrivateKey(secret=secret)
81 | addr = private_key.point.address(testnet=True)
82 | h160 = decode_base58(addr)
83 |
84 | target_address = 'mwJn1YPMq7y5F8J3LkC5Hxg9PHyZ5K4cFv'
85 | target_h160 = decode_base58(target_address)
86 | target_script = p2pkh_script(target_h160)
87 | fee = 5000 # fee in satoshis
88 |
89 |
90 | # connect to testnet.programmingbitcoin.com in testnet mode
91 | # create a bloom filter of size 30 and 5 functions. Add a tweak.
92 | # add the h160 to the bloom filter
93 | # complete the handshake
94 | # load the bloom filter with the filterload command
95 |
96 | # set start block to last_block from above
97 | # send a getheaders message with the starting block
98 |
99 | # wait for the headers message
100 | # store the last block as None
101 | # initialize the GetDataMessage
102 | # loop through the blocks in the headers
103 | # check that the proof of work on the block is valid
104 | # check that this block's prev_block is the last block
105 | # add a new item to the get_data_message
106 | # should be FILTERED_BLOCK_DATA_TYPE and block hash
107 | # set the last block to the current hash
108 | # send the getdata message
109 |
110 | # initialize prev_tx and prev_index to None
111 | # loop while prev_tx is None
112 | # wait for the merkleblock or tx commands
113 | # if we have the merkleblock command
114 | # check that the MerkleBlock is valid
115 | # else we have the tx command
116 | # set the tx's testnet to be True
117 | # loop through the tx outs
118 | # if our output has the same address as our address we found it
119 | # we found our utxo. set prev_tx, prev_index, and tx
120 | # create the TxIn
121 | # calculate the output amount (previous amount minus the fee)
122 | # create a new TxOut to the target script with the output amount
123 | # create a new transaction with the one input and one output
124 | # sign the only input of the transaction
125 | # serialize and hex to see what it looks like
126 | # send this signed transaction on the network
127 | # wait a sec so this message goes through with time.sleep(1)
128 | # now ask for this transaction from the other node
129 | # create a GetDataMessage
130 | # ask for our transaction by adding it to the message
131 | # send the message
132 | # now wait for a Tx response
133 | # if the received tx has the same id as our tx, we are done!
134 |
--------------------------------------------------------------------------------
/code-ch13/bloomfilter.py:
--------------------------------------------------------------------------------
1 | from unittest import TestCase
2 |
3 | from helper import (
4 | bit_field_to_bytes,
5 | encode_varint,
6 | int_to_little_endian,
7 | murmur3,
8 | )
9 | from network import GenericMessage
10 |
11 |
12 | BIP37_CONSTANT = 0xfba4c795
13 |
14 |
15 | class BloomFilter:
16 |
17 | def __init__(self, size, function_count, tweak):
18 | self.size = size
19 | self.bit_field = [0] * (size * 8)
20 | self.function_count = function_count
21 | self.tweak = tweak
22 |
23 | def add(self, item):
24 | '''Add an item to the filter'''
25 | # iterate self.function_count number of times
26 | for i in range(self.function_count):
27 | # BIP0037 spec seed is i*BIP37_CONSTANT + self.tweak
28 | seed = i * BIP37_CONSTANT + self.tweak
29 | # get the murmur3 hash given that seed
30 | h = murmur3(item, seed=seed)
31 | # set the bit at the hash mod the bitfield size (self.size*8)
32 | bit = h % (self.size * 8)
33 | # set the bit field at bit to be 1
34 | self.bit_field[bit] = 1
35 |
36 | def filter_bytes(self):
37 | return bit_field_to_bytes(self.bit_field)
38 |
39 | def filterload(self, flag=1):
40 | '''Return the filterload message'''
41 | # start the payload with the size of the filter in bytes
42 | payload = encode_varint(self.size)
43 | # next add the bit field using self.filter_bytes()
44 | payload += self.filter_bytes()
45 | # function count is 4 bytes little endian
46 | payload += int_to_little_endian(self.function_count, 4)
47 | # tweak is 4 bytes little endian
48 | payload += int_to_little_endian(self.tweak, 4)
49 | # flag is 1 byte little endian
50 | payload += int_to_little_endian(flag, 1)
51 | # return a GenericMessage whose command is b'filterload'
52 | # and payload is what we've calculated
53 | return GenericMessage(b'filterload', payload)
54 |
55 |
56 | class BloomFilterTest(TestCase):
57 |
58 | def test_add(self):
59 | bf = BloomFilter(10, 5, 99)
60 | item = b'Hello World'
61 | bf.add(item)
62 | expected = '0000000a080000000140'
63 | self.assertEqual(bf.filter_bytes().hex(), expected)
64 | item = b'Goodbye!'
65 | bf.add(item)
66 | expected = '4000600a080000010940'
67 | self.assertEqual(bf.filter_bytes().hex(), expected)
68 |
69 | def test_filterload(self):
70 | bf = BloomFilter(10, 5, 99)
71 | item = b'Hello World'
72 | bf.add(item)
73 | item = b'Goodbye!'
74 | bf.add(item)
75 | expected = '0a4000600a080000010940050000006300000001'
76 | self.assertEqual(bf.filterload().serialize().hex(), expected)
77 |
--------------------------------------------------------------------------------
/colo.html:
--------------------------------------------------------------------------------
1 |
2 |
Colophon
3 |
4 |
The animal on the cover of Programming Bitcoin is a honey badger (Mellivora capensis), also known as a ratel. This mammal, despite its name, resembles a weasel or polecat more than a badger. It is found throughout Africa, the Indian subcontinent, and Southwest Asia. The honey badger is carnivorous, and has few predators because of its incredibly fierce nature when defending itself.
5 |
6 |
This animal gets its common name from its habit of raiding beehives to eat its favored food of honey (and bee larvae); it has a thick hide that minimizes the effect of bee stings. Honey badgers have a very diverse diet, however, which also includes snakes (including the venomous variety), rodents, insects, frogs, eggs, birds, fruit, roots, and plant bulbs. The honey badger has been seen chasing young lions away from kills, and is one of the few species to have been observed using tools.
7 |
8 |
The honey badger is a sturdy animal with a long body, a broad back, and a small flat head. Its legs are short and its feet are tipped with strong claws that make it an exceptional digger. The badger digs not only to unearth prey, but also to create a burrow for itself (around 3–10 feet long, on average). It has a gland at the base of its tail filled with a smelly secretion used to mark territory and warn away other animals. The skin at the back of the badger's neck is loose, which allows it to twist around and bite when it is being held.
9 |
10 |
The honey badger was the subject of a viral video in 2011, featuring comical narration over National Geographic footage of the animal's fearless behavior.
11 |
12 |
Many of the animals on O'Reilly covers are endangered; all of them are important to the world. To learn more about how you can help, go to animals.oreilly.com.
13 |
14 |
The cover illustration is by Karen Montgomery, based on a black and white engraving from Natural History of Animals. The cover fonts are Gilroy Semibold and Guardian Sans. The text font is Adobe Minion Pro; the heading font is Adobe Myriad Condensed; and the code font is Dalton Maag's Ubuntu Mono.
Published by O'Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472.
10 |
11 |
O'Reilly books may be purchased for educational, business, or sales promotional use. Online editions are also available for most titles (http://oreilly.com). For more information, contact our corporate/institutional sales department: 800-998-9938 or corporate@oreilly.com.
12 |
13 |
14 |
15 | Editors:
16 | Mike Loukides and Michele Cronin
17 |
70 | The O’Reilly logo is a registered trademark of O’Reilly Media, Inc.
71 | Programming Bitcoin, the cover image, and related trade dress are
72 | trademarks of O’Reilly Media, Inc.
73 |
74 |
75 | The views expressed in this work are those of the author, and do not
76 | represent the publisher's views. While the publisher and the
77 | author have used good faith efforts to ensure that the information and
78 | instructions contained in this work are accurate, the publisher and the
79 | author disclaim all responsibility for errors or omissions, including
80 | without limitation responsibility for damages resulting from the use of or
81 | reliance on this work. Use of the information and instructions contained
82 | in this work is at your own risk. If any code samples or other technology
83 | this work contains or describes is subject to open source licenses or the
84 | intellectual property rights of others, it is your responsibility to
85 | ensure that your use thereof complies with such licenses and/or rights.
86 |
88 |
91 |
It’s fun to be a science fiction writer. To build a society where wealth is no longer a mirage erected on the empty promises of governments and manipulations of central banks, where exchanges of value can be completed among the trustless without paying a tax to middlemen, where code can be law, where collective decision making is not subject to the distortions of centralization…all I have to do is to open up a text editor and start making up stuff.
4 |
5 |
But compelling stories require more than imagination. They require knowledge of the world. “Worldbuilding” isn’t about literary verisimilitude or strings of technobabble—it’s about piercing through the superficial to ask “what if” questions that get at the heart of how the world works. The more a writer understands the mechanisms and codes that make up the world, the more interesting the questions they ask become.
6 |
7 |
Changing the real world is much, much harder than writing fiction, but it similarly requires knowledge. Beyond wisdom, idealism, grit, discipline, and single-minded determination in the face of doubt, a would-be world-changer needs understanding: of the available tools, their capabilities, and their limits.
8 |
9 |
The world of Bitcoin and blockchain today is still largely a world of fiction. Pundits selling hope and hype, with no understanding of the underlying reality, are far louder and more influential than those who are doing the hard work of bringing about change. Politically motivated screeds premised on fear and get-rich-quick schemes appealing to greed pass for knowledge with the help of a sprinkling of technobabble and trending hashtags.
10 |
11 |
But you can no more understand blockchain by reading whitepapers or thinkpieces than you can learn to build a company by going to business school and watching PowerPoints.
12 |
13 |
You have to code.
14 |
15 |
There is no better way to understand a technology than to build something useful to you in it. Until you’ve coded the fundamental building blocks of a blockchain application with your own hands, you will not be able to intuit the difference between empty hype and realizable possibility.
16 |
17 |
This book is the most efficient and comprehensive way to learn about Bitcoin and blockchain through coding. With great skill and insight, Jimmy Song has crafted a learning path that will take you from the basic math behind Bitcoin to the latest extensions and forks. Along the way, the exercises—refined through extensive work with live students—will not only teach you the mechanics of working with the blockchain, but also an intuition for the elegance and beauty of this technology.
18 |
19 |
The journey will not be easy. Even with a teacher as adept as Jimmy to guide you, this isn’t a book to be flipped through when you’re bored from bingeing on Netflix. It requires you to put in considerable work to get the most out of it. There is no shortcut, no CliffsNotes. But that is very much in line with the constitutive principle of Bitcoin: you must have skin in the game; you must demonstrate proof-of-work. Only then can you trust your knowledge.
20 |
21 |
Happy coding!
22 |
Ken Liu
23 |
24 |
25 |
26 |
A winner of the Nebula, Hugo, and World Fantasy awards, Ken Liu is the author of The Dandelion Dynasty, a silkpunk epic fantasy series in which the magic is engineering, and The Paper Menagerie and Other Stories, a collection. His SF story about blockchain, “Byzantine Empathy”, was originally published by the MIT Press.
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/generate_jupyter.py:
--------------------------------------------------------------------------------
1 | import nbformat
2 | import re
3 |
4 |
5 | FIRST_CELL = '''############## PLEASE RUN THIS CELL FIRST! ###################
6 |
7 | # import everything and define a test runner function
8 | from importlib import reload
9 | from helper import run
10 | '''
11 |
12 | UNITTEST_TEMPLATE_1 = '''### Exercise {num}
13 |
14 | {exercise}
15 |
16 | #### Make [this test](/edit/{path}/{module}.py) pass: `{module}.py:{test_suite}:{test}`'''
17 |
18 |
19 | UNITTEST_TEMPLATE_2 = '''# Exercise {num}
20 |
21 | reload({module})
22 | run({module}.{test_suite}("{test}"))'''
23 |
24 |
25 | PRACTICE_TEMPLATE_1 = '''### Exercise {num}
26 | {exercise}'''
27 |
28 |
29 | PRACTICE_TEMPLATE_2 = '''# Exercise {num}
30 |
31 | {hints}'''
32 |
33 |
34 | for chapter in range(1, 13):
35 | notebook = nbformat.v4.new_notebook()
36 | if chapter < 10:
37 | path = 'code-ch0{}'.format(chapter)
38 | else:
39 | path = 'code-ch{}'.format(chapter)
40 | with open('{}/examples.py'.format(path), 'r') as f:
41 | examples = {}
42 | current = ''
43 | current_key = None
44 | capture = False
45 | for line in f:
46 | if line.startswith('>>>') or line.startswith('...'):
47 | if line.endswith('\\\n'):
48 | current += line[4:-2]
49 | capture = True
50 | else:
51 | current += line[4:]
52 | elif capture:
53 | if line.endswith('\\\n'):
54 | current += line[:-2]
55 | capture = True
56 | else:
57 | current += line
58 | capture = False
59 | elif line.startswith('# tag::example'):
60 | index = line.rfind('[')
61 | current_key = line[7:index]
62 | elif line.startswith('# end::example'):
63 | raw = current
64 | raw = re.sub(r' \# \<[0-9]+\>', r'', raw)
65 | examples[current_key] = raw
66 | current = ''
67 | current_key = None
68 | with open('{}/answers.py'.format(path), 'r') as f:
69 | exercises = {}
70 | current = ''
71 | current_key = None
72 | capture = False
73 | for l in f:
74 | line = l.lstrip(' ')
75 | if line.startswith('# tag::exercise'):
76 | index = line.rfind('[')
77 | current_key = line[7:index]
78 | elif line.startswith('# end::exercise'):
79 | raw = current.strip()
80 | raw = raw[raw.find('\n\n')+1:]
81 | raw = re.sub(r'([a-zA-Z+-])~(.+?)~', r'\\\\(\1_{\2}\\\\)', raw)
82 | raw = re.sub(r'([a-zA-Z0-9()\-+]+)\^(.+?)\^', r'\\\\(\1^{\2}\\\\)', raw)
83 | exercises[current_key] = raw
84 | current = ''
85 | current_key = None
86 | elif current_key is not None:
87 | if line.endswith('\\\n'):
88 | current += line[:-2]
89 | else:
90 | current += line
91 | with open('{}/jupyter.txt'.format(path), 'r') as f:
92 | raw_cells = f.read().split('---\n')
93 | cells = notebook['cells']
94 | # first cell is always added with this:
95 | cells.append(nbformat.v4.new_code_cell(FIRST_CELL + raw_cells[0].strip()))
96 | for raw_cell in raw_cells[1:]:
97 | if raw_cell.startswith('exercise'):
98 | components = raw_cell.split(':')
99 | key = components[0]
100 | if len(components) == 4:
101 | template_dict = {
102 | 'path': path,
103 | 'num': key[8:],
104 | 'module': components[1],
105 | 'test_suite': components[2],
106 | 'test': components[3].strip(),
107 | 'exercise': exercises[key].strip(),
108 | }
109 | contents_1 = UNITTEST_TEMPLATE_1.format(**template_dict)
110 | contents_2 = UNITTEST_TEMPLATE_2.format(**template_dict)
111 | else:
112 | hints = ':'.join(components[1:]).strip()
113 | template_dict = {
114 | 'num': key[8:],
115 | 'exercise': exercises[key],
116 | 'hints': hints,
117 | }
118 | contents_1 = PRACTICE_TEMPLATE_1.format(**template_dict)
119 | contents_2 = PRACTICE_TEMPLATE_2.format(**template_dict)
120 | cells.append(nbformat.v4.new_markdown_cell(contents_1))
121 | cells.append(nbformat.v4.new_code_cell(contents_2))
122 | elif raw_cell.startswith('example'):
123 | key = raw_cell.strip()
124 | cells.append(nbformat.v4.new_code_cell(examples[key].strip()))
125 | else:
126 | raise RuntimeError
127 | nbformat.write(notebook, '{}/Chapter{}.ipynb'.format(path, chapter))
128 |
129 |
--------------------------------------------------------------------------------
/images/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/cover.png
--------------------------------------------------------------------------------
/images/prbc_0001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0001.png
--------------------------------------------------------------------------------
/images/prbc_0002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0002.png
--------------------------------------------------------------------------------
/images/prbc_0003.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0003.png
--------------------------------------------------------------------------------
/images/prbc_0004.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0004.png
--------------------------------------------------------------------------------
/images/prbc_0101.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0101.png
--------------------------------------------------------------------------------
/images/prbc_0102.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0102.png
--------------------------------------------------------------------------------
/images/prbc_0103.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0103.png
--------------------------------------------------------------------------------
/images/prbc_0201.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0201.png
--------------------------------------------------------------------------------
/images/prbc_0202.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0202.png
--------------------------------------------------------------------------------
/images/prbc_0203.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0203.png
--------------------------------------------------------------------------------
/images/prbc_0204.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0204.png
--------------------------------------------------------------------------------
/images/prbc_0205.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0205.png
--------------------------------------------------------------------------------
/images/prbc_0206.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0206.png
--------------------------------------------------------------------------------
/images/prbc_0207.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0207.png
--------------------------------------------------------------------------------
/images/prbc_0208.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0208.png
--------------------------------------------------------------------------------
/images/prbc_0209.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0209.png
--------------------------------------------------------------------------------
/images/prbc_0210.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0210.png
--------------------------------------------------------------------------------
/images/prbc_0211.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0211.png
--------------------------------------------------------------------------------
/images/prbc_0212.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0212.png
--------------------------------------------------------------------------------
/images/prbc_0213.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0213.png
--------------------------------------------------------------------------------
/images/prbc_0214.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0214.png
--------------------------------------------------------------------------------
/images/prbc_0215.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0215.png
--------------------------------------------------------------------------------
/images/prbc_0216.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0216.png
--------------------------------------------------------------------------------
/images/prbc_0217.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0217.png
--------------------------------------------------------------------------------
/images/prbc_0218.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0218.png
--------------------------------------------------------------------------------
/images/prbc_0219.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0219.png
--------------------------------------------------------------------------------
/images/prbc_0301.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0301.png
--------------------------------------------------------------------------------
/images/prbc_0302.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0302.png
--------------------------------------------------------------------------------
/images/prbc_0303.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0303.png
--------------------------------------------------------------------------------
/images/prbc_0304.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0304.png
--------------------------------------------------------------------------------
/images/prbc_0305.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0305.png
--------------------------------------------------------------------------------
/images/prbc_0306.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0306.png
--------------------------------------------------------------------------------
/images/prbc_0307.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0307.png
--------------------------------------------------------------------------------
/images/prbc_0308.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0308.png
--------------------------------------------------------------------------------
/images/prbc_0309.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0309.png
--------------------------------------------------------------------------------
/images/prbc_0401.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0401.png
--------------------------------------------------------------------------------
/images/prbc_0402.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0402.png
--------------------------------------------------------------------------------
/images/prbc_0403.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0403.png
--------------------------------------------------------------------------------
/images/prbc_0404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0404.png
--------------------------------------------------------------------------------
/images/prbc_0501.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0501.png
--------------------------------------------------------------------------------
/images/prbc_0502.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0502.png
--------------------------------------------------------------------------------
/images/prbc_0503.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0503.png
--------------------------------------------------------------------------------
/images/prbc_0504.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0504.png
--------------------------------------------------------------------------------
/images/prbc_0505.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0505.png
--------------------------------------------------------------------------------
/images/prbc_0506.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0506.png
--------------------------------------------------------------------------------
/images/prbc_0507.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0507.png
--------------------------------------------------------------------------------
/images/prbc_0508.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0508.png
--------------------------------------------------------------------------------
/images/prbc_0601.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0601.png
--------------------------------------------------------------------------------
/images/prbc_0602.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0602.png
--------------------------------------------------------------------------------
/images/prbc_0603.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0603.png
--------------------------------------------------------------------------------
/images/prbc_0604.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0604.png
--------------------------------------------------------------------------------
/images/prbc_0605.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0605.png
--------------------------------------------------------------------------------
/images/prbc_0606.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0606.png
--------------------------------------------------------------------------------
/images/prbc_0607.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0607.png
--------------------------------------------------------------------------------
/images/prbc_0608.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0608.png
--------------------------------------------------------------------------------
/images/prbc_0609.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0609.png
--------------------------------------------------------------------------------
/images/prbc_0610.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0610.png
--------------------------------------------------------------------------------
/images/prbc_0611.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0611.png
--------------------------------------------------------------------------------
/images/prbc_0612.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0612.png
--------------------------------------------------------------------------------
/images/prbc_0613.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0613.png
--------------------------------------------------------------------------------
/images/prbc_0614.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0614.png
--------------------------------------------------------------------------------
/images/prbc_0615.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0615.png
--------------------------------------------------------------------------------
/images/prbc_0616.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0616.png
--------------------------------------------------------------------------------
/images/prbc_0617.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0617.png
--------------------------------------------------------------------------------
/images/prbc_0618.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0618.png
--------------------------------------------------------------------------------
/images/prbc_0619.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0619.png
--------------------------------------------------------------------------------
/images/prbc_0620.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0620.png
--------------------------------------------------------------------------------
/images/prbc_0621.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0621.png
--------------------------------------------------------------------------------
/images/prbc_0622.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0622.png
--------------------------------------------------------------------------------
/images/prbc_0623.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0623.png
--------------------------------------------------------------------------------
/images/prbc_0624.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0624.png
--------------------------------------------------------------------------------
/images/prbc_0625.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0625.png
--------------------------------------------------------------------------------
/images/prbc_0626.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0626.png
--------------------------------------------------------------------------------
/images/prbc_0627.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0627.png
--------------------------------------------------------------------------------
/images/prbc_0628.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0628.png
--------------------------------------------------------------------------------
/images/prbc_0629.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0629.png
--------------------------------------------------------------------------------
/images/prbc_0630.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0630.png
--------------------------------------------------------------------------------
/images/prbc_0631.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0631.png
--------------------------------------------------------------------------------
/images/prbc_0632.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0632.png
--------------------------------------------------------------------------------
/images/prbc_0633.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0633.png
--------------------------------------------------------------------------------
/images/prbc_0701.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0701.png
--------------------------------------------------------------------------------
/images/prbc_0702.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0702.png
--------------------------------------------------------------------------------
/images/prbc_0703.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0703.png
--------------------------------------------------------------------------------
/images/prbc_0704.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0704.png
--------------------------------------------------------------------------------
/images/prbc_0705.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0705.png
--------------------------------------------------------------------------------
/images/prbc_0706.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0706.png
--------------------------------------------------------------------------------
/images/prbc_0801.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0801.png
--------------------------------------------------------------------------------
/images/prbc_0802.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0802.png
--------------------------------------------------------------------------------
/images/prbc_0803.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0803.png
--------------------------------------------------------------------------------
/images/prbc_0804.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0804.png
--------------------------------------------------------------------------------
/images/prbc_0805.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0805.png
--------------------------------------------------------------------------------
/images/prbc_0806.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0806.png
--------------------------------------------------------------------------------
/images/prbc_0807.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0807.png
--------------------------------------------------------------------------------
/images/prbc_0808.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0808.png
--------------------------------------------------------------------------------
/images/prbc_0809.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0809.png
--------------------------------------------------------------------------------
/images/prbc_0810.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0810.png
--------------------------------------------------------------------------------
/images/prbc_0811.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0811.png
--------------------------------------------------------------------------------
/images/prbc_0812.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0812.png
--------------------------------------------------------------------------------
/images/prbc_0813.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0813.png
--------------------------------------------------------------------------------
/images/prbc_0814.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0814.png
--------------------------------------------------------------------------------
/images/prbc_0815.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0815.png
--------------------------------------------------------------------------------
/images/prbc_0816.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0816.png
--------------------------------------------------------------------------------
/images/prbc_0817.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0817.png
--------------------------------------------------------------------------------
/images/prbc_0818.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0818.png
--------------------------------------------------------------------------------
/images/prbc_0819.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0819.png
--------------------------------------------------------------------------------
/images/prbc_0820.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0820.png
--------------------------------------------------------------------------------
/images/prbc_0821.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0821.png
--------------------------------------------------------------------------------
/images/prbc_0822.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0822.png
--------------------------------------------------------------------------------
/images/prbc_0823.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0823.png
--------------------------------------------------------------------------------
/images/prbc_0824.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0824.png
--------------------------------------------------------------------------------
/images/prbc_0825.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0825.png
--------------------------------------------------------------------------------
/images/prbc_0826.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0826.png
--------------------------------------------------------------------------------
/images/prbc_0827.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0827.png
--------------------------------------------------------------------------------
/images/prbc_0828.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0828.png
--------------------------------------------------------------------------------
/images/prbc_0829.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0829.png
--------------------------------------------------------------------------------
/images/prbc_0901.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0901.png
--------------------------------------------------------------------------------
/images/prbc_0902.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_0902.png
--------------------------------------------------------------------------------
/images/prbc_1001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1001.png
--------------------------------------------------------------------------------
/images/prbc_1002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1002.png
--------------------------------------------------------------------------------
/images/prbc_1003.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1003.png
--------------------------------------------------------------------------------
/images/prbc_1004.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1004.png
--------------------------------------------------------------------------------
/images/prbc_1101.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1101.png
--------------------------------------------------------------------------------
/images/prbc_1102.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1102.png
--------------------------------------------------------------------------------
/images/prbc_1103.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1103.png
--------------------------------------------------------------------------------
/images/prbc_1104.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1104.png
--------------------------------------------------------------------------------
/images/prbc_1105.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1105.png
--------------------------------------------------------------------------------
/images/prbc_1106.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1106.png
--------------------------------------------------------------------------------
/images/prbc_1107.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1107.png
--------------------------------------------------------------------------------
/images/prbc_1201.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1201.png
--------------------------------------------------------------------------------
/images/prbc_1202.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1202.png
--------------------------------------------------------------------------------
/images/prbc_1203.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1203.png
--------------------------------------------------------------------------------
/images/prbc_1204.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1204.png
--------------------------------------------------------------------------------
/images/prbc_1205.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1205.png
--------------------------------------------------------------------------------
/images/prbc_1301.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1301.png
--------------------------------------------------------------------------------
/images/prbc_1302.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1302.png
--------------------------------------------------------------------------------
/images/prbc_1303.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1303.png
--------------------------------------------------------------------------------
/images/prbc_1304.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1304.png
--------------------------------------------------------------------------------
/images/prbc_1305.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1305.png
--------------------------------------------------------------------------------
/images/prbc_1306.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1306.png
--------------------------------------------------------------------------------
/images/prbc_1307.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1307.png
--------------------------------------------------------------------------------
/images/prbc_1308.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1308.png
--------------------------------------------------------------------------------
/images/prbc_1309.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1309.png
--------------------------------------------------------------------------------
/images/prbc_1310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1310.png
--------------------------------------------------------------------------------
/images/prbc_1311.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1311.png
--------------------------------------------------------------------------------
/images/prbc_1312.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1312.png
--------------------------------------------------------------------------------
/images/prbc_1313.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1313.png
--------------------------------------------------------------------------------
/images/prbc_1314.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1314.png
--------------------------------------------------------------------------------
/images/prbc_1315.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1315.png
--------------------------------------------------------------------------------
/images/prbc_1316.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1316.png
--------------------------------------------------------------------------------
/images/prbc_1317.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1317.png
--------------------------------------------------------------------------------
/images/prbc_1318.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1318.png
--------------------------------------------------------------------------------
/images/prbc_1319.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1319.png
--------------------------------------------------------------------------------
/images/prbc_1320.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1320.png
--------------------------------------------------------------------------------
/images/prbc_1321.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1321.png
--------------------------------------------------------------------------------
/images/prbc_1322.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1322.png
--------------------------------------------------------------------------------
/images/prbc_1323.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1323.png
--------------------------------------------------------------------------------
/images/prbc_1324.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1324.png
--------------------------------------------------------------------------------
/images/prbc_1325.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1325.png
--------------------------------------------------------------------------------
/images/prbc_1326.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1326.png
--------------------------------------------------------------------------------
/images/prbc_1327.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1327.png
--------------------------------------------------------------------------------
/images/prbc_1328.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1328.png
--------------------------------------------------------------------------------
/images/prbc_1329.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1329.png
--------------------------------------------------------------------------------
/images/prbc_1330.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1330.png
--------------------------------------------------------------------------------
/images/prbc_1331.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1331.png
--------------------------------------------------------------------------------
/images/prbc_1332.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1332.png
--------------------------------------------------------------------------------
/images/prbc_1333.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1333.png
--------------------------------------------------------------------------------
/images/prbc_1334.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1334.png
--------------------------------------------------------------------------------
/images/prbc_1335.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1335.png
--------------------------------------------------------------------------------
/images/prbc_1336.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1336.png
--------------------------------------------------------------------------------
/images/prbc_1337.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1337.png
--------------------------------------------------------------------------------
/images/prbc_1338.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1338.png
--------------------------------------------------------------------------------
/images/prbc_1339.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1339.png
--------------------------------------------------------------------------------
/images/prbc_1340.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1340.png
--------------------------------------------------------------------------------
/images/prbc_1341.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1341.png
--------------------------------------------------------------------------------
/images/prbc_1342.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1342.png
--------------------------------------------------------------------------------
/images/prbc_1343.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1343.png
--------------------------------------------------------------------------------
/images/prbc_1344.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1344.png
--------------------------------------------------------------------------------
/images/prbc_1345.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jimmysong/programmingbitcoin/3fba6b992ece443e4256df057595cfbe91edda75/images/prbc_1345.png
--------------------------------------------------------------------------------
/ix.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | jupyter
2 | requests
3 |
--------------------------------------------------------------------------------
/sample_chapter.asciidoc:
--------------------------------------------------------------------------------
1 | [[unique_chapter_id]]
2 | == Chapter Title
3 |
4 | Chapter text begins here. Replace any of this placeholder text with your opus. The following portion outlines heading levels and section structure.
5 |
6 | === This Is a Top-Level Heading (A-Head)
7 |
8 | Within a chapter, the first and highest heading level uses three equals signs.
9 |
10 | ==== This Is a Second-Level Heading (B-Head)
11 |
12 | The second-level heading uses four equals signs. This heading level should only follow a top-level heading (A-head).
13 |
14 | ===== This Is a Third-Level Heading (C-Head)
15 |
16 | The third-level heading uses five equals signs. This heading level should only follow a second-level heading (B-head).
17 |
18 | Next we've included a few examples of commonly used block elements. You can add these elements using the buttons in the toolbar, as well.
19 |
20 | .This Is a Note
21 | [NOTE]
22 | ====
23 | Many people use notes to qualify a statement they made in the preceding paragraphs, or to warn their readers about pitfalls they might run into.
24 | ====
25 |
26 | [WARNING]
27 | ====
28 | This is a warning, used to alert readers to something important or encourage caution. Headings are optional for admonitions like notes and warnings.
29 | ====
30 |
31 | Here is an informal code listing:
32 |
33 | [source,python]
34 | ----
35 | print('hello world')
36 | ----
37 |
38 | And this is a formal listing, or example:
39 |
40 | [[EX1]]
41 | .Hello World in Python
42 | ====
43 | [source,python]
44 | ----
45 | print "Hello World"
46 | # Formal listings have titles that will be numbered in output.
47 | ----
48 | ====
49 |
50 | For either type of listing, you have the option of specifying the code language displayed; see http://docs.atlas.oreilly.com/ch12.html#asciidocref for more details.
51 |
52 | You can also specify inline text as code: +print "Hello World"+.
53 |
54 | Now, let's take a look at a figure with a caption:
55 |
56 | .Figures like this will be automatically numbered in output.
57 | image::images/images/tarsier.png["Drawing of Tarsiers"]
58 |
59 | Here is a blockquote with an author attribution:
60 |
61 | [quote, Lewis Carol]
62 | ____
63 | Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, 'and what is the use of a book,' thought Alice 'without pictures or conversation?'
64 | ____
65 |
66 | There are three types of lists available. Numbered (ordered) lists are often used to describe steps in a process:
67 |
68 | . Parse the request.
69 | . Choose a handler function.
70 | . Run the handler function.
71 |
72 | Bulleted (unordered) lists are good for describing a set of requirements:
73 |
74 | * HTML source
75 | * CSS stylesheets
76 | * JavaScript code
77 |
78 | Definition/variable lists serve as glossaries of terminology:
79 |
80 | selectSource::
81 | Returns a Source containing all the IDs and values from the database. This allows you to write streaming code.
82 | selectList::
83 | Returns a list containing all the IDs and values from the database. All records will be loaded into memory.
84 | selectFirst::
85 | Takes just the first ID and value from the database, if available.
86 | selectKeys::
87 | Returns only the keys, without the values, as a +Source+.
88 |
89 | .Sidebar Title
90 | ****
91 | Here's a sidebar. Sidebars are great for setting aside a section of text that is related to the surrounding content but that doesn't necessarily fit into the main flow.
92 | ****
93 |
94 | Finally, here's a sample table:
95 |
96 | .Features supported by Hadoop release series
97 | [options="header"]
98 | |=======
99 | |Feature|1.x|0.22|2.x
100 | |Secure authentication|Yes|No|Yes
101 | |Old configuration names|Yes|Deprecated|Deprecated
102 | |New configuration names|No|Yes|Yes
103 | |Old MapReduce API|Yes|Yes|Yes
104 | |New MapReduce API|Yes (with some missing libraries)|Yes|Yes
105 | |=======
106 |
107 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [flake8]
2 | ignore=E501,E125
--------------------------------------------------------------------------------
/test.py:
--------------------------------------------------------------------------------
1 | from os import chdir
2 | from subprocess import call
3 |
4 |
5 | for chapter in range(1, 14):
6 | if chapter < 10:
7 | ch = '0{}'.format(chapter)
8 | else:
9 | ch = '{}'.format(chapter)
10 | chdir('code-ch{}'.format(ch))
11 | call('nosetests --with-doctest *.py', shell=True)
12 | chdir('..')
13 |
--------------------------------------------------------------------------------
/theme/epub/epub.css:
--------------------------------------------------------------------------------
1 | /* Fix pre spacing in lists (STYL-1162) */
2 | li pre { line-height: 125% !important; }
3 |
--------------------------------------------------------------------------------
/theme/epub/layout.html:
--------------------------------------------------------------------------------
1 | {{ doctype }}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ title }}
11 |
12 |
13 | {{ content }}
14 |
15 |
16 |
--------------------------------------------------------------------------------
/theme/html/html.css:
--------------------------------------------------------------------------------
1 | /* Add your custom CSS styles for the HTML here */
--------------------------------------------------------------------------------
/theme/mobi/layout.html:
--------------------------------------------------------------------------------
1 | {{ doctype }}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ title }}
11 |
12 |
13 | {{ content }}
14 |
15 |
16 |
--------------------------------------------------------------------------------
/theme/mobi/mobi.css:
--------------------------------------------------------------------------------
1 | /* Add your custom CSS styles for the MOBI here */
--------------------------------------------------------------------------------
/theme/pdf/pdf.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 |
3 | /*--------Put Your Custom CSS Rules Below--------*/
4 | /*--- This oneoff overrides the code in https://github.com/oreillymedia/animal_theme/blob/master/pdf/pdf.css---*/
5 |
6 | /* for definition lists, roman term*/
7 | .dt_plain {
8 | font-style: normal;
9 | }
10 |
11 | /*sidebar less space in pagebreaks */
12 | .less_space {margin-top: 0 !important;}
13 |
14 | /*----Uncomment to turn on automatic code wrapping
15 |
16 | pre {
17 | white-space: pre-wrap;
18 | word-wrap: break-word;
19 | }
20 | ----*/
21 |
22 | /*----Uncomment to change the TOC start page (set
23 | the number to one page _after_ the one you want;
24 | so 6 to start on v, 8 to start on vii, etc.)
25 |
26 | @page toc:first {
27 | counter-reset: page 6;
28 | }
29 | ----*/
30 |
31 | /*----Uncomment to fix a bad break in the title
32 | (increase padding value to push down, decrease
33 | value to pull up)
34 |
35 | section[data-type="titlepage"] h1 {
36 | padding-left: 1.5in;
37 | }
38 | ----*/
39 |
40 | /*----Uncomment to fix a bad break in the subtitle
41 | (increase padding value to push down, decrease
42 | value to pull up)
43 |
44 | section[data-type="titlepage"] h2 {
45 | padding-left: 1in;
46 | }
47 | ----*/
48 |
49 | /*----Uncomment to fix a bad break in the author names
50 | (increase padding value to push down, decrease
51 | value to pull up)
52 |
53 | section[data-type="titlepage"] p.author {
54 | padding-left: 3in;
55 | }
56 | ----*/
57 |
--------------------------------------------------------------------------------
/theme/pdf/pdf.xsl:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/titlepage.html:
--------------------------------------------------------------------------------
1 |
2 |
Programming Bitcoin
3 |
4 |
Learn How to Program Bitcoin from Scratch
5 |
Jimmy Song
6 |
7 |
8 |
10 |
--------------------------------------------------------------------------------
/toc.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/tools/intakereport.txt:
--------------------------------------------------------------------------------
1 | Title: Programming Bitcoin
2 | ISBN: 9781492031499
3 | JIRA #: DCPSPROD-2866
4 |
5 |
6 | Stylesheet: animal_theme_sass
7 | Toolchain: Atlas 2
8 |
9 | Atlas URL: https://atlas.oreilly.com/oreillymedia/programming-bitcoin/
10 | Incoming format: Asciidoc
11 | Outgoing format: Asciidoc
12 |
13 | Preliminary pagecount: 346 with figs
14 |
15 | Is this project in Early Release? NO
16 |
17 | Resources
18 | =========
19 | ** There are 178 illustrations in this book.
20 |
21 | Once the figs are processed on /work, you'll need to add them to the book's repo.
22 |
23 | A report mapping original figure file names to their new names can be found in the tools folder for this project as figure_renaming_report.tsv.
24 |
25 |
26 | ** Intake Report:
27 | (Git repo) tools/intakereport.txt
28 |
29 | ** MS Snapshot:
30 | To view the submitted files, you'll need to checkout the git tag named 'manuscript_to_prod'
31 | by running the following command in your checkout:
32 |
33 | $ git checkout manuscript_to_prod
34 |
35 | This will temporarily switch the files in your repo to the state they were in when the manuscript_to_prod tag
36 | was created.
37 | To switch the files back to the current state, run:
38 |
39 | $ git checkout master
40 |
41 |
42 | Notes from Tools:
43 | =================
44 |
45 | ** PROD: Add any authors to project that need to be added.
46 | ** Syntax highlighting: applied to 228 out of 240 code listings.
47 |
48 | ** Every single figure xref was hardcoded in the source files. I added ids and xref markup, but I would encourage Prod Ed/Copyeditor to make sure the proper figures are linked.
49 |
50 | ** Please let Tools know ASAP if there are any other global problems for which we can help automate a fix.
51 |
52 | Notes at arrival in production:
53 | ==================
54 |
55 | PE: Kristen
56 | Source files: https://atlas.oreilly.com/oreillymedia/programming-bitcoin
57 | Pagecount: 330
58 | Incoming format: AsciiDoc
59 |
60 | Notes:
61 | * Ch 5 has width attribute errors [Nick: FIXED. This was caused by commas used in the figure alt text]
62 | * Ch 10 has href value errors [Nick: NOT FIXED. This is caused by missing callouts in the code block on line 267 of Chapter 10. Author will need to add callouts 4 and 5 to the code.
63 | * Is it OK to have subscript in headers? It's showing up OK in the epub, but wanted to check if this was a big no-no [Nick: This does not cause any epubcheck problems, so I did not make any changes.]
64 | * This book is on an abbreviated schedule, so a faster intake would be appreciated!
65 |
66 | ==================
67 |
68 | Please let me know about any other issues.
69 |
70 | Thanks,
71 | Nick
72 |
73 |
--------------------------------------------------------------------------------