Goal
Key custody: a Keeper trait + keripy/signify-compatible derivation paths, so Secure Enclave / Android Keystore / HSM / encrypted-file backends plug in ABOVE cesr while inception/rotation/signing logic stays pure and portable.
References: keripy app/keeping.py (Keeper/Manager), core/signing.py L331–484 (Salter); signify-ts core/keeping.ts (the 4-algo IdentifierManager family: Salty/Randy/Group/Extern) + core/manager.ts L298–305 (path construction).
What goes IN cesr (pure)
- Derivation paths, keripy/signify-compatible:
path = "{pidx:x}.{ridx:x}.{kidx:x}" (keripy) / stem-prefixed "signify:aid" + ridx + kidx (signify) → Salter::stretch(path, tier) → seed → Signer. Both stem conventions supported; differential-tested against both implementations so a cesr client can interop with existing wallets.
- The trait (modeled on signify's split, which cleanly hides Salty vs Randy vs hardware):
pub trait Keeper {
fn incept(&mut self, transferable: bool, count: usize, ncount: usize) -> Result<(Vec<Verfer>, Vec<Diger>), Self::Error>;
fn rotate(&mut self, count: usize, ncount: usize) -> Result<(Vec<Verfer>, Vec<Diger>), Self::Error>;
fn sign(&self, ser: &[u8], indices: Option<&[u32]>) -> Result<Vec<Siger>, Self::Error>;
fn params(&self) -> KeeperParams; // serializable custody state (sxlt/prxs-style), NEVER raw keys
type Error;
}
What stays OUT (bombay M4 custody card)
Platform backends (Secure Enclave/Keychain/Android Keystore/HSM = ExternKeeper impls), passcode UX, encrypted keystore files, group-multisig member coordination (the pure index math can live here; the ceremony can't).
Design note from the signify teardown
signify's model ships encrypted key state to the cloud agent (sxlt in keeper.params()). Our self-sovereign default is the opposite: params() exists for the caller to persist LOCALLY; document loudly that transmitting it is the key-light trade-off, not the default.
Acceptance
From the 2026-07-05 signify-ts + keripy teardowns. Depends on: #84 (x25519 wrap) for RandyKeeper. Enables: bombay M4 custody backends, lite-bombay phone keys.
Goal
Key custody: a
Keepertrait + keripy/signify-compatible derivation paths, so Secure Enclave / Android Keystore / HSM / encrypted-file backends plug in ABOVE cesr while inception/rotation/signing logic stays pure and portable.References: keripy
app/keeping.py(Keeper/Manager),core/signing.pyL331–484 (Salter); signify-tscore/keeping.ts(the 4-algo IdentifierManager family: Salty/Randy/Group/Extern) +core/manager.tsL298–305 (path construction).What goes IN cesr (pure)
path = "{pidx:x}.{ridx:x}.{kidx:x}"(keripy) / stem-prefixed"signify:aid" + ridx + kidx(signify) →Salter::stretch(path, tier)→ seed →Signer. Both stem conventions supported; differential-tested against both implementations so a cesr client can interop with existing wallets.SaltyKeeper(deterministic, argon2id-stretched — the phone/IoT default) andRandyKeeper(random keys, x25519-wrapped via [freeze] X25519 key exchange + sealed-box encryption (KeyPair API shape is cheaper to settle pre-1.0) #84). Private material inZeroizingper house convention.What stays OUT (bombay M4 custody card)
Platform backends (Secure Enclave/Keychain/Android Keystore/HSM =
ExternKeeperimpls), passcode UX, encrypted keystore files, group-multisig member coordination (the pure index math can live here; the ceremony can't).Design note from the signify teardown
signify's model ships encrypted key state to the cloud agent (sxlt in
keeper.params()). Our self-sovereign default is the opposite:params()exists for the caller to persist LOCALLY; document loudly that transmitting it is the key-light trade-off, not the default.Acceptance
Keeperobject-safe; SaltyKeeper drives a full icp→rot chain in tests with zero heap key exposure (miri-checked drop/zeroize)From the 2026-07-05 signify-ts + keripy teardowns. Depends on: #84 (x25519 wrap) for RandyKeeper. Enables: bombay M4 custody backends, lite-bombay phone keys.