Solana DID 方案对比:Solana Name Service 与 Civic Pass 的技术实现与场景适配

📅 2026/7/24 16:43:50 👁️ 阅读次数 📝 编程学习
Solana DID 方案对比:Solana Name Service 与 Civic Pass 的技术实现与场景适配

Solana DID 方案对比:Solana Name Service 与 Civic Pass 的技术实现与场景适配

一、Solana 生态的 DID:两条不同路径

Solana 的去中心化身份方案可以归为两条技术路线:以 Solana Name Service(SNS)为代表的"人类可读标识"路线,和以 Civic Pass 为代表的"可验证凭证"路线。二者解决的问题不同,技术实现不同,适用的场景也不同。

SNS 解决的是"地址不可读"的问题——把8xH4F...pQ9m映射为alice.sol,降低用户交互的认知门槛。它的设计类似 ENS,但针对 Solana 的高性能做了适配:域名的解析、反向解析和子域名管理都通过 Solana Program(智能合约)实现,利用了 Solana 的 Account 模型和 CPI(Cross-Program Invocation)。

Civic Pass 解决的是"身份可验证"的问题——证明一个地址通过了 KYC、反洗钱(AML)或年龄验证。它使用了一个链下身份验证网络(Identity Network),验证节点对用户的身份文档进行审核,审核通过后在 Solana 链上发放一个不可转让的 NFT(Soulbound Token),作为"已验证"的凭证。

两个方案不是竞争关系,而是上下游关系——用户可能先通过 Civic Pass 验证身份,再用 SNS 绑定一个易记的名字。

二、技术架构对比

SNS 的域名管理合约本质是一个注册表(Registry)。核心数据结构是一个 PDA(Program Derived Address),以域名 hash 为种子:

// SNS 域名记录的 PDA 派生 let (name_record_pda, _bump) = Pubkey::find_program_address( &[ b"name_record", &hashed_name, // 域名的 SHA256 &parent_name_class, // 父域名类别(如 .sol 根) ], &sns_program_id, );

域名记录中存储了域名所有者、解析目标地址、TTL 和自定义键值对数据。SNS 的一个优势是支持子域名(sub.alice.sol),这在企业级 DID 场景中很有用——公司可以拥有company.sol,然后给每个员工分配employee.company.sol

Civic Pass 的核心是GatewayProgram。Pass 本身是一个具有多种状态的生命周期对象:

  • ACTIVE:验证通过,pass 有效
  • REVOKED:被撤销(用户自行撤销或验证节点发现伪造)
  • FROZEN:被冻结(监管要求或安全事件触发)
  • EXPIRED:过期(部分类型的 Pass 有时效性)

Pass 的类型包括 ID 验证 Pass(KYC)、年龄验证 Pass、唯一性 Pass(抗 Sybil)和活跃度 Pass(CAPTCHA 替代)。

三、代码实现关键逻辑

