mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00

* Replace p2pkh with p2pk * Fix tests * Fix comments and variable names * Add README.md for genkeypair * Rename pubkey->publicKey * Rename p2pkh to p2pk * Use util.PublicKeySize where needed * Remove redundant pointer * Fix comment * Rename pubKey->publicKey
536 lines
15 KiB
Go
536 lines
15 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"
|
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/kaspanet/kaspad/domain/dagconfig"
|
|
"github.com/kaspanet/kaspad/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, version uint16) []byte {
|
|
s, err := parseShortForm(script, version)
|
|
if err != nil {
|
|
panic("invalid short form script in test source: err " +
|
|
err.Error() + ", script: " + script)
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// newAddressPublicKey returns a new util.AddressPublicKey from the
|
|
// provided 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 newAddressPublicKey(publicKey []byte) util.Address {
|
|
addr, err := util.NewAddressPublicKey(publicKey, util.Bech32PrefixKaspa)
|
|
if err != nil {
|
|
panic("invalid public key 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.Bech32PrefixKaspa)
|
|
if err != nil {
|
|
panic("invalid script hash in test source")
|
|
}
|
|
|
|
return addr
|
|
}
|
|
|
|
// TestExtractScriptPubKeyAddrs ensures that extracting the type, addresses, and
|
|
// number of required signatures from scriptPubKeys works as intended.
|
|
func TestExtractScriptPubKeyAddrs(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
script *externalapi.ScriptPublicKey
|
|
addr util.Address
|
|
class ScriptClass
|
|
}{
|
|
{
|
|
name: "standard p2pk",
|
|
script: &externalapi.ScriptPublicKey{
|
|
Script: hexToBytes("202454a285d8566b0cb2792919536ee0f1b6f69b58ba59e9850ecbc91eef722daeac"),
|
|
Version: 0,
|
|
},
|
|
addr: newAddressPublicKey(hexToBytes("2454a285d8566b0cb2792919536ee0f1b6f69b58ba59e9850ecbc91eef722dae")),
|
|
class: PubKeyTy,
|
|
},
|
|
{
|
|
name: "standard p2sh",
|
|
script: &externalapi.ScriptPublicKey{
|
|
Script: hexToBytes("aa2063bcc565f9e68ee0189dd5cc67f1b" +
|
|
"0e5f02f45cbad06dd6ddee55cbca9a9e37187"),
|
|
Version: 0,
|
|
},
|
|
addr: newAddressScriptHash(hexToBytes("63bcc565f9e6" +
|
|
"8ee0189dd5cc67f1b0e5f02f45cbad06dd6ddee55cbca9a9e371")),
|
|
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: &externalapi.ScriptPublicKey{
|
|
Script: hexToBytes("410411db93e1dcdb8a016b49840f8c53b" +
|
|
"c1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddf" +
|
|
"b84ccf9744464f82e160bfa9b8b64f9d4c03f999b864" +
|
|
"3f656b412a3"),
|
|
Version: 0,
|
|
},
|
|
addr: nil,
|
|
class: NonStandardTy,
|
|
},
|
|
{
|
|
name: "valid signature from a sigscript - no addresses",
|
|
script: &externalapi.ScriptPublicKey{
|
|
Script: hexToBytes("47304402204e45e16932b8af514961a1d" +
|
|
"3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41022" +
|
|
"0181522ec8eca07de4860a4acdd12909d831cc56cbba" +
|
|
"c4622082221a8768d1d0901"),
|
|
Version: 0,
|
|
},
|
|
addr: nil,
|
|
class: NonStandardTy,
|
|
},
|
|
// Note the technically the pubkey is the second item on the
|
|
// stack, but since the address extraction intentionally only
|
|
// works with standard scriptPubKeys, this should not return any
|
|
// addresses.
|
|
{
|
|
name: "valid sigscript to reedeem p2pk - no addresses",
|
|
script: &externalapi.ScriptPublicKey{
|
|
Script: hexToBytes("493046022100ddc69738bf2336318e4e0" +
|
|
"41a5a77f305da87428ab1606f023260017854350ddc0" +
|
|
"22100817af09d2eec36862d16009852b7e3a0f6dd765" +
|
|
"98290b7834e1453660367e07a014104cd4240c198e12" +
|
|
"523b6f9cb9f5bed06de1ba37e96a1bbd13745fcf9d11" +
|
|
"c25b1dff9a519675d198804ba9962d3eca2d5937d58e" +
|
|
"5a75a71042d40388a4d307f887d"),
|
|
Version: 0,
|
|
},
|
|
addr: nil,
|
|
class: NonStandardTy,
|
|
},
|
|
{
|
|
name: "empty script",
|
|
script: &externalapi.ScriptPublicKey{
|
|
Script: []byte{},
|
|
Version: 0,
|
|
},
|
|
addr: nil,
|
|
class: NonStandardTy,
|
|
},
|
|
{
|
|
name: "script that does not parse",
|
|
script: &externalapi.ScriptPublicKey{
|
|
Script: []byte{OpData45},
|
|
Version: 0,
|
|
},
|
|
addr: nil,
|
|
class: NonStandardTy,
|
|
},
|
|
}
|
|
|
|
t.Logf("Running %d tests.", len(tests))
|
|
for i, test := range tests {
|
|
class, addr, _ := ExtractScriptPubKeyAddress(
|
|
test.script, &dagconfig.MainnetParams)
|
|
|
|
if !reflect.DeepEqual(addr, test.addr) {
|
|
t.Errorf("ExtractScriptPubKeyAddress #%d (%s) unexpected "+
|
|
"address\ngot %v\nwant %v", i, test.name,
|
|
addr, test.addr)
|
|
continue
|
|
}
|
|
|
|
if class != test.class {
|
|
t.Errorf("ExtractScriptPubKeyAddress #%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
|
|
scriptPubKey string
|
|
|
|
isP2SH bool
|
|
|
|
scriptInfo ScriptInfo
|
|
scriptInfoErr error
|
|
}{
|
|
{
|
|
// Invented scripts, the hashes do not match
|
|
// Truncated version of test below:
|
|
name: "scriptPubKey doesn't parse",
|
|
sigScript: "1 81 DATA_8 2DUP EQUAL NOT VERIFY ABS " +
|
|
"SWAP ABS EQUAL",
|
|
scriptPubKey: "BLAKE2B DATA_32 0xfe441065b6532231de2fac56" +
|
|
"3152205ec4f59cfe441065b6532231de2fac56",
|
|
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",
|
|
scriptPubKey: "BLAKE2B DATA_32 0xfe441065b6532231de2fac56" +
|
|
"3152205ec4f59c74fe441065b6532231de2fac56 EQUAL",
|
|
isP2SH: true,
|
|
scriptInfoErr: scriptError(ErrMalformedPush, ""),
|
|
},
|
|
{
|
|
// Invented scripts, the hashes do not match
|
|
name: "p2sh standard script",
|
|
sigScript: "1 81 DATA_34 DATA_32 0x010203" +
|
|
"0405060708090a0b0c0d0e0f1011121314fe441065b6532231de2fac56 " +
|
|
"CHECKSIG",
|
|
scriptPubKey: "BLAKE2B DATA_32 0xfe441065b6532231de2fac56" +
|
|
"3152205ec4f59c74fe441065b6532231de2fac56 EQUAL",
|
|
isP2SH: true,
|
|
scriptInfo: ScriptInfo{
|
|
ScriptPubKeyClass: ScriptHashTy,
|
|
NumInputs: 3,
|
|
ExpectedInputs: 2, // nonstandard p2sh.
|
|
SigOps: 1,
|
|
},
|
|
},
|
|
{
|
|
name: "p2sh nonstandard script",
|
|
sigScript: "1 81 DATA_8 2DUP EQUAL NOT VERIFY ABS " +
|
|
"SWAP ABS EQUAL",
|
|
scriptPubKey: "BLAKE2B DATA_32 0xfe441065b6532231de2fac56" +
|
|
"3152205ec4f59c74fe441065b6532231de2fac56 EQUAL",
|
|
isP2SH: true,
|
|
scriptInfo: ScriptInfo{
|
|
ScriptPubKeyClass: ScriptHashTy,
|
|
NumInputs: 3,
|
|
ExpectedInputs: -1, // nonstandard p2sh.
|
|
SigOps: 0,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
sigScript := mustParseShortForm(test.sigScript, 0)
|
|
scriptPubKey := mustParseShortForm(test.scriptPubKey, 0)
|
|
|
|
si, err := CalcScriptInfo(sigScript, scriptPubKey, 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 ""
|
|
}
|
|
|
|
func (b *bogusAddress) Prefix() util.Bech32Prefix {
|
|
return util.Bech32PrefixUnknown
|
|
}
|
|
|
|
// TestPayToAddrScript ensures the PayToAddrScript function generates the
|
|
// correct scripts for the various types of addresses.
|
|
func TestPayToAddrScript(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
p2pkMain, err := util.NewAddressPublicKey(hexToBytes("e34cce70c86"+
|
|
"373273efcc54ce7d2a491bb4a0e84e34cce70c86373273efcc54c"), util.Bech32PrefixKaspa)
|
|
if err != nil {
|
|
t.Fatalf("Unable to create public key address: %v", err)
|
|
}
|
|
|
|
p2shMain, err := util.NewAddressScriptHashFromHash(hexToBytes("e8c300"+
|
|
"c87986efa84c37c0519929019ef86eb5b4e34cce70c86373273efcc54c"), util.Bech32PrefixKaspa)
|
|
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
|
|
expectedScript string
|
|
expectedVersion uint16
|
|
err error
|
|
}{
|
|
// pay-to-pubkey address on mainnet
|
|
{
|
|
p2pkMain,
|
|
"DATA_32 0xe34cce70c86373273efcc54ce7d2a4" +
|
|
"91bb4a0e84e34cce70c86373273efcc54c CHECKSIG",
|
|
0,
|
|
nil,
|
|
},
|
|
// pay-to-script-hash address on mainnet
|
|
{
|
|
p2shMain,
|
|
"BLAKE2B DATA_32 0xe8c300c87986efa84c37c0519929019ef8" +
|
|
"6eb5b4e34cce70c86373273efcc54c EQUAL",
|
|
0,
|
|
nil,
|
|
},
|
|
|
|
// Supported address types with nil pointers.
|
|
{(*util.AddressPublicKey)(nil), "", 0, errUnsupportedAddress},
|
|
{(*util.AddressScriptHash)(nil), "", 0, errUnsupportedAddress},
|
|
|
|
// Unsupported address type.
|
|
{&bogusAddress{}, "", 0, errUnsupportedAddress},
|
|
}
|
|
|
|
t.Logf("Running %d tests", len(tests))
|
|
for i, test := range tests {
|
|
scriptPublicKey, 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
|
|
}
|
|
|
|
var scriptPublicKeyScript []byte
|
|
var scriptPublicKeyVersion uint16
|
|
if scriptPublicKey != nil {
|
|
scriptPublicKeyScript = scriptPublicKey.Script
|
|
scriptPublicKeyVersion = scriptPublicKey.Version
|
|
}
|
|
|
|
expectedVersion := test.expectedVersion
|
|
expectedScript := mustParseShortForm(test.expectedScript, test.expectedVersion)
|
|
if !bytes.Equal(scriptPublicKeyScript, expectedScript) {
|
|
t.Errorf("PayToAddrScript #%d got: %x\nwant: %x",
|
|
i, scriptPublicKey, expectedScript)
|
|
continue
|
|
}
|
|
if scriptPublicKeyVersion != expectedVersion {
|
|
t.Errorf("PayToAddrScript #%d got version: %d\nwant: %d",
|
|
i, scriptPublicKeyVersion, expectedVersion)
|
|
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
|
|
{
|
|
name: "Pay Pubkey",
|
|
script: "DATA_32 0x89ac24ea10bb751af4939623ccc5e550d96842b64e8fca0f63e94b4373fd555e CHECKSIG",
|
|
class: PubKeyTy,
|
|
},
|
|
{
|
|
name: "Pay PubkeyHash",
|
|
script: "DUP BLAKE2B DATA_32 0x660d4ef3a743e3e696ad990364e55543e3e696ad990364e555e555" +
|
|
"c271ad504b EQUALVERIFY CHECKSIG",
|
|
class: NonStandardTy,
|
|
},
|
|
// mutlisig
|
|
{
|
|
name: "multisig",
|
|
script: "1 DATA_33 0x0232abdc893e7f0631364d7fd01cb33d24da4" +
|
|
"5329a00357b3a7886211ab414d55a 1 CHECKMULTISIG",
|
|
class: NonStandardTy,
|
|
},
|
|
{
|
|
name: "P2SH",
|
|
script: "BLAKE2B DATA_32 0x433ec2ac1ffa1b7b7d027f564529c57197fa1b7b7d027f564529c57197f" +
|
|
"9ae88 EQUAL",
|
|
class: ScriptHashTy,
|
|
},
|
|
|
|
{
|
|
// Nulldata. It is standard in Bitcoin but not in Kaspa
|
|
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, 0)
|
|
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: "pubkey",
|
|
class: PubKeyTy,
|
|
stringed: "pubkey",
|
|
},
|
|
{
|
|
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)
|
|
}
|
|
}
|
|
}
|