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

I wish, this DAO thing could also be used as the organization of the first testers and Users of the applications being built on Nervos CKB Ecosystem. This will enable developers to get initial users and testers who are committed to providing feedback on their usage of applications. We dont expect the apps to be adopted from outside inside, but better inside outside.
@BaClaire, Nervos Nation Telegram, March 22, 2026

The V1.1 proposal envisioned exactly this: a shared identity layer for the Nervos ecosystem, not just a governance tool.

This review examines what the identity system is, what was promised, and where it stands.

DAO V1.1 replaces Metaforo’s governance accounts with Web5 DID accounts: a custom identity layer built on AT Protocol. Metaforo already supports email and wallet login, so the change is not from email to wallets but from Metaforo-managed identity to a platform-managed did:ckb identity. Every governance action (proposing, voting, commenting) requires a DID.

1. How It Works

AT Protocol Fork

The platform runs a fork of rsky, a Rust AT Protocol implementation (the same protocol behind Bluesky). Each account gets a handle (e.g. alice.web5.ccfdao.org; Network.ts:20: USER_DOMAIN = 'web5.ccfdao.org') and a profile stored on the platform’s PDS (Personal Data Server).

Standard AT Protocol uses did:plc or did:web for identity; this fork uses did:ckb (Network.ts:22), a custom DID method resolved exclusively by the platform’s own indexer (DID_INDEXER = https://did-indexer.bbs.fans). Combined with 8 custom lexicons under fans.web5.ckb.* (web5-api/lexicons), these accounts cannot interoperate with Bluesky or any other AT Protocol service.

Identity and Funds

The DID itself is an on-chain cell (~455 CKB capacity, locked by the user’s wallet key; createAccount.ts), but the DID signing key does not control CKB funds (those stay under the wallet key, e.g. Neuron, JoyID, or any CCC-supported wallet). Your handle, proposals, and comments are all linked to the DID (AT Protocol records use the DID as the repo identifier). The signing key is what grants access to the DID; losing it means losing that governance identity, not your coins.

Votes are on-chain cells, but profiles, proposals, and comments are AT Protocol records on the PDS, while the app_view backend’s PostgreSQL mirrors proposal content, comments, and profiles from the PDS and adds governance state (voter lists, vote results, admin roles), workflow management (meetings, tasks), and activity timelines.

Key Storage

The DID signing key (secp256k1) is stored unencrypted in browser localStorage (storage.ts:56: setToken writes {did, signKey, walletAddress} as plaintext JSON under @dao:client). The browser cannot protect it: no save-password prompt, no cross-device sync. Functionally a session cookie, except it is your entire governance identity. Clearing browser data destroys it. This can happen without user action: Safari’s Intelligent Tracking Prevention deletes localStorage after 7 days without a site visit, and Chromium updates have occasionally wiped it. On a governance platform where votes happen periodically, a week away is enough to lose the key.

Backup is optional and not prompted at account creation: the file export (ExportDIDInfoModal.tsx) encrypts with AES-GCM (8-character password), but the QR export (KeyQRCodeModal.tsx) prompts for a 4-digit PIN that is included in the plaintext JSON alongside the signing key rather than used for encryption (source comments acknowledge this should be encrypted).

Key rotation is not implemented. The scaffolding exists at multiple levels: the on-chain DID cell uses TYPE_ID (allows cell consumption and recreation), Molecule schemas for PlcAuthorization witnesses exist in the frontend (molecules.ts:84-153), and CCC’s unmerged feat/did-ckb branch implements a full transferDidCkb function that consumes and recreates a DID cell with new data (key rotation, PDS endpoint change). But none of it is wired up: no UI, API, or released SDK exposes this. A compromised or lost key cannot be currently replaced; the DID must be abandoned and a new one created, losing the associated handle, proposals, and history.

Authorization Model

Posting and commenting are signed locally with the stored signing key (AT Protocol session), no wallet popup. Voting requires two steps: the frontend obtains a Sparse Merkle Tree (SMT) membership proof from the backend (/api/vote/prepare), then builds and submits an on-chain CKB transaction that requires wallet approval (votingUtils.ts:563: signer.signTransaction(tx)).

2. What Was Promised

The V1.1 proposal and AMAs describe the identity layer broadly:

User sovereignty.

Data portability.

The proposal’s budget table also lists “multi-device sync” as a feature of the registration/login module.

Sovereign data.

Ecosystem reuse.

Reiterated:

Cross-app identity.

Total decentralization.

3. Where It Stands

User sovereignty. The DID signing key is in the user’s browser. The on-chain DID cell is locked by the user’s wallet key. These are the sovereign components. But DID resolution depends on the platform operator’s indexer, the voter list is built by the operator’s backend, proof issuance is controlled by the operator, and profile data lives on the operator’s PDS. The user owns a key and an on-chain anchor; the operator controls everything those connect to.

Data portability. Not implemented. The DID document’s PDS endpoint is frozen at creation (currently no functional update mechanism, as described in Key Storage above). Users cannot point their DID to a different server. The rsky fork includes relay and firehose components that could enable cross-instance sync, but these are not exposed. Multi-device sync, listed in the proposal’s budget table, is not implemented: the signing key exists in one browser’s localStorage.

Sovereign data. Votes are on-chain cells: sovereign, independently verifiable. Proposals, comments, and profiles are AT Protocol records on the operator’s PDS: not on-chain, not sovereign, mirrored in the backend’s database but recoverable only by the operator. The proposal describes “all generated governance data” as sovereign; the implementation makes a subset sovereign.

Ecosystem reuse. The source code is open, and the did:ckb DID method is chain-native: any service that reads CKB cells can resolve a DID. This is the reusable primitive. But for another platform to adopt it, three gaps remain:

  1. No DID document updates: a DID cannot register additional services, so each platform would need its own DID per user.
  2. The DID document uses AT Protocol’s did:plc operation format (verificationMethods.atproto, services.atproto_pds) rather than W3C DID Core, and the indexer rejects documents without AT Protocol-specific fields (check_did_doc requires at:// in alsoKnownAs and an atproto verification method), limiting adoption by non-AT Protocol applications.
  3. The 8 custom lexicons (CKB-anchored account creation, repo writes, and session management via two-phase signing, plus queries and blob upload) are custom to this platform and unsupported by other AT Protocol services.

Cross-app identity. The DID is resolvable by anyone running the indexer. A second application could verify a user’s did:ckb signature. But the signing key lives in one browser’s localStorage with no cross-app sharing mechanism, the handle system (*.web5.ccfdao.org) is DNS-bound to the operator, and without DID document updates a user cannot register a second application’s endpoint. Cross-PDS login was mentioned in Dev Log #1 (“Integrate web5did-indexer for cross-PDS login, enabling BBS users to log into the DAO platform”), indicating awareness of the gap. The current implementation supports login across PDS instances within the same platform (user-account.ts: setLoginUserPDSClient switches the PDS client based on the user’s DID document), but not across independent platforms.

Total decentralization. The code review documented centralized control over voter lists, proof issuance, and vote tallying. The identity layer adds operator dependency for DID resolution and profile storage. The on-chain components (DID cells, vote cells) are decentralized; the governance workflow built on top of them is not.

4. Independent Operation

All source code is public: the DID on-chain contract (web5fans/did-ckb, MIT, registered in CCC as KnownScript.DidCkb), the PDS (web5fans/rsky fork), the DID and vote indexer (web5fans/web5-indexer, MIT), the bind indexer and vote CLI tools (CCF-DAO1-1/web5-components), and the backend (CCF-DAO1-1/app_view). The full stack is the latter 4 as services, each with its own PostgreSQL, plus a CKB node and S3 storage.

On-chain data (DIDs, votes, address bindings) is independently reconstructable from any CKB node. Off-chain data (profiles, proposals, comments) is not: the fork retains AT Protocol’s relay and firehose infrastructure, which could replicate PDS records to an independent instance, but the operator controls whether these endpoints are exposed, and account migration remains blocked by the frozen DID document.

An independent clone can verify but not override: recomputing the voter list’s SMT root hash and comparing it against the on-chain VoteMeta cell (the cell that configures each vote session, including the eligible voter set) would expose any discrepancy in who was included or excluded. Recomputing the vote tally from on-chain vote cells would expose any divergence from the operator’s reported results. But the on-chain contract enforces the SMT root hash from the VoteMeta cell. The contract never checks the VoteMeta cell’s lock script, so the operator’s exclusive control over voter eligibility is application-layer, not contract-enforced. An independent stack can prove misbehavior but cannot let an excluded voter cast a vote. The infrastructure for this verification exists in the source code; what is missing is anyone running it.

did:ckb is a chain-native identity primitive with the right foundation for ecosystem-wide use. The source code is open, the DID is on-chain, and the indexer is independently runnable. What stands between the current state and BaClaire’s vision are DID document updates (so a single identity can span multiple apps), account portability (so users are not locked to one server), and key management beyond browser localStorage.

5 Likes

Great to see people outside the team finally discussing Web5.


Let me walk through Web5’s history and its relationship with AT Protocol.

Web5 was originally proposed by former Twitter CEO Jack Dorsey to address financialization, industrial hollowing, and inefficiency in Web3, as well as over-centralization, privacy, and data ownership issues in Web2. It advocated for an organic integration of Web2 and Web3 to solve both sets of problems simultaneously.

The real-world implementation of Web5’s vision is Bluesky and its underlying protocol—AT Protocol. For unknown reasons, however, Bluesky never integrated with blockchain and instead pursued a loosely federated approach.

In early 2025, some CKB ecosystem members found Web5’s philosophy highly aligned with CKB’s principles (layered design, only putting essential data on-chain, etc.). They decided to borrow from AT Protocol to build a CKB application development framework.

Since it’s CKB ecosystem-led, DID was naturally designed to reside on the CKB chain. Thus, the Web5 we refer to here inherits Jack Dorsey’s original vision and adopts certain AT Protocol designs (PDS, app view, lexicon, etc.), but is neither compatible nor aligned in direction. In fact, Bluesky clearly leans toward Web2—perhaps “Web2.3”?—while CKB’s approach clearly leans toward Web3, maybe “Web2.7”?

The most significant difference: in AT Protocol, users’ sign keys are custodied in PDS. This is unacceptable in the “CKB Application Development Framework,” so we’ve restructured PDS for users to self-custody sign keys—more consistent with Web3 user habits.


Key Storage

As mentioned, users must self-custody sign keys.

We initially considered reusing existing CKB wallet infrastructure to co-manage sign keys. After discussions with multiple wallet developers, however, they argued that asset-holding private keys and post/like-signing keys have different security requirements. Wallets are designed for asset storage; introducing lower-security key support would increase asset loss risk. They advised against combining them. Additionally, since every Web5 app action requires sign key signatures, confirming each time would severely degrade UX. The final solution: plaintext storage in browser local storage.

In the Web5 Application Framework Modules ( GitHub - web5fans/modules · GitHub ), we developed a dedicated keystore—a lower-security-grade web wallet that even apps cannot access sign keys through.

Key rotation is implemented. The DID cell, controlled by the user’s wallet, holds the sign key’s public key (did:key format). Updating this did:key in the DID cell enables rotation. Both console (console.web5.fans) and portal (me.web5.fans) integrate this (update key). However, these sites are keystore-based, while bbs and ccfdao predate Modules’ design—migration is still being planned.


User sovereignty

DIDs are fully user-controlled. The DID indexer merely facilitates reverse lookup (DID → DID Cell). Many scenarios don’t require it—console and portal don’t depend on it. For specific users, providing a wallet address allows direct retrieval of live cells under that address to access the user’s DID Cell.


Data portability

We’ve followed AT Protocol’s design here. Users can export data and migrate between PDS instances, even self-host. Console (console.web5.fans) provides data export/import; combined with other features, full PDS migration is achievable. Portal (me.web5.fans) offers only export—migration is complex and risks data corruption, so it’s excluded. Console targets developers.

Relayer and other Web5 components are open-source: web5fans ¡ GitHub . These are generally considered Web5 infrastructure and rarely mentioned in ccfdao contexts.


Sovereign data

Web5 is inherently Web2-Web3 hybrid—not all data goes on-chain. Some remains off-chain, but we place critical data on-chain; off-chain data only affects the specific app.

A Web3 example: if a user owns a Spore across two Spore Market sites, each freely decides which Spores to list, how to categorize them, display order—this data stays off-chain as it’s app-specific. Regardless, the user’s Spore remains on-chain, unchanged.


Ecosystem Reuse

DIDs are updatable and reusable across apps.

A user registered on bbs.fans can directly log into ccfdao.org without re-registering a DID. One issue: users must manually migrate login credentials between sites. This improves once bbs and ccfdao migrate to keystore. However, 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.

AT Protocol compatibility was never a consideration—their paths diverge. Bluesky explicitly stated they won’t partner with any specific blockchain project.


Independent Operation

Yes, anomalies still require human (foundation) intervention. Full on-chain contract enforcement is nearly infeasible, especially for CKB.

Verifiable critical data suffices. Verifiability creates massive deterrence: if malicious acts are certain to be detected, they become net-negative, and no one commits them.

3 Likes

I noticed @phroi mentioned in another reply that key rotation isn’t reflected in ccfdao’s source code.

Let me explain Web5’s approach as a CKB application development framework.

As noted earlier, the Web5 we’re discussing leans more Web3—and the broader Web5 ecosystem follows this pattern. Here’s the comparison:

  • Web5 DIDs closely resemble CKB addresses in Web3: not created for any single Web5 app, automatically usable across all Web5 apps—just as CKB addresses work across CKB’s entire dapp ecosystem.

  • Unlike Web2 apps that vertically bundle user management, business logic, and data storage, Web5 decouples these three layers. User management becomes DID; data lives in PDS; a Web5 app handles only its own business logic.

  • Similar to Web3’s dapp ecosystem: one player typically suffices per niche (e.g., decentralized exchanges). So we modularize functionality into separate apps for easier reuse. Hence key rotation lives in portal, not ccfdao or bbs.

3 Likes

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
  1. Key Storage. Regarding the technical solution for key storage, we are continuing to advance it, but this is the work of the Web5 ecosystem. The functionality of ccfdao itself is complete. The technology and role of keystore are actually very similar to joyid in the CKB ecosystem. However, we cannot say that CKB could not launch until joyid appeared. Without joyid, users could still use it normally by manually backing up their private keys. The key point of key storage for Web5 applications is not about key encryption or remote signing. The crux of the problem lies in achieving a balance between security and user experience. A similar scenario is AI Agent payments, where users hope that AI Agents can complete payments completely autonomously, rather than waking users up in the middle of the night to perform confirmation operations when payment is needed. Other chains have already launched related solutions and products, mostly by directly configuring private keys to AI Agents, but preventing AI Agent abuse through whitelists, or frequency and amount limits. As mentioned earlier, we have communicated with many wallet developers, which was actually discussing similar scenarios, but CKB has not made any moves in this regard so far. Therefore, we developed keystore ourselves, using whitelists for call restrictions.

  2. Key Rotation. Our current solution is to use the did-ckb contract for on-chain key rotation. You can double-check this point.

  3. Regarding indexers. CKB light client and MMR technology cannot solve the problems we encounter. Taking the acquisition of the voter list as an example. Users’ CKB staking in Nervos DAO does occur on-chain, but the CKB chain only records users’ staking and redemption operations. What we need now is a list of users whose staking amount is greater than zero, and this list itself is not on the CKB chain. To calculate users’ staking amounts, we must traverse the entire blockchain history. Because many users staked at a very early stage and have not performed any operations since. For post-hoc verification, we must also obtain users’ staking amounts at specific points in time, which cannot be done without relying on an indexer. This is the difference between the account model and the UTXO/cell model.

  4. Regarding specific code defects, we will confirm and fix them one by one. Once again, we express our gratitude to phroi for the code review work.

  5. Regarding the comparison between Nostr and AT protocol, there are already numerous articles online, so I won’t repeat them here. Personally, I really like Nostr—it’s very lightweight, very flexible, and has an active community. However, AT protocol is clearly more complete and mature, despite having more limitations. To give a specific example, although PDS can be self-hosted, it requires domain name configuration, which significantly limits users’ desire to self-host. I initially couldn’t understand why it couldn’t simply use IP + Port like Nostr. But I later understood that domain names serve as a kind of honor mechanism to some extent. For instance, if an account uses the domain name of Time Magazine, then according to the existing domain name mechanism, this user must have some relationship with Time Magazine. Therefore, the content published by this user would be somewhat more trustworthy than that of a user using a public domain name.

3 Likes