// SNS 客户端:域名解析与反向解析 import { Connection, PublicKey } from '@solana/web3.js'; import { NameRegistryState, getHashedName, getNameAccountKey } from '@bonfida/spl-name-service'; /** * SNS 域名服务封装 * * 设计决策:在客户端维护一个解析结果的内存缓存 Map。 * Solana 的 RPC 节点对 getAccountInfo 调用有速率限制(免费层 ~25 req/s), * 一个页面上渲染 50 个地址的 SNS 名称可能触发 50 次 RPC 调用, * 不缓存的情况下很容易被限流。 * 缓存使用 LRU 策略,容量上限 1000,避免内存膨胀。 */ class SNSResolver { private connection: Connection; // LRU 缓存:域名 → 地址映射 private cache: Map<string, { address: string; ttl: number }>; constructor(rpcUrl: string) { this.connection = new Connection(rpcUrl); this.cache = new Map(); } async resolve(domain: string): Promise<string | null> { // 检查缓存并验证 TTL const cached = this.cache.get(domain); if (cached && Date.now() < cached.ttl) { return cached.address; } try { const hashedName = await getHashedName(domain.replace('.sol', '')); const nameAccountKey = await getNameAccountKey( hashedName, undefined, // 根域名类别 new PublicKey('namesLPneVpdA9RozvRuUvQnHPhHhDKQAuBnFuLfM2TF') // SNS Program ID ); const { registry } = await NameRegistryState.retrieve( this.connection, nameAccountKey ); this.cache.set(domain, { address: registry.owner.toBase58(), ttl: Date.now() + 300_000 // 5分钟 TTL }); return registry.owner.toBase58(); } catch { return null; } } async reverse(publicKey: string): Promise<string | null> { // 反向解析需要遍历链上记录(Solana 没有内置的反向解析表) // 实际使用 Bonfida 的 API 端点进行反向解析 const response = await fetch( `https://sns-api.bonfida.com/v2/reverse/${publicKey}` ); if (!response.ok) return null; const data = await response.json(); return data.result || null; } /** * 批量解析(优化:一次查询 50 个地址的 SNS 名称) * * 设计决策:批量解析使用 RPC 的 getMultipleAccountsInfo 一次性获取 * 所有域名记录,而非逐个请求。RPC 级别的批量调用比客户端并行调用的 * 吞吐量高约 3 倍(因为省去了多个 HTTP 连接的握手开销)。 */ async resolveBatch(addresses: string[]): Promise<Map<string, string | null>> { const results = new Map<string, string | null>(); // 一次获取所有地址的账户信息 const accounts = await this.connection.getMultipleAccountsInfo( addresses.map(a => new PublicKey(a)) ); // 反向解析匹配 SNS 域名记录格式 for (let i = 0; i < addresses.length; i++) { if (accounts[i]) { const name = this.tryParseSNSRecord(accounts[i]!.data); results.set(addresses[i], name); } } return results; } } // Civic Pass 验证客户端 import { Program, AnchorProvider } from '@coral-xyz/anchor'; interface CivicPassState { address: PublicKey; state: 'ACTIVE' | 'REVOKED' | 'FROZEN' | 'EXPIRED'; expiry: number; gatekeeperNetwork: PublicKey; } /** * Civic Pass 验证工具 * * 设计决策:每个 Gatekeeper Network 代表不同的验证标准 * (如 "Civic Default KYC" vs "某DAO的自定义KYC")。 * DApp 应该指定自己信任的 Gatekeeper Network, * 而不是信任所有类型的 Pass。 * 这类似于 TLS 中的证书颁发机构信任链——你信任哪些 CA, * 取决于你的安全策略。 */ class CivicPassVerifier { static async verify( connection: Connection, userAddress: PublicKey, gatekeeperNetwork: PublicKey ): Promise<{ valid: boolean; reason?: string }> { // 1. 查找用户持有的 Pass Token 账户 const tokenAccounts = await connection.getParsedTokenAccountsByOwner( userAddress, { programId: TOKEN_PROGRAM_ID } ); // 2. 筛选出 Civic Pass Mint 对应的 Token 账户 const civicPassMint = GATEKEEPER_NETWORK_MINTS[gatekeeperNetwork.toBase58()]; const passAccount = tokenAccounts.value.find( acc => acc.account.data.parsed.info.mint === civicPassMint ); if (!passAccount) { return { valid: false, reason: 'No Civic Pass found for this network' }; } // 3. 查询 Pass 状态 const passState = await getPassState(connection, new PublicKey( passAccount.pubkey )); if (passState.state !== 'ACTIVE') { return { valid: false, reason: `Pass state is ${passState.state}` }; } if (passState.expiry && passState.expiry < Date.now() / 1000) { return { valid: false, reason: 'Pass has expired' }; } return { valid: true }; } }

四、两种方案的局限与协同

SNS 的局限

  • 域名注册需要 SOL,目前.sol域名的年费约为 $5-$20(根据域名长度)。较高的注册成本反而成了稀缺性保证——不太可能被批量抢注。
  • 域名记录是公开的,任何人都能查询alice.sol绑定的地址,这在某些隐私敏感的场景下是不可接受的。
  • SNS 不验证身份,它只记录"这个域名指向这个地址",域名持有者可能是机器人、是 DAO 金库、或是一个 AI Agent。

Civic Pass 的局限

  • Pass 的发放依赖链下验证节点的人工审核,引入了信任假设。Civic 的验证网络是联盟制的,不是无许可的。
  • 隐私问题:Civic 声称"不存储用户数据",但验证过程中用户需要提交真实的身份文档。如果验证节点被攻击,这些文档可能泄露。
  • Pass 的单一性不足:一个真实的人可能持有多个 Pass(不同身份、不同国家),"一人一 Pass"的唯一性保证不如 Worldcoin 的虹膜方案可靠。

协同使用:SNS + Civic Pass 可以组合成一个两级 DID 系统。第一级:Civic Pass 验证"你是真实的人"(身份断言)。第二级:SNS 给这个验证过的身份绑定人类可读的名字(身份标签)。DApp 可以要求用户在拥有 Civic Pass 的前提下注册 SNS 域名,实现"命名空间内的唯一性保障"。

五、总结

Solana 的 DID 生态走的是实用主义路线。SNS 解决可读性,Civic Pass 解决可验证性——两者各司其职,比试图用一个协议解决所有问题更务实。

SNS 的技术实现精简高效:利用 Solana 的 Account 模型和 PDA 派生做域名注册表,复杂度低、Gas 费可控。Civic Pass 的技术重心在链下验证网络的设计——如何在不信任单个验证节点的情况下形成"已验证"的共识。两者的结合为 Solana 生态提供了一套完整的 DID 基础设施,从"你是谁"到"别人如何称呼你",形成了去中心化身份的完整闭环。