├── .github └── ISSUE_TEMPLATE │ ├── report-feedback--actual-issue-.md │ └── student-journal-entry.md ├── example-scripts └── tx-mess.js ├── week-1-intro.md ├── week-3-API_and_SPV.md ├── week-4-bech32m.md └── week-2-nodes.md /.github/ISSUE_TEMPLATE/report-feedback--actual-issue-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Report feedback (actual issue) 3 | about: Suggest a change to the content of the guides. 4 | title: "[ISSUE]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/student-journal-entry.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Student Journal Entry 3 | about: Tell us what you learned this week or ask related questions! 4 | title: "[JOURNAL] Week XX" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 20 | -------------------------------------------------------------------------------- /example-scripts/tx-mess.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const bcoin = require('bcoin'); 4 | const network = bcoin.Network.get(process.env.BCOIN_NETWORK); 5 | 6 | const walletClient = new bcoin.WalletClient({ 7 | port: network.walletPort, 8 | apiKey: process.env.BCOIN_WALLET_API_KEY 9 | }); 10 | 11 | const nodeClient = new bcoin.NodeClient({ 12 | port: network.rpcPort, 13 | apiKey: process.env.BCOIN_API_KEY 14 | }); 15 | 16 | (async () => { 17 | const feeRate = network.minRelay * 10; // for some reason bc segwit??!! 18 | 19 | const numInitBlocks = 144 * 3; // Initial blocks mined to activate SegWit. 20 | // Miner primary/default then evenly disperses 21 | // all funds to other wallet accounts 22 | 23 | const numTxBlocks = 1000; // How many blocks to randomly fill with txs 24 | const numTxPerBlock = 20; // How many txs to try to put in each block 25 | // (due to the random tx-generation, some txs will fail due to lack of funds) 26 | 27 | const maxOutputsPerTx = 4; // Each tx will have a random # of outputs 28 | const minSend = 5000000; // Each tx output will have a random value 29 | const maxSend = 100000000; 30 | 31 | let mocktime = network.genesis.time + 1; 32 | const blocktime = 60 * 60 * 24; // time between block timestamps 33 | 34 | const walletNames = [ 35 | 'Powell', 36 | 'Yellen', 37 | 'Bernanke', 38 | 'Greenspan', 39 | 'Volcker', 40 | 'Miller', 41 | 'Burns', 42 | 'Martin', 43 | 'McCabe', 44 | 'Eccles' 45 | ]; 46 | 47 | const accountNames = ['hot', 'cold']; 48 | 49 | const wallets = []; 50 | 51 | console.log('Creating wallets and accounts...'); 52 | for (const wName of walletNames) { 53 | try { 54 | const wwit = Boolean(Math.random() < 0.5); 55 | await walletClient.createWallet( 56 | wName, 57 | { 58 | witness: wwit 59 | } 60 | ); 61 | 62 | const newWallet = await walletClient.wallet(wName); 63 | wallets.push(newWallet); 64 | 65 | for (const aName of accountNames) { 66 | const awit = Boolean(Math.random() < 0.5); 67 | await newWallet.createAccount( 68 | aName, 69 | { 70 | witness: awit 71 | } 72 | ); 73 | } 74 | } catch (e) { 75 | console.log(`Error creating wallet ${wName}:`, e.message); 76 | } 77 | } 78 | 79 | if (!wallets.length) { 80 | console.log('No wallets created, likely this script has already been run'); 81 | return; 82 | } 83 | accountNames.push('default'); 84 | 85 | console.log('Mining initial blocks...'); 86 | const primary = walletClient.wallet('primary'); 87 | const minerReceive = await primary.createAddress('default'); 88 | 89 | for (let i = 0; i < numInitBlocks; i++) { 90 | await nodeClient.execute('setmocktime', [mocktime]); 91 | mocktime += blocktime; 92 | await nodeClient.execute( 93 | 'generatetoaddress', 94 | [1, minerReceive.address] 95 | ); 96 | } 97 | 98 | console.log('Air-dropping funds to the people...'); 99 | const balance = await primary.getBalance('default'); 100 | 101 | // hack the available balance bc of coinbase maturity 102 | const totalAmt = balance.confirmed * 0.8; 103 | const amtPerAcct = Math.floor( 104 | totalAmt / (walletNames.length * accountNames.length) 105 | ); 106 | const outputs = []; 107 | for (const wallet of wallets) { 108 | for (const aName of accountNames) { 109 | const recAddr = await wallet.createAddress(aName); 110 | outputs.push({ 111 | value: amtPerAcct, 112 | address: recAddr.address 113 | }); 114 | } 115 | } 116 | 117 | await primary.send({ 118 | outputs: outputs, 119 | rate: feeRate, 120 | subtractFee: true 121 | }); 122 | 123 | console.log('Confirming airdrop...'); 124 | await nodeClient.execute('setmocktime', [mocktime]); 125 | mocktime += blocktime; 126 | await nodeClient.execute( 127 | 'generatetoaddress', 128 | [1, minerReceive.address] 129 | ); 130 | 131 | console.log('Creating a big mess!...'); 132 | for (let b = 0; b < numTxBlocks; b++) { 133 | for (let t = 0; t < numTxPerBlock; t++) { 134 | // Randomly select recipients for this tx 135 | const outputs = []; 136 | const numOutputs = Math.floor(Math.random() * maxOutputsPerTx) + 1; 137 | for (let o = 0; o < numOutputs; o++) { 138 | const recWallet = wallets[Math.floor(Math.random() * wallets.length)]; 139 | const recAcct = 140 | accountNames[Math.floor(Math.random() * accountNames.length)]; 141 | const recAddr = await recWallet.createAddress(recAcct); 142 | const value = Math.floor( 143 | Math.random() * (maxSend - minSend) + minSend / numOutputs 144 | ); 145 | outputs.push({ 146 | value: value, 147 | address: recAddr.address 148 | }); 149 | } 150 | 151 | // Randomly choose a sender for this tx 152 | const sendWallet = wallets[Math.floor(Math.random() * wallets.length)]; 153 | const sendAcct = accountNames[Math.floor(Math.random() * wallets.length)]; 154 | try { 155 | await sendWallet.send({ 156 | account: sendAcct, 157 | outputs: outputs, 158 | rate: feeRate, 159 | subtractFee: true 160 | }); 161 | } catch (e) { 162 | console.log(`Problem sending tx: ${e}`); 163 | } 164 | } 165 | 166 | // CONFIRM 167 | await nodeClient.execute('setmocktime', [mocktime]); 168 | mocktime += blocktime; 169 | await nodeClient.execute( 170 | 'generatetoaddress', 171 | [1, minerReceive.address] 172 | ); 173 | } 174 | 175 | console.log('All done! Go play.'); 176 | })(); 177 | -------------------------------------------------------------------------------- /week-1-intro.md: -------------------------------------------------------------------------------- 1 | # Week 1: Intro to bcoin 2 | 3 | ## Introduction materials 4 | 5 | SF Bitcoin Developers Presentation: https://www.youtube.com/watch?v=MGm54LZ1T50 6 | 7 | SF NodeJS Meetup Presentation: https://www.youtube.com/watch?v=i9w7U4onn0M 8 | 9 | Project Homepage with guides and API docs: https://bcoin.io/ 10 | 11 | Stack Exchange questions about bcoin: https://bitcoin.stackexchange.com/search?q=bcoin 12 | 13 | ## Clone and install bcoin 14 | 15 | https://github.com/bcoin-org/bcoin/blob/master/docs/getting-started.md 16 | 17 | ``` 18 | git clone https://github.com/bcoin-org/bcoin 19 | cd bcoin 20 | npm rebuild 21 | npm install --global 22 | ``` 23 | 24 | ## Run the tests locally 25 | 26 | From inside bcoin repo directory: 27 | 28 | Run all tests: 29 | 30 | ``` 31 | npm run test 32 | ``` 33 | 34 | Run one test: 35 | 36 | ``` 37 | npm run test-file test/address-test.js 38 | ``` 39 | 40 | ## Run bcoin in regtest mode 41 | 42 | https://github.com/bcoin-org/bcoin/blob/master/docs/configuration.md 43 | 44 | If you installed bcoin globally correctly, you should be able to execute this command "from anywhere": 45 | 46 | ``` 47 | bcoin --network=regtest 48 | ``` 49 | 50 | This will start a bcoin full node in a local test mode with no peers and 0-difficulty mining. 51 | Remember, regtest mode is NOT REAL MONEY, so it's ok to make mistakes ;-) 52 | The bcoin full node will be launched with default options but by reading through the configuration guide above 53 | you can see what other settings are available. 54 | 55 | In a second terminal window (or tmux pane), use the bcoin-cli tool to get node info: 56 | 57 | ``` 58 | bcoin-cli --network=regtest info 59 | ``` 60 | 61 | You can also set an enviorment variable and then you don't need to pass `--network=...` to every command: 62 | 63 | ``` 64 | export BCOIN_NETWORK=regtest 65 | bcoin-cli info 66 | ``` 67 | 68 | ## Explore the API 69 | 70 | https://bcoin.io/api-docs/ 71 | 72 | By default, bcoin runs a wallet and that wallet is initialized on first launch. 73 | The wallet is called `primary` and has one account called `default`. For more 74 | details about how the wallet is structured (like what do I mean by "account"?) 75 | try reading through [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) 76 | and let me know if you have any questions about it. 77 | 78 | ### Bitcoin wallet practice 79 | 80 | bcoin also uses [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) 81 | seed phrases for its wallets. 82 | 83 | Get your wallet's seed phrase: https://bcoin.io/api-docs/?shell--cli#get-master-hd-key 84 | 85 | ``` 86 | bwallet-cli master 87 | ``` 88 | 89 | Write down that seed phrase and keep it safe! If this were mainnet, that phrase is 90 | how you can backup and restore your wallet! 91 | 92 | Get your wallet's current receive address: https://bcoin.io/api-docs/?shell--cli#generate-receiving-address 93 | 94 | ``` 95 | bwallet-cli --account=default address 96 | ``` 97 | 98 | Now that you can receive test Bitcoin we can mine some regtest blocks, generating 99 | coins and funding your wallet: https://bcoin.io/api-docs/?shell--cli#generatetoaddress 100 | 101 | ``` 102 | bcoin-cli rpc generatetoaddress 110 103 | ``` 104 | 105 | Did it work? https://bcoin.io/api-docs/?shell--cli#get-balance 106 | 107 | ``` 108 | bwallet-cli balance 109 | ``` 110 | 111 | Now let's create a second wallet, one that uses Segregated Witness. A brief explanation 112 | of this wallet type is [here](https://en.bitcoin.it/wiki/Segregated_Witness) but what is 113 | more valuable is the list of BIPs (141, 143, 144, and 173) that describe all the technical 114 | protocol upgrades. 115 | 116 | You can name this wallet whatever you want... 117 | 118 | https://bcoin.io/api-docs/?shell--cli#create-a-wallet 119 | 120 | ``` 121 | bwallet-cli mkwallet --id=test1 --witness=true 122 | ``` 123 | 124 | If this worked you should be able to get an address from the new wallet: 125 | 126 | ``` 127 | bwallet-cli --id=test1 --account=default address 128 | ``` 129 | 130 | Now let's send a transaction from the first wallet with the mined coins to this new wallet: 131 | https://bcoin.io/api-docs/?shell--cli#send-a-transaction 132 | 133 | _Note: If you do not pass an `--id=...` option to `bwallet-cli` it will execute the command 134 | with the `default` wallet (the wallet bcoin created on startup)._ 135 | 136 | ``` 137 | bwallet-cli send 10.12345678 138 | ``` 139 | 140 | Did it work? 141 | 142 | ``` 143 | bwallet-cli --id=test1 balance 144 | ``` 145 | 146 | Now confirm that transaction! 147 | 148 | ``` 149 | bcoin-cli rpc generatetoaddress 1 150 | ``` 151 | 152 | ## Review 153 | 154 | We started a full node in regtest, generated coins to a wallet, created a new 155 | wallet using SegWit, and sent coins from one wallet to the other. 156 | 157 | Questions: 158 | 159 | 1. When you requested an address you got a JSON blob with data like `account`, `branch` and `index`. 160 | Do you know what these mean? 161 | 162 | 2. What did you notice was different about the first wallet address vs. the SegWit address? 163 | 164 | 3. When you sent the transaction you got a big JSON blob back with data like `fee`, `rate`, and `path`. 165 | What do these mean? 166 | 167 | 4. When you checked your wallet balance there is `confirmed` and `unconfirmed`. 168 | What's the difference? (bonus: how does bcoin use the terms differently than Bitcoin Core?) 169 | 170 | ## Homework 171 | 172 | 1. Look through the API docs and find one or two other commands to experiment with. 173 | Are they RPC commands or REST commands? Can you execute the same command with both `curl` 174 | and the CLI tool? 175 | 176 | 2. Can you "restore your wallet from seed?" First stop the bcoin node, then delete 177 | your wallet database (!) with `rm -rf ~/.bcoin/regtest/wallet`. When you start bcoin 178 | again can you figure out how to use `mkwallet` to restore your wallet using the BIP39 179 | seed phrase? Once you've restored the wallet, is the balance correct? If not, why not? 180 | (hint: find `rescan` in the API docs). 181 | -------------------------------------------------------------------------------- /week-3-API_and_SPV.md: -------------------------------------------------------------------------------- 1 | # Week 3: API code and SPV nodes 2 | 3 | ## API modules: wallet/node, http/rpc 4 | 5 | Time to dig in to some code! 6 | 7 | Recall that bcoin runs two servers: a full node and a wallet. Each server 8 | has two APIs: JSON-RPC and REST. You can find those four discreet files in 9 | the library: 10 | 11 | ``` 12 | lib/node/http.js 13 | lib/node/rpc.js 14 | lib/wallet/http.js 15 | lib/wallet/rpc.js 16 | ``` 17 | 18 | The two RPC servers have their own test modules: 19 | 20 | ``` 21 | test/wallet-rpc-test.js 22 | test/node-rpc-test.js 23 | ``` 24 | 25 | Unfortunately, the REST API tests are not very obvious. They 26 | are combined into this file: 27 | 28 | ``` 29 | test/http-test.js 30 | ``` 31 | 32 | Take a look at these test files and try to match up the test blocks with the 33 | functions in the actual library modules. You can run a test individually like this: 34 | 35 | ``` 36 | npm run test-file test/node-rpc-test.js 37 | ``` 38 | 39 | Can you intentionally make a test fail by changing code in one of the test blocks? 40 | More importantly: can you make a test fail by changing code in the library functions? 41 | MOST importantly: can you find a way to modify the code in a library function that 42 | DOES NOT break a test but should? 43 | 44 | For example, we have been using the `generatetoaddress` 45 | RPC call quite a bit in the first two weeks. The implementation of that RPC 46 | method is [here](https://github.com/bcoin-org/bcoin/blob/master/lib/node/rpc.js#L1705-L1719). 47 | Can you break the code in some way that the tests don't catch? If you can accomplish this 48 | (no guarantees that you can) then can you add a test to cover the code? That is a huge 49 | help! Please open a pull request to bcoin with your additional test coverage! 50 | 51 | It may be quite hard to break an RPC method without failing the tests, but that's 52 | OK there is a much simpler way to contribute: just add more test coverage. 53 | 54 | You can see using `codecov` that the four API modules are not well covered by 55 | the tests: 56 | 57 | https://codecov.io/gh/bcoin-org/bcoin/src/master/lib/node/http.js 58 | 59 | https://codecov.io/gh/bcoin-org/bcoin/src/master/lib/node/rpc.js 60 | 61 | https://codecov.io/gh/bcoin-org/bcoin/src/master/lib/wallet/http.js 62 | 63 | https://codecov.io/gh/bcoin-org/bcoin/src/master/lib/wallet/rpc.js 64 | 65 | As you scroll through these visualizations you should notice the green and red lines, 66 | which represent covered and un-covered code. These webpages are generated by 67 | our CI service ("Continuous Integration") on master branch, and it also runs on 68 | any submitted pull request. You can generate these HTML files locally if you install 69 | [nyc](https://www.npmjs.com/package/nyc) but that is probably not worth your time. 70 | 71 | Anyway, this week's task is fairly simple: **open a pull request that adds test 72 | coverage to bcoin.** 73 | 74 | Here are some example of simple PRs that added good test coverage to the API modules: 75 | 76 | https://github.com/bcoin-org/bcoin/pull/919 77 | 78 | https://github.com/bcoin-org/bcoin/pull/802 79 | 80 | https://github.com/bcoin-org/bcoin/pull/810 81 | 82 | You may want to even browse through the [API docs](https://bcoin.io/api-docs/) 83 | to pick out commands to test. Does something look wrong in the documentation? 84 | You can open a pull request for the API docs in [this repository](https://github.com/bcoin-org/bcoin-org.github.io). 85 | 86 | ## SPV Nodes 87 | 88 | We are going to run our first node on **MAINNET**! 89 | 90 | ⚠️ REAL MONEY WARNING ⚠️ 91 | 92 | Don't worry, we are going to keep it simple to start. As you are probably aware 93 | a Bitcoin Full Node can take days to fully sync and require hundreds of gigabytes 94 | of disk space. We are going to simplify that a good deal by taking advantage 95 | of a unique feature of bcoin: SPV MODE. 96 | 97 | Reading: 98 | 99 | https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki 100 | 101 | https://en.bitcoin.it/wiki/BIP37_privacy_problems 102 | 103 | https://bitcoinops.org/en/newsletters/2019/07/31/#bloom-filter-discussion 104 | 105 | https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2019-July/017145.html 106 | 107 | SPV mode is defined in section 8 of [Satoshi's whitepaper](https://bitcoin.org/bitcoin.pdf) 108 | and will be completely deprecated soon. For now we can still try it out. 109 | 110 | A bcoin SPV node on mainnet may still take about 3-4 hours to sync but it will 111 | only use up about 180 MB of disk space. 112 | 113 | Terminal 1: Start bcoin in SPV mode 114 | 115 | ``` 116 | bcoin --spv 117 | ``` 118 | 119 | You will need to let this run for a while! You can watch the sync progress in 120 | a separate terminal with: 121 | 122 | ``` 123 | watch --interval 2 bcoin-cli info 124 | ``` 125 | 126 | Terminal 2: Create a wallet with a specific private key 127 | 128 | ⚠️ REAL MONEY WARNING ⚠️ 129 | 130 | The private key I'm about to give you is a TEST KEY. It encodes this entropy seed: 131 | 132 | ``` 133 | 0x00000000000000000000000000000000 134 | ``` 135 | 136 | So keep in mind, **EVERYONE KNOWS THIS PRIVATE KEY!** The reason we are going to 137 | use this is because this particular test wallet already has a ton of blockchain 138 | activity. 139 | 140 | While bcoin is syncing in SPV mode create a wallet with this specific seed phrase: 141 | 142 | ``` 143 | bwallet-cli mkwallet --id=abandon \ 144 | --mnemonic="abandon abandon abandon abandon abandon abandon \ 145 | abandon abandon abandon abandon abandon about" 146 | ``` 147 | 148 | Then execute this command: 149 | 150 | ``` 151 | bwallet-cli --id=abandon listen 152 | ``` 153 | 154 | This command will NOT return anything right away. In fact, the "abandon" wallet 155 | won't have any activity until block height `329232` or so, so no need to stare 156 | at the `listen` command all night. What it will do is "listen" 157 | for wallet events. As the wallet syncs the blockchain in SPV mode it will 158 | start to discover transactions sent to and from this wallet! Bcoin has an 159 | elaborate JavaScript events system and this command sends those events right 160 | to the terminal. 161 | 162 | You can read all about the event in bcoin [in this guide.](https://bcoin.io/guides/events.html) 163 | 164 | Meanwhile you can play with some of the data you have already downloaded. What happens 165 | when you execute these two commands? Do you know why? 166 | 167 | ``` 168 | bcoin-cli block 10000 169 | bcoin-cli header 10000 170 | ``` 171 | 172 | What other commands work / don't work in SPV mode? 173 | 174 | Once you are fully synced, all of the wallet API commands should work. What kind 175 | of things can you learn about this wallet? How many transactions are there? 176 | Does it still have a spendable balance? 177 | 178 | ## Bonus homework 179 | 180 | ⚠️ REAL MONEY WARNING ⚠️ 181 | 182 | Create YOUR OWN wallet with this mainnet SPV node. Make sure it is a segwit 183 | wallet by setting `--witness=true` in your `mkwallet` command. BACK UP YOUR SEED 184 | PHRASE! Encrypt your wallet: https://bcoin.io/api-docs/#encryptwallet 185 | 186 | Finally, send me your Bitcoin address! I will send you $10 in real Bitcoin! 187 | 188 | Can you send some of this BTC to each other? Can you find something online 189 | to buy with it? 190 | -------------------------------------------------------------------------------- /week-4-bech32m.md: -------------------------------------------------------------------------------- 1 | # Week 4: bech32m 2 | 3 | This week we are going to start on a new feature for bcoin: bech32m addresses. 4 | The goal is to extend the `Address` module to _send_ to bech32m addresses, 5 | which will be used for the first time for Taproot. 6 | 7 | ## Background 8 | 9 | Bitcoin has historically had several [address types](https://en.bitcoin.it/wiki/Invoice_address): 10 | 11 | - pay-to-public-key (p2pk): This wasn't even an address format but in the very 12 | early days, transaction output scripts contained a raw public key. Satoshi Nakamoto's 13 | famous first transaction to Hal Finney is a good example: https://blockstream.info/tx/0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9?expand 14 | 15 | - pay-to-pubic-key-hash (p2pkh): Using Base58check encoding: 16 | - https://en.bitcoin.it/Base58Check_encoding 17 | - https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses 18 | 19 | - pay-to-script-hash (p2sh): Using Base58check encoding: 20 | - https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki 21 | 22 | - pay-to-witness-public-key-hash (p2wpkh): Using Bech32 encoding: 23 | - https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#p2wpkh 24 | 25 | - pay-to-witness-script-hash (p2wsh): Using Bech32 encoding: 26 | - https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#P2WSH 27 | 28 | - pay-to-taproot (p2tr): Using Bech32m encoding: 29 | - https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki 30 | 31 | 32 | ## How do Bitcoin addresses work? 33 | 34 | - The address itself never appears on the blockchain, it is merely a UI element. 35 | - The recipient generates an address with their wallet. 36 | - The sender enters the address into their wallet, which creates a transaction 37 | output constructed exactly how the recipient wants it, so that they can spend 38 | it in the future. 39 | - The sender's wallet DOES NOT need to be able to spend from this address type. 40 | - This means that after your work is done, bcoin will be able to send money 41 | to taproot recipients, even though taproot functionality is not implemented in 42 | bcoin itself. 43 | 44 | ### Examples: 45 | 46 | Base58 p2pkh: 47 | - address: `1Ak8PffB2meyfYnbXZR9EGfLfFZVpzJvQP` 48 | - output script: `OP_DUP OP_HASH160 6ae1301cf44ca525751d1763ac4fef12d1153986 OP_EQUALVERIFY OP_CHECKSIG` 49 | 50 | Base58 p2sh: 51 | - address: `3Ftj6zuXhYaWmSC7jtsrJPtADtiWb9LUF2` 52 | - output script: `OP_HASH160 9bc8b64caf2a4c98e806e4aa3ce7550407d05cfd OP_EQUAL` 53 | 54 | bech32 p2pkh: 55 | - address: `bc1qjn3gredclhz3eqprlsuh4js066ygnnxmwmmeyu` 56 | - output script: `OP_0 94e281e5b8fdc51c8023fc397aca0fd68889ccdb` 57 | 58 | bech32m p2tr: 59 | - address: `bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0` 60 | - output script: `OP_1 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798` 61 | 62 | ## How does bcoin parse addresses? 63 | 64 | THe important modules to review are 65 | [`address.js`](https://github.com/bcoin-org/bcoin/blob/master/lib/primitives/address.js) 66 | and 67 | [`address-test.js`](https://github.com/bcoin-org/bcoin/blob/master/test/address-test.js) 68 | 69 | These two files aren't that long and to get in to this project I recommend reading 70 | them from top to bottom. Run the tests, try to break the tests by changing parts of 71 | code, etc so you understand what these functions are doing. 72 | 73 | ## bcrypto 74 | 75 | Notice at the top of `address.js` we actually require two modules from a dependency 76 | for the actual base58 and bech32 encoding: 77 | 78 | ``` 79 | const base58 = require('bcrypto/lib/encoding/base58'); 80 | const bech32 = require('bcrypto/lib/encoding/bech32'); 81 | ``` 82 | 83 | The [bcrypto library](https://github.com/bcoin-org/bcrypto) is an essential 84 | component of bcoin and part of your work for this project will be a pull request 85 | to that repository, in addition to the bcoin repository. 86 | 87 | bcrypto is kind of crazy because everything is implemented in BOTH C and JavaScript. 88 | You'll notice the file `bech32.js` is actually a proxy: 89 | 90 | ```js 91 | if (process.env.NODE_BACKEND === 'js') 92 | module.exports = require('../js/bech32'); 93 | else 94 | module.exports = require('../native/bech32'); 95 | ``` 96 | 97 | bech32 is implemented in JavaScript in [`lib/js/bech32.js`](https://github.com/bcoin-org/bcrypto/blob/master/lib/js/bech32.js) and in C in [`deps/torsion/src/encoding.c`](https://github.com/bcoin-org/bcrypto/blob/master/deps/torsion/src/encoding.c#L1180) 98 | which is bound to JavaScript in [`lib/native/bech32.js`](https://github.com/bcoin-org/bcrypto/blob/master/lib/native/bech32.js). 99 | 100 | ## bech32m 101 | 102 | What is the difference between bech32 and bech32m? Turns out, it's JUST the checksum: 103 | 104 | https://github.com/sipa/bech32/blob/master/ref/javascript/bech32.js#L35-L43 105 | 106 | ```js 107 | function getEncodingConst (enc) { 108 | if (enc == encodings.BECH32) { 109 | return 1; 110 | } else if (enc == encodings.BECH32M) { 111 | return 0x2bc830a3; 112 | } else { 113 | return null; 114 | } 115 | } 116 | ``` 117 | 118 | ## Project work 119 | 120 | We will just be adding bech32m support in JavaScript for now. Here is how I recommend 121 | you proceed: 122 | 123 | Start with bcoin master branch and then create a new branch: 124 | 125 | ``` 126 | cd bcoin 127 | git checkout master 128 | git checkout -b bech32m 129 | ``` 130 | 131 | Remove the `bcrpyto` package that came installed with the bcoin repo and re-clone 132 | it from git: 133 | 134 | ``` 135 | cd node_modules 136 | rm -rf bcrypto 137 | git clone https://bcoin-org/bcrypto 138 | cd bcrypto 139 | npm install 140 | git checkout -b bech32m 141 | ``` 142 | 143 | So this may be a bit confusing but keep in mind we have two git repos now one 144 | inside the other! 145 | 146 | So since bech32 and bech32m are the exact same algorithm but with different 147 | checksum constants, here is how I propose we do this: 148 | 149 | 1. Refactor the existing bech32 module as a class with a constructor. 150 | All the functions in that file need to be object methods now. 151 | 152 | ```js 153 | class bech32{ 154 | constructor(name) { 155 | this.checksum = null; 156 | 157 | switch (name) { 158 | 'BECH32': 159 | this.checksum = 1; 160 | break; 161 | 'BECH32m': 162 | this.checksum = 0x2bc830a3; 163 | break; 164 | default: 165 | throw new Error('Unknown variant.'); 166 | } 167 | } 168 | ... 169 | } 170 | ``` 171 | 172 | 2. Replace the bech32 checksum constant (`1`) with `this.checksum`: 173 | 174 | https://github.com/pinheadmz/bcrypto/blob/master/lib/js/bech32.js#L207 175 | 176 | 177 | 3. Refactor the file `lib/encoding/bech32.js` to look more like 178 | [`lib/js/secp256k1.js`](https://github.com/pinheadmz/bcrypto/blob/master/lib/js/secp256k1.js). 179 | What we want to do is `require` the bech32 module, but create a new instance and pass 180 | it the `BECH32` name. 181 | 182 | 4. This refactor should not have broken the library! Test: 183 | 184 | ``` 185 | export NODE_BACKEND=js 186 | bmocha test/bech32-test.js 187 | ``` 188 | 189 | 5. Now we can start integrating bech32m. Create a new file `lib/encoding/bech32m.js`: 190 | 191 | ```js 192 | 'use strict'; 193 | 194 | const bech32 = require('../js/bech32'); 195 | 196 | // TODO: Implement bech32m in C as well and bind here. 197 | module.exports = new bech32('BECH32m'); 198 | ``` 199 | 200 | 6. Add tests: You can edit the file `test/bech32-test.js` and add tests for bech32m. 201 | There are test vectors you can copy in [BIP350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki#Test_vectors_for_Bech32m) 202 | and you can also use Pieter Wuille's [demo website](http://bitcoin.sipa.be/bech32/demo/demo.html). 203 | 204 | 7. BACK TO BCOIN. Now that we have a bech32m implementation in bcrypto, we need 205 | to integrate it into the bcoin `Address` module - and add tests! 206 | 207 | Add this at the top of the file `address.js`: 208 | 209 | ```js 210 | const bech32m = require('bcrypto/lib/encoding/bech32m'); 211 | ``` 212 | 213 | You're on your own from here, but here are some hints: 214 | 215 | Take a look at the `decode()` and `encode()` functions in 216 | https://github.com/sipa/bech32/blob/master/ref/javascript/segwit_addr.js 217 | 218 | Remember that witness version 0 addresses are always bech32, and any other 219 | version is bech32m. 220 | 221 | 8. When you have it all working, you will need to open TWO pull requests 222 | - adding bech32m support in JavaScript to bcrypto 223 | - add bech32m sending support to bcoin 224 | 225 | -------------------------------------------------------------------------------- /week-2-nodes.md: -------------------------------------------------------------------------------- 1 | # Week 2: Nodes and P2P Connections 2 | 3 | ## Connect two full nodes in regtest 4 | 5 | This will require two terminal windows but might be easier to manage using `tmux` 6 | with two panes on the same screen. We are going to connect multiple nodes 7 | and watch them exchange messages. 8 | 9 | You may want to clear out your regtest chain first with `rm -rf ~/.bcoin/regtest` 10 | 11 | Terminal 1: Start bcoin in regtest with default options. 12 | 13 | ``` 14 | bcoin --network=regtest 15 | ``` 16 | 17 | Terminal 2: Start a second bcoin full node: 18 | 19 | ``` 20 | bcoin --network=regtest 21 | ``` 22 | 23 | You should get an error here, either that the data directory is already in use 24 | or that the http / p2p ports are in use (by the first node). To run the second 25 | node in its own space, add these options: 26 | 27 | ``` 28 | bcoin \ 29 | --network=regtest \ 30 | --prefix=~/.bcoin/regtest-2 \ 31 | --port=10000 \ 32 | --http-port=20000 \ 33 | --wallet-http-port=30000 34 | ``` 35 | 36 | Reminder: you can learn about these options and more [here](https://github.com/bcoin-org/bcoin/blob/master/docs/configuration.md). 37 | 38 | Terminal 3: CLI commands 39 | 40 | Using only default options (which will therefore target node #1, also set 41 | with default options), generate 100 blocks to your default wallet address: 42 | 43 | ``` 44 | bcoin-cli rpc generatetoaddress 100 `bwallet-cli rpc getnewaddress` 45 | ``` 46 | 47 | Notice the second command embedded using backticks - do you understand what's happening? 48 | 49 | Now check the chain height of both nodes: 50 | 51 | ``` 52 | bcoin-cli info | jq .chain 53 | bcoin-cli --http-port=20000 info | jq .chain 54 | ``` 55 | 56 | The two nodes are out of sync! Let's connect them: https://bcoin.io/api-docs/?shell--cli#addnode 57 | 58 | ``` 59 | bcoin-cli --http-port=20000 rpc addnode 127.0.0.1 onetry 60 | ``` 61 | 62 | Now check both nodes' `info` again, they should be synced and you should have been 63 | able to observe the second node syncing those 100 blocks from the first node. 64 | 65 | ## Demonstrate A Chain Reorganization 66 | 67 | Disconnect the second node from the first: https://bcoin.io/api-docs/?shell--cli#disconnectnode 68 | 69 | ``` 70 | bcoin-cli --http-port=20000 rpc disconnectnode 127.0.0.1 71 | ``` 72 | 73 | Generate 2 blocks on the first node and 1 block on the second node: 74 | 75 | ``` 76 | bcoin-cli rpc generatetoaddress 2 `bwallet-cli rpc getnewaddress` 77 | bcoin-cli --http-port=20000 rpc generatetoaddress 1 `bwallet-cli rpc getnewaddress` 78 | ``` 79 | 80 | If you check both nodes' `info` again, they should be out of sync. However, this time 81 | what we have is a CHAIN SPLIT. Both nodes have the same blockchain up to height 100. 82 | After that point, the second node has 1 new block but the first node has 2 totally different blocks. 83 | The two chains after height 100 are completely different. What will happen when we 84 | connect these two nodes back together again? 85 | 86 | ``` 87 | bcoin-cli --http-port=20000 rpc addnode 127.0.0.1 onetry 88 | ``` 89 | 90 | Once you execute this, observe the log from the second node. The actual log file 91 | can be found at `~/.bcoin/regtest-2/debug.log` if you want to inspect it later but 92 | the same output should be in the stdout of that terminal window / tmux pane. 93 | 94 | You should see a few warnings like this: 95 | 96 | ``` 97 | [warning] (chain) Heads up: Competing chain at height 101: tip-height=101 competitor-height=101 98 | tip-hash=397c49bcd2159b7e3cd528ede1981aa8760b758ec2a141ba1c21de97226b307c competitor-hash=4c6f53e2d911aa1d3c2a8f437b37d7fbe39eafdf0df016a1bfdd1f81c5d25df7 99 | tip-chainwork=204 competitor-chainwork=204 chainwork-diff=0 100 | [debug] (chain) Memory: rss=48mb, js-heap=10/12mb native-heap=36mb 101 | [info] (chain) Block 4c6f53e2d911aa1d3c2a8f437b37d7fbe39eafdf0df016a1bfdd1f81c5d25df7 (101) added to chain (size=280 txs=1 time=6.685683). 102 | [debug] (net) Received full compact block 0e463b7bf53efb2b2dc517196f37efaf58d09d5c8af636e0f99c4577fd538506 (127.0.0.1:48444). 103 | [warning] (chain) WARNING: Reorganizing chain. 104 | [debug] (wallet) Adding block: 101. 105 | [warning] (chain) Chain reorganization: 106 | old=397c49bcd2159b7e3cd528ede1981aa8760b758ec2a141ba1c21de97226b307c(101) 107 | new=0e463b7bf53efb2b2dc517196f37efaf58d09d5c8af636e0f99c4577fd538506(102) 108 | [debug] (wallet) Adding block: 102. 109 | ``` 110 | 111 | As an exercise, and to begin the deep-dive into bcoin development, review the 112 | parts of the code that generate these warnings and try to follow along. 113 | The relevant functions (in `lib/blockchain/chain.js`) are: 114 | - [`connect()`](https://github.com/bcoin-org/bcoin/blob/950e30c084141e7c8cb81233136b9e5bc7e1e02c/lib/blockchain/chain.js#L1440-L1450) 115 | - [`reorganize()`](https://github.com/bcoin-org/bcoin/blob/950e30c084141e7c8cb81233136b9e5bc7e1e02c/lib/blockchain/chain.js#L875-L887) 116 | - [`setBestChain()`](https://github.com/bcoin-org/bcoin/blob/0551096c0a0dae4ad8fb9f1e135b743d50a19983/lib/blockchain/chain.js#L1043) 117 | - [`saveAlternate()`](https://github.com/bcoin-org/bcoin/blob/0551096c0a0dae4ad8fb9f1e135b743d50a19983/lib/blockchain/chain.js#L1106) 118 | 119 | ## Double Spend Attack! 120 | 121 | There are several types of [double-spend attacks](https://en.bitcoin.it/wiki/Irreversible_Transactions) 122 | theoretically possible in Bitcoin. Since we have 100% of the mining hashrate in 123 | our two-node regtest network, we can easily demonstrate how it works. 124 | 125 | Given how we have executed our `generatetoaddress` commands thus far, the wallet 126 | in the first node should have a big balance, and the second node's wallet should be empty: 127 | 128 | ``` 129 | $ bwallet-cli balance 130 | { 131 | "account": -1, 132 | "tx": 102, 133 | "coin": 102, 134 | "unconfirmed": 510000000000, 135 | "confirmed": 510000000000 136 | } 137 | 138 | $ bwallet-cli --http-port=30000 balance 139 | { 140 | "account": -1, 141 | "tx": 0, 142 | "coin": 0, 143 | "unconfirmed": 0, 144 | "confirmed": 0 145 | } 146 | ``` 147 | 148 | We are going to send a transaction to the second node, and then double-spend-attack 149 | that wallet, effectively reversing the transaction and potentially stealing from 150 | that user! 151 | 152 | Get an address from the victim's wallet: 153 | 154 | ``` 155 | bwallet-cli --http-port=30000 rpc getnewaddress 156 | ``` 157 | 158 | Send 150 BTC from the first node to that address: 159 | 160 | ``` 161 | bwallet-cli send 150.00000000 --subtract-fee=true 162 | ``` 163 | 164 | _Why 150 BTC?_ In short, it's to guarantee the success of this test. 165 | You might be able to figure why we use this value on your own but here are some 166 | hints you can search for in addition to learning exactly what a double-spend is: 167 | - Coinbase maturity 168 | - Coin selection 169 | 170 | Confirm that TX in a block and note the block hash that is returned: 171 | 172 | (example) 173 | ``` 174 | $ bcoin-cli rpc generatetoaddress 1 `bwallet-cli rpc getnewaddress` 175 | [ 176 | "4965f6fd776265eb23a31f2a3c85eaaefc3a49ac447e47bf6cda274e9f380e2a" 177 | ] 178 | ``` 179 | 180 | At this point the "victim" (the second node's wallet) appears to have a confirmed 181 | balance of around 150 BTC (minus the transaction fee, do you know why?). 182 | Here is where we are going to start getting nasty. Notice how 183 | all the following commands are only executed to the first ("attacker") node, while remaining connected. 184 | Some of these steps are a bit hacky to side-step safety features normally built in to 185 | bcoin. 186 | 187 | Undo the last block, un-confirming the 150 BTC transaction: https://bcoin.io/api-docs/#invalidateblock 188 | 189 | ``` 190 | bcoin-cli rpc invalidateblock 191 | ``` 192 | 193 | If you check the nodes' `info` right now, you'll notice that the first node 194 | is now "behind" by one block -- the invalidation command is local only and does 195 | not affect other nodes on the network. 196 | 197 | Remove the now-unconfirmed transaction from the sender's wallet: https://bcoin.io/api-docs/?shell--cli#zap-transactions 198 | Without this step, the wallet will not let us double-spend our coins. 199 | 200 | ``` 201 | bwallet-cli zap --account=default --age=1 202 | ``` 203 | 204 | Check that the wallet has no "pending" transactions, this would prevent us 205 | from double-spending: 206 | 207 | ``` 208 | $ bwallet-cli pending 209 | [] 210 | ``` 211 | 212 | OK, let's steal our money back: 213 | 214 | ``` 215 | bwallet-cli send `bwallet-cli rpc getnewaddress` 150.00000000 --subtract-fee=true 216 | ``` 217 | 218 | Now we confirm that transaction once: 219 | 220 | ``` 221 | bcoin-cli rpc generatetoaddress 1 `bwallet-cli rpc getnewaddress` 222 | ``` 223 | 224 | The second node should log the familiar "Heads up" warning. 225 | 226 | Then we reorganize with one more block: 227 | 228 | ``` 229 | bcoin-cli rpc generatetoaddress 1 `bwallet-cli rpc getnewaddress` 230 | ``` 231 | 232 | WOW, the second node and its wallet really did not like that, huh? 233 | 234 | ``` 235 | [warning] (chain) WARNING: Reorganizing chain. 236 | [debug] (wallet) Rescan: reverting block 103 237 | [warning] (wallet) Disconnected wallet block 1103b6349e5c30a4e390132f2c3cfb108b52dfa8a1b87a1a2790df066027c458 (tx=1). 238 | [debug] (wallet) Adding block: 103. 239 | [info] (wallet) Incoming transaction for 1 wallets in WalletDB (2416818256d576c9ac8b24b7108ffb96b240e6d577a9745b49491b14f23ad929). 240 | [warning] (wallet) Handling conflicting tx: ba3a87a2cc42327cf7d08bdb33ca8785d301f260dbc432aeb664c65ca241c8a9. 241 | [warning] (wallet) Removed conflict: ba3a87a2cc42327cf7d08bdb33ca8785d301f260dbc432aeb664c65ca241c8a9. 242 | [warning] (chain) Chain reorganization: 243 | old=1103b6349e5c30a4e390132f2c3cfb108b52dfa8a1b87a1a2790df066027c458(103) 244 | new=62cab7c55d967b45b765fefabfe5aa99287c0f7c0161bec8c8a0ae33eadd3eab(104) 245 | [debug] (wallet) Adding block: 104. 246 | ``` 247 | 248 | Now check the balances of the two nodes: 249 | 250 | ``` 251 | $ bwallet-cli balance 252 | { 253 | "account": -1, 254 | "tx": 203, 255 | "coin": 103, 256 | "unconfirmed": 877500000000, 257 | "confirmed": 877500000000 258 | } 259 | 260 | $ bwallet-cli --http-port=30000 balance 261 | { 262 | "account": -1, 263 | "tx": 0, 264 | "coin": 0, 265 | "unconfirmed": 0, 266 | "confirmed": 0 267 | } 268 | ``` 269 | 270 | # BUMMER. 271 | 272 | ## Homework 273 | 274 | Remember the second node in our regtest network can ALSO generate blocks. 275 | Can you STEAL BACK the 150 BTC that was originally sent to the second node? 276 | 277 | It will involve getting the raw transaction hex (is it still printed to your 278 | terminal somewhere?) and doing our chain reorganization trick again, but this 279 | time broadcasting from the receiver's node using: https://bcoin.io/api-docs/?shell--cli#broadcast-transaction 280 | --------------------------------------------------------------------------------