@jccdex/vc-vocabularies 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +372 -18
- package/lib/index.d.ts +5 -0
- package/lib/index.js +8 -2
- package/lib/inline-contexts.d.ts +147 -10
- package/lib/inline-contexts.js +117 -9
- package/lib/profiles/file-access-authorization.d.ts +23 -0
- package/lib/profiles/file-access-authorization.js +35 -0
- package/lib/vc-types.d.ts +1 -0
- package/lib/vc-types.js +2 -1
- package/package.json +1 -1
- package/src/contexts/ccda-did-v1.json +5 -1
- package/src/contexts/ccda-vc-v1.json +9 -0
- package/src/contexts/jdid-did-v1.json +5 -1
- package/src/contexts/jdid-vc-v1.json +9 -0
package/README.md
CHANGED
|
@@ -8,85 +8,439 @@ VC 业务词汇表与签发 Profile,配合 [`@jccdex/did`](../did) 的 `issueV
|
|
|
8
8
|
yarn add @jccdex/vc-vocabularies @jccdex/did
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## 支持的 VC 类型
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
| 常量 | 类型字符串 | 场景 |
|
|
14
|
+
|------|-----------|------|
|
|
15
|
+
| `VC_TYPES.NFT_OWNERSHIP` | `NFTOwnership` | 证明持有某个 NFT |
|
|
16
|
+
| `VC_TYPES.NFT_USAGE_AUTHORIZATION` | `NFTUsageAuthorization` | 授权他人使用 NFT |
|
|
17
|
+
| `VC_TYPES.PHONE_VERIFICATION` | `PhoneVerificationCredential` | 手机号实名验证 |
|
|
18
|
+
| `VC_TYPES.FILE_ACCESS_AUTHORIZATION` | `FileAccessAuthorization` | 授权访问 IPFS/IPNS 上的文件或目录 |
|
|
19
|
+
|
|
20
|
+
品牌 ID:`"ccdao"`(`ccda.ooo`)、`"jdid"`(`jdid.cn`)。
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## VC 签发示例
|
|
25
|
+
|
|
26
|
+
> 以下示例展示签发描述符(Descriptor)的组装方式。描述符传入 `@jccdex/did` 的 `issueVC()` 后完成签名,最终产出标准 W3C VC JSON。
|
|
27
|
+
|
|
28
|
+
### 1. NFT 所有权(NFTOwnership)
|
|
29
|
+
|
|
30
|
+
证明某个地址持有特定 NFT。
|
|
14
31
|
|
|
15
32
|
```typescript
|
|
16
33
|
import { issueVC } from "@jccdex/did";
|
|
17
34
|
import { buildNftOwnershipDescriptor } from "@jccdex/vc-vocabularies";
|
|
18
35
|
|
|
19
36
|
const descriptor = buildNftOwnershipDescriptor(
|
|
20
|
-
"ccdao",
|
|
37
|
+
"ccdao",
|
|
21
38
|
{
|
|
22
39
|
id: holderDid,
|
|
23
40
|
owner: holderDid,
|
|
24
41
|
chainId: 1,
|
|
42
|
+
contractAddress: "0xAbCd...", // EVM 链填 contractAddress
|
|
43
|
+
// tokenName: "TokenSymbol", // SWTC 链改用 tokenName + nftIssuer
|
|
44
|
+
// nftIssuer: "rXxx...",
|
|
25
45
|
tokenId: "123",
|
|
26
|
-
contractAddress: "0x...",
|
|
27
46
|
standard: "ERC-721",
|
|
28
|
-
status: "Active"
|
|
47
|
+
status: "Active"
|
|
29
48
|
},
|
|
30
49
|
{
|
|
31
50
|
id: `${holderDid}#vc-1`,
|
|
32
|
-
expirationDate: "2099-01-01T00:00:00.000Z"
|
|
51
|
+
expirationDate: "2099-01-01T00:00:00.000Z"
|
|
33
52
|
}
|
|
34
53
|
);
|
|
35
54
|
|
|
36
55
|
const vcJSON = await issueVC(descriptor, { sign, keyDoc });
|
|
37
56
|
```
|
|
38
57
|
|
|
39
|
-
|
|
58
|
+
<details>
|
|
59
|
+
<summary>签发后的 VC JSON 结构</summary>
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"@context": [
|
|
64
|
+
"https://www.w3.org/2018/credentials/v1",
|
|
65
|
+
{
|
|
66
|
+
"NFTOwnership": "https://ccda.ooo/vc/v1#NFTOwnership",
|
|
67
|
+
"version": "https://ccda.ooo/did/v1",
|
|
68
|
+
"chainId": "https://ccda.ooo/did/v1#chainId",
|
|
69
|
+
"contractAddress": "https://ccda.ooo/did/v1#contractAddress",
|
|
70
|
+
"tokenName": "https://ccda.ooo/did/v1#tokenName",
|
|
71
|
+
"tokenId": "https://ccda.ooo/did/v1#tokenId",
|
|
72
|
+
"owner": "https://ccda.ooo/did/v1#owner",
|
|
73
|
+
"nftIssuer": "https://ccda.ooo/did/v1#nftIssuer",
|
|
74
|
+
"standard": "https://ccda.ooo/did/v1#standard",
|
|
75
|
+
"status": "https://ccda.ooo/did/v1#status"
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"type": ["VerifiableCredential", "NFTOwnership"],
|
|
79
|
+
"id": "did:ethr:0x1234...#vc-1",
|
|
80
|
+
"issuer": "did:ethr:0x1234...",
|
|
81
|
+
"issuanceDate": "2026-06-23T00:00:00Z",
|
|
82
|
+
"expirationDate": "2099-01-01T00:00:00Z",
|
|
83
|
+
"credentialSubject": {
|
|
84
|
+
"id": "did:ethr:0x1234...",
|
|
85
|
+
"owner": "did:ethr:0x1234...",
|
|
86
|
+
"chainId": 1,
|
|
87
|
+
"contractAddress": "0xAbCd...",
|
|
88
|
+
"tokenId": "123",
|
|
89
|
+
"standard": "ERC-721",
|
|
90
|
+
"status": "Active"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
</details>
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
### 2. NFT 使用授权(NFTUsageAuthorization)
|
|
100
|
+
|
|
101
|
+
所有者授权他人在指定范围内使用 NFT(如商业用途、衍生作品等)。
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { buildNftUsageAuthorizationDescriptor } from "@jccdex/vc-vocabularies";
|
|
105
|
+
|
|
106
|
+
const descriptor = buildNftUsageAuthorizationDescriptor(
|
|
107
|
+
"ccdao",
|
|
108
|
+
{
|
|
109
|
+
id: granteeDid,
|
|
110
|
+
grantee: granteeDid,
|
|
111
|
+
owner: ownerDid,
|
|
112
|
+
chainId: 1,
|
|
113
|
+
contractAddress: "0xAbCd...",
|
|
114
|
+
tokenId: "123",
|
|
115
|
+
standard: "ERC-721",
|
|
116
|
+
status: "Active",
|
|
117
|
+
usageRights: "exclusive",
|
|
118
|
+
restrictions: {
|
|
119
|
+
commercial: true,
|
|
120
|
+
derivative: false,
|
|
121
|
+
sublicense: false,
|
|
122
|
+
territories: ["CN", "US"],
|
|
123
|
+
platforms: ["web", "mobile"]
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: `${ownerDid}#vc-auth-1`,
|
|
128
|
+
expirationDate: "2027-01-01T00:00:00.000Z"
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
<details>
|
|
134
|
+
<summary>签发后的 VC JSON 结构</summary>
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"@context": [
|
|
139
|
+
"https://www.w3.org/2018/credentials/v1",
|
|
140
|
+
{
|
|
141
|
+
"NFTUsageAuthorization": "https://ccda.ooo/vc/v1#NFTUsageAuthorization",
|
|
142
|
+
"version": "https://ccda.ooo/did/v1",
|
|
143
|
+
"grantee": "https://ccda.ooo/vc/v1#grantee",
|
|
144
|
+
"usageRights": "https://ccda.ooo/vc/v1#usageRights",
|
|
145
|
+
"restrictions": {
|
|
146
|
+
"@id": "https://ccda.ooo/vc/v1#restrictions",
|
|
147
|
+
"@context": {
|
|
148
|
+
"commercial": "https://ccda.ooo/vc/v1#commercial",
|
|
149
|
+
"derivative": "https://ccda.ooo/vc/v1#derivative",
|
|
150
|
+
"sublicense": "https://ccda.ooo/vc/v1#sublicense",
|
|
151
|
+
"territories": "https://ccda.ooo/vc/v1#territories",
|
|
152
|
+
"platforms": "https://ccda.ooo/vc/v1#platforms"
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"chainId": "https://ccda.ooo/did/v1#chainId",
|
|
156
|
+
"contractAddress": "https://ccda.ooo/did/v1#contractAddress",
|
|
157
|
+
"tokenId": "https://ccda.ooo/did/v1#tokenId",
|
|
158
|
+
"owner": "https://ccda.ooo/did/v1#owner",
|
|
159
|
+
"standard": "https://ccda.ooo/did/v1#standard",
|
|
160
|
+
"status": "https://ccda.ooo/did/v1#status"
|
|
161
|
+
}
|
|
162
|
+
],
|
|
163
|
+
"type": ["VerifiableCredential", "NFTUsageAuthorization"],
|
|
164
|
+
"id": "did:ethr:0x1234...#vc-auth-1",
|
|
165
|
+
"issuer": "did:ethr:0x1234...",
|
|
166
|
+
"issuanceDate": "2026-06-23T00:00:00Z",
|
|
167
|
+
"expirationDate": "2027-01-01T00:00:00Z",
|
|
168
|
+
"credentialSubject": {
|
|
169
|
+
"id": "did:ethr:0xABCD...",
|
|
170
|
+
"grantee": "did:ethr:0xABCD...",
|
|
171
|
+
"owner": "did:ethr:0x1234...",
|
|
172
|
+
"chainId": 1,
|
|
173
|
+
"contractAddress": "0xAbCd...",
|
|
174
|
+
"tokenId": "123",
|
|
175
|
+
"standard": "ERC-721",
|
|
176
|
+
"status": "Active",
|
|
177
|
+
"usageRights": "exclusive",
|
|
178
|
+
"restrictions": {
|
|
179
|
+
"commercial": true,
|
|
180
|
+
"derivative": false,
|
|
181
|
+
"sublicense": false,
|
|
182
|
+
"territories": ["CN", "US"],
|
|
183
|
+
"platforms": ["web", "mobile"]
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
</details>
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
### 3. 电话验证(PhoneVerificationCredential)
|
|
194
|
+
|
|
195
|
+
证明某个 DID 完成了手机号实名验证。
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import { buildPhoneVerificationDescriptor } from "@jccdex/vc-vocabularies";
|
|
199
|
+
|
|
200
|
+
const descriptor = buildPhoneVerificationDescriptor(
|
|
201
|
+
"jdid",
|
|
202
|
+
{
|
|
203
|
+
id: userDid,
|
|
204
|
+
standard: "1.0",
|
|
205
|
+
status: "active",
|
|
206
|
+
verificationMethod: "sms-otp",
|
|
207
|
+
verificationProcess: {
|
|
208
|
+
verifiedAt: "2026-06-23T06:00:00Z",
|
|
209
|
+
updateAt: "2026-06-23T06:00:00Z",
|
|
210
|
+
verifierId: "did:ethr:0xVERIFIER..."
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: `${userDid}#vc-phone-1`,
|
|
215
|
+
expirationDate: "2027-06-23T00:00:00.000Z"
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
<details>
|
|
221
|
+
<summary>签发后的 VC JSON 结构</summary>
|
|
222
|
+
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"@context": [
|
|
226
|
+
"https://www.w3.org/2018/credentials/v1",
|
|
227
|
+
{
|
|
228
|
+
"PhoneVerificationCredential": "https://jdid.cn/vc/v1#PhoneVerificationCredential",
|
|
229
|
+
"version": "https://jdid.cn/did/v1",
|
|
230
|
+
"standard": "https://jdid.cn/did/v1#standard",
|
|
231
|
+
"status": "https://jdid.cn/did/v1#status",
|
|
232
|
+
"verificationMethod": "https://jdid.cn/did/v1#verificationMethod",
|
|
233
|
+
"verificationProcess": {
|
|
234
|
+
"@id": "https://jdid.cn/did/v1#verificationProcess",
|
|
235
|
+
"@context": {
|
|
236
|
+
"verifiedAt": "https://jdid.cn/did/v1#verifiedAt",
|
|
237
|
+
"updateAt": "https://jdid.cn/did/v1#updateAt",
|
|
238
|
+
"verifierId": "https://jdid.cn/did/v1#verifierId"
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
],
|
|
243
|
+
"type": ["VerifiableCredential", "PhoneVerificationCredential"],
|
|
244
|
+
"id": "did:ethr:0xUSER...#vc-phone-1",
|
|
245
|
+
"issuer": "did:ethr:0xVERIFIER...",
|
|
246
|
+
"issuanceDate": "2026-06-23T06:00:00Z",
|
|
247
|
+
"expirationDate": "2027-06-23T00:00:00Z",
|
|
248
|
+
"credentialSubject": {
|
|
249
|
+
"id": "did:ethr:0xUSER...",
|
|
250
|
+
"standard": "1.0",
|
|
251
|
+
"status": "active",
|
|
252
|
+
"verificationMethod": "sms-otp",
|
|
253
|
+
"verificationProcess": {
|
|
254
|
+
"verifiedAt": "2026-06-23T06:00:00Z",
|
|
255
|
+
"updateAt": "2026-06-23T06:00:00Z",
|
|
256
|
+
"verifierId": "did:ethr:0xVERIFIER..."
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
</details>
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
### 4. 文件访问授权(FileAccessAuthorization)
|
|
267
|
+
|
|
268
|
+
基于 IPFS/IPNS 存储模型,授权他人访问链上文件或目录。
|
|
269
|
+
|
|
270
|
+
**设计说明:**
|
|
271
|
+
- `ipnsName`:IPNS 固定入口,所有者更新文件后入口不变,被授权方始终可通过它访问最新内容。
|
|
272
|
+
- 不存 CID:CID 随文件更新而变化;IPFS 内容寻址协议在取回内容时自动验证完整性,无需在 VC 层声明。
|
|
273
|
+
- `resourceType`:区分授权粒度为单文件(`"file"`)或目录(`"directory"`)。
|
|
274
|
+
- `resourcePath`:目录授权时可指定子路径,为空则整个 IPNS 目录均可访问。
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { buildFileAccessAuthorizationDescriptor } from "@jccdex/vc-vocabularies";
|
|
278
|
+
|
|
279
|
+
const descriptor = buildFileAccessAuthorizationDescriptor(
|
|
280
|
+
"ccdao",
|
|
281
|
+
{
|
|
282
|
+
id: granteeDid,
|
|
283
|
+
grantee: granteeDid,
|
|
284
|
+
ipnsName: "k51qzi5uqu5dh9ihj4g2s7qopkrpwhvy49rlcdmqbcg58k8q8z32lkj7k8s",
|
|
285
|
+
resourceType: "directory",
|
|
286
|
+
resourcePath: "/reports/2026", // 省略则整个目录
|
|
287
|
+
owner: ownerDid,
|
|
288
|
+
accessRights: ["read", "download"],
|
|
289
|
+
accessRestrictions: {
|
|
290
|
+
expiresAt: "2027-06-23T00:00:00.000Z",
|
|
291
|
+
maxDownloads: 50, // 0 = 不限次数
|
|
292
|
+
ipWhitelist: [] // 空 = 不限 IP
|
|
293
|
+
},
|
|
294
|
+
standard: "1.0",
|
|
295
|
+
status: "active"
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
id: `${ownerDid}#vc-file-1`,
|
|
299
|
+
expirationDate: "2027-06-23T00:00:00.000Z"
|
|
300
|
+
}
|
|
301
|
+
);
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
<details>
|
|
305
|
+
<summary>签发后的 VC JSON 结构</summary>
|
|
306
|
+
|
|
307
|
+
```json
|
|
308
|
+
{
|
|
309
|
+
"@context": [
|
|
310
|
+
"https://www.w3.org/2018/credentials/v1",
|
|
311
|
+
{
|
|
312
|
+
"FileAccessAuthorization": "https://ccda.ooo/vc/v1#FileAccessAuthorization",
|
|
313
|
+
"version": "https://ccda.ooo/did/v1",
|
|
314
|
+
"grantee": "https://ccda.ooo/vc/v1#grantee",
|
|
315
|
+
"accessRights": "https://ccda.ooo/vc/v1#accessRights",
|
|
316
|
+
"accessRestrictions": {
|
|
317
|
+
"@id": "https://ccda.ooo/vc/v1#accessRestrictions",
|
|
318
|
+
"@context": {
|
|
319
|
+
"expiresAt": "https://ccda.ooo/vc/v1#expiresAt",
|
|
320
|
+
"maxDownloads": "https://ccda.ooo/vc/v1#maxDownloads",
|
|
321
|
+
"ipWhitelist": "https://ccda.ooo/vc/v1#ipWhitelist"
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
"ipnsName": "https://ccda.ooo/did/v1#ipnsName",
|
|
325
|
+
"resourceType": "https://ccda.ooo/did/v1#resourceType",
|
|
326
|
+
"resourcePath": "https://ccda.ooo/did/v1#resourcePath",
|
|
327
|
+
"owner": "https://ccda.ooo/did/v1#owner",
|
|
328
|
+
"standard": "https://ccda.ooo/did/v1#standard",
|
|
329
|
+
"status": "https://ccda.ooo/did/v1#status"
|
|
330
|
+
}
|
|
331
|
+
],
|
|
332
|
+
"type": ["VerifiableCredential", "FileAccessAuthorization"],
|
|
333
|
+
"id": "did:ethr:0x1234...#vc-file-1",
|
|
334
|
+
"issuer": "did:ethr:0x1234...",
|
|
335
|
+
"issuanceDate": "2026-06-23T06:34:00Z",
|
|
336
|
+
"expirationDate": "2027-06-23T00:00:00Z",
|
|
337
|
+
"credentialSubject": {
|
|
338
|
+
"id": "did:ethr:0xABCD...",
|
|
339
|
+
"grantee": "did:ethr:0xABCD...",
|
|
340
|
+
"ipnsName": "k51qzi5uqu5dh9ihj4g2s7qopkrpwhvy49rlcdmqbcg58k8q8z32lkj7k8s",
|
|
341
|
+
"resourceType": "directory",
|
|
342
|
+
"resourcePath": "/reports/2026",
|
|
343
|
+
"owner": "did:ethr:0x1234...",
|
|
344
|
+
"accessRights": ["read", "download"],
|
|
345
|
+
"accessRestrictions": {
|
|
346
|
+
"expiresAt": "2027-06-23T00:00:00Z",
|
|
347
|
+
"maxDownloads": 50,
|
|
348
|
+
"ipWhitelist": []
|
|
349
|
+
},
|
|
350
|
+
"standard": "1.0",
|
|
351
|
+
"status": "active"
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
</details>
|
|
357
|
+
|
|
358
|
+
**被授权方访问流程:**
|
|
359
|
+
|
|
360
|
+
```
|
|
361
|
+
1. 持 VC 向资源服务方出示,服务方验证签名
|
|
362
|
+
2. 从 credentialSubject.ipnsName 解析当前 CID(IPNS 查询)
|
|
363
|
+
3. 按 resourcePath 限定可访问范围
|
|
364
|
+
4. 从 IPFS 取内容,协议层自动以 CID 验证完整性(内容寻址天然自验证)
|
|
365
|
+
5. 检查 accessRestrictions(到期时间、下载次数、IP 白名单)
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
---
|
|
40
369
|
|
|
41
370
|
## 主要 API
|
|
42
371
|
|
|
43
372
|
| 导出 | 说明 |
|
|
44
373
|
|------|------|
|
|
45
|
-
| `buildNftOwnershipDescriptor` | NFT 所有权 VC |
|
|
46
|
-
| `buildNftUsageAuthorizationDescriptor` | NFT
|
|
47
|
-
| `buildPhoneVerificationDescriptor` | 电话验证 VC |
|
|
374
|
+
| `buildNftOwnershipDescriptor` | NFT 所有权 VC 签发 Profile |
|
|
375
|
+
| `buildNftUsageAuthorizationDescriptor` | NFT 使用授权 VC 签发 Profile |
|
|
376
|
+
| `buildPhoneVerificationDescriptor` | 电话验证 VC 签发 Profile |
|
|
377
|
+
| `buildFileAccessAuthorizationDescriptor` | 文件访问授权 VC 签发 Profile |
|
|
48
378
|
| `buildInlineContext` | 按字段生成最小内联 `@context` |
|
|
49
379
|
| `VC_TYPES` | VC 业务类型常量 |
|
|
50
|
-
| `getNftOwnershipContext` 等 |
|
|
380
|
+
| `getNftOwnershipContext` 等 | 按品牌获取完整内联 context 对象 |
|
|
381
|
+
| `CCDAO_*_CONTEXT` / `JDID_*_CONTEXT` | 各类型品牌具名 context 常量 |
|
|
51
382
|
|
|
52
|
-
|
|
383
|
+
---
|
|
53
384
|
|
|
54
385
|
## 自定义 Descriptor
|
|
55
386
|
|
|
56
|
-
不经过 Profile
|
|
387
|
+
不经过 Profile 时,可用 `buildInlineContext` 按需裁剪字段,减小 VC 体积:
|
|
57
388
|
|
|
58
389
|
```typescript
|
|
59
390
|
import {
|
|
60
391
|
buildInlineContext,
|
|
61
392
|
VC_TYPES,
|
|
62
|
-
W3C_VC_CONTEXT_URL
|
|
393
|
+
W3C_VC_CONTEXT_URL
|
|
63
394
|
} from "@jccdex/vc-vocabularies";
|
|
64
395
|
|
|
396
|
+
// 仅携带本次 subject 用到的字段,忽略 contractAddress 等未用术语
|
|
65
397
|
const descriptor = {
|
|
66
398
|
types: [VC_TYPES.NFT_OWNERSHIP],
|
|
67
399
|
contexts: [
|
|
68
400
|
W3C_VC_CONTEXT_URL,
|
|
69
401
|
buildInlineContext("jdid", {
|
|
70
402
|
types: ["NFTOwnership"],
|
|
71
|
-
fields: ["chainId", "tokenId", "tokenName", "nftIssuer", "owner", "standard", "status"]
|
|
72
|
-
})
|
|
403
|
+
fields: ["chainId", "tokenId", "tokenName", "nftIssuer", "owner", "standard", "status"]
|
|
404
|
+
})
|
|
73
405
|
],
|
|
74
406
|
subject: { id: holderDid, /* ... */ },
|
|
75
407
|
id: `${holderDid}#vc-1`,
|
|
76
|
-
expirationDate: "2099-01-01T00:00:00.000Z"
|
|
408
|
+
expirationDate: "2099-01-01T00:00:00.000Z"
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
// 文件授权最小 context(仅携带访问必需字段)
|
|
412
|
+
const fileDescriptor = {
|
|
413
|
+
types: [VC_TYPES.FILE_ACCESS_AUTHORIZATION],
|
|
414
|
+
contexts: [
|
|
415
|
+
W3C_VC_CONTEXT_URL,
|
|
416
|
+
buildInlineContext("ccdao", {
|
|
417
|
+
types: ["FileAccessAuthorization"],
|
|
418
|
+
fields: ["ipnsName", "resourceType", "resourcePath", "accessRights", "accessRestrictions"]
|
|
419
|
+
})
|
|
420
|
+
],
|
|
421
|
+
subject: { id: granteeDid, /* ... */ },
|
|
422
|
+
id: `${ownerDid}#vc-file-2`,
|
|
423
|
+
expirationDate: "2027-01-01T00:00:00.000Z"
|
|
77
424
|
};
|
|
78
425
|
```
|
|
79
426
|
|
|
427
|
+
---
|
|
428
|
+
|
|
80
429
|
## 词汇表 JSON(高级)
|
|
81
430
|
|
|
82
431
|
验签缓存与品牌服务器部署使用 `contexts/*.json`,可通过子路径引用:
|
|
83
432
|
|
|
84
433
|
```javascript
|
|
85
|
-
import
|
|
434
|
+
import ccdaoDidV1 from "@jccdex/vc-vocabularies/contexts/ccda-did-v1.json";
|
|
435
|
+
import jdidDidV1 from "@jccdex/vc-vocabularies/contexts/jdid-did-v1.json";
|
|
436
|
+
import ccdaoVcV1 from "@jccdex/vc-vocabularies/contexts/ccda-vc-v1.json";
|
|
437
|
+
import jdidVcV1 from "@jccdex/vc-vocabularies/contexts/jdid-vc-v1.json";
|
|
86
438
|
```
|
|
87
439
|
|
|
88
440
|
一般由 `@jccdex/did` 内部引用,应用层通常无需直接 import。
|
|
89
441
|
|
|
442
|
+
---
|
|
443
|
+
|
|
90
444
|
## 开发
|
|
91
445
|
|
|
92
446
|
```bash
|
package/lib/index.d.ts
CHANGED
|
@@ -12,9 +12,11 @@ export * from "./vc-types.js";
|
|
|
12
12
|
export { buildNftOwnershipDescriptor } from "./profiles/nft-ownership.js";
|
|
13
13
|
export { buildNftUsageAuthorizationDescriptor } from "./profiles/nft-usage-authorization.js";
|
|
14
14
|
export { buildPhoneVerificationDescriptor } from "./profiles/phone-verification.js";
|
|
15
|
+
export { buildFileAccessAuthorizationDescriptor } from "./profiles/file-access-authorization.js";
|
|
15
16
|
import { buildNftOwnershipDescriptor } from "./profiles/nft-ownership.js";
|
|
16
17
|
import { buildNftUsageAuthorizationDescriptor } from "./profiles/nft-usage-authorization.js";
|
|
17
18
|
import { buildPhoneVerificationDescriptor } from "./profiles/phone-verification.js";
|
|
19
|
+
import { buildFileAccessAuthorizationDescriptor } from "./profiles/file-access-authorization.js";
|
|
18
20
|
/** 按业务场景索引的 Profile 注册表,便于 DApp 动态选择签发模板 */
|
|
19
21
|
export declare const profiles: {
|
|
20
22
|
nftOwnership: {
|
|
@@ -26,4 +28,7 @@ export declare const profiles: {
|
|
|
26
28
|
phoneVerification: {
|
|
27
29
|
buildDescriptor: typeof buildPhoneVerificationDescriptor;
|
|
28
30
|
};
|
|
31
|
+
fileAccessAuthorization: {
|
|
32
|
+
buildDescriptor: typeof buildFileAccessAuthorizationDescriptor;
|
|
33
|
+
};
|
|
29
34
|
};
|
package/lib/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.profiles = exports.buildPhoneVerificationDescriptor = exports.buildNftUsageAuthorizationDescriptor = exports.buildNftOwnershipDescriptor = void 0;
|
|
17
|
+
exports.profiles = exports.buildFileAccessAuthorizationDescriptor = exports.buildPhoneVerificationDescriptor = exports.buildNftUsageAuthorizationDescriptor = exports.buildNftOwnershipDescriptor = void 0;
|
|
18
18
|
/**
|
|
19
19
|
* @jccdex/vc-vocabularies 包入口。
|
|
20
20
|
*
|
|
@@ -32,14 +32,20 @@ var nft_usage_authorization_js_1 = require("./profiles/nft-usage-authorization.j
|
|
|
32
32
|
Object.defineProperty(exports, "buildNftUsageAuthorizationDescriptor", { enumerable: true, get: function () { return nft_usage_authorization_js_1.buildNftUsageAuthorizationDescriptor; } });
|
|
33
33
|
var phone_verification_js_1 = require("./profiles/phone-verification.js");
|
|
34
34
|
Object.defineProperty(exports, "buildPhoneVerificationDescriptor", { enumerable: true, get: function () { return phone_verification_js_1.buildPhoneVerificationDescriptor; } });
|
|
35
|
+
var file_access_authorization_js_1 = require("./profiles/file-access-authorization.js");
|
|
36
|
+
Object.defineProperty(exports, "buildFileAccessAuthorizationDescriptor", { enumerable: true, get: function () { return file_access_authorization_js_1.buildFileAccessAuthorizationDescriptor; } });
|
|
35
37
|
const nft_ownership_js_2 = require("./profiles/nft-ownership.js");
|
|
36
38
|
const nft_usage_authorization_js_2 = require("./profiles/nft-usage-authorization.js");
|
|
37
39
|
const phone_verification_js_2 = require("./profiles/phone-verification.js");
|
|
40
|
+
const file_access_authorization_js_2 = require("./profiles/file-access-authorization.js");
|
|
38
41
|
/** 按业务场景索引的 Profile 注册表,便于 DApp 动态选择签发模板 */
|
|
39
42
|
exports.profiles = {
|
|
40
43
|
nftOwnership: { buildDescriptor: nft_ownership_js_2.buildNftOwnershipDescriptor },
|
|
41
44
|
nftUsageAuthorization: {
|
|
42
45
|
buildDescriptor: nft_usage_authorization_js_2.buildNftUsageAuthorizationDescriptor
|
|
43
46
|
},
|
|
44
|
-
phoneVerification: { buildDescriptor: phone_verification_js_2.buildPhoneVerificationDescriptor }
|
|
47
|
+
phoneVerification: { buildDescriptor: phone_verification_js_2.buildPhoneVerificationDescriptor },
|
|
48
|
+
fileAccessAuthorization: {
|
|
49
|
+
buildDescriptor: file_access_authorization_js_2.buildFileAccessAuthorizationDescriptor
|
|
50
|
+
}
|
|
45
51
|
};
|
package/lib/inline-contexts.d.ts
CHANGED
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 品牌内联 context 定义。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* ## 设计原则
|
|
5
|
+
* - 品牌(ccdao / jdid)仅区分国内外词汇表 URL 命名空间,术语字段集相同。
|
|
6
|
+
* - 链差异(EVM 用 contractAddress、SWTC 用 tokenName/nftIssuer)由应用层 subject 决定,
|
|
7
|
+
* 不在词汇层按品牌或链裁剪。
|
|
8
|
+
* - 维护原则:只增不删、不改 URI(append-only)。
|
|
7
9
|
*
|
|
8
|
-
*
|
|
10
|
+
* ## 命名空间规则
|
|
11
|
+
* - `did#` 前缀:跨 VC 类型通用的实体属性(owner、status、chainId 等)
|
|
12
|
+
* - `vc#` 前缀:业务专属术语(grantee、usageRights、accessRights 等)
|
|
9
13
|
*/
|
|
10
14
|
import type { BrandId } from "./types.js";
|
|
15
|
+
/** W3C Verifiable Credentials 标准 context URL,固定作为 @context 数组第一项 */
|
|
11
16
|
export declare const W3C_VC_CONTEXT_URL = "https://www.w3.org/2018/credentials/v1";
|
|
12
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* 构建 NFT 所有权 VC 的内联 @context 术语集。
|
|
19
|
+
*
|
|
20
|
+
* 链差异(EVM / SWTC)体现在 subject 字段选择上:
|
|
21
|
+
* - EVM:填 `contractAddress`
|
|
22
|
+
* - SWTC:填 `tokenName` + `nftIssuer`
|
|
23
|
+
* 词汇层两者均声明,由 DApp 按链选填。
|
|
24
|
+
*
|
|
25
|
+
* @param brand - 品牌 ID,决定 URL 命名空间
|
|
26
|
+
* @returns 内联 @context 对象,可直接放入 VC 的 `@context` 数组
|
|
27
|
+
*/
|
|
13
28
|
export declare function buildNftOwnershipContext(brand: BrandId): {
|
|
14
29
|
NFTOwnership: string;
|
|
15
30
|
version: "https://ccda.ooo/did/v1" | "https://jdid.cn/did/v1";
|
|
@@ -22,7 +37,15 @@ export declare function buildNftOwnershipContext(brand: BrandId): {
|
|
|
22
37
|
standard: string;
|
|
23
38
|
status: string;
|
|
24
39
|
};
|
|
25
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* 构建 NFT 使用授权 VC 的内联 @context 术语集。
|
|
42
|
+
*
|
|
43
|
+
* `restrictions` 是嵌套 context 对象,其子字段(commercial、derivative 等)
|
|
44
|
+
* 仅在该嵌套对象内有效,不与外层字段命名冲突。
|
|
45
|
+
*
|
|
46
|
+
* @param brand - 品牌 ID,决定 URL 命名空间
|
|
47
|
+
* @returns 内联 @context 对象,含嵌套 restrictions 术语集
|
|
48
|
+
*/
|
|
26
49
|
export declare function buildNftUsageAuthorizationContext(brand: BrandId): {
|
|
27
50
|
NFTUsageAuthorization: string;
|
|
28
51
|
version: "https://ccda.ooo/did/v1" | "https://jdid.cn/did/v1";
|
|
@@ -47,12 +70,27 @@ export declare function buildNftUsageAuthorizationContext(brand: BrandId): {
|
|
|
47
70
|
standard: string;
|
|
48
71
|
status: string;
|
|
49
72
|
};
|
|
50
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* 构建 DID 文档 version 字段的内联 @context。
|
|
75
|
+
*
|
|
76
|
+
* 仅包含 `version`(命名空间根 URL)与 `credentials` 两个术语,
|
|
77
|
+
* 用于 DID 文档自身,不是 VC subject 字段。
|
|
78
|
+
*
|
|
79
|
+
* @param brand - 品牌 ID,决定 URL 命名空间
|
|
80
|
+
* @returns 含 version / credentials 的最小 context 对象
|
|
81
|
+
*/
|
|
51
82
|
export declare function buildVersionContext(brand: BrandId): {
|
|
52
83
|
version: "https://ccda.ooo/did/v1" | "https://jdid.cn/did/v1";
|
|
53
84
|
credentials: string;
|
|
54
85
|
};
|
|
55
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* 构建电话验证 VC 的内联 @context 术语集。
|
|
88
|
+
*
|
|
89
|
+
* `verificationProcess` 是嵌套 context,记录验证操作的时间、操作方等审计信息。
|
|
90
|
+
*
|
|
91
|
+
* @param brand - 品牌 ID,决定 URL 命名空间
|
|
92
|
+
* @returns 内联 @context 对象,含嵌套 verificationProcess 术语集
|
|
93
|
+
*/
|
|
56
94
|
export declare function buildPhoneVerificationContext(brand: BrandId): {
|
|
57
95
|
PhoneVerificationCredential: string;
|
|
58
96
|
version: "https://ccda.ooo/did/v1" | "https://jdid.cn/did/v1";
|
|
@@ -68,11 +106,54 @@ export declare function buildPhoneVerificationContext(brand: BrandId): {
|
|
|
68
106
|
};
|
|
69
107
|
};
|
|
70
108
|
};
|
|
71
|
-
/**
|
|
109
|
+
/**
|
|
110
|
+
* 构建文件访问授权 VC 的内联 @context 术语集(基于 IPFS/IPNS 存储模型)。
|
|
111
|
+
*
|
|
112
|
+
* ## 字段设计说明
|
|
113
|
+
*
|
|
114
|
+
* | 字段 | 命名空间 | 说明 |
|
|
115
|
+
* |----------------|----------|------------------------------------------------------------|
|
|
116
|
+
* | ipnsName | did# | IPNS 固定入口,所有者更新文件后仍指向同一名称 |
|
|
117
|
+
* | resourceType | did# | 授权粒度:"file" \| "directory" |
|
|
118
|
+
* | resourcePath | did# | 目录授权时的子路径限制,为空则整个 IPNS 目录均可访问 |
|
|
119
|
+
* | grantee | vc# | 被授权方 DID |
|
|
120
|
+
* | accessRights | vc# | 权限列表,如 ["read", "download"] |
|
|
121
|
+
* | accessRestrictions | vc# | 访问限制(嵌套对象):到期时间、最大下载次数、IP 白名单 |
|
|
122
|
+
*
|
|
123
|
+
* ## 为何不存 CID
|
|
124
|
+
* CID 随文件更新而变化,存入 VC 后会立即过时;
|
|
125
|
+
* 内容完整性由 IPFS 内容寻址协议层自动保证(取回内容时重算 CID 对比),
|
|
126
|
+
* 无需在凭证层额外声明。
|
|
127
|
+
*
|
|
128
|
+
* @param brand - 品牌 ID,决定 URL 命名空间
|
|
129
|
+
* @returns 内联 @context 对象,含嵌套 accessRestrictions 术语集
|
|
130
|
+
*/
|
|
131
|
+
export declare function buildFileAccessAuthorizationContext(brand: BrandId): {
|
|
132
|
+
FileAccessAuthorization: string;
|
|
133
|
+
version: "https://ccda.ooo/did/v1" | "https://jdid.cn/did/v1";
|
|
134
|
+
grantee: string;
|
|
135
|
+
accessRights: string;
|
|
136
|
+
accessRestrictions: {
|
|
137
|
+
"@id": string;
|
|
138
|
+
"@context": {
|
|
139
|
+
expiresAt: string;
|
|
140
|
+
maxDownloads: string;
|
|
141
|
+
ipWhitelist: string;
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
ipnsName: string;
|
|
145
|
+
resourceType: string;
|
|
146
|
+
resourcePath: string;
|
|
147
|
+
owner: string;
|
|
148
|
+
standard: string;
|
|
149
|
+
status: string;
|
|
150
|
+
};
|
|
151
|
+
/** 便捷 getter 别名,供 DApp / Profile 使用(与 build* 函数等价) */
|
|
72
152
|
export declare const getNftOwnershipContext: typeof buildNftOwnershipContext;
|
|
73
153
|
export declare const getNftUsageAuthorizationContext: typeof buildNftUsageAuthorizationContext;
|
|
74
154
|
export declare const getVersionContext: typeof buildVersionContext;
|
|
75
155
|
export declare const getPhoneVerificationContext: typeof buildPhoneVerificationContext;
|
|
156
|
+
export declare const getFileAccessAuthorizationContext: typeof buildFileAccessAuthorizationContext;
|
|
76
157
|
/** 向后兼容的具名导出(结构已统一,仅 URL 命名空间不同) */
|
|
77
158
|
export declare const CCDAO_NFT_OWNERSHIP_CONTEXT: {
|
|
78
159
|
NFTOwnership: string;
|
|
@@ -184,14 +265,70 @@ export declare const JDID_PHONE_VERIFICATION_CONTEXT: {
|
|
|
184
265
|
};
|
|
185
266
|
};
|
|
186
267
|
};
|
|
268
|
+
export declare const CCDAO_FILE_ACCESS_AUTHORIZATION_CONTEXT: {
|
|
269
|
+
FileAccessAuthorization: string;
|
|
270
|
+
version: "https://ccda.ooo/did/v1" | "https://jdid.cn/did/v1";
|
|
271
|
+
grantee: string;
|
|
272
|
+
accessRights: string;
|
|
273
|
+
accessRestrictions: {
|
|
274
|
+
"@id": string;
|
|
275
|
+
"@context": {
|
|
276
|
+
expiresAt: string;
|
|
277
|
+
maxDownloads: string;
|
|
278
|
+
ipWhitelist: string;
|
|
279
|
+
};
|
|
280
|
+
};
|
|
281
|
+
ipnsName: string;
|
|
282
|
+
resourceType: string;
|
|
283
|
+
resourcePath: string;
|
|
284
|
+
owner: string;
|
|
285
|
+
standard: string;
|
|
286
|
+
status: string;
|
|
287
|
+
};
|
|
288
|
+
export declare const JDID_FILE_ACCESS_AUTHORIZATION_CONTEXT: {
|
|
289
|
+
FileAccessAuthorization: string;
|
|
290
|
+
version: "https://ccda.ooo/did/v1" | "https://jdid.cn/did/v1";
|
|
291
|
+
grantee: string;
|
|
292
|
+
accessRights: string;
|
|
293
|
+
accessRestrictions: {
|
|
294
|
+
"@id": string;
|
|
295
|
+
"@context": {
|
|
296
|
+
expiresAt: string;
|
|
297
|
+
maxDownloads: string;
|
|
298
|
+
ipWhitelist: string;
|
|
299
|
+
};
|
|
300
|
+
};
|
|
301
|
+
ipnsName: string;
|
|
302
|
+
resourceType: string;
|
|
303
|
+
resourcePath: string;
|
|
304
|
+
owner: string;
|
|
305
|
+
standard: string;
|
|
306
|
+
status: string;
|
|
307
|
+
};
|
|
187
308
|
/**
|
|
188
309
|
* 按本次签发需要的字段,生成最小内联 @context 对象。
|
|
189
310
|
*
|
|
311
|
+
* 相较于直接使用 `buildXxxContext`(返回全量术语),此函数只保留
|
|
312
|
+
* 本次 subject 实际用到的字段,减小 VC 体积。
|
|
313
|
+
*
|
|
314
|
+
* 自动保留:
|
|
315
|
+
* - 类型标识符(opts.types 中的每一项,如 "NFTOwnership")
|
|
316
|
+
* - `version`(命名空间声明,始终需要)
|
|
317
|
+
*
|
|
318
|
+
* @param brand - 品牌 ID,决定 URL 命名空间
|
|
319
|
+
* @param opts.types - VC 类型名数组,如 ["NFTOwnership"]
|
|
320
|
+
* @param opts.fields - 需要解释的字段名;不传则返回该类型完整预设
|
|
321
|
+
*
|
|
190
322
|
* @example
|
|
191
323
|
* // 仅解释 tokenId、owner,不会带上 contractAddress
|
|
192
324
|
* buildInlineContext("ccdao", { types: ["NFTOwnership"], fields: ["tokenId", "owner"] })
|
|
193
325
|
*
|
|
194
|
-
*
|
|
326
|
+
* @example
|
|
327
|
+
* // 目录授权,只携带访问地址与权限字段
|
|
328
|
+
* buildInlineContext("jdid", {
|
|
329
|
+
* types: ["FileAccessAuthorization"],
|
|
330
|
+
* fields: ["ipnsName", "resourceType", "resourcePath", "accessRights"]
|
|
331
|
+
* })
|
|
195
332
|
*/
|
|
196
333
|
export declare function buildInlineContext(brand: BrandId, opts: {
|
|
197
334
|
types: string[];
|
package/lib/inline-contexts.js
CHANGED
|
@@ -1,22 +1,35 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.JDID_PHONE_VERIFICATION_CONTEXT = exports.CCDAO_PHONE_VERIFICATION_CONTEXT = exports.JDID_VERSION_CONTEXT = exports.CCDAO_VERSION_CONTEXT = exports.JDID_NFT_USAGE_AUTHORIZATION_CONTEXT = exports.CCDAO_NFT_USAGE_AUTHORIZATION_CONTEXT = exports.JDID_NFT_OWNERSHIP_CONTEXT = exports.CCDAO_NFT_OWNERSHIP_CONTEXT = exports.getPhoneVerificationContext = exports.getVersionContext = exports.getNftUsageAuthorizationContext = exports.getNftOwnershipContext = exports.W3C_VC_CONTEXT_URL = void 0;
|
|
3
|
+
exports.JDID_FILE_ACCESS_AUTHORIZATION_CONTEXT = exports.CCDAO_FILE_ACCESS_AUTHORIZATION_CONTEXT = exports.JDID_PHONE_VERIFICATION_CONTEXT = exports.CCDAO_PHONE_VERIFICATION_CONTEXT = exports.JDID_VERSION_CONTEXT = exports.CCDAO_VERSION_CONTEXT = exports.JDID_NFT_USAGE_AUTHORIZATION_CONTEXT = exports.CCDAO_NFT_USAGE_AUTHORIZATION_CONTEXT = exports.JDID_NFT_OWNERSHIP_CONTEXT = exports.CCDAO_NFT_OWNERSHIP_CONTEXT = exports.getFileAccessAuthorizationContext = exports.getPhoneVerificationContext = exports.getVersionContext = exports.getNftUsageAuthorizationContext = exports.getNftOwnershipContext = exports.W3C_VC_CONTEXT_URL = void 0;
|
|
4
4
|
exports.buildNftOwnershipContext = buildNftOwnershipContext;
|
|
5
5
|
exports.buildNftUsageAuthorizationContext = buildNftUsageAuthorizationContext;
|
|
6
6
|
exports.buildVersionContext = buildVersionContext;
|
|
7
7
|
exports.buildPhoneVerificationContext = buildPhoneVerificationContext;
|
|
8
|
+
exports.buildFileAccessAuthorizationContext = buildFileAccessAuthorizationContext;
|
|
8
9
|
exports.buildInlineContext = buildInlineContext;
|
|
9
10
|
const ccdao_js_1 = require("./brands/ccdao.js");
|
|
10
11
|
const jdid_js_1 = require("./brands/jdid.js");
|
|
12
|
+
/** W3C Verifiable Credentials 标准 context URL,固定作为 @context 数组第一项 */
|
|
11
13
|
exports.W3C_VC_CONTEXT_URL = "https://www.w3.org/2018/credentials/v1";
|
|
12
14
|
const BRAND_URLS = {
|
|
13
15
|
ccdao: { did: ccdao_js_1.CCDAO_DID_VOCAB_URL, vc: ccdao_js_1.CCDAO_VC_VOCAB_URL },
|
|
14
16
|
jdid: { did: jdid_js_1.JDID_DID_VOCAB_URL, vc: jdid_js_1.JDID_VC_VOCAB_URL }
|
|
15
17
|
};
|
|
18
|
+
/** 根据品牌取词汇表 URL 前缀 */
|
|
16
19
|
function vocab(brand) {
|
|
17
20
|
return BRAND_URLS[brand];
|
|
18
21
|
}
|
|
19
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* 构建 NFT 所有权 VC 的内联 @context 术语集。
|
|
24
|
+
*
|
|
25
|
+
* 链差异(EVM / SWTC)体现在 subject 字段选择上:
|
|
26
|
+
* - EVM:填 `contractAddress`
|
|
27
|
+
* - SWTC:填 `tokenName` + `nftIssuer`
|
|
28
|
+
* 词汇层两者均声明,由 DApp 按链选填。
|
|
29
|
+
*
|
|
30
|
+
* @param brand - 品牌 ID,决定 URL 命名空间
|
|
31
|
+
* @returns 内联 @context 对象,可直接放入 VC 的 `@context` 数组
|
|
32
|
+
*/
|
|
20
33
|
function buildNftOwnershipContext(brand) {
|
|
21
34
|
const { did, vc } = vocab(brand);
|
|
22
35
|
return {
|
|
@@ -32,7 +45,15 @@ function buildNftOwnershipContext(brand) {
|
|
|
32
45
|
status: `${did}#status`
|
|
33
46
|
};
|
|
34
47
|
}
|
|
35
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* 构建 NFT 使用授权 VC 的内联 @context 术语集。
|
|
50
|
+
*
|
|
51
|
+
* `restrictions` 是嵌套 context 对象,其子字段(commercial、derivative 等)
|
|
52
|
+
* 仅在该嵌套对象内有效,不与外层字段命名冲突。
|
|
53
|
+
*
|
|
54
|
+
* @param brand - 品牌 ID,决定 URL 命名空间
|
|
55
|
+
* @returns 内联 @context 对象,含嵌套 restrictions 术语集
|
|
56
|
+
*/
|
|
36
57
|
function buildNftUsageAuthorizationContext(brand) {
|
|
37
58
|
const { did, vc } = vocab(brand);
|
|
38
59
|
return {
|
|
@@ -60,7 +81,15 @@ function buildNftUsageAuthorizationContext(brand) {
|
|
|
60
81
|
status: `${did}#status`
|
|
61
82
|
};
|
|
62
83
|
}
|
|
63
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* 构建 DID 文档 version 字段的内联 @context。
|
|
86
|
+
*
|
|
87
|
+
* 仅包含 `version`(命名空间根 URL)与 `credentials` 两个术语,
|
|
88
|
+
* 用于 DID 文档自身,不是 VC subject 字段。
|
|
89
|
+
*
|
|
90
|
+
* @param brand - 品牌 ID,决定 URL 命名空间
|
|
91
|
+
* @returns 含 version / credentials 的最小 context 对象
|
|
92
|
+
*/
|
|
64
93
|
function buildVersionContext(brand) {
|
|
65
94
|
const { did } = vocab(brand);
|
|
66
95
|
return {
|
|
@@ -68,7 +97,14 @@ function buildVersionContext(brand) {
|
|
|
68
97
|
credentials: `${did}#credentials`
|
|
69
98
|
};
|
|
70
99
|
}
|
|
71
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* 构建电话验证 VC 的内联 @context 术语集。
|
|
102
|
+
*
|
|
103
|
+
* `verificationProcess` 是嵌套 context,记录验证操作的时间、操作方等审计信息。
|
|
104
|
+
*
|
|
105
|
+
* @param brand - 品牌 ID,决定 URL 命名空间
|
|
106
|
+
* @returns 内联 @context 对象,含嵌套 verificationProcess 术语集
|
|
107
|
+
*/
|
|
72
108
|
function buildPhoneVerificationContext(brand) {
|
|
73
109
|
const { did, vc } = vocab(brand);
|
|
74
110
|
return {
|
|
@@ -87,11 +123,57 @@ function buildPhoneVerificationContext(brand) {
|
|
|
87
123
|
}
|
|
88
124
|
};
|
|
89
125
|
}
|
|
90
|
-
/**
|
|
126
|
+
/**
|
|
127
|
+
* 构建文件访问授权 VC 的内联 @context 术语集(基于 IPFS/IPNS 存储模型)。
|
|
128
|
+
*
|
|
129
|
+
* ## 字段设计说明
|
|
130
|
+
*
|
|
131
|
+
* | 字段 | 命名空间 | 说明 |
|
|
132
|
+
* |----------------|----------|------------------------------------------------------------|
|
|
133
|
+
* | ipnsName | did# | IPNS 固定入口,所有者更新文件后仍指向同一名称 |
|
|
134
|
+
* | resourceType | did# | 授权粒度:"file" \| "directory" |
|
|
135
|
+
* | resourcePath | did# | 目录授权时的子路径限制,为空则整个 IPNS 目录均可访问 |
|
|
136
|
+
* | grantee | vc# | 被授权方 DID |
|
|
137
|
+
* | accessRights | vc# | 权限列表,如 ["read", "download"] |
|
|
138
|
+
* | accessRestrictions | vc# | 访问限制(嵌套对象):到期时间、最大下载次数、IP 白名单 |
|
|
139
|
+
*
|
|
140
|
+
* ## 为何不存 CID
|
|
141
|
+
* CID 随文件更新而变化,存入 VC 后会立即过时;
|
|
142
|
+
* 内容完整性由 IPFS 内容寻址协议层自动保证(取回内容时重算 CID 对比),
|
|
143
|
+
* 无需在凭证层额外声明。
|
|
144
|
+
*
|
|
145
|
+
* @param brand - 品牌 ID,决定 URL 命名空间
|
|
146
|
+
* @returns 内联 @context 对象,含嵌套 accessRestrictions 术语集
|
|
147
|
+
*/
|
|
148
|
+
function buildFileAccessAuthorizationContext(brand) {
|
|
149
|
+
const { did, vc } = vocab(brand);
|
|
150
|
+
return {
|
|
151
|
+
FileAccessAuthorization: `${vc}#FileAccessAuthorization`,
|
|
152
|
+
version: did,
|
|
153
|
+
grantee: `${vc}#grantee`,
|
|
154
|
+
accessRights: `${vc}#accessRights`,
|
|
155
|
+
accessRestrictions: {
|
|
156
|
+
"@id": `${vc}#accessRestrictions`,
|
|
157
|
+
"@context": {
|
|
158
|
+
expiresAt: `${vc}#expiresAt`,
|
|
159
|
+
maxDownloads: `${vc}#maxDownloads`,
|
|
160
|
+
ipWhitelist: `${vc}#ipWhitelist`
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
ipnsName: `${did}#ipnsName`,
|
|
164
|
+
resourceType: `${did}#resourceType`,
|
|
165
|
+
resourcePath: `${did}#resourcePath`,
|
|
166
|
+
owner: `${did}#owner`,
|
|
167
|
+
standard: `${did}#standard`,
|
|
168
|
+
status: `${did}#status`
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/** 便捷 getter 别名,供 DApp / Profile 使用(与 build* 函数等价) */
|
|
91
172
|
exports.getNftOwnershipContext = buildNftOwnershipContext;
|
|
92
173
|
exports.getNftUsageAuthorizationContext = buildNftUsageAuthorizationContext;
|
|
93
174
|
exports.getVersionContext = buildVersionContext;
|
|
94
175
|
exports.getPhoneVerificationContext = buildPhoneVerificationContext;
|
|
176
|
+
exports.getFileAccessAuthorizationContext = buildFileAccessAuthorizationContext;
|
|
95
177
|
/** 向后兼容的具名导出(结构已统一,仅 URL 命名空间不同) */
|
|
96
178
|
exports.CCDAO_NFT_OWNERSHIP_CONTEXT = buildNftOwnershipContext("ccdao");
|
|
97
179
|
exports.JDID_NFT_OWNERSHIP_CONTEXT = buildNftOwnershipContext("jdid");
|
|
@@ -101,12 +183,22 @@ exports.CCDAO_VERSION_CONTEXT = buildVersionContext("ccdao");
|
|
|
101
183
|
exports.JDID_VERSION_CONTEXT = buildVersionContext("jdid");
|
|
102
184
|
exports.CCDAO_PHONE_VERIFICATION_CONTEXT = buildPhoneVerificationContext("ccdao");
|
|
103
185
|
exports.JDID_PHONE_VERIFICATION_CONTEXT = buildPhoneVerificationContext("jdid");
|
|
186
|
+
exports.CCDAO_FILE_ACCESS_AUTHORIZATION_CONTEXT = buildFileAccessAuthorizationContext("ccdao");
|
|
187
|
+
exports.JDID_FILE_ACCESS_AUTHORIZATION_CONTEXT = buildFileAccessAuthorizationContext("jdid");
|
|
188
|
+
/**
|
|
189
|
+
* 按 VC 类型名索引 context builder,供 `buildInlineContext` 内部使用。
|
|
190
|
+
* 新增 VC 类型时在此注册即可,无需修改 buildInlineContext 逻辑。
|
|
191
|
+
*/
|
|
104
192
|
const VC_TYPE_BUILDERS = {
|
|
105
193
|
NFTOwnership: buildNftOwnershipContext,
|
|
106
194
|
NFTUsageAuthorization: buildNftUsageAuthorizationContext,
|
|
107
|
-
PhoneVerificationCredential: buildPhoneVerificationContext
|
|
195
|
+
PhoneVerificationCredential: buildPhoneVerificationContext,
|
|
196
|
+
FileAccessAuthorization: buildFileAccessAuthorizationContext
|
|
108
197
|
};
|
|
109
|
-
/**
|
|
198
|
+
/**
|
|
199
|
+
* 从完整词汇表中仅取出 fields 列出的键,用于构建「最小内联 context」。
|
|
200
|
+
* 自动保留类型标识符(type term)和 version,其余字段按需裁剪。
|
|
201
|
+
*/
|
|
110
202
|
function pickFields(source, fields) {
|
|
111
203
|
const result = {};
|
|
112
204
|
for (const field of fields) {
|
|
@@ -119,11 +211,27 @@ function pickFields(source, fields) {
|
|
|
119
211
|
/**
|
|
120
212
|
* 按本次签发需要的字段,生成最小内联 @context 对象。
|
|
121
213
|
*
|
|
214
|
+
* 相较于直接使用 `buildXxxContext`(返回全量术语),此函数只保留
|
|
215
|
+
* 本次 subject 实际用到的字段,减小 VC 体积。
|
|
216
|
+
*
|
|
217
|
+
* 自动保留:
|
|
218
|
+
* - 类型标识符(opts.types 中的每一项,如 "NFTOwnership")
|
|
219
|
+
* - `version`(命名空间声明,始终需要)
|
|
220
|
+
*
|
|
221
|
+
* @param brand - 品牌 ID,决定 URL 命名空间
|
|
222
|
+
* @param opts.types - VC 类型名数组,如 ["NFTOwnership"]
|
|
223
|
+
* @param opts.fields - 需要解释的字段名;不传则返回该类型完整预设
|
|
224
|
+
*
|
|
122
225
|
* @example
|
|
123
226
|
* // 仅解释 tokenId、owner,不会带上 contractAddress
|
|
124
227
|
* buildInlineContext("ccdao", { types: ["NFTOwnership"], fields: ["tokenId", "owner"] })
|
|
125
228
|
*
|
|
126
|
-
*
|
|
229
|
+
* @example
|
|
230
|
+
* // 目录授权,只携带访问地址与权限字段
|
|
231
|
+
* buildInlineContext("jdid", {
|
|
232
|
+
* types: ["FileAccessAuthorization"],
|
|
233
|
+
* fields: ["ipnsName", "resourceType", "resourcePath", "accessRights"]
|
|
234
|
+
* })
|
|
127
235
|
*/
|
|
128
236
|
function buildInlineContext(brand, opts) {
|
|
129
237
|
const merged = {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { BrandId, DescriptorMeta, VCIssuanceDescriptor } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* 构建文件访问授权 VC 签发描述符(基于 IPFS/IPNS 存储模型)。
|
|
4
|
+
*
|
|
5
|
+
* 两品牌词汇结构相同,仅 URL 命名空间不同。
|
|
6
|
+
*
|
|
7
|
+
* @param brand - 品牌 ID:"ccdao" | "jdid"
|
|
8
|
+
* @param subject - credentialSubject,必须包含 `id`(被授权方 DID)及以下业务字段:
|
|
9
|
+
* - `ipnsName` IPNS 固定入口(被授权方用来访问文件/目录的稳定地址)
|
|
10
|
+
* - `resourceType` 授权粒度:"file" | "directory"
|
|
11
|
+
* - `resourcePath` (可选)目录内子路径,为空则整个 IPNS 目录均可访问
|
|
12
|
+
* - `grantee` 被授权方 DID(通常与 id 相同)
|
|
13
|
+
* - `accessRights` 权限数组,如 ["read", "download"]
|
|
14
|
+
* - `accessRestrictions` 访问限制:{ expiresAt, maxDownloads, ipWhitelist }
|
|
15
|
+
* - `owner` 文件所有者 DID
|
|
16
|
+
* - `standard` 协议版本号
|
|
17
|
+
* - `status` 凭证状态("active" | "revoked")
|
|
18
|
+
* @param meta - 签发元数据(id、expirationDate 等)
|
|
19
|
+
* @returns VCIssuanceDescriptor,可直接传入 @jccdex/did 的 issueVC()
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildFileAccessAuthorizationDescriptor(brand: BrandId, subject: Record<string, unknown> & {
|
|
22
|
+
id: string;
|
|
23
|
+
}, meta: DescriptorMeta): VCIssuanceDescriptor;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildFileAccessAuthorizationDescriptor = buildFileAccessAuthorizationDescriptor;
|
|
4
|
+
const inline_contexts_js_1 = require("../inline-contexts.js");
|
|
5
|
+
const vc_types_js_1 = require("../vc-types.js");
|
|
6
|
+
/**
|
|
7
|
+
* 构建文件访问授权 VC 签发描述符(基于 IPFS/IPNS 存储模型)。
|
|
8
|
+
*
|
|
9
|
+
* 两品牌词汇结构相同,仅 URL 命名空间不同。
|
|
10
|
+
*
|
|
11
|
+
* @param brand - 品牌 ID:"ccdao" | "jdid"
|
|
12
|
+
* @param subject - credentialSubject,必须包含 `id`(被授权方 DID)及以下业务字段:
|
|
13
|
+
* - `ipnsName` IPNS 固定入口(被授权方用来访问文件/目录的稳定地址)
|
|
14
|
+
* - `resourceType` 授权粒度:"file" | "directory"
|
|
15
|
+
* - `resourcePath` (可选)目录内子路径,为空则整个 IPNS 目录均可访问
|
|
16
|
+
* - `grantee` 被授权方 DID(通常与 id 相同)
|
|
17
|
+
* - `accessRights` 权限数组,如 ["read", "download"]
|
|
18
|
+
* - `accessRestrictions` 访问限制:{ expiresAt, maxDownloads, ipWhitelist }
|
|
19
|
+
* - `owner` 文件所有者 DID
|
|
20
|
+
* - `standard` 协议版本号
|
|
21
|
+
* - `status` 凭证状态("active" | "revoked")
|
|
22
|
+
* @param meta - 签发元数据(id、expirationDate 等)
|
|
23
|
+
* @returns VCIssuanceDescriptor,可直接传入 @jccdex/did 的 issueVC()
|
|
24
|
+
*/
|
|
25
|
+
function buildFileAccessAuthorizationDescriptor(brand, subject, meta) {
|
|
26
|
+
return {
|
|
27
|
+
types: [vc_types_js_1.VC_TYPES.FILE_ACCESS_AUTHORIZATION],
|
|
28
|
+
contexts: [inline_contexts_js_1.W3C_VC_CONTEXT_URL, (0, inline_contexts_js_1.getFileAccessAuthorizationContext)(brand)],
|
|
29
|
+
subject,
|
|
30
|
+
id: meta.id,
|
|
31
|
+
expirationDate: meta.expirationDate,
|
|
32
|
+
issuanceDate: meta.issuanceDate,
|
|
33
|
+
issuer: meta.issuer
|
|
34
|
+
};
|
|
35
|
+
}
|
package/lib/vc-types.d.ts
CHANGED
|
@@ -3,5 +3,6 @@ export declare const VC_TYPES: {
|
|
|
3
3
|
readonly NFT_OWNERSHIP: "NFTOwnership";
|
|
4
4
|
readonly NFT_USAGE_AUTHORIZATION: "NFTUsageAuthorization";
|
|
5
5
|
readonly PHONE_VERIFICATION: "PhoneVerificationCredential";
|
|
6
|
+
readonly FILE_ACCESS_AUTHORIZATION: "FileAccessAuthorization";
|
|
6
7
|
};
|
|
7
8
|
export type VCType = (typeof VC_TYPES)[keyof typeof VC_TYPES];
|
package/lib/vc-types.js
CHANGED
|
@@ -5,5 +5,6 @@ exports.VC_TYPES = void 0;
|
|
|
5
5
|
exports.VC_TYPES = {
|
|
6
6
|
NFT_OWNERSHIP: "NFTOwnership",
|
|
7
7
|
NFT_USAGE_AUTHORIZATION: "NFTUsageAuthorization",
|
|
8
|
-
PHONE_VERIFICATION: "PhoneVerificationCredential"
|
|
8
|
+
PHONE_VERIFICATION: "PhoneVerificationCredential",
|
|
9
|
+
FILE_ACCESS_AUTHORIZATION: "FileAccessAuthorization"
|
|
9
10
|
};
|
package/package.json
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"NFTOwnership": "https://ccda.ooo/vc/v1#NFTOwnership",
|
|
8
8
|
"NFTUsageAuthorization": "https://ccda.ooo/vc/v1#NFTUsageAuthorization",
|
|
9
9
|
"PhoneVerificationCredential": "https://ccda.ooo/vc/v1#PhoneVerificationCredential",
|
|
10
|
+
"FileAccessAuthorization": "https://ccda.ooo/vc/v1#FileAccessAuthorization",
|
|
10
11
|
"version": "https://ccda.ooo/did/v1#version",
|
|
11
12
|
"chainId": "https://ccda.ooo/did/v1#chainId",
|
|
12
13
|
"contractAddress": "https://ccda.ooo/did/v1#contractAddress",
|
|
@@ -25,6 +26,9 @@
|
|
|
25
26
|
"updateAt": "https://ccda.ooo/did/v1#updateAt",
|
|
26
27
|
"verifierId": "https://ccda.ooo/did/v1#verifierId"
|
|
27
28
|
}
|
|
28
|
-
}
|
|
29
|
+
},
|
|
30
|
+
"ipnsName": "https://ccda.ooo/did/v1#ipnsName",
|
|
31
|
+
"resourceType": "https://ccda.ooo/did/v1#resourceType",
|
|
32
|
+
"resourcePath": "https://ccda.ooo/did/v1#resourcePath"
|
|
29
33
|
}
|
|
30
34
|
}
|
|
@@ -15,6 +15,15 @@
|
|
|
15
15
|
"territories": "https://ccda.ooo/vc/v1#territories",
|
|
16
16
|
"platforms": "https://ccda.ooo/vc/v1#platforms"
|
|
17
17
|
}
|
|
18
|
+
},
|
|
19
|
+
"accessRights": "https://ccda.ooo/vc/v1#accessRights",
|
|
20
|
+
"accessRestrictions": {
|
|
21
|
+
"@id": "https://ccda.ooo/vc/v1#accessRestrictions",
|
|
22
|
+
"@context": {
|
|
23
|
+
"expiresAt": "https://ccda.ooo/vc/v1#expiresAt",
|
|
24
|
+
"maxDownloads": "https://ccda.ooo/vc/v1#maxDownloads",
|
|
25
|
+
"ipWhitelist": "https://ccda.ooo/vc/v1#ipWhitelist"
|
|
26
|
+
}
|
|
18
27
|
}
|
|
19
28
|
}
|
|
20
29
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"NFTOwnership": "https://jdid.cn/vc/v1#NFTOwnership",
|
|
8
8
|
"NFTUsageAuthorization": "https://jdid.cn/vc/v1#NFTUsageAuthorization",
|
|
9
9
|
"PhoneVerificationCredential": "https://jdid.cn/vc/v1#PhoneVerificationCredential",
|
|
10
|
+
"FileAccessAuthorization": "https://jdid.cn/vc/v1#FileAccessAuthorization",
|
|
10
11
|
"version": "https://jdid.cn/did/v1#version",
|
|
11
12
|
"chainId": "https://jdid.cn/did/v1#chainId",
|
|
12
13
|
"contractAddress": "https://jdid.cn/did/v1#contractAddress",
|
|
@@ -25,6 +26,9 @@
|
|
|
25
26
|
"updateAt": "https://jdid.cn/did/v1#updateAt",
|
|
26
27
|
"verifierId": "https://jdid.cn/did/v1#verifierId"
|
|
27
28
|
}
|
|
28
|
-
}
|
|
29
|
+
},
|
|
30
|
+
"ipnsName": "https://jdid.cn/did/v1#ipnsName",
|
|
31
|
+
"resourceType": "https://jdid.cn/did/v1#resourceType",
|
|
32
|
+
"resourcePath": "https://jdid.cn/did/v1#resourcePath"
|
|
29
33
|
}
|
|
30
34
|
}
|
|
@@ -15,6 +15,15 @@
|
|
|
15
15
|
"territories": "https://jdid.cn/vc/v1#territories",
|
|
16
16
|
"platforms": "https://jdid.cn/vc/v1#platforms"
|
|
17
17
|
}
|
|
18
|
+
},
|
|
19
|
+
"accessRights": "https://jdid.cn/vc/v1#accessRights",
|
|
20
|
+
"accessRestrictions": {
|
|
21
|
+
"@id": "https://jdid.cn/vc/v1#accessRestrictions",
|
|
22
|
+
"@context": {
|
|
23
|
+
"expiresAt": "https://jdid.cn/vc/v1#expiresAt",
|
|
24
|
+
"maxDownloads": "https://jdid.cn/vc/v1#maxDownloads",
|
|
25
|
+
"ipWhitelist": "https://jdid.cn/vc/v1#ipWhitelist"
|
|
26
|
+
}
|
|
18
27
|
}
|
|
19
28
|
}
|
|
20
29
|
}
|