btcsuite/btcscript

make opcode internals public

Closed this issue · 7 comments

tals commented

Hey there,

I'd like to construct some custom scripts (functionality it's similar to PayToScriptHashScript(), but the payload is different).
Sadly, opcodemap and unparseScript are private.

Any chance of making those public?

Thanks

@tals

The opcodes themselves are exported, so you can build custom scripts byte wise. I do agree that some type of "ScriptBuilder" interface might be a good addition for creating custom scripts, but I'm not really sure that simply exposing opcodemap/unparseScript is the best approach for that. It would most likely be the simplest approach however.

@owainga: What are your thoughts on this one?

In the mean time, while it's not the most user friendly thing, you can build a script by doing something similar to the following:

package main

import(
    "fmt"
    "github.com/conformal/btcscript"
    "github.com/conformal/btcutil"
)

func main() {
    // This would obviously be a real script like a multi-sig or similar.
    redeemScript := []byte{}
    scriptHash := btcutil.Hash160(redeemScript)

    // Construct the pay-to-script-hash script.
    script := make([]byte, 23)
    script[0] = btcscript.OP_HASH160
    script[1] = btcscript.OP_DATA_20
    copy(script[2:], scriptHash)
    script[22] = btcscript.OP_EQUAL

    // Ignoring the error here for demo purposes.
    disasm, _ := btcscript.DisasmString(script)
    fmt.Printf("Script: %x\n", script)
    fmt.Printf("Script disassembly: %s\n", disasm)
}

Which would produce the output:

Script: a914b472a266d0bd89c13706a4132ccfb16f7c3b9fcb87
Script disassembly: OP_HASH160 b472a266d0bd89c13706a4132ccfb16f7c3b9fcb OP_EQUAL
tals commented

@davecgh: Yes, the current interface is probably not the most interface for writing Script either :)
I guess I still have some coding anxiety from being new to Go :)

btw, since we're building the script bytes by hand, shouldn't the OP_DATA_20 part be:

script[0] = btcscript.OP_HASH160
script[1] = btcscript.OP_PUSHDATA1
script[2] = 20
copy(script[3:], scriptHash)
...

?

@tals:

No, OP_DATA_20 is the better choice. It is a single byte versus 2 bytes for the [OP_PUSHDATA1 20] pair. Also, I highly suspect that ultimately the standard transaction rules will be changed to only allow scripts which are canonical in regards to pushing data, meaning they only consume the least number of bytes possible to represent the data push. There has been some discussion amongst the community about it, and I, for one, believe it is something that really should happen.

@tals:

What would you think of an interface like the following?

builder := btcscript.NewScriptBuilder()
builder.PushOp(btcscript.OP_HASH160).PushData(scriptHash).PushOp(btcscript.OP_EQUAL)
fmt.Printf("Script: %x\n", builder.Script())
tals commented

Aaah, I didn't notice this fragment in scripts.h:

            if (opcode < OP_PUSHDATA1)
            {
                nSize = opcode;
            }

and GetOpName() doesn't know about (neither does the wiki), so I thought that the OP_DATA_XX was just something internal to btcscript :p

About the interface: I actually also planned on doing a "builder" style interface, so it looks awesome to me :)
What about opcode that require args? PushOp(btcscript.OP_HASH160, 123456)?
It might be a little nicer to have the opcode as funcs, so:

 ..Hash160().Data(scriptHash).Equal().

but either way is fine tbh.

@tals:

I've pushed the initial ScriptBuilder to master.

tals commented

Oh thanks! That's pretty handy!