// Copyright (c) 2021-2023 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. // Package stdaddr provides facilities for working with human-readable Decred // payment addresses. package stdaddr import ( "fmt" "github.com/decred/dcrd/crypto/ripemd160" ) // AddressParams defines an interface that is used to provide the parameters // 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. // Some examples include pay-to-pubkey (P2PK), pay-to-pubkey-hash (P2PKH), and // pay-to-script-hash (P2SH). Address is designed to be generic enough that // other kinds of addresses may be added in the future without changing the // decoding and encoding API. type Address interface { // String returns the string encoding of the payment address for the // associated script version and payment script. String() string // PaymentScript returns the script version associated with the address // along with a script to pay a transaction output to the address. PaymentScript() (uint16, []byte) } // StakeAddress is an interface for generating the specialized scripts that are // used in the staking system. Only specific address types are supported by the // staking system and therefore only those address types will implement this // interface. // // Version 1 and 3 staking transactions only support version 0 scripts and // address types of AddressPubKeyHashEcdsaSecp256k1V0 and AddressScriptHashV0. // // Callers can programmatically assert a specific address implements this // interface to access the methods. type StakeAddress interface { Address // VotingRightsScript returns the script version associated with the address // along with a script to give voting rights to the address. It is only // valid when used in stake ticket purchase transactions. VotingRightsScript() (uint16, []byte) // RewardCommitmentScript returns the script version associated with the // address along with a script that commits the original funds locked to // purchase a ticket plus the reward to the address along with limits to // impose on any fees (in atoms). // // Note that fee limits are encoded in the commitment script in terms of the // closest base 2 exponent that results in a limit that is >= the provided // limit. In other words, the limits are rounded up to the next power of 2 // when they are not already an exact power of 2. For example, a revocation // limit of 2^23 + 1 will result in allowing a revocation fee of up to 2^24 // atoms. RewardCommitmentScript(amount, voteFeeLimit, revocationFeeLimit int64) (uint16, []byte) // StakeChangeScript returns the script version associated with the address // along with a script to pay change to the address. It is only valid when // used in stake ticket purchase and treasury add transactions. StakeChangeScript() (uint16, []byte) // PayVoteCommitmentScript returns the script version associated with the // address along with a script to pay the original funds locked to purchase // a ticket plus the reward to the address. The address must have // previously been committed to by the ticket purchase. The script is only // valid when used in stake vote transactions whose associated tickets are // eligible to vote. PayVoteCommitmentScript() (uint16, []byte) // PayRevokeCommitmentScript returns the script version associated with the // address along with a script to revoke an expired or missed ticket which // pays the original funds locked to purchase a ticket to the address. The // address must have previously been committed to by the ticket purchase. // The script is only valid when used in stake revocation transactions whose // associated tickets have been missed or have expired. PayRevokeCommitmentScript() (uint16, []byte) // PayFromTreasuryScript returns the script version associated with the // address along with a script that pays funds from the treasury to the // address. The script is only valid when used in treasury spend // transactions. 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 // that public key. // // The specific public key and signature types are dependent on the original // address. type AddressPubKeyHasher interface { AddressPubKeyHash() Address } // Hash160er is an interface that allows the RIPEMD-160 hash to be obtained from // addresses that involve them. type Hash160er interface { Hash160() *[ripemd160.Size]byte } // Secp256k1PublicKey is an interface that represents a secp256k1 public key for // use in creating pay-to-pubkey addresses that involve them. type Secp256k1PublicKey interface { // SerializeCompressed serializes a public key in the 33-byte compressed // format. SerializeCompressed() []byte } // NewAddressPubKeyEcdsaSecp256k1Raw returns an address that represents a // payment destination which imposes an encumbrance that requires a valid ECDSA // signature for a specific secp256k1 public key. // // The provided public key MUST be a valid secp256k1 public key serialized in // the _compressed_ format or an error will be returned. // // See NewAddressPubKeyEcdsaSecp256k1 for a variant that accepts the public // key as a concrete type instance instead. // // This function can be useful to callers who already need the serialized public // key for other purposes to avoid the need to serialize it multiple times. // // NOTE: Version 0 scripts are the only currently supported version. func NewAddressPubKeyEcdsaSecp256k1Raw(scriptVersion uint16, serializedPubKey []byte, params AddressParams) (Address, error) { switch scriptVersion { case 0: return NewAddressPubKeyEcdsaSecp256k1V0Raw(serializedPubKey, params) } str := fmt.Sprintf("pubkey addresses for version %d are not supported", scriptVersion) return nil, makeError(ErrUnsupportedScriptVersion, str) } // NewAddressPubKeyEcdsaSecp256k1 returns an address that represents a // payment destination which imposes an encumbrance that requires a valid ECDSA // signature for a specific secp256k1 public key. // // See NewAddressPubKeyEcdsaSecp256k1Raw for a variant that accepts the public // key already serialized in the _compressed_ format instead of a concrete type. // It can be useful to callers who already need the serialized public key for // other purposes to avoid the need to serialize it multiple times. // // NOTE: Version 0 scripts are the only currently supported version. func NewAddressPubKeyEcdsaSecp256k1(scriptVersion uint16, pubKey Secp256k1PublicKey, params AddressParams) (Address, error) { switch scriptVersion { case 0: return NewAddressPubKeyEcdsaSecp256k1V0(pubKey, params) } str := fmt.Sprintf("pubkey addresses for version %d are not supported", scriptVersion) return nil, makeError(ErrUnsupportedScriptVersion, str) } // NewAddressPubKeyEd25519Raw returns an address that represents a payment // destination which imposes an encumbrance that requires a valid Ed25519 // signature for a specific Ed25519 public key. // // See NewAddressPubKeyEd25519 for a variant that accepts the public key as a // concrete type instance instead. // // NOTE: Version 0 scripts are the only currently supported version. func NewAddressPubKeyEd25519Raw(scriptVersion uint16, serializedPubKey []byte, params AddressParams) (Address, error) { switch scriptVersion { case 0: return NewAddressPubKeyEd25519V0Raw(serializedPubKey, params) } str := fmt.Sprintf("pubkey addresses for version %d are not supported", scriptVersion) return nil, makeError(ErrUnsupportedScriptVersion, str) } // Ed25519PublicKey is an interface type that represents an Ed25519 public key // for use in creating pay-to-pubkey addresses that involve them. type Ed25519PublicKey interface { // Serialize serializes the public key in a 32-byte compressed little endian // format. Serialize() []byte } // NewAddressPubKeyEd25519 returns an address that represents a payment // destination which imposes an encumbrance that requires a valid Ed25519 // signature for a specific Ed25519 public key. // // See NewAddressPubKeyEd25519Raw for a variant that accepts the public key // already serialized instead of a concrete type. It can be useful to callers // who already need the serialized public key for other purposes to avoid the // need to serialize it multiple times. // // NOTE: Version 0 scripts are the only currently supported version. func NewAddressPubKeyEd25519(scriptVersion uint16, pubKey Ed25519PublicKey, params AddressParams) (Address, error) { switch scriptVersion { case 0: return NewAddressPubKeyEd25519V0(pubKey, params) } str := fmt.Sprintf("pubkey addresses for version %d are not supported", scriptVersion) return nil, makeError(ErrUnsupportedScriptVersion, str) } // NewAddressPubKeySchnorrSecp256k1Raw returns an address that represents a // payment destination which imposes an encumbrance that requires a valid // EC-Schnorr-DCR signature for a specific secp256k1 public key. // // The provided public key MUST be a valid secp256k1 public key serialized in // the _compressed_ format or an error will be returned. // // See NewAddressPubKeySchnorrSecp256k1 for a variant that accepts the public // key as a concrete type instance instead. // // This function can be useful to callers who already need the serialized public // key for other purposes to avoid the need to serialize it multiple times. // // NOTE: Version 0 scripts are the only currently supported version. func NewAddressPubKeySchnorrSecp256k1Raw(scriptVersion uint16, serializedPubKey []byte, params AddressParams) (Address, error) { switch scriptVersion { case 0: return NewAddressPubKeySchnorrSecp256k1V0Raw(serializedPubKey, params) } str := fmt.Sprintf("pubkey addresses for version %d are not supported", scriptVersion) return nil, makeError(ErrUnsupportedScriptVersion, str) } // NewAddressPubKeySchnorrSecp256k1 returns an address that represents a payment // destination which imposes an encumbrance that requires a valid EC-Schnorr-DCR // signature for a specific secp256k1 public key. // // See NewAddressPubKeySchnorrSecp256k1Raw for a variant that accepts the public // key already serialized in the _compressed_ format instead of a concrete type. // It can be useful to callers who already need the serialized public key for // other purposes to avoid the need to serialize it multiple times. // // NOTE: Version 0 scripts are the only currently supported version. func NewAddressPubKeySchnorrSecp256k1(scriptVersion uint16, pubKey Secp256k1PublicKey, params AddressParams) (Address, error) { switch scriptVersion { case 0: return NewAddressPubKeySchnorrSecp256k1V0(pubKey, params) } str := fmt.Sprintf("pubkey addresses for version %d are not supported", scriptVersion) return nil, makeError(ErrUnsupportedScriptVersion, str) } // NewAddressPubKeyHashEcdsaSecp256k1 returns an address that represents a // payment destination which imposes an encumbrance that requires a secp256k1 // public key that hashes to the provided public key hash along with a valid // ECDSA signature for that public key. // // For version 0 scripts, the provided public key hash must be 20 bytes and is // expected to be the Hash160 of the associated secp256k1 public key serialized // in the _compressed_ format. // // It is important to note that while it is technically possible for legacy // reasons to create this specific type of address based on the hash of a public // key in the uncompressed format, so long as it is also redeemed with that same // public key in uncompressed format, it is *HIGHLY* recommended to use the // compressed format since it occupies less space on the chain and is more // consistent with other address formats where uncompressed public keys are NOT // supported. // // NOTE: Version 0 scripts are the only currently supported version. func NewAddressPubKeyHashEcdsaSecp256k1(scriptVersion uint16, pkHash []byte, params AddressParams) (Address, error) { switch scriptVersion { case 0: return NewAddressPubKeyHashEcdsaSecp256k1V0(pkHash, params) } str := fmt.Sprintf("pubkey hash addresses for version %d are not "+ "supported", scriptVersion) return nil, makeError(ErrUnsupportedScriptVersion, str) } // NewAddressPubKeyHashEd25519 returns an address that represents 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. // // For 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. // // 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) } // NewAddressPubKeyHashSchnorrSecp256k1 returns an address that represents a // payment destination which imposes an encumbrance that requires a secp256k1 // public key in the _compressed_ format that hashes to the provided public key // hash along with a valid EC-Schnorr-DCR signature for that public key. // // For version 0 scripts, the provided public key hash must be 20 bytes and is // expected to be the Hash160 of the associated secp256k1 public key serialized // in the _compressed_ format. // // WARNING: It is important to note that, unlike in the case of the ECDSA // variant of this type of address, redemption via a public key in the // uncompressed format is NOT supported by the consensus rules for this type, so // it is *EXTREMELY* important to ensure the provided hash is of the serialized // public key in the compressed format or the associated coins will NOT be // redeemable. // // NOTE: Version 0 scripts are the only currently supported version. func NewAddressPubKeyHashSchnorrSecp256k1(scriptVersion uint16, pkHash []byte, params AddressParams) (Address, error) { switch scriptVersion { case 0: return NewAddressPubKeyHashSchnorrSecp256k1V0(pkHash, params) } str := fmt.Sprintf("pubkey hash addresses for version %d are not "+ "supported", scriptVersion) return nil, makeError(ErrUnsupportedScriptVersion, str) } // NewAddressScriptHashFromHash returns an address that represents a payment // destination which imposes an encumbrance that requires a script that hashes // to the provided script hash along with all of the encumbrances that script // itself imposes. The script is commonly referred to as a redeem script. // // For version 0 scripts, the provided script hash must be 20 bytes and is // expected to be the Hash160 of the associated redeem script. // // See NewAddressScriptHash for a variant that accepts the redeem script instead // of its hash. It can be used as a convenience for callers that have the // redeem script available. // // NOTE: Version 0 scripts are the only currently supported version. func NewAddressScriptHashFromHash(scriptVersion uint16, scriptHash []byte, params AddressParams) (Address, error) { switch scriptVersion { case 0: return NewAddressScriptHashV0FromHash(scriptHash, params) } str := fmt.Sprintf("script hash addresses for version %d are not "+ "supported", scriptVersion) return nil, makeError(ErrUnsupportedScriptVersion, str) } // NewAddressScriptHash returns an address that represents a payment destination // which imposes an encumbrance that requires a script that hashes to the same // value as the provided script along with all of the encumbrances that script // itself imposes. The script is commonly referred to as a redeem script. // // See NewAddressScriptHashFromHash for a variant that accepts the hash of the // script directly instead of the script. It can be useful to callers that // either already have the script hash available or do not know the associated // script. // // NOTE: Version 0 scripts are the only currently supported version. func NewAddressScriptHash(scriptVersion uint16, redeemScript []byte, params AddressParams) (Address, error) { switch scriptVersion { case 0: return NewAddressScriptHashV0(redeemScript, params) } str := fmt.Sprintf("script hash addresses for version %d are not "+ "supported", scriptVersion) 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 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) }