// Copyright (c) 2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package stdaddr

import (
	"bytes"
	"encoding/hex"
	"errors"
	"testing"

	"github.com/decred/base58"
	"github.com/decred/dcrd/crypto/ripemd160"
	"github.com/decred/dcrd/dcrec/edwards/v2"
	"github.com/decred/dcrd/dcrec/secp256k1/v4"
)

// mockAddrParams implements the AddressParams interface and is used throughout
// the tests to mock multiple networks.
type mockAddrParams struct {
	pubKeyID     [2]byte
	pkhEcdsaID   [2]byte
	pkhEd25519ID [2]byte
	pkhSchnorrID [2]byte
	scriptHashID [2]byte
	privKeyID    [2]byte
}

// AddrIDPubKeyV0 returns the magic prefix bytes associated with the mock params
// for version 0 pay-to-pubkey addresses.
//
// This is part of the AddressParams interface.
func (p *mockAddrParams) AddrIDPubKeyV0() [2]byte {
	return p.pubKeyID
}

// AddrIDPubKeyHashECDSAV0 returns the magic prefix bytes associated with the
// mock params for version 0 pay-to-pubkey-hash addresses where the underlying
// pubkey is secp256k1 and the signature algorithm is ECDSA.
//
// This is part of the AddressParams interface.
func (p *mockAddrParams) AddrIDPubKeyHashECDSAV0() [2]byte {
	return p.pkhEcdsaID
}

// AddrIDPubKeyHashEd25519V0 returns the magic prefix bytes associated with the
// mock params for version 0 pay-to-pubkey-hash addresses where the underlying
// pubkey and signature algorithm are Ed25519.
//
// This is part of the AddressParams interface.
func (p *mockAddrParams) AddrIDPubKeyHashEd25519V0() [2]byte {
	return p.pkhEd25519ID
}

// AddrIDPubKeyHashSchnorrV0 returns the magic prefix bytes associated with the
// mock params for version 0 pay-to-pubkey-hash addresses where the underlying
// pubkey is secp256k1 and the signature algorithm is Schnorr.
//
// This is part of the AddressParams interface.
func (p *mockAddrParams) AddrIDPubKeyHashSchnorrV0() [2]byte {
	return p.pkhSchnorrID
}

// AddrIDScriptHashV0 returns the magic prefix bytes associated with the mock
// params for version 0 pay-to-script-hash addresses.
//
// This is part of the AddressParams interface.
func (p *mockAddrParams) AddrIDScriptHashV0() [2]byte {
	return p.scriptHashID
}

// mockMainNetParams returns mock mainnet address parameters to use throughout
// the tests.  They match the Decred mainnet params as of the time this comment
// was written.
func mockMainNetParams() *mockAddrParams {
	return &mockAddrParams{
		pubKeyID:     [2]byte{0x13, 0x86}, // starts with Dk
		pkhEcdsaID:   [2]byte{0x07, 0x3f}, // starts with Ds
		pkhEd25519ID: [2]byte{0x07, 0x1f}, // starts with De
		pkhSchnorrID: [2]byte{0x07, 0x01}, // starts with DS
		scriptHashID: [2]byte{0x07, 0x1a}, // starts with Dc
		privKeyID:    [2]byte{0x22, 0xde}, // starts with Pm
	}
}

// mockTestNetParams returns mock testnet address parameters to use throughout
// the tests.  They match the Decred testnet params as of the time this comment
// was written.
func mockTestNetParams() *mockAddrParams {
	return &mockAddrParams{
		pubKeyID:     [2]byte{0x28, 0xf7}, // starts with Tk
		pkhEcdsaID:   [2]byte{0x0f, 0x21}, // starts with Ts
		pkhEd25519ID: [2]byte{0x0f, 0x01}, // starts with Te
		pkhSchnorrID: [2]byte{0x0e, 0xe3}, // starts with TS
		scriptHashID: [2]byte{0x0e, 0xfc}, // starts with Tc
		privKeyID:    [2]byte{0x23, 0x0e}, // starts with Pt
	}
}

// mockRegNetParams returns mock regression test address parameters to use
// throughout the tests.  They match the Decred regnet params as of the time
// this comment was written.
func mockRegNetParams() *mockAddrParams {
	return &mockAddrParams{
		pubKeyID:     [2]byte{0x25, 0xe5}, // starts with Rk
		pkhEcdsaID:   [2]byte{0x0e, 0x00}, // starts with Rs
		pkhEd25519ID: [2]byte{0x0d, 0xe0}, // starts with Re
		pkhSchnorrID: [2]byte{0x0d, 0xc2}, // starts with RS
		scriptHashID: [2]byte{0x0d, 0xdb}, // starts with Rc
		privKeyID:    [2]byte{0x22, 0xfe}, // starts with Pr
	}
}

// hexToBytes converts the passed hex string into bytes and will panic if there
// is an error.  This is only provided for the hard-coded constants so errors in
// the source code can be detected. It will only (and must only) be called with
// hard-coded values.
func hexToBytes(s string) []byte {
	b, err := hex.DecodeString(s)
	if err != nil {
		panic("invalid hex in source file: " + s)
	}
	return b
}

