标准规范 / 中级 / 10 分钟

可验证展示(OID4VP)

凭证如何被共享与验证——面向可验证展示的 OpenID 协议

可验证展示要解决的问题

你手上有一份可验证凭证(VC)——可能是学位证书、在职证明、智能体 Mandate——你要如何将它共享给验证者?

面临的挑战:

  • 证明你控制该凭证 —— 任何人都能复制一份 VC;验证者如何知道才是持有者?
  • 选择性披露 —— 如果你只想共享凭证的一部分怎么办?
  • 请求/响应流程 —— 验证者如何请求特定的凭证?
  • 隐私性 —— 如何在不暴露不必要数据的情况下共享凭证?

可验证展示(VP) 解决了这个问题:一种标准化的方式,将 VC 打包,并附带证明(持有者)正在出示它们的证据。

面向可验证展示的 OpenID 协议(OID4VP) 补充了协议层面的规则:验证者如何请求 VP,持有者如何响应。


什么是可验证展示?

可验证展示 是一个容器,它:

  1. 承载一份或多份可验证凭证
  2. 证明持有者控制这些凭证
  3. 将该展示绑定到特定验证者(防止重放攻击)

结构:

{
  "@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 文档:

相关深度解析:

相关解决方案:

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