kaspad/txscript/standard_test.go

526 lines
14 KiB
Go

// Copyright (c) 2013-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package txscript
import (
"bytes"
"reflect"
"testing"
"github.com/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/util"
)
// mustParseShortForm parses the passed short form script 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 mustParseShortForm(script string) []byte {
s, err := parseShortForm(script)
if err != nil {
panic("invalid short form script in test source: err " +
err.Error() + ", script: " + script)
}
return s
}
// newAddressPubKeyHash returns a new util.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) util.Address {
addr, err := util.NewAddressPubKeyHash(pkHash, util.Bech32PrefixDAGCoin)
if err != nil {
panic("invalid public key hash in test source")
}
return addr
}
// newAddressScriptHash returns a new util.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) util.Address {
addr, err := util.NewAddressScriptHashFromHash(scriptHash,
util.Bech32PrefixDAGCoin)
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) {
t.Parallel()
tests := []struct {
name string
script []byte
addrs []util.Address
reqSigs int
class ScriptClass
}{
{
name: "standard p2pkh",
script: hexToBytes("76a914ad06dd6ddee55cbca9a9e3713bd" +
"7587509a3056488ac"),
addrs: []util.Address{
newAddressPubKeyHash(hexToBytes("ad06dd6ddee5" +
"5cbca9a9e3713bd7587509a30564")),
},
reqSigs: 1,
class: PubKeyHashTy,
},
{
name: "standard p2sh",
script: hexToBytes("a91463bcc565f9e68ee0189dd5cc67f1b" +
"0e5f02f45cb87"),
addrs: []util.Address{
newAddressScriptHash(hexToBytes("63bcc565f9e6" +
"8ee0189dd5cc67f1b0e5f02f45cb")),
},
reqSigs: 1,
class: ScriptHashTy,
},
// 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: hexToBytes("410411db93e1dcdb8a016b49840f8c53b" +
"c1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddf" +
"b84ccf9744464f82e160bfa9b8b64f9d4c03f999b864" +
"3f656b412a3"),
addrs: nil,
reqSigs: 0,
class: NonStandardTy,
},
{
name: "valid signature from a sigscript - no addresses",
script: hexToBytes("47304402204e45e16932b8af514961a1d" +
"3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41022" +
"0181522ec8eca07de4860a4acdd12909d831cc56cbba" +
"c4622082221a8768d1d0901"),
addrs: nil,
reqSigs: 0,
class: 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: hexToBytes("493046022100ddc69738bf2336318e4e0" +
"41a5a77f305da87428ab1606f023260017854350ddc0" +
"22100817af09d2eec36862d16009852b7e3a0f6dd765" +
"98290b7834e1453660367e07a014104cd4240c198e12" +
"523b6f9cb9f5bed06de1ba37e96a1bbd13745fcf9d11" +
"c25b1dff9a519675d198804ba9962d3eca2d5937d58e" +
"5a75a71042d40388a4d307f887d"),
addrs: nil,
reqSigs: 0,
class: NonStandardTy,
},
{
name: "empty script",
script: []byte{},
addrs: nil,
reqSigs: 0,
class: NonStandardTy,
},
{
name: "script that does not parse",
script: []byte{OpData45},
addrs: nil,
reqSigs: 0,
class: NonStandardTy,
},
}
t.Logf("Running %d tests.", len(tests))
for i, test := range tests {
class, addrs, reqSigs, err := ExtractPkScriptAddrs(
test.script, &dagconfig.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
}
}
}
// TestCalcScriptInfo ensures the CalcScriptInfo provides the expected results
// for various valid and invalid script pairs.
func TestCalcScriptInfo(t *testing.T) {
t.Parallel()
tests := []struct {
name string
sigScript string
pkScript string
isP2SH bool
scriptInfo ScriptInfo
scriptInfoErr error
}{
{
// Invented scripts, the hashes do not match
// Truncated version of test below:
name: "pkscript doesn't parse",
sigScript: "1 81 DATA_8 2DUP EQUAL NOT VERIFY ABS " +
"SWAP ABS EQUAL",
pkScript: "HASH160 DATA_20 0xfe441065b6532231de2fac56" +
"3152205ec4f59c",
isP2SH: true,
scriptInfoErr: scriptError(ErrMalformedPush, ""),
},
{
name: "sigScript doesn't parse",
// Truncated version of p2sh script below.
sigScript: "1 81 DATA_8 2DUP EQUAL NOT VERIFY ABS " +
"SWAP ABS",
pkScript: "HASH160 DATA_20 0xfe441065b6532231de2fac56" +
"3152205ec4f59c74 EQUAL",
isP2SH: true,
scriptInfoErr: scriptError(ErrMalformedPush, ""),
},
{
// Invented scripts, the hashes do not match
name: "p2sh standard script",
sigScript: "1 81 DATA_25 DUP HASH160 DATA_20 0x010203" +
"0405060708090a0b0c0d0e0f1011121314 EQUALVERIFY " +
"CHECKSIG",
pkScript: "HASH160 DATA_20 0xfe441065b6532231de2fac56" +
"3152205ec4f59c74 EQUAL",
isP2SH: true,
scriptInfo: ScriptInfo{
PkScriptClass: ScriptHashTy,
NumInputs: 3,
ExpectedInputs: 3, // nonstandard p2sh.
SigOps: 1,
},
},
{
// from 567a53d1ce19ce3d07711885168484439965501536d0d0294c5d46d46c10e53b
// from the blockchain.
name: "p2sh nonstandard script",
sigScript: "1 81 DATA_8 2DUP EQUAL NOT VERIFY ABS " +
"SWAP ABS EQUAL",
pkScript: "HASH160 DATA_20 0xfe441065b6532231de2fac56" +
"3152205ec4f59c74 EQUAL",
isP2SH: true,
scriptInfo: ScriptInfo{
PkScriptClass: ScriptHashTy,
NumInputs: 3,
ExpectedInputs: -1, // nonstandard p2sh.
SigOps: 0,
},
},
}
for _, test := range tests {
sigScript := mustParseShortForm(test.sigScript)
pkScript := mustParseShortForm(test.pkScript)
si, err := CalcScriptInfo(sigScript, pkScript, test.isP2SH)
if e := checkScriptError(err, test.scriptInfoErr); e != nil {
t.Errorf("scriptinfo test %q: %v", test.name, e)
continue
}
if err != nil {
continue
}
if *si != test.scriptInfo {
t.Errorf("%s: scriptinfo doesn't match expected. "+
"got: %q expected %q", test.name, *si,
test.scriptInfo)
continue
}
}
}
// bogusAddress implements the util.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 satisfy the
// util.Address interface.
func (b *bogusAddress) EncodeAddress() string {
return ""
}
// ScriptAddress simply returns an empty byte slice. It exists to satisfy the
// util.Address interface.
func (b *bogusAddress) ScriptAddress() []byte {
return nil
}
// IsForPrefix lies blatantly to satisfy the util.Address interface.
func (b *bogusAddress) IsForPrefix(prefix util.Bech32Prefix) bool {
return true // why not?
}
// String simply returns an empty string. It exists to satisfy the
// util.Address interface.
func (b *bogusAddress) String() string {
return ""
}
// TestPayToAddrScript ensures the PayToAddrScript function generates the
// correct scripts for the various types of addresses.
func TestPayToAddrScript(t *testing.T) {
t.Parallel()
// 1MirQ9bwyQcGVJPwKUgapu5ouK2E2Ey4gX
p2pkhMain, err := util.NewAddressPubKeyHash(hexToBytes("e34cce70c86"+
"373273efcc54ce7d2a491bb4a0e84"), util.Bech32PrefixDAGCoin)
if err != nil {
t.Fatalf("Unable to create public key hash address: %v", err)
}
// Taken from transaction:
// b0539a45de13b3e0403909b8bd1a555b8cbe45fd4e3f3fda76f3a5f52835c29d
p2shMain, _ := util.NewAddressScriptHashFromHash(hexToBytes("e8c300"+
"c87986efa84c37c0519929019ef86eb5b4"), util.Bech32PrefixDAGCoin)
if err != nil {
t.Fatalf("Unable to create script hash address: %v", err)
}
// Errors used in the tests below defined here for convenience and to
// keep the horizontal test size shorter.
errUnsupportedAddress := scriptError(ErrUnsupportedAddress, "")
tests := []struct {
in util.Address
expected string
err error
}{
// pay-to-pubkey-hash address on mainnet
{
p2pkhMain,
"DUP HASH160 DATA_20 0xe34cce70c86373273efcc54ce7d2a4" +
"91bb4a0e8488 CHECKSIG",
nil,
},
// pay-to-script-hash address on mainnet
{
p2shMain,
"HASH160 DATA_20 0xe8c300c87986efa84c37c0519929019ef8" +
"6eb5b4 EQUAL",
nil,
},
// Supported address types with nil pointers.
{(*util.AddressPubKeyHash)(nil), "", errUnsupportedAddress},
{(*util.AddressScriptHash)(nil), "", errUnsupportedAddress},
// Unsupported address type.
{&bogusAddress{}, "", errUnsupportedAddress},
}
t.Logf("Running %d tests", len(tests))
for i, test := range tests {
pkScript, err := PayToAddrScript(test.in)
if e := checkScriptError(err, test.err); e != nil {
t.Errorf("PayToAddrScript #%d unexpected error - "+
"got %v, want %v", i, err, test.err)
continue
}
expected := mustParseShortForm(test.expected)
if !bytes.Equal(pkScript, expected) {
t.Errorf("PayToAddrScript #%d got: %x\nwant: %x",
i, pkScript, expected)
continue
}
}
}
// scriptClassTests houses several test scripts used to ensure various class
// determination is working as expected. It's defined as a test global versus
// inside a function scope since this spans both the standard tests and the
// consensus tests (pay-to-script-hash is part of consensus).
var scriptClassTests = []struct {
name string
script string
class ScriptClass
}{
// p2pk. It is standard in BTC but not in DAGCoin
{
name: "Pay Pubkey",
script: "DATA_65 0x0411db93e1dcdb8a016b49840f8c53bc1eb68a382e" +
"97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e16" +
"0bfa9b8b64f9d4c03f999b8643f656b412a3 CHECKSIG",
class: NonStandardTy,
},
// tx 599e47a8114fe098103663029548811d2651991b62397e057f0c863c2bc9f9ea
{
name: "Pay PubkeyHash",
script: "DUP HASH160 DATA_20 0x660d4ef3a743e3e696ad990364e555" +
"c271ad504b EQUALVERIFY CHECKSIG",
class: PubKeyHashTy,
},
// mutlisig. It is standard in BTC but not in DAGCoin
{
name: "multisig",
script: "1 DATA_33 0x0232abdc893e7f0631364d7fd01cb33d24da4" +
"5329a00357b3a7886211ab414d55a 1 CHECKMULTISIG",
class: NonStandardTy,
},
// tx e5779b9e78f9650debc2893fd9636d827b26b4ddfa6a8172fe8708c924f5c39d
{
name: "P2SH",
script: "HASH160 DATA_20 0x433ec2ac1ffa1b7b7d027f564529c57197f" +
"9ae88 EQUAL",
class: ScriptHashTy,
},
{
// Nulldata. It is standard in BTC but not in DAGCoin
name: "nulldata",
script: "RETURN 0",
class: NonStandardTy,
},
// The next few are almost multisig (it is the more complex script type)
// but with various changes to make it fail.
{
// Multisig but invalid nsigs.
name: "strange 1",
script: "DUP DATA_33 0x0232abdc893e7f0631364d7fd01cb33d24da45" +
"329a00357b3a7886211ab414d55a 1 CHECKMULTISIG",
class: NonStandardTy,
},
{
// Multisig but invalid pubkey.
name: "strange 2",
script: "1 1 1 CHECKMULTISIG",
class: NonStandardTy,
},
{
// Multisig but no matching npubkeys opcode.
name: "strange 3",
script: "1 DATA_33 0x0232abdc893e7f0631364d7fd01cb33d24da4532" +
"9a00357b3a7886211ab414d55a DATA_33 0x0232abdc893e7f0" +
"631364d7fd01cb33d24da45329a00357b3a7886211ab414d55a " +
"CHECKMULTISIG",
class: NonStandardTy,
},
{
// Multisig but with multisigverify.
name: "strange 4",
script: "1 DATA_33 0x0232abdc893e7f0631364d7fd01cb33d24da4532" +
"9a00357b3a7886211ab414d55a 1 CHECKMULTISIGVERIFY",
class: NonStandardTy,
},
{
// Multisig but wrong length.
name: "strange 5",
script: "1 CHECKMULTISIG",
class: NonStandardTy,
},
{
name: "doesn't parse",
script: "DATA_5 0x01020304",
class: NonStandardTy,
},
{
name: "multisig script with wrong number of pubkeys",
script: "2 " +
"DATA_33 " +
"0x027adf5df7c965a2d46203c781bd4dd8" +
"21f11844136f6673af7cc5a4a05cd29380 " +
"DATA_33 " +
"0x02c08f3de8ee2de9be7bd770f4c10eb0" +
"d6ff1dd81ee96eedd3a9d4aeaf86695e80 " +
"3 CHECKMULTISIG",
class: NonStandardTy,
},
}
// TestScriptClass ensures all the scripts in scriptClassTests have the expected
// class.
func TestScriptClass(t *testing.T) {
t.Parallel()
for _, test := range scriptClassTests {
script := mustParseShortForm(test.script)
class := GetScriptClass(script)
if class != test.class {
t.Errorf("%s: expected %s got %s (script %x)", test.name,
test.class, class, script)
continue
}
}
}
// TestStringifyClass ensures the script class string returns the expected
// string for each script class.
func TestStringifyClass(t *testing.T) {
t.Parallel()
tests := []struct {
name string
class ScriptClass
stringed string
}{
{
name: "nonstandardty",
class: NonStandardTy,
stringed: "nonstandard",
},
{
name: "pubkeyhash",
class: PubKeyHashTy,
stringed: "pubkeyhash",
},
{
name: "scripthash",
class: ScriptHashTy,
stringed: "scripthash",
},
{
name: "broken",
class: ScriptClass(255),
stringed: "Invalid",
},
}
for _, test := range tests {
typeString := test.class.String()
if typeString != test.stringed {
t.Errorf("%s: got %#q, want %#q", test.name,
typeString, test.stringed)
}
}
}