txscript: Significantly improve errors.

This converts the majority of script errors from generic errors created
via errors.New and fmt.Errorf to use a concrete type that implements the
error interface with an error code and description.

This allows callers to programmatically detect the type of error via
type assertions and an error code while still allowing the errors to
provide more context.

For example, instead of just having an error the reads "disabled opcode"
as would happen prior to these changes when a disabled opcode is
encountered, the error will now read "attempt to execute disabled opcode
OP_FOO".

While it was previously possible to programmatically detect many errors
due to them being exported, they provided no additional context and
there were also various instances that were just returning errors
created on the spot which callers could not reliably detect without
resorting to looking at the actual error message, which is nearly always
bad practice.

Also, while here, export the MaxStackSize and MaxScriptSize constants
since they can be useful for consumers of the package and perform some
minor cleanup of some of the tests.
This commit is contained in:
Dave Collins 2017-01-07 11:31:03 -06:00
parent 283706b3dc
commit fdc2bc867b
No known key found for this signature in database
GPG Key ID: B8904D9D9C93D1F2
18 changed files with 1163 additions and 708 deletions

View File

@ -1,6 +1,6 @@
ISC License
Copyright (c) 2013-2016 The btcsuite developers
Copyright (c) 2013-2017 The btcsuite developers
Copyright (c) 2015-2016 The Decred developers
Permission to use, copy, modify, and distribute this software for any

View File

@ -1,4 +1,4 @@
// Copyright (c) 2013-2015 The btcsuite developers
// Copyright (c) 2013-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -32,8 +32,11 @@ what conditions must be met in order to spend bitcoins.
Errors
Errors returned by this package are of the form txscript.ErrStackX where X
indicates the specific error. See Variables in the package documentation for a
full list.
Errors returned by this package are of type txscript.Error. This allows the
caller to programmatically determine the specific error by examining the
ErrorCode field of the type asserted txscript.Error while still providing rich
error messages with contextual information. A convenience function named
IsErrorCode is also provided to allow callers to easily check for a specific
error code. See ErrorCode in the package documentation for a full list.
*/
package txscript

View File

