mirror of
https://github.com/PR0M3TH3AN/bitgate.git
synced 2026-10-27 08:31:00 +00:00
feat(verify): optional NIP-01 signature verification, on by default in the widget
Closes the gap the audit left open: administrative state fails closed without a verifier, and BitGate shipped none. @bitgate/verify is a separate package so core, nostr, and runtime keep no crypto dependency and can be audited without one. The widget bundles it, so the drop-in path is verified with no extra step; verify="off" disables it, sets data-unverified on the element, and emits bitgate:unverified — meant for local development, and meant to be visible. Verification recomputes the event id before checking the signature. Checking only the signature would let a forged event pass by reusing a genuine signature over an unrelated id, which is the failure mode that matters here. createVerifier also rejects events dated far in the future. A far-future created_at wins replaceable-event selection indefinitely and pins stale state in place; the signature is perfectly valid, so only a clock check catches it. Found while testing: #adminIngestAllowed checked that a verifier *exists*, not that this event was verified — so the public ingestEvent path, which "bring your own relays" integrations use, bypassed verification entirely. Verification now runs inside ingestEvent, with an already-verified flag for the loaders. An async verifier cannot be awaited there and is treated as unverified; ingestVerified() awaits properly. Conformance validated against 120 live events from a public relay across kinds 0, 1, 3 and 10002 — 59 with non-ASCII content and 71 containing quotes, newlines or tabs, which is where a serialization mismatch would surface. All 120 verified, zero id mismatches. Bundle grows 72K -> 120K. 855 tests; audit clean.
This commit is contained in:
1 parent
94ef6a72f5
commit
3b95a63058
14 files changed
+596
-12
No files matched your search
+17
-1
@@ -23,14 +23,30 @@ Administrative state **fails closed** without it. With no `verifySignature`
|
||||
configured, roles, policy, and contribution documents are rejected and counted
|
||||
in `diagnostics.rejectedUnverified`.
|
||||
|
||||
Use the optional package, or supply your own:
|
||||
|
||||
```js
|
||||
import { createVerifier } from "@bitgate/verify";
|
||||
|
||||
createBitGate({
|
||||
root: ROOT_PUBKEY,
|
||||
verifySignature: (event) => verifyEventSignature(event), // your crypto library
|
||||
verifySignature: createVerifier(),
|
||||
…
|
||||
});
|
||||
```
|
||||
|
||||
`<bitgate-provider>` does this automatically. Verification can be switched off
|
||||
with `verify="off"`, which sets `data-unverified` on the element and emits
|
||||
`bitgate:unverified` — it is meant for local development, and it is meant to be
|
||||
visible.
|
||||
|
||||
Verification runs inside `ingestEvent` itself, not only in the loaders, because
|
||||
`ingestEvent` is public API: an application feeding events from its own relay
|
||||
pool would otherwise get none. An asynchronous verifier cannot be awaited on
|
||||
that synchronous path and is treated as unverified — use `ingestVerified()` or
|
||||
the loaders, both of which await properly. Failing closed is the only safe
|
||||
reading of "I could not check this".
|
||||
|
||||
`trustUnsignedEvents: true` exists for local development and tests, where
|
||||
events are constructed in-process and there is no relay to lie. Never set it in
|
||||
production; `describe().signatureVerification` reports the posture.
|
||||
|
||||
Reference in new issue
Block a user