lbcd/txscript/standard.go

1034 lines
34 KiB
Go
Raw Normal View History

// Copyright (c) 2013-2020 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package txscript
import (
"fmt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
)
const (
// MaxDataCarrierSize is the maximum number of bytes allowed in pushed
// data to be considered a nulldata transaction
MaxDataCarrierSize = 80
// StandardVerifyFlags are the script flags which are used when
// executing transaction scripts to enforce additional checks which
// are required for the script to be considered standard. These checks
// help reduce issues related to transaction malleability as well as
// allow pay-to-script hash transactions. Note these flags are
// different than what is required for the consensus rules in that they
// are more strict.
//
// TODO: This definition does not belong here. It belongs in a policy
// package.
StandardVerifyFlags = ScriptBip16 |
ScriptVerifyDERSignatures |
ScriptVerifyStrictEncoding |
ScriptVerifyMinimalData |
ScriptStrictMultiSig |
ScriptDiscourageUpgradableNops |
ScriptVerifyCleanStack |
ScriptVerifyNullFail |
ScriptVerifyCheckLockTimeVerify |
ScriptVerifyCheckSequenceVerify |
ScriptVerifyLowS |
ScriptStrictMultiSig |
ScriptVerifyWitness |
ScriptVerifyDiscourageUpgradeableWitnessProgram |
ScriptVerifyMinimalIf |
ScriptVerifyWitnessPubKeyType
)
// ScriptClass is an enumeration for the list of standard types of script.
type ScriptClass byte
// Classes of script payment known about in the blockchain.
const (
NonStandardTy ScriptClass = iota // None of the recognized forms.
PubKeyTy // Pay pubkey.
PubKeyHashTy // Pay pubkey hash.
WitnessV0PubKeyHashTy // Pay witness pubkey hash.
ScriptHashTy // Pay to script hash.
WitnessV0ScriptHashTy // Pay to witness script hash.
MultiSigTy // Multi signature.
NullDataTy // Empty data-only (provably prunable).
WitnessUnknownTy // Witness unknown
)
// scriptClassToName houses the human-readable strings which describe each
// script class.
var scriptClassToName = []string{
NonStandardTy: "nonstandard",
PubKeyTy: "pubkey",
PubKeyHashTy: "pubkeyhash",
WitnessV0PubKeyHashTy: "witness_v0_keyhash",
ScriptHashTy: "scripthash",
WitnessV0ScriptHashTy: "witness_v0_scripthash",
MultiSigTy: "multisig",
NullDataTy: "nulldata",
WitnessUnknownTy: "witness_unknown",
}
// String implements the Stringer interface by returning the name of
// the enum script class. If the enum is invalid then "Invalid" will be
// returned.
func (t ScriptClass) String() string {
if int(t) > len(scriptClassToName) || int(t) < 0 {
return "Invalid"
}
return scriptClassToName[t]
}
txscript: Optimize IsPayToPubKey This converts the IsPayToScriptHash function to analyze the raw script instead of using the far less efficient parseScript, thereby significantly optimizing the function. In order to accomplish this, it introduces four new functions: extractCompressedPubKey, extractUncompressedPubKey, extractPubKey, and isPubKeyScript. The extractPubKey function makes use of extractCompressedPubKey and extractUncompressedPubKey to combine their functionality as a convenience and isPubKeyScript is defined in terms of extractPubKey. The extractCompressedPubKey works with the raw script bytes to simultaneously determine if the script is a pay-to-compressed-pubkey script, and in the case it is, extract and return the raw compressed pubkey bytes. Similarly, the extractUncompressedPubKey works in the same way except it determines if the script is a pay-to-uncompressed-pubkey script and returns the raw uncompressed pubkey bytes in the case it is. The extract function approach was chosen because it is common for callers to want to only extract relevant details from a script if the script is of the specific type. Extracting those details requires performing the exact same checks to ensure the script is of the correct type, so it is more efficient to combine the two into one and define the type determination in terms of the result so long as the extraction does not require allocations. The following is a before and after comparison of analyzing a large script: benchmark old ns/op new ns/op delta BenchmarkIsPubKeyScript-8 62323 2.97 -100.00% benchmark old allocs new allocs delta BenchmarkIsPubKeyScript-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsPubKeyScript-8 311299 0 -100.00%
2021-02-04 23:06:56 +01:00
// extractCompressedPubKey extracts a compressed public key from the passed
// script if it is a standard pay-to-compressed-secp256k1-pubkey script. It
// will return nil otherwise.
func extractCompressedPubKey(script []byte) []byte {
// A pay-to-compressed-pubkey script is of the form:
// OP_DATA_33 <33-byte compressed pubkey> OP_CHECKSIG
// All compressed secp256k1 public keys must start with 0x02 or 0x03.
if len(script) == 35 &&
script[34] == OP_CHECKSIG &&
script[0] == OP_DATA_33 &&
(script[1] == 0x02 || script[1] == 0x03) {
return script[1:34]
}
return nil
}
// extractUncompressedPubKey extracts an uncompressed public key from the
// passed script if it is a standard pay-to-uncompressed-secp256k1-pubkey
// script. It will return nil otherwise.
func extractUncompressedPubKey(script []byte) []byte {
// A pay-to-uncompressed-pubkey script is of the form:
// OP_DATA_65 <65-byte uncompressed pubkey> OP_CHECKSIG
//
// All non-hybrid uncompressed secp256k1 public keys must start with 0x04.
// Hybrid uncompressed secp256k1 public keys start with 0x06 or 0x07:
// - 0x06 => hybrid format for even Y coords
// - 0x07 => hybrid format for odd Y coords
if len(script) == 67 &&
script[66] == OP_CHECKSIG &&
script[0] == OP_DATA_65 &&
(script[1] == 0x04 || script[1] == 0x06 || script[1] == 0x07) {
return script[1:66]
}
return nil
}
// extractPubKey extracts either compressed or uncompressed public key from the
// passed script if it is a either a standard pay-to-compressed-secp256k1-pubkey
// or pay-to-uncompressed-secp256k1-pubkey script, respectively. It will return
// nil otherwise.
func extractPubKey(script []byte) []byte {
if pubKey := extractCompressedPubKey(script); pubKey != nil {
return pubKey
}
return extractUncompressedPubKey(script)
}
// isPubKeyScript returns whether or not the passed script is either a standard
// pay-to-compressed-secp256k1-pubkey or pay-to-uncompressed-secp256k1-pubkey
// script.
func isPubKeyScript(script []byte) bool {
return extractPubKey(script) != nil
}
// extractPubKeyHash extracts the public key hash from the passed script if it
// is a standard pay-to-pubkey-hash script. It will return nil otherwise.
func extractPubKeyHash(script []byte) []byte {
// A pay-to-pubkey-hash script is of the form:
// OP_DUP OP_HASH160 <20-byte hash> OP_EQUALVERIFY OP_CHECKSIG
if len(script) == 25 &&
script[0] == OP_DUP &&
script[1] == OP_HASH160 &&
script[2] == OP_DATA_20 &&
script[23] == OP_EQUALVERIFY &&
script[24] == OP_CHECKSIG {
return script[3:23]
}
return nil
}
// isPubKeyHashScript returns whether or not the passed script is a standard
// pay-to-pubkey-hash script.
func isPubKeyHashScript(script []byte) bool {
return extractPubKeyHash(script) != nil
}
txscript: Optimize IsPayToScriptHash. This converts the IsPayToScriptHash function to analyze the raw script instead of using the far less efficient parseScript thereby significantly optimizing the function. In order to accomplish this, it introduces two new functions. The first one is named extractScriptHash and works with the raw script bytes to simultaneously determine if the script is a p2sh script, and in the case it is, extract and return the hash. The second new function is named isScriptHashScript and is defined in terms of the former. The extract function approach was chosen because it is common for callers to want to only extract relevant details from a script if the script is of the specific type. Extracting those details requires performing the exact same checks to ensure the script is of the correct type, so it is more efficient to combine the two into one and define the type determination in terms of the result so long as the extraction does not require allocations. Finally, this also deprecates the isScriptHash function that requires opcodes in favor of the new functions and modifies the comment on IsPayToScriptHash to explicitly call out the script version semantics. The following is a before and after comparison of analyzing a large script that is not a p2sh script: benchmark old ns/op new ns/op delta BenchmarkIsPayToScriptHash-8 62393 0.60 -100.00% benchmark old allocs new allocs delta BenchmarkIsPayToScriptHash-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsPayToScriptHash-8 311299 0 -100.00%
2019-03-13 07:11:14 +01:00
// extractScriptHash extracts the script hash from the passed script if it is a
// standard pay-to-script-hash script. It will return nil otherwise.
//
// 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 extractScriptHash(script []byte) []byte {
// A pay-to-script-hash script is of the form:
// OP_HASH160 <20-byte scripthash> OP_EQUAL
if len(script) == 23 &&
script[0] == OP_HASH160 &&
script[1] == OP_DATA_20 &&
script[22] == OP_EQUAL {
return script[2:22]
}
return nil
}
// isScriptHashScript returns whether or not the passed script is a standard
// pay-to-script-hash script.
func isScriptHashScript(script []byte) bool {
return extractScriptHash(script) != nil
}
txscript: Optimize IsMultisigScript. This converts the IsMultisigScript function to make use of the new tokenizer instead of the far less efficient parseScript thereby significantly optimizing the function. In order to accomplish this, it introduces two new functions. The first one is named extractMultisigScriptDetails and works with the raw script bytes to simultaneously determine if the script is a multisignature script, and in the case it is, extract and return the relevant details. The second new function is named isMultisigScript and is defined in terms of the former. The extract function accepts the script version, raw script bytes, and a flag to determine whether or not the public keys should also be extracted. The flag is provided because extracting pubkeys results in an allocation that the caller might wish to avoid. The extract function approach was chosen because it is common for callers to want to only extract relevant details from a script if the script is of the specific type. Extracting those details requires performing the exact same checks to ensure the script is of the correct type, so it is more efficient to combine the two into one and define the type determination in terms of the result so long as the extraction does not require allocations. It is important to note that this new implementation intentionally has a semantic difference from the existing implementation in that it will now correctly identify a multisig script with zero pubkeys whereas previously it incorrectly required at least one pubkey. This change is acceptable because the function only deals with standardness rather than consensus rules. Finally, this also deprecates the isMultiSig function that requires opcodes in favor of the new functions and deprecates the error return on the export IsMultisigScript function since it really does not make sense given the purpose of the function. The following is a before and after comparison of analyzing both a large script that is not a multisig script and a 1-of-2 multisig public key script: benchmark old ns/op new ns/op delta BenchmarkIsMultisigScriptLarge-8 64166 5.52 -99.99% BenchmarkIsMultisigScript-8 630 59.4 -90.57% benchmark old allocs new allocs delta BenchmarkIsMultisigScriptLarge-8 1 0 -100.00% BenchmarkIsMultisigScript-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsMultisigScriptLarge-8 311299 0 -100.00% BenchmarkIsMultisigScript-8 2304 0 -100.00%
2019-03-13 07:11:16 +01:00
// multiSigDetails houses details extracted from a standard multisig script.
type multiSigDetails struct {
requiredSigs int
numPubKeys int
pubKeys [][]byte
valid bool
}
// extractMultisigScriptDetails attempts to extract details from the passed
// script if it is a standard multisig script. The returned details struct will
// have the valid flag set to false otherwise.
//
// The extract pubkeys flag indicates whether or not the pubkeys themselves
// should also be extracted and is provided because extracting them results in
// an allocation that the caller might wish to avoid. The pubKeys member of
// the returned details struct will be nil when the flag is false.
//
// NOTE: This function is only valid for version 0 scripts. The returned
// details struct will always be empty and have the valid flag set to false for
// other script versions.
func extractMultisigScriptDetails(scriptVersion uint16, script []byte, extractPubKeys bool) multiSigDetails {
// The only currently supported script version is 0.
if scriptVersion != 0 {
return multiSigDetails{}
}
// A multi-signature script is of the form:
// NUM_SIGS PUBKEY PUBKEY PUBKEY ... NUM_PUBKEYS OP_CHECKMULTISIG
// The script can't possibly be a multisig script if it doesn't end with
// OP_CHECKMULTISIG or have at least two small integer pushes preceding it.
// Fail fast to avoid more work below.
if len(script) < 3 || script[len(script)-1] != OP_CHECKMULTISIG {
return multiSigDetails{}
}
// The first opcode must be a small integer specifying the number of
// signatures required.
tokenizer := MakeScriptTokenizer(scriptVersion, script)
if !tokenizer.Next() || !isSmallInt(tokenizer.Opcode()) {
return multiSigDetails{}
}
requiredSigs := asSmallInt(tokenizer.Opcode())
// The next series of opcodes must either push public keys or be a small
// integer specifying the number of public keys.
var numPubKeys int
var pubKeys [][]byte
if extractPubKeys {
pubKeys = make([][]byte, 0, MaxPubKeysPerMultiSig)
}
for tokenizer.Next() {
if isSmallInt(tokenizer.Opcode()) {
break
}
data := tokenizer.Data()
numPubKeys++
if !isStrictPubKeyEncoding(data) {
continue
}
if extractPubKeys {
pubKeys = append(pubKeys, data)
}
}
if tokenizer.Done() {
return multiSigDetails{}
}
// The next opcode must be a small integer specifying the number of public
// keys required.
op := tokenizer.Opcode()
if !isSmallInt(op) || asSmallInt(op) != numPubKeys {
return multiSigDetails{}
}
// There must only be a single opcode left unparsed which will be
// OP_CHECKMULTISIG per the check above.
if int32(len(tokenizer.Script()))-tokenizer.ByteIndex() != 1 {
return multiSigDetails{}
}
return multiSigDetails{
requiredSigs: requiredSigs,
numPubKeys: numPubKeys,
pubKeys: pubKeys,
valid: true,
}
}
// isMultisigScript returns whether or not the passed script is a standard
// multisig script.
//
// NOTE: This function is only valid for version 0 scripts. It will always
// return false for other script versions.
func isMultisigScript(scriptVersion uint16, script []byte) bool {
// Since this is only checking the form of the script, don't extract the
// public keys to avoid the allocation.
details := extractMultisigScriptDetails(scriptVersion, script, false)
return details.valid
}
// IsMultisigScript returns whether or not the passed script is a standard
// multisignature script.
//
// 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.
//
// The error is DEPRECATED and will be removed in the major version bump.
func IsMultisigScript(script []byte) (bool, error) {
txscript: Optimize IsMultisigScript. This converts the IsMultisigScript function to make use of the new tokenizer instead of the far less efficient parseScript thereby significantly optimizing the function. In order to accomplish this, it introduces two new functions. The first one is named extractMultisigScriptDetails and works with the raw script bytes to simultaneously determine if the script is a multisignature script, and in the case it is, extract and return the relevant details. The second new function is named isMultisigScript and is defined in terms of the former. The extract function accepts the script version, raw script bytes, and a flag to determine whether or not the public keys should also be extracted. The flag is provided because extracting pubkeys results in an allocation that the caller might wish to avoid. The extract function approach was chosen because it is common for callers to want to only extract relevant details from a script if the script is of the specific type. Extracting those details requires performing the exact same checks to ensure the script is of the correct type, so it is more efficient to combine the two into one and define the type determination in terms of the result so long as the extraction does not require allocations. It is important to note that this new implementation intentionally has a semantic difference from the existing implementation in that it will now correctly identify a multisig script with zero pubkeys whereas previously it incorrectly required at least one pubkey. This change is acceptable because the function only deals with standardness rather than consensus rules. Finally, this also deprecates the isMultiSig function that requires opcodes in favor of the new functions and deprecates the error return on the export IsMultisigScript function since it really does not make sense given the purpose of the function. The following is a before and after comparison of analyzing both a large script that is not a multisig script and a 1-of-2 multisig public key script: benchmark old ns/op new ns/op delta BenchmarkIsMultisigScriptLarge-8 64166 5.52 -99.99% BenchmarkIsMultisigScript-8 630 59.4 -90.57% benchmark old allocs new allocs delta BenchmarkIsMultisigScriptLarge-8 1 0 -100.00% BenchmarkIsMultisigScript-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsMultisigScriptLarge-8 311299 0 -100.00% BenchmarkIsMultisigScript-8 2304 0 -100.00%
2019-03-13 07:11:16 +01:00
const scriptVersion = 0
return isMultisigScript(scriptVersion, script), nil
}
txscript: Optimize IsMultisigSigScript. This converts the IsMultisigSigScript function to analyze the raw script and make use of the new tokenizer instead of the far less efficient parseScript thereby significantly optimizing the function. In order to accomplish this, it first rejects scripts that can't possibly fit the bill due to the final byte of what would be the redeem script not being the appropriate opcode or the overall script not having enough bytes. Then, it uses a new function that is introduced named finalOpcodeData that uses the tokenizer to return any data associated with the final opcode in the signature script (which will be nil for non-push opcodes or if the script fails to parse) and analyzes it as if it were a redeem script when it is non nil. It is also worth noting that this new implementation intentionally has the same semantic difference from the existing implementation as the updated IsMultisigScript function in regards to allowing zero pubkeys whereas previously it incorrectly required at least one pubkey. Finally, the comment is modified to explicitly call out the script version semantics. The following is a before and after comparison of analyzing a large script that is not a multisig script and both a 1-of-2 multisig public key script (which should be false) and a signature script comprised of a pay-to-script-hash 1-of-2 multisig redeem script (which should be true): benchmark old ns/op new ns/op delta BenchmarkIsMultisigSigScriptLarge-8 69328 2.93 -100.00% BenchmarkIsMultisigSigScript-8 2375 146 -93.85% benchmark old allocs new allocs delta BenchmarkIsMultisigSigScriptLarge-8 5 0 -100.00% BenchmarkIsMultisigSigScript-8 3 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsMultisigSigScriptLarge-8 330035 0 -100.00% BenchmarkIsMultisigSigScript-8 9472 0 -100.00%
2019-03-13 07:11:18 +01:00
// IsMultisigSigScript returns whether or not the passed script appears to be a
// signature script which consists of a pay-to-script-hash multi-signature
// redeem script. Determining if a signature script is actually a redemption of
// pay-to-script-hash requires the associated public key script which is often
// expensive to obtain. Therefore, this makes a fast best effort guess that has
// a high probability of being correct by checking if the signature script ends
// with a data push and treating that data push as if it were a p2sh redeem
// script
//
// 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 IsMultisigSigScript(script []byte) bool {
txscript: Optimize IsMultisigSigScript. This converts the IsMultisigSigScript function to analyze the raw script and make use of the new tokenizer instead of the far less efficient parseScript thereby significantly optimizing the function. In order to accomplish this, it first rejects scripts that can't possibly fit the bill due to the final byte of what would be the redeem script not being the appropriate opcode or the overall script not having enough bytes. Then, it uses a new function that is introduced named finalOpcodeData that uses the tokenizer to return any data associated with the final opcode in the signature script (which will be nil for non-push opcodes or if the script fails to parse) and analyzes it as if it were a redeem script when it is non nil. It is also worth noting that this new implementation intentionally has the same semantic difference from the existing implementation as the updated IsMultisigScript function in regards to allowing zero pubkeys whereas previously it incorrectly required at least one pubkey. Finally, the comment is modified to explicitly call out the script version semantics. The following is a before and after comparison of analyzing a large script that is not a multisig script and both a 1-of-2 multisig public key script (which should be false) and a signature script comprised of a pay-to-script-hash 1-of-2 multisig redeem script (which should be true): benchmark old ns/op new ns/op delta BenchmarkIsMultisigSigScriptLarge-8 69328 2.93 -100.00% BenchmarkIsMultisigSigScript-8 2375 146 -93.85% benchmark old allocs new allocs delta BenchmarkIsMultisigSigScriptLarge-8 5 0 -100.00% BenchmarkIsMultisigSigScript-8 3 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsMultisigSigScriptLarge-8 330035 0 -100.00% BenchmarkIsMultisigSigScript-8 9472 0 -100.00%
2019-03-13 07:11:18 +01:00
const scriptVersion = 0
// The script can't possibly be a multisig signature script if it doesn't
// end with OP_CHECKMULTISIG in the redeem script or have at least two small
// integers preceding it, and the redeem script itself must be preceded by
// at least a data push opcode. Fail fast to avoid more work below.
if len(script) < 4 || script[len(script)-1] != OP_CHECKMULTISIG {
return false
}
txscript: Optimize IsMultisigSigScript. This converts the IsMultisigSigScript function to analyze the raw script and make use of the new tokenizer instead of the far less efficient parseScript thereby significantly optimizing the function. In order to accomplish this, it first rejects scripts that can't possibly fit the bill due to the final byte of what would be the redeem script not being the appropriate opcode or the overall script not having enough bytes. Then, it uses a new function that is introduced named finalOpcodeData that uses the tokenizer to return any data associated with the final opcode in the signature script (which will be nil for non-push opcodes or if the script fails to parse) and analyzes it as if it were a redeem script when it is non nil. It is also worth noting that this new implementation intentionally has the same semantic difference from the existing implementation as the updated IsMultisigScript function in regards to allowing zero pubkeys whereas previously it incorrectly required at least one pubkey. Finally, the comment is modified to explicitly call out the script version semantics. The following is a before and after comparison of analyzing a large script that is not a multisig script and both a 1-of-2 multisig public key script (which should be false) and a signature script comprised of a pay-to-script-hash 1-of-2 multisig redeem script (which should be true): benchmark old ns/op new ns/op delta BenchmarkIsMultisigSigScriptLarge-8 69328 2.93 -100.00% BenchmarkIsMultisigSigScript-8 2375 146 -93.85% benchmark old allocs new allocs delta BenchmarkIsMultisigSigScriptLarge-8 5 0 -100.00% BenchmarkIsMultisigSigScript-8 3 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsMultisigSigScriptLarge-8 330035 0 -100.00% BenchmarkIsMultisigSigScript-8 9472 0 -100.00%
2019-03-13 07:11:18 +01:00
// Parse through the script to find the last opcode and any data it might
// push and treat it as a p2sh redeem script even though it might not
// actually be one.
possibleRedeemScript := finalOpcodeData(scriptVersion, script)
if possibleRedeemScript == nil {
return false
}
txscript: Optimize IsMultisigSigScript. This converts the IsMultisigSigScript function to analyze the raw script and make use of the new tokenizer instead of the far less efficient parseScript thereby significantly optimizing the function. In order to accomplish this, it first rejects scripts that can't possibly fit the bill due to the final byte of what would be the redeem script not being the appropriate opcode or the overall script not having enough bytes. Then, it uses a new function that is introduced named finalOpcodeData that uses the tokenizer to return any data associated with the final opcode in the signature script (which will be nil for non-push opcodes or if the script fails to parse) and analyzes it as if it were a redeem script when it is non nil. It is also worth noting that this new implementation intentionally has the same semantic difference from the existing implementation as the updated IsMultisigScript function in regards to allowing zero pubkeys whereas previously it incorrectly required at least one pubkey. Finally, the comment is modified to explicitly call out the script version semantics. The following is a before and after comparison of analyzing a large script that is not a multisig script and both a 1-of-2 multisig public key script (which should be false) and a signature script comprised of a pay-to-script-hash 1-of-2 multisig redeem script (which should be true): benchmark old ns/op new ns/op delta BenchmarkIsMultisigSigScriptLarge-8 69328 2.93 -100.00% BenchmarkIsMultisigSigScript-8 2375 146 -93.85% benchmark old allocs new allocs delta BenchmarkIsMultisigSigScriptLarge-8 5 0 -100.00% BenchmarkIsMultisigSigScript-8 3 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsMultisigSigScriptLarge-8 330035 0 -100.00% BenchmarkIsMultisigSigScript-8 9472 0 -100.00%
2019-03-13 07:11:18 +01:00
// Finally, return if that possible redeem script is a multisig script.
return isMultisigScript(scriptVersion, possibleRedeemScript)
}
txscript: Optimize IsPayToWitnessPubKeyHash This converts the IsPayToWitnessPubKeyHash function to analyze the raw script instead of the far less efficient parseScript, thereby significantly optimizing the function. In order to accomplish this, it introduces two new functions. The first one is named extractWitnessPubKeyHash and works with the raw script bytes to simultaneously deteremine if the script is a p2wkh, and in case it is, extract and return the hash. The second new function is name isWitnessPubKeyHashScript which is defined in terms of the former. The extract function is approach was chosen because it is common for callers to want to only extract relevant details from the script if the script is of the specific type. Extracting those details requires the exact same checks to ensure the script is of the correct type, so it is more efficient to combine the two and define the type determination in terms of the result so long as the extraction does not require allocations. Finally, this deprecates the isWitnessPubKeyHash function that requires opcodes in favor of the new functions and modifies the comment on IsPayToWitnessPubKeyHash to explicitly call out the script version semantics. The following is a before and after comparison of executing IsPayToWitnessPubKeyHash on a large script: benchmark old ns/op new ns/op delta BenchmarkIsWitnessPubKeyHash-8 68927 0.53 -100.00% benchmark old allocs new allocs delta BenchmarkIsWitnessPubKeyHash-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsWitnessPubKeyHash-8 311299 0 -100.00%
2021-02-04 22:22:40 +01:00
// extractWitnessPubKeyHash extracts the witness public key hash from the passed
// script if it is a standard pay-to-witness-pubkey-hash script. It will return
// nil otherwise.
func extractWitnessPubKeyHash(script []byte) []byte {
// A pay-to-witness-pubkey-hash script is of the form:
// OP_0 OP_DATA_20 <20-byte-hash>
if len(script) == 22 &&
script[0] == OP_0 &&
script[1] == OP_DATA_20 {
return script[2:22]
}
txscript: Optimize IsPayToWitnessPubKeyHash This converts the IsPayToWitnessPubKeyHash function to analyze the raw script instead of the far less efficient parseScript, thereby significantly optimizing the function. In order to accomplish this, it introduces two new functions. The first one is named extractWitnessPubKeyHash and works with the raw script bytes to simultaneously deteremine if the script is a p2wkh, and in case it is, extract and return the hash. The second new function is name isWitnessPubKeyHashScript which is defined in terms of the former. The extract function is approach was chosen because it is common for callers to want to only extract relevant details from the script if the script is of the specific type. Extracting those details requires the exact same checks to ensure the script is of the correct type, so it is more efficient to combine the two and define the type determination in terms of the result so long as the extraction does not require allocations. Finally, this deprecates the isWitnessPubKeyHash function that requires opcodes in favor of the new functions and modifies the comment on IsPayToWitnessPubKeyHash to explicitly call out the script version semantics. The following is a before and after comparison of executing IsPayToWitnessPubKeyHash on a large script: benchmark old ns/op new ns/op delta BenchmarkIsWitnessPubKeyHash-8 68927 0.53 -100.00% benchmark old allocs new allocs delta BenchmarkIsWitnessPubKeyHash-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsWitnessPubKeyHash-8 311299 0 -100.00%
2021-02-04 22:22:40 +01:00
return nil
}
// isWitnessPubKeyHashScript returns whether or not the passed script is a
// standard pay-to-witness-pubkey-hash script.
func isWitnessPubKeyHashScript(script []byte) bool {
return extractWitnessPubKeyHash(script) != nil
}
txscript: Optimize IsPayToWitnessScriptHash This converts the IsPayToWitnessScriptHash function to analyze the raw script instead of using the far less efficient parseScript, thereby significantly optimizing the function. In order to accomplish this, it introduces two new functions. The first one is named extractWitnessScriptHash and works with the raw script byte to simultaneously deteremine if the script is a p2wsh script, and in the case that is is, extract and return the hash. The second new function is named isWitnessScriptHashScript and is defined in terms of the former. The extract function approach was chosed because it is common for callers to want to only extract relevant details from a script if the script is of the specific type. Extracting those details requires performing the exact same checks to ensure the script is of the correct type, so it is more efficient to combine the two into one and define the type determination in terms of the result, so long as the extraction does not require allocations. Finally, this also deprecates the isWitnessScriptHash function that requires opcodes in favor of the new functions and modifies the comment on IsPayToWitnessScriptHash to call out the script version semantics. The following is a before and after comparison of executing IsPayToWitnessScriptHash on a large script: benchmark old ns/op new ns/op delta BenchmarkIsWitnessScriptHash-8 62774 0.63 -100.00% benchmark old allocs new allocs delta BenchmarkIsWitnessScriptHash-8 1 0 -100.00% benchmark old bytes new bytes delta BenchmarkIsWitnessScriptHash-8 311299 0 -100.00%
2021-02-04 22:39:10 +01:00
// extractWitnessScriptHash extracts the witness script hash from the passed
// script if it is standard pay-to-witness-script-hash script. It will return
// nil otherwise.
func extractWitnessScriptHash(script []byte) []byte {
// A pay-to-witness-script-hash script is of the form:
// OP_0 OP_DATA_32 <32-byte-hash>
if len(script) == 34 &&
script[0] == OP_0 &&
script[1] == OP_DATA_32 {
return script[2:34]
}
return nil
}
// isWitnessScriptHashScript returns whether or not the passed script is a
// standard pay-to-witness-script-hash script.
func isWitnessScriptHashScript(script []byte) bool {
return extractWitnessScriptHash(script) != nil
}
// isNullDataScript returns whether or not the passed script is a standard
// null data script.
//
// NOTE: This function is only valid for version 0 scripts. It will always
// return false for other script versions.
func isNullDataScript(scriptVersion uint16, script []byte) bool {
// The only currently supported script version is 0.
if scriptVersion != 0 {
return false
}
// A null script is of the form:
// OP_RETURN <optional data>
//
// Thus, it can either be a single OP_RETURN or an OP_RETURN followed by a
// data push up to MaxDataCarrierSize bytes.
// The script can't possibly be a a null data script if it doesn't start
// with OP_RETURN. Fail fast to avoid more work below.
if len(script) < 1 || script[0] != OP_RETURN {
return false
}
// Single OP_RETURN.
if len(script) == 1 {
return true
}
// OP_RETURN followed by data push up to MaxDataCarrierSize bytes.
tokenizer := MakeScriptTokenizer(scriptVersion, script[1:])
return tokenizer.Next() && tokenizer.Done() &&
(isSmallInt(tokenizer.Opcode()) || tokenizer.Opcode() <= OP_PUSHDATA4) &&
len(tokenizer.Data()) <= MaxDataCarrierSize
}
// scriptType returns the type of the script being inspected from the known
// standard types.
//
// NOTE: All scripts that are not version 0 are currently considered non
// standard.
func typeOfScript(scriptVersion uint16, script []byte) ScriptClass {
if scriptVersion != 0 {
return NonStandardTy
}
switch {
case isPubKeyScript(script):
return PubKeyTy
case isPubKeyHashScript(script):
return PubKeyHashTy
case isScriptHashScript(script):
return ScriptHashTy
case isWitnessPubKeyHashScript(script):
return WitnessV0PubKeyHashTy
case isWitnessScriptHashScript(script):
return WitnessV0ScriptHashTy
case isMultisigScript(scriptVersion, script):
return MultiSigTy
case isNullDataScript(scriptVersion, script):
return NullDataTy
default:
return NonStandardTy
}
}
// GetScriptClass returns the class of the script passed.
//
// NonStandardTy will be returned when the script does not parse.
func GetScriptClass(script []byte) ScriptClass {
const scriptVersion = 0
return typeOfScript(scriptVersion, script)
}
// NewScriptClass returns the ScriptClass corresponding to the string name
// provided as argument. ErrUnsupportedScriptType error is returned if the
// name doesn't correspond to any known ScriptClass.
//
// Not to be confused with GetScriptClass.
func NewScriptClass(name string) (*ScriptClass, error) {
for i, n := range scriptClassToName {
if n == name {
value := ScriptClass(i)
return &value, nil
}
}
return nil, fmt.Errorf("%w: %s", ErrUnsupportedScriptType, name)
}
// expectedInputs returns the number of arguments required by a script.
// If the script is of unknown type such that the number can not be determined
// then -1 is returned. We are an internal function and thus assume that class
// is the real class of pops (and we can thus assume things that were determined
// while finding out the type).
//
// 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 expectedInputs(script []byte, class ScriptClass) int {
switch class {
case PubKeyTy:
return 1
case PubKeyHashTy:
return 2
case WitnessV0PubKeyHashTy:
return 2
case ScriptHashTy:
// Not including script. That is handled by the caller.
return 1
case WitnessV0ScriptHashTy:
// Not including script. That is handled by the caller.
return 1
case MultiSigTy:
// Standard multisig has a push a small number for the number
// of sigs and number of keys. Check the first push instruction
// to see how many arguments are expected. typeOfScript already
// checked this so we know it'll be a small int. Also, due to
// the original bitcoind bug where OP_CHECKMULTISIG pops an
// additional item from the stack, add an extra expected input
// for the extra push that is required to compensate.
return asSmallInt(script[0]) + 1
case NullDataTy:
fallthrough
default:
return -1
}
}
// ScriptInfo houses information about a script pair that is determined by
// CalcScriptInfo.
type ScriptInfo struct {
// PkScriptClass is the class of the public key script and is equivalent
// to calling GetScriptClass on it.
PkScriptClass ScriptClass
// NumInputs is the number of inputs provided by the public key script.
NumInputs int
// ExpectedInputs is the number of outputs required by the signature
// script and any pay-to-script-hash scripts. The number will be -1 if
// unknown.
ExpectedInputs int
// SigOps is the number of signature operations in the script pair.
SigOps int
}
// CalcScriptInfo returns a structure providing data about the provided script
// pair. It will error if the pair is in someway invalid such that they can not
// be analysed, i.e. if they do not parse or the pkScript is not a push-only
// script
//
// 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.
//
// DEPRECATED. This will be removed in the next major version bump.
func CalcScriptInfo(sigScript, pkScript []byte, witness wire.TxWitness,
bip16, segwit bool) (*ScriptInfo, error) {
// Count the number of opcodes in the signature script while also ensuring
// that successfully parses. Since there is a check below to ensure the
// script is push only, this equates to the number of inputs to the public
// key script.
const scriptVersion = 0
var numInputs int
tokenizer := MakeScriptTokenizer(scriptVersion, sigScript)
for tokenizer.Next() {
numInputs++
}
if err := tokenizer.Err(); err != nil {
return nil, err
}
if err := checkScriptParses(scriptVersion, pkScript); err != nil {
return nil, err
}
// Can't have a signature script that doesn't just push data.
if !IsPushOnlyScript(sigScript) {
return nil, scriptError(ErrNotPushOnly,
"signature script is not push only")
}
si := new(ScriptInfo)
si.PkScriptClass = typeOfScript(scriptVersion, pkScript)
si.ExpectedInputs = expectedInputs(pkScript, si.PkScriptClass)
switch {
// Count sigops taking into account pay-to-script-hash.
case si.PkScriptClass == ScriptHashTy && bip16 && !segwit:
// The redeem script is the final data push of the signature script.
redeemScript := finalOpcodeData(scriptVersion, sigScript)
reedeemClass := typeOfScript(scriptVersion, redeemScript)
rsInputs := expectedInputs(redeemScript, reedeemClass)
if rsInputs == -1 {
si.ExpectedInputs = -1
} else {
si.ExpectedInputs += rsInputs
}
si.SigOps = countSigOpsV0(redeemScript, true)
// All entries pushed to stack (or are OP_RESERVED and exec
// will fail).
si.NumInputs = numInputs
// If segwit is active, and this is a regular p2wkh output, then we'll
// treat the script as a p2pkh output in essence.
case si.PkScriptClass == WitnessV0PubKeyHashTy && segwit:
si.SigOps = GetWitnessSigOpCount(sigScript, pkScript, witness)
si.NumInputs = len(witness)
// We'll attempt to detect the nested p2sh case so we can accurately
// count the signature operations involved.
case si.PkScriptClass == ScriptHashTy &&
IsWitnessProgram(sigScript[1:]) && bip16 && segwit:
// Extract the pushed witness program from the sigScript so we
// can determine the number of expected inputs.
redeemClass := typeOfScript(scriptVersion, sigScript[1:])
shInputs := expectedInputs(sigScript[1:], redeemClass)
if shInputs == -1 {
si.ExpectedInputs = -1
} else {
si.ExpectedInputs += shInputs
}
si.SigOps = GetWitnessSigOpCount(sigScript, pkScript, witness)
si.NumInputs = len(witness)
si.NumInputs += numInputs
// If segwit is active, and this is a p2wsh output, then we'll need to
// examine the witness script to generate accurate script info.
case si.PkScriptClass == WitnessV0ScriptHashTy && segwit:
witnessScript := witness[len(witness)-1]
redeemClass := typeOfScript(scriptVersion, witnessScript)
shInputs := expectedInputs(witnessScript, redeemClass)
if shInputs == -1 {
si.ExpectedInputs = -1
} else {
si.ExpectedInputs += shInputs
}
si.SigOps = GetWitnessSigOpCount(sigScript, pkScript, witness)
si.NumInputs = len(witness)
default:
si.SigOps = countSigOpsV0(pkScript, true)
// All entries pushed to stack (or are OP_RESERVED and exec
// will fail).
si.NumInputs = numInputs
}
return si, nil
}
// CalcMultiSigStats returns the number of public keys and signatures from
// a multi-signature transaction script. The passed script MUST already be
// known to be a multi-signature script.
//
// 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 CalcMultiSigStats(script []byte) (int, int, error) {
// The public keys are not needed here, so pass false to avoid the extra
// allocation.
const scriptVersion = 0
details := extractMultisigScriptDetails(scriptVersion, script, false)
if !details.valid {
str := fmt.Sprintf("script %x is not a multisig script", script)
return 0, 0, scriptError(ErrNotMultisigScript, str)
}
return details.numPubKeys, details.requiredSigs, nil
}
// payToPubKeyHashScript creates a new script to pay a transaction
// output to a 20-byte pubkey hash. It is expected that the input is a valid
// hash.
func payToPubKeyHashScript(pubKeyHash []byte) ([]byte, error) {
return NewScriptBuilder().AddOp(OP_DUP).AddOp(OP_HASH160).
AddData(pubKeyHash).AddOp(OP_EQUALVERIFY).AddOp(OP_CHECKSIG).
Script()
}
// payToWitnessPubKeyHashScript creates a new script to pay to a version 0
// pubkey hash witness program. The passed hash is expected to be valid.
func payToWitnessPubKeyHashScript(pubKeyHash []byte) ([]byte, error) {
return NewScriptBuilder().AddOp(OP_0).AddData(pubKeyHash).Script()
}
// payToScriptHashScript creates a new script to pay a transaction output to a
// script hash. It is expected that the input is a valid hash.
func payToScriptHashScript(scriptHash []byte) ([]byte, error) {
return NewScriptBuilder().AddOp(OP_HASH160).AddData(scriptHash).
AddOp(OP_EQUAL).Script()
}
// payToWitnessPubKeyHashScript creates a new script to pay to a version 0
// script hash witness program. The passed hash is expected to be valid.
func payToWitnessScriptHashScript(scriptHash []byte) ([]byte, error) {
return NewScriptBuilder().AddOp(OP_0).AddData(scriptHash).Script()
}
// payToPubkeyScript creates a new script to pay a transaction output to a
// public key. It is expected that the input is a valid pubkey.
func payToPubKeyScript(serializedPubKey []byte) ([]byte, error) {
return NewScriptBuilder().AddData(serializedPubKey).
AddOp(OP_CHECKSIG).Script()
}
// PayToAddrScript creates a new script to pay a transaction output to a the
// specified address.
func PayToAddrScript(addr btcutil.Address) ([]byte, error) {
const nilAddrErrStr = "unable to generate payment script for nil address"
switch addr := addr.(type) {
case *btcutil.AddressPubKeyHash:
if addr == nil {
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
return payToPubKeyHashScript(addr.ScriptAddress())
case *btcutil.AddressScriptHash:
if addr == nil {
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
return payToScriptHashScript(addr.ScriptAddress())
case *btcutil.AddressPubKey:
if addr == nil {
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
return payToPubKeyScript(addr.ScriptAddress())
case *btcutil.AddressWitnessPubKeyHash:
if addr == nil {
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
return payToWitnessPubKeyHashScript(addr.ScriptAddress())
case *btcutil.AddressWitnessScriptHash:
if addr == nil {
return nil, scriptError(ErrUnsupportedAddress,
nilAddrErrStr)
}
return payToWitnessScriptHashScript(addr.ScriptAddress())
}
str := fmt.Sprintf("unable to generate payment script for unsupported "+
"address type %T", addr)
return nil, scriptError(ErrUnsupportedAddress, str)
}
// NullDataScript creates a provably-prunable script containing OP_RETURN
// followed by the passed data. An Error with the error code ErrTooMuchNullData
// will be returned if the length of the passed data exceeds MaxDataCarrierSize.
func NullDataScript(data []byte) ([]byte, error) {
if len(data) > MaxDataCarrierSize {
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()
}
// 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 Error with the error code ErrTooManyRequiredSigs will be
// returned if nrequired is larger than the number of keys provided.
func MultiSigScript(pubkeys []*btcutil.AddressPubKey, nrequired int) ([]byte, error) {
if len(pubkeys) < nrequired {
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))
for _, key := range pubkeys {
builder.AddData(key.ScriptAddress())
}
builder.AddInt64(int64(len(pubkeys)))
builder.AddOp(OP_CHECKMULTISIG)
return builder.Script()
}
// PushedData returns an array of byte slices containing any pushed data found
// in the passed script. This includes OP_0, but not OP_1 - OP_16.
//
// 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 PushedData(script []byte) ([][]byte, error) {
const scriptVersion = 0
var data [][]byte
tokenizer := MakeScriptTokenizer(scriptVersion, script)
for tokenizer.Next() {
if tokenizer.Data() != nil {
data = append(data, tokenizer.Data())
} else if tokenizer.Opcode() == OP_0 {
data = append(data, nil)
}
}
if err := tokenizer.Err(); err != nil {
return nil, err
}
return data, nil
}
// pubKeyHashToAddrs is a convenience function to attempt to convert the
// passed hash to a pay-to-pubkey-hash address housed within an address
// slice. It is used to consolidate common code.
func pubKeyHashToAddrs(hash []byte, params *chaincfg.Params) []btcutil.Address {
// Skip the pubkey hash if it's invalid for some reason.
var addrs []btcutil.Address
addr, err := btcutil.NewAddressPubKeyHash(hash, params)
if err == nil {
addrs = append(addrs, addr)
}
return addrs
}
// scriptHashToAddrs is a convenience function to attempt to convert the passed
// hash to a pay-to-script-hash address housed within an address slice. It is
// used to consolidate common code.
func scriptHashToAddrs(hash []byte, params *chaincfg.Params) []btcutil.Address {
// Skip the hash if it's invalid for some reason.
var addrs []btcutil.Address
addr, err := btcutil.NewAddressScriptHashFromHash(hash, params)
if err == nil {
addrs = append(addrs, addr)
}
return addrs
}
// ExtractPkScriptAddrs returns the type of script, addresses and required
// signatures associated with the passed PkScript. Note that it only works for
// 'standard' transaction script types. Any data such as public keys which are
// invalid are omitted from the results.
//
// NOTE: This function only attempts to identify version 0 scripts. The return
// value will indicate a nonstandard script type for other script versions along
// with an invalid script version error.
func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (ScriptClass, []btcutil.Address, int, error) {
// Check for pay-to-pubkey-hash script.
if hash := extractPubKeyHash(pkScript); hash != nil {
return PubKeyHashTy, pubKeyHashToAddrs(hash, chainParams), 1, nil
}
// Check for pay-to-script-hash.
if hash := extractScriptHash(pkScript); hash != nil {
return ScriptHashTy, scriptHashToAddrs(hash, chainParams), 1, nil
}
// Check for pay-to-pubkey script.
if data := extractPubKey(pkScript); data != nil {
var addrs []btcutil.Address
addr, err := btcutil.NewAddressPubKey(data, chainParams)
if err == nil {
addrs = append(addrs, addr)
}
return PubKeyTy, addrs, 1, nil
}
// Check for multi-signature script.
const scriptVersion = 0
details := extractMultisigScriptDetails(scriptVersion, pkScript, true)
if details.valid {
// Convert the public keys while skipping any that are invalid.
addrs := make([]btcutil.Address, 0, len(details.pubKeys))
for _, pubkey := range details.pubKeys {
addr, err := btcutil.NewAddressPubKey(pubkey, chainParams)
if err == nil {
addrs = append(addrs, addr)
}
}
return MultiSigTy, addrs, details.requiredSigs, nil
}
// Check for null data script.
if isNullDataScript(scriptVersion, pkScript) {
// Null data transactions have no addresses or required signatures.
return NullDataTy, nil, 0, nil
}
if hash := extractWitnessPubKeyHash(pkScript); hash != nil {
var addrs []btcutil.Address
addr, err := btcutil.NewAddressWitnessPubKeyHash(hash, chainParams)
if err == nil {
addrs = append(addrs, addr)
}
return WitnessV0PubKeyHashTy, addrs, 1, nil
}
if hash := extractWitnessScriptHash(pkScript); hash != nil {
var addrs []btcutil.Address
addr, err := btcutil.NewAddressWitnessScriptHash(hash, chainParams)
if err == nil {
addrs = append(addrs, addr)
}
return WitnessV0ScriptHashTy, addrs, 1, nil
}
// If none of the above passed, then the address must be non-standard.
return NonStandardTy, nil, 0, nil
}
// AtomicSwapDataPushes houses the data pushes found in atomic swap contracts.
type AtomicSwapDataPushes struct {
RecipientHash160 [20]byte
RefundHash160 [20]byte
SecretHash [32]byte
SecretSize int64
LockTime int64
}
// ExtractAtomicSwapDataPushes returns the data pushes from an atomic swap
// contract. If the script is not an atomic swap contract,
// ExtractAtomicSwapDataPushes returns (nil, nil). Non-nil errors are returned
// for unparsable scripts.
//
// NOTE: Atomic swaps are not considered standard script types by the dcrd
// mempool policy and should be used with P2SH. The atomic swap format is also
// expected to change to use a more secure hash function in the future.
//
// This function is only defined in the txscript package due to API limitations
// which prevent callers using txscript to parse nonstandard scripts.
//
// DEPRECATED. This will be removed in the next major version bump. The error
// should also likely be removed if the code is reimplemented by any callers
// since any errors result in a nil result anyway.
func ExtractAtomicSwapDataPushes(version uint16, pkScript []byte) (*AtomicSwapDataPushes, error) {
// An atomic swap is of the form:
// IF
// SIZE <secret size> EQUALVERIFY SHA256 <32-byte secret> EQUALVERIFY DUP
// HASH160 <20-byte recipient hash>
// ELSE
// <locktime> CHECKLOCKTIMEVERIFY DROP DUP HASH160 <20-byte refund hash>
// ENDIF
// EQUALVERIFY CHECKSIG
type templateMatch struct {
expectCanonicalInt bool
maxIntBytes int
opcode byte
extractedInt int64
extractedData []byte
}
var template = [20]templateMatch{
{opcode: OP_IF},
{opcode: OP_SIZE},
{expectCanonicalInt: true, maxIntBytes: maxScriptNumLen},
{opcode: OP_EQUALVERIFY},
{opcode: OP_SHA256},
{opcode: OP_DATA_32},
{opcode: OP_EQUALVERIFY},
{opcode: OP_DUP},
{opcode: OP_HASH160},
{opcode: OP_DATA_20},
{opcode: OP_ELSE},
{expectCanonicalInt: true, maxIntBytes: cltvMaxScriptNumLen},
{opcode: OP_CHECKLOCKTIMEVERIFY},
{opcode: OP_DROP},
{opcode: OP_DUP},
{opcode: OP_HASH160},
{opcode: OP_DATA_20},
{opcode: OP_ENDIF},
{opcode: OP_EQUALVERIFY},
{opcode: OP_CHECKSIG},
}
var templateOffset int
tokenizer := MakeScriptTokenizer(version, pkScript)
for tokenizer.Next() {
// Not an atomic swap script if it has more opcodes than expected in the
// template.
if templateOffset >= len(template) {
return nil, nil
}
op := tokenizer.Opcode()
data := tokenizer.Data()
tplEntry := &template[templateOffset]
if tplEntry.expectCanonicalInt {
switch {
case data != nil:
val, err := makeScriptNum(data, true, tplEntry.maxIntBytes)
if err != nil {
return nil, err
}
tplEntry.extractedInt = int64(val)
case isSmallInt(op):
tplEntry.extractedInt = int64(asSmallInt(op))
// Not an atomic swap script if the opcode does not push an int.
default:
return nil, nil
}
} else {
if op != tplEntry.opcode {
return nil, nil
}
tplEntry.extractedData = data
}
templateOffset++
}
if err := tokenizer.Err(); err != nil {
return nil, err
}
if !tokenizer.Done() || templateOffset != len(template) {
return nil, nil
}
// At this point, the script appears to be an atomic swap, so populate and
// return the extacted data.
pushes := AtomicSwapDataPushes{
SecretSize: template[2].extractedInt,
LockTime: template[11].extractedInt,
}
copy(pushes.SecretHash[:], template[5].extractedData)
copy(pushes.RecipientHash160[:], template[9].extractedData)
copy(pushes.RefundHash160[:], template[16].extractedData)
return &pushes, nil
}