docs(spec): sync identity-model section with shipped nsecbunkerd state
aiolabs/lnbits #9 + #18 are no longer in-flight; aiolabs/nsecbunkerd deploys live from dev with sign-time TTL+revoke (#27), windowed usage caps (#34), hard-reject on lapsed binding (#38), and a full NDK -> nostr-tools transport port (#43/#45/#47). §7.2 now mandates sign-time lifecycle enforcement; §7.3 lists expiresAt + usage caps as part of the binding artifact; §12 swaps the upstream link for the aiolabs fork and adds the never-full-wipe nsecbunker.db operational hazard. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
d089a4b021
commit
b1c37161dd
1 changed files with 63 additions and 19 deletions
|
|
@ -463,12 +463,15 @@ per-user signing plugs in without refactor. Recommended interface
|
|||
**The steady-state architecture is the operator-IdP-with-sidecar-bunker
|
||||
pattern.** The bot (or any other client of the identity provider) holds
|
||||
zero nsec material; a separate bunker process on the same host (e.g.
|
||||
`nak bunker`) holds every user's target key and signs on RPC. The bot
|
||||
`nsecbunkerd`) holds every user's target key and signs on RPC. The bot
|
||||
authenticates to the bunker with a per-user NIP-46 connection token
|
||||
issued at binding time (§7.3), scoped to only the kinds the bot needs
|
||||
to sign. Compromise of the bot leaks the scoped tokens; revoking them
|
||||
at the bunker side is one RPC and doesn't disturb the target key or
|
||||
any other client device the user has authorized.
|
||||
any other client device the user has authorized. The bunker MUST
|
||||
enforce the token's TTL and revocation **live at sign time**, not
|
||||
only at connect time — a token that lapses mid-session stops signing
|
||||
on the next request.
|
||||
|
||||
`LocalSigner` is included in the abstraction for two narrow reasons:
|
||||
(a) it's how identity providers migrate existing plaintext-nsec rows
|
||||
|
|
@ -524,9 +527,11 @@ sign on behalf of the user. The bot stores:
|
|||
- The user's pubkey (for display, addressing, ECDH)
|
||||
- The bunker URL (transport: which relay channel the bunker listens on)
|
||||
- The connection token (the credential to authenticate to the bunker)
|
||||
- The granted permission scope (e.g. `sign_event:31922,31923,31925`
|
||||
for a community-organizer bot — no kind-1 notes, no DMs, no profile
|
||||
edits)
|
||||
- The granted permission scope: a `(method, kind)` whitelist
|
||||
(e.g. `sign_event:31922,31923,31925` for a community-organizer bot —
|
||||
no kind-1 notes, no DMs, no profile edits), an `expiresAt` TTL, and
|
||||
optionally per-rule windowed usage caps (e.g. "20 sign_events per
|
||||
hour AND 200 per day")
|
||||
|
||||
Per-device-scoped tokens are the security property that makes
|
||||
multi-client identity safe. If the bot is compromised, the attacker
|
||||
|
|
@ -534,7 +539,10 @@ can publish NIP-52 events as the user — annoying, but they can't
|
|||
publish kind-1 notes, change the user's profile, send DMs, or do
|
||||
anything else the user's other devices can. Revocation of the bot's
|
||||
token at the bunker side is one RPC and doesn't disturb the target
|
||||
key or any other authorized device.
|
||||
key or any other authorized device. Any in-scope cap (TTL, usage
|
||||
window) is enforced live at sign time, not only at connect, so a
|
||||
compromise window is bounded by both the operator's explicit revoke
|
||||
and whichever cap fires first.
|
||||
|
||||
#### Identity-provider options
|
||||
|
||||
|
|
@ -804,12 +812,17 @@ Not yet decided in this draft:
|
|||
and a state transition spec.
|
||||
- **External signer story.** Architecture is concrete in §7.2 / §7.3
|
||||
/ §12 (operator-IdP with sidecar `nsecbunkerd`, per-device scoped
|
||||
connection tokens). Phase 1 of [aiolabs/lnbits#9](https://git.atitlan.io/aiolabs/lnbits/issues/9)
|
||||
has shipped the signer abstraction; phase 2 ([#18](https://git.atitlan.io/aiolabs/lnbits/issues/18))
|
||||
is the actual bunker integration. Pending design: ergonomics of the
|
||||
chat-side binding flow (DM the bot? web callback? both?), and how
|
||||
to handle `ClientSideOnlySigner` users whose events can't be
|
||||
bot-published at all (the bot subscribes and mirrors instead).
|
||||
connection tokens). The signer abstraction
|
||||
([aiolabs/lnbits#9](https://git.atitlan.io/aiolabs/lnbits/issues/9))
|
||||
and the bunker integration
|
||||
([aiolabs/lnbits#18](https://git.atitlan.io/aiolabs/lnbits/issues/18))
|
||||
have both shipped end-to-end; `nsecbunkerd` is deployed and signs
|
||||
live for the operator/agent identities on the reference instance.
|
||||
Pending design: ergonomics of the chat-side **per-user** binding
|
||||
flow (DM the bot? web callback? both?) — the tracker plugin's
|
||||
Phase 2c — and how to handle `ClientSideOnlySigner` users whose
|
||||
events can't be bot-published at all (the bot subscribes and
|
||||
mirrors instead).
|
||||
|
||||
Contributions welcome on any of these.
|
||||
|
||||
|
|
@ -881,10 +894,22 @@ on the same host:
|
|||
This is the architecture from [aiolabs/lnbits#9](https://git.atitlan.io/aiolabs/lnbits/issues/9)
|
||||
(the IdP framing and signer abstraction) and
|
||||
[aiolabs/lnbits#18](https://git.atitlan.io/aiolabs/lnbits/issues/18)
|
||||
(the concrete bunker integration using
|
||||
[`nsecbunkerd`](https://github.com/kind-0/nsecbunkerd)). PR #17 has
|
||||
shipped phase 1 (the abstraction + transitional `LocalSigner` + the
|
||||
classify migration); phase 2 (#18) is the actual bunker integration.
|
||||
(the concrete bunker integration). Both have shipped end-to-end: the
|
||||
signer abstraction lives in `lnbits/core/signers/`, and a forked
|
||||
[`nsecbunkerd`](https://git.atitlan.io/aiolabs/nsecbunkerd) (deploys
|
||||
from the `dev` branch — `master` is parked at upstream) runs as the
|
||||
sidecar. The fork enforces token TTL + revoke live at sign time
|
||||
([#27](https://git.atitlan.io/aiolabs/nsecbunkerd/pulls/27)),
|
||||
per-rule windowed usage caps
|
||||
([#34](https://git.atitlan.io/aiolabs/nsecbunkerd/pulls/34)),
|
||||
hard-rejects (rather than re-prompts) a lapsed binding
|
||||
([#38](https://git.atitlan.io/aiolabs/nsecbunkerd/pulls/38)), and is
|
||||
fully off NDK after the `nostr-tools` relay-pool port
|
||||
([#43](https://git.atitlan.io/aiolabs/nsecbunkerd/pulls/43) /
|
||||
[#45](https://git.atitlan.io/aiolabs/nsecbunkerd/pulls/45) /
|
||||
[#47](https://git.atitlan.io/aiolabs/nsecbunkerd/pulls/47)) — relay
|
||||
subscriptions survive reconnect, and cross-relay event dedup keeps
|
||||
usage caps from over-counting.
|
||||
|
||||
#### Why a sidecar bunker, not "encrypt at rest in the IdP"
|
||||
|
||||
|
|
@ -898,9 +923,18 @@ gets its own audit log, independent of the IdP's.
|
|||
|
||||
The pattern is reusable beyond LNbits — any operator who wants to
|
||||
provide identity-as-a-service to their users can run this same shape
|
||||
with any NIP-46-compliant bunker (`nsecbunkerd`, Pablo's reference
|
||||
implementation, or any future alternative). The Nostriga 2024 NIP-46
|
||||
panel describes the same architecture.
|
||||
with any NIP-46-compliant bunker (`nsecbunkerd`, `nak bunker`, or any
|
||||
future alternative). The Nostriga 2024 NIP-46 panel describes the
|
||||
same architecture.
|
||||
|
||||
> **Operational note for adopters using `nsecbunkerd`.** Pairing
|
||||
> state is split between the bunker's `nsecbunker.db` (per-account
|
||||
> `KeyUser` binding + redeemed `Token`) and the IdP's signer config
|
||||
> (`{token, client_nsec, policy_id}`). **Never full-wipe
|
||||
> `nsecbunker.db`** to strip stale ACL rows — full wipe orphans the
|
||||
> IdP's per-user signer config and the only auto-repair re-mints
|
||||
> identities. Strip with a targeted `DELETE FROM SigningCondition`.
|
||||
> See the bunker's `docs/runbook-migrations.md`.
|
||||
|
||||
#### Adopters without an IdP
|
||||
|
||||
|
|
@ -914,4 +948,14 @@ the bot signing as itself and human attribution carried in the
|
|||
|
||||
## Changelog
|
||||
|
||||
- **0.2** (2026-06-28) — reflect shipped state of the reference IdP +
|
||||
sidecar bunker. `aiolabs/lnbits#9` and `#18` are no longer
|
||||
in-flight; `aiolabs/nsecbunkerd` (deploys from `dev`) is live with
|
||||
sign-time enforcement of token TTL + revoke (#27), per-rule
|
||||
windowed usage caps (#34), hard-reject on lapsed binding (#38),
|
||||
and a full NDK→`nostr-tools` transport port (#43/#45/#47). §7.2
|
||||
now mandates sign-time lifecycle enforcement; §7.3 lists `expiresAt`
|
||||
+ usage caps as part of the binding artifact; §12 adds the
|
||||
full-wipe operational hazard. Open question on "external signer
|
||||
story" trimmed to per-user binding ergonomics (tracker Phase 2c).
|
||||
- **0.1** (2026-05-24) — initial draft.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue