dcrd/txscript/script.go

444 lines
13 KiB
Go
Raw Normal View History

// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2019 The Decred developers
2013-06-12 21:35:27 +00:00
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package txscript
2013-06-12 21:35:27 +00:00
import (
"bytes"
"fmt"
"strings"
2013-06-12 21:35:27 +00:00
)
// These are the constants specified for maximums in individual scripts.
const (
MaxOpsPerScript = 255 // Max number of non-push operations.
MaxPubKeysPerMultiSig = 20 // Multisig can't have more sigs than this.
MaxScriptElementSize = 2048 // Max bytes pushable to the stack.
2013-06-12 21:35:27 +00:00
)
// isSmallInt returns whether or not the opcode is considered a small integer,
// which is an OP_0, or OP_1 through OP_16.
//
// NOTE: This function is only valid for version 0 opcodes. Since the function
// does not accept a script version, the results are undefined for other script
// versions.
func isSmallInt(op byte) bool {
return op == OP_0 || (op >= OP_1 && op <= OP_16)
}
// IsPayToScriptHash returns true if the script is in the standard
// pay-to-script-hash (P2SH) format, false otherwise.
func IsPayToScriptHash(script []byte) bool {
pops, err := parseScript(script)
if err != nil {
return false
}
return isScriptHash(pops)
}
2013-06-12 21:35:27 +00:00
// isPushOnly returns true if the script only pushes data, false otherwise.
func isPushOnly(pops []parsedOpcode) bool {
// NOTE: This function does NOT verify opcodes directly since it is
// internal and is only called with parsed opcodes for scripts that did
// not have any parse errors. Thus, consensus is properly maintained.
2013-06-12 21:35:27 +00:00
for _, pop := range pops {
// All opcodes up to OP_16 are data push instructions.
// NOTE: This does consider OP_RESERVED to be a data push
// instruction, but execution of OP_RESERVED will fail anyways
// and matches the behavior required by consensus.
if pop.opcode.value > OP_16 {
2013-06-12 21:35:27 +00:00
return false
}
}
return true
}
// IsPushOnlyScript returns whether or not the passed script only pushes data.
//
// False will be returned when the script does not parse.
func IsPushOnlyScript(script []byte) bool {
pops, err := parseScript(script)
if err != nil {
return false
}
return isPushOnly(pops)
}
// isStakeOpcode returns whether or not the opcode is one of the stake tagging
// opcodes.
func isStakeOpcode(op *opcode) bool {
return op.value >= OP_SSTX && op.value <= OP_SSTXCHANGE
}
// isScriptHash returns whether or not the passed script is a regular
// pay-to-script-hash script.
func isScriptHash(pops []parsedOpcode) bool {
return len(pops) == 3 &&
pops[0].opcode.value == OP_HASH160 &&
pops[1].opcode.value == OP_DATA_20 &&
pops[2].opcode.value == OP_EQUAL
}
// isStakeScriptHash returns whether or not the passed script is a stake
// pay-to-script-hash script.
func isStakeScriptHash(pops []parsedOpcode) bool {
return len(pops) == 4 &&
isStakeOpcode(pops[0].opcode) &&
pops[1].opcode.value == OP_HASH160 &&
pops[2].opcode.value == OP_DATA_20 &&
pops[3].opcode.value == OP_EQUAL
}
// isAnyKindOfScriptHash returns whether or not the passed script is either a
// regular pay-to-script-hash script or a stake pay-to-script-hash script.
func isAnyKindOfScriptHash(pops []parsedOpcode) bool {
return isScriptHash(pops) || isStakeScriptHash(pops)
}
// HasP2SHScriptSigStakeOpCodes returns an error is the p2sh script has either
// stake opcodes or if the pkscript cannot be retrieved.
//
// DEPRECATED. This will be removed in the next major version bump.
func HasP2SHScriptSigStakeOpCodes(version uint16, scriptSig, scriptPubKey []byte) error {
class := GetScriptClass(version, scriptPubKey)
if IsStakeOutput(scriptPubKey) {
class, _ = GetStakeOutSubclass(scriptPubKey)
}
if class == ScriptHashTy {
// Obtain the embedded pkScript from the scriptSig of the
// current transaction. Then, ensure that it does not use
// any stake tagging OP codes.
2017-08-17 19:42:04 +00:00
pData, err := PushedData(scriptSig)
if err != nil {
return err
2017-08-17 19:42:04 +00:00
}
if len(pData) == 0 {
str := "script has no pushed data"
return scriptError(ErrNotPushOnly, str)
}
2017-08-17 19:42:04 +00:00
// The pay-to-hash-script is the final data push of the
// signature script.
shScript := pData[len(pData)-1]
hasStakeOpCodes, err := ContainsStakeOpCodes(shScript)
if err != nil {
return err
}
if hasStakeOpCodes {
str := "stake opcodes were found in a p2sh script"
return scriptError(ErrP2SHStakeOpCodes, str)
}
}
return nil
}
// 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) {
retScript := make([]parsedOpcode, 0, len(script))
2013-06-12 21:35:27 +00:00
for i := 0; i < len(script); {
instr := script[i]
op := &opcodes[instr]
pop := parsedOpcode{opcode: op}
// Parse data out of instruction.
2013-06-12 21:35:27 +00:00
switch {
// No additional data. Note that some of the opcodes, notably
// OP_1NEGATE, OP_0, and OP_[1-16] represent the data
// themselves.
2013-06-12 21:35:27 +00:00
case op.length == 1:
i++
// Data pushes of specific lengths -- OP_DATA_[1-75].
2013-06-12 21:35:27 +00:00
case op.length > 1:
if len(script[i:]) < op.length {
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)
2013-06-12 21:35:27 +00:00
}
// Slice out the data.
2013-06-12 21:35:27 +00:00
pop.data = script[i+1 : i+op.length]
i += op.length
// Data pushes with parsed lengths -- OP_PUSHDATAP{1,2,4}.
2013-06-12 21:35:27 +00:00
case op.length < 0:
var l uint
off := i + 1
if len(script[off:]) < -op.length {
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.
2013-06-12 21:35:27 +00:00
switch op.length {
case -1:
l = uint(script[off])
2013-06-12 21:35:27 +00:00
case -2:
l = ((uint(script[off+1]) << 8) |
uint(script[off]))
2013-06-12 21:35:27 +00:00
case -4:
l = ((uint(script[off+3]) << 24) |
(uint(script[off+2]) << 16) |
(uint(script[off+1]) << 8) |
uint(script[off]))
2013-06-12 21:35:27 +00:00
default:
str := fmt.Sprintf("invalid opcode length %d",
op.length)
return retScript, scriptError(ErrMalformedPush,
str)
2013-06-12 21:35:27 +00:00
}
// Move offset to beginning of the data.
off += -op.length
// Disallow entries that do not fit script or were
// sign extended.
if int(l) > len(script[off:]) || int(l) < 0 {
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)
2013-06-12 21:35:27 +00:00
}
2013-06-12 21:35:27 +00:00
pop.data = script[off : off+int(l)]
i += 1 - op.length + int(l)
}
2013-06-12 21:35:27 +00:00
retScript = append(retScript, pop)
}
2013-06-12 21:35:27 +00:00
return retScript, nil
}
// parseScript preparses the script in bytes into a list of parsedOpcodes while
// applying a number of sanity checks.
func parseScript(script []byte) ([]parsedOpcode, error) {
return parseScriptTemplate(script, &opcodeArray)
}
2013-06-12 21:35:27 +00:00
// unparseScript reversed the action of parseScript and returns the
// parsedOpcodes as a list of bytes
func unparseScript(pops []parsedOpcode) ([]byte, error) {
script := make([]byte, 0, len(pops))
2013-06-12 21:35:27 +00:00
for _, pop := range pops {
b, err := pop.bytes()
if err != nil {
return nil, err
}
script = append(script, b...)
2013-06-12 21:35:27 +00:00
}
return script, nil
2013-06-12 21:35:27 +00:00
}
// DisasmString formats a disassembled script for one line printing. When the
// script fails to parse, the returned string will contain the disassembled
// script up to the point the failure occurred along with the string '[error]'
// appended. In addition, the reason the script failed to parse is returned
// if the caller wants more information about the failure.
//
// NOTE: This function is only valid for version 0 scripts. Since the function
// does not accept a script version, the results are undefined for other script
// versions.
func DisasmString(script []byte) (string, error) {
const scriptVersion = 0
var disbuf strings.Builder
tokenizer := MakeScriptTokenizer(scriptVersion, script)
if tokenizer.Next() {
disasmOpcode(&disbuf, tokenizer.op, tokenizer.Data(), true)
}
for tokenizer.Next() {
disbuf.WriteByte(' ')
disasmOpcode(&disbuf, tokenizer.op, tokenizer.Data(), true)
}
if tokenizer.Err() != nil {
if tokenizer.ByteIndex() != 0 {
disbuf.WriteByte(' ')
}
disbuf.WriteString("[error]")
}
return disbuf.String(), tokenizer.Err()
}
// canonicalPush returns true if the object is either not a push instruction
// or the push instruction contained wherein is matches the canonical form
// or using the smallest instruction to do the job. False otherwise.
func canonicalPush(pop parsedOpcode) bool {
opcode := pop.opcode.value
data := pop.data
dataLen := len(pop.data)
if opcode > OP_16 {
return true
}
if opcode < OP_PUSHDATA1 && opcode > OP_0 && (dataLen == 1 && data[0] <= 16) {
return false
}
if opcode == OP_PUSHDATA1 && dataLen < OP_PUSHDATA1 {
return false
}
if opcode == OP_PUSHDATA2 && dataLen <= 0xff {
return false
}
if opcode == OP_PUSHDATA4 && dataLen <= 0xffff {
return false
}
return true
}
// removeOpcodeByData will return the script minus any opcodes that would push
// the passed data to the stack.
2013-06-12 21:35:27 +00:00
func removeOpcodeByData(pkscript []parsedOpcode, data []byte) []parsedOpcode {
retScript := make([]parsedOpcode, 0, len(pkscript))
2013-06-12 21:35:27 +00:00
for _, pop := range pkscript {
if !canonicalPush(pop) || !bytes.Contains(pop.data, data) {
2013-06-12 21:35:27 +00:00
retScript = append(retScript, pop)
}
}
return retScript
}
// asSmallInt returns the passed opcode, which must be true according to
// isSmallInt(), as an integer.
func asSmallInt(op byte) int {
if op == OP_0 {
return 0
}
return int(op - (OP_1 - 1))
}
// getSigOpCount is the implementation function for counting the number of
// signature operations in the script provided by pops. If precise mode is
// requested then we attempt to count the number of operations for a multisig
// op. Otherwise we use the maximum.
func getSigOpCount(pops []parsedOpcode, precise bool) int {
nSigs := 0
for i, pop := range pops {
switch pop.opcode.value {
case OP_CHECKSIG:
2013-06-21 00:33:56 +00:00
fallthrough
case OP_CHECKSIGVERIFY:
fallthrough
case OP_CHECKSIGALT:
fallthrough
case OP_CHECKSIGALTVERIFY:
nSigs++
case OP_CHECKMULTISIG:
2013-06-21 00:33:56 +00:00
fallthrough
case OP_CHECKMULTISIGVERIFY:
// If we are being precise then look for familiar
// patterns for multisig, for now all we recognize is
2013-06-21 00:33:56 +00:00
// OP_1 - OP_16 to signify the number of pubkeys.
// Otherwise, we use the max of 20.
if precise && i > 0 &&
2013-06-21 00:33:56 +00:00
pops[i-1].opcode.value >= OP_1 &&
pops[i-1].opcode.value <= OP_16 {
nSigs += asSmallInt(pops[i-1].opcode.value)
} else {
nSigs += MaxPubKeysPerMultiSig
}
default:
// Not a sigop.
}
}
return nSigs
}
// GetSigOpCount provides a quick count of the number of signature operations
// in a script. a CHECKSIG operations counts for 1, and a CHECK_MULTISIG for 20.
// If the script fails to parse, then the count up to the point of failure is
// returned.
func GetSigOpCount(script []byte) int {
// Don't check error since parseScript returns the parsed-up-to-error
// list of pops.
pops, _ := parseScript(script)
return getSigOpCount(pops, false)
}
// GetPreciseSigOpCount returns the number of signature operations in
// scriptPubKey. If bip16 is true then scriptSig may be searched for the
// Pay-To-Script-Hash script in order to find the precise number of signature
// operations in the transaction. If the script fails to parse, then the count
// up to the point of failure is returned.
func GetPreciseSigOpCount(scriptSig, scriptPubKey []byte, bip16 bool) int {
// Don't check error since parseScript returns the parsed-up-to-error
// list of pops.
pops, _ := parseScript(scriptPubKey)
// Treat non P2SH transactions as normal.
if !(bip16 && isScriptHash(pops)) {
return getSigOpCount(pops, true)
}
// The public key script is a pay-to-script-hash, so parse the signature
// script to get the final item. Scripts that fail to fully parse count
// as 0 signature operations.
sigPops, err := parseScript(scriptSig)
if err != nil {
return 0
}
// The signature script must only push data to the stack for P2SH to be
// a valid pair, so the signature operation count is 0 when that is not
// the case.
if !isPushOnly(sigPops) || len(sigPops) == 0 {
return 0
}
// The P2SH script is the last item the signature script pushes to the
// stack. When the script is empty, there are no signature operations.
shScript := sigPops[len(sigPops)-1].data
if len(shScript) == 0 {
return 0
}
// Parse the P2SH script and don't check the error since parseScript
// returns the parsed-up-to-error list of pops and the consensus rules
// dictate signature operations are counted up to the first parse
// failure.
shPops, _ := parseScript(shScript)
return getSigOpCount(shPops, true)
}
// checkScriptParses returns an error if the provided script fails to parse.
func checkScriptParses(scriptVersion uint16, script []byte) error {
tokenizer := MakeScriptTokenizer(scriptVersion, script)
for tokenizer.Next() {
// Nothing to do.
}
return tokenizer.Err()
}
// IsUnspendable returns whether the passed public key script is unspendable, or
// guaranteed to fail at execution. This allows inputs to be pruned instantly
blockchain: Rework to use new db interface. This commit is the first stage of several that are planned to convert the blockchain package into a concurrent safe package that will ultimately allow support for multi-peer download and concurrent chain processing. The goal is to update btcd proper after each step so it can take advantage of the enhancements as they are developed. In addition to the aforementioned benefit, this staged approach has been chosen since it is absolutely critical to maintain consensus. Separating the changes into several stages makes it easier for reviewers to logically follow what is happening and therefore helps prevent consensus bugs. Naturally there are significant automated tests to help prevent consensus issues as well. The main focus of this stage is to convert the blockchain package to use the new database interface and implement the chain-related functionality which it no longer handles. It also aims to improve efficiency in various areas by making use of the new database and chain capabilities. The following is an overview of the chain changes: - Update to use the new database interface - Add chain-related functionality that the old database used to handle - Main chain structure and state - Transaction spend tracking - Implement a new pruned unspent transaction output (utxo) set - Provides efficient direct access to the unspent transaction outputs - Uses a domain specific compression algorithm that understands the standard transaction scripts in order to significantly compress them - Removes reliance on the transaction index and paves the way toward eventually enabling block pruning - Modify the New function to accept a Config struct instead of inidividual parameters - Replace the old TxStore type with a new UtxoViewpoint type that makes use of the new pruned utxo set - Convert code to treat the new UtxoViewpoint as a rolling view that is used between connects and disconnects to improve efficiency - Make best chain state always set when the chain instance is created - Remove now unnecessary logic for dealing with unset best state - Make all exported functions concurrent safe - Currently using a single chain state lock as it provides a straight forward and easy to review path forward however this can be improved with more fine grained locking - Optimize various cases where full blocks were being loaded when only the header is needed to help reduce the I/O load - Add the ability for callers to get a snapshot of the current best chain stats in a concurrent safe fashion - Does not block callers while new blocks are being processed - Make error messages that reference transaction outputs consistently use <transaction hash>:<output index> - Introduce a new AssertError type an convert internal consistency checks to use it - Update tests and examples to reflect the changes - Add a full suite of tests to ensure correct functionality of the new code The following is an overview of the btcd changes: - Update to use the new database and chain interfaces - Temporarily remove all code related to the transaction index - Temporarily remove all code related to the address index - Convert all code that uses transaction stores to use the new utxo view - Rework several calls that required the block manager for safe concurrency to use the chain package directly now that it is concurrent safe - Change all calls to obtain the best hash to use the new best state snapshot capability from the chain package - Remove workaround for limits on fetching height ranges since the new database interface no longer imposes them - Correct the gettxout RPC handler to return the best chain hash as opposed the hash the txout was found in - Optimize various RPC handlers: - Change several of the RPC handlers to use the new chain snapshot capability to avoid needlessly loading data - Update several handlers to use new functionality to avoid accessing the block manager so they are able to return the data without blocking when the server is busy processing blocks - Update non-verbose getblock to avoid deserialization and serialization overhead - Update getblockheader to request the block height directly from chain and only load the header - Update getdifficulty to use the new cached data from chain - Update getmininginfo to use the new cached data from chain - Update non-verbose getrawtransaction to avoid deserialization and serialization overhead - Update gettxout to use the new utxo store versus loading full transactions using the transaction index The following is an overview of the utility changes: - Update addblock to use the new database and chain interfaces - Update findcheckpoint to use the new database and chain interfaces - Remove the dropafter utility which is no longer supported NOTE: The transaction index and address index will be reimplemented in another commit.
2015-08-26 04:03:18 +00:00
// when entering the UTXO set. In Decred, all zero value outputs are unspendable.
func IsUnspendable(amount int64, pkScript []byte) bool {
if amount == 0 {
return true
}
pops, err := parseScript(pkScript)
if err != nil {
return true
}
return len(pops) > 0 && pops[0].opcode.value == OP_RETURN
}