The DIG session / keystore layer: a small, custody-safe facade that turns stored, encrypted key material into a live signer and injects a bare signing primitive into downstream consumers.
It composes two lower-level crates and adds no cryptography of its own:
dig-keystore— encrypted, per-scheme secret-key storage (Keystore::<K>::load -> unlock -> SignerHandle).dig-identity— the canonical DIG BLS identity derivation (m/12381'/8444'/9'/0').
[dependencies]
dig-session = "0.1"use std::sync::Arc;
use dig_session::{Session, FileBackend, BackendKey, Password};
# fn main() -> dig_session::Result<()> {
let backend = Arc::new(FileBackend::new("/var/lib/dig/keys"));
// Enroll a new identity from BIP-39 seed bytes (derives the canonical
// dig-identity signing key and stores it encrypted).
let identity = Session::enroll_identity(
backend.clone(),
BackendKey::new("identity"),
Password::from("correct horse battery staple"),
b"seed bytes",
)?;
// Sign directly...
let _sig = identity.sign(b"message");
// ...or hand a downstream a bare signing primitive — it never sees a session type.
let sign = identity.signing_fn();
let _sig = sign(b"message");
// Reopen later.
let identity = Session::unlock::<dig_session::L1WalletBls>(
backend,
BackendKey::new("identity"),
Password::from("correct horse battery staple"),
)?;
# let _ = identity;
# Ok(())
# }An account's root is 32 bytes of BIP-39 entropy. Every derivation — identity,
per-profile DEK, wallet — reads the seed EXPANDED from it
(entropy -> 24 words -> to_seed("")), which is the standard Chia derivation, so
a DIG recovery phrase restores the same addresses in Sage and any other
conforming wallet.
use std::sync::Arc;
use dig_session::{Session, FileBackend, BackendKey, Password, ENTROPY_LEN};
# fn main() -> dig_session::Result<()> {
let backend = Arc::new(FileBackend::new("/var/lib/dig/keys"));
let path = BackendKey::new("account");
let password = Password::from("correct horse battery staple");
// Create: any 32 CSPRNG bytes are valid BIP-39 entropy.
let entropy = [0u8; ENTROPY_LEN]; // in production: fill from `OsRng`
let account = Session::enroll_master_seed(backend.clone(), path.clone(), password, &entropy)?;
// Show the user their 24 words — this does NOT consume the handle.
let phrase = account.recovery_phrase();
// The expanded 64-byte seed goes to wallet-backend's `MasterKey::from_seed_bytes`.
let seed = account.master_seed();
// Restore on another machine.
let restored = Session::enroll_from_recovery_phrase(
backend,
BackendKey::new("account-2"),
Password::from("correct horse battery staple"),
&phrase,
)?;
# let _ = (seed, restored);
# Ok(())
# }A blob written before the versioned seed envelope held a raw seed rather than entropy. Because the
two are byte-indistinguishable, unlock_master_seed fails closed with
SessionError::LegacySeedFormat instead of reinterpreting them — silently deriving a plausible wrong
wallet would be far worse than an error.
Those blobs exist in the field: the published 0.4 line auto-enrolled an account at first boot with
no user action. A legacy account is wedged — unlock refuses, and re-enrolling at the same key
returns AlreadyExists because enrolment never overwrites a custody root. So adopting 0.5 requires
a legacy-detection-and-re-enrolment path in your app: detect the variant specifically, preserve
(never delete) the old sealed blob — it may hold value and its password may live in an OS credential
store — tell the user in the UI, then re-enrol and show the new phrase. Merely logging the error
leaves the account permanently and silently without a signer. See the LegacySeedFormat rustdoc.
- No seal / decap. Recipient message encryption belongs to
dig-message, not here. - Identity keys are stored with
L1WalletBls, notBlsSigning. The identity key is already derived by dig-identity; storage must round-trip it viafrom_bytes.BlsSigningwould re-derive viafrom_seedand yield a different key (the dig_ecosystem #64/#57 pitfall). - Custody-safe.
UnlockedIdentityzeroizes its secret on drop, neverDebug-prints key material, and must never cross an IPC boundary.
See SPEC.md for the normative contract.
GPL-2.0-only.