275 lines
8.9 KiB
Go
275 lines
8.9 KiB
Go
|
// Copyright (c) 2013-2015 The btcsuite developers
|
||
|
// 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"
|
||
|
)
|
||
|
|
||
|
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
|
||
|
}
|
||
|
|
||
|
// AddOps pushes the passed opcodes to the end of the script. The script will
|
||
|
// not be modified if pushing the opcodes would cause the script to exceed the
|
||
|
// maximum allowed script engine size.
|
||
|
func (b *ScriptBuilder) AddOps(opcodes []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)+len(opcodes) > MaxScriptSize {
|
||
|
str := fmt.Sprintf("adding opcodes would exceed the maximum "+
|
||
|
"allowed canonical script length of %d", MaxScriptSize)
|
||
|
b.err = ErrScriptNotCanonical(str)
|
||
|
return b
|
||
|
}
|
||
|
|
||
|
b.script = append(b.script, opcodes...)
|
||
|
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, (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, MaxScriptElementSize)
|
||
|
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(scriptNum(val).Bytes())
|
||
|
}
|
||
|
|
||
|
// 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 occurred 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),
|
||
|
}
|
||
|
}
|