From b58ead1f57773bb6e946c326bc49682f222a94d6 Mon Sep 17 00:00:00 2001 From: Bucko Date: Fri, 29 Sep 2017 17:39:25 -0700 Subject: [PATCH 01/13] adding new advanced guide --- guides-markdown/crowdfund-tx.md | 174 ++++++++++++ guides/crowdfund-tx.html | 489 ++++++++++++++++++++++++++++++++ 2 files changed, 663 insertions(+) create mode 100644 guides-markdown/crowdfund-tx.md create mode 100644 guides/crowdfund-tx.html diff --git a/guides-markdown/crowdfund-tx.md b/guides-markdown/crowdfund-tx.md new file mode 100644 index 0000000..1e86a92 --- /dev/null +++ b/guides-markdown/crowdfund-tx.md @@ -0,0 +1,174 @@ +# Creating a Crowdfunding Transaction +```post-author +Buck Perley +``` + +## SIGHASH Flags +In most Bitcoin transactions, when a transaction is signed, the entirety of the data in that transaction is committed to in that input's signature, with all outputs and inputs being included in the unlocking script. This, however, doesn't always have to be the case. The level of commitment is indicated by using `SIGHASH` flags. The most common flag is `SIGHASH_ALL` which has a value of 0x01 (you'll see this represented as a `01` at the end of DER-encoded signatures). To learn more about how the flags work and what other options are available, you should checkout [Chapter 6](https://github.com/bitcoinbook/bitcoinbook/blob/8d01749bcf45f69f36cf23606bbbf3f0bd540db3/ch06.asciidoc) of *Mastering Bitcoin* by Andreas Antonopolous. In that chapter, Andreas posits one novel use of a specialized flag, `ALL|ANYONECANPAY`, and in this guide we'll try and build out a couple of examples implementing this idea using bcoin. + +## How it Works +The `ALL|ANYONECANPAY` flag indicates that a signature is committing all of the outputs and just one input. The suggested use case of this proposed in *Mastering Bitcoin* is a kickstarter-like crowdfunding application. Consider the situation where you have a fundraising goal, say 1BTC. You'd like multiple people to contribute to your goal and you want to prove to them that they won't have to pay *unless* you reach your goal (i.e. the transaction is invalid and won't be accepted by the network if attempted to be sent). This means you'd be committing to one output, the one that sends the funds to the charity or project you'd like to donate to, and only one input, your contribution. This allows multiple users to contribute to the same transaction and that transaction won't be valid until it's fully funded. + +Here's how it's explained in *Mastering Bitcoin*: +> ALL|ANYONECANPAY +This construction can be used to make a "crowdfunding”-style transaction. Someone attempting to raise funds can construct a transaction with a single output. The single output pays the "goal" amount to the fundraiser. Such a transaction is obviously not valid, as it has no inputs. However, others can now amend it by adding an input of their own, as a donation. They sign their own input with ALL|ANYONECANPAY. Unless enough inputs are gathered to reach the value of the output, the transaction is invalid. Each donation is a "pledge," which cannot be collected by the fundraiser until the entire goal amount is raised. + +## The Code +We'll walk through the steps of creating the transaction first without any wallet database or node running. Then we'll do the same thing using bcoin's walletdb to manage the keys to see how it would work in a more realistic application. At the end, we'll put out some ideas of how these can be built upon for a more robust, production ready application. (If this is something you'd be interested in building, [get in touch](http://bcoin.io/slack-signup.html)!). If you want to see the code, checkout the [repo on github](https://github.com/Bucko13/bitcoin-fundraise). + +If you're not comfortable with key management, coin selection, and how transactions are constructed, checkout the tutorial on [working with transactions](https://github.com/bcoin-org/bcoin/blob/master/docs/Working-with-transactions.md) first. + +### Version 1 - Manual +#### Step 1: Setup +Let's first start by importing the right tools, setting up some constants, and creating our keychains. (make sure you've installed the latest version of bcoin into your project with `npm install bcoin`) + +```javascript +'use strict'; + +const assert = require('assert'); +const bcoin = require('bcoin'); + +const MTX = bcoin.mtx; +const Keyring = bcoin.keyring; +const Outpoint = bcoin.outpoint; +const Script = bcoin.script; +const Coin = bcoin.coin; +const policy = bcoin.protocol.policy + +const fundingTarget = 100000000; // 1 BTC +const txRate = 10000; // 10000 satoshis/kb +``` + +Let's derive private hd key for fundee and two funders and create a "keyring" object for each (much of this is borrowed from the *Working with Transactions* guide). + +A keyring object is basically a key manager that is also able to tell you info such as: +- your redeem script +- your scripthash +- your program hash +- your pubkey hash +- your scripthash program hash + +```javascript +// Create an HD master keypair. +const master = bcoin.hd.generate(); + +const fundeeKey = master.derive(0); +const fundeeKeyring = new Keyring(fundeeKey.privateKey); +const fundeeAddress = fundeeKeyring.getAddress(); + +// Derive 2 more private hd keys and keyrings for funders +const funder1Key = master.derive(1); +const funder1Keyring = new Keyring(funder1Key.privateKey); + +const funder2Key = master.derive(2); +const funder2Keyring = new Keyring(funder2Key.privateKey); + +const funders = [funder1Keyring, funder2Keyring]; +``` + +#### Step 2: Fund the Keyrings +This example is working in an isolated environment, so it won't work on the actual network (main, test, or otherwise), but we also don't have to earn or send ourselves coins or wait for confirmation times. That means that we can "spawn" coins for our funder wallets that they can use to spend on the crowdfunding platform. + +Let's create some coinbase transactions to give our keyrings some coins that they can spend. + +```javascript +// create some coinbase transactions to fund our wallets +const coins = {}; + +for(let i=0; i < funders.length; i++) { + const cb = new MTX(); + + // Add a typical coinbase input + cb.addInput({ + prevout: new Outpoint(), + script: new Script() + }); + + cb.addOutput({ + address: funders[i].getAddress(), + value: 500000000 // give the funder 5BTC + }); + + assert(cb.inputs[0].isCoinbase()); + + // Convert the coinbase output to a Coin + // object and add it to the available coins for that keyring. + // In reality you might get these coins from a wallet. + coins[i] = [Coin.fromTX(cb, 0, -1)]; +} +``` + +The coins object you've created above should look something like this: + +```json +{ + '0': + [ + { + "type": 'pubkeyhash', + "version": 1, + "height": -1, + "value": '5.0', + "script": , + "coinbase": true, + "hash": '151e5551cdcec5fff06818fb78ac3d584361276e862b5700110ec8321869d650', + "index": 0, + "address": + } + ], + '1': [...] +} +``` + +#### Step 4: Prepare your Coins +Above, we just funded our funder accounts with a single 5BTC outpoint. This means that the next transaction funded from these accounts can only use that one outpoint (or *coin*) as an input and send the remainder back as change. Remember, in Bitcoin the way you send funds is you fund a transaction with a full UTXO (in this case we only have one worth 5BTC available to our keychains) and then send the change back to yourself as an additional output. Since ALL|ANYONECANPAY transactions mean a fixed output, you can't add new change outputs without other signatures becoming invalid which means we need a coin available equal to the amount we want to contribute to the crowdfund. + +So what we want to do is have each funder create a coin (UTXO) with the value of what they want to donate. + +Let's create an asyncronous utility function to help us split the coinbases we created in the previous step (we use the `fund` method on the `MTX` primitive to do this, which is asynchronous). + +```javascript +const splitCoinbase = async function splitCoinbase(keyrings, coins, targetAmount, txRate) { + + // loop through each coinbase coin to split + for(const coinsIndex in coins) { + // funder will be at the same index as the key of the coins we are accessing + const funderKeyring = keyrings[coinsIndex]; + const mtx = new MTX(); + + assert(coins[coinsIndex][0].value > targetAmount, 'coin value is not enough!'); + + // create a transaction that will have an output equal to what we want to fund + mtx.addOutput({ + address: funderKeyring.getAddress(), + value: targetAmount + }); + + // shift off the coinbase coin to use to fund the splitting transaction + // the fund method will automatically split the remaining funds to the change address + await mtx.fund([coins[coinsIndex].shift()], { + rate: txRate, + // send change back to an address belonging to the funder + changeAddress: funderKeyring.getAddress() + }).then(() => { + // sign the mtx to finalize split + mtx.sign(funderKeyring); + assert(mtx.verify()); + + const tx = mtx.toTX(); + assert(tx.verify(mtx.view)); + + const outputs = tx.outputs; + + // get coins from tx + outputs.forEach((outputs, index) => { + coins[coinsIndex].push(Coin.fromTX(tx, index, -1)); + }); + }) + .catch(e => console.log('There was an error: ', e)); + } + + return coins; +}; +``` + diff --git a/guides/crowdfund-tx.html b/guides/crowdfund-tx.html new file mode 100644 index 0000000..e2e4644 --- /dev/null +++ b/guides/crowdfund-tx.html @@ -0,0 +1,489 @@ + + + + + + + + + bcoin | Extending Bitcoin into Enterprise & Production + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
Loading... + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+ + +
+
+ +
+
+ +

 Guides  and  Videos 

+ +
+
+ +
+
+ + + +
+
+ +
+ + + + + +
+ +
+
+ + + +
+
+
+ +

Creating a Crowdfunding Transaction

SIGHASH Flags

