可验证展示要解决的问题
你手上有一份可验证凭证(VC)——可能是学位证书、在职证明、智能体 Mandate——你要如何将它共享给验证者?
面临的挑战:
- 证明你控制该凭证 —— 任何人都能复制一份 VC;验证者如何知道你才是持有者?
- 选择性披露 —— 如果你只想共享凭证的一部分怎么办?
- 请求/响应流程 —— 验证者如何请求特定的凭证?
- 隐私性 —— 如何在不暴露不必要数据的情况下共享凭证?
可验证展示(VP) 解决了这个问题:一种标准化的方式,将 VC 打包,并附带证明你(持有者)正在出示它们的证据。
面向可验证展示的 OpenID 协议(OID4VP) 补充了协议层面的规则:验证者如何请求 VP,持有者如何响应。
什么是可验证展示?
可验证展示 是一个容器,它:
- 承载一份或多份可验证凭证
- 证明持有者控制这些凭证
- 将该展示绑定到特定验证者(防止重放攻击)
结构:
{
"@context": ["https://www.w3.org/2018/credentials/v1"],
"type": ["VerifiablePresentation"],
"holder": "did:webvh:alice.com",
"verifiableCredential": [
{ /* VC: 大学学位证书 */ },
{ /* VC: 在职证明 */ }
],
"proof": {
"type": "Ed25519Signature2020",
"created": "2026-07-21T19:00:00Z",
"verificationMethod": "did:webvh:alice.com#key-1",
"proofPurpose": "authentication",
"challenge": "nonce-from-verifier-xyz",
"proofValue": "z3FXQi...signature..."
}
}
关键属性:
- 持有者 —— 谁在出示 (Alice)
- VC —— 被共享的凭证
- 证明 —— 证明 Alice 控制其 DID 的密码学签名
- 挑战值 —— 来自验证者的随机数 (防止重放攻击)
可验证展示的工作原理
1. 验证者请求凭证
验证者发送一个展示请求:
GET /request-presentation?
response_type=vp_token&
client_id=did:webvh:employer.com&
presentation_definition={...}
展示定义(验证者想要什么):
{
"id": "employment-check",
"input_descriptors": [{
"id": "diploma",
"constraints": {
"fields": [{
"path": ["$.type"],
"filter": { "type": "string", "const": "UniversityDegreeCredential" }
}]
}
}]
}
翻译过来就是:“我需要一份 UniversityDegreeCredential”
2. 持有者创建展示
持有者 (Alice) 选择匹配的 VC 并创建一份 VP:
// Alice 有一份学位证书 VC
const diplomaVC = await wallet.getCredential('diploma')
// 创建展示
const presentation = {
type: ['VerifiablePresentation'],
holder: 'did:webvh:alice.com',
verifiableCredential: [diplomaVC],
proof: await sign({
challenge: verifierChallenge,
holder: 'did:webvh:alice.com',
privateKey: alicePrivateKey
})
}
3. 持有者发送展示
Alice 将该 VP 发送给验证者:
POST /submit-presentation
Content-Type: application/json
{
"vp_token": "<signed-presentation>",
"presentation_submission": {
"id": "...",
"definition_id": "employment-check",
"descriptor_map": [...]
}
}
4. 验证者进行校验
验证者检查:
// 1. 验证 VP 的签名 (这真的是 Alice 出示的吗?)
const holderDID = presentation.holder
const holderKey = await resolve(holderDID)
const isValidPresentation = await verify(presentation.proof, holderKey)
// 2. 验证每份 VC 的签名 (这份学位证书真的来自斯坦福大学吗?)
for (const vc of presentation.verifiableCredential) {
const issuerKey = await resolve(vc.issuer)
const isValidVC = await verify(vc.proof, issuerKey)
}
// 3. 检查挑战值 (这个响应是针对我的请求吗?)
const challengeMatches = presentation.proof.challenge === myChallenge
// 4. 检查吊销状态 (斯坦福大学是否吊销了该学位证书?)
const isRevoked = await checkRevocation(diplomaVC)
// ✅ 所有检查通过 — 展示有效
OID4VP:协议层
面向可验证展示的 OpenID 协议(OID4VP) 使用 OAuth 2.0 模式对请求/响应流程进行了标准化。
授权请求(验证者 → 持有者)
GET /authorize?
response_type=vp_token&
client_id=did:webvh:employer.com&
redirect_uri=https://employer.com/callback&
presentation_definition={...}&
nonce=challenge-xyz
展示提交(持有者 → 验证者)
POST /callback
Content-Type: application/x-www-form-urlencoded
vp_token=<base64-encoded-presentation>&
presentation_submission=<metadata>
验证(验证者)
验证者对 VP 进行校验,并将成功/失败结果返回给应用流程。
使用 BBS+ 签名实现选择性披露
问题: 你想证明自己年满 21 岁,而不透露确切的出生日期。
解决方案: BBS+ 签名允许推导出一份新的凭证,用来证明某些属性,而不暴露完整声明。
原始 VC:
{
"credentialSubject": {
"id": "did:webvh:alice.com",
"name": "Alice",
"birthdate": "1995-03-15",
"address": "123 Main St"
},
"proof": { "type": "BbsBlsSignature2020", "proofValue": "..." }
}
派生 VC(选择性披露):
{
"credentialSubject": {
"id": "did:webvh:alice.com",
"over21": true // 通过密码学方式证明,不透露出生日期
},
"proof": { "type": "BbsBlsSignatureProof2020", "proofValue": "..." }
}
工作原理:
- BBS+ 允许证明谓词(年龄 > 21),而不暴露底层数据(出生日期)
- 该证明依然可以对照原始签发者的签名进行验证
- 验证者只能看到你选择披露的内容
真实应用场景
1. 智能体 Mandate 展示
场景: 智能体向合作伙伴系统出示其授权 Mandate。
流程:
// 验证者请求 Mandate
const request = {
presentation_definition: {
input_descriptors: [{
id: 'trading-mandate',
constraints: {
fields: [{
path: ['$.type'],
filter: { const: 'TradingMandate' }
}]
}
}]
}
}
// 智能体创建展示
const presentation = {
holder: 'did:webvh:bankA.com:agent-42',
verifiableCredential: [tradingMandateVC],
proof: await signPresentation({
challenge: request.nonce,
holderDID: 'did:webvh:bankA.com:agent-42',
privateKey: agentPrivateKey
})
}
// 验证者进行校验并放行交易
2. PHI 访问授权
场景: 智能体证明自己被授权访问患者的健康信息。
流程:
// 系统请求授权
const request = {
presentation_definition: {
input_descriptors: [{
id: 'phi-access',
constraints: {
fields: [
{ path: ['$.type'], filter: { const: 'PHIAccessAuthorization' } },
{ path: ['$.credentialSubject.patient'], filter: { const: 'patient-12345' } }
]
}
}]
}
}
// 智能体出示授权 VC
const presentation = {
holder: 'did:webvh:hospital.com:agent-99',
verifiableCredential: [phiAccessVC],
proof: { /* 智能体的签名 */ }
}
// 系统进行校验并授予访问权限
3. 跨组织身份验证
场景: 合作伙伴需要验证你的智能体是否真如其所声称的那样。
流程:
// 合作伙伴请求身份证明
const request = {
presentation_definition: {
input_descriptors: [{
id: 'agent-identity',
constraints: {
fields: [{
path: ['$.issuer'],
filter: { const: 'did:webvh:yourcompany.com' } // 必须来自你的组织
}]
}
}]
}
}
// 智能体出示身份凭证
const presentation = {
holder: 'did:webvh:yourcompany.com:agent-42',
verifiableCredential: [identityVC],
proof: { /* 智能体的签名 */ }
}
// 合作伙伴进行验证并建立信任
Affinidi 技术栈中的可验证展示
Elements Services 签发可供出示的 VC:
// 签发一份凭证
const vc = await elements.issueCredential({
holder: 'did:webvh:agent-42.com',
type: 'TradingMandate',
claims: { authorizedBy: 'trader-alice', expiry: '2026-12-31' }
})
// 存入钱包
await wallet.store(vc)
Affinidi Vault 保存 VC 并创建展示:
// 接收展示请求
const request = await vault.receivePresentationRequest(requestUrl)
// 选择匹配的凭证
const matchingVCs = await vault.findCredentials(request.presentation_definition)
// 创建展示
const presentation = await vault.createPresentation({
verifiableCredential: matchingVCs,
challenge: request.nonce,
holder: userDID
})
// 提交给验证者
await submitPresentation(presentation, request.callback_url)
Agent Gateway 验证展示:
// 智能体携带展示提出请求
const request = {
action: 'execute-trade',
presentation: vpToken
}
// Gateway 验证展示
const isValid = await agentGateway.verifyPresentation(request.presentation)
if (!isValid) throw new Error('凭证无效')
// 若有效则继续执行
快速上手
面向开发者
创建一份可验证展示:
npm install @affinidi/affinidi-tdk
import { VP } from '@affinidi/affinidi-tdk'
const presentation = await VP.create({
holder: 'did:webvh:alice.com',
verifiableCredential: [diplomaVC, employmentVC],
challenge: verifierChallenge,
privateKey: alicePrivateKey
})
console.log(presentation) // 已准备好发送给验证者
验证一份展示:
const isValid = await VP.verify(presentation, {
challenge: myChallenge
})
console.log(isValid) // true/false
面向架构师
何时应使用可验证展示:
- ✅ 需要证明持有者控制权的凭证共享
- ✅ 隐私保护型数据交换 (选择性披露)
- ✅ 无密码的跨组织认证
- ✅ 智能体授权证明
何时不应使用:
- ❌ 不需要持有者证明的持有者令牌 (Bearer Token) 场景
- ❌ 高频认证 (应改为缓存 VP 结果)
- ❌ 更简单的认证方式就足够的内部系统
延伸阅读
W3C 规范:
Affinidi 文档:
相关深度解析:
相关解决方案: