lbcd/scriptbuilder.go
Owain G. Ainsworth 5171cb803c Convert builder.Push* to builder.Add* we think this is clearer.
Also, shorter!  Discussed with @davecgh.
2014-02-20 18:42:58 +00:00

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),
}
}