diff --git a/internal/staging/stdaddr/address.go b/internal/staging/stdaddr/address.go index 1eef1125..0cf88c2c 100644 --- a/internal/staging/stdaddr/address.go +++ b/internal/staging/stdaddr/address.go @@ -16,6 +16,7 @@ import ( // required when encoding and decoding addresses. These values are typically // well-defined and unique per network. type AddressParams interface { + AddressParamsV0 } // Address represents any type of destination a transaction output may spend to. @@ -324,11 +325,42 @@ func NewAddressScriptHash(scriptVersion uint16, redeemScript []byte, return nil, makeError(ErrUnsupportedScriptVersion, str) } +// probablyV0Base58Addr returns true when the provided string looks like a +// version 0 base58 address as determined by their length and only containing +// runes in the base58 alphabet used by Decred for version 0 addresses. +func probablyV0Base58Addr(s string) bool { + // Ensure the length is one of the possible values for supported version 0 + // addresses. + if len(s) != 35 && len(s) != 53 { + return false + } + + // The modified base58 alphabet used by Decred for version 0 addresses is: + // 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz + for _, r := range s { + if r < '1' || r > 'z' || + r == 'I' || r == 'O' || r == 'l' || + (r > '9' && r < 'A') || (r > 'Z' && r < 'a') { + + return false + } + } + + return true +} + // DecodeAddress decodes the string encoding of an address and returns the // relevant Address if it is a valid encoding for a known address type and is // for the provided network. func DecodeAddress(addr string, params AddressParams) (Address, error) { - // Parsing code for future address/script versions goes here. + // Parsing code for future address/script versions should be added as the + // most recent case in the switch statement. The expectation is that newer + // version addresses will become more common, so they should be checked + // first. + switch { + case probablyV0Base58Addr(addr): + return DecodeAddressV0(addr, params) + } str := fmt.Sprintf("address %q is not a supported type", addr) return nil, makeError(ErrUnsupportedAddress, str) diff --git a/internal/staging/stdaddr/address_test.go b/internal/staging/stdaddr/address_test.go new file mode 100644 index 00000000..d5d6290c --- /dev/null +++ b/internal/staging/stdaddr/address_test.go @@ -0,0 +1,457 @@ +// 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" + "fmt" + "testing" + + "github.com/decred/base58" + "github.com/decred/dcrd/crypto/ripemd160" +) + +// 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 + } +} + +// 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() + + 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 + feeLimits uint16 // reward fee limits commitment + 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 + }{{ + // --------------------------------------------------------------------- + // 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, + }} + + 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.Address() + 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.feeLimits) + 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.Address() + 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 stringer returns the same address as the original. + if ds, ok := decodedAddr.(fmt.Stringer); ok && ds.String() != test.addr { + t.Errorf("%s: mismatched decoded stringer -- got %v, want %v", + test.name, ds.String(), test.addr) + continue + } + + // 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, + }} + + 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 + } + } +} diff --git a/internal/staging/stdaddr/addressv0.go b/internal/staging/stdaddr/addressv0.go new file mode 100644 index 00000000..55eb8bad --- /dev/null +++ b/internal/staging/stdaddr/addressv0.go @@ -0,0 +1,158 @@ +// 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 ( + "errors" + "fmt" + + "github.com/decred/base58" + "github.com/decred/dcrd/dcrec" + "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +const ( + // sigTypeSecp256k1PubKeyCompOddFlag specifies the bitmask to apply to the + // pubkey address signature type byte for those that deal with compressed + // secp256k1 pubkeys to specify the omitted y coordinate is odd. + sigTypeSecp256k1PubKeyCompOddFlag = uint8(1 << 7) +) + +// AddressParamsV0 defines an interface that is used to provide the parameters +// required when encoding and decoding addresses for version 0 scripts. These +// values are typically well-defined and unique per network. +type AddressParamsV0 interface { + // AddrIDPubKeyV0 returns the magic prefix bytes for version 0 pay-to-pubkey + // addresses. + AddrIDPubKeyV0() [2]byte + + // AddrIDPubKeyHashECDSAV0 returns the magic prefix bytes for version 0 + // pay-to-pubkey-hash addresses where the underlying pubkey is secp256k1 and + // the signature algorithm is ECDSA. + AddrIDPubKeyHashECDSAV0() [2]byte + + // AddrIDPubKeyHashEd25519V0 returns the magic prefix bytes for version 0 + // pay-to-pubkey-hash addresses where the underlying pubkey and signature + // algorithm are Ed25519. + AddrIDPubKeyHashEd25519V0() [2]byte + + // AddrIDPubKeyHashSchnorrV0 returns the magic prefix bytes for version 0 + // pay-to-pubkey-hash addresses where the underlying pubkey is secp256k1 and + // the signature algorithm is Schnorr. + AddrIDPubKeyHashSchnorrV0() [2]byte + + // AddrIDScriptHashV0 returns the magic prefix bytes for version 0 + // pay-to-script-hash addresses. + AddrIDScriptHashV0() [2]byte +} + +// DecodeAddressV0 decodes the string encoding of an address and returns the +// relevant Address if it is a valid encoding for a known version 0 address type +// and is for the network identified by the provided parameters. +func DecodeAddressV0(addr string, params AddressParamsV0) (Address, error) { + // Attempt to decode the address and address type. + decoded, addrID, err := base58.CheckDecode(addr) + if err != nil { + kind := ErrMalformedAddress + if errors.Is(err, base58.ErrChecksum) { + kind = ErrBadAddressChecksum + } + str := fmt.Sprintf("failed to decoded address %q: %v", addr, err) + return nil, makeError(kind, str) + } + + // Decode the address according to the address type. + switch addrID { + case params.AddrIDScriptHashV0(): + return NewAddressScriptHashFromHash(0, decoded, params) + + case params.AddrIDPubKeyHashECDSAV0(): + return NewAddressPubKeyHashEcdsaSecp256k1(0, decoded, params) + + case params.AddrIDPubKeyHashSchnorrV0(): + return NewAddressPubKeyHashSchnorrSecp256k1(0, decoded, params) + + case params.AddrIDPubKeyHashEd25519V0(): + return NewAddressPubKeyHashEd25519(0, decoded, params) + + case params.AddrIDPubKeyV0(): + // Ensure the decoded data has the expected signature type identifier + // byte. + if len(decoded) < 1 { + str := fmt.Sprintf("address %q decoded data is empty", addr) + return nil, makeError(ErrMalformedAddressData, str) + } + + // Decode according to the crypto algorithm and signature scheme. + sigType := decoded[0] & ^sigTypeSecp256k1PubKeyCompOddFlag + switch dcrec.SignatureType(sigType) { + case dcrec.STEcdsaSecp256k1: + // The encoded data for this case is the 32-byte X coordinate for a + // secp256k1 public key along with the oddness of the Y coordinate + // encoded via the high bit of the first byte. + // + // Reconstruct the standard compressed serialized public key format + // by choosing the correct prefix byte depending on the encoded + // Y-coordinate oddness pass it along to the constructor of the + // appropriate type to validate and return the relevant address + // instance. + const reqPubKeyLen = 33 + if len(decoded) != reqPubKeyLen { + str := fmt.Sprintf("public key is %d bytes vs required %d bytes", + len(decoded), reqPubKeyLen) + return nil, makeError(ErrMalformedAddressData, str) + } + isOddY := decoded[0]&sigTypeSecp256k1PubKeyCompOddFlag != 0 + prefix := secp256k1.PubKeyFormatCompressedEven + if isOddY { + prefix = secp256k1.PubKeyFormatCompressedOdd + } + decoded[0] = prefix + return NewAddressPubKeyEcdsaSecp256k1Raw(0, decoded, params) + + case dcrec.STEd25519: + const reqPubKeyLen = 32 + pubKey := decoded[1:] + if len(pubKey) != reqPubKeyLen { + str := fmt.Sprintf("public key is %d bytes vs required %d bytes", + len(pubKey), reqPubKeyLen) + return nil, makeError(ErrMalformedAddressData, str) + } + + // The encoded data for this case is the actual Ed25519 public key, + // so just pass it along unaltered to the constructor of the + // appropriate type to validate and return the relevant address + // instance. + return NewAddressPubKeyEd25519Raw(0, pubKey, params) + + case dcrec.STSchnorrSecp256k1: + // The encoded data for this case is the 32-byte X coordinate for a + // secp256k1 public key along with the oddness of the Y coordinate + // encoded via the high bit of the first byte. + // + // Reconstruct the standard compressed serialized public key format + // by choosing the correct prefix byte depending on the encoded + // Y-coordinate oddness pass it along to the constructor of the + // appropriate type to validate and return the relevant address + // instance. + const reqPubKeyLen = 33 + if len(decoded) != reqPubKeyLen { + str := fmt.Sprintf("public key is %d bytes vs required %d bytes", + len(decoded), reqPubKeyLen) + return nil, makeError(ErrMalformedAddressData, str) + } + isOddY := decoded[0]&sigTypeSecp256k1PubKeyCompOddFlag != 0 + prefix := secp256k1.PubKeyFormatCompressedEven + if isOddY { + prefix = secp256k1.PubKeyFormatCompressedOdd + } + decoded[0] = prefix + return NewAddressPubKeySchnorrSecp256k1Raw(0, decoded, params) + } + } + + str := fmt.Sprintf("address %q is not a supported type", addr) + return nil, makeError(ErrUnsupportedAddress, str) +} diff --git a/internal/staging/stdaddr/error.go b/internal/staging/stdaddr/error.go index 3f1e06ea..fed438a9 100644 --- a/internal/staging/stdaddr/error.go +++ b/internal/staging/stdaddr/error.go @@ -16,6 +16,17 @@ const ( // ErrUnsupportedScriptVersion indicates that an address type does not // support a given script version. ErrUnsupportedScriptVersion = ErrorKind("ErrUnsupportedScriptVersion") + + // ErrMalformedAddress indicates an address failed to decode. + ErrMalformedAddress = ErrorKind("ErrMalformedAddress") + + // ErrMalformedAddressData indicates an address successfully decoded and is + // a recognized type, but the encoded data is not the expected length. + ErrMalformedAddressData = ErrorKind("ErrMalformedAddressData") + + // ErrBadAddressChecksum indicates an address failed to decode due to an + // invalid checksum. + ErrBadAddressChecksum = ErrorKind("ErrBadAddressChecksum") ) // Error satisfies the error interface and prints human-readable errors. diff --git a/internal/staging/stdaddr/error_test.go b/internal/staging/stdaddr/error_test.go index 217f424f..c8904026 100644 --- a/internal/staging/stdaddr/error_test.go +++ b/internal/staging/stdaddr/error_test.go @@ -18,6 +18,9 @@ func TestErrorKindStringer(t *testing.T) { }{ {ErrUnsupportedAddress, "ErrUnsupportedAddress"}, {ErrUnsupportedScriptVersion, "ErrUnsupportedScriptVersion"}, + {ErrMalformedAddress, "ErrMalformedAddress"}, + {ErrMalformedAddressData, "ErrMalformedAddressData"}, + {ErrBadAddressChecksum, "ErrBadAddressChecksum"}, } for i, test := range tests {