Author

Topic: Using Pycoin to spend non-standard scripts (Read 1867 times)

full member
Activity: 233
Merit: 102
July 20, 2015, 11:15:28 PM
#15

Basically you have to override
Code:
sign(tx,i,priv,hashcode)
You want to change line 341 of transaction.py so that it accepts an input script.  You need to feed it the scriptPubKey of the UTXO you are spending.
Code:
   signing_tx = signature_form(tx, i, '', hashcode)

Then you need to sign the transaction with pubkey 042d...d17c, and copy the sig into "sig1", then sign the transaction with pubkey 0330...f63f, and copy the sig into "sig2".  

Finally your non-standard scriptSig is:
Code:

I've done it on the "Hello World" example I did above, but the code is kinda ugly.

Let me clean it up and I'll write a pybitcointools implementation to spend the coin.

Ah ha! OK, that makes perfect sense. I'll try it out.

There's a fork of pybitcointools which is updated more often here. One of the issues I've encountered with pybitcointools is the DER encoding; the fork checks for DER encoding, whereas the original pybitcointools isn't even BER in some instances (eg if the r or s value has the leading bit set and is less than 2**255, the encoding doesn't prepend nullbytes)
full member
Activity: 210
Merit: 104
“Create Your Decentralized Life”
Maybe we can work on this Tx: http://test.webbtc.com/tx/2e7f518ce5ab61c1c959d25e396bc9d3d684d22ea86dc477b1a90329c6ca354f

I've set up the script like this:

Code:
OP_IF
0330ed33784ee1891122bc608b89da2da45194efaca68564051e5a7be9bee7f63f
OP_CHECKSIGVERIFY
OP_ELSE
80bf07
OP_NOP2
OP_DROP
OP_ENDIF
042daa93315eebbe2cb9b5c3505df4c6fb6caca8b756786098567550d4820c09db988fe9997d049d687292f815ccd6e7fb5c1b1a91137999818d17c73d0f80aef9
OP_CHECKSIG

So its a master key, which is sha256("master"*42)

How would I use pybitcointools to spend this script?

Nb script:
Code:
myscript = "63210330ed33784ee1891122bc608b89da2da45194efaca68564051e5a7be9bee7f63fad670380bf07b1756841042daa93315eebbe2cb9b5c3505df4c6fb6caca8b756786098567550d4820c09db988fe9997d049d687292f815ccd6e7fb5c1b1a91137999818d17c73d0f80aef9ac"

I was working on a cleaner implementation... but got pulled away tonight...

Basically you have to override
Code:
sign(tx,i,priv,hashcode)
You want to change line 341 of transaction.py so that it accepts an input script.  You need to feed it the scriptPubKey of the UTXO you are spending.
Code:
   signing_tx = signature_form(tx, i, '', hashcode)

Then you need to sign the transaction with pubkey 042d...d17c, and copy the sig into "sig1", then sign the transaction with pubkey 0330...f63f, and copy the sig into "sig2".  

Finally your non-standard scriptSig is:
Code:

I've done it on the "Hello World" example I did above, but the code is kinda ugly.

Let me clean it up and I'll write a pybitcointools implementation to spend the coin.
full member
Activity: 233
Merit: 102
Maybe we can work on this Tx: http://test.webbtc.com/tx/2e7f518ce5ab61c1c959d25e396bc9d3d684d22ea86dc477b1a90329c6ca354f

I've set up the script like this:

Code:
OP_IF
0330ed33784ee1891122bc608b89da2da45194efaca68564051e5a7be9bee7f63f
OP_CHECKSIGVERIFY
OP_ELSE
80bf07
OP_NOP2
OP_DROP
OP_ENDIF
042daa93315eebbe2cb9b5c3505df4c6fb6caca8b756786098567550d4820c09db988fe9997d049d687292f815ccd6e7fb5c1b1a91137999818d17c73d0f80aef9
OP_CHECKSIG

So its a master key, which is sha256("master"*42)

How would I use pybitcointools to spend this script?

Nb script:
Code:
myscript = "63210330ed33784ee1891122bc608b89da2da45194efaca68564051e5a7be9bee7f63fad670380bf07b1756841042daa93315eebbe2cb9b5c3505df4c6fb6caca8b756786098567550d4820c09db988fe9997d049d687292f815ccd6e7fb5c1b1a91137999818d17c73d0f80aef9ac"
full member
Activity: 233
Merit: 102
Yep... there is a bug in there somewhere...

