lbcd/txscript/scriptbuilder.go
Dave Collins cbda064842 Import btcscript repo into txscript directory.
This commit contains the entire btcscript repository along with several
changes needed to move all of the files into the txscript directory in
order to prepare it for merging.  This does NOT update btcd or any of the
other packages to use the new location as that will be done separately.

- All import paths in the old btcscript test files have been changed to the
  new location
- All references to btcscript as the package name have been chagned to
  txscript

This is ongoing work toward #214.
2015-01-30 10:30:16 -06:00

286 lines
9.1 KiB
Go

// Copyright (c) 2013-2015 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package txscript
import (
"encoding/binary"
"fmt"
"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
)
// ErrScriptNotCanonical identifies a non-canonical script. The caller can use
// a type assertion to detect this error type.
type ErrScriptNotCanonical string
// Error implements the error interface.
func (e ErrScriptNotCanonical) Error() string {
return string(e)
}
// ScriptBuilder provides a facility for building custom scripts. It allows
// you to push opcodes, ints, and data while respecting canonical encoding. In
// general it does not ensure the script will execute correctly, however any
// data pushes which would exceed the maximum allowed script engine limits and
// are therefore guaranteed not to execute will not be pushed and will result in
// the Script function returning an error.
//
// For example, the following would build a 2-of-3 multisig script for usage in
// a pay-to-script-hash (although in this situation MultiSigScript() would be a
// better choice to generate the script):
// builder := txscript.NewScriptBuilder()
// builder.AddOp(txscript.OP_2).AddData(pubKey1).AddData(pubKey2)
// builder.AddData(pubKey3).AddOp(txscript.OP_3)
// builder.AddOp(txscript.OP_CHECKMULTISIG)
// script, err := builder.Script()
// if err != nil {
// // Handle the error.
// return
// }
// fmt.Printf("Final multi-sig script: %x\n", script)
type ScriptBuilder struct {
script []byte
err error
}
// AddOp pushes the passed opcode to the end of the script. The script will not
// be modified if pushing the opcode would cause the script to exceed the
// maximum allowed script engine size.
func (b *ScriptBuilder) AddOp(opcode byte) *ScriptBuilder {
if b.err != nil {
return b
}
// Pushes that would cause the script to exceed the largest allowed
// script size would result in a non-canonical script.
if len(b.script)+1 > maxScriptSize {
str := fmt.Sprintf("adding an opcode would exceed the maximum "+
"allowed canonical script length of %d", maxScriptSize)
b.err = ErrScriptNotCanonical(str)
return b
}
b.script = append(b.script, opcode)
return b
}
// canonicalDataSize returns the number of bytes the canonical encoding of the
// data will take.
func canonicalDataSize(data []byte) int {
dataLen := len(data)
// When the data consists of a single number that can be represented
// by one of the "small integer" opcodes, that opcode will be instead
// of a data push opcode followed by the number.
if dataLen == 0 {
return 1
} else if dataLen == 1 && data[0] <= 16 {
return 1
} else if dataLen == 1 && data[0] == 0x81 {
return 1
}
if dataLen < OP_PUSHDATA1 {
return 1 + dataLen
} else if dataLen <= 0xff {
return 2 + dataLen
} else if dataLen <= 0xffff {
return 3 + dataLen
}
return 5 + dataLen
}
// addData is the internal function that actually pushes the passed data to the
// end of the script. It automatically chooses canonical opcodes depending on
// the length of the data. A zero length buffer will lead to a push of empty
// data onto the stack (OP_0). No data limits are enforced with this function.
func (b *ScriptBuilder) addData(data []byte) *ScriptBuilder {
dataLen := len(data)
// 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 == 0 || 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
} else if dataLen == 1 && data[0] == 0x81 {
b.script = append(b.script, byte(OP_1NEGATE))
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
}
// AddFullData should not typically be used by ordinary users as it does not
// include the checks which prevent data pushes larger than the maximum allowed
// sizes which leads to scripts that can't be executed. This is provided for
// testing purposes such as regression tests where sizes are intentionally made
// larger than allowed.
//
// Use AddData instead.
func (b *ScriptBuilder) AddFullData(data []byte) *ScriptBuilder {
if b.err != nil {
return b
}
return b.addData(data)
}
// AddData pushes the passed data to the end of the script. It automatically
// chooses canonical opcodes depending on the length of the data. A zero length
// buffer will lead to a push of empty data onto the stack (OP_0) and any push
// of data greater than MaxScriptElementSize will not modify the script since
// that is not allowed by the script engine. Also, the script will not be
// modified if pushing the data would cause the script to exceed the maximum
// allowed script engine size.
func (b *ScriptBuilder) AddData(data []byte) *ScriptBuilder {
if b.err != nil {
return b
}
// Pushes that would cause the script to exceed the largest allowed
// script size would result in a non-canonical script.
dataSize := canonicalDataSize(data)
if len(b.script)+dataSize > maxScriptSize {
str := fmt.Sprintf("adding %d bytes of data would exceed the "+
"maximum allowed canonical script length of %d",
dataSize, maxScriptSize)
b.err = ErrScriptNotCanonical(str)
return b
}
// Pushes larger than the max script element size would result in a
// script that is not canonical.
dataLen := len(data)
if dataLen > MaxScriptElementSize {
str := fmt.Sprintf("adding a data element of %d bytes would "+
"exceed the maximum allowed script element size of %d",
dataLen, maxScriptSize)
b.err = ErrScriptNotCanonical(str)
return b
}
return b.addData(data)
}
// AddInt64 pushes the passed integer to the end of the script. The script will
// not be modified if pushing the data would cause the script to exceed the
// maximum allowed script engine size.
func (b *ScriptBuilder) AddInt64(val int64) *ScriptBuilder {
if b.err != nil {
return b
}
// Pushes that would cause the script to exceed the largest allowed
// script size would result in a non-canonical script.
if len(b.script)+1 > maxScriptSize {
str := fmt.Sprintf("adding an integer would exceed the "+
"maximum allow canonical script length of %d",
maxScriptSize)
b.err = ErrScriptNotCanonical(str)
return b
}
// 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. The script
// will not be modified if pushing the data would cause the script to
// exceed the maximum allowed script engine size.
func (b *ScriptBuilder) AddUint64(val uint64) *ScriptBuilder {
if b.err != nil {
return b
}
// Pushes that would cause the script to exceed the largest allowed
// script size would result in a non-canonical script.
if len(b.script)+1 > maxScriptSize {
str := fmt.Sprintf("adding an unsigned integer would exceed "+
"the maximum allow canonical script length of %d",
maxScriptSize)
b.err = ErrScriptNotCanonical(str)
return b
}
// 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]
b.err = nil
return b
}
// Script returns the currently built script. When any errors occured while
// building the script, the script will be returned up the point of the first
// error along with the error.
func (b *ScriptBuilder) Script() ([]byte, error) {
return b.script, b.err
}
// NewScriptBuilder returns a new instance of a script builder. See
// ScriptBuilder for details.
func NewScriptBuilder() *ScriptBuilder {
return &ScriptBuilder{
script: make([]byte, 0, defaultScriptAlloc),
}
}