diff --git a/internal/staging/stdaddr/address.go b/internal/staging/stdaddr/address.go index fed1ed40..6ebf0a39 100644 --- a/internal/staging/stdaddr/address.go +++ b/internal/staging/stdaddr/address.go @@ -307,9 +307,16 @@ func NewAddressPubKeyHashEcdsaSecp256k1(scriptVersion uint16, pkHash []byte, // the Hash160 of the correct public key or it will not be redeemable with the // expected public key because it would hash to a different value than the // payment script generated for the provided incorrect public key hash expects. +// +// NOTE: Version 0 scripts are the only currently supported version. func NewAddressPubKeyHashEd25519(scriptVersion uint16, pkHash []byte, params AddressParams) (Address, error) { + switch scriptVersion { + case 0: + return NewAddressPubKeyHashEd25519V0(pkHash, params) + } + str := fmt.Sprintf("pubkey hash addresses for version %d are not "+ "supported", scriptVersion) return nil, makeError(ErrUnsupportedScriptVersion, str) diff --git a/internal/staging/stdaddr/address_test.go b/internal/staging/stdaddr/address_test.go index cc75e62e..34d26aad 100644 --- a/internal/staging/stdaddr/address_test.go +++ b/internal/staging/stdaddr/address_test.go @@ -753,6 +753,80 @@ func TestAddresses(t *testing.T) { 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", }} for _, test := range tests { @@ -951,6 +1025,11 @@ func TestAddresses(t *testing.T) { 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() } if pkhAddr != nil { gotAddr := pkhAddr.Address() diff --git a/internal/staging/stdaddr/addressv0.go b/internal/staging/stdaddr/addressv0.go index 477d298d..360835c9 100644 --- a/internal/staging/stdaddr/addressv0.go +++ b/internal/staging/stdaddr/addressv0.go @@ -273,8 +273,10 @@ type AddressPubKeyEd25519V0 struct { serializedPubKey []byte } -// Ensure AddressPubKeyEd25519V0 implements the Address interfaces. +// Ensure AddressPubKeyEd25519V0 implements the Address and AddressPubKeyHasher +// interfaces. var _ Address = (*AddressPubKeyEd25519V0)(nil) +var _ AddressPubKeyHasher = (*AddressPubKeyEd25519V0)(nil) // NewAddressPubKeyEd25519V0Raw returns an address that represents a payment // destination which imposes an encumbrance that requires a valid Ed25519 @@ -356,6 +358,17 @@ func (addr *AddressPubKeyEd25519V0) PaymentScript() (uint16, []byte) { return 0, script[:] } +// AddressPubKeyHash returns the address converted to a +// pay-to-pubkey-hash-ed25519 address. +func (addr *AddressPubKeyEd25519V0) AddressPubKeyHash() Address { + pkHash := Hash160(addr.serializedPubKey) + addrPKH := &AddressPubKeyHashEd25519V0{ + netID: addr.pubKeyHashID, + } + copy(addrPKH.hash[:], pkHash) + return addrPKH +} + // String returns a human-readable string for the address. // // This is equivalent to calling Address, but is provided so the type can be @@ -697,6 +710,94 @@ func (addr *AddressPubKeyHashEcdsaSecp256k1V0) String() string { return addr.Address() } +// AddressPubKeyHashEd25519V0 specifies an address that represents a a payment +// destination which imposes an encumbrance that requires an Ed25519 public key +// that hashes to the given public key hash along with a valid Ed25519 signature +// for that public key. +// +// This is commonly referred to as pay-to-pubkey-hash-ed25519. +type AddressPubKeyHashEd25519V0 struct { + netID [2]byte + hash [ripemd160.Size]byte +} + +// Ensure AddressPubKeyHashEd25519V0 implements the Address and Hash160er +// interfaces. +var _ Address = (*AddressPubKeyHashEd25519V0)(nil) +var _ Hash160er = (*AddressPubKeyHashEd25519V0)(nil) + +// NewAddressPubKeyHashEd25519V0 returns an address that represents a a payment +// destination which imposes an encumbrance that requires an Ed25519 public key +// that hashes to the provided public key hash along with a valid Ed25519 +// signature for that public key using version 0 scripts. +// +// The provided public key hash must be 20 bytes and be the Hash160 of the +// correct public key or it will not be redeemable with the expected public key +// because it would hash to a different value than the payment script generated +// for the provided incorrect public key hash expects. +func NewAddressPubKeyHashEd25519V0(pkHash []byte, + params AddressParamsV0) (*AddressPubKeyHashEd25519V0, error) { + + // Check for a valid script hash length. + if len(pkHash) != ripemd160.Size { + str := fmt.Sprintf("public key hash is %d bytes vs required %d bytes", + len(pkHash), ripemd160.Size) + return nil, makeError(ErrInvalidHashLen, str) + } + + addr := &AddressPubKeyHashEd25519V0{ + netID: params.AddrIDPubKeyHashEd25519V0(), + } + copy(addr.hash[:], pkHash) + return addr, nil +} + +// Address returns the string encoding of the payment address for the associated +// script version and payment script. +// +// This is part of the Address interface implementation. +func (addr *AddressPubKeyHashEd25519V0) Address() string { + // The format for the data portion of addresses that encode 160-bit hashes + // is merely the hash itself: + // 20-byte ripemd160 hash + return encodeAddressV0(addr.hash[:ripemd160.Size], addr.netID) +} + +// PaymentScript returns the script version associated with the address along +// with a script to pay a transaction output to the address. +// +// This is part of the Address interface implementation. +func (addr *AddressPubKeyHashEd25519V0) PaymentScript() (uint16, []byte) { + // A pay-to-pubkey-hash-ed25519 script is of the form: + // DUP HASH160 <20-byte hash> EQUALVERIFY <1-byte sigtype> CHECKSIGALT + // + // Since the signature type is 1, it is pushed as a small integer. + var script [26]byte + script[0] = opDup + script[1] = opHash160 + script[2] = opData20 + copy(script[3:23], addr.hash[:]) + script[23] = opEqualVerify + script[24] = opPushSTEd25519 + script[25] = opCheckSigAlt + return 0, script[:] +} + +// Hash160 returns the underlying array of the pubkey hash. This can be useful +// when an array is more appropriate than a slice (for example, when used as map +// keys). +func (addr *AddressPubKeyHashEd25519V0) Hash160() *[ripemd160.Size]byte { + return &addr.hash +} + +// String returns a human-readable string for the address. +// +// This is equivalent to calling Address, but is provided so the type can be +// used as a fmt.Stringer. +func (addr *AddressPubKeyHashEd25519V0) String() string { + return addr.Address() +} + // 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.