In most Bitcoin transactions, when a transaction is signed, the entirety of the data in that transaction is committed to in that input's signature, with all outputs and inputs being included in the unlocking script. This, however, doesn't always have to be the case. The level of commitment is indicated by using SIGHASH flags. The most common flag is SIGHASH_ALL which has a value of 0x01 (you'll see this represented as a 01 at the end of DER-encoded signatures). To learn more about how the flags work and what other options are available, you should checkout Chapter 6 of Mastering Bitcoin by Andreas Antonopolous. In that chapter, Andreas posits one novel use of a specialized flag, ALL|ANYONECANPAY, and in this guide we'll try and build out a couple of examples implementing this idea using bcoin.

+

How it Works

The ALL|ANYONECANPAY flag indicates that a signature is committing all of the outputs and just one input. The suggested use case of this proposed in Mastering Bitcoin is a kickstarter-like crowdfunding application. Consider the situation where you have a fundraising goal, say 1BTC. You'd like multiple people to contribute to your goal and you want to prove to them that they won't have to pay unless you reach your goal (i.e. the transaction is invalid and won't be accepted by the network if attempted to be sent). This means you'd be committing to one output, the one that sends the funds to the charity or project you'd like to donate to, and only one input, your contribution. This allows multiple users to contribute to the same transaction and that transaction won't be valid until it's fully funded.

+

Here's how it's explained in Mastering Bitcoin:

+
+

ALL|ANYONECANPAY +This construction can be used to make a "crowdfunding”-style transaction. Someone attempting to raise funds can construct a transaction with a single output. The single output pays the "goal" amount to the fundraiser. Such a transaction is obviously not valid, as it has no inputs. However, others can now amend it by adding an input of their own, as a donation. They sign their own input with ALL|ANYONECANPAY. Unless enough inputs are gathered to reach the value of the output, the transaction is invalid. Each donation is a "pledge," which cannot be collected by the fundraiser until the entire goal amount is raised.

+
+

The Code

We'll walk through the steps of creating the transaction first without any wallet database or node running. Then we'll do the same thing using bcoin's walletdb to manage the keys to see how it would work in a more realistic application. At the end, we'll put out some ideas of how these can be built upon for a more robust, production ready application. (If this is something you'd be interested in building, get in touch!). If you want to see the code, checkout the repo on github.

+

If you're not comfortable with key management, coin selection, and how transactions are constructed, checkout the tutorial on working with transactions first.

+

Version 1 - Manual

Step 1: Setup

Let's first start by importing the right tools, setting up some constants, and creating our keychains. (make sure you've installed the latest version of bcoin into your project with npm install bcoin)

+
'use strict';
+
+const assert = require('assert');
+const bcoin = require('bcoin');
+
+const MTX = bcoin.mtx;
+const Keyring = bcoin.keyring;
+const Outpoint = bcoin.outpoint;
+const Script = bcoin.script;
+const Coin = bcoin.coin;
+const policy = bcoin.protocol.policy
+
+const fundingTarget = 100000000; // 1 BTC
+const txRate = 10000; // 10000 satoshis/kb

Let's derive private hd key for fundee and two funders and create a "keyring" object for each (much of this is borrowed from the Working with Transactions guide).

+

A keyring object is basically a key manager that is also able to tell you info such as:

+
    +
  • your redeem script
  • +
  • your scripthash
  • +
  • your program hash
  • +
  • your pubkey hash
  • +
  • your scripthash program hash
  • +
+
// Create an HD master keypair.
+const master = bcoin.hd.generate();
+
+const fundeeKey = master.derive(0);
+const fundeeKeyring = new Keyring(fundeeKey.privateKey);
+const fundeeAddress = fundeeKeyring.getAddress();
+
+// Derive 2 more private hd keys and keyrings for funders
+const funder1Key = master.derive(1);
+const funder1Keyring = new Keyring(funder1Key.privateKey);
+
+const funder2Key = master.derive(2);
+const funder2Keyring = new Keyring(funder2Key.privateKey);
+
+const funders = [funder1Keyring, funder2Keyring];

Step 2: Fund the Keyrings

This example is working in an isolated environment, so it won't work on the actual network (main, test, or otherwise), but we also don't have to earn or send ourselves coins or wait for confirmation times. That means that we can "spawn" coins for our funder wallets that they can use to spend on the crowdfunding platform.

+

Let's create some coinbase transactions to give our keyrings some coins that they can spend.

+
// create some coinbase transactions to fund our wallets
+const coins = {};
+
+for(let i=0; i < funders.length; i++) {
+  const cb = new MTX();
+
+  // Add a typical coinbase input
+  cb.addInput({
+    prevout: new Outpoint(),
+    script: new Script()
+  });
+
+  cb.addOutput({
+    address: funders[i].getAddress(),
+    value: 500000000 // give the funder 5BTC
+  });
+
+  assert(cb.inputs[0].isCoinbase());
+
+  // Convert the coinbase output to a Coin
+  // object and add it to the available coins for that keyring.
+  // In reality you might get these coins from a wallet.
+  coins[i] = [Coin.fromTX(cb, 0, -1)];
+}

The coins object you've created above should look something like this:

+
{
+  '0':
+      [
+        {
+          "type": 'pubkeyhash',
+          "version": 1,
+          "height": -1,
+          "value": '5.0',
+          "script": <Script: OP_DUP OP_HASH160 0x14 0x64cc4e55b2daec25431bd879ef39302a77c1c1ce OP_EQUALVERIFY OP_CHECKSIG>,
+          "coinbase": true,
+          "hash": '151e5551cdcec5fff06818fb78ac3d584361276e862b5700110ec8321869d650',
+          "index": 0,
+          "address": <Address: type=pubkeyhash version=-1 str=mphvcfcFneRZvyYsmzhy57cSDzFbGrWaRb>
+        }
+      ],
+  '1': [...]
+}
+ + +
+
+
+
+
+ + +
+ + + + +
+ +
+
+ + +
+ + + + + + + +
+
+ +
+
+
+

Ready to start building? Read the docs!

+ Documentation +
+
+
+ +
+
+ + + +
+
+ +
+
+ +
+
+
+
+ +

Bcoin Development Donation Address:
3Bi9H1hzCHWJoFEjc4xzVzEMywi35dyvsV

+ +
+
+
+
+

Copyright bcoin.io, Purse, 2017, All Rights Reserved.

+ +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From c1878bdd4b459f0609ba923e8dca15d5adab81b3 Mon Sep 17 00:00:00 2001 From: Bucko Date: Sun, 1 Oct 2017 18:23:59 -0700 Subject: [PATCH 02/13] finished templating guide --- guides-markdown/crowdfund-tx.md | 141 ++++++++++++++++++++++++++++++-- 1 file changed, 135 insertions(+), 6 deletions(-) diff --git a/guides-markdown/crowdfund-tx.md b/guides-markdown/crowdfund-tx.md index 1e86a92..22f71a1 100644 --- a/guides-markdown/crowdfund-tx.md +++ b/guides-markdown/crowdfund-tx.md @@ -20,7 +20,9 @@ If you're not comfortable with key management, coin selection, and how transacti ### Version 1 - Manual #### Step 1: Setup -Let's first start by importing the right tools, setting up some constants, and creating our keychains. (make sure you've installed the latest version of bcoin into your project with `npm install bcoin`) +Let's first start by importing the right tools, setting up some constants, and creating our keychains. (make sure you've installed the latest version of bcoin into your project with `npm install bcoin`). + +Note that we're setting the fundingTarget and amountToFund as constants for simplicity, but they could be set based on user input or some other variable circumstances. ```javascript 'use strict'; @@ -36,6 +38,7 @@ const Coin = bcoin.coin; const policy = bcoin.protocol.policy const fundingTarget = 100000000; // 1 BTC +const amountToFund = 50000000; // .5 BTC const txRate = 10000; // 10000 satoshis/kb ``` @@ -102,21 +105,21 @@ The coins object you've created above should look something like this: ```json { - '0': + "0": [ { - "type": 'pubkeyhash', + "type": "pubkeyhash", "version": 1, "height": -1, - "value": '5.0', + "value": "5.0", "script": , "coinbase": true, - "hash": '151e5551cdcec5fff06818fb78ac3d584361276e862b5700110ec8321869d650', + "hash": "151e5551cdcec5fff06818fb78ac3d584361276e862b5700110ec8321869d650", "index": 0, "address": } ], - '1': [...] + "1": [...] } ``` @@ -172,3 +175,129 @@ const splitCoinbase = async function splitCoinbase(keyrings, coins, targetAmount }; ``` +Because of the async methods being used, in order to take advantage of the async/await structure, the rest of the code will be enclosed in an async function. + +The first thing we'll do is split the coinbase coins we created earlier using the utility function we just built. + +```javascript +const composeCrowdfund = async function composeCrowdfund() { + const funderCoins = await splitCoinbase(funders, coins, amountToFund, txRate); + // ... we'll keep filling out the rest of the code here +}; +``` + +`funderCoins` should return x number of coin arrays, where X is the number of coinbases we created earlier (should be 2) with each array having a coin equal to the amount we want to donate. + +For example... + +```json +{ + "0": + [ + { + "type": "pubkeyhash", + "version": 1, + "height": -1, + "value": "0.5", + "script": , + "coinbase": false, + "hash": "774822d84bd5af02f1b3eacd6215e0a1bcf07cfb6675a000c8a01d2ea34f2a32", + "index": 0, + "address": + }, + ... + ], + "1": [...] +} +``` +#### Step 5: Calculating the Fee + +We have a tricky problem now. In a real world situation you're not going to know how many inputs (i.e. funders) you will have. But the more inputs you have, the bigger the transaction and thus the higher the fee you will need to broadcast it. The best we can do is to estimate the size based off of the max number of inputs we are willing to accept. + +In our example, we know there are two inputs. In a more complex application, you might put a cap of say 5, then estimate the fee based on that. If there turn out to be fewer then you just have a relatively high fee. + +So, let's next add a utility for estimating a max fee. The way this will work is by creating a mock transaction that uses one of our coins to add inputs up to our maximum, calculate the size of the transaction and the fee based on that amount. We'll also build a utility that we'll need again later for adding coins to a tx and signing the input created. + +```javascript +const getMaxFee = function getMaxFee(maxInputs, coin, address, keyring, rate) { + const fundingTarget = 100000000; // 1 BTC (arbitrary for purposes of this function) + const testMTX = new MTX(); + + testMTX.addOutput({ value: fundingTarget, address }) + + while(testMTX.inputs.length < maxInputs) { + const index = testMTX.inputs.length; + addInput(coin, index, testMTX, keyring); + } + + return testMTX.getMinFee(null, rate); +} + +const addInput = function addInput(coin, inputIndex, mtx, keyring, hashType) { + const sampleCoin = coin instanceof Coin ? coin : Coin.fromJSON(coin); + if(!hashType) hashType = Script.hashType.ANYONECANPAY | Script.hashType.ALL; + + mtx.addCoin(sampleCoin); + mtx.scriptInput(inputIndex, sampleCoin, keyring); + mtx.signInput(inputIndex, sampleCoin, keyring, hashType); + assert(mtx.isSigned(), 'Input was not signed properly'); +} + +``` + +Now we can get the fee for our funder transaction. We'll assume a max of 2 inputs, but this can be variable. + +```javascript +const composeCrowdfund = async function composeCrowdfund() { + //... + const maxInputs = 2; + const maxFee = getMaxFee( + maxInputs, + funderCoins['0'][0], + fundeeAddress, + funder1Keyring, + txRate + ); + + console.log(`Based on a rate of ${txRate} satoshis/kb and a tx with max ${maxInputs}`); + console.log(`the tx fee should be ${maxFee} satoshis`); +}; +``` +This should log something like: +> Based on a rate of 10000 satoshis/kb and a tx with max 2 the tx fee should be 3380 satoshis + +#### Step 6: Construct the Transaction +Now that we've got our tools and coins ready, we can start to build the transaction! Note that we are going to use the maxFee we calculated earlier and subtract it from the output we add to the mtx. + +```javascript +const composeCrowdfund = async function composeCrowdfund() { + //... + + const fundMe = new MTX(); + + // add an output with the target funding amount minus the fee calculated earlier + fundMe.addOutput({ value: fundingTarget - maxFee, address: fundeeAddress }); + + // fund with first funder (using the utility we built earlier) + let fundingCoin = funderCoins['0'][0]; + addInput(fundingCoin, 0, fundMe, funder1Keyring); + + // fund with second funder + fundingCoin = funderCoins['1'][0]; + addInput(fundingCoin, 1, fundMe, funder2Keyring); + + // We want to confirm that total value of inputs covers the funding goal + // NOTE: the difference goes to the miner in the form of fees + assert(fundMe.getInputValue() >= fundMe.outputs[0].value, 'Total inputs not enough to fund'); + assert(fundMe.verify(), 'The mtx is malformed'); + + const tx = fundMe.toTX(); + assert(tx.verify(fundMe.view), 'there is a problem with your tx'); + + return tx; +}; + +composeCrowdfund().then(myCrowdfundTx => console.log(myCrowdfundTx)); +``` + +`composeCrowdfund()` will now return a promise (thanks to async/await) that returns a fully templated transaction that can be transmitted to the network. It should have two inputs and one output equal to the funding target minus the fee. You should also notice the `SIGHASH` flag 0x81 at the end of the input scripts which confirms they are `ALL|ANYONECANPAY` scripts. \ No newline at end of file From 895eaf45f080e85f08e2e579d292006fca64249f Mon Sep 17 00:00:00 2001 From: Bucko Date: Sun, 1 Oct 2017 19:49:14 -0700 Subject: [PATCH 03/13] finished guide for transaction including wallet version --- guides-markdown/crowdfund-tx.md | 190 +++++++++++++++++++- guides/crowdfund-tx.html | 310 +++++++++++++++++++++++++++++++- 2 files changed, 490 insertions(+), 10 deletions(-) diff --git a/guides-markdown/crowdfund-tx.md b/guides-markdown/crowdfund-tx.md index 22f71a1..0b40796 100644 --- a/guides-markdown/crowdfund-tx.md +++ b/guides-markdown/crowdfund-tx.md @@ -18,7 +18,7 @@ We'll walk through the steps of creating the transaction first without any walle If you're not comfortable with key management, coin selection, and how transactions are constructed, checkout the tutorial on [working with transactions](https://github.com/bcoin-org/bcoin/blob/master/docs/Working-with-transactions.md) first. -### Version 1 - Manual +### Version 1 - Manual Key Management #### Step 1: Setup Let's first start by importing the right tools, setting up some constants, and creating our keychains. (make sure you've installed the latest version of bcoin into your project with `npm install bcoin`). @@ -264,7 +264,7 @@ const composeCrowdfund = async function composeCrowdfund() { }; ``` This should log something like: -> Based on a rate of 10000 satoshis/kb and a tx with max 2 the tx fee should be 3380 satoshis +> Based on a rate of 10000 satoshis/kb and a tx with max 2 inputs, the tx fee should be 3380 satoshis #### Step 6: Construct the Transaction Now that we've got our tools and coins ready, we can start to build the transaction! Note that we are going to use the maxFee we calculated earlier and subtract it from the output we add to the mtx. @@ -300,4 +300,188 @@ const composeCrowdfund = async function composeCrowdfund() { composeCrowdfund().then(myCrowdfundTx => console.log(myCrowdfundTx)); ``` -`composeCrowdfund()` will now return a promise (thanks to async/await) that returns a fully templated transaction that can be transmitted to the network. It should have two inputs and one output equal to the funding target minus the fee. You should also notice the `SIGHASH` flag 0x81 at the end of the input scripts which confirms they are `ALL|ANYONECANPAY` scripts. \ No newline at end of file +`composeCrowdfund()` will now return a promise (thanks to async/await) that returns a fully templated transaction that can be transmitted to the network. It should have two inputs and one output equal to the funding target minus the fee. You should also notice the `SIGHASH` flag 0x81 at the end of the input scripts which confirms they are `ALL|ANYONECANPAY` scripts. + +### Version 2: Using the Bcoin Wallet System +Since trying to manage your own keys is pretty tedious, not to mention impractical especially if you want to let other people contribute to your campaign, let's use the bcoin wallet system to take care of that part of the process. For the this example, we're going to interact via the wallet client, but you could also do something similar within a bcoin node (using the wallet and walletdb directly) which would also be more secure. + +Note that this is a little more tedious to test since you need to have funded wallets in order to actually fund your campaign. You can find a bitcoin testnet faucet to get some funds to play with or, as we will do in this example, create your own simnet or regtest network where you mine your own blocks to fund yourself. + +#### Step 1: Setup Our Wallets +We'll skip the setup of constants since and import modules since we can use the same from the previous example. Since there are a lot of asynchronous operations here now that we're using the client, we'll also put this in an async function and will continue to build out the contents of the function throughout this guide. + +```javascript +const network = 'simnet'; +const composeWalletCrowdfund = async function composeWalletCrowdfund() { + const client = await new bcoin.http.Client({ network }); + + // Step 1: Setup our wallets and funding targets + const fundeeWallet = await new httpWallet({ id: 'fundee', network }); + const fundeeAddress = await fundeeWallet.createAddress('default'); + const funders = { + 'funder1': await new httpWallet({ id: 'funder1', network }), + 'funder2': await new httpWallet({ id: 'funder2', network }) + }; + //... +} +``` + +#### Step 2: Prepare Your Coins +We have the same issue to deal with here as in step 2 in the previous example: we need to have "exact change" coins available for funding. The process of splitting is a little different with the wallet system though so let's walk through this version + +```javascript +const composeWalletCrowdfund = async function composeWalletCrowdfund() { + //... + + const fundingCoins = {}; + + // go through each funding wallet to prepare coins + for(let id in funders) { + const funder = funders[id]; + + const coins = await funder.getCoins(); + const funderInfo = await funder.getInfo(); + + // go through available coins to find a coin equal to or greater than value to fund + + // We didn't do this before because we knew what coins were available. But if we have one already in our wallets, then we can just use that! + let fundingCoin = {}; + for(let coin of coins) { + if (coin.value === amountToFund) { + // if we already have a coin of the right value we can use that + fundingCoin = coin; + break; + } + } + + if (!Object.keys(fundingCoin).length) { + // if we don't have a coin of the right amount to fund with + // we need to create one by sending the funder wallet + // a tx that includes an output of the right amount + + // this is similar to what we did in the manual version + const receiveAddress = await funder.createAddress('default') // send it back to the funder + const tx = await funder.send({ + rate, + outputs: [{ + value: amountToFund, + address: receiveAddress.address + }] + }); + + // get index of ouput for fundingCoin + let coinIndex; + for (let i=0; i < tx.outputs.length; i++) { + if (tx.outputs[i].value === amountToFund) { + coinIndex = i; + break; + } + } + + assert(tx.outputs[coinIndex].value === amountToFund, 'value of output at index not correct'); + + // first argument is for the account + // default is being used for all examples + fundingCoin = await funder.getCoin('default', tx.hash, coinIndex); + } + fundingCoins[funder.id] = fundingCoin; + } +} +``` + +We now should have an object available that has the information of the coins we will be using to fund our transaction with mapped to the name of each wallet (so we can retrieve the wallet information later). It should look something like this: + +```json +{ + "funder1": { + "version": 1, + "height": -1, + "value": 50000000, + "script": "76a914127cb1a40212169c49fe22d13307b18af1fa07ad88ac", + "address": "SNykaBMuTyeUQkK8exZyymWNrnYX5vVPuY", + "coinbase": false, + "hash": "163068016a39e2d9c869bcdb8646dbca93e07824db39217b5c444e7c61d1a82c", + "index": 0 + }, + "funder2": { + "version": 1, + "height": -1, + "value": 50000000, + "script": "76a9146e08c73b355e690ba0b1198d578c4c6c52b3813688ac", + "address": "SXKossD65D7h62fhzGrntERBrPeXUfiC92", + "coinbase": false, + "hash": "11f3180c5069f2692f1ee1463257b21dc217441e792493c8f5ee230c35d97d96", + "index": 0 + } +} +``` + +#### Step 3: Create our Crowdfund Tx +We also have to include the `getmaxFee` step as before, but we can use the same utility that we created for the other example. The way that we're funding the inputs is pretty insecure as we are sending private keys unencrypted across the network, but we'll leave for the sake of the example. **DON'T USE THIS IN PRODUCTION**. + +```javascript +const composeWalletCrowdfund = async function composeWalletCrowdfund() { + //... + const testKey = await funders['funder1'].getWIF(fundingCoins['funder1'].address); + const testKeyring = new bcoin.keyring.fromSecret(testKey.privateKey); + const maxFee = getMaxFee(maxInputs, fundingCoins['funder1'], fundeeAddress.address, testKeyring, rate); + + const fundMe = new MTX(); + + // Use the maxFee to calculate output value for transaction + fundMe.addOutput({value: fundingTarget - maxFee, address: fundeeAddress.address }); + + // go through our coins and add each as an input in our transaction + let inputCounter = 0; + for(let funder in fundingCoins) { + const wallet = funders[funder]; + const coinOptions = fundingCoins[funder]; + + const key = await wallet.getWIF(coinOptions.address); + const keyring = new bcoin.keyring.fromSecret(key.privateKey); + + // this is the same utility as we used in our other example + addInput(coinOptions, inputCounter, fundMe, keyring); + assert(fundMe.isSigned(), 'Input has not been signed correctly'); + inputCounter++; + } + + // confirm that the transaction has been properly templated and signed + assert( + fundMe.inputs.length === Object.keys(funders).length, + 'Number of inputs in MTX is incorrect' + ); + assert(fundMe.verify(), 'MTX is malformed'); + + // make our transaction immutable so we can send it to th enetwork + const tx = fundMe.toTX(); + + assert(tx.verify(fundMe.view), 'TX is malformed. Fix before broadcasting'); + + // Step 6: broadcast tx + try { + const broadcastStatus = await client.broadcast(tx); + return tx; + } catch (e) { + console.log('There was a problem: ', e); + } +} +composeWalletCrowdfund() + .then(myCrowdfundTx => console.log('Transaction broadcast: ', myCrowdfundTx)) + .catch(e => console.log('There was a problem: ', e)); +``` + +And there you have it! If you were doing this on testnet, your `fundeeWallet` should now be 1BTC richer. If you're on a simnet or regtest network, you'll have to mine a block with your transactions to get those funds confirmed. Also note that, unless you have exact change coins, there will be 3 transactions that need to be confirmed: one each for the wallets that are splitting coins, and one for the crowdfund transaction. + +## How to Build it Out +These examples are obviously pretty basic, but they should give you an idea of how to use Bitcoin's scripting to build out the foundation for more complex applications. Here are some ideas on how you could build on top of these examples and get closer to a production ready application. + +- More flexible contribution scheme (currently it's just 2 funders that split the amount evenly). E.g. custom number of contributers, custom contribution amount, etc. +- UX to let people interact with the transaction via a browser +- More advanced interface for fee estimation and include platform for large number of funders (for example, since you may be limited to number of funders per tx, you could include interface for multiple transactions for a single campaign. You would also want to include a check to make sure your tx is not bigger than 100kb otherwise it'll get rejected by the network) +- Add a fund matching scheme where someone can say they will match future contributions +- Currently the examples split transactions to make a coin available that equals the target contribution amount. This expensive since you ahve broadcast multiple transactions. An interface to choose to donate from available available coins might help to make this more efficient. + +Make sure to get in touch with us on Twitter or Slack if you build out any of these ideas! + +Again, for a working example of the code (without all the text and explanations), check out [the repo on github](https://github.com/Bucko13/bitcoin-fundraise). \ No newline at end of file diff --git a/guides/crowdfund-tx.html b/guides/crowdfund-tx.html index e2e4644..c57755c 100644 --- a/guides/crowdfund-tx.html +++ b/guides/crowdfund-tx.html @@ -311,7 +311,8 @@ This construction can be used to make a "crowdfunding”-style transaction.

The Code

We'll walk through the steps of creating the transaction first without any wallet database or node running. Then we'll do the same thing using bcoin's walletdb to manage the keys to see how it would work in a more realistic application. At the end, we'll put out some ideas of how these can be built upon for a more robust, production ready application. (If this is something you'd be interested in building, get in touch!). If you want to see the code, checkout the repo on github.

If you're not comfortable with key management, coin selection, and how transactions are constructed, checkout the tutorial on working with transactions first.

-

Version 1 - Manual

Step 1: Setup

Let's first start by importing the right tools, setting up some constants, and creating our keychains. (make sure you've installed the latest version of bcoin into your project with npm install bcoin)

+

Version 1 - Manual Key Management

Step 1: Setup

Let's first start by importing the right tools, setting up some constants, and creating our keychains. (make sure you've installed the latest version of bcoin into your project with npm install bcoin).

+

Note that we're setting the fundingTarget and amountToFund as constants for simplicity, but they could be set based on user input or some other variable circumstances.

'use strict';
 
 const assert = require('assert');
@@ -325,6 +326,7 @@ This construction can be used to make a "crowdfunding”-style transaction.
 const policy = bcoin.protocol.policy
 
 const fundingTarget = 100000000; // 1 BTC
+const amountToFund = 50000000; // .5 BTC
 const txRate = 10000; // 10000 satoshis/kb

Let's derive private hd key for fundee and two funders and create a "keyring" object for each (much of this is borrowed from the Working with Transactions guide).

A keyring object is basically a key manager that is also able to tell you info such as:

    @@ -375,22 +377,316 @@ This construction can be used to make a "crowdfunding”-style transaction. coins[i] = [Coin.fromTX(cb, 0, -1)]; }

    The coins object you've created above should look something like this:

    {
    -  '0':
    +  "0":
           [
             {
    -          "type": 'pubkeyhash',
    +          "type": "pubkeyhash",
               "version": 1,
               "height": -1,
    -          "value": '5.0',
    +          "value": "5.0",
               "script": <Script: OP_DUP OP_HASH160 0x14 0x64cc4e55b2daec25431bd879ef39302a77c1c1ce OP_EQUALVERIFY OP_CHECKSIG>,
               "coinbase": true,
    -          "hash": '151e5551cdcec5fff06818fb78ac3d584361276e862b5700110ec8321869d650',
    +          "hash": "151e5551cdcec5fff06818fb78ac3d584361276e862b5700110ec8321869d650",
               "index": 0,
               "address": <Address: type=pubkeyhash version=-1 str=mphvcfcFneRZvyYsmzhy57cSDzFbGrWaRb>
             }
           ],
    -  '1': [...]
    -}
    + "1": [...] +}

    Step 4: Prepare your Coins

    Above, we just funded our funder accounts with a single 5BTC outpoint. This means that the next transaction funded from these accounts can only use that one outpoint (or coin) as an input and send the remainder back as change. Remember, in Bitcoin the way you send funds is you fund a transaction with a full UTXO (in this case we only have one worth 5BTC available to our keychains) and then send the change back to yourself as an additional output. Since ALL|ANYONECANPAY transactions mean a fixed output, you can't add new change outputs without other signatures becoming invalid which means we need a coin available equal to the amount we want to contribute to the crowdfund.

    +

    So what we want to do is have each funder create a coin (UTXO) with the value of what they want to donate.

    +

    Let's create an asyncronous utility function to help us split the coinbases we created in the previous step (we use the fund method on the MTX primitive to do this, which is asynchronous).

    +
    const splitCoinbase = async function splitCoinbase(keyrings, coins, targetAmount, txRate) {
    +
    +  // loop through each coinbase coin to split
    +  for(const coinsIndex in coins) {
    +    // funder will be at the same index as the key of the coins we are accessing
    +    const funderKeyring = keyrings[coinsIndex];
    +    const mtx = new MTX();
    +
    +    assert(coins[coinsIndex][0].value > targetAmount, 'coin value is not enough!');
    +
    +    // create a transaction that will have an output equal to what we want to fund
    +    mtx.addOutput({
    +      address: funderKeyring.getAddress(),
    +      value: targetAmount
    +    });
    +
    +    // shift off the coinbase coin to use to fund the splitting transaction
    +    // the fund method will automatically split the remaining funds to the change address
    +    await mtx.fund([coins[coinsIndex].shift()], {
    +      rate: txRate,
    +      // send change back to an address belonging to the funder
    +      changeAddress: funderKeyring.getAddress()
    +    }).then(() => {
    +      // sign the mtx to finalize split
    +      mtx.sign(funderKeyring);
    +      assert(mtx.verify());
    +
    +      const tx = mtx.toTX();
    +      assert(tx.verify(mtx.view));
    +
    +      const outputs = tx.outputs;
    +
    +      // get coins from tx
    +      outputs.forEach((outputs, index) => {
    +        coins[coinsIndex].push(Coin.fromTX(tx, index, -1));
    +      });
    +    })
    +    .catch(e => console.log('There was an error: ', e));
    +  }
    +
    +  return coins;
    +};

    Because of the async methods being used, in order to take advantage of the async/await structure, the rest of the code will be enclosed in an async function.

    +

    The first thing we'll do is split the coinbase coins we created earlier using the utility function we just built.

    +
    const composeCrowdfund = async function composeCrowdfund() {
    +  const funderCoins = await splitCoinbase(funders, coins, amountToFund, txRate);
    +  // ... we'll keep filling out the rest of the code here
    +};

    funderCoins should return x number of coin arrays, where X is the number of coinbases we created earlier (should be 2) with each array having a coin equal to the amount we want to donate.

    +

    For example...

    +
    {
    +  "0":
    +     [
    +      {
    +        "type": "pubkeyhash",
    +        "version": 1,
    +        "height": -1,
    +        "value": "0.5",
    +        "script": <Script: OP_DUP OP_HASH160 0x14 0x62f725e83caf894aa6c3efd29ef28649fc448825 OP_EQUALVERIFY OP_CHECKSIG>,
    +        "coinbase": false,
    +        "hash": "774822d84bd5af02f1b3eacd6215e0a1bcf07cfb6675a000c8a01d2ea34f2a32",
    +        "index": 0,
    +        "address": <Address: type=pubkeyhash version=-1 str=mpYEb17KR7MVhuPZT1GsW3SywZx8ihYube>
    +       },
    +         ...
    +      ],
    +  "1": [...]
    +}

    Step 5: Calculating the Fee

    We have a tricky problem now. In a real world situation you're not going to know how many inputs (i.e. funders) you will have. But the more inputs you have, the bigger the transaction and thus the higher the fee you will need to broadcast it. The best we can do is to estimate the size based off of the max number of inputs we are willing to accept.

    +

    In our example, we know there are two inputs. In a more complex application, you might put a cap of say 5, then estimate the fee based on that. If there turn out to be fewer then you just have a relatively high fee.

    +

    So, let's next add a utility for estimating a max fee. The way this will work is by creating a mock transaction that uses one of our coins to add inputs up to our maximum, calculate the size of the transaction and the fee based on that amount. We'll also build a utility that we'll need again later for adding coins to a tx and signing the input created.

    +
    const getMaxFee = function getMaxFee(maxInputs, coin, address, keyring, rate) {
    +  const fundingTarget = 100000000; // 1 BTC (arbitrary for purposes of this function)
    +  const testMTX = new MTX();
    +
    +  testMTX.addOutput({ value: fundingTarget, address })
    +
    +  while(testMTX.inputs.length < maxInputs) {
    +    const index = testMTX.inputs.length;
    +    addInput(coin, index, testMTX, keyring);
    +  }
    +
    +  return testMTX.getMinFee(null, rate);
    +}
    +
    +const addInput = function addInput(coin, inputIndex, mtx, keyring, hashType) {
    +  const sampleCoin = coin instanceof Coin ? coin : Coin.fromJSON(coin);
    +  if(!hashType) hashType = Script.hashType.ANYONECANPAY | Script.hashType.ALL;
    +
    +  mtx.addCoin(sampleCoin);
    +  mtx.scriptInput(inputIndex, sampleCoin, keyring);
    +  mtx.signInput(inputIndex, sampleCoin, keyring, hashType);
    +  assert(mtx.isSigned(), 'Input was not signed properly');
    +}

    Now we can get the fee for our funder transaction. We'll assume a max of 2 inputs, but this can be variable.

    +
    const composeCrowdfund = async function composeCrowdfund() {
    +  //...
    +  const maxInputs = 2;
    +  const maxFee = getMaxFee(
    +    maxInputs,
    +    funderCoins['0'][0],
    +    fundeeAddress,
    +    funder1Keyring,
    +    txRate
    +  );
    +
    +  console.log(`Based on a rate of ${txRate} satoshis/kb and a tx with max ${maxInputs}`);
    +  console.log(`the tx fee should be ${maxFee} satoshis`);
    +};

    This should log something like:

    +
    +

    Based on a rate of 10000 satoshis/kb and a tx with max 2 the tx fee should be 3380 satoshis

    +
    +

    Step 6: Construct the Transaction

    Now that we've got our tools and coins ready, we can start to build the transaction! Note that we are going to use the maxFee we calculated earlier and subtract it from the output we add to the mtx.

    +
    const composeCrowdfund = async function composeCrowdfund() {
    +  //...
    +
    +  const fundMe = new MTX();
    +
    +  // add an output with the target funding amount minus the fee calculated earlier
    +  fundMe.addOutput({ value: fundingTarget - maxFee, address: fundeeAddress });
    +
    +  // fund with first funder (using the utility we built earlier)
    +  let fundingCoin = funderCoins['0'][0];
    +  addInput(fundingCoin, 0, fundMe, funder1Keyring);
    +
    +  // fund with second funder
    +  fundingCoin = funderCoins['1'][0];
    +  addInput(fundingCoin, 1, fundMe, funder2Keyring);
    +
    +  // We want to confirm that total value of inputs covers the funding goal
    +  // NOTE: the difference goes to the miner in the form of fees
    +  assert(fundMe.getInputValue() >= fundMe.outputs[0].value, 'Total inputs not enough to fund');
    +  assert(fundMe.verify(), 'The mtx is malformed');
    +
    +  const tx = fundMe.toTX();
    +  assert(tx.verify(fundMe.view), 'there is a problem with your tx');
    +
    +  return tx;
    +};
    +
    +composeCrowdfund().then(myCrowdfundTx => console.log(myCrowdfundTx));

    composeCrowdfund() will now return a promise (thanks to async/await) that returns a fully templated transaction that can be transmitted to the network. It should have two inputs and one output equal to the funding target minus the fee. You should also notice the SIGHASH flag 0x81 at the end of the input scripts which confirms they are ALL|ANYONECANPAY scripts.

    +

    Version 2: Using the Bcoin Wallet System

    Since trying to manage your own keys is pretty tedious, not to mention impractical especially if you want to let other people contribute to your campaign, let's use the bcoin wallet system to take care of that part of the process. For the this example, we're going to interact via the wallet client, but you could also do something similar within a bcoin node (using the wallet and walletdb directly) which would also be more secure.

    +

    Note that this is a little more tedious to test since you need to have funded wallets in order to actually fund your campaign. You can find a bitcoin testnet faucet to get some funds to play with or, as we will do in this example, create your own simnet or regtest network where you mine your own blocks to fund yourself.

    +

    Step 1: Setup Our Wallets

    We'll skip the setup of constants since and import modules since we can use the same from the previous example. Since there are a lot of asynchronous operations here now that we're using the client, we'll also put this in an async function and will continue to build out the contents of the function throughout this guide.

    +
    const network = 'simnet';
    +const composeWalletCrowdfund = async function composeWalletCrowdfund() {
    +  const client = await new bcoin.http.Client({ network });
    +
    +  // Step 1: Setup our wallets and funding targets
    +  const fundeeWallet = await new httpWallet({ id: 'fundee', network });
    +  const fundeeAddress = await fundeeWallet.createAddress('default');
    +  const funders = {
    +    'funder1': await new httpWallet({ id: 'funder1', network }),
    +    'funder2': await new httpWallet({ id: 'funder2', network })
    +  };
    +  //...
    +}

    Step 2: Prepare Your Coins

    We have the same issue to deal with here as in step 2 in the previous example: we need to have "exact change" coins available for funding. The process of splitting is a little different with the wallet system though so let's walk through this version

    +
    const composeWalletCrowdfund = async function composeWalletCrowdfund() {
    +  //...
    +
    +  const fundingCoins = {};
    +
    +  // go through each funding wallet to prepare coins
    +  for(let id in funders) {
    +    const funder = funders[id];
    +
    +    const coins = await funder.getCoins();
    +    const funderInfo = await funder.getInfo();
    +
    +    // go through available coins to find a coin equal to or greater than value to fund
    +
    +    // We didn't do this before because we knew what coins were available. But if we have one already in our wallets, then we can just use that!
    +    let fundingCoin = {};
    +    for(let coin of coins) {
    +      if (coin.value === amountToFund) {
    +        // if we already have a coin of the right value we can use that
    +        fundingCoin = coin;
    +        break;
    +      }
    +    }
    +
    +    if (!Object.keys(fundingCoin).length) {
    +      // if we don't have a coin of the right amount to fund with
    +      // we need to create one by sending the funder wallet
    +      // a tx that includes an output of the right amount
    +
    +      // this is similar to what we did in the manual version
    +      const receiveAddress = await funder.createAddress('default') // send it back to the funder
    +      const tx = await funder.send({
    +        rate,
    +        outputs: [{
    +          value: amountToFund,
    +          address: receiveAddress.address
    +        }]
    +      });
    +
    +      // get index of ouput for fundingCoin
    +      let coinIndex;
    +      for (let i=0; i < tx.outputs.length; i++) {
    +        if (tx.outputs[i].value === amountToFund) {
    +          coinIndex = i;
    +          break;
    +        }
    +      }
    +
    +      assert(tx.outputs[coinIndex].value === amountToFund, 'value of output at index not correct');
    +
    +      // first argument is for the account
    +      // default is being used for all examples
    +      fundingCoin = await funder.getCoin('default', tx.hash, coinIndex);
    +    }
    +    fundingCoins[funder.id] = fundingCoin;
    +  }
    +}

    We now should have an object available that has the information of the coins we will be using to fund our transaction with mapped to the name of each wallet (so we can retrieve the wallet information later). It should look something like this:

    +
    {
    +  "funder1": {
    +    "version": 1,
    +    "height": -1,
    +    "value": 50000000,
    +    "script": "76a914127cb1a40212169c49fe22d13307b18af1fa07ad88ac",
    +    "address": "SNykaBMuTyeUQkK8exZyymWNrnYX5vVPuY",
    +    "coinbase": false,
    +    "hash": "163068016a39e2d9c869bcdb8646dbca93e07824db39217b5c444e7c61d1a82c",
    +    "index": 0
    +  },
    +  "funder2": {
    +    "version": 1,
    +    "height": -1,
    +    "value": 50000000,
    +    "script": "76a9146e08c73b355e690ba0b1198d578c4c6c52b3813688ac",
    +    "address": "SXKossD65D7h62fhzGrntERBrPeXUfiC92",
    +    "coinbase": false,
    +    "hash": "11f3180c5069f2692f1ee1463257b21dc217441e792493c8f5ee230c35d97d96",
    +    "index": 0
    +  }
    +}

    Step 3: Create our Crowdfund Tx

    We also have to include the getmaxFee step as before, but we can use the same utility that we created for the other example. The way that we're funding the inputs is pretty insecure as we are sending private keys unencrypted across the network, but we'll leave for the sake of the example. DON'T USE THIS IN PRODUCTION.

    +
    const composeWalletCrowdfund = async function composeWalletCrowdfund() {
    +  //...
    +  const testKey = await funders['funder1'].getWIF(fundingCoins['funder1'].address);
    +  const testKeyring = new bcoin.keyring.fromSecret(testKey.privateKey);
    +  const maxFee = getMaxFee(maxInputs, fundingCoins['funder1'], fundeeAddress.address, testKeyring, rate);
    +
    +  const fundMe = new MTX();
    +
    +  // Use the maxFee to calculate output value for transaction
    +  fundMe.addOutput({value: fundingTarget - maxFee, address: fundeeAddress.address });
    +
    +  // go through our coins and add each as an input in our transaction
    +  let inputCounter = 0;
    +  for(let funder in fundingCoins) {
    +    const wallet = funders[funder];
    +    const coinOptions = fundingCoins[funder];
    +
    +    const key = await wallet.getWIF(coinOptions.address);
    +    const keyring = new bcoin.keyring.fromSecret(key.privateKey);
    +
    +    // this is the same utility as we used in our other example
    +    addInput(coinOptions, inputCounter, fundMe, keyring);
    +    assert(fundMe.isSigned(), 'Input has not been signed correctly');
    +    inputCounter++;
    +  }
    +
    +  // confirm that the transaction has been properly templated and signed
    +  assert(
    +    fundMe.inputs.length === Object.keys(funders).length,
    +    'Number of inputs in MTX is incorrect'
    +  );
    +  assert(fundMe.verify(), 'MTX is malformed');
    +
    +  // make our transaction immutable so we can send it to th enetwork
    +  const tx = fundMe.toTX();
    +
    +  assert(tx.verify(fundMe.view), 'TX is malformed. Fix before broadcasting');
    +
    +  // Step 6: broadcast tx
    +  try {
    +    const broadcastStatus = await client.broadcast(tx);
    +    return tx;
    +  } catch (e) {
    +    console.log('There was a problem: ', e);
    +  }
    +}
    +composeWalletCrowdfund()
    +  .then(myCrowdfundTx => console.log('Transaction broadcast: ', myCrowdfundTx))
    +  .catch(e => console.log('There was a problem: ', e));

    And there you have it! If you were doing this on testnet, your fundeeWallet should now be 1BTC richer. If you're on a simnet or regtest network, you'll have to mine a block with your transactions to get those funds confirmed. Also note that, unless you have exact change coins, there will be 3 transactions that need to be confirmed: one each for the wallets that are splitting coins, and one for the crowdfund transaction.

    +

    How to Build it Out

    These examples are obviously pretty basic, but they should give you an idea of how to use Bitcoin's scripting to build out the foundation for more complex applications. Here are some ideas on how you could build on top of these examples and get closer to a production ready application.

    +
      +
    • More flexible contribution scheme (currently it's just 2 funders that split the amount evenly). E.g. custom number of contributers, custom contribution amount, etc.
    • +
    • UX to let people interact with the transaction via a browser
    • +
    • More advanced interface for fee estimation and include platform for large number of funders (for example, since you may be limited to number of funders per tx, you could include interface for multiple transactions for a single campaign. You would also want to include a check to make sure your tx is not bigger than 100kb otherwise it'll get rejected by the network)
    • +
    • Add a fund matching scheme where someone can say they will match future contributions
    • +
    • Currently the examples split transactions to make a coin available that equals the target contribution amount. This expensive since you ahve broadcast multiple transactions. An interface to choose to donate from available available coins might help to make this more efficient.
    • +
    +

    Make sure to get in touch with us on Twitter or Slack if you build out any of these ideas!

    +

    Again, for a working example of the code (without all the text and explanations), check out the repo on github.

    + From e91b9a74f68d42a8e5f91418a28dc642971ca254 Mon Sep 17 00:00:00 2001 From: Bucko Date: Thu, 5 Oct 2017 17:04:28 -0700 Subject: [PATCH 04/13] merge with new conversion update and make it so that title is retrieved only from the first top level header element --- guides.html | 28 +++++- guides/crowdfund-tx.html | 163 ++++++++++++++++++++--------------- guides/generate-address.html | 7 +- guides/install-linux.html | 9 +- guides/install-mac.html | 9 +- guides/install-windows.html | 9 +- guides/op_return.html | 7 +- guides/scripting.html | 7 +- utils/createHTML.js | 2 +- utils/helpers.js | 2 +- 10 files changed, 146 insertions(+), 97 deletions(-) diff --git a/guides.html b/guides.html index a01ce4c..8f127b1 100644 --- a/guides.html +++ b/guides.html @@ -252,9 +252,9 @@
    Install

    @@ -264,6 +264,7 @@
  • Intro to Scripting
  • Create an OP_RETURN output transaction
  • Generate an Address
  • +
  • Creating a Crowdfunding Transaction
@@ -394,6 +395,27 @@ + diff --git a/guides/crowdfund-tx.html b/guides/crowdfund-tx.html index c57755c..7e64ca6 100644 --- a/guides/crowdfund-tx.html +++ b/guides/crowdfund-tx.html @@ -41,6 +41,8 @@ + + @@ -248,43 +250,46 @@
- - + +
@@ -302,18 +307,18 @@
-

Creating a Crowdfunding Transaction

SIGHASH Flags

In most Bitcoin transactions, when a transaction is signed, the entirety of the data in that transaction is committed to in that input's signature, with all outputs and inputs being included in the unlocking script. This, however, doesn't always have to be the case. The level of commitment is indicated by using SIGHASH flags. The most common flag is SIGHASH_ALL which has a value of 0x01 (you'll see this represented as a 01 at the end of DER-encoded signatures). To learn more about how the flags work and what other options are available, you should checkout Chapter 6 of Mastering Bitcoin by Andreas Antonopolous. In that chapter, Andreas posits one novel use of a specialized flag, ALL|ANYONECANPAY, and in this guide we'll try and build out a couple of examples implementing this idea using bcoin.

-

How it Works

The ALL|ANYONECANPAY flag indicates that a signature is committing all of the outputs and just one input. The suggested use case of this proposed in Mastering Bitcoin is a kickstarter-like crowdfunding application. Consider the situation where you have a fundraising goal, say 1BTC. You'd like multiple people to contribute to your goal and you want to prove to them that they won't have to pay unless you reach your goal (i.e. the transaction is invalid and won't be accepted by the network if attempted to be sent). This means you'd be committing to one output, the one that sends the funds to the charity or project you'd like to donate to, and only one input, your contribution. This allows multiple users to contribute to the same transaction and that transaction won't be valid until it's fully funded.

+

Creating a Crowdfunding Transaction

SIGHASH Flags

In most Bitcoin transactions, when a transaction is signed, the entirety of the data in that transaction is committed to in that input's signature, with all outputs and inputs being included in the unlocking script. This, however, doesn't always have to be the case. The level of commitment is indicated by using SIGHASH flags. The most common flag is SIGHASH_ALL which has a value of 0x01 (you'll see this represented as a 01 at the end of DER-encoded signatures). To learn more about how the flags work and what other options are available, you should checkout Chapter 6 of Mastering Bitcoin by Andreas Antonopolous. In that chapter, Andreas posits one novel use of a specialized flag, ALL|ANYONECANPAY, and in this guide we'll try and build out a couple of examples implementing this idea using bcoin.

+

How it Works

The ALL|ANYONECANPAY flag indicates that a signature is committing all of the outputs and just one input. The suggested use case of this proposed in Mastering Bitcoin is a kickstarter-like crowdfunding application. Consider the situation where you have a fundraising goal, say 1BTC. You'd like multiple people to contribute to your goal and you want to prove to them that they won't have to pay unless you reach your goal (i.e. the transaction is invalid and won't be accepted by the network if attempted to be sent). This means you'd be committing to one output, the one that sends the funds to the charity or project you'd like to donate to, and only one input, your contribution. This allows multiple users to contribute to the same transaction and that transaction won't be valid until it's fully funded.

Here's how it's explained in Mastering Bitcoin:

ALL|ANYONECANPAY This construction can be used to make a "crowdfunding”-style transaction. Someone attempting to raise funds can construct a transaction with a single output. The single output pays the "goal" amount to the fundraiser. Such a transaction is obviously not valid, as it has no inputs. However, others can now amend it by adding an input of their own, as a donation. They sign their own input with ALL|ANYONECANPAY. Unless enough inputs are gathered to reach the value of the output, the transaction is invalid. Each donation is a "pledge," which cannot be collected by the fundraiser until the entire goal amount is raised.

-

The Code

We'll walk through the steps of creating the transaction first without any wallet database or node running. Then we'll do the same thing using bcoin's walletdb to manage the keys to see how it would work in a more realistic application. At the end, we'll put out some ideas of how these can be built upon for a more robust, production ready application. (If this is something you'd be interested in building, get in touch!). If you want to see the code, checkout the repo on github.

+

The Code

We'll walk through the steps of creating the transaction first without any wallet database or node running. Then we'll do the same thing using bcoin's walletdb to manage the keys to see how it would work in a more realistic application. At the end, we'll put out some ideas of how these can be built upon for a more robust, production ready application. (If this is something you'd be interested in building, get in touch!). If you want to see the code, checkout the repo on github.

If you're not comfortable with key management, coin selection, and how transactions are constructed, checkout the tutorial on working with transactions first.

Version 1 - Manual Key Management

Step 1: Setup

Let's first start by importing the right tools, setting up some constants, and creating our keychains. (make sure you've installed the latest version of bcoin into your project with npm install bcoin).

Note that we're setting the fundingTarget and amountToFund as constants for simplicity, but they could be set based on user input or some other variable circumstances.

-
'use strict';
+
'use strict';
 
 const assert = require('assert');
 const bcoin = require('bcoin');
@@ -327,7 +332,7 @@ This construction can be used to make a "crowdfunding”-style transaction.
 
 const fundingTarget = 100000000; // 1 BTC
 const amountToFund = 50000000; // .5 BTC
-const txRate = 10000; // 10000 satoshis/kb

Let's derive private hd key for fundee and two funders and create a "keyring" object for each (much of this is borrowed from the Working with Transactions guide).

+const txRate = 10000; // 10000 satoshis/kb

Let's derive private hd key for fundee and two funders and create a "keyring" object for each (much of this is borrowed from the Working with Transactions guide).

A keyring object is basically a key manager that is also able to tell you info such as:

  • your redeem script
  • @@ -336,7 +341,7 @@ This construction can be used to make a "crowdfunding”-style transaction.
  • your pubkey hash
  • your scripthash program hash
-
// Create an HD master keypair.
+
// Create an HD master keypair.
 const master = bcoin.hd.generate();
 
 const fundeeKey = master.derive(0);
@@ -350,9 +355,9 @@ This construction can be used to make a "crowdfunding”-style transaction.
 const funder2Key = master.derive(2);
 const funder2Keyring = new Keyring(funder2Key.privateKey);
 
-const funders = [funder1Keyring, funder2Keyring];

Step 2: Fund the Keyrings

This example is working in an isolated environment, so it won't work on the actual network (main, test, or otherwise), but we also don't have to earn or send ourselves coins or wait for confirmation times. That means that we can "spawn" coins for our funder wallets that they can use to spend on the crowdfunding platform.

+const funders = [funder1Keyring, funder2Keyring];

Step 2: Fund the Keyrings

This example is working in an isolated environment, so it won't work on the actual network (main, test, or otherwise), but we also don't have to earn or send ourselves coins or wait for confirmation times. That means that we can "spawn" coins for our funder wallets that they can use to spend on the crowdfunding platform.

Let's create some coinbase transactions to give our keyrings some coins that they can spend.

-
// create some coinbase transactions to fund our wallets
+
// create some coinbase transactions to fund our wallets
 const coins = {};
 
 for(let i=0; i < funders.length; i++) {
@@ -375,8 +380,8 @@ This construction can be used to make a "crowdfunding”-style transaction.
   // object and add it to the available coins for that keyring.
   // In reality you might get these coins from a wallet.
   coins[i] = [Coin.fromTX(cb, 0, -1)];
-}

The coins object you've created above should look something like this:

-
{
+}

The coins object you've created above should look something like this:

+
{
   "0":
       [
         {
@@ -392,10 +397,10 @@ This construction can be used to make a "crowdfunding”-style transaction.
         }
       ],
   "1": [...]
-}

Step 4: Prepare your Coins

Above, we just funded our funder accounts with a single 5BTC outpoint. This means that the next transaction funded from these accounts can only use that one outpoint (or coin) as an input and send the remainder back as change. Remember, in Bitcoin the way you send funds is you fund a transaction with a full UTXO (in this case we only have one worth 5BTC available to our keychains) and then send the change back to yourself as an additional output. Since ALL|ANYONECANPAY transactions mean a fixed output, you can't add new change outputs without other signatures becoming invalid which means we need a coin available equal to the amount we want to contribute to the crowdfund.

+}

Step 4: Prepare your Coins

Above, we just funded our funder accounts with a single 5BTC outpoint. This means that the next transaction funded from these accounts can only use that one outpoint (or coin) as an input and send the remainder back as change. Remember, in Bitcoin the way you send funds is you fund a transaction with a full UTXO (in this case we only have one worth 5BTC available to our keychains) and then send the change back to yourself as an additional output. Since ALL|ANYONECANPAY transactions mean a fixed output, you can't add new change outputs without other signatures becoming invalid which means we need a coin available equal to the amount we want to contribute to the crowdfund.

So what we want to do is have each funder create a coin (UTXO) with the value of what they want to donate.

Let's create an asyncronous utility function to help us split the coinbases we created in the previous step (we use the fund method on the MTX primitive to do this, which is asynchronous).

-
const splitCoinbase = async function splitCoinbase(keyrings, coins, targetAmount, txRate) {
+
const splitCoinbase = async function splitCoinbase(keyrings, coins, targetAmount, txRate) {
 
   // loop through each coinbase coin to split
   for(const coinsIndex in coins) {
@@ -436,14 +441,14 @@ This construction can be used to make a "crowdfunding”-style transaction.
   }
 
   return coins;
-};

Because of the async methods being used, in order to take advantage of the async/await structure, the rest of the code will be enclosed in an async function.

+};

Because of the async methods being used, in order to take advantage of the async/await structure, the rest of the code will be enclosed in an async function.

The first thing we'll do is split the coinbase coins we created earlier using the utility function we just built.

-
const composeCrowdfund = async function composeCrowdfund() {
+
const composeCrowdfund = async function composeCrowdfund() {
   const funderCoins = await splitCoinbase(funders, coins, amountToFund, txRate);
   // ... we'll keep filling out the rest of the code here
-};

funderCoins should return x number of coin arrays, where X is the number of coinbases we created earlier (should be 2) with each array having a coin equal to the amount we want to donate.

+};

funderCoins should return x number of coin arrays, where X is the number of coinbases we created earlier (should be 2) with each array having a coin equal to the amount we want to donate.

For example...

-
{
+
{
   "0":
      [
       {
@@ -460,10 +465,10 @@ This construction can be used to make a "crowdfunding”-style transaction.
          ...
       ],
   "1": [...]
-}

Step 5: Calculating the Fee

We have a tricky problem now. In a real world situation you're not going to know how many inputs (i.e. funders) you will have. But the more inputs you have, the bigger the transaction and thus the higher the fee you will need to broadcast it. The best we can do is to estimate the size based off of the max number of inputs we are willing to accept.

+}

Step 5: Calculating the Fee

We have a tricky problem now. In a real world situation you're not going to know how many inputs (i.e. funders) you will have. But the more inputs you have, the bigger the transaction and thus the higher the fee you will need to broadcast it. The best we can do is to estimate the size based off of the max number of inputs we are willing to accept.

In our example, we know there are two inputs. In a more complex application, you might put a cap of say 5, then estimate the fee based on that. If there turn out to be fewer then you just have a relatively high fee.

So, let's next add a utility for estimating a max fee. The way this will work is by creating a mock transaction that uses one of our coins to add inputs up to our maximum, calculate the size of the transaction and the fee based on that amount. We'll also build a utility that we'll need again later for adding coins to a tx and signing the input created.

-
const getMaxFee = function getMaxFee(maxInputs, coin, address, keyring, rate) {
+
const getMaxFee = function getMaxFee(maxInputs, coin, address, keyring, rate) {
   const fundingTarget = 100000000; // 1 BTC (arbitrary for purposes of this function)
   const testMTX = new MTX();
 
@@ -485,8 +490,8 @@ This construction can be used to make a "crowdfunding”-style transaction.
   mtx.scriptInput(inputIndex, sampleCoin, keyring);
   mtx.signInput(inputIndex, sampleCoin, keyring, hashType);
   assert(mtx.isSigned(), 'Input was not signed properly');
-}

Now we can get the fee for our funder transaction. We'll assume a max of 2 inputs, but this can be variable.

-
const composeCrowdfund = async function composeCrowdfund() {
+}

Now we can get the fee for our funder transaction. We'll assume a max of 2 inputs, but this can be variable.

+
const composeCrowdfund = async function composeCrowdfund() {
   //...
   const maxInputs = 2;
   const maxFee = getMaxFee(
@@ -499,12 +504,12 @@ This construction can be used to make a "crowdfunding”-style transaction.
 
   console.log(`Based on a rate of ${txRate} satoshis/kb and a tx with max ${maxInputs}`);
   console.log(`the tx fee should be ${maxFee} satoshis`);
-};

This should log something like:

+};

This should log something like:

-

Based on a rate of 10000 satoshis/kb and a tx with max 2 the tx fee should be 3380 satoshis

+

Based on a rate of 10000 satoshis/kb and a tx with max 2 inputs, the tx fee should be 3380 satoshis

Step 6: Construct the Transaction

Now that we've got our tools and coins ready, we can start to build the transaction! Note that we are going to use the maxFee we calculated earlier and subtract it from the output we add to the mtx.

-
const composeCrowdfund = async function composeCrowdfund() {
+
const composeCrowdfund = async function composeCrowdfund() {
   //...
 
   const fundMe = new MTX();
@@ -531,11 +536,11 @@ This construction can be used to make a "crowdfunding”-style transaction.
   return tx;
 };
 
-composeCrowdfund().then(myCrowdfundTx => console.log(myCrowdfundTx));

composeCrowdfund() will now return a promise (thanks to async/await) that returns a fully templated transaction that can be transmitted to the network. It should have two inputs and one output equal to the funding target minus the fee. You should also notice the SIGHASH flag 0x81 at the end of the input scripts which confirms they are ALL|ANYONECANPAY scripts.

+composeCrowdfund().then(myCrowdfundTx => console.log(myCrowdfundTx));

composeCrowdfund() will now return a promise (thanks to async/await) that returns a fully templated transaction that can be transmitted to the network. It should have two inputs and one output equal to the funding target minus the fee. You should also notice the SIGHASH flag 0x81 at the end of the input scripts which confirms they are ALL|ANYONECANPAY scripts.

Version 2: Using the Bcoin Wallet System

Since trying to manage your own keys is pretty tedious, not to mention impractical especially if you want to let other people contribute to your campaign, let's use the bcoin wallet system to take care of that part of the process. For the this example, we're going to interact via the wallet client, but you could also do something similar within a bcoin node (using the wallet and walletdb directly) which would also be more secure.

Note that this is a little more tedious to test since you need to have funded wallets in order to actually fund your campaign. You can find a bitcoin testnet faucet to get some funds to play with or, as we will do in this example, create your own simnet or regtest network where you mine your own blocks to fund yourself.

Step 1: Setup Our Wallets

We'll skip the setup of constants since and import modules since we can use the same from the previous example. Since there are a lot of asynchronous operations here now that we're using the client, we'll also put this in an async function and will continue to build out the contents of the function throughout this guide.

-
const network = 'simnet';
+
const network = 'simnet';
 const composeWalletCrowdfund = async function composeWalletCrowdfund() {
   const client = await new bcoin.http.Client({ network });
 
@@ -547,8 +552,8 @@ This construction can be used to make a "crowdfunding”-style transaction.
     'funder2': await new httpWallet({ id: 'funder2', network })
   };
   //...
-}

Step 2: Prepare Your Coins

We have the same issue to deal with here as in step 2 in the previous example: we need to have "exact change" coins available for funding. The process of splitting is a little different with the wallet system though so let's walk through this version

-
const composeWalletCrowdfund = async function composeWalletCrowdfund() {
+}

Step 2: Prepare Your Coins

We have the same issue to deal with here as in step 2 in the previous example: we need to have "exact change" coins available for funding. The process of splitting is a little different with the wallet system though so let's walk through this version

+
const composeWalletCrowdfund = async function composeWalletCrowdfund() {
   //...
 
   const fundingCoins = {};
@@ -604,8 +609,8 @@ This construction can be used to make a "crowdfunding”-style transaction.
     }
     fundingCoins[funder.id] = fundingCoin;
   }
-}

We now should have an object available that has the information of the coins we will be using to fund our transaction with mapped to the name of each wallet (so we can retrieve the wallet information later). It should look something like this:

-
{
+}

We now should have an object available that has the information of the coins we will be using to fund our transaction with mapped to the name of each wallet (so we can retrieve the wallet information later). It should look something like this:

+
{
   "funder1": {
     "version": 1,
     "height": -1,
@@ -626,8 +631,8 @@ This construction can be used to make a "crowdfunding”-style transaction.
     "hash": "11f3180c5069f2692f1ee1463257b21dc217441e792493c8f5ee230c35d97d96",
     "index": 0
   }
-}

Step 3: Create our Crowdfund Tx

We also have to include the getmaxFee step as before, but we can use the same utility that we created for the other example. The way that we're funding the inputs is pretty insecure as we are sending private keys unencrypted across the network, but we'll leave for the sake of the example. DON'T USE THIS IN PRODUCTION.

-
const composeWalletCrowdfund = async function composeWalletCrowdfund() {
+}

Step 3: Create our Crowdfund Tx

We also have to include the getmaxFee step as before, but we can use the same utility that we created for the other example. The way that we're funding the inputs is pretty insecure as we are sending private keys unencrypted across the network, but we'll leave for the sake of the example. DON'T USE THIS IN PRODUCTION.

+
const composeWalletCrowdfund = async function composeWalletCrowdfund() {
   //...
   const testKey = await funders['funder1'].getWIF(fundingCoins['funder1'].address);
   const testKeyring = new bcoin.keyring.fromSecret(testKey.privateKey);
@@ -675,8 +680,8 @@ This construction can be used to make a "crowdfunding”-style transaction.
 }
 composeWalletCrowdfund()
   .then(myCrowdfundTx => console.log('Transaction broadcast: ', myCrowdfundTx))
-  .catch(e => console.log('There was a problem: ', e));

And there you have it! If you were doing this on testnet, your fundeeWallet should now be 1BTC richer. If you're on a simnet or regtest network, you'll have to mine a block with your transactions to get those funds confirmed. Also note that, unless you have exact change coins, there will be 3 transactions that need to be confirmed: one each for the wallets that are splitting coins, and one for the crowdfund transaction.

-

How to Build it Out

These examples are obviously pretty basic, but they should give you an idea of how to use Bitcoin's scripting to build out the foundation for more complex applications. Here are some ideas on how you could build on top of these examples and get closer to a production ready application.

+ .catch(e => console.log('There was a problem: ', e));

And there you have it! If you were doing this on testnet, your fundeeWallet should now be 1BTC richer. If you're on a simnet or regtest network, you'll have to mine a block with your transactions to get those funds confirmed. Also note that, unless you have exact change coins, there will be 3 transactions that need to be confirmed: one each for the wallets that are splitting coins, and one for the crowdfund transaction.

+

How to Build it Out

These examples are obviously pretty basic, but they should give you an idea of how to use Bitcoin's scripting to build out the foundation for more complex applications. Here are some ideas on how you could build on top of these examples and get closer to a production ready application.

  • More flexible contribution scheme (currently it's just 2 funders that split the amount evenly). E.g. custom number of contributers, custom contribution amount, etc.
  • UX to let people interact with the transaction via a browser
  • @@ -775,8 +780,24 @@ This construction can be used to make a "crowdfunding”-style transaction. - + + diff --git a/guides/generate-address.html b/guides/generate-address.html index 0f5ac0d..78729e5 100644 --- a/guides/generate-address.html +++ b/guides/generate-address.html @@ -257,9 +257,9 @@
    Install

    @@ -269,6 +269,7 @@
  • Intro to Scripting
  • Create an OP_RETURN output transaction
  • Generate an Address
  • +
  • Creating a Crowdfunding Transaction
diff --git a/guides/install-linux.html b/guides/install-linux.html index 04ab9bc..25a8b11 100644 --- a/guides/install-linux.html +++ b/guides/install-linux.html @@ -257,9 +257,9 @@
Install

@@ -269,6 +269,7 @@
  • Intro to Scripting
  • Create an OP_RETURN output transaction
  • Generate an Address
  • +
  • Creating a Crowdfunding Transaction
  • @@ -306,7 +307,7 @@
    -

    Install on Linux (Video)

    A quick step-by-step video to learn the basics of running a bcoin fullnode on Linux.

    +

    Install on Linux (Video)

    A quick step-by-step video to learn the basics of running a bcoin fullnode on Linux.

    diff --git a/guides/install-mac.html b/guides/install-mac.html index 5e3bd2e..bc31d7f 100644 --- a/guides/install-mac.html +++ b/guides/install-mac.html @@ -257,9 +257,9 @@
    Install

    @@ -269,6 +269,7 @@
  • Intro to Scripting
  • Create an OP_RETURN output transaction
  • Generate an Address
  • +
  • Creating a Crowdfunding Transaction
  • @@ -306,7 +307,7 @@
    -

    Install on Mac OS (Video)

    A quick step-by-step video to learn the basics of running a bcoin fullnode on a Mac.

    +

    Install on Mac OS (Video)

    A quick step-by-step video to learn the basics of running a bcoin fullnode on a Mac.

    diff --git a/guides/install-windows.html b/guides/install-windows.html index 20a997e..73affc8 100644 --- a/guides/install-windows.html +++ b/guides/install-windows.html @@ -257,9 +257,9 @@
    Install

    @@ -269,6 +269,7 @@
  • Intro to Scripting
  • Create an OP_RETURN output transaction
  • Generate an Address
  • +
  • Creating a Crowdfunding Transaction
  • @@ -306,7 +307,7 @@
    -

    Install on Windows (Video)

    A quick step-by-step video to learn the basics of running a bcoin fullnode on a Windows.

    +

    Install on Windows (Video)

    A quick step-by-step video to learn the basics of running a bcoin fullnode on a Windows.

    diff --git a/guides/op_return.html b/guides/op_return.html index 7ac95e7..48a7f69 100644 --- a/guides/op_return.html +++ b/guides/op_return.html @@ -257,9 +257,9 @@
    Install

    @@ -269,6 +269,7 @@
  • Intro to Scripting
  • Create an OP_RETURN output transaction
  • Generate an Address
  • +
  • Creating a Crowdfunding Transaction
  • diff --git a/guides/scripting.html b/guides/scripting.html index a1494b1..5122d33 100644 --- a/guides/scripting.html +++ b/guides/scripting.html @@ -257,9 +257,9 @@
    Install

    @@ -269,6 +269,7 @@
  • Intro to Scripting
  • Create an OP_RETURN output transaction
  • Generate an Address
  • +
  • Creating a Crowdfunding Transaction
  • diff --git a/utils/createHTML.js b/utils/createHTML.js index 57788a7..6f2ad24 100644 --- a/utils/createHTML.js +++ b/utils/createHTML.js @@ -24,7 +24,7 @@ const createHTML = async function createHTML(markdownFile, htmlFile, author, pos // Custom renderer for top two level headers renderer.heading = (text, level) => { - if (level == '2' || level == '1' ) { + if (level == '1' ) { let header = '

    ' + text + '

    '; diff --git a/utils/helpers.js b/utils/helpers.js index 68e2287..7c7f561 100644 --- a/utils/helpers.js +++ b/utils/helpers.js @@ -33,7 +33,7 @@ const getPostInfo = function getPostInfo(pathToFiles, postMeta) { // Add text to list of titles if in a header level 1 or 2 renderer.heading = (text, level) => { - if (level == '2' || level == '1' ) { + if (level == '1' ) { let iconCloseTag = text.indexOf('') if ( iconCloseTag > -1) { text = text.slice(iconCloseTag + 4); From 3098021175695813e03bf012db566225c1fc52cc Mon Sep 17 00:00:00 2001 From: Bucko Date: Thu, 5 Oct 2017 17:45:30 -0700 Subject: [PATCH 05/13] fix install guide headers --- guides-markdown/install/install-linux.md | 2 +- guides-markdown/install/install-mac.md | 2 +- guides-markdown/install/install-windows.md | 2 +- guides.html | 6 +++--- guides/crowdfund-tx.html | 6 +++--- guides/generate-address.html | 6 +++--- guides/install-linux.html | 8 ++++---- guides/install-mac.html | 8 ++++---- guides/install-windows.html | 8 ++++---- guides/op_return.html | 6 +++--- guides/scripting.html | 6 +++--- 11 files changed, 30 insertions(+), 30 deletions(-) diff --git a/guides-markdown/install/install-linux.md b/guides-markdown/install/install-linux.md index ea517ed..5f85aa9 100644 --- a/guides-markdown/install/install-linux.md +++ b/guides-markdown/install/install-linux.md @@ -1,4 +1,4 @@ -## Install on Linux (Video) +# Install on Linux (Video) ```post-author HeKaz ``` diff --git a/guides-markdown/install/install-mac.md b/guides-markdown/install/install-mac.md index 25b2b15..b7f6f3d 100644 --- a/guides-markdown/install/install-mac.md +++ b/guides-markdown/install/install-mac.md @@ -1,4 +1,4 @@ -## Install on Mac OS (Video) +# Install on Mac OS (Video) ```post-author HeKaz ``` diff --git a/guides-markdown/install/install-windows.md b/guides-markdown/install/install-windows.md index 013559b..cfd8aaa 100644 --- a/guides-markdown/install/install-windows.md +++ b/guides-markdown/install/install-windows.md @@ -1,4 +1,4 @@ -## Install on Windows (Video) +# Install on Windows (Video) ```post-author HeKaz ``` diff --git a/guides.html b/guides.html index 8f127b1..59db5d3 100644 --- a/guides.html +++ b/guides.html @@ -252,9 +252,9 @@
    Install

    diff --git a/guides/crowdfund-tx.html b/guides/crowdfund-tx.html index 7e64ca6..38723ed 100644 --- a/guides/crowdfund-tx.html +++ b/guides/crowdfund-tx.html @@ -257,9 +257,9 @@
    Install

    diff --git a/guides/generate-address.html b/guides/generate-address.html index 78729e5..b5763ab 100644 --- a/guides/generate-address.html +++ b/guides/generate-address.html @@ -257,9 +257,9 @@
    Install

    diff --git a/guides/install-linux.html b/guides/install-linux.html index 25a8b11..313146d 100644 --- a/guides/install-linux.html +++ b/guides/install-linux.html @@ -257,9 +257,9 @@
    Install

    @@ -307,7 +307,7 @@
    -

    Install on Linux (Video)

    A quick step-by-step video to learn the basics of running a bcoin fullnode on Linux.

    +

    Install on Linux (Video)

    A quick step-by-step video to learn the basics of running a bcoin fullnode on Linux.

    diff --git a/guides/install-mac.html b/guides/install-mac.html index bc31d7f..b225262 100644 --- a/guides/install-mac.html +++ b/guides/install-mac.html @@ -257,9 +257,9 @@
    Install

    @@ -307,7 +307,7 @@
    -

    Install on Mac OS (Video)

    A quick step-by-step video to learn the basics of running a bcoin fullnode on a Mac.

    +

    Install on Mac OS (Video)

    A quick step-by-step video to learn the basics of running a bcoin fullnode on a Mac.

    diff --git a/guides/install-windows.html b/guides/install-windows.html index 73affc8..441196a 100644 --- a/guides/install-windows.html +++ b/guides/install-windows.html @@ -257,9 +257,9 @@
    Install

    @@ -307,7 +307,7 @@
    -

    Install on Windows (Video)

    A quick step-by-step video to learn the basics of running a bcoin fullnode on a Windows.

    +

    Install on Windows (Video)

    A quick step-by-step video to learn the basics of running a bcoin fullnode on a Windows.

    diff --git a/guides/op_return.html b/guides/op_return.html index 48a7f69..b5a7675 100644 --- a/guides/op_return.html +++ b/guides/op_return.html @@ -257,9 +257,9 @@
    Install

    diff --git a/guides/scripting.html b/guides/scripting.html index 5122d33..9687d11 100644 --- a/guides/scripting.html +++ b/guides/scripting.html @@ -257,9 +257,9 @@
    Install

    From d221d6814620ce9cd2c55431bb0cd18deeb26a4b Mon Sep 17 00:00:00 2001 From: Bucko Date: Thu, 5 Oct 2017 18:55:55 -0700 Subject: [PATCH 06/13] fix id for headers that have symbols --- guides/crowdfund-tx.html | 26 +++++++++++++------------- guides/generate-address.html | 2 +- guides/op_return.html | 2 +- utils/createHTML.js | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/guides/crowdfund-tx.html b/guides/crowdfund-tx.html index 38723ed..bf7c000 100644 --- a/guides/crowdfund-tx.html +++ b/guides/crowdfund-tx.html @@ -307,16 +307,16 @@
    -

    Creating a Crowdfunding Transaction

    SIGHASH Flags

    In most Bitcoin transactions, when a transaction is signed, the entirety of the data in that transaction is committed to in that input's signature, with all outputs and inputs being included in the unlocking script. This, however, doesn't always have to be the case. The level of commitment is indicated by using SIGHASH flags. The most common flag is SIGHASH_ALL which has a value of 0x01 (you'll see this represented as a 01 at the end of DER-encoded signatures). To learn more about how the flags work and what other options are available, you should checkout Chapter 6 of Mastering Bitcoin by Andreas Antonopolous. In that chapter, Andreas posits one novel use of a specialized flag, ALL|ANYONECANPAY, and in this guide we'll try and build out a couple of examples implementing this idea using bcoin.

    -

    How it Works

    The ALL|ANYONECANPAY flag indicates that a signature is committing all of the outputs and just one input. The suggested use case of this proposed in Mastering Bitcoin is a kickstarter-like crowdfunding application. Consider the situation where you have a fundraising goal, say 1BTC. You'd like multiple people to contribute to your goal and you want to prove to them that they won't have to pay unless you reach your goal (i.e. the transaction is invalid and won't be accepted by the network if attempted to be sent). This means you'd be committing to one output, the one that sends the funds to the charity or project you'd like to donate to, and only one input, your contribution. This allows multiple users to contribute to the same transaction and that transaction won't be valid until it's fully funded.

    +

    Creating a Crowdfunding Transaction

    SIGHASH Flags

    In most Bitcoin transactions, when a transaction is signed, the entirety of the data in that transaction is committed to in that input's signature, with all outputs and inputs being included in the unlocking script. This, however, doesn't always have to be the case. The level of commitment is indicated by using SIGHASH flags. The most common flag is SIGHASH_ALL which has a value of 0x01 (you'll see this represented as a 01 at the end of DER-encoded signatures). To learn more about how the flags work and what other options are available, you should checkout Chapter 6 of Mastering Bitcoin by Andreas Antonopolous. In that chapter, Andreas posits one novel use of a specialized flag, ALL|ANYONECANPAY, and in this guide we'll try and build out a couple of examples implementing this idea using bcoin.

    +

    How it Works

    The ALL|ANYONECANPAY flag indicates that a signature is committing all of the outputs and just one input. The suggested use case of this proposed in Mastering Bitcoin is a kickstarter-like crowdfunding application. Consider the situation where you have a fundraising goal, say 1BTC. You'd like multiple people to contribute to your goal and you want to prove to them that they won't have to pay unless you reach your goal (i.e. the transaction is invalid and won't be accepted by the network if attempted to be sent). This means you'd be committing to one output, the one that sends the funds to the charity or project you'd like to donate to, and only one input, your contribution. This allows multiple users to contribute to the same transaction and that transaction won't be valid until it's fully funded.

    Here's how it's explained in Mastering Bitcoin:

    ALL|ANYONECANPAY This construction can be used to make a "crowdfunding”-style transaction. Someone attempting to raise funds can construct a transaction with a single output. The single output pays the "goal" amount to the fundraiser. Such a transaction is obviously not valid, as it has no inputs. However, others can now amend it by adding an input of their own, as a donation. They sign their own input with ALL|ANYONECANPAY. Unless enough inputs are gathered to reach the value of the output, the transaction is invalid. Each donation is a "pledge," which cannot be collected by the fundraiser until the entire goal amount is raised.

    -

    The Code

    We'll walk through the steps of creating the transaction first without any wallet database or node running. Then we'll do the same thing using bcoin's walletdb to manage the keys to see how it would work in a more realistic application. At the end, we'll put out some ideas of how these can be built upon for a more robust, production ready application. (If this is something you'd be interested in building, get in touch!). If you want to see the code, checkout the repo on github.

    +

    The Code

    We'll walk through the steps of creating the transaction first without any wallet database or node running. Then we'll do the same thing using bcoin's walletdb to manage the keys to see how it would work in a more realistic application. At the end, we'll put out some ideas of how these can be built upon for a more robust, production ready application. (If this is something you'd be interested in building, get in touch!). If you want to see the code, checkout the repo on github.

    If you're not comfortable with key management, coin selection, and how transactions are constructed, checkout the tutorial on working with transactions first.

    -

    Version 1 - Manual Key Management

    Step 1: Setup

    Let's first start by importing the right tools, setting up some constants, and creating our keychains. (make sure you've installed the latest version of bcoin into your project with npm install bcoin).

    +

    Version 1 - Manual Key Management

    Step 1: Setup

    Let's first start by importing the right tools, setting up some constants, and creating our keychains. (make sure you've installed the latest version of bcoin into your project with npm install bcoin).

    Note that we're setting the fundingTarget and amountToFund as constants for simplicity, but they could be set based on user input or some other variable circumstances.

    'use strict';
     
    @@ -355,7 +355,7 @@ This construction can be used to make a "crowdfunding”-style transaction.
     const funder2Key = master.derive(2);
     const funder2Keyring = new Keyring(funder2Key.privateKey);
     
    -const funders = [funder1Keyring, funder2Keyring];

    Step 2: Fund the Keyrings

    This example is working in an isolated environment, so it won't work on the actual network (main, test, or otherwise), but we also don't have to earn or send ourselves coins or wait for confirmation times. That means that we can "spawn" coins for our funder wallets that they can use to spend on the crowdfunding platform.

    +const funders = [funder1Keyring, funder2Keyring];

    Step 2: Fund the Keyrings

    This example is working in an isolated environment, so it won't work on the actual network (main, test, or otherwise), but we also don't have to earn or send ourselves coins or wait for confirmation times. That means that we can "spawn" coins for our funder wallets that they can use to spend on the crowdfunding platform.

    Let's create some coinbase transactions to give our keyrings some coins that they can spend.

    // create some coinbase transactions to fund our wallets
     const coins = {};
    @@ -397,7 +397,7 @@ This construction can be used to make a "crowdfunding”-style transaction.
             }
           ],
       "1": [...]
    -}

    Step 4: Prepare your Coins

    Above, we just funded our funder accounts with a single 5BTC outpoint. This means that the next transaction funded from these accounts can only use that one outpoint (or coin) as an input and send the remainder back as change. Remember, in Bitcoin the way you send funds is you fund a transaction with a full UTXO (in this case we only have one worth 5BTC available to our keychains) and then send the change back to yourself as an additional output. Since ALL|ANYONECANPAY transactions mean a fixed output, you can't add new change outputs without other signatures becoming invalid which means we need a coin available equal to the amount we want to contribute to the crowdfund.

    +}

    Step 4: Prepare your Coins

    Above, we just funded our funder accounts with a single 5BTC outpoint. This means that the next transaction funded from these accounts can only use that one outpoint (or coin) as an input and send the remainder back as change. Remember, in Bitcoin the way you send funds is you fund a transaction with a full UTXO (in this case we only have one worth 5BTC available to our keychains) and then send the change back to yourself as an additional output. Since ALL|ANYONECANPAY transactions mean a fixed output, you can't add new change outputs without other signatures becoming invalid which means we need a coin available equal to the amount we want to contribute to the crowdfund.

    So what we want to do is have each funder create a coin (UTXO) with the value of what they want to donate.

    Let's create an asyncronous utility function to help us split the coinbases we created in the previous step (we use the fund method on the MTX primitive to do this, which is asynchronous).

    const splitCoinbase = async function splitCoinbase(keyrings, coins, targetAmount, txRate) {
    @@ -465,7 +465,7 @@ This construction can be used to make a "crowdfunding”-style transaction.
              ...
           ],
       "1": [...]
    -}

    Step 5: Calculating the Fee

    We have a tricky problem now. In a real world situation you're not going to know how many inputs (i.e. funders) you will have. But the more inputs you have, the bigger the transaction and thus the higher the fee you will need to broadcast it. The best we can do is to estimate the size based off of the max number of inputs we are willing to accept.

    +}

    Step 5: Calculating the Fee

    We have a tricky problem now. In a real world situation you're not going to know how many inputs (i.e. funders) you will have. But the more inputs you have, the bigger the transaction and thus the higher the fee you will need to broadcast it. The best we can do is to estimate the size based off of the max number of inputs we are willing to accept.

    In our example, we know there are two inputs. In a more complex application, you might put a cap of say 5, then estimate the fee based on that. If there turn out to be fewer then you just have a relatively high fee.

    So, let's next add a utility for estimating a max fee. The way this will work is by creating a mock transaction that uses one of our coins to add inputs up to our maximum, calculate the size of the transaction and the fee based on that amount. We'll also build a utility that we'll need again later for adding coins to a tx and signing the input created.

    const getMaxFee = function getMaxFee(maxInputs, coin, address, keyring, rate) {
    @@ -508,7 +508,7 @@ This construction can be used to make a "crowdfunding”-style transaction.
     

    Based on a rate of 10000 satoshis/kb and a tx with max 2 inputs, the tx fee should be 3380 satoshis

    -

    Step 6: Construct the Transaction

    Now that we've got our tools and coins ready, we can start to build the transaction! Note that we are going to use the maxFee we calculated earlier and subtract it from the output we add to the mtx.

    +

    Step 6: Construct the Transaction

    Now that we've got our tools and coins ready, we can start to build the transaction! Note that we are going to use the maxFee we calculated earlier and subtract it from the output we add to the mtx.

    const composeCrowdfund = async function composeCrowdfund() {
       //...
     
    @@ -537,9 +537,9 @@ This construction can be used to make a "crowdfunding”-style transaction.
     };
     
     composeCrowdfund().then(myCrowdfundTx => console.log(myCrowdfundTx));

    composeCrowdfund() will now return a promise (thanks to async/await) that returns a fully templated transaction that can be transmitted to the network. It should have two inputs and one output equal to the funding target minus the fee. You should also notice the SIGHASH flag 0x81 at the end of the input scripts which confirms they are ALL|ANYONECANPAY scripts.

    -

    Version 2: Using the Bcoin Wallet System

    Since trying to manage your own keys is pretty tedious, not to mention impractical especially if you want to let other people contribute to your campaign, let's use the bcoin wallet system to take care of that part of the process. For the this example, we're going to interact via the wallet client, but you could also do something similar within a bcoin node (using the wallet and walletdb directly) which would also be more secure.

    +

    Version 2: Using the Bcoin Wallet System

    Since trying to manage your own keys is pretty tedious, not to mention impractical especially if you want to let other people contribute to your campaign, let's use the bcoin wallet system to take care of that part of the process. For the this example, we're going to interact via the wallet client, but you could also do something similar within a bcoin node (using the wallet and walletdb directly) which would also be more secure.

    Note that this is a little more tedious to test since you need to have funded wallets in order to actually fund your campaign. You can find a bitcoin testnet faucet to get some funds to play with or, as we will do in this example, create your own simnet or regtest network where you mine your own blocks to fund yourself.

    -

    Step 1: Setup Our Wallets

    We'll skip the setup of constants since and import modules since we can use the same from the previous example. Since there are a lot of asynchronous operations here now that we're using the client, we'll also put this in an async function and will continue to build out the contents of the function throughout this guide.

    +

    Step 1: Setup Our Wallets

    We'll skip the setup of constants since and import modules since we can use the same from the previous example. Since there are a lot of asynchronous operations here now that we're using the client, we'll also put this in an async function and will continue to build out the contents of the function throughout this guide.

    const network = 'simnet';
     const composeWalletCrowdfund = async function composeWalletCrowdfund() {
       const client = await new bcoin.http.Client({ network });
    @@ -552,7 +552,7 @@ This construction can be used to make a "crowdfunding”-style transaction.
         'funder2': await new httpWallet({ id: 'funder2', network })
       };
       //...
    -}

    Step 2: Prepare Your Coins

    We have the same issue to deal with here as in step 2 in the previous example: we need to have "exact change" coins available for funding. The process of splitting is a little different with the wallet system though so let's walk through this version

    +}

    Step 2: Prepare Your Coins

    We have the same issue to deal with here as in step 2 in the previous example: we need to have "exact change" coins available for funding. The process of splitting is a little different with the wallet system though so let's walk through this version

    const composeWalletCrowdfund = async function composeWalletCrowdfund() {
       //...
     
    @@ -631,7 +631,7 @@ This construction can be used to make a "crowdfunding”-style transaction.
         "hash": "11f3180c5069f2692f1ee1463257b21dc217441e792493c8f5ee230c35d97d96",
         "index": 0
       }
    -}

    Step 3: Create our Crowdfund Tx

    We also have to include the getmaxFee step as before, but we can use the same utility that we created for the other example. The way that we're funding the inputs is pretty insecure as we are sending private keys unencrypted across the network, but we'll leave for the sake of the example. DON'T USE THIS IN PRODUCTION.

    +}

    Step 3: Create our Crowdfund Tx

    We also have to include the getmaxFee step as before, but we can use the same utility that we created for the other example. The way that we're funding the inputs is pretty insecure as we are sending private keys unencrypted across the network, but we'll leave for the sake of the example. DON'T USE THIS IN PRODUCTION.

    const composeWalletCrowdfund = async function composeWalletCrowdfund() {
       //...
       const testKey = await funders['funder1'].getWIF(fundingCoins['funder1'].address);
    @@ -681,7 +681,7 @@ This construction can be used to make a "crowdfunding”-style transaction.
     composeWalletCrowdfund()
       .then(myCrowdfundTx => console.log('Transaction broadcast: ', myCrowdfundTx))
       .catch(e => console.log('There was a problem: ', e));

    And there you have it! If you were doing this on testnet, your fundeeWallet should now be 1BTC richer. If you're on a simnet or regtest network, you'll have to mine a block with your transactions to get those funds confirmed. Also note that, unless you have exact change coins, there will be 3 transactions that need to be confirmed: one each for the wallets that are splitting coins, and one for the crowdfund transaction.

    -

    How to Build it Out

    These examples are obviously pretty basic, but they should give you an idea of how to use Bitcoin's scripting to build out the foundation for more complex applications. Here are some ideas on how you could build on top of these examples and get closer to a production ready application.

    +

    How to Build it Out

    These examples are obviously pretty basic, but they should give you an idea of how to use Bitcoin's scripting to build out the foundation for more complex applications. Here are some ideas on how you could build on top of these examples and get closer to a production ready application.

    • More flexible contribution scheme (currently it's just 2 funders that split the amount evenly). E.g. custom number of contributers, custom contribution amount, etc.
    • UX to let people interact with the transaction via a browser
    • diff --git a/guides/generate-address.html b/guides/generate-address.html index b5763ab..88653ea 100644 --- a/guides/generate-address.html +++ b/guides/generate-address.html @@ -307,7 +307,7 @@
      -

      Generate an Address

      Follow along with the steps below to build a transaction from scratch using built-in bcoin utilities. These steps are based on those outlined in the Bitcoin Wiki.

      +

      Generate an Address

      Follow along with the steps below to build a transaction from scratch using built-in bcoin utilities. These steps are based on those outlined in the Bitcoin Wiki.

      Of course, if you're using the bcoin wallet module, it will do these steps for you automatically when you generate an address!

      // https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
       
      diff --git a/guides/op_return.html b/guides/op_return.html
      index d54bde4..960babb 100644
      --- a/guides/op_return.html
      +++ b/guides/op_return.html
      @@ -307,7 +307,7 @@
       									
      -

      Create an OP_RETURN output transaction

      OP_RETURN is a script opcode which can be used to store an arbitrary 40-byte +

      Create an OP_RETURN output transaction

      OP_RETURN is a script opcode which can be used to store an arbitrary 40-byte data as a part of the signature script (null data script), allowing one to embed a small amount of data into the blockchain. For example, it can be used as a way to track digital assets or certify a document with proof-of-existence. diff --git a/utils/createHTML.js b/utils/createHTML.js index 4098ff2..4685079 100644 --- a/utils/createHTML.js +++ b/utils/createHTML.js @@ -25,7 +25,7 @@ const createHTML = async function createHTML(markdownFile, htmlFile, author, pos // Custom renderer for top two level headers renderer.heading = (text, level) => { let textURL = text.replace(/\(.+\)/, '').trim(); // for url remove any parentheses and their contents - textURL = textURL.replace(/ /g, '-').toLowerCase(); // and replace spaces with dashes + textURL = textURL.replace(/[$-/:-?{-~!"^_`\[\]]/, '').replace(/ /g, '-').toLowerCase(); // and replace spaces with dashes if (level == '1' ) { // remove icon tag for the url From 0fb85e6906bbc8e11e09bef9d620658f699995af Mon Sep 17 00:00:00 2001 From: Bucko Date: Thu, 5 Oct 2017 18:59:13 -0700 Subject: [PATCH 07/13] add extra suggestion for how to deal with inputs --- guides-markdown/crowdfund-tx.md | 3 ++- guides/crowdfund-tx.html | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/guides-markdown/crowdfund-tx.md b/guides-markdown/crowdfund-tx.md index 0b40796..9aba1a0 100644 --- a/guides-markdown/crowdfund-tx.md +++ b/guides-markdown/crowdfund-tx.md @@ -480,7 +480,8 @@ These examples are obviously pretty basic, but they should give you an idea of h - UX to let people interact with the transaction via a browser - More advanced interface for fee estimation and include platform for large number of funders (for example, since you may be limited to number of funders per tx, you could include interface for multiple transactions for a single campaign. You would also want to include a check to make sure your tx is not bigger than 100kb otherwise it'll get rejected by the network) - Add a fund matching scheme where someone can say they will match future contributions -- Currently the examples split transactions to make a coin available that equals the target contribution amount. This expensive since you ahve broadcast multiple transactions. An interface to choose to donate from available available coins might help to make this more efficient. +- Currently the examples split transactions to make a coin available that equals the target contribution amount. This is expensive since you have to broadcast multiple transactions. An interface to choose to donate from available available coins might help to make this more efficient. +- This approach also isn't ideal if funders have non-p2pkh outputs. Something like a multisig will be much bigger and will cause the estimated fee to be too low. One possible approach would be to shift the burden of fees on the funders, have the application calculate the size of their contribution and add the resulting fee to their funding amount. Finally, use the amount calculated (including the fee) when [splitting the coins](#step-4-prepare-your-coins). This has the added advantage of not needing to limit the number of inputs Make sure to get in touch with us on Twitter or Slack if you build out any of these ideas! diff --git a/guides/crowdfund-tx.html b/guides/crowdfund-tx.html index bf7c000..b616ee6 100644 --- a/guides/crowdfund-tx.html +++ b/guides/crowdfund-tx.html @@ -687,7 +687,8 @@ This construction can be used to make a "crowdfunding”-style transaction.

    • UX to let people interact with the transaction via a browser
    • More advanced interface for fee estimation and include platform for large number of funders (for example, since you may be limited to number of funders per tx, you could include interface for multiple transactions for a single campaign. You would also want to include a check to make sure your tx is not bigger than 100kb otherwise it'll get rejected by the network)
    • Add a fund matching scheme where someone can say they will match future contributions
    • -
    • Currently the examples split transactions to make a coin available that equals the target contribution amount. This expensive since you ahve broadcast multiple transactions. An interface to choose to donate from available available coins might help to make this more efficient.
    • +
    • Currently the examples split transactions to make a coin available that equals the target contribution amount. This is expensive since you have to broadcast multiple transactions. An interface to choose to donate from available available coins might help to make this more efficient.
    • +
    • This approach also isn't ideal if funders have non-p2pkh outputs. Something like a multisig will be much bigger and will cause the estimated fee to be too low. One possible approach would be to shift the burden of fees on the funders, have the application calculate the size of their contribution and add the resulting fee to their funding amount. Finally, use this amount when splitting the coins.

    Make sure to get in touch with us on Twitter or Slack if you build out any of these ideas!

    Again, for a working example of the code (without all the text and explanations), check out the repo on github.

    From 954452113a5d983fb175a25e3ea6a350ccf5b68b Mon Sep 17 00:00:00 2001 From: Bucko Date: Fri, 6 Oct 2017 14:23:13 -0700 Subject: [PATCH 08/13] we won't be tracking htmls anymore --- guides/crowdfund-tx.html | 807 --------------------------------------- 1 file changed, 807 deletions(-) delete mode 100644 guides/crowdfund-tx.html diff --git a/guides/crowdfund-tx.html b/guides/crowdfund-tx.html deleted file mode 100644 index b616ee6..0000000 --- a/guides/crowdfund-tx.html +++ /dev/null @@ -1,807 +0,0 @@ - - - - - - - - - bcoin | Extending Bitcoin into Enterprise & Production - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    Loading... - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - - -
    -
    - - - - - - - -
    - -
    - - - - -
    -
    - - - -
    - - -
    -
    - -
    -
    - -

     Guides  and  Videos 

    - -
    -
    - -
    -
    - - - -
    -
    - -
    - - - - - -
    - -
    -
    - - - -
    -
    -
    - -

    Creating a Crowdfunding Transaction

    SIGHASH Flags

    In most Bitcoin transactions, when a transaction is signed, the entirety of the data in that transaction is committed to in that input's signature, with all outputs and inputs being included in the unlocking script. This, however, doesn't always have to be the case. The level of commitment is indicated by using SIGHASH flags. The most common flag is SIGHASH_ALL which has a value of 0x01 (you'll see this represented as a 01 at the end of DER-encoded signatures). To learn more about how the flags work and what other options are available, you should checkout Chapter 6 of Mastering Bitcoin by Andreas Antonopolous. In that chapter, Andreas posits one novel use of a specialized flag, ALL|ANYONECANPAY, and in this guide we'll try and build out a couple of examples implementing this idea using bcoin.

    -

    How it Works

    The ALL|ANYONECANPAY flag indicates that a signature is committing all of the outputs and just one input. The suggested use case of this proposed in Mastering Bitcoin is a kickstarter-like crowdfunding application. Consider the situation where you have a fundraising goal, say 1BTC. You'd like multiple people to contribute to your goal and you want to prove to them that they won't have to pay unless you reach your goal (i.e. the transaction is invalid and won't be accepted by the network if attempted to be sent). This means you'd be committing to one output, the one that sends the funds to the charity or project you'd like to donate to, and only one input, your contribution. This allows multiple users to contribute to the same transaction and that transaction won't be valid until it's fully funded.

    -

    Here's how it's explained in Mastering Bitcoin:

    -
    -

    ALL|ANYONECANPAY -This construction can be used to make a "crowdfunding”-style transaction. Someone attempting to raise funds can construct a transaction with a single output. The single output pays the "goal" amount to the fundraiser. Such a transaction is obviously not valid, as it has no inputs. However, others can now amend it by adding an input of their own, as a donation. They sign their own input with ALL|ANYONECANPAY. Unless enough inputs are gathered to reach the value of the output, the transaction is invalid. Each donation is a "pledge," which cannot be collected by the fundraiser until the entire goal amount is raised.

    -
    -

    The Code

    We'll walk through the steps of creating the transaction first without any wallet database or node running. Then we'll do the same thing using bcoin's walletdb to manage the keys to see how it would work in a more realistic application. At the end, we'll put out some ideas of how these can be built upon for a more robust, production ready application. (If this is something you'd be interested in building, get in touch!). If you want to see the code, checkout the repo on github.

    -

    If you're not comfortable with key management, coin selection, and how transactions are constructed, checkout the tutorial on working with transactions first.

    -

    Version 1 - Manual Key Management

    Step 1: Setup

    Let's first start by importing the right tools, setting up some constants, and creating our keychains. (make sure you've installed the latest version of bcoin into your project with npm install bcoin).

    -

    Note that we're setting the fundingTarget and amountToFund as constants for simplicity, but they could be set based on user input or some other variable circumstances.

    -
    'use strict';
    -
    -const assert = require('assert');
    -const bcoin = require('bcoin');
    -
    -const MTX = bcoin.mtx;
    -const Keyring = bcoin.keyring;
    -const Outpoint = bcoin.outpoint;
    -const Script = bcoin.script;
    -const Coin = bcoin.coin;
    -const policy = bcoin.protocol.policy
    -
    -const fundingTarget = 100000000; // 1 BTC
    -const amountToFund = 50000000; // .5 BTC
    -const txRate = 10000; // 10000 satoshis/kb

    Let's derive private hd key for fundee and two funders and create a "keyring" object for each (much of this is borrowed from the Working with Transactions guide).

    -

    A keyring object is basically a key manager that is also able to tell you info such as:

    -
      -
    • your redeem script
    • -
    • your scripthash
    • -
    • your program hash
    • -
    • your pubkey hash
    • -
    • your scripthash program hash
    • -
    -
    // Create an HD master keypair.
    -const master = bcoin.hd.generate();
    -
    -const fundeeKey = master.derive(0);
    -const fundeeKeyring = new Keyring(fundeeKey.privateKey);
    -const fundeeAddress = fundeeKeyring.getAddress();
    -
    -// Derive 2 more private hd keys and keyrings for funders
    -const funder1Key = master.derive(1);
    -const funder1Keyring = new Keyring(funder1Key.privateKey);
    -
    -const funder2Key = master.derive(2);
    -const funder2Keyring = new Keyring(funder2Key.privateKey);
    -
    -const funders = [funder1Keyring, funder2Keyring];

    Step 2: Fund the Keyrings

    This example is working in an isolated environment, so it won't work on the actual network (main, test, or otherwise), but we also don't have to earn or send ourselves coins or wait for confirmation times. That means that we can "spawn" coins for our funder wallets that they can use to spend on the crowdfunding platform.

    -

    Let's create some coinbase transactions to give our keyrings some coins that they can spend.

    -
    // create some coinbase transactions to fund our wallets
    -const coins = {};
    -
    -for(let i=0; i < funders.length; i++) {
    -  const cb = new MTX();
    -
    -  // Add a typical coinbase input
    -  cb.addInput({
    -    prevout: new Outpoint(),
    -    script: new Script()
    -  });
    -
    -  cb.addOutput({
    -    address: funders[i].getAddress(),
    -    value: 500000000 // give the funder 5BTC
    -  });
    -
    -  assert(cb.inputs[0].isCoinbase());
    -
    -  // Convert the coinbase output to a Coin
    -  // object and add it to the available coins for that keyring.
    -  // In reality you might get these coins from a wallet.
    -  coins[i] = [Coin.fromTX(cb, 0, -1)];
    -}

    The coins object you've created above should look something like this:

    -
    {
    -  "0":
    -      [
    -        {
    -          "type": "pubkeyhash",
    -          "version": 1,
    -          "height": -1,
    -          "value": "5.0",
    -          "script": <Script: OP_DUP OP_HASH160 0x14 0x64cc4e55b2daec25431bd879ef39302a77c1c1ce OP_EQUALVERIFY OP_CHECKSIG>,
    -          "coinbase": true,
    -          "hash": "151e5551cdcec5fff06818fb78ac3d584361276e862b5700110ec8321869d650",
    -          "index": 0,
    -          "address": <Address: type=pubkeyhash version=-1 str=mphvcfcFneRZvyYsmzhy57cSDzFbGrWaRb>
    -        }
    -      ],
    -  "1": [...]
    -}

    Step 4: Prepare your Coins

    Above, we just funded our funder accounts with a single 5BTC outpoint. This means that the next transaction funded from these accounts can only use that one outpoint (or coin) as an input and send the remainder back as change. Remember, in Bitcoin the way you send funds is you fund a transaction with a full UTXO (in this case we only have one worth 5BTC available to our keychains) and then send the change back to yourself as an additional output. Since ALL|ANYONECANPAY transactions mean a fixed output, you can't add new change outputs without other signatures becoming invalid which means we need a coin available equal to the amount we want to contribute to the crowdfund.

    -

    So what we want to do is have each funder create a coin (UTXO) with the value of what they want to donate.

    -

    Let's create an asyncronous utility function to help us split the coinbases we created in the previous step (we use the fund method on the MTX primitive to do this, which is asynchronous).

    -
    const splitCoinbase = async function splitCoinbase(keyrings, coins, targetAmount, txRate) {
    -
    -  // loop through each coinbase coin to split
    -  for(const coinsIndex in coins) {
    -    // funder will be at the same index as the key of the coins we are accessing
    -    const funderKeyring = keyrings[coinsIndex];
    -    const mtx = new MTX();
    -
    -    assert(coins[coinsIndex][0].value > targetAmount, 'coin value is not enough!');
    -
    -    // create a transaction that will have an output equal to what we want to fund
    -    mtx.addOutput({
    -      address: funderKeyring.getAddress(),
    -      value: targetAmount
    -    });
    -
    -    // shift off the coinbase coin to use to fund the splitting transaction
    -    // the fund method will automatically split the remaining funds to the change address
    -    await mtx.fund([coins[coinsIndex].shift()], {
    -      rate: txRate,
    -      // send change back to an address belonging to the funder
    -      changeAddress: funderKeyring.getAddress()
    -    }).then(() => {
    -      // sign the mtx to finalize split
    -      mtx.sign(funderKeyring);
    -      assert(mtx.verify());
    -
    -      const tx = mtx.toTX();
    -      assert(tx.verify(mtx.view));
    -
    -      const outputs = tx.outputs;
    -
    -      // get coins from tx
    -      outputs.forEach((outputs, index) => {
    -        coins[coinsIndex].push(Coin.fromTX(tx, index, -1));
    -      });
    -    })
    -    .catch(e => console.log('There was an error: ', e));
    -  }
    -
    -  return coins;
    -};

    Because of the async methods being used, in order to take advantage of the async/await structure, the rest of the code will be enclosed in an async function.

    -

    The first thing we'll do is split the coinbase coins we created earlier using the utility function we just built.

    -
    const composeCrowdfund = async function composeCrowdfund() {
    -  const funderCoins = await splitCoinbase(funders, coins, amountToFund, txRate);
    -  // ... we'll keep filling out the rest of the code here
    -};

    funderCoins should return x number of coin arrays, where X is the number of coinbases we created earlier (should be 2) with each array having a coin equal to the amount we want to donate.

    -

    For example...

    -
    {
    -  "0":
    -     [
    -      {
    -        "type": "pubkeyhash",
    -        "version": 1,
    -        "height": -1,
    -        "value": "0.5",
    -        "script": <Script: OP_DUP OP_HASH160 0x14 0x62f725e83caf894aa6c3efd29ef28649fc448825 OP_EQUALVERIFY OP_CHECKSIG>,
    -        "coinbase": false,
    -        "hash": "774822d84bd5af02f1b3eacd6215e0a1bcf07cfb6675a000c8a01d2ea34f2a32",
    -        "index": 0,
    -        "address": <Address: type=pubkeyhash version=-1 str=mpYEb17KR7MVhuPZT1GsW3SywZx8ihYube>
    -       },
    -         ...
    -      ],
    -  "1": [...]
    -}

    Step 5: Calculating the Fee

    We have a tricky problem now. In a real world situation you're not going to know how many inputs (i.e. funders) you will have. But the more inputs you have, the bigger the transaction and thus the higher the fee you will need to broadcast it. The best we can do is to estimate the size based off of the max number of inputs we are willing to accept.

    -

    In our example, we know there are two inputs. In a more complex application, you might put a cap of say 5, then estimate the fee based on that. If there turn out to be fewer then you just have a relatively high fee.

    -

    So, let's next add a utility for estimating a max fee. The way this will work is by creating a mock transaction that uses one of our coins to add inputs up to our maximum, calculate the size of the transaction and the fee based on that amount. We'll also build a utility that we'll need again later for adding coins to a tx and signing the input created.

    -
    const getMaxFee = function getMaxFee(maxInputs, coin, address, keyring, rate) {
    -  const fundingTarget = 100000000; // 1 BTC (arbitrary for purposes of this function)
    -  const testMTX = new MTX();
    -
    -  testMTX.addOutput({ value: fundingTarget, address })
    -
    -  while(testMTX.inputs.length < maxInputs) {
    -    const index = testMTX.inputs.length;
    -    addInput(coin, index, testMTX, keyring);
    -  }
    -
    -  return testMTX.getMinFee(null, rate);
    -}
    -
    -const addInput = function addInput(coin, inputIndex, mtx, keyring, hashType) {
    -  const sampleCoin = coin instanceof Coin ? coin : Coin.fromJSON(coin);
    -  if(!hashType) hashType = Script.hashType.ANYONECANPAY | Script.hashType.ALL;
    -
    -  mtx.addCoin(sampleCoin);
    -  mtx.scriptInput(inputIndex, sampleCoin, keyring);
    -  mtx.signInput(inputIndex, sampleCoin, keyring, hashType);
    -  assert(mtx.isSigned(), 'Input was not signed properly');
    -}

    Now we can get the fee for our funder transaction. We'll assume a max of 2 inputs, but this can be variable.

    -
    const composeCrowdfund = async function composeCrowdfund() {
    -  //...
    -  const maxInputs = 2;
    -  const maxFee = getMaxFee(
    -    maxInputs,
    -    funderCoins['0'][0],
    -    fundeeAddress,
    -    funder1Keyring,
    -    txRate
    -  );
    -
    -  console.log(`Based on a rate of ${txRate} satoshis/kb and a tx with max ${maxInputs}`);
    -  console.log(`the tx fee should be ${maxFee} satoshis`);
    -};

    This should log something like:

    -
    -

    Based on a rate of 10000 satoshis/kb and a tx with max 2 inputs, the tx fee should be 3380 satoshis

    -
    -

    Step 6: Construct the Transaction

    Now that we've got our tools and coins ready, we can start to build the transaction! Note that we are going to use the maxFee we calculated earlier and subtract it from the output we add to the mtx.

    -
    const composeCrowdfund = async function composeCrowdfund() {
    -  //...
    -
    -  const fundMe = new MTX();
    -
    -  // add an output with the target funding amount minus the fee calculated earlier
    -  fundMe.addOutput({ value: fundingTarget - maxFee, address: fundeeAddress });
    -
    -  // fund with first funder (using the utility we built earlier)
    -  let fundingCoin = funderCoins['0'][0];
    -  addInput(fundingCoin, 0, fundMe, funder1Keyring);
    -
    -  // fund with second funder
    -  fundingCoin = funderCoins['1'][0];
    -  addInput(fundingCoin, 1, fundMe, funder2Keyring);
    -
    -  // We want to confirm that total value of inputs covers the funding goal
    -  // NOTE: the difference goes to the miner in the form of fees
    -  assert(fundMe.getInputValue() >= fundMe.outputs[0].value, 'Total inputs not enough to fund');
    -  assert(fundMe.verify(), 'The mtx is malformed');
    -
    -  const tx = fundMe.toTX();
    -  assert(tx.verify(fundMe.view), 'there is a problem with your tx');
    -
    -  return tx;
    -};
    -
    -composeCrowdfund().then(myCrowdfundTx => console.log(myCrowdfundTx));

    composeCrowdfund() will now return a promise (thanks to async/await) that returns a fully templated transaction that can be transmitted to the network. It should have two inputs and one output equal to the funding target minus the fee. You should also notice the SIGHASH flag 0x81 at the end of the input scripts which confirms they are ALL|ANYONECANPAY scripts.

    -

    Version 2: Using the Bcoin Wallet System

    Since trying to manage your own keys is pretty tedious, not to mention impractical especially if you want to let other people contribute to your campaign, let's use the bcoin wallet system to take care of that part of the process. For the this example, we're going to interact via the wallet client, but you could also do something similar within a bcoin node (using the wallet and walletdb directly) which would also be more secure.

    -

    Note that this is a little more tedious to test since you need to have funded wallets in order to actually fund your campaign. You can find a bitcoin testnet faucet to get some funds to play with or, as we will do in this example, create your own simnet or regtest network where you mine your own blocks to fund yourself.

    -

    Step 1: Setup Our Wallets

    We'll skip the setup of constants since and import modules since we can use the same from the previous example. Since there are a lot of asynchronous operations here now that we're using the client, we'll also put this in an async function and will continue to build out the contents of the function throughout this guide.

    -
    const network = 'simnet';
    -const composeWalletCrowdfund = async function composeWalletCrowdfund() {
    -  const client = await new bcoin.http.Client({ network });
    -
    -  // Step 1: Setup our wallets and funding targets
    -  const fundeeWallet = await new httpWallet({ id: 'fundee', network });
    -  const fundeeAddress = await fundeeWallet.createAddress('default');
    -  const funders = {
    -    'funder1': await new httpWallet({ id: 'funder1', network }),
    -    'funder2': await new httpWallet({ id: 'funder2', network })
    -  };
    -  //...
    -}

    Step 2: Prepare Your Coins

    We have the same issue to deal with here as in step 2 in the previous example: we need to have "exact change" coins available for funding. The process of splitting is a little different with the wallet system though so let's walk through this version

    -
    const composeWalletCrowdfund = async function composeWalletCrowdfund() {
    -  //...
    -
    -  const fundingCoins = {};
    -
    -  // go through each funding wallet to prepare coins
    -  for(let id in funders) {
    -    const funder = funders[id];
    -
    -    const coins = await funder.getCoins();
    -    const funderInfo = await funder.getInfo();
    -
    -    // go through available coins to find a coin equal to or greater than value to fund
    -
    -    // We didn't do this before because we knew what coins were available. But if we have one already in our wallets, then we can just use that!
    -    let fundingCoin = {};
    -    for(let coin of coins) {
    -      if (coin.value === amountToFund) {
    -        // if we already have a coin of the right value we can use that
    -        fundingCoin = coin;
    -        break;
    -      }
    -    }
    -
    -    if (!Object.keys(fundingCoin).length) {
    -      // if we don't have a coin of the right amount to fund with
    -      // we need to create one by sending the funder wallet
    -      // a tx that includes an output of the right amount
    -
    -      // this is similar to what we did in the manual version
    -      const receiveAddress = await funder.createAddress('default') // send it back to the funder
    -      const tx = await funder.send({
    -        rate,
    -        outputs: [{
    -          value: amountToFund,
    -          address: receiveAddress.address
    -        }]
    -      });
    -
    -      // get index of ouput for fundingCoin
    -      let coinIndex;
    -      for (let i=0; i < tx.outputs.length; i++) {
    -        if (tx.outputs[i].value === amountToFund) {
    -          coinIndex = i;
    -          break;
    -        }
    -      }
    -
    -      assert(tx.outputs[coinIndex].value === amountToFund, 'value of output at index not correct');
    -
    -      // first argument is for the account
    -      // default is being used for all examples
    -      fundingCoin = await funder.getCoin('default', tx.hash, coinIndex);
    -    }
    -    fundingCoins[funder.id] = fundingCoin;
    -  }
    -}

    We now should have an object available that has the information of the coins we will be using to fund our transaction with mapped to the name of each wallet (so we can retrieve the wallet information later). It should look something like this:

    -
    {
    -  "funder1": {
    -    "version": 1,
    -    "height": -1,
    -    "value": 50000000,
    -    "script": "76a914127cb1a40212169c49fe22d13307b18af1fa07ad88ac",
    -    "address": "SNykaBMuTyeUQkK8exZyymWNrnYX5vVPuY",
    -    "coinbase": false,
    -    "hash": "163068016a39e2d9c869bcdb8646dbca93e07824db39217b5c444e7c61d1a82c",
    -    "index": 0
    -  },
    -  "funder2": {
    -    "version": 1,
    -    "height": -1,
    -    "value": 50000000,
    -    "script": "76a9146e08c73b355e690ba0b1198d578c4c6c52b3813688ac",
    -    "address": "SXKossD65D7h62fhzGrntERBrPeXUfiC92",
    -    "coinbase": false,
    -    "hash": "11f3180c5069f2692f1ee1463257b21dc217441e792493c8f5ee230c35d97d96",
    -    "index": 0
    -  }
    -}

    Step 3: Create our Crowdfund Tx

    We also have to include the getmaxFee step as before, but we can use the same utility that we created for the other example. The way that we're funding the inputs is pretty insecure as we are sending private keys unencrypted across the network, but we'll leave for the sake of the example. DON'T USE THIS IN PRODUCTION.

    -
    const composeWalletCrowdfund = async function composeWalletCrowdfund() {
    -  //...
    -  const testKey = await funders['funder1'].getWIF(fundingCoins['funder1'].address);
    -  const testKeyring = new bcoin.keyring.fromSecret(testKey.privateKey);
    -  const maxFee = getMaxFee(maxInputs, fundingCoins['funder1'], fundeeAddress.address, testKeyring, rate);
    -
    -  const fundMe = new MTX();
    -
    -  // Use the maxFee to calculate output value for transaction
    -  fundMe.addOutput({value: fundingTarget - maxFee, address: fundeeAddress.address });
    -
    -  // go through our coins and add each as an input in our transaction
    -  let inputCounter = 0;
    -  for(let funder in fundingCoins) {
    -    const wallet = funders[funder];
    -    const coinOptions = fundingCoins[funder];
    -
    -    const key = await wallet.getWIF(coinOptions.address);
    -    const keyring = new bcoin.keyring.fromSecret(key.privateKey);
    -
    -    // this is the same utility as we used in our other example
    -    addInput(coinOptions, inputCounter, fundMe, keyring);
    -    assert(fundMe.isSigned(), 'Input has not been signed correctly');
    -    inputCounter++;
    -  }
    -
    -  // confirm that the transaction has been properly templated and signed
    -  assert(
    -    fundMe.inputs.length === Object.keys(funders).length,
    -    'Number of inputs in MTX is incorrect'
    -  );
    -  assert(fundMe.verify(), 'MTX is malformed');
    -
    -  // make our transaction immutable so we can send it to th enetwork
    -  const tx = fundMe.toTX();
    -
    -  assert(tx.verify(fundMe.view), 'TX is malformed. Fix before broadcasting');
    -
    -  // Step 6: broadcast tx
    -  try {
    -    const broadcastStatus = await client.broadcast(tx);
    -    return tx;
    -  } catch (e) {
    -    console.log('There was a problem: ', e);
    -  }
    -}
    -composeWalletCrowdfund()
    -  .then(myCrowdfundTx => console.log('Transaction broadcast: ', myCrowdfundTx))
    -  .catch(e => console.log('There was a problem: ', e));

    And there you have it! If you were doing this on testnet, your fundeeWallet should now be 1BTC richer. If you're on a simnet or regtest network, you'll have to mine a block with your transactions to get those funds confirmed. Also note that, unless you have exact change coins, there will be 3 transactions that need to be confirmed: one each for the wallets that are splitting coins, and one for the crowdfund transaction.

    -

    How to Build it Out

    These examples are obviously pretty basic, but they should give you an idea of how to use Bitcoin's scripting to build out the foundation for more complex applications. Here are some ideas on how you could build on top of these examples and get closer to a production ready application.

    -
      -
    • More flexible contribution scheme (currently it's just 2 funders that split the amount evenly). E.g. custom number of contributers, custom contribution amount, etc.
    • -
    • UX to let people interact with the transaction via a browser
    • -
    • More advanced interface for fee estimation and include platform for large number of funders (for example, since you may be limited to number of funders per tx, you could include interface for multiple transactions for a single campaign. You would also want to include a check to make sure your tx is not bigger than 100kb otherwise it'll get rejected by the network)
    • -
    • Add a fund matching scheme where someone can say they will match future contributions
    • -
    • Currently the examples split transactions to make a coin available that equals the target contribution amount. This is expensive since you have to broadcast multiple transactions. An interface to choose to donate from available available coins might help to make this more efficient.
    • -
    • This approach also isn't ideal if funders have non-p2pkh outputs. Something like a multisig will be much bigger and will cause the estimated fee to be too low. One possible approach would be to shift the burden of fees on the funders, have the application calculate the size of their contribution and add the resulting fee to their funding amount. Finally, use this amount when splitting the coins.
    • -
    -

    Make sure to get in touch with us on Twitter or Slack if you build out any of these ideas!

    -

    Again, for a working example of the code (without all the text and explanations), check out the repo on github.

    - - - -
    -
    -
    -
    -
    - - -
    - - - - -
    - -
    -
    - - -
    - -
    - - - - - -
    -
    - -
    -
    -
    -

    Ready to start building? Read the docs!

    - Documentation -
    -
    -
    - -
    -
    - - - -
    -
    - -
    -
    - -
    -
    -
    -
    - -

    Bcoin Development Donation Address:
    3Bi9H1hzCHWJoFEjc4xzVzEMywi35dyvsV

    - -
    -
    -
    -
    -

    Copyright bcoin.io, Purse, 2017, All Rights Reserved.

    - -
    -
    - -
    -
    - - -
    - - - - - - - - - - - - - - - - - - - \ No newline at end of file From da82e7a26e6491d17a494ef200ad9a38ade3bec0 Mon Sep 17 00:00:00 2001 From: Bucko Date: Fri, 6 Oct 2017 16:32:25 -0700 Subject: [PATCH 09/13] update crowdfund guide with new fee calculator --- guides-markdown/crowdfund-tx.md | 241 +++++++++++++++++--------------- 1 file changed, 126 insertions(+), 115 deletions(-) diff --git a/guides-markdown/crowdfund-tx.md b/guides-markdown/crowdfund-tx.md index 9aba1a0..105f4a2 100644 --- a/guides-markdown/crowdfund-tx.md +++ b/guides-markdown/crowdfund-tx.md @@ -3,6 +3,10 @@ Buck Perley ``` +```post-description +Learn how SIGHASH flags work in Bitcoin by building out a custom crowdfunding transaction that lets anyone add their own inputs to a transaction with a fixed output. +``` + ## SIGHASH Flags In most Bitcoin transactions, when a transaction is signed, the entirety of the data in that transaction is committed to in that input's signature, with all outputs and inputs being included in the unlocking script. This, however, doesn't always have to be the case. The level of commitment is indicated by using `SIGHASH` flags. The most common flag is `SIGHASH_ALL` which has a value of 0x01 (you'll see this represented as a `01` at the end of DER-encoded signatures). To learn more about how the flags work and what other options are available, you should checkout [Chapter 6](https://github.com/bitcoinbook/bitcoinbook/blob/8d01749bcf45f69f36cf23606bbbf3f0bd540db3/ch06.asciidoc) of *Mastering Bitcoin* by Andreas Antonopolous. In that chapter, Andreas posits one novel use of a specialized flag, `ALL|ANYONECANPAY`, and in this guide we'll try and build out a couple of examples implementing this idea using bcoin. @@ -128,60 +132,107 @@ Above, we just funded our funder accounts with a single 5BTC outpoint. This mean So what we want to do is have each funder create a coin (UTXO) with the value of what they want to donate. -Let's create an asyncronous utility function to help us split the coinbases we created in the previous step (we use the `fund` method on the `MTX` primitive to do this, which is asynchronous). +The first thing we need to do make this work is calculate what the input will be. In our examples we are assuming that the funders cover the fee. Since different keyrings can be using different transaction types of different sizes (p2sh, multisig, etc.), we need a utility to calculate how much the fee should be for that input and add that to the amount to fund with. + +##### Utility functions +We'll need some utility functions to help us out. It's nice to split these out separate from our main operations since we'll actually be reusing some of the functionality. + +Before we build out the tools to calculate fees and split coins, we'll need a utility for composing the inputs for txs that we'll use for our mock transactions in the fee calculator and later for templating our real transaction ```javascript -const splitCoinbase = async function splitCoinbase(keyrings, coins, targetAmount, txRate) { +const addInput = function addInput(coin, inputIndex, mtx, keyring, hashType) { + const sampleCoin = coin instanceof Coin ? coin : Coin.fromJSON(coin); + if(!hashType) hashType = Script.hashType.ANYONECANPAY | Script.hashType.ALL; + + mtx.addCoin(sampleCoin); + mtx.scriptInput(inputIndex, sampleCoin, keyring); + mtx.signInput(inputIndex, sampleCoin, keyring, hashType); + assert(mtx.isSigned(), 'Input was not signed properly'); +} +``` + +Now let's build the utility for calculating the fee for a single input of unknown type or size + +```javascript +const getFeeForInput = function getFeeForInput(coin, address, keyring, rate) { + const fundingTarget = 100000000; // 1 BTC (arbitrary for purposes of this function) + const testMTX = new MTX(); + // we're not actually going to use this tx for anything other than calculate what fee should be + addInput(coin, 0, testMTX, keyring); + + return testMTX.getMinFee(null, rate); +} +``` + +Our last utility is an asyncronous function to help us split the coinbases we created in the previous step (we use the `fund` method on the `MTX` primitive to do this, which is asynchronous). + +```javascript +const splitCoinbase = async function splitCoinbase(funderKeyring, coin, targetAmount, txRate) { // loop through each coinbase coin to split - for(const coinsIndex in coins) { - // funder will be at the same index as the key of the coins we are accessing - const funderKeyring = keyrings[coinsIndex]; - const mtx = new MTX(); + let coins = []; - assert(coins[coinsIndex][0].value > targetAmount, 'coin value is not enough!'); + const mtx = new MTX(); - // create a transaction that will have an output equal to what we want to fund - mtx.addOutput({ - address: funderKeyring.getAddress(), - value: targetAmount - }); + assert(coin.value > targetAmount, 'coin value is not enough!'); - // shift off the coinbase coin to use to fund the splitting transaction - // the fund method will automatically split the remaining funds to the change address - await mtx.fund([coins[coinsIndex].shift()], { - rate: txRate, - // send change back to an address belonging to the funder - changeAddress: funderKeyring.getAddress() - }).then(() => { - // sign the mtx to finalize split - mtx.sign(funderKeyring); - assert(mtx.verify()); - - const tx = mtx.toTX(); - assert(tx.verify(mtx.view)); - - const outputs = tx.outputs; - - // get coins from tx - outputs.forEach((outputs, index) => { - coins[coinsIndex].push(Coin.fromTX(tx, index, -1)); - }); - }) - .catch(e => console.log('There was an error: ', e)); - } + // creating a transaction that will have an output equal to what we want to fund + mtx.addOutput({ + address: funderKeyring.getAddress(), + value: targetAmount + }); + + // the fund method will automatically split + // the remaining funds to the change address + // Note that in a real application these splitting transactions will also + // have to be broadcast to the network + await mtx.fund([coin], { + rate: txRate, + // send change back to an address belonging to the funder + changeAddress: funderKeyring.getAddress() + }).then(() => { + // sign the mtx to finalize split + mtx.sign(funderKeyring); + assert(mtx.verify()); + + const tx = mtx.toTX(); + assert(tx.verify(mtx.view)); + + const outputs = tx.outputs; + + // get coins from tx + outputs.forEach((outputs, index) => { + coins.push(Coin.fromTX(tx, index, -1)); + }); + }) + .catch(e => console.log('There was an error: ', e)); return coins; }; + ``` Because of the async methods being used, in order to take advantage of the async/await structure, the rest of the code will be enclosed in an async function. -The first thing we'll do is split the coinbase coins we created earlier using the utility function we just built. +The first thing we'll do is split the coinbase coins we created earlier using the utility function we just built (we'll also have to calculate the fee and add it to the funding amount in order to get an output of the right value). ```javascript -const composeCrowdfund = async function composeCrowdfund() { - const funderCoins = await splitCoinbase(funders, coins, amountToFund, txRate); +const composeCrowdfund = async function composeCrowdfund(coins) { + const funderCoins = {}; + // Loop through each coinbase + for (let index in coins) { + const coinbase = coins[index][0]; + // estimate fee for each coin (assuming their split coins will use same tx type) + const estimatedFee = getFeeForInput(coinbase, fundeeAddress, funders[index], txRate); + const targetPlusFee = amountToFund + estimatedFee; + + // split the coinbase with targetAmount plus estimated fee + const splitCoins = await Utils.splitCoinbase(funders[index], coinbase, targetPlusFee, txRate); + + // add to funderCoins object with returned coins from splitCoinbase being value, + // and index being the key + funderCoins[index] = splitCoins; + } // ... we'll keep filling out the rest of the code here }; ``` @@ -198,7 +249,7 @@ For example... "type": "pubkeyhash", "version": 1, "height": -1, - "value": "0.5", + "value": "0.5000157" "script": , "coinbase": false, "hash": "774822d84bd5af02f1b3eacd6215e0a1bcf07cfb6675a000c8a01d2ea34f2a32", @@ -210,75 +261,21 @@ For example... "1": [...] } ``` -#### Step 5: Calculating the Fee - -We have a tricky problem now. In a real world situation you're not going to know how many inputs (i.e. funders) you will have. But the more inputs you have, the bigger the transaction and thus the higher the fee you will need to broadcast it. The best we can do is to estimate the size based off of the max number of inputs we are willing to accept. - -In our example, we know there are two inputs. In a more complex application, you might put a cap of say 5, then estimate the fee based on that. If there turn out to be fewer then you just have a relatively high fee. - -So, let's next add a utility for estimating a max fee. The way this will work is by creating a mock transaction that uses one of our coins to add inputs up to our maximum, calculate the size of the transaction and the fee based on that amount. We'll also build a utility that we'll need again later for adding coins to a tx and signing the input created. - -```javascript -const getMaxFee = function getMaxFee(maxInputs, coin, address, keyring, rate) { - const fundingTarget = 100000000; // 1 BTC (arbitrary for purposes of this function) - const testMTX = new MTX(); - - testMTX.addOutput({ value: fundingTarget, address }) - - while(testMTX.inputs.length < maxInputs) { - const index = testMTX.inputs.length; - addInput(coin, index, testMTX, keyring); - } - - return testMTX.getMinFee(null, rate); -} - -const addInput = function addInput(coin, inputIndex, mtx, keyring, hashType) { - const sampleCoin = coin instanceof Coin ? coin : Coin.fromJSON(coin); - if(!hashType) hashType = Script.hashType.ANYONECANPAY | Script.hashType.ALL; - - mtx.addCoin(sampleCoin); - mtx.scriptInput(inputIndex, sampleCoin, keyring); - mtx.signInput(inputIndex, sampleCoin, keyring, hashType); - assert(mtx.isSigned(), 'Input was not signed properly'); -} - -``` - -Now we can get the fee for our funder transaction. We'll assume a max of 2 inputs, but this can be variable. - -```javascript -const composeCrowdfund = async function composeCrowdfund() { - //... - const maxInputs = 2; - const maxFee = getMaxFee( - maxInputs, - funderCoins['0'][0], - fundeeAddress, - funder1Keyring, - txRate - ); - - console.log(`Based on a rate of ${txRate} satoshis/kb and a tx with max ${maxInputs}`); - console.log(`the tx fee should be ${maxFee} satoshis`); -}; -``` -This should log something like: -> Based on a rate of 10000 satoshis/kb and a tx with max 2 inputs, the tx fee should be 3380 satoshis #### Step 6: Construct the Transaction -Now that we've got our tools and coins ready, we can start to build the transaction! Note that we are going to use the maxFee we calculated earlier and subtract it from the output we add to the mtx. +Now that we've got our tools and coins ready, we can start to build the transaction! ```javascript -const composeCrowdfund = async function composeCrowdfund() { +const composeCrowdfund = async function composeCrowdfund(coins) { //... const fundMe = new MTX(); - // add an output with the target funding amount minus the fee calculated earlier - fundMe.addOutput({ value: fundingTarget - maxFee, address: fundeeAddress }); + // add an output with the target funding amount + + fundMe.addOutput({ value: fundingTarget, address: fundeeAddress }); - // fund with first funder (using the utility we built earlier) + // fund with first funder let fundingCoin = funderCoins['0'][0]; addInput(fundingCoin, 0, fundMe, funder1Keyring); @@ -292,15 +289,18 @@ const composeCrowdfund = async function composeCrowdfund() { assert(fundMe.verify(), 'The mtx is malformed'); const tx = fundMe.toTX(); + console.log('total input value = ', fundMe.getInputValue()); + console.log('Fee getting sent to miners:', fundMe.getInputValue() - fundingTarget, 'satoshis'); + assert(tx.verify(fundMe.view), 'there is a problem with your tx'); return tx; }; -composeCrowdfund().then(myCrowdfundTx => console.log(myCrowdfundTx)); +composeCrowdfund(coins).then(myCrowdfundTx => console.log(myCrowdfundTx)); ``` -`composeCrowdfund()` will now return a promise (thanks to async/await) that returns a fully templated transaction that can be transmitted to the network. It should have two inputs and one output equal to the funding target minus the fee. You should also notice the `SIGHASH` flag 0x81 at the end of the input scripts which confirms they are `ALL|ANYONECANPAY` scripts. +`composeCrowdfund` will now return a promise (thanks to async/await) that returns a fully templated transaction that can be transmitted to the network. It should have two inputs and one output with a value of 1. The inputs will have a value equal to the amount to fund plus the cost of the fee. You should also notice the `SIGHASH` flag 0x81 at the end of the input scripts which confirms they are `ALL|ANYONECANPAY` scripts. ### Version 2: Using the Bcoin Wallet System Since trying to manage your own keys is pretty tedious, not to mention impractical especially if you want to let other people contribute to your campaign, let's use the bcoin wallet system to take care of that part of the process. For the this example, we're going to interact via the wallet client, but you could also do something similar within a bcoin node (using the wallet and walletdb directly) which would also be more secure. @@ -311,7 +311,7 @@ Note that this is a little more tedious to test since you need to have funded wa We'll skip the setup of constants since and import modules since we can use the same from the previous example. Since there are a lot of asynchronous operations here now that we're using the client, we'll also put this in an async function and will continue to build out the contents of the function throughout this guide. ```javascript -const network = 'simnet'; +const network = 'regtest'; const composeWalletCrowdfund = async function composeWalletCrowdfund() { const client = await new bcoin.http.Client({ network }); @@ -327,7 +327,9 @@ const composeWalletCrowdfund = async function composeWalletCrowdfund() { ``` #### Step 2: Prepare Your Coins -We have the same issue to deal with here as in step 2 in the previous example: we need to have "exact change" coins available for funding. The process of splitting is a little different with the wallet system though so let's walk through this version +We have the same issue to deal with here as in step 2 in the previous example: we need to have "exact change" coins available for funding. The process of splitting is a little different with the wallet system though so let's walk through this version. + +Note that the way that we're retrieving the keyring information is pretty insecure as we are sending private keys unencrypted across the network, but we'll leave for the sake of the example. **DON'T USE THIS IN PRODUCTION**. ```javascript const composeWalletCrowdfund = async function composeWalletCrowdfund() { @@ -342,7 +344,15 @@ const composeWalletCrowdfund = async function composeWalletCrowdfund() { const coins = await funder.getCoins(); const funderInfo = await funder.getInfo(); - // go through available coins to find a coin equal to or greater than value to fund + // Before we do anything we need to get + // the fee that will be necessary for each funder's input. + const funderKey = await funder.getWIF(coins[0].address); + const funderKeyring = new bcoin.keyring.fromSecret(funderKey.privateKey); + const feeForInput = Utils.getFeeForInput(coins[0], fundeeAddress.address, funderKeyring, rate); + amountToFund += feeForInput; + + // Next, go through available coins + // to find a coin equal to or greater than value to fund // We didn't do this before because we knew what coins were available. But if we have one already in our wallets, then we can just use that! let fundingCoin = {}; @@ -389,14 +399,14 @@ const composeWalletCrowdfund = async function composeWalletCrowdfund() { } ``` -We now should have an object available that has the information of the coins we will be using to fund our transaction with mapped to the name of each wallet (so we can retrieve the wallet information later). It should look something like this: +We now should have an object available that has the information of the coins we will be using to fund our transaction with mapped to the name of each wallet (so we can retrieve the wallet information later). It should look something like this (note that the values are larger than our funding amount due to the fee): ```json { "funder1": { "version": 1, "height": -1, - "value": 50000000, + "value": 50002370, "script": "76a914127cb1a40212169c49fe22d13307b18af1fa07ad88ac", "address": "SNykaBMuTyeUQkK8exZyymWNrnYX5vVPuY", "coinbase": false, @@ -406,7 +416,7 @@ We now should have an object available that has the information of the coins we "funder2": { "version": 1, "height": -1, - "value": 50000000, + "value": 50002370, "script": "76a9146e08c73b355e690ba0b1198d578c4c6c52b3813688ac", "address": "SXKossD65D7h62fhzGrntERBrPeXUfiC92", "coinbase": false, @@ -417,19 +427,17 @@ We now should have an object available that has the information of the coins we ``` #### Step 3: Create our Crowdfund Tx -We also have to include the `getmaxFee` step as before, but we can use the same utility that we created for the other example. The way that we're funding the inputs is pretty insecure as we are sending private keys unencrypted across the network, but we'll leave for the sake of the example. **DON'T USE THIS IN PRODUCTION**. +Now that we have our coins ready we can start to template the transaction! + +Please note again that the way we are funding the transactions is by sending our private keys unencrypted across the network so don't use this in production. ```javascript const composeWalletCrowdfund = async function composeWalletCrowdfund() { //... - const testKey = await funders['funder1'].getWIF(fundingCoins['funder1'].address); - const testKeyring = new bcoin.keyring.fromSecret(testKey.privateKey); - const maxFee = getMaxFee(maxInputs, fundingCoins['funder1'], fundeeAddress.address, testKeyring, rate); - const fundMe = new MTX(); // Use the maxFee to calculate output value for transaction - fundMe.addOutput({value: fundingTarget - maxFee, address: fundeeAddress.address }); + fundMe.addOutput({value: fundingTarget, address: fundeeAddress.address }); // go through our coins and add each as an input in our transaction let inputCounter = 0; @@ -458,7 +466,11 @@ const composeWalletCrowdfund = async function composeWalletCrowdfund() { assert(tx.verify(fundMe.view), 'TX is malformed. Fix before broadcasting'); - // Step 6: broadcast tx + // check the value of our inputs just to confirm what the fees are + console.log('Total value of inputs: ', fundMe.getInputValue() ); + console.log('Fee to go to miners: ', fundMe.getInputValue() - fundingTarget); + + // Finally, broadcast tx try { const broadcastStatus = await client.broadcast(tx); return tx; @@ -481,7 +493,6 @@ These examples are obviously pretty basic, but they should give you an idea of h - More advanced interface for fee estimation and include platform for large number of funders (for example, since you may be limited to number of funders per tx, you could include interface for multiple transactions for a single campaign. You would also want to include a check to make sure your tx is not bigger than 100kb otherwise it'll get rejected by the network) - Add a fund matching scheme where someone can say they will match future contributions - Currently the examples split transactions to make a coin available that equals the target contribution amount. This is expensive since you have to broadcast multiple transactions. An interface to choose to donate from available available coins might help to make this more efficient. -- This approach also isn't ideal if funders have non-p2pkh outputs. Something like a multisig will be much bigger and will cause the estimated fee to be too low. One possible approach would be to shift the burden of fees on the funders, have the application calculate the size of their contribution and add the resulting fee to their funding amount. Finally, use the amount calculated (including the fee) when [splitting the coins](#step-4-prepare-your-coins). This has the added advantage of not needing to limit the number of inputs Make sure to get in touch with us on Twitter or Slack if you build out any of these ideas! From f683cf07cfe07f3a24b9c58b380c832a236d01d8 Mon Sep 17 00:00:00 2001 From: Bucko Date: Fri, 6 Oct 2017 16:42:55 -0700 Subject: [PATCH 10/13] documenation updates and internal linking --- guides-markdown/crowdfund-tx.md | 4 +- guides.html | 2 +- guides/crowdfund-tx.html | 811 ++++++++++++++++++++++++++++++++ guides/multisig-tx.html | 9 +- 4 files changed, 819 insertions(+), 7 deletions(-) create mode 100644 guides/crowdfund-tx.html diff --git a/guides-markdown/crowdfund-tx.md b/guides-markdown/crowdfund-tx.md index 105f4a2..dc6544e 100644 --- a/guides-markdown/crowdfund-tx.md +++ b/guides-markdown/crowdfund-tx.md @@ -18,7 +18,7 @@ Here's how it's explained in *Mastering Bitcoin*: This construction can be used to make a "crowdfunding”-style transaction. Someone attempting to raise funds can construct a transaction with a single output. The single output pays the "goal" amount to the fundraiser. Such a transaction is obviously not valid, as it has no inputs. However, others can now amend it by adding an input of their own, as a donation. They sign their own input with ALL|ANYONECANPAY. Unless enough inputs are gathered to reach the value of the output, the transaction is invalid. Each donation is a "pledge," which cannot be collected by the fundraiser until the entire goal amount is raised. ## The Code -We'll walk through the steps of creating the transaction first without any wallet database or node running. Then we'll do the same thing using bcoin's walletdb to manage the keys to see how it would work in a more realistic application. At the end, we'll put out some ideas of how these can be built upon for a more robust, production ready application. (If this is something you'd be interested in building, [get in touch](http://bcoin.io/slack-signup.html)!). If you want to see the code, checkout the [repo on github](https://github.com/Bucko13/bitcoin-fundraise). +We'll walk through the steps of creating the transaction first without any wallet database or node running. Then we'll do the same thing using bcoin's walletdb to manage the keys to see how it would work in a more realistic application (skip to [Version 2](#version-2-using-the-bcoin-wallet-system)) further in the guide to check it out). At the end, we'll put out some ideas of how these can be built upon for a more robust, production ready application. (If this is something you'd be interested in building, [get in touch](http://bcoin.io/slack-signup.html)!). If you want to see the code, checkout the [repo on github](https://github.com/Bucko13/bitcoin-fundraise). If you're not comfortable with key management, coin selection, and how transactions are constructed, checkout the tutorial on [working with transactions](https://github.com/bcoin-org/bcoin/blob/master/docs/Working-with-transactions.md) first. @@ -308,7 +308,7 @@ Since trying to manage your own keys is pretty tedious, not to mention impractic Note that this is a little more tedious to test since you need to have funded wallets in order to actually fund your campaign. You can find a bitcoin testnet faucet to get some funds to play with or, as we will do in this example, create your own simnet or regtest network where you mine your own blocks to fund yourself. #### Step 1: Setup Our Wallets -We'll skip the setup of constants since and import modules since we can use the same from the previous example. Since there are a lot of asynchronous operations here now that we're using the client, we'll also put this in an async function and will continue to build out the contents of the function throughout this guide. +We'll skip the setup of constants import modules since we can use the same from the previous example. Since there are a lot of asynchronous operations here now that we're using the client, we'll also put this in an async function and will continue to build out the contents of the function throughout this guide. We'll also be using some of the same [utility functions that we created in the last example](#utility-functions). ```javascript const network = 'regtest'; diff --git a/guides.html b/guides.html index 0d7830c..1ecb75f 100644 --- a/guides.html +++ b/guides.html @@ -432,7 +432,7 @@ -

    +

    Learn how SIGHASH flags work in Bitcoin by building out a custom crowdfunding transaction that lets anyone add their own inputs to a transaction with a fixed output.

    Start ›
    diff --git a/guides/crowdfund-tx.html b/guides/crowdfund-tx.html new file mode 100644 index 0000000..92dbe5c --- /dev/null +++ b/guides/crowdfund-tx.html @@ -0,0 +1,811 @@ + + + + + + + + + bcoin | Extending Bitcoin into Enterprise & Production + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    Loading... + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + +
    +
    + + + + + + + +
    + +
    + + + + +
    +
    + + + +
    + + +
    +
    + +
    +
    + +

     Guides  and  Videos 

    + +
    +
    + +
    +
    + + + +
    +
    + +
    + + + + + +
    + +
    +
    + + + +
    +
    +
    + +

    Creating a Crowdfunding Transaction

    SIGHASH Flags

    In most Bitcoin transactions, when a transaction is signed, the entirety of the data in that transaction is committed to in that input's signature, with all outputs and inputs being included in the unlocking script. This, however, doesn't always have to be the case. The level of commitment is indicated by using SIGHASH flags. The most common flag is SIGHASH_ALL which has a value of 0x01 (you'll see this represented as a 01 at the end of DER-encoded signatures). To learn more about how the flags work and what other options are available, you should checkout Chapter 6 of Mastering Bitcoin by Andreas Antonopolous. In that chapter, Andreas posits one novel use of a specialized flag, ALL|ANYONECANPAY, and in this guide we'll try and build out a couple of examples implementing this idea using bcoin.

    +

    How it Works

    The ALL|ANYONECANPAY flag indicates that a signature is committing all of the outputs and just one input. The suggested use case of this proposed in Mastering Bitcoin is a kickstarter-like crowdfunding application. Consider the situation where you have a fundraising goal, say 1BTC. You'd like multiple people to contribute to your goal and you want to prove to them that they won't have to pay unless you reach your goal (i.e. the transaction is invalid and won't be accepted by the network if attempted to be sent). This means you'd be committing to one output, the one that sends the funds to the charity or project you'd like to donate to, and only one input, your contribution. This allows multiple users to contribute to the same transaction and that transaction won't be valid until it's fully funded.

    +

    Here's how it's explained in Mastering Bitcoin:

    +
    +

    ALL|ANYONECANPAY +This construction can be used to make a "crowdfunding”-style transaction. Someone attempting to raise funds can construct a transaction with a single output. The single output pays the "goal" amount to the fundraiser. Such a transaction is obviously not valid, as it has no inputs. However, others can now amend it by adding an input of their own, as a donation. They sign their own input with ALL|ANYONECANPAY. Unless enough inputs are gathered to reach the value of the output, the transaction is invalid. Each donation is a "pledge," which cannot be collected by the fundraiser until the entire goal amount is raised.

    +
    +

    The Code

    We'll walk through the steps of creating the transaction first without any wallet database or node running. Then we'll do the same thing using bcoin's walletdb to manage the keys to see how it would work in a more realistic application (skip to Version 2) further in the guide to check it out). At the end, we'll put out some ideas of how these can be built upon for a more robust, production ready application. (If this is something you'd be interested in building, get in touch!). If you want to see the code, checkout the repo on github.

    +

    If you're not comfortable with key management, coin selection, and how transactions are constructed, checkout the tutorial on working with transactions first.

    +

    Version 1 - Manual Key Management

    Step 1: Setup

    Let's first start by importing the right tools, setting up some constants, and creating our keychains. (make sure you've installed the latest version of bcoin into your project with npm install bcoin).

    +

    Note that we're setting the fundingTarget and amountToFund as constants for simplicity, but they could be set based on user input or some other variable circumstances.

    +
    'use strict';
    +
    +const assert = require('assert');
    +const bcoin = require('bcoin');
    +
    +const MTX = bcoin.mtx;
    +const Keyring = bcoin.keyring;
    +const Outpoint = bcoin.outpoint;
    +const Script = bcoin.script;
    +const Coin = bcoin.coin;
    +const policy = bcoin.protocol.policy
    +
    +const fundingTarget = 100000000; // 1 BTC
    +const amountToFund = 50000000; // .5 BTC
    +const txRate = 10000; // 10000 satoshis/kb

    Let's derive private hd key for fundee and two funders and create a "keyring" object for each (much of this is borrowed from the Working with Transactions guide).

    +

    A keyring object is basically a key manager that is also able to tell you info such as:

    +
      +
    • your redeem script
    • +
    • your scripthash
    • +
    • your program hash
    • +
    • your pubkey hash
    • +
    • your scripthash program hash
    • +
    +
    // Create an HD master keypair.
    +const master = bcoin.hd.generate();
    +
    +const fundeeKey = master.derive(0);
    +const fundeeKeyring = new Keyring(fundeeKey.privateKey);
    +const fundeeAddress = fundeeKeyring.getAddress();
    +
    +// Derive 2 more private hd keys and keyrings for funders
    +const funder1Key = master.derive(1);
    +const funder1Keyring = new Keyring(funder1Key.privateKey);
    +
    +const funder2Key = master.derive(2);
    +const funder2Keyring = new Keyring(funder2Key.privateKey);
    +
    +const funders = [funder1Keyring, funder2Keyring];

    Step 2: Fund the Keyrings

    This example is working in an isolated environment, so it won't work on the actual network (main, test, or otherwise), but we also don't have to earn or send ourselves coins or wait for confirmation times. That means that we can "spawn" coins for our funder wallets that they can use to spend on the crowdfunding platform.

    +

    Let's create some coinbase transactions to give our keyrings some coins that they can spend.

    +
    // create some coinbase transactions to fund our wallets
    +const coins = {};
    +
    +for(let i=0; i < funders.length; i++) {
    +  const cb = new MTX();
    +
    +  // Add a typical coinbase input
    +  cb.addInput({
    +    prevout: new Outpoint(),
    +    script: new Script()
    +  });
    +
    +  cb.addOutput({
    +    address: funders[i].getAddress(),
    +    value: 500000000 // give the funder 5BTC
    +  });
    +
    +  assert(cb.inputs[0].isCoinbase());
    +
    +  // Convert the coinbase output to a Coin
    +  // object and add it to the available coins for that keyring.
    +  // In reality you might get these coins from a wallet.
    +  coins[i] = [Coin.fromTX(cb, 0, -1)];
    +}

    The coins object you've created above should look something like this:

    +
    {
    +  "0":
    +      [
    +        {
    +          "type": "pubkeyhash",
    +          "version": 1,
    +          "height": -1,
    +          "value": "5.0",
    +          "script": <Script: OP_DUP OP_HASH160 0x14 0x64cc4e55b2daec25431bd879ef39302a77c1c1ce OP_EQUALVERIFY OP_CHECKSIG>,
    +          "coinbase": true,
    +          "hash": "151e5551cdcec5fff06818fb78ac3d584361276e862b5700110ec8321869d650",
    +          "index": 0,
    +          "address": <Address: type=pubkeyhash version=-1 str=mphvcfcFneRZvyYsmzhy57cSDzFbGrWaRb>
    +        }
    +      ],
    +  "1": [...]
    +}

    Step 4: Prepare your Coins

    Above, we just funded our funder accounts with a single 5BTC outpoint. This means that the next transaction funded from these accounts can only use that one outpoint (or coin) as an input and send the remainder back as change. Remember, in Bitcoin the way you send funds is you fund a transaction with a full UTXO (in this case we only have one worth 5BTC available to our keychains) and then send the change back to yourself as an additional output. Since ALL|ANYONECANPAY transactions mean a fixed output, you can't add new change outputs without other signatures becoming invalid which means we need a coin available equal to the amount we want to contribute to the crowdfund.

    +

    So what we want to do is have each funder create a coin (UTXO) with the value of what they want to donate.

    +

    The first thing we need to do make this work is calculate what the input will be. In our examples we are assuming that the funders cover the fee. Since different keyrings can be using different transaction types of different sizes (p2sh, multisig, etc.), we need a utility to calculate how much the fee should be for that input and add that to the amount to fund with.

    +
    Utility functions

    We'll need some utility functions to help us out. It's nice to split these out separate from our main operations since we'll actually be reusing some of the functionality.

    +

    Before we build out the tools to calculate fees and split coins, we'll need a utility for composing the inputs for txs that we'll use for our mock transactions in the fee calculator and later for templating our real transaction

    +
    const addInput = function addInput(coin, inputIndex, mtx, keyring, hashType) {
    +  const sampleCoin = coin instanceof Coin ? coin : Coin.fromJSON(coin);
    +  if(!hashType) hashType = Script.hashType.ANYONECANPAY | Script.hashType.ALL;
    +
    +  mtx.addCoin(sampleCoin);
    +  mtx.scriptInput(inputIndex, sampleCoin, keyring);
    +  mtx.signInput(inputIndex, sampleCoin, keyring, hashType);
    +  assert(mtx.isSigned(), 'Input was not signed properly');
    +}

    Now let's build the utility for calculating the fee for a single input of unknown type or size

    +
    const getFeeForInput = function getFeeForInput(coin, address, keyring, rate) {
    +  const fundingTarget = 100000000; // 1 BTC (arbitrary for purposes of this function)
    +  const testMTX = new MTX();
    +
    +  // we're not actually going to use this tx for anything other than calculate what fee should be
    +  addInput(coin, 0, testMTX, keyring);
    +
    +  return testMTX.getMinFee(null, rate);
    +}

    Our last utility is an asyncronous function to help us split the coinbases we created in the previous step (we use the fund method on the MTX primitive to do this, which is asynchronous).

    +
    const splitCoinbase = async function splitCoinbase(funderKeyring, coin, targetAmount, txRate) {
    +  // loop through each coinbase coin to split
    +  let coins = [];
    +
    +  const mtx = new MTX();
    +
    +  assert(coin.value > targetAmount, 'coin value is not enough!');
    +
    +  // creating a transaction that will have an output equal to what we want to fund
    +  mtx.addOutput({
    +    address: funderKeyring.getAddress(),
    +    value: targetAmount
    +  });
    +
    +  // the fund method will automatically split
    +  // the remaining funds to the change address
    +  // Note that in a real application these splitting transactions will also
    +  // have to be broadcast to the network
    +  await mtx.fund([coin], {
    +    rate: txRate,
    +    // send change back to an address belonging to the funder
    +    changeAddress: funderKeyring.getAddress()
    +  }).then(() => {
    +    // sign the mtx to finalize split
    +    mtx.sign(funderKeyring);
    +    assert(mtx.verify());
    +
    +    const tx = mtx.toTX();
    +    assert(tx.verify(mtx.view));
    +
    +    const outputs = tx.outputs;
    +
    +    // get coins from tx
    +    outputs.forEach((outputs, index) => {
    +      coins.push(Coin.fromTX(tx, index, -1));
    +    });
    +  })
    +  .catch(e => console.log('There was an error: ', e));
    +
    +  return coins;
    +};

    Because of the async methods being used, in order to take advantage of the async/await structure, the rest of the code will be enclosed in an async function.

    +

    The first thing we'll do is split the coinbase coins we created earlier using the utility function we just built (we'll also have to calculate the fee and add it to the funding amount in order to get an output of the right value).

    +
    const composeCrowdfund = async function composeCrowdfund(coins) {
    +  const funderCoins = {};
    +  // Loop through each coinbase
    +  for (let index in coins) {
    +    const coinbase = coins[index][0];
    +    // estimate fee for each coin (assuming their split coins will use same tx type)
    +    const estimatedFee = getFeeForInput(coinbase, fundeeAddress, funders[index], txRate);
    +    const targetPlusFee = amountToFund + estimatedFee;
    +
    +    // split the coinbase with targetAmount plus estimated fee
    +    const splitCoins = await Utils.splitCoinbase(funders[index], coinbase, targetPlusFee, txRate);
    +
    +    // add to funderCoins object with returned coins from splitCoinbase being value,
    +    // and index being the key
    +    funderCoins[index] = splitCoins;
    +  }
    +  // ... we'll keep filling out the rest of the code here
    +};

    funderCoins should return x number of coin arrays, where X is the number of coinbases we created earlier (should be 2) with each array having a coin equal to the amount we want to donate.

    +

    For example...

    +
    {
    +  "0":
    +     [
    +      {
    +        "type": "pubkeyhash",
    +        "version": 1,
    +        "height": -1,
    +        "value": "0.5000157"
    +        "script": <Script: OP_DUP OP_HASH160 0x14 0x62f725e83caf894aa6c3efd29ef28649fc448825 OP_EQUALVERIFY OP_CHECKSIG>,
    +        "coinbase": false,
    +        "hash": "774822d84bd5af02f1b3eacd6215e0a1bcf07cfb6675a000c8a01d2ea34f2a32",
    +        "index": 0,
    +        "address": <Address: type=pubkeyhash version=-1 str=mpYEb17KR7MVhuPZT1GsW3SywZx8ihYube>
    +       },
    +         ...
    +      ],
    +  "1": [...]
    +}

    Step 6: Construct the Transaction

    Now that we've got our tools and coins ready, we can start to build the transaction!

    +
    const composeCrowdfund = async function composeCrowdfund(coins) {
    +  //...
    +
    +  const fundMe = new MTX();
    +
    +  // add an output with the target funding amount
    +
    +  fundMe.addOutput({ value: fundingTarget, address: fundeeAddress });
    +
    +  // fund with first funder
    +  let fundingCoin = funderCoins['0'][0];
    +  addInput(fundingCoin, 0, fundMe, funder1Keyring);
    +
    +  // fund with second funder
    +  fundingCoin = funderCoins['1'][0];
    +  addInput(fundingCoin, 1, fundMe, funder2Keyring);
    +
    +  // We want to confirm that total value of inputs covers the funding goal
    +  // NOTE: the difference goes to the miner in the form of fees
    +  assert(fundMe.getInputValue() >= fundMe.outputs[0].value, 'Total inputs not enough to fund');
    +  assert(fundMe.verify(), 'The mtx is malformed');
    +
    +  const tx = fundMe.toTX();
    +  console.log('total input value = ', fundMe.getInputValue());
    +  console.log('Fee getting sent to miners:', fundMe.getInputValue() - fundingTarget, 'satoshis');
    +
    +  assert(tx.verify(fundMe.view), 'there is a problem with your tx');
    +
    +  return tx;
    +};
    +
    +composeCrowdfund(coins).then(myCrowdfundTx => console.log(myCrowdfundTx));

    composeCrowdfund will now return a promise (thanks to async/await) that returns a fully templated transaction that can be transmitted to the network. It should have two inputs and one output with a value of 1. The inputs will have a value equal to the amount to fund plus the cost of the fee. You should also notice the SIGHASH flag 0x81 at the end of the input scripts which confirms they are ALL|ANYONECANPAY scripts.

    +

    Version 2: Using the Bcoin Wallet System

    Since trying to manage your own keys is pretty tedious, not to mention impractical especially if you want to let other people contribute to your campaign, let's use the bcoin wallet system to take care of that part of the process. For the this example, we're going to interact via the wallet client, but you could also do something similar within a bcoin node (using the wallet and walletdb directly) which would also be more secure.

    +

    Note that this is a little more tedious to test since you need to have funded wallets in order to actually fund your campaign. You can find a bitcoin testnet faucet to get some funds to play with or, as we will do in this example, create your own simnet or regtest network where you mine your own blocks to fund yourself.

    +

    Step 1: Setup Our Wallets

    We'll skip the setup of constants import modules since we can use the same from the previous example. Since there are a lot of asynchronous operations here now that we're using the client, we'll also put this in an async function and will continue to build out the contents of the function throughout this guide. We'll also be using some of the same utility functions that we created in the last example.

    +
    const network = 'regtest';
    +const composeWalletCrowdfund = async function composeWalletCrowdfund() {
    +  const client = await new bcoin.http.Client({ network });
    +
    +  // Step 1: Setup our wallets and funding targets
    +  const fundeeWallet = await new httpWallet({ id: 'fundee', network });
    +  const fundeeAddress = await fundeeWallet.createAddress('default');
    +  const funders = {
    +    'funder1': await new httpWallet({ id: 'funder1', network }),
    +    'funder2': await new httpWallet({ id: 'funder2', network })
    +  };
    +  //...
    +}

    Step 2: Prepare Your Coins

    We have the same issue to deal with here as in step 2 in the previous example: we need to have "exact change" coins available for funding. The process of splitting is a little different with the wallet system though so let's walk through this version.

    +

    Note that the way that we're retrieving the keyring information is pretty insecure as we are sending private keys unencrypted across the network, but we'll leave for the sake of the example. DON'T USE THIS IN PRODUCTION.

    +
    const composeWalletCrowdfund = async function composeWalletCrowdfund() {
    +  //...
    +
    +  const fundingCoins = {};
    +
    +  // go through each funding wallet to prepare coins
    +  for(let id in funders) {
    +    const funder = funders[id];
    +
    +    const coins = await funder.getCoins();
    +    const funderInfo = await funder.getInfo();
    +
    +    // Before we do anything we need to get
    +    // the fee that will be necessary for each funder's input.
    +    const funderKey = await funder.getWIF(coins[0].address);
    +    const funderKeyring = new bcoin.keyring.fromSecret(funderKey.privateKey);
    +    const feeForInput = Utils.getFeeForInput(coins[0], fundeeAddress.address, funderKeyring, rate);
    +    amountToFund += feeForInput;
    +
    +    // Next, go through available coins
    +    // to find a coin equal to or greater than value to fund
    +
    +    // We didn't do this before because we knew what coins were available. But if we have one already in our wallets, then we can just use that!
    +    let fundingCoin = {};
    +    for(let coin of coins) {
    +      if (coin.value === amountToFund) {
    +        // if we already have a coin of the right value we can use that
    +        fundingCoin = coin;
    +        break;
    +      }
    +    }
    +
    +    if (!Object.keys(fundingCoin).length) {
    +      // if we don't have a coin of the right amount to fund with
    +      // we need to create one by sending the funder wallet
    +      // a tx that includes an output of the right amount
    +
    +      // this is similar to what we did in the manual version
    +      const receiveAddress = await funder.createAddress('default') // send it back to the funder
    +      const tx = await funder.send({
    +        rate,
    +        outputs: [{
    +          value: amountToFund,
    +          address: receiveAddress.address
    +        }]
    +      });
    +
    +      // get index of ouput for fundingCoin
    +      let coinIndex;
    +      for (let i=0; i < tx.outputs.length; i++) {
    +        if (tx.outputs[i].value === amountToFund) {
    +          coinIndex = i;
    +          break;
    +        }
    +      }
    +
    +      assert(tx.outputs[coinIndex].value === amountToFund, 'value of output at index not correct');
    +
    +      // first argument is for the account
    +      // default is being used for all examples
    +      fundingCoin = await funder.getCoin('default', tx.hash, coinIndex);
    +    }
    +    fundingCoins[funder.id] = fundingCoin;
    +  }
    +}

    We now should have an object available that has the information of the coins we will be using to fund our transaction with mapped to the name of each wallet (so we can retrieve the wallet information later). It should look something like this (note that the values are larger than our funding amount due to the fee):

    +
    {
    +  "funder1": {
    +    "version": 1,
    +    "height": -1,
    +    "value": 50002370,
    +    "script": "76a914127cb1a40212169c49fe22d13307b18af1fa07ad88ac",
    +    "address": "SNykaBMuTyeUQkK8exZyymWNrnYX5vVPuY",
    +    "coinbase": false,
    +    "hash": "163068016a39e2d9c869bcdb8646dbca93e07824db39217b5c444e7c61d1a82c",
    +    "index": 0
    +  },
    +  "funder2": {
    +    "version": 1,
    +    "height": -1,
    +    "value": 50002370,
    +    "script": "76a9146e08c73b355e690ba0b1198d578c4c6c52b3813688ac",
    +    "address": "SXKossD65D7h62fhzGrntERBrPeXUfiC92",
    +    "coinbase": false,
    +    "hash": "11f3180c5069f2692f1ee1463257b21dc217441e792493c8f5ee230c35d97d96",
    +    "index": 0
    +  }
    +}

    Step 3: Create our Crowdfund Tx

    Now that we have our coins ready we can start to template the transaction!

    +

    Please note again that the way we are funding the transactions is by sending our private keys unencrypted across the network so don't use this in production.

    +
    const composeWalletCrowdfund = async function composeWalletCrowdfund() {
    +  //...
    +  const fundMe = new MTX();
    +
    +  // Use the maxFee to calculate output value for transaction
    +  fundMe.addOutput({value: fundingTarget, address: fundeeAddress.address });
    +
    +  // go through our coins and add each as an input in our transaction
    +  let inputCounter = 0;
    +  for(let funder in fundingCoins) {
    +    const wallet = funders[funder];
    +    const coinOptions = fundingCoins[funder];
    +
    +    const key = await wallet.getWIF(coinOptions.address);
    +    const keyring = new bcoin.keyring.fromSecret(key.privateKey);
    +
    +    // this is the same utility as we used in our other example
    +    addInput(coinOptions, inputCounter, fundMe, keyring);
    +    assert(fundMe.isSigned(), 'Input has not been signed correctly');
    +    inputCounter++;
    +  }
    +
    +  // confirm that the transaction has been properly templated and signed
    +  assert(
    +    fundMe.inputs.length === Object.keys(funders).length,
    +    'Number of inputs in MTX is incorrect'
    +  );
    +  assert(fundMe.verify(), 'MTX is malformed');
    +
    +  // make our transaction immutable so we can send it to th enetwork
    +  const tx = fundMe.toTX();
    +
    +  assert(tx.verify(fundMe.view), 'TX is malformed. Fix before broadcasting');
    +
    +  // check the value of our inputs just to confirm what the fees are
    +  console.log('Total value of inputs: ', fundMe.getInputValue() );
    +  console.log('Fee to go to miners: ', fundMe.getInputValue() - fundingTarget);
    +
    +  // Finally, broadcast tx
    +  try {
    +    const broadcastStatus = await client.broadcast(tx);
    +    return tx;
    +  } catch (e) {
    +    console.log('There was a problem: ', e);
    +  }
    +}
    +composeWalletCrowdfund()
    +  .then(myCrowdfundTx => console.log('Transaction broadcast: ', myCrowdfundTx))
    +  .catch(e => console.log('There was a problem: ', e));

    And there you have it! If you were doing this on testnet, your fundeeWallet should now be 1BTC richer. If you're on a simnet or regtest network, you'll have to mine a block with your transactions to get those funds confirmed. Also note that, unless you have exact change coins, there will be 3 transactions that need to be confirmed: one each for the wallets that are splitting coins, and one for the crowdfund transaction.

    +

    How to Build it Out

    These examples are obviously pretty basic, but they should give you an idea of how to use Bitcoin's scripting to build out the foundation for more complex applications. Here are some ideas on how you could build on top of these examples and get closer to a production ready application.

    +
      +
    • More flexible contribution scheme (currently it's just 2 funders that split the amount evenly). E.g. custom number of contributers, custom contribution amount, etc.
    • +
    • UX to let people interact with the transaction via a browser
    • +
    • More advanced interface for fee estimation and include platform for large number of funders (for example, since you may be limited to number of funders per tx, you could include interface for multiple transactions for a single campaign. You would also want to include a check to make sure your tx is not bigger than 100kb otherwise it'll get rejected by the network)
    • +
    • Add a fund matching scheme where someone can say they will match future contributions
    • +
    • Currently the examples split transactions to make a coin available that equals the target contribution amount. This is expensive since you have to broadcast multiple transactions. An interface to choose to donate from available available coins might help to make this more efficient.
    • +
    +

    Make sure to get in touch with us on Twitter or Slack if you build out any of these ideas!

    +

    Again, for a working example of the code (without all the text and explanations), check out the repo on github.

    + + + +
    +
    +
    +
    +
    + + +
    + + + + +
    + +
    +
    + + +
    + +
    + + + + + +
    +
    + +
    +
    +
    +

    Ready to start building? Read the docs!

    + Documentation +
    +
    +
    + +
    +
    + + + +
    +
    + +
    +
    + +
    +
    +
    +
    + +

    Bcoin Development Donation Address:
    3Bi9H1hzCHWJoFEjc4xzVzEMywi35dyvsV

    + +
    +
    +
    +
    +

    Copyright bcoin.io, Purse, 2017, All Rights Reserved.

    + +
    +
    + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/guides/multisig-tx.html b/guides/multisig-tx.html index 00f87e6..86bbfe4 100644 --- a/guides/multisig-tx.html +++ b/guides/multisig-tx.html @@ -270,6 +270,7 @@
  • Create an OP_RETURN output transaction
  • Creating Multi Signature Transactions
  • Generate an Address
  • +
  • Creating a Crowdfunding Transaction
  • @@ -338,7 +339,7 @@ who'll be signing next. The next person will do the same, until you have After this process is done, your transaction is fully signed and you can broadcast your transaction.

    The Code

    Manual construction

    In this setup, we won't be running a node or running any of the blockchain or wallet functionality of bcoin. This is a slightly more abstract than constructing bare scripts ourselves.
    We'll split code in multiple files and share keys using the current directory (So you can use fresh dir).

    -

    Step 1: Address Creation

    In the following code, we'll import all necessary libraries, generate private and public keys, and create +

    Step 1: Address Creation

    In the following code, we'll import all necessary libraries, generate private and public keys, and create a multisig address.

    'use strict';
     
    @@ -384,7 +385,7 @@ console.log<
     
    const ring1 = KeyRing.generate(compressed, network);

    Here we generate a private key and public key pair. We need to provide information about the network and public key format. There are two Public key formats one compressed and one uncompressed. More details can be found at the Bitcoin Developer Guide

    -

    Step 2: Generate Transaction

    In this part, we assume that we received a transaction on the network with the following information:

    +

    Step 2: Generate Transaction

    In this part, we assume that we received a transaction on the network with the following information:

    Transaction ID: 3b1dd17cc82e2ac43ba62bf8f1c6a0fe805df43911653d22c902571eb3a212ce
    Output index: 0
    Amount: 100 BTC

    @@ -541,7 +542,7 @@ also calculate change and append a new output for it.
    Instead of the code abo convenient to handle all logic manually, and even more complex to deal with all HD wallet logic. So if you have a bcoin node running and you have access to it via HTTP, you can use bcoin.http.Client and bcoin.http.Wallet. These classes provide all API methods described on bcoin and will communicate with the node's Wallets.

    NOTE: You can check API Docs

    -

    Step 1: Address Creation

    In this step we'll create two new wallets for two cosigners. In this demo, they will exist on same node, +

    Step 1: Address Creation

    In this step we'll create two new wallets for two cosigners. In this demo, they will exist on same node, but it shouldn't matter if these two wallets are on the same node or not.

    'use strict';
     
    @@ -656,7 +657,7 @@ and both will be using xpubkey key derivation to come up with new addresses.
     You won't need to share any other public keys, they will derive them for you.
     Depth of the account is the only thing you'll need to keep in mind.
    addSharedKey in wallet/account is used for adding cosigner xpubkeys keys.

    -

    Step 2: Generate Transaction

    We have received transaction

    +

    Step 2: Generate Transaction

    We have received transaction

    Transaction ID: 3c12e1b260354fd2a2848030222c4a66339892f1d63b18752ff80ef4eb0197d2
    Output index: 0
    Amount: 100 BTC

    From 55dc2795385c57fa0f4f7ce83c9d35ba9e30bcdf Mon Sep 17 00:00:00 2001 From: Bucko Date: Fri, 6 Oct 2017 16:50:39 -0700 Subject: [PATCH 11/13] fix typos in multisig guide --- guides-markdown/multisig-tx.md | 45 +++++++++++++++++----------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/guides-markdown/multisig-tx.md b/guides-markdown/multisig-tx.md index 80e0c1d..16e3fb9 100644 --- a/guides-markdown/multisig-tx.md +++ b/guides-markdown/multisig-tx.md @@ -29,16 +29,16 @@ signatories chooses a different `m` or a different `n`, they'll end up with diff You also need to know the pubkey for all cosigners. You can share these pubkeys however you want. Wallets support various ways for sharing pubkeys, using QR Codes or sending base58check encoded strings. After you have collected all pubkeys and agreed on `m` and `n`, -you construct the multisig script and generate P2SH address from that. +you construct the multisig script and generate P2SH address from that. #### Spending Received Transaction After you've received a transaction on your multisig address, you can spend it if the minimum number of signatures are provided -in a signature script. -1. You need all public keys, the same as were used in address generation. -2. From that you can construct the redeem script, that is the original script you constructed for address. +in a signature script. +1. You need all public keys, the same as were used in address generation. +2. From that you can construct the redeem script, that is the original script you constructed for address. 3. Once you have the redeem script, you can start creating the signature script which will be constructed according -to BIP11 and BIP16. +to BIP11 and BIP16. 4. When you prepend your signature, you take this transaction (not yet fully valid) and send it to another pubkey owner, who'll be signing next. The next person will do the same, until you have `m` signatures in the sigscript. @@ -55,9 +55,9 @@ After this process is done, your transaction is fully signed and you can broadca ### Manual construction In this setup, we won't be running a node or running any of the blockchain or wallet functionality of bcoin. -This is a slightly more abstract than constructing bare scripts ourselves. +This is a slightly more abstract than constructing bare scripts ourselves. We'll split code in multiple files and share keys using the current directory (So you can use fresh dir). - + ### Step 1: Address Creation @@ -123,9 +123,9 @@ one compressed and one uncompressed. More details can be found at the [Bitcoin ### Step 2: Generate Transaction In this part, we assume that we received a transaction on the network with the following information: -> Transaction ID: 3b1dd17cc82e2ac43ba62bf8f1c6a0fe805df43911653d22c902571eb3a212ce -> Output index: 0 -> Amount: 100 BTC +> Transaction ID: 3b1dd17cc82e2ac43ba62bf8f1c6a0fe805df43911653d22c902571eb3a212ce +> Output index: 0 +> Amount: 100 BTC We are going to send `50 BTC` to `RF1PJ1VkHG6H9dwoE2k19a5aigWcWr6Lsu` on the regtest network. @@ -257,14 +257,14 @@ assert(spend2.verify(), 'Transaction isnt valid.'); console.log(spend2.toRaw().toString('hex')); ``` -Since there's a lot of code here, I wanted to review a couple of sections. +Since there's a lot of code here, I wanted to review a couple of sections. This snippet below will return a raw transaction and also makes sure the transaction has all the signatures. --- ```js -// send change to ourselves +// send change to ourselves spend1.addOutput({ address: changeAddr, value: Amount.fromBTC('49.99').toValue() @@ -279,7 +279,7 @@ spend1.addCoin(coin); In this next snippet we send change to ourselves and specify it manually. Alternatively, we could also use `MTX.prototype.fund` which automatically allocates coins to outputs, based on the amounts they need and -also calculate change and append a new output for it. +also calculate change and append a new output for it. Instead of the code above, we could have simpler and more automated calculations: @@ -423,7 +423,7 @@ const addSharedKey = async function addSharedKey(client, account, xpubkey, skipR You will notice that we grab the `.account.accountKey`, first key is the xpubkey and both will be using xpubkey key derivation to come up with new addresses. You won't need to share any other public keys, they will derive them for you. -Depth of the account is the only thing you'll need to keep in mind. +Depth of the account is the only thing you'll need to keep in mind. [addSharedKey](http://bcoin.io/api-docs/index.html#add-xpubkey-multisig) in wallet/account is used for adding cosigner xpubkeys keys. @@ -431,9 +431,9 @@ wallet/account is used for adding cosigner xpubkeys keys. We have received transaction -> Transaction ID: 3c12e1b260354fd2a2848030222c4a66339892f1d63b18752ff80ef4eb0197d2 -> Output index: 0 -> Amount: 100 BTC +> Transaction ID: 3c12e1b260354fd2a2848030222c4a66339892f1d63b18752ff80ef4eb0197d2 +> Output index: 0 +> Amount: 100 BTC We are going to send `1 BTC` to `RBg1TLaNuRpH6UTFzogFXhjqubPYZaqWgs` on the regtest network. @@ -457,7 +457,7 @@ const sendTo = 'RBg1TLaNuRpH6UTFzogFXhjqubPYZaqWgs'; // Because we can't sign and spend from account // We can't use `spend` as we do with normal transactions - // since it immediately publishes to the network + // since it immediately publishes to the network // and we need other signatures first. // So we first create the transaction const outputs = [{ address: sendTo, value: Amount.fromBTC(1).toValue() }]; @@ -470,7 +470,7 @@ const sendTo = 'RBg1TLaNuRpH6UTFzogFXhjqubPYZaqWgs'; // also create changeAddress and calculate fee const tx1 = await wallet1.createTX(options); - // Now you can share this raw output + // Now you can share this raw output const raw = tx1.hex; // Wallet2 will also sign the transaction @@ -485,7 +485,7 @@ const sendTo = 'RBg1TLaNuRpH6UTFzogFXhjqubPYZaqWgs'; }); ``` -Here you can see it's much cleaner and easier. +Here you can see it's much cleaner and easier. We still need to manually, using other means, share raw transaction data for signing. @@ -501,8 +501,9 @@ After that we can just broadcast the transaction to the network. ## Final Notes -I hope, this guide gives you opportunity to understand multisig transactions better and build apps on top of it. -You can play with this code and even build use it in production with small changes (e.g. rate estimation). +I hope this guide gives you the opportunity to better understand multisig transactions and build apps on top of it. + +You can play with this code, extend it, and even use it in production with small changes (e.g. rate estimation). Here are some other ideas for how to build out on top of the app we built in this guide: - Build UI for configuring and initializing `m` and `n`. From b4ca51c113453942a975e9376f6f8688ce9cea58 Mon Sep 17 00:00:00 2001 From: Bucko Date: Fri, 6 Oct 2017 16:53:45 -0700 Subject: [PATCH 12/13] update title --- guides-markdown/crowdfund-tx.md | 2 +- guides.html | 4 +-- guides/crowdfund-tx.html | 2 +- guides/generate-address.html | 2 +- guides/install-linux.html | 2 +- guides/install-mac.html | 2 +- guides/install-windows.html | 2 +- guides/multisig-tx.html | 43 +++++++++++++++++++-------------- guides/op_return.html | 4 +-- guides/scripting.html | 2 +- 10 files changed, 36 insertions(+), 29 deletions(-) diff --git a/guides-markdown/crowdfund-tx.md b/guides-markdown/crowdfund-tx.md index dc6544e..83d171b 100644 --- a/guides-markdown/crowdfund-tx.md +++ b/guides-markdown/crowdfund-tx.md @@ -1,4 +1,4 @@ -# Creating a Crowdfunding Transaction +# Create a Crowdfunding Transaction ```post-author Buck Perley ``` diff --git a/guides.html b/guides.html index 1ecb75f..e834aba 100644 --- a/guides.html +++ b/guides.html @@ -262,7 +262,7 @@
    -->
    -

    Create an OP_RETURN output transaction

    +

    Store Data on the Blockchain

    diff --git a/guides/crowdfund-tx.html b/guides/crowdfund-tx.html index 92dbe5c..aacd5fa 100644 --- a/guides/crowdfund-tx.html +++ b/guides/crowdfund-tx.html @@ -267,7 +267,7 @@
    • Intro to Scripting
    • -
    • Create an OP_RETURN output transaction
    • +
    • Store Data on the Blockchain
    • Creating Multi Signature Transactions
    • Generate an Address
    • Creating a Crowdfunding Transaction
    • diff --git a/guides/generate-address.html b/guides/generate-address.html index d256e45..c9a99a7 100644 --- a/guides/generate-address.html +++ b/guides/generate-address.html @@ -267,7 +267,7 @@
      • Intro to Scripting
      • -
      • Create an OP_RETURN output transaction
      • +
      • Store Data on the Blockchain
      • Creating Multi Signature Transactions
      • Generate an Address
      • Creating a Crowdfunding Transaction
      • diff --git a/guides/install-linux.html b/guides/install-linux.html index c40281a..ec556c2 100644 --- a/guides/install-linux.html +++ b/guides/install-linux.html @@ -267,7 +267,7 @@
        • Intro to Scripting
        • -
        • Create an OP_RETURN output transaction
        • +
        • Store Data on the Blockchain
        • Creating Multi Signature Transactions
        • Generate an Address
        • Creating a Crowdfunding Transaction
        • diff --git a/guides/install-mac.html b/guides/install-mac.html index 2a4e8b7..3593f0c 100644 --- a/guides/install-mac.html +++ b/guides/install-mac.html @@ -267,7 +267,7 @@
          • Intro to Scripting
          • -
          • Create an OP_RETURN output transaction
          • +
          • Store Data on the Blockchain
          • Creating Multi Signature Transactions
          • Generate an Address
          • Creating a Crowdfunding Transaction
          • diff --git a/guides/install-windows.html b/guides/install-windows.html index c6558c2..3e7e015 100644 --- a/guides/install-windows.html +++ b/guides/install-windows.html @@ -267,7 +267,7 @@
            • Intro to Scripting
            • -
            • Create an OP_RETURN output transaction
            • +
            • Store Data on the Blockchain
            • Creating Multi Signature Transactions
            • Generate an Address
            • Creating a Crowdfunding Transaction
            • diff --git a/guides/multisig-tx.html b/guides/multisig-tx.html index 86bbfe4..18abc86 100644 --- a/guides/multisig-tx.html +++ b/guides/multisig-tx.html @@ -267,7 +267,7 @@
              • Intro to Scripting
              • -
              • Create an OP_RETURN output transaction
              • +
              • Store Data on the Blockchain
              • Creating Multi Signature Transactions
              • Generate an Address
              • Creating a Crowdfunding Transaction
              • @@ -325,20 +325,21 @@ signatories chooses a different m or a different n, th You also need to know the pubkey for all cosigners. You can share these pubkeys however you want. Wallets support various ways for sharing pubkeys, using QR Codes or sending base58check encoded strings. After you have collected all pubkeys and agreed on m and n, -you construct the multisig script and generate P2SH address from that.

                +you construct the multisig script and generate P2SH address from that.

                Spending Received Transaction

                After you've received a transaction on your multisig address, you can spend it if the minimum number of signatures are provided -in a signature script.

                +in a signature script.

                  -
                1. You need all public keys, the same as were used in address generation.
                2. -
                3. From that you can construct the redeem script, that is the original script you constructed for address.
                4. +
                5. You need all public keys, the same as were used in address generation.
                6. +
                7. From that you can construct the redeem script, that is the original script you constructed for address.
                8. Once you have the redeem script, you can start creating the signature script which will be constructed according -to BIP11 and BIP16.
                9. +to BIP11 and BIP16.
                10. When you prepend your signature, you take this transaction (not yet fully valid) and send it to another pubkey owner, who'll be signing next. The next person will do the same, until you have m signatures in the sigscript.

                After this process is done, your transaction is fully signed and you can broadcast your transaction.

                The Code

                Manual construction

                In this setup, we won't be running a node or running any of the blockchain or wallet functionality of bcoin. -This is a slightly more abstract than constructing bare scripts ourselves.
                We'll split code in multiple files and share keys using the current directory (So you can use fresh dir).

                +This is a slightly more abstract than constructing bare scripts ourselves. +We'll split code in multiple files and share keys using the current directory (So you can use fresh dir).

                Step 1: Address Creation

                In the following code, we'll import all necessary libraries, generate private and public keys, and create a multisig address.

                'use strict';
                @@ -387,7 +388,9 @@ information about the network and public key format. There are two Bitcoin Developer Guide

                Step 2: Generate Transaction

                In this part, we assume that we received a transaction on the network with the following information:

                -

                Transaction ID: 3b1dd17cc82e2ac43ba62bf8f1c6a0fe805df43911653d22c902571eb3a212ce
                Output index: 0
                Amount: 100 BTC

                +

                Transaction ID: 3b1dd17cc82e2ac43ba62bf8f1c6a0fe805df43911653d22c902571eb3a212ce +Output index: 0 +Amount: 100 BTC

                We are going to send 50 BTC to RF1PJ1VkHG6H9dwoE2k19a5aigWcWr6Lsu on the regtest network.

                'use strict';
                @@ -514,11 +517,11 @@ spend2.signI
                 // Let's make sure that the transaction is valid
                 assert(spend2.verify(), 'Transaction isnt valid.');
                 
                -console.log(spend2.toRaw().toString('hex'));

                Since there's a lot of code here, I wanted to review a couple of sections. +console.log(spend2.toRaw().toString('hex'));

                Since there's a lot of code here, I wanted to review a couple of sections. This snippet below will return a raw transaction and also makes sure the transaction has all the signatures.


                -
                // send change to ourselves 
                +
                // send change to ourselves
                 spend1.addOutput({
                   address: changeAddr,
                   value: Amount.fromBTC('49.99').toValue()
                @@ -530,7 +533,8 @@ spend1.addOu
                 spend1.addCoin(coin);

                In this next snippet we send change to ourselves and specify it manually. Alternatively, we could also use MTX.prototype.fund which automatically allocates coins to outputs, based on the amounts they need and -also calculate change and append a new output for it.
                Instead of the code above, we could have simpler and more automated

                +also calculate change and append a new output for it. +Instead of the code above, we could have simpler and more automated

                calculations:

                // this will automatically select coins and
                 // send change back to our address
                @@ -655,11 +659,14 @@ but it shouldn't matter if these two wallets are on the same node or not.

                });

                You will notice that we grab the .account.accountKey, first key is the xpubkey and both will be using xpubkey key derivation to come up with new addresses. You won't need to share any other public keys, they will derive them for you. -Depth of the account is the only thing you'll need to keep in mind.
                addSharedKey in +Depth of the account is the only thing you'll need to keep in mind. +addSharedKey in wallet/account is used for adding cosigner xpubkeys keys.

                Step 2: Generate Transaction

                We have received transaction

                -

                Transaction ID: 3c12e1b260354fd2a2848030222c4a66339892f1d63b18752ff80ef4eb0197d2
                Output index: 0
                Amount: 100 BTC

                +

                Transaction ID: 3c12e1b260354fd2a2848030222c4a66339892f1d63b18752ff80ef4eb0197d2 +Output index: 0 +Amount: 100 BTC

                We are going to send 1 BTC to RBg1TLaNuRpH6UTFzogFXhjqubPYZaqWgs on the regtest network.

                We won't need transaction ID and output index when using wallet API. It will be automatically @@ -680,7 +687,7 @@ allocated from coins by bcoin node wallet service.

                // Because we can't sign and spend from account // We can't use `spend` as we do with normal transactions - // since it immediately publishes to the network + // since it immediately publishes to the network // and we need other signatures first. // So we first create the transaction const outputs = [{ address: sendTo, value: Amount.fromBTC(1).toValue() }]; @@ -693,7 +700,7 @@ allocated from coins by bcoin node wallet service.

                // also create changeAddress and calculate fee const tx1 = await wallet1.createTX(options); - // Now you can share this raw output + // Now you can share this raw output const raw = tx1.hex; // Wallet2 will also sign the transaction @@ -705,7 +712,7 @@ allocated from coins by bcoin node wallet service.

                })().catch((e) => { console.error(e); process.exit(1); -});

                Here you can see it's much cleaner and easier. +});

                Here you can see it's much cleaner and easier. We still need to manually, using other means, share raw transaction data for signing.

                wallet1.createTX(options) will automatically find the coins @@ -713,8 +720,8 @@ sent to the multisig wallet, allocate them for spending, send remaining funds (minus fee) to change address and sign it.

                wallet2.sign will take raw transaction and sign it with according key. After that we can just broadcast the transaction to the network.

                -

                Final Notes

                I hope, this guide gives you opportunity to understand multisig transactions better and build apps on top of it. -You can play with this code and even build use it in production with small changes (e.g. rate estimation).

                +

                Final Notes

                I hope this guide gives you the opportunity to better understand multisig transactions and build apps on top of it.

                +

                You can play with this code, extend it, and even use it in production with small changes (e.g. rate estimation).

                Here are some other ideas for how to build out on top of the app we built in this guide:

                • Build UI for configuring and initializing m and n.
                • diff --git a/guides/op_return.html b/guides/op_return.html index 9c00e4e..6a56e4e 100644 --- a/guides/op_return.html +++ b/guides/op_return.html @@ -267,7 +267,7 @@
    @@ -308,7 +308,7 @@
    -

    Creating a Crowdfunding Transaction

    SIGHASH Flags

    In most Bitcoin transactions, when a transaction is signed, the entirety of the data in that transaction is committed to in that input's signature, with all outputs and inputs being included in the unlocking script. This, however, doesn't always have to be the case. The level of commitment is indicated by using SIGHASH flags. The most common flag is SIGHASH_ALL which has a value of 0x01 (you'll see this represented as a 01 at the end of DER-encoded signatures). To learn more about how the flags work and what other options are available, you should checkout Chapter 6 of Mastering Bitcoin by Andreas Antonopolous. In that chapter, Andreas posits one novel use of a specialized flag, ALL|ANYONECANPAY, and in this guide we'll try and build out a couple of examples implementing this idea using bcoin.

    +

    Create a Crowdfunding Transaction

    SIGHASH Flags

    In most Bitcoin transactions, when a transaction is signed, the entirety of the data in that transaction is committed to in that input's signature, with all outputs and inputs being included in the unlocking script. This, however, doesn't always have to be the case. The level of commitment is indicated by using SIGHASH flags. The most common flag is SIGHASH_ALL which has a value of 0x01 (you'll see this represented as a 01 at the end of DER-encoded signatures). To learn more about how the flags work and what other options are available, you should checkout Chapter 6 of Mastering Bitcoin by Andreas Antonopolous. In that chapter, Andreas posits one novel use of a specialized flag, ALL|ANYONECANPAY, and in this guide we'll try and build out a couple of examples implementing this idea using bcoin.

    How it Works

    The ALL|ANYONECANPAY flag indicates that a signature is committing all of the outputs and just one input. The suggested use case of this proposed in Mastering Bitcoin is a kickstarter-like crowdfunding application. Consider the situation where you have a fundraising goal, say 1BTC. You'd like multiple people to contribute to your goal and you want to prove to them that they won't have to pay unless you reach your goal (i.e. the transaction is invalid and won't be accepted by the network if attempted to be sent). This means you'd be committing to one output, the one that sends the funds to the charity or project you'd like to donate to, and only one input, your contribution. This allows multiple users to contribute to the same transaction and that transaction won't be valid until it's fully funded.

    Here's how it's explained in Mastering Bitcoin:

    diff --git a/guides/generate-address.html b/guides/generate-address.html index c9a99a7..c17f8a3 100644 --- a/guides/generate-address.html +++ b/guides/generate-address.html @@ -270,7 +270,7 @@
  • Store Data on the Blockchain
  • Creating Multi Signature Transactions
  • Generate an Address
  • -
  • Creating a Crowdfunding Transaction
  • +
  • Create a Crowdfunding Transaction
  • diff --git a/guides/install-linux.html b/guides/install-linux.html index ec556c2..37aa6a4 100644 --- a/guides/install-linux.html +++ b/guides/install-linux.html @@ -270,7 +270,7 @@
  • Store Data on the Blockchain
  • Creating Multi Signature Transactions
  • Generate an Address
  • -
  • Creating a Crowdfunding Transaction
  • +
  • Create a Crowdfunding Transaction
  • diff --git a/guides/install-mac.html b/guides/install-mac.html index 3593f0c..e5cd9c3 100644 --- a/guides/install-mac.html +++ b/guides/install-mac.html @@ -270,7 +270,7 @@
  • Store Data on the Blockchain
  • Creating Multi Signature Transactions
  • Generate an Address
  • -
  • Creating a Crowdfunding Transaction
  • +
  • Create a Crowdfunding Transaction
  • diff --git a/guides/install-windows.html b/guides/install-windows.html index 3e7e015..48dee67 100644 --- a/guides/install-windows.html +++ b/guides/install-windows.html @@ -270,7 +270,7 @@
  • Store Data on the Blockchain
  • Creating Multi Signature Transactions
  • Generate an Address
  • -
  • Creating a Crowdfunding Transaction
  • +
  • Create a Crowdfunding Transaction
  • diff --git a/guides/multisig-tx.html b/guides/multisig-tx.html index 18abc86..3062f75 100644 --- a/guides/multisig-tx.html +++ b/guides/multisig-tx.html @@ -270,7 +270,7 @@
  • Store Data on the Blockchain
  • Creating Multi Signature Transactions
  • Generate an Address
  • -
  • Creating a Crowdfunding Transaction
  • +
  • Create a Crowdfunding Transaction
  • @@ -388,9 +388,7 @@ information about the network and public key format. There are two Bitcoin Developer Guide

    Step 2: Generate Transaction

    In this part, we assume that we received a transaction on the network with the following information:

    -

    Transaction ID: 3b1dd17cc82e2ac43ba62bf8f1c6a0fe805df43911653d22c902571eb3a212ce -Output index: 0 -Amount: 100 BTC

    +

    Transaction ID: 3b1dd17cc82e2ac43ba62bf8f1c6a0fe805df43911653d22c902571eb3a212ce
    Output index: 0
    Amount: 100 BTC

    We are going to send 50 BTC to RF1PJ1VkHG6H9dwoE2k19a5aigWcWr6Lsu on the regtest network.

    'use strict';
    @@ -664,9 +662,7 @@ Depth of the account is the only thing you'll need to keep in mind.
     wallet/account is used for adding cosigner xpubkeys keys.

    Step 2: Generate Transaction

    We have received transaction

    -

    Transaction ID: 3c12e1b260354fd2a2848030222c4a66339892f1d63b18752ff80ef4eb0197d2 -Output index: 0 -Amount: 100 BTC

    +

    Transaction ID: c12e1b260354fd2a2848030222c4a66339892f1d63b18752ff80ef4eb0197d2
    Output index: 0
    Amount: 100 BTC

    We are going to send 1 BTC to RBg1TLaNuRpH6UTFzogFXhjqubPYZaqWgs on the regtest network.

    We won't need transaction ID and output index when using wallet API. It will be automatically diff --git a/guides/op_return.html b/guides/op_return.html index 6a56e4e..0eeda58 100644 --- a/guides/op_return.html +++ b/guides/op_return.html @@ -270,7 +270,7 @@

  • Store Data on the Blockchain
  • Creating Multi Signature Transactions
  • Generate an Address
  • -
  • Creating a Crowdfunding Transaction
  • +
  • Create a Crowdfunding Transaction
  • diff --git a/guides/scripting.html b/guides/scripting.html index 731c917..8b14427 100644 --- a/guides/scripting.html +++ b/guides/scripting.html @@ -270,7 +270,7 @@
  • Store Data on the Blockchain
  • Creating Multi Signature Transactions
  • Generate an Address
  • -
  • Creating a Crowdfunding Transaction
  • +
  • Create a Crowdfunding Transaction