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:
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 first:
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.
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
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.
The coins object you've created above should look something like this:
Step 3: 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
Now let's build the utility for calculating the fee for a single input of unknown type or size
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).
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).
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...
Step 4: Construct the Transaction
Now that we've got our tools and coins ready, we can start to build the transaction!
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.
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.
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):
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.
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 Extend and Improve
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 Telegram 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.