// TestAddresses ensures that address-related APIs work as intended including
// that they are properly encoded and decoded, that they produce the expected
// payment-related scripts, and that error paths fail as expected.  For
// addresses that implement the stake address interface, the stake-related
// scripts are also tested.
func TestAddresses(t *testing.T) {
	mainNetParams := mockMainNetParams()
	testNetParams := mockTestNetParams()
	regNetParams := mockRegNetParams()

	type newAddrFn func() (Address, error)
	tests := []struct {
		name         string        // test description
		makeAddr     newAddrFn     // function to construct new address via API
		makeErr      error         // expected error from new address function
		addr         string        // expected address and address to decode
		net          AddressParams // params for network
		decodeErr    error         // expected error from decode
		version      uint16        // expected scripts version
		payScript    string        // hex-encoded expected payment script
		voteScript   string        // hex-encoded expected voting rights script
		rewardAmount int64         // reward commitment amount
		voteFeeLimit int64         // reward commitment vote fee limit
		revFeeLimit  int64         // reward commitment revoke fee limit
		rewardScript string        // hex-encoded expected reward commitment script
		changeScript string        // hex-encoded expected stake change script
		commitScript string        // hex-encoded expected vote commitment script
		revokeScript string        // hex-encoded expected revoke commitment script
		trsyScript   string        // hex-encoded expected pay from treasury script
		pubKey       string        // hex-encoded expected public key
	}{{
		// ---------------------------------------------------------------------
		// Misc decoding error tests.
		// ---------------------------------------------------------------------

		name:      "bad checksum",
		addr:      "TsmWaPM77WSyA3aiQ2Q1KnwGDVWvEkhip23",
		net:       testNetParams,
		decodeErr: ErrBadAddressChecksum,
	}, {
		name:      "parse valid mainnet address with testnet rejected",
		addr:      "DsUZxxoHJSty8DCfwfartwTYbuhmVct7tJu",
		net:       testNetParams,
		decodeErr: ErrUnsupportedAddress,
	}, {
		name:      "mainnet p2pk with no data for pubkey",
		addr:      "Aiz5jz1s",
		net:       mainNetParams,
		decodeErr: ErrUnsupportedAddress,
	}, {
		name:      "invalid base58 (l not in base58 alphabet)",
		addr:      "DsUZxxoHlSty8DCfwfartwTYbuhmVct7tJu",
		net:       mainNetParams,
		decodeErr: ErrUnsupportedAddress,
	}, {
		// ---------------------------------------------------------------------
		// Negative P2PK ECDSA secp256k1 tests.
		// ---------------------------------------------------------------------

		name: "mainnet p2pk-ecdsa-secp256k1 uncompressed (0x04) rejected via constructor",
		makeAddr: func() (Address, error) {
			pkHex := "0464c44653d6567eff5753c5d24a682ddc2b2cadfe1b0c6433b163" +
				"74dace6778f0b87ca4279b565d2130ce59f75bfbb2b88da794143d7cfd3" +
				"e80808a1fa3203904"
			pk := hexToBytes(pkHex)
			return NewAddressPubKeyEcdsaSecp256k1Raw(0, pk, mainNetParams)
		},
		makeErr: ErrInvalidPubKeyFormat,
	}, {
		name:      "mainnet p2pk-ecdsa-secp256k1 uncompressed (0x04) rejected via decode",
		addr:      "HiQeNVx8PNYP8ysyunUoicyNdfRUrEu1kzPE6v5gECBHBYgDzXCg8BsDGjmaHCpV97ytaQGHz5XDMJgJVHjv9YeSXWkHfwmBJj",
		net:       mainNetParams,
		decodeErr: ErrUnsupportedAddress,
	}, {
		name: "mainnet p2pk-ecdsa-secp256k1 hybrid (0x06) rejected via constructor",
		makeAddr: func() (Address, error) {
			pkHex := "0664c44653d6567eff5753c5d24a682ddc2b2cadfe1b0c6433b163" +
				"74dace6778f0b87ca4279b565d2130ce59f75bfbb2b88da794143d7cfd3" +
				"e80808a1fa3203904"
			pk := hexToBytes(pkHex)
			return NewAddressPubKeyEcdsaSecp256k1Raw(0, pk, mainNetParams)
		},
		makeErr: ErrInvalidPubKeyFormat,
	}, {
		name: "mainnet p2pk-ecdsa-secp256k1 hybrid (0x07) rejected via constructor",
		makeAddr: func() (Address, error) {
			pkHex := "07348d8aeb4253ca52456fe5da94ab1263bfee16bb8192497f6663" +
				"89ca964f84798375129d7958843b14258b905dc94faed324dd8a9d67ffa" +
				"c8cc0a85be84bac5d"
			pk := hexToBytes(pkHex)
			return NewAddressPubKeyEcdsaSecp256k1Raw(0, pk, mainNetParams)
		},
		makeErr: ErrInvalidPubKeyFormat,
	}, {
		name: "p2pk-ecdsa-secp256k1 unsupported script version",
		makeAddr: func() (Address, error) {
			pkHex := "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed"
			pk := hexToBytes(pkHex)
			return NewAddressPubKeyEcdsaSecp256k1Raw(9999, pk, mainNetParams)
		},
		makeErr: ErrUnsupportedScriptVersion,
	}, {
		name: "p2pk-ecdsa-secp256k1 unsupported script version via concrete constructor",
		makeAddr: func() (Address, error) {
			pkHex := "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed"
			pk, err := secp256k1.ParsePubKey(hexToBytes(pkHex))
			if err != nil {
				return nil, err
			}
			return NewAddressPubKeyEcdsaSecp256k1(9999, pk, mainNetParams)
		},
		makeErr: ErrUnsupportedScriptVersion,
	}, {
		name: "p2pk-ecdsa-secp256k1 malformed pubkey",
		makeAddr: func() (Address, error) {
			pkHex := "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2"
			pk := hexToBytes(pkHex)
			return NewAddressPubKeyEcdsaSecp256k1Raw(0, pk, mainNetParams)
		},
		makeErr: ErrInvalidPubKey,
	}, {
		name:      "p2pk-ecdsa-secp256k1 malformed pubkey via decode",
		addr:      "3tWTcxjUnAKTzHh8pHPYpSsUKVbTvziNGHtbBFQkY12khQWuW83p",
		net:       mainNetParams,
		decodeErr: ErrUnsupportedAddress,
	}, {
		// ---------------------------------------------------------------------
		// Positive P2PK ECDSA secp256k1 tests.
		// ---------------------------------------------------------------------

		name: "mainnet p2pk-ecdsa-secp256k1 compressed (0x02)",
		makeAddr: func() (Address, error) {
			pkHex := "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed"
			pk := hexToBytes(pkHex)
			return NewAddressPubKeyEcdsaSecp256k1Raw(0, pk, mainNetParams)
		},
		makeErr:   nil,
		addr:      "DkM3ZigNyiwHrsXRjkDQ8t8tW6uKGW9g61qEkG3bMqQPQWYEf5X3J",
		net:       mainNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "21028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2edac",
		pubKey:    "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed",
	}, {
		name: "mainnet p2pk-ecdsa-secp256k1 compressed (0x03)",
		makeAddr: func() (Address, error) {
			pkHex := "03e925aafc1edd44e7c7f1ea4fb7d265dc672f204c3d0c81930389c10b81fb75de"
			pk := hexToBytes(pkHex)
			return NewAddressPubKeyEcdsaSecp256k1Raw(0, pk, mainNetParams)
		},
		makeErr:   nil,
		addr:      "DkRM4ZcdejbYRu4AbcEdfDLzU9w1ZTqPXatXvL1g8Q77ibDjz7gwF",
		net:       mainNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "2103e925aafc1edd44e7c7f1ea4fb7d265dc672f204c3d0c81930389c10b81fb75deac",
		pubKey:    "03e925aafc1edd44e7c7f1ea4fb7d265dc672f204c3d0c81930389c10b81fb75de",
	}, {
		name: "mainnet p2pk-ecdsa-secp256k1 compressed via concrete constructor",
		makeAddr: func() (Address, error) {
			pkHex := "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed"
			pk, err := secp256k1.ParsePubKey(hexToBytes(pkHex))
			if err != nil {
				return nil, err
			}
			return NewAddressPubKeyEcdsaSecp256k1(0, pk, mainNetParams)
		},
		makeErr:   nil,
		addr:      "DkM3ZigNyiwHrsXRjkDQ8t8tW6uKGW9g61qEkG3bMqQPQWYEf5X3J",
		net:       mainNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "21028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2edac",
		pubKey:    "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed",
	}, {
		name: "mainnet p2pk-ecdsa-secp256k1 compressed from uncompressed via concrete constructor",
		makeAddr: func() (Address, error) {
			pkHex := "0464c44653d6567eff5753c5d24a682ddc2b2cadfe1b0c6433b163" +
				"74dace6778f0b87ca4279b565d2130ce59f75bfbb2b88da794143d7cfd3" +
				"e80808a1fa3203904"
			pk, err := secp256k1.ParsePubKey(hexToBytes(pkHex))
			if err != nil {
				return nil, err
			}
			return NewAddressPubKeyEcdsaSecp256k1(0, pk, mainNetParams)
		},
		makeErr:   nil,
		addr:      "DkM3EyZ546GghVSkvzb6J47PvGDyntqiDtFgipQhNj78Xm2mUYRpf",
		net:       mainNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "210264c44653d6567eff5753c5d24a682ddc2b2cadfe1b0c6433b16374dace6778f0ac",
		pubKey:    "0264c44653d6567eff5753c5d24a682ddc2b2cadfe1b0c6433b16374dace6778f0",
	}, {
		name: "testnet p2pk-ecdsa-secp256k1 compressed (0x02)",
		makeAddr: func() (Address, error) {
			pkHex := "026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e"
			pk := hexToBytes(pkHex)
			return NewAddressPubKeyEcdsaSecp256k1Raw(0, pk, testNetParams)
		},
		makeErr:   nil,
		addr:      "TkKmMiY5iDh4U3KkSopYgkU1AzhAcQZiSoVhYhFymZHGMi9LM9Fdt",
		net:       testNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "21026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06eac",
		pubKey:    "026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e",
	}, {
		name: "testnet p2pk-ecdsa-secp256k1 compressed (0x03)",
		makeAddr: func() (Address, error) {
			pkHex := "030844ee70d8384d5250e9bb3a6a73d4b5bec770e8b31d6a0ae9fb739009d91af5"
			pk := hexToBytes(pkHex)
			return NewAddressPubKeyEcdsaSecp256k1Raw(0, pk, testNetParams)
		},
		makeErr:   nil,
		addr:      "TkQ3RrFierkUUbgipYwgeVfV8ch3fktfrDamGyDYESPBXMaVNNmWG",
		net:       testNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "21030844ee70d8384d5250e9bb3a6a73d4b5bec770e8b31d6a0ae9fb739009d91af5ac",
		pubKey:    "030844ee70d8384d5250e9bb3a6a73d4b5bec770e8b31d6a0ae9fb739009d91af5",
	}, {
		name: "testnet p2pk-ecdsa-secp256k1 compressed via concrete constructor",
		makeAddr: func() (Address, error) {
			pkHex := "026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e"
			pk, err := secp256k1.ParsePubKey(hexToBytes(pkHex))
			if err != nil {
				return nil, err
			}
			return NewAddressPubKeyEcdsaSecp256k1(0, pk, testNetParams)
		},
		makeErr:   nil,
		addr:      "TkKmMiY5iDh4U3KkSopYgkU1AzhAcQZiSoVhYhFymZHGMi9LM9Fdt",
		net:       testNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "21026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06eac",
		pubKey:    "026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e",
	}, {
		name: "testnet p2pk-ecdsa-secp256k1 compressed from uncompressed via concrete constructor",
		makeAddr: func() (Address, error) {
			pkHex := "046a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e12" +
				"20eacf4be06ed548c8c16fb5eb9007cb94220b3bb89491d5a1fd2d77867" +
				"fca64217acecf2244"
			pk, err := secp256k1.ParsePubKey(hexToBytes(pkHex))
			if err != nil {
				return nil, err
			}
			return NewAddressPubKeyEcdsaSecp256k1(0, pk, testNetParams)
		},
		makeErr:   nil,
		addr:      "TkKmMiY5iDh4U3KkSopYgkU1AzhAcQZiSoVhYhFymZHGMi9LM9Fdt",
		net:       testNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "21026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06eac",
		pubKey:    "026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e",
	}, {
		name:      "regnet p2pk-ecdsa-secp256k1 compressed (0x02)",
		addr:      "Rk41kKgrecrxQ8bLg8GJm1feMPBFtFeb4rG56tDfMdAtvPy4HneyR",
		net:       regNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "21026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06eac",
		pubKey:    "026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e",
	}, {
		name:      "regnet p2pk-ecdsa-secp256k1 compressed (0x03)",
		addr:      "Rk8HpTQVbFvNQgxK3sPSiks8K1B8wbyYUGM8qABDpWGp63Q5mnG52",
		net:       regNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "21030844ee70d8384d5250e9bb3a6a73d4b5bec770e8b31d6a0ae9fb739009d91af5ac",
		pubKey:    "030844ee70d8384d5250e9bb3a6a73d4b5bec770e8b31d6a0ae9fb739009d91af5",
	}, {
		// ---------------------------------------------------------------------
		// Negative P2PK Ed25519 tests.
		// ---------------------------------------------------------------------

		name: "p2pk-ed25519 unsupported script version",
		makeAddr: func() (Address, error) {
			pkHex := "cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc"
			pk := hexToBytes(pkHex)
			return NewAddressPubKeyEd25519Raw(9999, pk, mainNetParams)
		},
		makeErr: ErrUnsupportedScriptVersion,
	}, {
		name: "p2pk-ed25519 unsupported script version via concrete constructor",
		makeAddr: func() (Address, error) {
			pkHex := "cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc"
			pk, err := edwards.ParsePubKey(hexToBytes(pkHex))
			if err != nil {
				return nil, err
			}
			return NewAddressPubKeyEd25519(9999, pk, mainNetParams)
		},
		makeErr: ErrUnsupportedScriptVersion,
	}, {
		name: "p2pk-ed25519 malformed pubkey",
		makeAddr: func() (Address, error) {
			return NewAddressPubKeyEd25519Raw(0, nil, mainNetParams)
		},
		makeErr: ErrInvalidPubKey,
	}, {
		name:      "p2pk-ed25519 malformed pubkey (only 31 bytes) via decode",
		addr:      "3tWUQtEa3P4SDQwjER81wkTxe4kiYLgNAso3pt2X5k3NFHRVQeNv",
		net:       mainNetParams,
		decodeErr: ErrUnsupportedAddress,
	}, {
		// ---------------------------------------------------------------------
		// Positive P2PK Ed25519 tests.
		// ---------------------------------------------------------------------

		name: "mainnet p2pk-ed25519",
		makeAddr: func() (Address, error) {
			// From pubkey for privkey 0x00...01.
			pkHex := "cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc"
			pk := hexToBytes(pkHex)
			return NewAddressPubKeyEd25519Raw(0, pk, mainNetParams)
		},
		makeErr:   nil,
		addr:      "DkM5zR8tqWNAHngZQDTyAeqzabZxMKrkSbCFULDhmvySn3uHmm221",
		net:       mainNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "20cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc51be",
		pubKey:    "cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc",
	}, {
		name: "mainnet p2pk-ed25519 via concrete constructor",
		makeAddr: func() (Address, error) {
			// From pubkey for privkey 0x00...01.
			pkHex := "cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc"
			pk, err := edwards.ParsePubKey(hexToBytes(pkHex))
			if err != nil {
				return nil, err
			}
			return NewAddressPubKeyEd25519(0, pk, mainNetParams)
		},
		makeErr:   nil,
		addr:      "DkM5zR8tqWNAHngZQDTyAeqzabZxMKrkSbCFULDhmvySn3uHmm221",
		net:       mainNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "20cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc51be",
		pubKey:    "cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc",
	}, {
		name: "testnet p2pk-ed25519",
		makeAddr: func() (Address, error) {
			// From pubkey for privkey 0x00...01.
			pkHex := "cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc"
			pk := hexToBytes(pkHex)
			return NewAddressPubKeyEd25519Raw(0, pk, testNetParams)
		},
		makeErr:   nil,
		addr:      "TkKp4jynaSAyyV5FooNX3UBGzeXhxYq7e96YtjbRS5XEaar5zFom4",
		net:       testNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "20cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc51be",
		pubKey:    "cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc",
	}, {
		name: "testnet p2pk-ed25519 via concrete constructor",
		makeAddr: func() (Address, error) {
			// From pubkey for privkey 0x00...01.
			pkHex := "cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc"
			pk, err := edwards.ParsePubKey(hexToBytes(pkHex))
			if err != nil {
				return nil, err
			}
			return NewAddressPubKeyEd25519(0, pk, testNetParams)
		},
		makeErr:   nil,
		addr:      "TkKp4jynaSAyyV5FooNX3UBGzeXhxYq7e96YtjbRS5XEaar5zFom4",
		net:       testNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "20cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc51be",
		pubKey:    "cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc",
	}, {
		name: "regnet p2pk-ed25519",
		makeAddr: func() (Address, error) {
			// From pubkey for privkey 0x00...01.
			pkHex := "cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc"
			pk := hexToBytes(pkHex)
			return NewAddressPubKeyEd25519Raw(0, pk, regNetParams)
		},
		makeErr:   nil,
		addr:      "Rk44TM8ZWqLsuaLr37pH7jNvB31oEPuzGBrvSvZ729Qs9GfoiBryE",
		net:       regNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "20cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc51be",
		pubKey:    "cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc",
	}, {
		// ---------------------------------------------------------------------
		// Negative P2PK Schnorr secp256k1 tests.
		// ---------------------------------------------------------------------

		name: "mainnet p2pk-schnorr-secp256k1 uncompressed (0x04) rejected via constructor",
		makeAddr: func() (Address, error) {
			pkHex := "0464c44653d6567eff5753c5d24a682ddc2b2cadfe1b0c6433b163" +
				"74dace6778f0b87ca4279b565d2130ce59f75bfbb2b88da794143d7cfd3" +
				"e80808a1fa3203904"
			pk := hexToBytes(pkHex)
			return NewAddressPubKeySchnorrSecp256k1Raw(0, pk, mainNetParams)
		},
		makeErr: ErrInvalidPubKeyFormat,
	}, {
		name:      "mainnet p2pk-schnorr-secp256k1 uncompressed (0x04) rejected via decode",
		addr:      "HiQjU9uCJtiQD7osQuYHWJRFiBCTuqtaTw8QFMtMgAW2ny4nUENeXDiV5VxfVZrK6PZynKPDpL7bwc6XLFNpV8k7ePDJmkkVCh",
		net:       mainNetParams,
		decodeErr: ErrUnsupportedAddress,
	}, {
		name: "mainnet p2pk-schnorr-secp256k1 hybrid (0x06) rejected via constructor",
		makeAddr: func() (Address, error) {
			pkHex := "0664c44653d6567eff5753c5d24a682ddc2b2cadfe1b0c6433b163" +
				"74dace6778f0b87ca4279b565d2130ce59f75bfbb2b88da794143d7cfd3" +
				"e80808a1fa3203904"
			pk := hexToBytes(pkHex)
			return NewAddressPubKeySchnorrSecp256k1Raw(0, pk, mainNetParams)
		},
		makeErr: ErrInvalidPubKeyFormat,
	}, {
		name: "mainnet p2pk-schnorr-secp256k1 hybrid (0x07) rejected via constructor",
		makeAddr: func() (Address, error) {
			pkHex := "07348d8aeb4253ca52456fe5da94ab1263bfee16bb8192497f6663" +
				"89ca964f84798375129d7958843b14258b905dc94faed324dd8a9d67ffa" +
				"c8cc0a85be84bac5d"
			pk := hexToBytes(pkHex)
			return NewAddressPubKeySchnorrSecp256k1Raw(0, pk, mainNetParams)
		},
		makeErr: ErrInvalidPubKeyFormat,
	}, {
		name: "p2pk-schnorr-secp256k1 unsupported script version",
		makeAddr: func() (Address, error) {
			pkHex := "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed"
			pk := hexToBytes(pkHex)
			return NewAddressPubKeySchnorrSecp256k1Raw(9999, pk, mainNetParams)
		},
		makeErr: ErrUnsupportedScriptVersion,
	}, {
		name: "p2pk-schnorr-secp256k1 unsupported script version via concrete constructor",
		makeAddr: func() (Address, error) {
			pkHex := "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed"
			pk, err := secp256k1.ParsePubKey(hexToBytes(pkHex))
			if err != nil {
				return nil, err
			}
			return NewAddressPubKeySchnorrSecp256k1(9999, pk, mainNetParams)
		},
		makeErr: ErrUnsupportedScriptVersion,
	}, {
		name: "p2pk-schnorr-secp256k1 malformed pubkey",
		makeAddr: func() (Address, error) {
			pkHex := "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2"
			pk := hexToBytes(pkHex)
			return NewAddressPubKeySchnorrSecp256k1Raw(0, pk, mainNetParams)
		},
		makeErr: ErrInvalidPubKey,
	}, {
		name:      "p2pk-schnorr-secp256k1 malformed pubkey via decode",
		addr:      "3tWUW3oD87XtmFVGnLX4Z3Hdesm2qRvvN8H5kq3yXxJumCxbvCpo",
		net:       mainNetParams,
		decodeErr: ErrUnsupportedAddress,
	}, {
		// ---------------------------------------------------------------------
		// Positive P2PK Schnorr secp256k1 tests.
		// ---------------------------------------------------------------------

		name:      "mainnet p2pk-schnorr-secp256k1 compressed (0x02)",
		addr:      "DkM7TD2qsne9DKo4uA2ZNt3XhejYVwT5mmQWtUXtjdPhRHXTSKxN4",
		net:       mainNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "21028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed52be",
		pubKey:    "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed",
	}, {
		name:      "mainnet p2pk-schnorr-secp256k1 compressed (0x03)",
		addr:      "DkRQx3y6YoJPnMKom23nuDFdfhmEnu8oDLTp4YVyWC6RjND19UxHk",
		net:       mainNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "2103e925aafc1edd44e7c7f1ea4fb7d265dc672f204c3d0c81930389c10b81fb75de52be",
		pubKey:    "03e925aafc1edd44e7c7f1ea4fb7d265dc672f204c3d0c81930389c10b81fb75de",
	}, {
		name: "mainnet p2pk-schnorr-secp256k1 compressed via concrete constructor",
		makeAddr: func() (Address, error) {
			pkHex := "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed"
			pk, err := secp256k1.ParsePubKey(hexToBytes(pkHex))
			if err != nil {
				return nil, err
			}
			return NewAddressPubKeySchnorrSecp256k1(0, pk, mainNetParams)
		},
		makeErr:   nil,
		addr:      "DkM7TD2qsne9DKo4uA2ZNt3XhejYVwT5mmQWtUXtjdPhRHXTSKxN4",
		net:       mainNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "21028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed52be",
		pubKey:    "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed",
	}, {
		name: "mainnet p2pk-schnorr-secp256k1 compressed from uncompressed via concrete constructor",
		makeAddr: func() (Address, error) {
			pkHex := "048f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da1" +
				"43a02b0fe2ed91badd9f6403cc485dc3d5ca83ee8917dca57414866b083" +
				"087c1c83b7a8e3304"
			pk, err := secp256k1.ParsePubKey(hexToBytes(pkHex))
			if err != nil {
				return nil, err
			}
			return NewAddressPubKeySchnorrSecp256k1(0, pk, mainNetParams)
		},
		makeErr:   nil,
		addr:      "DkM7TD2qsne9DKo4uA2ZNt3XhejYVwT5mmQWtUXtjdPhRHXTSKxN4",
		net:       mainNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "21028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed52be",
		pubKey:    "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed",
	}, {
		name:      "testnet p2pk-schnorr-secp256k1 compressed (0x02)",
		addr:      "TkKqFCtYcHPupVbPcDdhvkNeNYXPqqs88Z4ygukH9MGaNV8e1WWhX",
		net:       testNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "21026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e52be",
		pubKey:    "026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e",
	}, {
		name:      "testnet p2pk-schnorr-secp256k1 compressed (0x03)",
		addr:      "TkQ7KLcBYvTKq3xMyxkqtVa8LAXGuCC5XyA3RBhqcENVY8ZiDjuAB",
		net:       testNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "21030844ee70d8384d5250e9bb3a6a73d4b5bec770e8b31d6a0ae9fb739009d91af552be",
		pubKey:    "030844ee70d8384d5250e9bb3a6a73d4b5bec770e8b31d6a0ae9fb739009d91af5",
	}, {
		name:      "regnet p2pk-schnorr-secp256k1 compressed (0x02)",
		addr:      "Rk45dp3KYgZokaryqY5U11aHYw1V7gwzkbqMF6hxjRACwAxDivM6N",
		net:       regNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "21026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e52be",
		pubKey:    "026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e",
	}, {
		name:      "regnet p2pk-schnorr-secp256k1 compressed (0x03)",
		addr:      "Rk8MhwkxVKdDm9DxDHCbxkmmWZ1NB3GxA1vQyNfXCJG86pPPnnn9M",
		net:       regNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "21030844ee70d8384d5250e9bb3a6a73d4b5bec770e8b31d6a0ae9fb739009d91af552be",
		pubKey:    "030844ee70d8384d5250e9bb3a6a73d4b5bec770e8b31d6a0ae9fb739009d91af5",
	}, {
		// ---------------------------------------------------------------------
		// Negative P2PKH ECDSA secp256k1 tests.
		// ---------------------------------------------------------------------

		name: "p2pkh-ecdsa-secp256k1 wrong hash length",
		makeAddr: func() (Address, error) {
			hash := hexToBytes("000ef030107fd26e0b6bf40512bca2ceb1dd80adaa")
			return NewAddressPubKeyHashEcdsaSecp256k1(0, hash, mainNetParams)
		},
		makeErr: ErrInvalidHashLen,
	}, {
		name: "p2pkh-ecdsa-secp256k1 unsupported script version",
		makeAddr: func() (Address, error) {
			hash := hexToBytes("2789d58cfa0957d206f025c2af056fc8a77cebb0")
			return NewAddressPubKeyHashEcdsaSecp256k1(9999, hash, mainNetParams)
		},
		makeErr: ErrUnsupportedScriptVersion,
	}, {
		// ---------------------------------------------------------------------
		// Positive P2PKH ECDSA secp256k1 tests.
		// ---------------------------------------------------------------------

		name: "mainnet p2pkh-ecdsa-secp256k1",
		makeAddr: func() (Address, error) {
			hash := hexToBytes("2789d58cfa0957d206f025c2af056fc8a77cebb0")
			return NewAddressPubKeyHashEcdsaSecp256k1(0, hash, mainNetParams)
		},
		makeErr:      nil,
		addr:         "DsUZxxoHJSty8DCfwfartwTYbuhmVct7tJu",
		net:          mainNetParams,
		decodeErr:    nil,
		version:      0,
		payScript:    "76a9142789d58cfa0957d206f025c2af056fc8a77cebb088ac",
		voteScript:   "ba76a9142789d58cfa0957d206f025c2af056fc8a77cebb088ac",
		rewardAmount: 1e8,
		revFeeLimit:  16777216,
		rewardScript: "6a1e2789d58cfa0957d206f025c2af056fc8a77cebb000e1f505000000000058",
		changeScript: "bd76a9142789d58cfa0957d206f025c2af056fc8a77cebb088ac",
		commitScript: "bb76a9142789d58cfa0957d206f025c2af056fc8a77cebb088ac",
		revokeScript: "bc76a9142789d58cfa0957d206f025c2af056fc8a77cebb088ac",
		trsyScript:   "c376a9142789d58cfa0957d206f025c2af056fc8a77cebb088ac",
	}, {
		name: "mainnet p2pkh-ecdsa-secp256k1 2",
		makeAddr: func() (Address, error) {
			hash := hexToBytes("229ebac30efd6a69eec9c1a48e048b7c975c25f2")
			return NewAddressPubKeyHashEcdsaSecp256k1(0, hash, mainNetParams)
		},
		makeErr:      nil,
		addr:         "DsU7xcg53nxaKLLcAUSKyRndjG78Z2VZnX9",
		net:          mainNetParams,
		decodeErr:    nil,
		version:      0,
		payScript:    "76a914229ebac30efd6a69eec9c1a48e048b7c975c25f288ac",
		voteScript:   "ba76a914229ebac30efd6a69eec9c1a48e048b7c975c25f288ac",
		rewardAmount: 9556193632,
		revFeeLimit:  33554432,
		rewardScript: "6a1e229ebac30efd6a69eec9c1a48e048b7c975c25f260f19739020000000059",
		changeScript: "bd76a914229ebac30efd6a69eec9c1a48e048b7c975c25f288ac",
		commitScript: "bb76a914229ebac30efd6a69eec9c1a48e048b7c975c25f288ac",
		revokeScript: "bc76a914229ebac30efd6a69eec9c1a48e048b7c975c25f288ac",
		trsyScript:   "c376a914229ebac30efd6a69eec9c1a48e048b7c975c25f288ac",
	}, {
		name: "testnet p2pkh-ecdsa-secp256k1",
		makeAddr: func() (Address, error) {
			hash := hexToBytes("f15da1cb8d1bcb162c6ab446c95757a6e791c916")
			return NewAddressPubKeyHashEcdsaSecp256k1(0, hash, testNetParams)
		},
		makeErr:      nil,
		addr:         "Tso2MVTUeVrjHTBFedFhiyM7yVTbieqp91h",
		net:          testNetParams,
		decodeErr:    nil,
		version:      0,
		payScript:    "76a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac",
		voteScript:   "ba76a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac",
		rewardAmount: 2428220961,
		revFeeLimit:  16777216,
		rewardScript: "6a1ef15da1cb8d1bcb162c6ab446c95757a6e791c91621b6bb90000000000058",
		changeScript: "bd76a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac",
		commitScript: "bb76a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac",
		revokeScript: "bc76a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac",
		trsyScript:   "c376a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac",
	}, {
		name: "regnet p2pkh-sep256k1-ecdsa",
		makeAddr: func() (Address, error) {
			hash := hexToBytes("f15da1cb8d1bcb162c6ab446c95757a6e791c916")
			return NewAddressPubKeyHashEcdsaSecp256k1(0, hash, regNetParams)
		},
		makeErr:      nil,
		addr:         "RsWM2w5LPJip56uxcZ1Scq7Tcbg97EfiwPA",
		net:          regNetParams,
		decodeErr:    nil,
		version:      0,
		payScript:    "76a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac",
		voteScript:   "ba76a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac",
		rewardAmount: 2428220961,
		revFeeLimit:  16777216,
		rewardScript: "6a1ef15da1cb8d1bcb162c6ab446c95757a6e791c91621b6bb90000000000058",
		changeScript: "bd76a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac",
		commitScript: "bb76a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac",
		revokeScript: "bc76a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac",
		trsyScript:   "c376a914f15da1cb8d1bcb162c6ab446c95757a6e791c91688ac",
	}, {
		// ---------------------------------------------------------------------
		// Negative P2PKH ed25519 tests.
		// ---------------------------------------------------------------------

		name: "p2pkh-ed25519 wrong hash length",
		makeAddr: func() (Address, error) {
			hash := hexToBytes("000ef030107fd26e0b6bf40512bca2ceb1dd80adaa")
			return NewAddressPubKeyHashEd25519(0, hash, mainNetParams)
		},
		makeErr: ErrInvalidHashLen,
	}, {
		name: "p2pkh-ed25519 unsupported script version",
		makeAddr: func() (Address, error) {
			hash := hexToBytes("0ef030107fd26e0b6bf40512bca2ceb1dd80adaa")
			return NewAddressPubKeyHashEd25519(9999, hash, mainNetParams)
		},
		makeErr: ErrUnsupportedScriptVersion,
	}, {
		// ---------------------------------------------------------------------
		// Positive P2PKH Ed25519 tests.
		// ---------------------------------------------------------------------

		name: "mainnet p2pkh-ed25519",
		makeAddr: func() (Address, error) {
			// From pubkey for privkey 0x00...01.
			hash := hexToBytes("456d8ee57a4b9121987b4ecab8c3bcb5797e8a53")
			return NewAddressPubKeyHashEd25519(0, hash, mainNetParams)
		},
		makeErr:   nil,
		addr:      "DeeUhrRoTp4DftsqddVW96yMGMW4sgQFYUE",
		net:       mainNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "76a914456d8ee57a4b9121987b4ecab8c3bcb5797e8a538851be",
	}, {
		name: "mainnet p2pkh-ed25519 2",
		makeAddr: func() (Address, error) {
			// From pubkey for privkey 0x00...02.
			hash := hexToBytes("09788a8dcb216efa354e487d57d76255b1af4320")
			return NewAddressPubKeyHashEd25519(0, hash, mainNetParams)
		},
		makeErr:   nil,
		addr:      "DeZ1gU2ta8auk5et79R74GYR3pnF31KXbFo",
		net:       mainNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "76a91409788a8dcb216efa354e487d57d76255b1af43208851be",
	}, {
		name: "testnet p2pkh-ed25519",
		makeAddr: func() (Address, error) {
			// From pubkey for privkey 0x00...01.
			hash := hexToBytes("456d8ee57a4b9121987b4ecab8c3bcb5797e8a53")
			return NewAddressPubKeyHashEd25519(0, hash, testNetParams)
		},
		makeErr:   nil,
		addr:      "TeeXvqZJrc7KnFZCT27fHfzcrTTzSF1aSRG",
		net:       testNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "76a914456d8ee57a4b9121987b4ecab8c3bcb5797e8a538851be",
	}, {
		name: "regnet p2pkh-ed25519",
		makeAddr: func() (Address, error) {
			// From pubkey for privkey 0x00...01.
			hash := hexToBytes("456d8ee57a4b9121987b4ecab8c3bcb5797e8a53")
			return NewAddressPubKeyHashEd25519(0, hash, regNetParams)
		},
		makeErr:   nil,
		addr:      "ReMrcHBAbQyQZuHuQwsQBXkxVZgXpnbqX72",
		net:       regNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "76a914456d8ee57a4b9121987b4ecab8c3bcb5797e8a538851be",
	}, {
		// ---------------------------------------------------------------------
		// Negative P2PKH Schnorr secp256k1 tests.
		// ---------------------------------------------------------------------

		name: "p2pkh-schnorr-secp256k1 wrong hash length",
		makeAddr: func() (Address, error) {
			hash := hexToBytes("000ef030107fd26e0b6bf40512bca2ceb1dd80adaa")
			return NewAddressPubKeyHashSchnorrSecp256k1(0, hash, mainNetParams)
		},
		makeErr: ErrInvalidHashLen,
	}, {
		name: "p2pkh-schnorr-secp256k1 unsupported script version",
		makeAddr: func() (Address, error) {
			hash := hexToBytes("0ef030107fd26e0b6bf40512bca2ceb1dd80adaa")
			return NewAddressPubKeyHashSchnorrSecp256k1(9999, hash, mainNetParams)
		},
		makeErr: ErrUnsupportedScriptVersion,
	}, {
		// ---------------------------------------------------------------------
		// Positive P2PKH Schnorr secp256k1 tests.
		// ---------------------------------------------------------------------

		name: "mainnet p2pkh-schnorr-secp256k1",
		makeAddr: func() (Address, error) {
			hash := hexToBytes("2789d58cfa0957d206f025c2af056fc8a77cebb0")
			return NewAddressPubKeyHashSchnorrSecp256k1(0, hash, mainNetParams)
		},
		makeErr:   nil,
		addr:      "DSXcZv4oSRiEoWL2a9aD8sgfptRo1YEXNKj",
		net:       mainNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "76a9142789d58cfa0957d206f025c2af056fc8a77cebb08852be",
	}, {
		name: "mainnet p2pkh-schnorr-secp256k1 2",
		makeAddr: func() (Address, error) {
			hash := hexToBytes("229ebac30efd6a69eec9c1a48e048b7c975c25f2")
			return NewAddressPubKeyHashSchnorrSecp256k1(0, hash, mainNetParams)
		},
		makeErr:   nil,
		addr:      "DSXAZZwbBmmqzdTxnxRgDN1kxEqA4xJfufA",
		net:       mainNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "76a914229ebac30efd6a69eec9c1a48e048b7c975c25f28852be",
	}, {
		name: "testnet p2pkh-schnorr-secp256k1",
		makeAddr: func() (Address, error) {
			hash := hexToBytes("f15da1cb8d1bcb162c6ab446c95757a6e791c916")
			return NewAddressPubKeyHashSchnorrSecp256k1(0, hash, testNetParams)
		},
		makeErr:   nil,
		addr:      "TSr4xSiznUfzxkJcH7F3xuaFCUBdEb5Jfzg",
		net:       testNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "76a914f15da1cb8d1bcb162c6ab446c95757a6e791c9168852be",
	}, {
		name: "regnet p2pkh-sep256k1-schnorr",
		makeAddr: func() (Address, error) {
			hash := hexToBytes("f15da1cb8d1bcb162c6ab446c95757a6e791c916")
			return NewAddressPubKeyHashSchnorrSecp256k1(0, hash, regNetParams)
		},
		makeErr:   nil,
		addr:      "RSZPdtLrXHY5kQ3KF2znrmLaqaQAdB3CRu7",
		net:       regNetParams,
		decodeErr: nil,
		version:   0,
		payScript: "76a914f15da1cb8d1bcb162c6ab446c95757a6e791c9168852be",
	}, {
		// ---------------------------------------------------------------------
		// Negative P2SH tests.
		// ---------------------------------------------------------------------

		name: "p2sh wrong hash length",
		makeAddr: func() (Address, error) {
			hash := hexToBytes("00f815b036d9bbbce5e9f2a00abd1bf3dc91e95510")
			return NewAddressScriptHashFromHash(0, hash, mainNetParams)
		},
		makeErr: ErrInvalidHashLen,
	}, {
		name: "p2sh from script hash unsupported script version",
		makeAddr: func() (Address, error) {
			hash := hexToBytes("f815b036d9bbbce5e9f2a00abd1bf3dc91e95510")
			return NewAddressScriptHashFromHash(9999, hash, mainNetParams)
		},
		makeErr: ErrUnsupportedScriptVersion,
	}, {
		name: "p2sh from redeem script unsupported script version",
		makeAddr: func() (Address, error) {
			script := hexToBytes("a914f0b4e85100aee1a996f22915eb3c3f764d53779a87")
			return NewAddressScriptHash(9999, script, mainNetParams)
		},
		makeErr: ErrUnsupportedScriptVersion,
	}, {
		// ---------------------------------------------------------------------
		// Positive P2SH tests.
		// ---------------------------------------------------------------------

		name: "mainnet p2sh",
		makeAddr: func() (Address, error) {
			script := hexToBytes("512103aa43f0a6c15730d886cc1f0342046d2017548" +
				"3d90d7ccb657f90c489111d794c51ae")
			return NewAddressScriptHash(0, script, mainNetParams)
		},
		makeErr:      nil,
		addr:         "DcuQKx8BES9wU7C6Q5VmLBjw436r27hayjS",
		net:          mainNetParams,
		decodeErr:    nil,
		version:      0,
		payScript:    "a914f0b4e85100aee1a996f22915eb3c3f764d53779a87",
		rewardAmount: 1e8,
		revFeeLimit:  16777216,
		rewardScript: "6a1ef0b4e85100aee1a996f22915eb3c3f764d53779a00e1f505000000800058",
		voteScript:   "baa914f0b4e85100aee1a996f22915eb3c3f764d53779a87",
		changeScript: "bda914f0b4e85100aee1a996f22915eb3c3f764d53779a87",
		commitScript: "bba914f0b4e85100aee1a996f22915eb3c3f764d53779a87",
		revokeScript: "bca914f0b4e85100aee1a996f22915eb3c3f764d53779a87",
		trsyScript:   "c3a914f0b4e85100aee1a996f22915eb3c3f764d53779a87",
	}, {
		name: "mainnet p2sh 2",
		makeAddr: func() (Address, error) {
			hash := hexToBytes("c7da5095683436f4435fc4e7163dcafda1a2d007")
			return NewAddressScriptHashFromHash(0, hash, mainNetParams)
		},
		makeErr:      nil,
		addr:         "DcqgK4N4Ccucu2Sq4VDAdu4wH4LASLhzLVp",
		net:          mainNetParams,
		decodeErr:    nil,
		version:      0,
		payScript:    "a914c7da5095683436f4435fc4e7163dcafda1a2d00787",
		voteScript:   "baa914c7da5095683436f4435fc4e7163dcafda1a2d00787",
		rewardAmount: 9556193632,
		revFeeLimit:  33554432,
		rewardScript: "6a1ec7da5095683436f4435fc4e7163dcafda1a2d00760f19739020000800059",
		changeScript: "bda914c7da5095683436f4435fc4e7163dcafda1a2d00787",
		commitScript: "bba914c7da5095683436f4435fc4e7163dcafda1a2d00787",
		revokeScript: "bca914c7da5095683436f4435fc4e7163dcafda1a2d00787",
		trsyScript:   "c3a914c7da5095683436f4435fc4e7163dcafda1a2d00787",
	}, {
		name: "testnet p2sh",
		makeAddr: func() (Address, error) {
			hash := hexToBytes("36c1ca10a8a6a4b5d4204ac970853979903aa284")
			return NewAddressScriptHashFromHash(0, hash, testNetParams)
		},
		makeErr:      nil,
		addr:         "TccWLgcquqvwrfBocq5mcK5kBiyw8MvyvCi",
		net:          testNetParams,
		decodeErr:    nil,
		version:      0,
		payScript:    "a91436c1ca10a8a6a4b5d4204ac970853979903aa28487",
		voteScript:   "baa91436c1ca10a8a6a4b5d4204ac970853979903aa28487",
		rewardAmount: 2428220961,
		revFeeLimit:  16777216,
		rewardScript: "6a1e36c1ca10a8a6a4b5d4204ac970853979903aa28421b6bb90000000800058",
		changeScript: "bda91436c1ca10a8a6a4b5d4204ac970853979903aa28487",
		commitScript: "bba91436c1ca10a8a6a4b5d4204ac970853979903aa28487",
		revokeScript: "bca91436c1ca10a8a6a4b5d4204ac970853979903aa28487",
		trsyScript:   "c3a91436c1ca10a8a6a4b5d4204ac970853979903aa28487",
	}, {
		name: "regnet p2sh",
		makeAddr: func() (Address, error) {
			hash := hexToBytes("36c1ca10a8a6a4b5d4204ac970853979903aa284")
			return NewAddressScriptHashFromHash(0, hash, regNetParams)
		},
		makeErr:      nil,
		addr:         "RcKq28Eheeo2eJvWakqWWAr5pqCUWykwDHe",
		net:          regNetParams,
		decodeErr:    nil,
		version:      0,
		payScript:    "a91436c1ca10a8a6a4b5d4204ac970853979903aa28487",
		voteScript:   "baa91436c1ca10a8a6a4b5d4204ac970853979903aa28487",
		rewardAmount: 2428220961,
		revFeeLimit:  16777216,
		rewardScript: "6a1e36c1ca10a8a6a4b5d4204ac970853979903aa28421b6bb90000000800058",
		changeScript: "bda91436c1ca10a8a6a4b5d4204ac970853979903aa28487",
		commitScript: "bba91436c1ca10a8a6a4b5d4204ac970853979903aa28487",
		revokeScript: "bca91436c1ca10a8a6a4b5d4204ac970853979903aa28487",
		trsyScript:   "c3a91436c1ca10a8a6a4b5d4204ac970853979903aa28487",
	}}

	for _, test := range tests {
		// Create address from test constructor and ensure it produces the
		// expected encoded address when the constructor is specified.
		if test.makeAddr != nil {
			addr, err := test.makeAddr()
			if !errors.Is(err, test.makeErr) {
				t.Errorf("%s: mismatched err -- got %v, want %v", test.name, err,
					test.makeErr)
				continue
			}
			if err != nil {
				continue
			}

			// Ensure encoding the address is the same as the original.
			encoded := addr.String()
			if encoded != test.addr {
				t.Errorf("%s: unexpected address -- got %v, want %v", test.name,
					encoded, test.addr)
				continue
			}
		}

		// Decode address and ensure the expected error is received.
		decodedAddr, err := DecodeAddress(test.addr, test.net)
		if !errors.Is(err, test.decodeErr) {
			t.Errorf("%s: mismatched err -- got %v, want %v", test.name, err,
				test.decodeErr)
			continue
		}
		if err != nil {
			continue
		}

		// Ensure the payment script version and contents are the expected
		// values.
		wantPayScript, err := hex.DecodeString(test.payScript)
		if err != nil {
			t.Errorf("%s: unexpected hex decode err: %v", test.name, err)
			continue
		}
		gotPayScriptVersion, gotPayScript := decodedAddr.PaymentScript()
		if gotPayScriptVersion != test.version {
			t.Errorf("%s: mismatched payment script version -- got %d, want %d",
				test.name, gotPayScriptVersion, test.version)
			continue
		}
		if !bytes.Equal(gotPayScript, wantPayScript) {
			t.Errorf("%s: mismatched payment script -- got %x, want %x",
				test.name, gotPayScript, wantPayScript)
			continue
		}

		// Ensure stake-specific interface results produce the expected values.
		if stakeAddr, ok := decodedAddr.(StakeAddress); ok {
			// Ensure the voting rights script version and contents are the
			// expected values.
			wantScript, err := hex.DecodeString(test.voteScript)
			if err != nil {
				t.Errorf("%s: unexpected hex decode err: %v", test.name, err)
				continue
			}
			gotScriptVer, gotScript := stakeAddr.VotingRightsScript()
			if gotScriptVer != test.version {
				t.Errorf("%s: mismatched voting rights script version -- got "+
					"%d, want %d", test.name, gotScriptVer,
					test.version)
				continue
			}
			if !bytes.Equal(gotScript, wantScript) {
				t.Errorf("%s: mismatched voting rights script -- got %x, want %x",
					test.name, gotScript, wantScript)
				continue
			}

			// Ensure the reward commitment script version and contents are the
			// expected values.
			wantScript, err = hex.DecodeString(test.rewardScript)
			if err != nil {
				t.Errorf("%s: unexpected hex decode err: %v", test.name, err)
				continue
			}
			gotScriptVer, gotScript = stakeAddr.RewardCommitmentScript(
				test.rewardAmount, test.voteFeeLimit, test.revFeeLimit)
			if gotScriptVer != test.version {
				t.Errorf("%s: mismatched reward cmt script version -- got %d, "+
					"want %d", test.name, gotScriptVer, test.version)
				continue
			}
			if !bytes.Equal(gotScript, wantScript) {
				t.Errorf("%s: mismatched reward cmt script -- got %x, want %x",
					test.name, gotScript, wantScript)
				continue
			}

			// Ensure the stake change script version and contents are the
			// expected values.
			wantScript, err = hex.DecodeString(test.changeScript)
			if err != nil {
				t.Errorf("%s: unexpected hex decode err: %v", test.name, err)
				continue
			}
			gotScriptVer, gotScript = stakeAddr.StakeChangeScript()
			if gotScriptVer != test.version {
				t.Errorf("%s: mismatched change script version -- got %d, "+
					"want %d", test.name, gotScriptVer, test.version)
				continue
			}
			if !bytes.Equal(gotScript, wantScript) {
				t.Errorf("%s: mismatched change script -- got %x, want %x",
					test.name, gotScript, wantScript)
				continue
			}

			// Ensure the vote commitment script version and contents are the
			// expected values.
			wantScript, err = hex.DecodeString(test.commitScript)
			if err != nil {
				t.Errorf("%s: unexpected hex decode err: %v", test.name, err)
				continue
			}
			gotScriptVer, gotScript = stakeAddr.PayVoteCommitmentScript()
			if gotScriptVer != test.version {
				t.Errorf("%s: mismatched vote commit script version -- got %d, "+
					"want %d", test.name, gotScriptVer, test.version)
				continue
			}
			if !bytes.Equal(gotScript, wantScript) {
				t.Errorf("%s: mismatched vote commit script -- got %x, want %x",
					test.name, gotScript, wantScript)
				continue
			}

			// Ensure the revoke commitment script version and contents are the
			// expected values.
			wantScript, err = hex.DecodeString(test.revokeScript)
			if err != nil {
				t.Errorf("%s: unexpected hex decode err: %v", test.name, err)
				continue
			}
			gotScriptVer, gotScript = stakeAddr.PayRevokeCommitmentScript()
			if gotScriptVer != test.version {
				t.Errorf("%s: mismatched revoke cmt script version -- got %d, "+
					"want %d", test.name, gotScriptVer, test.version)
				continue
			}
			if !bytes.Equal(gotScript, wantScript) {
				t.Errorf("%s: mismatched revoke cmt script -- got %x, want %x",
					test.name, gotScript, wantScript)
				continue
			}

			// Ensure the pay from treasury script version and contents are the
			// expected values.
			wantScript, err = hex.DecodeString(test.trsyScript)
			if err != nil {
				t.Errorf("%s: unexpected hex decode err: %v", test.name, err)
				continue
			}
			gotScriptVer, gotScript = stakeAddr.PayFromTreasuryScript()
			if gotScriptVer != test.version {
				t.Errorf("%s: mismatched treasury change script version -- "+
					"got %d, want %d", test.name, gotScriptVer, test.version)
				continue
			}
			if !bytes.Equal(gotScript, wantScript) {
				t.Errorf("%s: mismatched treasury change script -- got %x, "+
					"want %x", test.name, gotScript, wantScript)
				continue
			}
		}

		// Ensure encoding the address is the same as the original.
		encoded := decodedAddr.String()
		if encoded != test.addr {
			t.Errorf("%s: decoding and encoding produced different addresses "+
				"-- got %v, want %v", test.name, encoded, test.addr)
			continue
		}

		// Ensure the method to get the serialized public key for the addresses
		// that support it returns the expected value.
		if pubKeyer, ok := decodedAddr.(SerializedPubKeyer); ok {
			wantPubKey, err := hex.DecodeString(test.pubKey)
			if err != nil {
				t.Errorf("%s: unexpected hex decode err: %v", test.name, err)
				continue
			}
			gotPubKey := pubKeyer.SerializedPubKey()
			if !bytes.Equal(gotPubKey, wantPubKey) {
				t.Errorf("%s: mismatched public key -- got %x, want %x",
					test.name, gotPubKey, wantPubKey)
				continue
			}
		}

		// Ensure the AddressPubKeyHash method for the address types that
		// support it returns the expected address.
		var pkhAddr Address
		var wantPkhAddr string
		switch a := decodedAddr.(type) {
		case *AddressPubKeyEcdsaSecp256k1V0:
			id := test.net.AddrIDPubKeyHashECDSAV0()
			wantPkhAddr = base58.CheckEncode(Hash160(a.serializedPubKey), id)
			pkhAddr = a.AddressPubKeyHash()

		case *AddressPubKeyEd25519V0:
			id := test.net.AddrIDPubKeyHashEd25519V0()
			wantPkhAddr = base58.CheckEncode(Hash160(a.serializedPubKey), id)
			pkhAddr = a.AddressPubKeyHash()

		case *AddressPubKeySchnorrSecp256k1V0:
			id := test.net.AddrIDPubKeyHashSchnorrV0()
			wantPkhAddr = base58.CheckEncode(Hash160(a.serializedPubKey), id)
			pkhAddr = a.AddressPubKeyHash()
		}
		if pkhAddr != nil {
			gotAddr := pkhAddr.String()
			if gotAddr != wantPkhAddr {
				t.Errorf("%s: mismatched pkh address -- got %s, want %s",
					test.name, gotAddr, wantPkhAddr)
			}
		}

		// Ensure the Hash160 method for the addresses that support it returns
		// the expected value.
		if h160er, ok := decodedAddr.(Hash160er); ok {
			decodedBytes := base58.Decode(test.addr)
			wantH160 := decodedBytes[2 : 2+ripemd160.Size]
			if gotH160 := h160er.Hash160()[:]; !bytes.Equal(gotH160, wantH160) {
				t.Errorf("%s: mismatched hash160 -- got %x, want %x", test.name,
					gotH160, wantH160)
				return
			}
		}
	}
}

