diff --git a/txscript/address.go b/txscript/address.go deleted file mode 100644 index bc184909..00000000 --- a/txscript/address.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) 2013-2015 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package txscript - -import ( - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcutil" -) - -// 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. -func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (ScriptClass, []btcutil.Address, int, error) { - var addrs []btcutil.Address - var requiredSigs int - - // No valid addresses or required signatures if the script doesn't - // parse. - pops, err := parseScript(pkScript) - if err != nil { - return NonStandardTy, nil, 0, err - } - - scriptClass := typeOfScript(pops) - switch scriptClass { - case PubKeyHashTy: - // A pay-to-pubkey-hash script is of the form: - // OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG - // Therefore the pubkey hash is the 3rd item on the stack. - // Skip the pubkey hash if it's invalid for some reason. - requiredSigs = 1 - addr, err := btcutil.NewAddressPubKeyHash(pops[2].data, - chainParams) - if err == nil { - addrs = append(addrs, addr) - } - - case PubKeyTy: - // A pay-to-pubkey script is of the form: - // OP_CHECKSIG - // Therefore the pubkey is the first item on the stack. - // Skip the pubkey if it's invalid for some reason. - requiredSigs = 1 - addr, err := btcutil.NewAddressPubKey(pops[0].data, chainParams) - if err == nil { - addrs = append(addrs, addr) - } - - case ScriptHashTy: - // A pay-to-script-hash script is of the form: - // OP_HASH160 OP_EQUAL - // Therefore the script hash is the 2nd item on the stack. - // Skip the script hash if it's invalid for some reason. - requiredSigs = 1 - addr, err := btcutil.NewAddressScriptHashFromHash(pops[1].data, - chainParams) - if err == nil { - addrs = append(addrs, addr) - } - - case MultiSigTy: - // A multi-signature script is of the form: - // ... OP_CHECKMULTISIG - // Therefore the number of required signatures is the 1st item - // on the stack and the number of public keys is the 2nd to last - // item on the stack. - requiredSigs = asSmallInt(pops[0].opcode) - numPubKeys := asSmallInt(pops[len(pops)-2].opcode) - - // Extract the public keys while skipping any that are invalid. - addrs = make([]btcutil.Address, 0, numPubKeys) - for i := 0; i < numPubKeys; i++ { - addr, err := btcutil.NewAddressPubKey(pops[i+1].data, - chainParams) - if err == nil { - addrs = append(addrs, addr) - } - } - - case NullDataTy: - // Null data transactions have no addresses or required - // signatures. - - case NonStandardTy: - // Don't attempt to extract addresses or required signatures for - // nonstandard transactions. - } - - return scriptClass, addrs, requiredSigs, nil -} diff --git a/txscript/address_test.go b/txscript/address_test.go deleted file mode 100644 index 36d79539..00000000 --- a/txscript/address_test.go +++ /dev/null @@ -1,368 +0,0 @@ -// Copyright (c) 2013-2015 The btcsuite developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. -package txscript_test - -import ( - "encoding/hex" - "reflect" - "testing" - - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcutil" -) - -// decodeHex decodes the passed hex string and returns the resulting bytes. It -// panics if an error occurs. This is only used in the tests as a helper since -// the only way it can fail is if there is an error in the test source code. -func decodeHex(hexStr string) []byte { - b, err := hex.DecodeString(hexStr) - if err != nil { - panic("invalid hex string in test source: err " + err.Error() + - ", hex: " + hexStr) - } - - return b -} - -// newAddressPubKey returns a new btcutil.AddressPubKey from the provided -// serialized public key. It panics if an error occurs. This is only used in -// the tests as a helper since the only way it can fail is if there is an error -// in the test source code. -func newAddressPubKey(serializedPubKey []byte) btcutil.Address { - addr, err := btcutil.NewAddressPubKey(serializedPubKey, - &chaincfg.MainNetParams) - if err != nil { - panic("invalid public key in test source") - } - - return addr -} - -// newAddressPubKeyHash returns a new btcutil.AddressPubKeyHash from the -// provided hash. It panics if an error occurs. This is only used in the tests -// as a helper since the only way it can fail is if there is an error in the -// test source code. -func newAddressPubKeyHash(pkHash []byte) btcutil.Address { - addr, err := btcutil.NewAddressPubKeyHash(pkHash, &chaincfg.MainNetParams) - if err != nil { - panic("invalid public key hash in test source") - } - - return addr -} - -// newAddressScriptHash returns a new btcutil.AddressScriptHash from the -// provided hash. It panics if an error occurs. This is only used in the tests -// as a helper since the only way it can fail is if there is an error in the -// test source code. -func newAddressScriptHash(scriptHash []byte) btcutil.Address { - addr, err := btcutil.NewAddressScriptHashFromHash(scriptHash, - &chaincfg.MainNetParams) - if err != nil { - panic("invalid script hash in test source") - } - - return addr -} - -// TestExtractPkScriptAddrs ensures that extracting the type, addresses, and -// number of required signatures from PkScripts works as intended. -func TestExtractPkScriptAddrs(t *testing.T) { - tests := []struct { - name string - script []byte - addrs []btcutil.Address - reqSigs int - class txscript.ScriptClass - }{ - { - name: "standard p2pk with compressed pubkey (0x02)", - script: decodeHex("2102192d74d0cb94344c9569c2e7790157" + - "3d8d7903c3ebec3a957724895dca52c6b4ac"), - addrs: []btcutil.Address{ - newAddressPubKey(decodeHex("02192d74d0cb94344" + - "c9569c2e77901573d8d7903c3ebec3a95772" + - "4895dca52c6b4")), - }, - reqSigs: 1, - class: txscript.PubKeyTy, - }, - { - name: "standard p2pk with uncompressed pubkey (0x04)", - script: decodeHex("410411db93e1dcdb8a016b49840f8c53bc" + - "1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb" + - "84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643" + - "f656b412a3ac"), - addrs: []btcutil.Address{ - newAddressPubKey(decodeHex("0411db93e1dcdb8a0" + - "16b49840f8c53bc1eb68a382e97b1482ecad" + - "7b148a6909a5cb2e0eaddfb84ccf9744464f" + - "82e160bfa9b8b64f9d4c03f999b8643f656b" + - "412a3")), - }, - reqSigs: 1, - class: txscript.PubKeyTy, - }, - { - name: "standard p2pk with hybrid pubkey (0x06)", - script: decodeHex("4106192d74d0cb94344c9569c2e7790157" + - "3d8d7903c3ebec3a957724895dca52c6b40d45264838" + - "c0bd96852662ce6a847b197376830160c6d2eb5e6a4c" + - "44d33f453eac"), - addrs: []btcutil.Address{ - newAddressPubKey(decodeHex("06192d74d0cb94344" + - "c9569c2e77901573d8d7903c3ebec3a95772" + - "4895dca52c6b40d45264838c0bd96852662c" + - "e6a847b197376830160c6d2eb5e6a4c44d33" + - "f453e")), - }, - reqSigs: 1, - class: txscript.PubKeyTy, - }, - { - name: "standard p2pk with compressed pubkey (0x03)", - script: decodeHex("2103b0bd634234abbb1ba1e986e884185c" + - "61cf43e001f9137f23c2c409273eb16e65ac"), - addrs: []btcutil.Address{ - newAddressPubKey(decodeHex("03b0bd634234abbb1" + - "ba1e986e884185c61cf43e001f9137f23c2c" + - "409273eb16e65")), - }, - reqSigs: 1, - class: txscript.PubKeyTy, - }, - { - name: "2nd standard p2pk with uncompressed pubkey (0x04)", - script: decodeHex("4104b0bd634234abbb1ba1e986e884185c" + - "61cf43e001f9137f23c2c409273eb16e6537a576782e" + - "ba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c" + - "1e0908ef7bac"), - addrs: []btcutil.Address{ - newAddressPubKey(decodeHex("04b0bd634234abbb1" + - "ba1e986e884185c61cf43e001f9137f23c2c" + - "409273eb16e6537a576782eba668a7ef8bd3" + - "b3cfb1edb7117ab65129b8a2e681f3c1e090" + - "8ef7b")), - }, - reqSigs: 1, - class: txscript.PubKeyTy, - }, - { - name: "standard p2pk with hybrid pubkey (0x07)", - script: decodeHex("4107b0bd634234abbb1ba1e986e884185c" + - "61cf43e001f9137f23c2c409273eb16e6537a576782e" + - "ba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c" + - "1e0908ef7bac"), - addrs: []btcutil.Address{ - newAddressPubKey(decodeHex("07b0bd634234abbb1" + - "ba1e986e884185c61cf43e001f9137f23c2c" + - "409273eb16e6537a576782eba668a7ef8bd3" + - "b3cfb1edb7117ab65129b8a2e681f3c1e090" + - "8ef7b")), - }, - reqSigs: 1, - class: txscript.PubKeyTy, - }, - { - name: "standard p2pkh", - script: decodeHex("76a914ad06dd6ddee55cbca9a9e3713bd7" + - "587509a3056488ac"), - addrs: []btcutil.Address{ - newAddressPubKeyHash(decodeHex("ad06dd6ddee55" + - "cbca9a9e3713bd7587509a30564")), - }, - reqSigs: 1, - class: txscript.PubKeyHashTy, - }, - { - name: "standard p2sh", - script: decodeHex("a91463bcc565f9e68ee0189dd5cc67f1b0" + - "e5f02f45cb87"), - addrs: []btcutil.Address{ - newAddressScriptHash(decodeHex("63bcc565f9e68" + - "ee0189dd5cc67f1b0e5f02f45cb")), - }, - reqSigs: 1, - class: txscript.ScriptHashTy, - }, - // from real tx 60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1, vout 0 - { - name: "standard 1 of 2 multisig", - script: decodeHex("514104cc71eb30d653c0c3163990c47b97" + - "6f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473" + - "e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11" + - "fcdd0d348ac4410461cbdcc5409fb4b4d42b51d33381" + - "354d80e550078cb532a34bfa2fcfdeb7d76519aecc62" + - "770f5b0e4ef8551946d8a540911abe3e7854a26f39f5" + - "8b25c15342af52ae"), - addrs: []btcutil.Address{ - newAddressPubKey(decodeHex("04cc71eb30d653c0c" + - "3163990c47b976f3fb3f37cccdcbedb169a1" + - "dfef58bbfbfaff7d8a473e7e2e6d317b87ba" + - "fe8bde97e3cf8f065dec022b51d11fcdd0d3" + - "48ac4")), - newAddressPubKey(decodeHex("0461cbdcc5409fb4b" + - "4d42b51d33381354d80e550078cb532a34bf" + - "a2fcfdeb7d76519aecc62770f5b0e4ef8551" + - "946d8a540911abe3e7854a26f39f58b25c15" + - "342af")), - }, - reqSigs: 1, - class: txscript.MultiSigTy, - }, - // from real tx d646f82bd5fbdb94a36872ce460f97662b80c3050ad3209bef9d1e398ea277ab, vin 1 - { - name: "standard 2 of 3 multisig", - script: decodeHex("524104cb9c3c222c5f7a7d3b9bd152f363" + - "a0b6d54c9eb312c4d4f9af1e8551b6c421a6a4ab0e29" + - "105f24de20ff463c1c91fcf3bf662cdde4783d4799f7" + - "87cb7c08869b4104ccc588420deeebea22a7e900cc8b" + - "68620d2212c374604e3487ca08f1ff3ae12bdc639514" + - "d0ec8612a2d3c519f084d9a00cbbe3b53d071e9b09e7" + - "1e610b036aa24104ab47ad1939edcb3db65f7fedea62" + - "bbf781c5410d3f22a7a3a56ffefb2238af8627363bdf" + - "2ed97c1f89784a1aecdb43384f11d2acc64443c7fc29" + - "9cef0400421a53ae"), - addrs: []btcutil.Address{ - newAddressPubKey(decodeHex("04cb9c3c222c5f7a7" + - "d3b9bd152f363a0b6d54c9eb312c4d4f9af1" + - "e8551b6c421a6a4ab0e29105f24de20ff463" + - "c1c91fcf3bf662cdde4783d4799f787cb7c0" + - "8869b")), - newAddressPubKey(decodeHex("04ccc588420deeebe" + - "a22a7e900cc8b68620d2212c374604e3487c" + - "a08f1ff3ae12bdc639514d0ec8612a2d3c51" + - "9f084d9a00cbbe3b53d071e9b09e71e610b0" + - "36aa2")), - newAddressPubKey(decodeHex("04ab47ad1939edcb3" + - "db65f7fedea62bbf781c5410d3f22a7a3a56" + - "ffefb2238af8627363bdf2ed97c1f89784a1" + - "aecdb43384f11d2acc64443c7fc299cef040" + - "0421a")), - }, - reqSigs: 2, - class: txscript.MultiSigTy, - }, - - // The below are nonstandard script due to things such as - // invalid pubkeys, failure to parse, and not being of a - // standard form. - - { - name: "p2pk with uncompressed pk missing OP_CHECKSIG", - script: decodeHex("410411db93e1dcdb8a016b49840f8c53bc" + - "1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb" + - "84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643" + - "f656b412a3"), - addrs: nil, - reqSigs: 0, - class: txscript.NonStandardTy, - }, - { - name: "valid signature from a sigscript - no addresses", - script: decodeHex("47304402204e45e16932b8af514961a1d3" + - "a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220" + - "181522ec8eca07de4860a4acdd12909d831cc56cbbac" + - "4622082221a8768d1d0901"), - addrs: nil, - reqSigs: 0, - class: txscript.NonStandardTy, - }, - // Note the technically the pubkey is the second item on the - // stack, but since the address extraction intentionally only - // works with standard PkScripts, this should not return any - // addresses. - { - name: "valid sigscript to reedeem p2pk - no addresses", - script: decodeHex("493046022100ddc69738bf2336318e4e04" + - "1a5a77f305da87428ab1606f023260017854350ddc02" + - "2100817af09d2eec36862d16009852b7e3a0f6dd7659" + - "8290b7834e1453660367e07a014104cd4240c198e125" + - "23b6f9cb9f5bed06de1ba37e96a1bbd13745fcf9d11c" + - "25b1dff9a519675d198804ba9962d3eca2d5937d58e5" + - "a75a71042d40388a4d307f887d"), - addrs: nil, - reqSigs: 0, - class: txscript.NonStandardTy, - }, - // from real tx 691dd277dc0e90a462a3d652a1171686de49cf19067cd33c7df0392833fb986a, vout 0 - // invalid public keys - { - name: "1 of 3 multisig with invalid pubkeys", - script: decodeHex("51411c2200007353455857696b696c6561" + - "6b73204361626c6567617465204261636b75700a0a63" + - "61626c65676174652d3230313031323034313831312e" + - "377a0a0a446f41776e6c6f61642074686520666f6c6c" + - "6f77696e67207472616e73616374696f6e7320776974" + - "68205361746f736869204e616b616d6f746f27732064" + - "6f776e6c6f61416420746f6f6c2077686963680a6361" + - "6e20626520666f756e6420696e207472616e73616374" + - "696f6e20366335336364393837313139656637393764" + - "35616463636453ae"), - addrs: []btcutil.Address{}, - reqSigs: 1, - class: txscript.MultiSigTy, - }, - // from real tx: 691dd277dc0e90a462a3d652a1171686de49cf19067cd33c7df0392833fb986a, vout 44 - // invalid public keys - { - name: "1 of 3 multisig with invalid pubkeys 2", - script: decodeHex("5141346333656332353963373464616365" + - "36666430383862343463656638630a63363662633139" + - "39366338623934613338313162333635363138666531" + - "65396231623541366361636365393933613339383861" + - "34363966636336643664616266640a32363633636661" + - "39636634633033633630396335393363336539316665" + - "64653730323921313233646434326432353633396433" + - "38613663663530616234636434340a00000053ae"), - addrs: []btcutil.Address{}, - reqSigs: 1, - class: txscript.MultiSigTy, - }, - { - name: "empty script", - script: []byte{}, - addrs: nil, - reqSigs: 0, - class: txscript.NonStandardTy, - }, - { - name: "script that does not parse", - script: []byte{txscript.OP_DATA_45}, - addrs: nil, - reqSigs: 0, - class: txscript.NonStandardTy, - }, - } - - t.Logf("Running %d tests.", len(tests)) - for i, test := range tests { - class, addrs, reqSigs, err := txscript.ExtractPkScriptAddrs( - test.script, &chaincfg.MainNetParams) - if err != nil { - } - - if !reflect.DeepEqual(addrs, test.addrs) { - t.Errorf("ExtractPkScriptAddrs #%d (%s) unexpected "+ - "addresses\ngot %v\nwant %v", i, test.name, - addrs, test.addrs) - continue - } - - if reqSigs != test.reqSigs { - t.Errorf("ExtractPkScriptAddrs #%d (%s) unexpected "+ - "number of required signatures - got %d, "+ - "want %d", i, test.name, reqSigs, test.reqSigs) - continue - } - - if class != test.class { - t.Errorf("ExtractPkScriptAddrs #%d (%s) unexpected "+ - "script type - got %s, want %s", i, test.name, - class, test.class) - continue - } - } -} diff --git a/txscript/engine.go b/txscript/engine.go index 0decebc2..93855ea2 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -59,24 +59,6 @@ const ( // ScriptVerifyStrictEncoding defines that signature scripts and // public keys must follow the strict encoding requirements. ScriptVerifyStrictEncoding - - // 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 ) const ( diff --git a/txscript/script.go b/txscript/script.go index 7457050d..333774c6 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -11,13 +11,6 @@ import ( "time" "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 ) // Bip16Activation is the timestamp where BIP0016 is valid to use in the @@ -48,40 +41,6 @@ const ( MaxScriptElementSize = 520 // Max bytes pushable to the stack. ) -// 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. - ScriptHashTy // Pay to script hash. - MultiSigTy // Multi signature. - NullDataTy // Empty data-only (provably prunable). -) - -// scriptClassToName houses the human-readable strings which describe each -// script class. -var scriptClassToName = []string{ - NonStandardTy: "nonstandard", - PubKeyTy: "pubkey", - PubKeyHashTy: "pubkeyhash", - ScriptHashTy: "scripthash", - MultiSigTy: "multisig", - NullDataTy: "nulldata", -} - -// 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] -} - // isSmallInt returns whether or not the opcode is considered a small integer, // which is an OP_0, or OP_1 through OP_16. func isSmallInt(op *opcode) bool { @@ -91,27 +50,6 @@ func isSmallInt(op *opcode) bool { return false } -// isPubkey returns true if the script passed is a pay-to-pubkey transaction, -// false otherwise. -func isPubkey(pops []parsedOpcode) bool { - // Valid pubkeys are either 33 or 65 bytes. - return len(pops) == 2 && - (len(pops[0].data) == 33 || len(pops[0].data) == 65) && - pops[1].opcode.value == OP_CHECKSIG -} - -// isPubkeyHash returns true if the script passed is a pay-to-pubkey-hash -// transaction, false otherwise. -func isPubkeyHash(pops []parsedOpcode) bool { - return len(pops) == 5 && - pops[0].opcode.value == OP_DUP && - pops[1].opcode.value == OP_HASH160 && - pops[2].opcode.value == OP_DATA_20 && - pops[3].opcode.value == OP_EQUALVERIFY && - pops[4].opcode.value == OP_CHECKSIG - -} - // isScriptHash returns true if the script passed is a pay-to-script-hash // transaction, false otherwise. func isScriptHash(pops []parsedOpcode) bool { @@ -131,50 +69,6 @@ func IsPayToScriptHash(script []byte) bool { return isScriptHash(pops) } -// isMultiSig returns true if the passed script is a multisig transaction, false -// otherwise. -func isMultiSig(pops []parsedOpcode) bool { - // The absolute minimum is 1 pubkey: - // OP_0/OP_1-16 OP_1 OP_CHECKMULTISIG - l := len(pops) - if l < 4 { - return false - } - if !isSmallInt(pops[0].opcode) { - return false - } - if !isSmallInt(pops[l-2].opcode) { - return false - } - if pops[l-1].opcode.value != OP_CHECKMULTISIG { - return false - } - for _, pop := range pops[1 : l-2] { - // Valid pubkeys are either 33 or 65 bytes. - if len(pop.data) != 33 && len(pop.data) != 65 { - return false - } - } - return true -} - -// isNullData returns true if the passed script is a null data transaction, -// false otherwise. -func isNullData(pops []parsedOpcode) bool { - // A nulldata transaction is either a single OP_RETURN or an - // OP_RETURN SMALLDATA (where SMALLDATA is a data push up to - // maxDataCarrierSize bytes). - l := len(pops) - if l == 1 && pops[0].opcode.value == OP_RETURN { - return true - } - - return l == 2 && - pops[0].opcode.value == OP_RETURN && - pops[1].opcode.value <= OP_PUSHDATA4 && - len(pops[1].data) <= maxDataCarrierSize -} - // 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 @@ -204,34 +98,6 @@ func IsPushOnlyScript(script []byte) bool { return isPushOnly(pops) } -// scriptType returns the type of the script being inspected from the known -// standard types. -func typeOfScript(pops []parsedOpcode) ScriptClass { - if isPubkey(pops) { - return PubKeyTy - } else if isPubkeyHash(pops) { - return PubKeyHashTy - } else if isScriptHash(pops) { - return ScriptHashTy - } else if isMultiSig(pops) { - return MultiSigTy - } else if isNullData(pops) { - return NullDataTy - } - 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 { - pops, err := parseScript(script) - if err != nil { - return NonStandardTy - } - return typeOfScript(pops) -} - // 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. @@ -602,221 +468,3 @@ func GetPreciseSigOpCount(scriptSig, scriptPubKey []byte, bip16 bool) int { shPops, _ := parseScript(shScript) return getSigOpCount(shPops, true) } - -// 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() -} - -// 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() -} - -// 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) { - switch addr := addr.(type) { - case *btcutil.AddressPubKeyHash: - if addr == nil { - return nil, ErrUnsupportedAddress - } - return payToPubKeyHashScript(addr.ScriptAddress()) - - case *btcutil.AddressScriptHash: - if addr == nil { - return nil, ErrUnsupportedAddress - } - return payToScriptHashScript(addr.ScriptAddress()) - - case *btcutil.AddressPubKey: - if addr == nil { - return nil, ErrUnsupportedAddress - } - return payToPubKeyScript(addr.ScriptAddress()) - } - - return nil, ErrUnsupportedAddress -} - -// 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 []*btcutil.AddressPubKey, nrequired int) ([]byte, error) { - if len(pubkeys) < nrequired { - return nil, ErrBadNumRequired - } - - 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() -} - -// 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). -func expectedInputs(pops []parsedOpcode, class ScriptClass) int { - switch class { - case PubKeyTy: - return 1 - - case PubKeyHashTy: - return 2 - - case ScriptHashTy: - // 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(pops[0].opcode) + 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 -func CalcScriptInfo(sigScript, pkScript []byte, bip16 bool) (*ScriptInfo, error) { - sigPops, err := parseScript(sigScript) - if err != nil { - return nil, err - } - - pkPops, err := parseScript(pkScript) - if err != nil { - return nil, err - } - - // Push only sigScript makes little sense. - si := new(ScriptInfo) - si.PkScriptClass = typeOfScript(pkPops) - - // Can't have a pkScript that doesn't just push data. - if !isPushOnly(sigPops) { - return nil, ErrStackNonPushOnly - } - - si.ExpectedInputs = expectedInputs(pkPops, si.PkScriptClass) - - // All entries pushed to stack (or are OP_RESERVED and exec will fail). - si.NumInputs = len(sigPops) - - // Count sigops taking into account pay-to-script-hash. - if si.PkScriptClass == ScriptHashTy && bip16 { - // The pay-to-hash-script is the final data push of the - // signature script. - script := sigPops[len(sigPops)-1].data - shPops, err := parseScript(script) - if err != nil { - return nil, err - } - - shInputs := expectedInputs(shPops, typeOfScript(shPops)) - if shInputs == -1 { - si.ExpectedInputs = -1 - } else { - si.ExpectedInputs += shInputs - } - si.SigOps = getSigOpCount(shPops, true) - } else { - si.SigOps = getSigOpCount(pkPops, true) - } - - 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. -func CalcMultiSigStats(script []byte) (int, int, error) { - pops, err := parseScript(script) - if err != nil { - return 0, 0, err - } - - // A multi-signature script is of the pattern: - // NUM_SIGS PUBKEY PUBKEY PUBKEY... NUM_PUBKEYS OP_CHECKMULTISIG - // Therefore the number of signatures is the oldest item on the stack - // and the number of pubkeys is the 2nd to last. Also, the absolute - // minimum for a multi-signature script is 1 pubkey, so at least 4 - // items must be on the stack per: - // OP_1 PUBKEY OP_1 OP_CHECKMULTISIG - if len(pops) < 4 { - return 0, 0, ErrStackUnderflow - } - - numSigs := asSmallInt(pops[0].opcode) - numPubKeys := asSmallInt(pops[len(pops)-2].opcode) - return numPubKeys, numSigs, nil -} - -// 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. -func PushedData(script []byte) ([][]byte, error) { - pops, err := parseScript(script) - if err != nil { - return nil, err - } - - var data [][]byte - for _, pop := range pops { - if pop.data != nil { - data = append(data, pop.data) - } else if pop.opcode.value == OP_0 { - data = append(data, []byte{}) - } - } - return data, nil -} diff --git a/txscript/script_test.go b/txscript/script_test.go index bbf47599..b90a49de 100644 --- a/txscript/script_test.go +++ b/txscript/script_test.go @@ -8,10 +8,8 @@ import ( "bytes" "testing" - "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" ) // builderScript is a convenience function which is used in the tests. It @@ -1750,198 +1748,6 @@ func TestGetPreciseSignOps(t *testing.T) { } } -type scriptInfoTest struct { - name string - sigScript []byte - pkScript []byte - bip16 bool - scriptInfo txscript.ScriptInfo - scriptInfoErr error -} - -func TestScriptInfo(t *testing.T) { - t.Parallel() - - for _, test := range txTests { - si, err := txscript.CalcScriptInfo( - test.tx.TxIn[test.idx].SignatureScript, - test.pkScript, test.bip16) - 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) - continue - } - } - - extraTests := []scriptInfoTest{ - { - // Invented scripts, the hashes do not match - name: "pkscript doesn't parse", - sigScript: []byte{txscript.OP_TRUE, - txscript.OP_DATA_1, 81, - txscript.OP_DATA_8, - txscript.OP_2DUP, txscript.OP_EQUAL, - txscript.OP_NOT, txscript.OP_VERIFY, - txscript.OP_ABS, txscript.OP_SWAP, - txscript.OP_ABS, txscript.OP_EQUAL, - }, - // truncated version of test below: - pkScript: []byte{txscript.OP_HASH160, - txscript.OP_DATA_20, - 0xfe, 0x44, 0x10, 0x65, 0xb6, 0x53, 0x22, 0x31, - 0xde, 0x2f, 0xac, 0x56, 0x31, 0x52, 0x20, 0x5e, - 0xc4, 0xf5, 0x9c, - }, - bip16: true, - scriptInfoErr: txscript.ErrStackShortScript, - }, - { - name: "sigScript doesn't parse", - // Truncated version of p2sh script below. - sigScript: []byte{txscript.OP_TRUE, - txscript.OP_DATA_1, 81, - txscript.OP_DATA_8, - txscript.OP_2DUP, txscript.OP_EQUAL, - txscript.OP_NOT, txscript.OP_VERIFY, - txscript.OP_ABS, txscript.OP_SWAP, - txscript.OP_ABS, - }, - pkScript: []byte{txscript.OP_HASH160, - txscript.OP_DATA_20, - 0xfe, 0x44, 0x10, 0x65, 0xb6, 0x53, 0x22, 0x31, - 0xde, 0x2f, 0xac, 0x56, 0x31, 0x52, 0x20, 0x5e, - 0xc4, 0xf5, 0x9c, 0x74, txscript.OP_EQUAL, - }, - bip16: true, - scriptInfoErr: txscript.ErrStackShortScript, - }, - { - // Invented scripts, the hashes do not match - name: "p2sh standard script", - sigScript: []byte{txscript.OP_TRUE, - txscript.OP_DATA_1, 81, - txscript.OP_DATA_25, - txscript.OP_DUP, txscript.OP_HASH160, - txscript.OP_DATA_20, 0x1, 0x2, 0x3, 0x4, 0x5, - 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, - 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, - txscript.OP_EQUALVERIFY, txscript.OP_CHECKSIG, - }, - pkScript: []byte{txscript.OP_HASH160, - txscript.OP_DATA_20, - 0xfe, 0x44, 0x10, 0x65, 0xb6, 0x53, 0x22, 0x31, - 0xde, 0x2f, 0xac, 0x56, 0x31, 0x52, 0x20, 0x5e, - 0xc4, 0xf5, 0x9c, 0x74, txscript.OP_EQUAL, - }, - bip16: true, - scriptInfo: txscript.ScriptInfo{ - PkScriptClass: txscript.ScriptHashTy, - NumInputs: 3, - ExpectedInputs: 3, // nonstandard p2sh. - SigOps: 1, - }, - }, - { - // from 567a53d1ce19ce3d07711885168484439965501536d0d0294c5d46d46c10e53b - // from the blockchain. - name: "p2sh nonstandard script", - sigScript: []byte{txscript.OP_TRUE, - txscript.OP_DATA_1, 81, - txscript.OP_DATA_8, - txscript.OP_2DUP, txscript.OP_EQUAL, - txscript.OP_NOT, txscript.OP_VERIFY, - txscript.OP_ABS, txscript.OP_SWAP, - txscript.OP_ABS, txscript.OP_EQUAL, - }, - pkScript: []byte{txscript.OP_HASH160, - txscript.OP_DATA_20, - 0xfe, 0x44, 0x10, 0x65, 0xb6, 0x53, 0x22, 0x31, - 0xde, 0x2f, 0xac, 0x56, 0x31, 0x52, 0x20, 0x5e, - 0xc4, 0xf5, 0x9c, 0x74, txscript.OP_EQUAL, - }, - bip16: true, - scriptInfo: txscript.ScriptInfo{ - PkScriptClass: txscript.ScriptHashTy, - NumInputs: 3, - ExpectedInputs: -1, // nonstandard p2sh. - SigOps: 0, - }, - }, - { - // Script is invented, numbers all fake. - name: "multisig script", - sigScript: []byte{txscript.OP_TRUE, - txscript.OP_TRUE, txscript.OP_TRUE, - txscript.OP_0, // Extra arg for OP_CHECKMULTISIG bug - }, - pkScript: []byte{ - txscript.OP_3, txscript.OP_DATA_33, - 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, - 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, - 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, - 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, - txscript.OP_DATA_33, - 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, - 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, - 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, - 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, - txscript.OP_DATA_33, - 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, - 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, - 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, - 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, - txscript.OP_3, txscript.OP_CHECKMULTISIG, - }, - bip16: true, - scriptInfo: txscript.ScriptInfo{ - PkScriptClass: txscript.MultiSigTy, - NumInputs: 4, - ExpectedInputs: 4, - SigOps: 3, - }, - }, - } - - for _, test := range extraTests { - si, err := txscript.CalcScriptInfo(test.sigScript, - test.pkScript, test.bip16) - 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) - continue - } - } - -} - type removeOpcodeTest struct { name string before []byte @@ -2155,6 +1961,7 @@ func testRemoveOpcodeByData(t *testing.T, test *removeOpcodeByDataTest) { " got: \"%v\"", test.name, test.after, result) } } + func TestRemoveOpcodeByDatas(t *testing.T) { t.Parallel() @@ -2165,243 +1972,6 @@ func TestRemoveOpcodeByDatas(t *testing.T) { // Tests for the script type logic -type scriptTypeTest struct { - name string - script []byte - scripttype txscript.ScriptClass -} - -var scriptTypeTests = []scriptTypeTest{ - // tx 0437cd7f8525ceed2324359c2d0ba26006d92d85. - { - name: "Pay Pubkey", - script: []byte{ - txscript.OP_DATA_65, - 0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, 0x01, - 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, 0xb6, - 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, 0xd7, - 0xb1, 0x48, 0xa6, 0x90, 0x9a, 0x5c, 0xb2, 0xe0, 0xea, - 0xdd, 0xfb, 0x84, 0xcc, 0xf9, 0x74, 0x44, 0x64, 0xf8, - 0x2e, 0x16, 0x0b, 0xfa, 0x9b, 0x8b, 0x64, 0xf9, 0xd4, - 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, 0xb4, - 0x12, 0xa3, - txscript.OP_CHECKSIG, - }, - scripttype: txscript.PubKeyTy, - }, - // tx 599e47a8114fe098103663029548811d2651991b62397e057f0c863c2bc9f9ea - { - name: "Pay PubkeyHash", - script: []byte{ - txscript.OP_DUP, - txscript.OP_HASH160, - txscript.OP_DATA_20, - 0x66, 0x0d, 0x4e, 0xf3, 0xa7, 0x43, 0xe3, 0xe6, 0x96, - 0xad, 0x99, 0x03, 0x64, 0xe5, 0x55, 0xc2, 0x71, 0xad, - 0x50, 0x4b, - txscript.OP_EQUALVERIFY, - txscript.OP_CHECKSIG, - }, - scripttype: txscript.PubKeyHashTy, - }, - // part of tx 6d36bc17e947ce00bb6f12f8e7a56a1585c5a36188ffa2b05e10b4743273a74b - // codeseparator parts have been elided. (bitcoind's checks for multisig - // type doesn't have codesep etc either. - { - name: "multisig", - script: []byte{ - txscript.OP_TRUE, - txscript.OP_DATA_33, - 0x02, 0x32, 0xab, 0xdc, 0x89, 0x3e, 0x7f, 0x06, 0x31, - 0x36, 0x4d, 0x7f, 0xd0, 0x1c, 0xb3, 0x3d, 0x24, 0xda, - 0x45, 0x32, 0x9a, 0x00, 0x35, 0x7b, 0x3a, 0x78, 0x86, - 0x21, 0x1a, 0xb4, 0x14, 0xd5, 0x5a, - txscript.OP_TRUE, - txscript.OP_CHECKMULTISIG, - }, - scripttype: txscript.MultiSigTy, - }, - // tx e5779b9e78f9650debc2893fd9636d827b26b4ddfa6a8172fe8708c924f5c39d - // P2SH - { - name: "P2SH", - script: []byte{ - txscript.OP_HASH160, - txscript.OP_DATA_20, - 0x43, 0x3e, 0xc2, 0xac, 0x1f, 0xfa, 0x1b, 0x7b, 0x7d, - 0x02, 0x7f, 0x56, 0x45, 0x29, 0xc5, 0x71, 0x97, 0xf9, - 0xae, 0x88, - txscript.OP_EQUAL, - }, - scripttype: txscript.ScriptHashTy, - }, - // Nulldata with no data at all. - { - name: "nulldata", - script: []byte{ - txscript.OP_RETURN, - }, - scripttype: txscript.NullDataTy, - }, - // Nulldata with small data. - { - name: "nulldata2", - script: []byte{ - txscript.OP_RETURN, - txscript.OP_DATA_8, - 0x04, 0x67, 0x08, 0xaf, 0xdb, 0x0f, 0xe5, 0x54, - }, - scripttype: txscript.NullDataTy, - }, - // Nulldata with max allowed data. - { - name: "nulldata3", - script: []byte{ - txscript.OP_RETURN, - txscript.OP_PUSHDATA1, - 0x50, // 80 - 0x04, 0x67, 0x08, 0xaf, 0xdb, 0x0f, 0xe5, 0x54, - 0x82, 0x71, 0x96, 0x7f, 0x1a, 0x67, 0x13, 0x0b, - 0x71, 0x05, 0xcd, 0x6a, 0x82, 0x8e, 0x03, 0x90, - 0x9a, 0x67, 0x96, 0x2e, 0x0e, 0xa1, 0xf6, 0x1d, - 0xeb, 0x64, 0x9f, 0x6b, 0xc3, 0xf4, 0xce, 0xf3, - 0x04, 0x67, 0x08, 0xaf, 0xdb, 0x0f, 0xe5, 0x54, - 0x82, 0x71, 0x96, 0x7f, 0x1a, 0x67, 0x13, 0x0b, - 0x71, 0x05, 0xcd, 0x6a, 0x82, 0x8e, 0x03, 0x90, - 0x9a, 0x67, 0x96, 0x2e, 0x0e, 0xa1, 0xf6, 0x1d, - 0xeb, 0x64, 0x9f, 0x6b, 0xc3, 0xf4, 0xce, 0xf3, - }, - scripttype: txscript.NullDataTy, - }, - // Nulldata with more than max allowed data (so therefore nonstandard) - { - name: "nulldata4", - script: []byte{ - txscript.OP_RETURN, - txscript.OP_PUSHDATA1, - 0x51, // 81 - 0x04, 0x67, 0x08, 0xaf, 0xdb, 0x0f, 0xe5, 0x54, - 0x82, 0x71, 0x96, 0x7f, 0x1a, 0x67, 0x13, 0x0b, - 0x71, 0x05, 0xcd, 0x6a, 0x82, 0x8e, 0x03, 0x90, - 0x9a, 0x67, 0x96, 0x2e, 0x0e, 0xa1, 0xf6, 0x1d, - 0xeb, 0x64, 0x9f, 0x6b, 0xc3, 0xf4, 0xce, 0xf3, - 0x04, 0x67, 0x08, 0xaf, 0xdb, 0x0f, 0xe5, 0x54, - 0x82, 0x71, 0x96, 0x7f, 0x1a, 0x67, 0x13, 0x0b, - 0x71, 0x05, 0xcd, 0x6a, 0x82, 0x8e, 0x03, 0x90, - 0x9a, 0x67, 0x96, 0x2e, 0x0e, 0xa1, 0xf6, 0x1d, - 0xeb, 0x64, 0x9f, 0x6b, 0xc3, 0xf4, 0xce, 0xf3, - 0x08, - }, - scripttype: txscript.NonStandardTy, - }, - // Almost nulldata, but add an additional opcode after the data to make - // it nonstandard. - { - name: "nulldata5", - script: []byte{ - txscript.OP_RETURN, - txscript.OP_DATA_1, - 0x04, - txscript.OP_TRUE, - }, - scripttype: txscript.NonStandardTy, - }, // The next few are almost multisig (it is the more complex script type) - // but with various changes to make it fail. - { - // multisig but funny nsigs.. - name: "strange 1", - script: []byte{ - txscript.OP_DUP, - txscript.OP_DATA_33, - 0x02, 0x32, 0xab, 0xdc, 0x89, 0x3e, 0x7f, 0x06, 0x31, - 0x36, 0x4d, 0x7f, 0xd0, 0x1c, 0xb3, 0x3d, 0x24, 0xda, - 0x45, 0x32, 0x9a, 0x00, 0x35, 0x7b, 0x3a, 0x78, 0x86, - 0x21, 0x1a, 0xb4, 0x14, 0xd5, 0x5a, - txscript.OP_TRUE, - txscript.OP_CHECKMULTISIG, - }, - scripttype: txscript.NonStandardTy, - }, - { - name: "strange 2", - // multisig but funny pubkey. - script: []byte{ - txscript.OP_TRUE, - txscript.OP_TRUE, - txscript.OP_TRUE, - txscript.OP_CHECKMULTISIG, - }, - scripttype: txscript.NonStandardTy, - }, - { - name: "strange 3", - // multisig but no matching npubkeys opcode. - script: []byte{ - txscript.OP_TRUE, - txscript.OP_DATA_33, - 0x02, 0x32, 0xab, 0xdc, 0x89, 0x3e, 0x7f, 0x06, 0x31, - 0x36, 0x4d, 0x7f, 0xd0, 0x1c, 0xb3, 0x3d, 0x24, 0xda, - 0x45, 0x32, 0x9a, 0x00, 0x35, 0x7b, 0x3a, 0x78, 0x86, - 0x21, 0x1a, 0xb4, 0x14, 0xd5, 0x5a, - txscript.OP_DATA_33, - 0x02, 0x32, 0xab, 0xdc, 0x89, 0x3e, 0x7f, 0x06, 0x31, - 0x36, 0x4d, 0x7f, 0xd0, 0x1c, 0xb3, 0x3d, 0x24, 0xda, - 0x45, 0x32, 0x9a, 0x00, 0x35, 0x7b, 0x3a, 0x78, 0x86, - 0x21, 0x1a, 0xb4, 0x14, 0xd5, 0x5a, - // No number. - txscript.OP_CHECKMULTISIG, - }, - scripttype: txscript.NonStandardTy, - }, - { - name: "strange 4", - // multisig but with multisigverify - script: []byte{ - txscript.OP_TRUE, - txscript.OP_DATA_33, - 0x02, 0x32, 0xab, 0xdc, 0x89, 0x3e, 0x7f, 0x06, 0x31, - 0x36, 0x4d, 0x7f, 0xd0, 0x1c, 0xb3, 0x3d, 0x24, 0xda, - 0x45, 0x32, 0x9a, 0x00, 0x35, 0x7b, 0x3a, 0x78, 0x86, - 0x21, 0x1a, 0xb4, 0x14, 0xd5, 0x5a, - txscript.OP_TRUE, - txscript.OP_CHECKMULTISIGVERIFY, - }, - scripttype: txscript.NonStandardTy, - }, - { - name: "strange 5", - // multisig but wrong length. - script: []byte{ - txscript.OP_TRUE, - txscript.OP_CHECKMULTISIG, - }, - scripttype: txscript.NonStandardTy, - }, - { - name: "doesn't parse", - script: []byte{ - txscript.OP_DATA_5, 0x1, 0x2, 0x3, 0x4, - }, - scripttype: txscript.NonStandardTy, - }, -} - -func testScriptType(t *testing.T, test *scriptTypeTest) { - scripttype := txscript.GetScriptClass(test.script) - if scripttype != test.scripttype { - t.Errorf("%s: expected %s got %s", test.name, test.scripttype, - scripttype) - } -} - -func TestScriptTypes(t *testing.T) { - t.Parallel() - - for i := range scriptTypeTests { - testScriptType(t, &scriptTypeTests[i]) - } -} - func TestIsPayToScriptHash(t *testing.T) { t.Parallel() @@ -2415,472 +1985,6 @@ func TestIsPayToScriptHash(t *testing.T) { } } -func TestStringifyClass(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - scriptclass txscript.ScriptClass - stringed string - }{ - { - name: "nonstandardty", - scriptclass: txscript.NonStandardTy, - stringed: "nonstandard", - }, - { - name: "pubkey", - scriptclass: txscript.PubKeyTy, - stringed: "pubkey", - }, - { - name: "pubkeyhash", - scriptclass: txscript.PubKeyHashTy, - stringed: "pubkeyhash", - }, - { - name: "scripthash", - scriptclass: txscript.ScriptHashTy, - stringed: "scripthash", - }, - { - name: "multisigty", - scriptclass: txscript.MultiSigTy, - stringed: "multisig", - }, - { - name: "nulldataty", - scriptclass: txscript.NullDataTy, - stringed: "nulldata", - }, - { - name: "broken", - scriptclass: txscript.ScriptClass(255), - stringed: "Invalid", - }, - } - - for _, test := range tests { - typeString := test.scriptclass.String() - if typeString != test.stringed { - t.Errorf("%s: got \"%s\" expected \"%s\"", test.name, - typeString, test.stringed) - } - } -} - -// bogusAddress implements the btcutil.Address interface so the tests can ensure -// unsupported address types are handled properly. -type bogusAddress struct{} - -// EncodeAddress simply returns an empty string. It exists to satsify the -// btcutil.Address interface. -func (b *bogusAddress) EncodeAddress() string { - return "" -} - -// ScriptAddress simply returns an empty byte slice. It exists to satsify the -// btcutil.Address interface. -func (b *bogusAddress) ScriptAddress() []byte { - return []byte{} -} - -// IsForNet lies blatantly to satisfy the btcutil.Address interface. -func (b *bogusAddress) IsForNet(chainParams *chaincfg.Params) bool { - return true // why not? -} - -// String simply returns an empty string. It exists to satsify the -// btcutil.Address interface. -func (b *bogusAddress) String() string { - return "" -} - -func TestPayToAddrScript(t *testing.T) { - t.Parallel() - - // 1MirQ9bwyQcGVJPwKUgapu5ouK2E2Ey4gX - p2pkhMain, err := btcutil.NewAddressPubKeyHash([]byte{ - 0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc, - 0xc5, 0x4c, 0xe7, 0xd2, 0xa4, 0x91, 0xbb, 0x4a, 0x0e, 0x84, - }, &chaincfg.MainNetParams) - if err != nil { - t.Errorf("Unable to create public key hash address: %v", err) - return - } - - // Taken from transaction: - // b0539a45de13b3e0403909b8bd1a555b8cbe45fd4e3f3fda76f3a5f52835c29d - p2shMain, _ := btcutil.NewAddressScriptHashFromHash([]byte{ - 0xe8, 0xc3, 0x00, 0xc8, 0x79, 0x86, 0xef, 0xa8, 0x4c, 0x37, - 0xc0, 0x51, 0x99, 0x29, 0x01, 0x9e, 0xf8, 0x6e, 0xb5, 0xb4, - }, &chaincfg.MainNetParams) - if err != nil { - t.Errorf("Unable to create script hash address: %v", err) - return - } - - // mainnet p2pk 13CG6SJ3yHUXo4Cr2RY4THLLJrNFuG3gUg - p2pkCompressedMain, err := btcutil.NewAddressPubKey([]byte{ - 0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, - 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, - 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, - 0x52, 0xc6, 0xb4}, &chaincfg.MainNetParams) - if err != nil { - t.Errorf("Unable to create pubkey address (compressed): %v", - err) - return - } - p2pkCompressed2Main, err := btcutil.NewAddressPubKey([]byte{ - 0x03, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, - 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, - 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, - 0xb1, 0x6e, 0x65}, &chaincfg.MainNetParams) - if err != nil { - t.Errorf("Unable to create pubkey address (compressed 2): %v", - err) - return - } - - p2pkUncompressedMain, err := btcutil.NewAddressPubKey([]byte{ - 0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, 0x01, 0x6b, - 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, 0xb6, 0x8a, 0x38, - 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, - 0x90, 0x9a, 0x5c, 0xb2, 0xe0, 0xea, 0xdd, 0xfb, 0x84, 0xcc, - 0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, - 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, - 0xf6, 0x56, 0xb4, 0x12, 0xa3}, &chaincfg.MainNetParams) - if err != nil { - t.Errorf("Unable to create pubkey address (uncompressed): %v", - err) - return - } - - tests := []struct { - in btcutil.Address - expected []byte - err error - }{ - // pay-to-pubkey-hash address on mainnet - { - p2pkhMain, - []byte{ - 0x76, 0xa9, 0x14, 0xe3, 0x4c, 0xce, 0x70, 0xc8, - 0x63, 0x73, 0x27, 0x3e, 0xfc, 0xc5, 0x4c, 0xe7, - 0xd2, 0xa4, 0x91, 0xbb, 0x4a, 0x0e, 0x84, 0x88, - 0xac, - }, - nil, - }, - // pay-to-script-hash address on mainnet - { - p2shMain, - []byte{ - 0xa9, 0x14, 0xe8, 0xc3, 0x00, 0xc8, 0x79, 0x86, - 0xef, 0xa8, 0x4c, 0x37, 0xc0, 0x51, 0x99, 0x29, - 0x01, 0x9e, 0xf8, 0x6e, 0xb5, 0xb4, 0x87, - }, - nil, - }, - // pay-to-pubkey address on mainnet. compressed key. - { - p2pkCompressedMain, - []byte{ - txscript.OP_DATA_33, - 0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, - 0x4c, 0x95, 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, - 0x3d, 0x8d, 0x79, 0x03, 0xc3, 0xeb, 0xec, 0x3a, - 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, 0x52, 0xc6, - 0xb4, txscript.OP_CHECKSIG, - }, - nil, - }, - // pay-to-pubkey address on mainnet. compressed key (other way). - { - p2pkCompressed2Main, - []byte{ - txscript.OP_DATA_33, - 0x03, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, - 0x1b, 0xa1, 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, - 0x61, 0xcf, 0x43, 0xe0, 0x01, 0xf9, 0x13, 0x7f, - 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, 0xb1, 0x6e, - 0x65, txscript.OP_CHECKSIG, - }, - nil, - }, - // pay-to-pubkey address on mainnet. uncompressed key. - { - p2pkUncompressedMain, - []byte{ - txscript.OP_DATA_65, - 0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, - 0x01, 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, - 0x1e, 0xb6, 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48, - 0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, 0x90, 0x9a, - 0x5c, 0xb2, 0xe0, 0xea, 0xdd, 0xfb, 0x84, 0xcc, - 0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b, - 0xfa, 0x9b, 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f, - 0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, 0xb4, 0x12, - 0xa3, txscript.OP_CHECKSIG, - }, - nil, - }, - - // Supported address types with nil pointers. - {(*btcutil.AddressPubKeyHash)(nil), []byte{}, txscript.ErrUnsupportedAddress}, - {(*btcutil.AddressScriptHash)(nil), []byte{}, txscript.ErrUnsupportedAddress}, - {(*btcutil.AddressPubKey)(nil), []byte{}, txscript.ErrUnsupportedAddress}, - - // Unsupported address type. - {&bogusAddress{}, []byte{}, txscript.ErrUnsupportedAddress}, - } - - t.Logf("Running %d tests", len(tests)) - for i, test := range tests { - pkScript, err := txscript.PayToAddrScript(test.in) - if err != test.err { - t.Errorf("PayToAddrScript #%d unexpected error - "+ - "got %v, want %v", i, err, test.err) - continue - } - - if !bytes.Equal(pkScript, test.expected) { - t.Errorf("PayToAddrScript #%d got: %x\nwant: %x", - i, pkScript, test.expected) - continue - } - } -} - -func TestMultiSigScript(t *testing.T) { - t.Parallel() - - // mainnet p2pk 13CG6SJ3yHUXo4Cr2RY4THLLJrNFuG3gUg - p2pkCompressedMain, err := btcutil.NewAddressPubKey([]byte{ - 0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, - 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, - 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, - 0x52, 0xc6, 0xb4}, &chaincfg.MainNetParams) - if err != nil { - t.Errorf("Unable to create pubkey address (compressed): %v", - err) - return - } - p2pkCompressed2Main, err := btcutil.NewAddressPubKey([]byte{ - 0x03, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, - 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, - 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, - 0xb1, 0x6e, 0x65}, &chaincfg.MainNetParams) - if err != nil { - t.Errorf("Unable to create pubkey address (compressed 2): %v", - err) - return - } - - p2pkUncompressedMain, err := btcutil.NewAddressPubKey([]byte{ - 0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, 0x01, 0x6b, - 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, 0xb6, 0x8a, 0x38, - 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, - 0x90, 0x9a, 0x5c, 0xb2, 0xe0, 0xea, 0xdd, 0xfb, 0x84, 0xcc, - 0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, - 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, - 0xf6, 0x56, 0xb4, 0x12, 0xa3}, &chaincfg.MainNetParams) - if err != nil { - t.Errorf("Unable to create pubkey address (uncompressed): %v", - err) - return - } - - tests := []struct { - keys []*btcutil.AddressPubKey - nrequired int - expected []byte - err error - }{ - { - []*btcutil.AddressPubKey{ - p2pkCompressedMain, - p2pkCompressed2Main, - }, - 1, - []byte{ - txscript.OP_1, - txscript.OP_DATA_33, - 0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, - 0x4c, 0x95, 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, - 0x3d, 0x8d, 0x79, 0x03, 0xc3, 0xeb, 0xec, 0x3a, - 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, 0x52, 0xc6, - 0xb4, - txscript.OP_DATA_33, - 0x03, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, - 0x1b, 0xa1, 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, - 0x61, 0xcf, 0x43, 0xe0, 0x01, 0xf9, 0x13, 0x7f, - 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, 0xb1, 0x6e, - 0x65, - txscript.OP_2, txscript.OP_CHECKMULTISIG, - }, - nil, - }, - { - []*btcutil.AddressPubKey{ - p2pkCompressedMain, - p2pkCompressed2Main, - }, - 2, - []byte{ - txscript.OP_2, - txscript.OP_DATA_33, - 0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, - 0x4c, 0x95, 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, - 0x3d, 0x8d, 0x79, 0x03, 0xc3, 0xeb, 0xec, 0x3a, - 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, 0x52, 0xc6, - 0xb4, - txscript.OP_DATA_33, - 0x03, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, - 0x1b, 0xa1, 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, - 0x61, 0xcf, 0x43, 0xe0, 0x01, 0xf9, 0x13, 0x7f, - 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, 0xb1, 0x6e, - 0x65, - txscript.OP_2, txscript.OP_CHECKMULTISIG, - }, - nil, - }, - { - []*btcutil.AddressPubKey{ - p2pkCompressedMain, - p2pkCompressed2Main, - }, - 3, - []byte{}, - txscript.ErrBadNumRequired, - }, - { - []*btcutil.AddressPubKey{ - p2pkUncompressedMain, - }, - 1, - []byte{ - txscript.OP_1, txscript.OP_DATA_65, - 0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, - 0x01, 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, - 0x1e, 0xb6, 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48, - 0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, 0x90, 0x9a, - 0x5c, 0xb2, 0xe0, 0xea, 0xdd, 0xfb, 0x84, 0xcc, - 0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b, - 0xfa, 0x9b, 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f, - 0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, 0xb4, 0x12, - 0xa3, - txscript.OP_1, txscript.OP_CHECKMULTISIG, - }, - nil, - }, - { - []*btcutil.AddressPubKey{ - p2pkUncompressedMain, - }, - 2, - []byte{}, - txscript.ErrBadNumRequired, - }, - } - - t.Logf("Running %d tests", len(tests)) - for i, test := range tests { - script, err := txscript.MultiSigScript(test.keys, - test.nrequired) - if err != test.err { - t.Errorf("MultiSigScript #%d unexpected error - "+ - "got %v, want %v", i, err, test.err) - continue - } - - if !bytes.Equal(script, test.expected) { - t.Errorf("MultiSigScript #%d got: %x\nwant: %x", - i, script, test.expected) - continue - } - } -} - -func TestCalcMultiSigStats(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - script []byte - expected error - }{ - { - name: "short script", - script: []byte{ - 0x04, 0x67, 0x08, 0xaf, 0xdb, 0x0f, 0xe5, 0x54, - 0x82, 0x71, 0x96, 0x7f, 0x1a, 0x67, 0x13, 0x0b, - 0x71, 0x05, 0xcd, 0x6a, 0x82, 0x8e, 0x03, 0x90, - 0x9a, 0x67, 0x96, 0x2e, 0x0e, 0xa1, 0xf6, 0x1d, - }, - expected: txscript.ErrStackShortScript, - }, - { - name: "stack underflow", - script: []byte{ - txscript.OP_RETURN, - txscript.OP_PUSHDATA1, - 0x29, - 0x04, 0x67, 0x08, 0xaf, 0xdb, 0x0f, 0xe5, 0x54, - 0x82, 0x71, 0x96, 0x7f, 0x1a, 0x67, 0x13, 0x0b, - 0x71, 0x05, 0xcd, 0x6a, 0x82, 0x8e, 0x03, 0x90, - 0x9a, 0x67, 0x96, 0x2e, 0x0e, 0xa1, 0xf6, 0x1d, - 0xeb, 0x64, 0x9f, 0x6b, 0xc3, 0xf4, 0xce, 0xf3, - 0x08, - }, - expected: txscript.ErrStackUnderflow, - }, - { - name: "multisig script", - script: []uint8{ - txscript.OP_FALSE, - txscript.OP_DATA_72, - 0x30, 0x45, 0x02, 0x20, 0x10, - 0x6a, 0x3e, 0x4e, 0xf0, 0xb5, - 0x1b, 0x76, 0x4a, 0x28, 0x87, - 0x22, 0x62, 0xff, 0xef, 0x55, - 0x84, 0x65, 0x14, 0xda, 0xcb, - 0xdc, 0xbb, 0xdd, 0x65, 0x2c, - 0x84, 0x9d, 0x39, 0x5b, 0x43, - 0x84, 0x02, 0x21, 0x00, 0xe0, - 0x3a, 0xe5, 0x54, 0xc3, 0xcb, - 0xb4, 0x06, 0x00, 0xd3, 0x1d, - 0xd4, 0x6f, 0xc3, 0x3f, 0x25, - 0xe4, 0x7b, 0xf8, 0x52, 0x5b, - 0x1f, 0xe0, 0x72, 0x82, 0xe3, - 0xb6, 0xec, 0xb5, 0xf3, 0xbb, - 0x28, 0x01, - txscript.OP_CODESEPARATOR, - txscript.OP_TRUE, - txscript.OP_DATA_33, - 0x02, 0x32, 0xab, 0xdc, 0x89, - 0x3e, 0x7f, 0x06, 0x31, 0x36, - 0x4d, 0x7f, 0xd0, 0x1c, 0xb3, - 0x3d, 0x24, 0xda, 0x45, 0x32, - 0x9a, 0x00, 0x35, 0x7b, 0x3a, - 0x78, 0x86, 0x21, 0x1a, 0xb4, - 0x14, 0xd5, 0x5a, - txscript.OP_TRUE, - txscript.OP_CHECKMULTISIG, - }, - expected: nil, - }, - } - - for i, test := range tests { - if _, _, err := txscript.CalcMultiSigStats(test.script); err != test.expected { - t.Errorf("CalcMultiSigStats #%d (%s) wrong result\n"+ - "got: %v\nwant: %v", i, test.name, err, - test.expected) - } - } -} - func TestHasCanonicalPushes(t *testing.T) { t.Parallel() diff --git a/txscript/standard.go b/txscript/standard.go new file mode 100644 index 00000000..7bf7d2d4 --- /dev/null +++ b/txscript/standard.go @@ -0,0 +1,462 @@ +// Copyright (c) 2013-2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package txscript + +import ( + "github.com/btcsuite/btcd/chaincfg" + "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 +) + +// 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. + ScriptHashTy // Pay to script hash. + MultiSigTy // Multi signature. + NullDataTy // Empty data-only (provably prunable). +) + +// scriptClassToName houses the human-readable strings which describe each +// script class. +var scriptClassToName = []string{ + NonStandardTy: "nonstandard", + PubKeyTy: "pubkey", + PubKeyHashTy: "pubkeyhash", + ScriptHashTy: "scripthash", + MultiSigTy: "multisig", + NullDataTy: "nulldata", +} + +// 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] +} + +// isPubkey returns true if the script passed is a pay-to-pubkey transaction, +// false otherwise. +func isPubkey(pops []parsedOpcode) bool { + // Valid pubkeys are either 33 or 65 bytes. + return len(pops) == 2 && + (len(pops[0].data) == 33 || len(pops[0].data) == 65) && + pops[1].opcode.value == OP_CHECKSIG +} + +// isPubkeyHash returns true if the script passed is a pay-to-pubkey-hash +// transaction, false otherwise. +func isPubkeyHash(pops []parsedOpcode) bool { + return len(pops) == 5 && + pops[0].opcode.value == OP_DUP && + pops[1].opcode.value == OP_HASH160 && + pops[2].opcode.value == OP_DATA_20 && + pops[3].opcode.value == OP_EQUALVERIFY && + pops[4].opcode.value == OP_CHECKSIG + +} + +// isMultiSig returns true if the passed script is a multisig transaction, false +// otherwise. +func isMultiSig(pops []parsedOpcode) bool { + // The absolute minimum is 1 pubkey: + // OP_0/OP_1-16 OP_1 OP_CHECKMULTISIG + l := len(pops) + if l < 4 { + return false + } + if !isSmallInt(pops[0].opcode) { + return false + } + if !isSmallInt(pops[l-2].opcode) { + return false + } + if pops[l-1].opcode.value != OP_CHECKMULTISIG { + return false + } + for _, pop := range pops[1 : l-2] { + // Valid pubkeys are either 33 or 65 bytes. + if len(pop.data) != 33 && len(pop.data) != 65 { + return false + } + } + return true +} + +// isNullData returns true if the passed script is a null data transaction, +// false otherwise. +func isNullData(pops []parsedOpcode) bool { + // A nulldata transaction is either a single OP_RETURN or an + // OP_RETURN SMALLDATA (where SMALLDATA is a data push up to + // maxDataCarrierSize bytes). + l := len(pops) + if l == 1 && pops[0].opcode.value == OP_RETURN { + return true + } + + return l == 2 && + pops[0].opcode.value == OP_RETURN && + pops[1].opcode.value <= OP_PUSHDATA4 && + len(pops[1].data) <= maxDataCarrierSize +} + +// scriptType returns the type of the script being inspected from the known +// standard types. +func typeOfScript(pops []parsedOpcode) ScriptClass { + if isPubkey(pops) { + return PubKeyTy + } else if isPubkeyHash(pops) { + return PubKeyHashTy + } else if isScriptHash(pops) { + return ScriptHashTy + } else if isMultiSig(pops) { + return MultiSigTy + } else if isNullData(pops) { + return NullDataTy + } + 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 { + pops, err := parseScript(script) + if err != nil { + return NonStandardTy + } + return typeOfScript(pops) +} + +// 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). +func expectedInputs(pops []parsedOpcode, class ScriptClass) int { + switch class { + case PubKeyTy: + return 1 + + case PubKeyHashTy: + return 2 + + case ScriptHashTy: + // 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(pops[0].opcode) + 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 +func CalcScriptInfo(sigScript, pkScript []byte, bip16 bool) (*ScriptInfo, error) { + sigPops, err := parseScript(sigScript) + if err != nil { + return nil, err + } + + pkPops, err := parseScript(pkScript) + if err != nil { + return nil, err + } + + // Push only sigScript makes little sense. + si := new(ScriptInfo) + si.PkScriptClass = typeOfScript(pkPops) + + // Can't have a pkScript that doesn't just push data. + if !isPushOnly(sigPops) { + return nil, ErrStackNonPushOnly + } + + si.ExpectedInputs = expectedInputs(pkPops, si.PkScriptClass) + + // All entries pushed to stack (or are OP_RESERVED and exec will fail). + si.NumInputs = len(sigPops) + + // Count sigops taking into account pay-to-script-hash. + if si.PkScriptClass == ScriptHashTy && bip16 { + // The pay-to-hash-script is the final data push of the + // signature script. + script := sigPops[len(sigPops)-1].data + shPops, err := parseScript(script) + if err != nil { + return nil, err + } + + shInputs := expectedInputs(shPops, typeOfScript(shPops)) + if shInputs == -1 { + si.ExpectedInputs = -1 + } else { + si.ExpectedInputs += shInputs + } + si.SigOps = getSigOpCount(shPops, true) + } else { + si.SigOps = getSigOpCount(pkPops, true) + } + + 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. +func CalcMultiSigStats(script []byte) (int, int, error) { + pops, err := parseScript(script) + if err != nil { + return 0, 0, err + } + + // A multi-signature script is of the pattern: + // NUM_SIGS PUBKEY PUBKEY PUBKEY... NUM_PUBKEYS OP_CHECKMULTISIG + // Therefore the number of signatures is the oldest item on the stack + // and the number of pubkeys is the 2nd to last. Also, the absolute + // minimum for a multi-signature script is 1 pubkey, so at least 4 + // items must be on the stack per: + // OP_1 PUBKEY OP_1 OP_CHECKMULTISIG + if len(pops) < 4 { + return 0, 0, ErrStackUnderflow + } + + numSigs := asSmallInt(pops[0].opcode) + numPubKeys := asSmallInt(pops[len(pops)-2].opcode) + return numPubKeys, numSigs, 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() +} + +// 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() +} + +// 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) { + switch addr := addr.(type) { + case *btcutil.AddressPubKeyHash: + if addr == nil { + return nil, ErrUnsupportedAddress + } + return payToPubKeyHashScript(addr.ScriptAddress()) + + case *btcutil.AddressScriptHash: + if addr == nil { + return nil, ErrUnsupportedAddress + } + return payToScriptHashScript(addr.ScriptAddress()) + + case *btcutil.AddressPubKey: + if addr == nil { + return nil, ErrUnsupportedAddress + } + return payToPubKeyScript(addr.ScriptAddress()) + } + + return nil, ErrUnsupportedAddress +} + +// 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 []*btcutil.AddressPubKey, nrequired int) ([]byte, error) { + if len(pubkeys) < nrequired { + return nil, ErrBadNumRequired + } + + 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. +func PushedData(script []byte) ([][]byte, error) { + pops, err := parseScript(script) + if err != nil { + return nil, err + } + + var data [][]byte + for _, pop := range pops { + if pop.data != nil { + data = append(data, pop.data) + } else if pop.opcode.value == OP_0 { + data = append(data, []byte{}) + } + } + return data, nil +} + +// 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. +func ExtractPkScriptAddrs(pkScript []byte, chainParams *chaincfg.Params) (ScriptClass, []btcutil.Address, int, error) { + var addrs []btcutil.Address + var requiredSigs int + + // No valid addresses or required signatures if the script doesn't + // parse. + pops, err := parseScript(pkScript) + if err != nil { + return NonStandardTy, nil, 0, err + } + + scriptClass := typeOfScript(pops) + switch scriptClass { + case PubKeyHashTy: + // A pay-to-pubkey-hash script is of the form: + // OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG + // Therefore the pubkey hash is the 3rd item on the stack. + // Skip the pubkey hash if it's invalid for some reason. + requiredSigs = 1 + addr, err := btcutil.NewAddressPubKeyHash(pops[2].data, + chainParams) + if err == nil { + addrs = append(addrs, addr) + } + + case PubKeyTy: + // A pay-to-pubkey script is of the form: + // OP_CHECKSIG + // Therefore the pubkey is the first item on the stack. + // Skip the pubkey if it's invalid for some reason. + requiredSigs = 1 + addr, err := btcutil.NewAddressPubKey(pops[0].data, chainParams) + if err == nil { + addrs = append(addrs, addr) + } + + case ScriptHashTy: + // A pay-to-script-hash script is of the form: + // OP_HASH160 OP_EQUAL + // Therefore the script hash is the 2nd item on the stack. + // Skip the script hash if it's invalid for some reason. + requiredSigs = 1 + addr, err := btcutil.NewAddressScriptHashFromHash(pops[1].data, + chainParams) + if err == nil { + addrs = append(addrs, addr) + } + + case MultiSigTy: + // A multi-signature script is of the form: + // ... OP_CHECKMULTISIG + // Therefore the number of required signatures is the 1st item + // on the stack and the number of public keys is the 2nd to last + // item on the stack. + requiredSigs = asSmallInt(pops[0].opcode) + numPubKeys := asSmallInt(pops[len(pops)-2].opcode) + + // Extract the public keys while skipping any that are invalid. + addrs = make([]btcutil.Address, 0, numPubKeys) + for i := 0; i < numPubKeys; i++ { + addr, err := btcutil.NewAddressPubKey(pops[i+1].data, + chainParams) + if err == nil { + addrs = append(addrs, addr) + } + } + + case NullDataTy: + // Null data transactions have no addresses or required + // signatures. + + case NonStandardTy: + // Don't attempt to extract addresses or required signatures for + // nonstandard transactions. + } + + return scriptClass, addrs, requiredSigs, nil +} diff --git a/txscript/standard_test.go b/txscript/standard_test.go new file mode 100644 index 00000000..57fde9b5 --- /dev/null +++ b/txscript/standard_test.go @@ -0,0 +1,1264 @@ +// Copyright (c) 2013-2015 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. +package txscript_test + +import ( + "bytes" + "encoding/hex" + "reflect" + "testing" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcutil" +) + +// decodeHex decodes the passed hex string and returns the resulting bytes. It +// panics if an error occurs. This is only used in the tests as a helper since +// the only way it can fail is if there is an error in the test source code. +func decodeHex(hexStr string) []byte { + b, err := hex.DecodeString(hexStr) + if err != nil { + panic("invalid hex string in test source: err " + err.Error() + + ", hex: " + hexStr) + } + + return b +} + +// newAddressPubKey returns a new btcutil.AddressPubKey from the provided +// serialized public key. It panics if an error occurs. This is only used in +// the tests as a helper since the only way it can fail is if there is an error +// in the test source code. +func newAddressPubKey(serializedPubKey []byte) btcutil.Address { + addr, err := btcutil.NewAddressPubKey(serializedPubKey, + &chaincfg.MainNetParams) + if err != nil { + panic("invalid public key in test source") + } + + return addr +} + +// newAddressPubKeyHash returns a new btcutil.AddressPubKeyHash from the +// provided hash. It panics if an error occurs. This is only used in the tests +// as a helper since the only way it can fail is if there is an error in the +// test source code. +func newAddressPubKeyHash(pkHash []byte) btcutil.Address { + addr, err := btcutil.NewAddressPubKeyHash(pkHash, &chaincfg.MainNetParams) + if err != nil { + panic("invalid public key hash in test source") + } + + return addr +} + +// newAddressScriptHash returns a new btcutil.AddressScriptHash from the +// provided hash. It panics if an error occurs. This is only used in the tests +// as a helper since the only way it can fail is if there is an error in the +// test source code. +func newAddressScriptHash(scriptHash []byte) btcutil.Address { + addr, err := btcutil.NewAddressScriptHashFromHash(scriptHash, + &chaincfg.MainNetParams) + if err != nil { + panic("invalid script hash in test source") + } + + return addr +} + +// TestExtractPkScriptAddrs ensures that extracting the type, addresses, and +// number of required signatures from PkScripts works as intended. +func TestExtractPkScriptAddrs(t *testing.T) { + tests := []struct { + name string + script []byte + addrs []btcutil.Address + reqSigs int + class txscript.ScriptClass + }{ + { + name: "standard p2pk with compressed pubkey (0x02)", + script: decodeHex("2102192d74d0cb94344c9569c2e7790157" + + "3d8d7903c3ebec3a957724895dca52c6b4ac"), + addrs: []btcutil.Address{ + newAddressPubKey(decodeHex("02192d74d0cb94344" + + "c9569c2e77901573d8d7903c3ebec3a95772" + + "4895dca52c6b4")), + }, + reqSigs: 1, + class: txscript.PubKeyTy, + }, + { + name: "standard p2pk with uncompressed pubkey (0x04)", + script: decodeHex("410411db93e1dcdb8a016b49840f8c53bc" + + "1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb" + + "84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643" + + "f656b412a3ac"), + addrs: []btcutil.Address{ + newAddressPubKey(decodeHex("0411db93e1dcdb8a0" + + "16b49840f8c53bc1eb68a382e97b1482ecad" + + "7b148a6909a5cb2e0eaddfb84ccf9744464f" + + "82e160bfa9b8b64f9d4c03f999b8643f656b" + + "412a3")), + }, + reqSigs: 1, + class: txscript.PubKeyTy, + }, + { + name: "standard p2pk with hybrid pubkey (0x06)", + script: decodeHex("4106192d74d0cb94344c9569c2e7790157" + + "3d8d7903c3ebec3a957724895dca52c6b40d45264838" + + "c0bd96852662ce6a847b197376830160c6d2eb5e6a4c" + + "44d33f453eac"), + addrs: []btcutil.Address{ + newAddressPubKey(decodeHex("06192d74d0cb94344" + + "c9569c2e77901573d8d7903c3ebec3a95772" + + "4895dca52c6b40d45264838c0bd96852662c" + + "e6a847b197376830160c6d2eb5e6a4c44d33" + + "f453e")), + }, + reqSigs: 1, + class: txscript.PubKeyTy, + }, + { + name: "standard p2pk with compressed pubkey (0x03)", + script: decodeHex("2103b0bd634234abbb1ba1e986e884185c" + + "61cf43e001f9137f23c2c409273eb16e65ac"), + addrs: []btcutil.Address{ + newAddressPubKey(decodeHex("03b0bd634234abbb1" + + "ba1e986e884185c61cf43e001f9137f23c2c" + + "409273eb16e65")), + }, + reqSigs: 1, + class: txscript.PubKeyTy, + }, + { + name: "2nd standard p2pk with uncompressed pubkey (0x04)", + script: decodeHex("4104b0bd634234abbb1ba1e986e884185c" + + "61cf43e001f9137f23c2c409273eb16e6537a576782e" + + "ba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c" + + "1e0908ef7bac"), + addrs: []btcutil.Address{ + newAddressPubKey(decodeHex("04b0bd634234abbb1" + + "ba1e986e884185c61cf43e001f9137f23c2c" + + "409273eb16e6537a576782eba668a7ef8bd3" + + "b3cfb1edb7117ab65129b8a2e681f3c1e090" + + "8ef7b")), + }, + reqSigs: 1, + class: txscript.PubKeyTy, + }, + { + name: "standard p2pk with hybrid pubkey (0x07)", + script: decodeHex("4107b0bd634234abbb1ba1e986e884185c" + + "61cf43e001f9137f23c2c409273eb16e6537a576782e" + + "ba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c" + + "1e0908ef7bac"), + addrs: []btcutil.Address{ + newAddressPubKey(decodeHex("07b0bd634234abbb1" + + "ba1e986e884185c61cf43e001f9137f23c2c" + + "409273eb16e6537a576782eba668a7ef8bd3" + + "b3cfb1edb7117ab65129b8a2e681f3c1e090" + + "8ef7b")), + }, + reqSigs: 1, + class: txscript.PubKeyTy, + }, + { + name: "standard p2pkh", + script: decodeHex("76a914ad06dd6ddee55cbca9a9e3713bd7" + + "587509a3056488ac"), + addrs: []btcutil.Address{ + newAddressPubKeyHash(decodeHex("ad06dd6ddee55" + + "cbca9a9e3713bd7587509a30564")), + }, + reqSigs: 1, + class: txscript.PubKeyHashTy, + }, + { + name: "standard p2sh", + script: decodeHex("a91463bcc565f9e68ee0189dd5cc67f1b0" + + "e5f02f45cb87"), + addrs: []btcutil.Address{ + newAddressScriptHash(decodeHex("63bcc565f9e68" + + "ee0189dd5cc67f1b0e5f02f45cb")), + }, + reqSigs: 1, + class: txscript.ScriptHashTy, + }, + // from real tx 60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1, vout 0 + { + name: "standard 1 of 2 multisig", + script: decodeHex("514104cc71eb30d653c0c3163990c47b97" + + "6f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473" + + "e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11" + + "fcdd0d348ac4410461cbdcc5409fb4b4d42b51d33381" + + "354d80e550078cb532a34bfa2fcfdeb7d76519aecc62" + + "770f5b0e4ef8551946d8a540911abe3e7854a26f39f5" + + "8b25c15342af52ae"), + addrs: []btcutil.Address{ + newAddressPubKey(decodeHex("04cc71eb30d653c0c" + + "3163990c47b976f3fb3f37cccdcbedb169a1" + + "dfef58bbfbfaff7d8a473e7e2e6d317b87ba" + + "fe8bde97e3cf8f065dec022b51d11fcdd0d3" + + "48ac4")), + newAddressPubKey(decodeHex("0461cbdcc5409fb4b" + + "4d42b51d33381354d80e550078cb532a34bf" + + "a2fcfdeb7d76519aecc62770f5b0e4ef8551" + + "946d8a540911abe3e7854a26f39f58b25c15" + + "342af")), + }, + reqSigs: 1, + class: txscript.MultiSigTy, + }, + // from real tx d646f82bd5fbdb94a36872ce460f97662b80c3050ad3209bef9d1e398ea277ab, vin 1 + { + name: "standard 2 of 3 multisig", + script: decodeHex("524104cb9c3c222c5f7a7d3b9bd152f363" + + "a0b6d54c9eb312c4d4f9af1e8551b6c421a6a4ab0e29" + + "105f24de20ff463c1c91fcf3bf662cdde4783d4799f7" + + "87cb7c08869b4104ccc588420deeebea22a7e900cc8b" + + "68620d2212c374604e3487ca08f1ff3ae12bdc639514" + + "d0ec8612a2d3c519f084d9a00cbbe3b53d071e9b09e7" + + "1e610b036aa24104ab47ad1939edcb3db65f7fedea62" + + "bbf781c5410d3f22a7a3a56ffefb2238af8627363bdf" + + "2ed97c1f89784a1aecdb43384f11d2acc64443c7fc29" + + "9cef0400421a53ae"), + addrs: []btcutil.Address{ + newAddressPubKey(decodeHex("04cb9c3c222c5f7a7" + + "d3b9bd152f363a0b6d54c9eb312c4d4f9af1" + + "e8551b6c421a6a4ab0e29105f24de20ff463" + + "c1c91fcf3bf662cdde4783d4799f787cb7c0" + + "8869b")), + newAddressPubKey(decodeHex("04ccc588420deeebe" + + "a22a7e900cc8b68620d2212c374604e3487c" + + "a08f1ff3ae12bdc639514d0ec8612a2d3c51" + + "9f084d9a00cbbe3b53d071e9b09e71e610b0" + + "36aa2")), + newAddressPubKey(decodeHex("04ab47ad1939edcb3" + + "db65f7fedea62bbf781c5410d3f22a7a3a56" + + "ffefb2238af8627363bdf2ed97c1f89784a1" + + "aecdb43384f11d2acc64443c7fc299cef040" + + "0421a")), + }, + reqSigs: 2, + class: txscript.MultiSigTy, + }, + + // The below are nonstandard script due to things such as + // invalid pubkeys, failure to parse, and not being of a + // standard form. + + { + name: "p2pk with uncompressed pk missing OP_CHECKSIG", + script: decodeHex("410411db93e1dcdb8a016b49840f8c53bc" + + "1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb" + + "84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643" + + "f656b412a3"), + addrs: nil, + reqSigs: 0, + class: txscript.NonStandardTy, + }, + { + name: "valid signature from a sigscript - no addresses", + script: decodeHex("47304402204e45e16932b8af514961a1d3" + + "a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220" + + "181522ec8eca07de4860a4acdd12909d831cc56cbbac" + + "4622082221a8768d1d0901"), + addrs: nil, + reqSigs: 0, + class: txscript.NonStandardTy, + }, + // Note the technically the pubkey is the second item on the + // stack, but since the address extraction intentionally only + // works with standard PkScripts, this should not return any + // addresses. + { + name: "valid sigscript to reedeem p2pk - no addresses", + script: decodeHex("493046022100ddc69738bf2336318e4e04" + + "1a5a77f305da87428ab1606f023260017854350ddc02" + + "2100817af09d2eec36862d16009852b7e3a0f6dd7659" + + "8290b7834e1453660367e07a014104cd4240c198e125" + + "23b6f9cb9f5bed06de1ba37e96a1bbd13745fcf9d11c" + + "25b1dff9a519675d198804ba9962d3eca2d5937d58e5" + + "a75a71042d40388a4d307f887d"), + addrs: nil, + reqSigs: 0, + class: txscript.NonStandardTy, + }, + // from real tx 691dd277dc0e90a462a3d652a1171686de49cf19067cd33c7df0392833fb986a, vout 0 + // invalid public keys + { + name: "1 of 3 multisig with invalid pubkeys", + script: decodeHex("51411c2200007353455857696b696c6561" + + "6b73204361626c6567617465204261636b75700a0a63" + + "61626c65676174652d3230313031323034313831312e" + + "377a0a0a446f41776e6c6f61642074686520666f6c6c" + + "6f77696e67207472616e73616374696f6e7320776974" + + "68205361746f736869204e616b616d6f746f27732064" + + "6f776e6c6f61416420746f6f6c2077686963680a6361" + + "6e20626520666f756e6420696e207472616e73616374" + + "696f6e20366335336364393837313139656637393764" + + "35616463636453ae"), + addrs: []btcutil.Address{}, + reqSigs: 1, + class: txscript.MultiSigTy, + }, + // from real tx: 691dd277dc0e90a462a3d652a1171686de49cf19067cd33c7df0392833fb986a, vout 44 + // invalid public keys + { + name: "1 of 3 multisig with invalid pubkeys 2", + script: decodeHex("5141346333656332353963373464616365" + + "36666430383862343463656638630a63363662633139" + + "39366338623934613338313162333635363138666531" + + "65396231623541366361636365393933613339383861" + + "34363966636336643664616266640a32363633636661" + + "39636634633033633630396335393363336539316665" + + "64653730323921313233646434326432353633396433" + + "38613663663530616234636434340a00000053ae"), + addrs: []btcutil.Address{}, + reqSigs: 1, + class: txscript.MultiSigTy, + }, + { + name: "empty script", + script: []byte{}, + addrs: nil, + reqSigs: 0, + class: txscript.NonStandardTy, + }, + { + name: "script that does not parse", + script: []byte{txscript.OP_DATA_45}, + addrs: nil, + reqSigs: 0, + class: txscript.NonStandardTy, + }, + } + + t.Logf("Running %d tests.", len(tests)) + for i, test := range tests { + class, addrs, reqSigs, err := txscript.ExtractPkScriptAddrs( + test.script, &chaincfg.MainNetParams) + if err != nil { + } + + if !reflect.DeepEqual(addrs, test.addrs) { + t.Errorf("ExtractPkScriptAddrs #%d (%s) unexpected "+ + "addresses\ngot %v\nwant %v", i, test.name, + addrs, test.addrs) + continue + } + + if reqSigs != test.reqSigs { + t.Errorf("ExtractPkScriptAddrs #%d (%s) unexpected "+ + "number of required signatures - got %d, "+ + "want %d", i, test.name, reqSigs, test.reqSigs) + continue + } + + if class != test.class { + t.Errorf("ExtractPkScriptAddrs #%d (%s) unexpected "+ + "script type - got %s, want %s", i, test.name, + class, test.class) + continue + } + } +} + +type scriptInfoTest struct { + name string + sigScript []byte + pkScript []byte + bip16 bool + scriptInfo txscript.ScriptInfo + scriptInfoErr error +} + +func TestScriptInfo(t *testing.T) { + t.Parallel() + + for _, test := range txTests { + si, err := txscript.CalcScriptInfo( + test.tx.TxIn[test.idx].SignatureScript, + test.pkScript, test.bip16) + 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) + continue + } + } + + extraTests := []scriptInfoTest{ + { + // Invented scripts, the hashes do not match + name: "pkscript doesn't parse", + sigScript: []byte{txscript.OP_TRUE, + txscript.OP_DATA_1, 81, + txscript.OP_DATA_8, + txscript.OP_2DUP, txscript.OP_EQUAL, + txscript.OP_NOT, txscript.OP_VERIFY, + txscript.OP_ABS, txscript.OP_SWAP, + txscript.OP_ABS, txscript.OP_EQUAL, + }, + // truncated version of test below: + pkScript: []byte{txscript.OP_HASH160, + txscript.OP_DATA_20, + 0xfe, 0x44, 0x10, 0x65, 0xb6, 0x53, 0x22, 0x31, + 0xde, 0x2f, 0xac, 0x56, 0x31, 0x52, 0x20, 0x5e, + 0xc4, 0xf5, 0x9c, + }, + bip16: true, + scriptInfoErr: txscript.ErrStackShortScript, + }, + { + name: "sigScript doesn't parse", + // Truncated version of p2sh script below. + sigScript: []byte{txscript.OP_TRUE, + txscript.OP_DATA_1, 81, + txscript.OP_DATA_8, + txscript.OP_2DUP, txscript.OP_EQUAL, + txscript.OP_NOT, txscript.OP_VERIFY, + txscript.OP_ABS, txscript.OP_SWAP, + txscript.OP_ABS, + }, + pkScript: []byte{txscript.OP_HASH160, + txscript.OP_DATA_20, + 0xfe, 0x44, 0x10, 0x65, 0xb6, 0x53, 0x22, 0x31, + 0xde, 0x2f, 0xac, 0x56, 0x31, 0x52, 0x20, 0x5e, + 0xc4, 0xf5, 0x9c, 0x74, txscript.OP_EQUAL, + }, + bip16: true, + scriptInfoErr: txscript.ErrStackShortScript, + }, + { + // Invented scripts, the hashes do not match + name: "p2sh standard script", + sigScript: []byte{txscript.OP_TRUE, + txscript.OP_DATA_1, 81, + txscript.OP_DATA_25, + txscript.OP_DUP, txscript.OP_HASH160, + txscript.OP_DATA_20, 0x1, 0x2, 0x3, 0x4, 0x5, + 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, + 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, + txscript.OP_EQUALVERIFY, txscript.OP_CHECKSIG, + }, + pkScript: []byte{txscript.OP_HASH160, + txscript.OP_DATA_20, + 0xfe, 0x44, 0x10, 0x65, 0xb6, 0x53, 0x22, 0x31, + 0xde, 0x2f, 0xac, 0x56, 0x31, 0x52, 0x20, 0x5e, + 0xc4, 0xf5, 0x9c, 0x74, txscript.OP_EQUAL, + }, + bip16: true, + scriptInfo: txscript.ScriptInfo{ + PkScriptClass: txscript.ScriptHashTy, + NumInputs: 3, + ExpectedInputs: 3, // nonstandard p2sh. + SigOps: 1, + }, + }, + { + // from 567a53d1ce19ce3d07711885168484439965501536d0d0294c5d46d46c10e53b + // from the blockchain. + name: "p2sh nonstandard script", + sigScript: []byte{txscript.OP_TRUE, + txscript.OP_DATA_1, 81, + txscript.OP_DATA_8, + txscript.OP_2DUP, txscript.OP_EQUAL, + txscript.OP_NOT, txscript.OP_VERIFY, + txscript.OP_ABS, txscript.OP_SWAP, + txscript.OP_ABS, txscript.OP_EQUAL, + }, + pkScript: []byte{txscript.OP_HASH160, + txscript.OP_DATA_20, + 0xfe, 0x44, 0x10, 0x65, 0xb6, 0x53, 0x22, 0x31, + 0xde, 0x2f, 0xac, 0x56, 0x31, 0x52, 0x20, 0x5e, + 0xc4, 0xf5, 0x9c, 0x74, txscript.OP_EQUAL, + }, + bip16: true, + scriptInfo: txscript.ScriptInfo{ + PkScriptClass: txscript.ScriptHashTy, + NumInputs: 3, + ExpectedInputs: -1, // nonstandard p2sh. + SigOps: 0, + }, + }, + { + // Script is invented, numbers all fake. + name: "multisig script", + sigScript: []byte{txscript.OP_TRUE, + txscript.OP_TRUE, txscript.OP_TRUE, + txscript.OP_0, // Extra arg for OP_CHECKMULTISIG bug + }, + pkScript: []byte{ + txscript.OP_3, txscript.OP_DATA_33, + 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, + txscript.OP_DATA_33, + 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, + txscript.OP_DATA_33, + 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, + txscript.OP_3, txscript.OP_CHECKMULTISIG, + }, + bip16: true, + scriptInfo: txscript.ScriptInfo{ + PkScriptClass: txscript.MultiSigTy, + NumInputs: 4, + ExpectedInputs: 4, + SigOps: 3, + }, + }, + } + + for _, test := range extraTests { + si, err := txscript.CalcScriptInfo(test.sigScript, + test.pkScript, test.bip16) + 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) + continue + } + } + +} + +// bogusAddress implements the btcutil.Address interface so the tests can ensure +// unsupported address types are handled properly. +type bogusAddress struct{} + +// EncodeAddress simply returns an empty string. It exists to satsify the +// btcutil.Address interface. +func (b *bogusAddress) EncodeAddress() string { + return "" +} + +// ScriptAddress simply returns an empty byte slice. It exists to satsify the +// btcutil.Address interface. +func (b *bogusAddress) ScriptAddress() []byte { + return []byte{} +} + +// IsForNet lies blatantly to satisfy the btcutil.Address interface. +func (b *bogusAddress) IsForNet(chainParams *chaincfg.Params) bool { + return true // why not? +} + +// String simply returns an empty string. It exists to satsify the +// btcutil.Address interface. +func (b *bogusAddress) String() string { + return "" +} + +func TestPayToAddrScript(t *testing.T) { + t.Parallel() + + // 1MirQ9bwyQcGVJPwKUgapu5ouK2E2Ey4gX + p2pkhMain, err := btcutil.NewAddressPubKeyHash([]byte{ + 0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc, + 0xc5, 0x4c, 0xe7, 0xd2, 0xa4, 0x91, 0xbb, 0x4a, 0x0e, 0x84, + }, &chaincfg.MainNetParams) + if err != nil { + t.Errorf("Unable to create public key hash address: %v", err) + return + } + + // Taken from transaction: + // b0539a45de13b3e0403909b8bd1a555b8cbe45fd4e3f3fda76f3a5f52835c29d + p2shMain, _ := btcutil.NewAddressScriptHashFromHash([]byte{ + 0xe8, 0xc3, 0x00, 0xc8, 0x79, 0x86, 0xef, 0xa8, 0x4c, 0x37, + 0xc0, 0x51, 0x99, 0x29, 0x01, 0x9e, 0xf8, 0x6e, 0xb5, 0xb4, + }, &chaincfg.MainNetParams) + if err != nil { + t.Errorf("Unable to create script hash address: %v", err) + return + } + + // mainnet p2pk 13CG6SJ3yHUXo4Cr2RY4THLLJrNFuG3gUg + p2pkCompressedMain, err := btcutil.NewAddressPubKey([]byte{ + 0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, + 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, + 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, + 0x52, 0xc6, 0xb4}, &chaincfg.MainNetParams) + if err != nil { + t.Errorf("Unable to create pubkey address (compressed): %v", + err) + return + } + p2pkCompressed2Main, err := btcutil.NewAddressPubKey([]byte{ + 0x03, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, + 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, + 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, + 0xb1, 0x6e, 0x65}, &chaincfg.MainNetParams) + if err != nil { + t.Errorf("Unable to create pubkey address (compressed 2): %v", + err) + return + } + + p2pkUncompressedMain, err := btcutil.NewAddressPubKey([]byte{ + 0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, 0x01, 0x6b, + 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, 0xb6, 0x8a, 0x38, + 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, + 0x90, 0x9a, 0x5c, 0xb2, 0xe0, 0xea, 0xdd, 0xfb, 0x84, 0xcc, + 0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, + 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, + 0xf6, 0x56, 0xb4, 0x12, 0xa3}, &chaincfg.MainNetParams) + if err != nil { + t.Errorf("Unable to create pubkey address (uncompressed): %v", + err) + return + } + + tests := []struct { + in btcutil.Address + expected []byte + err error + }{ + // pay-to-pubkey-hash address on mainnet + { + p2pkhMain, + []byte{ + 0x76, 0xa9, 0x14, 0xe3, 0x4c, 0xce, 0x70, 0xc8, + 0x63, 0x73, 0x27, 0x3e, 0xfc, 0xc5, 0x4c, 0xe7, + 0xd2, 0xa4, 0x91, 0xbb, 0x4a, 0x0e, 0x84, 0x88, + 0xac, + }, + nil, + }, + // pay-to-script-hash address on mainnet + { + p2shMain, + []byte{ + 0xa9, 0x14, 0xe8, 0xc3, 0x00, 0xc8, 0x79, 0x86, + 0xef, 0xa8, 0x4c, 0x37, 0xc0, 0x51, 0x99, 0x29, + 0x01, 0x9e, 0xf8, 0x6e, 0xb5, 0xb4, 0x87, + }, + nil, + }, + // pay-to-pubkey address on mainnet. compressed key. + { + p2pkCompressedMain, + []byte{ + txscript.OP_DATA_33, + 0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, + 0x4c, 0x95, 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, + 0x3d, 0x8d, 0x79, 0x03, 0xc3, 0xeb, 0xec, 0x3a, + 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, 0x52, 0xc6, + 0xb4, txscript.OP_CHECKSIG, + }, + nil, + }, + // pay-to-pubkey address on mainnet. compressed key (other way). + { + p2pkCompressed2Main, + []byte{ + txscript.OP_DATA_33, + 0x03, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, + 0x1b, 0xa1, 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, + 0x61, 0xcf, 0x43, 0xe0, 0x01, 0xf9, 0x13, 0x7f, + 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, 0xb1, 0x6e, + 0x65, txscript.OP_CHECKSIG, + }, + nil, + }, + // pay-to-pubkey address on mainnet. uncompressed key. + { + p2pkUncompressedMain, + []byte{ + txscript.OP_DATA_65, + 0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, + 0x01, 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, + 0x1e, 0xb6, 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48, + 0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, 0x90, 0x9a, + 0x5c, 0xb2, 0xe0, 0xea, 0xdd, 0xfb, 0x84, 0xcc, + 0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b, + 0xfa, 0x9b, 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f, + 0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, 0xb4, 0x12, + 0xa3, txscript.OP_CHECKSIG, + }, + nil, + }, + + // Supported address types with nil pointers. + {(*btcutil.AddressPubKeyHash)(nil), []byte{}, txscript.ErrUnsupportedAddress}, + {(*btcutil.AddressScriptHash)(nil), []byte{}, txscript.ErrUnsupportedAddress}, + {(*btcutil.AddressPubKey)(nil), []byte{}, txscript.ErrUnsupportedAddress}, + + // Unsupported address type. + {&bogusAddress{}, []byte{}, txscript.ErrUnsupportedAddress}, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + pkScript, err := txscript.PayToAddrScript(test.in) + if err != test.err { + t.Errorf("PayToAddrScript #%d unexpected error - "+ + "got %v, want %v", i, err, test.err) + continue + } + + if !bytes.Equal(pkScript, test.expected) { + t.Errorf("PayToAddrScript #%d got: %x\nwant: %x", + i, pkScript, test.expected) + continue + } + } +} + +func TestMultiSigScript(t *testing.T) { + t.Parallel() + + // mainnet p2pk 13CG6SJ3yHUXo4Cr2RY4THLLJrNFuG3gUg + p2pkCompressedMain, err := btcutil.NewAddressPubKey([]byte{ + 0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, + 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, + 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, + 0x52, 0xc6, 0xb4}, &chaincfg.MainNetParams) + if err != nil { + t.Errorf("Unable to create pubkey address (compressed): %v", + err) + return + } + p2pkCompressed2Main, err := btcutil.NewAddressPubKey([]byte{ + 0x03, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, + 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, + 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, + 0xb1, 0x6e, 0x65}, &chaincfg.MainNetParams) + if err != nil { + t.Errorf("Unable to create pubkey address (compressed 2): %v", + err) + return + } + + p2pkUncompressedMain, err := btcutil.NewAddressPubKey([]byte{ + 0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, 0x01, 0x6b, + 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, 0xb6, 0x8a, 0x38, + 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, + 0x90, 0x9a, 0x5c, 0xb2, 0xe0, 0xea, 0xdd, 0xfb, 0x84, 0xcc, + 0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, + 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, + 0xf6, 0x56, 0xb4, 0x12, 0xa3}, &chaincfg.MainNetParams) + if err != nil { + t.Errorf("Unable to create pubkey address (uncompressed): %v", + err) + return + } + + tests := []struct { + keys []*btcutil.AddressPubKey + nrequired int + expected []byte + err error + }{ + { + []*btcutil.AddressPubKey{ + p2pkCompressedMain, + p2pkCompressed2Main, + }, + 1, + []byte{ + txscript.OP_1, + txscript.OP_DATA_33, + 0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, + 0x4c, 0x95, 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, + 0x3d, 0x8d, 0x79, 0x03, 0xc3, 0xeb, 0xec, 0x3a, + 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, 0x52, 0xc6, + 0xb4, + txscript.OP_DATA_33, + 0x03, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, + 0x1b, 0xa1, 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, + 0x61, 0xcf, 0x43, 0xe0, 0x01, 0xf9, 0x13, 0x7f, + 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, 0xb1, 0x6e, + 0x65, + txscript.OP_2, txscript.OP_CHECKMULTISIG, + }, + nil, + }, + { + []*btcutil.AddressPubKey{ + p2pkCompressedMain, + p2pkCompressed2Main, + }, + 2, + []byte{ + txscript.OP_2, + txscript.OP_DATA_33, + 0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, + 0x4c, 0x95, 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, + 0x3d, 0x8d, 0x79, 0x03, 0xc3, 0xeb, 0xec, 0x3a, + 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, 0x52, 0xc6, + 0xb4, + txscript.OP_DATA_33, + 0x03, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, + 0x1b, 0xa1, 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, + 0x61, 0xcf, 0x43, 0xe0, 0x01, 0xf9, 0x13, 0x7f, + 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, 0xb1, 0x6e, + 0x65, + txscript.OP_2, txscript.OP_CHECKMULTISIG, + }, + nil, + }, + { + []*btcutil.AddressPubKey{ + p2pkCompressedMain, + p2pkCompressed2Main, + }, + 3, + []byte{}, + txscript.ErrBadNumRequired, + }, + { + []*btcutil.AddressPubKey{ + p2pkUncompressedMain, + }, + 1, + []byte{ + txscript.OP_1, txscript.OP_DATA_65, + 0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, + 0x01, 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, + 0x1e, 0xb6, 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48, + 0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, 0x90, 0x9a, + 0x5c, 0xb2, 0xe0, 0xea, 0xdd, 0xfb, 0x84, 0xcc, + 0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b, + 0xfa, 0x9b, 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f, + 0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, 0xb4, 0x12, + 0xa3, + txscript.OP_1, txscript.OP_CHECKMULTISIG, + }, + nil, + }, + { + []*btcutil.AddressPubKey{ + p2pkUncompressedMain, + }, + 2, + []byte{}, + txscript.ErrBadNumRequired, + }, + } + + t.Logf("Running %d tests", len(tests)) + for i, test := range tests { + script, err := txscript.MultiSigScript(test.keys, + test.nrequired) + if err != test.err { + t.Errorf("MultiSigScript #%d unexpected error - "+ + "got %v, want %v", i, err, test.err) + continue + } + + if !bytes.Equal(script, test.expected) { + t.Errorf("MultiSigScript #%d got: %x\nwant: %x", + i, script, test.expected) + continue + } + } +} + +func TestCalcMultiSigStats(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + script []byte + expected error + }{ + { + name: "short script", + script: []byte{ + 0x04, 0x67, 0x08, 0xaf, 0xdb, 0x0f, 0xe5, 0x54, + 0x82, 0x71, 0x96, 0x7f, 0x1a, 0x67, 0x13, 0x0b, + 0x71, 0x05, 0xcd, 0x6a, 0x82, 0x8e, 0x03, 0x90, + 0x9a, 0x67, 0x96, 0x2e, 0x0e, 0xa1, 0xf6, 0x1d, + }, + expected: txscript.ErrStackShortScript, + }, + { + name: "stack underflow", + script: []byte{ + txscript.OP_RETURN, + txscript.OP_PUSHDATA1, + 0x29, + 0x04, 0x67, 0x08, 0xaf, 0xdb, 0x0f, 0xe5, 0x54, + 0x82, 0x71, 0x96, 0x7f, 0x1a, 0x67, 0x13, 0x0b, + 0x71, 0x05, 0xcd, 0x6a, 0x82, 0x8e, 0x03, 0x90, + 0x9a, 0x67, 0x96, 0x2e, 0x0e, 0xa1, 0xf6, 0x1d, + 0xeb, 0x64, 0x9f, 0x6b, 0xc3, 0xf4, 0xce, 0xf3, + 0x08, + }, + expected: txscript.ErrStackUnderflow, + }, + { + name: "multisig script", + script: []uint8{ + txscript.OP_FALSE, + txscript.OP_DATA_72, + 0x30, 0x45, 0x02, 0x20, 0x10, + 0x6a, 0x3e, 0x4e, 0xf0, 0xb5, + 0x1b, 0x76, 0x4a, 0x28, 0x87, + 0x22, 0x62, 0xff, 0xef, 0x55, + 0x84, 0x65, 0x14, 0xda, 0xcb, + 0xdc, 0xbb, 0xdd, 0x65, 0x2c, + 0x84, 0x9d, 0x39, 0x5b, 0x43, + 0x84, 0x02, 0x21, 0x00, 0xe0, + 0x3a, 0xe5, 0x54, 0xc3, 0xcb, + 0xb4, 0x06, 0x00, 0xd3, 0x1d, + 0xd4, 0x6f, 0xc3, 0x3f, 0x25, + 0xe4, 0x7b, 0xf8, 0x52, 0x5b, + 0x1f, 0xe0, 0x72, 0x82, 0xe3, + 0xb6, 0xec, 0xb5, 0xf3, 0xbb, + 0x28, 0x01, + txscript.OP_CODESEPARATOR, + txscript.OP_TRUE, + txscript.OP_DATA_33, + 0x02, 0x32, 0xab, 0xdc, 0x89, + 0x3e, 0x7f, 0x06, 0x31, 0x36, + 0x4d, 0x7f, 0xd0, 0x1c, 0xb3, + 0x3d, 0x24, 0xda, 0x45, 0x32, + 0x9a, 0x00, 0x35, 0x7b, 0x3a, + 0x78, 0x86, 0x21, 0x1a, 0xb4, + 0x14, 0xd5, 0x5a, + txscript.OP_TRUE, + txscript.OP_CHECKMULTISIG, + }, + expected: nil, + }, + } + + for i, test := range tests { + if _, _, err := txscript.CalcMultiSigStats(test.script); err != test.expected { + t.Errorf("CalcMultiSigStats #%d (%s) wrong result\n"+ + "got: %v\nwant: %v", i, test.name, err, + test.expected) + } + } +} + +type scriptTypeTest struct { + name string + script []byte + scripttype txscript.ScriptClass +} + +var scriptTypeTests = []scriptTypeTest{ + // tx 0437cd7f8525ceed2324359c2d0ba26006d92d85. + { + name: "Pay Pubkey", + script: []byte{ + txscript.OP_DATA_65, + 0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, 0x01, + 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, 0xb6, + 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, 0xd7, + 0xb1, 0x48, 0xa6, 0x90, 0x9a, 0x5c, 0xb2, 0xe0, 0xea, + 0xdd, 0xfb, 0x84, 0xcc, 0xf9, 0x74, 0x44, 0x64, 0xf8, + 0x2e, 0x16, 0x0b, 0xfa, 0x9b, 0x8b, 0x64, 0xf9, 0xd4, + 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, 0xb4, + 0x12, 0xa3, + txscript.OP_CHECKSIG, + }, + scripttype: txscript.PubKeyTy, + }, + // tx 599e47a8114fe098103663029548811d2651991b62397e057f0c863c2bc9f9ea + { + name: "Pay PubkeyHash", + script: []byte{ + txscript.OP_DUP, + txscript.OP_HASH160, + txscript.OP_DATA_20, + 0x66, 0x0d, 0x4e, 0xf3, 0xa7, 0x43, 0xe3, 0xe6, 0x96, + 0xad, 0x99, 0x03, 0x64, 0xe5, 0x55, 0xc2, 0x71, 0xad, + 0x50, 0x4b, + txscript.OP_EQUALVERIFY, + txscript.OP_CHECKSIG, + }, + scripttype: txscript.PubKeyHashTy, + }, + // part of tx 6d36bc17e947ce00bb6f12f8e7a56a1585c5a36188ffa2b05e10b4743273a74b + // codeseparator parts have been elided. (bitcoind's checks for multisig + // type doesn't have codesep etc either. + { + name: "multisig", + script: []byte{ + txscript.OP_TRUE, + txscript.OP_DATA_33, + 0x02, 0x32, 0xab, 0xdc, 0x89, 0x3e, 0x7f, 0x06, 0x31, + 0x36, 0x4d, 0x7f, 0xd0, 0x1c, 0xb3, 0x3d, 0x24, 0xda, + 0x45, 0x32, 0x9a, 0x00, 0x35, 0x7b, 0x3a, 0x78, 0x86, + 0x21, 0x1a, 0xb4, 0x14, 0xd5, 0x5a, + txscript.OP_TRUE, + txscript.OP_CHECKMULTISIG, + }, + scripttype: txscript.MultiSigTy, + }, + // tx e5779b9e78f9650debc2893fd9636d827b26b4ddfa6a8172fe8708c924f5c39d + // P2SH + { + name: "P2SH", + script: []byte{ + txscript.OP_HASH160, + txscript.OP_DATA_20, + 0x43, 0x3e, 0xc2, 0xac, 0x1f, 0xfa, 0x1b, 0x7b, 0x7d, + 0x02, 0x7f, 0x56, 0x45, 0x29, 0xc5, 0x71, 0x97, 0xf9, + 0xae, 0x88, + txscript.OP_EQUAL, + }, + scripttype: txscript.ScriptHashTy, + }, + // Nulldata with no data at all. + { + name: "nulldata", + script: []byte{ + txscript.OP_RETURN, + }, + scripttype: txscript.NullDataTy, + }, + // Nulldata with small data. + { + name: "nulldata2", + script: []byte{ + txscript.OP_RETURN, + txscript.OP_DATA_8, + 0x04, 0x67, 0x08, 0xaf, 0xdb, 0x0f, 0xe5, 0x54, + }, + scripttype: txscript.NullDataTy, + }, + // Nulldata with max allowed data. + { + name: "nulldata3", + script: []byte{ + txscript.OP_RETURN, + txscript.OP_PUSHDATA1, + 0x50, // 80 + 0x04, 0x67, 0x08, 0xaf, 0xdb, 0x0f, 0xe5, 0x54, + 0x82, 0x71, 0x96, 0x7f, 0x1a, 0x67, 0x13, 0x0b, + 0x71, 0x05, 0xcd, 0x6a, 0x82, 0x8e, 0x03, 0x90, + 0x9a, 0x67, 0x96, 0x2e, 0x0e, 0xa1, 0xf6, 0x1d, + 0xeb, 0x64, 0x9f, 0x6b, 0xc3, 0xf4, 0xce, 0xf3, + 0x04, 0x67, 0x08, 0xaf, 0xdb, 0x0f, 0xe5, 0x54, + 0x82, 0x71, 0x96, 0x7f, 0x1a, 0x67, 0x13, 0x0b, + 0x71, 0x05, 0xcd, 0x6a, 0x82, 0x8e, 0x03, 0x90, + 0x9a, 0x67, 0x96, 0x2e, 0x0e, 0xa1, 0xf6, 0x1d, + 0xeb, 0x64, 0x9f, 0x6b, 0xc3, 0xf4, 0xce, 0xf3, + }, + scripttype: txscript.NullDataTy, + }, + // Nulldata with more than max allowed data (so therefore nonstandard) + { + name: "nulldata4", + script: []byte{ + txscript.OP_RETURN, + txscript.OP_PUSHDATA1, + 0x51, // 81 + 0x04, 0x67, 0x08, 0xaf, 0xdb, 0x0f, 0xe5, 0x54, + 0x82, 0x71, 0x96, 0x7f, 0x1a, 0x67, 0x13, 0x0b, + 0x71, 0x05, 0xcd, 0x6a, 0x82, 0x8e, 0x03, 0x90, + 0x9a, 0x67, 0x96, 0x2e, 0x0e, 0xa1, 0xf6, 0x1d, + 0xeb, 0x64, 0x9f, 0x6b, 0xc3, 0xf4, 0xce, 0xf3, + 0x04, 0x67, 0x08, 0xaf, 0xdb, 0x0f, 0xe5, 0x54, + 0x82, 0x71, 0x96, 0x7f, 0x1a, 0x67, 0x13, 0x0b, + 0x71, 0x05, 0xcd, 0x6a, 0x82, 0x8e, 0x03, 0x90, + 0x9a, 0x67, 0x96, 0x2e, 0x0e, 0xa1, 0xf6, 0x1d, + 0xeb, 0x64, 0x9f, 0x6b, 0xc3, 0xf4, 0xce, 0xf3, + 0x08, + }, + scripttype: txscript.NonStandardTy, + }, + // Almost nulldata, but add an additional opcode after the data to make + // it nonstandard. + { + name: "nulldata5", + script: []byte{ + txscript.OP_RETURN, + txscript.OP_DATA_1, + 0x04, + txscript.OP_TRUE, + }, + scripttype: txscript.NonStandardTy, + }, // The next few are almost multisig (it is the more complex script type) + // but with various changes to make it fail. + { + // multisig but funny nsigs.. + name: "strange 1", + script: []byte{ + txscript.OP_DUP, + txscript.OP_DATA_33, + 0x02, 0x32, 0xab, 0xdc, 0x89, 0x3e, 0x7f, 0x06, 0x31, + 0x36, 0x4d, 0x7f, 0xd0, 0x1c, 0xb3, 0x3d, 0x24, 0xda, + 0x45, 0x32, 0x9a, 0x00, 0x35, 0x7b, 0x3a, 0x78, 0x86, + 0x21, 0x1a, 0xb4, 0x14, 0xd5, 0x5a, + txscript.OP_TRUE, + txscript.OP_CHECKMULTISIG, + }, + scripttype: txscript.NonStandardTy, + }, + { + name: "strange 2", + // multisig but funny pubkey. + script: []byte{ + txscript.OP_TRUE, + txscript.OP_TRUE, + txscript.OP_TRUE, + txscript.OP_CHECKMULTISIG, + }, + scripttype: txscript.NonStandardTy, + }, + { + name: "strange 3", + // multisig but no matching npubkeys opcode. + script: []byte{ + txscript.OP_TRUE, + txscript.OP_DATA_33, + 0x02, 0x32, 0xab, 0xdc, 0x89, 0x3e, 0x7f, 0x06, 0x31, + 0x36, 0x4d, 0x7f, 0xd0, 0x1c, 0xb3, 0x3d, 0x24, 0xda, + 0x45, 0x32, 0x9a, 0x00, 0x35, 0x7b, 0x3a, 0x78, 0x86, + 0x21, 0x1a, 0xb4, 0x14, 0xd5, 0x5a, + txscript.OP_DATA_33, + 0x02, 0x32, 0xab, 0xdc, 0x89, 0x3e, 0x7f, 0x06, 0x31, + 0x36, 0x4d, 0x7f, 0xd0, 0x1c, 0xb3, 0x3d, 0x24, 0xda, + 0x45, 0x32, 0x9a, 0x00, 0x35, 0x7b, 0x3a, 0x78, 0x86, + 0x21, 0x1a, 0xb4, 0x14, 0xd5, 0x5a, + // No number. + txscript.OP_CHECKMULTISIG, + }, + scripttype: txscript.NonStandardTy, + }, + { + name: "strange 4", + // multisig but with multisigverify + script: []byte{ + txscript.OP_TRUE, + txscript.OP_DATA_33, + 0x02, 0x32, 0xab, 0xdc, 0x89, 0x3e, 0x7f, 0x06, 0x31, + 0x36, 0x4d, 0x7f, 0xd0, 0x1c, 0xb3, 0x3d, 0x24, 0xda, + 0x45, 0x32, 0x9a, 0x00, 0x35, 0x7b, 0x3a, 0x78, 0x86, + 0x21, 0x1a, 0xb4, 0x14, 0xd5, 0x5a, + txscript.OP_TRUE, + txscript.OP_CHECKMULTISIGVERIFY, + }, + scripttype: txscript.NonStandardTy, + }, + { + name: "strange 5", + // multisig but wrong length. + script: []byte{ + txscript.OP_TRUE, + txscript.OP_CHECKMULTISIG, + }, + scripttype: txscript.NonStandardTy, + }, + { + name: "doesn't parse", + script: []byte{ + txscript.OP_DATA_5, 0x1, 0x2, 0x3, 0x4, + }, + scripttype: txscript.NonStandardTy, + }, +} + +func testScriptType(t *testing.T, test *scriptTypeTest) { + scripttype := txscript.GetScriptClass(test.script) + if scripttype != test.scripttype { + t.Errorf("%s: expected %s got %s", test.name, test.scripttype, + scripttype) + } +} + +func TestScriptTypes(t *testing.T) { + t.Parallel() + + for i := range scriptTypeTests { + testScriptType(t, &scriptTypeTests[i]) + } +} + +func TestStringifyClass(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + scriptclass txscript.ScriptClass + stringed string + }{ + { + name: "nonstandardty", + scriptclass: txscript.NonStandardTy, + stringed: "nonstandard", + }, + { + name: "pubkey", + scriptclass: txscript.PubKeyTy, + stringed: "pubkey", + }, + { + name: "pubkeyhash", + scriptclass: txscript.PubKeyHashTy, + stringed: "pubkeyhash", + }, + { + name: "scripthash", + scriptclass: txscript.ScriptHashTy, + stringed: "scripthash", + }, + { + name: "multisigty", + scriptclass: txscript.MultiSigTy, + stringed: "multisig", + }, + { + name: "nulldataty", + scriptclass: txscript.NullDataTy, + stringed: "nulldata", + }, + { + name: "broken", + scriptclass: txscript.ScriptClass(255), + stringed: "Invalid", + }, + } + + for _, test := range tests { + typeString := test.scriptclass.String() + if typeString != test.stringed { + t.Errorf("%s: got \"%s\" expected \"%s\"", test.name, + typeString, test.stringed) + } + } +}