L1 DIDFacet — 设计

概述

在 L1 Diamond Proxy 中新增 DIDFacet,实现 L1↔L2 DID 系统的双向同步:

  • L2→L1: L2 DID 状态变更通过 L1Messenger 发送消息,L1 DIDFacet 通过 Mailbox proof 验证后缓存

  • L1→L2: L1 用户通过 DIDFacet 发起 DID 操作,经 Mailbox requestL2TransactionDirect() 在 L2 执行

架构

┌─────── L1 (Diamond Proxy) ───────┐    ┌─────── L2 (系统合约) ───────────┐
│                                   │    │                                  │
│  DIDFacet                         │    │  DIDRegistry (0x8017)            │
│  ├─ syncDIDDocument()    ◄────────┼────┤  ├─ createDID() → L1Messenger   │
│  ├─ syncCredential()     ◄────────┼────┤  ├─ updateDocument() → L1Msg    │
│  ├─ syncCompliance()     ◄────────┼────┤  └─ deactivateDID() → L1Msg    │
│  │                                │    │                                  │
│  ├─ requestCreateDID()   ────────►┼────┤  CredentialRegistry (0x8018)     │
│  ├─ requestIssueCredential()─────►┼────┤  ├─ issueCredential() → L1Msg   │
│  ├─ requestAddTrustedIssuer()────►┼────┤  └─ revokeCredential() → L1Msg  │
│  │                       Mailbox  │    │                                  │
│  ├─ getDIDDocument()  (L1 cache)  │    │  IdentityVerifier (0x8019)       │
│  ├─ isIdentityActive()            │    │  └─ setCompliance() → L1Msg     │
│  └─ isCredentialValid()           │    │                                  │
└───────────────────────────────────┘    └──────────────────────────────────┘

数据流

L2→L1 同步流程:

  1. L2 DID 合约执行状态变更(createDID, issueCredential 等)

  2. 函数末尾调用 L1_MESSENGER.sendToL1(abi.encode(messageType, data))

  3. 消息包含在 batch 中提交到 L1

  4. 任何人调用 DIDFacet.syncXxx() 并提供 Mailbox Merkle proof

  5. DIDFacet 验证 proof,解码消息,更新本地缓存

L1→L2 操作流程:

  1. 用户调用 DIDFacet.requestXxx() 并支付 L2 gas(msg.value)

  2. DIDFacet 编码目标 L2 函数 calldata

  3. 通过 Mailbox.requestL2TransactionDirect() 发送优先交易

  4. L2 operator 从优先队列取出交易并执行

  5. 执行成功后 L2 合约自动发送 L1Messenger 消息(触发 L2→L1 同步)


L2 合约改动

消息类型

消息编码格式

类型
编码

DID_CREATED

encode(uint8 type, address identity, bytes32[] methods, bytes32 serviceHash, uint256 nonce)

DID_UPDATED

同 DID_CREATED

DID_DEACTIVATED

encode(uint8 type, address identity, uint256 nonce)

CREDENTIAL_ISSUED

encode(uint8 type, bytes32 credId, address issuer, address subject, bytes32 credType, bytes32 claimHash, uint64 issuedAt, uint64 expiresAt)

CREDENTIAL_REVOKED

encode(uint8 type, bytes32 credId, address issuer)

COMPLIANCE_UPDATED

encode(uint8 type, address identity, bytes32 requirement, bool status)

TRUSTED_ISSUER_ADDED

encode(uint8 type, address issuer)

TRUSTED_ISSUER_REMOVED

encode(uint8 type, address issuer)

改动文件

DIDRegistry.sol (era-contracts-l1/system-contracts/contracts/):

  • createDID() 末尾: L1_MESSENGER.sendToL1(encode(DID_CREATED, msg.sender, methods, serviceHash, nonce))

  • updateDocument() 末尾: L1_MESSENGER.sendToL1(encode(DID_UPDATED, ...))

  • deactivateDID() 末尾: L1_MESSENGER.sendToL1(encode(DID_DEACTIVATED, msg.sender, nonce))

CredentialRegistry.sol (era-contracts-l1/system-contracts/contracts/):

  • issueCredential() 末尾: L1_MESSENGER.sendToL1(encode(CREDENTIAL_ISSUED, ...))

  • revokeCredential() 末尾: L1_MESSENGER.sendToL1(encode(CREDENTIAL_REVOKED, credId, msg.sender))

IdentityVerifier.sol (era-contracts-l1/system-contracts/contracts/):

  • setCompliance() 末尾: L1_MESSENGER.sendToL1(encode(COMPLIANCE_UPDATED, ...))

  • addTrustedIssuer() 末尾: L1_MESSENGER.sendToL1(encode(TRUSTED_ISSUER_ADDED, issuer))

  • removeTrustedIssuer() 末尾: L1_MESSENGER.sendToL1(encode(TRUSTED_ISSUER_REMOVED, issuer))