// TestDecodeAddressV0Corners ensures that some additional errors that are
// specific to decoding version 0 addresses directly, as opposed to via the
// generic API, work as intended.  This is necessary because the generic address
// decoding function contains additional logic to avoid even attempting to
// decode addresses which can't possibly be one of the supported version 0
// address types, while the version 0 decoding logic specifically attempts to
// decode the address in order to provide more detailed errors.
func TestDecodeAddressV0Corners(t *testing.T) {
	mainNetParams := mockMainNetParams()

	tests := []struct {
		name      string        // test description
		addr      string        // expected address and address to decode
		net       AddressParams // params for network
		decodeErr error         // expected error from decode
	}{{
		// ---------------------------------------------------------------------
		// Misc decoding error tests.
		// ---------------------------------------------------------------------

		name:      "mainnet p2pk with no data for pubkey",
		addr:      "Aiz5jz1s",
		net:       mainNetParams,
		decodeErr: ErrMalformedAddressData,
	}, {
		name:      "invalid base58 (l not in base58 alphabet)",
		addr:      "DsUZxxoHlSty8DCfwfartwTYbuhmVct7tJu",
		net:       mainNetParams,
		decodeErr: ErrMalformedAddress,
	}, {
		name:      "exceeds max version 0 address size",
		addr:      "5HLcANeEU3u2pxrCZFmLoyY4bAH7sgEuiTGcionhA7tzHdecFmgg1LJk",
		net:       mainNetParams,
		decodeErr: ErrMalformedAddress,
	}, {
		// ---------------------------------------------------------------------
		// Negative P2PK ECDSA secp256k1 tests.
		// ---------------------------------------------------------------------

		name:      "mainnet p2pk-ecdsa-secp256k1 uncompressed (0x04) rejected via decode",
		addr:      "HiQeNVx8PNYP8ysyunUoicyNdfRUrEu1kzPE6v5gECBHBYgDzXCg8BsDGjmaHCpV97ytaQGHz5XDMJgJVHjv9YeSXWkHfwmBJj",
		net:       mainNetParams,
		decodeErr: ErrMalformedAddress,
	}, {
		name:      "p2pk-ecdsa-secp256k1 malformed pubkey via decode",
		addr:      "3tWTcxjUnAKTzHh8pHPYpSsUKVbTvziNGHtbBFQkY12khQWuW83p",
		net:       mainNetParams,
		decodeErr: ErrMalformedAddressData,
	}, {
		// ---------------------------------------------------------------------
		// Negative P2PK Ed25519 tests.
		// ---------------------------------------------------------------------

		name:      "p2pk-ed25519 malformed pubkey (only 31 bytes) via decode",
		addr:      "3tWUQtEa3P4SDQwjER81wkTxe4kiYLgNAso3pt2X5k3NFHRVQeNv",
		net:       mainNetParams,
		decodeErr: ErrMalformedAddressData,
	}, {
		// ---------------------------------------------------------------------
		// Negative P2PK Schnorr secp256k1 tests.
		// ---------------------------------------------------------------------

		name:      "mainnet p2pk-schnorr-secp256k1 uncompressed (0x04) rejected via decode",
		addr:      "HiQjU9uCJtiQD7osQuYHWJRFiBCTuqtaTw8QFMtMgAW2ny4nUENeXDiV5VxfVZrK6PZynKPDpL7bwc6XLFNpV8k7ePDJmkkVCh",
		net:       mainNetParams,
		decodeErr: ErrMalformedAddress,
	}, {
		name:      "p2pk-schnorr-secp256k1 malformed pubkey via decode",
		addr:      "3tWUW3oD87XtmFVGnLX4Z3Hdesm2qRvvN8H5kq3yXxJumCxbvCpo",
		net:       mainNetParams,
		decodeErr: ErrMalformedAddressData,
	}}

	for _, test := range tests {
		_, err := DecodeAddressV0(test.addr, test.net)
		if !errors.Is(err, test.decodeErr) {
			t.Errorf("%s: mismatched err -- got %v, want %v", test.name, err,
				test.decodeErr)
			continue
		}
	}
}

