Documentation
¶
Overview ¶
Package common provides shared types and utilities for QRL wallet implementations.
This package contains:
- Address validation and generation utilities
- Common seed handling for different wallet types
- Domain-separated signing-context construction (SigningContext)
- Shared error messages and constants
Signing Context ¶
SigningContext returns the fixed-length byte string
"ZOND" || SigningContextVersion || descriptor (8 bytes)
used by wallet packages to bind a signature cryptographically to its descriptor. ML-DSA-87 uses it as the FIPS 204 ctx parameter; SPHINCS+-256s prepends it to the message. Wallet-type-specific packages call this helper internally and callers should not usually need to construct it themselves. Bumping SigningContextVersion breaks signature compatibility and requires a coordinated consensus activation.
Address Format ¶
QRL address bytes are generated from public keys with a descriptor prefix. The string form used by modern wallet packages prepends "Q" and hex-encodes the address bytes. The exact byte format depends on the wallet type:
Legacy XMSS (byte form): Descriptor + SHA256(PK) + Checksum (39 bytes total)
ML-DSA-87 and SPHINCS+-256s (byte form): SHAKE256(Descriptor || PK)[:64] (64 bytes total)
Addresses are validated using IsValidAddress. For address generation:
- Use GetAddress for untrusted inputs (validates descriptor and pk length)
- Use UnsafeGetAddress only when the descriptor and public key have already been validated by the caller (wallet implementations rely on this fast-path).
For example:
if !common.IsValidAddress(addr) {
return errors.New("invalid QRL address")
}
Seed Derivation ¶
QRL wallets use a common 48-byte seed that is derived differently for each signature algorithm:
- ML-DSA-87: SHA-256(seed) → 32 bytes
- SPHINCS+-256s: SHAKE-256(seed) → 96 bytes
- XMSS: Direct use of seed bytes
This allows the same mnemonic to generate different wallet types.
Index ¶
- Constants
- Variables
- func GetAddress(pk []byte, desc descriptor.Descriptor) ([AddressSize]byte, error)
- func IsValidAddress(addr string) bool
- func IsValidChecksumAddress(addr string) bool
- func SigningContext(d descriptor.Descriptor) []byte
- func ToChecksumAddress(addr [AddressSize]byte) string
- func UnsafeGetAddress(pk []byte, desc descriptor.Descriptor) [AddressSize]byte
- type ExtendedSeed
- type Seed
Constants ¶
const ( AddressSize = 64 SeedSize = 48 ExtendedSeedSize = descriptor.DescriptorSize + SeedSize MLDSA87PKSize = 2592 SPHINCSPlus256sPKSize = 64 )
const ( ErrSeedGenerationFailure = "failed to generate random seed for %s address: %v" ErrInvalidDescriptor = "invalid %s descriptor" ErrDescriptorFromExtendedSeed = "failed to generate %s descriptor from extended seed: %v" ErrExtendedSeedToSeed = "failed to convert %s extended seed to seed: %v" ErrMnemonicToBin = "failed to convert %s mnemonic to bin: %v" ErrExtendedSeedFromMnemonic = "failed to create %s extended seed from mnemonic: %v" ErrExtendedSeedFromDescriptorAndSeed = "failed to create %s extended seed from descriptor and seed: %v" ErrInvalidSignatureSize = "%s unexpected signature size %d, expected signature size %d" ErrDecodeHexSeed = "failed to decode hex seed for %s: %v" ErrInvalidPKSize = "%s invalid pkBytes size %d, expected %d" ErrInvalidSeedLength = "%s invalid seed length %d, expected %d" ErrInvalidExtendedSeedLength = "%s invalid extended seed length %d, expected %d" )
fmt.Errorf format strings used for wrapping errors with wallet-type context. These are not sentinel errors; for errors.Is comparisons see the sentinel block below.
const SigningContextSize = len(SigningContextPrefix) + 1 + descriptor.DescriptorSize
SigningContextSize is the fixed on-wire length of a signing context constructed by SigningContext.
const SigningContextVersion byte = 0x01
SigningContextVersion is the current signing-context format version.
Bumping this value is a hard break of the signature wire format: all signatures produced under a new version will fail to verify under the old version and vice-versa. A version bump must coincide with a coordinated consensus/library activation.
Variables ¶
var ( ErrWalletTypeNotIssuable = errors.New("wallet type is not currently issuable") ErrWalletTypeNotVerifiable = errors.New("wallet type is not currently verifiable") )
Sentinel errors for wallet-type gating. Compare with errors.Is.
ErrWalletTypeNotIssuable is returned by wallet constructors when the requested wallet type is recognised by the descriptor format but is not currently enabled for new wallet construction. Today this means SPHINCSPLUS_256S, which is reserved as a forward placeholder for QRL's eventual SLH-DSA (FIPS 205) adoption. See github.com/theQRL/go-qrllib/wallet/common/wallettype.WalletType.IsIssuable.
ErrWalletTypeNotVerifiable is the equivalent for wallet-level Verify dispatch. Today wallet-level Verify functions return false rather than surfacing this error directly (Verify's signature is a bool), but the sentinel is exposed for callers that need to distinguish "signature invalid" from "wallet type not currently supported".
var SigningContextPrefix = [...]byte{'Z', 'O', 'N', 'D'}
SigningContextPrefix is the application-domain tag embedded in every signature's context.
Functions ¶
func GetAddress ¶ added in v0.1.1
func GetAddress(pk []byte, desc descriptor.Descriptor) ([AddressSize]byte, error)
GetAddress validates the descriptor and public key length, then derives the address. It is the safe wrapper around UnsafeGetAddress for untrusted inputs.
func IsValidAddress ¶ added in v0.1.1
IsValidAddress validates a QRL address string.
A valid address has the format "Q" followed by AddressSize*2 hex characters. The hex body must be one of:
- all lowercase (case-uniform), or
- all uppercase (case-uniform), or
- mixed-case matching the EIP-55-style checksum (see checksummedHex).
Mixed-case strings that do not match the checksum are rejected. This is the permissive check; use IsValidChecksumAddress to require a properly checksummed string.
func IsValidChecksumAddress ¶ added in v0.8.0
IsValidChecksumAddress is the strict check: it returns true only when addr exactly matches the canonical checksummed form produced by ToChecksumAddress. The "Q" prefix must be uppercase and the hex body must match character-for-character. All-lowercase and all-uppercase addresses that contain letters return false; digit-only hex bodies have no checksum information and return true when the rest of the format is valid.
func SigningContext ¶ added in v0.4.0
func SigningContext(d descriptor.Descriptor) []byte
SigningContext builds the domain-separated bytes that bind a signature to its descriptor:
"ZOND" || SigningContextVersion || descriptor (fixed 8 bytes)
The descriptor is embedded verbatim (type byte + reserved metadata bytes), so any change to wallet type or future metadata produces a distinct context. The version byte allows a later redesign of the context layout without colliding with the current scheme.
The layout is fixed-length, so the downstream consumers (ML-DSA-87's length-prefixed pre-string, or the SPHINCS+-256s message prefix) receive an unambiguous, canonically-encoded byte string.
func ToChecksumAddress ¶ added in v0.8.0
func ToChecksumAddress(addr [AddressSize]byte) string
ToChecksumAddress returns the EIP-55-style mixed-case checksummed string form of an address. The returned string always uses uppercase "Q".
func UnsafeGetAddress ¶
func UnsafeGetAddress(pk []byte, desc descriptor.Descriptor) [AddressSize]byte
UnsafeGetAddress builds the address bytes from a validated descriptor and public key.
Rationale: this is a fast-path used by wallet implementations that already validate the descriptor type and public key length during construction. Skipping checks avoids repeat validation on every call, but it is unsafe for untrusted inputs.
Callers MUST ensure:
- desc.IsValid() is true for the intended wallet type
- pk is the correct length for that wallet type
Types ¶
type ExtendedSeed ¶
type ExtendedSeed [ExtendedSeedSize]byte
func NewExtendedSeed ¶
func NewExtendedSeed(desc descriptor.Descriptor, seed Seed) (ExtendedSeed, error)
func NewExtendedSeedFromBytes ¶
func NewExtendedSeedFromBytes(extendedSeedBytes []byte) (ExtendedSeed, error)
func NewExtendedSeedFromHexString ¶
func NewExtendedSeedFromHexString(extendedSeedStr string) (ExtendedSeed, error)
func (ExtendedSeed) GetDescriptorBytes ¶
func (e ExtendedSeed) GetDescriptorBytes() [descriptor.DescriptorSize]byte
func (ExtendedSeed) GetSeed ¶
func (e ExtendedSeed) GetSeed() (Seed, error)
func (ExtendedSeed) GetSeedBytes ¶
func (e ExtendedSeed) GetSeedBytes() []byte
func (ExtendedSeed) ToBytes ¶
func (e ExtendedSeed) ToBytes() []byte