Try the following
Code:
from bitcoin import *
serialize_script(deserialize_script('ac'))
---
RuntimeError: maximum recursion depth exceeded while calling a Python object

Submitted issue #104

I fixed it
full member
Activity: 210
Merit: 104
“Create Your Decentralized Life”
Yep... there is a bug in there somewhere...

Try the following
Code:
from bitcoin import *
serialize_script(deserialize_script('ac'))
---
RuntimeError: maximum recursion depth exceeded while calling a Python object

Submitted issue #104
sr. member
Activity: 352
Merit: 250
https://www.realitykeys.com
I don't know if this is related but something seems to have changed in the way pybitcointool serializes scripts since I was working with this around:
https://github.com/vbuterin/pybitcointools/commit/87aaf6dc3f38d853dd8cb324a4eaf72ae309d322
...which I think is still the version you get if you do
pip install pybitcointool
(This is before the name changed to "bitcoin".)

I haven't had a chance to get to the bottom of this and it may be that the old version is broken and the new one is right, but you might like to try comparing what you get with the old version.
full member
Activity: 210
Merit: 104
“Create Your Decentralized Life”
How would I go about tweaking this code to avoid the manual addition of push20?


ie. I want to use
Code:
mk_script(['76', 'a9', 'dd6cce9f255a8cc17bda8ba0373df8e861cb866e', '88', 'ac'])
... instead of
Code:
mk_script(['76', 'a9', '14', 'dd6cce9f255a8cc17bda8ba0373df8e861cb866e', '88', 'ac'])
(note the "14"  preceding the pubkeyhash, which acts as push 20 bytes).

I'd prefer to avoid using the push bytes
Yeah, the bit about push20 is clean... no bug.  The fact that you have a list item of 40 hex digits is proof that deserialize consumed a push20.  If it consumed a push10 you would have a list item of 20 hex digits instead..

get it?
full member
Activity: 233
Merit: 102
Yep... there is a bug in there somewhere...

Try the following
Code:
from bitcoin import *
serialize_script(deserialize_script('ac'))
---
RuntimeError: maximum recursion depth exceeded while calling a Python object

Yeh, I often run scripts on iOS Pythonista, and there's a lot of recursion errors that come up since by default the recursion depth is 256; so setting sys.setrecursiondepth(512) often works in that environment.

The pybitcointools bug is strange because the code serializes multisig scripts, but there's a bug with the CHECKMULTISIG; so instead of serializing the 'ae', it just appends 'ae' to the end of the returned string.

I am looking at using this code:

Code:
def mk_script(*args):
    # lst = ['76', 'a9', '14', 'dd6cce9f255a8cc17bda8ba0373df8e861cb866e', '88', 'ac']
    if len(args) == 1 and isinstance(args[0], (list, tuple))
        lst = list(args[0])
    elif len(args) > 1 and all(map(lambda o: isinstance(o, str), args)):
        lst = [args]
    else:
        lst = [changebase(str(x), 10, 16, 2) if isinstance(x, (int, long)) else x for x in args]
   
    llens = [len(changebase(x, 16, 256, 1)) for x in lst]    # byte lengths
    lint = map(lambda h: decode(h, 16), lst)                 # list as ints
   
    asm = 0xff
    for i in range(len(lint)):
        asm = asm << (8*llens[i]) | lint[i]
   
    asmhex = "0x" + encode(asm, 16, (sum(llens) + 1)*2)
    final = asmhex.partition('0xff')[-1]
    return final

How would I go about tweaking this code to avoid the manual addition of push20?


ie. I want to use
Code:
mk_script(['76', 'a9', 'dd6cce9f255a8cc17bda8ba0373df8e861cb866e', '88', 'ac'])
... instead of
Code:
mk_script(['76', 'a9', '14', 'dd6cce9f255a8cc17bda8ba0373df8e861cb866e', '88', 'ac'])
(note the "14"  preceding the pubkeyhash, which acts as push 20 bytes).

I'd prefer to avoid using the push bytes
full member
Activity: 210
Merit: 104
“Create Your Decentralized Life”
I was simply thinking of the serialize(tx_dict).  I was thinking you would right your own (or overwrite) the serialize_script method.  I mean there are less than 100 op codes right?

It worked....



