@jccdex/vc-vocabularies
v0.1.2
Published
VC vocabulary and issuance profiles for JCCDEx DID ecosystem
Readme
@jccdex/vc-vocabularies
VC 业务词汇表与签发 Profile,配合 @jccdex/did 的 issueVC() 使用。
安装
yarn add @jccdex/vc-vocabularies @jccdex/did支持的 VC 类型
| 常量 | 类型字符串 | 场景 |
|------|-----------|------|
| VC_TYPES.NFT_OWNERSHIP | NFTOwnership | 证明持有某个 NFT |
| VC_TYPES.NFT_USAGE_AUTHORIZATION | NFTUsageAuthorization | 授权他人使用 NFT |
| VC_TYPES.PHONE_VERIFICATION | PhoneVerificationCredential | 手机号实名验证 |
| VC_TYPES.FILE_ACCESS_AUTHORIZATION | FileAccessAuthorization | 授权访问 IPFS/IPNS 上的文件或目录 |
品牌 ID:"ccdao"(ccda.ooo)、"jdid"(jdid.cn)。
VC 签发示例
以下示例展示签发描述符(Descriptor)的组装方式。描述符传入
@jccdex/did的issueVC()后完成签名,最终产出标准 W3C VC JSON。
1. NFT 所有权(NFTOwnership)
证明某个地址持有特定 NFT。
import { issueVC } from "@jccdex/did";
import { buildNftOwnershipDescriptor } from "@jccdex/vc-vocabularies";
const descriptor = buildNftOwnershipDescriptor(
"ccdao",
{
id: holderDid,
owner: holderDid,
chainId: 1,
contractAddress: "0xAbCd...", // EVM 链填 contractAddress
// tokenName: "TokenSymbol", // SWTC 链改用 tokenName + nftIssuer
// nftIssuer: "rXxx...",
tokenId: "123",
standard: "ERC-721",
status: "Active"
},
{
id: `${holderDid}#vc-1`,
expirationDate: "2099-01-01T00:00:00.000Z"
}
);
const vcJSON = await issueVC(descriptor, { sign, keyDoc });{
"@context": [
"https://www.w3.org/2018/credentials/v1",
{
"NFTOwnership": "https://ccda.ooo/vc/v1#NFTOwnership",
"version": "https://ccda.ooo/did/v1",
"chainId": "https://ccda.ooo/did/v1#chainId",
"contractAddress": "https://ccda.ooo/did/v1#contractAddress",
"tokenName": "https://ccda.ooo/did/v1#tokenName",
"tokenId": "https://ccda.ooo/did/v1#tokenId",
"owner": "https://ccda.ooo/did/v1#owner",
"nftIssuer": "https://ccda.ooo/did/v1#nftIssuer",
"standard": "https://ccda.ooo/did/v1#standard",
"status": "https://ccda.ooo/did/v1#status"
}
],
"type": ["VerifiableCredential", "NFTOwnership"],
"id": "did:ethr:0x1234...#vc-1",
"issuer": "did:ethr:0x1234...",
"issuanceDate": "2026-06-23T00:00:00Z",
"expirationDate": "2099-01-01T00:00:00Z",
"credentialSubject": {
"id": "did:ethr:0x1234...",
"owner": "did:ethr:0x1234...",
"chainId": 1,
"contractAddress": "0xAbCd...",
"tokenId": "123",
"standard": "ERC-721",
"status": "Active"
}
}2. NFT 使用授权(NFTUsageAuthorization)
所有者授权他人在指定范围内使用 NFT(如商业用途、衍生作品等)。
import { buildNftUsageAuthorizationDescriptor } from "@jccdex/vc-vocabularies";
const descriptor = buildNftUsageAuthorizationDescriptor(
"ccdao",
{
id: granteeDid,
grantee: granteeDid,
owner: ownerDid,
chainId: 1,
contractAddress: "0xAbCd...",
tokenId: "123",
standard: "ERC-721",
status: "Active",
usageRights: "exclusive",
restrictions: {
commercial: true,
derivative: false,
sublicense: false,
territories: ["CN", "US"],
platforms: ["web", "mobile"]
}
},
{
id: `${ownerDid}#vc-auth-1`,
expirationDate: "2027-01-01T00:00:00.000Z"
}
);{
"@context": [
"https://www.w3.org/2018/credentials/v1",
{
"NFTUsageAuthorization": "https://ccda.ooo/vc/v1#NFTUsageAuthorization",
"version": "https://ccda.ooo/did/v1",
"grantee": "https://ccda.ooo/vc/v1#grantee",
"usageRights": "https://ccda.ooo/vc/v1#usageRights",
"restrictions": {
"@id": "https://ccda.ooo/vc/v1#restrictions",
"@context": {
"commercial": "https://ccda.ooo/vc/v1#commercial",
"derivative": "https://ccda.ooo/vc/v1#derivative",
"sublicense": "https://ccda.ooo/vc/v1#sublicense",
"territories": "https://ccda.ooo/vc/v1#territories",
"platforms": "https://ccda.ooo/vc/v1#platforms"
}
},
"chainId": "https://ccda.ooo/did/v1#chainId",
"contractAddress": "https://ccda.ooo/did/v1#contractAddress",
"tokenId": "https://ccda.ooo/did/v1#tokenId",
"owner": "https://ccda.ooo/did/v1#owner",
"standard": "https://ccda.ooo/did/v1#standard",
"status": "https://ccda.ooo/did/v1#status"
}
],
"type": ["VerifiableCredential", "NFTUsageAuthorization"],
"id": "did:ethr:0x1234...#vc-auth-1",
"issuer": "did:ethr:0x1234...",
"issuanceDate": "2026-06-23T00:00:00Z",
"expirationDate": "2027-01-01T00:00:00Z",
"credentialSubject": {
"id": "did:ethr:0xABCD...",
"grantee": "did:ethr:0xABCD...",
"owner": "did:ethr:0x1234...",
"chainId": 1,
"contractAddress": "0xAbCd...",
"tokenId": "123",
"standard": "ERC-721",
"status": "Active",
"usageRights": "exclusive",
"restrictions": {
"commercial": true,
"derivative": false,
"sublicense": false,
"territories": ["CN", "US"],
"platforms": ["web", "mobile"]
}
}
}3. 电话验证(PhoneVerificationCredential)
证明某个 DID 完成了手机号实名验证。
import { buildPhoneVerificationDescriptor } from "@jccdex/vc-vocabularies";
const descriptor = buildPhoneVerificationDescriptor(
"jdid",
{
id: userDid,
standard: "1.0",
status: "active",
verificationMethod: "sms-otp",
verificationProcess: {
verifiedAt: "2026-06-23T06:00:00Z",
updateAt: "2026-06-23T06:00:00Z",
verifierId: "did:ethr:0xVERIFIER..."
}
},
{
id: `${userDid}#vc-phone-1`,
expirationDate: "2027-06-23T00:00:00.000Z"
}
);{
"@context": [
"https://www.w3.org/2018/credentials/v1",
{
"PhoneVerificationCredential": "https://jdid.cn/vc/v1#PhoneVerificationCredential",
"version": "https://jdid.cn/did/v1",
"standard": "https://jdid.cn/did/v1#standard",
"status": "https://jdid.cn/did/v1#status",
"verificationMethod": "https://jdid.cn/did/v1#verificationMethod",
"verificationProcess": {
"@id": "https://jdid.cn/did/v1#verificationProcess",
"@context": {
"verifiedAt": "https://jdid.cn/did/v1#verifiedAt",
"updateAt": "https://jdid.cn/did/v1#updateAt",
"verifierId": "https://jdid.cn/did/v1#verifierId"
}
}
}
],
"type": ["VerifiableCredential", "PhoneVerificationCredential"],
"id": "did:ethr:0xUSER...#vc-phone-1",
"issuer": "did:ethr:0xVERIFIER...",
"issuanceDate": "2026-06-23T06:00:00Z",
"expirationDate": "2027-06-23T00:00:00Z",
"credentialSubject": {
"id": "did:ethr:0xUSER...",
"standard": "1.0",
"status": "active",
"verificationMethod": "sms-otp",
"verificationProcess": {
"verifiedAt": "2026-06-23T06:00:00Z",
"updateAt": "2026-06-23T06:00:00Z",
"verifierId": "did:ethr:0xVERIFIER..."
}
}
}4. 文件访问授权(FileAccessAuthorization)
基于 IPFS/IPNS 存储模型,授权他人访问链上文件或目录。
设计说明:
ipnsName:IPNS 固定入口,所有者更新文件后入口不变,被授权方始终可通过它访问最新内容。- 不存 CID:CID 随文件更新而变化;IPFS 内容寻址协议在取回内容时自动验证完整性,无需在 VC 层声明。
resourceType:区分授权粒度为单文件("file")或目录("directory")。resourcePath:目录授权时可指定子路径,为空则整个 IPNS 目录均可访问。
import { buildFileAccessAuthorizationDescriptor } from "@jccdex/vc-vocabularies";
const descriptor = buildFileAccessAuthorizationDescriptor(
"ccdao",
{
id: granteeDid,
grantee: granteeDid,
ipnsName: "k51qzi5uqu5dh9ihj4g2s7qopkrpwhvy49rlcdmqbcg58k8q8z32lkj7k8s",
resourceType: "directory",
resourcePath: "/reports/2026", // 省略则整个目录
owner: ownerDid,
accessRights: ["read", "download"],
accessRestrictions: {
expiresAt: "2027-06-23T00:00:00.000Z",
maxDownloads: 50, // 0 = 不限次数
ipWhitelist: [] // 空 = 不限 IP
},
standard: "1.0",
status: "active"
},
{
id: `${ownerDid}#vc-file-1`,
expirationDate: "2027-06-23T00:00:00.000Z"
}
);{
"@context": [
"https://www.w3.org/2018/credentials/v1",
{
"FileAccessAuthorization": "https://ccda.ooo/vc/v1#FileAccessAuthorization",
"version": "https://ccda.ooo/did/v1",
"grantee": "https://ccda.ooo/vc/v1#grantee",
"accessRights": "https://ccda.ooo/vc/v1#accessRights",
"accessRestrictions": {
"@id": "https://ccda.ooo/vc/v1#accessRestrictions",
"@context": {
"expiresAt": "https://ccda.ooo/vc/v1#expiresAt",
"maxDownloads": "https://ccda.ooo/vc/v1#maxDownloads",
"ipWhitelist": "https://ccda.ooo/vc/v1#ipWhitelist"
}
},
"ipnsName": "https://ccda.ooo/did/v1#ipnsName",
"resourceType": "https://ccda.ooo/did/v1#resourceType",
"resourcePath": "https://ccda.ooo/did/v1#resourcePath",
"owner": "https://ccda.ooo/did/v1#owner",
"standard": "https://ccda.ooo/did/v1#standard",
"status": "https://ccda.ooo/did/v1#status"
}
],
"type": ["VerifiableCredential", "FileAccessAuthorization"],
"id": "did:ethr:0x1234...#vc-file-1",
"issuer": "did:ethr:0x1234...",
"issuanceDate": "2026-06-23T06:34:00Z",
"expirationDate": "2027-06-23T00:00:00Z",
"credentialSubject": {
"id": "did:ethr:0xABCD...",
"grantee": "did:ethr:0xABCD...",
"ipnsName": "k51qzi5uqu5dh9ihj4g2s7qopkrpwhvy49rlcdmqbcg58k8q8z32lkj7k8s",
"resourceType": "directory",
"resourcePath": "/reports/2026",
"owner": "did:ethr:0x1234...",
"accessRights": ["read", "download"],
"accessRestrictions": {
"expiresAt": "2027-06-23T00:00:00Z",
"maxDownloads": 50,
"ipWhitelist": []
},
"standard": "1.0",
"status": "active"
}
}被授权方访问流程:
1. 持 VC 向资源服务方出示,服务方验证签名
2. 从 credentialSubject.ipnsName 解析当前 CID(IPNS 查询)
3. 按 resourcePath 限定可访问范围
4. 从 IPFS 取内容,协议层自动以 CID 验证完整性(内容寻址天然自验证)
5. 检查 accessRestrictions(到期时间、下载次数、IP 白名单)主要 API
| 导出 | 说明 |
|------|------|
| buildNftOwnershipDescriptor | NFT 所有权 VC 签发 Profile |
| buildNftUsageAuthorizationDescriptor | NFT 使用授权 VC 签发 Profile |
| buildPhoneVerificationDescriptor | 电话验证 VC 签发 Profile |
| buildFileAccessAuthorizationDescriptor | 文件访问授权 VC 签发 Profile |
| buildInlineContext | 按字段生成最小内联 @context |
| VC_TYPES | VC 业务类型常量 |
| getNftOwnershipContext 等 | 按品牌获取完整内联 context 对象 |
| CCDAO_*_CONTEXT / JDID_*_CONTEXT | 各类型品牌具名 context 常量 |
自定义 Descriptor
不经过 Profile 时,可用 buildInlineContext 按需裁剪字段,减小 VC 体积:
import {
buildInlineContext,
VC_TYPES,
W3C_VC_CONTEXT_URL
} from "@jccdex/vc-vocabularies";
// 仅携带本次 subject 用到的字段,忽略 contractAddress 等未用术语
const descriptor = {
types: [VC_TYPES.NFT_OWNERSHIP],
contexts: [
W3C_VC_CONTEXT_URL,
buildInlineContext("jdid", {
types: ["NFTOwnership"],
fields: ["chainId", "tokenId", "tokenName", "nftIssuer", "owner", "standard", "status"]
})
],
subject: { id: holderDid, /* ... */ },
id: `${holderDid}#vc-1`,
expirationDate: "2099-01-01T00:00:00.000Z"
};
// 文件授权最小 context(仅携带访问必需字段)
const fileDescriptor = {
types: [VC_TYPES.FILE_ACCESS_AUTHORIZATION],
contexts: [
W3C_VC_CONTEXT_URL,
buildInlineContext("ccdao", {
types: ["FileAccessAuthorization"],
fields: ["ipnsName", "resourceType", "resourcePath", "accessRights", "accessRestrictions"]
})
],
subject: { id: granteeDid, /* ... */ },
id: `${ownerDid}#vc-file-2`,
expirationDate: "2027-01-01T00:00:00.000Z"
};词汇表 JSON(高级)
验签缓存与品牌服务器部署使用 contexts/*.json,可通过子路径引用:
import ccdaoDidV1 from "@jccdex/vc-vocabularies/contexts/ccda-did-v1.json";
import jdidDidV1 from "@jccdex/vc-vocabularies/contexts/jdid-did-v1.json";
import ccdaoVcV1 from "@jccdex/vc-vocabularies/contexts/ccda-vc-v1.json";
import jdidVcV1 from "@jccdex/vc-vocabularies/contexts/jdid-vc-v1.json";一般由 @jccdex/did 内部引用,应用层通常无需直接 import。
开发
yarn build
yarn test相关包
@jccdex/did— DID 文档与 VC 签发/验签did_DApp— 参考集成示例
