DAO V1.1 Web5 Identity Layer: Community-Led Review

Hey @david-fi5box, thanks for the detailed response. I went back to the repos and checked their current state against what you described.

Dev responses correctly describe the broader Web5 ecosystem (keystore, key rotation, data export), but the live CCFDAO path still uses localStorage for signing keys, plaintext QR backup, manual credential migration, and operator-run indexers. The modules keystore has critical implementation flaws. Additional issues include SQL injection in the relayer and DID document format divergence across repos. None are fixed in current HEADs.

Sections marked NOSTR alternative: show how existing NOSTR NIPs (Nostr Improvement Proposals) address the same problems the dev cited as hard to decentralize. These are not speculative: NIP-46, NIP-49, and did:nostr are all existing code. Where a CKB-native approach exists (e.g. the CKB light client for indexer-free DID resolution), it is noted separately.


Dev responses vs current code

Key storage

The modules project has a separate keystore app storing keys in the browser. Host apps use a client API to get DID keys and sign messages.

But the CCFDAO UI stores {did, walletAddress, signKey} in localStorage and reads the signing key back for every signature. The broader Web5 framework has a keystore; the CCFDAO path has not adopted it. Additionally, the keystore has critical implementation flaws (see “New security and reliability findings” below).

Verdict: Correct for the broader framework, not for the CCFDAO path. Signing keys remain in localStorage without encryption.

NOSTR alternative: NIP-46 (Nostr Remote Signing) eliminates browser key storage entirely: the client never touches the private key.

Backup

The dev did not address backup specifically in the forum response. The code shows:

Verdict: File export is encrypted with AES-GCM but QR export remains plaintext JSON with unenforced expiry.

NOSTR alternative: NIP-49 (Private Key Encryption) provides standardized encrypted key export, making the QR-vs-file distinction irrelevant.

Key rotation

The modules project implements key rotation. The portal wires it into sign-key updates. The console wires it into key management.

Verdict: Significant correction, but applies to the broader framework, not the CCFDAO path. A compromised or lost CCFDAO signing key cannot currently be replaced; the DID must be abandoned.

NOSTR alternative: NOSTR identity (raw secp256k1 pubkey) has no built-in rotation; the identity owner must publish a new pubkey and re-establish reputation.

CKB-native alternative: The did-ckb contract can serve as an on-chain rotation layer: it validates PLC rotation histories on-chain, so even if the signing key is stolen, rotation keys replace it.

DID and governance: operator-run indexers

  • Login performs DID-document lookup through the operator’s DID_INDEXER (setLoginUserPDSClient).
  • Voter-list construction calls the operator’s did_set endpoint (build_voter_list).
  • The public web5-indexer exposes only five endpoints (route setup), not the full DID pipeline.

The broader modules DID tooling can query live cells directly. The CCFDAO path still depends on the operator’s DID indexer for login and voter-list construction.

Verdict: Correct for broader tooling, not for the CCFDAO path. Login and voter-list construction depend on the operator’s DID indexer.

CKB-native alternative: The CKB light client compiles to WASM and runs in the browser, syncing headers via MMR (Merkle Mountain Range) proofs and filtering blocks by script; it can track DID cells directly without any indexer.

Data export/import

Verdict: Broader stack has CAR export/import; CCFDAO has none.

NOSTR alternative: NOSTR events are inherently portable across relays; no CAR format or export/import tooling needed.

Same-DID login

Login performs DID-document lookup through the operator’s DID_INDEXER and switches PDS client based on services.atproto_pds.endpoint, signing the challenge with the locally stored signing key.

Verdict: Same-DID login works across PDS endpoints; credentials still require manual transfer (see “Key storage” above). The dev acknowledges this follows from the self-custody design: “cross-device usage (desktop registration → mobile login) still requires manual credential migration, a consequence of the initial choice discussed in ‘Key Storage,’ with no perfect solution.”

NOSTR alternative: With NOSTR, identity is the keypair; no server-side credentials to transfer, cross-device access via NIP-46 remote signer.

Interoperability: limited in app and indexer stack

  • The web5-indexer rejects non-AT Protocol DID documents unless alsoKnownAs[0] starts with at:// and verificationMethods.atproto is present.
  • The CCFDAO stack ships custom lexicons under fans.web5.ckb.* for account creation and indexing.
  • The lower-level did-ckb operation format is more generic.

Verdict: Warning holds: the web5-indexer rejects non-AT Protocol DID documents and CCFDAO ships custom lexicons incompatible with upstream. The dev confirms AT Protocol compatibility was not a goal.

NOSTR alternative: NOSTR events are portable across relays by design; custom event kinds replace custom lexicons, specified in a NIP rather than platform-specific files.

