diff --git a/internal/staging/stdaddr/README.md b/internal/staging/stdaddr/README.md index ce1fbca88f..ebdb625927 100644 --- a/internal/staging/stdaddr/README.md +++ b/internal/staging/stdaddr/README.md @@ -26,18 +26,19 @@ implicit depending on the specific concrete encoding of the address. ### Supported Version 0 Addresses The following table lists the `version 0` address types this package supports -along with whether the type is supported by the staking system, whether it has -an associated `hash160`, and some additional notes and recommendations: - -Version 0 Address Type | Staking? | Hash160? | Notes / Recommendations ---------------------------|----------|----------|---------------------------------- -p2pk-ecdsa-secp256k1 | N | N | Prefer p2pkh in on-chain txns [1] -p2pk-ed25519 | N | N | Not recommended [2] -p2pk-schnorr-secp256k1 | N | N | Prefer p2pkh, single party [1,3] -**p2pkh-ecdsa-secp256k1** | **Y** | **Y** | **Preferred v0 address** -p2pkh-ed25519 | N | Y | Not recommended [2] -p2pkh-schnorr-secp256k1 | N | Y | Only use with single party [3] -p2sh | Y | Y | - +along with whether the type is supported by the staking system, whether it has a +raw public key that can be obtained, whether it has an associated `hash160`, and +some additional notes and recommendations: + +Version 0 Address Type | Staking? | PubKey? | Hash160? | Notes / Recommendations +--------------------------|----------|---------|----------|---------------------------------- +p2pk-ecdsa-secp256k1 | N | Y | N | Prefer p2pkh in on-chain txns [1] +p2pk-ed25519 | N | Y | N | Not recommended [2] +p2pk-schnorr-secp256k1 | N | Y | N | Prefer p2pkh, single party [1,3] +**p2pkh-ecdsa-secp256k1** | **Y** | **N** | **Y** | **Preferred v0 address** +p2pkh-ed25519 | N | N | Y | Not recommended [2] +p2pkh-schnorr-secp256k1 | N | N | Y | Only use with single party [3] +p2sh | Y | N | Y | - Abbreviations: @@ -182,6 +183,24 @@ if an address can be converted to its public key hash variant by type asserting the interface and then convert it by making use of the `AddressPubKeyHash` method provided by the interface. +### Obtaining Serialized Public Key From Public Key Addresses + +When making use of public key addresses, callers often need to obtain the +serialized public key associated with the address for further processing. + +This package provides the `SerializedPubKeyer` interface, which is only +implemented by the public key address types, for this purpose. Callers may +determine if a serialized public key can be obtained from an address by type +asserting the interface and then extract it by making use of the +`SerializedPubKey` method provided by the interface. + +However, it is also worth noting that merely obtaining the serialized public key +via the generic interface is typically not sufficient on its own for a caller to +be able to work with it since parsing a public key requires knowing what type of +key it is as well. In that case, the caller will likely need to fall back to +type asserting the specific concrete implementation to determine which type of +public key it is dealing with. + ### Hash160 Use in Addresses The term `Hash160` is used as shorthand to refer to a hash that is created via a diff --git a/internal/staging/stdaddr/address.go b/internal/staging/stdaddr/address.go index 53acce90a5..17a32e9d11 100644 --- a/internal/staging/stdaddr/address.go +++ b/internal/staging/stdaddr/address.go @@ -86,6 +86,12 @@ type StakeAddress interface { PayFromTreasuryScript() (uint16, []byte) } +// SerializedPubKeyer is an interface for public key addresses that allows the +// serialized public key to be obtained from addresses that involve them. +type SerializedPubKeyer interface { + SerializedPubKey() []byte +} + // AddressPubKeyHasher is an interface for public key addresses that can be // converted to an address that imposes an encumbrance that requires the public // key that hashes to a given public key hash along with a valid signature for diff --git a/internal/staging/stdaddr/address_test.go b/internal/staging/stdaddr/address_test.go index a45a43c37c..b7a1e5e269 100644 --- a/internal/staging/stdaddr/address_test.go +++ b/internal/staging/stdaddr/address_test.go @@ -153,6 +153,7 @@ func TestAddresses(t *testing.T) { 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. @@ -265,6 +266,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "21028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2edac", + pubKey: "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed", }, { name: "mainnet p2pk-ecdsa-secp256k1 compressed (0x03)", makeAddr: func() (Address, error) { @@ -278,6 +280,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "2103e925aafc1edd44e7c7f1ea4fb7d265dc672f204c3d0c81930389c10b81fb75deac", + pubKey: "03e925aafc1edd44e7c7f1ea4fb7d265dc672f204c3d0c81930389c10b81fb75de", }, { name: "mainnet p2pk-ecdsa-secp256k1 compressed via concrete constructor", makeAddr: func() (Address, error) { @@ -294,6 +297,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "21028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2edac", + pubKey: "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed", }, { name: "mainnet p2pk-ecdsa-secp256k1 compressed from uncompressed via concrete constructor", makeAddr: func() (Address, error) { @@ -312,6 +316,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "210264c44653d6567eff5753c5d24a682ddc2b2cadfe1b0c6433b16374dace6778f0ac", + pubKey: "0264c44653d6567eff5753c5d24a682ddc2b2cadfe1b0c6433b16374dace6778f0", }, { name: "testnet p2pk-ecdsa-secp256k1 compressed (0x02)", makeAddr: func() (Address, error) { @@ -325,6 +330,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "21026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06eac", + pubKey: "026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e", }, { name: "testnet p2pk-ecdsa-secp256k1 compressed (0x03)", makeAddr: func() (Address, error) { @@ -338,6 +344,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "21030844ee70d8384d5250e9bb3a6a73d4b5bec770e8b31d6a0ae9fb739009d91af5ac", + pubKey: "030844ee70d8384d5250e9bb3a6a73d4b5bec770e8b31d6a0ae9fb739009d91af5", }, { name: "testnet p2pk-ecdsa-secp256k1 compressed via concrete constructor", makeAddr: func() (Address, error) { @@ -354,6 +361,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "21026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06eac", + pubKey: "026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e", }, { name: "testnet p2pk-ecdsa-secp256k1 compressed from uncompressed via concrete constructor", makeAddr: func() (Address, error) { @@ -372,6 +380,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "21026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06eac", + pubKey: "026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e", }, { name: "regnet p2pk-ecdsa-secp256k1 compressed (0x02)", addr: "Rk41kKgrecrxQ8bLg8GJm1feMPBFtFeb4rG56tDfMdAtvPy4HneyR", @@ -379,6 +388,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "21026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06eac", + pubKey: "026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e", }, { name: "regnet p2pk-ecdsa-secp256k1 compressed (0x03)", addr: "Rk8HpTQVbFvNQgxK3sPSiks8K1B8wbyYUGM8qABDpWGp63Q5mnG52", @@ -386,6 +396,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "21030844ee70d8384d5250e9bb3a6a73d4b5bec770e8b31d6a0ae9fb739009d91af5ac", + pubKey: "030844ee70d8384d5250e9bb3a6a73d4b5bec770e8b31d6a0ae9fb739009d91af5", }, { // --------------------------------------------------------------------- // Negative P2PK Ed25519 tests. @@ -438,6 +449,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "20cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc51be", + pubKey: "cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", }, { name: "mainnet p2pk-ed25519 via concrete constructor", makeAddr: func() (Address, error) { @@ -455,6 +467,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "20cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc51be", + pubKey: "cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", }, { name: "testnet p2pk-ed25519", makeAddr: func() (Address, error) { @@ -469,6 +482,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "20cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc51be", + pubKey: "cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", }, { name: "testnet p2pk-ed25519 via concrete constructor", makeAddr: func() (Address, error) { @@ -486,6 +500,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "20cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc51be", + pubKey: "cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", }, { name: "regnet p2pk-ed25519", makeAddr: func() (Address, error) { @@ -500,6 +515,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "20cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc51be", + pubKey: "cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", }, { // --------------------------------------------------------------------- // Negative P2PK Schnorr secp256k1 tests. @@ -582,6 +598,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "21028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed52be", + pubKey: "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed", }, { name: "mainnet p2pk-schnorr-secp256k1 compressed (0x03)", addr: "DkRQx3y6YoJPnMKom23nuDFdfhmEnu8oDLTp4YVyWC6RjND19UxHk", @@ -589,6 +606,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "2103e925aafc1edd44e7c7f1ea4fb7d265dc672f204c3d0c81930389c10b81fb75de52be", + pubKey: "03e925aafc1edd44e7c7f1ea4fb7d265dc672f204c3d0c81930389c10b81fb75de", }, { name: "mainnet p2pk-schnorr-secp256k1 compressed via concrete constructor", makeAddr: func() (Address, error) { @@ -605,6 +623,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "21028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed52be", + pubKey: "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed", }, { name: "mainnet p2pk-schnorr-secp256k1 compressed from uncompressed via concrete constructor", makeAddr: func() (Address, error) { @@ -623,6 +642,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "21028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed52be", + pubKey: "028f53838b7639563f27c94845549a41e5146bcd52e7fef0ea6da143a02b0fe2ed", }, { name: "testnet p2pk-schnorr-secp256k1 compressed (0x02)", addr: "TkKqFCtYcHPupVbPcDdhvkNeNYXPqqs88Z4ygukH9MGaNV8e1WWhX", @@ -630,6 +650,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "21026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e52be", + pubKey: "026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e", }, { name: "testnet p2pk-schnorr-secp256k1 compressed (0x03)", addr: "TkQ7KLcBYvTKq3xMyxkqtVa8LAXGuCC5XyA3RBhqcENVY8ZiDjuAB", @@ -637,6 +658,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "21030844ee70d8384d5250e9bb3a6a73d4b5bec770e8b31d6a0ae9fb739009d91af552be", + pubKey: "030844ee70d8384d5250e9bb3a6a73d4b5bec770e8b31d6a0ae9fb739009d91af5", }, { name: "regnet p2pk-schnorr-secp256k1 compressed (0x02)", addr: "Rk45dp3KYgZokaryqY5U11aHYw1V7gwzkbqMF6hxjRACwAxDivM6N", @@ -644,6 +666,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "21026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e52be", + pubKey: "026a40c403e74670c4de7656a09caa2353d4b383a9ce66eef51e1220eacf4be06e", }, { name: "regnet p2pk-schnorr-secp256k1 compressed (0x03)", addr: "Rk8MhwkxVKdDm9DxDHCbxkmmWZ1NB3GxA1vQyNfXCJG86pPPnnn9M", @@ -651,6 +674,7 @@ func TestAddresses(t *testing.T) { decodeErr: nil, version: 0, payScript: "21030844ee70d8384d5250e9bb3a6a73d4b5bec770e8b31d6a0ae9fb739009d91af552be", + pubKey: "030844ee70d8384d5250e9bb3a6a73d4b5bec770e8b31d6a0ae9fb739009d91af5", }, { // --------------------------------------------------------------------- // Negative P2PKH ECDSA secp256k1 tests. @@ -1196,6 +1220,22 @@ func TestAddresses(t *testing.T) { 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 diff --git a/internal/staging/stdaddr/addressv0.go b/internal/staging/stdaddr/addressv0.go index c98b1b1533..dbae5b3841 100644 --- a/internal/staging/stdaddr/addressv0.go +++ b/internal/staging/stdaddr/addressv0.go @@ -258,6 +258,12 @@ func (addr *AddressPubKeyEcdsaSecp256k1V0) AddressPubKeyHash() Address { return addrPKH } +// SerializedPubKey returns the compressed serialization of the secp256k1 public +// key. The bytes must not be modified. +func (addr *AddressPubKeyEcdsaSecp256k1V0) SerializedPubKey() []byte { + return addr.serializedPubKey +} + // String returns a human-readable string for the address. // // This is equivalent to calling Address, but is provided so the type can be @@ -373,6 +379,12 @@ func (addr *AddressPubKeyEd25519V0) AddressPubKeyHash() Address { return addrPKH } +// SerializedPubKey returns the serialization of the ed25519 public key. The +// bytes must not be modified. +func (addr *AddressPubKeyEd25519V0) SerializedPubKey() []byte { + return addr.serializedPubKey +} + // String returns a human-readable string for the address. // // This is equivalent to calling Address, but is provided so the type can be @@ -517,6 +529,12 @@ func (addr *AddressPubKeySchnorrSecp256k1V0) AddressPubKeyHash() Address { return addrPKH } +// SerializedPubKey returns the compressed serialization of the secp256k1 public +// key. The bytes must not be modified. +func (addr *AddressPubKeySchnorrSecp256k1V0) SerializedPubKey() []byte { + return addr.serializedPubKey +} + // String returns a human-readable string for the address. // // This is equivalent to calling Address, but is provided so the type can be