@ -1,4 +1,4 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2013-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -72,12 +72,12 @@ const (
)
const (
// maxStackSize is the maximum combined height of stack and alt stack
// MaxStackSize is the maximum combined height of stack and alt stack
// during execution.
maxStackSize = 1000
MaxStackSize = 1000
// maxScriptSize is the maximum allowed length of a raw script.
maxScriptSize = 10000
// MaxScriptSize is the maximum allowed length of a raw script.
MaxScriptSize = 10000
)
// halforder is used to tame ECDSA malleability (see BIP0062).
@ -123,23 +123,31 @@ func (vm *Engine) isBranchExecuting() bool {
func (vm *Engine) executeOpcode(pop *parsedOpcode) error {
// Disabled opcodes are fail on program counter.
if pop.isDisabled() {
return ErrStackOpDisabled
str := fmt.Sprintf("attempt to execute disabled opcode %s",
pop.opcode.name)
return scriptError(ErrDisabledOpcode, str)
}
// Always-illegal opcodes are fail on program counter.
if pop.alwaysIllegal() {
return ErrStackReservedOpcode
str := fmt.Sprintf("attempt to execute reserved opcode %s",
pop.opcode.name)
return scriptError(ErrReservedOpcode, str)
}
// Note that this includes OP_RESERVED which counts as a push operation.
if pop.opcode.value > OP_16 {
vm.numOps++
if vm.numOps > MaxOpsPerScript {
return ErrStackTooManyOperations
str := fmt.Sprintf("exceeded max operation limit of %d",
MaxOpsPerScript)
return scriptError(ErrTooManyOperations, str)
}
} else if len(pop.data) > MaxScriptElementSize {
return ErrStackElementTooBig
str := fmt.Sprintf("element size %d exceeds max allowed size %d",
len(pop.data), MaxScriptElementSize)
return scriptError(ErrElementTooBig, str)
}
// Nothing left to do when this is not a conditional opcode and it is
@ -174,13 +182,15 @@ func (vm *Engine) disasm(scriptIdx int, scriptOff int) string {
// execution, nil otherwise.
func (vm *Engine) validPC() error {
if vm.scriptIdx >= len(vm.scripts) {
return fmt.Errorf("past input scripts %v:%v %v:xxxx",
str := fmt.Sprintf("past input scripts %v:%v %v:xxxx",
vm.scriptIdx, vm.scriptOff, len(vm.scripts))
return scriptError(ErrInvalidProgramCounter, str)
}
if vm.scriptOff >= len(vm.scripts[vm.scriptIdx]) {
return fmt.Errorf("past input scripts %v:%v %v:%04d",
str := fmt.Sprintf("past input scripts %v:%v %v:%04d",
vm.scriptIdx, vm.scriptOff, vm.scriptIdx,
len(vm.scripts[vm.scriptIdx]))
return scriptError(ErrInvalidProgramCounter, str)
}
return nil
}
@ -210,7 +220,9 @@ func (vm *Engine) DisasmPC() (string, error) {
// script.
func (vm *Engine) DisasmScript(idx int) (string, error) {
if idx >= len(vm.scripts) {
return "", ErrStackInvalidIndex
str := fmt.Sprintf("script index %d >= total scripts %d", idx,
len(vm.scripts))
return "", scriptError(ErrInvalidIndex, str)
}
var disstr string
@ -227,14 +239,18 @@ func (vm *Engine) CheckErrorCondition(finalScript bool) error {
// Check execution is actually done. When pc is past the end of script
// array there are no more scripts to run.
if vm.scriptIdx < len(vm.scripts) {
return ErrStackScriptUnfinished
return scriptError(ErrScriptUnfinished,
"error check when script unfinished")
}
if finalScript && vm.hasFlag(ScriptVerifyCleanStack) &&
vm.dstack.Depth() != 1 {
return ErrStackCleanStack
str := fmt.Sprintf("stack contains %d unexpected items",
vm.dstack.Depth()-1)
return scriptError(ErrCleanStack, str)
} else if vm.dstack.Depth() < 1 {
return ErrStackEmptyStack
return scriptError(ErrEmptyStack,
"stack empty at end of script execution")
}
v, err := vm.dstack.PopBool()
@ -249,7 +265,8 @@ func (vm *Engine) CheckErrorCondition(finalScript bool) error {
return fmt.Sprintf("scripts failed: script0: %s\n"+
"script1: %s", dis0, dis1)
}))
return ErrStackScriptFailed
return scriptError(ErrEvalFalse,
"false stack entry at end of script execution")
}
return nil
}
@ -278,8 +295,11 @@ func (vm *Engine) Step() (done bool, err error) {
// The number of elements in the combination of the data and alt stacks
// must not exceed the maximum number of stack elements allowed.
if vm.dstack.Depth()+vm.astack.Depth() > maxStackSize {
return false, ErrStackOverflow
combinedStackSize := vm.dstack.Depth() + vm.astack.Depth()
if combinedStackSize > MaxStackSize {
str := fmt.Sprintf("combined stack size %d > max allowed %d",
combinedStackSize, MaxStackSize)
return false, scriptError(ErrStackOverflow, str)
}
// Prepare for next instruction.
@ -287,7 +307,8 @@ func (vm *Engine) Step() (done bool, err error) {
if vm.scriptOff >= len(vm.scripts[vm.scriptIdx]) {
// Illegal to have an `if' that straddles two scripts.
if err == nil && len(vm.condStack) != 0 {
return false, ErrStackMissingEndif
return false, scriptError(ErrUnbalancedConditional,
"end of script reached in conditional execution")
}
// Alt stack doesn't persist.
@ -382,7 +403,8 @@ func (vm *Engine) checkHashTypeEncoding(hashType SigHashType) error {
sigHashType := hashType & ^SigHashAnyOneCanPay
if sigHashType < SigHashAll || sigHashType > SigHashSingle {
return fmt.Errorf("invalid hashtype: 0x%x\n", hashType)
str := fmt.Sprintf("invalid hash type 0x%x", hashType)
return scriptError(ErrInvalidSigHashType, str)
}
return nil
}
@ -402,7 +424,7 @@ func (vm *Engine) checkPubKeyEncoding(pubKey []byte) error {
// Uncompressed
return nil
}
return ErrStackInvalidPubKey
return scriptError(ErrPubKeyType, "unsupported public key type")
}
// checkSignatureEncoding returns whether or not the passed signature adheres to
@ -438,8 +460,9 @@ func (vm *Engine) checkSignatureEncoding(sig []byte) error {
// 0x30 + <1-byte> + 0x02 + 0x01 + <byte> + 0x2 + 0x01 + <byte>
if len(sig) < 8 {
// Too short
return fmt.Errorf("malformed signature: too short: %d < 8",
str := fmt.Sprintf("malformed signature: too short: %d < 8",
len(sig))
return scriptError(ErrSigDER, str)
}
// Maximum length is when both numbers are 33 bytes each. It is 33
@ -448,25 +471,29 @@ func (vm *Engine) checkSignatureEncoding(sig []byte) error {
// 0x30 + <1-byte> + 0x02 + 0x21 + <33 bytes> + 0x2 + 0x21 + <33 bytes>
if len(sig) > 72 {
// Too long
return fmt.Errorf("malformed signature: too long: %d > 72",
str := fmt.Sprintf("malformed signature: too long: %d > 72",
len(sig))
return scriptError(ErrSigDER, str)
}
if sig[0] != 0x30 {
// Wrong type
return fmt.Errorf("malformed signature: format has wrong type: 0x%x",
sig[0])
str := fmt.Sprintf("malformed signature: format has wrong "+
"type: 0x%x", sig[0])
return scriptError(ErrSigDER, str)
}
if int(sig[1]) != len(sig)-2 {
// Invalid length
return fmt.Errorf("malformed signature: bad length: %d != %d",
str := fmt.Sprintf("malformed signature: bad length: %d != %d",
sig[1], len(sig)-2)
return scriptError(ErrSigDER, str)
}
rLen := int(sig[3])
// Make sure S is inside the signature.
if rLen+5 > len(sig) {
return fmt.Errorf("malformed signature: S out of bounds")
return scriptError(ErrSigDER,
"malformed signature: S out of bounds")
}
sLen := int(sig[rLen+5])
@ -474,49 +501,58 @@ func (vm *Engine) checkSignatureEncoding(sig []byte) error {
// The length of the elements does not match the length of the
// signature.
if rLen+sLen+6 != len(sig) {
return fmt.Errorf("malformed signature: invalid R length")
return scriptError(ErrSigDER,
"malformed signature: invalid R length")
}
// R elements must be integers.
if sig[2] != 0x02 {
return fmt.Errorf("malformed signature: missing first integer marker")
return scriptError(ErrSigDER,
"malformed signature: missing first integer marker")
}
// Zero-length integers are not allowed for R.
if rLen == 0 {
return fmt.Errorf("malformed signature: R length is zero")
return scriptError(ErrSigDER,
"malformed signature: R length is zero")
}
// R must not be negative.
if sig[4]&0x80 != 0 {
return fmt.Errorf("malformed signature: R value is negative")
return scriptError(ErrSigDER,
"malformed signature: R value is negative")
}
// Null bytes at the start of R are not allowed, unless R would
// otherwise be interpreted as a negative number.
if rLen > 1 && sig[4] == 0x00 && sig[5]&0x80 == 0 {
return fmt.Errorf("malformed signature: invalid R value")
return scriptError(ErrSigDER,
"malformed signature: invalid R value")
}
// S elements must be integers.
if sig[rLen+4] != 0x02 {
return fmt.Errorf("malformed signature: missing second integer marker")
return scriptError(ErrSigDER,
"malformed signature: missing second integer marker")
}
// Zero-length integers are not allowed for S.
if sLen == 0 {
return fmt.Errorf("malformed signature: S length is zero")
return scriptError(ErrSigDER,
"malformed signature: S length is zero")
}
// S must not be negative.
if sig[rLen+6]&0x80 != 0 {
return fmt.Errorf("malformed signature: S value is negative")
return scriptError(ErrSigDER,
"malformed signature: S value is negative")
}
// Null bytes at the start of S are not allowed, unless S would
// otherwise be interpreted as a negative number.
if sLen > 1 && sig[rLen+6] == 0x00 && sig[rLen+7]&0x80 == 0 {
return fmt.Errorf("malformed signature: invalid S value")
return scriptError(ErrSigDER,
"malformed signature: invalid S value")
}
// Verify the S value is <= half the order of the curve. This check is
@ -529,7 +565,9 @@ func (vm *Engine) checkSignatureEncoding(sig []byte) error {
if vm.hasFlag(ScriptVerifyLowS) {
sValue := new(big.Int).SetBytes(sig[rLen+6 : rLen+6+sLen])
if sValue.Cmp(halfOrder) > 0 {
return ErrStackInvalidLowSSignature
return scriptError(ErrSigHighS,
"signature is not canonical due to "+
"unnecessarily high S value")
}
}
@ -587,10 +625,21 @@ func (vm *Engine) SetAltStack(data [][]byte) {
func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags, sigCache *SigCache) (*Engine, error) {
// The provided transaction input index must refer to a valid input.
if txIdx < 0 || txIdx >= len(tx.TxIn) {
return nil, ErrInvalidIndex
str := fmt.Sprintf("transaction input index %d is negative or "+
">= %d", txIdx, len(tx.TxIn))
return nil, scriptError(ErrInvalidIndex, str)
}
scriptSig := tx.TxIn[txIdx].SignatureScript
// When both the signature script and public key script are empty the
// result is necessarily an error since the stack would end up being
// empty which is equivalent to a false top element. Thus, just return
// the relevant error now as an optimization.
if len(scriptSig) == 0 && len(scriptPubKey) == 0 {
return nil, scriptError(ErrEvalFalse,
"false stack entry at end of script execution")
}
// The clean stack flag (ScriptVerifyCleanStack) is not allowed without
// the pay-to-script-hash (P2SH) evaluation (ScriptBip16) flag.
//
@ -601,13 +650,15 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
// it should be.
vm := Engine{flags: flags, sigCache: sigCache}
if vm.hasFlag(ScriptVerifyCleanStack) && !vm.hasFlag(ScriptBip16) {
return nil, ErrInvalidFlags
return nil, scriptError(ErrInvalidFlags,
"invalid flags combination")
}
// The signature script must only contain data pushes when the
// associated flag is set.
if vm.hasFlag(ScriptVerifySigPushOnly) && !IsPushOnlyScript(scriptSig) {
return nil, ErrStackNonPushOnly
return nil, scriptError(ErrNotPushOnly,
"signature script is not push only")
}
// The engine stores the scripts in parsed form using a slice. This
@ -617,8 +668,10 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
scripts := [][]byte{scriptSig, scriptPubKey}
vm.scripts = make([][]parsedOpcode, len(scripts))
for i, scr := range scripts {
if len(scr) > maxScriptSize {
return nil, ErrStackLongScript
if len(scr) > MaxScriptSize {
str := fmt.Sprintf("script size %d is larger than max "+
"allowed size %d", len(scr), MaxScriptSize)
return nil, scriptError(ErrScriptTooBig, str)
}
var err error
vm.scripts[i], err = parseScript(scr)
@ -637,7 +690,8 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
if vm.hasFlag(ScriptBip16) && isScriptHash(vm.scripts[1]) {
// Only accept input scripts that push data for P2SH.
if !isPushOnly(vm.scripts[0]) {
return nil, ErrStackP2SHNonPushOnly
return nil, scriptError(ErrNotPushOnly,
"pay to script hash is not push only")
}
vm.bip16 = true
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2013-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -119,29 +119,24 @@ func TestCheckErrorCondition(t *testing.T) {
for i := 0; i < len(pkScript)-1; i++ {
done, err := vm.Step()
if err != nil {
t.Errorf("failed to step %dth time: %v", i, err)
return
t.Fatalf("failed to step %dth time: %v", i, err)
}
if done {
t.Errorf("finshed early on %dth time", i)
return
t.Fatalf("finshed early on %dth time", i)
}
err = vm.CheckErrorCondition(false)
if err != ErrStackScriptUnfinished {
t.Errorf("got unexepected error %v on %dth iteration",
if !IsErrorCode(err, ErrScriptUnfinished) {
t.Fatalf("got unexepected error %v on %dth iteration",
err, i)
return
}
}
done, err := vm.Step()
if err != nil {
t.Errorf("final step failed %v", err)
return
t.Fatalf("final step failed %v", err)
}
if !done {
t.Errorf("final step isn't done!")
return
t.Fatalf("final step isn't done!")
}
err = vm.CheckErrorCondition(false)
@ -193,7 +188,7 @@ func TestInvalidFlagCombinations(t *testing.T) {
for i, test := range tests {
_, err := NewEngine(pkScript, tx, 0, test, nil)
if err != ErrInvalidFlags {
if !IsErrorCode(err, ErrInvalidFlags) {
t.Fatalf("TestInvalidFlagCombinations #%d unexpected "+
"error: %v", i, err)
}

View File

@ -1,154 +1,313 @@
// Copyright (c) 2013-2015 The btcsuite developers
// Copyright (c) 2013-2017 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 (
"errors"
"fmt"
)
var (
// ErrStackShortScript is returned if the script has an opcode that is
// too long for the length of the script.
ErrStackShortScript = errors.New("execute past end of script")
// ErrorCode identifies a kind of script error.
type ErrorCode int
// ErrStackLongScript is returned if the script has an opcode that is
// too long for the length of the script.
ErrStackLongScript = errors.New("script is longer than maximum allowed")
// These constants are used to identify a specific Error.
const (
// ErrInternal is returned if internal consistency checks fail. In
// practice this error should never be seen as it would mean there is an
// error in the engine logic.
ErrInternal ErrorCode = iota
// ErrStackUnderflow is returned if an opcode requires more items on the
// stack than is present.f
ErrStackUnderflow = errors.New("stack underflow")
// ---------------------------------------
// Failures related to improper API usage.
// ---------------------------------------
// ErrStackInvalidArgs is returned if the argument for an opcode is out
// of acceptable range.
ErrStackInvalidArgs = errors.New("invalid argument")
// ErrStackOpDisabled is returned when a disabled opcode is encountered
// in the script.
ErrStackOpDisabled = errors.New("disabled opcode")
// ErrStackVerifyFailed is returned when one of the OP_VERIFY or
// OP_*VERIFY instructions is executed and the conditions fails.
ErrStackVerifyFailed = errors.New("verify failed")
// ErrStackNumberTooBig is returned when the argument for an opcode that
// should be an offset is obviously far too large.
ErrStackNumberTooBig = errors.New("number too big")
// ErrStackInvalidOpcode is returned when an opcode marked as invalid or
// a completely undefined opcode is encountered.
ErrStackInvalidOpcode = errors.New("invalid opcode")
// ErrStackReservedOpcode is returned when an opcode marked as reserved
// is encountered.
ErrStackReservedOpcode = errors.New("reserved opcode")
// ErrStackEarlyReturn is returned when OP_RETURN is executed in the
// script.
ErrStackEarlyReturn = errors.New("script returned early")
// ErrStackNoIf is returned if an OP_ELSE or OP_ENDIF is encountered
// without first having an OP_IF or OP_NOTIF in the script.
ErrStackNoIf = errors.New("OP_ELSE or OP_ENDIF with no matching OP_IF")
// ErrStackMissingEndif is returned if the end of a script is reached
// without and OP_ENDIF to correspond to a conditional expression.
ErrStackMissingEndif = fmt.Errorf("execute fail, in conditional execution")
// ErrStackTooManyPubKeys is returned if an OP_CHECKMULTISIG is
// encountered with more than MaxPubKeysPerMultiSig pubkeys present.
ErrStackTooManyPubKeys = errors.New("invalid pubkey count in OP_CHECKMULTISIG")
// ErrStackTooManyOperations is returned if a script has more than
// MaxOpsPerScript opcodes that do not push data.
ErrStackTooManyOperations = errors.New("too many operations in script")
// ErrStackElementTooBig is returned if the size of an element to be
// pushed to the stack is over MaxScriptElementSize.
ErrStackElementTooBig = errors.New("element in script too large")
// ErrStackUnknownAddress is returned when ScriptToAddrHash does not
// recognize the pattern of the script and thus can not find the address
// for payment.
ErrStackUnknownAddress = errors.New("non-recognised address")
// ErrStackScriptFailed is returned when at the end of a script the
// boolean on top of the stack is false signifying that the script has
// failed.
ErrStackScriptFailed = errors.New("execute fail, fail on stack")
// ErrStackScriptUnfinished is returned when CheckErrorCondition is
// called on a script that has not finished executing.
ErrStackScriptUnfinished = errors.New("error check when script unfinished")
// ErrStackEmptyStack is returned when the stack is empty at the end of
// execution. Normal operation requires that a boolean is on top of the
// stack when the scripts have finished executing.
ErrStackEmptyStack = errors.New("stack empty at end of execution")
// ErrStackP2SHNonPushOnly is returned when a Pay-to-Script-Hash
// transaction is encountered and the ScriptSig does operations other
// than push data (in violation of bip16).
ErrStackP2SHNonPushOnly = errors.New("pay to script hash with non " +
"pushonly input")
// ErrStackInvalidParseType is an internal error returned from
// ScriptToAddrHash ony if the internal data tables are wrong.
ErrStackInvalidParseType = errors.New("internal error: invalid parsetype found")
// ErrStackInvalidAddrOffset is an internal error returned from
// ScriptToAddrHash ony if the internal data tables are wrong.
ErrStackInvalidAddrOffset = errors.New("internal error: invalid offset found")
// ErrStackInvalidIndex is returned when an out-of-bounds index was
// passed to a function.
ErrStackInvalidIndex = errors.New("invalid script index")
// ErrStackNonPushOnly is returned when ScriptInfo is called with a
// pkScript that peforms operations other that pushing data to the stack.
ErrStackNonPushOnly = errors.New("SigScript is non pushonly")
// ErrStackOverflow is returned when stack and altstack combined depth
// is over the limit.
ErrStackOverflow = errors.New("stack overflow")
// ErrStackInvalidLowSSignature is returned when the ScriptVerifyLowS
// flag is set and the script contains any signatures whose S values
// are higher than the half order.
ErrStackInvalidLowSSignature = errors.New("invalid low s signature")
// ErrStackInvalidPubKey is returned when the ScriptVerifyScriptEncoding
// flag is set and the script contains invalid pubkeys.
ErrStackInvalidPubKey = errors.New("invalid strict pubkey")
// ErrStackCleanStack is returned when the ScriptVerifyCleanStack flag
// is set and after evalution the stack does not contain only one element,
// which also must be true if interpreted as a boolean.
ErrStackCleanStack = errors.New("stack is not clean")
// ErrStackMinimalData is returned when the ScriptVerifyMinimalData flag
// is set and the script contains push operations that do not use
// the minimal opcode required.
ErrStackMinimalData = errors.New("non-minimally encoded script number")
)
var (
// ErrInvalidFlags is returned when the passed flags to NewScript
// ErrInvalidFlags is returned when the passed flags to NewEngine
// contain an invalid combination.
ErrInvalidFlags = errors.New("invalid flags combination")
ErrInvalidFlags
// ErrInvalidIndex is returned when the passed input index for the
// provided transaction is out of range.
ErrInvalidIndex = errors.New("invalid input index")
// ErrInvalidIndex is returned when an out-of-bounds index is passed to
// a function.
ErrInvalidIndex
// ErrUnsupportedAddress is returned when a concrete type that
// implements a btcutil.Address is not a supported type.
ErrUnsupportedAddress = errors.New("unsupported address type")
ErrUnsupportedAddress
// ErrBadNumRequired is returned from MultiSigScript when nrequired is
// larger than the number of provided public keys.
ErrBadNumRequired = errors.New("more signatures required than keys present")
// ErrNotMultisigScript is returned from CalcMultiSigStats when the
// provided script is not a multisig script.
ErrNotMultisigScript
// ErrTooManyRequiredSigs is returned from MultiSigScript when the
// specified number of required signatures is larger than the number of
// provided public keys.
ErrTooManyRequiredSigs
// ErrTooMuchNullData is returned from NullDataScript when the length of
// the provided data exceeds MaxDataCarrierSize.
ErrTooMuchNullData
// ------------------------------------------
// Failures related to final execution state.
// ------------------------------------------
// ErrEarlyReturn is returned when OP_RETURN is executed in the script.
ErrEarlyReturn
// ErrEmptyStack is returned when the script evaluated without error,
// but terminated with an empty top stack element.
ErrEmptyStack
// ErrEvalFalse is returned when the script evaluated without error but
// terminated with a false top stack element.
ErrEvalFalse
// ErrScriptUnfinished is returned when CheckErrorCondition is called on
// a script that has not finished executing.
ErrScriptUnfinished
// ErrScriptDone is returned when an attempt to execute an opcode is
// made once all of them have already been executed. This can happen
// due to things such as a second call to Execute or calling Step after
// all opcodes have already been executed.
ErrInvalidProgramCounter
// -----------------------------------------------------
// Failures related to exceeding maximum allowed limits.
// -----------------------------------------------------
// ErrScriptTooBig is returned if a script is larger than MaxScriptSize.
ErrScriptTooBig
// ErrElementTooBig is returned if the size of an element to be pushed
// to the stack is over MaxScriptElementSize.
ErrElementTooBig
// ErrTooManyOperations is returned if a script has more than
// MaxOpsPerScript opcodes that do not push data.
ErrTooManyOperations
// ErrStackOverflow is returned when stack and altstack combined depth
// is over the limit.
ErrStackOverflow
// ErrInvalidPubKeyCount is returned when the number of public keys
// specified for a multsig is either negative or greater than
// MaxPubKeysPerMultiSig.
ErrInvalidPubKeyCount
// ErrInvalidSignatureCount is returned when the number of signatures
// specified for a multisig is either negative or greater than the
// number of public keys.
ErrInvalidSignatureCount
// ErrNumberTooBig is returned when the argument for an opcode that
// expects numeric input is larger than the expected maximum number of
// bytes. For the most part, opcodes that deal with stack manipulation
// via offsets, arithmetic, numeric comparison, and boolean logic are
// those that this applies to. However, any opcode that expects numeric
// input may fail with this code.
ErrNumberTooBig
// --------------------------------------------
// Failures related to verification operations.
// --------------------------------------------
// ErrVerify is returned when OP_VERIFY is encountered in a script and
// the top item on the data stack does not evaluate to true.
ErrVerify
// ErrEqualVerify is returned when OP_EQUALVERIFY is encountered in a
// script and the top item on the data stack does not evaluate to true.
ErrEqualVerify
// ErrNumEqualVerify is returned when OP_NUMEQUALVERIFY is encountered
// in a script and the top item on the data stack does not evaluate to
// true.
ErrNumEqualVerify
// ErrCheckSigVerify is returned when OP_CHECKSIGVERIFY is encountered
// in a script and the top item on the data stack does not evaluate to
// true.
ErrCheckSigVerify
// ErrCheckSigVerify is returned when OP_CHECKMULTISIGVERIFY is
// encountered in a script and the top item on the data stack does not
// evaluate to true.
ErrCheckMultiSigVerify
// --------------------------------------------
// Failures related to improper use of opcodes.
// --------------------------------------------
// ErrDisabledOpcode is returned when a disabled opcode is encountered
// in a script.
ErrDisabledOpcode
// ErrReservedOpcode is returned when an opcode marked as reserved
// is encountered in a script.
ErrReservedOpcode
// ErrMalformedPush is returned when a data push opcode tries to push
// more bytes than are left in the script.
ErrMalformedPush
// ErrInvalidStackOperation is returned when a stack operation is
// attempted with a number that is invalid for the current stack size.
ErrInvalidStackOperation
// ErrUnbalancedConditional is returned when an OP_ELSE or OP_ENDIF is
// encountered in a script without first having an OP_IF or OP_NOTIF or
// the end of script is reached without encountering an OP_ENDIF when
// an OP_IF or OP_NOTIF was previously encountered.
ErrUnbalancedConditional
// ---------------------------------
// Failures related to malleability.
// ---------------------------------
// ErrMinimalData is returned when the ScriptVerifyMinimalData flag
// is set and the script contains push operations that do not use
// the minimal opcode required.
ErrMinimalData
// ErrInvalidSigHashType is returned when a signature hash type is not
// one of the supported types.
ErrInvalidSigHashType
// ErrSigDER is returned when a signature is not a canonically-encoded
// DER signature.
ErrSigDER
// ErrSigHighS is returned when the ScriptVerifyLowS flag is set and the
// script contains any signatures whose S values are higher than the
// half order.
ErrSigHighS
// ErrNotPushOnly is returned when a script that is required to only
// push data to the stack performs other operations. A couple of cases
// where this applies is for a pay-to-script-hash signature script when
// bip16 is active and when the ScriptVerifySigPushOnly flag is set.
ErrNotPushOnly
// ErrSigNullDummy is returned when the ScriptStrictMultiSig flag is set
// and a multisig script has anything other than 0 for the extra dummy
// argument.
ErrSigNullDummy
// ErrPubKeyType is returned when the ScriptVerifyStrictEncoding
// flag is set and the script contains invalid public keys.
ErrPubKeyType
// ErrCleanStack is returned when the ScriptVerifyCleanStack flag
// is set, and after evalution, the stack does not contain only a
// single element.
ErrCleanStack
// -------------------------------
// Failures related to soft forks.
// -------------------------------
// ErrDiscourageUpgradableNOPs is returned when the
// ScriptDiscourageUpgradableNops flag is set and a NOP opcode is
// encountered in a script.
ErrDiscourageUpgradableNOPs
// ErrNegativeLockTime is returned when a script contains an opcode that
// interprets a negative lock time.
ErrNegativeLockTime
// ErrUnsatisfiedLockTime is returned when a script contains an opcode
// that involves a lock time and the required lock time has not been
// reached.
ErrUnsatisfiedLockTime
// numErrorCodes is the maximum error code number used in tests. This
// entry MUST be the last entry in the enum.
numErrorCodes
)
// Map of ErrorCode values back to their constant names for pretty printing.
var errorCodeStrings = map[ErrorCode]string{
ErrInternal: "ErrInternal",
ErrInvalidFlags: "ErrInvalidFlags",
ErrInvalidIndex: "ErrInvalidIndex",
ErrUnsupportedAddress: "ErrUnsupportedAddress",
ErrNotMultisigScript: "ErrNotMultisigScript",
ErrTooManyRequiredSigs: "ErrTooManyRequiredSigs",
ErrTooMuchNullData: "ErrTooMuchNullData",
ErrEarlyReturn: "ErrEarlyReturn",
ErrEmptyStack: "ErrEmptyStack",
ErrEvalFalse: "ErrEvalFalse",
ErrScriptUnfinished: "ErrScriptUnfinished",
ErrInvalidProgramCounter: "ErrInvalidProgramCounter",
ErrScriptTooBig: "ErrScriptTooBig",
ErrElementTooBig: "ErrElementTooBig",
ErrTooManyOperations: "ErrTooManyOperations",
ErrStackOverflow: "ErrStackOverflow",
ErrInvalidPubKeyCount: "ErrInvalidPubKeyCount",
ErrInvalidSignatureCount: "ErrInvalidSignatureCount",
ErrNumberTooBig: "ErrNumberTooBig",
ErrVerify: "ErrVerify",
ErrEqualVerify: "ErrEqualVerify",
ErrNumEqualVerify: "ErrNumEqualVerify",
ErrCheckSigVerify: "ErrCheckSigVerify",
ErrCheckMultiSigVerify: "ErrCheckMultiSigVerify",
ErrDisabledOpcode: "ErrDisabledOpcode",
ErrReservedOpcode: "ErrReservedOpcode",
ErrMalformedPush: "ErrMalformedPush",
ErrInvalidStackOperation: "ErrInvalidStackOperation",
ErrUnbalancedConditional: "ErrUnbalancedConditional",
ErrMinimalData: "ErrMinimalData",
ErrInvalidSigHashType: "ErrInvalidSigHashType",
ErrSigDER: "ErrSigDER",
ErrSigHighS: "ErrSigHighS",
ErrNotPushOnly: "ErrNotPushOnly",
ErrSigNullDummy: "ErrSigNullDummy",
ErrPubKeyType: "ErrPubKeyType",
ErrCleanStack: "ErrCleanStack",
ErrDiscourageUpgradableNOPs: "ErrDiscourageUpgradableNOPs",
ErrNegativeLockTime: "ErrNegativeLockTime",
ErrUnsatisfiedLockTime: "ErrUnsatisfiedLockTime",
}
// String returns the ErrorCode as a human-readable name.
func (e ErrorCode) String() string {
if s := errorCodeStrings[e]; s != "" {
return s
}
return fmt.Sprintf("Unknown ErrorCode (%d)", int(e))
}
// Error identifies a script-related error. It is used to indicate three
// classes of errors:
// 1) Script execution failures due to violating one of the many requirements
// imposed by the script engine or evaluating to false
// 2) Improper API usage by callers
// 3) Internal consistency check failures
//
// The caller can use type assertions on the returned errors to access the
// ErrorCode field to ascertain the specific reason for the error. As an
// additional convenience, the caller may make use of the IsErrorCode function
// to check for a specific error code.
type Error struct {
ErrorCode ErrorCode
Description string
}
// Error satisfies the error interface and prints human-readable errors.
func (e Error) Error() string {
return e.Description
}
// scriptError creates an Error given a set of arguments.
func scriptError(c ErrorCode, desc string) Error {
return Error{ErrorCode: c, Description: desc}
}
// IsErrorCode returns whether or not the provided error is a script error with
// the provided error code.
func IsErrorCode(err error, c ErrorCode) bool {
serr, ok := err.(Error)
return ok && serr.ErrorCode == c
}

106
txscript/error_test.go Normal file
View File

@ -0,0 +1,106 @@
// Copyright (c) 2017 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 (
"testing"
)
// TestErrorCodeStringer tests the stringized output for the ErrorCode type.
func TestErrorCodeStringer(t *testing.T) {
t.Parallel()
tests := []struct {
in ErrorCode
want string
}{
{ErrInternal, "ErrInternal"},
{ErrInvalidFlags, "ErrInvalidFlags"},
{ErrInvalidIndex, "ErrInvalidIndex"},
{ErrUnsupportedAddress, "ErrUnsupportedAddress"},
{ErrTooManyRequiredSigs, "ErrTooManyRequiredSigs"},
{ErrTooMuchNullData, "ErrTooMuchNullData"},
{ErrNotMultisigScript, "ErrNotMultisigScript"},
{ErrEarlyReturn, "ErrEarlyReturn"},
{ErrEmptyStack, "ErrEmptyStack"},
{ErrEvalFalse, "ErrEvalFalse"},
{ErrScriptUnfinished, "ErrScriptUnfinished"},
{ErrInvalidProgramCounter, "ErrInvalidProgramCounter"},
{ErrScriptTooBig, "ErrScriptTooBig"},
{ErrElementTooBig, "ErrElementTooBig"},
{ErrTooManyOperations, "ErrTooManyOperations"},
{ErrStackOverflow, "ErrStackOverflow"},
{ErrInvalidPubKeyCount, "ErrInvalidPubKeyCount"},
{ErrInvalidSignatureCount, "ErrInvalidSignatureCount"},
{ErrNumberTooBig, "ErrNumberTooBig"},
{ErrVerify, "ErrVerify"},
{ErrEqualVerify, "ErrEqualVerify"},
{ErrNumEqualVerify, "ErrNumEqualVerify"},
{ErrCheckSigVerify, "ErrCheckSigVerify"},
{ErrCheckMultiSigVerify, "ErrCheckMultiSigVerify"},
{ErrDisabledOpcode, "ErrDisabledOpcode"},
{ErrReservedOpcode, "ErrReservedOpcode"},
{ErrMalformedPush, "ErrMalformedPush"},
{ErrInvalidStackOperation, "ErrInvalidStackOperation"},
{ErrUnbalancedConditional, "ErrUnbalancedConditional"},
{ErrMinimalData, "ErrMinimalData"},
{ErrInvalidSigHashType, "ErrInvalidSigHashType"},
{ErrSigDER, "ErrSigDER"},
{ErrSigHighS, "ErrSigHighS"},
{ErrNotPushOnly, "ErrNotPushOnly"},
{ErrSigNullDummy, "ErrSigNullDummy"},
{ErrPubKeyType, "ErrPubKeyType"},
{ErrCleanStack, "ErrCleanStack"},
{ErrDiscourageUpgradableNOPs, "ErrDiscourageUpgradableNOPs"},
{ErrNegativeLockTime, "ErrNegativeLockTime"},
{ErrUnsatisfiedLockTime, "ErrUnsatisfiedLockTime"},
{0xffff, "Unknown ErrorCode (65535)"},
}
// Detect additional error codes that don't have the stringer added.
if len(tests)-1 != int(numErrorCodes) {
t.Errorf("It appears an error code was added without adding an " +
"associated stringer test")
}
t.Logf("Running %d tests", len(tests))
for i, test := range tests {
result := test.in.String()
if result != test.want {
t.Errorf("String #%d\n got: %s want: %s", i, result,
test.want)
continue
}
}
}
// TestError tests the error output for the Error type.
func TestError(t *testing.T) {
t.Parallel()
tests := []struct {
in Error
want string
}{
{
Error{Description: "some error"},
"some error",
},
{
Error{Description: "human-readable error"},
"human-readable error",
},
}
t.Logf("Running %d tests", len(tests))
for i, test := range tests {
result := test.in.Error()
if result != test.want {
t.Errorf("Error #%d\n got: %s want: %s", i, result,
test.want)
continue
}
}
}

View File

@ -9,7 +9,6 @@ import (
"crypto/sha1"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"hash"
@ -698,28 +697,45 @@ func (pop *parsedOpcode) checkMinimalDataPush() error {
opcode := pop.opcode.value
if dataLen == 0 && opcode != OP_0 {
return ErrStackMinimalData
str := fmt.Sprintf("zero length data push is encoded with "+
"opcode %s instead of OP_0", pop.opcode.name)
return scriptError(ErrMinimalData, str)
} else if dataLen == 1 && data[0] >= 1 && data[0] <= 16 {
if opcode != OP_1+data[0]-1 {
// Should have used OP_1 .. OP_16
return ErrStackMinimalData
str := fmt.Sprintf("data push of the value %d encoded "+
"with opcode %s instead of OP_%d", data[0],
pop.opcode.name, data[0])
return scriptError(ErrMinimalData, str)
}
} else if dataLen == 1 && data[0] == 0x81 {
if opcode != OP_1NEGATE {
return ErrStackMinimalData
str := fmt.Sprintf("data push of the value -1 encoded "+
"with opcode %s instead of OP_1NEGATE",
pop.opcode.name)
return scriptError(ErrMinimalData, str)
}
} else if dataLen <= 75 {
if int(opcode) != dataLen {
// Should have used a direct push
return ErrStackMinimalData
str := fmt.Sprintf("data push of %d bytes encoded "+
"with opcode %s instead of OP_DATA_%d", dataLen,
pop.opcode.name, dataLen)
return scriptError(ErrMinimalData, str)
}
} else if dataLen <= 255 {
if opcode != OP_PUSHDATA1 {
return ErrStackMinimalData
str := fmt.Sprintf("data push of %d bytes encoded "+
"with opcode %s instead of OP_PUSHDATA1",
dataLen, pop.opcode.name)
return scriptError(ErrMinimalData, str)
}
} else if dataLen <= 65535 {
if opcode != OP_PUSHDATA2 {
return ErrStackMinimalData
str := fmt.Sprintf("data push of %d bytes encoded "+
"with opcode %s instead of OP_PUSHDATA2",
dataLen, pop.opcode.name)
return scriptError(ErrMinimalData, str)
}
}
return nil
@ -780,7 +796,11 @@ func (pop *parsedOpcode) bytes() ([]byte, error) {
retbytes[0] = pop.opcode.value
if pop.opcode.length == 1 {
if len(pop.data) != 0 {
return nil, ErrStackInvalidOpcode
str := fmt.Sprintf("internal consistency error - "+
"parsed opcode %s has data length %d when %d "+
"was expected", pop.opcode.name, len(pop.data),
0)
return nil, scriptError(ErrInternal, str)
}
return retbytes, nil
}
@ -809,7 +829,10 @@ func (pop *parsedOpcode) bytes() ([]byte, error) {
retbytes = append(retbytes, pop.data...)
if len(retbytes) != nbytes {
return nil, ErrStackInvalidOpcode
str := fmt.Sprintf("internal consistency error - "+
"parsed opcode %s has data length %d when %d was "+
"expected", pop.opcode.name, len(retbytes), nbytes)
return nil, scriptError(ErrInternal, str)
}
return retbytes, nil
@ -826,19 +849,25 @@ func (pop *parsedOpcode) bytes() ([]byte, error) {
// dictate the script doesn't fail until the program counter passes over a
// disabled opcode (even when they appear in a branch that is not executed).
func opcodeDisabled(op *parsedOpcode, vm *Engine) error {
return ErrStackOpDisabled
str := fmt.Sprintf("attempt to execute disabled opcode %s",
op.opcode.name)
return scriptError(ErrDisabledOpcode, str)
}
// opcodeReserved is a common handler for all reserved opcodes. It returns an
// appropriate error indicating the opcode is reserved.
func opcodeReserved(op *parsedOpcode, vm *Engine) error {
return ErrStackReservedOpcode
str := fmt.Sprintf("attempt to execute reserved opcode %s",
op.opcode.name)
return scriptError(ErrReservedOpcode, str)
}
// opcodeInvalid is a common handler for all invalid opcodes. It returns an
// appropriate error indicating the opcode is invalid.
func opcodeInvalid(op *parsedOpcode, vm *Engine) error {
return ErrStackInvalidOpcode
str := fmt.Sprintf("attempt to execute invalid opcode %s",
op.opcode.name)
return scriptError(ErrReservedOpcode, str)
}
// opcodeFalse pushes an empty array to the data stack to represent false. Note
@ -880,8 +909,9 @@ func opcodeNop(op *parsedOpcode, vm *Engine) error {
case OP_NOP1, OP_NOP4, OP_NOP5,
OP_NOP6, OP_NOP7, OP_NOP8, OP_NOP9, OP_NOP10:
if vm.hasFlag(ScriptDiscourageUpgradableNops) {
return fmt.Errorf("OP_NOP%d reserved for soft-fork "+
str := fmt.Sprintf("OP_NOP%d reserved for soft-fork "+
"upgrades", op.opcode.value-(OP_NOP1-1))
return scriptError(ErrDiscourageUpgradableNOPs, str)
}
}
return nil
@ -959,7 +989,9 @@ func opcodeNotIf(op *parsedOpcode, vm *Engine) error {
// Conditional stack transformation: [... OpCondValue] -> [... !OpCondValue]
func opcodeElse(op *parsedOpcode, vm *Engine) error {
if len(vm.condStack) == 0 {
return ErrStackNoIf
str := fmt.Sprintf("encountered opcode %s with no matching "+
"opcode to begin conditional execution", op.opcode.name)
return scriptError(ErrUnbalancedConditional, str)
}
conditionalIdx := len(vm.condStack) - 1
@ -983,31 +1015,43 @@ func opcodeElse(op *parsedOpcode, vm *Engine) error {
// Conditional stack transformation: [... OpCondValue] -> [...]
func opcodeEndif(op *parsedOpcode, vm *Engine) error {
if len(vm.condStack) == 0 {
return ErrStackNoIf
str := fmt.Sprintf("encountered opcode %s with no matching "+
"opcode to begin conditional execution", op.opcode.name)
return scriptError(ErrUnbalancedConditional, str)
}
vm.condStack = vm.condStack[:len(vm.condStack)-1]
return nil
}
// opcodeVerify examines the top item on the data stack as a boolean value and
// verifies it evaluates to true. An error is returned if it does not.
func opcodeVerify(op *parsedOpcode, vm *Engine) error {
// abstractVerify examines the top item on the data stack as a boolean value and
// verifies it evaluates to true. An error is returned either when there is no
// item on the stack or when that item evaluates to false. In the latter case
// where the verification fails specifically due to the top item evaluating
// to false, the returned error will use the passed error code.
func abstractVerify(op *parsedOpcode, vm *Engine, c ErrorCode) error {
verified, err := vm.dstack.PopBool()
if err != nil {
return err
}
if !verified {
return ErrStackVerifyFailed
str := fmt.Sprintf("%s failed", op.opcode.name)
return scriptError(c, str)
}
return nil
}
// opcodeVerify examines the top item on the data stack as a boolean value and
// verifies it evaluates to true. An error is returned if it does not.
func opcodeVerify(op *parsedOpcode, vm *Engine) error {
return abstractVerify(op, vm, ErrVerify)
}
// opcodeReturn returns an appropriate error since it is always an error to
// return early from a script.
func opcodeReturn(op *parsedOpcode, vm *Engine) error {
return ErrStackEarlyReturn
return scriptError(ErrEarlyReturn, "script returned early")
}
// verifyLockTime is a helper function used to validate locktimes.
@ -1016,14 +1060,16 @@ func verifyLockTime(txLockTime, threshold, lockTime int64) error {
// type.
if !((txLockTime < threshold && lockTime < threshold) ||
(txLockTime >= threshold && lockTime >= threshold)) {
return fmt.Errorf("mismatched locktime types -- tx locktime %d, stack "+
"locktime %d", txLockTime, lockTime)
str := fmt.Sprintf("mismatched locktime types -- tx locktime "+
"%d, stack locktime %d", txLockTime, lockTime)
return scriptError(ErrUnsatisfiedLockTime, str)
}
if lockTime > txLockTime {
str := "locktime requirement not satisfied -- locktime is " +
"greater than the transaction locktime: %d > %d"
return fmt.Errorf(str, lockTime, txLockTime)
str := fmt.Sprintf("locktime requirement not satisfied -- "+
"locktime is greater than the transaction locktime: "+
"%d > %d", lockTime, txLockTime)
return scriptError(ErrUnsatisfiedLockTime, str)
}
return nil
@ -1039,8 +1085,8 @@ func opcodeCheckLockTimeVerify(op *parsedOpcode, vm *Engine) error {
// opcode as OP_NOP2 instead.
if !vm.hasFlag(ScriptVerifyCheckLockTimeVerify) {
if vm.hasFlag(ScriptDiscourageUpgradableNops) {
return errors.New("OP_NOP2 reserved for soft-fork " +
"upgrades")
return scriptError(ErrDiscourageUpgradableNOPs,
"OP_NOP2 reserved for soft-fork upgrades")
}
return nil
}
@ -1067,7 +1113,8 @@ func opcodeCheckLockTimeVerify(op *parsedOpcode, vm *Engine) error {
// arithmetic being done first, you can always use
// 0 OP_MAX OP_CHECKLOCKTIMEVERIFY.
if lockTime < 0 {
return fmt.Errorf("negative locktime: %d", lockTime)
str := fmt.Sprintf("negative lock time: %d", lockTime)
return scriptError(ErrNegativeLockTime, str)
}
// The lock time field of a transaction is either a block height at
@ -1095,7 +1142,8 @@ func opcodeCheckLockTimeVerify(op *parsedOpcode, vm *Engine) error {
// another input being unlocked, the opcode execution will still fail when the
// input being used by the opcode is locked.
if vm.tx.TxIn[vm.txIdx].Sequence == wire.MaxTxInSequenceNum {
return errors.New("transaction input is finalized")
return scriptError(ErrUnsatisfiedLockTime,
"transaction input is finalized")
}
return nil
@ -1111,8 +1159,8 @@ func opcodeCheckSequenceVerify(op *parsedOpcode, vm *Engine) error {
// opcode as OP_NOP3 instead.
if !vm.hasFlag(ScriptVerifyCheckSequenceVerify) {
if vm.hasFlag(ScriptDiscourageUpgradableNops) {
return errors.New("OP_NOP3 reserved for soft-fork " +
"upgrades")
return scriptError(ErrDiscourageUpgradableNOPs,
"OP_NOP3 reserved for soft-fork upgrades")
}
return nil
}
@ -1139,7 +1187,8 @@ func opcodeCheckSequenceVerify(op *parsedOpcode, vm *Engine) error {
// arithmetic being done first, you can always use
// 0 OP_MAX OP_CHECKSEQUENCEVERIFY.
if stackSequence < 0 {
return fmt.Errorf("negative sequence: %d", stackSequence)
str := fmt.Sprintf("negative sequence: %d", stackSequence)
return scriptError(ErrNegativeLockTime, str)
}
sequence := int64(stackSequence)
@ -1154,8 +1203,9 @@ func opcodeCheckSequenceVerify(op *parsedOpcode, vm *Engine) error {
// Transaction version numbers not high enough to trigger CSV rules must
// fail.
if vm.tx.Version < 2 {
return fmt.Errorf("invalid transaction version: %d",
str := fmt.Sprintf("invalid transaction version: %d",
vm.tx.Version)
return scriptError(ErrUnsatisfiedLockTime, str)
}
// Sequence numbers with their most significant bit set are not
@ -1164,8 +1214,9 @@ func opcodeCheckSequenceVerify(op *parsedOpcode, vm *Engine) error {
// to get around a CHECKSEQUENCEVERIFY check.
txSequence := int64(vm.tx.TxIn[vm.txIdx].Sequence)
if txSequence&int64(wire.SequenceLockTimeDisabled) != 0 {
return fmt.Errorf("transaction sequence has sequence "+
str := fmt.Sprintf("transaction sequence has sequence "+
"locktime disabled bit set: 0x%x", txSequence)
return scriptError(ErrUnsatisfiedLockTime, str)
}
// Mask off non-consensus bits before doing comparisons.
@ -1399,7 +1450,7 @@ func opcodeEqual(op *parsedOpcode, vm *Engine) error {
func opcodeEqualVerify(op *parsedOpcode, vm *Engine) error {
err := opcodeEqual(op, vm)
if err == nil {
err = opcodeVerify(op, vm)
err = abstractVerify(op, vm, ErrEqualVerify)
}
return err
}
@ -1637,7 +1688,7 @@ func opcodeNumEqual(op *parsedOpcode, vm *Engine) error {
func opcodeNumEqualVerify(op *parsedOpcode, vm *Engine) error {
err := opcodeNumEqual(op, vm)
if err == nil {
err = opcodeVerify(op, vm)
err = abstractVerify(op, vm, ErrNumEqualVerify)
}
return err
}
@ -2045,7 +2096,7 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
func opcodeCheckSigVerify(op *parsedOpcode, vm *Engine) error {
err := opcodeCheckSig(op, vm)
if err == nil {
err = opcodeVerify(op, vm)
err = abstractVerify(op, vm, ErrCheckSigVerify)
}
return err
}
@ -2085,12 +2136,21 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
}
numPubKeys := int(numKeys.Int32())
if numPubKeys < 0 || numPubKeys > MaxPubKeysPerMultiSig {
return ErrStackTooManyPubKeys
if numPubKeys < 0 {
str := fmt.Sprintf("number of pubkeys %d is negative",
numPubKeys)
return scriptError(ErrInvalidPubKeyCount, str)
}
if numPubKeys > MaxPubKeysPerMultiSig {
str := fmt.Sprintf("too many pubkeys: %d > %d",
numPubKeys, MaxPubKeysPerMultiSig)
return scriptError(ErrInvalidPubKeyCount, str)
}
vm.numOps += numPubKeys
if vm.numOps > MaxOpsPerScript {
return ErrStackTooManyOperations
str := fmt.Sprintf("exceeded max operation limit of %d",
MaxOpsPerScript)
return scriptError(ErrTooManyOperations, str)
}
pubKeys := make([][]byte, 0, numPubKeys)
@ -2108,12 +2168,15 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
}
numSignatures := int(numSigs.Int32())
if numSignatures < 0 {
return fmt.Errorf("number of signatures '%d' is less than 0",
str := fmt.Sprintf("number of signatures %d is negative",
numSignatures)
return scriptError(ErrInvalidSignatureCount, str)
}
if numSignatures > numPubKeys {
return fmt.Errorf("more signatures than pubkeys: %d > %d",
str := fmt.Sprintf("more signatures than pubkeys: %d > %d",
numSignatures, numPubKeys)
return scriptError(ErrInvalidSignatureCount, str)
}
signatures := make([]*parsedSigInfo, 0, numSignatures)
@ -2139,8 +2202,9 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
// value which unfortunately provides a source of malleability. Thus,
// there is a script flag to force an error when the value is NOT 0.
if vm.hasFlag(ScriptStrictMultiSig) && len(dummy) != 0 {
return fmt.Errorf("multisig dummy argument is not zero length: %d",
len(dummy))
str := fmt.Sprintf("multisig dummy argument has length %d "+
"instead of 0", len(dummy))
return scriptError(ErrSigNullDummy, str)
}
// Get script starting from the most recent OP_CODESEPARATOR.
@ -2267,7 +2331,7 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
func opcodeCheckMultiSigVerify(op *parsedOpcode, vm *Engine) error {
err := opcodeCheckMultiSig(op, vm)
if err == nil {
err = opcodeVerify(op, vm)
err = abstractVerify(op, vm, ErrCheckMultiSigVerify)
}
return err
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2013-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -24,10 +24,11 @@ func TestOpcodeDisabled(t *testing.T) {
}
for _, opcodeVal := range tests {
pop := parsedOpcode{opcode: &opcodeArray[opcodeVal], data: nil}
if err := opcodeDisabled(&pop, nil); err != ErrStackOpDisabled {
err := opcodeDisabled(&pop, nil)
if !IsErrorCode(err, ErrDisabledOpcode) {
t.Errorf("opcodeDisabled: unexpected error - got %v, "+
"want %v", err, ErrStackOpDisabled)
return
"want %v", err, ErrDisabledOpcode)
continue
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2013-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -120,7 +120,11 @@ func parseScriptTemplate(script []byte, opcodes *[256]opcode) ([]parsedOpcode, e
// Data pushes of specific lengths -- OP_DATA_[1-75].
case op.length > 1:
if len(script[i:]) < op.length {
return retScript, ErrStackShortScript
str := fmt.Sprintf("opcode %s requires %d "+
"bytes, but script only has %d remaining",
op.name, op.length, len(script[i:]))
return retScript, scriptError(ErrMalformedPush,
str)
}
// Slice out the data.
@ -133,7 +137,11 @@ func parseScriptTemplate(script []byte, opcodes *[256]opcode) ([]parsedOpcode, e
off := i + 1
if len(script[off:]) < -op.length {
return retScript, ErrStackShortScript
str := fmt.Sprintf("opcode %s requires %d "+
"bytes, but script only has %d remaining",
op.name, -op.length, len(script[off:]))
return retScript, scriptError(ErrMalformedPush,
str)
}
// Next -length bytes are little endian length of data.
@ -149,9 +157,10 @@ func parseScriptTemplate(script []byte, opcodes *[256]opcode) ([]parsedOpcode, e
(uint(script[off+1]) << 8) |
uint(script[off]))
default:
return retScript,
fmt.Errorf("invalid opcode length %d",
op.length)
str := fmt.Sprintf("invalid opcode length %d",
op.length)
return retScript, scriptError(ErrMalformedPush,
str)
}
// Move offset to beginning of the data.
@ -160,7 +169,11 @@ func parseScriptTemplate(script []byte, opcodes *[256]opcode) ([]parsedOpcode, e
// Disallow entries that do not fit script or were
// sign extended.
if int(l) > len(script[off:]) || int(l) < 0 {
return retScript, ErrStackShortScript
str := fmt.Sprintf("opcode %s pushes %d bytes, "+
"but script only has %d remaining",
op.name, int(l), len(script[off:]))
return retScript, scriptError(ErrMalformedPush,
str)
}
pop.data = script[off : off+int(l)]

File diff suppressed because it is too large Load Diff

View File

@ -62,9 +62,9 @@ func (b *ScriptBuilder) AddOp(opcode byte) *ScriptBuilder {
// 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 {
if len(b.script)+1 > MaxScriptSize {
str := fmt.Sprintf("adding an opcode would exceed the maximum "+
"allowed canonical script length of %d", maxScriptSize)
"allowed canonical script length of %d", MaxScriptSize)
b.err = ErrScriptNotCanonical(str)
return b
}
@ -83,9 +83,9 @@ func (b *ScriptBuilder) AddOps(opcodes []byte) *ScriptBuilder {
// 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 {
if len(b.script)+len(opcodes) > MaxScriptSize {
str := fmt.Sprintf("adding opcodes would exceed the maximum "+
"allowed canonical script length of %d", maxScriptSize)
"allowed canonical script length of %d", MaxScriptSize)
b.err = ErrScriptNotCanonical(str)
return b
}
@ -198,10 +198,10 @@ func (b *ScriptBuilder) AddData(data []byte) *ScriptBuilder {
// 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 {
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)
dataSize, MaxScriptSize)
b.err = ErrScriptNotCanonical(str)
return b
}
@ -212,7 +212,7 @@ func (b *ScriptBuilder) AddData(data []byte) *ScriptBuilder {
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)
dataLen, MaxScriptElementSize)
b.err = ErrScriptNotCanonical(str)
return b
}
@ -230,10 +230,10 @@ func (b *ScriptBuilder) AddInt64(val int64) *ScriptBuilder {
// 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 {
if len(b.script)+1 > MaxScriptSize {
str := fmt.Sprintf("adding an integer would exceed the "+
"maximum allow canonical script length of %d",
maxScriptSize)
MaxScriptSize)
b.err = ErrScriptNotCanonical(str)
return b
}

View File

@ -291,7 +291,7 @@ func TestExceedMaxScriptSize(t *testing.T) {
// Start off by constructing a max size script.
builder := NewScriptBuilder()
builder.Reset().AddFullData(make([]byte, maxScriptSize-3))
builder.Reset().AddFullData(make([]byte, MaxScriptSize-3))
origScript, err := builder.Script()
if err != nil {
t.Fatalf("Unexpected error for max size script: %v", err)
@ -311,7 +311,7 @@ func TestExceedMaxScriptSize(t *testing.T) {
// Ensure adding an opcode that would exceed the maximum size of the
// script does not add the data.
builder.Reset().AddFullData(make([]byte, maxScriptSize-3))
builder.Reset().AddFullData(make([]byte, MaxScriptSize-3))
script, err = builder.AddOp(OP_0).Script()
if _, ok := err.(ErrScriptNotCanonical); !ok || err == nil {
t.Fatalf("ScriptBuilder.AddOp unexpected modified script - "+
@ -324,7 +324,7 @@ func TestExceedMaxScriptSize(t *testing.T) {
// Ensure adding an integer that would exceed the maximum size of the
// script does not add the data.
builder.Reset().AddFullData(make([]byte, maxScriptSize-3))
builder.Reset().AddFullData(make([]byte, MaxScriptSize-3))
script, err = builder.AddInt64(0).Script()
if _, ok := err.(ErrScriptNotCanonical); !ok || err == nil {
t.Fatalf("ScriptBuilder.AddInt64 unexpected modified script - "+
@ -345,7 +345,7 @@ func TestErroredScript(t *testing.T) {
// space left to add each data type without an error and force an
// initial error condition.
builder := NewScriptBuilder()
builder.Reset().AddFullData(make([]byte, maxScriptSize-8))
builder.Reset().AddFullData(make([]byte, MaxScriptSize-8))
origScript, err := builder.Script()
if err != nil {
t.Fatalf("ScriptBuilder.AddFullData unexpected error: %v", err)

View File

@ -1,9 +1,13 @@
// Copyright (c) 2015 The btcsuite developers
// Copyright (c) 2015-2017 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 (
"fmt"
)
const (
maxInt32 = 1<<31 - 1
minInt32 = -1 << 31
@ -61,7 +65,9 @@ func checkMinimalDataEncoding(v []byte) error {
// is +-255, which encode to 0xff00 and 0xff80 respectively.
// (big-endian).
if len(v) == 1 || v[len(v)-2]&0x80 == 0 {
return ErrStackMinimalData
str := fmt.Sprintf("numeric value encoded as %x is "+
"not minimally encoded", v)
return scriptError(ErrMinimalData, str)
}
}
@ -180,7 +186,10 @@ func makeScriptNum(v []byte, requireMinimal bool, scriptNumLen int) (scriptNum,
// Interpreting data requires that it is not larger than
// the the passed scriptNumLen value.
if len(v) > scriptNumLen {
return 0, ErrStackNumberTooBig
str := fmt.Sprintf("numeric value encoded as %x is %d bytes "+
"which exceeds the max allowed of %d", v, len(v),
scriptNumLen)
return 0, scriptError(ErrNumberTooBig, str)
}
// Enforce minimal encoded if requested.

View File

@ -1,4 +1,4 @@
// Copyright (c) 2015 The btcsuite developers
// Copyright (c) 2015-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -91,6 +91,11 @@ func TestScriptNumBytes(t *testing.T) {
func TestMakeScriptNum(t *testing.T) {
t.Parallel()
// Errors used in the tests below defined here for convenience and to
// keep the horizontal test size shorter.
errNumTooBig := scriptError(ErrNumberTooBig, "")
errMinimalData := scriptError(ErrMinimalData, "")
tests := []struct {
serialized []byte
num scriptNum
@ -99,7 +104,7 @@ func TestMakeScriptNum(t *testing.T) {
err error
}{
// Minimal encoding must reject negative 0.
{hexToBytes("80"), 0, defaultScriptNumLen, true, ErrStackMinimalData},
{hexToBytes("80"), 0, defaultScriptNumLen, true, errMinimalData},
// Minimally encoded valid values with minimal encoding flag.
// Should not error and return expected integral number.
@ -140,35 +145,35 @@ func TestMakeScriptNum(t *testing.T) {
// Minimally encoded values that are out of range for data that
// is interpreted as script numbers with the minimal encoding
// flag set. Should error and return 0.
{hexToBytes("0000008000"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("0000008080"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("0000009000"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("0000009080"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffff00"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffff80"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("0000000001"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("0000000081"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffff00"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffff80"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffffff00"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffffff80"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffffff7f"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffffffff"), 0, defaultScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("0000008000"), 0, defaultScriptNumLen, true, errNumTooBig},
{hexToBytes("0000008080"), 0, defaultScriptNumLen, true, errNumTooBig},
{hexToBytes("0000009000"), 0, defaultScriptNumLen, true, errNumTooBig},
{hexToBytes("0000009080"), 0, defaultScriptNumLen, true, errNumTooBig},
{hexToBytes("ffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig},
{hexToBytes("ffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig},
{hexToBytes("0000000001"), 0, defaultScriptNumLen, true, errNumTooBig},
{hexToBytes("0000000081"), 0, defaultScriptNumLen, true, errNumTooBig},
{hexToBytes("ffffffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig},
{hexToBytes("ffffffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig},
{hexToBytes("ffffffffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig},
{hexToBytes("ffffffffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig},
{hexToBytes("ffffffffffffff7f"), 0, defaultScriptNumLen, true, errNumTooBig},
{hexToBytes("ffffffffffffffff"), 0, defaultScriptNumLen, true, errNumTooBig},
// Non-minimally encoded, but otherwise valid values with
// minimal encoding flag. Should error and return 0.
{hexToBytes("00"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 0
{hexToBytes("0100"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 1
{hexToBytes("7f00"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 127
{hexToBytes("800000"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 128
{hexToBytes("810000"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 129
{hexToBytes("000100"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 256
{hexToBytes("ff7f00"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 32767
{hexToBytes("00800000"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 32768
{hexToBytes("ffff0000"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 65535
{hexToBytes("00000800"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 524288
{hexToBytes("00007000"), 0, defaultScriptNumLen, true, ErrStackMinimalData}, // 7340032
{hexToBytes("0009000100"), 0, 5, true, ErrStackMinimalData}, // 16779520
{hexToBytes("00"), 0, defaultScriptNumLen, true, errMinimalData}, // 0
{hexToBytes("0100"), 0, defaultScriptNumLen, true, errMinimalData}, // 1
{hexToBytes("7f00"), 0, defaultScriptNumLen, true, errMinimalData}, // 127
{hexToBytes("800000"), 0, defaultScriptNumLen, true, errMinimalData}, // 128
{hexToBytes("810000"), 0, defaultScriptNumLen, true, errMinimalData}, // 129
{hexToBytes("000100"), 0, defaultScriptNumLen, true, errMinimalData}, // 256
{hexToBytes("ff7f00"), 0, defaultScriptNumLen, true, errMinimalData}, // 32767
{hexToBytes("00800000"), 0, defaultScriptNumLen, true, errMinimalData}, // 32768
{hexToBytes("ffff0000"), 0, defaultScriptNumLen, true, errMinimalData}, // 65535
{hexToBytes("00000800"), 0, defaultScriptNumLen, true, errMinimalData}, // 524288
{hexToBytes("00007000"), 0, defaultScriptNumLen, true, errMinimalData}, // 7340032
{hexToBytes("0009000100"), 0, 5, true, errMinimalData}, // 16779520
// Non-minimally encoded, but otherwise valid values without
// minimal encoding flag. Should not error and return expected
@ -188,18 +193,18 @@ func TestMakeScriptNum(t *testing.T) {
}
for _, test := range tests {
// Ensure the error code is of the expected type and the error
// code matches the value specified in the test instance.
gotNum, err := makeScriptNum(test.serialized, test.minimalEncoding,
test.numLen)
if err != test.err {
t.Errorf("makeScriptNum: did not received expected "+
"error for %x - got %v, want %v",
test.serialized, err, test.err)
if e := tstCheckScriptError(err, test.err); e != nil {
t.Errorf("makeScriptNum(%#x): %v", test.serialized, e)
continue
}
if gotNum != test.num {
t.Errorf("makeScriptNum: did not get expected number "+
"for %x - got %d, want %d", test.serialized,
t.Errorf("makeScriptNum(%#x): did not get expected "+
"number - got %d, want %d", test.serialized,
gotNum, test.num)
continue
}

View File

@ -1,10 +1,13 @@
// Copyright (c) 2013-2015 The btcsuite developers
// Copyright (c) 2013-2017 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/hex"
import (
"encoding/hex"
"fmt"
)
// asBool gets the boolean value of the byte array.
func asBool(t []byte) bool {
@ -103,7 +106,9 @@ func (s *stack) PopBool() (bool, error) {
func (s *stack) PeekByteArray(idx int32) ([]byte, error) {
sz := int32(len(s.stk))
if idx < 0 || idx >= sz {
return nil, ErrStackUnderflow
str := fmt.Sprintf("index %d is invalid for stack size %d", idx,
sz)
return nil, scriptError(ErrInvalidStackOperation, str)
}
return s.stk[sz-idx-1], nil
@ -141,7 +146,9 @@ func (s *stack) PeekBool(idx int32) (bool, error) {
func (s *stack) nipN(idx int32) ([]byte, error) {
sz := int32(len(s.stk))
if idx < 0 || idx > sz-1 {
return nil, ErrStackUnderflow
str := fmt.Sprintf("index %d is invalid for stack size %d", idx,
sz)
return nil, scriptError(ErrInvalidStackOperation, str)
}
so := s.stk[sz-idx-1]
@ -197,7 +204,8 @@ func (s *stack) Tuck() error {
// DropN(2): [... x1 x2] -> [...]
func (s *stack) DropN(n int32) error {
if n < 1 {
return ErrStackInvalidArgs
str := fmt.Sprintf("attempt to drop %d items from stack", n)
return scriptError(ErrInvalidStackOperation, str)
}
for ; n > 0; n-- {
@ -216,7 +224,8 @@ func (s *stack) DropN(n int32) error {
// DupN(2): [... x1 x2] -> [... x1 x2 x1 x2]
func (s *stack) DupN(n int32) error {
if n < 1 {
return ErrStackInvalidArgs
str := fmt.Sprintf("attempt to dup %d stack items", n)
return scriptError(ErrInvalidStackOperation, str)
}
// Iteratively duplicate the value n-1 down the stack n times.
@ -238,7 +247,8 @@ func (s *stack) DupN(n int32) error {
// RotN(2): [... x1 x2 x3 x4 x5 x6] -> [... x3 x4 x5 x6 x1 x2]
func (s *stack) RotN(n int32) error {
if n < 1 {
return ErrStackInvalidArgs
str := fmt.Sprintf("attempt to rotate %d stack items", n)
return scriptError(ErrInvalidStackOperation, str)
}
// Nip the 3n-1th item from the stack to the top n times to rotate
@ -262,7 +272,8 @@ func (s *stack) RotN(n int32) error {
// SwapN(2): [... x1 x2 x3 x4] -> [... x3 x4 x1 x2]
func (s *stack) SwapN(n int32) error {
if n < 1 {
return ErrStackInvalidArgs
str := fmt.Sprintf("attempt to swap %d stack items", n)
return scriptError(ErrInvalidStackOperation, str)
}
entry := 2*n - 1
@ -285,7 +296,9 @@ func (s *stack) SwapN(n int32) error {
// OverN(2): [... x1 x2 x3 x4] -> [... x1 x2 x3 x4 x1 x2]
func (s *stack) OverN(n int32) error {
if n < 1 {
return ErrStackInvalidArgs
str := fmt.Sprintf("attempt to perform over on %d stack items",
n)
return scriptError(ErrInvalidStackOperation, str)
}
// Copy 2n-1th entry to top of the stack.

View File

@ -1,4 +1,4 @@
// Copyright (c) 2013-2015 The btcsuite developers
// Copyright (c) 2013-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -8,19 +8,52 @@ import (
"bytes"
"errors"
"fmt"
"reflect"
"testing"
)
// tstCheckScriptError ensures the type of the two passed errors are of the
// same type (either both nil or both of type Error) and their error codes
// match when not nil.
func tstCheckScriptError(gotErr, wantErr error) error {
// Ensure the error code is of the expected type and the error
// code matches the value specified in the test instance.
if reflect.TypeOf(gotErr) != reflect.TypeOf(wantErr) {
return fmt.Errorf("wrong error - got %T (%[1]v), want %T",
gotErr, wantErr)
}
if gotErr == nil {
return nil
}
// Ensure the want error type is a script error.
werr, ok := wantErr.(Error)
if !ok {
return fmt.Errorf("unexpected test error type %T", wantErr)
}
// Ensure the error codes match. It's safe to use a raw type assert
// here since the code above already proved they are the same type and
// the want error is a script error.
gotErrorCode := gotErr.(Error).ErrorCode
if gotErrorCode != werr.ErrorCode {
return fmt.Errorf("mismatched error code - got %v (%v), want %v",
gotErrorCode, gotErr, werr.ErrorCode)
}
return nil
}
// TestStack tests that all of the stack operations work as expected.
func TestStack(t *testing.T) {
t.Parallel()
tests := []struct {
name string
before [][]byte
operation func(*stack) error
expectedReturn error
after [][]byte
name string
before [][]byte
operation func(*stack) error
err error
after [][]byte
}{
{
"noop",
@ -38,7 +71,7 @@ func TestStack(t *testing.T) {
_, err := s.PeekByteArray(5)
return err
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -48,7 +81,7 @@ func TestStack(t *testing.T) {
_, err := s.PeekInt(5)
return err
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -58,7 +91,7 @@ func TestStack(t *testing.T) {
_, err := s.PeekBool(5)
return err
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -104,7 +137,7 @@ func TestStack(t *testing.T) {
}
return nil
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -148,7 +181,7 @@ func TestStack(t *testing.T) {
_, err := s.PopBool()
return err
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -370,7 +403,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.DupN(0)
},
ErrStackInvalidArgs,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -379,7 +412,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.DupN(-1)
},
ErrStackInvalidArgs,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -388,7 +421,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.DupN(2)
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -519,7 +552,7 @@ func TestStack(t *testing.T) {
// bite off more than we can chew
return s.NipN(3)
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
[][]byte{{2}, {3}},
},
{
@ -537,7 +570,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.Tuck()
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -546,7 +579,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.Tuck()
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -591,7 +624,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.DropN(5)
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -600,7 +633,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.DropN(0)
},
ErrStackInvalidArgs,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -627,7 +660,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.RotN(1)
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -636,7 +669,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.RotN(0)
},
ErrStackInvalidArgs,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -663,7 +696,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.SwapN(1)
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -672,7 +705,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.SwapN(0)
},
ErrStackInvalidArgs,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -699,7 +732,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.OverN(1)
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -708,7 +741,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.OverN(0)
},
ErrStackInvalidArgs,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -735,7 +768,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.PickN(1)
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -762,7 +795,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.RollN(1)
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -865,32 +898,39 @@ func TestStack(t *testing.T) {
_, err := s.PopInt()
return err
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
}
for _, test := range tests {
// Setup the initial stack state and perform the test operation.
s := stack{}
for i := range test.before {
s.PushByteArray(test.before[i])
}
err := test.operation(&s)
if err != test.expectedReturn {
t.Errorf("%s: operation return not what expected: %v "+
"vs %v", test.name, err, test.expectedReturn)
// Ensure the error code is of the expected type and the error
// code matches the value specified in the test instance.
if e := tstCheckScriptError(err, test.err); e != nil {
t.Errorf("%s: %v", test.name, e)
continue
}
if err != nil {
continue
}
// Ensure the resulting stack is the expected length.
if int32(len(test.after)) != s.Depth() {
t.Errorf("%s: stack depth doesn't match expected: %v "+
"vs %v", test.name, len(test.after),
s.Depth())
continue
}
// Ensure all items of the resulting stack are the expected
// values.
for i := range test.after {
val, err := s.PeekByteArray(s.Depth() - int32(i) - 1)
if err != nil {

View File

@ -1,10 +1,12 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2013-2017 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 (
"fmt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"
)
@ -243,9 +245,10 @@ func CalcScriptInfo(sigScript, pkScript []byte, bip16 bool) (*ScriptInfo, error)
si := new(ScriptInfo)
si.PkScriptClass = typeOfScript(pkPops)
// Can't have a pkScript that doesn't just push data.
// Can't have a signature script that doesn't just push data.
if !isPushOnly(sigPops) {
return nil, ErrStackNonPushOnly
return nil, scriptError(ErrNotPushOnly,
"signature script is not push only")
}
si.ExpectedInputs = expectedInputs(pkPops, si.PkScriptClass)
@ -294,7 +297,8 @@ func CalcMultiSigStats(script []byte) (int, int, error) {
// items must be on the stack per:
// OP_1 PUBKEY OP_1 OP_CHECKMULTISIG
if len(pops) < 4 {
return 0, 0, ErrStackUnderflow
str := fmt.Sprintf("script %x is not a multisig script", script)
return 0, 0, scriptError(ErrNotMultisigScript, str)
}
numSigs := asSmallInt(pops[0].opcode)
@ -328,34 +332,44 @@ func payToPubKeyScript(serializedPubKey []byte) ([]byte, error) {
// PayToAddrScript creates a new script to pay a transaction output to a the
// specified address.
func PayToAddrScript(addr btcutil.Address) ([]byte, error) {
const nilAddrErrStr = "unable to generate payment script for nil address"
switch addr := addr.(type) {
case *btcutil.AddressPubKeyHash:
if addr == nil {
return nil, ErrUnsupportedAddress
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
return payToPubKeyHashScript(addr.ScriptAddress())
case *btcutil.AddressScriptHash:
if addr == nil {
return nil, ErrUnsupportedAddress
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
return payToScriptHashScript(addr.ScriptAddress())
case *btcutil.AddressPubKey:
if addr == nil {
return nil, ErrUnsupportedAddress
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
return payToPubKeyScript(addr.ScriptAddress())
}
return nil, ErrUnsupportedAddress
str := fmt.Sprintf("unable to generate payment script for unsupported "+
"address type %T", addr)
return nil, scriptError(ErrUnsupportedAddress, str)
}
// NullDataScript creates a provably-prunable script containing OP_RETURN
// followed by the passed data.
// followed by the passed data. An Error with the error code ErrTooMuchNullData
// will be returned if the length of the passed data exceeds MaxDataCarrierSize.
func NullDataScript(data []byte) ([]byte, error) {
if len(data) > MaxDataCarrierSize {
return nil, ErrStackLongScript
str := fmt.Sprintf("data size %d is larger than max "+
"allowed size %d", len(data), MaxDataCarrierSize)
return nil, scriptError(ErrTooMuchNullData, str)
}
return NewScriptBuilder().AddOp(OP_RETURN).AddData(data).Script()
@ -363,11 +377,14 @@ func NullDataScript(data []byte) ([]byte, error) {
// MultiSigScript returns a valid script for a multisignature redemption where
// nrequired of the keys in pubkeys are required to have signed the transaction
// for success. An ErrBadNumRequired will be returned if nrequired is larger
// than the number of keys provided.
// for success. An Error with the error code ErrTooManyRequiredSigs will be
// returned if nrequired is larger than the number of keys provided.
func MultiSigScript(pubkeys []*btcutil.AddressPubKey, nrequired int) ([]byte, error) {
if len(pubkeys) < nrequired {
return nil, ErrBadNumRequired
str := fmt.Sprintf("unable to generate multisig script with "+
"%d required signatures when there are only %d public "+
"keys available", nrequired, len(pubkeys))
return nil, scriptError(ErrTooManyRequiredSigs, str)
}
builder := NewScriptBuilder().AddInt64(int64(nrequired))

View File

@ -1,4 +1,4 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2013-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -392,7 +392,7 @@ func TestCalcScriptInfo(t *testing.T) {
pkScript: "HASH160 DATA_20 0xfe441065b6532231de2fac56" +
"3152205ec4f59c",
bip16: true,
scriptInfoErr: ErrStackShortScript,
scriptInfoErr: scriptError(ErrMalformedPush, ""),
},
{
name: "sigScript doesn't parse",
@ -402,7 +402,7 @@ func TestCalcScriptInfo(t *testing.T) {
pkScript: "HASH160 DATA_20 0xfe441065b6532231de2fac56" +
"3152205ec4f59c74 EQUAL",
bip16: true,
scriptInfoErr: ErrStackShortScript,
scriptInfoErr: scriptError(ErrMalformedPush, ""),
},
{
// Invented scripts, the hashes do not match
@ -461,25 +461,19 @@ func TestCalcScriptInfo(t *testing.T) {
for _, test := range tests {
sigScript := mustParseShortForm(test.sigScript)
pkScript := mustParseShortForm(test.pkScript)
si, err := CalcScriptInfo(sigScript, pkScript,
test.bip16)
si, err := CalcScriptInfo(sigScript, pkScript, test.bip16)
if e := tstCheckScriptError(err, test.scriptInfoErr); e != nil {
t.Errorf("scriptinfo test %q: %v", test.name, e)
continue
}
if err != nil {
if err != test.scriptInfoErr {
t.Errorf("scriptinfo test \"%s\": got \"%v\""+
"expected \"%v\"", test.name, err,
test.scriptInfoErr)
}
continue
}
if test.scriptInfoErr != nil {
t.Errorf("%s: succeeded when expecting \"%v\"",
test.name, test.scriptInfoErr)
continue
}
if *si != test.scriptInfo {
t.Errorf("%s: scriptinfo doesn't match expected. "+
"got: \"%v\" expected \"%v\"", test.name,
*si, test.scriptInfo)
"got: %q expected %q", test.name, *si,
test.scriptInfo)
continue
}
}
@ -521,8 +515,7 @@ func TestPayToAddrScript(t *testing.T) {
p2pkhMain, err := btcutil.NewAddressPubKeyHash(hexToBytes("e34cce70c86"+
"373273efcc54ce7d2a491bb4a0e84"), &chaincfg.MainNetParams)
if err != nil {
t.Errorf("Unable to create public key hash address: %v", err)
return
t.Fatalf("Unable to create public key hash address: %v", err)
}
// Taken from transaction:
@ -530,8 +523,7 @@ func TestPayToAddrScript(t *testing.T) {
p2shMain, _ := btcutil.NewAddressScriptHashFromHash(hexToBytes("e8c300"+
"c87986efa84c37c0519929019ef86eb5b4"), &chaincfg.MainNetParams)
if err != nil {
t.Errorf("Unable to create script hash address: %v", err)
return
t.Fatalf("Unable to create script hash address: %v", err)
}
// mainnet p2pk 13CG6SJ3yHUXo4Cr2RY4THLLJrNFuG3gUg
@ -539,17 +531,15 @@ func TestPayToAddrScript(t *testing.T) {
"74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4"),
&chaincfg.MainNetParams)
if err != nil {
t.Errorf("Unable to create pubkey address (compressed): %v",
t.Fatalf("Unable to create pubkey address (compressed): %v",
err)
return
}
p2pkCompressed2Main, err := btcutil.NewAddressPubKey(hexToBytes("03b0b"+
"d634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e65"),
&chaincfg.MainNetParams)
if err != nil {
t.Errorf("Unable to create pubkey address (compressed 2): %v",
t.Fatalf("Unable to create pubkey address (compressed 2): %v",
err)
return
}
p2pkUncompressedMain, err := btcutil.NewAddressPubKey(hexToBytes("0411"+
@ -557,11 +547,14 @@ func TestPayToAddrScript(t *testing.T) {
"cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b4"+
"12a3"), &chaincfg.MainNetParams)
if err != nil {
t.Errorf("Unable to create pubkey address (uncompressed): %v",
t.Fatalf("Unable to create pubkey address (uncompressed): %v",
err)
return
}
// Errors used in the tests below defined here for convenience and to
// keep the horizontal test size shorter.
errUnsupportedAddress := scriptError(ErrUnsupportedAddress, "")
tests := []struct {
in btcutil.Address
expected string
@ -606,18 +599,18 @@ func TestPayToAddrScript(t *testing.T) {
},
// Supported address types with nil pointers.
{(*btcutil.AddressPubKeyHash)(nil), "", ErrUnsupportedAddress},
{(*btcutil.AddressScriptHash)(nil), "", ErrUnsupportedAddress},
{(*btcutil.AddressPubKey)(nil), "", ErrUnsupportedAddress},
{(*btcutil.AddressPubKeyHash)(nil), "", errUnsupportedAddress},
{(*btcutil.AddressScriptHash)(nil), "", errUnsupportedAddress},
{(*btcutil.AddressPubKey)(nil), "", errUnsupportedAddress},
// Unsupported address type.
{&bogusAddress{}, "", ErrUnsupportedAddress},
{&bogusAddress{}, "", errUnsupportedAddress},
}
t.Logf("Running %d tests", len(tests))
for i, test := range tests {
pkScript, err := PayToAddrScript(test.in)
if err != test.err {
if e := tstCheckScriptError(err, test.err); e != nil {
t.Errorf("PayToAddrScript #%d unexpected error - "+
"got %v, want %v", i, err, test.err)
continue
@ -642,17 +635,15 @@ func TestMultiSigScript(t *testing.T) {
"74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4"),
&chaincfg.MainNetParams)
if err != nil {
t.Errorf("Unable to create pubkey address (compressed): %v",
t.Fatalf("Unable to create pubkey address (compressed): %v",
err)
return
}
p2pkCompressed2Main, err := btcutil.NewAddressPubKey(hexToBytes("03b0b"+
"d634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e65"),
&chaincfg.MainNetParams)
if err != nil {
t.Errorf("Unable to create pubkey address (compressed 2): %v",
t.Fatalf("Unable to create pubkey address (compressed 2): %v",
err)
return
}
p2pkUncompressedMain, err := btcutil.NewAddressPubKey(hexToBytes("0411"+
@ -660,9 +651,8 @@ func TestMultiSigScript(t *testing.T) {
"cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b4"+
"12a3"), &chaincfg.MainNetParams)
if err != nil {
t.Errorf("Unable to create pubkey address (uncompressed): %v",
t.Fatalf("Unable to create pubkey address (uncompressed): %v",
err)
return
}
tests := []struct {
@ -702,7 +692,7 @@ func TestMultiSigScript(t *testing.T) {
},
3,
"",
ErrBadNumRequired,
scriptError(ErrTooManyRequiredSigs, ""),
},
{
[]*btcutil.AddressPubKey{
@ -721,16 +711,15 @@ func TestMultiSigScript(t *testing.T) {
},
2,
"",
ErrBadNumRequired,
scriptError(ErrTooManyRequiredSigs, ""),
},
}
t.Logf("Running %d tests", len(tests))
for i, test := range tests {
script, err := MultiSigScript(test.keys, test.nrequired)
if err != test.err {
t.Errorf("MultiSigScript #%d unexpected error - "+
"got %v, want %v", i, err, test.err)
if e := tstCheckScriptError(err, test.err); e != nil {
t.Errorf("MultiSigScript #%d: %v", i, e)
continue
}
@ -757,14 +746,14 @@ func TestCalcMultiSigStats(t *testing.T) {
name: "short script",
script: "0x046708afdb0fe5548271967f1a67130b7105cd6a828" +
"e03909a67962e0ea1f61d",
err: ErrStackShortScript,
err: scriptError(ErrMalformedPush, ""),
},
{
name: "stack underflow",
script: "RETURN DATA_41 0x046708afdb0fe5548271967f1a" +
"67130b7105cd6a828e03909a67962e0ea1f61deb649f6" +
"bc3f4cef308",
err: ErrStackUnderflow,
err: scriptError(ErrNotMultisigScript, ""),
},
{
name: "multisig script",
@ -780,10 +769,11 @@ func TestCalcMultiSigStats(t *testing.T) {
for i, test := range tests {
script := mustParseShortForm(test.script)
if _, _, err := CalcMultiSigStats(script); err != test.err {
t.Errorf("CalcMultiSigStats #%d (%s) unexpected "+
"error\ngot: %v\nwant: %v", i, test.name, err,
test.err)
_, _, err := CalcMultiSigStats(script)
if e := tstCheckScriptError(err, test.err); e != nil {
t.Errorf("CalcMultiSigStats #%d (%s): %v", i, test.name,
e)
continue
}
}
}
@ -968,7 +958,7 @@ func TestScriptClass(t *testing.T) {
if class != test.class {
t.Errorf("%s: expected %s got %s (script %x)", test.name,
test.class, class, script)
return
continue
}
}
}
@ -1082,17 +1072,18 @@ func TestNullDataScript(t *testing.T) {
"728292a2b2c2d2e2f303132333435363738393a3b3c3" +
"d3e3f404142434445464748494a4b4c4d4e4f50"),
expected: nil,
err: ErrStackLongScript,
err: scriptError(ErrTooMuchNullData, ""),
class: NonStandardTy,
},
}
for i, test := range tests {
script, err := NullDataScript(test.data)
if err != test.err {
t.Errorf("NullDataScript: #%d (%s) unexpected error: "+
"got %v, want %v", i, test.name, err, test.err)
if e := tstCheckScriptError(err, test.err); e != nil {
t.Errorf("NullDataScript: #%d (%s): %v", i, test.name,
e)
continue
}
// Check that the expected result was returned.