Transaction 5e4cf125218fcab2746303485ce3a2a74dc12271f678e851fc95fd97cd0153e4 puts "Hello World" on the blockchain
Transaction 602c4d614351ee615ee1dc280c16a4cf962ccc928a8c3a0dca6f9ccd46104976 spends "Hello World" on the blockchain.

Interestingly... the script debugger says OP_SIGCHECK failed... but it didn't.  Also, the funky output script does an interesting job of making the coins quasi-anonomized.
full member
Activity: 210
Merit: 104
“Create Your Decentralized Life”
Yep... there is a bug in there somewhere...

Try the following
Code:
from bitcoin import *
serialize_script(deserialize_script('ac'))
---
RuntimeError: maximum recursion depth exceeded while calling a Python object
full member
Activity: 233
Merit: 102
I never realized there was a serialize_script method.  I was simply thinking of the serialize(tx_dict).  I was thinking you would right your own (or overwrite) the serialize_script method.  I mean there are less than 100 op codes right?[/size]

Yeah, that's certainly do-able. I'll fork the pybitcointools library and see how I go with it.

I've been looking at Peter Todd's python-bitcoinlib and Richard Kiss' Pycoin, mainly because the classes are much more powerful for playing around with scripting, SIGHASH, etc. That being said, the OOP for python-bitcoinlib tries to emulate the Core software naming conventions, and it's really complicated.

If anyone else can provide a single example of a non-standard script Tx using Pycoin/python-bitcoinlib, I'd really appreciate it.


d4n13: Thanks for the input, I'll look at trying this with pybitcointools
full member
Activity: 210
Merit: 104
“Create Your Decentralized Life”
I'm really familiar with pybitcointools, so I can certainly see how your suggestion works. However, the issue is when using serialize_script; namely, if you've got objects (like a pubkey), the serialize_script method doesn't add push codes for the size of the object.

Ie:

Code:
my_script = serialize_script([OP_foo, OP_bar, "20byte_pubkey_hash", OP_spam])

myscript will return 11 22 01234567890123456789 33 instead of 11 22 14 01234567890123456789 33
LOL...

I'm terribly unfamiliar with PyBitcoinTools, as my 'Wow there is a PyBitcoinTools module" post from today will likely attest.

I never realized there was a serialize_script method.  I was simply thinking of the serialize(tx_dict).  I was thinking you would right your own (or overwrite) the serialize_script method.  I mean there are less than 100 op codes right?
full member
Activity: 233
Merit: 102
How would I use Pycoin to play around with non-standard scripts like this? Alternatively, pybitcointools.
Writing a Script "compiler" in python should be very straight forward.  PyBitcoinTools will parse out the scripts with deserialize, then you take the Script 'asm' and write a decompiler.

If you then put the 'asm' data back in the dict and serialize with pybitcointools you should be good to go.

Thanks for the reply!

I'm really familiar with pybitcointools, so I can certainly see how your suggestion works. However, the issue is when using serialize_script; namely, if you've got objects (like a pubkey), the serialize_script method doesn't add push codes for the size of the object.

Ie:

Code:
my_script = serialize_script([OP_foo, OP_bar, "20byte_pubkey_hash", OP_spam])

myscript will return 11 22 01234567890123456789 33 instead of 11 22 14 01234567890123456789 33
full member
Activity: 210
Merit: 104
“Create Your Decentralized Life”
How would I use Pycoin to play around with non-standard scripts like this? Alternatively, pybitcointools.
Writing a Script "compiler" in python should be very straight forward.  PyBitcoinTools will parse out the scripts with deserialize, then you take the Script 'asm' and write a decompiler.

If you then put the 'asm' data back in the dict and serialize with pybitcointools you should be good to go.

I'll give it a shot next week.
full member
Activity: 233
Merit: 102
Let's take http://test.webbtc.com/tx/76cf175637ee40281b9893e691113a9b5c835d8eb44875954a68b9d582b14a9e as an example (Testnet, TxID: 76cf175637ee40281b9893e691113a9b5c835d8eb44875954a68b9d582b14a9e)

(I found this through the Unknown P2SH Scripts page of webbtc.com)

Code:
2
OP_PICK
1
OP_PICK
OP_NOT
OP_BOOLOR
OP_VERIFY
1
OP_PICK
OP_VERIFY
OP_2DROP
OP_DROP
1

How would I use Pycoin to play around with non-standard scripts like this? Alternatively, pybitcointools.
Jump to: