I’ve decided to go through creating a multisig address using libbitcoin both because it’s one of the powerful features of bitcoin and it allows me to see the abstraction libbitcoin uses to manage bitcoin scripting without having to go through an entire transaction.
For this practice script I’m going to be using the HD wallet class I created in my previous tutorial, so go here if you haven’t built that yet or just go here to grab the source code. The one adjustment I’m going to make to the wallet class is adding the functionality to randomly generate a wallet from entropy as an argument-free constructor.
HD_Wallet() { entropy = data_chunk(16); pseudo_random_fill(entropy); mnemonic = wallet::create_mnemonic(entropy); seed = to_chunk(wallet::decode_mnemonic(mnemonic)); privateKey = wallet::hd_private(seed); publicKey = privateKey.to_public(); }
With that addition, I’m going to save the “HD_Wallet.cpp” file and create a new one named “scriptingTest.cpp”. I’ll then need to add my include statements for my wallet class and libbitcoin’s library as well as namespacing the scopes I want to work with, which in this case is going to be “wallet”, “machine” and “chain”.
#include <bitcoin/bitcoin.hpp> #include "HD_wallet.cpp" using namespace bc; using namespace bc::wallet; using namespace bc::machine; using namespace bc::chain;
Multiple Signature Addresses
Multisig Addresses take advantage of bitcoin’s scripting language and the concept of pay-to-script hash addresses in order to trustlessly create a conditional account whose funds can only be spent if the conditions are met, in the case of multisig that means reaching a preset threshold of keys present in a transaction.
For example, say you’re managing a team of three researchers and want to give them an expense account for the project but want to make sure that there is at least some discussion and agreement on purchases. You could create a 2 of 3 multisig address that requires at least two of the three keys to be present and sign the transaction in order to spend any money, which means if two out of the three researchers don’t think the purchase is a good idea the purchase won’t happen.
For this utility, I’ll want to create 3 keys and then use them to generate the multisig address I’ll be funding with the proper conditions (2 of 3). I would then need to distribute the corresponding private keys to my researchers for spending.
Multisig Utility
- Generate 3 sets of Keys
- Create a 2/3 redeem script
- Create a pay to script hash locking script
- Output the resulting multisig address
- Output three mnemonics for the private keys
In more practical uses the researchers could simply send their own public keys to me in order to generate the address since private keys are not required for the construction of a multsig address.
Multisig Addresses are created by using bitcoin’s specialized scripting language to create a locking script that can only be satisfied by the hash of a signature from a certain number of keys. Their are three parts to a multisig transaction the “redeem script”, this will encode the rules of the address and the hash of this script gets encoded into an address, “the locking script”, this is a special pay to script hash(P2SH) script which includes a hash of the redeem script. The last part is the unlocking script which is the combination of the signatures and the redeem script required to spend money from the address.
The redeem script sets the rules for the address, which in this case is a relatively easy template script:
2 [PUBKEY1] [PUBKEY2] [PUBKEY3] 3 CHECKMULTISIGVERIFY
By making this script into a P2SH address we’re able to get a multisig bitcoin address that can be paid just like any other address.
That means we would double hash the above script using sha256 and then ripemd160 and put that hash in the following script to create what is known as the Locking Script.
HASH160 [20-byte script hash] EQUAL
The 20-byte has of the original MultiSig script would then be base58-check encoded to a bitcoin address.
358vPZP3zaBpNMatFgRBxX58ftUbqDreMN
All multisig Addresses start with a 3.
The Code
Okay, so now that we have the concept down let’s dive into some code. Since this utility is just a script, I’m going to put the whole thing in the main function. To start I’ll need to generate 3 HD wallets using the empty constructor I just added. From there, I’ll need to derive a child key from each of them and save the compressed public key as a data chunk.
HD_Wallet wallet1 = HD_Wallet(); HD_Wallet wallet2 = HD_Wallet(); HD_Wallet wallet3 = HD_Wallet(); data_chunk pubkey1 = to_chunk(wallet1.childPublicKey(1).point()); data_chunk pubkey2 = to_chunk(wallet2.childPublicKey(1).point()); data_chunk pubkey3 = to_chunk(wallet3.childPublicKey(1).point());
Scripting
Bitcoin has it’s own scripting language consisting of various op_codes used to program transactions. The libbitcoin framework has three basic structures for scripting. The first is a c++ enum class of all the opcodes allowing us to pass opcodes to a constructor and return the operation. The next is an operation object to hold all the members and routines of an operation. One level higher is the script object which takes a bunch of operations and accesses all the serialization and validation routines for the entire script.
So, to build our script we are going to take advantage of a special operation type list which is a vector of operations. First we pass the opcode to the opcode enum class and pass that opcode to an operation object which we in turn are passing to our operation vector(list).
We’re passing the code ’82’ first which corresponds to push positive 2, our scripts first argument. Then we pass The three public keys. After doing that for all 3 public keys we pass the codes for 3 and checkmultisigverfy.
After we have the whole operation list we pass the vector to a script constuctor.
operation::list opList {operation(opcode(82)), operation(pubkey1), operation(pubkey2), operation(pubkey3), operation(opcode(83)), operation(opcode(174))}; script multisigScript(opList); if(script().is_pay_multsig_pattern(opList) { std::cout << "Is Multisig Pattern!" << std::endl; }
We can then check the validity of our script before we output it to the console both as a string and in hex.
if(multisigScript.is_valid()) { std::cout << "Script is Valid!\n" << std::endl; }else{ std::cout << "Script Invalid! \n" << std::endl; } std::cout << "Redeeem Script: \n" << std::endl; std::cout << multisigScript.to_string(0) << "\n" << std::endl; std::cout << encode_base16(multisigScript.to_data(0)) << "\n" << std::endl;
Now, we can use the libbitcoin function “bitcoin_short_hash” to invoke the hash ripemd160(sha256(script)). Then using a special factory member of the script object we can pass the hash into a template stack for a pay to script hash type transaction.
short_hash scriptHash = bitcoin_short_hash(multisigScript.to_data(0)); script pay2ScriptHash = script(multisigScript.to_pay_script_hash_pattern(scriptHash));
After that we have created a P2SH script which allows us to view the locking script. The original MultiSig script can then be hashed and encoded as an address whose balance will only be spendable if the underlying multsig script is satisfied. We can pass the libbitcoin payment_address() either the redeem scripts hash or the script object itself and retrieve the multisig address.
std::cout << "Locking Script: " << std::endl; std::cout << pay2ScriptHash.to_string(0) << "\n" << std::endl; payment_address multsigAddress(multisigScript); std::cout << "Payment Address: " << std::endl; std::cout << multsigAddress.encoded() << "\n" << std::endl; std::cout << payment_address(scriptHash).encoded() << "\n" << std::endl;
Once the address is funded, I can distribute to my researchers the three mnemonic seeds whose first child key corresponds to this address.
std::cout << "Private Key Mnemonics: \n" << std::endl; std::cout << "Key One: " << wallet1.displayMnemonic() << std::endl; std::cout << "Key Two: " << wallet2.displayMnemonic() << std::endl; std::cout << "Key Three: " << wallet3.displayMnemonic() << "\n" << std::endl;
Now, this script can be compiled an run as usual and the result should look something like usual.
Funds sent to this address can only be spent in a transaction that includes the unlocking script consisting of at least 2 of the private keys signatures and the redeem script. My next post will go over creating a transaction from this multisig address.
As always sample code is on Github.