@arcblock/vc 1.29.19 → 1.29.21

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 CHANGED
@@ -2,18 +2,15 @@ import { WalletObject } from "@ocap/wallet";
2
2
  import stringify from "json-stable-stringify";
3
3
 
4
4
  //#region src/index.d.ts
5
- interface VerificationMethod {
6
- id: string;
7
- type: string;
8
- controller: string;
9
- publicKeyMultibase: string;
10
- }
11
5
  interface Proof {
12
6
  type: string;
13
7
  created: string;
14
8
  proofPurpose: string;
15
9
  jws: string;
10
+ id?: string;
11
+ signer?: string;
16
12
  pk?: string;
13
+ previousProof?: string | string[];
17
14
  verificationMethod?: string;
18
15
  }
19
16
  interface Issuer {
@@ -38,7 +35,6 @@ interface VerifiableCredential {
38
35
  issuanceDate: string;
39
36
  expirationDate?: string;
40
37
  credentialSubject: CredentialSubject;
41
- verificationMethod?: VerificationMethod[];
42
38
  proof: Proof | Proof[];
43
39
  tag?: string;
44
40
  credentialStatus?: CredentialStatus;
@@ -58,12 +54,10 @@ interface Credential {
58
54
  id: string;
59
55
  issued: string;
60
56
  issuer: Issuer;
61
- verificationMethod?: VerificationMethod[];
62
57
  proof: Proof | Proof[];
63
58
  claim: unknown;
64
59
  }
65
60
  declare const proofTypes: Record<number, string>;
66
- declare const verificationKeyTypes: Record<number, string>;
67
61
  /**
68
62
  * Create a valid verifiable credential
69
63
  *
@@ -120,6 +114,25 @@ declare function verify({
120
114
  trustedIssuers: string | string[];
121
115
  ignoreExpired?: boolean;
122
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>;
123
136
  /**
124
137
  * Verify that the Presentation is valid
125
138
  * - It is signed by VC's owner
@@ -162,4 +175,4 @@ declare function verifyCredentialList({
162
175
  }): Promise<unknown[]>;
163
176
  declare const stableStringify: typeof stringify;
164
177
  //#endregion
165
- export { create, createCredentialList, proofTypes, stableStringify, verificationKeyTypes, verify, verifyCredentialList, verifyPresentation };
178
+ export { counterSign, create, createCredentialList, proofTypes, stableStringify, verify, verifyCredentialList, verifyPresentation };
package/esm/index.mjs CHANGED
@@ -22,18 +22,6 @@ const proofTypes = {
22
22
  [types.KeyType.SECP256K1]: "Secp256k1Signature",
23
23
  [types.KeyType.ETHEREUM]: "EthereumSignature"
24
24
  };
25
- const verificationKeyTypes = {
26
- [types.KeyType.ED25519]: "Ed25519VerificationKey2020",
27
- [types.KeyType.SECP256K1]: "EcdsaSecp256k1VerificationKey2019",
28
- [types.KeyType.ETHEREUM]: "EcdsaSecp256k1VerificationKey2019"
29
- };
30
- function resolvePublicKey(proof, verificationMethods, fallbackPk) {
31
- if (proof.verificationMethod && verificationMethods?.length) {
32
- const keyEntry = verificationMethods.find((k) => k.id === proof.verificationMethod);
33
- if (keyEntry?.publicKeyMultibase) return keyEntry.publicKeyMultibase;
34
- }
35
- return fallbackPk;
36
- }
37
25
  /**
38
26
  * Create a valid verifiable credential
39
27
  *
@@ -68,7 +56,6 @@ async function create({ type, subject, issuer, issuanceDate, expirationDate, tag
68
56
  if (!proofTypes[pkType]) throw new Error("Unsupported signer type when create verifiable credential");
69
57
  const issuanceDateValue = issuanceDate || (/* @__PURE__ */ new Date()).toISOString();
70
58
  const issuerPk = toBase58(wallet.publicKey);
71
- const keyId = `did:abt:${issuerDid}#key-1`;
72
59
  const vcObj = {
73
60
  "@context": ["https://www.w3.org/2018/credentials/v1", "https://schema.arcblock.io/v0.1/context.jsonld"],
74
61
  id: vcDid,
@@ -80,13 +67,7 @@ async function create({ type, subject, issuer, issuanceDate, expirationDate, tag
80
67
  },
81
68
  issuanceDate: issuanceDateValue,
82
69
  expirationDate,
83
- credentialSubject: subject,
84
- verificationMethod: [{
85
- id: keyId,
86
- type: verificationKeyTypes[pkType],
87
- controller: `did:abt:${issuerDid}`,
88
- publicKeyMultibase: issuerPk
89
- }]
70
+ credentialSubject: subject
90
71
  };
91
72
  if (tag) vcObj.tag = tag;
92
73
  if (endpoint) vcObj.credentialStatus = {
@@ -100,7 +81,9 @@ async function create({ type, subject, issuer, issuanceDate, expirationDate, tag
100
81
  type: proofTypes[pkType],
101
82
  created: issuanceDateValue,
102
83
  proofPurpose: "assertionMethod",
103
- verificationMethod: keyId,
84
+ id: crypto.randomUUID(),
85
+ signer: issuerDid,
86
+ pk: issuerPk,
104
87
  jws: toBase64(signature)
105
88
  },
106
89
  ...vcObj
@@ -135,16 +118,72 @@ async function verify({ vc, ownerDid, trustedIssuers, ignoreExpired = false }) {
135
118
  if (vc.issuanceDate === void 0) throw Error("Invalid verifiable credential issue date");
136
119
  if (new Date(vc.issuanceDate).getTime() > Date.now()) throw Error("Verifiable credential has not take effect");
137
120
  if (!ignoreExpired && vc.expirationDate !== void 0 && new Date(vc.expirationDate).getTime() < Date.now()) throw Error("Verifiable credential has expired");
138
- const issuerDid = (Array.isArray(trustedIssuers) ? trustedIssuers : [trustedIssuers]).find((x) => x === vc.issuer.id);
139
- if (!issuerDid) throw new Error("Verifiable credential not issued by trusted issuers");
140
- 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");
141
123
  if (ownerDid !== vc.credentialSubject.id) throw new Error("Verifiable credential not owned by specified owner did");
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
+ });
142
165
  const clone = cloneDeep(vc);
143
- delete clone.proof;
144
166
  delete clone.signature;
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;
145
181
  const signedContent = stringify(clone);
146
- for (const proof of proofList) if (await fromPublicKey(resolvePublicKey(proof, vc.verificationMethod, vc.issuer.pk), toTypeInfo(issuerDid)).verify(signedContent, fromBase64(proof.jws)) !== true) throw Error("Verifiable credential signature not valid");
147
- return true;
182
+ newProof.jws = toBase64(await wallet.sign(signedContent));
183
+ return {
184
+ ...vc,
185
+ proof: [...proofsWithIds, newProof]
186
+ };
148
187
  }
149
188
  /**
150
189
  * Verify that the Presentation is valid
@@ -194,7 +233,6 @@ async function createCredentialList({ claims, issuer, issuanceDate }) {
194
233
  const issued = issuanceDate || (/* @__PURE__ */ new Date()).toISOString();
195
234
  const pkType = typeInfo.pk;
196
235
  const issuerPk = toBase58(wallet.publicKey);
197
- const keyId = `did:abt:${issuerDid}#key-1`;
198
236
  return await Promise.all(claims.map(async (x) => {
199
237
  const vc = { claim: x };
200
238
  vc.id = fromPublicKeyHash(wallet.hash(stringify(vc.claim)), vcType);
@@ -204,18 +242,14 @@ async function createCredentialList({ claims, issuer, issuanceDate }) {
204
242
  pk: issuerPk,
205
243
  name: name$1 || issuerDid
206
244
  };
207
- vc.verificationMethod = [{
208
- id: keyId,
209
- type: verificationKeyTypes[pkType],
210
- controller: `did:abt:${issuerDid}`,
211
- publicKeyMultibase: issuerPk
212
- }];
213
245
  const signature = await wallet.sign(stringify(vc));
214
246
  vc.proof = {
215
247
  type: proofTypes[pkType],
216
248
  created: issued,
217
249
  proofPurpose: "assertionMethod",
218
- verificationMethod: keyId,
250
+ id: crypto.randomUUID(),
251
+ signer: issuerDid,
252
+ pk: issuerPk,
219
253
  jws: toBase64(signature)
220
254
  };
221
255
  return vc;
@@ -224,7 +258,8 @@ async function createCredentialList({ claims, issuer, issuanceDate }) {
224
258
  async function verifyCredentialList({ credentials, trustedIssuers }) {
225
259
  if (!credentials || !Array.isArray(credentials)) throw new Error("Can not verify with empty credentials list");
226
260
  return Promise.all(credentials.map(async (x) => {
227
- const issuerDid = (Array.isArray(trustedIssuers) ? trustedIssuers : [trustedIssuers]).find((d) => d === x.issuer.id);
261
+ const issuers = Array.isArray(trustedIssuers) ? trustedIssuers : [trustedIssuers];
262
+ const issuerDid = issuers.find((d) => d === x.issuer.id);
228
263
  if (!issuerDid) throw new Error("Credential not issued by trusted issuers");
229
264
  if (!isFromPublicKey(issuerDid, x.issuer.pk)) throw new Error("Credential not issuer pk not match with issuer did");
230
265
  const proofList = Array.isArray(x.proof) ? x.proof : x.proof ? [x.proof] : [];
@@ -232,11 +267,17 @@ async function verifyCredentialList({ credentials, trustedIssuers }) {
232
267
  const clone = cloneDeep(x);
233
268
  delete clone.proof;
234
269
  const signedContent = stringify(clone);
235
- for (const proof of proofList) if (await fromPublicKey(resolvePublicKey(proof, x.verificationMethod, x.issuer.pk), toTypeInfo(issuerDid)).verify(signedContent, fromBase64(proof.jws)) !== true) throw Error("Status credential signature not valid");
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
+ }
236
277
  return x.claim;
237
278
  }));
238
279
  }
239
280
  const stableStringify = stringify;
240
281
 
241
282
  //#endregion
242
- export { create, createCredentialList, proofTypes, stableStringify, verificationKeyTypes, verify, verifyCredentialList, verifyPresentation };
283
+ export { counterSign, create, createCredentialList, proofTypes, stableStringify, verify, verifyCredentialList, verifyPresentation };
package/lib/index.cjs CHANGED
@@ -27,18 +27,6 @@ const proofTypes = {
27
27
  [_ocap_mcrypto.types.KeyType.SECP256K1]: "Secp256k1Signature",
28
28
  [_ocap_mcrypto.types.KeyType.ETHEREUM]: "EthereumSignature"
29
29
  };
30
- const verificationKeyTypes = {
31
- [_ocap_mcrypto.types.KeyType.ED25519]: "Ed25519VerificationKey2020",
32
- [_ocap_mcrypto.types.KeyType.SECP256K1]: "EcdsaSecp256k1VerificationKey2019",
33
- [_ocap_mcrypto.types.KeyType.ETHEREUM]: "EcdsaSecp256k1VerificationKey2019"
34
- };
35
- function resolvePublicKey(proof, verificationMethods, fallbackPk) {
36
- if (proof.verificationMethod && verificationMethods?.length) {
37
- const keyEntry = verificationMethods.find((k) => k.id === proof.verificationMethod);
38
- if (keyEntry?.publicKeyMultibase) return keyEntry.publicKeyMultibase;
39
- }
40
- return fallbackPk;
41
- }
42
30
  /**
43
31
  * Create a valid verifiable credential
44
32
  *
@@ -73,7 +61,6 @@ async function create({ type, subject, issuer, issuanceDate, expirationDate, tag
73
61
  if (!proofTypes[pkType]) throw new Error("Unsupported signer type when create verifiable credential");
74
62
  const issuanceDateValue = issuanceDate || (/* @__PURE__ */ new Date()).toISOString();
75
63
  const issuerPk = (0, _ocap_util.toBase58)(wallet.publicKey);
76
- const keyId = `did:abt:${issuerDid}#key-1`;
77
64
  const vcObj = {
78
65
  "@context": ["https://www.w3.org/2018/credentials/v1", "https://schema.arcblock.io/v0.1/context.jsonld"],
79
66
  id: vcDid,
@@ -85,13 +72,7 @@ async function create({ type, subject, issuer, issuanceDate, expirationDate, tag
85
72
  },
86
73
  issuanceDate: issuanceDateValue,
87
74
  expirationDate,
88
- credentialSubject: subject,
89
- verificationMethod: [{
90
- id: keyId,
91
- type: verificationKeyTypes[pkType],
92
- controller: `did:abt:${issuerDid}`,
93
- publicKeyMultibase: issuerPk
94
- }]
75
+ credentialSubject: subject
95
76
  };
96
77
  if (tag) vcObj.tag = tag;
97
78
  if (endpoint) vcObj.credentialStatus = {
@@ -105,7 +86,9 @@ async function create({ type, subject, issuer, issuanceDate, expirationDate, tag
105
86
  type: proofTypes[pkType],
106
87
  created: issuanceDateValue,
107
88
  proofPurpose: "assertionMethod",
108
- verificationMethod: keyId,
89
+ id: crypto.randomUUID(),
90
+ signer: issuerDid,
91
+ pk: issuerPk,
109
92
  jws: (0, _ocap_util.toBase64)(signature)
110
93
  },
111
94
  ...vcObj
@@ -140,16 +123,72 @@ async function verify({ vc, ownerDid, trustedIssuers, ignoreExpired = false }) {
140
123
  if (vc.issuanceDate === void 0) throw Error("Invalid verifiable credential issue date");
141
124
  if (new Date(vc.issuanceDate).getTime() > Date.now()) throw Error("Verifiable credential has not take effect");
142
125
  if (!ignoreExpired && vc.expirationDate !== void 0 && new Date(vc.expirationDate).getTime() < Date.now()) throw Error("Verifiable credential has expired");
143
- const issuerDid = (Array.isArray(trustedIssuers) ? trustedIssuers : [trustedIssuers]).find((x) => x === vc.issuer.id);
144
- if (!issuerDid) throw new Error("Verifiable credential not issued by trusted issuers");
145
- 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");
146
128
  if (ownerDid !== vc.credentialSubject.id) throw new Error("Verifiable credential not owned by specified owner did");
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
+ });
147
170
  const clone = (0, lodash_cloneDeep.default)(vc);
148
- delete clone.proof;
149
171
  delete clone.signature;
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;
150
186
  const signedContent = (0, json_stable_stringify.default)(clone);
151
- for (const proof of proofList) if (await (0, _ocap_wallet.fromPublicKey)(resolvePublicKey(proof, vc.verificationMethod, vc.issuer.pk), (0, _arcblock_did.toTypeInfo)(issuerDid)).verify(signedContent, (0, _ocap_util.fromBase64)(proof.jws)) !== true) throw Error("Verifiable credential signature not valid");
152
- return true;
187
+ newProof.jws = (0, _ocap_util.toBase64)(await wallet.sign(signedContent));
188
+ return {
189
+ ...vc,
190
+ proof: [...proofsWithIds, newProof]
191
+ };
153
192
  }
154
193
  /**
155
194
  * Verify that the Presentation is valid
@@ -199,7 +238,6 @@ async function createCredentialList({ claims, issuer, issuanceDate }) {
199
238
  const issued = issuanceDate || (/* @__PURE__ */ new Date()).toISOString();
200
239
  const pkType = typeInfo.pk;
201
240
  const issuerPk = (0, _ocap_util.toBase58)(wallet.publicKey);
202
- const keyId = `did:abt:${issuerDid}#key-1`;
203
241
  return await Promise.all(claims.map(async (x) => {
204
242
  const vc = { claim: x };
205
243
  vc.id = (0, _arcblock_did.fromPublicKeyHash)(wallet.hash((0, json_stable_stringify.default)(vc.claim)), vcType);
@@ -209,18 +247,14 @@ async function createCredentialList({ claims, issuer, issuanceDate }) {
209
247
  pk: issuerPk,
210
248
  name: name$1 || issuerDid
211
249
  };
212
- vc.verificationMethod = [{
213
- id: keyId,
214
- type: verificationKeyTypes[pkType],
215
- controller: `did:abt:${issuerDid}`,
216
- publicKeyMultibase: issuerPk
217
- }];
218
250
  const signature = await wallet.sign((0, json_stable_stringify.default)(vc));
219
251
  vc.proof = {
220
252
  type: proofTypes[pkType],
221
253
  created: issued,
222
254
  proofPurpose: "assertionMethod",
223
- verificationMethod: keyId,
255
+ id: crypto.randomUUID(),
256
+ signer: issuerDid,
257
+ pk: issuerPk,
224
258
  jws: (0, _ocap_util.toBase64)(signature)
225
259
  };
226
260
  return vc;
@@ -229,7 +263,8 @@ async function createCredentialList({ claims, issuer, issuanceDate }) {
229
263
  async function verifyCredentialList({ credentials, trustedIssuers }) {
230
264
  if (!credentials || !Array.isArray(credentials)) throw new Error("Can not verify with empty credentials list");
231
265
  return Promise.all(credentials.map(async (x) => {
232
- const issuerDid = (Array.isArray(trustedIssuers) ? trustedIssuers : [trustedIssuers]).find((d) => d === x.issuer.id);
266
+ const issuers = Array.isArray(trustedIssuers) ? trustedIssuers : [trustedIssuers];
267
+ const issuerDid = issuers.find((d) => d === x.issuer.id);
233
268
  if (!issuerDid) throw new Error("Credential not issued by trusted issuers");
234
269
  if (!(0, _arcblock_did.isFromPublicKey)(issuerDid, x.issuer.pk)) throw new Error("Credential not issuer pk not match with issuer did");
235
270
  const proofList = Array.isArray(x.proof) ? x.proof : x.proof ? [x.proof] : [];
@@ -237,18 +272,24 @@ async function verifyCredentialList({ credentials, trustedIssuers }) {
237
272
  const clone = (0, lodash_cloneDeep.default)(x);
238
273
  delete clone.proof;
239
274
  const signedContent = (0, json_stable_stringify.default)(clone);
240
- for (const proof of proofList) if (await (0, _ocap_wallet.fromPublicKey)(resolvePublicKey(proof, x.verificationMethod, x.issuer.pk), (0, _arcblock_did.toTypeInfo)(issuerDid)).verify(signedContent, (0, _ocap_util.fromBase64)(proof.jws)) !== true) throw Error("Status credential signature not valid");
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
+ }
241
282
  return x.claim;
242
283
  }));
243
284
  }
244
285
  const stableStringify = json_stable_stringify.default;
245
286
 
246
287
  //#endregion
288
+ exports.counterSign = counterSign;
247
289
  exports.create = create;
248
290
  exports.createCredentialList = createCredentialList;
249
291
  exports.proofTypes = proofTypes;
250
292
  exports.stableStringify = stableStringify;
251
- exports.verificationKeyTypes = verificationKeyTypes;
252
293
  exports.verify = verify;
253
294
  exports.verifyCredentialList = verifyCredentialList;
254
295
  exports.verifyPresentation = verifyPresentation;
package/lib/index.d.cts CHANGED
@@ -2,18 +2,15 @@ import { WalletObject } from "@ocap/wallet";
2
2
  import stringify from "json-stable-stringify";
3
3
 
4
4
  //#region src/index.d.ts
5
- interface VerificationMethod {
6
- id: string;
7
- type: string;
8
- controller: string;
9
- publicKeyMultibase: string;
10
- }
11
5
  interface Proof {
12
6
  type: string;
13
7
  created: string;
14
8
  proofPurpose: string;
15
9
  jws: string;
10
+ id?: string;
11
+ signer?: string;
16
12
  pk?: string;
13
+ previousProof?: string | string[];
17
14
  verificationMethod?: string;
18
15
  }
19
16
  interface Issuer {
@@ -38,7 +35,6 @@ interface VerifiableCredential {
38
35
  issuanceDate: string;
39
36
  expirationDate?: string;
40
37
  credentialSubject: CredentialSubject;
41
- verificationMethod?: VerificationMethod[];
42
38
  proof: Proof | Proof[];
43
39
  tag?: string;
44
40
  credentialStatus?: CredentialStatus;
@@ -58,12 +54,10 @@ interface Credential {
58
54
  id: string;
59
55
  issued: string;
60
56
  issuer: Issuer;
61
- verificationMethod?: VerificationMethod[];
62
57
  proof: Proof | Proof[];
63
58
  claim: unknown;
64
59
  }
65
60
  declare const proofTypes: Record<number, string>;
66
- declare const verificationKeyTypes: Record<number, string>;
67
61
  /**
68
62
  * Create a valid verifiable credential
69
63
  *
@@ -120,6 +114,25 @@ declare function verify({
120
114
  trustedIssuers: string | string[];
121
115
  ignoreExpired?: boolean;
122
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>;
123
136
  /**
124
137
  * Verify that the Presentation is valid
125
138
  * - It is signed by VC's owner
@@ -162,4 +175,4 @@ declare function verifyCredentialList({
162
175
  }): Promise<unknown[]>;
163
176
  declare const stableStringify: typeof stringify;
164
177
  //#endregion
165
- export { create, createCredentialList, proofTypes, stableStringify, verificationKeyTypes, 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.19",
3
+ "version": "1.29.21",
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.19",
76
- "@ocap/mcrypto": "1.29.19",
77
- "@ocap/util": "1.29.19",
78
- "@ocap/wallet": "1.29.19",
75
+ "@arcblock/did": "1.29.21",
76
+ "@ocap/mcrypto": "1.29.21",
77
+ "@ocap/util": "1.29.21",
78
+ "@ocap/wallet": "1.29.21",
79
79
  "debug": "^4.4.3",
80
80
  "is-absolute-url": "^3.0.3",
81
81
  "json-stable-stringify": "^1.0.1",