Key Management System
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.
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:
| Backend | Type | Properties | Specified by |
|---|---|---|---|
| Software | software | password | AES-256-GCM + Argon2id, this LP |
| macOS Keychain | keychain | password (biometric) | this LP |
| Linux Secret Service | secret-service | password | this LP |
| Yubikey | yubikey | hardware | this LP |
| WalletConnect | walletconnect | remote-signing | this LP |
| Environment | env | — | this LP |
| Distributed (threshold) | kchain | network, threshold | this 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
Copyright and related rights waived via CC0.