forked from cooganb/bitcoin-whitepaper-exercises
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added solutions for all except incentives
- Loading branch information
Showing
13 changed files
with
856 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Bitcoin Whitepaper Exercises - Hashing | ||
|
||
In this exercise, you will practice writing code to create blocks, compute block hashes, and verify blocks based on those hashes. | ||
|
||
## Part 1 | ||
|
||
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. | ||
|
||
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. | ||
|
||
Each block should have the following fields: | ||
|
||
* `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 | ||
|
||
* `prevHash`: the value of the `hash` field from the last block in the `blocks` array | ||
|
||
* `data`: the string value passed into `createBlock(..)` | ||
|
||
* `timestamp`: the numeric timestamp (from `Date.now()`) of the moment the block is created | ||
|
||
* `hash`: the SHA256 hash of the block's other fields (`index`, `prevHash`, `data`, and `timestamp`) | ||
|
||
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. | ||
|
||
## Part 2 | ||
|
||
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. | ||
|
||
Each block should be checked for the following: | ||
|
||
* `data` must be non-empty | ||
* for the genesis block only, the hash must be `"000000"` | ||
* `prevHash` must be non-empty | ||
* `index` must be an integer >= `0` | ||
* the `hash` must match what recomputing the hash with `blockHash(..)` produces | ||
|
||
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. | ||
|
||
Print out verification that the blockchain is valid after having added all the poem text as blocks. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
"use strict"; | ||
|
||
var crypto = require("crypto"); | ||
|
||
// The Power of a Smile | ||
// by Tupac Shakur | ||
var poem = [ | ||
"The power of a gun can kill", | ||
"and the power of fire can burn", | ||
"the power of wind can chill", | ||
"and the power of a mind can learn", | ||
"the power of anger can rage", | ||
"inside until it tears u apart", | ||
"but the power of a smile", | ||
"especially yours can heal a frozen heart", | ||
]; | ||
|
||
var Blockchain = { | ||
blocks: [], | ||
}; | ||
|
||
// Genesis block | ||
Blockchain.blocks.push({ | ||
index: 0, | ||
hash: "000000", | ||
data: "", | ||
timestamp: Date.now(), | ||
}); | ||
|
||
for (let line of poem) { | ||
Blockchain.blocks.push( | ||
createBlock(line) | ||
); | ||
} | ||
|
||
console.log(`Blockchain is valid: ${verifyChain(Blockchain)}`); | ||
|
||
|
||
// ********************************** | ||
|
||
function createBlock(data) { | ||
var bl = { | ||
index: Blockchain.blocks.length, | ||
prevHash: Blockchain.blocks[Blockchain.blocks.length-1].hash, | ||
data, | ||
timestamp: Date.now(), | ||
}; | ||
|
||
bl.hash = blockHash(bl); | ||
|
||
return bl; | ||
} | ||
|
||
function blockHash(bl) { | ||
return crypto.createHash("sha256").update( | ||
`${bl.index};${bl.prevHash};${JSON.stringify(bl.data)};${bl.timestamp}` | ||
).digest("hex"); | ||
} | ||
|
||
function verifyBlock(bl) { | ||
if (bl.data == null) return false; | ||
if (bl.index === 0) { | ||
if (bl.hash !== "000000") return false; | ||
} | ||
else { | ||
if (!bl.prevHash) return false; | ||
if (!( | ||
typeof bl.index === "number" && | ||
Number.isInteger(bl.index) && | ||
bl.index > 0 | ||
)) { | ||
return false; | ||
} | ||
if (bl.hash !== blockHash(bl)) return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
function verifyChain(chain) { | ||
var prevHash; | ||
for (let bl of chain.blocks) { | ||
if (prevHash && bl.prevHash !== prevHash) return false; | ||
if (!verifyBlock(bl)) return false; | ||
prevHash = bl.hash; | ||
} | ||
|
||
return true; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Bitcoin Whitepaper Exercises - Proof of Work | ||
|
||
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. | ||
|
||
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. | ||
|
||
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. | ||
|
||
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`. | ||
|
||
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: | ||
|
||
* 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. | ||
|
||
* You can create a number value from a string representing its binary bits like this: `Number("0b001011011")`, which produces the number value `91`. | ||
|
||
* `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. | ||
|
||
* JavaScript now supports a `padStart(..)` utility for strings, which may be useful here. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
"use strict"; | ||
|
||
var crypto = require("crypto"); | ||
|
||
// The Power of a Smile | ||
// by Tupac Shakur | ||
var poem = [ | ||
"The power of a gun can kill", | ||
"and the power of fire can burn", | ||
"the power of wind can chill", | ||
"and the power of a mind can learn", | ||
"the power of anger can rage", | ||
"inside until it tears u apart", | ||
"but the power of a smile", | ||
"especially yours can heal a frozen heart", | ||
]; | ||
|
||
var difficulty = 10; | ||
|
||
var Blockchain = { | ||
blocks: [], | ||
}; | ||
|
||
// Genesis block | ||
Blockchain.blocks.push({ | ||
index: 0, | ||
hash: "000000", | ||
data: "", | ||
timestamp: Date.now(), | ||
}); | ||
|
||
for (let line of poem) { | ||
let bl = createBlock(line); | ||
Blockchain.blocks.push(bl); | ||
console.log(`Hash (Difficulty: ${difficulty}): ${bl.hash}`); | ||
|
||
difficulty++; | ||
} | ||
|
||
|
||
// ********************************** | ||
|
||
function createBlock(data) { | ||
var bl = { | ||
index: Blockchain.blocks.length, | ||
prevHash: Blockchain.blocks[Blockchain.blocks.length-1].hash, | ||
data, | ||
timestamp: Date.now(), | ||
}; | ||
|
||
bl.hash = blockHash(bl); | ||
|
||
return bl; | ||
} | ||
|
||
function blockHash(bl) { | ||
while (true) { | ||
bl.nonce = Math.trunc(Math.random() * 1E7); | ||
let hash = crypto.createHash("sha256").update( | ||
`${bl.index};${bl.prevHash};${JSON.stringify(bl.data)};${bl.timestamp};${bl.nonce}` | ||
).digest("hex"); | ||
|
||
if (hashIsLowEnough(hash)) { | ||
return hash; | ||
} | ||
} | ||
} | ||
|
||
function hashIsLowEnough(hash) { | ||
var neededChars = Math.ceil(difficulty / 4); | ||
var threshold = Number(`0b${"".padStart(neededChars * 4,"1111".padStart(4 + difficulty,"0"))}`); | ||
var prefix = Number(`0x${hash.substr(0,neededChars)}`); | ||
return prefix <= threshold; | ||
} | ||
|
||
function verifyBlock(bl) { | ||
if (bl.data == null) return false; | ||
if (bl.index === 0) { | ||
if (bl.hash !== "000000") return false; | ||
} | ||
else { | ||
if (!bl.prevHash) return false; | ||
if (!( | ||
typeof bl.index === "number" && | ||
Number.isInteger(bl.index) && | ||
bl.index > 0 | ||
)) { | ||
return false; | ||
} | ||
if (bl.hash !== blockHash(bl)) return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
function verifyChain(chain) { | ||
var prevHash; | ||
for (let bl of chain.blocks) { | ||
if (prevHash && bl.prevHash !== prevHash) return false; | ||
if (!verifyBlock(bl)) return false; | ||
prevHash = bl.hash; | ||
} | ||
|
||
return true; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# Bitcoin Whitepaper Exercises - Transactions | ||
|
||
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. | ||
|
||
## Setup | ||
|
||
Run `npm install` in this exercise folder to install the dependencies listed in the included `package.json` file. | ||
|
||
Run `node generate-keys.js` from the command-line to create the `keys/` sub-directory which includes a keypair to use for digital signatures. | ||
|
||
## Part 1 | ||
|
||
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(..)`. | ||
|
||
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(..)`. | ||
|
||
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. | ||
|
||
## Part 2 | ||
|
||
Modify `verifyBlock(..)` to validate all the transactions in the `data` of a block. It may be useful to define a `verifyTransaction(..)` function. | ||
|
||
Each transaction should be verified according to: | ||
|
||
* `hash` should match what's computed with `transactionHash(..)` | ||
|
||
* should include `pubKey` string and `signature` string fields | ||
|
||
* the `signature` should verify correctly with `verifySignature(..)` | ||
|
||
Print out verification that the blockchain is valid after having added all the poem text as transactions. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
"use strict"; | ||
|
||
var path = require("path"); | ||
var fs = require("fs"); | ||
var openpgp = require("openpgp"); | ||
|
||
const KEYS_DIR = path.join(__dirname,"keys"); | ||
|
||
var options = { | ||
userIds: [{ name: "Bitcoin Whitepaper", email: "[email protected]" }], | ||
numBits: 2048, | ||
passphrase: "", | ||
}; | ||
|
||
openpgp.generateKey(options).then(function onGenerated(key) { | ||
try { fs.mkdirSync(KEYS_DIR); } catch (err) {} | ||
|
||
fs.writeFileSync(path.join(KEYS_DIR,"priv.pgp.key"),key.privateKeyArmored,"utf8"); | ||
fs.writeFileSync(path.join(KEYS_DIR,"pub.pgp.key"),key.publicKeyArmored,"utf8"); | ||
|
||
console.log("Keypair generated."); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"name": "solutions-transactions", | ||
"main": "transactions.js", | ||
"dependencies": { | ||
"openpgp": "~3.0.8" | ||
} | ||
} |
Oops, something went wrong.