Independent operation: dev confirms “verify but not override”

The dev is responding to the original review’s Section 4 finding that “an independent clone can verify but not override.” The dev confirms the limitation: anomalies require human intervention, and reframes it as a design position: verification provides deterrence, not full decentralization.

The original review’s observation stands: “The infrastructure for this verification exists in the source code; what is missing is anyone running it.” Verifiability only deters if someone actually verifies. If no one runs the independent verifier, the deterrence claim is unfalsifiable.

Still, the dev’s position does not rule out moving tallying on-chain. The remaining question is whether “sufficient by design” was a deliberate tradeoff or a temporary acceptance of a gap to close.

Verdict: Dev confirms the “verify but not override” limitation and reframes it as sufficient by design, not as a gap to close.

CKB-native alternative: Approaches exist (open-vote mode without a voter whitelist, incremental tally with a public accumulator, ZK-compressed tally) that could shift the boundary from “verify but not override” toward “verify and enforce on-chain.”


New security and reliability findings

Reliability issues in CCFDAO v1.1

  • deleteErrUser is called automatically when preIndexAction returns CkbDidocCellNotFound. A network timeout or indexer lag triggers permanent account deletion with no confirmation.
  • build_voter_list failures from did_set are swallowed by the cron caller with .ok(). No backoff, no alerting. Extended indexer downtime prevents all vote initiation.

Modules keystore: critical flaws

The keystore is architecturally sound (separate origin, separate tab) but has implementation flaws that undermine its security model:

  1. Origin validation bypass. The keystore checks request.origin, a field inside the message payload that the sender chooses, instead of event.origin, which the browser guarantees. Any page can spoof a whitelisted origin and get arbitrary messages signed; the response is also sent to the attacker-controlled request.origin.
  2. No message authentication. Messages are plain JSON with a source string tag. No HMAC, no envelope signature, no shared secret. Any page that can observe postMessage traffic can forge requests or responses.
  3. Client does not verify response origin. KeystoreClient.ts checks data.source === 'keystore-auth' but never verifies event.origin came from the keystore. Any open tab can forge responses.
  4. Plaintext key storage persists. Keys are stored in localStorage without encryption despite encryptData/decryptData being available. The backup is encrypted; the live working copy is not.
  5. No user confirmation for signing. Whitelisted origins get signatures immediately with no prompt. A compromised whitelisted page signs arbitrary data silently.
  6. ready broadcast to '*' origin. The keystore announces readiness to any opener, leaking that the keystore tab is open.
  7. Localhost in default whitelist. http://localhost:3000/3004/3001 are hardcoded and cannot be removed via the UI.

Issues 1-3 together mean any page in the browser can impersonate a whitelisted origin, request signatures, and receive them. The postMessage boundary (the keystore’s primary security improvement over direct localStorage access) is bypassed. A compromised third-party script in any co-browsing tab can exploit the same path.

NOSTR alternative: NIP-46 directly addresses all seven weaknesses: messages are NIP-44 encrypted, connection secrets provide single-use auth, and permission scoping limits what a client can request.

SQL injection and voter-list integrity

  • All four relayer DELETE handlers use string-interpolated URIs: format!("'{uri}'") with no parameterization. URI values come from the relayer WebSocket (user-controlled AT Protocol data), creating a SQL injection vector across profile, proposal, reply, and like tables. A DID containing a single quote produces a malformed statement that PostgreSQL rejects or interprets unpredictably.
  • build_vote_meta fetches the voter list from PostgreSQL, rebuilds the SMT (Sparse Merkle Tree), and commits the root hash on-chain. There is no integrity check between the stored list and any independent commitment. If the DB row is tampered, the tampered list becomes canonical.

NOSTR alternative: NOSTR has no relayer; events are signed by the user and published to relays directly, eliminating string-interpolated DELETE queries over user-controlled data.


Where it stands now: promise by promise

The original review evaluated six promises from the V1.1 proposal. The dev responses and modules framework alter some assessments. Each is re-evaluated against current code and the new findings above.

User sovereignty

Original review: The DID signing key and on-chain cell are user-owned, but DID resolution, voter-list construction, and proof issuance depend on the operator.

Current state: Partially resolved in the modules framework: fetchDidCkbCellsInfo in did/src/logic.ts queries live cells via the signer client, bypassing the indexer. But CCFDAO’s login path still calls the operator’s DID_INDEXER for every authentication, and voter-list construction still calls the operator’s did_set endpoint. The sovereignty gap is narrower in the broader framework, unchanged in CCFDAO.

NOSTR alternative: With NOSTR, the keypair is the identity; no operator’s DID indexer, resolver, or PDS is needed.

