feat(#16): boot-time autounlock of encrypted keys from a configured passphrase source #17
1 changed files with 13 additions and 0 deletions
fix(daemon): make unlockKey idempotent (#16)
`unlockKey(keyName, passphrase)` previously had no short-circuit on re-entry — calling it against an already-unlocked key would happily run through the full path: 1. decryptNsec (cheap, same result) 2. overwrite this.activeKeys[keyName] with the same nsec 3. call startKey(keyName, nsec) → spawn a SECOND Backend instance Step 3 is the actual hazard. Each Backend opens its own NIP-46 kind- 24133 subscription with the relay, scoped to the key's pubkey. Two Backends → duplicate subscription → wire events delivered twice and each handler races to publish its response. Response amplification + ordering hazards downstream, plus a slow leak of NDK subscription state every time unlock fires. This bug was latent under the manual-unlock posture today (admins rarely re-issue unlock_key for the same name in one session) but becomes load-bearing for #16's autounlock loop, which is designed to run alongside the existing startKeys() loops and may legitimately encounter a key that was already loaded via the unencrypted-config path. Belt-and-suspenders lnbits-side scripts + future periodic "re-unlock sweeps for paranoia" can also fire this. Fix: short-circuit on `this.activeKeys[keyName]` already set. Return true so callers can rely on "after this call returns, the key is unlocked and ready" regardless of whether work was done. Doesn't break the manual flow (still unlocks first-time), doesn't change the failure path (corrupt blob / wrong passphrase still throws), just closes the re-entry foot-gun. Refs aiolabs/nsecbunkerd#16 (autounlock — this is the idempotency sub-task lnbits flagged in the design surface).
commit
b6f8abdb23
|
|
@ -236,6 +236,19 @@ class Daemon {
|
|||
}
|
||||
|
||||
async unlockKey(keyName: string, passphrase: string): Promise<boolean> {
|
||||
// Idempotency guard: if a Backend instance already exists for this
|
||||
// keyName, the key is already unlocked and the relay subscription
|
||||
// for its kind-24133 channel is already active. Calling startKey
|
||||
// again would spawn a SECOND Backend with a duplicate subscription
|
||||
// — wire events would be handled twice, with race/amplification
|
||||
// hazards on the response side. Return success without re-running
|
||||
// startKey so callers (admin `unlock_key` RPC, autounlock loop,
|
||||
// belt-and-suspenders fallback paths) can fire safely against
|
||||
// already-unlocked keys.
|
||||
if (this.activeKeys[keyName]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const keyData = this.config.allKeys[keyName];
|
||||
const { iv, data } = keyData;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue