├── README.md ├── hashing ├── README.md └── hashing.js ├── incentives ├── README.md ├── generate-keys.js └── incentives.js ├── pow ├── README.md └── pow.js ├── solutions ├── hashing │ ├── README.md │ └── hashing.js ├── pow │ ├── README.md │ └── pow.js ├── transactions │ ├── README.md │ ├── generate-keys.js │ ├── package.json │ └── transactions.js └── wallet │ ├── README.md │ ├── blockchain.js │ ├── generate-keys.js │ ├── package.json │ └── wallet.js ├── transactions ├── README.md ├── generate-keys.js ├── package.json └── transactions.js └── wallet ├── README.md ├── blockchain.js ├── generate-keys.js ├── package.json └── wallet.js /README.md: -------------------------------------------------------------------------------- 1 | # Bitcoin Whitepaper Exercises 2 | 3 | In these exercises, you will learn about blockchains from the perspective of the [original Bitcoin Whitepaper](https://bitcoin.org/en/bitcoin-paper). 4 | 5 | * [Hashing](hashing/README.md) 6 | * [Transactions](transactions/README.md) 7 | * [Wallet](wallet/README.md) 8 | * [Proof of Work](pow/README.md) 9 | * [Incentives](incentives/README.md) 10 | -------------------------------------------------------------------------------- /hashing/README.md: -------------------------------------------------------------------------------- 1 | # Bitcoin Whitepaper Exercises - Hashing 2 | 3 | In this exercise, you will practice writing code to create blocks, compute block hashes, and verify blocks based on those hashes. 4 | 5 | ## Part 1 6 | 7 | You are provided lines of text from a poem, and you should loop through them and add each line as its own block of data to the provided blockchain. 8 | 9 | Define a `createBlock(..)` function which takes the text for its data, creates an object for the block, and computes its hash, finally returning the block object. Insert this object into the `blocks` array for the blockchain. 10 | 11 | Each block should have the following fields: 12 | 13 | * `index`: an incrementing number that's the 0-based position of the new block within the `blocks` array; the genesis block has `index` of `0`, so the next block will have `index` of `1`, and so on 14 | 15 | * `prevHash`: the value of the `hash` field from the last block in the `blocks` array 16 | 17 | * `data`: the string value passed into `createBlock(..)` 18 | 19 | * `timestamp`: the numeric timestamp (from `Date.now()`) of the moment the block is created 20 | 21 | * `hash`: the SHA256 hash of the block's other fields (`index`, `prevHash`, `data`, and `timestamp`) 22 | 23 | Verify that your blockchain includes all 8 lines of the poem, each as separate blocks, for a total of 9 blocks including the genesis block. 24 | 25 | ## Part 2 26 | 27 | Define a `verifyChain(..)` function that checks all blocks in the chain to ensure the chain is valid, and returns `true` or `false` accordingly. It may be useful to define a `verifyBlock(..)` function that can be called for each block object. 28 | 29 | Each block should be checked for the following: 30 | 31 | * `data` must be non-empty 32 | * for the genesis block only, the hash must be `"000000"` 33 | * `prevHash` must be non-empty 34 | * `index` must be an integer >= `0` 35 | * the `hash` must match what recomputing the hash with `blockHash(..)` produces 36 | 37 | In addition to verifying a block, the linkage between one block and its previous block must be checked, throughout the whole chain. That is, the block at position 4 needs to have a `prevHash` equal to the `hash` of the block at position `3`, and so on. 38 | 39 | Print out verification that the blockchain is valid after having added all the poem text as blocks. 40 | -------------------------------------------------------------------------------- /hashing/hashing.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var crypto = require("crypto"); 4 | 5 | // The Power of a Smile 6 | // by Tupac Shakur 7 | var poem = [ 8 | "The power of a gun can kill", 9 | "and the power of fire can burn", 10 | "the power of wind can chill", 11 | "and the power of a mind can learn", 12 | "the power of anger can rage", 13 | "inside until it tears u apart", 14 | "but the power of a smile", 15 | "especially yours can heal a frozen heart", 16 | ]; 17 | 18 | var Blockchain = { 19 | blocks: [], 20 | }; 21 | 22 | // Genesis block 23 | Blockchain.blocks.push({ 24 | index: 0, 25 | hash: "000000", 26 | data: "", 27 | timestamp: Date.now(), 28 | }); 29 | 30 | // TODO: insert each line into blockchain 31 | // for (let line of poem) { 32 | // } 33 | 34 | // console.log(`Blockchain is valid: ${verifyChain(Blockchain)}`); 35 | 36 | 37 | // ********************************** 38 | 39 | function blockHash(bl) { 40 | return crypto.createHash("sha256").update( 41 | // TODO: use block data to calculate hash 42 | ).digest("hex"); 43 | } 44 | -------------------------------------------------------------------------------- /incentives/README.md: -------------------------------------------------------------------------------- 1 | # Bitcoin Whitepaper Exercises - Incentives 2 | 3 | In this exercise, our focus is on the incentives we can use to motivate the time and resources spent running a blockchain node. We'll do this in a simple (greedy) way by implementing a selection algorithm for picking transactions to add to the blockchain that maximizes our payoff from transaction/block fees for each block we create. 4 | 5 | ## Setup 6 | 7 | Run `npm install` in this exercise folder to install the dependencies listed in the included `package.json` file. 8 | 9 | Run `node generate-keys.js` from the command-line to create the `keys/` sub-directory which includes a keypair. NOTE: we'll only use the public key for this exercise. 10 | 11 | ## Exercise 12 | 13 | We'll create each line as a transaction, but instead of adding each transaction in order to the blockchain right away, we'll add the transactions to a "pool" (an array). 14 | 15 | Define `addPoem(..)` to loop through the lines of the poem and create a new transaction for each, adding each transaction to the `transactionPool` array. Each transaction should include not only the `data` (the line of the poem) but also a randomly generated positive integer value between `1` and `10` to use for its transaction fee, as its `fee` field. 16 | 17 | After the poem's lines have been created as transactions in the pool, we'll then process the entire pool by selecting several transactions at a time to add as a single block to the blockchain, and repeating until the pool is empty. Because our selection order will be dependent on the amounts of the transaction fees, the lines of the poem will likely not be added to the blockchain in their chronological order; that's perfectly OK for this exercise. 18 | 19 | Define `processPool(..)` to select transactions from the pool in descending order of their `fee` value, pulling them out of the `transactionPool` array for adding to a new block. Each block's list of transactions should start with an object representing the block-fee to be paid for adding this transaction; this object should include a `blockFee` field equal to the amount in the `blockFee` constant, as well as an `account` field with the public key (`PUB_KEY_TEXT`). 20 | 21 | The maximum number of transactions (including the block fee) for each block is specified by `maxBlockSize`, so make sure not to exceed that count. 22 | 23 | Finally, define `countMyEarnings(..)` that traverses the whole blockchain -- skip the genesis block! -- and adds up all the block fees and transaction fees. Print out your total earnings. NOTE: since transaction fees are randomly generated, this value will change between each run of your program. 24 | 25 | BONUS: inspect your blockchain to make sure you added all the transactions to blocks in descending order of their fee. 26 | -------------------------------------------------------------------------------- /incentives/generate-keys.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var path = require("path"); 4 | var fs = require("fs"); 5 | var openpgp = require("openpgp"); 6 | 7 | const KEYS_DIR = path.join(__dirname,"keys"); 8 | 9 | var options = { 10 | userIds: [{ name: "Bitcoin Whitepaper", email: "bitcoin@whitepaper.tld" }], 11 | numBits: 2048, 12 | passphrase: "", 13 | }; 14 | 15 | openpgp.generateKey(options).then(function onGenerated(key) { 16 | try { fs.mkdirSync(KEYS_DIR); } catch (err) {} 17 | 18 | fs.writeFileSync(path.join(KEYS_DIR,"priv.pgp.key"),key.privateKeyArmored,"utf8"); 19 | fs.writeFileSync(path.join(KEYS_DIR,"pub.pgp.key"),key.publicKeyArmored,"utf8"); 20 | 21 | console.log("Keypair generated."); 22 | }); 23 | -------------------------------------------------------------------------------- /incentives/incentives.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var path = require("path"); 4 | var fs = require("fs"); 5 | var crypto = require("crypto"); 6 | 7 | const KEYS_DIR = path.join(__dirname,"keys"); 8 | const PUB_KEY_TEXT = fs.readFileSync(path.join(KEYS_DIR,"pub.pgp.key"),"utf8"); 9 | 10 | // The Power of a Smile 11 | // by Tupac Shakur 12 | var poem = [ 13 | "The power of a gun can kill", 14 | "and the power of fire can burn", 15 | "the power of wind can chill", 16 | "and the power of a mind can learn", 17 | "the power of anger can rage", 18 | "inside until it tears u apart", 19 | "but the power of a smile", 20 | "especially yours can heal a frozen heart", 21 | ]; 22 | 23 | const maxBlockSize = 4; 24 | const blockFee = 5; 25 | var difficulty = 16; 26 | 27 | var Blockchain = { 28 | blocks: [], 29 | }; 30 | 31 | // Genesis block 32 | Blockchain.blocks.push({ 33 | index: 0, 34 | hash: "000000", 35 | data: "", 36 | timestamp: Date.now(), 37 | }); 38 | 39 | var transactionPool = []; 40 | 41 | addPoem(); 42 | processPool(); 43 | countMyEarnings(); 44 | 45 | 46 | // ********************************** 47 | 48 | function addPoem() { 49 | // TODO: add lines of poem as transactions to the transaction-pool 50 | } 51 | 52 | function processPool() { 53 | // TODO: process the transaction-pool in order of highest fees 54 | } 55 | 56 | function countMyEarnings() { 57 | // TODO: count up block-fees and transaction-fees 58 | } 59 | 60 | function createBlock(data) { 61 | var bl = { 62 | index: Blockchain.blocks.length, 63 | prevHash: Blockchain.blocks[Blockchain.blocks.length-1].hash, 64 | data, 65 | timestamp: Date.now(), 66 | }; 67 | 68 | bl.hash = blockHash(bl); 69 | 70 | return bl; 71 | } 72 | 73 | function blockHash(bl) { 74 | while (true) { 75 | bl.nonce = Math.trunc(Math.random() * 1E7); 76 | let hash = crypto.createHash("sha256").update( 77 | `${bl.index};${bl.prevHash};${JSON.stringify(bl.data)};${bl.timestamp};${bl.nonce}` 78 | ).digest("hex"); 79 | 80 | if (hashIsLowEnough(hash)) { 81 | return hash; 82 | } 83 | } 84 | } 85 | 86 | function hashIsLowEnough(hash) { 87 | var neededChars = Math.ceil(difficulty / 4); 88 | var threshold = Number(`0b${"".padStart(neededChars * 4,"1111".padStart(4 + difficulty,"0"))}`); 89 | var prefix = Number(`0x${hash.substr(0,neededChars)}`); 90 | return prefix <= threshold; 91 | } 92 | 93 | function createTransaction(data) { 94 | var tr = { 95 | data, 96 | }; 97 | 98 | tr.hash = transactionHash(tr); 99 | 100 | return tr; 101 | } 102 | 103 | function transactionHash(tr) { 104 | return crypto.createHash("sha256").update( 105 | `${JSON.stringify(tr.data)}` 106 | ).digest("hex"); 107 | } 108 | -------------------------------------------------------------------------------- /pow/README.md: -------------------------------------------------------------------------------- 1 | # Bitcoin Whitepaper Exercises - Proof of Work 2 | 3 | In this exercise, we're going back to the simplicity of the first exercise; we're just adding a piece of text (a line from the poem) to a block, no transactions or signatures or inputs/outputs or any of that complication. 4 | 5 | Instead, this exercise focuses on a simple implementation of the "Proof of Work" consensus algorithm -- the goal of which being to make it "difficult" / "not worth it" for a malicious person to recompute all the hashes in an entire chain just so they can rewrite a bit of history. 6 | 7 | To implement Proof of Work, we're going to keep trying to compute a hash for a block until the hash is "lower" than a certain threshold, as defined by an incrementing `difficulty` integer. This `difficulty` value represents the required number of leading (on the left) `0`'s in the **binary representation** of the hash -- not the normal hexadecimal represtation where each character represents 4 bits. The more leading `0`'s there are, the lower that hash value is. 8 | 9 | To get a new hash each time you compute, you need to change the data in the block. For this reason, blocks need a `nonce` field added, which is simply a random number. Each time you generate a new `nonce`, recompute the hash and compare (with `hashIsLowEnough(..)`) to see if it's low enough to be accepted for the current `difficulty`. 10 | 11 | There are different ways of comparing a hash value's binary bit representation to see if it's low enough. Here are some hints to get you started: 12 | 13 | * You don't need to compare the whole hash, only the first X hexadecimal digits (characters) of it from the left, depending on how many bits the `difficulty` value implies. Remember, 4 bits is one hexadecimal digit character. 14 | 15 | * You can create a number value from a string representing its binary bits like this: `Number("0b001011011")`, which produces the number value `91`. 16 | 17 | * `difficulty` means how many leading `0`'s must be present when representing a hash's left-most characters in binary. You may do this comparison based on string characters or numeric values (of either binary or base-10 form), whichever seems best to you. But, make sure you compare values in the same kind of representation/base. 18 | 19 | * JavaScript now supports a `padStart(..)` utility for strings, which may be useful here. 20 | -------------------------------------------------------------------------------- /pow/pow.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var crypto = require("crypto"); 4 | 5 | // The Power of a Smile 6 | // by Tupac Shakur 7 | var poem = [ 8 | "The power of a gun can kill", 9 | "and the power of fire can burn", 10 | "the power of wind can chill", 11 | "and the power of a mind can learn", 12 | "the power of anger can rage", 13 | "inside until it tears u apart", 14 | "but the power of a smile", 15 | "especially yours can heal a frozen heart", 16 | ]; 17 | 18 | var difficulty = 10; 19 | 20 | var Blockchain = { 21 | blocks: [], 22 | }; 23 | 24 | // Genesis block 25 | Blockchain.blocks.push({ 26 | index: 0, 27 | hash: "000000", 28 | data: "", 29 | timestamp: Date.now(), 30 | }); 31 | 32 | for (let line of poem) { 33 | let bl = createBlock(line); 34 | Blockchain.blocks.push(bl); 35 | console.log(`Hash (Difficulty: ${difficulty}): ${bl.hash}`); 36 | 37 | difficulty++; 38 | } 39 | 40 | 41 | // ********************************** 42 | 43 | function createBlock(data) { 44 | var bl = { 45 | index: Blockchain.blocks.length, 46 | prevHash: Blockchain.blocks[Blockchain.blocks.length-1].hash, 47 | data, 48 | timestamp: Date.now(), 49 | }; 50 | 51 | bl.hash = blockHash(bl); 52 | 53 | return bl; 54 | } 55 | 56 | function blockHash(bl) { 57 | // TODO 58 | } 59 | 60 | function hashIsLowEnough(hash) { 61 | // TODO 62 | } 63 | 64 | function verifyBlock(bl) { 65 | if (bl.data == null) return false; 66 | if (bl.index === 0) { 67 | if (bl.hash !== "000000") return false; 68 | } 69 | else { 70 | if (!bl.prevHash) return false; 71 | if (!( 72 | typeof bl.index === "number" && 73 | Number.isInteger(bl.index) && 74 | bl.index > 0 75 | )) { 76 | return false; 77 | } 78 | if (bl.hash !== blockHash(bl)) return false; 79 | } 80 | 81 | return true; 82 | } 83 | 84 | function verifyChain(chain) { 85 | var prevHash; 86 | for (let bl of chain.blocks) { 87 | if (prevHash && bl.prevHash !== prevHash) return false; 88 | if (!verifyBlock(bl)) return false; 89 | prevHash = bl.hash; 90 | } 91 | 92 | return true; 93 | } 94 | -------------------------------------------------------------------------------- /solutions/hashing/README.md: -------------------------------------------------------------------------------- 1 | # Bitcoin Whitepaper Exercises - Hashing 2 | 3 | In this exercise, you will practice writing code to create blocks, compute block hashes, and verify blocks based on those hashes. 4 | 5 | ## Part 1 6 | 7 | You are provided lines of text from a poem, and you should loop through them and add each line as its own block of data to the provided blockchain. 8 | 9 | Define a `createBlock(..)` function which takes the text for its data, creates an object for the block, and computes its hash, finally returning the block object. Insert this object into the `blocks` array for the blockchain. 10 | 11 | Each block should have the following fields: 12 | 13 | * `index`: an incrementing number that's the 0-based position of the new block within the `blocks` array; the genesis block has `index` of `0`, so the next block will have `index` of `1`, and so on 14 | 15 | * `prevHash`: the value of the `hash` field from the last block in the `blocks` array 16 | 17 | * `data`: the string value passed into `createBlock(..)` 18 | 19 | * `timestamp`: the numeric timestamp (from `Date.now()`) of the moment the block is created 20 | 21 | * `hash`: the SHA256 hash of the block's other fields (`index`, `prevHash`, `data`, and `timestamp`) 22 | 23 | Verify that your blockchain includes all 8 lines of the poem, each as separate blocks, for a total of 9 blocks including the genesis block. 24 | 25 | ## Part 2 26 | 27 | Define a `verifyChain(..)` function that checks all blocks in the chain to ensure the chain is valid, and returns `true` or `false` accordingly. It may be useful to define a `verifyBlock(..)` function that can be called for each block object. 28 | 29 | Each block should be checked for the following: 30 | 31 | * `data` must be non-empty 32 | * for the genesis block only, the hash must be `"000000"` 33 | * `prevHash` must be non-empty 34 | * `index` must be an integer >= `0` 35 | * the `hash` must match what recomputing the hash with `blockHash(..)` produces 36 | 37 | In addition to verifying a block, the linkage between one block and its previous block must be checked, throughout the whole chain. That is, the block at position 4 needs to have a `prevHash` equal to the `hash` of the block at position `3`, and so on. 38 | 39 | Print out verification that the blockchain is valid after having added all the poem text as blocks. 40 | -------------------------------------------------------------------------------- /solutions/hashing/hashing.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var crypto = require("crypto"); 4 | 5 | // The Power of a Smile 6 | // by Tupac Shakur 7 | var poem = [ 8 | "The power of a gun can kill", 9 | "and the power of fire can burn", 10 | "the power of wind can chill", 11 | "and the power of a mind can learn", 12 | "the power of anger can rage", 13 | "inside until it tears u apart", 14 | "but the power of a smile", 15 | "especially yours can heal a frozen heart", 16 | ]; 17 | 18 | var Blockchain = { 19 | blocks: [], 20 | }; 21 | 22 | // Genesis block 23 | Blockchain.blocks.push({ 24 | index: 0, 25 | hash: "000000", 26 | data: "", 27 | timestamp: Date.now(), 28 | }); 29 | 30 | for (let line of poem) { 31 | Blockchain.blocks.push( 32 | createBlock(line) 33 | ); 34 | } 35 | 36 | console.log(`Blockchain is valid: ${verifyChain(Blockchain)}`); 37 | 38 | 39 | // ********************************** 40 | 41 | function createBlock(data) { 42 | var bl = { 43 | index: Blockchain.blocks.length, 44 | prevHash: Blockchain.blocks[Blockchain.blocks.length-1].hash, 45 | data, 46 | timestamp: Date.now(), 47 | }; 48 | 49 | bl.hash = blockHash(bl); 50 | 51 | return bl; 52 | } 53 | 54 | function blockHash(bl) { 55 | return crypto.createHash("sha256").update( 56 | `${bl.index};${bl.prevHash};${JSON.stringify(bl.data)};${bl.timestamp}` 57 | ).digest("hex"); 58 | } 59 | 60 | function verifyBlock(bl) { 61 | if (bl.data == null) return false; 62 | if (bl.index === 0) { 63 | if (bl.hash !== "000000") return false; 64 | } 65 | else { 66 | if (!bl.prevHash) return false; 67 | if (!( 68 | typeof bl.index === "number" && 69 | Number.isInteger(bl.index) && 70 | bl.index > 0 71 | )) { 72 | return false; 73 | } 74 | if (bl.hash !== blockHash(bl)) return false; 75 | } 76 | 77 | return true; 78 | } 79 | 80 | function verifyChain(chain) { 81 | var prevHash; 82 | for (let bl of chain.blocks) { 83 | if (prevHash && bl.prevHash !== prevHash) return false; 84 | if (!verifyBlock(bl)) return false; 85 | prevHash = bl.hash; 86 | } 87 | 88 | return true; 89 | } 90 | -------------------------------------------------------------------------------- /solutions/pow/README.md: -------------------------------------------------------------------------------- 1 | # Bitcoin Whitepaper Exercises - Proof of Work 2 | 3 | In this exercise, we're going back to the simplicity of the first exercise; we're just adding a piece of text (a line from the poem) to a block, no transactions or signatures or inputs/outputs or any of that complication. 4 | 5 | Instead, this exercise focuses on a simple implementation of the "Proof of Work" consensus algorithm -- the goal of which being to make it "difficult" / "not worth it" for a malicious person to recompute all the hashes in an entire chain just so they can rewrite a bit of history. 6 | 7 | To implement Proof of Work, we're going to keep trying to compute a hash for a block until the hash is "lower" than a certain threshold, as defined by an incrementing `difficulty` integer. This `difficulty` value represents the required number of leading (on the left) `0`'s in the **binary representation** of the hash -- not the normal hexadecimal represtation where each character represents 4 bits. The more leading `0`'s there are, the lower that hash value is. 8 | 9 | To get a new hash each time you compute, you need to change the data in the block. For this reason, blocks need a `nonce` field added, which is simply a random number. Each time you generate a new `nonce`, recompute the hash and compare (with `hashIsLowEnough(..)`) to see if it's low enough to be accepted for the current `difficulty`. 10 | 11 | There are different ways of comparing a hash value's binary bit representation to see if it's low enough. Here are some hints to get you started: 12 | 13 | * You don't need to compare the whole hash, only the first X hexadecimal digits (characters) of it from the left, depending on how many bits the `difficulty` value implies. Remember, 4 bits is one hexadecimal digit character. 14 | 15 | * You can create a number value from a string representing its binary bits like this: `Number("0b001011011")`, which produces the number value `91`. 16 | 17 | * `difficulty` means how many leading `0`'s must be present when representing a hash's left-most characters in binary. You may do this comparison based on string characters or numeric values (of either binary or base-10 form), whichever seems best to you. But, make sure you compare values in the same kind of representation/base. 18 | 19 | * JavaScript now supports a `padStart(..)` utility for strings, which may be useful here. 20 | -------------------------------------------------------------------------------- /solutions/pow/pow.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var crypto = require("crypto"); 4 | 5 | // The Power of a Smile 6 | // by Tupac Shakur 7 | var poem = [ 8 | "The power of a gun can kill", 9 | "and the power of fire can burn", 10 | "the power of wind can chill", 11 | "and the power of a mind can learn", 12 | "the power of anger can rage", 13 | "inside until it tears u apart", 14 | "but the power of a smile", 15 | "especially yours can heal a frozen heart", 16 | ]; 17 | 18 | var difficulty = 10; 19 | 20 | var Blockchain = { 21 | blocks: [], 22 | }; 23 | 24 | // Genesis block 25 | Blockchain.blocks.push({ 26 | index: 0, 27 | hash: "000000", 28 | data: "", 29 | timestamp: Date.now(), 30 | }); 31 | 32 | for (let line of poem) { 33 | let bl = createBlock(line); 34 | Blockchain.blocks.push(bl); 35 | console.log(`Hash (Difficulty: ${difficulty}): ${bl.hash}`); 36 | 37 | difficulty++; 38 | } 39 | 40 | 41 | // ********************************** 42 | 43 | function createBlock(data) { 44 | var bl = { 45 | index: Blockchain.blocks.length, 46 | prevHash: Blockchain.blocks[Blockchain.blocks.length-1].hash, 47 | data, 48 | timestamp: Date.now(), 49 | }; 50 | 51 | bl.hash = blockHash(bl); 52 | 53 | return bl; 54 | } 55 | 56 | function blockHash(bl) { 57 | while (true) { 58 | bl.nonce = Math.trunc(Math.random() * 1E7); 59 | let hash = crypto.createHash("sha256").update( 60 | `${bl.index};${bl.prevHash};${JSON.stringify(bl.data)};${bl.timestamp};${bl.nonce}` 61 | ).digest("hex"); 62 | 63 | if (hashIsLowEnough(hash)) { 64 | return hash; 65 | } 66 | } 67 | } 68 | 69 | function hashIsLowEnough(hash) { 70 | var neededChars = Math.ceil(difficulty / 4); 71 | var threshold = Number(`0b${"".padStart(neededChars * 4,"1111".padStart(4 + difficulty,"0"))}`); 72 | var prefix = Number(`0x${hash.substr(0,neededChars)}`); 73 | return prefix <= threshold; 74 | } 75 | 76 | function verifyBlock(bl) { 77 | if (bl.data == null) return false; 78 | if (bl.index === 0) { 79 | if (bl.hash !== "000000") return false; 80 | } 81 | else { 82 | if (!bl.prevHash) return false; 83 | if (!( 84 | typeof bl.index === "number" && 85 | Number.isInteger(bl.index) && 86 | bl.index > 0 87 | )) { 88 | return false; 89 | } 90 | if (bl.hash !== blockHash(bl)) return false; 91 | } 92 | 93 | return true; 94 | } 95 | 96 | function verifyChain(chain) { 97 | var prevHash; 98 | for (let bl of chain.blocks) { 99 | if (prevHash && bl.prevHash !== prevHash) return false; 100 | if (!verifyBlock(bl)) return false; 101 | prevHash = bl.hash; 102 | } 103 | 104 | return true; 105 | } 106 | -------------------------------------------------------------------------------- /solutions/transactions/README.md: -------------------------------------------------------------------------------- 1 | # Bitcoin Whitepaper Exercises - Transactions 2 | 3 | Similar to the previous Hashing exercise, in this exercise you will add the 8 lines of text from the provided poem to a blockchain. But this time, you will add each one as a separate transaction, authorize the transaction with a key and signature, and then add all 8 transactions to a single block. Finally, you'll validate the blockchain, including all the transaction signatures. 4 | 5 | ## Setup 6 | 7 | Run `npm install` in this exercise folder to install the dependencies listed in the included `package.json` file. 8 | 9 | Run `node generate-keys.js` from the command-line to create the `keys/` sub-directory which includes a keypair to use for digital signatures. 10 | 11 | ## Part 1 12 | 13 | Define a `createTransaction(..)` function that takes the text (line of a poem) and creates a transaction object. The transaction object should have a `data`. The transaction then needs a `hash` field with the value returned from `transactionHash(..)`. 14 | 15 | Define an asynchronous `authorizeTransaction(..)` function which then adds to the transaction object, a `pubKey` field with the public key text (`PUB_KEY_TEXT`), and a `signature` field with the signature created by `await`ing a call to `createSignature(..)`. 16 | 17 | In `addPoem()`, for each line of the poem, create and authorize a transaction object and store it in the `transactions` array. Then add set those transactions as the data for a new block, and insert the block into the blockchain. 18 | 19 | ## Part 2 20 | 21 | Modify `verifyBlock(..)` to validate all the transactions in the `data` of a block. It may be useful to define a `verifyTransaction(..)` function. 22 | 23 | Each transaction should be verified according to: 24 | 25 | * `hash` should match what's computed with `transactionHash(..)` 26 | 27 | * should include `pubKey` string and `signature` string fields 28 | 29 | * the `signature` should verify correctly with `verifySignature(..)` 30 | 31 | Print out verification that the blockchain is valid after having added all the poem text as transactions. 32 | -------------------------------------------------------------------------------- /solutions/transactions/generate-keys.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var path = require("path"); 4 | var fs = require("fs"); 5 | var openpgp = require("openpgp"); 6 | 7 | const KEYS_DIR = path.join(__dirname,"keys"); 8 | 9 | var options = { 10 | userIds: [{ name: "Bitcoin Whitepaper", email: "bitcoin@whitepaper.tld" }], 11 | numBits: 2048, 12 | passphrase: "", 13 | }; 14 | 15 | openpgp.generateKey(options).then(function onGenerated(key) { 16 | try { fs.mkdirSync(KEYS_DIR); } catch (err) {} 17 | 18 | fs.writeFileSync(path.join(KEYS_DIR,"priv.pgp.key"),key.privateKeyArmored,"utf8"); 19 | fs.writeFileSync(path.join(KEYS_DIR,"pub.pgp.key"),key.publicKeyArmored,"utf8"); 20 | 21 | console.log("Keypair generated."); 22 | }); 23 | -------------------------------------------------------------------------------- /solutions/transactions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solutions-transactions", 3 | "main": "transactions.js", 4 | "dependencies": { 5 | "openpgp": "~3.0.8" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /solutions/transactions/transactions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var path = require("path"); 4 | var fs = require("fs"); 5 | var crypto = require("crypto"); 6 | var openpgp = require("openpgp"); 7 | 8 | const KEYS_DIR = path.join(__dirname,"keys"); 9 | const PRIV_KEY_TEXT = fs.readFileSync(path.join(KEYS_DIR,"priv.pgp.key"),"utf8"); 10 | const PUB_KEY_TEXT = fs.readFileSync(path.join(KEYS_DIR,"pub.pgp.key"),"utf8"); 11 | 12 | // The Power of a Smile 13 | // by Tupac Shakur 14 | var poem = [ 15 | "The power of a gun can kill", 16 | "and the power of fire can burn", 17 | "the power of wind can chill", 18 | "and the power of a mind can learn", 19 | "the power of anger can rage", 20 | "inside until it tears u apart", 21 | "but the power of a smile", 22 | "especially yours can heal a frozen heart", 23 | ]; 24 | 25 | var Blockchain = { 26 | blocks: [], 27 | }; 28 | 29 | // Genesis block 30 | Blockchain.blocks.push({ 31 | index: 0, 32 | hash: "000000", 33 | data: "", 34 | timestamp: Date.now(), 35 | }); 36 | 37 | addPoem() 38 | .then(checkPoem) 39 | .catch(console.log); 40 | 41 | 42 | // ********************************** 43 | 44 | async function addPoem() { 45 | var transactions = []; 46 | 47 | for (let line of poem) { 48 | let tr = createTransaction(line); 49 | tr = await authorizeTransaction(tr); 50 | transactions.push(tr); 51 | } 52 | 53 | var bl = createBlock(transactions); 54 | 55 | Blockchain.blocks.push(bl); 56 | 57 | return Blockchain; 58 | } 59 | 60 | async function checkPoem(chain) { 61 | console.log(await verifyChain(chain)); 62 | } 63 | 64 | function createBlock(data) { 65 | var bl = { 66 | index: Blockchain.blocks.length, 67 | prevHash: Blockchain.blocks[Blockchain.blocks.length-1].hash, 68 | data, 69 | timestamp: Date.now(), 70 | }; 71 | 72 | bl.hash = blockHash(bl); 73 | 74 | return bl; 75 | } 76 | 77 | function createTransaction(data) { 78 | var tr = { 79 | data, 80 | }; 81 | 82 | tr.hash = transactionHash(tr); 83 | 84 | return tr; 85 | } 86 | 87 | async function authorizeTransaction(tr) { 88 | tr.pubKey = PUB_KEY_TEXT; 89 | tr.signature = await createSignature(tr.hash,PRIV_KEY_TEXT); 90 | return tr; 91 | } 92 | 93 | function transactionHash(tr) { 94 | return crypto.createHash("sha256").update( 95 | `${JSON.stringify(tr.data)}` 96 | ).digest("hex"); 97 | } 98 | 99 | async function verifyTransaction(tr) { 100 | if (tr.data == null) return false; 101 | if (typeof tr.pubKey !== "string") return false; 102 | if (typeof tr.signature !== "string") return false; 103 | 104 | return verifySignature(tr.signature,tr.pubKey); 105 | } 106 | 107 | async function createSignature(text,privKey) { 108 | var privKeyObj = openpgp.key.readArmored(privKey).keys[0]; 109 | 110 | var options = { 111 | data: text, 112 | privateKeys: [privKeyObj], 113 | }; 114 | 115 | return (await openpgp.sign(options)).data; 116 | } 117 | 118 | async function verifySignature(signature,pubKey) { 119 | try { 120 | let pubKeyObj = openpgp.key.readArmored(pubKey).keys[0]; 121 | 122 | let options = { 123 | message: openpgp.cleartext.readArmored(signature), 124 | publicKeys: pubKeyObj, 125 | }; 126 | 127 | return (await openpgp.verify(options)).signatures[0].valid; 128 | } 129 | catch (err) {} 130 | 131 | return false; 132 | } 133 | 134 | function blockHash(bl) { 135 | return crypto.createHash("sha256").update( 136 | `${bl.index};${bl.prevHash};${JSON.stringify(bl.data)};${bl.timestamp}` 137 | ).digest("hex"); 138 | } 139 | 140 | async function verifyBlock(bl) { 141 | if (bl.data == null) return false; 142 | if (bl.index === 0) { 143 | if (bl.hash !== "000000") return false; 144 | } 145 | else { 146 | if (!bl.prevHash) return false; 147 | if (!( 148 | typeof bl.index === "number" && 149 | Number.isInteger(bl.index) && 150 | bl.index > 0 151 | )) { 152 | return false; 153 | } 154 | if (bl.hash !== blockHash(bl)) return false; 155 | if (!Array.isArray(bl.data)) return false; 156 | 157 | for (let tr of bl.data) { 158 | if (!(await verifyTransaction(tr))) return false; 159 | } 160 | } 161 | 162 | return true; 163 | } 164 | 165 | async function verifyChain(chain) { 166 | var prevHash; 167 | for (let bl of chain.blocks) { 168 | if (prevHash && bl.prevHash !== prevHash) return false; 169 | if (!(await verifyBlock(bl))) return false; 170 | prevHash = bl.hash; 171 | } 172 | 173 | return true; 174 | } 175 | -------------------------------------------------------------------------------- /solutions/wallet/README.md: -------------------------------------------------------------------------------- 1 | # Bitcoin Whitepaper Exercises - Wallet 2 | 3 | In this exercise, you will create and authorize transactions which "spend" money by transferring from one account to the other, and back. You will define a wallet that tracks two accounts, including `outputs` that determine each account's balances, and spend those `output`s as `input`s to the other account, and vice versa. One of the expenditures must be rejected for insufficient balance, and gracefully noted. 4 | 5 | Finally, you will add all these transactions to the blockchain, verify the chain, and compute and print out the final balances after the transactions. 6 | 7 | ## Setup 8 | 9 | Run `npm install` in this exercise folder to install the dependencies listed in the included `package.json` file. 10 | 11 | Run `node generate-keys.js` from the command-line to create the `keys/` sub-directory which includes two keypairs, one for each account, to use for digital signatures. 12 | 13 | ## Part 1 14 | 15 | Notice that `addAccount(..)` (which is called twice for you) creates two entries in our wallet, with each entry tracking the keypair for that account as well as its current list of valid `outputs` that represent all the funds available to that account to spend. 16 | 17 | In addition to recording our transactions on the blockchain, this list of outputs, for each account, will be updated each time an expenditure is made. 18 | 19 | Unlike the previous Transactions exercise, where all we added to a transaction object was `data` that was a string value (line of a poem), the `data` for a transaction will be an object that holds two arrays, `inputs` and `outputs`. Each element in these arrays will be an object that represents either the source (`inputs`) or target (`outputs`) of funds, as well as a signature to verify each input. 20 | 21 | Modify `spend(..)` to create the transaction data as just described as an object, including verifying each input, then create that transaction, then insert it into a block. 22 | 23 | The rules for spending dictate that we should sort the outputs we have in an account in greatest to least order, and spend the biggest ones first. For whatever amount we're spending, we need to add up enough `outputs` to equal or exceed that amount. Each of the selected `outputs` are recorded as `inputs` in the transaction data, and each of these should include a signature signed by the private key of the originating account. 24 | 25 | If our total selected `outputs` (aka `inputs`) amounts exceeds the intended amount to spend, we'll need another `output` for this transaction that represents the change/refund back to the originating account for the difference. If we don't have enough `outputs` in an account for the amount we're spending, that should be noted as an error and the expenditure transaction should be aborted. 26 | 27 | For example, if you have outputs of `5`, `10`, and `15`, and you want to spend `23`, you'd sort the list to `15`, `10`, and `5`, and thus `15` and `10` would be used. But since that exceeds `23`, you need to give "change" of `2` back as an `input` to the originating account (from whom these two `outputs` were selected). 28 | 29 | ## Part 2 30 | 31 | Define `accountBalance(..)` to compute the total balance for an account in the wallet by adding up all its `outputs` amounts. 32 | 33 | The notice that one of the expenditures was not possible because of lack of funds should be printed, along with the balances of both accounts. Finally, print out the result of calling `verifyChain(..)` to verify the state of the blockchain. 34 | -------------------------------------------------------------------------------- /solutions/wallet/blockchain.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var path = require("path"); 4 | var fs = require("fs"); 5 | var crypto = require("crypto"); 6 | var openpgp = require("openpgp"); 7 | 8 | var myChain = { 9 | blocks: [], 10 | }; 11 | 12 | // Genesis block 13 | myChain.blocks.push({ 14 | index: 0, 15 | hash: "000000", 16 | data: "", 17 | timestamp: Date.now(), 18 | }); 19 | 20 | 21 | Object.assign(module.exports,{ 22 | chain: myChain, 23 | 24 | createBlock, 25 | insertBlock, 26 | createTransaction, 27 | authorizeInput, 28 | verifyChain, 29 | }); 30 | 31 | 32 | // ********************************** 33 | 34 | function createBlock(data) { 35 | var bl = { 36 | index: myChain.blocks.length, 37 | prevHash: myChain.blocks[myChain.blocks.length-1].hash, 38 | data, 39 | timestamp: Date.now(), 40 | }; 41 | 42 | bl.hash = blockHash(bl); 43 | 44 | return bl; 45 | } 46 | 47 | function insertBlock(bl) { 48 | myChain.blocks.push(bl); 49 | } 50 | 51 | function createTransaction(data) { 52 | var tr = { 53 | data, 54 | }; 55 | 56 | tr.hash = transactionHash(tr); 57 | 58 | return tr; 59 | } 60 | 61 | async function authorizeInput(input,privKey) { 62 | input.signature = await createSignature(input.account,privKey); 63 | return input; 64 | } 65 | 66 | function transactionHash(tr) { 67 | return crypto.createHash("sha256").update( 68 | `${JSON.stringify(tr.data)}` 69 | ).digest("hex"); 70 | } 71 | 72 | async function verifyTransaction(tr) { 73 | if (tr.data == null) return false; 74 | if (!Array.isArray(tr.data.inputs)) return false; 75 | if (!Array.isArray(tr.data.outputs)) return false; 76 | 77 | for (let input of tr.data.inputs) { 78 | if (!(await verifySignature(input.signature,input.account))) { 79 | return false; 80 | } 81 | } 82 | 83 | return true; 84 | } 85 | 86 | async function createSignature(text,privKey) { 87 | var privKeyObj = openpgp.key.readArmored(privKey).keys[0]; 88 | 89 | var options = { 90 | data: text, 91 | privateKeys: [privKeyObj], 92 | }; 93 | 94 | return (await openpgp.sign(options)).data; 95 | } 96 | 97 | async function verifySignature(signature,pubKey) { 98 | try { 99 | let pubKeyObj = openpgp.key.readArmored(pubKey).keys[0]; 100 | 101 | let options = { 102 | message: openpgp.cleartext.readArmored(signature), 103 | publicKeys: pubKeyObj, 104 | }; 105 | 106 | return (await openpgp.verify(options)).signatures[0].valid; 107 | } 108 | catch (err) {} 109 | 110 | return false; 111 | } 112 | 113 | function blockHash(bl) { 114 | return crypto.createHash("sha256").update( 115 | `${bl.index};${bl.prevHash};${JSON.stringify(bl.data)};${bl.timestamp}` 116 | ).digest("hex"); 117 | } 118 | 119 | async function verifyBlock(bl) { 120 | if (bl.data == null) return false; 121 | if (bl.index === 0) { 122 | if (bl.hash !== "000000") return false; 123 | } 124 | else { 125 | if (!bl.prevHash) return false; 126 | if (!( 127 | typeof bl.index === "number" && 128 | Number.isInteger(bl.index) && 129 | bl.index > 0 130 | )) { 131 | return false; 132 | } 133 | if (bl.hash !== blockHash(bl)) return false; 134 | if (!Array.isArray(bl.data)) return false; 135 | 136 | for (let tr of bl.data) { 137 | if (!(await verifyTransaction(tr))) return false; 138 | } 139 | } 140 | 141 | return true; 142 | } 143 | 144 | async function verifyChain(chain) { 145 | var prevHash; 146 | for (let bl of chain.blocks) { 147 | if (prevHash && bl.prevHash !== prevHash) return false; 148 | if (!(await verifyBlock(bl))) return false; 149 | prevHash = bl.hash; 150 | } 151 | 152 | return true; 153 | } 154 | -------------------------------------------------------------------------------- /solutions/wallet/generate-keys.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var path = require("path"); 4 | var fs = require("fs"); 5 | var openpgp = require("openpgp"); 6 | 7 | const KEYS_DIR = path.join(__dirname,"keys"); 8 | 9 | var options = { 10 | userIds: [{ name: "Bitcoin Whitepaper", email: "bitcoin@whitepaper.tld" }], 11 | numBits: 2048, 12 | passphrase: "", 13 | }; 14 | 15 | Promise.all([ 16 | openpgp.generateKey(options), 17 | openpgp.generateKey(options), 18 | ]) 19 | .then(function onGenerated([key1,key2]) { 20 | try { fs.mkdirSync(KEYS_DIR); } catch (err) {} 21 | 22 | fs.writeFileSync(path.join(KEYS_DIR,"1.priv.pgp.key"),key1.privateKeyArmored,"utf8"); 23 | fs.writeFileSync(path.join(KEYS_DIR,"1.pub.pgp.key"),key1.publicKeyArmored,"utf8"); 24 | fs.writeFileSync(path.join(KEYS_DIR,"2.priv.pgp.key"),key2.privateKeyArmored,"utf8"); 25 | fs.writeFileSync(path.join(KEYS_DIR,"2.pub.pgp.key"),key2.publicKeyArmored,"utf8"); 26 | 27 | console.log("Two keypairs generated."); 28 | }); 29 | -------------------------------------------------------------------------------- /solutions/wallet/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solutions-wallet", 3 | "main": "wallet.js", 4 | "dependencies": { 5 | "openpgp": "~3.0.8" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /solutions/wallet/wallet.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var path = require("path"); 4 | var fs = require("fs"); 5 | 6 | var Blockchain = require(path.join(__dirname,"blockchain.js")); 7 | 8 | const KEYS_DIR = path.join(__dirname,"keys"); 9 | const PRIV_KEY_TEXT_1 = fs.readFileSync(path.join(KEYS_DIR,"1.priv.pgp.key"),"utf8"); 10 | const PUB_KEY_TEXT_1 = fs.readFileSync(path.join(KEYS_DIR,"1.pub.pgp.key"),"utf8"); 11 | const PRIV_KEY_TEXT_2 = fs.readFileSync(path.join(KEYS_DIR,"2.priv.pgp.key"),"utf8"); 12 | const PUB_KEY_TEXT_2 = fs.readFileSync(path.join(KEYS_DIR,"2.pub.pgp.key"),"utf8"); 13 | 14 | var wallet = { 15 | accounts: {}, 16 | }; 17 | 18 | addAccount(PRIV_KEY_TEXT_1,PUB_KEY_TEXT_1); 19 | addAccount(PRIV_KEY_TEXT_2,PUB_KEY_TEXT_2); 20 | 21 | // fake an initial balance in account #1 22 | wallet.accounts[PUB_KEY_TEXT_1].outputs.push( 23 | { 24 | account: PUB_KEY_TEXT_1, 25 | amount: 42, 26 | } 27 | ); 28 | 29 | main().catch(console.log); 30 | 31 | 32 | // ********************************** 33 | 34 | async function main() { 35 | await spend( 36 | /*from=*/wallet.accounts[PUB_KEY_TEXT_1], 37 | /*to=*/wallet.accounts[PUB_KEY_TEXT_2], 38 | /*amount=*/13 39 | ); 40 | 41 | await spend( 42 | /*from=*/wallet.accounts[PUB_KEY_TEXT_2], 43 | /*to=*/wallet.accounts[PUB_KEY_TEXT_1], 44 | /*amount=*/5 45 | ); 46 | 47 | await spend( 48 | /*from=*/wallet.accounts[PUB_KEY_TEXT_1], 49 | /*to=*/wallet.accounts[PUB_KEY_TEXT_2], 50 | /*amount=*/31 51 | ); 52 | 53 | try { 54 | await spend( 55 | /*from=*/wallet.accounts[PUB_KEY_TEXT_2], 56 | /*to=*/wallet.accounts[PUB_KEY_TEXT_1], 57 | /*amount=*/40 58 | ); 59 | } 60 | catch (err) { 61 | console.log(err); 62 | } 63 | 64 | console.log(accountBalance(PUB_KEY_TEXT_1)); 65 | console.log(accountBalance(PUB_KEY_TEXT_2)); 66 | console.log(await Blockchain.verifyChain(Blockchain.chain)); 67 | } 68 | 69 | function addAccount(privKey,pubKey) { 70 | wallet.accounts[pubKey] = { 71 | privKey, 72 | pubKey, 73 | outputs: [] 74 | }; 75 | } 76 | 77 | async function spend(fromAccount,toAccount,amountToSpend) { 78 | var trData = { 79 | inputs: [], 80 | outputs: [], 81 | }; 82 | 83 | // pick inputs to use from fromAccount's outputs, sorted descending 84 | var sortedInputs = [...fromAccount.outputs].sort(function sort(a,b){ 85 | return b.amount - a.amount; 86 | }); 87 | var inputsToUse = []; 88 | var inputAmounts = 0; 89 | for (let input of sortedInputs) { 90 | // remove input from output-list 91 | fromAccount.outputs.splice(fromAccount.outputs.indexOf(input),1); 92 | 93 | inputsToUse.push(input); 94 | inputAmounts += input.amount; 95 | 96 | // do we have enough inputs to cover the spent amount? 97 | if (inputAmounts >= amountToSpend) break; 98 | } 99 | 100 | if (inputAmounts < amountToSpend) { 101 | fromAccount.outputs.push(...inputsToUse); 102 | throw `Don't have enough to spend ${amountToSpend}!`; 103 | } 104 | 105 | // sign and record inputs 106 | var fromPrivKey = fromAccount.privKey; 107 | for (let input of inputsToUse) { 108 | trData.inputs.push( 109 | await Blockchain.authorizeInput({ 110 | account: input.account, 111 | amount: input.amount, 112 | },fromPrivKey) 113 | ); 114 | } 115 | 116 | // record output 117 | trData.outputs.push({ account: toAccount.pubKey, amount: amountToSpend, }); 118 | 119 | // is "change" output needed? 120 | if (inputAmounts >= amountToSpend) { 121 | trData.outputs.push({ account: fromAccount.pubKey, amount: (inputAmounts - amountToSpend), }); 122 | } 123 | 124 | // create transaction and add it to blockchain 125 | var tr = Blockchain.createTransaction(trData); 126 | Blockchain.insertBlock( 127 | Blockchain.createBlock([ tr ]) 128 | ); 129 | 130 | // record outputs in our wallet (if needed) 131 | for (let output of trData.outputs) { 132 | if (output.account in wallet.accounts) { 133 | wallet.accounts[output.account].outputs.push(output); 134 | } 135 | } 136 | } 137 | 138 | function accountBalance(account) { 139 | var balance = 0; 140 | 141 | if (account in wallet.accounts) { 142 | for (let output of wallet.accounts[account].outputs) { 143 | balance += output.amount; 144 | } 145 | } 146 | 147 | return balance; 148 | } 149 | 150 | -------------------------------------------------------------------------------- /transactions/README.md: -------------------------------------------------------------------------------- 1 | # Bitcoin Whitepaper Exercises - Transactions 2 | 3 | Similar to the previous Hashing exercise, in this exercise you will add the 8 lines of text from the provided poem to a blockchain. But this time, you will add each one as a separate transaction, authorize the transaction with a key and signature, and then add all 8 transactions to a single block. Finally, you'll validate the blockchain, including all the transaction signatures. 4 | 5 | ## Setup 6 | 7 | Run `npm install` in this exercise folder to install the dependencies listed in the included `package.json` file. 8 | 9 | Run `node generate-keys.js` from the command-line to create the `keys/` sub-directory which includes a keypair to use for digital signatures. 10 | 11 | ## Part 1 12 | 13 | Define a `createTransaction(..)` function that takes the text (line of a poem) and creates a transaction object. The transaction object should have a `data`. The transaction then needs a `hash` field with the value returned from `transactionHash(..)`. 14 | 15 | Define an asynchronous `authorizeTransaction(..)` function which then adds to the transaction object, a `pubKey` field with the public key text (`PUB_KEY_TEXT`), and a `signature` field with the signature created by `await`ing a call to `createSignature(..)`. 16 | 17 | In `addPoem()`, for each line of the poem, create and authorize a transaction object and store it in the `transactions` array. Then add set those transactions as the data for a new block, and insert the block into the blockchain. 18 | 19 | ## Part 2 20 | 21 | Modify `verifyBlock(..)` to validate all the transactions in the `data` of a block. It may be useful to define a `verifyTransaction(..)` function. 22 | 23 | Each transaction should be verified according to: 24 | 25 | * `hash` should match what's computed with `transactionHash(..)` 26 | 27 | * should include `pubKey` string and `signature` string fields 28 | 29 | * the `signature` should verify correctly with `verifySignature(..)` 30 | 31 | Print out verification that the blockchain is valid after having added all the poem text as transactions. 32 | -------------------------------------------------------------------------------- /transactions/generate-keys.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var path = require("path"); 4 | var fs = require("fs"); 5 | var openpgp = require("openpgp"); 6 | 7 | const KEYS_DIR = path.join(__dirname,"keys"); 8 | 9 | var options = { 10 | userIds: [{ name: "Bitcoin Whitepaper", email: "bitcoin@whitepaper.tld" }], 11 | numBits: 2048, 12 | passphrase: "", 13 | }; 14 | 15 | openpgp.generateKey(options).then(function onGenerated(key) { 16 | try { fs.mkdirSync(KEYS_DIR); } catch (err) {} 17 | 18 | fs.writeFileSync(path.join(KEYS_DIR,"priv.pgp.key"),key.privateKeyArmored,"utf8"); 19 | fs.writeFileSync(path.join(KEYS_DIR,"pub.pgp.key"),key.publicKeyArmored,"utf8"); 20 | 21 | console.log("Keypair generated."); 22 | }); 23 | -------------------------------------------------------------------------------- /transactions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exercises-transactions", 3 | "main": "transactions.js", 4 | "dependencies": { 5 | "openpgp": "~3.0.8" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /transactions/transactions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var path = require("path"); 4 | var fs = require("fs"); 5 | var crypto = require("crypto"); 6 | var openpgp = require("openpgp"); 7 | 8 | const KEYS_DIR = path.join(__dirname,"keys"); 9 | const PRIV_KEY_TEXT = fs.readFileSync(path.join(KEYS_DIR,"priv.pgp.key"),"utf8"); 10 | const PUB_KEY_TEXT = fs.readFileSync(path.join(KEYS_DIR,"pub.pgp.key"),"utf8"); 11 | 12 | // The Power of a Smile 13 | // by Tupac Shakur 14 | var poem = [ 15 | "The power of a gun can kill", 16 | "and the power of fire can burn", 17 | "the power of wind can chill", 18 | "and the power of a mind can learn", 19 | "the power of anger can rage", 20 | "inside until it tears u apart", 21 | "but the power of a smile", 22 | "especially yours can heal a frozen heart", 23 | ]; 24 | 25 | var Blockchain = { 26 | blocks: [], 27 | }; 28 | 29 | // Genesis block 30 | Blockchain.blocks.push({ 31 | index: 0, 32 | hash: "000000", 33 | data: "", 34 | timestamp: Date.now(), 35 | }); 36 | 37 | addPoem() 38 | .then(checkPoem) 39 | .catch(console.log); 40 | 41 | 42 | // ********************************** 43 | 44 | async function addPoem() { 45 | var transactions = []; 46 | 47 | // TODO: add poem lines as authorized transactions 48 | // for (let line of poem) { 49 | // } 50 | 51 | var bl = createBlock(transactions); 52 | 53 | Blockchain.blocks.push(bl); 54 | 55 | return Blockchain; 56 | } 57 | 58 | async function checkPoem(chain) { 59 | console.log(await verifyChain(chain)); 60 | } 61 | 62 | function createBlock(data) { 63 | var bl = { 64 | index: Blockchain.blocks.length, 65 | prevHash: Blockchain.blocks[Blockchain.blocks.length-1].hash, 66 | data, 67 | timestamp: Date.now(), 68 | }; 69 | 70 | bl.hash = blockHash(bl); 71 | 72 | return bl; 73 | } 74 | 75 | function transactionHash(tr) { 76 | return crypto.createHash("sha256").update( 77 | `${JSON.stringify(tr.data)}` 78 | ).digest("hex"); 79 | } 80 | 81 | async function createSignature(text,privKey) { 82 | var privKeyObj = openpgp.key.readArmored(privKey).keys[0]; 83 | 84 | var options = { 85 | data: text, 86 | privateKeys: [privKeyObj], 87 | }; 88 | 89 | return (await openpgp.sign(options)).data; 90 | } 91 | 92 | async function verifySignature(signature,pubKey) { 93 | try { 94 | let pubKeyObj = openpgp.key.readArmored(pubKey).keys[0]; 95 | 96 | let options = { 97 | message: openpgp.cleartext.readArmored(signature), 98 | publicKeys: pubKeyObj, 99 | }; 100 | 101 | return (await openpgp.verify(options)).signatures[0].valid; 102 | } 103 | catch (err) {} 104 | 105 | return false; 106 | } 107 | 108 | function blockHash(bl) { 109 | return crypto.createHash("sha256").update( 110 | `${bl.index};${bl.prevHash};${JSON.stringify(bl.data)};${bl.timestamp}` 111 | ).digest("hex"); 112 | } 113 | 114 | async function verifyBlock(bl) { 115 | if (bl.data == null) return false; 116 | if (bl.index === 0) { 117 | if (bl.hash !== "000000") return false; 118 | } 119 | else { 120 | if (!bl.prevHash) return false; 121 | if (!( 122 | typeof bl.index === "number" && 123 | Number.isInteger(bl.index) && 124 | bl.index > 0 125 | )) { 126 | return false; 127 | } 128 | if (bl.hash !== blockHash(bl)) return false; 129 | if (!Array.isArray(bl.data)) return false; 130 | 131 | // TODO: verify transactions in block 132 | } 133 | 134 | return true; 135 | } 136 | 137 | async function verifyChain(chain) { 138 | var prevHash; 139 | for (let bl of chain.blocks) { 140 | if (prevHash && bl.prevHash !== prevHash) return false; 141 | if (!(await verifyBlock(bl))) return false; 142 | prevHash = bl.hash; 143 | } 144 | 145 | return true; 146 | } 147 | -------------------------------------------------------------------------------- /wallet/README.md: -------------------------------------------------------------------------------- 1 | # Bitcoin Whitepaper Exercises - Wallet 2 | 3 | In this exercise, you will create and authorize transactions which "spend" money by transferring from one account to the other, and back. You will define a wallet that tracks two accounts, including `outputs` that determine each account's balances, and spend those `output`s as `input`s to the other account, and vice versa. One of the expenditures must be rejected for insufficient balance, and gracefully noted. 4 | 5 | Finally, you will add all these transactions to the blockchain, verify the chain, and compute and print out the final balances after the transactions. 6 | 7 | ## Setup 8 | 9 | Run `npm install` in this exercise folder to install the dependencies listed in the included `package.json` file. 10 | 11 | Run `node generate-keys.js` from the command-line to create the `keys/` sub-directory which includes two keypairs, one for each account, to use for digital signatures. 12 | 13 | ## Part 1 14 | 15 | Notice that `addAccount(..)` (which is called twice for you) creates two entries in our wallet, with each entry tracking the keypair for that account as well as its current list of valid `outputs` that represent all the funds available to that account to spend. 16 | 17 | In addition to recording our transactions on the blockchain, this list of outputs, for each account, will be updated each time an expenditure is made. 18 | 19 | Unlike the previous Transactions exercise, where all we added to a transaction object was `data` that was a string value (line of a poem), the `data` for a transaction will be an object that holds two arrays, `inputs` and `outputs`. Each element in these arrays will be an object that represents either the source (`inputs`) or target (`outputs`) of funds, as well as a signature to verify each input. 20 | 21 | Modify `spend(..)` to create the transaction data as just described as an object, including verifying each input, then create that transaction, then insert it into a block. 22 | 23 | The rules for spending dictate that we should sort the outputs we have in an account in greatest to least order, and spend the biggest ones first. For whatever amount we're spending, we need to add up enough `outputs` to equal or exceed that amount. Each of the selected `outputs` are recorded as `inputs` in the transaction data, and each of these should include a signature signed by the private key of the originating account. 24 | 25 | If our total selected `outputs` (aka `inputs`) amounts exceeds the intended amount to spend, we'll need another `output` for this transaction that represents the change/refund back to the originating account for the difference. If we don't have enough `outputs` in an account for the amount we're spending, that should be noted as an error and the expenditure transaction should be aborted. 26 | 27 | For example, if you have outputs of `5`, `10`, and `15`, and you want to spend `23`, you'd sort the list to `15`, `10`, and `5`, and thus `15` and `10` would be used. But since that exceeds `23`, you need to give "change" of `2` back as an `input` to the originating account (from whom these two `outputs` were selected). 28 | 29 | ## Part 2 30 | 31 | Define `accountBalance(..)` to compute the total balance for an account in the wallet by adding up all its `outputs` amounts. 32 | 33 | The notice that one of the expenditures was not possible because of lack of funds should be printed, along with the balances of both accounts. Finally, print out the result of calling `verifyChain(..)` to verify the state of the blockchain. 34 | -------------------------------------------------------------------------------- /wallet/blockchain.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var path = require("path"); 4 | var fs = require("fs"); 5 | var crypto = require("crypto"); 6 | var openpgp = require("openpgp"); 7 | 8 | var myChain = { 9 | blocks: [], 10 | }; 11 | 12 | // Genesis block 13 | myChain.blocks.push({ 14 | index: 0, 15 | hash: "000000", 16 | data: "", 17 | timestamp: Date.now(), 18 | }); 19 | 20 | 21 | Object.assign(module.exports,{ 22 | chain: myChain, 23 | 24 | createBlock, 25 | insertBlock, 26 | createTransaction, 27 | authorizeInput, 28 | verifyChain, 29 | }); 30 | 31 | 32 | // ********************************** 33 | 34 | function createBlock(data) { 35 | var bl = { 36 | index: myChain.blocks.length, 37 | prevHash: myChain.blocks[myChain.blocks.length-1].hash, 38 | data, 39 | timestamp: Date.now(), 40 | }; 41 | 42 | bl.hash = blockHash(bl); 43 | 44 | return bl; 45 | } 46 | 47 | function insertBlock(bl) { 48 | myChain.blocks.push(bl); 49 | } 50 | 51 | function createTransaction(data) { 52 | var tr = { 53 | data, 54 | }; 55 | 56 | tr.hash = transactionHash(tr); 57 | 58 | return tr; 59 | } 60 | 61 | async function authorizeInput(input,privKey) { 62 | input.signature = await createSignature(input.account,privKey); 63 | return input; 64 | } 65 | 66 | function transactionHash(tr) { 67 | return crypto.createHash("sha256").update( 68 | `${JSON.stringify(tr.data)}` 69 | ).digest("hex"); 70 | } 71 | 72 | async function verifyTransaction(tr) { 73 | if (tr.data == null) return false; 74 | if (!Array.isArray(tr.data.inputs)) return false; 75 | if (!Array.isArray(tr.data.outputs)) return false; 76 | 77 | for (let input of tr.data.inputs) { 78 | if (!(await verifySignature(input.signature,input.account))) { 79 | return false; 80 | } 81 | } 82 | 83 | return true; 84 | } 85 | 86 | async function createSignature(text,privKey) { 87 | var privKeyObj = openpgp.key.readArmored(privKey).keys[0]; 88 | 89 | var options = { 90 | data: text, 91 | privateKeys: [privKeyObj], 92 | }; 93 | 94 | return (await openpgp.sign(options)).data; 95 | } 96 | 97 | async function verifySignature(signature,pubKey) { 98 | try { 99 | let pubKeyObj = openpgp.key.readArmored(pubKey).keys[0]; 100 | 101 | let options = { 102 | message: openpgp.cleartext.readArmored(signature), 103 | publicKeys: pubKeyObj, 104 | }; 105 | 106 | return (await openpgp.verify(options)).signatures[0].valid; 107 | } 108 | catch (err) {} 109 | 110 | return false; 111 | } 112 | 113 | function blockHash(bl) { 114 | return crypto.createHash("sha256").update( 115 | `${bl.index};${bl.prevHash};${JSON.stringify(bl.data)};${bl.timestamp}` 116 | ).digest("hex"); 117 | } 118 | 119 | async function verifyBlock(bl) { 120 | if (bl.data == null) return false; 121 | if (bl.index === 0) { 122 | if (bl.hash !== "000000") return false; 123 | } 124 | else { 125 | if (!bl.prevHash) return false; 126 | if (!( 127 | typeof bl.index === "number" && 128 | Number.isInteger(bl.index) && 129 | bl.index > 0 130 | )) { 131 | return false; 132 | } 133 | if (bl.hash !== blockHash(bl)) return false; 134 | if (!Array.isArray(bl.data)) return false; 135 | 136 | for (let tr of bl.data) { 137 | if (!(await verifyTransaction(tr))) return false; 138 | } 139 | } 140 | 141 | return true; 142 | } 143 | 144 | async function verifyChain(chain) { 145 | var prevHash; 146 | for (let bl of chain.blocks) { 147 | if (prevHash && bl.prevHash !== prevHash) return false; 148 | if (!(await verifyBlock(bl))) return false; 149 | prevHash = bl.hash; 150 | } 151 | 152 | return true; 153 | } 154 | -------------------------------------------------------------------------------- /wallet/generate-keys.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var path = require("path"); 4 | var fs = require("fs"); 5 | var openpgp = require("openpgp"); 6 | 7 | const KEYS_DIR = path.join(__dirname,"keys"); 8 | 9 | var options = { 10 | userIds: [{ name: "Bitcoin Whitepaper", email: "bitcoin@whitepaper.tld" }], 11 | numBits: 2048, 12 | passphrase: "", 13 | }; 14 | 15 | Promise.all([ 16 | openpgp.generateKey(options), 17 | openpgp.generateKey(options), 18 | ]) 19 | .then(function onGenerated([key1,key2]) { 20 | try { fs.mkdirSync(KEYS_DIR); } catch (err) {} 21 | 22 | fs.writeFileSync(path.join(KEYS_DIR,"1.priv.pgp.key"),key1.privateKeyArmored,"utf8"); 23 | fs.writeFileSync(path.join(KEYS_DIR,"1.pub.pgp.key"),key1.publicKeyArmored,"utf8"); 24 | fs.writeFileSync(path.join(KEYS_DIR,"2.priv.pgp.key"),key2.privateKeyArmored,"utf8"); 25 | fs.writeFileSync(path.join(KEYS_DIR,"2.pub.pgp.key"),key2.publicKeyArmored,"utf8"); 26 | 27 | console.log("Two keypairs generated."); 28 | }); 29 | -------------------------------------------------------------------------------- /wallet/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exercises-wallet", 3 | "main": "wallet.js", 4 | "dependencies": { 5 | "openpgp": "~3.0.8" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /wallet/wallet.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var path = require("path"); 4 | var fs = require("fs"); 5 | 6 | var Blockchain = require(path.join(__dirname,"blockchain.js")); 7 | 8 | const KEYS_DIR = path.join(__dirname,"keys"); 9 | const PRIV_KEY_TEXT_1 = fs.readFileSync(path.join(KEYS_DIR,"1.priv.pgp.key"),"utf8"); 10 | const PUB_KEY_TEXT_1 = fs.readFileSync(path.join(KEYS_DIR,"1.pub.pgp.key"),"utf8"); 11 | const PRIV_KEY_TEXT_2 = fs.readFileSync(path.join(KEYS_DIR,"2.priv.pgp.key"),"utf8"); 12 | const PUB_KEY_TEXT_2 = fs.readFileSync(path.join(KEYS_DIR,"2.pub.pgp.key"),"utf8"); 13 | 14 | var wallet = { 15 | accounts: {}, 16 | }; 17 | 18 | addAccount(PRIV_KEY_TEXT_1,PUB_KEY_TEXT_1); 19 | addAccount(PRIV_KEY_TEXT_2,PUB_KEY_TEXT_2); 20 | 21 | // fake an initial balance in account #1 22 | wallet.accounts[PUB_KEY_TEXT_1].outputs.push( 23 | { 24 | account: PUB_KEY_TEXT_1, 25 | amount: 42, 26 | } 27 | ); 28 | 29 | main().catch(console.log); 30 | 31 | 32 | // ********************************** 33 | 34 | async function main() { 35 | await spend( 36 | /*from=*/wallet.accounts[PUB_KEY_TEXT_1], 37 | /*to=*/wallet.accounts[PUB_KEY_TEXT_2], 38 | /*amount=*/13 39 | ); 40 | 41 | await spend( 42 | /*from=*/wallet.accounts[PUB_KEY_TEXT_2], 43 | /*to=*/wallet.accounts[PUB_KEY_TEXT_1], 44 | /*amount=*/5 45 | ); 46 | 47 | await spend( 48 | /*from=*/wallet.accounts[PUB_KEY_TEXT_1], 49 | /*to=*/wallet.accounts[PUB_KEY_TEXT_2], 50 | /*amount=*/31 51 | ); 52 | 53 | try { 54 | await spend( 55 | /*from=*/wallet.accounts[PUB_KEY_TEXT_2], 56 | /*to=*/wallet.accounts[PUB_KEY_TEXT_1], 57 | /*amount=*/40 58 | ); 59 | } 60 | catch (err) { 61 | console.log(err); 62 | } 63 | 64 | console.log(accountBalance(PUB_KEY_TEXT_1)); 65 | console.log(accountBalance(PUB_KEY_TEXT_2)); 66 | console.log(await Blockchain.verifyChain(Blockchain.chain)); 67 | } 68 | 69 | function addAccount(privKey,pubKey) { 70 | wallet.accounts[pubKey] = { 71 | privKey, 72 | pubKey, 73 | outputs: [] 74 | }; 75 | } 76 | 77 | async function spend(fromAccount,toAccount,amountToSpend) { 78 | // TODO 79 | var trData = { 80 | inputs: [], 81 | outputs: [], 82 | }; 83 | 84 | // pick inputs to use from fromAccount's outputs (i.e. previous txns, see line 22), sorted descending 85 | // var sortedInputs = 86 | 87 | // for (let input of sortedInputs) { 88 | // // remove input from output-list 89 | 90 | 91 | // // do we have enough inputs to cover the spent amount? 92 | 93 | 94 | 95 | // } 96 | 97 | 98 | 99 | // if (inputAmounts < amountToSpend) { 100 | 101 | // throw `Don't have enough to spend ${amountToSpend}!`; 102 | // } 103 | 104 | // sign and record inputs 105 | 106 | 107 | // record output 108 | 109 | 110 | // is "change" output needed? 111 | 112 | 113 | // create transaction and add it to blockchain 114 | var tr = Blockchain.createTransaction(trData); 115 | Blockchain.insertBlock( 116 | // TODO .createBlock method 117 | 118 | ); 119 | 120 | // record outputs in our wallet (if needed) 121 | 122 | } 123 | 124 | function accountBalance(account) { 125 | var balance = 0; 126 | 127 | // if (account in wallet.accounts) { 128 | 129 | // } 130 | 131 | 132 | // return balance; 133 | 134 | } 135 | --------------------------------------------------------------------------------