Saturday, April 12, 2014

Multisig with pybitcointools

Multisig is all the rage these days and for good reason.  It is very useful and so much more secure.  Take, for example, a scenario where you are pooling bitcoin resources with a group of friends for investment or mining.  In a typical single signature situation there are some difficult choices to make.  Is the secret key just held by one person?  What if something happened to that one person?  Maybe everyone in the group should hold the private key.  What if someone doesn't encrypt or has their copy compromised?  What if someone is not as trustworthy as you first thought?  In comes multisignature transactions to resolve all your problems.  If someone loses a key or has it compromised the holdings are still safe and internal shenanigans requires coordinated effort.

I am going to go through with code to show you how this can all be done with pybitcointools.  For this I am going to assume that you are going to create a 2 of 3 transaction.  This means there are 3 keys and any two of them can be used to sign the transaction.

For reference, I got started here: http://bitcoinmagazine.com/11113/pybitcointools-multisig-tutorial/
I found this tutorial to be a great start but it left me with too many questions and too many unknowns.  This tutorial is MUCH more comprehensive.


1. Generate the secret keys.

Multisig addresses are made using the public keys that are generated from 3 private keys.  Because of this, each party should be responsible for generating their own key on their own machine.  Secret keys never have to be shared.  Here is the code for this:

import sys
sys.path.append('pybitcointools')
import bitcoin

secret = bitcoin.random_key()
public = bitcoin.privtopub(secret)
print "Secret: " + secret
print "Public: " + public

All the public keys should be distributed to all of the partners.  Then everyone can generate the same public multisig address with the same redemption script.  Please note that you have to agree on the order of the public keys as changing the order will alter the multisig address and the redemption script.  While the different address would certainly be problematic, the redemption script should be fine whatever the order.  I have not tested this though.  Please feel free to correct me.

In any case, here is the code for creating the address and redemption script:

//assume the same imports at the top
pub  = []

// put the public keys in this array however you would like

for x in range(0, 3)
    pub.append(raw_input("Enter a public key: "))
script = bitcoin.mk_multisig_script(pub[0], pub[1], pub[2], 2, 3)
address = bitcoin.scriptaddr(script)

print address
print script

In "mk_multisig_script" the first three values are the public keys, the next value is the number of signatures required (could be 1 or 3 instead of 2 if that is what you want), and the last value is the number of total keys being used to create this transaction.

So you send bitcoin to that address and all is right with the world, but bitcoin is not yours unless you are able to transfer it.  I will explain how to spend multisig transactions now.  This part is a bit more complicated.

The first thing you want to do is figure out how much bitcoin is available in at your address.  Pybitcointools makes that very easy with the function unspent(address).  Like so:

history = bitcoin.unspent(fromAddress)
total = bitcoin.sum(bitcoin.multiaccess(history, 'value'))

unspent(address) will return a json object with transactions that the address can still spend. This will include the transaction id, the output index of the transaction that this address can spend, and the value of the transaction.  I want to spend a bit of time on this because transaction indexes will come up again.  Remember that a transaction can have multiple inputs and multiple outputs.  Most transactions will have more than one output just because it needs to send change.  If the transaction was set up to spend to 6 different addresses of which you were only one, it is important to know that portion of the transaction you can spend is at index 2 or whatever it happens to be.  The index starts at 0 for the first output.

The next line iterates through the json object and sums the values of unspent transactions.  It is good to know how much you can spend.

The next portion of the code assumes that you have set something up to enter in the outgoing address, value you want to send, and the fee you would like to pay.  This code traverses the available transactions available to spend and selects enough value to make the transaction work.  There are certainly more efficient ways to select inputs.

totalSend = amount + fee
currentValue = 0
inputs = []
for trans in history:
 inputs.append(trans)
 currentValue += trans['value']
 if currentValue >= totalSend:
  break

inputLen = len(inputs)

