Bitcoin Forum
September 07, 2024, 05:08:38 PM *
News: Latest Bitcoin Core release: 27.1 [Torrent]
 
   Home   Help Search Login Register More  
Pages: [1]
  Print  
Author Topic: Using Pycoin to spend non-standard scripts  (Read 1853 times)
HeadsOrTails (OP)
Full Member
***
Offline Offline

Activity: 233
Merit: 102



View Profile
July 16, 2015, 05:25:34 AM
Merited by ABCbits (1)
 #1

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.
d4n13
Full Member
***
Offline Offline

Activity: 210
Merit: 104


“Create Your Decentralized Life”


View Profile
July 17, 2015, 06:17:43 PM
Merited by ABCbits (1)
 #2

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.

HeadsOrTails (OP)
Full Member
***
Offline Offline

Activity: 233
Merit: 102



View Profile
July 18, 2015, 06:41:04 AM
 #3

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
d4n13
Full Member
***
Offline Offline

Activity: 210
Merit: 104


“Create Your Decentralized Life”


View Profile
July 18, 2015, 06:53:46 AM
Last edit: July 18, 2015, 10:09:13 AM by d4n13
 #4

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?

HeadsOrTails (OP)
Full Member
***
Offline Offline

Activity: 233
Merit: 102



View Profile
July 18, 2015, 08:49:12 AM
 #5

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
d4n13
Full Member
***
Offline Offline

Activity: 210
Merit: 104


“Create Your Decentralized Life”


View Profile
July 19, 2015, 06:28:34 AM
 #6

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

d4n13
Full Member
***
Offline Offline

Activity: 210
Merit: 104


“Create Your Decentralized Life”


View Profile
July 20, 2015, 07:13:30 AM
 #7

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.

HeadsOrTails (OP)
Full Member
***
Offline Offline

Activity: 233
Merit: 102



View Profile
July 20, 2015, 09:09:31 AM
 #8

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
d4n13
Full Member
***
Offline Offline

Activity: 210
Merit: 104


“Create Your Decentralized Life”


View Profile
July 20, 2015, 09:39:45 AM
 #9

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?

edmundedgar
Sr. Member
****
Offline Offline

Activity: 352
Merit: 250


https://www.realitykeys.com


View Profile WWW
July 20, 2015, 12:41:57 PM
 #10

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.
d4n13
Full Member
***
Offline Offline

Activity: 210
Merit: 104


“Create Your Decentralized Life”


View Profile
July 20, 2015, 11:26:09 PM
 #11

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

HeadsOrTails (OP)
Full Member
***
Offline Offline

Activity: 233
Merit: 102



View Profile
July 21, 2015, 03:06:08 AM
 #12

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
HeadsOrTails (OP)
Full Member
***
Offline Offline

Activity: 233
Merit: 102



View Profile
July 21, 2015, 03:15:40 AM
 #13

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"
d4n13
Full Member
***
Offline Offline

Activity: 210
Merit: 104


“Create Your Decentralized Life”


View Profile
July 21, 2015, 04:07:50 AM
 #14

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, '<utxo_scriptPubKey>', 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:
<sig1>
<sig2>
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.

HeadsOrTails (OP)
Full Member
***
Offline Offline

Activity: 233
Merit: 102



View Profile
July 21, 2015, 04:15:28 AM
 #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, '<utxo_scriptPubKey>', 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:
<sig1>
<sig2>
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)
Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!