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 2018-06-29 11:58:31 -05:00
parent dd0bfb639c
commit d8306ee602
No known key found for this signature in database
GPG Key ID: B8904D9D9C93D1F2
21 changed files with 1575 additions and 847 deletions

View File

@ -28,7 +28,7 @@ const (
func ExtractCoinbaseNullData(pkScript []byte) ([]byte, error) {
pops, err := parseScript(pkScript)
if err != nil {
return nil, fmt.Errorf("script parse failure")
return nil, err
}
// The nulldata in the coinbase must be a single OP_RETURN followed by a
@ -54,5 +54,7 @@ func ExtractCoinbaseNullData(pkScript []byte) ([]byte, error) {
return pops[1].data, nil
}
return nil, fmt.Errorf("not a properly-formed nulldata script")
str := fmt.Sprintf("script %x is not well-formed coinbase nulldata",
pkScript)
return nil, scriptError(ErrMalformedCoinbaseNullData, str)
}

View File

@ -48,8 +48,6 @@
["'' 2147483647 2147483646", "SUBSTR '' EQUAL", "P2SH", "SUBSTR of an empty string produces an empty byte push regardless of out of bounds indices <=4 bytes"],
["'abcd' 3 3 1SUB", "SUBSTR 'c' EQUAL", "P2SH", "SUBSTR start index must work with the result of math operations"],
["'abcd' 4 1 SUB 2", "SUBSTR 'c' EQUAL", "P2SH", "SUBSTR end index must work with the result of math operations"],
["'abc' 2 0", "IF LEFT ELSE 1 ENDIF", "P2SH"],
["'abc' 2 0", "IF RIGHT ELSE 1 ENDIF", "P2SH"],
["Left substring related test coverage"],
["'abcd' 0", "LEFT NOT", "P2SH", "LEFT index of zero must produce empty byte push which is equivalent to FALSE"],

View File

@ -1,5 +1,5 @@
// Copyright (c) 2013-2015 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -29,8 +29,11 @@ what conditions must be met in order to spend decreds.
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,5 +1,5 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2017 The Decred developers
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -65,12 +65,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 = 1024
MaxStackSize = 1024
// maxScriptSize is the maximum allowed length of a raw script.
maxScriptSize = 16384
// MaxScriptSize is the maximum allowed length of a raw script.
MaxScriptSize = 16384
// DefaultScriptVersion is the default scripting language version
// representing extended Decred script.
@ -122,23 +122,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
@ -173,13 +181,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
}
@ -209,7 +219,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
@ -226,13 +238,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()
@ -247,7 +264,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
}
@ -276,8 +294,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.
@ -285,7 +306,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.
@ -384,7 +406,8 @@ func (vm *Engine) subScript() []parsedOpcode {
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
}
@ -400,7 +423,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
@ -469,25 +492,29 @@ func (vm *Engine) checkSignatureEncoding(sig []byte) error {
// The signature must adhere to the minimum and maximum allowed length.
sigLen := len(sig)
if sigLen < minSigLen {
return fmt.Errorf("malformed signature: too short: %d < %d", sigLen,
str := fmt.Sprintf("malformed signature: too short: %d < %d", sigLen,
minSigLen)
return scriptError(ErrSigTooShort, str)
}
if sigLen > maxSigLen {
return fmt.Errorf("malformed signature: too long: %d > %d", sigLen,
str := fmt.Sprintf("malformed signature: too long: %d > %d", sigLen,
maxSigLen)
return scriptError(ErrSigTooLong, str)
}
// The signature must start with the ASN.1 sequence identifier.
if sig[sequenceOffset] != asn1SequenceID {
return fmt.Errorf("malformed signature: format has wrong type: %#x",
str := fmt.Sprintf("malformed signature: format has wrong type: %#x",
sig[sequenceOffset])
return scriptError(ErrSigInvalidSeqID, str)
}
// The signature must indicate the correct amount of data for all elements
// related to R and S.
if int(sig[dataLenOffset]) != sigLen-2 {
return fmt.Errorf("malformed signature: bad length: %d != %d",
str := fmt.Sprintf("malformed signature: bad length: %d != %d",
sig[dataLenOffset], sigLen-2)
return scriptError(ErrSigInvalidDataLen, str)
}
// Calculate the offsets of the elements related to S and ensure S is inside
@ -504,62 +531,76 @@ func (vm *Engine) checkSignatureEncoding(sig []byte) error {
rLen := int(sig[rLenOffset])
sTypeOffset := rOffset + rLen
sLenOffset := sTypeOffset + 1
sOffset := sLenOffset + 1
if sOffset > sigLen {
return fmt.Errorf("malformed signature: S out of bounds")
if sTypeOffset >= sigLen {
str := "malformed signature: S type indicator missing"
return scriptError(ErrSigMissingSTypeID, str)
}
if sLenOffset >= sigLen {
str := "malformed signature: S length missing"
return scriptError(ErrSigMissingSLen, str)
}
// The lengths of R and S must match the overall length of the signature.
//
// sLen specifies the length of the big-endian encoded number which
// represents the S value of the signature.
sOffset := sLenOffset + 1
sLen := int(sig[sLenOffset])
if sOffset+sLen != sigLen {
return fmt.Errorf("malformed signature: invalid S length")
str := "malformed signature: invalid S length"
return scriptError(ErrSigInvalidSLen, str)
}
// R elements must be ASN.1 integers.
if sig[rTypeOffset] != asn1IntegerID {
return fmt.Errorf("malformed signature: R integer marker: %#x != %#x",
str := fmt.Sprintf("malformed signature: R integer marker: %#x != %#x",
sig[rTypeOffset], asn1IntegerID)
return scriptError(ErrSigInvalidRIntID, str)
}
// Zero-length integers are not allowed for R.
if rLen == 0 {
return fmt.Errorf("malformed signature: R length is zero")
str := "malformed signature: R length is zero"
return scriptError(ErrSigZeroRLen, str)
}
// R must not be negative.
if sig[rOffset]&0x80 != 0 {
return fmt.Errorf("malformed signature: R value is negative")
str := "malformed signature: R is negative"
return scriptError(ErrSigNegativeR, str)
}
// Null bytes at the start of R are not allowed, unless R would otherwise be
// interpreted as a negative number.
if rLen > 1 && sig[rOffset] == 0x00 && sig[rOffset+1]&0x80 == 0 {
return fmt.Errorf("malformed signature: invalid R value")
str := "malformed signature: R value has too much padding"
return scriptError(ErrSigTooMuchRPadding, str)
}
// S elements must be ASN.1 integers.
if sig[sTypeOffset] != asn1IntegerID {
return fmt.Errorf("malformed signature: S integer marker: %#x != %#x",
str := fmt.Sprintf("malformed signature: S integer marker: %#x != %#x",
sig[sTypeOffset], asn1IntegerID)
return scriptError(ErrSigInvalidSIntID, str)
}
// Zero-length integers are not allowed for S.
if sLen == 0 {
return fmt.Errorf("malformed signature: S length is zero")
str := "malformed signature: S length is zero"
return scriptError(ErrSigZeroSLen, str)
}
// S must not be negative.
if sig[sOffset]&0x80 != 0 {
return fmt.Errorf("malformed signature: S value is negative")
str := "malformed signature: S is negative"
return scriptError(ErrSigNegativeS, str)
}
// Null bytes at the start of S are not allowed, unless S would otherwise be
// interpreted as a negative number.
if sLen > 1 && sig[sOffset] == 0x00 && sig[sOffset+1]&0x80 == 0 {
return fmt.Errorf("malformed signature: invalid S value")
str := "malformed signature: S value has too much padding"
return scriptError(ErrSigTooMuchSPadding, str)
}
// Verify the S value is <= half the order of the curve. This check is done
@ -567,7 +608,8 @@ func (vm *Engine) checkSignatureEncoding(sig []byte) error {
// instead which is a shorter encoding by 1 byte.
sValue := new(big.Int).SetBytes(sig[sOffset : sOffset+sLen])
if sValue.Cmp(halfOrder) > 0 {
return ErrStackInvalidLowSSignature
return scriptError(ErrSigHighS, "signature is not canonical due to "+
"unnecessarily high S value")
}
return nil
@ -621,15 +663,24 @@ func (vm *Engine) SetAltStack(data [][]byte) {
// NewEngine returns a new script engine for the provided public key script,
// transaction, and input index. The flags modify the behavior of the script
// engine according to the description provided by each flag.
func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int,
flags ScriptFlags, scriptVersion uint16, sigCache *SigCache) (*Engine, error) {
func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags, scriptVersion uint16, 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.
//
@ -640,13 +691,15 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int,
// it should be.
vm := Engine{version: scriptVersion, 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")
}
// Subscripts for pay to script hash outputs are not allowed
@ -666,8 +719,10 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int,
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)
@ -686,7 +741,8 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int,
if vm.hasFlag(ScriptBip16) && isAnyKindOfScriptHash(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
// Copyright (c) 2015-2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -123,29 +123,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)
@ -194,7 +189,7 @@ func TestInvalidFlagCombinations(t *testing.T) {
for i, test := range tests {
_, err := NewEngine(pkScript, tx, 0, test, 0, nil)
if err != ErrInvalidFlags {
if !IsErrorCode(err, ErrInvalidFlags) {
t.Fatalf("TestInvalidFlagCombinations #%d unexpected "+
"error: %v", i, err)
}

View File

@ -1,191 +1,476 @@
// Copyright (c) 2013-2015 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2018 The Decred 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"
)
// Engine execution errors.
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 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")
)
// Engine script errors.
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
// ErrInvalidSigHashSingleIndex is returned when an attempt is
// made to sign an input with the SigHashSingle hash type and an
// index that is greater than or equal to the number of outputs.
ErrInvalidSigHashSingleIndex
// ErrUnsupportedAddress is returned when a concrete type that
// implements a dcrutil.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
// ErrSighashSingleIdx
ErrSighashSingleIdx = errors.New("invalid SIGHASH_SINGLE script index")
// ErrTooManyRequiredSigs is returned from MultiSigScript when the
// specified number of required signatures is larger than the number of
// provided public keys.
ErrTooManyRequiredSigs
// ErrSubstrIndexNegative indicates that the substring index was negative
// and thus invalid.
ErrSubstrIdxNegative = errors.New("negative number given for substring " +
"index")
// ErrMalformedCoinbaseNullData is returned when the nulldata output
// of a coinbase transaction that is used to ensure the coinbase has a
// unique hash is not properly formed.
ErrMalformedCoinbaseNullData
// ErrSubstrIdxOutOfBounds indicates that the substring index was too large
// and thus invalid.
ErrSubstrIdxOutOfBounds = errors.New("out of bounds number given for " +
"substring index")
// ErrTooMuchNullData is returned from NullDataScript when the length of
// the provided data exceeds MaxDataCarrierSize.
ErrTooMuchNullData
// ErrNegativeRotation indicates that too low of a rotation depth was given
// for a uint32 bit rotation.
ErrNegativeRotation = errors.New("rotation depth negative")
// ------------------------------------------
// Failures related to final execution state.
// ------------------------------------------
// ErrRotationOverflow indicates that too high of a rotation depth was given
// for a uint32 bit rotation.
ErrRotationOverflow = errors.New("rotation depth out of bounds")
// ErrEarlyReturn is returned when OP_RETURN is executed in the script.
ErrEarlyReturn
// ErrNegativeRotation indicates that too low of a shift depth was given
// for a uint32 bit shift.
ErrNegativeShift = errors.New("shift depth negative")
// ErrEmptyStack is returned when the script evaluated without error,
// but terminated with an empty top stack element.
ErrEmptyStack
// ErrShiftOverflow indicates that too high of a shift depth was given
// for a uint32 bit shift.
ErrShiftOverflow = errors.New("shift depth out of bounds")
// ErrEvalFalse is returned when the script evaluated without error but
// terminated with a false top stack element.
ErrEvalFalse
// ErrDivideByZero indicates that a user attempted to divide by zero.
ErrDivideByZero = errors.New("division by zero")
// ErrScriptUnfinished is returned when CheckErrorCondition is called on
// a script that has not finished executing.
ErrScriptUnfinished
// ErrP2SHStakeOpCodes indicates a P2SH script contained stake op codes.
ErrP2SHStakeOpCodes = errors.New("stake opcodes were found in a p2sh script")
// 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
// ErrNumOutOfRange 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.
ErrNumOutOfRange
// --------------------------------------------
// 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.
// --------------------------------------------
// ErrP2SHStakeOpCodes is returned when one or more stake opcodes are
// found in the redeem script of a pay-to-script-hash script.
ErrP2SHStakeOpCodes
// 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
// ErrNegativeSubstrIdx is returned when an OP_SUBSTR, OP_LEFT, or
// OP_RIGHT opcode encounters a negative index.
ErrNegativeSubstrIdx
// ErrOverflowSubstrIdx is returned when an OP_SUBSTR, OP_LEFT, or
// OP_RIGHT opcode encounters an index that is larger than the max
// allowed index that can operate on the string or the start index
// is greater than the end index for OP_SUBSTR.
ErrOverflowSubstrIdx
// ErrNegativeRotation is returned when an OP_ROTL or OP_ROTR attempts
// to perform a rotation with a negative rotation count.
ErrNegativeRotation
// ErrOverflowRotation is returned when an OP_ROTL or OP_ROTR opcode
// encounters a rotation count that is larger than the maximum allowed
// value for a uint32 bit rotation.
ErrOverflowRotation
// ErrDivideByZero is returned when an OP_DIV of OP_MOD attempts to
// divide by zero.
ErrDivideByZero
// ErrNegativeRotation is returned when an OP_LSHIFT or OP_RSHIFT opcode
// attempts to perform a shift with a negative count.
ErrNegativeShift
// ErrOverflowShift is returned when an OP_LSHIFT or OP_RSHIFT opcode
// encounters a shift count that is larger than the maximum allowed value
// for a shift.
ErrOverflowShift
// ---------------------------------
// 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
// ErrSigTooShort is returned when the ScriptVerifyDERSignatures flag is
// set and a signature that should be a canonically-encoded DER
// signature is too short.
ErrSigTooShort
// ErrSigTooLong is returned when the ScriptVerifyDERSignatures flag is
// set and a signature that should be a canonically-encoded DER
// signature is too long.
ErrSigTooLong
// ErrSigInvalidSeqID is returned when the ScriptVerifyDERSignatures
// flag is set and a signature that should be a canonically-encoded DER
// signature does not have the expected ASN.1 sequence ID.
ErrSigInvalidSeqID
// ErrSigInvalidDataLen is returned when the ScriptVerifyDERSignatures
// flag is set and a signature that should be a canonically-encoded DER
// signature does not specify the correct number of remaining bytes for
// the R and S portions.
ErrSigInvalidDataLen
// ErrSigMissingSTypeID is returned when the ScriptVerifyDERSignatures
// flag is set and a signature that should be a canonically-encoded DER
// signature does not provide the ASN.1 type ID for S.
ErrSigMissingSTypeID
// ErrSigMissingSLen is returned when the ScriptVerifyDERSignatures flag
// is set and a signature that should be a canonically-encoded DER
// signature does not provide the length of S.
ErrSigMissingSLen
// ErrSigInvalidSLen is returned when the ScriptVerifyDERSignatures flag
// is set and a signature that should be a canonically-encoded DER
// signature does not specify the correct number of bytes for the S
// portion.
ErrSigInvalidSLen
// ErrSigInvalidRIntID is returned when the ScriptVerifyDERSignatures
// flag is set and a signature that should be a canonically-encoded DER
// signature does not have the expected ASN.1 integer ID for R.
ErrSigInvalidRIntID
// ErrSigZeroRLen is returned when the ScriptVerifyDERSignatures flag is
// set and a signature that should be a canonically-encoded DER
// signature has an R length of zero.
ErrSigZeroRLen
// ErrSigNegativeR is returned when the ScriptVerifyDERSignatures flag
// is set and a signature that should be a canonically-encoded DER
// signature has a negative value for R.
ErrSigNegativeR
// ErrSigTooMuchRPadding is returned when the ScriptVerifyDERSignatures flag
// is set and a signature that should be a canonically-encoded DER
// signature has too much padding for R.
ErrSigTooMuchRPadding
// ErrSigInvalidSIntID is returned when the ScriptVerifyDERSignatures
// flag is set and a signature that should be a canonically-encoded DER
// signature does not have the expected ASN.1 integer ID for S.
ErrSigInvalidSIntID
// ErrSigZeroSLen is returned when the ScriptVerifyDERSignatures flag is
// set and a signature that should be a canonically-encoded DER
// signature has an S length of zero.
ErrSigZeroSLen
// ErrSigNegativeS is returned when the ScriptVerifyDERSignatures flag
// is set and a signature that should be a canonically-encoded DER
// signature has a negative value for S.
ErrSigNegativeS
// ErrSigTooMuchSPadding is returned when the ScriptVerifyDERSignatures flag
// is set and a signature that should be a canonically-encoded DER
// signature has too much padding for S.
ErrSigTooMuchSPadding
// ErrSigHighS is returned when the ScriptVerifyDERSignatures flag is
// set and a signature that should be a canonically-encoded DER signature
// has an S value that is higher than the curve 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
// 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",
ErrInvalidSigHashSingleIndex: "ErrInvalidSigHashSingleIndex",
ErrUnsupportedAddress: "ErrUnsupportedAddress",
ErrNotMultisigScript: "ErrNotMultisigScript",
ErrTooManyRequiredSigs: "ErrTooManyRequiredSigs",
ErrMalformedCoinbaseNullData: "ErrMalformedCoinbaseNullData",
ErrTooMuchNullData: "ErrTooMuchNullData",
ErrEarlyReturn: "ErrEarlyReturn",
ErrEmptyStack: "ErrEmptyStack",
ErrEvalFalse: "ErrEvalFalse",
ErrScriptUnfinished: "ErrScriptUnfinished",
ErrInvalidProgramCounter: "ErrInvalidProgramCounter",
ErrScriptTooBig: "ErrScriptTooBig",
ErrElementTooBig: "ErrElementTooBig",
ErrTooManyOperations: "ErrTooManyOperations",
ErrStackOverflow: "ErrStackOverflow",
ErrInvalidPubKeyCount: "ErrInvalidPubKeyCount",
ErrInvalidSignatureCount: "ErrInvalidSignatureCount",
ErrNumOutOfRange: "ErrNumOutOfRange",
ErrVerify: "ErrVerify",
ErrEqualVerify: "ErrEqualVerify",
ErrNumEqualVerify: "ErrNumEqualVerify",
ErrCheckSigVerify: "ErrCheckSigVerify",
ErrCheckMultiSigVerify: "ErrCheckMultiSigVerify",
ErrP2SHStakeOpCodes: "ErrP2SHStakeOpCodes",
ErrDisabledOpcode: "ErrDisabledOpcode",
ErrReservedOpcode: "ErrReservedOpcode",
ErrMalformedPush: "ErrMalformedPush",
ErrInvalidStackOperation: "ErrInvalidStackOperation",
ErrUnbalancedConditional: "ErrUnbalancedConditional",
ErrNegativeSubstrIdx: "ErrNegativeSubstrIdx",
ErrSigTooMuchSPadding: "ErrSigTooMuchSPadding",
ErrOverflowSubstrIdx: "ErrOverflowSubstrIdx",
ErrNegativeRotation: "ErrNegativeRotation",
ErrOverflowRotation: "ErrOverflowRotation",
ErrDivideByZero: "ErrDivideByZero",
ErrNegativeShift: "ErrNegativeShift",
ErrOverflowShift: "ErrOverflowShift",
ErrMinimalData: "ErrMinimalData",
ErrInvalidSigHashType: "ErrInvalidSigHashType",
ErrSigTooShort: "ErrSigTooShort",
ErrSigTooLong: "ErrSigTooLong",
ErrSigInvalidSeqID: "ErrSigInvalidSeqID",
ErrSigInvalidDataLen: "ErrSigInvalidDataLen",
ErrSigMissingSTypeID: "ErrSigMissingSTypeID",
ErrSigMissingSLen: "ErrSigMissingSLen",
ErrSigInvalidSLen: "ErrSigInvalidSLen",
ErrSigInvalidRIntID: "ErrSigInvalidRIntID",
ErrSigZeroRLen: "ErrSigZeroRLen",
ErrSigNegativeR: "ErrSigNegativeR",
ErrSigTooMuchRPadding: "ErrSigTooMuchRPadding",
ErrSigInvalidSIntID: "ErrSigInvalidSIntID",
ErrSigZeroSLen: "ErrSigZeroSLen",
ErrSigNegativeS: "ErrSigNegativeS",
ErrSigHighS: "ErrSigHighS",
ErrNotPushOnly: "ErrNotPushOnly",
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
}
// IsDERSigError returns whether or not the provided error is a script error
// with one of the error codes which are caused due to encountering a signature
// that is not a canonically-encoded DER signature.
//
// Note that the strict DER signature checks are only performed if scripts
// are executed with the ScriptVerifyDERSignatures flag.
func IsDERSigError(err error) bool {
serr, ok := err.(Error)
if !ok {
return false
}
switch serr.ErrorCode {
case ErrSigTooShort, ErrSigTooLong, ErrSigInvalidSeqID,
ErrSigInvalidDataLen, ErrSigMissingSTypeID, ErrSigMissingSLen,
ErrSigInvalidSLen, ErrSigInvalidRIntID, ErrSigZeroRLen, ErrSigNegativeR,
ErrSigTooMuchRPadding, ErrSigInvalidSIntID, ErrSigZeroSLen,
ErrSigNegativeS, ErrSigTooMuchSPadding, ErrSigHighS:
return true
}
return false
}

165
txscript/error_test.go Normal file
View File

@ -0,0 +1,165 @@
// 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"},
{ErrInvalidSigHashSingleIndex, "ErrInvalidSigHashSingleIndex"},
{ErrUnsupportedAddress, "ErrUnsupportedAddress"},
{ErrTooManyRequiredSigs, "ErrTooManyRequiredSigs"},
{ErrMalformedCoinbaseNullData, "ErrMalformedCoinbaseNullData"},
{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"},
{ErrNumOutOfRange, "ErrNumOutOfRange"},
{ErrVerify, "ErrVerify"},
{ErrEqualVerify, "ErrEqualVerify"},
{ErrNumEqualVerify, "ErrNumEqualVerify"},
{ErrCheckSigVerify, "ErrCheckSigVerify"},
{ErrCheckMultiSigVerify, "ErrCheckMultiSigVerify"},
{ErrP2SHStakeOpCodes, "ErrP2SHStakeOpCodes"},
{ErrDisabledOpcode, "ErrDisabledOpcode"},
{ErrReservedOpcode, "ErrReservedOpcode"},
{ErrMalformedPush, "ErrMalformedPush"},
{ErrInvalidStackOperation, "ErrInvalidStackOperation"},
{ErrUnbalancedConditional, "ErrUnbalancedConditional"},
{ErrNegativeSubstrIdx, "ErrNegativeSubstrIdx"},
{ErrOverflowSubstrIdx, "ErrOverflowSubstrIdx"},
{ErrNegativeRotation, "ErrNegativeRotation"},
{ErrOverflowRotation, "ErrOverflowRotation"},
{ErrDivideByZero, "ErrDivideByZero"},
{ErrNegativeShift, "ErrNegativeShift"},
{ErrOverflowShift, "ErrOverflowShift"},
{ErrMinimalData, "ErrMinimalData"},
{ErrInvalidSigHashType, "ErrInvalidSigHashType"},
{ErrSigTooShort, "ErrSigTooShort"},
{ErrSigTooLong, "ErrSigTooLong"},
{ErrSigInvalidSeqID, "ErrSigInvalidSeqID"},
{ErrSigInvalidDataLen, "ErrSigInvalidDataLen"},
{ErrSigMissingSTypeID, "ErrSigMissingSTypeID"},
{ErrSigMissingSLen, "ErrSigMissingSLen"},
{ErrSigInvalidSLen, "ErrSigInvalidSLen"},
{ErrSigInvalidRIntID, "ErrSigInvalidRIntID"},
{ErrSigZeroRLen, "ErrSigZeroRLen"},
{ErrSigNegativeR, "ErrSigNegativeR"},
{ErrSigTooMuchRPadding, "ErrSigTooMuchRPadding"},
{ErrSigInvalidSIntID, "ErrSigInvalidSIntID"},
{ErrSigZeroSLen, "ErrSigZeroSLen"},
{ErrSigNegativeS, "ErrSigNegativeS"},
{ErrSigTooMuchSPadding, "ErrSigTooMuchSPadding"},
{ErrSigHighS, "ErrSigHighS"},
{ErrNotPushOnly, "ErrNotPushOnly"},
{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
}
}
}
// TestIsDERSigError ensures IsDERSigError returns true for all error codes
// that can be returned as a result of non-canonically-encoded DER signatures.
func TestIsDERSigError(t *testing.T) {
tests := []struct {
code ErrorCode
want bool
}{
{ErrSigTooShort, true},
{ErrSigTooLong, true},
{ErrSigInvalidSeqID, true},
{ErrSigInvalidDataLen, true},
{ErrSigMissingSTypeID, true},
{ErrSigMissingSLen, true},
{ErrSigInvalidSLen, true},
{ErrSigInvalidRIntID, true},
{ErrSigZeroRLen, true},
{ErrSigNegativeR, true},
{ErrSigTooMuchRPadding, true},
{ErrSigInvalidSIntID, true},
{ErrSigZeroSLen, true},
{ErrSigNegativeS, true},
{ErrSigTooMuchSPadding, true},
{ErrSigHighS, true},
{ErrEvalFalse, false},
{ErrInvalidIndex, false},
}
for _, test := range tests {
result := IsDERSigError(Error{ErrorCode: test.code})
if result != test.want {
t.Errorf("IsDERSigError(%v): unexpected result -- got: %v want: %v",
test.code, result, test.want)
continue
}
}
}

View File

@ -10,7 +10,6 @@ import (
"crypto/sha1"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"hash"
@ -683,28 +682,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
@ -765,7 +781,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
}
@ -794,7 +814,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
@ -811,19 +834,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
@ -881,8 +910,9 @@ func opcodeNop(op *parsedOpcode, vm *Engine) error {
OP_UNKNOWN248:
if vm.hasFlag(ScriptDiscourageUpgradableNops) {
return fmt.Errorf("%s reserved for upgrades",
str := fmt.Sprintf("%s reserved for upgrades",
op.opcode.name)
return scriptError(ErrDiscourageUpgradableNOPs, str)
}
}
return nil
@ -960,7 +990,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
@ -984,31 +1016,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.
@ -1017,14 +1061,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
@ -1040,8 +1086,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
}
@ -1068,7 +1114,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
@ -1096,7 +1143,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
@ -1112,8 +1160,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
}
@ -1140,7 +1188,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)
@ -1155,8 +1204,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
@ -1165,8 +1215,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.
@ -1393,7 +1444,9 @@ func opcodeCat(op *parsedOpcode, vm *Engine) error {
// Ensure the result does not overflow the maximum stack item size.
combinedLen := len(a) + len(b)
if combinedLen > MaxScriptElementSize {
return ErrStackElementTooBig
str := fmt.Sprintf("element size %d exceeds max allowed size %d",
combinedLen, MaxScriptElementSize)
return scriptError(ErrElementTooBig, str)
}
// Push the concatenated result back to the stack.
@ -1446,17 +1499,28 @@ func opcodeSubstr(op *parsedOpcode, vm *Engine) error {
// which means it is possible to provide a start index just after the
// final character in the string, so long as the end index is the same
// value, and an empty byte push will be produced.
if startIdx < 0 || endIdx < 0 {
return ErrSubstrIdxNegative
if startIdx < 0 {
str := fmt.Sprintf("start index %d is negative", startIdx)
return scriptError(ErrNegativeSubstrIdx, str)
}
if endIdx < 0 {
str := fmt.Sprintf("end index %d is negative", endIdx)
return scriptError(ErrNegativeSubstrIdx, str)
}
if startIdx > aLen {
return ErrSubstrIdxOutOfBounds
str := fmt.Sprintf("start index %d exceeds length %d", startIdx,
aLen)
return scriptError(ErrOverflowSubstrIdx, str)
}
if endIdx > aLen {
return ErrSubstrIdxOutOfBounds
str := fmt.Sprintf("end index %d exceeds length %d", endIdx,
aLen)
return scriptError(ErrOverflowSubstrIdx, str)
}
if startIdx > endIdx {
return ErrSubstrIdxOutOfBounds
str := fmt.Sprintf("start index %d is after end index %d",
startIdx, endIdx)
return scriptError(ErrOverflowSubstrIdx, str)
}
// Push the requested substring back to the stack. Note that identical
@ -1499,10 +1563,12 @@ func opcodeLeft(op *parsedOpcode, vm *Engine) error {
// Ensure the provided index is in bounds.
if endIdx < 0 {
return ErrSubstrIdxNegative
str := fmt.Sprintf("index %d is negative", endIdx)
return scriptError(ErrNegativeSubstrIdx, str)
}
if endIdx > aLen {
return ErrSubstrIdxOutOfBounds
str := fmt.Sprintf("index %d exceeds length %d", endIdx, aLen)
return scriptError(ErrOverflowSubstrIdx, str)
}
// Push the requested substring back to the stack. Note that a zero
@ -1542,7 +1608,6 @@ func opcodeRight(op *parsedOpcode, vm *Engine) error {
vm.dstack.PushByteArray(nil)
return nil
}
// Ensure the provided index is in bounds.
//
// Take special note that the start index check is > as opposed to >=,
@ -1550,10 +1615,12 @@ func opcodeRight(op *parsedOpcode, vm *Engine) error {
// final character in the string, and an empty byte push will be
// produced.
if startIdx < 0 {
return ErrSubstrIdxNegative
str := fmt.Sprintf("index %d is negative", startIdx)
return scriptError(ErrNegativeSubstrIdx, str)
}
if startIdx > aLen {
return ErrSubstrIdxOutOfBounds
str := fmt.Sprintf("index %d exceeds length %d", startIdx, aLen)
return scriptError(ErrOverflowSubstrIdx, str)
}
// Push the requested substring back to the stack. Note that a start
@ -1579,6 +1646,7 @@ func opcodeSize(op *parsedOpcode, vm *Engine) error {
// opcodeInvert pops the top item off the stack, interprets it as an int32,
// inverts the bits, and then pushes it back to the stack.
//
// Stack transformation: [... x1] -> [... ~x1]
func opcodeInvert(op *parsedOpcode, vm *Engine) error {
v0, err := vm.dstack.PopInt(mathOpCodeMaxScriptNumLen)
@ -1592,6 +1660,7 @@ func opcodeInvert(op *parsedOpcode, vm *Engine) error {
// opcodeAnd pops the top two items off the stack, interprets them as int32s,
// bitwise ANDs the value, and then pushes the result back to the stack.
//
// Stack transformation: [... x1 x2] -> [... x1 & x2]
func opcodeAnd(op *parsedOpcode, vm *Engine) error {
v0, err := vm.dstack.PopInt(mathOpCodeMaxScriptNumLen)
@ -1610,6 +1679,7 @@ func opcodeAnd(op *parsedOpcode, vm *Engine) error {
// opcodeOr pops the top two items off the stack, interprets them as int32s,
// bitwise ORs the value, and then pushes the result back to the stack.
//
// Stack transformation: [... x1 x2] -> [... x1 | x2]
func opcodeOr(op *parsedOpcode, vm *Engine) error {
v0, err := vm.dstack.PopInt(mathOpCodeMaxScriptNumLen)
@ -1628,6 +1698,7 @@ func opcodeOr(op *parsedOpcode, vm *Engine) error {
// opcodeXor pops the top two items off the stack, interprets them as int32s,
// bitwise XORs the value, and then pushes the result back to the stack.
//
// Stack transformation: [... x1 x2] -> [... x1 ^ x2]
func opcodeXor(op *parsedOpcode, vm *Engine) error {
v0, err := vm.dstack.PopInt(mathOpCodeMaxScriptNumLen)
@ -1672,7 +1743,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
}
@ -1717,10 +1788,12 @@ func opcodeRotr(op *parsedOpcode, vm *Engine) error {
// Don't allow invalid or pointless rotations.
if count < 0 {
return ErrNegativeRotation
str := fmt.Sprintf("rotation count %d is negative", count)
return scriptError(ErrNegativeRotation, str)
}
if count > 31 {
return ErrRotationOverflow
str := fmt.Sprintf("rotation count %d > 31", count)
return scriptError(ErrOverflowRotation, str)
}
vm.dstack.PushInt(scriptNum(rotateRight(value, count)))
@ -1767,10 +1840,12 @@ func opcodeRotl(op *parsedOpcode, vm *Engine) error {
// Don't allow invalid or pointless rotations.
if count < 0 {
return ErrNegativeRotation
str := fmt.Sprintf("rotation count %d is negative", count)
return scriptError(ErrNegativeRotation, str)
}
if count > 31 {
return ErrRotationOverflow
str := fmt.Sprintf("rotation count %d > 31", count)
return scriptError(ErrOverflowRotation, str)
}
vm.dstack.PushInt(scriptNum(rotateLeft(value, count)))
@ -1963,7 +2038,7 @@ func opcodeDiv(op *parsedOpcode, vm *Engine) error {
dividend := v1.Int32()
if divisor == 0 {
return ErrDivideByZero
return scriptError(ErrDivideByZero, "division by zero")
}
vm.dstack.PushInt(scriptNum(dividend / divisor))
@ -1994,7 +2069,7 @@ func opcodeMod(op *parsedOpcode, vm *Engine) error {
dividend := v1.Int32()
if divisor == 0 {
return ErrDivideByZero
return scriptError(ErrDivideByZero, "division by zero")
}
vm.dstack.PushInt(scriptNum(dividend % divisor))
@ -2035,10 +2110,12 @@ func opcodeLShift(op *parsedOpcode, vm *Engine) error {
// Don't allow invalid or pointless shifts.
if count < 0 {
return ErrNegativeShift
str := fmt.Sprintf("shift count %d is negative", count)
return scriptError(ErrNegativeShift, str)
}
if count > 32 {
return ErrShiftOverflow
str := fmt.Sprintf("shift count %d > 32", count)
return scriptError(ErrOverflowShift, str)
}
vm.dstack.PushInt(scriptNum(value << uint(count)))
@ -2079,10 +2156,12 @@ func opcodeRShift(op *parsedOpcode, vm *Engine) error {
// Don't allow invalid or pointless shifts.
if count < 0 {
return ErrNegativeShift
str := fmt.Sprintf("shift count %d is negative", count)
return scriptError(ErrNegativeShift, str)
}
if count > 32 {
return ErrShiftOverflow
str := fmt.Sprintf("shift count %d > 32", count)
return scriptError(ErrOverflowShift, str)
}
vm.dstack.PushInt(scriptNum(value >> uint(count)))
@ -2179,7 +2258,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
}
@ -2446,7 +2525,8 @@ func opcodeSha256(op *parsedOpcode, vm *Engine) error {
// SHA256 opcode is not set.
if !vm.hasFlag(ScriptVerifySHA256) {
if vm.hasFlag(ScriptDiscourageUpgradableNops) {
return errors.New("OP_UNKNOWN192 reserved for upgrades")
return scriptError(ErrDiscourageUpgradableNOPs,
"OP_UNKNOWN192 reserved for upgrades")
}
return nil
}
@ -2600,11 +2680,11 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
// The opcodeCheckSig function is invoked followed by opcodeVerify. See the
// documentation for each of those opcodes for more details.
//
// Stack transformation: signature pubkey] -> [... bool] -> [...]
// Stack transformation: [... signature pubkey] -> [... bool] -> [...]
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
}
@ -2638,12 +2718,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)
@ -2661,12 +2750,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)
@ -2802,11 +2894,11 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
// See the documentation for each of those opcodes for more details.
//
// Stack transformation:
// [... dummy [sig ...] numsigs [pubkey ...] numpubkeys] -> [... bool] -> [...]
// [... [sig ...] numsigs [pubkey ...] numpubkeys] -> [... bool] -> [...]
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,5 +1,5 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2017 The Decred developers
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -40,10 +40,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

@ -712,12 +712,13 @@ testloop:
// parseSigHashExpectedResult parses the provided expected result string into
// allowed error codes. An error is returned if the expected result string is
// not supported.
func parseSigHashExpectedResult(expected string) (error, error) {
func parseSigHashExpectedResult(expected string) (*ErrorCode, error) {
switch expected {
case "OK":
return nil, nil
case "SIGHASH_SINGLE_IDX":
return ErrSighashSingleIdx, nil
code := ErrInvalidSigHashSingleIndex
return &code, nil
}
return nil, fmt.Errorf("unrecognized expected result in test data: %v",
@ -827,14 +828,21 @@ func TestCalcSignatureHashReference(t *testing.T) {
// Calculate the signature hash and verify expected result.
hash, err := calcSignatureHash(parsedScript, hashType, &tx,
int(inputIdxF64), nil)
if err != expectedErr {
t.Errorf("Test #%d: unexpected error: want %v, got %v", i,
expectedErr, err)
if (err == nil) != (expectedErr == nil) ||
expectedErr != nil && !IsErrorCode(err, *expectedErr) {
if serr, ok := err.(Error); ok {
t.Errorf("Test #%d: want error code %v, got %v", i, expectedErr,
serr.ErrorCode)
continue
}
t.Errorf("Test #%d: want error code %v, got err: %v (%T)", i,
expectedErr, err, err)
continue
}
if !bytes.Equal(hash, expectedHash) {
t.Errorf("Test #%d: signature hash mismatch - got %x, "+
"want %x", i, hash, expectedHash)
t.Errorf("Test #%d: signature hash mismatch - got %x, want %x", i,
hash, expectedHash)
continue
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -98,8 +98,7 @@ func isAnyKindOfScriptHash(pops []parsedOpcode) bool {
// HasP2SHScriptSigStakeOpCodes returns an error is the p2sh script has either
// stake opcodes or if the pkscript cannot be retrieved.
func HasP2SHScriptSigStakeOpCodes(version uint16, scriptSig,
scriptPubKey []byte) error {
func HasP2SHScriptSigStakeOpCodes(version uint16, scriptSig, scriptPubKey []byte) error {
class := GetScriptClass(version, scriptPubKey)
if IsStakeOutput(scriptPubKey) {
class, _ = GetStakeOutSubclass(scriptPubKey)
@ -110,11 +109,11 @@ func HasP2SHScriptSigStakeOpCodes(version uint16, scriptSig,
// any stake tagging OP codes.
pData, err := PushedData(scriptSig)
if err != nil {
return fmt.Errorf("error retrieving pushed data "+
"from script: %v", err)
return err
}
if len(pData) == 0 {
return fmt.Errorf("script has no pushed data")
str := "script has no pushed data"
return scriptError(ErrNotPushOnly, str)
}
// The pay-to-hash-script is the final data push of the
@ -123,11 +122,11 @@ func HasP2SHScriptSigStakeOpCodes(version uint16, scriptSig,
hasStakeOpCodes, err := ContainsStakeOpCodes(shScript)
if err != nil {
return fmt.Errorf("unexpected error checking pkscript "+
"from p2sh transaction: %v", err.Error())
return err
}
if hasStakeOpCodes {
return ErrP2SHStakeOpCodes
str := "stake opcodes were found in a p2sh script"
return scriptError(ErrP2SHStakeOpCodes, str)
}
}
@ -137,8 +136,7 @@ func HasP2SHScriptSigStakeOpCodes(version uint16, scriptSig,
// parseScriptTemplate is the same as parseScript but allows the passing of the
// template list for testing purposes. When there are parse errors, it returns
// the list of parsed opcodes up to the point of failure along with the error.
func parseScriptTemplate(script []byte, opcodes *[256]opcode) ([]parsedOpcode,
error) {
func parseScriptTemplate(script []byte, opcodes *[256]opcode) ([]parsedOpcode, error) {
retScript := make([]parsedOpcode, 0, len(script))
for i := 0; i < len(script); {
instr := script[i]
@ -156,7 +154,11 @@ func parseScriptTemplate(script []byte, opcodes *[256]opcode) ([]parsedOpcode,
// 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.
@ -169,7 +171,11 @@ func parseScriptTemplate(script []byte, opcodes *[256]opcode) ([]parsedOpcode,
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.
@ -185,9 +191,10 @@ func parseScriptTemplate(script []byte, opcodes *[256]opcode) ([]parsedOpcode,
(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.
@ -196,7 +203,11 @@ func parseScriptTemplate(script []byte, opcodes *[256]opcode) ([]parsedOpcode,
// 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

@ -1,5 +1,5 @@
// Copyright (c) 2013-2015 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Copyright (c) 2015-2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -63,9 +63,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
}
@ -84,9 +84,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
}
@ -199,10 +199,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
}
@ -213,7 +213,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
}
@ -231,10 +231,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

@ -292,7 +292,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)
@ -312,7 +312,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 - "+
@ -325,7 +325,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 - "+
@ -346,7 +346,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,10 +1,14 @@
// Copyright (c) 2015 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Copyright (c) 2015-2017 The btcsuite developers
// Copyright (c) 2015-2018 The Decred 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
@ -66,7 +70,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)
}
}
@ -174,8 +180,8 @@ func (n scriptNum) Int32() int32 {
// requireMinimal enabled.
//
// The scriptNumLen is the maximum number of bytes the encoded value can be
// before an ErrStackNumberTooBig is returned. This effectively limits the
// range of allowed values.
// before an ErrNumberTooBig is returned. This effectively limits the range of
// allowed values.
// WARNING: Great care should be taken if passing a value larger than
// defaultScriptNumLen, which could lead to addition and multiplication
// overflows.
@ -185,7 +191,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(ErrNumOutOfRange, str)
}
// Enforce minimal encoded if requested.

View File

@ -1,5 +1,5 @@
// Copyright (c) 2015 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Copyright (c) 2015-2017 The btcsuite developers
// Copyright (c) 2015-2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -92,6 +92,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.
errOutOfRange := scriptError(ErrNumOutOfRange, "")
errMinimalData := scriptError(ErrMinimalData, "")
tests := []struct {
serialized []byte
num scriptNum
@ -100,7 +105,7 @@ func TestMakeScriptNum(t *testing.T) {
err error
}{
// Minimal encoding must reject negative 0.
{hexToBytes("80"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackMinimalData},
{hexToBytes("80"), 0, mathOpCodeMaxScriptNumLen, true, errMinimalData},
// Minimally encoded valid values with minimal encoding flag.
// Should not error and return expected integral number.
@ -141,35 +146,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, mathOpCodeMaxScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("0000008080"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("0000009000"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("0000009080"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffff00"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffff80"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("0000000001"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("0000000081"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffff00"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffff80"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffffff00"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffffff80"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffffff7f"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("ffffffffffffffff"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackNumberTooBig},
{hexToBytes("0000008000"), 0, mathOpCodeMaxScriptNumLen, true, errOutOfRange},
{hexToBytes("0000008080"), 0, mathOpCodeMaxScriptNumLen, true, errOutOfRange},
{hexToBytes("0000009000"), 0, mathOpCodeMaxScriptNumLen, true, errOutOfRange},
{hexToBytes("0000009080"), 0, mathOpCodeMaxScriptNumLen, true, errOutOfRange},
{hexToBytes("ffffffff00"), 0, mathOpCodeMaxScriptNumLen, true, errOutOfRange},
{hexToBytes("ffffffff80"), 0, mathOpCodeMaxScriptNumLen, true, errOutOfRange},
{hexToBytes("0000000001"), 0, mathOpCodeMaxScriptNumLen, true, errOutOfRange},
{hexToBytes("0000000081"), 0, mathOpCodeMaxScriptNumLen, true, errOutOfRange},
{hexToBytes("ffffffffffff00"), 0, mathOpCodeMaxScriptNumLen, true, errOutOfRange},
{hexToBytes("ffffffffffff80"), 0, mathOpCodeMaxScriptNumLen, true, errOutOfRange},
{hexToBytes("ffffffffffffff00"), 0, mathOpCodeMaxScriptNumLen, true, errOutOfRange},
{hexToBytes("ffffffffffffff80"), 0, mathOpCodeMaxScriptNumLen, true, errOutOfRange},
{hexToBytes("ffffffffffffff7f"), 0, mathOpCodeMaxScriptNumLen, true, errOutOfRange},
{hexToBytes("ffffffffffffffff"), 0, mathOpCodeMaxScriptNumLen, true, errOutOfRange},
// Non-minimally encoded, but otherwise valid values with
// minimal encoding flag. Should error and return 0.
{hexToBytes("00"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackMinimalData}, // 0
{hexToBytes("0100"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackMinimalData}, // 1
{hexToBytes("7f00"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackMinimalData}, // 127
{hexToBytes("800000"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackMinimalData}, // 128
{hexToBytes("810000"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackMinimalData}, // 129
{hexToBytes("000100"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackMinimalData}, // 256
{hexToBytes("ff7f00"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackMinimalData}, // 32767
{hexToBytes("00800000"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackMinimalData}, // 32768
{hexToBytes("ffff0000"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackMinimalData}, // 65535
{hexToBytes("00000800"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackMinimalData}, // 524288
{hexToBytes("00007000"), 0, mathOpCodeMaxScriptNumLen, true, ErrStackMinimalData}, // 7340032
{hexToBytes("0009000100"), 0, 5, true, ErrStackMinimalData}, // 16779520
{hexToBytes("00"), 0, mathOpCodeMaxScriptNumLen, true, errMinimalData}, // 0
{hexToBytes("0100"), 0, mathOpCodeMaxScriptNumLen, true, errMinimalData}, // 1
{hexToBytes("7f00"), 0, mathOpCodeMaxScriptNumLen, true, errMinimalData}, // 127
{hexToBytes("800000"), 0, mathOpCodeMaxScriptNumLen, true, errMinimalData}, // 128
{hexToBytes("810000"), 0, mathOpCodeMaxScriptNumLen, true, errMinimalData}, // 129
{hexToBytes("000100"), 0, mathOpCodeMaxScriptNumLen, true, errMinimalData}, // 256
{hexToBytes("ff7f00"), 0, mathOpCodeMaxScriptNumLen, true, errMinimalData}, // 32767
{hexToBytes("00800000"), 0, mathOpCodeMaxScriptNumLen, true, errMinimalData}, // 32768
{hexToBytes("ffff0000"), 0, mathOpCodeMaxScriptNumLen, true, errMinimalData}, // 65535
{hexToBytes("00000800"), 0, mathOpCodeMaxScriptNumLen, true, errMinimalData}, // 524288
{hexToBytes("00007000"), 0, mathOpCodeMaxScriptNumLen, 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
@ -189,18 +194,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

@ -7,6 +7,7 @@ package txscript
import (
"encoding/binary"
"fmt"
"math"
"github.com/decred/dcrd/chaincfg"
@ -234,7 +235,9 @@ func calcSignatureHash(prevOutScript []parsedOpcode, hashType SigHashType, tx *w
// is improper to use SigHashSingle on input indices that don't have a
// corresponding output.
if hashType&sigHashMask == SigHashSingle && idx >= len(tx.TxOut) {
return nil, ErrSighashSingleIdx
str := fmt.Sprintf("attempt to sign single input at index %d "+
">= %d outputs", idx, len(tx.TxOut))
return nil, scriptError(ErrInvalidSigHashSingleIndex, str)
}
// Remove all instances of OP_CODESEPARATOR from the script.

View File

@ -1,11 +1,14 @@
// Copyright (c) 2013-2015 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2018 The Decred 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 {
@ -104,7 +107,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
@ -142,7 +147,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]
@ -198,7 +205,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-- {
@ -217,7 +225,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.
@ -239,7 +248,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
@ -263,7 +273,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
@ -286,7 +297,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,5 +1,5 @@
// Copyright (c) 2013-2015 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -9,19 +9,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",
@ -39,7 +72,7 @@ func TestStack(t *testing.T) {
_, err := s.PeekByteArray(5)
return err
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -49,7 +82,7 @@ func TestStack(t *testing.T) {
_, err := s.PeekInt(5)
return err
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -59,7 +92,7 @@ func TestStack(t *testing.T) {
_, err := s.PeekBool(5)
return err
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -105,7 +138,7 @@ func TestStack(t *testing.T) {
}
return nil
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -149,7 +182,7 @@ func TestStack(t *testing.T) {
_, err := s.PopBool()
return err
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -371,7 +404,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.DupN(0)
},
ErrStackInvalidArgs,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -380,7 +413,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.DupN(-1)
},
ErrStackInvalidArgs,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -389,7 +422,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.DupN(2)
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -520,7 +553,7 @@ func TestStack(t *testing.T) {
// bite off more than we can chew
return s.NipN(3)
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
[][]byte{{2}, {3}},
},
{
@ -538,7 +571,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.Tuck()
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -547,7 +580,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.Tuck()
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -592,7 +625,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.DropN(5)
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -601,7 +634,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.DropN(0)
},
ErrStackInvalidArgs,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -628,7 +661,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.RotN(1)
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -637,7 +670,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.RotN(0)
},
ErrStackInvalidArgs,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -664,7 +697,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.SwapN(1)
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -673,7 +706,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.SwapN(0)
},
ErrStackInvalidArgs,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -700,7 +733,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.OverN(1)
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -709,7 +742,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.OverN(0)
},
ErrStackInvalidArgs,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -736,7 +769,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.PickN(1)
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -763,7 +796,7 @@ func TestStack(t *testing.T) {
func(s *stack) error {
return s.RollN(1)
},
ErrStackUnderflow,
scriptError(ErrInvalidStackOperation, ""),
nil,
},
{
@ -866,32 +899,39 @@ func TestStack(t *testing.T) {
_, err := s.PopInt(mathOpCodeMaxScriptNumLen)
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,5 +1,5 @@
// Copyright (c) 2013-2015 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -19,6 +19,11 @@ const (
// MaxDataCarrierSize is the maximum number of bytes allowed in pushed
// data to be considered a nulldata transaction.
MaxDataCarrierSize = 256
// nilAddrErrStr is the common error string to use for attempts to
// generate payment scripts to nil addresses embedded within a
// dcrutil.Address interface.
nilAddrErrStr = "unable to generate payment script for nil address"
)
// ScriptClass is an enumeration for the list of standard types of script.
@ -521,9 +526,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")
}
subClass := ScriptClass(0)
@ -583,7 +589,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)
@ -699,22 +706,32 @@ func payToSchnorrPubKeyScript(serializedPubKey []byte) ([]byte, error) {
// public key hash, but tags the output with OP_SSTX. For use in constructing
// valid SStxs.
func PayToSStx(addr dcrutil.Address) ([]byte, error) {
if addr == nil {
return nil, ErrUnsupportedAddress
}
// Only pay to pubkey hash and pay to script hash are
// supported.
scriptType := PubKeyHashTy
switch addr := addr.(type) {
case *dcrutil.AddressPubKeyHash:
if addr.DSA(addr.Net()) != chainec.ECTypeSecp256k1 {
return nil, ErrUnsupportedAddress
if addr == nil {
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
if addr.DSA(addr.Net()) != chainec.ECTypeSecp256k1 {
str := "unable to generate payment script for " +
"unsupported digital signature algorithm"
return nil, scriptError(ErrUnsupportedAddress, str)
}
case *dcrutil.AddressScriptHash:
if addr == nil {
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
scriptType = ScriptHashTy
default:
return nil, ErrUnsupportedAddress
str := fmt.Sprintf("unable to generate payment script for "+
"unsupported address type %T", addr)
return nil, scriptError(ErrUnsupportedAddress, str)
}
hash := addr.ScriptAddress()
@ -732,22 +749,32 @@ func PayToSStx(addr dcrutil.Address) ([]byte, error) {
// public key hash, but tags the output with OP_SSTXCHANGE. For use in constructing
// valid SStxs.
func PayToSStxChange(addr dcrutil.Address) ([]byte, error) {
if addr == nil {
return nil, ErrUnsupportedAddress
}
// Only pay to pubkey hash and pay to script hash are
// supported.
scriptType := PubKeyHashTy
switch addr := addr.(type) {
case *dcrutil.AddressPubKeyHash:
if addr.DSA(addr.Net()) != chainec.ECTypeSecp256k1 {
return nil, ErrUnsupportedAddress
if addr == nil {
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
if addr.DSA(addr.Net()) != chainec.ECTypeSecp256k1 {
str := "unable to generate payment script for " +
"unsupported digital signature algorithm"
return nil, scriptError(ErrUnsupportedAddress, str)
}
case *dcrutil.AddressScriptHash:
if addr == nil {
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
scriptType = ScriptHashTy
default:
return nil, ErrUnsupportedAddress
str := fmt.Sprintf("unable to generate payment script for "+
"unsupported address type %T", addr)
return nil, scriptError(ErrUnsupportedAddress, str)
}
hash := addr.ScriptAddress()
@ -765,22 +792,32 @@ func PayToSStxChange(addr dcrutil.Address) ([]byte, error) {
// hash or script hash, but tags the output with OP_SSGEN. For use in constructing
// valid SSGen txs.
func PayToSSGen(addr dcrutil.Address) ([]byte, error) {
if addr == nil {
return nil, ErrUnsupportedAddress
}
// Only pay to pubkey hash and pay to script hash are
// supported.
scriptType := PubKeyHashTy
switch addr := addr.(type) {
case *dcrutil.AddressPubKeyHash:
if addr.DSA(addr.Net()) != chainec.ECTypeSecp256k1 {
return nil, ErrUnsupportedAddress
if addr == nil {
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
if addr.DSA(addr.Net()) != chainec.ECTypeSecp256k1 {
str := "unable to generate payment script for " +
"unsupported digital signature algorithm"
return nil, scriptError(ErrUnsupportedAddress, str)
}
case *dcrutil.AddressScriptHash:
if addr == nil {
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
scriptType = ScriptHashTy
default:
return nil, ErrUnsupportedAddress
str := fmt.Sprintf("unable to generate payment script for "+
"unsupported address type %T", addr)
return nil, scriptError(ErrUnsupportedAddress, str)
}
hash := addr.ScriptAddress()
@ -799,10 +836,6 @@ func PayToSSGen(addr dcrutil.Address) ([]byte, error) {
// valid SSGen txs. Unlike PayToSSGen, this function directly uses the HASH160
// pubkeyhash (instead of an address).
func PayToSSGenPKHDirect(pkh []byte) ([]byte, error) {
if pkh == nil {
return nil, ErrUnsupportedAddress
}
return NewScriptBuilder().AddOp(OP_SSGEN).AddOp(OP_DUP).
AddOp(OP_HASH160).AddData(pkh).AddOp(OP_EQUALVERIFY).
AddOp(OP_CHECKSIG).Script()
@ -813,10 +846,6 @@ func PayToSSGenPKHDirect(pkh []byte) ([]byte, error) {
// valid SSGen txs. Unlike PayToSSGen, this function directly uses the HASH160
// script hash (instead of an address).
func PayToSSGenSHDirect(sh []byte) ([]byte, error) {
if sh == nil {
return nil, ErrUnsupportedAddress
}
return NewScriptBuilder().AddOp(OP_SSGEN).AddOp(OP_HASH160).
AddData(sh).AddOp(OP_EQUAL).Script()
}
@ -825,22 +854,32 @@ func PayToSSGenSHDirect(sh []byte) ([]byte, error) {
// public key hash, but tags the output with OP_SSRTX. For use in constructing
// valid SSRtx.
func PayToSSRtx(addr dcrutil.Address) ([]byte, error) {
if addr == nil {
return nil, ErrUnsupportedAddress
}
// Only pay to pubkey hash and pay to script hash are
// supported.
scriptType := PubKeyHashTy
switch addr := addr.(type) {
case *dcrutil.AddressPubKeyHash:
if addr.DSA(addr.Net()) != chainec.ECTypeSecp256k1 {
return nil, ErrUnsupportedAddress
if addr == nil {
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
if addr.DSA(addr.Net()) != chainec.ECTypeSecp256k1 {
str := "unable to generate payment script for " +
"unsupported digital signature algorithm"
return nil, scriptError(ErrUnsupportedAddress, str)
}
case *dcrutil.AddressScriptHash:
if addr == nil {
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
scriptType = ScriptHashTy
default:
return nil, ErrUnsupportedAddress
str := fmt.Sprintf("unable to generate payment script for "+
"unsupported address type %T", addr)
return nil, scriptError(ErrUnsupportedAddress, str)
}
hash := addr.ScriptAddress()
@ -859,10 +898,6 @@ func PayToSSRtx(addr dcrutil.Address) ([]byte, error) {
// valid SSRtx. Unlike PayToSSRtx, this function directly uses the HASH160
// pubkeyhash (instead of an address).
func PayToSSRtxPKHDirect(pkh []byte) ([]byte, error) {
if pkh == nil {
return nil, ErrUnsupportedAddress
}
return NewScriptBuilder().AddOp(OP_SSRTX).AddOp(OP_DUP).
AddOp(OP_HASH160).AddData(pkh).AddOp(OP_EQUALVERIFY).
AddOp(OP_CHECKSIG).Script()
@ -873,34 +908,39 @@ func PayToSSRtxPKHDirect(pkh []byte) ([]byte, error) {
// valid SSRtx. Unlike PayToSSRtx, this function directly uses the HASH160
// script hash (instead of an address).
func PayToSSRtxSHDirect(sh []byte) ([]byte, error) {
if sh == nil {
return nil, ErrUnsupportedAddress
}
return NewScriptBuilder().AddOp(OP_SSRTX).AddOp(OP_HASH160).
AddData(sh).AddOp(OP_EQUAL).Script()
}
// GenerateSStxAddrPush generates an OP_RETURN push for SSGen payment addresses in
// an SStx.
func GenerateSStxAddrPush(addr dcrutil.Address, amount dcrutil.Amount,
limits uint16) ([]byte, error) {
if addr == nil {
return nil, ErrUnsupportedAddress
}
func GenerateSStxAddrPush(addr dcrutil.Address, amount dcrutil.Amount, limits uint16) ([]byte, error) {
// Only pay to pubkey hash and pay to script hash are
// supported.
scriptType := PubKeyHashTy
switch addr := addr.(type) {
case *dcrutil.AddressPubKeyHash:
if addr.DSA(addr.Net()) != chainec.ECTypeSecp256k1 {
return nil, ErrUnsupportedAddress
if addr == nil {
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
if addr.DSA(addr.Net()) != chainec.ECTypeSecp256k1 {
str := "unable to generate payment script for " +
"unsupported digital signature algorithm"
return nil, scriptError(ErrUnsupportedAddress, str)
}
case *dcrutil.AddressScriptHash:
if addr == nil {
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
scriptType = ScriptHashTy
default:
return nil, ErrUnsupportedAddress
str := fmt.Sprintf("unable to generate payment script for "+
"unsupported address type %T", addr)
return nil, scriptError(ErrUnsupportedAddress, str)
}
// Prefix
@ -932,8 +972,7 @@ func GenerateSStxAddrPush(addr dcrutil.Address, amount dcrutil.Amount,
// GenerateSSGenBlockRef generates an OP_RETURN push for the block header hash and
// height which the block votes on.
func GenerateSSGenBlockRef(blockHash chainhash.Hash, height uint32) ([]byte,
error) {
func GenerateSSGenBlockRef(blockHash chainhash.Hash, height uint32) ([]byte, error) {
// Prefix
dataPushes := []byte{
0x6a, // OP_RETURN
@ -970,10 +1009,15 @@ func GenerateSSGenVotes(votebits uint16) ([]byte, error) {
return voteBitsOut, nil
}
// GenerateProvablyPruneableOut creates an OP_RETURN push of arbitrary data.
// GenerateProvablyPruneableOut creates a provably-prunable script containing
// OP_RETURN 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 GenerateProvablyPruneableOut(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()
@ -985,7 +1029,8 @@ func PayToAddrScript(addr dcrutil.Address) ([]byte, error) {
switch addr := addr.(type) {
case *dcrutil.AddressPubKeyHash:
if addr == nil {
return nil, ErrUnsupportedAddress
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
switch addr.DSA(addr.Net()) {
case chainec.ECTypeSecp256k1:
@ -998,40 +1043,48 @@ func PayToAddrScript(addr dcrutil.Address) ([]byte, error) {
case *dcrutil.AddressScriptHash:
if addr == nil {
return nil, ErrUnsupportedAddress
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
return payToScriptHashScript(addr.ScriptAddress())
case *dcrutil.AddressSecpPubKey:
if addr == nil {
return nil, ErrUnsupportedAddress
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
return payToPubKeyScript(addr.ScriptAddress())
case *dcrutil.AddressEdwardsPubKey:
if addr == nil {
return nil, ErrUnsupportedAddress
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
return payToEdwardsPubKeyScript(addr.ScriptAddress())
case *dcrutil.AddressSecSchnorrPubKey:
if addr == nil {
return nil, ErrUnsupportedAddress
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
return payToSchnorrPubKeyScript(addr.ScriptAddress())
}
return nil, ErrUnsupportedAddress
str := fmt.Sprintf("unable to generate payment script for unsupported "+
"address type %T", addr)
return nil, scriptError(ErrUnsupportedAddress, str)
}
// 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.
func MultiSigScript(pubkeys []*dcrutil.AddressSecpPubKey, nrequired int) ([]byte,
error) {
// 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 []*dcrutil.AddressSecpPubKey, 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-2015 The btcsuite developers
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2018 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -372,7 +372,7 @@ func TestCalcScriptInfo(t *testing.T) {
pkScript: "HASH160 DATA_20 0xfe441065b6532231de2fac56" +
"3152205ec4f59c",
bip16: true,
scriptInfoErr: ErrStackShortScript,
scriptInfoErr: scriptError(ErrMalformedPush, ""),
},
{
name: "sigScript doesn't parse",
@ -382,7 +382,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
@ -442,23 +442,18 @@ func TestCalcScriptInfo(t *testing.T) {
sigScript := mustParseShortForm(test.sigScript)
pkScript := mustParseShortForm(test.pkScript)
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
}
}
@ -517,8 +512,7 @@ func TestPayToAddrScript(t *testing.T) {
"373273efcc54ce7d2a491bb4a0e84"), &chaincfg.MainNetParams,
chainec.ECTypeSecp256k1)
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:
@ -526,8 +520,7 @@ func TestPayToAddrScript(t *testing.T) {
p2shMain, _ := dcrutil.NewAddressScriptHashFromHash(hexToBytes("e8c30"+
"0c87986efa84c37c0519929019ef86eb5b4"), &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
@ -535,23 +528,25 @@ func TestPayToAddrScript(t *testing.T) {
"4d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4"),
&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 := dcrutil.NewAddressSecpPubKey(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 := newAddressPubKey(hexToBytes("0411db" +
"93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2" +
"e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3"))
// Errors used in the tests below defined here for convenience and to
// keep the horizontal test size shorter.
errUnsupportedAddress := scriptError(ErrUnsupportedAddress, "")
tests := []struct {
in dcrutil.Address
expected string
@ -596,18 +591,20 @@ func TestPayToAddrScript(t *testing.T) {
},
// Supported address types with nil pointers.
{(*dcrutil.AddressPubKeyHash)(nil), "", ErrUnsupportedAddress},
{(*dcrutil.AddressScriptHash)(nil), "", ErrUnsupportedAddress},
{(*dcrutil.AddressSecpPubKey)(nil), "", ErrUnsupportedAddress},
{(*dcrutil.AddressPubKeyHash)(nil), "", errUnsupportedAddress},
{(*dcrutil.AddressScriptHash)(nil), "", errUnsupportedAddress},
{(*dcrutil.AddressSecpPubKey)(nil), "", errUnsupportedAddress},
{(*dcrutil.AddressEdwardsPubKey)(nil), "", errUnsupportedAddress},
{(*dcrutil.AddressSecSchnorrPubKey)(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
@ -632,17 +629,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 := dcrutil.NewAddressSecpPubKey(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 := newAddressPubKey(hexToBytes("0411d" +
@ -687,7 +682,7 @@ func TestMultiSigScript(t *testing.T) {
},
3,
"",
ErrBadNumRequired,
scriptError(ErrTooManyRequiredSigs, ""),
},
{
// By default compressed pubkeys are used in Decred.
@ -705,16 +700,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
}
@ -741,14 +735,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",
@ -764,10 +758,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
}
}
}
@ -954,7 +949,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
}
}
}
@ -1086,17 +1081,18 @@ func TestGenerateProvablyPruneableOut(t *testing.T) {
"4a4b4c4d4e4f202122232425262728292a2b2c2d2e2f303132333435363738393" +
"a3b3c3d3e3f3f"),
expected: nil,
err: ErrStackLongScript,
err: scriptError(ErrTooMuchNullData, ""),
class: NonStandardTy,
},
}
for i, test := range tests {
script, err := GenerateProvablyPruneableOut(test.data)
if err != test.err {
t.Errorf("GenerateProvablyPruneableOut: #%d (%s) unexpected error: "+
"got %v, want %v", i, test.name, err, test.err)
if e := tstCheckScriptError(err, test.err); e != nil {
t.Errorf("GenerateProvablyPruneableOut: #%d (%s) %v: ",
i, test.name, e)
continue
}
// Check that the expected result was returned.