dcrd/txscript/stdaddr/address.go
2023-08-23 14:13:51 -05:00

458 lines
18 KiB
Go

// 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)
}