// TestProbablyV0Base58Addr ensures the function that determines if an address
// is probably a base58 address works as intended by checking off by ones and
// ensuring all allowed characters in the modified base58 alphabet are accepted.
func TestProbablyV0Base58Addr(t *testing.T) {
	tests := []struct {
		name string // test description
		str  string // string to test
		want bool   // expected result
	}{{
		name: "all allowed base58 chars part 1",
		str:  "123456789ABCDEFGHJKLMNPQRSTUVWXYZab",
		want: true,
	}, {
		name: "all allowed base58 chars part 2",
		str:  "QRSTUVWXYZabcdefghijkmnopqrstuvwxyz",
		want: true,
	}, {
		name: "invalid base58 (0 not in base58 alphabet, one less than '1')",
		str:  "DsUZxxoH0Sty8DCfwfartwTYbuhmVct7tJu",
		want: false,
	}, {
		name: "invalid base58 ({ not in base58 alphabet, one more than 'z')",
		str:  "DsUZxxoH{Sty8DCfwfartwTYbuhmVct7tJu",
		want: false,
	}, {
		name: "invalid base58 (I not in base58 alphabet)",
		str:  "DsUZxxoHISty8DCfwfartwTYbuhmVct7tJu",
		want: false,
	}, {
		name: "invalid base58 (O not in base58 alphabet)",
		str:  "DsUZxxoHOSty8DCfwfartwTYbuhmVct7tJu",
		want: false,
	}, {
		name: "invalid base58 (l not in base58 alphabet)",
		str:  "DsUZxxoHlSty8DCfwfartwTYbuhmVct7tJu",
		want: false,
	}, {
		name: "invalid base58 (: not in base58 alphabet, one more than '9')",
		str:  "DsUZxxoH:Sty8DCfwfartwTYbuhmVct7tJu",
		want: false,
	}, {
		name: "invalid base58 (@ not in base58 alphabet, one less than 'A')",
		str:  "DsUZxxoH@Sty8DCfwfartwTYbuhmVct7tJu",
		want: false,
	}, {
		name: "invalid base58 ([ not in base58 alphabet, one more than 'Z')",
		str:  "DsUZxxoH[Sty8DCfwfartwTYbuhmVct7tJu",
		want: false,
	}, {
		name: "invalid base58 (` not in base58 alphabet, one less than 'a')",
		str:  "DsUZxxoH`Sty8DCfwfartwTYbuhmVct7tJu",
		want: false,
	}}

	for _, test := range tests {
		got := probablyV0Base58Addr(test.str)
		if got != test.want {
			t.Errorf("%q: unexpected result -- got %v, want %v", test.name, got,
				test.want)
			continue
		}
	}
}
