@arcblock/vc 1.28.9 → 1.29.0

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 CHANGED
@@ -1,6 +1,5 @@
1
1
  ![Verifiable Credentials](https://www.arcblock.io/.netlify/functions/badge/?text=Verifiable+Credentials)
2
2
 
3
- [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
4
3
  [![docs](https://img.shields.io/badge/powered%20by-arcblock-green.svg)](https://docs.arcblock.io)
5
4
  [![Gitter](https://badges.gitter.im/ArcBlock/community.svg)](https://gitter.im/ArcBlock/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
6
5
 
@@ -0,0 +1,155 @@
1
+ import { WalletObject } from "@ocap/wallet";
2
+ import stringify from "json-stable-stringify";
3
+
4
+ //#region src/index.d.ts
5
+ interface Proof {
6
+ type: string;
7
+ created: string;
8
+ proofPurpose: string;
9
+ jws: string;
10
+ pk?: string;
11
+ }
12
+ interface Issuer {
13
+ id: string;
14
+ pk: string;
15
+ name: string;
16
+ }
17
+ interface CredentialSubject {
18
+ id: string;
19
+ [key: string]: unknown;
20
+ }
21
+ interface CredentialStatus {
22
+ id: string;
23
+ type: string;
24
+ scope: string;
25
+ }
26
+ interface VerifiableCredential {
27
+ '@context': string;
28
+ id: string;
29
+ type: string;
30
+ issuer: Issuer;
31
+ issuanceDate: string;
32
+ expirationDate?: string;
33
+ credentialSubject: CredentialSubject;
34
+ proof: Proof;
35
+ tag?: string;
36
+ credentialStatus?: CredentialStatus;
37
+ signature?: unknown;
38
+ }
39
+ interface IssuerInfo {
40
+ wallet: WalletObject;
41
+ name?: string;
42
+ }
43
+ interface Presentation {
44
+ challenge: string;
45
+ verifiableCredential: string | string[];
46
+ proof: Proof | Proof[];
47
+ [key: string]: unknown;
48
+ }
49
+ interface Credential {
50
+ id: string;
51
+ issued: string;
52
+ issuer: Issuer;
53
+ proof: Proof;
54
+ claim: unknown;
55
+ }
56
+ declare const proofTypes: Record<number, string>;
57
+ /**
58
+ * Create a valid verifiable credential
59
+ *
60
+ * @param params
61
+ * @param params.type - The type of credential
62
+ * @param params.subject - The content of credential
63
+ * @param params.issuer - The issuer name and wallet
64
+ * @param params.issuanceDate
65
+ * @param params.expirationDate
66
+ * @param params.endpoint - Status endpoint url
67
+ * @param params.endpointScope - Endpoint scope, either be public or private
68
+ * @returns Promise<object>
69
+ */
70
+ declare function create({
71
+ type,
72
+ subject,
73
+ issuer,
74
+ issuanceDate,
75
+ expirationDate,
76
+ tag,
77
+ endpoint,
78
+ endpointScope
79
+ }: {
80
+ type: string;
81
+ subject: CredentialSubject;
82
+ issuer: IssuerInfo;
83
+ issuanceDate?: string;
84
+ expirationDate?: string;
85
+ tag?: string;
86
+ endpoint?: string;
87
+ endpointScope?: string;
88
+ }): Promise<VerifiableCredential | null>;
89
+ /**
90
+ * Verify that the verifiable credential is valid
91
+ * - It is signed by a whitelist of issuers
92
+ * - It is owned by the vc.subject.id
93
+ * - It has valid signature by the issuer
94
+ * - It is not expired
95
+ *
96
+ * @param vc - the verifiable credential object
97
+ * @param ownerDid - vc holder/owner did
98
+ * @param trustedIssuers - list of issuer did
99
+ * @throws Error
100
+ * @returns Promise<boolean>
101
+ */
102
+ declare function verify({
103
+ vc,
104
+ ownerDid,
105
+ trustedIssuers,
106
+ ignoreExpired
107
+ }: {
108
+ vc: VerifiableCredential | null;
109
+ ownerDid: string;
110
+ trustedIssuers: string | string[];
111
+ ignoreExpired?: boolean;
112
+ }): Promise<boolean>;
113
+ /**
114
+ * Verify that the Presentation is valid
115
+ * - It is signed by VC's owner
116
+ * - It contain challenge
117
+ * - It has valid signature by the issuer
118
+ * - It is not expired
119
+ *
120
+ * @param presentation - the presentation object
121
+ * @param trustedIssuers - list of issuer did
122
+ * @param challenge - Random byte you want
123
+ * @throws Error
124
+ * @returns Promise<boolean>
125
+ */
126
+ declare function verifyPresentation({
127
+ presentation,
128
+ trustedIssuers,
129
+ challenge,
130
+ ignoreExpired
131
+ }: {
132
+ presentation: Presentation;
133
+ trustedIssuers: string[];
134
+ challenge: string;
135
+ ignoreExpired?: boolean;
136
+ }): Promise<boolean>;
137
+ declare function createCredentialList({
138
+ claims,
139
+ issuer,
140
+ issuanceDate
141
+ }: {
142
+ claims: unknown[];
143
+ issuer: IssuerInfo;
144
+ issuanceDate?: string;
145
+ }): Promise<Credential[]>;
146
+ declare function verifyCredentialList({
147
+ credentials,
148
+ trustedIssuers
149
+ }: {
150
+ credentials: Credential[];
151
+ trustedIssuers: string | string[];
152
+ }): Promise<unknown[]>;
153
+ declare const stableStringify: typeof stringify;
154
+ //#endregion
155
+ export { create, createCredentialList, proofTypes, stableStringify, verify, verifyCredentialList, verifyPresentation };
package/esm/index.mjs ADDED
@@ -0,0 +1,209 @@
1
+ import { name } from "./package.mjs";
2
+ import { fromPublicKeyHash, isFromPublicKey, isValid, toTypeInfo } from "@arcblock/did";
3
+ import { types } from "@ocap/mcrypto";
4
+ import { fromBase58, fromBase64, toBase58, toBase64 } from "@ocap/util";
5
+ import { fromPublicKey } from "@ocap/wallet";
6
+ import Debug from "debug";
7
+ import isAbsoluteUrl from "is-absolute-url";
8
+ import stringify from "json-stable-stringify";
9
+ import cloneDeep from "lodash/cloneDeep.js";
10
+
11
+ //#region src/index.ts
12
+ /**
13
+ * @fileOverview Utility functions to create/verify vc
14
+ *
15
+ * @module @arcblock/vc
16
+ * @requires @arcblock/did
17
+ * @requires @ocap/util
18
+ */
19
+ const debug = Debug(name);
20
+ const proofTypes = {
21
+ [types.KeyType.ED25519]: "Ed25519Signature",
22
+ [types.KeyType.SECP256K1]: "Secp256k1Signature",
23
+ [types.KeyType.ETHEREUM]: "EthereumSignature"
24
+ };
25
+ /**
26
+ * Create a valid verifiable credential
27
+ *
28
+ * @param params
29
+ * @param params.type - The type of credential
30
+ * @param params.subject - The content of credential
31
+ * @param params.issuer - The issuer name and wallet
32
+ * @param params.issuanceDate
33
+ * @param params.expirationDate
34
+ * @param params.endpoint - Status endpoint url
35
+ * @param params.endpointScope - Endpoint scope, either be public or private
36
+ * @returns Promise<object>
37
+ */
38
+ async function create({ type, subject, issuer, issuanceDate, expirationDate, tag = "", endpoint = "", endpointScope = "public" }) {
39
+ if (!type) throw new Error("Can not create verifiable credential without empty type");
40
+ if (!subject) throw new Error("Can not create verifiable credential from empty subject");
41
+ if (!subject.id) throw new Error("Can not create verifiable credential without holder");
42
+ if (!isValid(subject.id)) throw new Error("Can not create verifiable credential invalid holder did");
43
+ if (endpoint && isAbsoluteUrl(endpoint) === false) throw new Error("VC Endpoint must be absolute url");
44
+ if (endpointScope && ["public", "private"].includes(endpointScope) === false) throw new Error("VC Endpoint scope must be either public or private");
45
+ const { wallet, name: issuerName } = issuer;
46
+ const issuerDid = wallet.address;
47
+ const typeInfo = toTypeInfo(issuerDid);
48
+ const vcType = {
49
+ ...typeInfo,
50
+ role: types.RoleType.ROLE_VC
51
+ };
52
+ const vcDid = fromPublicKeyHash(wallet.hash(stringify(subject)), vcType);
53
+ const issuanceDateValue = issuanceDate || (/* @__PURE__ */ new Date()).toISOString();
54
+ const vcObj = {
55
+ "@context": "https://schema.arcblock.io/v0.1/context.jsonld",
56
+ id: vcDid,
57
+ type,
58
+ issuer: {
59
+ id: issuerDid,
60
+ pk: toBase58(wallet.publicKey),
61
+ name: issuerName || issuerDid
62
+ },
63
+ issuanceDate: issuanceDateValue,
64
+ expirationDate,
65
+ credentialSubject: subject
66
+ };
67
+ if (tag) vcObj.tag = tag;
68
+ if (endpoint) vcObj.credentialStatus = {
69
+ id: endpoint,
70
+ type: "NFTStatusList2021",
71
+ scope: endpointScope || "public"
72
+ };
73
+ const pkType = typeInfo.pk;
74
+ if (!proofTypes[pkType]) throw new Error("Unsupported signer type when create verifiable credential");
75
+ const signature = await wallet.sign(stringify(vcObj));
76
+ const result = {
77
+ proof: {
78
+ type: proofTypes[pkType],
79
+ created: issuanceDateValue,
80
+ proofPurpose: "assertionMethod",
81
+ jws: toBase64(signature)
82
+ },
83
+ ...vcObj
84
+ };
85
+ debug("create", result);
86
+ if (await verify({
87
+ vc: result,
88
+ ownerDid: subject.id,
89
+ trustedIssuers: [issuerDid]
90
+ })) return result;
91
+ return null;
92
+ }
93
+ /**
94
+ * Verify that the verifiable credential is valid
95
+ * - It is signed by a whitelist of issuers
96
+ * - It is owned by the vc.subject.id
97
+ * - It has valid signature by the issuer
98
+ * - It is not expired
99
+ *
100
+ * @param vc - the verifiable credential object
101
+ * @param ownerDid - vc holder/owner did
102
+ * @param trustedIssuers - list of issuer did
103
+ * @throws Error
104
+ * @returns Promise<boolean>
105
+ */
106
+ async function verify({ vc, ownerDid, trustedIssuers, ignoreExpired = false }) {
107
+ if (!vc) throw new Error("Empty verifiable credential object");
108
+ if (!vc.issuer || !vc.issuer.id || !vc.issuer.pk || !isValid(vc.issuer.id)) throw new Error("Invalid verifiable credential issuer");
109
+ if (!vc.credentialSubject || !vc.credentialSubject.id || !isValid(vc.credentialSubject.id)) throw new Error("Invalid verifiable credential subject");
110
+ if (!vc.proof || !vc.proof.jws) throw new Error("Invalid verifiable credential proof");
111
+ if (vc.issuanceDate === void 0) throw Error("Invalid verifiable credential issue date");
112
+ if (new Date(vc.issuanceDate).getTime() > Date.now()) throw Error("Verifiable credential has not take effect");
113
+ if (!ignoreExpired && vc.expirationDate !== void 0 && new Date(vc.expirationDate).getTime() < Date.now()) throw Error("Verifiable credential has expired");
114
+ const issuerDid = (Array.isArray(trustedIssuers) ? trustedIssuers : [trustedIssuers]).find((x) => x === vc.issuer.id);
115
+ if (!issuerDid) throw new Error("Verifiable credential not issued by trusted issuers");
116
+ if (!isFromPublicKey(issuerDid, vc.issuer.pk)) throw new Error("Verifiable credential not issuer pk not match with issuer did");
117
+ if (ownerDid !== vc.credentialSubject.id) throw new Error("Verifiable credential not owned by specified owner did");
118
+ const issuerWallet = fromPublicKey(vc.issuer.pk, toTypeInfo(issuerDid));
119
+ const clone = cloneDeep(vc);
120
+ const signatureStr = clone.proof.jws;
121
+ delete clone.proof;
122
+ delete clone.signature;
123
+ if (await issuerWallet.verify(stringify(clone), fromBase64(signatureStr)) !== true) throw Error("Verifiable credential signature not valid");
124
+ return true;
125
+ }
126
+ /**
127
+ * Verify that the Presentation is valid
128
+ * - It is signed by VC's owner
129
+ * - It contain challenge
130
+ * - It has valid signature by the issuer
131
+ * - It is not expired
132
+ *
133
+ * @param presentation - the presentation object
134
+ * @param trustedIssuers - list of issuer did
135
+ * @param challenge - Random byte you want
136
+ * @throws Error
137
+ * @returns Promise<boolean>
138
+ */
139
+ async function verifyPresentation({ presentation, trustedIssuers, challenge, ignoreExpired = false }) {
140
+ if (!presentation.challenge || challenge !== presentation.challenge) throw Error("Invalid challenge included on vc presentation");
141
+ const vcList = Array.isArray(presentation.verifiableCredential) ? presentation.verifiableCredential : [presentation.verifiableCredential];
142
+ const proofList = Array.isArray(presentation.proof) ? presentation.proof : [presentation.proof];
143
+ const clone = cloneDeep(presentation);
144
+ delete clone.proof;
145
+ await Promise.all(vcList.map(async (vcStr) => {
146
+ const vcObj = JSON.parse(vcStr);
147
+ const proof = proofList.find((x) => isFromPublicKey(vcObj.credentialSubject.id, x.pk));
148
+ if (!proof) throw Error(`VC does not have corresponding proof: ${vcStr}`);
149
+ const signatureStr = proof.jws;
150
+ if (await fromPublicKey(fromBase58(proof.pk), toTypeInfo(vcObj.credentialSubject.id)).verify(stringify(clone), fromBase64(signatureStr)) !== true) throw Error("Presentation signature invalid");
151
+ await verify({
152
+ vc: vcObj,
153
+ ownerDid: vcObj.credentialSubject.id,
154
+ trustedIssuers,
155
+ ignoreExpired
156
+ });
157
+ }));
158
+ return true;
159
+ }
160
+ async function createCredentialList({ claims, issuer, issuanceDate }) {
161
+ if (!claims || !Array.isArray(claims)) throw new Error("Can not create credential list with empty claim list");
162
+ if (!issuer || !issuer.wallet || !issuer.name) throw new Error("Can not create credential list with empty issuer name or wallet");
163
+ if (typeof issuer.wallet.sign !== "function") throw new Error("Can not create credential list with invalid issuer wallet");
164
+ const { wallet, name: name$1 } = issuer;
165
+ const issuerDid = wallet.address;
166
+ const typeInfo = toTypeInfo(issuerDid);
167
+ const vcType = {
168
+ ...typeInfo,
169
+ role: types.RoleType.ROLE_VC
170
+ };
171
+ const issued = issuanceDate || (/* @__PURE__ */ new Date()).toISOString();
172
+ const pkType = typeInfo.pk;
173
+ return await Promise.all(claims.map(async (x) => {
174
+ const vc = { claim: x };
175
+ vc.id = fromPublicKeyHash(wallet.hash(stringify(vc.claim)), vcType);
176
+ vc.issued = issued;
177
+ vc.issuer = {
178
+ id: issuerDid,
179
+ pk: toBase58(wallet.publicKey),
180
+ name: name$1 || issuerDid
181
+ };
182
+ const signature = await wallet.sign(stringify(vc));
183
+ vc.proof = {
184
+ type: proofTypes[pkType],
185
+ created: issued,
186
+ proofPurpose: "assertionMethod",
187
+ jws: toBase64(signature)
188
+ };
189
+ return vc;
190
+ }));
191
+ }
192
+ async function verifyCredentialList({ credentials, trustedIssuers }) {
193
+ if (!credentials || !Array.isArray(credentials)) throw new Error("Can not verify with empty credentials list");
194
+ return Promise.all(credentials.map(async (x) => {
195
+ const issuerDid = (Array.isArray(trustedIssuers) ? trustedIssuers : [trustedIssuers]).find((d) => d === x.issuer.id);
196
+ if (!issuerDid) throw new Error("Credential not issued by trusted issuers");
197
+ if (!isFromPublicKey(issuerDid, x.issuer.pk)) throw new Error("Credential not issuer pk not match with issuer did");
198
+ const issuerWallet = fromPublicKey(x.issuer.pk, toTypeInfo(issuerDid));
199
+ const clone = cloneDeep(x);
200
+ const signatureStr = clone.proof.jws;
201
+ delete clone.proof;
202
+ if (await issuerWallet.verify(stringify(clone), fromBase64(signatureStr)) !== true) throw Error("Status credential signature not valid");
203
+ return x.claim;
204
+ }));
205
+ }
206
+ const stableStringify = stringify;
207
+
208
+ //#endregion
209
+ export { create, createCredentialList, proofTypes, stableStringify, verify, verifyCredentialList, verifyPresentation };
@@ -0,0 +1,5 @@
1
+ //#region package.json
2
+ var name = "@arcblock/vc";
3
+
4
+ //#endregion
5
+ export { name };
@@ -0,0 +1,29 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) {
13
+ __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ }
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+
27
+ //#endregion
28
+
29
+ exports.__toESM = __toESM;
package/lib/index.cjs ADDED
@@ -0,0 +1,220 @@
1
+ const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
2
+ const require_package = require('./package.cjs');
3
+ let _arcblock_did = require("@arcblock/did");
4
+ let _ocap_mcrypto = require("@ocap/mcrypto");
5
+ let _ocap_util = require("@ocap/util");
6
+ let _ocap_wallet = require("@ocap/wallet");
7
+ let debug = require("debug");
8
+ debug = require_rolldown_runtime.__toESM(debug);
9
+ let is_absolute_url = require("is-absolute-url");
10
+ is_absolute_url = require_rolldown_runtime.__toESM(is_absolute_url);
11
+ let json_stable_stringify = require("json-stable-stringify");
12
+ json_stable_stringify = require_rolldown_runtime.__toESM(json_stable_stringify);
13
+ let lodash_cloneDeep = require("lodash/cloneDeep");
14
+ lodash_cloneDeep = require_rolldown_runtime.__toESM(lodash_cloneDeep);
15
+
16
+ //#region src/index.ts
17
+ /**
18
+ * @fileOverview Utility functions to create/verify vc
19
+ *
20
+ * @module @arcblock/vc
21
+ * @requires @arcblock/did
22
+ * @requires @ocap/util
23
+ */
24
+ const debug$1 = (0, debug.default)(require_package.name);
25
+ const proofTypes = {
26
+ [_ocap_mcrypto.types.KeyType.ED25519]: "Ed25519Signature",
27
+ [_ocap_mcrypto.types.KeyType.SECP256K1]: "Secp256k1Signature",
28
+ [_ocap_mcrypto.types.KeyType.ETHEREUM]: "EthereumSignature"
29
+ };
30
+ /**
31
+ * Create a valid verifiable credential
32
+ *
33
+ * @param params
34
+ * @param params.type - The type of credential
35
+ * @param params.subject - The content of credential
36
+ * @param params.issuer - The issuer name and wallet
37
+ * @param params.issuanceDate
38
+ * @param params.expirationDate
39
+ * @param params.endpoint - Status endpoint url
40
+ * @param params.endpointScope - Endpoint scope, either be public or private
41
+ * @returns Promise<object>
42
+ */
43
+ async function create({ type, subject, issuer, issuanceDate, expirationDate, tag = "", endpoint = "", endpointScope = "public" }) {
44
+ if (!type) throw new Error("Can not create verifiable credential without empty type");
45
+ if (!subject) throw new Error("Can not create verifiable credential from empty subject");
46
+ if (!subject.id) throw new Error("Can not create verifiable credential without holder");
47
+ if (!(0, _arcblock_did.isValid)(subject.id)) throw new Error("Can not create verifiable credential invalid holder did");
48
+ if (endpoint && (0, is_absolute_url.default)(endpoint) === false) throw new Error("VC Endpoint must be absolute url");
49
+ if (endpointScope && ["public", "private"].includes(endpointScope) === false) throw new Error("VC Endpoint scope must be either public or private");
50
+ const { wallet, name: issuerName } = issuer;
51
+ const issuerDid = wallet.address;
52
+ const typeInfo = (0, _arcblock_did.toTypeInfo)(issuerDid);
53
+ const vcType = {
54
+ ...typeInfo,
55
+ role: _ocap_mcrypto.types.RoleType.ROLE_VC
56
+ };
57
+ const vcDid = (0, _arcblock_did.fromPublicKeyHash)(wallet.hash((0, json_stable_stringify.default)(subject)), vcType);
58
+ const issuanceDateValue = issuanceDate || (/* @__PURE__ */ new Date()).toISOString();
59
+ const vcObj = {
60
+ "@context": "https://schema.arcblock.io/v0.1/context.jsonld",
61
+ id: vcDid,
62
+ type,
63
+ issuer: {
64
+ id: issuerDid,
65
+ pk: (0, _ocap_util.toBase58)(wallet.publicKey),
66
+ name: issuerName || issuerDid
67
+ },
68
+ issuanceDate: issuanceDateValue,
69
+ expirationDate,
70
+ credentialSubject: subject
71
+ };
72
+ if (tag) vcObj.tag = tag;
73
+ if (endpoint) vcObj.credentialStatus = {
74
+ id: endpoint,
75
+ type: "NFTStatusList2021",
76
+ scope: endpointScope || "public"
77
+ };
78
+ const pkType = typeInfo.pk;
79
+ if (!proofTypes[pkType]) throw new Error("Unsupported signer type when create verifiable credential");
80
+ const signature = await wallet.sign((0, json_stable_stringify.default)(vcObj));
81
+ const result = {
82
+ proof: {
83
+ type: proofTypes[pkType],
84
+ created: issuanceDateValue,
85
+ proofPurpose: "assertionMethod",
86
+ jws: (0, _ocap_util.toBase64)(signature)
87
+ },
88
+ ...vcObj
89
+ };
90
+ debug$1("create", result);
91
+ if (await verify({
92
+ vc: result,
93
+ ownerDid: subject.id,
94
+ trustedIssuers: [issuerDid]
95
+ })) return result;
96
+ return null;
97
+ }
98
+ /**
99
+ * Verify that the verifiable credential is valid
100
+ * - It is signed by a whitelist of issuers
101
+ * - It is owned by the vc.subject.id
102
+ * - It has valid signature by the issuer
103
+ * - It is not expired
104
+ *
105
+ * @param vc - the verifiable credential object
106
+ * @param ownerDid - vc holder/owner did
107
+ * @param trustedIssuers - list of issuer did
108
+ * @throws Error
109
+ * @returns Promise<boolean>
110
+ */
111
+ async function verify({ vc, ownerDid, trustedIssuers, ignoreExpired = false }) {
112
+ if (!vc) throw new Error("Empty verifiable credential object");
113
+ if (!vc.issuer || !vc.issuer.id || !vc.issuer.pk || !(0, _arcblock_did.isValid)(vc.issuer.id)) throw new Error("Invalid verifiable credential issuer");
114
+ if (!vc.credentialSubject || !vc.credentialSubject.id || !(0, _arcblock_did.isValid)(vc.credentialSubject.id)) throw new Error("Invalid verifiable credential subject");
115
+ if (!vc.proof || !vc.proof.jws) throw new Error("Invalid verifiable credential proof");
116
+ if (vc.issuanceDate === void 0) throw Error("Invalid verifiable credential issue date");
117
+ if (new Date(vc.issuanceDate).getTime() > Date.now()) throw Error("Verifiable credential has not take effect");
118
+ if (!ignoreExpired && vc.expirationDate !== void 0 && new Date(vc.expirationDate).getTime() < Date.now()) throw Error("Verifiable credential has expired");
119
+ const issuerDid = (Array.isArray(trustedIssuers) ? trustedIssuers : [trustedIssuers]).find((x) => x === vc.issuer.id);
120
+ if (!issuerDid) throw new Error("Verifiable credential not issued by trusted issuers");
121
+ if (!(0, _arcblock_did.isFromPublicKey)(issuerDid, vc.issuer.pk)) throw new Error("Verifiable credential not issuer pk not match with issuer did");
122
+ if (ownerDid !== vc.credentialSubject.id) throw new Error("Verifiable credential not owned by specified owner did");
123
+ const issuerWallet = (0, _ocap_wallet.fromPublicKey)(vc.issuer.pk, (0, _arcblock_did.toTypeInfo)(issuerDid));
124
+ const clone = (0, lodash_cloneDeep.default)(vc);
125
+ const signatureStr = clone.proof.jws;
126
+ delete clone.proof;
127
+ delete clone.signature;
128
+ if (await issuerWallet.verify((0, json_stable_stringify.default)(clone), (0, _ocap_util.fromBase64)(signatureStr)) !== true) throw Error("Verifiable credential signature not valid");
129
+ return true;
130
+ }
131
+ /**
132
+ * Verify that the Presentation is valid
133
+ * - It is signed by VC's owner
134
+ * - It contain challenge
135
+ * - It has valid signature by the issuer
136
+ * - It is not expired
137
+ *
138
+ * @param presentation - the presentation object
139
+ * @param trustedIssuers - list of issuer did
140
+ * @param challenge - Random byte you want
141
+ * @throws Error
142
+ * @returns Promise<boolean>
143
+ */
144
+ async function verifyPresentation({ presentation, trustedIssuers, challenge, ignoreExpired = false }) {
145
+ if (!presentation.challenge || challenge !== presentation.challenge) throw Error("Invalid challenge included on vc presentation");
146
+ const vcList = Array.isArray(presentation.verifiableCredential) ? presentation.verifiableCredential : [presentation.verifiableCredential];
147
+ const proofList = Array.isArray(presentation.proof) ? presentation.proof : [presentation.proof];
148
+ const clone = (0, lodash_cloneDeep.default)(presentation);
149
+ delete clone.proof;
150
+ await Promise.all(vcList.map(async (vcStr) => {
151
+ const vcObj = JSON.parse(vcStr);
152
+ const proof = proofList.find((x) => (0, _arcblock_did.isFromPublicKey)(vcObj.credentialSubject.id, x.pk));
153
+ if (!proof) throw Error(`VC does not have corresponding proof: ${vcStr}`);
154
+ const signatureStr = proof.jws;
155
+ if (await (0, _ocap_wallet.fromPublicKey)((0, _ocap_util.fromBase58)(proof.pk), (0, _arcblock_did.toTypeInfo)(vcObj.credentialSubject.id)).verify((0, json_stable_stringify.default)(clone), (0, _ocap_util.fromBase64)(signatureStr)) !== true) throw Error("Presentation signature invalid");
156
+ await verify({
157
+ vc: vcObj,
158
+ ownerDid: vcObj.credentialSubject.id,
159
+ trustedIssuers,
160
+ ignoreExpired
161
+ });
162
+ }));
163
+ return true;
164
+ }
165
+ async function createCredentialList({ claims, issuer, issuanceDate }) {
166
+ if (!claims || !Array.isArray(claims)) throw new Error("Can not create credential list with empty claim list");
167
+ if (!issuer || !issuer.wallet || !issuer.name) throw new Error("Can not create credential list with empty issuer name or wallet");
168
+ if (typeof issuer.wallet.sign !== "function") throw new Error("Can not create credential list with invalid issuer wallet");
169
+ const { wallet, name: name$1 } = issuer;
170
+ const issuerDid = wallet.address;
171
+ const typeInfo = (0, _arcblock_did.toTypeInfo)(issuerDid);
172
+ const vcType = {
173
+ ...typeInfo,
174
+ role: _ocap_mcrypto.types.RoleType.ROLE_VC
175
+ };
176
+ const issued = issuanceDate || (/* @__PURE__ */ new Date()).toISOString();
177
+ const pkType = typeInfo.pk;
178
+ return await Promise.all(claims.map(async (x) => {
179
+ const vc = { claim: x };
180
+ vc.id = (0, _arcblock_did.fromPublicKeyHash)(wallet.hash((0, json_stable_stringify.default)(vc.claim)), vcType);
181
+ vc.issued = issued;
182
+ vc.issuer = {
183
+ id: issuerDid,
184
+ pk: (0, _ocap_util.toBase58)(wallet.publicKey),
185
+ name: name$1 || issuerDid
186
+ };
187
+ const signature = await wallet.sign((0, json_stable_stringify.default)(vc));
188
+ vc.proof = {
189
+ type: proofTypes[pkType],
190
+ created: issued,
191
+ proofPurpose: "assertionMethod",
192
+ jws: (0, _ocap_util.toBase64)(signature)
193
+ };
194
+ return vc;
195
+ }));
196
+ }
197
+ async function verifyCredentialList({ credentials, trustedIssuers }) {
198
+ if (!credentials || !Array.isArray(credentials)) throw new Error("Can not verify with empty credentials list");
199
+ return Promise.all(credentials.map(async (x) => {
200
+ const issuerDid = (Array.isArray(trustedIssuers) ? trustedIssuers : [trustedIssuers]).find((d) => d === x.issuer.id);
201
+ if (!issuerDid) throw new Error("Credential not issued by trusted issuers");
202
+ if (!(0, _arcblock_did.isFromPublicKey)(issuerDid, x.issuer.pk)) throw new Error("Credential not issuer pk not match with issuer did");
203
+ const issuerWallet = (0, _ocap_wallet.fromPublicKey)(x.issuer.pk, (0, _arcblock_did.toTypeInfo)(issuerDid));
204
+ const clone = (0, lodash_cloneDeep.default)(x);
205
+ const signatureStr = clone.proof.jws;
206
+ delete clone.proof;
207
+ if (await issuerWallet.verify((0, json_stable_stringify.default)(clone), (0, _ocap_util.fromBase64)(signatureStr)) !== true) throw Error("Status credential signature not valid");
208
+ return x.claim;
209
+ }));
210
+ }
211
+ const stableStringify = json_stable_stringify.default;
212
+
213
+ //#endregion
214
+ exports.create = create;
215
+ exports.createCredentialList = createCredentialList;
216
+ exports.proofTypes = proofTypes;
217
+ exports.stableStringify = stableStringify;
218
+ exports.verify = verify;
219
+ exports.verifyCredentialList = verifyCredentialList;
220
+ exports.verifyPresentation = verifyPresentation;
@@ -0,0 +1,155 @@
1
+ import { WalletObject } from "@ocap/wallet";
2
+ import stringify from "json-stable-stringify";
3
+
4
+ //#region src/index.d.ts
5
+ interface Proof {
6
+ type: string;
7
+ created: string;
8
+ proofPurpose: string;
9
+ jws: string;
10
+ pk?: string;
11
+ }
12
+ interface Issuer {
13
+ id: string;
14
+ pk: string;
15
+ name: string;
16
+ }
17
+ interface CredentialSubject {
18
+ id: string;
19
+ [key: string]: unknown;
20
+ }
21
+ interface CredentialStatus {
22
+ id: string;
23
+ type: string;
24
+ scope: string;
25
+ }
26
+ interface VerifiableCredential {
27
+ '@context': string;
28
+ id: string;
29
+ type: string;
30
+ issuer: Issuer;
31
+ issuanceDate: string;
32
+ expirationDate?: string;
33
+ credentialSubject: CredentialSubject;
34
+ proof: Proof;
35
+ tag?: string;
36
+ credentialStatus?: CredentialStatus;
37
+ signature?: unknown;
38
+ }
39
+ interface IssuerInfo {
40
+ wallet: WalletObject;
41
+ name?: string;
42
+ }
43
+ interface Presentation {
44
+ challenge: string;
45
+ verifiableCredential: string | string[];
46
+ proof: Proof | Proof[];
47
+ [key: string]: unknown;
48
+ }
49
+ interface Credential {
50
+ id: string;
51
+ issued: string;
52
+ issuer: Issuer;
53
+ proof: Proof;
54
+ claim: unknown;
55
+ }
56
+ declare const proofTypes: Record<number, string>;
57
+ /**
58
+ * Create a valid verifiable credential
59
+ *
60
+ * @param params
61
+ * @param params.type - The type of credential
62
+ * @param params.subject - The content of credential
63
+ * @param params.issuer - The issuer name and wallet
64
+ * @param params.issuanceDate
65
+ * @param params.expirationDate
66
+ * @param params.endpoint - Status endpoint url
67
+ * @param params.endpointScope - Endpoint scope, either be public or private
68
+ * @returns Promise<object>
69
+ */
70
+ declare function create({
71
+ type,
72
+ subject,
73
+ issuer,
74
+ issuanceDate,
75
+ expirationDate,
76
+ tag,
77
+ endpoint,
78
+ endpointScope
79
+ }: {
80
+ type: string;
81
+ subject: CredentialSubject;
82
+ issuer: IssuerInfo;
83
+ issuanceDate?: string;
84
+ expirationDate?: string;
85
+ tag?: string;
86
+ endpoint?: string;
87
+ endpointScope?: string;
88
+ }): Promise<VerifiableCredential | null>;
89
+ /**
90
+ * Verify that the verifiable credential is valid
91
+ * - It is signed by a whitelist of issuers
92
+ * - It is owned by the vc.subject.id
93
+ * - It has valid signature by the issuer
94
+ * - It is not expired
95
+ *
96
+ * @param vc - the verifiable credential object
97
+ * @param ownerDid - vc holder/owner did
98
+ * @param trustedIssuers - list of issuer did
99
+ * @throws Error
100
+ * @returns Promise<boolean>
101
+ */
102
+ declare function verify({
103
+ vc,
104
+ ownerDid,
105
+ trustedIssuers,
106
+ ignoreExpired
107
+ }: {
108
+ vc: VerifiableCredential | null;
109
+ ownerDid: string;
110
+ trustedIssuers: string | string[];
111
+ ignoreExpired?: boolean;
112
+ }): Promise<boolean>;
113
+ /**
114
+ * Verify that the Presentation is valid
115
+ * - It is signed by VC's owner
116
+ * - It contain challenge
117
+ * - It has valid signature by the issuer
118
+ * - It is not expired
119
+ *
120
+ * @param presentation - the presentation object
121
+ * @param trustedIssuers - list of issuer did
122
+ * @param challenge - Random byte you want
123
+ * @throws Error
124
+ * @returns Promise<boolean>
125
+ */
126
+ declare function verifyPresentation({
127
+ presentation,
128
+ trustedIssuers,
129
+ challenge,
130
+ ignoreExpired
131
+ }: {
132
+ presentation: Presentation;
133
+ trustedIssuers: string[];
134
+ challenge: string;
135
+ ignoreExpired?: boolean;
136
+ }): Promise<boolean>;
137
+ declare function createCredentialList({
138
+ claims,
139
+ issuer,
140
+ issuanceDate
141
+ }: {
142
+ claims: unknown[];
143
+ issuer: IssuerInfo;
144
+ issuanceDate?: string;
145
+ }): Promise<Credential[]>;
146
+ declare function verifyCredentialList({
147
+ credentials,
148
+ trustedIssuers
149
+ }: {
150
+ credentials: Credential[];
151
+ trustedIssuers: string | string[];
152
+ }): Promise<unknown[]>;
153
+ declare const stableStringify: typeof stringify;
154
+ //#endregion
155
+ export { create, createCredentialList, proofTypes, stableStringify, verify, verifyCredentialList, verifyPresentation };
@@ -0,0 +1,11 @@
1
+
2
+ //#region package.json
3
+ var name = "@arcblock/vc";
4
+
5
+ //#endregion
6
+ Object.defineProperty(exports, 'name', {
7
+ enumerable: true,
8
+ get: function () {
9
+ return name;
10
+ }
11
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arcblock/vc",
3
- "version": "1.28.9",
4
- "description": "Javascript lib to work with ArcBlock Verifiable Credentials",
3
+ "version": "1.29.0",
4
+ "description": "TypeScript lib to work with ArcBlock Verifiable Credentials",
5
5
  "keywords": [
6
6
  "arcblock",
7
7
  "blockchain",
@@ -19,24 +19,50 @@
19
19
  "contributors": [
20
20
  "wangshijun <shijun@arcblock.io> (https://github.com/wangshijun)"
21
21
  ],
22
- "homepage": "https://github.com/ArcBlock/blockchain/tree/master/did/vc",
22
+ "homepage": "https://github.com/ArcBlock/blockchain/tree/master/asset/vc",
23
23
  "license": "Apache-2.0",
24
- "main": "lib/index.js",
24
+ "type": "module",
25
+ "main": "./lib/index.cjs",
26
+ "module": "./esm/index.mjs",
27
+ "types": "./esm/index.d.mts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./esm/index.d.mts",
31
+ "import": "./esm/index.mjs",
32
+ "default": "./lib/index.cjs"
33
+ },
34
+ "./lib/*.js": {
35
+ "types": "./esm/*.d.mts",
36
+ "import": "./esm/*.mjs",
37
+ "default": "./lib/*.cjs"
38
+ },
39
+ "./lib/*": {
40
+ "types": "./esm/*.d.mts",
41
+ "import": "./esm/*.mjs",
42
+ "default": "./lib/*.cjs"
43
+ }
44
+ },
25
45
  "files": [
26
- "lib"
46
+ "lib",
47
+ "esm"
27
48
  ],
28
- "devDependencies": {},
49
+ "devDependencies": {
50
+ "@types/debug": "^4.1.12",
51
+ "@types/json-stable-stringify": "^1.1.0",
52
+ "@types/lodash": "^4.17.0"
53
+ },
29
54
  "repository": {
30
55
  "type": "git",
31
56
  "url": "git+https://github.com/ArcBlock/blockchain.git"
32
57
  },
33
58
  "scripts": {
59
+ "build": "tsdown",
60
+ "prebuild": "rm -rf lib esm",
34
61
  "lint": "biome check",
35
62
  "lint:fix": "biome check --write",
36
- "docs": "bun run gen-dts && bun run gen-docs && bun run cleanup-docs && bun run format-docs",
63
+ "docs": "bun run gen-docs && bun run cleanup-docs && bun run format-docs",
37
64
  "cleanup-docs": "node ../../scripts/cleanup-docs.js docs/README.md $npm_package_name",
38
- "gen-docs": "jsdoc2md lib/index.js > docs/README.md",
39
- "gen-dts": "j2d lib/index.js",
65
+ "gen-docs": "jsdoc2md src/index.ts > docs/README.md",
40
66
  "format-docs": "remark . -o",
41
67
  "test": "bun test",
42
68
  "coverage": "npm run test -- --coverage"
@@ -45,13 +71,13 @@
45
71
  "url": "https://github.com/ArcBlock/blockchain/issues"
46
72
  },
47
73
  "dependencies": {
48
- "@arcblock/did": "1.28.9",
49
- "@ocap/mcrypto": "1.28.9",
50
- "@ocap/util": "1.28.9",
51
- "@ocap/wallet": "1.28.9",
52
- "debug": "^4.3.6",
74
+ "@arcblock/did": "1.29.0",
75
+ "@ocap/mcrypto": "1.29.0",
76
+ "@ocap/util": "1.29.0",
77
+ "@ocap/wallet": "1.29.0",
78
+ "debug": "^4.4.3",
53
79
  "is-absolute-url": "^3.0.3",
54
80
  "json-stable-stringify": "^1.0.1",
55
- "lodash": "^4.17.21"
81
+ "lodash": "^4.17.23"
56
82
  }
57
83
  }
package/lib/index.d.ts DELETED
@@ -1,84 +0,0 @@
1
- // Generate by [js2dts@0.3.3](https://github.com/whxaxes/js2dts#readme)
2
-
3
- import * as jsonStableStringify from 'json-stable-stringify';
4
- /**
5
- * Create a valid verifiable credential
6
- *
7
- * @param {object} params
8
- * @param {string} params.type - The type of credential
9
- * @param {object} params.subject - The content of credential
10
- * @param {object} params.issuer - The issuer name and wallet
11
- * @param {Date} params.issuanceDate
12
- * @param {Date} params.expirationDate
13
- * @param {String} params.endpoint - Status endpoint url
14
- * @param {String} params.endpointScope - Endpoint scope, either be public or private
15
- * @returns {Promise<object>}
16
- */
17
- declare function create(T100: _Lib.T101): Promise<any>;
18
- /**
19
- * Verify that the verifiable credential is valid
20
- * - It is signed by a whitelist of issuers
21
- * - It is owned by the vc.subject.id
22
- * - It has valid signature by the issuer
23
- * - It is not expired
24
- *
25
- * @param {object} vc - the verifiable credential object
26
- * @param {string} ownerDid - vc holder/owner did
27
- * @param {Array} trustedIssuers - list of issuer did
28
- * @throws {Error}
29
- * @returns {Promise<boolean>}
30
- */
31
- declare function verify(T102: any): Promise<boolean>;
32
- /**
33
- * Verify that the Presentation is valid
34
- * - It is signed by VC's owner
35
- * - It contain challenge
36
- * - It has valid signature by the issuer
37
- * - It is not expired
38
- *
39
- * @param {object} presentation - the presentation object
40
- * @param {Array} trustedIssuers - list of issuer did
41
- * @param {String} challenge - Random byte you want
42
- * @throws {Error}
43
- * @returns {Promise<boolean>}
44
- */
45
- declare function verifyPresentation(T103: any): Promise<boolean>;
46
- declare function createCredentialList(T105: _Lib.T106): Promise<_Lib.T107[]>;
47
- declare function verifyCredentialList(T108: _Lib.T109): Promise<any[]>;
48
- declare const _Lib: _Lib.T110;
49
- declare namespace _Lib {
50
- export interface T101 {
51
- type: string;
52
- subject: any;
53
- issuer: any;
54
- issuanceDate: Date;
55
- expirationDate: Date;
56
- endpoint: string;
57
- endpointScope: string;
58
- }
59
- export interface T104 {
60
- [key: string]: any;
61
- }
62
- export interface T106 {
63
- claims: any;
64
- issuer: any;
65
- issuanceDate: any;
66
- }
67
- export interface T107 {
68
- claim: any;
69
- }
70
- export interface T109 {
71
- credentials: any;
72
- trustedIssuers: any;
73
- }
74
- export interface T110 {
75
- create: typeof create;
76
- verify: typeof verify;
77
- verifyPresentation: typeof verifyPresentation;
78
- stableStringify: typeof jsonStableStringify.default;
79
- proofTypes: _Lib.T104;
80
- createCredentialList: typeof createCredentialList;
81
- verifyCredentialList: typeof verifyCredentialList;
82
- }
83
- }
84
- export = _Lib;
package/lib/index.js DELETED
@@ -1,341 +0,0 @@
1
- /**
2
- * @fileOverview Utility functions to create/verify vc
3
- *
4
- * @module @arcblock/vc
5
- * @requires @arcblock/did
6
- * @requires @ocap/util
7
- */
8
- const isAbsoluteUrl = require('is-absolute-url');
9
- const stringify = require('json-stable-stringify');
10
- const cloneDeep = require('lodash/cloneDeep');
11
- const { types } = require('@ocap/mcrypto');
12
- const { fromPublicKey } = require('@ocap/wallet');
13
- const { toTypeInfo, isValid, isFromPublicKey, fromPublicKeyHash } = require('@arcblock/did');
14
- const { toBase58, toBase64, fromBase64, fromBase58 } = require('@ocap/util');
15
-
16
- const debug = require('debug')(require('../package.json').name);
17
-
18
- const proofTypes = {
19
- [types.KeyType.ED25519]: 'Ed25519Signature',
20
- [types.KeyType.SECP256K1]: 'Secp256k1Signature',
21
- [types.KeyType.ETHEREUM]: 'EthereumSignature',
22
- };
23
-
24
- /**
25
- * Create a valid verifiable credential
26
- *
27
- * @param {object} params
28
- * @param {string} params.type - The type of credential
29
- * @param {object} params.subject - The content of credential
30
- * @param {object} params.issuer - The issuer name and wallet
31
- * @param {Date} params.issuanceDate
32
- * @param {Date} params.expirationDate
33
- * @param {String} params.endpoint - Status endpoint url
34
- * @param {String} params.endpointScope - Endpoint scope, either be public or private
35
- * @returns {Promise<object>}
36
- */
37
- async function create({
38
- type,
39
- subject,
40
- issuer,
41
- issuanceDate,
42
- expirationDate,
43
- tag = '',
44
- endpoint = '',
45
- endpointScope = 'public',
46
- }) {
47
- if (!type) {
48
- throw new Error('Can not create verifiable credential without empty type');
49
- }
50
-
51
- if (!subject) {
52
- throw new Error('Can not create verifiable credential from empty subject');
53
- }
54
-
55
- // Should have an owner
56
- if (!subject.id) {
57
- throw new Error('Can not create verifiable credential without holder');
58
- }
59
-
60
- if (!isValid(subject.id)) {
61
- throw new Error('Can not create verifiable credential invalid holder did');
62
- }
63
-
64
- if (endpoint && isAbsoluteUrl(endpoint) === false) {
65
- throw new Error('VC Endpoint must be absolute url');
66
- }
67
-
68
- if (endpointScope && ['public', 'private'].includes(endpointScope) === false) {
69
- throw new Error('VC Endpoint scope must be either public or private');
70
- }
71
-
72
- const { wallet, name: issuerName } = issuer;
73
- const issuerDid = wallet.address;
74
- const typeInfo = toTypeInfo(issuerDid);
75
-
76
- // The { pk, hash } type should be same as issuer, role type must be `ROLE_VC`
77
- const vcType = { ...typeInfo, role: types.RoleType.ROLE_VC };
78
- const vcDid = fromPublicKeyHash(wallet.hash(stringify(subject)), vcType);
79
-
80
- issuanceDate = issuanceDate || new Date().toISOString();
81
-
82
- const vcObj = {
83
- '@context': 'https://schema.arcblock.io/v0.1/context.jsonld',
84
- id: vcDid,
85
- type,
86
- issuer: {
87
- id: issuerDid,
88
- pk: toBase58(wallet.publicKey),
89
- name: issuerName || issuerDid,
90
- },
91
- issuanceDate,
92
- expirationDate,
93
- credentialSubject: subject,
94
- };
95
-
96
- if (tag) {
97
- vcObj.tag = tag;
98
- }
99
-
100
- if (endpoint) {
101
- vcObj.credentialStatus = {
102
- id: endpoint,
103
- type: 'NFTStatusList2021',
104
- scope: endpointScope || 'public',
105
- };
106
- }
107
-
108
- if (!proofTypes[typeInfo.pk]) {
109
- throw new Error('Unsupported signer type when create verifiable credential');
110
- }
111
-
112
- const signature = await wallet.sign(stringify(vcObj));
113
- const proof = {
114
- type: proofTypes[typeInfo.pk],
115
- created: issuanceDate,
116
- proofPurpose: 'assertionMethod',
117
- jws: toBase64(signature),
118
- };
119
-
120
- // NOTE: we should be able to verify the vc before return
121
- const result = { proof, ...vcObj };
122
-
123
- debug('create', result);
124
-
125
- if (await verify({ vc: result, ownerDid: subject.id, trustedIssuers: [issuerDid] })) {
126
- return result;
127
- }
128
-
129
- return null;
130
- }
131
-
132
- /**
133
- * Verify that the verifiable credential is valid
134
- * - It is signed by a whitelist of issuers
135
- * - It is owned by the vc.subject.id
136
- * - It has valid signature by the issuer
137
- * - It is not expired
138
- *
139
- * @param {object} vc - the verifiable credential object
140
- * @param {string} ownerDid - vc holder/owner did
141
- * @param {Array} trustedIssuers - list of issuer did
142
- * @throws {Error}
143
- * @returns {Promise<boolean>}
144
- */
145
- async function verify({ vc, ownerDid, trustedIssuers, ignoreExpired = false }) {
146
- // Integrity check
147
- if (!vc) {
148
- throw new Error('Empty verifiable credential object');
149
- }
150
- if (!vc.issuer || !vc.issuer.id || !vc.issuer.pk || !isValid(vc.issuer.id)) {
151
- throw new Error('Invalid verifiable credential issuer');
152
- }
153
- if (!vc.credentialSubject || !vc.credentialSubject.id || !isValid(vc.credentialSubject.id)) {
154
- throw new Error('Invalid verifiable credential subject');
155
- }
156
- if (!vc.proof || !vc.proof.jws) {
157
- throw new Error('Invalid verifiable credential proof');
158
- }
159
-
160
- // Verify dates
161
- if (vc.issuanceDate === undefined) {
162
- throw Error('Invalid verifiable credential issue date');
163
- }
164
- if (new Date(vc.issuanceDate).getTime() > Date.now()) {
165
- throw Error('Verifiable credential has not take effect');
166
- }
167
-
168
- if (!ignoreExpired && vc.expirationDate !== undefined && new Date(vc.expirationDate).getTime() < Date.now()) {
169
- throw Error('Verifiable credential has expired');
170
- }
171
-
172
- // Verify issuer
173
- const issuers = Array.isArray(trustedIssuers) ? trustedIssuers : [trustedIssuers];
174
- const issuerDid = issuers.find((x) => x === vc.issuer.id);
175
- if (!issuerDid) {
176
- throw new Error('Verifiable credential not issued by trusted issuers');
177
- }
178
- if (!isFromPublicKey(issuerDid, vc.issuer.pk)) {
179
- throw new Error('Verifiable credential not issuer pk not match with issuer did');
180
- }
181
-
182
- // Verify owner
183
- if (ownerDid !== vc.credentialSubject.id) {
184
- throw new Error('Verifiable credential not owned by specified owner did');
185
- }
186
-
187
- // Construct the issuer wallet
188
- const issuer = fromPublicKey(vc.issuer.pk, toTypeInfo(issuerDid));
189
-
190
- // NOTE: we are ignoring other fields of the proof
191
- const clone = cloneDeep(vc);
192
- const signature = clone.proof.jws;
193
- delete clone.proof;
194
- delete clone.signature;
195
-
196
- // Verify signature
197
- if ((await issuer.verify(stringify(clone), fromBase64(signature))) !== true) {
198
- throw Error('Verifiable credential signature not valid');
199
- }
200
-
201
- // TODO: support verify revoked from endpoint
202
-
203
- return true;
204
- }
205
-
206
- /**
207
- * Verify that the Presentation is valid
208
- * - It is signed by VC's owner
209
- * - It contain challenge
210
- * - It has valid signature by the issuer
211
- * - It is not expired
212
- *
213
- * @param {object} presentation - the presentation object
214
- * @param {Array} trustedIssuers - list of issuer did
215
- * @param {String} challenge - Random byte you want
216
- * @throws {Error}
217
- * @returns {Promise<boolean>}
218
- */
219
- async function verifyPresentation({ presentation, trustedIssuers, challenge, ignoreExpired = false }) {
220
- if (!presentation.challenge || challenge !== presentation.challenge) {
221
- throw Error('Invalid challenge included on vc presentation');
222
- }
223
-
224
- const vcList = Array.isArray(presentation.verifiableCredential)
225
- ? presentation.verifiableCredential
226
- : [presentation.verifiableCredential];
227
-
228
- const proofList = Array.isArray(presentation.proof) ? presentation.proof : [presentation.proof];
229
- const clone = cloneDeep(presentation);
230
- delete clone.proof;
231
-
232
- await Promise.all(
233
- vcList.map(async (vcStr) => {
234
- const vcObj = JSON.parse(vcStr);
235
- const proof = proofList.find((x) => isFromPublicKey(vcObj.credentialSubject.id, x.pk));
236
-
237
- if (!proof) {
238
- throw Error(`VC does not have corresponding proof: ${vcStr}`);
239
- }
240
-
241
- const signature = proof.jws;
242
- const holder = fromPublicKey(fromBase58(proof.pk), toTypeInfo(vcObj.credentialSubject.id));
243
- if ((await holder.verify(stringify(clone), fromBase64(signature))) !== true) {
244
- throw Error('Presentation signature invalid');
245
- }
246
-
247
- await verify({ vc: vcObj, ownerDid: vcObj.credentialSubject.id, trustedIssuers, ignoreExpired });
248
- })
249
- );
250
-
251
- return true;
252
- }
253
-
254
- async function createCredentialList({ claims, issuer, issuanceDate }) {
255
- if (!claims || !Array.isArray(claims)) {
256
- throw new Error('Can not create credential list with empty claim list');
257
- }
258
- if (!issuer || !issuer.wallet || !issuer.name) {
259
- throw new Error('Can not create credential list with empty issuer name or wallet');
260
- }
261
- if (typeof issuer.wallet.sign !== 'function') {
262
- throw new Error('Can not create credential list with invalid issuer wallet');
263
- }
264
-
265
- const { wallet, name } = issuer;
266
- const issuerDid = wallet.address;
267
- const typeInfo = toTypeInfo(issuerDid);
268
- const vcType = { ...typeInfo, role: types.RoleType.ROLE_VC };
269
-
270
- const issued = issuanceDate || new Date().toISOString();
271
-
272
- const results = await Promise.all(
273
- claims.map(async (x) => {
274
- const vc = { claim: x };
275
-
276
- vc.id = fromPublicKeyHash(wallet.hash(stringify(vc.claim)), vcType);
277
- vc.issued = issued;
278
- vc.issuer = {
279
- id: issuerDid,
280
- pk: toBase58(wallet.publicKey),
281
- name: name || issuerDid,
282
- };
283
-
284
- const signature = await wallet.sign(stringify(vc));
285
- vc.proof = {
286
- type: proofTypes[typeInfo.pk],
287
- created: issued,
288
- proofPurpose: 'assertionMethod',
289
- jws: toBase64(signature),
290
- };
291
-
292
- return vc;
293
- })
294
- );
295
- return results;
296
- }
297
-
298
- async function verifyCredentialList({ credentials, trustedIssuers }) {
299
- if (!credentials || !Array.isArray(credentials)) {
300
- throw new Error('Can not verify with empty credentials list');
301
- }
302
-
303
- return Promise.all(
304
- credentials.map(async (x) => {
305
- // Verify issuer
306
- const issuers = Array.isArray(trustedIssuers) ? trustedIssuers : [trustedIssuers];
307
- const issuerDid = issuers.find((d) => d === x.issuer.id);
308
- if (!issuerDid) {
309
- throw new Error('Credential not issued by trusted issuers');
310
- }
311
- if (!isFromPublicKey(issuerDid, x.issuer.pk)) {
312
- throw new Error('Credential not issuer pk not match with issuer did');
313
- }
314
-
315
- // Construct the issuer wallet
316
- const issuer = fromPublicKey(x.issuer.pk, toTypeInfo(issuerDid));
317
-
318
- // NOTE: we are ignoring other fields of the proof
319
- const clone = cloneDeep(x);
320
- const signature = clone.proof.jws;
321
- delete clone.proof;
322
-
323
- // Verify signature
324
- if ((await issuer.verify(stringify(clone), fromBase64(signature))) !== true) {
325
- throw Error('Status credential signature not valid');
326
- }
327
-
328
- return x.claim;
329
- })
330
- );
331
- }
332
-
333
- module.exports = {
334
- create,
335
- verify,
336
- verifyPresentation,
337
- stableStringify: stringify,
338
- proofTypes,
339
- createCredentialList,
340
- verifyCredentialList,
341
- };