Data portability

Original review: Not implemented. The DID document’s PDS endpoint is frozen at creation. Multi-device sync is not implemented.

Current state: CAR export/import exists in the rsky fork and modules console. Portal supports export only. CCFDAO has no data export. The dev acknowledges CCFDAO migration is “still being planned.” Cross-device sync remains unimplemented: the signing key exists in one browser’s localStorage (CCFDAO) or one browser’s keystore tab (modules).

NOSTR alternative: NOSTR events are inherently portable across relays; data moves by connecting to different relays.

Sovereign data

Original review: Votes are on-chain cells (sovereign). Proposals, comments, and profiles are AT Protocol records on the operator’s PDS (not sovereign). The proposal describes “all generated governance data” as sovereign; the implementation makes a subset sovereign.

Current state: The dev’s framing (sovereignty means critical data on-chain, not all data on-chain) is narrower than the proposal’s “all generated governance data.” Votes remain sovereign. Proposals and comments remain on the operator’s PDS. The web5-indexer’s limited endpoints mean even on-chain DID data requires the operator’s service for standard lookups.

NOSTR alternative: NOSTR events are stored on relays the user chooses, not the operator’s PDS; custom event kinds (proposals, comments) are signed by the user and stored on any relay.

Ecosystem reuse

Original review: The source code is open and did:ckb is chain-native, but three gaps remain: no DID document updates, AT Protocol-specific DID document format limits non-AT Protocol adoption, and 8 custom lexicons are platform-specific.

Current state: The modules framework adds DID document updates via transferDidCkb (key rotation, handle updates, PDS endpoint changes). This is a significant correction to the original review’s finding. However, the DID document format issue remains: all repos use a flat string map ({ verificationMethods: { atproto: "did:key:..." } }), while the upstream rsky identity module expects W3C format (Vec<VerificationMethod>). The web5-indexer still rejects documents without at:// in alsoKnownAs and an atproto verification method. For a non-AT Protocol application to adopt did:ckb, these constraints would need to be lifted.

NOSTR alternative: did:nostr is a W3C Community Group draft, not a platform-specific format; custom event kinds replace custom lexicons, specified in a NIP.

Cross-app identity

Original review: The signing key lives in one browser’s localStorage with no cross-app sharing mechanism. The handle system is DNS-bound to the operator. Same-DID login works within the same platform but not across independent platforms.

Current state: Same-DID login confirmed working across PDS endpoints within the same operator. The keystore could provide cross-app key sharing, but CCFDAO has not adopted it, and the keystore has critical implementation flaws (see above). The handle system (*.web5.ccfdao.org, *.web5.bbsfans.dev) remains DNS-bound to the operator. Cross-platform identity (a user of an independent platform logging into CCFDAO) is not demonstrated.

NOSTR alternative: With NOSTR, identity is the keypair; NIP-05 handles are domain-portable (any DNS name the user controls), NIP-46 remote signing handles cross-device.

Total decentralization

Original review: The on-chain components are decentralized; the governance workflow built on top is not. Operator controls voter lists, proof issuance, and vote tallying.

Current state: The dev confirms the original finding. The position is that verification (recomputing SMT roots, tallying on-chain votes) provides sufficient decentralization for the current use case, not that full decentralization has been achieved. The original review’s finding that “the on-chain contract never checks the VoteMeta cell’s lock script, so the operator’s exclusive control over voter eligibility is application-layer, not contract-enforced” stands.

CKB-native alternative: If vote tallying moved on-chain (open vote mode, no whitelist), the operator would lose control over voter eligibility and vote counting.


NOSTR as a decentralized alternative

CCFDAO need AT Protocol approach (current) NOSTR equivalent
Identity/DID did:ckb + custom DID indexer + PDS did:nostr (W3C Community Group draft, secp256k1, offline-first resolution)
Remote keystore Modules keystore (postMessage, custom protocol) NIP-46 (Nostr Remote Signing, NIP-44 encrypted, permission-scoped)
Key backup Modules encryptData/decryptData (AES-GCM) NIP-49 (Private Key Encryption)
Structured data Custom lexicons (fans.web5.ckb.*) + PDS repos Custom NIPs (event kinds with validated content schemas)
Weighted voting Custom lexicons + operator-run indexer Custom event kind for votes + CKB light client for trustless on-chain verification (WASM, script-level filtering)
Groups Not implemented NIP-29 (Relay-based Groups)
Data export/import CAR export/import via PDS NOSTR events are inherently portable across relays

None of the NOSTR-based approaches described in this document are implemented. The table above shows what exists today in each ecosystem and where the CCFDAO path stands relative to both.

Love & Peace, Phroi

3 Likes