Bitcoin multisig the hard way: Understanding raw P2SH multisig transactions
Recently, inspired by Ken Shirriff's and Bryce Neal's low level looks at the Bitcoin protocol, I set about constructing Bitcoin's much talked about multisignature transactions from scratch to understand their capabilities and limitations. Specifically, I used Bitcoin's Pay-to-ScriptHash (P2SH) transaction type to create a M-of-N multisignature transaction. The code to do it all in Go is available as go-bitcoin-multsig on GitHub and I'd like to go through how all of this works at the Bitcoin protocol level. We'll also step through creating and spending a multisig transaction to make it all clearer.
In many ways, this is a follow up to Ken's amazing explanation of the Bitcoin protocol and constructing a Pay-to-PubKeyHash (P2PKH) transaction, so I won't cover things covered there in any great detail. Please check out his post out first if you're completely new to the Bitcoin protocol.
I'll be using go-bitcoin-multisig to generate keys and transactions along the way, explaining each step. If you'd like to follow along and create a multisig transaction yourself, you'll need to follow the simple build instructions for go-bitcoin-multisig.
What is a Pay-to-ScriptHash (P2SH) transaction?
A typical Bitcoin address that looks like 15Cytz9sHqeqtKCw2vnpEyNQ8teKtrTPjp is actually a specific type of Bitcoin address known as a Pay-to-PubKeyHash (P2PKH) address. To spend Bitcoin funds sent to this type of address, the recipient must use the private key associated with the public key hash specified in that address to create a digital signature, which is put into the scriptSig of a spending transaction, unlocking the funds.
A Pay-to-ScriptHash (P2SH) Bitcoin address looks and works quite differently. A typical P2SH address looks like 347N1Thc213QqfYCz3PZkjoJpNv5b14kBd. A P2SH address always begins with a '3', instead of a '1' as in P2PKH addresses. This is because P2SH addresses have a version byte prefix of 0x05, instead of the 0x00 prefix in P2PKH addresses, and these come out as a '3' and '1' after base58check encoding.
So what information is encoded in a P2SH address? A specific unspent Bitcoin can actually have a whole range of different spending conditions attached to it, the most common being a typical P2PKH which just requires the recipient to provide a signature matching the public key hash. The Bitcoin core developers realized that people were looking at the capabilities of Bitcoin's Script language and seeing a whole array of possibilities about what spending conditions you could attach to a Bitcoin output, to create much more elaborate transactions than just P2PKH transactions. The core developers decided that instead of letting senders put in long scripts into their scriptPubKey (where spending conditions usually go), they would let each sender put in a hash of their spending conditions instead. These spending conditions are known as the redeem script, and a P2SH funding transaction simply contains a hash of this redeem script in the scriptPubKey of the funding transaction.The redeem script itself is only revealed, checked against the redeem script hash, and evaluated during the spending transaction.
Source: https://bitcoin.org
This puts the responsibility of providing the full redeem script on to the recipient of the P2SH funds. This has a number of advantages:
- The sender can fund any arbitrary redeem script without knowing what those spending conditions are. This makes sense because a sender largely does not care about how their funds will be spent in the future -- this is an issue for the recipient who cares about the conditions of further spending. In the case of multisig transactions, the sender can send funds without knowing the required public keys (belonging to the recipient) of a multisignature address, which are revealed only when the recipient is spending the funds. This increases security for the recipient.
- The sender can use a short, 34-character address like the one above, instead of a long, unwieldy one containing details of a full redeem script. This lets a recipient put up just a short address on their payment page or message, reducing the chance of human errors in transcription.
- It lowers the transaction fees for the sender of funds. Transaction fees are proportional to the size of a transaction, and a fixed length hash lets the sender send funds to any arbitrary redeem script without worrying about paying higher fees. It is the responsibility of the recipient who creates the redeem script to determine how large their spending transaction will be and how much it will cost. This is a small issue at the moment since transaction costs are quite small, but they may be more important in the future as block rewards get smaller in Bitcoin.
All of this will hopefully make more sense as we go ahead and craft a multisignature P2SH transaction. If you'd like to learn more, the Bitcoin developer guide has a full explanation of P2SH transactions.
Creating a 2-of-3 multisig P2SH address
We will create a 2-of-3 multisignature address, where 2 digital signatures of 3 possible public keys are required to spend funds sent to this address.
First we need the hex representations of 3 public keys. There are lots of private/public key pair generators out there, but here we will use the one built into go-bitcoin-multisig. These keys are cryptographically secure to the limits of Go's crypto/rand package, which uses /dev/urandom/ on Unix-like systems and CryptGenRandom API on Windows:
go-bitcoin-multisig keys --count 3 --concise
Which outputs for us: (your generated keys will be different, of course)
--------------
KEY #1
Private key:
5JruagvxNLXTnkksyLMfgFgf3CagJ3Ekxu5oGxpTm5mPfTAPez3
Public key hex:
04a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd
Public Bitcoin address:
1JzVFZSN1kxGLTHG41EVvY5gHxLAX7q1Rh
--------------
--------------
KEY #2
Private key:
5JX3qAwDEEaapvLXRfbXRMSiyRgRSW9WjgxeyJQWwBugbudCwsk
Public key hex:
046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187
Public Bitcoin address:
14JfSvgEq8A8S7qcvxeaSCxhn1u1L71vo4
--------------
--------------
KEY #3
Private key:
5JjHVMwJdjPEPQhq34WMUhzLcEd4SD7HgZktEh8WHstWcCLRceV
Public key hex:
0411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e83
Public Bitcoin address:
1Kyy7pxzSKG75L9HhahRZgYoer9FePZL4R
--------------
So we have 3 public keys in hex ready to go:
Key A:
04a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd
Key B:
046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187
Key C:
0411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e83
Now, we specify that we want a 2-of-3 address and provide our 3 public keys to generate our P2SH address:
go-bitcoin-multisig address --m 2 --n 3 --public-keys 04a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd,046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187,0411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e83
Which outputs for us:
---------------------
Your *P2SH ADDRESS* is:
347N1Thc213QqfYCz3PZkjoJpNv5b14kBd
Give this to sender funding multisig address with Bitcoin.
---------------------
---------------------
Your *REDEEM SCRIPT* is:
524104a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd41046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187410411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e8353ae
Keep private and provide this to redeem multisig balance later.
---------------------
Let's breakdown that redeem script since that is where all the magic happens. A valid multisignature redeem script, according to the Bitcoin protocol, looks like:
<OP_2> <A pubkey> <B pubkey> <C pubkey> <OP_3> <OP_CHECKMULTISIG>
And this is what our particular redeem script contains:
Description | redeemScript bytes |
---|---|
OP_2 | 52 |
Push 65 bytes to stack | 41 |
<pubKeyA> | 04a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd |
Push 65 bytes to stack | 41 |
<pubKeyB> | 046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187 |
Push 65 bytes to stack | 41 |
<pubKeyC> | 0411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e83 |
OP_3 | 53 |
OP_CHECKMULTISIG | ae |
From this redeemScript, our P2SH address is generated with two more steps:
1. Double hash our redeemScript (in pseudocode to show specific hash functions):
redeemScriptHash = RIPEMD160(SHA256(redeemScript))
2. Base58check encode our redeemscriptHash with a prefix of 0x05
P2SHAddress := base58check.Encode("05", redeemScriptHash)
And that's how go-bitcoin-multisig gives us our P2SH address of 347N1Thc213QqfYCz3PZkjoJpNv5b14kBd. It contains a hashed redeem script with our chosen public keys and multisig script, but this will not be revealed publicly until the spending transaction, since it has been hashed. We would at this point pass this address to the sender who is funding our multisig address.
Funding our P2SH address
To fund our multisig address now, we need a funding source of Bitcoins. go-bitcoin-multisig will fund from a standard P2PKH output, and we will need the input transaction id (txid), its matching private key, the amount to send (with the remaining balance taken as fees) and the destination P2SH address (which we just generated):
go-bitcoin-multisig fund --input-tx 3ad337270ac0ba14fbce812291b7d95338c878709ea8123a4d88c3c29efbc6ac --private-key 5JJyqG4bb15zqi7fTA4b227aUxQhBo1Ux6qX69ngeXYLr7fk2hs --destination 347N1Thc213QqfYCz3PZkjoJpNv5b14kBd --amount 65600
Which outputs for us:
-----------------------------------------------------------------------------------------------------------------------------------
Your raw funding transaction is:
0100000001acc6fb9ec2c3884d3a12a89e7078c83853d9b7912281cefb14bac00a2737d33a000000008a47304402204e63d034c6074f17e9c5f8766bc7b5468a0dce5b69578bd08554e8f21434c58e0220763c6966f47c39068c8dcd3f3dbd8e2a4ea13ac9e9c899ca1fbc00e2558cbb8b01410431393af9984375830971ab5d3094c6a7d02db3568b2b06212a7090094549701bbb9e84d9477451acc42638963635899ce91bacb451a1bb6da73ddfbcf596bddfffffffff01400001000000000017a9141a8b0026343166625c7475f01e48b5ede8c0252e8700000000
Broadcast this transaction to fund your P2SH address.
-----------------------------------------------------------------------------------------------------------------------------------
Note that the generated transaction changes slightly each time because of the nonce in the digital signatures and this may change the total size of the transaction slightly each time. Everything else should remain the same.
Let's breakdown our funding transaction:
Description | Hex Bytes | |
---|---|---|
Version byte | 01000000 | |
Input count | 01 | |
Previous tx hash (reversed) | acc6fb9ec2c3884d3a12a89e7078c83853d9b7912281cefb14bac00a2737d33a | |
Output index | 00000000 | |
scriptSig length of 138 bytes | 8a | |
scriptSig | Push 71 bytes to stack | 47 |
<signature> | 304402204e63d034c6074f17e9c5f8766bc7b5468a0dce5b69578bd08554e8f21434c58e0220763c6966f47c39068c8dcd3f3dbd8e2a4ea13ac9e9c899ca1fbc00e2558cbb8b01 | |
Push 65 bytes to stack | 41 | |
<pubKey> | 0431393af9984375830971ab5d3094c6a7d02db3568b2b06212a7090094549701bbb9e84d9477451acc42638963635899ce91bacb451a1bb6da73ddfbcf596bddf | |
Sequence | ffffffff | |
No. of outputs | 01 | |
Amount of 65600 in LittleEndian | 4000010000000000 | |
scriptPubKey length of 23 bytes | 17 | |
scriptPubKey | OP_HASH160 | a9 |
Push 20 bytes to stack | 14 | |
redeemScriptHash | 1a8b0026343166625c7475f01e48b5ede8c0252e | |
OP_EQUAL | 87 | |
locktime | 00000000 |
The key difference compared to a typical P2PKH transaction is the scriptPubKey. We now have a scriptPubKey of the form:
<OP_HASH160> <redeemScriptHash> <OP_EQUAL>
Remember that OP_HASH160 in Bitcoin Script is just a RIPEMD160(SHA256()) function. This is used to compare the redeem script provided in the spending transaction to the hash in the funding transaction. We'll see how the scriptPubKey here and the scriptSig of the spending transaction come together shortly.
At this point, you can broadcast your own funding transaction and have it actually confirmed on the network. Don't forget to include a transaction fee (maybe ~10000 satoshi) so miners choose to include it in their blocks. The Blockchain.info pushtx tool or any other broadcast tool will work fine. The transaction above was broadcast and confirmed as txid 02b082113e35d5386285094c2829e7e2963fa0b5369fb7f4b79c4c90877dcd3d.
Spending our multisig P2SH funds
Now we want to be able to spend our P2SH multisig funds. First let's generate another key pair to be our destination where we can send our multisig funds.
go-bitcoin-multisig keys --concise
Which outputs for us:
--------------
KEY #1
Private key:
5Jmnhuc5gPWtTNczYVfL9yTbM6RArzXe3QYdnE9nbV4SBfppLcx
Public key hex:
04459b7e1711f31e64507061bccb89fb618e86b254140dc98a42093e449fef067f2ece0a9b11a63697a11c5176528c436570499a13aa22824be53ea2718173b45a
Public Bitcoin address:
18tiB1yNTzJMCg6bQS1Eh29dvJngq8QTfx
--------------
Now, we will need 2 of the 3 private keys of the public keys used to generate our P2SH address. We'll use our 1st and 3rd original generated private keys (any 2 of 3 would work, of course).
Now, this is important: the order of keys does matter. We can obviously skip keys when our M required keys is less than our N possible keys, but they must show up in our signed spending transaction in the same order that they were provided in the redeem script.[1] go-bitcoin-multisig will sign the spending transaction in the order of keys given.
To create our spending transaction, we need the input txid of the funding transaction, our amount (with the remaining balance going to transaction fees) and the destination. We must also provide the original redeem script. Remember, the destination P2SH address is a hash and doesn't reveal our redeem script. Only the recipient who created the P2SH address knows the full redeem script, and in this case, we are that recipient and can provide it:
go-bitcoin-multisig spend --input-tx 02b082113e35d5386285094c2829e7e2963fa0b5369fb7f4b79c4c90877dcd3d --amount 55600 --destination 18tiB1yNTzJMCg6bQS1Eh29dvJngq8QTfx --private-keys 5JruagvxNLXTnkksyLMfgFgf3CagJ3Ekxu5oGxpTm5mPfTAPez3,5JjHVMwJdjPEPQhq34WMUhzLcEd4SD7HgZktEh8WHstWcCLRceV --redeemScript 524104a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd41046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187410411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e8353ae
Which outputs for us:
Your raw spending transaction is:
01000000013dcd7d87904c9cb7f4b79f36b5a03f96e2e729284c09856238d5353e1182b00200000000fd5d01004730440220762ce7bca626942975bfd5b130ed3470b9f538eb2ac120c2043b445709369628022051d73c80328b543f744aa64b7e9ebefa7ade3e5c716eab4a09b408d2c307ccd701483045022100abf740b58d79cab000f8b0d328c2fff7eb88933971d1b63f8b99e89ca3f2dae602203354770db3cc2623349c87dea7a50cee1f78753141a5052b2d58aeb592bcf50f014cc9524104a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd41046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187410411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e8353aeffffffff0130d90000000000001976a914569076ba39fc4ff6a2291d9ea9196d8c08f9c7ab88ac00000000
Broadcast this transaction to spend your multisig P2SH funds.
Again, the transaction will look slightly different each time because of the changing nonce in the digital signature, but everything else should look the same.
Let's breakdown our raw spending transaction:
Description | Hex Bytes | |
---|---|---|
Version byte | 01000000 | |
Input count | 01 | |
Previous tx hash (reversed) | 3dcd7d87904c9cb7f4b79f36b5a03f96e2e729284c09856238d5353e1182b002 | |
Output index | 00000000 | |
scriptSig length of 349 bytes | fd5d01 | |
scriptSig | OP_0 | 00 |
Push 71 bytes | 47 | |
<sig A> | 30440220762ce7bca626942975bfd5b130ed3470b9f538eb2ac120c2043b445709369628022051d73c80328b543f744aa64b7e9ebefa7ade3e5c716eab4a09b408d2c307ccd701 | |
Push 72 bytes | 48 | |
<sig C> | 3045022100abf740b58d79cab000f8b0d328c2fff7eb88933971d1b63f8b99e89ca3f2dae602203354770db3cc2623349c87dea7a50cee1f78753141a5052b2d58aeb592bcf50f01 | |
OP_PUSHDATA1 | 4c | |
Push 201 bytes | c9 | |
<redeemScript> | 524104a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd41046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187410411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e8353ae | |
Sequence | ffffffff | |
No. of outputs | 01 | |
Amount of 55600 in LittleEndian | 30d9000000000000 | |
scriptPubKey length of 25 bytes | 19 | |
scriptPubKey | OP_DUP | 76 |
OP_HASH160 | a9 | |
Push 20 bytes | 14 | |
<pubKeyHash> | 569076ba39fc4ff6a2291d9ea9196d8c08f9c7ab | |
OP_EQUALVERIFY | 88 | |
OP_CHECKSIG | ac | |
locktime | 00000000 |
Let's look at how the Bitcoin protocol will run through the script here. Combining the spending transaction scriptSig and funding transaction scriptPubKey, we get:
<OP_0> <sig A> <sig C> <redeemScript> <OP_HASH160> <redeemScriptHash> <OP_EQUAL>
Stepping through this script:
-
OP_0 and the two signatures are added to the stack, kept for later.
-
The redeemScript is added to the stack.
-
OP_HASH160 hashes our redeemScript.
-
redeemScriptHash is added to the stack.
-
OP_EQUAL will compare OP_HASH160(redeemScript) and redeemScriptHash and check for equality. This confirms that our spending transaction is providing the correct redeemScript.
-
Now our redeemScript can be evaluated:
<OP_2> <A pubkey> <B pubkey> <C pubkey> <OP_3> <OP_CHECKMULTISIG>
-
OP_CHECKMULTISIG will look at the 3 public keys and 2 signatures in the stack, and compare them one by one. As stated earlier, the order of signatures matters here and must match the order that the public keys were provided in.[1].
-
OP_0 is included only to deal with an off-by-one error in Bitcoin Core when using OP_CHECKMULTISIG.
A couple of important notes, especially for troubleshooting, on how this raw transaction is created:
- Ken talks in his post about how there is a temporary scriptSig when signing the raw transaction, before a signature (and hence final scriptSig) is available. For a P2PKH, this temporary scriptSig is the scriptPubKey of the input transaction. For a P2SH, the temporary scriptSig is the redeemScript itself. I am yet to find this clearly documented in the protocol specifications anywhere, but I may have just not found it. The only way I figured it out (after much pain) was by reverse engineering bitcoinjs-lib.[2]
- When pushing items to the stack in Bitcoin Script, the usual format is <size of item> <item>. However, if the length of the item is greater than 75 bytes, this length would start to look identical to OP codes 76 and up. Therefore, we use the special opcodes OP_PUSHDATA1, OP_PUSHDATA2 and OP_PUSHDATA4 (indicating that the next 1, 2 or 4 bytes, respectively, specify the size of item to be pushed to stack) in these cases, and this happens for our larger redeemScript.[3]
- The scriptSig length is included in a transaction as a var_int type in the Bitcoin protocol. This means that it can take up more than the usual one byte length if needed. For any scriptSig longer than 253 bytes, we can use two bytes by writing 0xfd (253) followed by two bytes specifying the scriptSig length, in little endian format.[4] Be careful though, you can only use two bytes if and only if it is needed. If it is smaller than 253 bytes, use the usual one byte to store the scriptSig length. Otherwise your signature with an unnecessarily long var_int will be considered invalid. This was another painful lesson learned only through repeated tests and reverse engineering bitcoinjs-lib. See here for the relevant Go code to see exactly what I mean.
We can now broadcast this transaction to spend our multisig P2SH funds. You can see the above transaction confirmed as txid eeab3ef6cbea5f812b1bb8b8270a163b781eb7cde10ae5a7d8a3f452a57dca93.
Wrap-up
Voila! We've just generated an M-of-N multisig P2SH address, funded it from a Bitcoin output and spent those funds by providing M signatures. I hope all of that was helpful for anyone trying to understand the innards of the Bitcoin protocol or trying to build multisig applications on top of the raw Bitcoin protocol. If there are any issues or questions about any part of the process, feel free as always to reach out to me on Twitter @soroushjp.
Happy Holidays and Happy Bitcoining!
— Soroush Pour (@soroushjp)
Appendix
- This is part of how OP_CHECKMULTISIG works. See more details at the OP_CHECKMULTISIG warning in the Bitcoin Developer Guide.
- If someone knows where and how this is specified in the Bitcoin protocol, please reach out, I'd love to understand this better. Twitter @soroushjp, email at me_AT_soroushjp.com.
- For full details, see Bitcoin's Script language wiki page.
- For full details, see Bitcoin's var_int Specification.
References
- Ken Shirriff -- Bitcoins the hard way
- Bitcoin Developer Guide
- Bryce Neal's hellobitcoin
- Bitcoin Script
- Bitcoin Protocol Specification
- Bitcoinjs-lib
- Gavin Andresen reference gist on multisig
Helpful Tools
- Blockchain TX Broadcast Tool
- Blockchain TX Decode Tool
- Coinb.in Multisig Tools (hosted bitcoinjs-lib)
Thanks
Thanks to Alex Browne, Fabio Berger and Julian Borrey for reading through drafts of this post.
Member discussion