@arcblock/vc 1.29.18 → 1.29.20
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/esm/index.d.mts +29 -6
- package/esm/index.mjs +95 -21
- package/lib/index.cjs +95 -20
- package/lib/index.d.cts +29 -6
- package/package.json +5 -5
package/esm/index.d.mts
CHANGED
|
@@ -7,7 +7,11 @@ interface Proof {
|
|
|
7
7
|
created: string;
|
|
8
8
|
proofPurpose: string;
|
|
9
9
|
jws: string;
|
|
10
|
+
id?: string;
|
|
11
|
+
signer?: string;
|
|
10
12
|
pk?: string;
|
|
13
|
+
previousProof?: string | string[];
|
|
14
|
+
verificationMethod?: string;
|
|
11
15
|
}
|
|
12
16
|
interface Issuer {
|
|
13
17
|
id: string;
|
|
@@ -24,14 +28,14 @@ interface CredentialStatus {
|
|
|
24
28
|
scope: string;
|
|
25
29
|
}
|
|
26
30
|
interface VerifiableCredential {
|
|
27
|
-
'@context': string;
|
|
31
|
+
'@context': string | string[];
|
|
28
32
|
id: string;
|
|
29
|
-
type: string;
|
|
33
|
+
type: string | string[];
|
|
30
34
|
issuer: Issuer;
|
|
31
35
|
issuanceDate: string;
|
|
32
36
|
expirationDate?: string;
|
|
33
37
|
credentialSubject: CredentialSubject;
|
|
34
|
-
proof: Proof;
|
|
38
|
+
proof: Proof | Proof[];
|
|
35
39
|
tag?: string;
|
|
36
40
|
credentialStatus?: CredentialStatus;
|
|
37
41
|
signature?: unknown;
|
|
@@ -50,7 +54,7 @@ interface Credential {
|
|
|
50
54
|
id: string;
|
|
51
55
|
issued: string;
|
|
52
56
|
issuer: Issuer;
|
|
53
|
-
proof: Proof;
|
|
57
|
+
proof: Proof | Proof[];
|
|
54
58
|
claim: unknown;
|
|
55
59
|
}
|
|
56
60
|
declare const proofTypes: Record<number, string>;
|
|
@@ -77,7 +81,7 @@ declare function create({
|
|
|
77
81
|
endpoint,
|
|
78
82
|
endpointScope
|
|
79
83
|
}: {
|
|
80
|
-
type: string;
|
|
84
|
+
type: string | string[];
|
|
81
85
|
subject: CredentialSubject;
|
|
82
86
|
issuer: IssuerInfo;
|
|
83
87
|
issuanceDate?: string;
|
|
@@ -110,6 +114,25 @@ declare function verify({
|
|
|
110
114
|
trustedIssuers: string | string[];
|
|
111
115
|
ignoreExpired?: boolean;
|
|
112
116
|
}): Promise<boolean>;
|
|
117
|
+
/**
|
|
118
|
+
* Counter-sign an existing verifiable credential
|
|
119
|
+
* Adds a new proof from a different signer to the VC
|
|
120
|
+
*
|
|
121
|
+
* @param params
|
|
122
|
+
* @param params.vc - The already-signed verifiable credential
|
|
123
|
+
* @param params.wallet - The counter-signer's wallet
|
|
124
|
+
* @param params.mode - 'set' (independent proof) or 'chain' (references previous proofs)
|
|
125
|
+
* @returns Promise<VerifiableCredential>
|
|
126
|
+
*/
|
|
127
|
+
declare function counterSign({
|
|
128
|
+
vc,
|
|
129
|
+
wallet,
|
|
130
|
+
mode
|
|
131
|
+
}: {
|
|
132
|
+
vc: VerifiableCredential;
|
|
133
|
+
wallet: WalletObject;
|
|
134
|
+
mode?: 'set' | 'chain';
|
|
135
|
+
}): Promise<VerifiableCredential>;
|
|
113
136
|
/**
|
|
114
137
|
* Verify that the Presentation is valid
|
|
115
138
|
* - It is signed by VC's owner
|
|
@@ -152,4 +175,4 @@ declare function verifyCredentialList({
|
|
|
152
175
|
}): Promise<unknown[]>;
|
|
153
176
|
declare const stableStringify: typeof stringify;
|
|
154
177
|
//#endregion
|
|
155
|
-
export { create, createCredentialList, proofTypes, stableStringify, verify, verifyCredentialList, verifyPresentation };
|
|
178
|
+
export { counterSign, create, createCredentialList, proofTypes, stableStringify, verify, verifyCredentialList, verifyPresentation };
|
package/esm/index.mjs
CHANGED
|
@@ -36,7 +36,9 @@ const proofTypes = {
|
|
|
36
36
|
* @returns Promise<object>
|
|
37
37
|
*/
|
|
38
38
|
async function create({ type, subject, issuer, issuanceDate, expirationDate, tag = "", endpoint = "", endpointScope = "public" }) {
|
|
39
|
-
|
|
39
|
+
const typeArray = Array.isArray(type) ? [...type] : type ? [type] : [];
|
|
40
|
+
if (typeArray.length === 0 || typeArray.some((t) => !t)) throw new Error("Can not create verifiable credential without empty type");
|
|
41
|
+
if (!typeArray.includes("VerifiableCredential")) typeArray.unshift("VerifiableCredential");
|
|
40
42
|
if (!subject) throw new Error("Can not create verifiable credential from empty subject");
|
|
41
43
|
if (!subject.id) throw new Error("Can not create verifiable credential without holder");
|
|
42
44
|
if (!isValid(subject.id)) throw new Error("Can not create verifiable credential invalid holder did");
|
|
@@ -50,14 +52,17 @@ async function create({ type, subject, issuer, issuanceDate, expirationDate, tag
|
|
|
50
52
|
role: types.RoleType.ROLE_VC
|
|
51
53
|
};
|
|
52
54
|
const vcDid = fromPublicKeyHash(wallet.hash(stringify(subject)), vcType);
|
|
55
|
+
const pkType = typeInfo.pk;
|
|
56
|
+
if (!proofTypes[pkType]) throw new Error("Unsupported signer type when create verifiable credential");
|
|
53
57
|
const issuanceDateValue = issuanceDate || (/* @__PURE__ */ new Date()).toISOString();
|
|
58
|
+
const issuerPk = toBase58(wallet.publicKey);
|
|
54
59
|
const vcObj = {
|
|
55
|
-
"@context": "https://schema.arcblock.io/v0.1/context.jsonld",
|
|
60
|
+
"@context": ["https://www.w3.org/2018/credentials/v1", "https://schema.arcblock.io/v0.1/context.jsonld"],
|
|
56
61
|
id: vcDid,
|
|
57
|
-
type,
|
|
62
|
+
type: typeArray,
|
|
58
63
|
issuer: {
|
|
59
64
|
id: issuerDid,
|
|
60
|
-
pk:
|
|
65
|
+
pk: issuerPk,
|
|
61
66
|
name: issuerName || issuerDid
|
|
62
67
|
},
|
|
63
68
|
issuanceDate: issuanceDateValue,
|
|
@@ -70,14 +75,15 @@ async function create({ type, subject, issuer, issuanceDate, expirationDate, tag
|
|
|
70
75
|
type: "NFTStatusList2021",
|
|
71
76
|
scope: endpointScope || "public"
|
|
72
77
|
};
|
|
73
|
-
const pkType = typeInfo.pk;
|
|
74
|
-
if (!proofTypes[pkType]) throw new Error("Unsupported signer type when create verifiable credential");
|
|
75
78
|
const signature = await wallet.sign(stringify(vcObj));
|
|
76
79
|
const result = {
|
|
77
80
|
proof: {
|
|
78
81
|
type: proofTypes[pkType],
|
|
79
82
|
created: issuanceDateValue,
|
|
80
83
|
proofPurpose: "assertionMethod",
|
|
84
|
+
id: crypto.randomUUID(),
|
|
85
|
+
signer: issuerDid,
|
|
86
|
+
pk: issuerPk,
|
|
81
87
|
jws: toBase64(signature)
|
|
82
88
|
},
|
|
83
89
|
...vcObj
|
|
@@ -107,21 +113,77 @@ async function verify({ vc, ownerDid, trustedIssuers, ignoreExpired = false }) {
|
|
|
107
113
|
if (!vc) throw new Error("Empty verifiable credential object");
|
|
108
114
|
if (!vc.issuer || !vc.issuer.id || !vc.issuer.pk || !isValid(vc.issuer.id)) throw new Error("Invalid verifiable credential issuer");
|
|
109
115
|
if (!vc.credentialSubject || !vc.credentialSubject.id || !isValid(vc.credentialSubject.id)) throw new Error("Invalid verifiable credential subject");
|
|
110
|
-
|
|
116
|
+
const proofList = Array.isArray(vc.proof) ? vc.proof : vc.proof ? [vc.proof] : [];
|
|
117
|
+
if (proofList.length === 0 || proofList.some((p) => !p || !p.jws)) throw new Error("Invalid verifiable credential proof");
|
|
111
118
|
if (vc.issuanceDate === void 0) throw Error("Invalid verifiable credential issue date");
|
|
112
119
|
if (new Date(vc.issuanceDate).getTime() > Date.now()) throw Error("Verifiable credential has not take effect");
|
|
113
120
|
if (!ignoreExpired && vc.expirationDate !== void 0 && new Date(vc.expirationDate).getTime() < Date.now()) throw Error("Verifiable credential has expired");
|
|
114
|
-
const
|
|
115
|
-
if (!
|
|
116
|
-
if (!isFromPublicKey(issuerDid, vc.issuer.pk)) throw new Error("Verifiable credential not issuer pk not match with issuer did");
|
|
121
|
+
const issuers = Array.isArray(trustedIssuers) ? trustedIssuers : [trustedIssuers];
|
|
122
|
+
if (!isFromPublicKey(vc.issuer.id, vc.issuer.pk)) throw new Error("Verifiable credential not issuer pk not match with issuer did");
|
|
117
123
|
if (ownerDid !== vc.credentialSubject.id) throw new Error("Verifiable credential not owned by specified owner did");
|
|
118
|
-
const
|
|
124
|
+
for (const proof of proofList) {
|
|
125
|
+
const proofPk = proof.pk || vc.issuer.pk;
|
|
126
|
+
const proofSigner = proof.signer || vc.issuer.id;
|
|
127
|
+
if (!issuers.includes(proofSigner)) throw new Error("Proof signer not in trusted issuers");
|
|
128
|
+
if (proof.signer && proof.pk && !isFromPublicKey(proof.signer, proof.pk)) throw new Error("Proof pk does not match signer");
|
|
129
|
+
const clone = cloneDeep(vc);
|
|
130
|
+
delete clone.signature;
|
|
131
|
+
const prevProofRefs = proof.previousProof;
|
|
132
|
+
if (prevProofRefs && (typeof prevProofRefs === "string" || Array.isArray(prevProofRefs) && prevProofRefs.length > 0)) {
|
|
133
|
+
const prevIds = Array.isArray(prevProofRefs) ? prevProofRefs : [prevProofRefs];
|
|
134
|
+
const matchingProofs = proofList.filter((p) => p.id && prevIds.includes(p.id));
|
|
135
|
+
if (matchingProofs.length !== prevIds.length) throw new Error("Referenced previous proof not found");
|
|
136
|
+
clone.proof = matchingProofs;
|
|
137
|
+
} else delete clone.proof;
|
|
138
|
+
const signedContent = stringify(clone);
|
|
139
|
+
if (await fromPublicKey(proofPk, toTypeInfo(proofSigner)).verify(signedContent, fromBase64(proof.jws)) !== true) throw Error("Verifiable credential signature not valid");
|
|
140
|
+
}
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Counter-sign an existing verifiable credential
|
|
145
|
+
* Adds a new proof from a different signer to the VC
|
|
146
|
+
*
|
|
147
|
+
* @param params
|
|
148
|
+
* @param params.vc - The already-signed verifiable credential
|
|
149
|
+
* @param params.wallet - The counter-signer's wallet
|
|
150
|
+
* @param params.mode - 'set' (independent proof) or 'chain' (references previous proofs)
|
|
151
|
+
* @returns Promise<VerifiableCredential>
|
|
152
|
+
*/
|
|
153
|
+
async function counterSign({ vc, wallet, mode = "set" }) {
|
|
154
|
+
if (!vc) throw new Error("Cannot counter-sign empty verifiable credential");
|
|
155
|
+
const existingProofs = Array.isArray(vc.proof) ? vc.proof : vc.proof ? [vc.proof] : [];
|
|
156
|
+
if (existingProofs.length === 0) throw new Error("Cannot counter-sign verifiable credential without existing proof");
|
|
157
|
+
const signerDid = wallet.address;
|
|
158
|
+
const pkType = toTypeInfo(signerDid).pk;
|
|
159
|
+
if (!proofTypes[pkType]) throw new Error("Unsupported signer type when counter-signing verifiable credential");
|
|
160
|
+
const signerPk = toBase58(wallet.publicKey);
|
|
161
|
+
const proofsWithIds = existingProofs.map((p) => p.id ? p : {
|
|
162
|
+
...p,
|
|
163
|
+
id: crypto.randomUUID()
|
|
164
|
+
});
|
|
119
165
|
const clone = cloneDeep(vc);
|
|
120
|
-
const signatureStr = clone.proof.jws;
|
|
121
|
-
delete clone.proof;
|
|
122
166
|
delete clone.signature;
|
|
123
|
-
|
|
124
|
-
|
|
167
|
+
const newProof = {
|
|
168
|
+
type: proofTypes[pkType],
|
|
169
|
+
created: (/* @__PURE__ */ new Date()).toISOString(),
|
|
170
|
+
proofPurpose: "assertionMethod",
|
|
171
|
+
id: crypto.randomUUID(),
|
|
172
|
+
signer: signerDid,
|
|
173
|
+
pk: signerPk,
|
|
174
|
+
jws: ""
|
|
175
|
+
};
|
|
176
|
+
if (mode === "chain") {
|
|
177
|
+
clone.proof = proofsWithIds;
|
|
178
|
+
const prevIds = proofsWithIds.map((p) => p.id);
|
|
179
|
+
newProof.previousProof = prevIds.length === 1 ? prevIds[0] : prevIds;
|
|
180
|
+
} else delete clone.proof;
|
|
181
|
+
const signedContent = stringify(clone);
|
|
182
|
+
newProof.jws = toBase64(await wallet.sign(signedContent));
|
|
183
|
+
return {
|
|
184
|
+
...vc,
|
|
185
|
+
proof: [...proofsWithIds, newProof]
|
|
186
|
+
};
|
|
125
187
|
}
|
|
126
188
|
/**
|
|
127
189
|
* Verify that the Presentation is valid
|
|
@@ -170,13 +232,14 @@ async function createCredentialList({ claims, issuer, issuanceDate }) {
|
|
|
170
232
|
};
|
|
171
233
|
const issued = issuanceDate || (/* @__PURE__ */ new Date()).toISOString();
|
|
172
234
|
const pkType = typeInfo.pk;
|
|
235
|
+
const issuerPk = toBase58(wallet.publicKey);
|
|
173
236
|
return await Promise.all(claims.map(async (x) => {
|
|
174
237
|
const vc = { claim: x };
|
|
175
238
|
vc.id = fromPublicKeyHash(wallet.hash(stringify(vc.claim)), vcType);
|
|
176
239
|
vc.issued = issued;
|
|
177
240
|
vc.issuer = {
|
|
178
241
|
id: issuerDid,
|
|
179
|
-
pk:
|
|
242
|
+
pk: issuerPk,
|
|
180
243
|
name: name$1 || issuerDid
|
|
181
244
|
};
|
|
182
245
|
const signature = await wallet.sign(stringify(vc));
|
|
@@ -184,6 +247,9 @@ async function createCredentialList({ claims, issuer, issuanceDate }) {
|
|
|
184
247
|
type: proofTypes[pkType],
|
|
185
248
|
created: issued,
|
|
186
249
|
proofPurpose: "assertionMethod",
|
|
250
|
+
id: crypto.randomUUID(),
|
|
251
|
+
signer: issuerDid,
|
|
252
|
+
pk: issuerPk,
|
|
187
253
|
jws: toBase64(signature)
|
|
188
254
|
};
|
|
189
255
|
return vc;
|
|
@@ -192,18 +258,26 @@ async function createCredentialList({ claims, issuer, issuanceDate }) {
|
|
|
192
258
|
async function verifyCredentialList({ credentials, trustedIssuers }) {
|
|
193
259
|
if (!credentials || !Array.isArray(credentials)) throw new Error("Can not verify with empty credentials list");
|
|
194
260
|
return Promise.all(credentials.map(async (x) => {
|
|
195
|
-
const
|
|
261
|
+
const issuers = Array.isArray(trustedIssuers) ? trustedIssuers : [trustedIssuers];
|
|
262
|
+
const issuerDid = issuers.find((d) => d === x.issuer.id);
|
|
196
263
|
if (!issuerDid) throw new Error("Credential not issued by trusted issuers");
|
|
197
264
|
if (!isFromPublicKey(issuerDid, x.issuer.pk)) throw new Error("Credential not issuer pk not match with issuer did");
|
|
198
|
-
const
|
|
265
|
+
const proofList = Array.isArray(x.proof) ? x.proof : x.proof ? [x.proof] : [];
|
|
266
|
+
if (proofList.length === 0 || proofList.some((p) => !p || !p.jws)) throw new Error("Invalid credential proof");
|
|
199
267
|
const clone = cloneDeep(x);
|
|
200
|
-
const signatureStr = clone.proof.jws;
|
|
201
268
|
delete clone.proof;
|
|
202
|
-
|
|
269
|
+
const signedContent = stringify(clone);
|
|
270
|
+
for (const proof of proofList) {
|
|
271
|
+
const proofPk = proof.pk || x.issuer.pk;
|
|
272
|
+
const proofSigner = proof.signer || x.issuer.id;
|
|
273
|
+
if (!issuers.includes(proofSigner)) throw new Error("Proof signer not in trusted issuers");
|
|
274
|
+
if (proof.signer && proof.pk && !isFromPublicKey(proof.signer, proof.pk)) throw new Error("Proof pk does not match signer");
|
|
275
|
+
if (await fromPublicKey(proofPk, toTypeInfo(proofSigner)).verify(signedContent, fromBase64(proof.jws)) !== true) throw Error("Status credential signature not valid");
|
|
276
|
+
}
|
|
203
277
|
return x.claim;
|
|
204
278
|
}));
|
|
205
279
|
}
|
|
206
280
|
const stableStringify = stringify;
|
|
207
281
|
|
|
208
282
|
//#endregion
|
|
209
|
-
export { create, createCredentialList, proofTypes, stableStringify, verify, verifyCredentialList, verifyPresentation };
|
|
283
|
+
export { counterSign, create, createCredentialList, proofTypes, stableStringify, verify, verifyCredentialList, verifyPresentation };
|
package/lib/index.cjs
CHANGED
|
@@ -41,7 +41,9 @@ const proofTypes = {
|
|
|
41
41
|
* @returns Promise<object>
|
|
42
42
|
*/
|
|
43
43
|
async function create({ type, subject, issuer, issuanceDate, expirationDate, tag = "", endpoint = "", endpointScope = "public" }) {
|
|
44
|
-
|
|
44
|
+
const typeArray = Array.isArray(type) ? [...type] : type ? [type] : [];
|
|
45
|
+
if (typeArray.length === 0 || typeArray.some((t) => !t)) throw new Error("Can not create verifiable credential without empty type");
|
|
46
|
+
if (!typeArray.includes("VerifiableCredential")) typeArray.unshift("VerifiableCredential");
|
|
45
47
|
if (!subject) throw new Error("Can not create verifiable credential from empty subject");
|
|
46
48
|
if (!subject.id) throw new Error("Can not create verifiable credential without holder");
|
|
47
49
|
if (!(0, _arcblock_did.isValid)(subject.id)) throw new Error("Can not create verifiable credential invalid holder did");
|
|
@@ -55,14 +57,17 @@ async function create({ type, subject, issuer, issuanceDate, expirationDate, tag
|
|
|
55
57
|
role: _ocap_mcrypto.types.RoleType.ROLE_VC
|
|
56
58
|
};
|
|
57
59
|
const vcDid = (0, _arcblock_did.fromPublicKeyHash)(wallet.hash((0, json_stable_stringify.default)(subject)), vcType);
|
|
60
|
+
const pkType = typeInfo.pk;
|
|
61
|
+
if (!proofTypes[pkType]) throw new Error("Unsupported signer type when create verifiable credential");
|
|
58
62
|
const issuanceDateValue = issuanceDate || (/* @__PURE__ */ new Date()).toISOString();
|
|
63
|
+
const issuerPk = (0, _ocap_util.toBase58)(wallet.publicKey);
|
|
59
64
|
const vcObj = {
|
|
60
|
-
"@context": "https://schema.arcblock.io/v0.1/context.jsonld",
|
|
65
|
+
"@context": ["https://www.w3.org/2018/credentials/v1", "https://schema.arcblock.io/v0.1/context.jsonld"],
|
|
61
66
|
id: vcDid,
|
|
62
|
-
type,
|
|
67
|
+
type: typeArray,
|
|
63
68
|
issuer: {
|
|
64
69
|
id: issuerDid,
|
|
65
|
-
pk:
|
|
70
|
+
pk: issuerPk,
|
|
66
71
|
name: issuerName || issuerDid
|
|
67
72
|
},
|
|
68
73
|
issuanceDate: issuanceDateValue,
|
|
@@ -75,14 +80,15 @@ async function create({ type, subject, issuer, issuanceDate, expirationDate, tag
|
|
|
75
80
|
type: "NFTStatusList2021",
|
|
76
81
|
scope: endpointScope || "public"
|
|
77
82
|
};
|
|
78
|
-
const pkType = typeInfo.pk;
|
|
79
|
-
if (!proofTypes[pkType]) throw new Error("Unsupported signer type when create verifiable credential");
|
|
80
83
|
const signature = await wallet.sign((0, json_stable_stringify.default)(vcObj));
|
|
81
84
|
const result = {
|
|
82
85
|
proof: {
|
|
83
86
|
type: proofTypes[pkType],
|
|
84
87
|
created: issuanceDateValue,
|
|
85
88
|
proofPurpose: "assertionMethod",
|
|
89
|
+
id: crypto.randomUUID(),
|
|
90
|
+
signer: issuerDid,
|
|
91
|
+
pk: issuerPk,
|
|
86
92
|
jws: (0, _ocap_util.toBase64)(signature)
|
|
87
93
|
},
|
|
88
94
|
...vcObj
|
|
@@ -112,21 +118,77 @@ async function verify({ vc, ownerDid, trustedIssuers, ignoreExpired = false }) {
|
|
|
112
118
|
if (!vc) throw new Error("Empty verifiable credential object");
|
|
113
119
|
if (!vc.issuer || !vc.issuer.id || !vc.issuer.pk || !(0, _arcblock_did.isValid)(vc.issuer.id)) throw new Error("Invalid verifiable credential issuer");
|
|
114
120
|
if (!vc.credentialSubject || !vc.credentialSubject.id || !(0, _arcblock_did.isValid)(vc.credentialSubject.id)) throw new Error("Invalid verifiable credential subject");
|
|
115
|
-
|
|
121
|
+
const proofList = Array.isArray(vc.proof) ? vc.proof : vc.proof ? [vc.proof] : [];
|
|
122
|
+
if (proofList.length === 0 || proofList.some((p) => !p || !p.jws)) throw new Error("Invalid verifiable credential proof");
|
|
116
123
|
if (vc.issuanceDate === void 0) throw Error("Invalid verifiable credential issue date");
|
|
117
124
|
if (new Date(vc.issuanceDate).getTime() > Date.now()) throw Error("Verifiable credential has not take effect");
|
|
118
125
|
if (!ignoreExpired && vc.expirationDate !== void 0 && new Date(vc.expirationDate).getTime() < Date.now()) throw Error("Verifiable credential has expired");
|
|
119
|
-
const
|
|
120
|
-
if (!
|
|
121
|
-
if (!(0, _arcblock_did.isFromPublicKey)(issuerDid, vc.issuer.pk)) throw new Error("Verifiable credential not issuer pk not match with issuer did");
|
|
126
|
+
const issuers = Array.isArray(trustedIssuers) ? trustedIssuers : [trustedIssuers];
|
|
127
|
+
if (!(0, _arcblock_did.isFromPublicKey)(vc.issuer.id, vc.issuer.pk)) throw new Error("Verifiable credential not issuer pk not match with issuer did");
|
|
122
128
|
if (ownerDid !== vc.credentialSubject.id) throw new Error("Verifiable credential not owned by specified owner did");
|
|
123
|
-
const
|
|
129
|
+
for (const proof of proofList) {
|
|
130
|
+
const proofPk = proof.pk || vc.issuer.pk;
|
|
131
|
+
const proofSigner = proof.signer || vc.issuer.id;
|
|
132
|
+
if (!issuers.includes(proofSigner)) throw new Error("Proof signer not in trusted issuers");
|
|
133
|
+
if (proof.signer && proof.pk && !(0, _arcblock_did.isFromPublicKey)(proof.signer, proof.pk)) throw new Error("Proof pk does not match signer");
|
|
134
|
+
const clone = (0, lodash_cloneDeep.default)(vc);
|
|
135
|
+
delete clone.signature;
|
|
136
|
+
const prevProofRefs = proof.previousProof;
|
|
137
|
+
if (prevProofRefs && (typeof prevProofRefs === "string" || Array.isArray(prevProofRefs) && prevProofRefs.length > 0)) {
|
|
138
|
+
const prevIds = Array.isArray(prevProofRefs) ? prevProofRefs : [prevProofRefs];
|
|
139
|
+
const matchingProofs = proofList.filter((p) => p.id && prevIds.includes(p.id));
|
|
140
|
+
if (matchingProofs.length !== prevIds.length) throw new Error("Referenced previous proof not found");
|
|
141
|
+
clone.proof = matchingProofs;
|
|
142
|
+
} else delete clone.proof;
|
|
143
|
+
const signedContent = (0, json_stable_stringify.default)(clone);
|
|
144
|
+
if (await (0, _ocap_wallet.fromPublicKey)(proofPk, (0, _arcblock_did.toTypeInfo)(proofSigner)).verify(signedContent, (0, _ocap_util.fromBase64)(proof.jws)) !== true) throw Error("Verifiable credential signature not valid");
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Counter-sign an existing verifiable credential
|
|
150
|
+
* Adds a new proof from a different signer to the VC
|
|
151
|
+
*
|
|
152
|
+
* @param params
|
|
153
|
+
* @param params.vc - The already-signed verifiable credential
|
|
154
|
+
* @param params.wallet - The counter-signer's wallet
|
|
155
|
+
* @param params.mode - 'set' (independent proof) or 'chain' (references previous proofs)
|
|
156
|
+
* @returns Promise<VerifiableCredential>
|
|
157
|
+
*/
|
|
158
|
+
async function counterSign({ vc, wallet, mode = "set" }) {
|
|
159
|
+
if (!vc) throw new Error("Cannot counter-sign empty verifiable credential");
|
|
160
|
+
const existingProofs = Array.isArray(vc.proof) ? vc.proof : vc.proof ? [vc.proof] : [];
|
|
161
|
+
if (existingProofs.length === 0) throw new Error("Cannot counter-sign verifiable credential without existing proof");
|
|
162
|
+
const signerDid = wallet.address;
|
|
163
|
+
const pkType = (0, _arcblock_did.toTypeInfo)(signerDid).pk;
|
|
164
|
+
if (!proofTypes[pkType]) throw new Error("Unsupported signer type when counter-signing verifiable credential");
|
|
165
|
+
const signerPk = (0, _ocap_util.toBase58)(wallet.publicKey);
|
|
166
|
+
const proofsWithIds = existingProofs.map((p) => p.id ? p : {
|
|
167
|
+
...p,
|
|
168
|
+
id: crypto.randomUUID()
|
|
169
|
+
});
|
|
124
170
|
const clone = (0, lodash_cloneDeep.default)(vc);
|
|
125
|
-
const signatureStr = clone.proof.jws;
|
|
126
|
-
delete clone.proof;
|
|
127
171
|
delete clone.signature;
|
|
128
|
-
|
|
129
|
-
|
|
172
|
+
const newProof = {
|
|
173
|
+
type: proofTypes[pkType],
|
|
174
|
+
created: (/* @__PURE__ */ new Date()).toISOString(),
|
|
175
|
+
proofPurpose: "assertionMethod",
|
|
176
|
+
id: crypto.randomUUID(),
|
|
177
|
+
signer: signerDid,
|
|
178
|
+
pk: signerPk,
|
|
179
|
+
jws: ""
|
|
180
|
+
};
|
|
181
|
+
if (mode === "chain") {
|
|
182
|
+
clone.proof = proofsWithIds;
|
|
183
|
+
const prevIds = proofsWithIds.map((p) => p.id);
|
|
184
|
+
newProof.previousProof = prevIds.length === 1 ? prevIds[0] : prevIds;
|
|
185
|
+
} else delete clone.proof;
|
|
186
|
+
const signedContent = (0, json_stable_stringify.default)(clone);
|
|
187
|
+
newProof.jws = (0, _ocap_util.toBase64)(await wallet.sign(signedContent));
|
|
188
|
+
return {
|
|
189
|
+
...vc,
|
|
190
|
+
proof: [...proofsWithIds, newProof]
|
|
191
|
+
};
|
|
130
192
|
}
|
|
131
193
|
/**
|
|
132
194
|
* Verify that the Presentation is valid
|
|
@@ -175,13 +237,14 @@ async function createCredentialList({ claims, issuer, issuanceDate }) {
|
|
|
175
237
|
};
|
|
176
238
|
const issued = issuanceDate || (/* @__PURE__ */ new Date()).toISOString();
|
|
177
239
|
const pkType = typeInfo.pk;
|
|
240
|
+
const issuerPk = (0, _ocap_util.toBase58)(wallet.publicKey);
|
|
178
241
|
return await Promise.all(claims.map(async (x) => {
|
|
179
242
|
const vc = { claim: x };
|
|
180
243
|
vc.id = (0, _arcblock_did.fromPublicKeyHash)(wallet.hash((0, json_stable_stringify.default)(vc.claim)), vcType);
|
|
181
244
|
vc.issued = issued;
|
|
182
245
|
vc.issuer = {
|
|
183
246
|
id: issuerDid,
|
|
184
|
-
pk:
|
|
247
|
+
pk: issuerPk,
|
|
185
248
|
name: name$1 || issuerDid
|
|
186
249
|
};
|
|
187
250
|
const signature = await wallet.sign((0, json_stable_stringify.default)(vc));
|
|
@@ -189,6 +252,9 @@ async function createCredentialList({ claims, issuer, issuanceDate }) {
|
|
|
189
252
|
type: proofTypes[pkType],
|
|
190
253
|
created: issued,
|
|
191
254
|
proofPurpose: "assertionMethod",
|
|
255
|
+
id: crypto.randomUUID(),
|
|
256
|
+
signer: issuerDid,
|
|
257
|
+
pk: issuerPk,
|
|
192
258
|
jws: (0, _ocap_util.toBase64)(signature)
|
|
193
259
|
};
|
|
194
260
|
return vc;
|
|
@@ -197,20 +263,29 @@ async function createCredentialList({ claims, issuer, issuanceDate }) {
|
|
|
197
263
|
async function verifyCredentialList({ credentials, trustedIssuers }) {
|
|
198
264
|
if (!credentials || !Array.isArray(credentials)) throw new Error("Can not verify with empty credentials list");
|
|
199
265
|
return Promise.all(credentials.map(async (x) => {
|
|
200
|
-
const
|
|
266
|
+
const issuers = Array.isArray(trustedIssuers) ? trustedIssuers : [trustedIssuers];
|
|
267
|
+
const issuerDid = issuers.find((d) => d === x.issuer.id);
|
|
201
268
|
if (!issuerDid) throw new Error("Credential not issued by trusted issuers");
|
|
202
269
|
if (!(0, _arcblock_did.isFromPublicKey)(issuerDid, x.issuer.pk)) throw new Error("Credential not issuer pk not match with issuer did");
|
|
203
|
-
const
|
|
270
|
+
const proofList = Array.isArray(x.proof) ? x.proof : x.proof ? [x.proof] : [];
|
|
271
|
+
if (proofList.length === 0 || proofList.some((p) => !p || !p.jws)) throw new Error("Invalid credential proof");
|
|
204
272
|
const clone = (0, lodash_cloneDeep.default)(x);
|
|
205
|
-
const signatureStr = clone.proof.jws;
|
|
206
273
|
delete clone.proof;
|
|
207
|
-
|
|
274
|
+
const signedContent = (0, json_stable_stringify.default)(clone);
|
|
275
|
+
for (const proof of proofList) {
|
|
276
|
+
const proofPk = proof.pk || x.issuer.pk;
|
|
277
|
+
const proofSigner = proof.signer || x.issuer.id;
|
|
278
|
+
if (!issuers.includes(proofSigner)) throw new Error("Proof signer not in trusted issuers");
|
|
279
|
+
if (proof.signer && proof.pk && !(0, _arcblock_did.isFromPublicKey)(proof.signer, proof.pk)) throw new Error("Proof pk does not match signer");
|
|
280
|
+
if (await (0, _ocap_wallet.fromPublicKey)(proofPk, (0, _arcblock_did.toTypeInfo)(proofSigner)).verify(signedContent, (0, _ocap_util.fromBase64)(proof.jws)) !== true) throw Error("Status credential signature not valid");
|
|
281
|
+
}
|
|
208
282
|
return x.claim;
|
|
209
283
|
}));
|
|
210
284
|
}
|
|
211
285
|
const stableStringify = json_stable_stringify.default;
|
|
212
286
|
|
|
213
287
|
//#endregion
|
|
288
|
+
exports.counterSign = counterSign;
|
|
214
289
|
exports.create = create;
|
|
215
290
|
exports.createCredentialList = createCredentialList;
|
|
216
291
|
exports.proofTypes = proofTypes;
|
package/lib/index.d.cts
CHANGED
|
@@ -7,7 +7,11 @@ interface Proof {
|
|
|
7
7
|
created: string;
|
|
8
8
|
proofPurpose: string;
|
|
9
9
|
jws: string;
|
|
10
|
+
id?: string;
|
|
11
|
+
signer?: string;
|
|
10
12
|
pk?: string;
|
|
13
|
+
previousProof?: string | string[];
|
|
14
|
+
verificationMethod?: string;
|
|
11
15
|
}
|
|
12
16
|
interface Issuer {
|
|
13
17
|
id: string;
|
|
@@ -24,14 +28,14 @@ interface CredentialStatus {
|
|
|
24
28
|
scope: string;
|
|
25
29
|
}
|
|
26
30
|
interface VerifiableCredential {
|
|
27
|
-
'@context': string;
|
|
31
|
+
'@context': string | string[];
|
|
28
32
|
id: string;
|
|
29
|
-
type: string;
|
|
33
|
+
type: string | string[];
|
|
30
34
|
issuer: Issuer;
|
|
31
35
|
issuanceDate: string;
|
|
32
36
|
expirationDate?: string;
|
|
33
37
|
credentialSubject: CredentialSubject;
|
|
34
|
-
proof: Proof;
|
|
38
|
+
proof: Proof | Proof[];
|
|
35
39
|
tag?: string;
|
|
36
40
|
credentialStatus?: CredentialStatus;
|
|
37
41
|
signature?: unknown;
|
|
@@ -50,7 +54,7 @@ interface Credential {
|
|
|
50
54
|
id: string;
|
|
51
55
|
issued: string;
|
|
52
56
|
issuer: Issuer;
|
|
53
|
-
proof: Proof;
|
|
57
|
+
proof: Proof | Proof[];
|
|
54
58
|
claim: unknown;
|
|
55
59
|
}
|
|
56
60
|
declare const proofTypes: Record<number, string>;
|
|
@@ -77,7 +81,7 @@ declare function create({
|
|
|
77
81
|
endpoint,
|
|
78
82
|
endpointScope
|
|
79
83
|
}: {
|
|
80
|
-
type: string;
|
|
84
|
+
type: string | string[];
|
|
81
85
|
subject: CredentialSubject;
|
|
82
86
|
issuer: IssuerInfo;
|
|
83
87
|
issuanceDate?: string;
|
|
@@ -110,6 +114,25 @@ declare function verify({
|
|
|
110
114
|
trustedIssuers: string | string[];
|
|
111
115
|
ignoreExpired?: boolean;
|
|
112
116
|
}): Promise<boolean>;
|
|
117
|
+
/**
|
|
118
|
+
* Counter-sign an existing verifiable credential
|
|
119
|
+
* Adds a new proof from a different signer to the VC
|
|
120
|
+
*
|
|
121
|
+
* @param params
|
|
122
|
+
* @param params.vc - The already-signed verifiable credential
|
|
123
|
+
* @param params.wallet - The counter-signer's wallet
|
|
124
|
+
* @param params.mode - 'set' (independent proof) or 'chain' (references previous proofs)
|
|
125
|
+
* @returns Promise<VerifiableCredential>
|
|
126
|
+
*/
|
|
127
|
+
declare function counterSign({
|
|
128
|
+
vc,
|
|
129
|
+
wallet,
|
|
130
|
+
mode
|
|
131
|
+
}: {
|
|
132
|
+
vc: VerifiableCredential;
|
|
133
|
+
wallet: WalletObject;
|
|
134
|
+
mode?: 'set' | 'chain';
|
|
135
|
+
}): Promise<VerifiableCredential>;
|
|
113
136
|
/**
|
|
114
137
|
* Verify that the Presentation is valid
|
|
115
138
|
* - It is signed by VC's owner
|
|
@@ -152,4 +175,4 @@ declare function verifyCredentialList({
|
|
|
152
175
|
}): Promise<unknown[]>;
|
|
153
176
|
declare const stableStringify: typeof stringify;
|
|
154
177
|
//#endregion
|
|
155
|
-
export { create, createCredentialList, proofTypes, stableStringify, verify, verifyCredentialList, verifyPresentation };
|
|
178
|
+
export { counterSign, create, createCredentialList, proofTypes, stableStringify, verify, verifyCredentialList, verifyPresentation };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcblock/vc",
|
|
3
|
-
"version": "1.29.
|
|
3
|
+
"version": "1.29.20",
|
|
4
4
|
"description": "TypeScript lib to work with ArcBlock Verifiable Credentials",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"arcblock",
|
|
@@ -72,10 +72,10 @@
|
|
|
72
72
|
"url": "https://github.com/ArcBlock/blockchain/issues"
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
|
-
"@arcblock/did": "1.29.
|
|
76
|
-
"@ocap/mcrypto": "1.29.
|
|
77
|
-
"@ocap/util": "1.29.
|
|
78
|
-
"@ocap/wallet": "1.29.
|
|
75
|
+
"@arcblock/did": "1.29.20",
|
|
76
|
+
"@ocap/mcrypto": "1.29.20",
|
|
77
|
+
"@ocap/util": "1.29.20",
|
|
78
|
+
"@ocap/wallet": "1.29.20",
|
|
79
79
|
"debug": "^4.4.3",
|
|
80
80
|
"is-absolute-url": "^3.0.3",
|
|
81
81
|
"json-stable-stringify": "^1.0.1",
|