458 lines
18 KiB
Go
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)
|
|
}
|