问题所在: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 是什么时候轮换密钥的?”
- ❓ “是哪个密钥对这笔历史交易签名的?”
因缺失历史记录而失效的应用场景:
- 审计追踪:“证明这个密钥在 2026 年 7 月 15 日签署了这份凭证”——无法验证此后密钥是否已被轮换
- 入侵检测:有人入侵了 example.com,更改了 DID 文档 → 无法检测到未授权的更改
- 密钥轮换争议:“我从未授权过这个密钥”——没有防篡改日志可以证明真相
- 长期有效的凭证:凭证于 2026 年签发,2036 年才被验证——在签发时签名密钥是否有效?
did:webvh 解决了这个问题: 一种带有防篡改版本历史的 Web 型 DID 方法。
什么是 did:webvh?
did:webvh = did:web + 版本历史
核心特性:
- 基于 Web:通过 HTTPS 解析(与
did:web一样)——无需区块链,无需特殊基础设施 - 版本历史:每次变更都会生成一个新版本——所有更新形成不可变日志
- 防篡改:密码学哈希链可防止未授权的更改(类似 Git 提交历史)
- 可回溯:可解析出 DID 在历史任意时间点的状态
- 密钥轮换安全:旧密钥保留在历史记录中——即使轮换后依然可验证历史签名
示例:
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 的哈希值 → 结果:
abc123... - 检查版本 2 的
previousVersionHash字段 → 是否匹配?✓ - 对版本 3、4……重复此过程
如果有人篡改(例如,更改了版本 2 的密钥):
- 版本 2 的哈希值会改变 →
abc123...→xyz789... - 版本 3 的
previousVersionHash仍指向abc123...→ 哈希不匹配 - 篡改被检测到
结果: 防篡改日志——就像 Git 的提交历史一样。
时间回溯:重建历史状态
查询: “给我看看 did:webvh:example.com:alice 在 2026 年 6 月 15 日时的状态”
过程:
- 获取整个
.jsonl文件(所有版本) - 筛选版本:
versionTime ≤ 2026 年 6 月 15 日 - 结果:版本 1 和版本 2(版本 3 是 7 月 20 日,在截止日期之后)
- 应用版本 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:webvh | did: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 | 已在链上,与区块链原生集成 |
| 企业 IAM | did: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) - DID 是什么、为何重要
- 可验证凭证 - did:webvh 用于签署什么
- 信任注册表与 TRQP - 信任注册表如何解析 did:webvh
相关解决方案
总结
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 费。