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
|
**The steady-state architecture is the operator-IdP-with-sidecar-bunker
|
||||||
pattern.** The bot (or any other client of the identity provider) holds
|
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.
|
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
|
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
|
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
|
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
|
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:
|
`LocalSigner` is included in the abstraction for two narrow reasons:
|
||||||
(a) it's how identity providers migrate existing plaintext-nsec rows
|
(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 user's pubkey (for display, addressing, ECDH)
|
||||||
- The bunker URL (transport: which relay channel the bunker listens on)
|
- The bunker URL (transport: which relay channel the bunker listens on)
|
||||||
- The connection token (the credential to authenticate to the bunker)
|
- The connection token (the credential to authenticate to the bunker)
|
||||||
- The granted permission scope (e.g. `sign_event:31922,31923,31925`
|
- The granted permission scope: a `(method, kind)` whitelist
|
||||||
for a community-organizer bot — no kind-1 notes, no DMs, no profile
|
(e.g. `sign_event:31922,31923,31925` for a community-organizer bot —
|
||||||
edits)
|
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
|
Per-device-scoped tokens are the security property that makes
|
||||||
multi-client identity safe. If the bot is compromised, the attacker
|
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
|
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
|
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
|
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
|
#### Identity-provider options
|
||||||
|
|
||||||
|
|
@ -804,12 +812,17 @@ Not yet decided in this draft:
|
||||||
and a state transition spec.
|
and a state transition spec.
|
||||||
- **External signer story.** Architecture is concrete in §7.2 / §7.3
|
- **External signer story.** Architecture is concrete in §7.2 / §7.3
|
||||||
/ §12 (operator-IdP with sidecar `nsecbunkerd`, per-device scoped
|
/ §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)
|
connection tokens). The signer abstraction
|
||||||
has shipped the signer abstraction; phase 2 ([#18](https://git.atitlan.io/aiolabs/lnbits/issues/18))
|
([aiolabs/lnbits#9](https://git.atitlan.io/aiolabs/lnbits/issues/9))
|
||||||
is the actual bunker integration. Pending design: ergonomics of the
|
and the bunker integration
|
||||||
chat-side binding flow (DM the bot? web callback? both?), and how
|
([aiolabs/lnbits#18](https://git.atitlan.io/aiolabs/lnbits/issues/18))
|
||||||
to handle `ClientSideOnlySigner` users whose events can't be
|
have both shipped end-to-end; `nsecbunkerd` is deployed and signs
|
||||||
bot-published at all (the bot subscribes and mirrors instead).
|
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.
|
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)
|
This is the architecture from [aiolabs/lnbits#9](https://git.atitlan.io/aiolabs/lnbits/issues/9)
|
||||||
(the IdP framing and signer abstraction) and
|
(the IdP framing and signer abstraction) and
|
||||||
[aiolabs/lnbits#18](https://git.atitlan.io/aiolabs/lnbits/issues/18)
|
[aiolabs/lnbits#18](https://git.atitlan.io/aiolabs/lnbits/issues/18)
|
||||||
(the concrete bunker integration using
|
(the concrete bunker integration). Both have shipped end-to-end: the
|
||||||
[`nsecbunkerd`](https://github.com/kind-0/nsecbunkerd)). PR #17 has
|
signer abstraction lives in `lnbits/core/signers/`, and a forked
|
||||||
shipped phase 1 (the abstraction + transitional `LocalSigner` + the
|
[`nsecbunkerd`](https://git.atitlan.io/aiolabs/nsecbunkerd) (deploys
|
||||||
classify migration); phase 2 (#18) is the actual bunker integration.
|
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"
|
#### 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
|
The pattern is reusable beyond LNbits — any operator who wants to
|
||||||
provide identity-as-a-service to their users can run this same shape
|
provide identity-as-a-service to their users can run this same shape
|
||||||
with any NIP-46-compliant bunker (`nsecbunkerd`, Pablo's reference
|
with any NIP-46-compliant bunker (`nsecbunkerd`, `nak bunker`, or any
|
||||||
implementation, or any future alternative). The Nostriga 2024 NIP-46
|
future alternative). The Nostriga 2024 NIP-46 panel describes the
|
||||||
panel describes the same architecture.
|
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
|
#### Adopters without an IdP
|
||||||
|
|
||||||
|
|
@ -914,4 +948,14 @@ the bot signing as itself and human attribution carried in the
|
||||||
|
|
||||||
## Changelog
|
## 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.
|
- **0.1** (2026-05-24) — initial draft.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue