Hey everyone,
I've been playing around with Python and bitcoin and while there are some implementations of the network protocol floating around on the web, I haven't found anything that can parse and run scripts from transactions and verify if they're valid.
After messing around, I've made a (really horribly) script that, given two linked transactions, checks that the second transaction is allowed to redeem the referenced output from the first transaction.
import binascii
import hashlib
import M2Crypto
import structures
#Transaction 2 uses an output from Transaction 1
#Transaction 1: http://blockexplorer.com/tx/945691940e0ccd9f526ee1edd57a77ce170804915749702f5564c49b1f70f330
#Transaction 2: http://blockexplorer.com/tx/ff954e099764d192c5bb531c9c14c18c230b0c0a63f02cd168a4ea94548c890f#i325189
tx1raw= '\x01\x00\x00\x00\x02\x0f{\x7f\xb8mL\xf6F\x05\x8eA\xd3\xb0\x07\x18?\xdfysn\xd1\x9b*th\xab\xc5\xbd\x04\xb1n\x91\x00\x00\x00\x00\x8cI0F\x02!\x00\xb2\xee9\xd2\xfc\xc2\xe5TJW\xc3\x0f{NI\xcf\xb8""fm\x03O\xb9\x0e"4\x8e\x17\xe2\x8e\x0f\x02!\x00\xdb\x91\xc3\x19\x9c\xc7\xb4\x1dMz\xfc\xe0\xcc\xb4\xce\xb4$\xb9GmQ\xc0aBX=\xafS\xce\n\x9bf\x01A\x04\xc3"\x15\xa9\t0\x11\xbd
\xfb\xd8-\x149\xbe\x01A\x04\xc3"\x15\xa9\t0\x11\xbdtx2raw= "\x01\x00\x00\x00\x030\xf3p\x1f\x9b\xc4dU/pIW\x91\x04\x08\x17\xcewz\xd5\xed\xe1nR\x9f\xcd\x0c\x0e\x94\x91V\x94\x00\x00\x00\x00\x8cI0F\x02!\x00\xf5tk\x0b%OZ7\xe7RQE\x9cz#\xb6\xdf\xcb\x86\x8a\xc7F~\xdd\x9ao\xdd\x1d\x96\x98q\xbe\x02!\x00\x88\x94\x8a\xea)\xb6\x91a\xca4\x1cI\xc0&\x86\xa8\x1d\x8c\xbbs\x94\x0f\x91\x7f\xa0\xedqThm>[\x01A\x04G\xd4\x90V\x1f9l\x8a\x9e\xfc\x14Hk\xc1\x98\x88K\xa1\x83y\xbc\xac.\x0b\xe2\xd8RQ4\xabt/0\x1a\x9a\xca6`n])\xaa#\x8a\x9e)\x93\x001PB=\xf6\x92Ecd-J\xfe\x9b\xf4\xfe(\xff\xff\xff\xffr\x14+\xf7hl\xe9,m\xe5\xb73e\xbf\xb9\xd5\x9b\xb6\x0c,\x80\x98-YX\xc1\xe6\xa3\xb0\x8e\xa6\x89\x00\x00\x00\x00JI0F\x02!\x00\xbc\xe4:\xd3\xac\xbcy\xb0$~T\xc8\xc9\x1e\xac\x1c\xf9\x03u\x05\x00\x0e\x01\xd1\xfd\x81\x18T\xd8[\xc2\x1a\x02!\x00\x99*oo/\xebob\xd3po;\x9a\xaa\xb8\x8d\x9f\x112\x95j\x1d\xff\xa9&\xcdUn\xd5S`\xdf\x01\xff\xff\xff\xff\xd2\x81(\xbb\xb6 |\x1c=\nc\x0c\xc6\x19\xdc~{\xeaV\xac\x19\xa1\xda\xb1'\xc6,x\xfa\x1bc,\x00\x00\x00\x00IH0E\x02 \x97W6\x81aSw\x08\xfd)\xd8\x9b\xb1\xe9\xd6H\x00yI\xec\xfd\xedx\x9bQ\xa9c$\xcbe\x18\x02!\x00\xcd\x0f|0!9\x16H+n\x16m\x8aO+\x98\x1fw~\xb1\x84\xcd\x8aI_\x1b=6\x90\xfb\xbf-\x01\xff\xff\xff\xff\x01\x00\xa6\xf7_\x02\x00\x00\x00\x19v\xa9\x14\x9e5\xd9
tx1 = structures.Tx.deserialize(tx1raw)[0]
tx2 = structures.Tx.deserialize(tx2raw)[0]
#We're going to check that the first input of Tx2 is correctly redeemed
def run_script(script, stack):
while script:
opcode = ord(script.pop(0))
if opcode <= 75:
value = ""
for x in xrange(0, opcode):
value += script.pop(0)
stack.append(value)
elif opcode == 118:
#OP_DUP
stack.append(stack[-1])
elif opcode == 169:
#OP_HASH160
#The input is hashed twice: first with SHA-256 and then with RIPEMD-160.
value = stack.pop()
value = hashlib.sha256(value).digest()
ripemd = hashlib.new('ripemd160')
ripemd.update(value)
value = ripemd.digest()
stack.append(value)
elif opcode == 136:
#OP_EQUALVERIFY
v1 = stack.pop()
v2 = stack.pop()
if v1 != v2:
raise Exception()
elif opcode == 172:
#OP_CHECKSIG
#1. the public key and the signature are popped from the stack, in that order.
pubkey = stack.pop()
signature = stack.pop()
#2. A new subscript is created from the instruction from the most recent OP_CODESEPARATOR to the end of the script. If there is no OP_CODESEPARATOR the entire script becomes the subscript (hereby referred to as subScript)
#(we don't do the OP_CODESEPARATOR thing yet...)
subscript = tx1.txout[0].script
#3. the signature is deleted from subscript
subscript = subscript.replace(chr(len(signature)) + signature, "")
#4. The hashtype is removed from the last byte of the sig and stored
hashtype = signature[-1]
signature = signature[:-1]
#5. A deep copy is made of the current transaction (hereby referred to txCopy)
#(we do this by serializing and deserialing this transaction again...)
txCopy = structures.Tx.deserialize(tx2.serialize())[0]
#6. All OP_CODESEPARATORS are removed from subScript
#(we don't do this yet...)
#7. The scripts for all transaction inputs in txCopy are set to empty scripts
for txin in txCopy.txins:
txin.script = ""
#8. The script for the current transaction input in txCopy is set to subScript
#We're checking the first input
txCopy.txins[0].script = subscript
#An array of bytes is constructed from the serialized txCopy + four bytes for the hash type.
#This array is sha256 hashed twice, then the public key is used to to check the supplied
#signature against the hash.
#Load the public key. I have no idea what this magical string does...
pkey = pubkey[::-1] + "0042030a0004812b050601023dce48862a070610305630".decode("hex")
pkey = M2Crypto.EC.pub_key_from_der(pkey[::-1])
#Serialize the transaction and add the hashtype to the end as an int
txhash = txCopy.serialize() + "\x01\x00\x00\x00"
#And sha256 it twice
txhash = hashlib.sha256(hashlib.sha256(txhash).digest()).digest()
if pkey.verify_dsa_asn1(txhash, signature):
stack.append(True)
else:
stack.append(False)
stack = []
run_script(list(tx2.txins[0].script), stack)
run_script(list(tx1.txout[0].script), stack)
print stack
This code needs
structures.py which is my implementation of the (de)serialization of the various bitcoin messages.
The above script is really messy and more a quick proof-of-concept than anything else. Using it like that will be harmful for the bitcoin network as it is far from fully implemented and misses some small details.
The script checks that input 1 from
this transaction is valid with the referenced transaction.
My biggest problem was with M2Crypto: it doesn't seem to implement o2i_ECPublicKey from openssl, which is used by bitcoin to set the public key. I also looked at using pyOpenssl and pycrypto, but it looks like neither of these libraries can handle elliptic curve cryptography...
To make M2Crypto work for this I had to use some magic, which I'm really not happy about:
pkey = pubkey[::-1] + "0042030a0004812b050601023dce48862a070610305630".decode("hex")
pkey = M2Crypto.EC.pub_key_from_der(pkey[::-1])
As you can see, I need to add a fixed string in order to load the public key. Without this it fails to load it and I get an exception (ValueError: Received a NULL pointer.)
This string I got by making new public/private key pairs with M2Crypto and printing them out: I noticed that this part is fixed and never changes. I assume this string defines the parameters used (NID_secp256k)
Anyways, I hope this is useful to someone. Feel free to use this code for anything, but keep in mind that it is horrible and needs lots of cleaning up.
And if anyone feels like improving it, go for it
Edit: I forgot to mention that this only validates standard transactions to another bitcoin address.