diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/dbnavigator.xml b/.idea/dbnavigator.xml new file mode 100644 index 00000000..a110605c --- /dev/null +++ b/.idea/dbnavigator.xml @@ -0,0 +1,457 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..0b59a6af --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..105ce2da --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..184fcba9 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..97742c21 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/programmingbitcoin.iml b/.idea/programmingbitcoin.iml new file mode 100644 index 00000000..8e5446ac --- /dev/null +++ b/.idea/programmingbitcoin.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/code-ch01/Chapter1.ipynb b/code-ch01/Chapter1.ipynb index f8055f46..3f4e7a5a 100644 --- a/code-ch01/Chapter1.ipynb +++ b/code-ch01/Chapter1.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 45, "metadata": {}, "outputs": [], "source": [ @@ -19,9 +19,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 46, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "False\n", + "True\n" + ] + } + ], "source": [ "from ecc import FieldElement\n", "a = FieldElement(7, 13)\n", @@ -43,30 +52,56 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 47, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ".\n", + "----------------------------------------------------------------------\n", + "Ran 1 test in 0.001s\n", + "\n", + "OK\n" + ] + } + ], "source": [ "# Exercise 1\n", - "\n", - "reload(ecc)\n", "run(ecc.FieldElementTest(\"test_ne\"))" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 48, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n" + ] + } + ], "source": [ "print(7 % 3)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 49, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "12\n" + ] + } + ], "source": [ "print(-27 % 13)" ] @@ -87,25 +122,49 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 50, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "FieldElement_57(20)\n" + ] + } + ], "source": [ "# Exercise 2\n", "\n", "# remember that % is the modulo operator\n", "prime = 57\n", "# 44+33\n", + "a = ecc.FieldElement(44, 57)\n", + "b = ecc.FieldElement(33, 57)\n", + "print(a.__add__(b))\n", "# 9-29\n", "# 17+42+49\n", "# 52-30-38" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 51, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], "source": [ "from ecc import FieldElement\n", "a = FieldElement(7, 13)\n", @@ -127,9 +186,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 52, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ".\n", + "----------------------------------------------------------------------\n", + "Ran 1 test in 0.002s\n", + "\n", + "OK\n" + ] + } + ], "source": [ "# Exercise 3\n", "\n", @@ -152,13 +223,33 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 67, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ".\n", + "----------------------------------------------------------------------\n", + "Ran 1 test in 0.002s\n", + "\n", + "OK\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final is FieldElement_31(22)\n" + ] + } + ], "source": [ "# Exercise 4\n", "\n", - "prime = 97\n", + "# prime = 97\n", + "run(ecc.FieldElementTest(\"test_mul\"))\n", "\n", "# 95*45*31\n", "# 17*13*19*44\n", @@ -180,9 +271,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 81, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]\n", + "[0, 3, 6, 9, 12, 15, 18, 2, 5, 8, 11, 14, 17, 1, 4, 7, 10, 13, 16]\n", + "[0, 7, 14, 2, 9, 16, 4, 11, 18, 6, 13, 1, 8, 15, 3, 10, 17, 5, 12]\n", + "[0, 13, 7, 1, 14, 8, 2, 15, 9, 3, 16, 10, 4, 17, 11, 5, 18, 12, 6]\n", + "[0, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]\n" + ] + } + ], "source": [ "# Exercise 5\n", "\n", @@ -190,15 +293,25 @@ "k = 1 # 3, 7, 13 and 18 are the other possibilities\n", "# loop through all possible k's 0 up to prime-1\n", "# calculate k*iterator % prime\n", - "\n", + "for k in (1, 3, 7, 13, 18):\n", + " print([k * i % prime for i in range(prime)])\n", "# Hint - sort!" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 65, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final is FieldElement_13(10)\n", + "True\n" + ] + } + ], "source": [ "from ecc import FieldElement\n", "a = FieldElement(3, 13)\n", @@ -220,9 +333,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 63, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ".\n", + "----------------------------------------------------------------------\n", + "Ran 1 test in 0.001s\n", + "\n", + "OK\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final is FieldElement_31(22)\n" + ] + } + ], "source": [ "# Exercise 6\n", "\n", @@ -232,9 +364,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 64, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], "source": [ "from ecc import FieldElement\n", "a = FieldElement(3, 13)\n", @@ -255,13 +395,27 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 84, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 1, 1, 1, 1, 1]\n", + "[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]\n", + "[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]\n", + "[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]\n" + ] + } + ], "source": [ "# Exercise 7\n", "\n", - "primes = [7, 11, 17, 31, 43]" + "primes = [7, 11, 17, 31, 43]\n", + "\n", + "for prime in (7, 11, 17, 31):\n", + " print([pow(i, prime - 1, prime) for i in range(1, prime)])\n" ] }, { @@ -279,15 +433,30 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 85, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4\n", + "29\n", + "13\n" + ] + } + ], "source": [ "# Exercise 8\n", "\n", "# 3/24\n", "# 17**-3\n", - "# 4**-4*11" + "# 4**-4*11\n", + "prime = 31\n", + "print(3 * pow(24, prime - 2, prime) % prime)\n", + "print(pow(17, prime - 4, prime))\n", + "print(pow(4, prime - 5, prime) * 11 % prime)\n", + "\n" ] }, { @@ -305,9 +474,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 80, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ".\n", + "----------------------------------------------------------------------\n", + "Ran 1 test in 0.001s\n", + "\n", + "OK\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final is FieldElement_31(13)\n" + ] + } + ], "source": [ "# Exercise 9\n", "\n", @@ -317,9 +505,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 61, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], "source": [ "from ecc import FieldElement\n", "a = FieldElement(7, 13)\n", @@ -328,7 +524,28 @@ ] } ], - "metadata": {}, + "metadata": { + "interpreter": { + "hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49" + }, + "kernelspec": { + "display_name": "Python 3.10.2 64-bit", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.2" + } + }, "nbformat": 4, "nbformat_minor": 2 } diff --git a/code-ch01/ecc.py b/code-ch01/ecc.py index b5bf616e..174ea298 100644 --- a/code-ch01/ecc.py +++ b/code-ch01/ecc.py @@ -22,8 +22,9 @@ def __eq__(self, other): # end::source1[] def __ne__(self, other): - # this should be the inverse of the == operator - raise NotImplementedError + if other is None: + return True + return self.num != other.num or self.prime != other.prime # tag::source2[] def __add__(self, other): @@ -36,18 +37,20 @@ def __add__(self, other): def __sub__(self, other): if self.prime != other.prime: raise TypeError('Cannot subtract two numbers in different Fields') - # self.num and other.num are the actual values - # self.prime is what we need to mod against - # We return an element of the same class - raise NotImplementedError + num = (self.num - other.num) % self.prime + return self.__class__(num, self.prime) def __mul__(self, other): if self.prime != other.prime: raise TypeError('Cannot multiply two numbers in different Fields') - # self.num and other.num are the actual values - # self.prime is what we need to mod against - # We return an element of the same class - raise NotImplementedError + original = self.num + counter = 1 + while counter < other.num: + original = original + self.num + counter+=1 + final = original % self.prime + print(f'final is {self.__class__(final, self.prime)}') + return self.__class__(final, self.prime) # tag::source3[] def __pow__(self, exponent): @@ -59,12 +62,8 @@ def __pow__(self, exponent): def __truediv__(self, other): if self.prime != other.prime: raise TypeError('Cannot divide two numbers in different Fields') - # use fermat's little theorem: - # self.num**(p-1) % p == 1 - # this means: - # 1/n == pow(n, p-2, p) - # We return an element of the same class - raise NotImplementedError + num = self.num * pow(other.num, self.prime - 2, self.prime) % self.prime + return self.__class__(num, self.prime) class FieldElementTest(TestCase): diff --git a/code-ch03/Chapter3.ipynb b/code-ch03/Chapter3.ipynb index bf0ffe20..75c1628c 100644 --- a/code-ch03/Chapter3.ipynb +++ b/code-ch03/Chapter3.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -30,24 +30,64 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "False\n", + "False\n", + "False\n", + "False\n", + "False\n" + ] + } + ], "source": [ "# Exercise 1\n", + "is_on_curve = lambda y, x: y**2 == x**3 + (a*x) + b\n", "\n", "prime = 223\n", - "a = FieldElement(0, prime)\n", - "b = FieldElement(7, prime)\n", + "\n", + "a = FieldElement(192, prime)\n", + "b = FieldElement(105, prime)\n", + "print(is_on_curve(a, b))\n", + "\n", + "a = FieldElement(17, prime)\n", + "b = FieldElement(56, prime)\n", + "print(is_on_curve(a, b))\n", + "\n", + "\n", + "a = FieldElement(200, prime)\n", + "b = FieldElement(119, prime)\n", + "print(is_on_curve(a, b))\n", + "\n", + "a = FieldElement(1, prime)\n", + "b = FieldElement(193, prime)\n", + "print(is_on_curve(a, b))\n", + "\n", + "a = FieldElement(42, prime)\n", + "b = FieldElement(99, prime)\n", + "print(is_on_curve(a, b))\n", "\n", "# (192,105), (17,56), (200,119), (1,193), (42,99)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Point(192,105)_0_7 FieldElement(223)\n" + ] + } + ], "source": [ "from ecc import FieldElement, Point\n", "a = FieldElement(num=0, prime=223)\n", @@ -396,7 +436,28 @@ ] } ], - "metadata": {}, + "metadata": { + "interpreter": { + "hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49" + }, + "kernelspec": { + "display_name": "Python 3.10.2 64-bit", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.2" + } + }, "nbformat": 4, "nbformat_minor": 2 } diff --git a/code-ch04/Chapter4.ipynb b/code-ch04/Chapter4.ipynb index 68289b9f..8c8d3dfa 100644 --- a/code-ch04/Chapter4.ipynb +++ b/code-ch04/Chapter4.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -30,16 +30,29 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "04ffe558e388852f0120e46af2d1b370f85854a8eb0841811ece0e3e03d282d57c315dc72890a4f10a1481c031b03b351b0dc79901ca18a00cf009dbdb157a1d10\n", + "04027f3da1918455e03c46f659266a1bb5204e959db7364d2f473bdf8f0a13cc9dff87647fd023c13b4a4994f17691895806e1b40b57f4fd22581a4f46851f3b06\n" + ] + } + ], "source": [ "# Exercise 1\n", "\n", "from ecc import PrivateKey\n", - "\n", "# 5000\n", + "private_key = PrivateKey(5000)\n", + "print(private_key.point.sec(compressed=False).hex())\n", "# 2018**5\n", + "private_key = PrivateKey(2018**5)\n", + "print(private_key.point.sec(compressed=False).hex())\n", + "\n", "# 0xdeadbeef12345\n", "# privatekey.point is the public key for a private key" ] @@ -59,16 +72,29 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0357a4f368868a8a6d572991e484e664810ff14c05c0fa023275251151fe0e53d1\n", + "02933ec2d2b111b92737ec12f1c5d20f3233a0ad21cd8b36d0bca7a0cfa5cb8701\n" + ] + } + ], "source": [ "# Exercise 2\n", "\n", "from ecc import PrivateKey\n", "\n", "# 5001\n", + "private_key = PrivateKey(5001)\n", + "print(private_key.point.sec(compressed=True).hex())\n", "# 2019**5\n", + "private_key = PrivateKey(2019**5)\n", + "print(private_key.point.sec(compressed=True).hex())\n", "# 0xdeadbeef54321" ] }, @@ -91,16 +117,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3045022037206a0610995c58074999cb9767b87af4c4978db68c06e8e6e81d282047a7c60221008ca63759c1157ebeaec0d03cecca119fc9a75bf8e6d0fa65c841c8e2738cdaec\n" + ] + } + ], "source": [ "# Exercise 3\n", "\n", "from ecc import Signature\n", "\n", "r = 0x37206a0610995c58074999cb9767b87af4c4978db68c06e8e6e81d282047a7c6\n", - "s = 0x8ca63759c1157ebeaec0d03cecca119fc9a75bf8e6d0fa65c841c8e2738cdaec" + "s = 0x8ca63759c1157ebeaec0d03cecca119fc9a75bf8e6d0fa65c841c8e2738cdaec\n", + "print(Signature(r,s).der().hex())" ] }, { @@ -118,17 +153,30 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "9MA8fRQrT4u8Zj8ZRd6MAiiyaxb2Y1CMpvVkHQu5hVM6\n", + "4fE3H2E6XMp4SsxtwinF7w9a34ooUrwWe4WsW1458Pd\n", + "EQJsjkd6JaGwxrjEhfeqPenqHwrBmPQZjJGNSCHBkcF7\n" + ] + } + ], "source": [ "# Exercise 4\n", "\n", "from helper import encode_base58\n", "\n", "# 7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d\n", - "# eff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c\n", - "# c7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6" + "print(encode_base58(bytes.fromhex('7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d')))\n", + "#eff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c\n", + "print(encode_base58(bytes.fromhex('eff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c')))\n", + "# c7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6\n", + "print(encode_base58(bytes.fromhex('c7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab6')))\n" ] }, { @@ -146,17 +194,31 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "mmTPbXQFxboEtNRkwfh6K51jvdtHLxGeMA\n", + "muC5h84joNRi2Aiw1FzvS9RkPEXGwaoxGE\n", + "mg2MoJnGVV7JKFEawp6zKrKL6gDsoXJJRA\n" + ] + } + ], "source": [ "# Exercise 5\n", "\n", "from ecc import PrivateKey\n", "\n", "# 5002 (use uncompressed SEC, on testnet)\n", + "print(PrivateKey(5002).point.address(compressed=False, testnet=True))\n", "# 2020**5 (use compressed SEC, on testnet)\n", - "# 0x12345deadbeef (use compressed SEC on mainnet)" + "print(PrivateKey(2020**5).point.address(compressed=False, testnet=True))\n", + "# 0x12345deadbeef (use compressed SEC on mainnet)\n", + "print(PrivateKey(0x12345deadbeef).point.address(compressed=False, testnet=True))\n", + "\n" ] }, { @@ -174,13 +236,26 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN8rFTv2sfUK\n", + "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjpWAxgzczjbCwxic\n", + "KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgiuQJv1h8Ytr2S53a\n" + ] + } + ], "source": [ "# Exercise 6\n", "\n", "from ecc import PrivateKey\n", + "print(PrivateKey(5003).wif(compressed=True, testnet=True))\n", + "print(PrivateKey(2021**5).wif(compressed=False, testnet=True))\n", + "print(PrivateKey(0x54321deadbeef).wif(compressed=True, testnet=False))\n", "\n", "# 5003\n", "# 2021**5\n", @@ -200,9 +275,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ".\n", + "----------------------------------------------------------------------\n", + "Ran 1 test in 0.001s\n", + "\n", + "OK\n" + ] + } + ], "source": [ "# Exercise 7\n", "\n", @@ -223,9 +310,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ".\n", + "----------------------------------------------------------------------\n", + "Ran 1 test in 0.001s\n", + "\n", + "OK\n" + ] + } + ], "source": [ "# Exercise 8\n", "\n", @@ -244,9 +343,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "secret: 70828496956310515876653719000055877884684432812736799231962040068791188138200\n", + "private key: cSq6WBCbDw6ygiRendBbrpa7gq4Fphi4W9Bp1CT51g35NVvsooc8\n" + ] + } + ], "source": [ "# Exercise 9\n", "\n", @@ -254,14 +362,38 @@ "from helper import hash256, little_endian_to_int\n", "\n", "# select a passphrase here, add your email address into the passphrase for security\n", - "# passphrase = b'your@email.address some secret only you know'\n", - "# secret = little_endian_to_int(hash256(passphrase))\n", + "passphrase = b'email.address some secret only you know'\n", + "secret = little_endian_to_int(hash256(passphrase))\n", + "print(f'secret: {secret}')\n", "# create a private key using your secret\n", - "# print an address from the public point of the private key with testnet=True" + "private_key = PrivateKey(secret).wif(compressed=True, testnet=True)\n", + "# print an address from the public point of the private key with testnet=True\n", + "print(f'private key: {private_key}')" ] } ], - "metadata": {}, + "metadata": { + "interpreter": { + "hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49" + }, + "kernelspec": { + "display_name": "Python 3.10.2 64-bit", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.2" + } + }, "nbformat": 4, "nbformat_minor": 2 } diff --git a/code-ch04/helper.py b/code-ch04/helper.py index 56e1d53e..deb2de10 100644 --- a/code-ch04/helper.py +++ b/code-ch04/helper.py @@ -63,17 +63,12 @@ def decode_base58(s): def little_endian_to_int(b): - '''little_endian_to_int takes byte sequence as a little-endian number. - Returns an integer''' - # use int.from_bytes() - raise NotImplementedError + return int.from_bytes(b, 'little') def int_to_little_endian(n, length): - '''endian_to_little_endian takes an integer and returns the little-endian - byte sequence of length''' - # use n.to_bytes() - raise NotImplementedError + + return n.to_bytes(length, 'little') class HelperTest(TestCase): diff --git a/code-ch05/Chapter5.ipynb b/code-ch05/Chapter5.ipynb index 16f1aaf1..a2ebe063 100644 --- a/code-ch05/Chapter5.ipynb +++ b/code-ch05/Chapter5.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -11,10 +11,7 @@ "# import everything and define a test runner function\n", "from importlib import reload\n", "from helper import run\n", - "import ecc\n", - "import helper\n", - "import script\n", - "import tx" + "import ecc\n" ] }, { @@ -30,21 +27,41 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ".\n", + "----------------------------------------------------------------------\n", + "Ran 1 test in 0.005s\n", + "\n", + "OK\n" + ] + } + ], "source": [ "# Exercise 1\n", - "\n", - "reload(tx)\n", + "import tx\n", + "# reload(tx)\n", "run(tx.TxTest(\"test_parse_version\"))" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01 0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278a\n" + ] + } + ], "source": [ "from io import BytesIO\n", "from script import Script\n", @@ -67,9 +84,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ".\n", + "----------------------------------------------------------------------\n", + "Ran 1 test in 0.003s\n", + "\n", + "OK\n" + ] + } + ], "source": [ "# Exercise 2\n", "\n", @@ -90,9 +119,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ".\n", + "----------------------------------------------------------------------\n", + "Ran 1 test in 0.003s\n", + "\n", + "OK\n" + ] + } + ], "source": [ "# Exercise 3\n", "\n", @@ -100,6 +141,11 @@ "run(tx.TxTest(\"test_parse_outputs\"))" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, { "cell_type": "markdown", "metadata": {}, @@ -187,9 +233,35 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "E\n", + "======================================================================\n", + "ERROR: test_fee (tx.TxTest)\n", + "----------------------------------------------------------------------\n", + "Traceback (most recent call last):\n", + " File \"/Users/laurenceadams/programming/programmingbitcoin/code-ch05/tx.py\", line 290, in test_fee\n", + " self.assertEqual(tx.fee(), 40000)\n", + " File \"/Users/laurenceadams/programming/programmingbitcoin/code-ch05/tx.py\", line 138, in fee\n", + " tx_in_value += tx_in.value(testnet=self.testnet)\n", + " File \"/Users/laurenceadams/programming/programmingbitcoin/code-ch05/tx.py\", line 199, in value\n", + " tx = self.fetch_tx(testnet=testnet)\n", + " File \"/Users/laurenceadams/programming/programmingbitcoin/code-ch05/tx.py\", line 193, in fetch_tx\n", + " return TxFetcher.fetch(self.prev_tx.hex(), testnet=testnet)\n", + "AttributeError: type object 'TxFetcher' has no attribute 'fetch'\n", + "\n", + "----------------------------------------------------------------------\n", + "Ran 1 test in 0.010s\n", + "\n", + "FAILED (errors=1)\n" + ] + } + ], "source": [ "# Exercise 6\n", "\n", @@ -198,7 +270,28 @@ ] } ], - "metadata": {}, + "metadata": { + "interpreter": { + "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" + }, + "kernelspec": { + "display_name": "Python 3.8.9 64-bit", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.9" + } + }, "nbformat": 4, "nbformat_minor": 2 } diff --git a/code-ch05/tx.py b/code-ch05/tx.py index 276e866c..01179375 100644 --- a/code-ch05/tx.py +++ b/code-ch05/tx.py @@ -2,7 +2,7 @@ from unittest import TestCase import json -import requests +# import requests from helper import ( encode_varint, @@ -25,27 +25,27 @@ def get_url(cls, testnet=False): else: return 'https://blockstream.info/api/' - @classmethod - def fetch(cls, tx_id, testnet=False, fresh=False): - if fresh or (tx_id not in cls.cache): - url = '{}/tx/{}/hex'.format(cls.get_url(testnet), tx_id) - response = requests.get(url) - try: - raw = bytes.fromhex(response.text.strip()) - except ValueError: - raise ValueError('unexpected response: {}'.format(response.text)) - if raw[4] == 0: - raw = raw[:4] + raw[6:] - tx = Tx.parse(BytesIO(raw), testnet=testnet) - tx.locktime = little_endian_to_int(raw[-4:]) - else: - tx = Tx.parse(BytesIO(raw), testnet=testnet) - if tx.id() != tx_id: # <1> - raise ValueError('not the same id: {} vs {}'.format(tx.id(), - tx_id)) - cls.cache[tx_id] = tx - cls.cache[tx_id].testnet = testnet - return cls.cache[tx_id] + # @classmethod + # def fetch(cls, tx_id, testnet=False, fresh=False): + # if fresh or (tx_id not in cls.cache): + # url = '{}/tx/{}/hex'.format(cls.get_url(testnet), tx_id) + # response = requests.get(url) + # try: + # raw = bytes.fromhex(response.text.strip()) + # except ValueError: + # raise ValueError('unexpected response: {}'.format(response.text)) + # if raw[4] == 0: + # raw = raw[:4] + raw[6:] + # tx = Tx.parse(BytesIO(raw), testnet=testnet) + # tx.locktime = little_endian_to_int(raw[-4:]) + # else: + # tx = Tx.parse(BytesIO(raw), testnet=testnet) + # if tx.id() != tx_id: # <1> + # raise ValueError('not the same id: {} vs {}'.format(tx.id(), + # tx_id)) + # cls.cache[tx_id] = tx + # cls.cache[tx_id].testnet = testnet + # return cls.cache[tx_id] # end::source7[] @classmethod @@ -105,18 +105,16 @@ def hash(self): # <4> @classmethod def parse(cls, s, testnet=False): - '''Takes a byte stream and parses the transaction at the start - return a Tx object - ''' - # s.read(n) will return n bytes - # version is an integer in 4 bytes, little-endian - # num_inputs is a varint, use read_varint(s) - # parse num_inputs number of TxIns - # num_outputs is a varint, use read_varint(s) - # parse num_outputs number of TxOuts - # locktime is an integer in 4 bytes, little-endian - # return an instance of the class (see __init__ for args) - raise NotImplementedError + version = little_endian_to_int(s.read(4)) + num_inputs = read_varint(s) + inputs = [] + for _ in range(num_inputs): + inputs.append(TxIn.parse(s)) + num_outputs = read_varint(s) + outputs = [] + for _ in range(num_outputs): + outputs.append(TxOut.parse(s)) + return cls(version, inputs, outputs, None, testnet=testnet) # tag::source6[] def serialize(self): @@ -134,11 +132,18 @@ def serialize(self): def fee(self): '''Returns the fee of this transaction in satoshi''' + tx_in_value = 0 + tx_out_amount = 0 + for tx_in in self.tx_ins: + tx_in_value += tx_in.value(testnet=self.testnet) + for tx_out in self.tx_outs: + tx_out_amount += tx_out.amount + return tx_in_value - tx_out_amount # initialize input sum and output sum # use TxIn.value() to sum up the input amounts # use TxOut.amount to sum up the output amounts # fee is input sum - output sum - raise NotImplementedError + # tag::source2[] @@ -161,15 +166,16 @@ def __repr__(self): @classmethod def parse(cls, s): - '''Takes a byte stream and parses the tx_input at the start - return a TxIn object - ''' # prev_tx is 32 bytes, little endian # prev_index is an integer in 4 bytes, little endian # use Script.parse to get the ScriptSig # sequence is an integer in 4 bytes, little-endian # return an instance of the class (see __init__ for args) - raise NotImplementedError + prev_tx = s.read(32)[::-1] + prev_index = little_endian_to_int(s.read(4)) + script_sig = Script.parse(s) + sequence = little_endian_to_int(s.read(4)) + return cls(prev_tx, prev_index, script_sig, sequence) # tag::source5[] def serialize(self): @@ -178,6 +184,7 @@ def serialize(self): result += int_to_little_endian(self.prev_index, 4) result += self.script_sig.serialize() result += int_to_little_endian(self.sequence, 4) + print(result) return result # end::source5[] @@ -217,10 +224,9 @@ def parse(cls, s): '''Takes a byte stream and parses the tx_output at the start return a TxOut object ''' - # amount is an integer in 8 bytes, little endian - # use Script.parse to get the ScriptPubKey - # return an instance of the class (see __init__ for args) - raise NotImplementedError + amount = little_endian_to_int(s.read(8)) + script_pubkey = Script.parse(s) + return cls(amount, script_pubkey) # tag::source4[] def serialize(self): # <1> diff --git a/code-ch06/Chapter6.ipynb b/code-ch06/Chapter6.ipynb index 13f4830e..eae7bbfc 100644 --- a/code-ch06/Chapter6.ipynb +++ b/code-ch06/Chapter6.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -28,9 +28,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ".\n", + "----------------------------------------------------------------------\n", + "Ran 1 test in 0.001s\n", + "\n", + "OK\n" + ] + } + ], "source": [ "# Exercise 1\n", "\n", @@ -40,9 +52,23 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "NotImplementedError", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNotImplementedError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m/Users/laurenceadams/programming/programmingbitcoin/code-ch06/Chapter6.ipynb Cell 4'\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 6\u001b[0m script_sig \u001b[39m=\u001b[39m Script([sig])\n\u001b[1;32m 7\u001b[0m combined_script \u001b[39m=\u001b[39m script_sig \u001b[39m+\u001b[39m script_pubkey\n\u001b[0;32m----> 8\u001b[0m \u001b[39mprint\u001b[39m(combined_script\u001b[39m.\u001b[39;49mevaluate(z))\n", + "File \u001b[0;32m~/programming/programmingbitcoin/code-ch06/script.py:123\u001b[0m, in \u001b[0;36mScript.evaluate\u001b[0;34m(self, z)\u001b[0m\n\u001b[1;32m 121\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mFalse\u001b[39;00m\n\u001b[1;32m 122\u001b[0m \u001b[39melif\u001b[39;00m cmd \u001b[39min\u001b[39;00m (\u001b[39m172\u001b[39m, \u001b[39m173\u001b[39m, \u001b[39m174\u001b[39m, \u001b[39m175\u001b[39m): \u001b[39m# <6>\u001b[39;00m\n\u001b[0;32m--> 123\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mnot\u001b[39;00m operation(stack, z):\n\u001b[1;32m 124\u001b[0m LOGGER\u001b[39m.\u001b[39minfo(\u001b[39m'\u001b[39m\u001b[39mbad op: \u001b[39m\u001b[39m{}\u001b[39;00m\u001b[39m'\u001b[39m\u001b[39m.\u001b[39mformat(OP_CODE_NAMES[cmd]))\n\u001b[1;32m 125\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mFalse\u001b[39;00m\n", + "File \u001b[0;32m~/programming/programmingbitcoin/code-ch06/op.py:676\u001b[0m, in \u001b[0;36mop_checksig\u001b[0;34m(stack, z)\u001b[0m\n\u001b[1;32m 668\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mop_checksig\u001b[39m(stack, z):\n\u001b[1;32m 669\u001b[0m \u001b[39m# check that there are at least 2 elements on the stack\u001b[39;00m\n\u001b[1;32m 670\u001b[0m \u001b[39m# the top element of the stack is the SEC pubkey\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 674\u001b[0m \u001b[39m# verify the signature using S256Point.verify()\u001b[39;00m\n\u001b[1;32m 675\u001b[0m \u001b[39m# push an encoded 1 or 0 depending on whether the signature verified\u001b[39;00m\n\u001b[0;32m--> 676\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mNotImplementedError\u001b[39;00m\n", + "\u001b[0;31mNotImplementedError\u001b[0m: " + ] + } + ], "source": [ "from script import Script\n", "z = 0x7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d\n", @@ -67,9 +93,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ".\n", + "----------------------------------------------------------------------\n", + "Ran 1 test in 0.149s\n", + "\n", + "OK\n" + ] + } + ], "source": [ "# Exercise 2\n", "\n", @@ -96,16 +134,31 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 23, "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "TypeError", + "evalue": "'function' object is not subscriptable", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m/Users/laurenceadams/programming/programmingbitcoin/code-ch06/Chapter6.ipynb Cell 8'\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 6\u001b[0m script_sig \u001b[39m=\u001b[39m Script([OP_CODE_FUNCTIONS[\u001b[39m118\u001b[39m],OP_CODE_FUNCTIONS[\u001b[39m118\u001b[39m],OP_CODE_FUNCTIONS[\u001b[39m149\u001b[39m],OP_CODE_FUNCTIONS[\u001b[39m147\u001b[39m], OP_CODE_FUNCTIONS[\u001b[39m86\u001b[39m], OP_CODE_FUNCTIONS[\u001b[39m135\u001b[39m]]) \u001b[39m# FILL THIS IN\u001b[39;00m\n\u001b[1;32m 7\u001b[0m combined_script \u001b[39m=\u001b[39m script_sig \u001b[39m+\u001b[39m script_pubkey\n\u001b[0;32m----> 8\u001b[0m \u001b[39mprint\u001b[39m(combined_script\u001b[39m.\u001b[39;49mevaluate(\u001b[39m0\u001b[39;49m))\n", + "File \u001b[0;32m~/programming/programmingbitcoin/code-ch06/script.py:127\u001b[0m, in \u001b[0;36mScript.evaluate\u001b[0;34m(self, z)\u001b[0m\n\u001b[1;32m 125\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mFalse\u001b[39;00m\n\u001b[1;32m 126\u001b[0m \u001b[39melse\u001b[39;00m:\n\u001b[0;32m--> 127\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mnot\u001b[39;00m operation(stack):\n\u001b[1;32m 128\u001b[0m LOGGER\u001b[39m.\u001b[39minfo(\u001b[39m'\u001b[39m\u001b[39mbad op: \u001b[39m\u001b[39m{}\u001b[39;00m\u001b[39m'\u001b[39m\u001b[39m.\u001b[39mformat(OP_CODE_NAMES[cmd]))\n\u001b[1;32m 129\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mFalse\u001b[39;00m\n", + "File \u001b[0;32m~/programming/programmingbitcoin/code-ch06/op.py:479\u001b[0m, in \u001b[0;36mop_mul\u001b[0;34m(stack)\u001b[0m\n\u001b[1;32m 477\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mlen\u001b[39m(stack) \u001b[39m<\u001b[39m \u001b[39m2\u001b[39m:\n\u001b[1;32m 478\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mFalse\u001b[39;00m\n\u001b[0;32m--> 479\u001b[0m element1 \u001b[39m=\u001b[39m decode_num(stack\u001b[39m.\u001b[39;49mpop())\n\u001b[1;32m 480\u001b[0m element2 \u001b[39m=\u001b[39m decode_num(stack\u001b[39m.\u001b[39mpop())\n\u001b[1;32m 481\u001b[0m stack\u001b[39m.\u001b[39mappend(encode_num(element2 \u001b[39m*\u001b[39m element1))\n", + "File \u001b[0;32m~/programming/programmingbitcoin/code-ch06/op.py:39\u001b[0m, in \u001b[0;36mdecode_num\u001b[0;34m(element)\u001b[0m\n\u001b[1;32m 37\u001b[0m \u001b[39mif\u001b[39;00m element \u001b[39m==\u001b[39m \u001b[39mb\u001b[39m\u001b[39m'\u001b[39m\u001b[39m'\u001b[39m:\n\u001b[1;32m 38\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39m0\u001b[39m\n\u001b[0;32m---> 39\u001b[0m big_endian \u001b[39m=\u001b[39m element[::\u001b[39m-\u001b[39;49m\u001b[39m1\u001b[39;49m]\n\u001b[1;32m 40\u001b[0m \u001b[39mif\u001b[39;00m big_endian[\u001b[39m0\u001b[39m] \u001b[39m&\u001b[39m \u001b[39m0x80\u001b[39m:\n\u001b[1;32m 41\u001b[0m negative \u001b[39m=\u001b[39m \u001b[39mTrue\u001b[39;00m\n", + "\u001b[0;31mTypeError\u001b[0m: 'function' object is not subscriptable" + ] + } + ], "source": [ "# Exercise 3\n", "\n", "from script import Script\n", - "\n", + "from op import OP_CODE_FUNCTIONS\n", "script_pubkey = Script([0x76, 0x76, 0x95, 0x93, 0x56, 0x87])\n", - "script_sig = Script([]) # FILL THIS IN\n", + "script_sig = Script([OP_CODE_FUNCTIONS[118],OP_CODE_FUNCTIONS[118],OP_CODE_FUNCTIONS[149],OP_CODE_FUNCTIONS[147], OP_CODE_FUNCTIONS[86], OP_CODE_FUNCTIONS[135]]) # FILL THIS IN\n", "combined_script = script_sig + script_pubkey\n", "print(combined_script.evaluate(0))" ] @@ -147,7 +200,28 @@ ] } ], - "metadata": {}, + "metadata": { + "interpreter": { + "hash": "ddf34bd14ab601ab5ba1afcb9bdcdce15fa952f799e72bd09b477fea761a4e6b" + }, + "kernelspec": { + "display_name": "Python 3.10.2 ('venv': venv)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.2" + } + }, "nbformat": 4, "nbformat_minor": 2 } diff --git a/code-ch06/ecc.py b/code-ch06/ecc.py index a3c357cd..3596a177 100644 --- a/code-ch06/ecc.py +++ b/code-ch06/ecc.py @@ -10,7 +10,7 @@ class FieldElement: - def __init__(self, num, prime): + def __init__(self, num, prime): if num >= prime or num < 0: error = 'Num {} not in field range 0 to {}'.format( num, prime - 1) diff --git a/code-ch06/op.py b/code-ch06/op.py index 7c17be32..ad7b8b66 100644 --- a/code-ch06/op.py +++ b/code-ch06/op.py @@ -644,10 +644,15 @@ def op_sha256(stack): def op_hash160(stack): + if len(stack) < 1: + return False + else: + stack.append(hash160(stack.pop())) + return True # check that there's at least 1 element on the stack # pop off the top element from the stack # push a hash160 of the popped off element to the stack - raise NotImplementedError + # tag::source2[] @@ -668,7 +673,20 @@ def op_checksig(stack, z): # parse the serialized pubkey and signature into objects # verify the signature using S256Point.verify() # push an encoded 1 or 0 depending on whether the signature verified - raise NotImplementedError + if len(stack) < 2: + return False + pubkey = stack.pop() + der_signature = stack.pop() + hash_type = der_signature[:-1] + + parsed_pub_key = S256Point.parse(pubkey) + signature = Signature.parse(hash_type) + + if parsed_pub_key.verify(z, signature): + stack.append(encode_num(1)) + else: + stack.append(encode_num(0)) + return True def op_checksigverify(stack, z): diff --git a/code-ch07/Chapter7.ipynb b/code-ch07/Chapter7.ipynb index bb7f7867..238542ef 100644 --- a/code-ch07/Chapter7.ipynb +++ b/code-ch07/Chapter7.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -19,9 +19,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], "source": [ "from tx import Tx\n", "from io import BytesIO\n", @@ -33,9 +41,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], "source": [ "from ecc import S256Point, Signature\n", "sec = bytes.fromhex('0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278a')\n", @@ -48,9 +64,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0x27e0c5994dec7824e56dec6b2fcb342eb7cdb0d0957c2fce9882f715e85d81a6\n" + ] + } + ], "source": [ "from helper import hash256\n", "modified_tx = bytes.fromhex('0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000001976a914a802fc56c704ce87c42d7c92eb75e7896bdc41ae88acfeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac1943060001000000')\n", @@ -61,9 +85,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "from ecc import S256Point, Signature\n", "sec = bytes.fromhex('0349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278a')\n", @@ -87,9 +122,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ".\n", + "----------------------------------------------------------------------\n", + "Ran 1 test in 0.006s\n", + "\n", + "OK\n" + ] + } + ], "source": [ "# Exercise 1\n", "\n", @@ -110,9 +157,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ".\n", + "----------------------------------------------------------------------\n", + "Ran 1 test in 0.302s\n", + "\n", + "OK\n" + ] + } + ], "source": [ "# Exercise 2\n", "\n", @@ -122,9 +181,24 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "tx: cd30a8da777d28ef0e61efe68a9f7c559c1d3e5bcd7b265c850ccb4068598d11\n", + "version: 1\n", + "tx_ins:\n", + "0d6fe5213c0b3291f208cba8bfb59b7476dffacc4e5cb66f6eb20a080843a299:13\n", + "tx_outs:\n", + "33000000:OP_DUP OP_HASH160 d52ad7ca9b3d096a38e752c2018e6fbc40cdf26f OP_EQUALVERIFY OP_CHECKSIG\n", + "10000000:OP_DUP OP_HASH160 507b27411ccf7f16f10297de6cef3f291623eddf OP_EQUALVERIFY OP_CHECKSIG\n", + "locktime: 0\n" + ] + } + ], "source": [ "from helper import decode_base58, SIGHASH_ALL\n", "from script import p2pkh_script, Script\n", @@ -147,9 +221,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006a47304402207db2402a3311a3b845b038885e3dd889c08126a8570f26a844e3e4049c482a11022010178cdca4129eacbeab7c44648bf5ac1f9cac217cd609d216ec2ebc8d242c0a012103935581e52c354cd2f484fe8ed83af7a3097005b2f9c60bff71d35bd795f54b67feffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600\n" + ] + } + ], "source": [ "from ecc import PrivateKey\n", "from helper import SIGHASH_ALL\n", @@ -165,9 +247,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "mn81594PzKZa9K3Jyy1ushpuEzrnTnxhVg\n" + ] + } + ], "source": [ "from ecc import PrivateKey\n", "from helper import hash256, little_endian_to_int\n", @@ -189,14 +279,42 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ".\n", + "----------------------------------------------------------------------\n", + "Ran 1 test in 0.250s\n", + "\n", + "OK\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "n38rkhz4Wt3EALiXUsT825QqD4sBZjmMMV\n" + ] + } + ], "source": [ "# Exercise 3\n", "\n", "reload(tx)\n", - "run(tx.TxTest(\"test_sign_input\"))" + "run(tx.TxTest(\"test_sign_input\"))\n", + "\n", + "# Not an Exercise\n", + "from ecc import PrivateKey\n", + "from helper import hash256, little_endian_to_int\n", + "\n", + "secret = little_endian_to_int(hash256(b'what who where do we go?'))\n", + "private_key = PrivateKey(secret)\n", + "print(private_key.point.address(testnet=True))\n", + "\n" ] }, { @@ -212,9 +330,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "prev_text b'\\x9d\\x98\\x9a\\x0b\\x081Z\\xbd\\xb1\\xbd\\xa8\\xb7\\n\\x05\\x0c\\xec\\xe2R\\xcc\\xae\\x0e\\xceg\\xe6\\xed\\x80=\\x8f|8\\xaaV'\n", + "priv \n", + "True\n", + "010000000156aa387c8f3d80ede667ce0eaecc52e2ec0c050ab7a8bdb1bd5a31080b9a989d010000006b483045022100ece9798beac1bda1b073f18593e662db5138014f7938f22488960eba272d8cfa022046dc69a3a783044effebf73555b89ed3d8bd780a35635472f6ce90165709df0c012103935581e52c354cd2f484fe8ed83af7a3097005b2f9c60bff71d35bd795f54b67ffffffff0270170000000000001976a914ad346f8eb57dee9a37981716e498120ae80e44f788ac90010000000000001976a914ad346f8eb57dee9a37981716e498120ae80e44f788ac00000000\n" + ] + } + ], "source": [ "# Exercise 4\n", "\n", @@ -223,30 +352,46 @@ "from script import p2pkh_script, Script\n", "from tx import TxIn, TxOut, Tx\n", "\n", - "# create 1 TxIn and 2 TxOuts\n", + "# get the prev_tx and prev_index from the transaction where you got\n", + "# some testnet coins\n", + "prev_tx = bytes.fromhex('9d989a0b08315abdb1bda8b70a050cece252ccae0ece67e6ed803d8f7c38aa56')\n", + "print(f'prev_text {prev_tx}')\n", + "prev_index = 1\n", "# 1 of the TxOuts should be back to your address\n", "# the other TxOut should be to this address\n", "target_address = 'mwJn1YPMq7y5F8J3LkC5Hxg9PHyZ5K4cFv'\n", - "\n", - "# get the private key from the exercise in Chapter 4\n", - "# change address should be the address generated from Chapter 4\n", - "\n", - "# get the prev_tx and prev_index from the transaction where you got\n", - "# some testnet coins\n", - "# create a transaction input for the previous transaction with\n", - "# the default ScriptSig and sequence\n", - "\n", "# target amount should be 60% of the output amount\n", + "target_amount = .00006000\n", "# set the fee to some reasonable amount\n", + "# change address should be the address generated from Chapter 4\n", + "change_address = 'mmgSjGVNNHBqbmM3UjbPySeZFL9NLmcAhd'\n", "# change amount = amount from the prev tx - target amount - fee\n", - "\n", + "change_amount = .000030820\n", + "# get the private key from the exercise in Chapter 4\n", + "secret = 8675309\n", + "priv = PrivateKey(secret=secret)\n", + "print(f'priv {priv}')\n", + "# create 1 TxIn and 2 TxOuts\n", + "tx_ins = []\n", + "# create a transaction input for the previous transaction with\n", + "# the default ScriptSig and sequence\n", + "tx_ins.append(TxIn(prev_tx, prev_index))\n", + "tx_outs = []\n", + "h160 = decode_base58(target_address)\n", + "script_pubkey = p2pkh_script(h160)\n", + "target_satoshis = int(target_amount * 100000000)\n", "# create a transaction output for the target amount and address\n", + "tx_outs.append(TxOut(target_satoshis, script_pubkey))\n", "# create a transaction output for the change amount and address\n", + "change_satoshis = int(change_amount*10000000)\n", + "tx_outs.append(TxOut(change_satoshis, script_pubkey))\n", "# create the transaction object\n", - "\n", + "tx_obj = Tx(1, tx_ins, tx_outs, 0, testnet=True)\n", "# sign the one input in the transaction object using the private key\n", + "print(tx_obj.sign_input(0, priv))\n", "# print the transaction's serialization in hex\n", - "# broadcast at http://testnet.blockchain.info/pushtx" + "print(tx_obj.serialize().hex())\n", + "# broadcast at http://testnet.blockchain.info/pushtx\n" ] }, { @@ -299,7 +444,28 @@ ] } ], - "metadata": {}, + "metadata": { + "interpreter": { + "hash": "ddf34bd14ab601ab5ba1afcb9bdcdce15fa952f799e72bd09b477fea761a4e6b" + }, + "kernelspec": { + "display_name": "Python 3.10.2 ('venv': venv)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.2" + } + }, "nbformat": 4, "nbformat_minor": 2 } diff --git a/code-ch07/tx.py b/code-ch07/tx.py index d3beba9c..df544014 100644 --- a/code-ch07/tx.py +++ b/code-ch07/tx.py @@ -162,28 +162,54 @@ def sig_hash(self, input_index): signed for index input_index''' # start the serialization with version # use int_to_little_endian in 4 bytes + s = int_to_little_endian(self.version, 4) # add how many inputs there are using encode_varint + s += encode_varint(len(self.tx_ins)) # loop through each input using enumerate, so we have the input index + for i, tx_in in enumerate(self.tx_ins): # if the input index is the one we're signing - # the previous tx's ScriptPubkey is the ScriptSig + if i == input_index: + # the previous tx's ScriptPubkey is the ScriptSig + s += TxIn( + prev_tx=tx_in.prev_tx, + prev_index=tx_in.prev_index, + script_sig=tx_in.script_pubkey(self.testnet), + sequence=tx_in.sequence + ).serialize() # add the serialization of the input with the ScriptSig we want # Otherwise, the ScriptSig is empty - # add the serialization of the input with the ScriptSig we want + else: + s += TxIn( + prev_tx=tx_in.prev_tx, + prev_index=tx_in.prev_index, + sequence=tx_in.sequence, + ).serialize()# add the serialization of the input with the ScriptSig we want + # add how many outputs there are using encode_varint - # add the serialization of each output + s += encode_varint(len(self.tx_outs)) + for tx_out in self.tx_outs: + # add the serialization of each output + s += tx_out.serialize() # add the locktime using int_to_little_endian in 4 bytes + s += int_to_little_endian(self.locktime, 4) # add SIGHASH_ALL using int_to_little_endian in 4 bytes + s += int_to_little_endian(SIGHASH_ALL, 4) # hash256 the serialization + h256 = hash256(s) # convert the result to an integer using int.from_bytes(x, 'big') - raise NotImplementedError + return int.from_bytes(h256, 'big') def verify_input(self, input_index): '''Returns whether the input has a valid signature''' # get the relevant input + current_input = self.tx_ins[input_index] # grab the previous ScriptPubKey + script_pub_key = current_input.script_pubkey(self.testnet) # get the signature hash (z) + z = self.sig_hash(input_index) # combine the current ScriptSig and the previous ScriptPubKey + combined = current_input.script_sig + script_pub_key # evaluate the combined script - raise NotImplementedError + return combined.evaluate(z) # tag::source2[] def verify(self): @@ -198,13 +224,19 @@ def verify(self): def sign_input(self, input_index, private_key): # get the signature hash (z) + z = self.sig_hash(input_index) # get der signature of z from private key + der = private_key.sign(z).der() # append the SIGHASH_ALL to der (use SIGHASH_ALL.to_bytes(1, 'big')) + appended_der = der + SIGHASH_ALL.to_bytes(1, 'big') # calculate the sec + sec = private_key.point.sec() # initialize a new script with [sig, sec] as the cmds + new_script = Script([appended_der, sec]) # change input's script_sig to new script + self.tx_ins[input_index].script_sig = new_script # return whether sig is valid using self.verify_input - raise NotImplementedError + return self.verify_input(input_index) class TxIn: