标准规范 / 高级 / 14 分钟

did:webvh —— 防篡改标识符

带版本历史的 Web 型 DID——无需区块链即可实现可回溯、可审计、防篡改的身份

问题所在:DID 需要历史记录

去中心化标识符(DID) 为智能体和个人提供密码学身份——但大多数 DID 方法没有历史记录

示例:did:web(标准的 Web 型 DID)

DID: did:web:example.com:alice

解析:
  GET https://example.com/.well-known/did.json
  → 返回当前的 DID 文档 (密钥、服务端点)

问题: 你只能看到当前状态——没有任何变更历史。

你无法回答的问题:

  • ❓ “这个密钥在签发凭证时(6 个月前)是否有效?”
  • ❓ “有人是否篡改过这份 DID 文档(未经授权更改密钥)?”
  • ❓ “这个 DID 是什么时候轮换密钥的?”
  • ❓ “是哪个密钥对这笔历史交易签名的?”

因缺失历史记录而失效的应用场景:

  1. 审计追踪:“证明这个密钥在 2026 年 7 月 15 日签署了这份凭证”——无法验证此后密钥是否已被轮换
  2. 入侵检测:有人入侵了 example.com,更改了 DID 文档 → 无法检测到未授权的更改
  3. 密钥轮换争议:“我从未授权过这个密钥”——没有防篡改日志可以证明真相
  4. 长期有效的凭证:凭证于 2026 年签发,2036 年才被验证——在签发时签名密钥是否有效?

did:webvh 解决了这个问题: 一种带有防篡改版本历史的 Web 型 DID 方法。


什么是 did:webvh?

did:webvh = did:web + 版本历史

核心特性:

  1. 基于 Web:通过 HTTPS 解析(与 did:web 一样)——无需区块链,无需特殊基础设施
  2. 版本历史:每次变更都会生成一个新版本——所有更新形成不可变日志
  3. 防篡改:密码学哈希链可防止未授权的更改(类似 Git 提交历史)
  4. 可回溯:可解析出 DID 在历史任意时间点的状态
  5. 密钥轮换安全:旧密钥保留在历史记录中——即使轮换后依然可验证历史签名

示例:

DID: did:webvh:example.com:alice

解析 (当前状态):
  GET https://example.com/.well-known/did.jsonl
  → 返回所有版本 (只追加的日志)
  → 最新版本 = 当前状态

解析 (历史状态):
  "给我看看 did:webvh:example.com:alice 在 2026 年 7 月 15 日时的状态"
  → 回放日志至该日期 → 重建 DID 文档

结果: 可审计、防篡改、带完整历史的身份。


did:webvh 的工作原理:作为哈希链的版本历史

结构:只追加日志

DID 文档以 .jsonl 文件形式存储(JSON Lines 格式——每行一个 JSON 对象):

{"versionId":1,"versionTime":"2026-01-15T00:00:00Z","parameters":{"method":"webvh","scid":"..."}, "state":{"verificationMethod":[{"id":"#key-1","type":"Ed25519VerificationKey2020","publicKeyMultibase":"z6MkpTHR..."}]}}
{"versionId":2,"versionTime":"2026-06-01T00:00:00Z","parameters":{"updateKeys":["#key-1"],"prerotation":true},"state":{"verificationMethod":[{"id":"#key-1","type":"Ed25519VerificationKey2020","publicKeyMultibase":"z6MkpTHR..."},{"id":"#key-2","type":"Ed25519VerificationKey2020","publicKeyMultibase":"z6Mknew..."}]}}
{"versionId":3,"versionTime":"2026-07-20T00:00:00Z","parameters":{"updateKeys":["#key-2"],"deactivated":false},"state":{"verificationMethod":[{"id":"#key-2","type":"Ed25519VerificationKey2020","publicKeyMultibase":"z6Mknew..."}]}}

每一行 = 一个版本:

  • 版本 1(2026-01-15):DID 被创建,添加了 key-1
  • 版本 2(2026-06-01):添加 key-2(密钥轮换——预轮换阶段)
  • 版本 3(2026-07-20):移除 key-1,key-2 成为唯一密钥

只追加: 新版本被追加到末尾,旧版本永不删除

防篡改机制:哈希链

每个版本都包含前一个版本的哈希值:

{
  "versionId": 2,
  "versionTime": "2026-06-01T00:00:00Z",
  "previousVersionHash": "abc123...",  // 版本 1 的哈希值
  "state": { ... }
}

验证过程:

  1. 计算版本 1 的哈希值 → 结果:abc123...
  2. 检查版本 2 的 previousVersionHash 字段 → 是否匹配?✓
  3. 对版本 3、4……重复此过程

如果有人篡改(例如,更改了版本 2 的密钥):

  • 版本 2 的哈希值会改变 → abc123...xyz789...
  • 版本 3 的 previousVersionHash 仍指向 abc123...哈希不匹配
  • 篡改被检测到

结果: 防篡改日志——就像 Git 的提交历史一样。

时间回溯:重建历史状态

查询: “给我看看 did:webvh:example.com:alice 在 2026 年 6 月 15 日时的状态”

过程:

  1. 获取整个 .jsonl 文件(所有版本)
  2. 筛选版本:versionTime ≤ 2026 年 6 月 15 日
  3. 结果:版本 1 和版本 2(版本 3 是 7 月 20 日,在截止日期之后)
  4. 应用版本 2 的状态 → DID 文档当时拥有密钥 1 和密钥 2

应用场景:

  • 凭证于 2026 年 6 月 15 日用 key-1 签名
  • 今天(2026 年 7 月 22 日),key-1 已被轮换出局
  • 验证者提问:“key-1 在 6 月 15 日是否有效?”
  • 时间回溯:重建 6 月 15 日的 DID 状态 → key-1 当时处于活跃状态 ✓
  • 签名有效

没有版本历史的情况下: key-1 不在当前 DID 文档中 → 签名验证失败(假阴性)。


核心功能

1. 安全密钥轮换(预轮换)

问题: 密钥轮换存在风险:

  • 立即轮换:移除旧密钥 → 无法验证历史签名
  • 永久保留旧密钥:已泄露的密钥留在 DID 文档中 → 安全隐患

did:webvh 的解决方案:预轮换(Prerotation)

阶段 1:添加新密钥(版本 2)

{
  "versionId": 2,
  "state": {
    "verificationMethod": [
      { "id": "#key-1", "type": "Ed25519...", "publicKeyMultibase": "z6MkOLD..." },
      { "id": "#key-2", "type": "Ed25519...", "publicKeyMultibase": "z6MkNEW..." }
    ]
  },
  "parameters": { "updateKeys": ["#key-1"], "prerotation": true }
}

两个密钥同时有效: 智能体可以使用 key-1 或 key-2 中的任意一个签名。

阶段 2:移除旧密钥(版本 3)

{
  "versionId": 3,
  "state": {
    "verificationMethod": [
      { "id": "#key-2", "type": "Ed25519...", "publicKeyMultibase": "z6MkNEW..." }
    ]
  },
  "parameters": { "updateKeys": ["#key-2"] }
}

只有 key-2 有效: 新签名使用 key-2。

优势:

  • 渐进式迁移:过渡期间两个密钥都可用(无停机)
  • 历史签名依然有效:key-1 出现在版本 2 中 → 旧签名可验证
  • 已泄露密钥被移除:key-1 从当前状态中消失 → 无法用它签署新消息

2. 入侵检测

场景: 攻击者入侵了 example.com,试图更改 DID 文档,添加攻击者自己的密钥。

没有版本历史(did:web)时:

  • 攻击者修改 /did.json → 添加攻击者的密钥
  • 无法检测未授权的更改(没有审计日志)
  • 结果: 攻击者可以冒充 DID 所有者

有版本历史(did:webvh)时:

  • 攻击者添加带有攻击者密钥的新版本
  • 但是: 新版本需要来自updateKeys(前一版本授权密钥)的签名
  • 攻击者没有授权 updateKeys 的私钥 → 无法为新版本签名
  • 结果: 未授权的版本被拒绝(签名无效)

如果攻击者修改了已有版本:

  • 哈希链断裂(版本 N 的哈希 ≠ 版本 N+1 的 previousVersionHash
  • 结果: 篡改被检测到

3. 可审计的变更

每次变更都会被记录:

  • 版本 1:DID 创建
  • 版本 2:添加密钥
  • 版本 3:移除密钥
  • 版本 4:添加服务端点
  • 版本 5:DID 被停用

可回答的审计问题:

  • “key-2 是何时添加的?” → 版本 2,2026 年 6 月 1 日
  • “谁授权了 key-2?” → 版本 1 的 updateKeys 对版本 2 进行了签名
  • “一共发生过几次密钥轮换?” → 统计 verificationMethod 发生变化的版本数
  • “这个 DID 是否曾被入侵?” → 检查哈希链的完整性

应用场景:合规审计

  • 审计员:“证明这个 DID 在 2026 年 6 月 15 日经过 CFO 授权”
  • 回应:版本日志显示 CFO 的密钥在 6 月 15 日签署了版本 2 ✓

4. 停用(不可变)

停用 DID:

{
  "versionId": 6,
  "versionTime": "2026-12-31T00:00:00Z",
  "parameters": { "deactivated": true },
  "state": null
}

效果:

  • DID 被标记为已停用
  • 不允许再进行更新
  • 历史依然保留: 所有历史版本仍可访问(审计追踪得以保留)

应用场景:

  • 员工离职 → DID 被停用
  • 公司仍可验证该员工 DID 此前签署的历史凭证
  • 无法签发凭证(DID 已停用)

did:webvh 与 did:web、区块链 DID 的对比

did:webvhdid:web区块链 DID
解析方式HTTPS(.jsonl 文件)HTTPS(.json 文件)区块链查询
版本历史✓ 有(只追加日志)✗ 无(仅当前状态)✓ 有(链上交易记录)
防篡改✓ 哈希链✗ 无(可被覆盖)✓ 区块链不可篡改性
时间回溯✓ 可重建历史状态✗ 不可✓ 可查询历史区块
密钥轮换安全性✓ 支持预轮换⚠ 手动(无指导机制)⚠ 取决于具体方法
托管方式✓ Web 服务器(自托管)✓ Web 服务器(自托管)✗ 区块链(需支付 Gas 费)
成本✓ 免费(Web 托管)✓ 免费(Web 托管)✗ 每次更新需付 Gas 费
速度✓ HTTPS(毫秒级)✓ HTTPS(毫秒级)✗ 区块确认(数秒至数分钟)
隐私性✓ 链下(符合 GDPR)✓ 链下✗ 链上(公开账本)

did:webvh 兼具两者优点:

  • ✅ 基于 Web(无区块链开销)
  • ✅ 版本历史(可审计性)
  • ✅ 防篡改(哈希链)

实现方式:如何创建 did:webvh

步骤 1:生成初始 DID 文档

import { createDID, signVersion } from 'did-webvh-sdk'

// 生成密钥对
const keyPair = generateEd25519KeyPair()

// 创建 DID 文档(版本 1)
const didDoc = {
  versionId: 1,
  versionTime: new Date().toISOString(),
  parameters: {
    method: 'webvh',
    scid: generateSCID(), // 自证明标识符 (初始状态的哈希值)
  },
  state: {
    verificationMethod: [{
      id: '#key-1',
      type: 'Ed25519VerificationKey2020',
      publicKeyMultibase: encodeMultibase(keyPair.publicKey)
    }],
    authentication: ['#key-1']
  }
}

// 对版本 1 签名(自签名)
const signedVersion1 = signVersion(didDoc, keyPair.privateKey)

步骤 2:托管 DID 文档

# 保存为 .jsonl 文件 (JSON Lines 格式)
echo '{"versionId":1,...}' > did.jsonl

# 托管到 Web 服务器
cp did.jsonl /var/www/html/.well-known/did.jsonl

# DID 标识符:
did:webvh:example.com
# 解析为: https://example.com/.well-known/did.jsonl

步骤 3:更新 DID 文档(添加密钥)

// 获取当前 DID 文档
const currentDoc = await fetch('https://example.com/.well-known/did.jsonl')
const versions = parseJSONL(currentDoc)
const latestVersion = versions[versions.length - 1]

// 生成新密钥
const newKeyPair = generateEd25519KeyPair()

// 创建版本 2(添加 key-2)
const version2 = {
  versionId: latestVersion.versionId + 1,
  versionTime: new Date().toISOString(),
  previousVersionHash: hash(latestVersion),
  parameters: {
    updateKeys: ['#key-1'],  // 只有 key-1 可以授权此次更新
    prerotation: true
  },
  state: {
    verificationMethod: [
      latestVersion.state.verificationMethod[0],  // 保留 key-1
      {
        id: '#key-2',
        type: 'Ed25519VerificationKey2020',
        publicKeyMultibase: encodeMultibase(newKeyPair.publicKey)
      }
    ],
    authentication: ['#key-1', '#key-2']
  }
}

// 用 key-1 (授权的 updateKey) 对版本 2 签名
const signedVersion2 = signVersion(version2, keyPair.privateKey)

// 追加到 did.jsonl
appendToFile('/var/www/html/.well-known/did.jsonl', signedVersion2)

步骤 4:轮换密钥(移除旧密钥)

// 创建版本 3 (移除 key-1,保留 key-2)
const version3 = {
  versionId: version2.versionId + 1,
  versionTime: new Date().toISOString(),
  previousVersionHash: hash(version2),
  parameters: {
    updateKeys: ['#key-2'],  // 现在 key-2 被授权进行更新
  },
  state: {
    verificationMethod: [{
      id: '#key-2',
      type: 'Ed25519VerificationKey2020',
      publicKeyMultibase: encodeMultibase(newKeyPair.publicKey)
    }],
    authentication: ['#key-2']
  }
}

// 用 key-2 (新的授权 updateKey) 对版本 3 签名
const signedVersion3 = signVersion(version3, newKeyPair.privateKey)

// 追加到 did.jsonl
appendToFile('/var/www/html/.well-known/did.jsonl', signedVersion3)

结果: 密钥轮换完成,历史记录得以保留。


解析:验证者如何使用 did:webvh

解析当前状态

// 解析 did:webvh:example.com
const did = 'did:webvh:example.com'

// 获取 .jsonl 文件
const response = await fetch('https://example.com/.well-known/did.jsonl')
const versions = parseJSONL(response)

// 验证哈希链
for (let i = 1; i < versions.length; i++) {
  const prevHash = hash(versions[i - 1])
  const declaredHash = versions[i].previousVersionHash
  if (prevHash !== declaredHash) {
    throw new Error('哈希链断裂——检测到篡改')
  }
}

// 返回最新版本的状态
const currentState = versions[versions.length - 1].state

时间回溯解析

// 解析 did:webvh:example.com 在 2026 年 6 月 15 日时的状态
const targetDate = new Date('2026-06-15T00:00:00Z')

// 获取所有版本
const versions = await fetchVersions('did:webvh:example.com')

// 筛选截至目标日期的版本
const historicalVersions = versions.filter(v => 
  new Date(v.versionTime) <= targetDate
)

// 返回目标日期之前最后一个版本的状态
const historicalState = historicalVersions[historicalVersions.length - 1].state

应用场景:

  • 验证于 2026 年 6 月 15 日签名的凭证
  • 检查签名密钥在当时是否有效
  • 结果: 历史解析显示该密钥当时处于活跃状态 ✓

真实应用场景

1. 供应链溯源

场景: 产品批次于 2026 年 1 月 15 日由制造商签名。零售商在 2026 年 12 月进行验证。

问题: 制造商在 2026 年 6 月轮换了密钥——签名验证失败(该密钥不在当前 DID 文档中)。

did:webvh 解决方案:

  • 时间回溯:解析制造商 DID 在 2026 年 1 月 15 日时的状态
  • 历史 DID 显示原始密钥当时处于活跃状态
  • 签名有效(即便此后密钥已被轮换)

2. 智能体密钥轮换

场景: 智能体每 90 天轮换一次密钥(安全最佳实践)。历史交易由旧密钥签名。

问题: 经过 4 次轮换后,原始密钥已有 360 天历史——如何验证历史交易?

did:webvh 解决方案:

  • 版本历史显示曾使用过的所有密钥
  • 时间回溯到交易日期 → 用当时的密钥重建 DID
  • 历史交易依然可验证

3. 入侵检测

场景: 攻击者入侵公司网站,试图将攻击者的密钥添加到 DID 文档中。

没有版本历史(did:web)时:

  • 攻击者修改 did.json → 添加攻击者的密钥
  • 无法检测到未授权的更改

有版本历史(did:webvh)时:

  • 攻击者尝试追加带有攻击者密钥的新版本
  • 新版本需要来自updateKeys(前一版本授权密钥)的签名
  • 攻击者没有私钥 → 无法签署有效的版本
  • 攻击被阻止

如果攻击者覆盖了文件:

  • 哈希链断裂 → 检测到篡改
  • 验证者拒绝该 DID 文档(历史无效)

4. 监管合规(审计追踪)

场景: 金融监管机构审计:“证明智能体 X 在 2026 年 7 月 15 日经过 CFO 授权。”

did:webvh 解决方案:

  • 版本日志显示:版本 5(2026 年 7 月 15 日)添加了智能体 X 的密钥
  • 版本 5 由 CFO 的密钥签名(来自版本 4 的 updateKeys
  • 审计追踪:密码学证明 CFO 授权了智能体 X

没有版本历史: 无法证明 7 月 15 日发生了什么——只能看到当前状态。


局限性与权衡

1. 文件体积增长

问题: 只追加日志会随时间增长。

  • 1 个版本 ≈ 1 KB
  • 1,000 个版本 ≈ 1 MB
  • 10,000 个版本(27 年间每天更新一次)≈ 10 MB

缓解措施:

  • 压缩:对 .jsonl 文件进行 gzip 压缩(可减小 70-90% 的体积)
  • 归档:将旧版本移至单独文件(例如 did-archive.jsonl
  • 修剪:N 年后移除非常旧的版本(仅保留检查点版本)

权衡: 修剪会损失完整历史——需要在存储成本与可审计性之间权衡。

2. 解析开销

问题: 获取完整版本历史比获取单一文档(did:web)更慢。

  • did:web:获取 did.json(约 1 KB)
  • did:webvh:获取 did.jsonl(1,000 个版本约 1 MB)

缓解措施:

  • 缓存:解析器缓存 .jsonl 文件(每 24 小时刷新一次)
  • 部分获取:使用 HTTP range 请求——只获取最近的版本
  • 摘要端点:托管 /did-summary.json(当前状态 + 版本数量)以实现快速解析

权衡: 完整历史解析较慢,但借助缓存,当前状态解析可以做到很快。

3. 托管依赖

问题: did:webvh 依赖 Web 托管(域名 + HTTPS)。

  • 若域名过期 → DID 无法解析
  • 若网站被黑客攻击 → DID 可能被入侵(尽管哈希链能检测到篡改)

缓解措施:

  • 域名长期性:使用长期域名(而非临时域名)
  • 备份托管:在多个服务器(CDN、IPFS)上镜像 .jsonl 文件
  • 监控:DID 文档发生意外修改时发出告警

权衡: 与 did:web 相同(两者都依赖 Web 托管)。


did:webvh 与区块链:该用哪个?

应用场景推荐方案原因
智能体身份did:webvh解析速度快(毫秒级)、免费托管、符合 GDPR
供应链溯源did:webvh防篡改日志、无 Gas 费、基于 Web 的解析
政府数字身份区块链 DID公共信任(无单点托管故障风险)、不可篡改性
加密货币钱包区块链 DID已在链上,与区块链原生集成
企业 IAMdid:webvh自托管、无区块链开销、解析速度快

一般性建议:

  • 使用 did:webvh,当:可接受 Web 托管、追求快速解析、想避免 Gas 费、需要符合 GDPR
  • 使用区块链 DID,当:需要公共信任、已经上链、愿意支付 Gas 费、不可篡改性是硬性要求

快速上手 did:webvh

Affinidi 的 DID 实现基于 did:webvh:

  • Agent Gateway:为智能体签发 did:webvh 标识符
  • 信任注册表:解析 did:webvh,支持时间回溯
  • Elements Services:使用 did:webvh 密钥对凭证签名

创建你的第一个 did:webvh:

npm install did-webvh-sdk

# 生成 DID
did-webvh create --domain example.com --output did.jsonl

# 托管 DID 文档
cp did.jsonl /var/www/html/.well-known/did.jsonl

# DID 标识符: did:webvh:example.com

开始构建 →

文档:


相关资源

技术深度解析

相关解决方案


总结

did:webvh = did:web + 版本历史

核心特性:

  • 版本历史:记录所有变更的只追加日志
  • 防篡改:哈希链可防止未授权的修改
  • 可回溯:可解析 DID 在历史任意时间点的状态
  • 安全的密钥轮换:支持预轮换,历史密钥依然可验证
  • 可审计:每次变更都带有时间戳 + 签名记录

与 did:web 对比:

  • did:web:仅有当前状态(无历史记录)
  • did:webvh:完整历史记录(可审计性 + 时间回溯)

与区块链 DID 对比:

  • 区块链:链上(需支付 Gas 费,公开账本)
  • did:webvh:基于 Web(免费托管,符合 GDPR)

应用场景:

  • 智能体身份(回溯验证历史签名)
  • 供应链溯源(防篡改的监管链)
  • 监管合规(带密码学证明的审计追踪)
  • 密钥轮换(安全迁移而不破坏历史签名)

Affinidi 使用 did:webvh 的场景:

  • Agent Gateway(为智能体签发 did:webvh)
  • 信任注册表(解析 did:webvh,支持时间回溯)
  • Elements Services(使用 did:webvh 密钥签署凭证)

结果: 具备类区块链可审计性的 Web 型 DID——防篡改、可回溯、无需 Gas 费。

Cookie Preferences

We use cookies to enhance your experience. You can manage your preferences below. For more information, read our Cookie Policy.

Strictly Necessary Always Active

These cookies are essential for core website functions such as security, session integrity, and cookie preference storage. They cannot be disabled.

  • _cf_bm: Distinguishes humans from bots (Cloudflare) · 30m
  • _cfuvid: Ensures secure browsing (Cloudflare) · Session
  • __hs_initial_opt_in: Prevents HubSpot's banner · 7 days
  • _gtm_debug: GTM debug mode (testing only) · Session
Analytics

These cookies help us understand how visitors interact with the site so we can improve content and performance. All data is aggregated and anonymous.

  • _ga, _gid, _gat: Google Analytics · Session – 2 years
  • __hstc, hubspotutk, __hssrc: HubSpot visitor tracking · 13 months
  • __hs_opt_out: HubSpot opt-out preference · 6 months
Marketing & Targeting

These cookies allow us and our partners to serve personalised ads and measure campaign performance.

  • _gcl_au, _gcl_dc: Google Ads conversion tracking · 90 days
  • IDE: Google Display Network personalisation · 1 year
  • _fbp: Meta / Facebook remarketing · 90 days
  • li_gc, _li_fat_id, bcookie: LinkedIn tracking · 1–24 months
  • guest_id, personalization_id: Twitter/X analytics · 2 years