5171cb803c
Also, shorter! Discussed with @davecgh.
135 lines
4.1 KiB
Go
135 lines
4.1 KiB
Go
// Copyright (c) 2013-2014 Conformal Systems LLC.
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package btcscript
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"math/big"
|
|
)
|
|
|
|
const (
|
|
// defaultScriptAlloc is the default size used for the backing array
|
|
// for a script being built by the ScriptBuilder. The array will
|
|
// dynamically grow as needed, but this figure is intended to provide
|
|
// enough space for vast majority of scripts without needing to grow the
|
|
// backing array multiple times.
|
|
defaultScriptAlloc = 500
|
|
)
|
|
|
|
// ScriptBuilder provides a facility for building custom scripts. It allows
|
|
// you to push opcodes, ints, and data while respecting canonical encoding. It
|
|
// does not ensure the script will execute correctly.
|
|
//
|
|
// For example, the following would build a 2-of-3 multisig script for usage in
|
|
// a pay-to-script-hash:
|
|
// builder := btcscript.NewScriptBuilder()
|
|
// builder.AddOp(btcscript.OP_2).AddData(pubKey1).AddData(pubKey2)
|
|
// builder.AddData(pubKey3).AddOp(btcscript.OP_3)
|
|
// builder.AddOp(btcscript.OP_CHECKMULTISIG)
|
|
// fmt.Printf("Final multi-sig script: %x\n", builder.Script())
|
|
type ScriptBuilder struct {
|
|
script []byte
|
|
}
|
|
|
|
// AddOp pushes the passed opcode to the end of the script.
|
|
func (b *ScriptBuilder) AddOp(opcode byte) *ScriptBuilder {
|
|
b.script = append(b.script, opcode)
|
|
return b
|
|
}
|
|
|
|
// AddData pushes the passed data to the end of the script. It automatically
|
|
// chooses canonical opcodes depending on the length of the data.
|
|
func (b *ScriptBuilder) AddData(data []byte) *ScriptBuilder {
|
|
// Don't modify the script at all if no data was passed.
|
|
dataLen := len(data)
|
|
if dataLen == 0 {
|
|
return b
|
|
}
|
|
|
|
// When the data consists of a single number that can be represented
|
|
// by one of the "small integer" opcodes, use that opcode instead of
|
|
// a data push opcode followed by the number.
|
|
if dataLen == 1 && data[0] == 0 {
|
|
b.script = append(b.script, OP_0)
|
|
return b
|
|
} else if dataLen == 1 && data[0] <= 16 {
|
|
b.script = append(b.script, byte((OP_1-1)+data[0]))
|
|
return b
|
|
}
|
|
|
|
// Use one of the OP_DATA_# opcodes if the length of the data is small
|
|
// enough so the data push instruction is only a single byte.
|
|
// Otherwise, choose the smallest possible OP_PUSHDATA# opcode that
|
|
// can represent the length of the data.
|
|
if dataLen < OP_PUSHDATA1 {
|
|
b.script = append(b.script, byte((OP_DATA_1-1)+dataLen))
|
|
} else if dataLen <= 0xff {
|
|
b.script = append(b.script, OP_PUSHDATA1, byte(dataLen))
|
|
} else if dataLen <= 0xffff {
|
|
buf := make([]byte, 2)
|
|
binary.LittleEndian.PutUint16(buf, uint16(dataLen))
|
|
b.script = append(b.script, OP_PUSHDATA2)
|
|
b.script = append(b.script, buf...)
|
|
} else {
|
|
buf := make([]byte, 4)
|
|
binary.LittleEndian.PutUint32(buf, uint32(dataLen))
|
|
b.script = append(b.script, OP_PUSHDATA4)
|
|
b.script = append(b.script, buf...)
|
|
}
|
|
|
|
// Append the actual data.
|
|
b.script = append(b.script, data...)
|
|
|
|
return b
|
|
}
|
|
|
|
// AddInt64 pushes the passed integer to the end of the script.
|
|
func (b *ScriptBuilder) AddInt64(val int64) *ScriptBuilder {
|
|
// Fast path for small integers and OP_1NEGATE.
|
|
if val == 0 {
|
|
b.script = append(b.script, OP_0)
|
|
return b
|
|
}
|
|
if val == -1 || (val >= 1 && val <= 16) {
|
|
b.script = append(b.script, byte((OP_1-1)+val))
|
|
return b
|
|
}
|
|
|
|
return b.AddData(fromInt(new(big.Int).SetInt64(val)))
|
|
}
|
|
|
|
// AddUint64 pushes the passed integer to the end of the script.
|
|
func (b *ScriptBuilder) AddUint64(val uint64) *ScriptBuilder {
|
|
// Fast path for small integers.
|
|
if val == 0 {
|
|
b.script = append(b.script, OP_0)
|
|
return b
|
|
}
|
|
if val >= 1 && val <= 16 {
|
|
b.script = append(b.script, byte((OP_1-1)+val))
|
|
return b
|
|
}
|
|
|
|
return b.AddData(fromInt(new(big.Int).SetUint64(val)))
|
|
}
|
|
|
|
// Reset resets the script so it has no content.
|
|
func (b *ScriptBuilder) Reset() *ScriptBuilder {
|
|
b.script = b.script[0:0]
|
|
return b
|
|
}
|
|
|
|
// Script returns the currently built script.
|
|
func (b *ScriptBuilder) Script() []byte {
|
|
return b.script
|
|
}
|
|
|
|
// NewScriptBuilder returns a new instance of a script builder. See
|
|
// ScriptBuilder for details.
|
|
func NewScriptBuilder() *ScriptBuilder {
|
|
return &ScriptBuilder{
|
|
script: make([]byte, 0, defaultScriptAlloc),
|
|
}
|
|
}
|