每个函数仅增加 ~3 行代码。


L1 DIDFacet 设计

存储

使用独立 Diamond Storage slot(避免修改 ZKChainStorage 导致上游 merge conflict):

数据结构

接口

L2→L1 同步函数

每个 sync 函数的流程:

  1. 计算 messageHash = keccak256(_l2BatchNumber, _l2MessageIndex) 检查重放

  2. 构造 L2Message{txNumberInBatch, sender=对应L2合约, data=_message}

  3. 调用 _proveL2MessageInclusion(batchNumber, index, l2Message, proof) 验证

  4. 解码 _message 获取 messageType + data

  5. 根据 messageType 分发到对应处理逻辑

  6. 更新本地缓存 + 标记已处理 + 发出事件

L1→L2 操作函数

每个 request 函数的流程:

  1. 编码 L2 目标函数 calldata(如 abi.encodeCall(IDIDRegistry.createDID, (methods, serviceHash))

  2. 调用 Mailbox requestL2TransactionDirect{value: msg.value}(WritePriorityOpParams{...})

  3. 返回 L2 交易哈希

只读查询函数

管理函数

事件


开发测试合约

DIDFacetLocal (contracts/src/did/)

简化版本用于 Foundry 单元测试,不继承 ZKChainBase:

复用已有的 MockMailbox(L1OracleReader 测试中创建)。

测试范围

  1. syncDIDDocument 测试 (~6):

    • 成功同步 DID_CREATED

    • 成功同步 DID_UPDATED

    • 成功同步 DID_DEACTIVATED

    • revert: 重放同一消息

    • revert: 无效 proof

    • revert: sender 不是 L2 DIDRegistry

  2. syncCredential 测试 (~4):

    • 成功同步 CREDENTIAL_ISSUED

    • 成功同步 CREDENTIAL_REVOKED

    • isCredentialValid: 过期返回 false

    • revert: sender 不是 L2 CredentialRegistry

  3. syncCompliance 测试 (~4):

    • 成功同步 COMPLIANCE_UPDATED

    • 成功同步 TRUSTED_ISSUER_ADDED

    • 成功同步 TRUSTED_ISSUER_REMOVED

    • revert: sender 不是 L2 IdentityVerifier

  4. requestCreateDID 测试 (~3):

    • 成功发送 L2 交易

    • calldata 编码正确

    • emits L2TransactionRequested 事件

  5. requestIssueCredential 测试 (~2):

    • 成功发送

    • calldata 编码正确

  6. requestDeactivateDID + requestRevokeCredential + requestAddTrustedIssuer (~3):

    • 各一个成功用例

  7. 查询函数测试 (~4):

    • getDIDDocument 返回同步后的快照

    • isIdentityActive 返回正确状态

    • isCredentialValid: 正常/过期/已撤销

    • checkCompliance 返回缓存值

  8. 管理函数测试 (~3):

    • setL2Addresses (成功 + 非管理员 revert)

    • setSyncOperator

  9. E2E 流程测试 (~1):

    • L1 requestCreateDID → mock L2 创建 → syncDIDDocument → getDIDDocument 验证

预计总测试: ~30


文件清单

新建文件

文件
用途

contracts/src/did/DIDFacetLocal.sol

L1 DIDFacet 本地测试版

contracts/src/did/IDIDFacet.sol

DIDFacet 接口

contracts/src/did/DIDMessageLib.sol

消息类型 + 编码/解码工具库

contracts/test/DIDFacet.t.sol

Foundry 测试 (~30 tests)

修改文件

文件
改动

era-contracts-l1/system-contracts/contracts/DIDRegistry.sol

添加 L1Messenger 调用 (~9 行)

era-contracts-l1/system-contracts/contracts/CredentialRegistry.sol

添加 L1Messenger 调用 (~6 行)

era-contracts-l1/system-contracts/contracts/IdentityVerifier.sol

添加 L1Messenger 调用 (~9 行)


交付计划

任务
内容
预估

Task 1

DIDMessageLib 消息库 + IDIDFacet 接口

20 min

Task 2

DIDFacetLocal L2→L1 sync 实现 + 测试

40 min

Task 3

DIDFacetLocal L1→L2 request 实现 + 测试

30 min

Task 4

查询函数 + 管理函数 + E2E 测试

25 min

Task 5

L2 系统合约 L1Messenger 集成 + 编译验证

30 min

Task 6

全量回归测试 + dev-log 更新

15 min

Last updated