Now that we have the length of the inputs for our new transaction, we can make sure that we are supplying the appropriate minimum fee.  For safety's sake we are going to assume that each input takes 400 bytes, though the truth is closer to 387.

neededFee = int(.0001 * 100000000) //unit used is satoshi
transSize = inputLen * 400 + 34 * 2 + 10 // assume change so 34x(2 outputs)

if transSize > 10000:
 while transSize > 1000:
  neededFee += int(.0001 * 100000000)
  transSize -= 1000

Ok, so now we have all of our inputs ready to make our transaction.  Since we are already making the transaction so we should also go ahead and create our signatures as well using the only private key we control.

rawTX = bitcoin.mksend(inputs, [toAddress+':'+str(amount)], fromAddress, neededFee)

mySig = []
for x in range(0,inputLen):
 mySig.append(bitcoin.multisign(rawTX, x, script, myPrivKey))

There is a lot going on here so let me break it down. mksend() is a helper function for mktx(). It takes the additional parameters of a change address and fee and generates the appropriate final output to send change back. Remember that 'amount' is an integer representing the number of satoshis being sent. In this code I am sending change back to the original multisig address. You may not want to do that. In the second portion of the code I am generating a signature for each input of this transaction. Remember that the inputs for this transaction are outputs from previous transactions. Those outputs had an index designating where to find it in that transaction. In this code 'x' is also an index but it is the input index. So the first input you use will be index 0 and the second would be index 1. The 'script' parameter is the redemption script you generated earlier. The output of this function is NOT a signed transaction. Instead it is the signature for this transaction. Once you have run this you will need to send another key holder the following pieces of data:
  • The original raw transaction
  • The redemption script
  • The signature for each input
Once another key holder has this information, they can generate their own signature(s), apply their signature(s) and your signature(s), and publish the signed transaction. That is done like this:

mySig = []
for x in range(0,inputCount):
 mySig.append(bitcoin.multisign(rawTx, x, script, myPrivKey))
 if (mySig[x] == otherSig[x]):
  print "You already signed this.  Send it to another key holder."
  sys.exit(0)
fullySignedTx = rawTx
for x in range(0,inputCount):
 fullySignedTx = bitcoin.apply_multisignatures(fullySignedTx, x, script, mySig[x], otherSig[x])

answer = raw_input("Do you want to send this transaction? (Type 'yes' default is 'no')") or "no"
if answer == 'yes':
 print bitcoin.eligius_pushtx(fullySignedTx)
 print "The transaction has been double signed and sent.  Thank you!"
else:
 print "You have not sent this transaction."

Again, a signature is being generated for the transaction with the key the new person is holding. There is a check to make sure it isn't actually the same key so that we can generate a useful message. The next step is to iterate through the inputs in the transaction and apply the signatures to each one. Please note that this does return a transaction and we would send the transaction with the first input signed to the function to have the second input signed. It does no good to sign the first input of the raw transaction and then sign the second input of the same raw (unsigned) transaction. It would return a transaction without the first input signed.

Once the signatures have been applied we are ready to broadcast the transaction to the network. Currently blockchain.info does not like multisig transactions generated like this so we have to submit it to eligius.

I hope this makes working with multisig addresses much easier and I hope that you learned something about Bitcoin transactions. If I made any mistakes or could have done something better please let me know. I like learning as well.

2 comments:

  1. > Currently blockchain.info does not like multisig transactions generated like this so we have to submit it to eligius

    These transactions are currently considered nonstandard by (I guess most) nodes.

    I wish I had read the ending note first :-)

    PS: Nice doc.

    ReplyDelete
  2. Hey, do you mind doing a little write up on showing how you would sign and push a single transaction with multiple inputs (from same address, or even different ones) as well as including the fee, and possible change address.

    Your multisig tutorial is spot on and very helpful, I'm trying to dissect what I need from it, but I just want to make sure that I'm doing it correctly.

    ReplyDelete