LPsLux Proposals
Key Management
LP-70

Key Management System

Final

One interface, many backends — every key store, from software to HSM to distributed custody, satisfies the same KeyBackend contract so callers never branch on where a key lives.

Category
Core
Created
2025-01-23

Abstract

Keys live in many places — encrypted on disk, in the macOS Keychain, on a Yubikey, behind a remote signer, or split across validators. A caller that needs a signature should not care which. This LP defines the one thing they all share: the KeyBackend interface. Every store implements it; callers program against it and never branch on the backend.

This LP is the interface and nothing else. A distributed backend plugs in here; chain-level MPC topology is M-Chain (LP-7100), the on-chain key vault is K-Chain (LP-7336), and secret sync into a cluster is LP-7326 — each specified there, not braided in here.

Invariant

Every key store satisfies one interface, KeyBackend. A caller creates, loads, signs with, and locks a key through that interface alone — the backend is a value chosen at the edge, never a branch in the caller.

Why (first principles)

Different deployments need different security models: a developer wants software encryption, an enterprise wants an HSM, a high-security network wants distributed custody. If each model had its own API, every caller would carry a switch over all of them, and adding a model would mean editing every caller.

So the model is a value behind one contract, not a place each caller knows. KeyBackend names the operations a key store must answer — identity, availability, lifecycle, key operations, session, signing — and each store implements them its own way. A Yubikey reports RequiresHardware() == true; a remote signer reports SupportsRemoteSigning() == true; the caller reads those properties instead of knowing the backend by name. Picking the store is a single edge decision; everything downstream is backend-blind. Adding a new store adds one implementation and touches no caller.

Specification

KeyBackend is the whole contract (github.com/luxfi/cli/pkg/key/backend.go):

type KeyBackend interface {
    // Identity
    Type() BackendType
    Name() string

    // Availability — properties a caller reads instead of knowing the backend
    Available() bool
    RequiresPassword() bool
    RequiresHardware() bool
    SupportsRemoteSigning() bool

    // Lifecycle
    Initialize(ctx context.Context) error
    Close() error

    // Key operations
    CreateKey(ctx context.Context, name string, opts CreateKeyOptions) (*HDKeySet, error)
    LoadKey(ctx context.Context, name, password string) (*HDKeySet, error)
    SaveKey(ctx context.Context, keySet *HDKeySet, password string) error
    DeleteKey(ctx context.Context, name string) error
    ListKeys(ctx context.Context) ([]KeyInfo, error)

    // Session
    Lock(ctx context.Context, name string) error
    Unlock(ctx context.Context, name, password string) error
    IsLocked(name string) bool

    // Signing
    Sign(ctx context.Context, name string, request SignRequest) (*SignResponse, error)
}

Each backend is one implementation of it. The store, its properties, and where it is specified:

BackendTypePropertiesSpecified by
SoftwaresoftwarepasswordAES-256-GCM + Argon2id, this LP
macOS Keychainkeychainpassword (biometric)this LP
Linux Secret Servicesecret-servicepasswordthis LP
Yubikeyyubikeyhardwarethis LP
WalletConnectwalletconnectremote-signingthis LP
Environmentenvthis LP
Distributed (threshold)kchainnetwork, thresholdthis LP; chain-level MPC → LP-7100

Enforcement

The contract is one Go interface; a backend that does not implement every method does not compile as a KeyBackend (github.com/luxfi/cli/pkg/key/backend.go, types at :28-49). Reference implementations satisfy it identically — software (github.com/luxfi/cli/pkg/key/backend_software.go) and the distributed backend (backend_kchain.go) — so a caller holding a KeyBackend cannot tell them apart beyond the properties they advertise.

The distributed backend (BackendKChain = "kchain", backend_kchain.go:27) splits a key K-of-N across validator addresses with luxfi/crypto/threshold and reconstructs or threshold-signs on demand (backend_kchain.go:21-46); behind the interface it is just another Sign that needs a quorum. Chain-level MPC topology — CGGMP21, FROST, Pulsar-general ceremonies for bridge custody — is M-Chain's concern, not this CLI's, and is specified in LP-7100 (per the LP-134 naming, the MPC chain is M-Chain, formerly read as "T-Chain"/"K-Chain threshold"). The on-chain key vault is K-Chain (LP-7336). Secret material a service consumes in a cluster is synced by the KMSSecret operator (LP-7326).

Rationale

One interface is what keeps "many security models" from becoming "many APIs." The properties (RequiresHardware, SupportsRemoteSigning) let a caller adapt to a backend's needs without naming it, so a UI can prompt for a password only when RequiresPassword() is true and never special-case a store. The braided earlier draft of this LP also specified a distributed-secrets RPC, session timeouts, and threshold operations inline; those are separate concerns and now live in their own LPs (LP-7100, LP-7326, LP-7336), leaving this one to state the interface and stop.

Copyright and related rights waived via CC0.