From 068b431276cbcd55c35648818e482ae36ae98592 Mon Sep 17 00:00:00 2001 From: eugbyte Date: Tue, 29 Jun 2021 16:22:23 +0800 Subject: [PATCH 1/2] chore: added typeRoots --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 30a1cf29..3f0ac65f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,7 @@ "moduleResolution": "node", "strict": true, "baseUrl": ".", - "typeRoots": ["./src/@types", "./node_modules/@types"], + "typeRoots": ["./src/@types", "./src/shared/@types", "./node_modules/@types"], "skipLibCheck": true, "resolveJsonModule": true, "esModuleInterop": true From d954aa9406175d8bceffba67fb67cb5e7952ed5d Mon Sep 17 00:00:00 2001 From: eugbyte Date: Tue, 29 Jun 2021 21:06:56 +0800 Subject: [PATCH 2/2] chore: dist --- .gitignore | 1 - dist/cjs/2.0/digest.js | 29 + dist/cjs/2.0/obfuscate.js | 47 + dist/cjs/2.0/salt.js | 113 + dist/cjs/2.0/schema/schema.json | 249 ++ dist/cjs/2.0/sign.js | 91 + dist/cjs/2.0/types.js | 38 + dist/cjs/2.0/verify.js | 21 + dist/cjs/2.0/wrap.js | 63 + dist/cjs/3.0/digest.js | 20 + dist/cjs/3.0/obfuscate.js | 55 + dist/cjs/3.0/salt.js | 45 + dist/cjs/3.0/schema/schema.json | 230 ++ dist/cjs/3.0/sign.js | 83 + dist/cjs/3.0/traverseAndFlatten.js | 20 + dist/cjs/3.0/types.js | 37 + dist/cjs/3.0/validate/__mocks__/validate.js | 45 + dist/cjs/3.0/validate/index.js | 13 + dist/cjs/3.0/validate/validate.js | 161 ++ dist/cjs/3.0/verify.js | 38 + dist/cjs/3.0/wrap.js | 122 + dist/cjs/__generated__/schema.2.0.js | 21 + dist/cjs/__generated__/schema.3.0.js | 40 + dist/cjs/index.js | 148 ++ dist/cjs/shared/@types/document.js | 14 + dist/cjs/shared/@types/sign.js | 12 + dist/cjs/shared/@types/wrap.js | 8 + dist/cjs/shared/ajv.js | 55 + dist/cjs/shared/logger.js | 16 + dist/cjs/shared/merkle/index.js | 13 + dist/cjs/shared/merkle/merkle.js | 102 + dist/cjs/shared/serialize/flatten.js | 29 + dist/cjs/shared/signer/index.js | 13 + .../Secp256k1VerificationKey2018.js | 22 + .../Secp256k1VerificationKey2018/index.js | 13 + .../shared/signer/signatureSchemes/index.js | 13 + .../signatureSchemes/signatureSchemes.js | 6 + dist/cjs/shared/signer/signer.js | 13 + dist/cjs/shared/utils/diagnose.js | 134 + dist/cjs/shared/utils/guard.js | 44 + dist/cjs/shared/utils/index.js | 15 + dist/cjs/shared/utils/utils.js | 175 ++ dist/cjs/shared/validate/index.js | 13 + dist/cjs/shared/validate/validate.js | 21 + dist/esm/2.0/digest.js | 24 + dist/esm/2.0/obfuscate.js | 42 + dist/esm/2.0/salt.js | 100 + dist/esm/2.0/schema/schema.json | 249 ++ dist/esm/2.0/sign.js | 87 + dist/esm/2.0/types.js | 25 + dist/esm/2.0/verify.js | 17 + dist/esm/2.0/wrap.js | 58 + dist/esm/3.0/digest.js | 16 + dist/esm/3.0/obfuscate.js | 51 + dist/esm/3.0/salt.js | 38 + dist/esm/3.0/schema/schema.json | 230 ++ dist/esm/3.0/sign.js | 79 + dist/esm/3.0/traverseAndFlatten.js | 16 + dist/esm/3.0/types.js | 24 + dist/esm/3.0/validate/__mocks__/validate.js | 41 + dist/esm/3.0/validate/index.js | 1 + dist/esm/3.0/validate/validate.js | 154 ++ dist/esm/3.0/verify.js | 34 + dist/esm/3.0/wrap.js | 117 + dist/esm/__generated__/schema.2.0.js | 18 + dist/esm/__generated__/schema.3.0.js | 37 + dist/esm/index.js | 109 + dist/esm/shared/@types/document.js | 11 + dist/esm/shared/@types/sign.js | 9 + dist/esm/shared/@types/wrap.js | 4 + dist/esm/shared/ajv.js | 47 + dist/esm/shared/logger.js | 9 + dist/esm/shared/merkle/index.js | 1 + dist/esm/shared/merkle/merkle.js | 98 + dist/esm/shared/serialize/flatten.js | 25 + dist/esm/shared/signer/index.js | 1 + .../Secp256k1VerificationKey2018.js | 18 + .../Secp256k1VerificationKey2018/index.js | 1 + .../shared/signer/signatureSchemes/index.js | 1 + .../signatureSchemes/signatureSchemes.js | 3 + dist/esm/shared/signer/signer.js | 9 + dist/esm/shared/utils/diagnose.js | 130 + dist/esm/shared/utils/guard.js | 37 + dist/esm/shared/utils/index.js | 3 + dist/esm/shared/utils/utils.js | 156 ++ dist/esm/shared/validate/index.js | 1 + dist/esm/shared/validate/validate.js | 17 + dist/index.umd.js | 2239 +++++++++++++++++ dist/types/2.0/digest.d.ts | 4 + dist/types/2.0/obfuscate.d.ts | 7 + dist/types/2.0/salt.d.ts | 24 + dist/types/2.0/sign.d.ts | 5 + dist/types/2.0/types.d.ts | 57 + dist/types/2.0/verify.d.ts | 3 + dist/types/2.0/wrap.d.ts | 5 + dist/types/3.0/digest.d.ts | 3 + dist/types/3.0/obfuscate.d.ts | 3 + dist/types/3.0/salt.d.ts | 5 + dist/types/3.0/sign.d.ts | 4 + dist/types/3.0/traverseAndFlatten.d.ts | 12 + dist/types/3.0/types.d.ts | 63 + .../3.0/validate/__mocks__/validate.d.ts | 1 + dist/types/3.0/validate/index.d.ts | 1 + dist/types/3.0/validate/validate.d.ts | 3 + dist/types/3.0/verify.d.ts | 2 + dist/types/3.0/wrap.d.ts | 5 + dist/types/__generated__/schema.2.0.d.ts | 106 + dist/types/__generated__/schema.3.0.d.ts | 178 ++ dist/types/index.d.ts | 33 + dist/types/shared/@types/document.d.ts | 19 + dist/types/shared/@types/sign.d.ts | 15 + dist/types/shared/@types/wrap.d.ts | 14 + dist/types/shared/ajv.d.ts | 6 + dist/types/shared/logger.d.ts | 10 + dist/types/shared/merkle/index.d.ts | 1 + dist/types/shared/merkle/merkle.d.ts | 16 + dist/types/shared/serialize/flatten.d.ts | 7 + dist/types/shared/signer/index.d.ts | 1 + .../Secp256k1VerificationKey2018.d.ts | 3 + .../Secp256k1VerificationKey2018/index.d.ts | 1 + .../shared/signer/signatureSchemes/index.d.ts | 1 + .../signatureSchemes/signatureSchemes.d.ts | 2 + dist/types/shared/signer/signer.d.ts | 6 + dist/types/shared/utils/diagnose.d.ts | 22 + dist/types/shared/utils/guard.d.ts | 35 + dist/types/shared/utils/index.d.ts | 3 + dist/types/shared/utils/utils.d.ts | 46 + dist/types/shared/validate/index.d.ts | 1 + dist/types/shared/validate/validate.d.ts | 2 + 129 files changed, 7582 insertions(+), 1 deletion(-) create mode 100644 dist/cjs/2.0/digest.js create mode 100644 dist/cjs/2.0/obfuscate.js create mode 100644 dist/cjs/2.0/salt.js create mode 100644 dist/cjs/2.0/schema/schema.json create mode 100644 dist/cjs/2.0/sign.js create mode 100644 dist/cjs/2.0/types.js create mode 100644 dist/cjs/2.0/verify.js create mode 100644 dist/cjs/2.0/wrap.js create mode 100644 dist/cjs/3.0/digest.js create mode 100644 dist/cjs/3.0/obfuscate.js create mode 100644 dist/cjs/3.0/salt.js create mode 100644 dist/cjs/3.0/schema/schema.json create mode 100644 dist/cjs/3.0/sign.js create mode 100644 dist/cjs/3.0/traverseAndFlatten.js create mode 100644 dist/cjs/3.0/types.js create mode 100644 dist/cjs/3.0/validate/__mocks__/validate.js create mode 100644 dist/cjs/3.0/validate/index.js create mode 100644 dist/cjs/3.0/validate/validate.js create mode 100644 dist/cjs/3.0/verify.js create mode 100644 dist/cjs/3.0/wrap.js create mode 100644 dist/cjs/__generated__/schema.2.0.js create mode 100644 dist/cjs/__generated__/schema.3.0.js create mode 100644 dist/cjs/index.js create mode 100644 dist/cjs/shared/@types/document.js create mode 100644 dist/cjs/shared/@types/sign.js create mode 100644 dist/cjs/shared/@types/wrap.js create mode 100644 dist/cjs/shared/ajv.js create mode 100644 dist/cjs/shared/logger.js create mode 100644 dist/cjs/shared/merkle/index.js create mode 100644 dist/cjs/shared/merkle/merkle.js create mode 100644 dist/cjs/shared/serialize/flatten.js create mode 100644 dist/cjs/shared/signer/index.js create mode 100644 dist/cjs/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/Secp256k1VerificationKey2018.js create mode 100644 dist/cjs/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/index.js create mode 100644 dist/cjs/shared/signer/signatureSchemes/index.js create mode 100644 dist/cjs/shared/signer/signatureSchemes/signatureSchemes.js create mode 100644 dist/cjs/shared/signer/signer.js create mode 100644 dist/cjs/shared/utils/diagnose.js create mode 100644 dist/cjs/shared/utils/guard.js create mode 100644 dist/cjs/shared/utils/index.js create mode 100644 dist/cjs/shared/utils/utils.js create mode 100644 dist/cjs/shared/validate/index.js create mode 100644 dist/cjs/shared/validate/validate.js create mode 100644 dist/esm/2.0/digest.js create mode 100644 dist/esm/2.0/obfuscate.js create mode 100644 dist/esm/2.0/salt.js create mode 100644 dist/esm/2.0/schema/schema.json create mode 100644 dist/esm/2.0/sign.js create mode 100644 dist/esm/2.0/types.js create mode 100644 dist/esm/2.0/verify.js create mode 100644 dist/esm/2.0/wrap.js create mode 100644 dist/esm/3.0/digest.js create mode 100644 dist/esm/3.0/obfuscate.js create mode 100644 dist/esm/3.0/salt.js create mode 100644 dist/esm/3.0/schema/schema.json create mode 100644 dist/esm/3.0/sign.js create mode 100644 dist/esm/3.0/traverseAndFlatten.js create mode 100644 dist/esm/3.0/types.js create mode 100644 dist/esm/3.0/validate/__mocks__/validate.js create mode 100644 dist/esm/3.0/validate/index.js create mode 100644 dist/esm/3.0/validate/validate.js create mode 100644 dist/esm/3.0/verify.js create mode 100644 dist/esm/3.0/wrap.js create mode 100644 dist/esm/__generated__/schema.2.0.js create mode 100644 dist/esm/__generated__/schema.3.0.js create mode 100644 dist/esm/index.js create mode 100644 dist/esm/shared/@types/document.js create mode 100644 dist/esm/shared/@types/sign.js create mode 100644 dist/esm/shared/@types/wrap.js create mode 100644 dist/esm/shared/ajv.js create mode 100644 dist/esm/shared/logger.js create mode 100644 dist/esm/shared/merkle/index.js create mode 100644 dist/esm/shared/merkle/merkle.js create mode 100644 dist/esm/shared/serialize/flatten.js create mode 100644 dist/esm/shared/signer/index.js create mode 100644 dist/esm/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/Secp256k1VerificationKey2018.js create mode 100644 dist/esm/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/index.js create mode 100644 dist/esm/shared/signer/signatureSchemes/index.js create mode 100644 dist/esm/shared/signer/signatureSchemes/signatureSchemes.js create mode 100644 dist/esm/shared/signer/signer.js create mode 100644 dist/esm/shared/utils/diagnose.js create mode 100644 dist/esm/shared/utils/guard.js create mode 100644 dist/esm/shared/utils/index.js create mode 100644 dist/esm/shared/utils/utils.js create mode 100644 dist/esm/shared/validate/index.js create mode 100644 dist/esm/shared/validate/validate.js create mode 100644 dist/index.umd.js create mode 100644 dist/types/2.0/digest.d.ts create mode 100644 dist/types/2.0/obfuscate.d.ts create mode 100644 dist/types/2.0/salt.d.ts create mode 100644 dist/types/2.0/sign.d.ts create mode 100644 dist/types/2.0/types.d.ts create mode 100644 dist/types/2.0/verify.d.ts create mode 100644 dist/types/2.0/wrap.d.ts create mode 100644 dist/types/3.0/digest.d.ts create mode 100644 dist/types/3.0/obfuscate.d.ts create mode 100644 dist/types/3.0/salt.d.ts create mode 100644 dist/types/3.0/sign.d.ts create mode 100644 dist/types/3.0/traverseAndFlatten.d.ts create mode 100644 dist/types/3.0/types.d.ts create mode 100644 dist/types/3.0/validate/__mocks__/validate.d.ts create mode 100644 dist/types/3.0/validate/index.d.ts create mode 100644 dist/types/3.0/validate/validate.d.ts create mode 100644 dist/types/3.0/verify.d.ts create mode 100644 dist/types/3.0/wrap.d.ts create mode 100644 dist/types/__generated__/schema.2.0.d.ts create mode 100644 dist/types/__generated__/schema.3.0.d.ts create mode 100644 dist/types/index.d.ts create mode 100644 dist/types/shared/@types/document.d.ts create mode 100644 dist/types/shared/@types/sign.d.ts create mode 100644 dist/types/shared/@types/wrap.d.ts create mode 100644 dist/types/shared/ajv.d.ts create mode 100644 dist/types/shared/logger.d.ts create mode 100644 dist/types/shared/merkle/index.d.ts create mode 100644 dist/types/shared/merkle/merkle.d.ts create mode 100644 dist/types/shared/serialize/flatten.d.ts create mode 100644 dist/types/shared/signer/index.d.ts create mode 100644 dist/types/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/Secp256k1VerificationKey2018.d.ts create mode 100644 dist/types/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/index.d.ts create mode 100644 dist/types/shared/signer/signatureSchemes/index.d.ts create mode 100644 dist/types/shared/signer/signatureSchemes/signatureSchemes.d.ts create mode 100644 dist/types/shared/signer/signer.d.ts create mode 100644 dist/types/shared/utils/diagnose.d.ts create mode 100644 dist/types/shared/utils/guard.d.ts create mode 100644 dist/types/shared/utils/index.d.ts create mode 100644 dist/types/shared/utils/utils.d.ts create mode 100644 dist/types/shared/validate/index.d.ts create mode 100644 dist/types/shared/validate/validate.d.ts diff --git a/.gitignore b/.gitignore index e1632f13..6b2f7b1e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ .nyc_output yarn-error.log yarn.lock -/dist /.idea *.iml /public diff --git a/dist/cjs/2.0/digest.js b/dist/cjs/2.0/digest.js new file mode 100644 index 00000000..e23aab2e --- /dev/null +++ b/dist/cjs/2.0/digest.js @@ -0,0 +1,29 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.digestDocument = exports.flattenHashArray = void 0; +var lodash_1 = require("lodash"); +var js_sha3_1 = require("js-sha3"); +var flatten_1 = require("../shared/serialize/flatten"); +var isKeyOrValueUndefined = function (value, key) { return value === undefined || key === undefined; }; +var flattenHashArray = function (data) { + var flattenedData = lodash_1.omitBy(flatten_1.flatten(data), isKeyOrValueUndefined); + return Object.keys(flattenedData).map(function (k) { + var obj = {}; + obj[k] = flattenedData[k]; + return js_sha3_1.keccak256(JSON.stringify(obj)); + }); +}; +exports.flattenHashArray = flattenHashArray; +var digestDocument = function (document) { + // Prepare array of hashes from filtered data + var hashedDataArray = lodash_1.get(document, "privacy.obfuscatedData", []); + // Prepare array of hashes from visible data + var unhashedData = lodash_1.get(document, "data"); + var hashedUnhashedDataArray = exports.flattenHashArray(unhashedData); + // Combine both array and sort them to ensure determinism + var combinedHashes = hashedDataArray.concat(hashedUnhashedDataArray); + var sortedHashes = lodash_1.sortBy(combinedHashes); + // Finally, return the digest of the entire set of data + return js_sha3_1.keccak256(JSON.stringify(sortedHashes)); +}; +exports.digestDocument = digestDocument; diff --git a/dist/cjs/2.0/obfuscate.js b/dist/cjs/2.0/obfuscate.js new file mode 100644 index 00000000..22010489 --- /dev/null +++ b/dist/cjs/2.0/obfuscate.js @@ -0,0 +1,47 @@ +"use strict"; +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.obfuscateDocument = exports.obfuscateData = void 0; +var lodash_1 = require("lodash"); +var flatten_1 = require("../shared/serialize/flatten"); +var utils_1 = require("../shared/utils"); +var obfuscateData = function (_data, fields) { + var data = lodash_1.cloneDeep(_data); // Prevents alteration of original data + var fieldsToRemove = Array.isArray(fields) ? fields : [fields]; + // Obfuscate data by hashing them with the key + var dataToObfuscate = flatten_1.flatten(lodash_1.pick(data, fieldsToRemove)); + var obfuscatedData = Object.keys(dataToObfuscate).map(function (k) { + var obj = {}; + obj[k] = dataToObfuscate[k]; + return utils_1.toBuffer(obj).toString("hex"); + }); + // Return remaining data + fieldsToRemove.forEach(function (path) { + lodash_1.unset(data, path); + }); + return { + data: data, + obfuscatedData: obfuscatedData, + }; +}; +exports.obfuscateData = obfuscateData; +// TODO the return type could be improve by using Exclude eventually to remove the obfuscated properties +var obfuscateDocument = function (document, fields) { + var _a, _b; + var existingData = document.data; + var _c = exports.obfuscateData(existingData, fields), data = _c.data, obfuscatedData = _c.obfuscatedData; + var currentObfuscatedData = (_b = (_a = document === null || document === void 0 ? void 0 : document.privacy) === null || _a === void 0 ? void 0 : _a.obfuscatedData) !== null && _b !== void 0 ? _b : []; + var newObfuscatedData = currentObfuscatedData.concat(obfuscatedData); + return __assign(__assign({}, document), { data: data, privacy: __assign(__assign({}, document.privacy), { obfuscatedData: newObfuscatedData }) }); +}; +exports.obfuscateDocument = obfuscateDocument; diff --git a/dist/cjs/2.0/salt.js b/dist/cjs/2.0/salt.js new file mode 100644 index 00000000..1c8ae4ca --- /dev/null +++ b/dist/cjs/2.0/salt.js @@ -0,0 +1,113 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.unsaltData = exports.saltData = exports.unsalt = exports.uuidSalt = exports.typedStringToPrimitive = exports.primitiveToTypedString = exports.deepMap = void 0; +var lodash_1 = require("lodash"); +var isUUID_1 = __importDefault(require("validator/lib/isUUID")); +var uuid_1 = require("uuid"); +var UUIDV4_LENGTH = 37; +var PRIMITIVE_TYPES = ["string", "number", "boolean", "undefined"]; +/* eslint-disable no-use-before-define */ +/** + * Curried function that takes (iteratee)(value), + * if value is a collection then recurse into it + * otherwise apply `iteratee` on the primitive value + */ +var recursivelyApply = function (iteratee) { return function (value) { + if (lodash_1.includes(PRIMITIVE_TYPES, typeof value) || value === null) { + return iteratee(value); + } + return exports.deepMap(value, iteratee); // eslint-disable-line @typescript-eslint/no-use-before-define +}; }; +/** + * Applies `iteratee` to all fields in objects, goes into arrays as well. + * Refer to test for example + */ +var deepMap = function (collection, iteratee) { + if (iteratee === void 0) { iteratee = lodash_1.identity; } + if (collection instanceof Array) { + return lodash_1.map(collection, recursivelyApply(iteratee)); + } + if (typeof collection === "object") { + return lodash_1.mapValues(collection, recursivelyApply(iteratee)); + } + return collection; +}; +exports.deepMap = deepMap; +/* eslint-enable no-use-before-define */ +// disabling this because of mutual recursion +var startsWithUuidV4 = function (input) { + if (input && typeof input === "string") { + var elements = input.split(":"); + return isUUID_1.default(elements[0], 4); + } + return false; +}; +/** + * Detects the type of a value and returns a string with type annotation + */ +function primitiveToTypedString(value) { + switch (typeof value) { + case "number": + case "string": + case "boolean": + case "undefined": + return typeof value + ":" + String(value); + default: + if (value === null) { + // typeof null is 'object' so we have to check for it + return "null:null"; + } + throw new Error("Parsing error, value is not of primitive type: " + value); + } +} +exports.primitiveToTypedString = primitiveToTypedString; +/** + * Returns an appropriately typed value given a string with type annotations, e.g: "number:5" + */ +function typedStringToPrimitive(input) { + var _a = input.split(":"), type = _a[0], valueArray = _a.slice(1); + var value = valueArray.join(":"); // just in case there are colons in the value + switch (type) { + case "number": + return Number(value); + case "string": + return String(value); + case "boolean": + return value === "true"; + case "null": + return null; + case "undefined": + return undefined; + default: + throw new Error("Parsing error, type annotation not found in string: " + input); + } +} +exports.typedStringToPrimitive = typedStringToPrimitive; +/** + * Returns a salted value using a randomly generated uuidv4 string for salt + */ +function uuidSalt(value) { + var salt = uuid_1.v4(); + return salt + ":" + primitiveToTypedString(value); +} +exports.uuidSalt = uuidSalt; +/** + * Value salted string in the format "salt:type:value", example: "ee7f3323-1634-4dea-8c12-f0bb83aff874:number:5" + * Returns an appropriately typed value when given a salted string with type annotation + */ +function unsalt(value) { + if (startsWithUuidV4(value)) { + var untypedValue = value.substring(UUIDV4_LENGTH).trim(); + return typedStringToPrimitive(untypedValue); + } + return value; +} +exports.unsalt = unsalt; +// Use uuid salting method to recursively salt data +var saltData = function (data) { return exports.deepMap(data, uuidSalt); }; +exports.saltData = saltData; +var unsaltData = function (data) { return exports.deepMap(data, unsalt); }; +exports.unsaltData = unsaltData; diff --git a/dist/cjs/2.0/schema/schema.json b/dist/cjs/2.0/schema/schema.json new file mode 100644 index 00000000..e7dc8fad --- /dev/null +++ b/dist/cjs/2.0/schema/schema.json @@ -0,0 +1,249 @@ +{ + "title": "Open Attestation Schema 2.0", + "$id": "https://schema.openattestation.com/2.0/schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "identityProofDns": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["DNS-TXT"] + }, + "location": { + "type": "string", + "description": "Url of the website referencing to document store" + } + }, + "required": ["type", "location"] + }, + "identityProofDid": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["DID"] + }, + "key": { + "type": "string", + "description": "Public key associated" + } + }, + "required": ["type", "key"] + }, + "identityProofDnsDid": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["DNS-DID"] + }, + "key": { + "type": "string", + "description": "Public key associated" + }, + "location": { + "type": "string", + "description": "Url of the website referencing to document store" + } + }, + "required": ["type", "key", "location"] + }, + "identityProof": { + "type": "object", + "oneOf": [ + { "$ref": "#/definitions/identityProofDns" }, + { "$ref": "#/definitions/identityProofDnsDid" }, + { "$ref": "#/definitions/identityProofDid" } + ] + }, + "issuer": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Issuer's id, DID can be used" + }, + "name": { + "type": "string", + "description": "Issuer's name" + }, + "revocation": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["NONE", "REVOCATION_STORE"] + }, + "location": { + "type": "string", + "description": "Smart contract address or url of certificate revocation list for Revocation Store type revocation" + } + } + }, + "identityProof": { "$ref": "#/definitions/identityProof" } + }, + "required": ["name", "identityProof"], + "additionalProperties": true + }, + "documentStore": { + "allOf": [ + { "$ref": "#/definitions/issuer" }, + { + "type": "object", + "properties": { + "documentStore": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "Smart contract address of document store" + } + }, + "required": ["documentStore"] + } + ] + }, + "certificateStore": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Issuer's name" + }, + "certificateStore": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "deprecationMessage": "Use documentStore and identityProof instead of this", + "description": "Smart contract address of certificate store. Same as documentStore" + } + }, + "required": ["name", "certificateStore"], + "additionalProperties": true + }, + "tokenRegistry": { + "allOf": [ + { "$ref": "#/definitions/issuer" }, + { + "type": "object", + "properties": { + "tokenRegistry": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "Smart contract address of token registry" + } + }, + "required": ["tokenRegistry"] + } + ] + } + }, + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Internal reference, usually serial number, of this document" + }, + "$template": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Template name to be use by template renderer to determine the template to use" + }, + "type": { + "type": "string", + "description": "Type of renderer template", + "enum": ["EMBEDDED_RENDERER"] + }, + "url": { + "type": "string", + "description": "URL of a decentralised renderer to render this document" + } + }, + "required": ["name", "type"] + } + ] + }, + "documentUrl": { + "type": "string", + "description": "URL of the stored document" + }, + "issuers": { + "type": "array", + "items": { + "type": "object", + "title": "issuer", + "oneOf": [ + { + "$ref": "#/definitions/tokenRegistry" + }, + { + "$ref": "#/definitions/documentStore" + }, + { + "$ref": "#/definitions/certificateStore" + }, + { + "allOf": [ + { "$ref": "#/definitions/issuer" }, + { + "not": { + "anyOf": [ + { + "required": ["certificateStore"] + }, + { + "required": ["tokenRegistry"] + }, + { + "required": ["documentStore"] + } + ] + } + } + ] + } + ] + }, + "minItems": 1 + }, + "recipient": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Recipient's name" + } + }, + "additionalProperties": true + }, + "attachments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "filename": { + "type": "string", + "description": "Name of attachment, with appropriate extensions" + }, + "type": { + "type": "string", + "description": "Type of attachment" + }, + "data": { + "type": "string", + "description": "Base64 encoding of attachment" + } + }, + "required": ["filename", "type", "data"], + "additionalProperties": false + } + } + }, + "required": ["issuers"], + "additionalProperties": true +} diff --git a/dist/cjs/2.0/sign.js b/dist/cjs/2.0/sign.js new file mode 100644 index 00000000..91c90fe2 --- /dev/null +++ b/dist/cjs/2.0/sign.js @@ -0,0 +1,91 @@ +"use strict"; +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __spreadArray = (this && this.__spreadArray) || function (to, from) { + for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) + to[j] = from[i]; + return to; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.signDocument = void 0; +var signer_1 = require("../shared/signer"); +var utils_1 = require("../shared/utils"); +var sign_1 = require("../shared/@types/sign"); +var signDocument = function (document, algorithm, keyOrSigner) { return __awaiter(void 0, void 0, void 0, function () { + var merkleRoot, signature, proof, _a, _b; + var _c; + return __generator(this, function (_d) { + switch (_d.label) { + case 0: + merkleRoot = "0x" + document.signature.merkleRoot; + return [4 /*yield*/, signer_1.sign(algorithm, merkleRoot, keyOrSigner)]; + case 1: + signature = _d.sent(); + _c = { + type: "OpenAttestationSignature2018", + created: new Date().toISOString(), + proofPurpose: "assertionMethod" + }; + if (!sign_1.SigningKey.guard(keyOrSigner)) return [3 /*break*/, 2]; + _a = keyOrSigner.public; + return [3 /*break*/, 4]; + case 2: + _b = "did:ethr:"; + return [4 /*yield*/, keyOrSigner.getAddress()]; + case 3: + _a = _b + (_d.sent()) + "#controller"; + _d.label = 4; + case 4: + proof = (_c.verificationMethod = _a, + _c.signature = signature, + _c); + return [2 /*return*/, __assign(__assign({}, document), { proof: utils_1.isSignedWrappedV2Document(document) ? __spreadArray(__spreadArray([], document.proof), [proof]) : [proof] })]; + } + }); +}); }; +exports.signDocument = signDocument; diff --git a/dist/cjs/2.0/types.js b/dist/cjs/2.0/types.js new file mode 100644 index 00000000..f5fea512 --- /dev/null +++ b/dist/cjs/2.0/types.js @@ -0,0 +1,38 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SignatureStrict = exports.Signature = exports.ArrayProof = exports.Proof = exports.ObfuscationMetadata = void 0; +var document_1 = require("../shared/@types/document"); +var runtypes_1 = require("runtypes"); +exports.ObfuscationMetadata = runtypes_1.Partial({ + obfuscatedData: runtypes_1.Array(document_1.OpenAttestationHexString), +}); +exports.Proof = runtypes_1.Record({ + type: document_1.ProofType, + created: runtypes_1.String, + proofPurpose: document_1.ProofPurpose, + verificationMethod: runtypes_1.String, + signature: runtypes_1.String, +}); +exports.ArrayProof = runtypes_1.Array(exports.Proof); +exports.Signature = runtypes_1.Record({ + type: runtypes_1.Literal("SHA3MerkleProof"), + targetHash: runtypes_1.String, + merkleRoot: runtypes_1.String, + proof: runtypes_1.Array(runtypes_1.String), +}); +exports.SignatureStrict = exports.Signature.And(runtypes_1.Record({ + targetHash: document_1.OpenAttestationHexString, + merkleRoot: document_1.OpenAttestationHexString, + proof: runtypes_1.Array(document_1.OpenAttestationHexString), +})); +__exportStar(require("../__generated__/schema.2.0"), exports); diff --git a/dist/cjs/2.0/verify.js b/dist/cjs/2.0/verify.js new file mode 100644 index 00000000..1f1ba1ee --- /dev/null +++ b/dist/cjs/2.0/verify.js @@ -0,0 +1,21 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.verify = void 0; +var lodash_1 = require("lodash"); +var digest_1 = require("./digest"); +var merkle_1 = require("../shared/merkle"); +var verify = function (document) { + var _a, _b, _c, _d; + var signature = lodash_1.get(document, "signature"); + if (!signature) { + return false; + } + // Checks target hash + var digest = digest_1.digestDocument(document); + var targetHash = lodash_1.get(document, "signature.targetHash"); + if (digest !== targetHash) + return false; + // Calculates merkle root from target hash and proof, then compare to merkle root in document + return merkle_1.checkProof((_b = (_a = document === null || document === void 0 ? void 0 : document.signature) === null || _a === void 0 ? void 0 : _a.proof) !== null && _b !== void 0 ? _b : [], (_c = document === null || document === void 0 ? void 0 : document.signature) === null || _c === void 0 ? void 0 : _c.merkleRoot, (_d = document === null || document === void 0 ? void 0 : document.signature) === null || _d === void 0 ? void 0 : _d.targetHash); +}; +exports.verify = verify; diff --git a/dist/cjs/2.0/wrap.js b/dist/cjs/2.0/wrap.js new file mode 100644 index 00000000..5168bbdc --- /dev/null +++ b/dist/cjs/2.0/wrap.js @@ -0,0 +1,63 @@ +"use strict"; +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.wrapDocuments = exports.wrapDocument = void 0; +var digest_1 = require("./digest"); +var merkle_1 = require("../shared/merkle"); +var utils_1 = require("../shared/utils"); +var document_1 = require("../shared/@types/document"); +var validate_1 = require("../shared/validate"); +var salt_1 = require("./salt"); +var ajv_1 = require("../shared/ajv"); +var createDocument = function (data, option) { + var documentSchema = { + version: document_1.SchemaId.v2, + data: salt_1.saltData(data), + }; + if (option === null || option === void 0 ? void 0 : option.externalSchemaId) { + documentSchema.schema = option.externalSchemaId; + } + return documentSchema; +}; +var wrapDocument = function (data, options) { + var _a; + var document = createDocument(data, options); + var errors = validate_1.validateSchema(document, ajv_1.getSchema((_a = options === null || options === void 0 ? void 0 : options.version) !== null && _a !== void 0 ? _a : document_1.SchemaId.v2)); + if (errors.length > 0) { + throw new utils_1.SchemaValidationError("Invalid document", errors, document); + } + var digest = digest_1.digestDocument(document); + var signature = { + type: "SHA3MerkleProof", + targetHash: digest, + proof: [], + merkleRoot: digest, + }; + return __assign(__assign({}, document), { signature: signature }); +}; +exports.wrapDocument = wrapDocument; +var wrapDocuments = function (data, options) { + // wrap documents individually + var documents = data.map(function (d) { return exports.wrapDocument(d, options); }); + // get all the target hashes to compute the merkle tree and the merkle root + var merkleTree = new merkle_1.MerkleTree(documents.map(function (document) { return document.signature.targetHash; }).map(utils_1.hashToBuffer)); + var merkleRoot = merkleTree.getRoot().toString("hex"); + // for each document, update the merkle root and add the proofs needed + return documents.map(function (document) { + var merkleProof = merkleTree + .getProof(utils_1.hashToBuffer(document.signature.targetHash)) + .map(function (buffer) { return buffer.toString("hex"); }); + return __assign(__assign({}, document), { signature: __assign(__assign({}, document.signature), { proof: merkleProof, merkleRoot: merkleRoot }) }); + }); +}; +exports.wrapDocuments = wrapDocuments; diff --git a/dist/cjs/3.0/digest.js b/dist/cjs/3.0/digest.js new file mode 100644 index 00000000..4cc3e3af --- /dev/null +++ b/dist/cjs/3.0/digest.js @@ -0,0 +1,20 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.digestCredential = void 0; +var lodash_1 = require("lodash"); +var js_sha3_1 = require("js-sha3"); +var digestCredential = function (document, salts, obfuscatedData) { + // Prepare array of hashes from visible data + var hashedUnhashedDataArray = salts + .filter(function (salt) { return lodash_1.get(document, salt.path); }) + .map(function (salt) { + var _a; + return js_sha3_1.keccak256(JSON.stringify((_a = {}, _a[salt.path] = salt.value + ":" + lodash_1.get(document, salt.path), _a))); + }); + // Combine both array and sort them to ensure determinism + var combinedHashes = obfuscatedData.concat(hashedUnhashedDataArray); + var sortedHashes = lodash_1.sortBy(combinedHashes); + // Finally, return the digest of the entire set of data + return js_sha3_1.keccak256(JSON.stringify(sortedHashes)); +}; +exports.digestCredential = digestCredential; diff --git a/dist/cjs/3.0/obfuscate.js b/dist/cjs/3.0/obfuscate.js new file mode 100644 index 00000000..ac1af3ec --- /dev/null +++ b/dist/cjs/3.0/obfuscate.js @@ -0,0 +1,55 @@ +"use strict"; +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.obfuscateVerifiableCredential = void 0; +var utils_1 = require("../shared/utils"); +var lodash_1 = require("lodash"); +var salt_1 = require("./salt"); +var traverseAndFlatten_1 = require("./traverseAndFlatten"); +var obfuscate = function (_data, fields) { + var data = lodash_1.cloneDeep(_data); // Prevents alteration of original data + var fieldsAsArray = [].concat(fields); + // fields to remove will contain the list of each expanded keys from the fields passed in parameter, it's for instance useful in case of + // object obfuscation, where the object itself is not part of the salts, but each individual keys are + var fieldsToRemove = traverseAndFlatten_1.traverseAndFlatten(lodash_1.pick(data, fieldsAsArray), { + iteratee: function (_a) { + var path = _a.path; + return path; + }, + }); + var salts = salt_1.decodeSalt(data.proof.salts); + // Obfuscate data by hashing them with the key + var obfuscatedData = fieldsToRemove.map(function (field) { + var _a; + var value = lodash_1.get(data, field); + var salt = salts.find(function (s) { return s.path === field; }); + if (!salt) { + throw new Error("Salt not found for " + field); + } + return utils_1.toBuffer((_a = {}, _a[salt.path] = salt.value + ":" + value, _a)).toString("hex"); + }); + // remove fields from the object + fieldsAsArray.forEach(function (field) { return lodash_1.unset(data, field); }); + data.proof.salts = salt_1.encodeSalt(salts.filter(function (s) { return !fieldsToRemove.includes(s.path); })); + return { + data: data, + obfuscatedData: obfuscatedData, + }; +}; +var obfuscateVerifiableCredential = function (document, fields) { + var _a = obfuscate(document, fields), data = _a.data, obfuscatedData = _a.obfuscatedData; + var currentObfuscatedData = document.proof.privacy.obfuscated; + var newObfuscatedData = currentObfuscatedData.concat(obfuscatedData); + return __assign(__assign({}, data), { proof: __assign(__assign({}, data.proof), { privacy: __assign(__assign({}, data.proof.privacy), { obfuscated: newObfuscatedData }) }) }); +}; +exports.obfuscateVerifiableCredential = obfuscateVerifiableCredential; diff --git a/dist/cjs/3.0/salt.js b/dist/cjs/3.0/salt.js new file mode 100644 index 00000000..1f53befb --- /dev/null +++ b/dist/cjs/3.0/salt.js @@ -0,0 +1,45 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.decodeSalt = exports.encodeSalt = exports.salt = exports.secureRandomString = void 0; +var crypto_1 = require("crypto"); +var js_base64_1 = require("js-base64"); +var traverseAndFlatten_1 = require("./traverseAndFlatten"); +var ENTROPY_IN_BYTES = 32; +var illegalCharactersCheck = function (data) { + Object.entries(data).forEach(function (_a) { + var key = _a[0], value = _a[1]; + if (key.includes(".")) { + throw new Error("Key names must not have . in them"); + } + if (key.includes("[") || key.includes("]")) { + throw new Error("Key names must not have '[' or ']' in them"); + } + if (value && typeof value === "object") { + return illegalCharactersCheck(value); // Recursively search if property contains sub-properties + } + }); +}; +// Using 32 bytes of entropy as compared to 16 bytes in uuid +// Using hex encoding as compared to base64 for constant string length +var secureRandomString = function () { return crypto_1.randomBytes(ENTROPY_IN_BYTES).toString("hex"); }; +exports.secureRandomString = secureRandomString; +var salt = function (data) { + // Check for illegal characters e.g. '.', '[' or ']' + illegalCharactersCheck(data); + return traverseAndFlatten_1.traverseAndFlatten(data, { iteratee: function (_a) { + var path = _a.path; + return ({ value: exports.secureRandomString(), path: path }); + } }); +}; +exports.salt = salt; +var encodeSalt = function (salts) { return js_base64_1.Base64.encode(JSON.stringify(salts)); }; +exports.encodeSalt = encodeSalt; +var decodeSalt = function (salts) { + var decoded = JSON.parse(js_base64_1.Base64.decode(salts)); + decoded.forEach(function (salt) { + if (salt.value.length !== ENTROPY_IN_BYTES * 2) + throw new Error("Salt must be " + ENTROPY_IN_BYTES + " bytes"); + }); + return decoded; +}; +exports.decodeSalt = decodeSalt; diff --git a/dist/cjs/3.0/schema/schema.json b/dist/cjs/3.0/schema/schema.json new file mode 100644 index 00000000..39ded66d --- /dev/null +++ b/dist/cjs/3.0/schema/schema.json @@ -0,0 +1,230 @@ +{ + "title": "Open Attestation Schema 3.0", + "$id": "https://schema.openattestation.com/3.0/schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "definitions": { + "type": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "issuer": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uri", + "description": "URI when dereferenced, results in a document containing machine-readable information about the issuer that can be used to verify the information expressed in the credential. More information in https://www.w3.org/TR/vc-data-model/#issuer" + }, + "name": { + "type": "string", + "description": "Issuer's name" + } + }, + "required": ["id", "name"], + "additionalProperties": true + } + }, + "properties": { + "@context": { + "type": "array", + "items": { + "type": ["string", "object"], + "format": "uri" + }, + "description": "List of URI to determine the terminology used in the verifiable credential as explained by https://www.w3.org/TR/vc-data-model/#contexts" + }, + "id": { + "type": "string", + "format": "uri", + "description": "URI to the subject of the credential as explained by https://www.w3.org/TR/vc-data-model/#credential-subject" + }, + "type": { + "$ref": "#/definitions/type", + "description": "Specific verifiable credential type as explained by https://www.w3.org/TR/vc-data-model/#types" + }, + "reference": { + "type": "string", + "description": "Internal reference, usually a serial number, of this document" + }, + "name": { + "type": "string", + "description": "Human readable name of this credential" + }, + "issuanceDate": { + "type": "string", + "format": "date-time", + "description": "The date and time when this credential becomes valid (may be deprecated in favor of issued/validFrom a future version of W3C's VC Data Model)" + }, + "expirationDate": { + "type": "string", + "format": "date-time", + "description": "The date and time when this credential expires" + }, + "issued": { + "type": "string", + "format": "date-time", + "description": "The date and time when this credential becomes valid" + }, + "validFrom": { + "type": "string", + "format": "date-time", + "description": "The date and time when this credential becomes valid" + }, + "validUntil": { + "type": "string", + "format": "date-time", + "description": "The date and time when this credential expires" + }, + "credentialSubject": { + "oneOf": [ + { + "type": "object" + }, + { + "type": "array", + "items": { + "type": "object" + } + } + ] + }, + "credentialStatus": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uri", + "examples": ["https://example.edu/status/24"] + }, + "type": { + "type": "string", + "examples": ["CredentialStatusList2017"], + "description": "Express the credential status type (also referred to as the credential status method). It is expected that the value will provide enough information to determine the current status of the credential. For example, the object could contain a link to an external document noting whether or not the credential is suspended or revoked." + } + }, + "required": ["id", "type"] + }, + "issuer": { + "oneOf": [ + { + "$ref": "#/definitions/issuer" + }, + { + "type": "string" + } + ] + }, + "openAttestationMetadata": { + "type": "object", + "properties": { + "template": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Template name to be use by template renderer to determine the template to use" + }, + "type": { + "type": "string", + "description": "Type of renderer template", + "enum": ["EMBEDDED_RENDERER"] + }, + "url": { + "type": "string", + "description": "URL of a decentralised renderer to render this document", + "pattern": "^(https?)://" + } + }, + "required": ["name", "type", "url"], + "additionalProperties": false + }, + "proof": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Proof method name as explained by https://www.w3.org/TR/vc-data-model/#types", + "enum": ["OpenAttestationProofMethod"] + }, + "method": { + "type": "string", + "description": "Proof Open Attestation method", + "enum": ["TOKEN_REGISTRY", "DOCUMENT_STORE", "DID"] + }, + "value": { + "description": "Proof value of issuer(s). Smart contract address for TOKEN_REGISTRY & DOCUMENT_STORE, did for DID method", + "type": "string" + }, + "revocation": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Revocation method (if required by proof method)", + "enum": ["NONE", "REVOCATION_STORE"] + }, + "location": { + "type": "string", + "description": "Smart contract address or url of certificate revocation list for Revocation Store type revocation" + } + }, + "required": ["type"] + } + }, + "required": ["type", "method", "value"], + "additionalProperties": true + }, + "identityProof": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["DNS-TXT", "DNS-DID", "DID"] + }, + "identifier": { + "type": "string", + "description": "Identifier to be shown to end user upon verifying the identity" + } + }, + "additionalProperties": false, + "required": ["type", "identifier"] + } + }, + "required": ["proof", "identityProof"] + }, + "attachments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fileName": { + "type": "string", + "description": "Name of this attachment, with appropriate extensions" + }, + "mimeType": { + "type": "string", + "description": "Media type (or MIME type) of this attachment" + }, + "data": { + "type": "string", + "description": "Base64 encoding of this attachment" + } + }, + "required": ["fileName", "mimeType", "data"], + "additionalProperties": false + } + } + }, + "required": ["@context", "type", "credentialSubject", "issuer", "issuanceDate", "openAttestationMetadata"], + "additionalProperties": true +} diff --git a/dist/cjs/3.0/sign.js b/dist/cjs/3.0/sign.js new file mode 100644 index 00000000..d82d8abb --- /dev/null +++ b/dist/cjs/3.0/sign.js @@ -0,0 +1,83 @@ +"use strict"; +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.signDocument = void 0; +var signer_1 = require("../shared/signer"); +var sign_1 = require("../shared/@types/sign"); +var utils_1 = require("../shared/utils"); +var signDocument = function (document, algorithm, keyOrSigner) { return __awaiter(void 0, void 0, void 0, function () { + var merkleRoot, signature, proof, _a, _b, _c; + var _d; + return __generator(this, function (_e) { + switch (_e.label) { + case 0: + if (utils_1.isSignedWrappedV3Document(document)) + throw new Error("Document has been signed"); + merkleRoot = "0x" + document.proof.merkleRoot; + return [4 /*yield*/, signer_1.sign(algorithm, merkleRoot, keyOrSigner)]; + case 1: + signature = _e.sent(); + _a = [__assign({}, document.proof)]; + _d = {}; + if (!sign_1.SigningKey.guard(keyOrSigner)) return [3 /*break*/, 2]; + _b = keyOrSigner.public; + return [3 /*break*/, 4]; + case 2: + _c = "did:ethr:"; + return [4 /*yield*/, keyOrSigner.getAddress()]; + case 3: + _b = _c + (_e.sent()) + "#controller"; + _e.label = 4; + case 4: + proof = __assign.apply(void 0, _a.concat([(_d.key = _b, _d.signature = signature, _d)])); + return [2 /*return*/, __assign(__assign({}, document), { proof: proof })]; + } + }); +}); }; +exports.signDocument = signDocument; diff --git a/dist/cjs/3.0/traverseAndFlatten.js b/dist/cjs/3.0/traverseAndFlatten.js new file mode 100644 index 00000000..f2704118 --- /dev/null +++ b/dist/cjs/3.0/traverseAndFlatten.js @@ -0,0 +1,20 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.traverseAndFlatten = void 0; +function traverseAndFlatten(data, _a) { + var iteratee = _a.iteratee, _b = _a.path, path = _b === void 0 ? "" : _b; + if (Array.isArray(data)) { + return data.flatMap(function (v, index) { return traverseAndFlatten(v, { iteratee: iteratee, path: path + "[" + index + "]" }); }); + } + // Since null datas are allowed but typeof null === "object", the "&& data" is used to skip this + if (typeof data === "object" && data) { + return Object.keys(data).flatMap(function (key) { + return traverseAndFlatten(data[key], { iteratee: iteratee, path: path ? path + "." + key : key }); + }); + } + if (typeof data === "string" || typeof data === "number" || typeof data === "boolean" || data === null) { + return iteratee({ value: data, path: path }); + } + throw new Error("Unexpected data '" + data + "' in '" + path + "'"); +} +exports.traverseAndFlatten = traverseAndFlatten; diff --git a/dist/cjs/3.0/types.js b/dist/cjs/3.0/types.js new file mode 100644 index 00000000..d71e2ffc --- /dev/null +++ b/dist/cjs/3.0/types.js @@ -0,0 +1,37 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.VerifiableCredentialSignedProof = exports.VerifiableCredentialWrappedProofStrict = exports.VerifiableCredentialWrappedProof = exports.ObfuscationMetadata = void 0; +var document_1 = require("../shared/@types/document"); +var runtypes_1 = require("runtypes"); +exports.ObfuscationMetadata = runtypes_1.Record({ + obfuscated: runtypes_1.Array(document_1.OpenAttestationHexString), +}); +exports.VerifiableCredentialWrappedProof = runtypes_1.Record({ + type: document_1.SignatureAlgorithm, + targetHash: runtypes_1.String, + merkleRoot: runtypes_1.String, + proofs: runtypes_1.Array(runtypes_1.String), + salts: runtypes_1.String, + privacy: exports.ObfuscationMetadata, + proofPurpose: document_1.ProofPurpose, +}); +exports.VerifiableCredentialWrappedProofStrict = exports.VerifiableCredentialWrappedProof.And(runtypes_1.Record({ + targetHash: document_1.OpenAttestationHexString, + merkleRoot: document_1.OpenAttestationHexString, + proofs: runtypes_1.Array(document_1.OpenAttestationHexString), +})); +exports.VerifiableCredentialSignedProof = exports.VerifiableCredentialWrappedProof.And(runtypes_1.Record({ + key: runtypes_1.String, + signature: runtypes_1.String, +})); +__exportStar(require("../__generated__/schema.3.0"), exports); diff --git a/dist/cjs/3.0/validate/__mocks__/validate.js b/dist/cjs/3.0/validate/__mocks__/validate.js new file mode 100644 index 00000000..1b11f1b7 --- /dev/null +++ b/dist/cjs/3.0/validate/__mocks__/validate.js @@ -0,0 +1,45 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.validateW3C = void 0; +// Allow validateW3C to pass without checking the actual schema +// eslint-disable-next-line @typescript-eslint/no-empty-function +var validateW3C = function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { + return [2 /*return*/]; +}); }); }; +exports.validateW3C = validateW3C; diff --git a/dist/cjs/3.0/validate/index.js b/dist/cjs/3.0/validate/index.js new file mode 100644 index 00000000..6edc8502 --- /dev/null +++ b/dist/cjs/3.0/validate/index.js @@ -0,0 +1,13 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +__exportStar(require("./validate"), exports); diff --git a/dist/cjs/3.0/validate/validate.js b/dist/cjs/3.0/validate/validate.js new file mode 100644 index 00000000..19cf9a88 --- /dev/null +++ b/dist/cjs/3.0/validate/validate.js @@ -0,0 +1,161 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.validateW3C = void 0; +var jsonld_1 = require("jsonld"); +var node_fetch_1 = __importDefault(require("node-fetch")); +var getId = function (objectOrString) { + if (typeof objectOrString === "string") { + return objectOrString; + } + return objectOrString.id; +}; +/* Based on https://tools.ietf.org/html/rfc3339#section-5.6 */ +var dateFullYear = /[0-9]{4}/; +var dateMonth = /(0[1-9]|1[0-2])/; +var dateMDay = /([12]\d|0[1-9]|3[01])/; +var timeHour = /([01][0-9]|2[0-3])/; +var timeMinute = /[0-5][0-9]/; +var timeSecond = /([0-5][0-9]|60)/; +var timeSecFrac = /(\.[0-9]+)?/; +var timeNumOffset = new RegExp("[-+]".concat(timeHour.source, ":").concat(timeMinute.source)); +var timeOffset = new RegExp("([zZ]|".concat(timeNumOffset.source, ")")); +var partialTime = new RegExp("".concat(timeHour.source, ":").concat(timeMinute.source, ":").concat(timeSecond.source).concat(timeSecFrac.source)); +var fullDate = new RegExp("".concat(dateFullYear.source, "-").concat(dateMonth.source, "-").concat(dateMDay.source)); +var fullTime = new RegExp("".concat(partialTime.source).concat(timeOffset.source)); +var rfc3339 = new RegExp("".concat(fullDate.source, "[ tT]").concat(fullTime.source)); +var isValidRFC3339 = function (str) { + return rfc3339.test(str); +}; +/* Based on https://tools.ietf.org/html/rfc3986 and https://github.com/ajv-validator/ajv/search?q=uri&unscoped_q=uri */ +var uri = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i; +var rfc3986 = new RegExp(uri); +var isValidRFC3986 = function (str) { + return rfc3986.test(str); +}; +var preloadedContextList = [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1", + "https://schemata.openattestation.com/com/openattestation/1.0/DrivingLicenceCredential.json", + "https://schemata.openattestation.com/com/openattestation/1.0/OpenAttestation.v3.json", + "https://schemata.openattestation.com/com/openattestation/1.0/CustomContext.json", +]; +var contexts = new Map(); +var nodeDocumentLoader = jsonld_1.documentLoaders.xhr ? jsonld_1.documentLoaders.xhr() : jsonld_1.documentLoaders.node(); +var preload = true; +var documentLoader = function (url) { return __awaiter(void 0, void 0, void 0, function () { + var _i, preloadedContextList_1, url_1, promise, promise; + var _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + if (preload) { + preload = false; + for (_i = 0, preloadedContextList_1 = preloadedContextList; _i < preloadedContextList_1.length; _i++) { + url_1 = preloadedContextList_1[_i]; + contexts.set(url_1, node_fetch_1.default(url_1, { headers: { accept: "application/json" } }).then(function (res) { return res.json(); })); + } + } + if (!contexts.get(url)) return [3 /*break*/, 2]; + promise = contexts.get(url); + _a = { + contextUrl: undefined + }; + return [4 /*yield*/, promise]; + case 1: return [2 /*return*/, (_a.document = _b.sent(), + _a.documentUrl = url, + _a)]; + case 2: + promise = nodeDocumentLoader(url); + contexts.set(url, promise.then(function (_a) { + var document = _a.document; + return document; + })); + return [2 /*return*/, promise]; + } + }); +}); }; +function validateW3C(credential) { + return __awaiter(this, void 0, void 0, function () { + var issuerId; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + // ensure first context is 'https://www.w3.org/2018/credentials/v1' as it's mandatory, see https://www.w3.org/TR/vc-data-model/#contexts + if (!Array.isArray(credential["@context"]) || + (Array.isArray(credential["@context"]) && credential["@context"][0] !== "https://www.w3.org/2018/credentials/v1")) { + throw new Error("https://www.w3.org/2018/credentials/v1 needs to be first in the list of contexts"); + } + issuerId = getId(credential.issuer); + if (!isValidRFC3986(issuerId)) { + throw new Error("Property 'issuer' id must be a valid RFC 3986 URI"); + } + // ensure issuanceDate is a valid RFC3339 date, see https://www.w3.org/TR/vc-data-model/#issuance-date + if (!isValidRFC3339(credential.issuanceDate)) { + throw new Error("Property 'issuanceDate' must be a valid RFC 3339 date"); + } + // ensure expirationDate is a valid RFC3339 date, see https://www.w3.org/TR/vc-data-model/#expiration + if (credential.expirationDate && !isValidRFC3339(credential.expirationDate)) { + throw new Error("Property 'expirationDate' must be a valid RFC 3339 date"); + } + // https://www.w3.org/TR/vc-data-model/#types + if (!credential.type || !Array.isArray(credential.type)) { + throw new Error("Property 'type' must exist and be an array"); + } + if (!credential.type.includes("VerifiableCredential")) { + throw new Error("Property 'type' must have VerifiableCredential as one of the items"); + } + return [4 /*yield*/, jsonld_1.expand(credential, { + expansionMap: function (info) { + if (info.unmappedProperty) { + throw new Error("\"The property " + (info.activeProperty ? info.activeProperty + "." : "") + info.unmappedProperty + " in the input was not defined in the context\""); + } + }, + documentLoader: documentLoader, + })]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); + }); +} +exports.validateW3C = validateW3C; diff --git a/dist/cjs/3.0/verify.js b/dist/cjs/3.0/verify.js new file mode 100644 index 00000000..9441be6a --- /dev/null +++ b/dist/cjs/3.0/verify.js @@ -0,0 +1,38 @@ +"use strict"; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.verify = void 0; +var digest_1 = require("./digest"); +var merkle_1 = require("../shared/merkle"); +var salt_1 = require("./salt"); +var verify = function (document) { + if (!document.proof) { + return false; + } + // Remove proof from document + // eslint-disable-next-line no-unused-vars,@typescript-eslint/no-unused-vars + var proof = document.proof, documentWithoutProof = __rest(document, ["proof"]); + var decodedSalts = salt_1.decodeSalt(document.proof.salts); + // Checks to ensure there are no added/removed values, so visibleSalts.length must match decodedSalts.length + var visibleSalts = salt_1.salt(documentWithoutProof); + if (visibleSalts.length !== decodedSalts.length) + return false; + // Checks target hash + var digest = digest_1.digestCredential(documentWithoutProof, decodedSalts, document.proof.privacy.obfuscated); + var targetHash = document.proof.targetHash; + if (digest !== targetHash) + return false; + // Calculates merkle root from target hash and proof, then compare to merkle root in document + return merkle_1.checkProof(document.proof.proofs, document.proof.merkleRoot, document.proof.targetHash); +}; +exports.verify = verify; diff --git a/dist/cjs/3.0/wrap.js b/dist/cjs/3.0/wrap.js new file mode 100644 index 00000000..46f4320b --- /dev/null +++ b/dist/cjs/3.0/wrap.js @@ -0,0 +1,122 @@ +"use strict"; +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.wrapDocuments = exports.wrapDocument = void 0; +var utils_1 = require("../shared/utils"); +var merkle_1 = require("../shared/merkle"); +var document_1 = require("../shared/@types/document"); +var digest_1 = require("../3.0/digest"); +var validate_1 = require("../shared/validate"); +var salt_1 = require("./salt"); +var validate_2 = require("./validate"); +var ajv_1 = require("../shared/ajv"); +var getExternalSchema = function (schema) { return (schema ? { schema: schema } : {}); }; +var wrapDocument = function (credential, options) { return __awaiter(void 0, void 0, void 0, function () { + var document, salts, digest, batchBuffers, merkleTree, merkleRoot, merkleProof, verifiableCredential, errors; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + document = __assign(__assign({ version: document_1.SchemaId.v3 }, getExternalSchema(options.externalSchemaId)), credential); + // To ensure that base @context exists, but this also means some of our validateW3C errors may be unreachable + if (!document["@context"]) { + document["@context"] = ["https://www.w3.org/2018/credentials/v1"]; + } + // Since our wrapper adds in OA-specific properties, we should push our OA context. This is also to pass W3C VC test suite. + if (Array.isArray(document["@context"]) && + !document["@context"].includes("https://schemata.openattestation.com/com/openattestation/1.0/OpenAttestation.v3.json")) { + document["@context"].push("https://schemata.openattestation.com/com/openattestation/1.0/OpenAttestation.v3.json"); + } + salts = salt_1.salt(document); + digest = digest_1.digestCredential(document, salts, []); + batchBuffers = [digest].map(utils_1.hashToBuffer); + merkleTree = new merkle_1.MerkleTree(batchBuffers); + merkleRoot = merkleTree.getRoot().toString("hex"); + merkleProof = merkleTree.getProof(utils_1.hashToBuffer(digest)).map(function (buffer) { return buffer.toString("hex"); }); + verifiableCredential = __assign(__assign({}, document), { proof: { + type: "OpenAttestationMerkleProofSignature2018", + proofPurpose: "assertionMethod", + targetHash: digest, + proofs: merkleProof, + merkleRoot: merkleRoot, + salts: salt_1.encodeSalt(salts), + privacy: { + obfuscated: [], + }, + } }); + errors = validate_1.validateSchema(verifiableCredential, ajv_1.getSchema(document_1.SchemaId.v3)); + if (errors.length > 0) { + throw new utils_1.SchemaValidationError("Invalid document", errors, verifiableCredential); + } + return [4 /*yield*/, validate_2.validateW3C(verifiableCredential)]; + case 1: + _a.sent(); + return [2 /*return*/, verifiableCredential]; + } + }); +}); }; +exports.wrapDocument = wrapDocument; +var wrapDocuments = function (documents, options) { return __awaiter(void 0, void 0, void 0, function () { + var verifiableCredentials, merkleTree, merkleRoot; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, Promise.all(documents.map(function (document) { return exports.wrapDocument(document, options); }))]; + case 1: + verifiableCredentials = _a.sent(); + merkleTree = new merkle_1.MerkleTree(verifiableCredentials.map(function (verifiableCredential) { return verifiableCredential.proof.targetHash; }).map(utils_1.hashToBuffer)); + merkleRoot = merkleTree.getRoot().toString("hex"); + // for each document, update the merkle root and add the proofs needed + return [2 /*return*/, verifiableCredentials.map(function (verifiableCredential) { + var digest = verifiableCredential.proof.targetHash; + var merkleProof = merkleTree.getProof(utils_1.hashToBuffer(digest)).map(function (buffer) { return buffer.toString("hex"); }); + return __assign(__assign({}, verifiableCredential), { proof: __assign(__assign({}, verifiableCredential.proof), { proofs: merkleProof, merkleRoot: merkleRoot }) }); + })]; + } + }); +}); }; +exports.wrapDocuments = wrapDocuments; diff --git a/dist/cjs/__generated__/schema.2.0.js b/dist/cjs/__generated__/schema.2.0.js new file mode 100644 index 00000000..de1f2e63 --- /dev/null +++ b/dist/cjs/__generated__/schema.2.0.js @@ -0,0 +1,21 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RevocationType = exports.IdentityProofType = exports.TemplateType = void 0; +/** + * Type of renderer template + */ +var TemplateType; +(function (TemplateType) { + TemplateType["EmbeddedRenderer"] = "EMBEDDED_RENDERER"; +})(TemplateType = exports.TemplateType || (exports.TemplateType = {})); +var IdentityProofType; +(function (IdentityProofType) { + IdentityProofType["DNSDid"] = "DNS-DID"; + IdentityProofType["DNSTxt"] = "DNS-TXT"; + IdentityProofType["Did"] = "DID"; +})(IdentityProofType = exports.IdentityProofType || (exports.IdentityProofType = {})); +var RevocationType; +(function (RevocationType) { + RevocationType["None"] = "NONE"; + RevocationType["RevocationStore"] = "REVOCATION_STORE"; +})(RevocationType = exports.RevocationType || (exports.RevocationType = {})); diff --git a/dist/cjs/__generated__/schema.3.0.js b/dist/cjs/__generated__/schema.3.0.js new file mode 100644 index 00000000..c944e865 --- /dev/null +++ b/dist/cjs/__generated__/schema.3.0.js @@ -0,0 +1,40 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TemplateType = exports.ProofType = exports.RevocationType = exports.Method = exports.IdentityProofType = void 0; +var IdentityProofType; +(function (IdentityProofType) { + IdentityProofType["DNSDid"] = "DNS-DID"; + IdentityProofType["DNSTxt"] = "DNS-TXT"; + IdentityProofType["Did"] = "DID"; +})(IdentityProofType = exports.IdentityProofType || (exports.IdentityProofType = {})); +/** + * Proof Open Attestation method + */ +var Method; +(function (Method) { + Method["Did"] = "DID"; + Method["DocumentStore"] = "DOCUMENT_STORE"; + Method["TokenRegistry"] = "TOKEN_REGISTRY"; +})(Method = exports.Method || (exports.Method = {})); +/** + * Revocation method (if required by proof method) + */ +var RevocationType; +(function (RevocationType) { + RevocationType["None"] = "NONE"; + RevocationType["RevocationStore"] = "REVOCATION_STORE"; +})(RevocationType = exports.RevocationType || (exports.RevocationType = {})); +/** + * Proof method name as explained by https://www.w3.org/TR/vc-data-model/#types + */ +var ProofType; +(function (ProofType) { + ProofType["OpenAttestationProofMethod"] = "OpenAttestationProofMethod"; +})(ProofType = exports.ProofType || (exports.ProofType = {})); +/** + * Type of renderer template + */ +var TemplateType; +(function (TemplateType) { + TemplateType["EmbeddedRenderer"] = "EMBEDDED_RENDERER"; +})(TemplateType = exports.TemplateType || (exports.TemplateType = {})); diff --git a/dist/cjs/index.js b/dist/cjs/index.js new file mode 100644 index 00000000..141cb654 --- /dev/null +++ b/dist/cjs/index.js @@ -0,0 +1,148 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.v3 = exports.v2 = exports.getData = exports.utils = exports.obfuscateDocument = exports.MerkleTree = exports.checkProof = exports.digestCredential = exports.digestDocument = exports.signDocument = exports.isSchemaValidationError = exports.obfuscate = exports.verifySignature = exports.validateSchema = exports.wrapDocuments = exports.wrapDocument = exports.__unsafe__use__it__at__your__own__risks__wrapDocuments = exports.__unsafe__use__it__at__your__own__risks__wrapDocument = void 0; +var validate_1 = require("./shared/validate"); +var verify_1 = require("./2.0/verify"); +var verify_2 = require("./3.0/verify"); +var wrap_1 = require("./2.0/wrap"); +var sign_1 = require("./2.0/sign"); +var wrap_2 = require("./3.0/wrap"); +var sign_2 = require("./3.0/sign"); +var document_1 = require("./shared/@types/document"); +var utils = __importStar(require("./shared/utils")); +exports.utils = utils; +var v2 = __importStar(require("./2.0/types")); +exports.v2 = v2; +var v3 = __importStar(require("./3.0/types")); +exports.v3 = v3; +var obfuscate_1 = require("./2.0/obfuscate"); +var obfuscate_2 = require("./3.0/obfuscate"); +var sign_3 = require("./shared/@types/sign"); +var ethers_1 = require("ethers"); +var ajv_1 = require("./shared/ajv"); +function __unsafe__use__it__at__your__own__risks__wrapDocument(data, options) { + return wrap_2.wrapDocument(data, options !== null && options !== void 0 ? options : { version: document_1.SchemaId.v3 }); +} +exports.__unsafe__use__it__at__your__own__risks__wrapDocument = __unsafe__use__it__at__your__own__risks__wrapDocument; +function __unsafe__use__it__at__your__own__risks__wrapDocuments(dataArray, options) { + return wrap_2.wrapDocuments(dataArray, options !== null && options !== void 0 ? options : { version: document_1.SchemaId.v3 }); +} +exports.__unsafe__use__it__at__your__own__risks__wrapDocuments = __unsafe__use__it__at__your__own__risks__wrapDocuments; +function wrapDocument(data, options) { + return wrap_1.wrapDocument(data, { externalSchemaId: options === null || options === void 0 ? void 0 : options.externalSchemaId }); +} +exports.wrapDocument = wrapDocument; +function wrapDocuments(dataArray, options) { + return wrap_1.wrapDocuments(dataArray, { externalSchemaId: options === null || options === void 0 ? void 0 : options.externalSchemaId }); +} +exports.wrapDocuments = wrapDocuments; +var validateSchema = function (document) { + return validate_1.validateSchema(document, ajv_1.getSchema("" + ((document === null || document === void 0 ? void 0 : document.version) || document_1.SchemaId.v2))).length === 0; +}; +exports.validateSchema = validateSchema; +function verifySignature(document) { + return utils.isWrappedV3Document(document) ? verify_2.verify(document) : verify_1.verify(document); +} +exports.verifySignature = verifySignature; +function obfuscate(document, fields) { + return document.version === document_1.SchemaId.v3 + ? obfuscate_2.obfuscateVerifiableCredential(document, fields) + : obfuscate_1.obfuscateDocument(document, fields); +} +exports.obfuscate = obfuscate; +exports.obfuscateDocument = obfuscate; +var isSchemaValidationError = function (error) { + return !!error.validationErrors; +}; +exports.isSchemaValidationError = isSchemaValidationError; +function signDocument(document, algorithm, keyOrSigner) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + // rj was worried it could happen deep in the code, so I moved it to the boundaries + if (!sign_3.SigningKey.guard(keyOrSigner) && !ethers_1.Signer.isSigner(keyOrSigner)) { + throw new Error("Either a keypair or ethers.js Signer must be provided"); + } + switch (true) { + case utils.isWrappedV2Document(document): + return [2 /*return*/, sign_1.signDocument(document, algorithm, keyOrSigner)]; + case utils.isWrappedV3Document(document): + return [2 /*return*/, sign_2.signDocument(document, algorithm, keyOrSigner)]; + default: + // Unreachable code atm until utils.isWrappedV2Document & utils.isWrappedV3Document becomes more strict + throw new Error("Unsupported document type: Only OpenAttestation v2 & v3 documents can be signed"); + } + return [2 /*return*/]; + }); + }); +} +exports.signDocument = signDocument; +var digest_1 = require("./2.0/digest"); +Object.defineProperty(exports, "digestDocument", { enumerable: true, get: function () { return digest_1.digestDocument; } }); +var digest_2 = require("./3.0/digest"); +Object.defineProperty(exports, "digestCredential", { enumerable: true, get: function () { return digest_2.digestCredential; } }); +var merkle_1 = require("./shared/merkle"); +Object.defineProperty(exports, "checkProof", { enumerable: true, get: function () { return merkle_1.checkProof; } }); +Object.defineProperty(exports, "MerkleTree", { enumerable: true, get: function () { return merkle_1.MerkleTree; } }); +__exportStar(require("./shared/@types/document"), exports); +__exportStar(require("./shared/@types/sign"), exports); +__exportStar(require("./shared/signer"), exports); +var utils_1 = require("./shared/utils"); // keep it to avoid breaking change, moved from privacy to utils +Object.defineProperty(exports, "getData", { enumerable: true, get: function () { return utils_1.getData; } }); diff --git a/dist/cjs/shared/@types/document.js b/dist/cjs/shared/@types/document.js new file mode 100644 index 00000000..f0d84705 --- /dev/null +++ b/dist/cjs/shared/@types/document.js @@ -0,0 +1,14 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ProofPurpose = exports.ProofType = exports.SignatureAlgorithm = exports.OpenAttestationHexString = exports.SchemaId = void 0; +var runtypes_1 = require("runtypes"); +var ethers_1 = require("ethers"); +var SchemaId; +(function (SchemaId) { + SchemaId["v2"] = "https://schema.openattestation.com/2.0/schema.json"; + SchemaId["v3"] = "https://schema.openattestation.com/3.0/schema.json"; +})(SchemaId = exports.SchemaId || (exports.SchemaId = {})); +exports.OpenAttestationHexString = runtypes_1.String.withConstraint(function (value) { return ethers_1.ethers.utils.isHexString("0x" + value, 32) || value + " has not the expected length of 32 bytes"; }); +exports.SignatureAlgorithm = runtypes_1.Literal("OpenAttestationMerkleProofSignature2018"); +exports.ProofType = runtypes_1.Literal("OpenAttestationSignature2018"); +exports.ProofPurpose = runtypes_1.Literal("assertionMethod"); diff --git a/dist/cjs/shared/@types/sign.js b/dist/cjs/shared/@types/sign.js new file mode 100644 index 00000000..0a248f77 --- /dev/null +++ b/dist/cjs/shared/@types/sign.js @@ -0,0 +1,12 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SigningKey = exports.SUPPORTED_SIGNING_ALGORITHM = void 0; +var runtypes_1 = require("runtypes"); +var SUPPORTED_SIGNING_ALGORITHM; +(function (SUPPORTED_SIGNING_ALGORITHM) { + SUPPORTED_SIGNING_ALGORITHM["Secp256k1VerificationKey2018"] = "Secp256k1VerificationKey2018"; +})(SUPPORTED_SIGNING_ALGORITHM = exports.SUPPORTED_SIGNING_ALGORITHM || (exports.SUPPORTED_SIGNING_ALGORITHM = {})); +exports.SigningKey = runtypes_1.Record({ + private: runtypes_1.String, + public: runtypes_1.String, +}); diff --git a/dist/cjs/shared/@types/wrap.js b/dist/cjs/shared/@types/wrap.js new file mode 100644 index 00000000..4eeb6c8b --- /dev/null +++ b/dist/cjs/shared/@types/wrap.js @@ -0,0 +1,8 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.isWrapDocumentOptionV3 = void 0; +var document_1 = require("./document"); +var isWrapDocumentOptionV3 = function (options) { + return (options === null || options === void 0 ? void 0 : options.version) === document_1.SchemaId.v3; +}; +exports.isWrapDocumentOptionV3 = isWrapDocumentOptionV3; diff --git a/dist/cjs/shared/ajv.js b/dist/cjs/shared/ajv.js new file mode 100644 index 00000000..1f1cee0c --- /dev/null +++ b/dist/cjs/shared/ajv.js @@ -0,0 +1,55 @@ +"use strict"; +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getSchema = exports.buildAjv = void 0; +var ajv_1 = __importDefault(require("ajv")); +var ajv_formats_1 = __importDefault(require("ajv-formats")); +var schema_json_1 = __importDefault(require("../2.0/schema/schema.json")); +var schema_json_2 = __importDefault(require("../3.0/schema/schema.json")); +var defaultTransform = function (schema) { return schema; }; +var buildAjv = function (options) { + if (options === void 0) { options = { + transform: defaultTransform, + }; } + var transform = options.transform, ajvOptions = __rest(options, ["transform"]); + var ajv = new ajv_1.default(__assign({ allErrors: true, allowUnionTypes: true }, ajvOptions)); + ajv_formats_1.default(ajv); + ajv.addKeyword("deprecationMessage"); + ajv.compile(transform(schema_json_1.default)); + ajv.compile(transform(schema_json_2.default)); + return ajv; +}; +exports.buildAjv = buildAjv; +var localAjv = exports.buildAjv(); +var getSchema = function (key, ajv) { + if (ajv === void 0) { ajv = localAjv; } + var schema = ajv.getSchema(key); + if (!schema) + throw new Error("Could not find " + key + " schema"); + return schema; +}; +exports.getSchema = getSchema; diff --git a/dist/cjs/shared/logger.js b/dist/cjs/shared/logger.js new file mode 100644 index 00000000..5a111682 --- /dev/null +++ b/dist/cjs/shared/logger.js @@ -0,0 +1,16 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getLogger = void 0; +var debug_1 = __importDefault(require("debug")); +var logger = debug_1.default("open-attestation"); +var getLogger = function (namespace) { return ({ + trace: logger.extend("trace:" + namespace), + debug: logger.extend("debug:" + namespace), + info: logger.extend("info:" + namespace), + warn: logger.extend("warn:" + namespace), + error: logger.extend("error:" + namespace), +}); }; +exports.getLogger = getLogger; diff --git a/dist/cjs/shared/merkle/index.js b/dist/cjs/shared/merkle/index.js new file mode 100644 index 00000000..40ed570c --- /dev/null +++ b/dist/cjs/shared/merkle/index.js @@ -0,0 +1,13 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +__exportStar(require("./merkle"), exports); diff --git a/dist/cjs/shared/merkle/merkle.js b/dist/cjs/shared/merkle/merkle.js new file mode 100644 index 00000000..0009006f --- /dev/null +++ b/dist/cjs/shared/merkle/merkle.js @@ -0,0 +1,102 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.checkProof = exports.MerkleTree = void 0; +var utils_1 = require("../utils"); +function getNextLayer(elements) { + return elements.reduce(function (layer, element, index, arr) { + if (index % 2 === 0) { + // only calculate hash for even indexes + layer.push(utils_1.combineHashBuffers(element, arr[index + 1])); + } + return layer; + }, []); +} +/** + * This function produces the hashes and the merkle tree + * If there are no elements, return empty array of array + */ +function getLayers(elements) { + if (elements.length === 0) { + return [[]]; + } + var layers = []; + layers.push(elements); + while (layers[layers.length - 1].length > 1) { + layers.push(getNextLayer(layers[layers.length - 1])); + } + return layers; +} +/** + * This function takes a given index and determines if it is the first or second element in a pair, then returns the first element of the pair + * If the given index is the last element in a layer with an odd number of elements, then null is returned + * E.g 1: + * + * layer = [ A, B, C, D ], + * if index = 2, then return A + * if index = 3, then return C + * + * E.g 2: + * + * layer = [ A, B, C, D, E] + * if index = 5, then return null + * if index = 4, then return C + */ +function getPair(index, layer) { + var pairIndex = index % 2 ? index - 1 : index + 1; // if odd return the index before it, else if even return the index after it + if (pairIndex < layer.length) { + return layer[pairIndex]; + } + return null; // this happens when the given index is the last element in a layer with odd number of elements +} +/** + * Finds all the "uncle" nodes required to prove a given element in the merkle tree + */ +function getProof(index, layers) { + var i = index; + var proof = layers.reduce(function (current, layer) { + var pair = getPair(i, layer); + if (pair) { + current.push(pair); + } + i = Math.floor(i / 2); // finds the index of the parent of the current node + return current; + }, []); + return proof; +} +var MerkleTree = /** @class */ (function () { + function MerkleTree(_elements) { + this.elements = utils_1.hashArray(_elements); + // check buffers + if (this.elements.some(function (e) { return !(e.length === 32 && Buffer.isBuffer(e)); })) { + throw new Error("elements must be 32 byte buffers"); + } + this.layers = getLayers(this.elements); + } + MerkleTree.prototype.getRoot = function () { + return this.layers[this.layers.length - 1][0]; + }; + MerkleTree.prototype.getProof = function (_element) { + var element = utils_1.toBuffer(_element); + var index = this.elements.findIndex(function (e) { return e.equals(element); }); // searches for given element in the merkle tree and returns the index + if (index === -1) { + throw new Error("Element not found"); + } + return getProof(index, this.layers); + }; + return MerkleTree; +}()); +exports.MerkleTree = MerkleTree; +/** + * Function that runs through the supplied hashes to arrive at the supplied merkle root hash + * @param _proof The list of uncle hashes required to arrive at the supplied merkle root + * @param _root The merkle root + * @param _element The leaf node that is being verified + */ +var checkProof = function (_proof, _root, _element) { + var proof = _proof.map(function (step) { return utils_1.hashToBuffer(step); }); + var root = utils_1.hashToBuffer(_root); + var element = utils_1.hashToBuffer(_element); + var proofRoot = proof.reduce(function (hash, pair) { return utils_1.combineHashBuffers(hash, pair); }, element); + return root.equals(proofRoot); +}; +exports.checkProof = checkProof; diff --git a/dist/cjs/shared/serialize/flatten.js b/dist/cjs/shared/serialize/flatten.js new file mode 100644 index 00000000..72887cda --- /dev/null +++ b/dist/cjs/shared/serialize/flatten.js @@ -0,0 +1,29 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.flatten = void 0; +var flatley_1 = require("flatley"); +var lodash_1 = require("lodash"); +var hasPeriodInKey = function (key) { + if (key.indexOf(".") >= 0) { + throw new Error("Key names must not have . in them"); + } + return false; +}; +var filters = [{ test: hasPeriodInKey }]; +/** + * Calls external flatten library but ensures that global filters are always applied + * @param data + * @param options + */ +var flatten = function (data, options) { + var _a; + var newOptions = options ? lodash_1.cloneDeep(options) : {}; + if (newOptions.coercion) { + (_a = newOptions.coercion).push.apply(_a, filters); + } + else { + newOptions.coercion = filters; + } + return flatley_1.flatten(data, newOptions); +}; +exports.flatten = flatten; diff --git a/dist/cjs/shared/signer/index.js b/dist/cjs/shared/signer/index.js new file mode 100644 index 00000000..cbeba044 --- /dev/null +++ b/dist/cjs/shared/signer/index.js @@ -0,0 +1,13 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +__exportStar(require("./signer"), exports); diff --git a/dist/cjs/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/Secp256k1VerificationKey2018.js b/dist/cjs/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/Secp256k1VerificationKey2018.js new file mode 100644 index 00000000..db2ca543 --- /dev/null +++ b/dist/cjs/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/Secp256k1VerificationKey2018.js @@ -0,0 +1,22 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.sign = exports.name = void 0; +var ethers_1 = require("ethers"); +var sign_1 = require("../../../@types/sign"); +exports.name = "Secp256k1VerificationKey2018"; +var sign = function (message, keyOrSigner, options) { + if (options === void 0) { options = {}; } + var signer; + if (sign_1.SigningKey.guard(keyOrSigner)) { + var wallet = new ethers_1.Wallet(keyOrSigner.private); + if (!keyOrSigner.public.toLowerCase().includes(wallet.address.toLowerCase())) { + throw new Error("Private key is wrong for " + keyOrSigner.public); + } + signer = wallet; + } + else { + signer = keyOrSigner; + } + return signer.signMessage(options.signAsString ? message : ethers_1.utils.arrayify(message)); +}; +exports.sign = sign; diff --git a/dist/cjs/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/index.js b/dist/cjs/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/index.js new file mode 100644 index 00000000..e0c9173a --- /dev/null +++ b/dist/cjs/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/index.js @@ -0,0 +1,13 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +__exportStar(require("./Secp256k1VerificationKey2018"), exports); diff --git a/dist/cjs/shared/signer/signatureSchemes/index.js b/dist/cjs/shared/signer/signatureSchemes/index.js new file mode 100644 index 00000000..76761a87 --- /dev/null +++ b/dist/cjs/shared/signer/signatureSchemes/index.js @@ -0,0 +1,13 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +__exportStar(require("./signatureSchemes"), exports); diff --git a/dist/cjs/shared/signer/signatureSchemes/signatureSchemes.js b/dist/cjs/shared/signer/signatureSchemes/signatureSchemes.js new file mode 100644 index 00000000..c54eaac9 --- /dev/null +++ b/dist/cjs/shared/signer/signatureSchemes/signatureSchemes.js @@ -0,0 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.defaultSigners = void 0; +var Secp256k1VerificationKey2018_1 = require("./Secp256k1VerificationKey2018"); +exports.defaultSigners = new Map(); +exports.defaultSigners.set(Secp256k1VerificationKey2018_1.name, Secp256k1VerificationKey2018_1.sign); diff --git a/dist/cjs/shared/signer/signer.js b/dist/cjs/shared/signer/signer.js new file mode 100644 index 00000000..13951aca --- /dev/null +++ b/dist/cjs/shared/signer/signer.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.defaultSigners = exports.sign = exports.signerBuilder = void 0; +var signatureSchemes_1 = require("./signatureSchemes"); +Object.defineProperty(exports, "defaultSigners", { enumerable: true, get: function () { return signatureSchemes_1.defaultSigners; } }); +var signerBuilder = function (signers) { return function (alg, message, keyOrSigner, options) { + var signer = signers.get(alg); + if (!signer) + throw new Error(alg + " is not supported as a signing algorithm"); + return signer(message, keyOrSigner, options); +}; }; +exports.signerBuilder = signerBuilder; +exports.sign = exports.signerBuilder(signatureSchemes_1.defaultSigners); diff --git a/dist/cjs/shared/utils/diagnose.js b/dist/cjs/shared/utils/diagnose.js new file mode 100644 index 00000000..195be202 --- /dev/null +++ b/dist/cjs/shared/utils/diagnose.js @@ -0,0 +1,134 @@ +"use strict"; +var __spreadArray = (this && this.__spreadArray) || function (to, from) { + for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) + to[j] = from[i]; + return to; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.diagnose = void 0; +var ethers_1 = require("ethers"); +var __1 = require("../.."); +var validate_1 = require("../validate"); +var types_1 = require("../../3.0/types"); +var types_2 = require("../../2.0/types"); +var lodash_1 = require("lodash"); +var ajv_1 = require("../ajv"); +var handleError = function (debug) { + var messages = []; + for (var _i = 1; _i < arguments.length; _i++) { + messages[_i - 1] = arguments[_i]; + } + if (debug) { + for (var _a = 0, messages_1 = messages; _a < messages_1.length; _a++) { + var message = messages_1[_a]; + ethers_1.logger.info(message); + } + } + return messages.map(function (message) { return ({ message: message }); }); +}; +// remove enum and pattern from the schema +function transformSchema(schema) { + var _a, _b, _c, _d, _e, _f; + var excludeKeys = ["enum", "pattern"]; + function omit(value) { + if (value && typeof value === "object") { + var key = excludeKeys.find(function (key) { return value[key]; }); + if (key) { + var node_1 = lodash_1.clone(value); + excludeKeys.forEach(function (key) { + delete node_1[key]; + }); + return node_1; + } + } + } + var newSchema = lodash_1.cloneDeepWith(schema, omit); + // because we remove check on enum (DNS-DID, DNS-TXT, etc.) the identity proof can match multiple sub schema in v2. + // so here we change oneOf to anyOf, so that if more than one identityProof matches, it still passes + if ((_b = (_a = newSchema === null || newSchema === void 0 ? void 0 : newSchema.definitions) === null || _a === void 0 ? void 0 : _a.identityProof) === null || _b === void 0 ? void 0 : _b.oneOf) { + newSchema.definitions.identityProof.anyOf = (_d = (_c = newSchema === null || newSchema === void 0 ? void 0 : newSchema.definitions) === null || _c === void 0 ? void 0 : _c.identityProof) === null || _d === void 0 ? void 0 : _d.oneOf; + (_f = (_e = newSchema === null || newSchema === void 0 ? void 0 : newSchema.definitions) === null || _e === void 0 ? void 0 : _e.identityProof) === null || _f === void 0 ? true : delete _f.oneOf; + } + return newSchema; +} +// custom ajv for loose schema validation +// it will allow invalid format, invalid pattern and invalid enum +var ajv = ajv_1.buildAjv({ transform: transformSchema, validateFormats: false }); +/** + * Tools to give information about the validity of a document. It will return and eventually output the errors found. + * @param version 2.0 or 3.0 + * @param kind wrapped or signed + * @param debug turn on to output in the console, the errors found + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + * @param document the document to validate + */ +var diagnose = function (_a) { + var version = _a.version, kind = _a.kind, document = _a.document, _b = _a.debug, debug = _b === void 0 ? false : _b, mode = _a.mode; + if (!document) { + return handleError(debug, "The document must not be empty"); + } + if (typeof document !== "object") { + return handleError(debug, "The document must be an object"); + } + var errors = validate_1.validateSchema(document, ajv_1.getSchema(version === "3.0" ? __1.SchemaId.v3 : __1.SchemaId.v2, mode === "non-strict" ? ajv : undefined)); + if (errors.length > 0) { + // TODO this can be improved later + return handleError.apply(void 0, __spreadArray([debug, "The document does not match OpenAttestation schema " + (version === "3.0" ? "3.0" : "2.0")], errors.map(function (error) { return (error.instancePath || "document") + " - " + error.message; }))); + } + if (version === "3.0") { + return diagnoseV3({ mode: mode, debug: debug, document: document, kind: kind }); + } + else { + return diagnoseV2({ mode: mode, debug: debug, document: document, kind: kind }); + } +}; +exports.diagnose = diagnose; +var diagnoseV2 = function (_a) { + var kind = _a.kind, document = _a.document, debug = _a.debug, mode = _a.mode; + try { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + mode === "strict" ? types_2.SignatureStrict.check(document.signature) : types_2.Signature.check(document.signature); + } + catch (e) { + return handleError(debug, e.message); + } + if (kind === "signed") { + if (!document.proof || !(document.proof.length > 0)) { + return handleError(debug, "The document does not have a proof"); + } + try { + types_2.ArrayProof.check(document.proof); + } + catch (e) { + return handleError(debug, e.message); + } + } + return []; +}; +var diagnoseV3 = function (_a) { + var kind = _a.kind, document = _a.document, debug = _a.debug, mode = _a.mode; + if (document.version !== __1.SchemaId.v3) { + return handleError(debug, "The document schema version is wrong. Expected " + __1.SchemaId.v3 + ", received " + document.version); + } + try { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + mode === "strict" + ? types_1.VerifiableCredentialWrappedProofStrict.check(document.proof) + : types_1.VerifiableCredentialWrappedProof.check(document.proof); + } + catch (e) { + return handleError(debug, e.message); + } + if (kind === "signed") { + if (!document.proof) { + return handleError(debug, "The document does not have a proof"); + } + try { + types_1.VerifiableCredentialSignedProof.check(document.proof); + } + catch (e) { + return handleError(debug, e.message); + } + } + return []; +}; diff --git a/dist/cjs/shared/utils/guard.js b/dist/cjs/shared/utils/guard.js new file mode 100644 index 00000000..87ba1db7 --- /dev/null +++ b/dist/cjs/shared/utils/guard.js @@ -0,0 +1,44 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.isSignedWrappedV3Document = exports.isSignedWrappedV2Document = exports.isWrappedV2Document = exports.isWrappedV3Document = void 0; +var diagnose_1 = require("./diagnose"); +/** + * + * @param document + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + */ +var isWrappedV3Document = function (document, _a) { + var _b = _a === void 0 ? { mode: "non-strict" } : _a, mode = _b.mode; + return diagnose_1.diagnose({ version: "3.0", kind: "wrapped", document: document, debug: false, mode: mode }).length === 0; +}; +exports.isWrappedV3Document = isWrappedV3Document; +/** + * + * @param document + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + */ +var isWrappedV2Document = function (document, _a) { + var _b = _a === void 0 ? { mode: "non-strict" } : _a, mode = _b.mode; + return diagnose_1.diagnose({ version: "2.0", kind: "wrapped", document: document, debug: false, mode: mode }).length === 0; +}; +exports.isWrappedV2Document = isWrappedV2Document; +/** + * + * @param document + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + */ +var isSignedWrappedV2Document = function (document, _a) { + var _b = _a === void 0 ? { mode: "non-strict" } : _a, mode = _b.mode; + return diagnose_1.diagnose({ version: "2.0", kind: "signed", document: document, debug: false, mode: mode }).length === 0; +}; +exports.isSignedWrappedV2Document = isSignedWrappedV2Document; +/** + * + * @param document + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + */ +var isSignedWrappedV3Document = function (document, _a) { + var _b = _a === void 0 ? { mode: "non-strict" } : _a, mode = _b.mode; + return diagnose_1.diagnose({ version: "3.0", kind: "signed", document: document, debug: false, mode: mode }).length === 0; +}; +exports.isSignedWrappedV3Document = isSignedWrappedV3Document; diff --git a/dist/cjs/shared/utils/index.js b/dist/cjs/shared/utils/index.js new file mode 100644 index 00000000..95fa2cf3 --- /dev/null +++ b/dist/cjs/shared/utils/index.js @@ -0,0 +1,15 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +__exportStar(require("./utils"), exports); +__exportStar(require("./guard"), exports); +__exportStar(require("./diagnose"), exports); diff --git a/dist/cjs/shared/utils/utils.js b/dist/cjs/shared/utils/utils.js new file mode 100644 index 00000000..012149b0 --- /dev/null +++ b/dist/cjs/shared/utils/utils.js @@ -0,0 +1,175 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __spreadArray = (this && this.__spreadArray) || function (to, from) { + for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) + to[j] = from[i]; + return to; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getObfuscatedData = exports.isObfuscated = exports.keccak256 = exports.isSchemaValidationError = exports.SchemaValidationError = exports.getAssetId = exports.isTransferableAsset = exports.getTargetHash = exports.getMerkleRoot = exports.getIssuerAddress = exports.combineHashString = exports.combineHashBuffers = exports.hashArray = exports.toBuffer = exports.hashToBuffer = exports.bufSortJoin = exports.getData = void 0; +var js_sha3_1 = require("js-sha3"); +var salt_1 = require("../../2.0/salt"); +var guard_1 = require("./guard"); +var getData = function (document) { + return salt_1.unsaltData(document.data); +}; +exports.getData = getData; +/** + * Sorts the given Buffers lexicographically and then concatenates them to form one continuous Buffer + */ +function bufSortJoin() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + return Buffer.concat(__spreadArray([], args).sort(Buffer.compare)); +} +exports.bufSortJoin = bufSortJoin; +// If hash is not a buffer, convert it to buffer (without hashing it) +function hashToBuffer(hash) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore https://github.com/Microsoft/TypeScript/issues/23155 + return Buffer.isBuffer(hash) && hash.length === 32 ? hash : Buffer.from(hash, "hex"); +} +exports.hashToBuffer = hashToBuffer; +// If element is not a buffer, stringify it and then hash it to be a buffer +function toBuffer(element) { + return Buffer.isBuffer(element) && element.length === 32 ? element : hashToBuffer(js_sha3_1.keccak256(JSON.stringify(element))); +} +exports.toBuffer = toBuffer; +/** + * Turns array of data into sorted array of hashes + */ +function hashArray(arr) { + return arr.map(function (i) { return toBuffer(i); }).sort(Buffer.compare); +} +exports.hashArray = hashArray; +/** + * Returns the keccak hash of two buffers after concatenating them and sorting them + * If either hash is not given, the input is returned + */ +function combineHashBuffers(first, second) { + if (!second) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return first; // it should always be valued if second is not + } + if (!first) { + return second; + } + return hashToBuffer(js_sha3_1.keccak256(bufSortJoin(first, second))); +} +exports.combineHashBuffers = combineHashBuffers; +/** + * Returns the keccak hash of two string after concatenating them and sorting them + * If either hash is not given, the input is returned + * @param first A string to be hashed (without 0x) + * @param second A string to be hashed (without 0x) + * @returns Resulting string after the hash is combined (without 0x) + */ +function combineHashString(first, second) { + return first && second + ? combineHashBuffers(hashToBuffer(first), hashToBuffer(second)).toString("hex") + : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + (first || second); // this should always return a value right ? :) +} +exports.combineHashString = combineHashString; +function getIssuerAddress(document) { + if (guard_1.isWrappedV2Document(document)) { + var data = exports.getData(document); + return data.issuers.map(function (issuer) { return issuer.certificateStore || issuer.documentStore || issuer.tokenRegistry; }); + } + else if (guard_1.isWrappedV3Document(document)) { + return document.openAttestationMetadata.proof.value; + } + throw new Error("Unsupported document type: Only can retrieve issuer address from wrapped OpenAttestation v2 & v3 documents."); +} +exports.getIssuerAddress = getIssuerAddress; +var getMerkleRoot = function (document) { + switch (true) { + case guard_1.isWrappedV2Document(document): + return document.signature.merkleRoot; + case guard_1.isWrappedV3Document(document): + return document.proof.merkleRoot; + default: + throw new Error("Unsupported document type: Only can retrieve merkle root from wrapped OpenAttestation v2 & v3 documents."); + } +}; +exports.getMerkleRoot = getMerkleRoot; +var getTargetHash = function (document) { + switch (true) { + case guard_1.isWrappedV2Document(document): + return document.signature.targetHash; + case guard_1.isWrappedV3Document(document): + return document.proof.targetHash; + default: + throw new Error("Unsupported document type: Only can retrieve target hash from wrapped OpenAttestation v2 & v3 documents."); + } +}; +exports.getTargetHash = getTargetHash; +var isTransferableAsset = function (document) { + var _a, _b, _c, _d; + return (!!((_b = (_a = exports.getData(document)) === null || _a === void 0 ? void 0 : _a.issuers[0]) === null || _b === void 0 ? void 0 : _b.tokenRegistry) || + ((_d = (_c = document === null || document === void 0 ? void 0 : document.openAttestationMetadata) === null || _c === void 0 ? void 0 : _c.proof) === null || _d === void 0 ? void 0 : _d.method) === "TOKEN_REGISTRY"); +}; +exports.isTransferableAsset = isTransferableAsset; +var getAssetId = function (document) { + if (exports.isTransferableAsset(document)) { + return exports.getTargetHash(document); + } + throw new Error("Unsupported document type: Only can retrieve asset id from wrapped OpenAttestation v2 & v3 transferable documents."); +}; +exports.getAssetId = getAssetId; +var SchemaValidationError = /** @class */ (function (_super) { + __extends(SchemaValidationError, _super); + function SchemaValidationError(message, validationErrors, document) { + var _this = _super.call(this, message) || this; + _this.validationErrors = validationErrors; + _this.document = document; + return _this; + } + return SchemaValidationError; +}(Error)); +exports.SchemaValidationError = SchemaValidationError; +var isSchemaValidationError = function (error) { + return !!error.validationErrors; +}; +exports.isSchemaValidationError = isSchemaValidationError; +// make it available for consumers +var js_sha3_2 = require("js-sha3"); +Object.defineProperty(exports, "keccak256", { enumerable: true, get: function () { return js_sha3_2.keccak256; } }); +var isObfuscated = function (document) { + var _a, _b, _c, _d; + if (guard_1.isWrappedV3Document(document)) { + return !!((_b = (_a = document.proof.privacy) === null || _a === void 0 ? void 0 : _a.obfuscated) === null || _b === void 0 ? void 0 : _b.length); + } + if (guard_1.isWrappedV2Document(document)) { + return !!((_d = (_c = document.privacy) === null || _c === void 0 ? void 0 : _c.obfuscatedData) === null || _d === void 0 ? void 0 : _d.length); + } + throw new Error("Unsupported document type: Can only check if there are obfuscated data from wrapped OpenAttestation v2 & v3 documents."); +}; +exports.isObfuscated = isObfuscated; +var getObfuscatedData = function (document) { + var _a, _b; + if (guard_1.isWrappedV3Document(document)) { + return (_a = document.proof.privacy) === null || _a === void 0 ? void 0 : _a.obfuscated; + } + if (guard_1.isWrappedV2Document(document)) { + return ((_b = document.privacy) === null || _b === void 0 ? void 0 : _b.obfuscatedData) || []; + } + throw new Error("Unsupported document type: Can only retrieve obfuscated data from wrapped OpenAttestation v2 & v3 documents."); +}; +exports.getObfuscatedData = getObfuscatedData; diff --git a/dist/cjs/shared/validate/index.js b/dist/cjs/shared/validate/index.js new file mode 100644 index 00000000..6edc8502 --- /dev/null +++ b/dist/cjs/shared/validate/index.js @@ -0,0 +1,13 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +__exportStar(require("./validate"), exports); diff --git a/dist/cjs/shared/validate/validate.js b/dist/cjs/shared/validate/validate.js new file mode 100644 index 00000000..f459341b --- /dev/null +++ b/dist/cjs/shared/validate/validate.js @@ -0,0 +1,21 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.validateSchema = void 0; +var logger_1 = require("../logger"); +var document_1 = require("../@types/document"); +var utils_1 = require("../utils"); +var logger = logger_1.getLogger("validate"); +var validateSchema = function (document, validator) { + var _a; + if (!validator) { + throw new Error("No schema validator provided"); + } + var valid = validator(document.version === document_1.SchemaId.v3 ? document : utils_1.getData(document)); + if (!valid) { + logger.debug("There are errors in the document: " + JSON.stringify(validator.errors)); + return (_a = validator.errors) !== null && _a !== void 0 ? _a : []; + } + logger.debug("Document is a valid open attestation document v" + document.version); + return []; +}; +exports.validateSchema = validateSchema; diff --git a/dist/esm/2.0/digest.js b/dist/esm/2.0/digest.js new file mode 100644 index 00000000..3acd3759 --- /dev/null +++ b/dist/esm/2.0/digest.js @@ -0,0 +1,24 @@ +import { get, omitBy, sortBy } from "lodash"; +import { keccak256 } from "js-sha3"; +import { flatten } from "../shared/serialize/flatten"; +var isKeyOrValueUndefined = function (value, key) { return value === undefined || key === undefined; }; +export var flattenHashArray = function (data) { + var flattenedData = omitBy(flatten(data), isKeyOrValueUndefined); + return Object.keys(flattenedData).map(function (k) { + var obj = {}; + obj[k] = flattenedData[k]; + return keccak256(JSON.stringify(obj)); + }); +}; +export var digestDocument = function (document) { + // Prepare array of hashes from filtered data + var hashedDataArray = get(document, "privacy.obfuscatedData", []); + // Prepare array of hashes from visible data + var unhashedData = get(document, "data"); + var hashedUnhashedDataArray = flattenHashArray(unhashedData); + // Combine both array and sort them to ensure determinism + var combinedHashes = hashedDataArray.concat(hashedUnhashedDataArray); + var sortedHashes = sortBy(combinedHashes); + // Finally, return the digest of the entire set of data + return keccak256(JSON.stringify(sortedHashes)); +}; diff --git a/dist/esm/2.0/obfuscate.js b/dist/esm/2.0/obfuscate.js new file mode 100644 index 00000000..bd73bf1a --- /dev/null +++ b/dist/esm/2.0/obfuscate.js @@ -0,0 +1,42 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +import { cloneDeep, pick, unset } from "lodash"; +import { flatten } from "../shared/serialize/flatten"; +import { toBuffer } from "../shared/utils"; +export var obfuscateData = function (_data, fields) { + var data = cloneDeep(_data); // Prevents alteration of original data + var fieldsToRemove = Array.isArray(fields) ? fields : [fields]; + // Obfuscate data by hashing them with the key + var dataToObfuscate = flatten(pick(data, fieldsToRemove)); + var obfuscatedData = Object.keys(dataToObfuscate).map(function (k) { + var obj = {}; + obj[k] = dataToObfuscate[k]; + return toBuffer(obj).toString("hex"); + }); + // Return remaining data + fieldsToRemove.forEach(function (path) { + unset(data, path); + }); + return { + data: data, + obfuscatedData: obfuscatedData, + }; +}; +// TODO the return type could be improve by using Exclude eventually to remove the obfuscated properties +export var obfuscateDocument = function (document, fields) { + var _a, _b; + var existingData = document.data; + var _c = obfuscateData(existingData, fields), data = _c.data, obfuscatedData = _c.obfuscatedData; + var currentObfuscatedData = (_b = (_a = document === null || document === void 0 ? void 0 : document.privacy) === null || _a === void 0 ? void 0 : _a.obfuscatedData) !== null && _b !== void 0 ? _b : []; + var newObfuscatedData = currentObfuscatedData.concat(obfuscatedData); + return __assign(__assign({}, document), { data: data, privacy: __assign(__assign({}, document.privacy), { obfuscatedData: newObfuscatedData }) }); +}; diff --git a/dist/esm/2.0/salt.js b/dist/esm/2.0/salt.js new file mode 100644 index 00000000..00fddcf7 --- /dev/null +++ b/dist/esm/2.0/salt.js @@ -0,0 +1,100 @@ +import { includes, mapValues, map, identity } from "lodash"; +import isUUID from "validator/lib/isUUID"; +import { v4 as uuid } from "uuid"; +var UUIDV4_LENGTH = 37; +var PRIMITIVE_TYPES = ["string", "number", "boolean", "undefined"]; +/* eslint-disable no-use-before-define */ +/** + * Curried function that takes (iteratee)(value), + * if value is a collection then recurse into it + * otherwise apply `iteratee` on the primitive value + */ +var recursivelyApply = function (iteratee) { return function (value) { + if (includes(PRIMITIVE_TYPES, typeof value) || value === null) { + return iteratee(value); + } + return deepMap(value, iteratee); // eslint-disable-line @typescript-eslint/no-use-before-define +}; }; +/** + * Applies `iteratee` to all fields in objects, goes into arrays as well. + * Refer to test for example + */ +export var deepMap = function (collection, iteratee) { + if (iteratee === void 0) { iteratee = identity; } + if (collection instanceof Array) { + return map(collection, recursivelyApply(iteratee)); + } + if (typeof collection === "object") { + return mapValues(collection, recursivelyApply(iteratee)); + } + return collection; +}; +/* eslint-enable no-use-before-define */ +// disabling this because of mutual recursion +var startsWithUuidV4 = function (input) { + if (input && typeof input === "string") { + var elements = input.split(":"); + return isUUID(elements[0], 4); + } + return false; +}; +/** + * Detects the type of a value and returns a string with type annotation + */ +export function primitiveToTypedString(value) { + switch (typeof value) { + case "number": + case "string": + case "boolean": + case "undefined": + return typeof value + ":" + String(value); + default: + if (value === null) { + // typeof null is 'object' so we have to check for it + return "null:null"; + } + throw new Error("Parsing error, value is not of primitive type: " + value); + } +} +/** + * Returns an appropriately typed value given a string with type annotations, e.g: "number:5" + */ +export function typedStringToPrimitive(input) { + var _a = input.split(":"), type = _a[0], valueArray = _a.slice(1); + var value = valueArray.join(":"); // just in case there are colons in the value + switch (type) { + case "number": + return Number(value); + case "string": + return String(value); + case "boolean": + return value === "true"; + case "null": + return null; + case "undefined": + return undefined; + default: + throw new Error("Parsing error, type annotation not found in string: " + input); + } +} +/** + * Returns a salted value using a randomly generated uuidv4 string for salt + */ +export function uuidSalt(value) { + var salt = uuid(); + return salt + ":" + primitiveToTypedString(value); +} +/** + * Value salted string in the format "salt:type:value", example: "ee7f3323-1634-4dea-8c12-f0bb83aff874:number:5" + * Returns an appropriately typed value when given a salted string with type annotation + */ +export function unsalt(value) { + if (startsWithUuidV4(value)) { + var untypedValue = value.substring(UUIDV4_LENGTH).trim(); + return typedStringToPrimitive(untypedValue); + } + return value; +} +// Use uuid salting method to recursively salt data +export var saltData = function (data) { return deepMap(data, uuidSalt); }; +export var unsaltData = function (data) { return deepMap(data, unsalt); }; diff --git a/dist/esm/2.0/schema/schema.json b/dist/esm/2.0/schema/schema.json new file mode 100644 index 00000000..e7dc8fad --- /dev/null +++ b/dist/esm/2.0/schema/schema.json @@ -0,0 +1,249 @@ +{ + "title": "Open Attestation Schema 2.0", + "$id": "https://schema.openattestation.com/2.0/schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "identityProofDns": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["DNS-TXT"] + }, + "location": { + "type": "string", + "description": "Url of the website referencing to document store" + } + }, + "required": ["type", "location"] + }, + "identityProofDid": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["DID"] + }, + "key": { + "type": "string", + "description": "Public key associated" + } + }, + "required": ["type", "key"] + }, + "identityProofDnsDid": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["DNS-DID"] + }, + "key": { + "type": "string", + "description": "Public key associated" + }, + "location": { + "type": "string", + "description": "Url of the website referencing to document store" + } + }, + "required": ["type", "key", "location"] + }, + "identityProof": { + "type": "object", + "oneOf": [ + { "$ref": "#/definitions/identityProofDns" }, + { "$ref": "#/definitions/identityProofDnsDid" }, + { "$ref": "#/definitions/identityProofDid" } + ] + }, + "issuer": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Issuer's id, DID can be used" + }, + "name": { + "type": "string", + "description": "Issuer's name" + }, + "revocation": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["NONE", "REVOCATION_STORE"] + }, + "location": { + "type": "string", + "description": "Smart contract address or url of certificate revocation list for Revocation Store type revocation" + } + } + }, + "identityProof": { "$ref": "#/definitions/identityProof" } + }, + "required": ["name", "identityProof"], + "additionalProperties": true + }, + "documentStore": { + "allOf": [ + { "$ref": "#/definitions/issuer" }, + { + "type": "object", + "properties": { + "documentStore": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "Smart contract address of document store" + } + }, + "required": ["documentStore"] + } + ] + }, + "certificateStore": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Issuer's name" + }, + "certificateStore": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "deprecationMessage": "Use documentStore and identityProof instead of this", + "description": "Smart contract address of certificate store. Same as documentStore" + } + }, + "required": ["name", "certificateStore"], + "additionalProperties": true + }, + "tokenRegistry": { + "allOf": [ + { "$ref": "#/definitions/issuer" }, + { + "type": "object", + "properties": { + "tokenRegistry": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]{40}$", + "description": "Smart contract address of token registry" + } + }, + "required": ["tokenRegistry"] + } + ] + } + }, + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Internal reference, usually serial number, of this document" + }, + "$template": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Template name to be use by template renderer to determine the template to use" + }, + "type": { + "type": "string", + "description": "Type of renderer template", + "enum": ["EMBEDDED_RENDERER"] + }, + "url": { + "type": "string", + "description": "URL of a decentralised renderer to render this document" + } + }, + "required": ["name", "type"] + } + ] + }, + "documentUrl": { + "type": "string", + "description": "URL of the stored document" + }, + "issuers": { + "type": "array", + "items": { + "type": "object", + "title": "issuer", + "oneOf": [ + { + "$ref": "#/definitions/tokenRegistry" + }, + { + "$ref": "#/definitions/documentStore" + }, + { + "$ref": "#/definitions/certificateStore" + }, + { + "allOf": [ + { "$ref": "#/definitions/issuer" }, + { + "not": { + "anyOf": [ + { + "required": ["certificateStore"] + }, + { + "required": ["tokenRegistry"] + }, + { + "required": ["documentStore"] + } + ] + } + } + ] + } + ] + }, + "minItems": 1 + }, + "recipient": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Recipient's name" + } + }, + "additionalProperties": true + }, + "attachments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "filename": { + "type": "string", + "description": "Name of attachment, with appropriate extensions" + }, + "type": { + "type": "string", + "description": "Type of attachment" + }, + "data": { + "type": "string", + "description": "Base64 encoding of attachment" + } + }, + "required": ["filename", "type", "data"], + "additionalProperties": false + } + } + }, + "required": ["issuers"], + "additionalProperties": true +} diff --git a/dist/esm/2.0/sign.js b/dist/esm/2.0/sign.js new file mode 100644 index 00000000..aeba6cfb --- /dev/null +++ b/dist/esm/2.0/sign.js @@ -0,0 +1,87 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __spreadArray = (this && this.__spreadArray) || function (to, from) { + for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) + to[j] = from[i]; + return to; +}; +import { sign } from "../shared/signer"; +import { isSignedWrappedV2Document } from "../shared/utils"; +import { SigningKey } from "../shared/@types/sign"; +export var signDocument = function (document, algorithm, keyOrSigner) { return __awaiter(void 0, void 0, void 0, function () { + var merkleRoot, signature, proof, _a, _b; + var _c; + return __generator(this, function (_d) { + switch (_d.label) { + case 0: + merkleRoot = "0x" + document.signature.merkleRoot; + return [4 /*yield*/, sign(algorithm, merkleRoot, keyOrSigner)]; + case 1: + signature = _d.sent(); + _c = { + type: "OpenAttestationSignature2018", + created: new Date().toISOString(), + proofPurpose: "assertionMethod" + }; + if (!SigningKey.guard(keyOrSigner)) return [3 /*break*/, 2]; + _a = keyOrSigner.public; + return [3 /*break*/, 4]; + case 2: + _b = "did:ethr:"; + return [4 /*yield*/, keyOrSigner.getAddress()]; + case 3: + _a = _b + (_d.sent()) + "#controller"; + _d.label = 4; + case 4: + proof = (_c.verificationMethod = _a, + _c.signature = signature, + _c); + return [2 /*return*/, __assign(__assign({}, document), { proof: isSignedWrappedV2Document(document) ? __spreadArray(__spreadArray([], document.proof), [proof]) : [proof] })]; + } + }); +}); }; diff --git a/dist/esm/2.0/types.js b/dist/esm/2.0/types.js new file mode 100644 index 00000000..ecbae931 --- /dev/null +++ b/dist/esm/2.0/types.js @@ -0,0 +1,25 @@ +import { OpenAttestationHexString, ProofPurpose, ProofType } from "../shared/@types/document"; +import { Array as RunTypesArray, Literal, Partial, Record as RunTypesRecord, String } from "runtypes"; +export var ObfuscationMetadata = Partial({ + obfuscatedData: RunTypesArray(OpenAttestationHexString), +}); +export var Proof = RunTypesRecord({ + type: ProofType, + created: String, + proofPurpose: ProofPurpose, + verificationMethod: String, + signature: String, +}); +export var ArrayProof = RunTypesArray(Proof); +export var Signature = RunTypesRecord({ + type: Literal("SHA3MerkleProof"), + targetHash: String, + merkleRoot: String, + proof: RunTypesArray(String), +}); +export var SignatureStrict = Signature.And(RunTypesRecord({ + targetHash: OpenAttestationHexString, + merkleRoot: OpenAttestationHexString, + proof: RunTypesArray(OpenAttestationHexString), +})); +export * from "../__generated__/schema.2.0"; diff --git a/dist/esm/2.0/verify.js b/dist/esm/2.0/verify.js new file mode 100644 index 00000000..41da7aa3 --- /dev/null +++ b/dist/esm/2.0/verify.js @@ -0,0 +1,17 @@ +import { get } from "lodash"; +import { digestDocument } from "./digest"; +import { checkProof } from "../shared/merkle"; +export var verify = function (document) { + var _a, _b, _c, _d; + var signature = get(document, "signature"); + if (!signature) { + return false; + } + // Checks target hash + var digest = digestDocument(document); + var targetHash = get(document, "signature.targetHash"); + if (digest !== targetHash) + return false; + // Calculates merkle root from target hash and proof, then compare to merkle root in document + return checkProof((_b = (_a = document === null || document === void 0 ? void 0 : document.signature) === null || _a === void 0 ? void 0 : _a.proof) !== null && _b !== void 0 ? _b : [], (_c = document === null || document === void 0 ? void 0 : document.signature) === null || _c === void 0 ? void 0 : _c.merkleRoot, (_d = document === null || document === void 0 ? void 0 : document.signature) === null || _d === void 0 ? void 0 : _d.targetHash); +}; diff --git a/dist/esm/2.0/wrap.js b/dist/esm/2.0/wrap.js new file mode 100644 index 00000000..649a8942 --- /dev/null +++ b/dist/esm/2.0/wrap.js @@ -0,0 +1,58 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +import { digestDocument } from "./digest"; +import { MerkleTree } from "../shared/merkle"; +import { hashToBuffer, SchemaValidationError } from "../shared/utils"; +import { SchemaId } from "../shared/@types/document"; +import { validateSchema as validate } from "../shared/validate"; +import { saltData } from "./salt"; +import { getSchema } from "../shared/ajv"; +var createDocument = function (data, option) { + var documentSchema = { + version: SchemaId.v2, + data: saltData(data), + }; + if (option === null || option === void 0 ? void 0 : option.externalSchemaId) { + documentSchema.schema = option.externalSchemaId; + } + return documentSchema; +}; +export var wrapDocument = function (data, options) { + var _a; + var document = createDocument(data, options); + var errors = validate(document, getSchema((_a = options === null || options === void 0 ? void 0 : options.version) !== null && _a !== void 0 ? _a : SchemaId.v2)); + if (errors.length > 0) { + throw new SchemaValidationError("Invalid document", errors, document); + } + var digest = digestDocument(document); + var signature = { + type: "SHA3MerkleProof", + targetHash: digest, + proof: [], + merkleRoot: digest, + }; + return __assign(__assign({}, document), { signature: signature }); +}; +export var wrapDocuments = function (data, options) { + // wrap documents individually + var documents = data.map(function (d) { return wrapDocument(d, options); }); + // get all the target hashes to compute the merkle tree and the merkle root + var merkleTree = new MerkleTree(documents.map(function (document) { return document.signature.targetHash; }).map(hashToBuffer)); + var merkleRoot = merkleTree.getRoot().toString("hex"); + // for each document, update the merkle root and add the proofs needed + return documents.map(function (document) { + var merkleProof = merkleTree + .getProof(hashToBuffer(document.signature.targetHash)) + .map(function (buffer) { return buffer.toString("hex"); }); + return __assign(__assign({}, document), { signature: __assign(__assign({}, document.signature), { proof: merkleProof, merkleRoot: merkleRoot }) }); + }); +}; diff --git a/dist/esm/3.0/digest.js b/dist/esm/3.0/digest.js new file mode 100644 index 00000000..129f2d5f --- /dev/null +++ b/dist/esm/3.0/digest.js @@ -0,0 +1,16 @@ +import { get, sortBy } from "lodash"; +import { keccak256 } from "js-sha3"; +export var digestCredential = function (document, salts, obfuscatedData) { + // Prepare array of hashes from visible data + var hashedUnhashedDataArray = salts + .filter(function (salt) { return get(document, salt.path); }) + .map(function (salt) { + var _a; + return keccak256(JSON.stringify((_a = {}, _a[salt.path] = salt.value + ":" + get(document, salt.path), _a))); + }); + // Combine both array and sort them to ensure determinism + var combinedHashes = obfuscatedData.concat(hashedUnhashedDataArray); + var sortedHashes = sortBy(combinedHashes); + // Finally, return the digest of the entire set of data + return keccak256(JSON.stringify(sortedHashes)); +}; diff --git a/dist/esm/3.0/obfuscate.js b/dist/esm/3.0/obfuscate.js new file mode 100644 index 00000000..80ef2d36 --- /dev/null +++ b/dist/esm/3.0/obfuscate.js @@ -0,0 +1,51 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +import { toBuffer } from "../shared/utils"; +import { cloneDeep, get, unset, pick } from "lodash"; +import { decodeSalt, encodeSalt } from "./salt"; +import { traverseAndFlatten } from "./traverseAndFlatten"; +var obfuscate = function (_data, fields) { + var data = cloneDeep(_data); // Prevents alteration of original data + var fieldsAsArray = [].concat(fields); + // fields to remove will contain the list of each expanded keys from the fields passed in parameter, it's for instance useful in case of + // object obfuscation, where the object itself is not part of the salts, but each individual keys are + var fieldsToRemove = traverseAndFlatten(pick(data, fieldsAsArray), { + iteratee: function (_a) { + var path = _a.path; + return path; + }, + }); + var salts = decodeSalt(data.proof.salts); + // Obfuscate data by hashing them with the key + var obfuscatedData = fieldsToRemove.map(function (field) { + var _a; + var value = get(data, field); + var salt = salts.find(function (s) { return s.path === field; }); + if (!salt) { + throw new Error("Salt not found for " + field); + } + return toBuffer((_a = {}, _a[salt.path] = salt.value + ":" + value, _a)).toString("hex"); + }); + // remove fields from the object + fieldsAsArray.forEach(function (field) { return unset(data, field); }); + data.proof.salts = encodeSalt(salts.filter(function (s) { return !fieldsToRemove.includes(s.path); })); + return { + data: data, + obfuscatedData: obfuscatedData, + }; +}; +export var obfuscateVerifiableCredential = function (document, fields) { + var _a = obfuscate(document, fields), data = _a.data, obfuscatedData = _a.obfuscatedData; + var currentObfuscatedData = document.proof.privacy.obfuscated; + var newObfuscatedData = currentObfuscatedData.concat(obfuscatedData); + return __assign(__assign({}, data), { proof: __assign(__assign({}, data.proof), { privacy: __assign(__assign({}, data.proof.privacy), { obfuscated: newObfuscatedData }) }) }); +}; diff --git a/dist/esm/3.0/salt.js b/dist/esm/3.0/salt.js new file mode 100644 index 00000000..fddef944 --- /dev/null +++ b/dist/esm/3.0/salt.js @@ -0,0 +1,38 @@ +import { randomBytes } from "crypto"; +import { Base64 } from "js-base64"; +import { traverseAndFlatten } from "./traverseAndFlatten"; +var ENTROPY_IN_BYTES = 32; +var illegalCharactersCheck = function (data) { + Object.entries(data).forEach(function (_a) { + var key = _a[0], value = _a[1]; + if (key.includes(".")) { + throw new Error("Key names must not have . in them"); + } + if (key.includes("[") || key.includes("]")) { + throw new Error("Key names must not have '[' or ']' in them"); + } + if (value && typeof value === "object") { + return illegalCharactersCheck(value); // Recursively search if property contains sub-properties + } + }); +}; +// Using 32 bytes of entropy as compared to 16 bytes in uuid +// Using hex encoding as compared to base64 for constant string length +export var secureRandomString = function () { return randomBytes(ENTROPY_IN_BYTES).toString("hex"); }; +export var salt = function (data) { + // Check for illegal characters e.g. '.', '[' or ']' + illegalCharactersCheck(data); + return traverseAndFlatten(data, { iteratee: function (_a) { + var path = _a.path; + return ({ value: secureRandomString(), path: path }); + } }); +}; +export var encodeSalt = function (salts) { return Base64.encode(JSON.stringify(salts)); }; +export var decodeSalt = function (salts) { + var decoded = JSON.parse(Base64.decode(salts)); + decoded.forEach(function (salt) { + if (salt.value.length !== ENTROPY_IN_BYTES * 2) + throw new Error("Salt must be " + ENTROPY_IN_BYTES + " bytes"); + }); + return decoded; +}; diff --git a/dist/esm/3.0/schema/schema.json b/dist/esm/3.0/schema/schema.json new file mode 100644 index 00000000..39ded66d --- /dev/null +++ b/dist/esm/3.0/schema/schema.json @@ -0,0 +1,230 @@ +{ + "title": "Open Attestation Schema 3.0", + "$id": "https://schema.openattestation.com/3.0/schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "definitions": { + "type": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "issuer": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uri", + "description": "URI when dereferenced, results in a document containing machine-readable information about the issuer that can be used to verify the information expressed in the credential. More information in https://www.w3.org/TR/vc-data-model/#issuer" + }, + "name": { + "type": "string", + "description": "Issuer's name" + } + }, + "required": ["id", "name"], + "additionalProperties": true + } + }, + "properties": { + "@context": { + "type": "array", + "items": { + "type": ["string", "object"], + "format": "uri" + }, + "description": "List of URI to determine the terminology used in the verifiable credential as explained by https://www.w3.org/TR/vc-data-model/#contexts" + }, + "id": { + "type": "string", + "format": "uri", + "description": "URI to the subject of the credential as explained by https://www.w3.org/TR/vc-data-model/#credential-subject" + }, + "type": { + "$ref": "#/definitions/type", + "description": "Specific verifiable credential type as explained by https://www.w3.org/TR/vc-data-model/#types" + }, + "reference": { + "type": "string", + "description": "Internal reference, usually a serial number, of this document" + }, + "name": { + "type": "string", + "description": "Human readable name of this credential" + }, + "issuanceDate": { + "type": "string", + "format": "date-time", + "description": "The date and time when this credential becomes valid (may be deprecated in favor of issued/validFrom a future version of W3C's VC Data Model)" + }, + "expirationDate": { + "type": "string", + "format": "date-time", + "description": "The date and time when this credential expires" + }, + "issued": { + "type": "string", + "format": "date-time", + "description": "The date and time when this credential becomes valid" + }, + "validFrom": { + "type": "string", + "format": "date-time", + "description": "The date and time when this credential becomes valid" + }, + "validUntil": { + "type": "string", + "format": "date-time", + "description": "The date and time when this credential expires" + }, + "credentialSubject": { + "oneOf": [ + { + "type": "object" + }, + { + "type": "array", + "items": { + "type": "object" + } + } + ] + }, + "credentialStatus": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uri", + "examples": ["https://example.edu/status/24"] + }, + "type": { + "type": "string", + "examples": ["CredentialStatusList2017"], + "description": "Express the credential status type (also referred to as the credential status method). It is expected that the value will provide enough information to determine the current status of the credential. For example, the object could contain a link to an external document noting whether or not the credential is suspended or revoked." + } + }, + "required": ["id", "type"] + }, + "issuer": { + "oneOf": [ + { + "$ref": "#/definitions/issuer" + }, + { + "type": "string" + } + ] + }, + "openAttestationMetadata": { + "type": "object", + "properties": { + "template": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Template name to be use by template renderer to determine the template to use" + }, + "type": { + "type": "string", + "description": "Type of renderer template", + "enum": ["EMBEDDED_RENDERER"] + }, + "url": { + "type": "string", + "description": "URL of a decentralised renderer to render this document", + "pattern": "^(https?)://" + } + }, + "required": ["name", "type", "url"], + "additionalProperties": false + }, + "proof": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Proof method name as explained by https://www.w3.org/TR/vc-data-model/#types", + "enum": ["OpenAttestationProofMethod"] + }, + "method": { + "type": "string", + "description": "Proof Open Attestation method", + "enum": ["TOKEN_REGISTRY", "DOCUMENT_STORE", "DID"] + }, + "value": { + "description": "Proof value of issuer(s). Smart contract address for TOKEN_REGISTRY & DOCUMENT_STORE, did for DID method", + "type": "string" + }, + "revocation": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Revocation method (if required by proof method)", + "enum": ["NONE", "REVOCATION_STORE"] + }, + "location": { + "type": "string", + "description": "Smart contract address or url of certificate revocation list for Revocation Store type revocation" + } + }, + "required": ["type"] + } + }, + "required": ["type", "method", "value"], + "additionalProperties": true + }, + "identityProof": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["DNS-TXT", "DNS-DID", "DID"] + }, + "identifier": { + "type": "string", + "description": "Identifier to be shown to end user upon verifying the identity" + } + }, + "additionalProperties": false, + "required": ["type", "identifier"] + } + }, + "required": ["proof", "identityProof"] + }, + "attachments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "fileName": { + "type": "string", + "description": "Name of this attachment, with appropriate extensions" + }, + "mimeType": { + "type": "string", + "description": "Media type (or MIME type) of this attachment" + }, + "data": { + "type": "string", + "description": "Base64 encoding of this attachment" + } + }, + "required": ["fileName", "mimeType", "data"], + "additionalProperties": false + } + } + }, + "required": ["@context", "type", "credentialSubject", "issuer", "issuanceDate", "openAttestationMetadata"], + "additionalProperties": true +} diff --git a/dist/esm/3.0/sign.js b/dist/esm/3.0/sign.js new file mode 100644 index 00000000..94ff585f --- /dev/null +++ b/dist/esm/3.0/sign.js @@ -0,0 +1,79 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +import { sign } from "../shared/signer"; +import { SigningKey } from "../shared/@types/sign"; +import { isSignedWrappedV3Document } from "../shared/utils"; +export var signDocument = function (document, algorithm, keyOrSigner) { return __awaiter(void 0, void 0, void 0, function () { + var merkleRoot, signature, proof, _a, _b, _c; + var _d; + return __generator(this, function (_e) { + switch (_e.label) { + case 0: + if (isSignedWrappedV3Document(document)) + throw new Error("Document has been signed"); + merkleRoot = "0x" + document.proof.merkleRoot; + return [4 /*yield*/, sign(algorithm, merkleRoot, keyOrSigner)]; + case 1: + signature = _e.sent(); + _a = [__assign({}, document.proof)]; + _d = {}; + if (!SigningKey.guard(keyOrSigner)) return [3 /*break*/, 2]; + _b = keyOrSigner.public; + return [3 /*break*/, 4]; + case 2: + _c = "did:ethr:"; + return [4 /*yield*/, keyOrSigner.getAddress()]; + case 3: + _b = _c + (_e.sent()) + "#controller"; + _e.label = 4; + case 4: + proof = __assign.apply(void 0, _a.concat([(_d.key = _b, _d.signature = signature, _d)])); + return [2 /*return*/, __assign(__assign({}, document), { proof: proof })]; + } + }); +}); }; diff --git a/dist/esm/3.0/traverseAndFlatten.js b/dist/esm/3.0/traverseAndFlatten.js new file mode 100644 index 00000000..646219d7 --- /dev/null +++ b/dist/esm/3.0/traverseAndFlatten.js @@ -0,0 +1,16 @@ +export function traverseAndFlatten(data, _a) { + var iteratee = _a.iteratee, _b = _a.path, path = _b === void 0 ? "" : _b; + if (Array.isArray(data)) { + return data.flatMap(function (v, index) { return traverseAndFlatten(v, { iteratee: iteratee, path: path + "[" + index + "]" }); }); + } + // Since null datas are allowed but typeof null === "object", the "&& data" is used to skip this + if (typeof data === "object" && data) { + return Object.keys(data).flatMap(function (key) { + return traverseAndFlatten(data[key], { iteratee: iteratee, path: path ? path + "." + key : key }); + }); + } + if (typeof data === "string" || typeof data === "number" || typeof data === "boolean" || data === null) { + return iteratee({ value: data, path: path }); + } + throw new Error("Unexpected data '" + data + "' in '" + path + "'"); +} diff --git a/dist/esm/3.0/types.js b/dist/esm/3.0/types.js new file mode 100644 index 00000000..481a3aa1 --- /dev/null +++ b/dist/esm/3.0/types.js @@ -0,0 +1,24 @@ +import { OpenAttestationHexString, ProofPurpose, SignatureAlgorithm } from "../shared/@types/document"; +import { Array as RunTypesArray, Record as RunTypesRecord, String } from "runtypes"; +export var ObfuscationMetadata = RunTypesRecord({ + obfuscated: RunTypesArray(OpenAttestationHexString), +}); +export var VerifiableCredentialWrappedProof = RunTypesRecord({ + type: SignatureAlgorithm, + targetHash: String, + merkleRoot: String, + proofs: RunTypesArray(String), + salts: String, + privacy: ObfuscationMetadata, + proofPurpose: ProofPurpose, +}); +export var VerifiableCredentialWrappedProofStrict = VerifiableCredentialWrappedProof.And(RunTypesRecord({ + targetHash: OpenAttestationHexString, + merkleRoot: OpenAttestationHexString, + proofs: RunTypesArray(OpenAttestationHexString), +})); +export var VerifiableCredentialSignedProof = VerifiableCredentialWrappedProof.And(RunTypesRecord({ + key: String, + signature: String, +})); +export * from "../__generated__/schema.3.0"; diff --git a/dist/esm/3.0/validate/__mocks__/validate.js b/dist/esm/3.0/validate/__mocks__/validate.js new file mode 100644 index 00000000..7010c891 --- /dev/null +++ b/dist/esm/3.0/validate/__mocks__/validate.js @@ -0,0 +1,41 @@ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +// Allow validateW3C to pass without checking the actual schema +// eslint-disable-next-line @typescript-eslint/no-empty-function +export var validateW3C = function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { + return [2 /*return*/]; +}); }); }; diff --git a/dist/esm/3.0/validate/index.js b/dist/esm/3.0/validate/index.js new file mode 100644 index 00000000..13529009 --- /dev/null +++ b/dist/esm/3.0/validate/index.js @@ -0,0 +1 @@ +export * from "./validate"; diff --git a/dist/esm/3.0/validate/validate.js b/dist/esm/3.0/validate/validate.js new file mode 100644 index 00000000..82d080d8 --- /dev/null +++ b/dist/esm/3.0/validate/validate.js @@ -0,0 +1,154 @@ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +import { documentLoaders, expand } from "jsonld"; +import fetch from "node-fetch"; +var getId = function (objectOrString) { + if (typeof objectOrString === "string") { + return objectOrString; + } + return objectOrString.id; +}; +/* Based on https://tools.ietf.org/html/rfc3339#section-5.6 */ +var dateFullYear = /[0-9]{4}/; +var dateMonth = /(0[1-9]|1[0-2])/; +var dateMDay = /([12]\d|0[1-9]|3[01])/; +var timeHour = /([01][0-9]|2[0-3])/; +var timeMinute = /[0-5][0-9]/; +var timeSecond = /([0-5][0-9]|60)/; +var timeSecFrac = /(\.[0-9]+)?/; +var timeNumOffset = new RegExp("[-+]".concat(timeHour.source, ":").concat(timeMinute.source)); +var timeOffset = new RegExp("([zZ]|".concat(timeNumOffset.source, ")")); +var partialTime = new RegExp("".concat(timeHour.source, ":").concat(timeMinute.source, ":").concat(timeSecond.source).concat(timeSecFrac.source)); +var fullDate = new RegExp("".concat(dateFullYear.source, "-").concat(dateMonth.source, "-").concat(dateMDay.source)); +var fullTime = new RegExp("".concat(partialTime.source).concat(timeOffset.source)); +var rfc3339 = new RegExp("".concat(fullDate.source, "[ tT]").concat(fullTime.source)); +var isValidRFC3339 = function (str) { + return rfc3339.test(str); +}; +/* Based on https://tools.ietf.org/html/rfc3986 and https://github.com/ajv-validator/ajv/search?q=uri&unscoped_q=uri */ +var uri = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i; +var rfc3986 = new RegExp(uri); +var isValidRFC3986 = function (str) { + return rfc3986.test(str); +}; +var preloadedContextList = [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1", + "https://schemata.openattestation.com/com/openattestation/1.0/DrivingLicenceCredential.json", + "https://schemata.openattestation.com/com/openattestation/1.0/OpenAttestation.v3.json", + "https://schemata.openattestation.com/com/openattestation/1.0/CustomContext.json", +]; +var contexts = new Map(); +var nodeDocumentLoader = documentLoaders.xhr ? documentLoaders.xhr() : documentLoaders.node(); +var preload = true; +var documentLoader = function (url) { return __awaiter(void 0, void 0, void 0, function () { + var _i, preloadedContextList_1, url_1, promise, promise; + var _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + if (preload) { + preload = false; + for (_i = 0, preloadedContextList_1 = preloadedContextList; _i < preloadedContextList_1.length; _i++) { + url_1 = preloadedContextList_1[_i]; + contexts.set(url_1, fetch(url_1, { headers: { accept: "application/json" } }).then(function (res) { return res.json(); })); + } + } + if (!contexts.get(url)) return [3 /*break*/, 2]; + promise = contexts.get(url); + _a = { + contextUrl: undefined + }; + return [4 /*yield*/, promise]; + case 1: return [2 /*return*/, (_a.document = _b.sent(), + _a.documentUrl = url, + _a)]; + case 2: + promise = nodeDocumentLoader(url); + contexts.set(url, promise.then(function (_a) { + var document = _a.document; + return document; + })); + return [2 /*return*/, promise]; + } + }); +}); }; +export function validateW3C(credential) { + return __awaiter(this, void 0, void 0, function () { + var issuerId; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + // ensure first context is 'https://www.w3.org/2018/credentials/v1' as it's mandatory, see https://www.w3.org/TR/vc-data-model/#contexts + if (!Array.isArray(credential["@context"]) || + (Array.isArray(credential["@context"]) && credential["@context"][0] !== "https://www.w3.org/2018/credentials/v1")) { + throw new Error("https://www.w3.org/2018/credentials/v1 needs to be first in the list of contexts"); + } + issuerId = getId(credential.issuer); + if (!isValidRFC3986(issuerId)) { + throw new Error("Property 'issuer' id must be a valid RFC 3986 URI"); + } + // ensure issuanceDate is a valid RFC3339 date, see https://www.w3.org/TR/vc-data-model/#issuance-date + if (!isValidRFC3339(credential.issuanceDate)) { + throw new Error("Property 'issuanceDate' must be a valid RFC 3339 date"); + } + // ensure expirationDate is a valid RFC3339 date, see https://www.w3.org/TR/vc-data-model/#expiration + if (credential.expirationDate && !isValidRFC3339(credential.expirationDate)) { + throw new Error("Property 'expirationDate' must be a valid RFC 3339 date"); + } + // https://www.w3.org/TR/vc-data-model/#types + if (!credential.type || !Array.isArray(credential.type)) { + throw new Error("Property 'type' must exist and be an array"); + } + if (!credential.type.includes("VerifiableCredential")) { + throw new Error("Property 'type' must have VerifiableCredential as one of the items"); + } + return [4 /*yield*/, expand(credential, { + expansionMap: function (info) { + if (info.unmappedProperty) { + throw new Error("\"The property " + (info.activeProperty ? info.activeProperty + "." : "") + info.unmappedProperty + " in the input was not defined in the context\""); + } + }, + documentLoader: documentLoader, + })]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); + }); +} diff --git a/dist/esm/3.0/verify.js b/dist/esm/3.0/verify.js new file mode 100644 index 00000000..c9b9733e --- /dev/null +++ b/dist/esm/3.0/verify.js @@ -0,0 +1,34 @@ +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +import { digestCredential } from "./digest"; +import { checkProof } from "../shared/merkle"; +import { decodeSalt, salt } from "./salt"; +export var verify = function (document) { + if (!document.proof) { + return false; + } + // Remove proof from document + // eslint-disable-next-line no-unused-vars,@typescript-eslint/no-unused-vars + var proof = document.proof, documentWithoutProof = __rest(document, ["proof"]); + var decodedSalts = decodeSalt(document.proof.salts); + // Checks to ensure there are no added/removed values, so visibleSalts.length must match decodedSalts.length + var visibleSalts = salt(documentWithoutProof); + if (visibleSalts.length !== decodedSalts.length) + return false; + // Checks target hash + var digest = digestCredential(documentWithoutProof, decodedSalts, document.proof.privacy.obfuscated); + var targetHash = document.proof.targetHash; + if (digest !== targetHash) + return false; + // Calculates merkle root from target hash and proof, then compare to merkle root in document + return checkProof(document.proof.proofs, document.proof.merkleRoot, document.proof.targetHash); +}; diff --git a/dist/esm/3.0/wrap.js b/dist/esm/3.0/wrap.js new file mode 100644 index 00000000..8c79995d --- /dev/null +++ b/dist/esm/3.0/wrap.js @@ -0,0 +1,117 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +import { hashToBuffer, SchemaValidationError } from "../shared/utils"; +import { MerkleTree } from "../shared/merkle"; +import { SchemaId } from "../shared/@types/document"; +import { digestCredential } from "../3.0/digest"; +import { validateSchema as validate } from "../shared/validate"; +import { encodeSalt, salt } from "./salt"; +import { validateW3C } from "./validate"; +import { getSchema } from "../shared/ajv"; +var getExternalSchema = function (schema) { return (schema ? { schema: schema } : {}); }; +export var wrapDocument = function (credential, options) { return __awaiter(void 0, void 0, void 0, function () { + var document, salts, digest, batchBuffers, merkleTree, merkleRoot, merkleProof, verifiableCredential, errors; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + document = __assign(__assign({ version: SchemaId.v3 }, getExternalSchema(options.externalSchemaId)), credential); + // To ensure that base @context exists, but this also means some of our validateW3C errors may be unreachable + if (!document["@context"]) { + document["@context"] = ["https://www.w3.org/2018/credentials/v1"]; + } + // Since our wrapper adds in OA-specific properties, we should push our OA context. This is also to pass W3C VC test suite. + if (Array.isArray(document["@context"]) && + !document["@context"].includes("https://schemata.openattestation.com/com/openattestation/1.0/OpenAttestation.v3.json")) { + document["@context"].push("https://schemata.openattestation.com/com/openattestation/1.0/OpenAttestation.v3.json"); + } + salts = salt(document); + digest = digestCredential(document, salts, []); + batchBuffers = [digest].map(hashToBuffer); + merkleTree = new MerkleTree(batchBuffers); + merkleRoot = merkleTree.getRoot().toString("hex"); + merkleProof = merkleTree.getProof(hashToBuffer(digest)).map(function (buffer) { return buffer.toString("hex"); }); + verifiableCredential = __assign(__assign({}, document), { proof: { + type: "OpenAttestationMerkleProofSignature2018", + proofPurpose: "assertionMethod", + targetHash: digest, + proofs: merkleProof, + merkleRoot: merkleRoot, + salts: encodeSalt(salts), + privacy: { + obfuscated: [], + }, + } }); + errors = validate(verifiableCredential, getSchema(SchemaId.v3)); + if (errors.length > 0) { + throw new SchemaValidationError("Invalid document", errors, verifiableCredential); + } + return [4 /*yield*/, validateW3C(verifiableCredential)]; + case 1: + _a.sent(); + return [2 /*return*/, verifiableCredential]; + } + }); +}); }; +export var wrapDocuments = function (documents, options) { return __awaiter(void 0, void 0, void 0, function () { + var verifiableCredentials, merkleTree, merkleRoot; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, Promise.all(documents.map(function (document) { return wrapDocument(document, options); }))]; + case 1: + verifiableCredentials = _a.sent(); + merkleTree = new MerkleTree(verifiableCredentials.map(function (verifiableCredential) { return verifiableCredential.proof.targetHash; }).map(hashToBuffer)); + merkleRoot = merkleTree.getRoot().toString("hex"); + // for each document, update the merkle root and add the proofs needed + return [2 /*return*/, verifiableCredentials.map(function (verifiableCredential) { + var digest = verifiableCredential.proof.targetHash; + var merkleProof = merkleTree.getProof(hashToBuffer(digest)).map(function (buffer) { return buffer.toString("hex"); }); + return __assign(__assign({}, verifiableCredential), { proof: __assign(__assign({}, verifiableCredential.proof), { proofs: merkleProof, merkleRoot: merkleRoot }) }); + })]; + } + }); +}); }; diff --git a/dist/esm/__generated__/schema.2.0.js b/dist/esm/__generated__/schema.2.0.js new file mode 100644 index 00000000..bc8d4ce3 --- /dev/null +++ b/dist/esm/__generated__/schema.2.0.js @@ -0,0 +1,18 @@ +/** + * Type of renderer template + */ +export var TemplateType; +(function (TemplateType) { + TemplateType["EmbeddedRenderer"] = "EMBEDDED_RENDERER"; +})(TemplateType || (TemplateType = {})); +export var IdentityProofType; +(function (IdentityProofType) { + IdentityProofType["DNSDid"] = "DNS-DID"; + IdentityProofType["DNSTxt"] = "DNS-TXT"; + IdentityProofType["Did"] = "DID"; +})(IdentityProofType || (IdentityProofType = {})); +export var RevocationType; +(function (RevocationType) { + RevocationType["None"] = "NONE"; + RevocationType["RevocationStore"] = "REVOCATION_STORE"; +})(RevocationType || (RevocationType = {})); diff --git a/dist/esm/__generated__/schema.3.0.js b/dist/esm/__generated__/schema.3.0.js new file mode 100644 index 00000000..3b988ce9 --- /dev/null +++ b/dist/esm/__generated__/schema.3.0.js @@ -0,0 +1,37 @@ +export var IdentityProofType; +(function (IdentityProofType) { + IdentityProofType["DNSDid"] = "DNS-DID"; + IdentityProofType["DNSTxt"] = "DNS-TXT"; + IdentityProofType["Did"] = "DID"; +})(IdentityProofType || (IdentityProofType = {})); +/** + * Proof Open Attestation method + */ +export var Method; +(function (Method) { + Method["Did"] = "DID"; + Method["DocumentStore"] = "DOCUMENT_STORE"; + Method["TokenRegistry"] = "TOKEN_REGISTRY"; +})(Method || (Method = {})); +/** + * Revocation method (if required by proof method) + */ +export var RevocationType; +(function (RevocationType) { + RevocationType["None"] = "NONE"; + RevocationType["RevocationStore"] = "REVOCATION_STORE"; +})(RevocationType || (RevocationType = {})); +/** + * Proof method name as explained by https://www.w3.org/TR/vc-data-model/#types + */ +export var ProofType; +(function (ProofType) { + ProofType["OpenAttestationProofMethod"] = "OpenAttestationProofMethod"; +})(ProofType || (ProofType = {})); +/** + * Type of renderer template + */ +export var TemplateType; +(function (TemplateType) { + TemplateType["EmbeddedRenderer"] = "EMBEDDED_RENDERER"; +})(TemplateType || (TemplateType = {})); diff --git a/dist/esm/index.js b/dist/esm/index.js new file mode 100644 index 00000000..8989c4d5 --- /dev/null +++ b/dist/esm/index.js @@ -0,0 +1,109 @@ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +import { validateSchema as validate } from "./shared/validate"; +import { verify } from "./2.0/verify"; +import { verify as verifyV3 } from "./3.0/verify"; +import { wrapDocument as wrapV2Document, wrapDocuments as wrapV2Documents } from "./2.0/wrap"; +import { signDocument as signV2Document } from "./2.0/sign"; +import { wrapDocument as wrapV3Document, wrapDocuments as wrapV3Documents } from "./3.0/wrap"; +import { signDocument as signV3Document } from "./3.0/sign"; +import { SchemaId } from "./shared/@types/document"; +import * as utils from "./shared/utils"; +import * as v2 from "./2.0/types"; +import * as v3 from "./3.0/types"; +import { obfuscateDocument as obfuscateDocumentV2 } from "./2.0/obfuscate"; +import { obfuscateVerifiableCredential } from "./3.0/obfuscate"; +import { SigningKey } from "./shared/@types/sign"; +import { Signer } from "ethers"; +import { getSchema } from "./shared/ajv"; +export function __unsafe__use__it__at__your__own__risks__wrapDocument(data, options) { + return wrapV3Document(data, options !== null && options !== void 0 ? options : { version: SchemaId.v3 }); +} +export function __unsafe__use__it__at__your__own__risks__wrapDocuments(dataArray, options) { + return wrapV3Documents(dataArray, options !== null && options !== void 0 ? options : { version: SchemaId.v3 }); +} +export function wrapDocument(data, options) { + return wrapV2Document(data, { externalSchemaId: options === null || options === void 0 ? void 0 : options.externalSchemaId }); +} +export function wrapDocuments(dataArray, options) { + return wrapV2Documents(dataArray, { externalSchemaId: options === null || options === void 0 ? void 0 : options.externalSchemaId }); +} +export var validateSchema = function (document) { + return validate(document, getSchema("" + ((document === null || document === void 0 ? void 0 : document.version) || SchemaId.v2))).length === 0; +}; +export function verifySignature(document) { + return utils.isWrappedV3Document(document) ? verifyV3(document) : verify(document); +} +export function obfuscate(document, fields) { + return document.version === SchemaId.v3 + ? obfuscateVerifiableCredential(document, fields) + : obfuscateDocumentV2(document, fields); +} +export var isSchemaValidationError = function (error) { + return !!error.validationErrors; +}; +export function signDocument(document, algorithm, keyOrSigner) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + // rj was worried it could happen deep in the code, so I moved it to the boundaries + if (!SigningKey.guard(keyOrSigner) && !Signer.isSigner(keyOrSigner)) { + throw new Error("Either a keypair or ethers.js Signer must be provided"); + } + switch (true) { + case utils.isWrappedV2Document(document): + return [2 /*return*/, signV2Document(document, algorithm, keyOrSigner)]; + case utils.isWrappedV3Document(document): + return [2 /*return*/, signV3Document(document, algorithm, keyOrSigner)]; + default: + // Unreachable code atm until utils.isWrappedV2Document & utils.isWrappedV3Document becomes more strict + throw new Error("Unsupported document type: Only OpenAttestation v2 & v3 documents can be signed"); + } + return [2 /*return*/]; + }); + }); +} +export { digestDocument } from "./2.0/digest"; +export { digestCredential } from "./3.0/digest"; +export { checkProof, MerkleTree } from "./shared/merkle"; +export { obfuscate as obfuscateDocument }; +export { utils }; +export * from "./shared/@types/document"; +export * from "./shared/@types/sign"; +export * from "./shared/signer"; +export { getData } from "./shared/utils"; // keep it to avoid breaking change, moved from privacy to utils +export { v2 }; +export { v3 }; diff --git a/dist/esm/shared/@types/document.js b/dist/esm/shared/@types/document.js new file mode 100644 index 00000000..bfb51737 --- /dev/null +++ b/dist/esm/shared/@types/document.js @@ -0,0 +1,11 @@ +import { Literal, String } from "runtypes"; +import { ethers } from "ethers"; +export var SchemaId; +(function (SchemaId) { + SchemaId["v2"] = "https://schema.openattestation.com/2.0/schema.json"; + SchemaId["v3"] = "https://schema.openattestation.com/3.0/schema.json"; +})(SchemaId || (SchemaId = {})); +export var OpenAttestationHexString = String.withConstraint(function (value) { return ethers.utils.isHexString("0x" + value, 32) || value + " has not the expected length of 32 bytes"; }); +export var SignatureAlgorithm = Literal("OpenAttestationMerkleProofSignature2018"); +export var ProofType = Literal("OpenAttestationSignature2018"); +export var ProofPurpose = Literal("assertionMethod"); diff --git a/dist/esm/shared/@types/sign.js b/dist/esm/shared/@types/sign.js new file mode 100644 index 00000000..03361712 --- /dev/null +++ b/dist/esm/shared/@types/sign.js @@ -0,0 +1,9 @@ +import { Record, String } from "runtypes"; +export var SUPPORTED_SIGNING_ALGORITHM; +(function (SUPPORTED_SIGNING_ALGORITHM) { + SUPPORTED_SIGNING_ALGORITHM["Secp256k1VerificationKey2018"] = "Secp256k1VerificationKey2018"; +})(SUPPORTED_SIGNING_ALGORITHM || (SUPPORTED_SIGNING_ALGORITHM = {})); +export var SigningKey = Record({ + private: String, + public: String, +}); diff --git a/dist/esm/shared/@types/wrap.js b/dist/esm/shared/@types/wrap.js new file mode 100644 index 00000000..f01178c3 --- /dev/null +++ b/dist/esm/shared/@types/wrap.js @@ -0,0 +1,4 @@ +import { SchemaId } from "./document"; +export var isWrapDocumentOptionV3 = function (options) { + return (options === null || options === void 0 ? void 0 : options.version) === SchemaId.v3; +}; diff --git a/dist/esm/shared/ajv.js b/dist/esm/shared/ajv.js new file mode 100644 index 00000000..c8d15485 --- /dev/null +++ b/dist/esm/shared/ajv.js @@ -0,0 +1,47 @@ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +import Ajv from "ajv"; +import addFormats from "ajv-formats"; +import openAttestationSchemav2 from "../2.0/schema/schema.json"; +import openAttestationSchemav3 from "../3.0/schema/schema.json"; +var defaultTransform = function (schema) { return schema; }; +export var buildAjv = function (options) { + if (options === void 0) { options = { + transform: defaultTransform, + }; } + var transform = options.transform, ajvOptions = __rest(options, ["transform"]); + var ajv = new Ajv(__assign({ allErrors: true, allowUnionTypes: true }, ajvOptions)); + addFormats(ajv); + ajv.addKeyword("deprecationMessage"); + ajv.compile(transform(openAttestationSchemav2)); + ajv.compile(transform(openAttestationSchemav3)); + return ajv; +}; +var localAjv = buildAjv(); +export var getSchema = function (key, ajv) { + if (ajv === void 0) { ajv = localAjv; } + var schema = ajv.getSchema(key); + if (!schema) + throw new Error("Could not find " + key + " schema"); + return schema; +}; diff --git a/dist/esm/shared/logger.js b/dist/esm/shared/logger.js new file mode 100644 index 00000000..fa7d4550 --- /dev/null +++ b/dist/esm/shared/logger.js @@ -0,0 +1,9 @@ +import debug from "debug"; +var logger = debug("open-attestation"); +export var getLogger = function (namespace) { return ({ + trace: logger.extend("trace:" + namespace), + debug: logger.extend("debug:" + namespace), + info: logger.extend("info:" + namespace), + warn: logger.extend("warn:" + namespace), + error: logger.extend("error:" + namespace), +}); }; diff --git a/dist/esm/shared/merkle/index.js b/dist/esm/shared/merkle/index.js new file mode 100644 index 00000000..e4d67691 --- /dev/null +++ b/dist/esm/shared/merkle/index.js @@ -0,0 +1 @@ +export * from "./merkle"; diff --git a/dist/esm/shared/merkle/merkle.js b/dist/esm/shared/merkle/merkle.js new file mode 100644 index 00000000..4adebd21 --- /dev/null +++ b/dist/esm/shared/merkle/merkle.js @@ -0,0 +1,98 @@ +import { hashArray, toBuffer, hashToBuffer, combineHashBuffers } from "../utils"; +function getNextLayer(elements) { + return elements.reduce(function (layer, element, index, arr) { + if (index % 2 === 0) { + // only calculate hash for even indexes + layer.push(combineHashBuffers(element, arr[index + 1])); + } + return layer; + }, []); +} +/** + * This function produces the hashes and the merkle tree + * If there are no elements, return empty array of array + */ +function getLayers(elements) { + if (elements.length === 0) { + return [[]]; + } + var layers = []; + layers.push(elements); + while (layers[layers.length - 1].length > 1) { + layers.push(getNextLayer(layers[layers.length - 1])); + } + return layers; +} +/** + * This function takes a given index and determines if it is the first or second element in a pair, then returns the first element of the pair + * If the given index is the last element in a layer with an odd number of elements, then null is returned + * E.g 1: + * + * layer = [ A, B, C, D ], + * if index = 2, then return A + * if index = 3, then return C + * + * E.g 2: + * + * layer = [ A, B, C, D, E] + * if index = 5, then return null + * if index = 4, then return C + */ +function getPair(index, layer) { + var pairIndex = index % 2 ? index - 1 : index + 1; // if odd return the index before it, else if even return the index after it + if (pairIndex < layer.length) { + return layer[pairIndex]; + } + return null; // this happens when the given index is the last element in a layer with odd number of elements +} +/** + * Finds all the "uncle" nodes required to prove a given element in the merkle tree + */ +function getProof(index, layers) { + var i = index; + var proof = layers.reduce(function (current, layer) { + var pair = getPair(i, layer); + if (pair) { + current.push(pair); + } + i = Math.floor(i / 2); // finds the index of the parent of the current node + return current; + }, []); + return proof; +} +var MerkleTree = /** @class */ (function () { + function MerkleTree(_elements) { + this.elements = hashArray(_elements); + // check buffers + if (this.elements.some(function (e) { return !(e.length === 32 && Buffer.isBuffer(e)); })) { + throw new Error("elements must be 32 byte buffers"); + } + this.layers = getLayers(this.elements); + } + MerkleTree.prototype.getRoot = function () { + return this.layers[this.layers.length - 1][0]; + }; + MerkleTree.prototype.getProof = function (_element) { + var element = toBuffer(_element); + var index = this.elements.findIndex(function (e) { return e.equals(element); }); // searches for given element in the merkle tree and returns the index + if (index === -1) { + throw new Error("Element not found"); + } + return getProof(index, this.layers); + }; + return MerkleTree; +}()); +export { MerkleTree }; +/** + * Function that runs through the supplied hashes to arrive at the supplied merkle root hash + * @param _proof The list of uncle hashes required to arrive at the supplied merkle root + * @param _root The merkle root + * @param _element The leaf node that is being verified + */ +export var checkProof = function (_proof, _root, _element) { + var proof = _proof.map(function (step) { return hashToBuffer(step); }); + var root = hashToBuffer(_root); + var element = hashToBuffer(_element); + var proofRoot = proof.reduce(function (hash, pair) { return combineHashBuffers(hash, pair); }, element); + return root.equals(proofRoot); +}; diff --git a/dist/esm/shared/serialize/flatten.js b/dist/esm/shared/serialize/flatten.js new file mode 100644 index 00000000..35c28aba --- /dev/null +++ b/dist/esm/shared/serialize/flatten.js @@ -0,0 +1,25 @@ +import { flatten as flatleyFlatten } from "flatley"; +import { cloneDeep } from "lodash"; +var hasPeriodInKey = function (key) { + if (key.indexOf(".") >= 0) { + throw new Error("Key names must not have . in them"); + } + return false; +}; +var filters = [{ test: hasPeriodInKey }]; +/** + * Calls external flatten library but ensures that global filters are always applied + * @param data + * @param options + */ +export var flatten = function (data, options) { + var _a; + var newOptions = options ? cloneDeep(options) : {}; + if (newOptions.coercion) { + (_a = newOptions.coercion).push.apply(_a, filters); + } + else { + newOptions.coercion = filters; + } + return flatleyFlatten(data, newOptions); +}; diff --git a/dist/esm/shared/signer/index.js b/dist/esm/shared/signer/index.js new file mode 100644 index 00000000..2bf29658 --- /dev/null +++ b/dist/esm/shared/signer/index.js @@ -0,0 +1 @@ +export * from "./signer"; diff --git a/dist/esm/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/Secp256k1VerificationKey2018.js b/dist/esm/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/Secp256k1VerificationKey2018.js new file mode 100644 index 00000000..80365245 --- /dev/null +++ b/dist/esm/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/Secp256k1VerificationKey2018.js @@ -0,0 +1,18 @@ +import { Wallet, utils } from "ethers"; +import { SigningKey } from "../../../@types/sign"; +export var name = "Secp256k1VerificationKey2018"; +export var sign = function (message, keyOrSigner, options) { + if (options === void 0) { options = {}; } + var signer; + if (SigningKey.guard(keyOrSigner)) { + var wallet = new Wallet(keyOrSigner.private); + if (!keyOrSigner.public.toLowerCase().includes(wallet.address.toLowerCase())) { + throw new Error("Private key is wrong for " + keyOrSigner.public); + } + signer = wallet; + } + else { + signer = keyOrSigner; + } + return signer.signMessage(options.signAsString ? message : utils.arrayify(message)); +}; diff --git a/dist/esm/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/index.js b/dist/esm/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/index.js new file mode 100644 index 00000000..114c1d75 --- /dev/null +++ b/dist/esm/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/index.js @@ -0,0 +1 @@ +export * from "./Secp256k1VerificationKey2018"; diff --git a/dist/esm/shared/signer/signatureSchemes/index.js b/dist/esm/shared/signer/signatureSchemes/index.js new file mode 100644 index 00000000..734c05c5 --- /dev/null +++ b/dist/esm/shared/signer/signatureSchemes/index.js @@ -0,0 +1 @@ +export * from "./signatureSchemes"; diff --git a/dist/esm/shared/signer/signatureSchemes/signatureSchemes.js b/dist/esm/shared/signer/signatureSchemes/signatureSchemes.js new file mode 100644 index 00000000..1a7a0b67 --- /dev/null +++ b/dist/esm/shared/signer/signatureSchemes/signatureSchemes.js @@ -0,0 +1,3 @@ +import { sign as Secp256k1VerificationKey2018Sign, name as Secp256k1VerificationKey2018Name, } from "./Secp256k1VerificationKey2018"; +export var defaultSigners = new Map(); +defaultSigners.set(Secp256k1VerificationKey2018Name, Secp256k1VerificationKey2018Sign); diff --git a/dist/esm/shared/signer/signer.js b/dist/esm/shared/signer/signer.js new file mode 100644 index 00000000..0dd8715d --- /dev/null +++ b/dist/esm/shared/signer/signer.js @@ -0,0 +1,9 @@ +import { defaultSigners } from "./signatureSchemes"; +export var signerBuilder = function (signers) { return function (alg, message, keyOrSigner, options) { + var signer = signers.get(alg); + if (!signer) + throw new Error(alg + " is not supported as a signing algorithm"); + return signer(message, keyOrSigner, options); +}; }; +export var sign = signerBuilder(defaultSigners); +export { defaultSigners }; diff --git a/dist/esm/shared/utils/diagnose.js b/dist/esm/shared/utils/diagnose.js new file mode 100644 index 00000000..4e978bb5 --- /dev/null +++ b/dist/esm/shared/utils/diagnose.js @@ -0,0 +1,130 @@ +var __spreadArray = (this && this.__spreadArray) || function (to, from) { + for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) + to[j] = from[i]; + return to; +}; +import { logger } from "ethers"; +import { SchemaId } from "../.."; +import { validateSchema as validate } from "../validate"; +import { VerifiableCredentialSignedProof, VerifiableCredentialWrappedProof, VerifiableCredentialWrappedProofStrict, } from "../../3.0/types"; +import { ArrayProof, Signature, SignatureStrict } from "../../2.0/types"; +import { clone, cloneDeepWith } from "lodash"; +import { buildAjv, getSchema } from "../ajv"; +var handleError = function (debug) { + var messages = []; + for (var _i = 1; _i < arguments.length; _i++) { + messages[_i - 1] = arguments[_i]; + } + if (debug) { + for (var _a = 0, messages_1 = messages; _a < messages_1.length; _a++) { + var message = messages_1[_a]; + logger.info(message); + } + } + return messages.map(function (message) { return ({ message: message }); }); +}; +// remove enum and pattern from the schema +function transformSchema(schema) { + var _a, _b, _c, _d, _e, _f; + var excludeKeys = ["enum", "pattern"]; + function omit(value) { + if (value && typeof value === "object") { + var key = excludeKeys.find(function (key) { return value[key]; }); + if (key) { + var node_1 = clone(value); + excludeKeys.forEach(function (key) { + delete node_1[key]; + }); + return node_1; + } + } + } + var newSchema = cloneDeepWith(schema, omit); + // because we remove check on enum (DNS-DID, DNS-TXT, etc.) the identity proof can match multiple sub schema in v2. + // so here we change oneOf to anyOf, so that if more than one identityProof matches, it still passes + if ((_b = (_a = newSchema === null || newSchema === void 0 ? void 0 : newSchema.definitions) === null || _a === void 0 ? void 0 : _a.identityProof) === null || _b === void 0 ? void 0 : _b.oneOf) { + newSchema.definitions.identityProof.anyOf = (_d = (_c = newSchema === null || newSchema === void 0 ? void 0 : newSchema.definitions) === null || _c === void 0 ? void 0 : _c.identityProof) === null || _d === void 0 ? void 0 : _d.oneOf; + (_f = (_e = newSchema === null || newSchema === void 0 ? void 0 : newSchema.definitions) === null || _e === void 0 ? void 0 : _e.identityProof) === null || _f === void 0 ? true : delete _f.oneOf; + } + return newSchema; +} +// custom ajv for loose schema validation +// it will allow invalid format, invalid pattern and invalid enum +var ajv = buildAjv({ transform: transformSchema, validateFormats: false }); +/** + * Tools to give information about the validity of a document. It will return and eventually output the errors found. + * @param version 2.0 or 3.0 + * @param kind wrapped or signed + * @param debug turn on to output in the console, the errors found + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + * @param document the document to validate + */ +export var diagnose = function (_a) { + var version = _a.version, kind = _a.kind, document = _a.document, _b = _a.debug, debug = _b === void 0 ? false : _b, mode = _a.mode; + if (!document) { + return handleError(debug, "The document must not be empty"); + } + if (typeof document !== "object") { + return handleError(debug, "The document must be an object"); + } + var errors = validate(document, getSchema(version === "3.0" ? SchemaId.v3 : SchemaId.v2, mode === "non-strict" ? ajv : undefined)); + if (errors.length > 0) { + // TODO this can be improved later + return handleError.apply(void 0, __spreadArray([debug, "The document does not match OpenAttestation schema " + (version === "3.0" ? "3.0" : "2.0")], errors.map(function (error) { return (error.instancePath || "document") + " - " + error.message; }))); + } + if (version === "3.0") { + return diagnoseV3({ mode: mode, debug: debug, document: document, kind: kind }); + } + else { + return diagnoseV2({ mode: mode, debug: debug, document: document, kind: kind }); + } +}; +var diagnoseV2 = function (_a) { + var kind = _a.kind, document = _a.document, debug = _a.debug, mode = _a.mode; + try { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + mode === "strict" ? SignatureStrict.check(document.signature) : Signature.check(document.signature); + } + catch (e) { + return handleError(debug, e.message); + } + if (kind === "signed") { + if (!document.proof || !(document.proof.length > 0)) { + return handleError(debug, "The document does not have a proof"); + } + try { + ArrayProof.check(document.proof); + } + catch (e) { + return handleError(debug, e.message); + } + } + return []; +}; +var diagnoseV3 = function (_a) { + var kind = _a.kind, document = _a.document, debug = _a.debug, mode = _a.mode; + if (document.version !== SchemaId.v3) { + return handleError(debug, "The document schema version is wrong. Expected " + SchemaId.v3 + ", received " + document.version); + } + try { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + mode === "strict" + ? VerifiableCredentialWrappedProofStrict.check(document.proof) + : VerifiableCredentialWrappedProof.check(document.proof); + } + catch (e) { + return handleError(debug, e.message); + } + if (kind === "signed") { + if (!document.proof) { + return handleError(debug, "The document does not have a proof"); + } + try { + VerifiableCredentialSignedProof.check(document.proof); + } + catch (e) { + return handleError(debug, e.message); + } + } + return []; +}; diff --git a/dist/esm/shared/utils/guard.js b/dist/esm/shared/utils/guard.js new file mode 100644 index 00000000..105aad33 --- /dev/null +++ b/dist/esm/shared/utils/guard.js @@ -0,0 +1,37 @@ +import { diagnose } from "./diagnose"; +/** + * + * @param document + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + */ +export var isWrappedV3Document = function (document, _a) { + var _b = _a === void 0 ? { mode: "non-strict" } : _a, mode = _b.mode; + return diagnose({ version: "3.0", kind: "wrapped", document: document, debug: false, mode: mode }).length === 0; +}; +/** + * + * @param document + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + */ +export var isWrappedV2Document = function (document, _a) { + var _b = _a === void 0 ? { mode: "non-strict" } : _a, mode = _b.mode; + return diagnose({ version: "2.0", kind: "wrapped", document: document, debug: false, mode: mode }).length === 0; +}; +/** + * + * @param document + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + */ +export var isSignedWrappedV2Document = function (document, _a) { + var _b = _a === void 0 ? { mode: "non-strict" } : _a, mode = _b.mode; + return diagnose({ version: "2.0", kind: "signed", document: document, debug: false, mode: mode }).length === 0; +}; +/** + * + * @param document + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + */ +export var isSignedWrappedV3Document = function (document, _a) { + var _b = _a === void 0 ? { mode: "non-strict" } : _a, mode = _b.mode; + return diagnose({ version: "3.0", kind: "signed", document: document, debug: false, mode: mode }).length === 0; +}; diff --git a/dist/esm/shared/utils/index.js b/dist/esm/shared/utils/index.js new file mode 100644 index 00000000..ee2c0318 --- /dev/null +++ b/dist/esm/shared/utils/index.js @@ -0,0 +1,3 @@ +export * from "./utils"; +export * from "./guard"; +export * from "./diagnose"; diff --git a/dist/esm/shared/utils/utils.js b/dist/esm/shared/utils/utils.js new file mode 100644 index 00000000..9ef9b1ab --- /dev/null +++ b/dist/esm/shared/utils/utils.js @@ -0,0 +1,156 @@ +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __spreadArray = (this && this.__spreadArray) || function (to, from) { + for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) + to[j] = from[i]; + return to; +}; +import { keccak256 } from "js-sha3"; +import { unsaltData } from "../../2.0/salt"; +import { isWrappedV2Document, isWrappedV3Document } from "./guard"; +export var getData = function (document) { + return unsaltData(document.data); +}; +/** + * Sorts the given Buffers lexicographically and then concatenates them to form one continuous Buffer + */ +export function bufSortJoin() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + return Buffer.concat(__spreadArray([], args).sort(Buffer.compare)); +} +// If hash is not a buffer, convert it to buffer (without hashing it) +export function hashToBuffer(hash) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore https://github.com/Microsoft/TypeScript/issues/23155 + return Buffer.isBuffer(hash) && hash.length === 32 ? hash : Buffer.from(hash, "hex"); +} +// If element is not a buffer, stringify it and then hash it to be a buffer +export function toBuffer(element) { + return Buffer.isBuffer(element) && element.length === 32 ? element : hashToBuffer(keccak256(JSON.stringify(element))); +} +/** + * Turns array of data into sorted array of hashes + */ +export function hashArray(arr) { + return arr.map(function (i) { return toBuffer(i); }).sort(Buffer.compare); +} +/** + * Returns the keccak hash of two buffers after concatenating them and sorting them + * If either hash is not given, the input is returned + */ +export function combineHashBuffers(first, second) { + if (!second) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return first; // it should always be valued if second is not + } + if (!first) { + return second; + } + return hashToBuffer(keccak256(bufSortJoin(first, second))); +} +/** + * Returns the keccak hash of two string after concatenating them and sorting them + * If either hash is not given, the input is returned + * @param first A string to be hashed (without 0x) + * @param second A string to be hashed (without 0x) + * @returns Resulting string after the hash is combined (without 0x) + */ +export function combineHashString(first, second) { + return first && second + ? combineHashBuffers(hashToBuffer(first), hashToBuffer(second)).toString("hex") + : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + (first || second); // this should always return a value right ? :) +} +export function getIssuerAddress(document) { + if (isWrappedV2Document(document)) { + var data = getData(document); + return data.issuers.map(function (issuer) { return issuer.certificateStore || issuer.documentStore || issuer.tokenRegistry; }); + } + else if (isWrappedV3Document(document)) { + return document.openAttestationMetadata.proof.value; + } + throw new Error("Unsupported document type: Only can retrieve issuer address from wrapped OpenAttestation v2 & v3 documents."); +} +export var getMerkleRoot = function (document) { + switch (true) { + case isWrappedV2Document(document): + return document.signature.merkleRoot; + case isWrappedV3Document(document): + return document.proof.merkleRoot; + default: + throw new Error("Unsupported document type: Only can retrieve merkle root from wrapped OpenAttestation v2 & v3 documents."); + } +}; +export var getTargetHash = function (document) { + switch (true) { + case isWrappedV2Document(document): + return document.signature.targetHash; + case isWrappedV3Document(document): + return document.proof.targetHash; + default: + throw new Error("Unsupported document type: Only can retrieve target hash from wrapped OpenAttestation v2 & v3 documents."); + } +}; +export var isTransferableAsset = function (document) { + var _a, _b, _c, _d; + return (!!((_b = (_a = getData(document)) === null || _a === void 0 ? void 0 : _a.issuers[0]) === null || _b === void 0 ? void 0 : _b.tokenRegistry) || + ((_d = (_c = document === null || document === void 0 ? void 0 : document.openAttestationMetadata) === null || _c === void 0 ? void 0 : _c.proof) === null || _d === void 0 ? void 0 : _d.method) === "TOKEN_REGISTRY"); +}; +export var getAssetId = function (document) { + if (isTransferableAsset(document)) { + return getTargetHash(document); + } + throw new Error("Unsupported document type: Only can retrieve asset id from wrapped OpenAttestation v2 & v3 transferable documents."); +}; +var SchemaValidationError = /** @class */ (function (_super) { + __extends(SchemaValidationError, _super); + function SchemaValidationError(message, validationErrors, document) { + var _this = _super.call(this, message) || this; + _this.validationErrors = validationErrors; + _this.document = document; + return _this; + } + return SchemaValidationError; +}(Error)); +export { SchemaValidationError }; +export var isSchemaValidationError = function (error) { + return !!error.validationErrors; +}; +// make it available for consumers +export { keccak256 } from "js-sha3"; +export var isObfuscated = function (document) { + var _a, _b, _c, _d; + if (isWrappedV3Document(document)) { + return !!((_b = (_a = document.proof.privacy) === null || _a === void 0 ? void 0 : _a.obfuscated) === null || _b === void 0 ? void 0 : _b.length); + } + if (isWrappedV2Document(document)) { + return !!((_d = (_c = document.privacy) === null || _c === void 0 ? void 0 : _c.obfuscatedData) === null || _d === void 0 ? void 0 : _d.length); + } + throw new Error("Unsupported document type: Can only check if there are obfuscated data from wrapped OpenAttestation v2 & v3 documents."); +}; +export var getObfuscatedData = function (document) { + var _a, _b; + if (isWrappedV3Document(document)) { + return (_a = document.proof.privacy) === null || _a === void 0 ? void 0 : _a.obfuscated; + } + if (isWrappedV2Document(document)) { + return ((_b = document.privacy) === null || _b === void 0 ? void 0 : _b.obfuscatedData) || []; + } + throw new Error("Unsupported document type: Can only retrieve obfuscated data from wrapped OpenAttestation v2 & v3 documents."); +}; diff --git a/dist/esm/shared/validate/index.js b/dist/esm/shared/validate/index.js new file mode 100644 index 00000000..13529009 --- /dev/null +++ b/dist/esm/shared/validate/index.js @@ -0,0 +1 @@ +export * from "./validate"; diff --git a/dist/esm/shared/validate/validate.js b/dist/esm/shared/validate/validate.js new file mode 100644 index 00000000..95a95832 --- /dev/null +++ b/dist/esm/shared/validate/validate.js @@ -0,0 +1,17 @@ +import { getLogger } from "../logger"; +import { SchemaId } from "../@types/document"; +import { getData } from "../utils"; +var logger = getLogger("validate"); +export var validateSchema = function (document, validator) { + var _a; + if (!validator) { + throw new Error("No schema validator provided"); + } + var valid = validator(document.version === SchemaId.v3 ? document : getData(document)); + if (!valid) { + logger.debug("There are errors in the document: " + JSON.stringify(validator.errors)); + return (_a = validator.errors) !== null && _a !== void 0 ? _a : []; + } + logger.debug("Document is a valid open attestation document v" + document.version); + return []; +}; diff --git a/dist/index.umd.js b/dist/index.umd.js new file mode 100644 index 00000000..8bb59625 --- /dev/null +++ b/dist/index.umd.js @@ -0,0 +1,2239 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('debug'), require('runtypes'), require('ethers'), require('js-sha3'), require('lodash'), require('validator/lib/isUUID'), require('uuid'), require('flatley'), require('crypto'), require('js-base64'), require('ajv'), require('ajv-formats'), require('jsonld'), require('node-fetch')) : + typeof define === 'function' && define.amd ? define(['exports', 'debug', 'runtypes', 'ethers', 'js-sha3', 'lodash', 'validator/lib/isUUID', 'uuid', 'flatley', 'crypto', 'js-base64', 'ajv', 'ajv-formats', 'jsonld', 'node-fetch'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.openAttestation = {}, global.debug, global.runtypes, global.ethers, global.jsSha3, global.lodash, global.isUUID, global.uuid, global.flatley, global.crypto, global.jsBase64, global.Ajv, global.addFormats, global.jsonld, global.fetch)); +}(this, (function (exports, debug, runtypes, ethers, jsSha3, lodash, isUUID, uuid, flatley, crypto, jsBase64, Ajv, addFormats, jsonld, fetch) { 'use strict'; + + function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } + + var debug__default = /*#__PURE__*/_interopDefaultLegacy(debug); + var isUUID__default = /*#__PURE__*/_interopDefaultLegacy(isUUID); + var Ajv__default = /*#__PURE__*/_interopDefaultLegacy(Ajv); + var addFormats__default = /*#__PURE__*/_interopDefaultLegacy(addFormats); + var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch); + + var logger$1 = debug__default['default']("open-attestation"); + var getLogger = function (namespace) { return ({ + trace: logger$1.extend("trace:" + namespace), + debug: logger$1.extend("debug:" + namespace), + info: logger$1.extend("info:" + namespace), + warn: logger$1.extend("warn:" + namespace), + error: logger$1.extend("error:" + namespace), + }); }; + + exports.SchemaId = void 0; + (function (SchemaId) { + SchemaId["v2"] = "https://schema.openattestation.com/2.0/schema.json"; + SchemaId["v3"] = "https://schema.openattestation.com/3.0/schema.json"; + })(exports.SchemaId || (exports.SchemaId = {})); + var OpenAttestationHexString = runtypes.String.withConstraint(function (value) { return ethers.ethers.utils.isHexString("0x" + value, 32) || value + " has not the expected length of 32 bytes"; }); + var SignatureAlgorithm = runtypes.Literal("OpenAttestationMerkleProofSignature2018"); + var ProofType$1 = runtypes.Literal("OpenAttestationSignature2018"); + var ProofPurpose = runtypes.Literal("assertionMethod"); + + var UUIDV4_LENGTH = 37; + var PRIMITIVE_TYPES = ["string", "number", "boolean", "undefined"]; + /* eslint-disable no-use-before-define */ + /** + * Curried function that takes (iteratee)(value), + * if value is a collection then recurse into it + * otherwise apply `iteratee` on the primitive value + */ + var recursivelyApply = function (iteratee) { return function (value) { + if (lodash.includes(PRIMITIVE_TYPES, typeof value) || value === null) { + return iteratee(value); + } + return deepMap(value, iteratee); // eslint-disable-line @typescript-eslint/no-use-before-define + }; }; + /** + * Applies `iteratee` to all fields in objects, goes into arrays as well. + * Refer to test for example + */ + var deepMap = function (collection, iteratee) { + if (iteratee === void 0) { iteratee = lodash.identity; } + if (collection instanceof Array) { + return lodash.map(collection, recursivelyApply(iteratee)); + } + if (typeof collection === "object") { + return lodash.mapValues(collection, recursivelyApply(iteratee)); + } + return collection; + }; + /* eslint-enable no-use-before-define */ + // disabling this because of mutual recursion + var startsWithUuidV4 = function (input) { + if (input && typeof input === "string") { + var elements = input.split(":"); + return isUUID__default['default'](elements[0], 4); + } + return false; + }; + /** + * Detects the type of a value and returns a string with type annotation + */ + function primitiveToTypedString(value) { + switch (typeof value) { + case "number": + case "string": + case "boolean": + case "undefined": + return typeof value + ":" + String(value); + default: + if (value === null) { + // typeof null is 'object' so we have to check for it + return "null:null"; + } + throw new Error("Parsing error, value is not of primitive type: " + value); + } + } + /** + * Returns an appropriately typed value given a string with type annotations, e.g: "number:5" + */ + function typedStringToPrimitive(input) { + var _a = input.split(":"), type = _a[0], valueArray = _a.slice(1); + var value = valueArray.join(":"); // just in case there are colons in the value + switch (type) { + case "number": + return Number(value); + case "string": + return String(value); + case "boolean": + return value === "true"; + case "null": + return null; + case "undefined": + return undefined; + default: + throw new Error("Parsing error, type annotation not found in string: " + input); + } + } + /** + * Returns a salted value using a randomly generated uuidv4 string for salt + */ + function uuidSalt(value) { + var salt = uuid.v4(); + return salt + ":" + primitiveToTypedString(value); + } + /** + * Value salted string in the format "salt:type:value", example: "ee7f3323-1634-4dea-8c12-f0bb83aff874:number:5" + * Returns an appropriately typed value when given a salted string with type annotation + */ + function unsalt(value) { + if (startsWithUuidV4(value)) { + var untypedValue = value.substring(UUIDV4_LENGTH).trim(); + return typedStringToPrimitive(untypedValue); + } + return value; + } + // Use uuid salting method to recursively salt data + var saltData = function (data) { return deepMap(data, uuidSalt); }; + var unsaltData = function (data) { return deepMap(data, unsalt); }; + + var IdentityProofType$1; + (function (IdentityProofType) { + IdentityProofType["DNSDid"] = "DNS-DID"; + IdentityProofType["DNSTxt"] = "DNS-TXT"; + IdentityProofType["Did"] = "DID"; + })(IdentityProofType$1 || (IdentityProofType$1 = {})); + /** + * Proof Open Attestation method + */ + var Method; + (function (Method) { + Method["Did"] = "DID"; + Method["DocumentStore"] = "DOCUMENT_STORE"; + Method["TokenRegistry"] = "TOKEN_REGISTRY"; + })(Method || (Method = {})); + /** + * Revocation method (if required by proof method) + */ + var RevocationType$1; + (function (RevocationType) { + RevocationType["None"] = "NONE"; + RevocationType["RevocationStore"] = "REVOCATION_STORE"; + })(RevocationType$1 || (RevocationType$1 = {})); + /** + * Proof method name as explained by https://www.w3.org/TR/vc-data-model/#types + */ + var ProofType; + (function (ProofType) { + ProofType["OpenAttestationProofMethod"] = "OpenAttestationProofMethod"; + })(ProofType || (ProofType = {})); + /** + * Type of renderer template + */ + var TemplateType$1; + (function (TemplateType) { + TemplateType["EmbeddedRenderer"] = "EMBEDDED_RENDERER"; + })(TemplateType$1 || (TemplateType$1 = {})); + + var ObfuscationMetadata$1 = runtypes.Record({ + obfuscated: runtypes.Array(OpenAttestationHexString), + }); + var VerifiableCredentialWrappedProof = runtypes.Record({ + type: SignatureAlgorithm, + targetHash: runtypes.String, + merkleRoot: runtypes.String, + proofs: runtypes.Array(runtypes.String), + salts: runtypes.String, + privacy: ObfuscationMetadata$1, + proofPurpose: ProofPurpose, + }); + var VerifiableCredentialWrappedProofStrict = VerifiableCredentialWrappedProof.And(runtypes.Record({ + targetHash: OpenAttestationHexString, + merkleRoot: OpenAttestationHexString, + proofs: runtypes.Array(OpenAttestationHexString), + })); + var VerifiableCredentialSignedProof = VerifiableCredentialWrappedProof.And(runtypes.Record({ + key: runtypes.String, + signature: runtypes.String, + })); + + var types$1 = /*#__PURE__*/Object.freeze({ + __proto__: null, + ObfuscationMetadata: ObfuscationMetadata$1, + VerifiableCredentialWrappedProof: VerifiableCredentialWrappedProof, + VerifiableCredentialWrappedProofStrict: VerifiableCredentialWrappedProofStrict, + VerifiableCredentialSignedProof: VerifiableCredentialSignedProof, + get IdentityProofType () { return IdentityProofType$1; }, + get Method () { return Method; }, + get RevocationType () { return RevocationType$1; }, + get ProofType () { return ProofType; }, + get TemplateType () { return TemplateType$1; } + }); + + /** + * Type of renderer template + */ + var TemplateType; + (function (TemplateType) { + TemplateType["EmbeddedRenderer"] = "EMBEDDED_RENDERER"; + })(TemplateType || (TemplateType = {})); + var IdentityProofType; + (function (IdentityProofType) { + IdentityProofType["DNSDid"] = "DNS-DID"; + IdentityProofType["DNSTxt"] = "DNS-TXT"; + IdentityProofType["Did"] = "DID"; + })(IdentityProofType || (IdentityProofType = {})); + var RevocationType; + (function (RevocationType) { + RevocationType["None"] = "NONE"; + RevocationType["RevocationStore"] = "REVOCATION_STORE"; + })(RevocationType || (RevocationType = {})); + + var ObfuscationMetadata = runtypes.Partial({ + obfuscatedData: runtypes.Array(OpenAttestationHexString), + }); + var Proof = runtypes.Record({ + type: ProofType$1, + created: runtypes.String, + proofPurpose: ProofPurpose, + verificationMethod: runtypes.String, + signature: runtypes.String, + }); + var ArrayProof = runtypes.Array(Proof); + var Signature = runtypes.Record({ + type: runtypes.Literal("SHA3MerkleProof"), + targetHash: runtypes.String, + merkleRoot: runtypes.String, + proof: runtypes.Array(runtypes.String), + }); + var SignatureStrict = Signature.And(runtypes.Record({ + targetHash: OpenAttestationHexString, + merkleRoot: OpenAttestationHexString, + proof: runtypes.Array(OpenAttestationHexString), + })); + + var types = /*#__PURE__*/Object.freeze({ + __proto__: null, + ObfuscationMetadata: ObfuscationMetadata, + Proof: Proof, + ArrayProof: ArrayProof, + Signature: Signature, + SignatureStrict: SignatureStrict, + get TemplateType () { return TemplateType; }, + get IdentityProofType () { return IdentityProofType; }, + get RevocationType () { return RevocationType; } + }); + + var title$1 = "Open Attestation Schema 2.0"; + var $id$1 = "https://schema.openattestation.com/2.0/schema.json"; + var $schema$1 = "http://json-schema.org/draft-07/schema#"; + var definitions$1 = { + identityProofDns: { + type: "object", + properties: { + type: { + type: "string", + "enum": [ + "DNS-TXT" + ] + }, + location: { + type: "string", + description: "Url of the website referencing to document store" + } + }, + required: [ + "type", + "location" + ] + }, + identityProofDid: { + type: "object", + properties: { + type: { + type: "string", + "enum": [ + "DID" + ] + }, + key: { + type: "string", + description: "Public key associated" + } + }, + required: [ + "type", + "key" + ] + }, + identityProofDnsDid: { + type: "object", + properties: { + type: { + type: "string", + "enum": [ + "DNS-DID" + ] + }, + key: { + type: "string", + description: "Public key associated" + }, + location: { + type: "string", + description: "Url of the website referencing to document store" + } + }, + required: [ + "type", + "key", + "location" + ] + }, + identityProof: { + type: "object", + oneOf: [ + { + $ref: "#/definitions/identityProofDns" + }, + { + $ref: "#/definitions/identityProofDnsDid" + }, + { + $ref: "#/definitions/identityProofDid" + } + ] + }, + issuer: { + type: "object", + properties: { + id: { + type: "string", + description: "Issuer's id, DID can be used" + }, + name: { + type: "string", + description: "Issuer's name" + }, + revocation: { + type: "object", + properties: { + type: { + type: "string", + "enum": [ + "NONE", + "REVOCATION_STORE" + ] + }, + location: { + type: "string", + description: "Smart contract address or url of certificate revocation list for Revocation Store type revocation" + } + } + }, + identityProof: { + $ref: "#/definitions/identityProof" + } + }, + required: [ + "name", + "identityProof" + ], + additionalProperties: true + }, + documentStore: { + allOf: [ + { + $ref: "#/definitions/issuer" + }, + { + type: "object", + properties: { + documentStore: { + type: "string", + pattern: "^0x[a-fA-F0-9]{40}$", + description: "Smart contract address of document store" + } + }, + required: [ + "documentStore" + ] + } + ] + }, + certificateStore: { + type: "object", + properties: { + name: { + type: "string", + description: "Issuer's name" + }, + certificateStore: { + type: "string", + pattern: "^0x[a-fA-F0-9]{40}$", + deprecationMessage: "Use documentStore and identityProof instead of this", + description: "Smart contract address of certificate store. Same as documentStore" + } + }, + required: [ + "name", + "certificateStore" + ], + additionalProperties: true + }, + tokenRegistry: { + allOf: [ + { + $ref: "#/definitions/issuer" + }, + { + type: "object", + properties: { + tokenRegistry: { + type: "string", + pattern: "^0x[a-fA-F0-9]{40}$", + description: "Smart contract address of token registry" + } + }, + required: [ + "tokenRegistry" + ] + } + ] + } + }; + var type$1 = "object"; + var properties$1 = { + id: { + type: "string", + description: "Internal reference, usually serial number, of this document" + }, + $template: { + oneOf: [ + { + type: "string" + }, + { + type: "object", + properties: { + name: { + type: "string", + description: "Template name to be use by template renderer to determine the template to use" + }, + type: { + type: "string", + description: "Type of renderer template", + "enum": [ + "EMBEDDED_RENDERER" + ] + }, + url: { + type: "string", + description: "URL of a decentralised renderer to render this document" + } + }, + required: [ + "name", + "type" + ] + } + ] + }, + documentUrl: { + type: "string", + description: "URL of the stored document" + }, + issuers: { + type: "array", + items: { + type: "object", + title: "issuer", + oneOf: [ + { + $ref: "#/definitions/tokenRegistry" + }, + { + $ref: "#/definitions/documentStore" + }, + { + $ref: "#/definitions/certificateStore" + }, + { + allOf: [ + { + $ref: "#/definitions/issuer" + }, + { + not: { + anyOf: [ + { + required: [ + "certificateStore" + ] + }, + { + required: [ + "tokenRegistry" + ] + }, + { + required: [ + "documentStore" + ] + } + ] + } + } + ] + } + ] + }, + minItems: 1 + }, + recipient: { + type: "object", + properties: { + name: { + type: "string", + description: "Recipient's name" + } + }, + additionalProperties: true + }, + attachments: { + type: "array", + items: { + type: "object", + properties: { + filename: { + type: "string", + description: "Name of attachment, with appropriate extensions" + }, + type: { + type: "string", + description: "Type of attachment" + }, + data: { + type: "string", + description: "Base64 encoding of attachment" + } + }, + required: [ + "filename", + "type", + "data" + ], + additionalProperties: false + } + } + }; + var required$1 = [ + "issuers" + ]; + var additionalProperties$1 = true; + var openAttestationSchemav2 = { + title: title$1, + $id: $id$1, + $schema: $schema$1, + definitions: definitions$1, + type: type$1, + properties: properties$1, + required: required$1, + additionalProperties: additionalProperties$1 + }; + + var title = "Open Attestation Schema 3.0"; + var $id = "https://schema.openattestation.com/3.0/schema.json"; + var $schema = "http://json-schema.org/draft-07/schema#"; + var type = "object"; + var definitions = { + type: { + oneOf: [ + { + type: "array", + items: { + type: "string" + } + }, + { + type: "string" + } + ] + }, + issuer: { + type: "object", + properties: { + id: { + type: "string", + format: "uri", + description: "URI when dereferenced, results in a document containing machine-readable information about the issuer that can be used to verify the information expressed in the credential. More information in https://www.w3.org/TR/vc-data-model/#issuer" + }, + name: { + type: "string", + description: "Issuer's name" + } + }, + required: [ + "id", + "name" + ], + additionalProperties: true + } + }; + var properties = { + "@context": { + type: "array", + items: { + type: [ + "string", + "object" + ], + format: "uri" + }, + description: "List of URI to determine the terminology used in the verifiable credential as explained by https://www.w3.org/TR/vc-data-model/#contexts" + }, + id: { + type: "string", + format: "uri", + description: "URI to the subject of the credential as explained by https://www.w3.org/TR/vc-data-model/#credential-subject" + }, + type: { + $ref: "#/definitions/type", + description: "Specific verifiable credential type as explained by https://www.w3.org/TR/vc-data-model/#types" + }, + reference: { + type: "string", + description: "Internal reference, usually a serial number, of this document" + }, + name: { + type: "string", + description: "Human readable name of this credential" + }, + issuanceDate: { + type: "string", + format: "date-time", + description: "The date and time when this credential becomes valid (may be deprecated in favor of issued/validFrom a future version of W3C's VC Data Model)" + }, + expirationDate: { + type: "string", + format: "date-time", + description: "The date and time when this credential expires" + }, + issued: { + type: "string", + format: "date-time", + description: "The date and time when this credential becomes valid" + }, + validFrom: { + type: "string", + format: "date-time", + description: "The date and time when this credential becomes valid" + }, + validUntil: { + type: "string", + format: "date-time", + description: "The date and time when this credential expires" + }, + credentialSubject: { + oneOf: [ + { + type: "object" + }, + { + type: "array", + items: { + type: "object" + } + } + ] + }, + credentialStatus: { + type: "object", + properties: { + id: { + type: "string", + format: "uri", + examples: [ + "https://example.edu/status/24" + ] + }, + type: { + type: "string", + examples: [ + "CredentialStatusList2017" + ], + description: "Express the credential status type (also referred to as the credential status method). It is expected that the value will provide enough information to determine the current status of the credential. For example, the object could contain a link to an external document noting whether or not the credential is suspended or revoked." + } + }, + required: [ + "id", + "type" + ] + }, + issuer: { + oneOf: [ + { + $ref: "#/definitions/issuer" + }, + { + type: "string" + } + ] + }, + openAttestationMetadata: { + type: "object", + properties: { + template: { + type: "object", + properties: { + name: { + type: "string", + description: "Template name to be use by template renderer to determine the template to use" + }, + type: { + type: "string", + description: "Type of renderer template", + "enum": [ + "EMBEDDED_RENDERER" + ] + }, + url: { + type: "string", + description: "URL of a decentralised renderer to render this document", + pattern: "^(https?)://" + } + }, + required: [ + "name", + "type", + "url" + ], + additionalProperties: false + }, + proof: { + type: "object", + properties: { + type: { + type: "string", + description: "Proof method name as explained by https://www.w3.org/TR/vc-data-model/#types", + "enum": [ + "OpenAttestationProofMethod" + ] + }, + method: { + type: "string", + description: "Proof Open Attestation method", + "enum": [ + "TOKEN_REGISTRY", + "DOCUMENT_STORE", + "DID" + ] + }, + value: { + description: "Proof value of issuer(s). Smart contract address for TOKEN_REGISTRY & DOCUMENT_STORE, did for DID method", + type: "string" + }, + revocation: { + type: "object", + properties: { + type: { + type: "string", + description: "Revocation method (if required by proof method)", + "enum": [ + "NONE", + "REVOCATION_STORE" + ] + }, + location: { + type: "string", + description: "Smart contract address or url of certificate revocation list for Revocation Store type revocation" + } + }, + required: [ + "type" + ] + } + }, + required: [ + "type", + "method", + "value" + ], + additionalProperties: true + }, + identityProof: { + type: "object", + properties: { + type: { + type: "string", + "enum": [ + "DNS-TXT", + "DNS-DID", + "DID" + ] + }, + identifier: { + type: "string", + description: "Identifier to be shown to end user upon verifying the identity" + } + }, + additionalProperties: false, + required: [ + "type", + "identifier" + ] + } + }, + required: [ + "proof", + "identityProof" + ] + }, + attachments: { + type: "array", + items: { + type: "object", + properties: { + fileName: { + type: "string", + description: "Name of this attachment, with appropriate extensions" + }, + mimeType: { + type: "string", + description: "Media type (or MIME type) of this attachment" + }, + data: { + type: "string", + description: "Base64 encoding of this attachment" + } + }, + required: [ + "fileName", + "mimeType", + "data" + ], + additionalProperties: false + } + } + }; + var required = [ + "@context", + "type", + "credentialSubject", + "issuer", + "issuanceDate", + "openAttestationMetadata" + ]; + var additionalProperties = true; + var openAttestationSchemav3 = { + title: title, + $id: $id, + $schema: $schema, + type: type, + definitions: definitions, + properties: properties, + required: required, + additionalProperties: additionalProperties + }; + + var __assign$6 = (undefined && undefined.__assign) || function () { + __assign$6 = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign$6.apply(this, arguments); + }; + var __rest$1 = (undefined && undefined.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; + }; + var defaultTransform = function (schema) { return schema; }; + var buildAjv = function (options) { + if (options === void 0) { options = { + transform: defaultTransform, + }; } + var transform = options.transform, ajvOptions = __rest$1(options, ["transform"]); + var ajv = new Ajv__default['default'](__assign$6({ allErrors: true, allowUnionTypes: true }, ajvOptions)); + addFormats__default['default'](ajv); + ajv.addKeyword("deprecationMessage"); + ajv.compile(transform(openAttestationSchemav2)); + ajv.compile(transform(openAttestationSchemav3)); + return ajv; + }; + var localAjv = buildAjv(); + var getSchema = function (key, ajv) { + if (ajv === void 0) { ajv = localAjv; } + var schema = ajv.getSchema(key); + if (!schema) + throw new Error("Could not find " + key + " schema"); + return schema; + }; + + var __spreadArray$2 = (undefined && undefined.__spreadArray) || function (to, from) { + for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) + to[j] = from[i]; + return to; + }; + var handleError = function (debug) { + var messages = []; + for (var _i = 1; _i < arguments.length; _i++) { + messages[_i - 1] = arguments[_i]; + } + if (debug) { + for (var _a = 0, messages_1 = messages; _a < messages_1.length; _a++) { + var message = messages_1[_a]; + ethers.logger.info(message); + } + } + return messages.map(function (message) { return ({ message: message }); }); + }; + // remove enum and pattern from the schema + function transformSchema(schema) { + var _a, _b, _c, _d, _e, _f; + var excludeKeys = ["enum", "pattern"]; + function omit(value) { + if (value && typeof value === "object") { + var key = excludeKeys.find(function (key) { return value[key]; }); + if (key) { + var node_1 = lodash.clone(value); + excludeKeys.forEach(function (key) { + delete node_1[key]; + }); + return node_1; + } + } + } + var newSchema = lodash.cloneDeepWith(schema, omit); + // because we remove check on enum (DNS-DID, DNS-TXT, etc.) the identity proof can match multiple sub schema in v2. + // so here we change oneOf to anyOf, so that if more than one identityProof matches, it still passes + if ((_b = (_a = newSchema === null || newSchema === void 0 ? void 0 : newSchema.definitions) === null || _a === void 0 ? void 0 : _a.identityProof) === null || _b === void 0 ? void 0 : _b.oneOf) { + newSchema.definitions.identityProof.anyOf = (_d = (_c = newSchema === null || newSchema === void 0 ? void 0 : newSchema.definitions) === null || _c === void 0 ? void 0 : _c.identityProof) === null || _d === void 0 ? void 0 : _d.oneOf; + (_f = (_e = newSchema === null || newSchema === void 0 ? void 0 : newSchema.definitions) === null || _e === void 0 ? void 0 : _e.identityProof) === null || _f === void 0 ? true : delete _f.oneOf; + } + return newSchema; + } + // custom ajv for loose schema validation + // it will allow invalid format, invalid pattern and invalid enum + var ajv = buildAjv({ transform: transformSchema, validateFormats: false }); + /** + * Tools to give information about the validity of a document. It will return and eventually output the errors found. + * @param version 2.0 or 3.0 + * @param kind wrapped or signed + * @param debug turn on to output in the console, the errors found + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + * @param document the document to validate + */ + var diagnose = function (_a) { + var version = _a.version, kind = _a.kind, document = _a.document, _b = _a.debug, debug = _b === void 0 ? false : _b, mode = _a.mode; + if (!document) { + return handleError(debug, "The document must not be empty"); + } + if (typeof document !== "object") { + return handleError(debug, "The document must be an object"); + } + var errors = validateSchema$1(document, getSchema(version === "3.0" ? exports.SchemaId.v3 : exports.SchemaId.v2, mode === "non-strict" ? ajv : undefined)); + if (errors.length > 0) { + // TODO this can be improved later + return handleError.apply(void 0, __spreadArray$2([debug, "The document does not match OpenAttestation schema " + (version === "3.0" ? "3.0" : "2.0")], errors.map(function (error) { return (error.instancePath || "document") + " - " + error.message; }))); + } + if (version === "3.0") { + return diagnoseV3({ mode: mode, debug: debug, document: document, kind: kind }); + } + else { + return diagnoseV2({ mode: mode, debug: debug, document: document, kind: kind }); + } + }; + var diagnoseV2 = function (_a) { + var kind = _a.kind, document = _a.document, debug = _a.debug, mode = _a.mode; + try { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + mode === "strict" ? SignatureStrict.check(document.signature) : Signature.check(document.signature); + } + catch (e) { + return handleError(debug, e.message); + } + if (kind === "signed") { + if (!document.proof || !(document.proof.length > 0)) { + return handleError(debug, "The document does not have a proof"); + } + try { + ArrayProof.check(document.proof); + } + catch (e) { + return handleError(debug, e.message); + } + } + return []; + }; + var diagnoseV3 = function (_a) { + var kind = _a.kind, document = _a.document, debug = _a.debug, mode = _a.mode; + if (document.version !== exports.SchemaId.v3) { + return handleError(debug, "The document schema version is wrong. Expected " + exports.SchemaId.v3 + ", received " + document.version); + } + try { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + mode === "strict" + ? VerifiableCredentialWrappedProofStrict.check(document.proof) + : VerifiableCredentialWrappedProof.check(document.proof); + } + catch (e) { + return handleError(debug, e.message); + } + if (kind === "signed") { + if (!document.proof) { + return handleError(debug, "The document does not have a proof"); + } + try { + VerifiableCredentialSignedProof.check(document.proof); + } + catch (e) { + return handleError(debug, e.message); + } + } + return []; + }; + + /** + * + * @param document + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + */ + var isWrappedV3Document = function (document, _a) { + var _b = _a === void 0 ? { mode: "non-strict" } : _a, mode = _b.mode; + return diagnose({ version: "3.0", kind: "wrapped", document: document, debug: false, mode: mode }).length === 0; + }; + /** + * + * @param document + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + */ + var isWrappedV2Document = function (document, _a) { + var _b = _a === void 0 ? { mode: "non-strict" } : _a, mode = _b.mode; + return diagnose({ version: "2.0", kind: "wrapped", document: document, debug: false, mode: mode }).length === 0; + }; + /** + * + * @param document + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + */ + var isSignedWrappedV2Document = function (document, _a) { + var _b = _a === void 0 ? { mode: "non-strict" } : _a, mode = _b.mode; + return diagnose({ version: "2.0", kind: "signed", document: document, debug: false, mode: mode }).length === 0; + }; + /** + * + * @param document + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + */ + var isSignedWrappedV3Document = function (document, _a) { + var _b = _a === void 0 ? { mode: "non-strict" } : _a, mode = _b.mode; + return diagnose({ version: "3.0", kind: "signed", document: document, debug: false, mode: mode }).length === 0; + }; + + var __extends = (undefined && undefined.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; + })(); + var __spreadArray$1 = (undefined && undefined.__spreadArray) || function (to, from) { + for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) + to[j] = from[i]; + return to; + }; + var getData = function (document) { + return unsaltData(document.data); + }; + /** + * Sorts the given Buffers lexicographically and then concatenates them to form one continuous Buffer + */ + function bufSortJoin() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + return Buffer.concat(__spreadArray$1([], args).sort(Buffer.compare)); + } + // If hash is not a buffer, convert it to buffer (without hashing it) + function hashToBuffer(hash) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore https://github.com/Microsoft/TypeScript/issues/23155 + return Buffer.isBuffer(hash) && hash.length === 32 ? hash : Buffer.from(hash, "hex"); + } + // If element is not a buffer, stringify it and then hash it to be a buffer + function toBuffer(element) { + return Buffer.isBuffer(element) && element.length === 32 ? element : hashToBuffer(jsSha3.keccak256(JSON.stringify(element))); + } + /** + * Turns array of data into sorted array of hashes + */ + function hashArray(arr) { + return arr.map(function (i) { return toBuffer(i); }).sort(Buffer.compare); + } + /** + * Returns the keccak hash of two buffers after concatenating them and sorting them + * If either hash is not given, the input is returned + */ + function combineHashBuffers(first, second) { + if (!second) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return first; // it should always be valued if second is not + } + if (!first) { + return second; + } + return hashToBuffer(jsSha3.keccak256(bufSortJoin(first, second))); + } + /** + * Returns the keccak hash of two string after concatenating them and sorting them + * If either hash is not given, the input is returned + * @param first A string to be hashed (without 0x) + * @param second A string to be hashed (without 0x) + * @returns Resulting string after the hash is combined (without 0x) + */ + function combineHashString(first, second) { + return first && second + ? combineHashBuffers(hashToBuffer(first), hashToBuffer(second)).toString("hex") + : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + (first || second); // this should always return a value right ? :) + } + function getIssuerAddress(document) { + if (isWrappedV2Document(document)) { + var data = getData(document); + return data.issuers.map(function (issuer) { return issuer.certificateStore || issuer.documentStore || issuer.tokenRegistry; }); + } + else if (isWrappedV3Document(document)) { + return document.openAttestationMetadata.proof.value; + } + throw new Error("Unsupported document type: Only can retrieve issuer address from wrapped OpenAttestation v2 & v3 documents."); + } + var getMerkleRoot = function (document) { + switch (true) { + case isWrappedV2Document(document): + return document.signature.merkleRoot; + case isWrappedV3Document(document): + return document.proof.merkleRoot; + default: + throw new Error("Unsupported document type: Only can retrieve merkle root from wrapped OpenAttestation v2 & v3 documents."); + } + }; + var getTargetHash = function (document) { + switch (true) { + case isWrappedV2Document(document): + return document.signature.targetHash; + case isWrappedV3Document(document): + return document.proof.targetHash; + default: + throw new Error("Unsupported document type: Only can retrieve target hash from wrapped OpenAttestation v2 & v3 documents."); + } + }; + var isTransferableAsset = function (document) { + var _a, _b, _c, _d; + return (!!((_b = (_a = getData(document)) === null || _a === void 0 ? void 0 : _a.issuers[0]) === null || _b === void 0 ? void 0 : _b.tokenRegistry) || + ((_d = (_c = document === null || document === void 0 ? void 0 : document.openAttestationMetadata) === null || _c === void 0 ? void 0 : _c.proof) === null || _d === void 0 ? void 0 : _d.method) === "TOKEN_REGISTRY"); + }; + var getAssetId = function (document) { + if (isTransferableAsset(document)) { + return getTargetHash(document); + } + throw new Error("Unsupported document type: Only can retrieve asset id from wrapped OpenAttestation v2 & v3 transferable documents."); + }; + var SchemaValidationError = /** @class */ (function (_super) { + __extends(SchemaValidationError, _super); + function SchemaValidationError(message, validationErrors, document) { + var _this = _super.call(this, message) || this; + _this.validationErrors = validationErrors; + _this.document = document; + return _this; + } + return SchemaValidationError; + }(Error)); + var isSchemaValidationError$1 = function (error) { + return !!error.validationErrors; + }; + var isObfuscated = function (document) { + var _a, _b, _c, _d; + if (isWrappedV3Document(document)) { + return !!((_b = (_a = document.proof.privacy) === null || _a === void 0 ? void 0 : _a.obfuscated) === null || _b === void 0 ? void 0 : _b.length); + } + if (isWrappedV2Document(document)) { + return !!((_d = (_c = document.privacy) === null || _c === void 0 ? void 0 : _c.obfuscatedData) === null || _d === void 0 ? void 0 : _d.length); + } + throw new Error("Unsupported document type: Can only check if there are obfuscated data from wrapped OpenAttestation v2 & v3 documents."); + }; + var getObfuscatedData = function (document) { + var _a, _b; + if (isWrappedV3Document(document)) { + return (_a = document.proof.privacy) === null || _a === void 0 ? void 0 : _a.obfuscated; + } + if (isWrappedV2Document(document)) { + return ((_b = document.privacy) === null || _b === void 0 ? void 0 : _b.obfuscatedData) || []; + } + throw new Error("Unsupported document type: Can only retrieve obfuscated data from wrapped OpenAttestation v2 & v3 documents."); + }; + + var index = /*#__PURE__*/Object.freeze({ + __proto__: null, + keccak256: jsSha3.keccak256, + getData: getData, + bufSortJoin: bufSortJoin, + hashToBuffer: hashToBuffer, + toBuffer: toBuffer, + hashArray: hashArray, + combineHashBuffers: combineHashBuffers, + combineHashString: combineHashString, + getIssuerAddress: getIssuerAddress, + getMerkleRoot: getMerkleRoot, + getTargetHash: getTargetHash, + isTransferableAsset: isTransferableAsset, + getAssetId: getAssetId, + SchemaValidationError: SchemaValidationError, + isSchemaValidationError: isSchemaValidationError$1, + isObfuscated: isObfuscated, + getObfuscatedData: getObfuscatedData, + isWrappedV3Document: isWrappedV3Document, + isWrappedV2Document: isWrappedV2Document, + isSignedWrappedV2Document: isSignedWrappedV2Document, + isSignedWrappedV3Document: isSignedWrappedV3Document, + diagnose: diagnose + }); + + var logger = getLogger("validate"); + var validateSchema$1 = function (document, validator) { + var _a; + if (!validator) { + throw new Error("No schema validator provided"); + } + var valid = validator(document.version === exports.SchemaId.v3 ? document : getData(document)); + if (!valid) { + logger.debug("There are errors in the document: " + JSON.stringify(validator.errors)); + return (_a = validator.errors) !== null && _a !== void 0 ? _a : []; + } + logger.debug("Document is a valid open attestation document v" + document.version); + return []; + }; + + var hasPeriodInKey = function (key) { + if (key.indexOf(".") >= 0) { + throw new Error("Key names must not have . in them"); + } + return false; + }; + var filters = [{ test: hasPeriodInKey }]; + /** + * Calls external flatten library but ensures that global filters are always applied + * @param data + * @param options + */ + var flatten = function (data, options) { + var _a; + var newOptions = options ? lodash.cloneDeep(options) : {}; + if (newOptions.coercion) { + (_a = newOptions.coercion).push.apply(_a, filters); + } + else { + newOptions.coercion = filters; + } + return flatley.flatten(data, newOptions); + }; + + var isKeyOrValueUndefined = function (value, key) { return value === undefined || key === undefined; }; + var flattenHashArray = function (data) { + var flattenedData = lodash.omitBy(flatten(data), isKeyOrValueUndefined); + return Object.keys(flattenedData).map(function (k) { + var obj = {}; + obj[k] = flattenedData[k]; + return jsSha3.keccak256(JSON.stringify(obj)); + }); + }; + var digestDocument = function (document) { + // Prepare array of hashes from filtered data + var hashedDataArray = lodash.get(document, "privacy.obfuscatedData", []); + // Prepare array of hashes from visible data + var unhashedData = lodash.get(document, "data"); + var hashedUnhashedDataArray = flattenHashArray(unhashedData); + // Combine both array and sort them to ensure determinism + var combinedHashes = hashedDataArray.concat(hashedUnhashedDataArray); + var sortedHashes = lodash.sortBy(combinedHashes); + // Finally, return the digest of the entire set of data + return jsSha3.keccak256(JSON.stringify(sortedHashes)); + }; + + function getNextLayer(elements) { + return elements.reduce(function (layer, element, index, arr) { + if (index % 2 === 0) { + // only calculate hash for even indexes + layer.push(combineHashBuffers(element, arr[index + 1])); + } + return layer; + }, []); + } + /** + * This function produces the hashes and the merkle tree + * If there are no elements, return empty array of array + */ + function getLayers(elements) { + if (elements.length === 0) { + return [[]]; + } + var layers = []; + layers.push(elements); + while (layers[layers.length - 1].length > 1) { + layers.push(getNextLayer(layers[layers.length - 1])); + } + return layers; + } + /** + * This function takes a given index and determines if it is the first or second element in a pair, then returns the first element of the pair + * If the given index is the last element in a layer with an odd number of elements, then null is returned + * E.g 1: + * + * layer = [ A, B, C, D ], + * if index = 2, then return A + * if index = 3, then return C + * + * E.g 2: + * + * layer = [ A, B, C, D, E] + * if index = 5, then return null + * if index = 4, then return C + */ + function getPair(index, layer) { + var pairIndex = index % 2 ? index - 1 : index + 1; // if odd return the index before it, else if even return the index after it + if (pairIndex < layer.length) { + return layer[pairIndex]; + } + return null; // this happens when the given index is the last element in a layer with odd number of elements + } + /** + * Finds all the "uncle" nodes required to prove a given element in the merkle tree + */ + function getProof(index, layers) { + var i = index; + var proof = layers.reduce(function (current, layer) { + var pair = getPair(i, layer); + if (pair) { + current.push(pair); + } + i = Math.floor(i / 2); // finds the index of the parent of the current node + return current; + }, []); + return proof; + } + var MerkleTree = /** @class */ (function () { + function MerkleTree(_elements) { + this.elements = hashArray(_elements); + // check buffers + if (this.elements.some(function (e) { return !(e.length === 32 && Buffer.isBuffer(e)); })) { + throw new Error("elements must be 32 byte buffers"); + } + this.layers = getLayers(this.elements); + } + MerkleTree.prototype.getRoot = function () { + return this.layers[this.layers.length - 1][0]; + }; + MerkleTree.prototype.getProof = function (_element) { + var element = toBuffer(_element); + var index = this.elements.findIndex(function (e) { return e.equals(element); }); // searches for given element in the merkle tree and returns the index + if (index === -1) { + throw new Error("Element not found"); + } + return getProof(index, this.layers); + }; + return MerkleTree; + }()); + /** + * Function that runs through the supplied hashes to arrive at the supplied merkle root hash + * @param _proof The list of uncle hashes required to arrive at the supplied merkle root + * @param _root The merkle root + * @param _element The leaf node that is being verified + */ + var checkProof = function (_proof, _root, _element) { + var proof = _proof.map(function (step) { return hashToBuffer(step); }); + var root = hashToBuffer(_root); + var element = hashToBuffer(_element); + var proofRoot = proof.reduce(function (hash, pair) { return combineHashBuffers(hash, pair); }, element); + return root.equals(proofRoot); + }; + + var verify$1 = function (document) { + var _a, _b, _c, _d; + var signature = lodash.get(document, "signature"); + if (!signature) { + return false; + } + // Checks target hash + var digest = digestDocument(document); + var targetHash = lodash.get(document, "signature.targetHash"); + if (digest !== targetHash) + return false; + // Calculates merkle root from target hash and proof, then compare to merkle root in document + return checkProof((_b = (_a = document === null || document === void 0 ? void 0 : document.signature) === null || _a === void 0 ? void 0 : _a.proof) !== null && _b !== void 0 ? _b : [], (_c = document === null || document === void 0 ? void 0 : document.signature) === null || _c === void 0 ? void 0 : _c.merkleRoot, (_d = document === null || document === void 0 ? void 0 : document.signature) === null || _d === void 0 ? void 0 : _d.targetHash); + }; + + var digestCredential = function (document, salts, obfuscatedData) { + // Prepare array of hashes from visible data + var hashedUnhashedDataArray = salts + .filter(function (salt) { return lodash.get(document, salt.path); }) + .map(function (salt) { + var _a; + return jsSha3.keccak256(JSON.stringify((_a = {}, _a[salt.path] = salt.value + ":" + lodash.get(document, salt.path), _a))); + }); + // Combine both array and sort them to ensure determinism + var combinedHashes = obfuscatedData.concat(hashedUnhashedDataArray); + var sortedHashes = lodash.sortBy(combinedHashes); + // Finally, return the digest of the entire set of data + return jsSha3.keccak256(JSON.stringify(sortedHashes)); + }; + + function traverseAndFlatten(data, _a) { + var iteratee = _a.iteratee, _b = _a.path, path = _b === void 0 ? "" : _b; + if (Array.isArray(data)) { + return data.flatMap(function (v, index) { return traverseAndFlatten(v, { iteratee: iteratee, path: path + "[" + index + "]" }); }); + } + // Since null datas are allowed but typeof null === "object", the "&& data" is used to skip this + if (typeof data === "object" && data) { + return Object.keys(data).flatMap(function (key) { + return traverseAndFlatten(data[key], { iteratee: iteratee, path: path ? path + "." + key : key }); + }); + } + if (typeof data === "string" || typeof data === "number" || typeof data === "boolean" || data === null) { + return iteratee({ value: data, path: path }); + } + throw new Error("Unexpected data '" + data + "' in '" + path + "'"); + } + + var ENTROPY_IN_BYTES = 32; + var illegalCharactersCheck = function (data) { + Object.entries(data).forEach(function (_a) { + var key = _a[0], value = _a[1]; + if (key.includes(".")) { + throw new Error("Key names must not have . in them"); + } + if (key.includes("[") || key.includes("]")) { + throw new Error("Key names must not have '[' or ']' in them"); + } + if (value && typeof value === "object") { + return illegalCharactersCheck(value); // Recursively search if property contains sub-properties + } + }); + }; + // Using 32 bytes of entropy as compared to 16 bytes in uuid + // Using hex encoding as compared to base64 for constant string length + var secureRandomString = function () { return crypto.randomBytes(ENTROPY_IN_BYTES).toString("hex"); }; + var salt = function (data) { + // Check for illegal characters e.g. '.', '[' or ']' + illegalCharactersCheck(data); + return traverseAndFlatten(data, { iteratee: function (_a) { + var path = _a.path; + return ({ value: secureRandomString(), path: path }); + } }); + }; + var encodeSalt = function (salts) { return jsBase64.Base64.encode(JSON.stringify(salts)); }; + var decodeSalt = function (salts) { + var decoded = JSON.parse(jsBase64.Base64.decode(salts)); + decoded.forEach(function (salt) { + if (salt.value.length !== ENTROPY_IN_BYTES * 2) + throw new Error("Salt must be " + ENTROPY_IN_BYTES + " bytes"); + }); + return decoded; + }; + + var __rest = (undefined && undefined.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; + }; + var verify = function (document) { + if (!document.proof) { + return false; + } + // Remove proof from document + // eslint-disable-next-line no-unused-vars,@typescript-eslint/no-unused-vars + document.proof; var documentWithoutProof = __rest(document, ["proof"]); + var decodedSalts = decodeSalt(document.proof.salts); + // Checks to ensure there are no added/removed values, so visibleSalts.length must match decodedSalts.length + var visibleSalts = salt(documentWithoutProof); + if (visibleSalts.length !== decodedSalts.length) + return false; + // Checks target hash + var digest = digestCredential(documentWithoutProof, decodedSalts, document.proof.privacy.obfuscated); + var targetHash = document.proof.targetHash; + if (digest !== targetHash) + return false; + // Calculates merkle root from target hash and proof, then compare to merkle root in document + return checkProof(document.proof.proofs, document.proof.merkleRoot, document.proof.targetHash); + }; + + var __assign$5 = (undefined && undefined.__assign) || function () { + __assign$5 = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign$5.apply(this, arguments); + }; + var createDocument = function (data, option) { + var documentSchema = { + version: exports.SchemaId.v2, + data: saltData(data), + }; + if (option === null || option === void 0 ? void 0 : option.externalSchemaId) { + documentSchema.schema = option.externalSchemaId; + } + return documentSchema; + }; + var wrapDocument$2 = function (data, options) { + var _a; + var document = createDocument(data, options); + var errors = validateSchema$1(document, getSchema((_a = options === null || options === void 0 ? void 0 : options.version) !== null && _a !== void 0 ? _a : exports.SchemaId.v2)); + if (errors.length > 0) { + throw new SchemaValidationError("Invalid document", errors, document); + } + var digest = digestDocument(document); + var signature = { + type: "SHA3MerkleProof", + targetHash: digest, + proof: [], + merkleRoot: digest, + }; + return __assign$5(__assign$5({}, document), { signature: signature }); + }; + var wrapDocuments$2 = function (data, options) { + // wrap documents individually + var documents = data.map(function (d) { return wrapDocument$2(d, options); }); + // get all the target hashes to compute the merkle tree and the merkle root + var merkleTree = new MerkleTree(documents.map(function (document) { return document.signature.targetHash; }).map(hashToBuffer)); + var merkleRoot = merkleTree.getRoot().toString("hex"); + // for each document, update the merkle root and add the proofs needed + return documents.map(function (document) { + var merkleProof = merkleTree + .getProof(hashToBuffer(document.signature.targetHash)) + .map(function (buffer) { return buffer.toString("hex"); }); + return __assign$5(__assign$5({}, document), { signature: __assign$5(__assign$5({}, document.signature), { proof: merkleProof, merkleRoot: merkleRoot }) }); + }); + }; + + exports.SUPPORTED_SIGNING_ALGORITHM = void 0; + (function (SUPPORTED_SIGNING_ALGORITHM) { + SUPPORTED_SIGNING_ALGORITHM["Secp256k1VerificationKey2018"] = "Secp256k1VerificationKey2018"; + })(exports.SUPPORTED_SIGNING_ALGORITHM || (exports.SUPPORTED_SIGNING_ALGORITHM = {})); + var SigningKey = runtypes.Record({ + private: runtypes.String, + public: runtypes.String, + }); + + var name = "Secp256k1VerificationKey2018"; + var sign$1 = function (message, keyOrSigner, options) { + if (options === void 0) { options = {}; } + var signer; + if (SigningKey.guard(keyOrSigner)) { + var wallet = new ethers.Wallet(keyOrSigner.private); + if (!keyOrSigner.public.toLowerCase().includes(wallet.address.toLowerCase())) { + throw new Error("Private key is wrong for " + keyOrSigner.public); + } + signer = wallet; + } + else { + signer = keyOrSigner; + } + return signer.signMessage(options.signAsString ? message : ethers.utils.arrayify(message)); + }; + + var defaultSigners = new Map(); + defaultSigners.set(name, sign$1); + + var signerBuilder = function (signers) { return function (alg, message, keyOrSigner, options) { + var signer = signers.get(alg); + if (!signer) + throw new Error(alg + " is not supported as a signing algorithm"); + return signer(message, keyOrSigner, options); + }; }; + var sign = signerBuilder(defaultSigners); + + var __assign$4 = (undefined && undefined.__assign) || function () { + __assign$4 = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign$4.apply(this, arguments); + }; + var __awaiter$4 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); + }; + var __generator$4 = (undefined && undefined.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } + }; + var __spreadArray = (undefined && undefined.__spreadArray) || function (to, from) { + for (var i = 0, il = from.length, j = to.length; i < il; i++, j++) + to[j] = from[i]; + return to; + }; + var signDocument$2 = function (document, algorithm, keyOrSigner) { return __awaiter$4(void 0, void 0, void 0, function () { + var merkleRoot, signature, proof, _a, _b; + var _c; + return __generator$4(this, function (_d) { + switch (_d.label) { + case 0: + merkleRoot = "0x" + document.signature.merkleRoot; + return [4 /*yield*/, sign(algorithm, merkleRoot, keyOrSigner)]; + case 1: + signature = _d.sent(); + _c = { + type: "OpenAttestationSignature2018", + created: new Date().toISOString(), + proofPurpose: "assertionMethod" + }; + if (!SigningKey.guard(keyOrSigner)) return [3 /*break*/, 2]; + _a = keyOrSigner.public; + return [3 /*break*/, 4]; + case 2: + _b = "did:ethr:"; + return [4 /*yield*/, keyOrSigner.getAddress()]; + case 3: + _a = _b + (_d.sent()) + "#controller"; + _d.label = 4; + case 4: + proof = (_c.verificationMethod = _a, + _c.signature = signature, + _c); + return [2 /*return*/, __assign$4(__assign$4({}, document), { proof: isSignedWrappedV2Document(document) ? __spreadArray(__spreadArray([], document.proof), [proof]) : [proof] })]; + } + }); + }); }; + + var __awaiter$3 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); + }; + var __generator$3 = (undefined && undefined.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } + }; + var getId = function (objectOrString) { + if (typeof objectOrString === "string") { + return objectOrString; + } + return objectOrString.id; + }; + /* Based on https://tools.ietf.org/html/rfc3339#section-5.6 */ + var dateFullYear = /[0-9]{4}/; + var dateMonth = /(0[1-9]|1[0-2])/; + var dateMDay = /([12]\d|0[1-9]|3[01])/; + var timeHour = /([01][0-9]|2[0-3])/; + var timeMinute = /[0-5][0-9]/; + var timeSecond = /([0-5][0-9]|60)/; + var timeSecFrac = /(\.[0-9]+)?/; + var timeNumOffset = new RegExp("[-+]".concat(timeHour.source, ":").concat(timeMinute.source)); + var timeOffset = new RegExp("([zZ]|".concat(timeNumOffset.source, ")")); + var partialTime = new RegExp("".concat(timeHour.source, ":").concat(timeMinute.source, ":").concat(timeSecond.source).concat(timeSecFrac.source)); + var fullDate = new RegExp("".concat(dateFullYear.source, "-").concat(dateMonth.source, "-").concat(dateMDay.source)); + var fullTime = new RegExp("".concat(partialTime.source).concat(timeOffset.source)); + var rfc3339 = new RegExp("".concat(fullDate.source, "[ tT]").concat(fullTime.source)); + var isValidRFC3339 = function (str) { + return rfc3339.test(str); + }; + /* Based on https://tools.ietf.org/html/rfc3986 and https://github.com/ajv-validator/ajv/search?q=uri&unscoped_q=uri */ + var uri = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i; + var rfc3986 = new RegExp(uri); + var isValidRFC3986 = function (str) { + return rfc3986.test(str); + }; + var preloadedContextList = [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1", + "https://schemata.openattestation.com/com/openattestation/1.0/DrivingLicenceCredential.json", + "https://schemata.openattestation.com/com/openattestation/1.0/OpenAttestation.v3.json", + "https://schemata.openattestation.com/com/openattestation/1.0/CustomContext.json", + ]; + var contexts = new Map(); + var nodeDocumentLoader = jsonld.documentLoaders.xhr ? jsonld.documentLoaders.xhr() : jsonld.documentLoaders.node(); + var preload = true; + var documentLoader = function (url) { return __awaiter$3(void 0, void 0, void 0, function () { + var _i, preloadedContextList_1, url_1, promise, promise; + var _a; + return __generator$3(this, function (_b) { + switch (_b.label) { + case 0: + if (preload) { + preload = false; + for (_i = 0, preloadedContextList_1 = preloadedContextList; _i < preloadedContextList_1.length; _i++) { + url_1 = preloadedContextList_1[_i]; + contexts.set(url_1, fetch__default['default'](url_1, { headers: { accept: "application/json" } }).then(function (res) { return res.json(); })); + } + } + if (!contexts.get(url)) return [3 /*break*/, 2]; + promise = contexts.get(url); + _a = { + contextUrl: undefined + }; + return [4 /*yield*/, promise]; + case 1: return [2 /*return*/, (_a.document = _b.sent(), + _a.documentUrl = url, + _a)]; + case 2: + promise = nodeDocumentLoader(url); + contexts.set(url, promise.then(function (_a) { + var document = _a.document; + return document; + })); + return [2 /*return*/, promise]; + } + }); + }); }; + function validateW3C(credential) { + return __awaiter$3(this, void 0, void 0, function () { + var issuerId; + return __generator$3(this, function (_a) { + switch (_a.label) { + case 0: + // ensure first context is 'https://www.w3.org/2018/credentials/v1' as it's mandatory, see https://www.w3.org/TR/vc-data-model/#contexts + if (!Array.isArray(credential["@context"]) || + (Array.isArray(credential["@context"]) && credential["@context"][0] !== "https://www.w3.org/2018/credentials/v1")) { + throw new Error("https://www.w3.org/2018/credentials/v1 needs to be first in the list of contexts"); + } + issuerId = getId(credential.issuer); + if (!isValidRFC3986(issuerId)) { + throw new Error("Property 'issuer' id must be a valid RFC 3986 URI"); + } + // ensure issuanceDate is a valid RFC3339 date, see https://www.w3.org/TR/vc-data-model/#issuance-date + if (!isValidRFC3339(credential.issuanceDate)) { + throw new Error("Property 'issuanceDate' must be a valid RFC 3339 date"); + } + // ensure expirationDate is a valid RFC3339 date, see https://www.w3.org/TR/vc-data-model/#expiration + if (credential.expirationDate && !isValidRFC3339(credential.expirationDate)) { + throw new Error("Property 'expirationDate' must be a valid RFC 3339 date"); + } + // https://www.w3.org/TR/vc-data-model/#types + if (!credential.type || !Array.isArray(credential.type)) { + throw new Error("Property 'type' must exist and be an array"); + } + if (!credential.type.includes("VerifiableCredential")) { + throw new Error("Property 'type' must have VerifiableCredential as one of the items"); + } + return [4 /*yield*/, jsonld.expand(credential, { + expansionMap: function (info) { + if (info.unmappedProperty) { + throw new Error("\"The property " + (info.activeProperty ? info.activeProperty + "." : "") + info.unmappedProperty + " in the input was not defined in the context\""); + } + }, + documentLoader: documentLoader, + })]; + case 1: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + } + + var __assign$3 = (undefined && undefined.__assign) || function () { + __assign$3 = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign$3.apply(this, arguments); + }; + var __awaiter$2 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); + }; + var __generator$2 = (undefined && undefined.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } + }; + var getExternalSchema = function (schema) { return (schema ? { schema: schema } : {}); }; + var wrapDocument$1 = function (credential, options) { return __awaiter$2(void 0, void 0, void 0, function () { + var document, salts, digest, batchBuffers, merkleTree, merkleRoot, merkleProof, verifiableCredential, errors; + return __generator$2(this, function (_a) { + switch (_a.label) { + case 0: + document = __assign$3(__assign$3({ version: exports.SchemaId.v3 }, getExternalSchema(options.externalSchemaId)), credential); + // To ensure that base @context exists, but this also means some of our validateW3C errors may be unreachable + if (!document["@context"]) { + document["@context"] = ["https://www.w3.org/2018/credentials/v1"]; + } + // Since our wrapper adds in OA-specific properties, we should push our OA context. This is also to pass W3C VC test suite. + if (Array.isArray(document["@context"]) && + !document["@context"].includes("https://schemata.openattestation.com/com/openattestation/1.0/OpenAttestation.v3.json")) { + document["@context"].push("https://schemata.openattestation.com/com/openattestation/1.0/OpenAttestation.v3.json"); + } + salts = salt(document); + digest = digestCredential(document, salts, []); + batchBuffers = [digest].map(hashToBuffer); + merkleTree = new MerkleTree(batchBuffers); + merkleRoot = merkleTree.getRoot().toString("hex"); + merkleProof = merkleTree.getProof(hashToBuffer(digest)).map(function (buffer) { return buffer.toString("hex"); }); + verifiableCredential = __assign$3(__assign$3({}, document), { proof: { + type: "OpenAttestationMerkleProofSignature2018", + proofPurpose: "assertionMethod", + targetHash: digest, + proofs: merkleProof, + merkleRoot: merkleRoot, + salts: encodeSalt(salts), + privacy: { + obfuscated: [], + }, + } }); + errors = validateSchema$1(verifiableCredential, getSchema(exports.SchemaId.v3)); + if (errors.length > 0) { + throw new SchemaValidationError("Invalid document", errors, verifiableCredential); + } + return [4 /*yield*/, validateW3C(verifiableCredential)]; + case 1: + _a.sent(); + return [2 /*return*/, verifiableCredential]; + } + }); + }); }; + var wrapDocuments$1 = function (documents, options) { return __awaiter$2(void 0, void 0, void 0, function () { + var verifiableCredentials, merkleTree, merkleRoot; + return __generator$2(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, Promise.all(documents.map(function (document) { return wrapDocument$1(document, options); }))]; + case 1: + verifiableCredentials = _a.sent(); + merkleTree = new MerkleTree(verifiableCredentials.map(function (verifiableCredential) { return verifiableCredential.proof.targetHash; }).map(hashToBuffer)); + merkleRoot = merkleTree.getRoot().toString("hex"); + // for each document, update the merkle root and add the proofs needed + return [2 /*return*/, verifiableCredentials.map(function (verifiableCredential) { + var digest = verifiableCredential.proof.targetHash; + var merkleProof = merkleTree.getProof(hashToBuffer(digest)).map(function (buffer) { return buffer.toString("hex"); }); + return __assign$3(__assign$3({}, verifiableCredential), { proof: __assign$3(__assign$3({}, verifiableCredential.proof), { proofs: merkleProof, merkleRoot: merkleRoot }) }); + })]; + } + }); + }); }; + + var __assign$2 = (undefined && undefined.__assign) || function () { + __assign$2 = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign$2.apply(this, arguments); + }; + var __awaiter$1 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); + }; + var __generator$1 = (undefined && undefined.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } + }; + var signDocument$1 = function (document, algorithm, keyOrSigner) { return __awaiter$1(void 0, void 0, void 0, function () { + var merkleRoot, signature, proof, _a, _b, _c; + var _d; + return __generator$1(this, function (_e) { + switch (_e.label) { + case 0: + if (isSignedWrappedV3Document(document)) + throw new Error("Document has been signed"); + merkleRoot = "0x" + document.proof.merkleRoot; + return [4 /*yield*/, sign(algorithm, merkleRoot, keyOrSigner)]; + case 1: + signature = _e.sent(); + _a = [__assign$2({}, document.proof)]; + _d = {}; + if (!SigningKey.guard(keyOrSigner)) return [3 /*break*/, 2]; + _b = keyOrSigner.public; + return [3 /*break*/, 4]; + case 2: + _c = "did:ethr:"; + return [4 /*yield*/, keyOrSigner.getAddress()]; + case 3: + _b = _c + (_e.sent()) + "#controller"; + _e.label = 4; + case 4: + proof = __assign$2.apply(void 0, _a.concat([(_d.key = _b, _d.signature = signature, _d)])); + return [2 /*return*/, __assign$2(__assign$2({}, document), { proof: proof })]; + } + }); + }); }; + + var __assign$1 = (undefined && undefined.__assign) || function () { + __assign$1 = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign$1.apply(this, arguments); + }; + var obfuscateData = function (_data, fields) { + var data = lodash.cloneDeep(_data); // Prevents alteration of original data + var fieldsToRemove = Array.isArray(fields) ? fields : [fields]; + // Obfuscate data by hashing them with the key + var dataToObfuscate = flatten(lodash.pick(data, fieldsToRemove)); + var obfuscatedData = Object.keys(dataToObfuscate).map(function (k) { + var obj = {}; + obj[k] = dataToObfuscate[k]; + return toBuffer(obj).toString("hex"); + }); + // Return remaining data + fieldsToRemove.forEach(function (path) { + lodash.unset(data, path); + }); + return { + data: data, + obfuscatedData: obfuscatedData, + }; + }; + // TODO the return type could be improve by using Exclude eventually to remove the obfuscated properties + var obfuscateDocument = function (document, fields) { + var _a, _b; + var existingData = document.data; + var _c = obfuscateData(existingData, fields), data = _c.data, obfuscatedData = _c.obfuscatedData; + var currentObfuscatedData = (_b = (_a = document === null || document === void 0 ? void 0 : document.privacy) === null || _a === void 0 ? void 0 : _a.obfuscatedData) !== null && _b !== void 0 ? _b : []; + var newObfuscatedData = currentObfuscatedData.concat(obfuscatedData); + return __assign$1(__assign$1({}, document), { data: data, privacy: __assign$1(__assign$1({}, document.privacy), { obfuscatedData: newObfuscatedData }) }); + }; + + var __assign = (undefined && undefined.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); + }; + var obfuscate$1 = function (_data, fields) { + var data = lodash.cloneDeep(_data); // Prevents alteration of original data + var fieldsAsArray = [].concat(fields); + // fields to remove will contain the list of each expanded keys from the fields passed in parameter, it's for instance useful in case of + // object obfuscation, where the object itself is not part of the salts, but each individual keys are + var fieldsToRemove = traverseAndFlatten(lodash.pick(data, fieldsAsArray), { + iteratee: function (_a) { + var path = _a.path; + return path; + }, + }); + var salts = decodeSalt(data.proof.salts); + // Obfuscate data by hashing them with the key + var obfuscatedData = fieldsToRemove.map(function (field) { + var _a; + var value = lodash.get(data, field); + var salt = salts.find(function (s) { return s.path === field; }); + if (!salt) { + throw new Error("Salt not found for " + field); + } + return toBuffer((_a = {}, _a[salt.path] = salt.value + ":" + value, _a)).toString("hex"); + }); + // remove fields from the object + fieldsAsArray.forEach(function (field) { return lodash.unset(data, field); }); + data.proof.salts = encodeSalt(salts.filter(function (s) { return !fieldsToRemove.includes(s.path); })); + return { + data: data, + obfuscatedData: obfuscatedData, + }; + }; + var obfuscateVerifiableCredential = function (document, fields) { + var _a = obfuscate$1(document, fields), data = _a.data, obfuscatedData = _a.obfuscatedData; + var currentObfuscatedData = document.proof.privacy.obfuscated; + var newObfuscatedData = currentObfuscatedData.concat(obfuscatedData); + return __assign(__assign({}, data), { proof: __assign(__assign({}, data.proof), { privacy: __assign(__assign({}, data.proof.privacy), { obfuscated: newObfuscatedData }) }) }); + }; + + var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); + }; + var __generator = (undefined && undefined.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } + }; + function __unsafe__use__it__at__your__own__risks__wrapDocument(data, options) { + return wrapDocument$1(data, options !== null && options !== void 0 ? options : { version: exports.SchemaId.v3 }); + } + function __unsafe__use__it__at__your__own__risks__wrapDocuments(dataArray, options) { + return wrapDocuments$1(dataArray, options !== null && options !== void 0 ? options : { version: exports.SchemaId.v3 }); + } + function wrapDocument(data, options) { + return wrapDocument$2(data, { externalSchemaId: options === null || options === void 0 ? void 0 : options.externalSchemaId }); + } + function wrapDocuments(dataArray, options) { + return wrapDocuments$2(dataArray, { externalSchemaId: options === null || options === void 0 ? void 0 : options.externalSchemaId }); + } + var validateSchema = function (document) { + return validateSchema$1(document, getSchema("" + ((document === null || document === void 0 ? void 0 : document.version) || exports.SchemaId.v2))).length === 0; + }; + function verifySignature(document) { + return isWrappedV3Document(document) ? verify(document) : verify$1(document); + } + function obfuscate(document, fields) { + return document.version === exports.SchemaId.v3 + ? obfuscateVerifiableCredential(document, fields) + : obfuscateDocument(document, fields); + } + var isSchemaValidationError = function (error) { + return !!error.validationErrors; + }; + function signDocument(document, algorithm, keyOrSigner) { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + // rj was worried it could happen deep in the code, so I moved it to the boundaries + if (!SigningKey.guard(keyOrSigner) && !ethers.Signer.isSigner(keyOrSigner)) { + throw new Error("Either a keypair or ethers.js Signer must be provided"); + } + switch (true) { + case isWrappedV2Document(document): + return [2 /*return*/, signDocument$2(document, algorithm, keyOrSigner)]; + case isWrappedV3Document(document): + return [2 /*return*/, signDocument$1(document, algorithm, keyOrSigner)]; + default: + // Unreachable code atm until utils.isWrappedV2Document & utils.isWrappedV3Document becomes more strict + throw new Error("Unsupported document type: Only OpenAttestation v2 & v3 documents can be signed"); + } + }); + }); + } + + exports.MerkleTree = MerkleTree; + exports.OpenAttestationHexString = OpenAttestationHexString; + exports.ProofPurpose = ProofPurpose; + exports.ProofType = ProofType$1; + exports.SignatureAlgorithm = SignatureAlgorithm; + exports.SigningKey = SigningKey; + exports.__unsafe__use__it__at__your__own__risks__wrapDocument = __unsafe__use__it__at__your__own__risks__wrapDocument; + exports.__unsafe__use__it__at__your__own__risks__wrapDocuments = __unsafe__use__it__at__your__own__risks__wrapDocuments; + exports.checkProof = checkProof; + exports.defaultSigners = defaultSigners; + exports.digestCredential = digestCredential; + exports.digestDocument = digestDocument; + exports.getData = getData; + exports.isSchemaValidationError = isSchemaValidationError; + exports.obfuscate = obfuscate; + exports.obfuscateDocument = obfuscate; + exports.sign = sign; + exports.signDocument = signDocument; + exports.signerBuilder = signerBuilder; + exports.utils = index; + exports.v2 = types; + exports.v3 = types$1; + exports.validateSchema = validateSchema; + exports.verifySignature = verifySignature; + exports.wrapDocument = wrapDocument; + exports.wrapDocuments = wrapDocuments; + + Object.defineProperty(exports, '__esModule', { value: true }); + +}))); diff --git a/dist/types/2.0/digest.d.ts b/dist/types/2.0/digest.d.ts new file mode 100644 index 00000000..c390ffe1 --- /dev/null +++ b/dist/types/2.0/digest.d.ts @@ -0,0 +1,4 @@ +import { OpenAttestationDocument } from "../__generated__/schema.2.0"; +import { SchematisedDocument } from "./types"; +export declare const flattenHashArray: (data: any) => string[]; +export declare const digestDocument: (document: SchematisedDocument) => string; diff --git a/dist/types/2.0/obfuscate.d.ts b/dist/types/2.0/obfuscate.d.ts new file mode 100644 index 00000000..e23eac4d --- /dev/null +++ b/dist/types/2.0/obfuscate.d.ts @@ -0,0 +1,7 @@ +import { OpenAttestationDocument } from "../__generated__/schema.2.0"; +import { WrappedDocument } from "./types"; +export declare const obfuscateData: (_data: any, fields: string[] | string) => { + data: any; + obfuscatedData: string[]; +}; +export declare const obfuscateDocument: (document: WrappedDocument, fields: string[] | string) => WrappedDocument; diff --git a/dist/types/2.0/salt.d.ts b/dist/types/2.0/salt.d.ts new file mode 100644 index 00000000..3eb44d24 --- /dev/null +++ b/dist/types/2.0/salt.d.ts @@ -0,0 +1,24 @@ +/** + * Applies `iteratee` to all fields in objects, goes into arrays as well. + * Refer to test for example + */ +export declare const deepMap: (collection: any, iteratee?: (arg: any) => any) => any; +/** + * Detects the type of a value and returns a string with type annotation + */ +export declare function primitiveToTypedString(value: any): string; +/** + * Returns an appropriately typed value given a string with type annotations, e.g: "number:5" + */ +export declare function typedStringToPrimitive(input: string): string | number | boolean | null | undefined; +/** + * Returns a salted value using a randomly generated uuidv4 string for salt + */ +export declare function uuidSalt(value: string): string; +/** + * Value salted string in the format "salt:type:value", example: "ee7f3323-1634-4dea-8c12-f0bb83aff874:number:5" + * Returns an appropriately typed value when given a salted string with type annotation + */ +export declare function unsalt(value: string): string | number | boolean | null | undefined; +export declare const saltData: (data: any) => any; +export declare const unsaltData: (data: any) => any; diff --git a/dist/types/2.0/sign.d.ts b/dist/types/2.0/sign.d.ts new file mode 100644 index 00000000..46fec9c5 --- /dev/null +++ b/dist/types/2.0/sign.d.ts @@ -0,0 +1,5 @@ +import { WrappedDocument } from "./types"; +import { OpenAttestationDocument, SignedWrappedDocument } from "./types"; +import { SigningKey, SUPPORTED_SIGNING_ALGORITHM } from "../shared/@types/sign"; +import { ethers } from "ethers"; +export declare const signDocument: (document: SignedWrappedDocument | WrappedDocument, algorithm: SUPPORTED_SIGNING_ALGORITHM, keyOrSigner: ethers.Signer | SigningKey) => Promise>; diff --git a/dist/types/2.0/types.d.ts b/dist/types/2.0/types.d.ts new file mode 100644 index 00000000..7b6a2470 --- /dev/null +++ b/dist/types/2.0/types.d.ts @@ -0,0 +1,57 @@ +import { OpenAttestationDocument as OpenAttestationDocumentV2 } from "../__generated__/schema.2.0"; +import { SchemaId } from "../shared/@types/document"; +import { Array as RunTypesArray, Literal, Partial, Record as RunTypesRecord, Static, String } from "runtypes"; +export declare const ObfuscationMetadata: Partial<{ + obfuscatedData: RunTypesArray, false>; +}, false>; +export declare type ObfuscationMetadata = Static; +export declare const Proof: RunTypesRecord<{ + type: Literal<"OpenAttestationSignature2018">; + created: String; + proofPurpose: Literal<"assertionMethod">; + verificationMethod: String; + signature: String; +}, false>; +export declare type Proof = Static; +export declare const ArrayProof: RunTypesArray; + created: String; + proofPurpose: Literal<"assertionMethod">; + verificationMethod: String; + signature: String; +}, false>, false>; +export declare type ArrayProof = Static; +export declare const Signature: RunTypesRecord<{ + type: Literal<"SHA3MerkleProof">; + targetHash: String; + merkleRoot: String; + proof: RunTypesArray; +}, false>; +export declare type Signature = Static; +export declare const SignatureStrict: import("runtypes").Intersect<[RunTypesRecord<{ + type: Literal<"SHA3MerkleProof">; + targetHash: String; + merkleRoot: String; + proof: RunTypesArray; +}, false>, RunTypesRecord<{ + targetHash: import("runtypes").Constraint; + merkleRoot: import("runtypes").Constraint; + proof: RunTypesArray, false>; +}, false>]>; +export declare type SignatureStrict = Static; +export interface SchematisedDocument { + version: SchemaId; + data: DeepStringify; + schema?: string; + privacy?: ObfuscationMetadata; +} +export interface WrappedDocument extends SchematisedDocument { + signature: Signature; +} +export interface SignedWrappedDocument extends WrappedDocument { + proof: Proof[]; +} +export declare type DeepStringify = { + [P in keyof T]: T[P] extends Array ? Array : T[P] extends Array ? Array : T[P] extends Record ? DeepStringify : T[P] extends Array> ? DeepStringify : number extends T[P] ? string : undefined extends T[P] ? DeepStringify : T[P] extends string ? string : DeepStringify; +}; +export * from "../__generated__/schema.2.0"; diff --git a/dist/types/2.0/verify.d.ts b/dist/types/2.0/verify.d.ts new file mode 100644 index 00000000..c160f0e6 --- /dev/null +++ b/dist/types/2.0/verify.d.ts @@ -0,0 +1,3 @@ +import { OpenAttestationDocument } from "../__generated__/schema.2.0"; +import { WrappedDocument } from "./types"; +export declare const verify: (document: any) => document is WrappedDocument; diff --git a/dist/types/2.0/wrap.d.ts b/dist/types/2.0/wrap.d.ts new file mode 100644 index 00000000..1422bb60 --- /dev/null +++ b/dist/types/2.0/wrap.d.ts @@ -0,0 +1,5 @@ +import { WrappedDocument } from "./types"; +import { OpenAttestationDocument } from "../__generated__/schema.2.0"; +import { WrapDocumentOptionV2 } from "../shared/@types/wrap"; +export declare const wrapDocument: (data: T, options?: WrapDocumentOptionV2 | undefined) => WrappedDocument; +export declare const wrapDocuments: (data: T[], options?: WrapDocumentOptionV2 | undefined) => WrappedDocument[]; diff --git a/dist/types/3.0/digest.d.ts b/dist/types/3.0/digest.d.ts new file mode 100644 index 00000000..f21715fc --- /dev/null +++ b/dist/types/3.0/digest.d.ts @@ -0,0 +1,3 @@ +import { Salt } from "./types"; +import { OpenAttestationDocument } from "../__generated__/schema.3.0"; +export declare const digestCredential: (document: OpenAttestationDocument, salts: Salt[], obfuscatedData: string[]) => string; diff --git a/dist/types/3.0/obfuscate.d.ts b/dist/types/3.0/obfuscate.d.ts new file mode 100644 index 00000000..a2631af3 --- /dev/null +++ b/dist/types/3.0/obfuscate.d.ts @@ -0,0 +1,3 @@ +import { OpenAttestationDocument } from "../__generated__/schema.3.0"; +import { WrappedDocument } from "./types"; +export declare const obfuscateVerifiableCredential: (document: WrappedDocument, fields: string[] | string) => WrappedDocument; diff --git a/dist/types/3.0/salt.d.ts b/dist/types/3.0/salt.d.ts new file mode 100644 index 00000000..44be8455 --- /dev/null +++ b/dist/types/3.0/salt.d.ts @@ -0,0 +1,5 @@ +import { Salt } from "./types"; +export declare const secureRandomString: () => string; +export declare const salt: (data: any) => Salt[]; +export declare const encodeSalt: (salts: Salt[]) => string; +export declare const decodeSalt: (salts: string) => Salt[]; diff --git a/dist/types/3.0/sign.d.ts b/dist/types/3.0/sign.d.ts new file mode 100644 index 00000000..dd7a43a2 --- /dev/null +++ b/dist/types/3.0/sign.d.ts @@ -0,0 +1,4 @@ +import { OpenAttestationDocument, SignedWrappedDocument, WrappedDocument } from "./types"; +import { SigningKey, SUPPORTED_SIGNING_ALGORITHM } from "../shared/@types/sign"; +import { ethers } from "ethers"; +export declare const signDocument: (document: SignedWrappedDocument | WrappedDocument, algorithm: SUPPORTED_SIGNING_ALGORITHM, keyOrSigner: SigningKey | ethers.Signer) => Promise>; diff --git a/dist/types/3.0/traverseAndFlatten.d.ts b/dist/types/3.0/traverseAndFlatten.d.ts new file mode 100644 index 00000000..bb743b04 --- /dev/null +++ b/dist/types/3.0/traverseAndFlatten.d.ts @@ -0,0 +1,12 @@ +import { Options } from "jsonld"; +interface Options { + iteratee: (data: { + value: any; + path: string; + }) => T; + path?: string; +} +export declare function traverseAndFlatten(data: any[], options: Options): T[]; +export declare function traverseAndFlatten(data: string | number | boolean | null, options: Options): T; +export declare function traverseAndFlatten(data: any, options: Options): T[]; +export {}; diff --git a/dist/types/3.0/types.d.ts b/dist/types/3.0/types.d.ts new file mode 100644 index 00000000..426f679a --- /dev/null +++ b/dist/types/3.0/types.d.ts @@ -0,0 +1,63 @@ +import { OpenAttestationDocument as OpenAttestationDocumentV3 } from "../__generated__/schema.3.0"; +import { SchemaId } from "../shared/@types/document"; +import { Array as RunTypesArray, Record as RunTypesRecord, Static, String } from "runtypes"; +export interface Salt { + value: string; + path: string; +} +export declare const ObfuscationMetadata: RunTypesRecord<{ + obfuscated: RunTypesArray, false>; +}, false>; +export declare type ObfuscationMetadata = Static; +export declare const VerifiableCredentialWrappedProof: RunTypesRecord<{ + type: import("runtypes").Literal<"OpenAttestationMerkleProofSignature2018">; + targetHash: String; + merkleRoot: String; + proofs: RunTypesArray; + salts: String; + privacy: RunTypesRecord<{ + obfuscated: RunTypesArray, false>; + }, false>; + proofPurpose: import("runtypes").Literal<"assertionMethod">; +}, false>; +export declare type VerifiableCredentialWrappedProof = Static; +export declare const VerifiableCredentialWrappedProofStrict: import("runtypes").Intersect<[RunTypesRecord<{ + type: import("runtypes").Literal<"OpenAttestationMerkleProofSignature2018">; + targetHash: String; + merkleRoot: String; + proofs: RunTypesArray; + salts: String; + privacy: RunTypesRecord<{ + obfuscated: RunTypesArray, false>; + }, false>; + proofPurpose: import("runtypes").Literal<"assertionMethod">; +}, false>, RunTypesRecord<{ + targetHash: import("runtypes").Constraint; + merkleRoot: import("runtypes").Constraint; + proofs: RunTypesArray, false>; +}, false>]>; +export declare type VerifiableCredentialWrappedProofStrict = Static; +export declare const VerifiableCredentialSignedProof: import("runtypes").Intersect<[RunTypesRecord<{ + type: import("runtypes").Literal<"OpenAttestationMerkleProofSignature2018">; + targetHash: String; + merkleRoot: String; + proofs: RunTypesArray; + salts: String; + privacy: RunTypesRecord<{ + obfuscated: RunTypesArray, false>; + }, false>; + proofPurpose: import("runtypes").Literal<"assertionMethod">; +}, false>, RunTypesRecord<{ + key: String; + signature: String; +}, false>]>; +export declare type VerifiableCredentialSignedProof = Static; +export declare type WrappedDocument = T & { + version: SchemaId.v3; + schema?: string; + proof: VerifiableCredentialWrappedProof; +}; +export declare type SignedWrappedDocument = WrappedDocument & { + proof: VerifiableCredentialSignedProof; +}; +export * from "../__generated__/schema.3.0"; diff --git a/dist/types/3.0/validate/__mocks__/validate.d.ts b/dist/types/3.0/validate/__mocks__/validate.d.ts new file mode 100644 index 00000000..692c5ecd --- /dev/null +++ b/dist/types/3.0/validate/__mocks__/validate.d.ts @@ -0,0 +1 @@ +export declare const validateW3C: () => Promise; diff --git a/dist/types/3.0/validate/index.d.ts b/dist/types/3.0/validate/index.d.ts new file mode 100644 index 00000000..13529009 --- /dev/null +++ b/dist/types/3.0/validate/index.d.ts @@ -0,0 +1 @@ +export * from "./validate"; diff --git a/dist/types/3.0/validate/validate.d.ts b/dist/types/3.0/validate/validate.d.ts new file mode 100644 index 00000000..21b4f2db --- /dev/null +++ b/dist/types/3.0/validate/validate.d.ts @@ -0,0 +1,3 @@ +import { OpenAttestationDocument } from "../../__generated__/schema.3.0"; +import { WrappedDocument } from "../../3.0/types"; +export declare function validateW3C(credential: WrappedDocument): Promise; diff --git a/dist/types/3.0/verify.d.ts b/dist/types/3.0/verify.d.ts new file mode 100644 index 00000000..d731c833 --- /dev/null +++ b/dist/types/3.0/verify.d.ts @@ -0,0 +1,2 @@ +import { WrappedDocument } from "./types"; +export declare const verify: >(document: T) => document is WrappedDocument; diff --git a/dist/types/3.0/wrap.d.ts b/dist/types/3.0/wrap.d.ts new file mode 100644 index 00000000..5c0631bb --- /dev/null +++ b/dist/types/3.0/wrap.d.ts @@ -0,0 +1,5 @@ +import { WrappedDocument } from "./types"; +import { WrapDocumentOptionV3 } from "../shared/@types/wrap"; +import { OpenAttestationDocument } from "../__generated__/schema.3.0"; +export declare const wrapDocument: (credential: T, options: WrapDocumentOptionV3) => Promise>; +export declare const wrapDocuments: (documents: T[], options: WrapDocumentOptionV3) => Promise[]>; diff --git a/dist/types/__generated__/schema.2.0.d.ts b/dist/types/__generated__/schema.2.0.d.ts new file mode 100644 index 00000000..b1024420 --- /dev/null +++ b/dist/types/__generated__/schema.2.0.d.ts @@ -0,0 +1,106 @@ +export interface OpenAttestationDocument { + $template?: TemplateObject | string; + attachments?: Attachment[]; + /** + * URL of the stored document + */ + documentUrl?: string; + /** + * Internal reference, usually serial number, of this document + */ + id?: string; + issuers: Issuer[]; + recipient?: Recipient; +} +export interface TemplateObject { + /** + * Template name to be use by template renderer to determine the template to use + */ + name: string; + /** + * Type of renderer template + */ + type: TemplateType; + /** + * URL of a decentralised renderer to render this document + */ + url?: string; +} +/** + * Type of renderer template + */ +export declare enum TemplateType { + EmbeddedRenderer = "EMBEDDED_RENDERER" +} +export interface Attachment { + /** + * Base64 encoding of attachment + */ + data: string; + /** + * Name of attachment, with appropriate extensions + */ + filename: string; + /** + * Type of attachment + */ + type: string; +} +export interface Issuer { + /** + * Issuer's id, DID can be used + */ + id?: string; + identityProof?: IdentityProof; + /** + * Issuer's name + */ + name: string; + revocation?: Revocation; + /** + * Smart contract address of token registry + */ + tokenRegistry?: string; + /** + * Smart contract address of document store + */ + documentStore?: string; + /** + * Smart contract address of certificate store. Same as documentStore + */ + certificateStore?: string; +} +export interface IdentityProof { + /** + * Url of the website referencing to document store + */ + location?: string; + type: IdentityProofType; + /** + * Public key associated + */ + key?: string; +} +export declare enum IdentityProofType { + DNSDid = "DNS-DID", + DNSTxt = "DNS-TXT", + Did = "DID" +} +export interface Revocation { + /** + * Smart contract address or url of certificate revocation list for Revocation Store type + * revocation + */ + location?: string; + type?: RevocationType; +} +export declare enum RevocationType { + None = "NONE", + RevocationStore = "REVOCATION_STORE" +} +export interface Recipient { + /** + * Recipient's name + */ + name?: string; +} diff --git a/dist/types/__generated__/schema.3.0.d.ts b/dist/types/__generated__/schema.3.0.d.ts new file mode 100644 index 00000000..31d71490 --- /dev/null +++ b/dist/types/__generated__/schema.3.0.d.ts @@ -0,0 +1,178 @@ +export interface OpenAttestationDocument { + /** + * List of URI to determine the terminology used in the verifiable credential as explained + * by https://www.w3.org/TR/vc-data-model/#contexts + */ + "@context": Array<{ + [key: string]: any; + } | string>; + attachments?: Attachment[]; + credentialStatus?: CredentialStatus; + credentialSubject: { + [key: string]: any; + }[] | { + [key: string]: any; + }; + /** + * The date and time when this credential expires + */ + expirationDate?: string; + /** + * URI to the subject of the credential as explained by + * https://www.w3.org/TR/vc-data-model/#credential-subject + */ + id?: string; + /** + * The date and time when this credential becomes valid (may be deprecated in favor of + * issued/validFrom a future version of W3C's VC Data Model) + */ + issuanceDate: string; + /** + * The date and time when this credential becomes valid + */ + issued?: string; + issuer: Issuer | string; + /** + * Human readable name of this credential + */ + name?: string; + openAttestationMetadata: OpenAttestationMetadata; + /** + * Internal reference, usually a serial number, of this document + */ + reference?: string; + /** + * Specific verifiable credential type as explained by + * https://www.w3.org/TR/vc-data-model/#types + */ + type: string[] | string; + /** + * The date and time when this credential becomes valid + */ + validFrom?: string; + /** + * The date and time when this credential expires + */ + validUntil?: string; +} +export interface Attachment { + /** + * Base64 encoding of this attachment + */ + data: string; + /** + * Name of this attachment, with appropriate extensions + */ + fileName: string; + /** + * Media type (or MIME type) of this attachment + */ + mimeType: string; +} +export interface CredentialStatus { + id: string; + /** + * Express the credential status type (also referred to as the credential status method). It + * is expected that the value will provide enough information to determine the current + * status of the credential. For example, the object could contain a link to an external + * document noting whether or not the credential is suspended or revoked. + */ + type: string; +} +export interface Issuer { + /** + * URI when dereferenced, results in a document containing machine-readable information + * about the issuer that can be used to verify the information expressed in the credential. + * More information in https://www.w3.org/TR/vc-data-model/#issuer + */ + id: string; + /** + * Issuer's name + */ + name: string; +} +export interface OpenAttestationMetadata { + identityProof: IdentityProof; + proof: Proof; + template?: Template; +} +export interface IdentityProof { + /** + * Identifier to be shown to end user upon verifying the identity + */ + identifier: string; + type: IdentityProofType; +} +export declare enum IdentityProofType { + DNSDid = "DNS-DID", + DNSTxt = "DNS-TXT", + Did = "DID" +} +export interface Proof { + /** + * Proof Open Attestation method + */ + method: Method; + revocation?: Revocation; + /** + * Proof method name as explained by https://www.w3.org/TR/vc-data-model/#types + */ + type: ProofType; + /** + * Proof value of issuer(s). Smart contract address for TOKEN_REGISTRY & DOCUMENT_STORE, did + * for DID method + */ + value: string; +} +/** + * Proof Open Attestation method + */ +export declare enum Method { + Did = "DID", + DocumentStore = "DOCUMENT_STORE", + TokenRegistry = "TOKEN_REGISTRY" +} +export interface Revocation { + /** + * Smart contract address or url of certificate revocation list for Revocation Store type + * revocation + */ + location?: string; + /** + * Revocation method (if required by proof method) + */ + type: RevocationType; +} +/** + * Revocation method (if required by proof method) + */ +export declare enum RevocationType { + None = "NONE", + RevocationStore = "REVOCATION_STORE" +} +/** + * Proof method name as explained by https://www.w3.org/TR/vc-data-model/#types + */ +export declare enum ProofType { + OpenAttestationProofMethod = "OpenAttestationProofMethod" +} +export interface Template { + /** + * Template name to be use by template renderer to determine the template to use + */ + name: string; + /** + * Type of renderer template + */ + type: TemplateType; + /** + * URL of a decentralised renderer to render this document + */ + url: string; +} +/** + * Type of renderer template + */ +export declare enum TemplateType { + EmbeddedRenderer = "EMBEDDED_RENDERER" +} diff --git a/dist/types/index.d.ts b/dist/types/index.d.ts new file mode 100644 index 00000000..278edcb6 --- /dev/null +++ b/dist/types/index.d.ts @@ -0,0 +1,33 @@ +import { WrappedDocument as WrappedDocumentV2 } from "./2.0/types"; +import { WrappedDocument as WrappedDocumentV3 } from "./3.0/types"; +import { WrappedDocument, OpenAttestationDocument } from "./shared/@types/document"; +import * as utils from "./shared/utils"; +import * as v2 from "./2.0/types"; +import { OpenAttestationDocument as OpenAttestationDocumentV2 } from "./__generated__/schema.2.0"; +import * as v3 from "./3.0/types"; +import { OpenAttestationDocument as OpenAttestationDocumentV3 } from "./__generated__/schema.3.0"; +import { WrapDocumentOptionV2, WrapDocumentOptionV3 } from "./shared/@types/wrap"; +import { SigningKey, SUPPORTED_SIGNING_ALGORITHM } from "./shared/@types/sign"; +import { ethers } from "ethers"; +export declare function __unsafe__use__it__at__your__own__risks__wrapDocument(data: T, options?: WrapDocumentOptionV3): Promise>; +export declare function __unsafe__use__it__at__your__own__risks__wrapDocuments(dataArray: T[], options?: WrapDocumentOptionV3): Promise[]>; +export declare function wrapDocument(data: T, options?: WrapDocumentOptionV2): WrappedDocumentV2; +export declare function wrapDocuments(dataArray: T[], options?: WrapDocumentOptionV2): WrappedDocumentV2[]; +export declare const validateSchema: (document: WrappedDocument) => boolean; +export declare function verifySignature>(document: T): boolean; +export declare function obfuscate(document: WrappedDocument, fields: string[] | string): WrappedDocument; +export declare function obfuscate(document: WrappedDocument, fields: string[] | string): WrappedDocument; +export declare const isSchemaValidationError: (error: any) => error is utils.SchemaValidationError; +export declare function signDocument(document: v3.SignedWrappedDocument | v3.WrappedDocument, algorithm: SUPPORTED_SIGNING_ALGORITHM, keyOrSigner: SigningKey | ethers.Signer): Promise>; +export declare function signDocument(document: v2.SignedWrappedDocument | v2.WrappedDocument, algorithm: SUPPORTED_SIGNING_ALGORITHM, keyOrSigner: SigningKey | ethers.Signer): Promise>; +export { digestDocument } from "./2.0/digest"; +export { digestCredential } from "./3.0/digest"; +export { checkProof, MerkleTree } from "./shared/merkle"; +export { obfuscate as obfuscateDocument }; +export { utils }; +export * from "./shared/@types/document"; +export * from "./shared/@types/sign"; +export * from "./shared/signer"; +export { getData } from "./shared/utils"; +export { v2 }; +export { v3 }; diff --git a/dist/types/shared/@types/document.d.ts b/dist/types/shared/@types/document.d.ts new file mode 100644 index 00000000..b089fc80 --- /dev/null +++ b/dist/types/shared/@types/document.d.ts @@ -0,0 +1,19 @@ +import { OpenAttestationDocument as OpenAttestationDocumentV2 } from "../../__generated__/schema.2.0"; +import { OpenAttestationDocument as OpenAttestationDocumentV3 } from "../../__generated__/schema.3.0"; +import { SignedWrappedDocument as SignedWrappedDocumentV2, WrappedDocument as WrappedDocumentV2 } from "../../2.0/types"; +import { SignedWrappedDocument as SignedWrappedDocumentV3, WrappedDocument as WrappedDocumentV3 } from "../../3.0/types"; +import { Literal, Static, String } from "runtypes"; +export declare type OpenAttestationDocument = OpenAttestationDocumentV2 | OpenAttestationDocumentV3; +export declare type WrappedDocument = T extends OpenAttestationDocumentV2 ? WrappedDocumentV2 : T extends OpenAttestationDocumentV3 ? WrappedDocumentV3 : unknown; +export declare type SignedWrappedDocument = T extends OpenAttestationDocumentV2 ? SignedWrappedDocumentV2 : T extends OpenAttestationDocumentV3 ? SignedWrappedDocumentV3 : unknown; +export declare enum SchemaId { + v2 = "https://schema.openattestation.com/2.0/schema.json", + v3 = "https://schema.openattestation.com/3.0/schema.json" +} +export declare const OpenAttestationHexString: import("runtypes").Constraint; +export declare const SignatureAlgorithm: Literal<"OpenAttestationMerkleProofSignature2018">; +export declare type SignatureAlgorithm = Static; +export declare const ProofType: Literal<"OpenAttestationSignature2018">; +export declare type ProofType = Static; +export declare const ProofPurpose: Literal<"assertionMethod">; +export declare type ProofPurpose = Static; diff --git a/dist/types/shared/@types/sign.d.ts b/dist/types/shared/@types/sign.d.ts new file mode 100644 index 00000000..62712ec0 --- /dev/null +++ b/dist/types/shared/@types/sign.d.ts @@ -0,0 +1,15 @@ +import { ethers } from "ethers"; +import { Record, Static, String } from "runtypes"; +export declare enum SUPPORTED_SIGNING_ALGORITHM { + Secp256k1VerificationKey2018 = "Secp256k1VerificationKey2018" +} +export interface SigningOptions { + signAsString?: boolean; + signer?: ethers.Signer; +} +export declare const SigningKey: Record<{ + private: String; + public: String; +}, false>; +export declare type SigningKey = Static; +export declare type SigningFunction = (message: string, key: SigningKey | ethers.Signer, options?: SigningOptions) => Promise; diff --git a/dist/types/shared/@types/wrap.d.ts b/dist/types/shared/@types/wrap.d.ts new file mode 100644 index 00000000..de6491f8 --- /dev/null +++ b/dist/types/shared/@types/wrap.d.ts @@ -0,0 +1,14 @@ +import { SchemaId } from "./document"; +export interface WrapDocumentOption { + externalSchemaId?: string; + version?: SchemaId; +} +export interface WrapDocumentOptionV2 { + externalSchemaId?: string; + version?: SchemaId.v2; +} +export interface WrapDocumentOptionV3 { + externalSchemaId?: string; + version: SchemaId.v3; +} +export declare const isWrapDocumentOptionV3: (options: any) => options is WrapDocumentOptionV3; diff --git a/dist/types/shared/ajv.d.ts b/dist/types/shared/ajv.d.ts new file mode 100644 index 00000000..3afee1e5 --- /dev/null +++ b/dist/types/shared/ajv.d.ts @@ -0,0 +1,6 @@ +import Ajv from "ajv"; +import { CurrentOptions } from "ajv/dist/core"; +export declare const buildAjv: (options?: CurrentOptions & { + transform: (schema: Record) => Record; +}) => Ajv; +export declare const getSchema: (key: string, ajv?: Ajv) => import("ajv/dist/types").AnyValidateFunction; diff --git a/dist/types/shared/logger.d.ts b/dist/types/shared/logger.d.ts new file mode 100644 index 00000000..af0a5dd5 --- /dev/null +++ b/dist/types/shared/logger.d.ts @@ -0,0 +1,10 @@ +import debug from "debug"; +interface Logger { + trace: debug.Debugger; + debug: debug.Debugger; + info: debug.Debugger; + warn: debug.Debugger; + error: debug.Debugger; +} +export declare const getLogger: (namespace: string) => Logger; +export {}; diff --git a/dist/types/shared/merkle/index.d.ts b/dist/types/shared/merkle/index.d.ts new file mode 100644 index 00000000..e4d67691 --- /dev/null +++ b/dist/types/shared/merkle/index.d.ts @@ -0,0 +1 @@ +export * from "./merkle"; diff --git a/dist/types/shared/merkle/merkle.d.ts b/dist/types/shared/merkle/merkle.d.ts new file mode 100644 index 00000000..b23eae94 --- /dev/null +++ b/dist/types/shared/merkle/merkle.d.ts @@ -0,0 +1,16 @@ +/// +import { Hash } from "../utils"; +export declare class MerkleTree { + elements: Buffer[]; + layers: Buffer[][]; + constructor(_elements: any[]); + getRoot(): Buffer; + getProof(_element: any): Buffer[]; +} +/** + * Function that runs through the supplied hashes to arrive at the supplied merkle root hash + * @param _proof The list of uncle hashes required to arrive at the supplied merkle root + * @param _root The merkle root + * @param _element The leaf node that is being verified + */ +export declare const checkProof: (_proof: Hash[], _root: Hash, _element: Hash) => boolean; diff --git a/dist/types/shared/serialize/flatten.d.ts b/dist/types/shared/serialize/flatten.d.ts new file mode 100644 index 00000000..c5061af3 --- /dev/null +++ b/dist/types/shared/serialize/flatten.d.ts @@ -0,0 +1,7 @@ +import { FlatleyOptions } from "flatley"; +/** + * Calls external flatten library but ensures that global filters are always applied + * @param data + * @param options + */ +export declare const flatten: (data: any, options?: FlatleyOptions | undefined) => any; diff --git a/dist/types/shared/signer/index.d.ts b/dist/types/shared/signer/index.d.ts new file mode 100644 index 00000000..2bf29658 --- /dev/null +++ b/dist/types/shared/signer/index.d.ts @@ -0,0 +1 @@ +export * from "./signer"; diff --git a/dist/types/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/Secp256k1VerificationKey2018.d.ts b/dist/types/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/Secp256k1VerificationKey2018.d.ts new file mode 100644 index 00000000..4133c040 --- /dev/null +++ b/dist/types/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/Secp256k1VerificationKey2018.d.ts @@ -0,0 +1,3 @@ +import { SigningFunction } from "../../../@types/sign"; +export declare const name = "Secp256k1VerificationKey2018"; +export declare const sign: SigningFunction; diff --git a/dist/types/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/index.d.ts b/dist/types/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/index.d.ts new file mode 100644 index 00000000..114c1d75 --- /dev/null +++ b/dist/types/shared/signer/signatureSchemes/Secp256k1VerificationKey2018/index.d.ts @@ -0,0 +1 @@ +export * from "./Secp256k1VerificationKey2018"; diff --git a/dist/types/shared/signer/signatureSchemes/index.d.ts b/dist/types/shared/signer/signatureSchemes/index.d.ts new file mode 100644 index 00000000..734c05c5 --- /dev/null +++ b/dist/types/shared/signer/signatureSchemes/index.d.ts @@ -0,0 +1 @@ +export * from "./signatureSchemes"; diff --git a/dist/types/shared/signer/signatureSchemes/signatureSchemes.d.ts b/dist/types/shared/signer/signatureSchemes/signatureSchemes.d.ts new file mode 100644 index 00000000..afb0a5cb --- /dev/null +++ b/dist/types/shared/signer/signatureSchemes/signatureSchemes.d.ts @@ -0,0 +1,2 @@ +import { SigningFunction } from "../../@types/sign"; +export declare const defaultSigners: Map; diff --git a/dist/types/shared/signer/signer.d.ts b/dist/types/shared/signer/signer.d.ts new file mode 100644 index 00000000..843fb99d --- /dev/null +++ b/dist/types/shared/signer/signer.d.ts @@ -0,0 +1,6 @@ +import { SigningFunction, SigningOptions, SigningKey } from "../../shared/@types/sign"; +import { defaultSigners } from "./signatureSchemes"; +import { ethers } from "ethers"; +export declare const signerBuilder: (signers: Map) => (alg: string, message: string, keyOrSigner: SigningKey | ethers.Signer, options?: SigningOptions | undefined) => Promise; +export declare const sign: (alg: string, message: string, keyOrSigner: SigningKey | ethers.Signer, options?: SigningOptions | undefined) => Promise; +export { defaultSigners }; diff --git a/dist/types/shared/utils/diagnose.d.ts b/dist/types/shared/utils/diagnose.d.ts new file mode 100644 index 00000000..08462a7c --- /dev/null +++ b/dist/types/shared/utils/diagnose.d.ts @@ -0,0 +1,22 @@ +declare type Version = "2.0" | "3.0"; +declare type Kind = "wrapped" | "signed"; +export declare type Mode = "strict" | "non-strict"; +interface DiagnoseError { + message: string; +} +/** + * Tools to give information about the validity of a document. It will return and eventually output the errors found. + * @param version 2.0 or 3.0 + * @param kind wrapped or signed + * @param debug turn on to output in the console, the errors found + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + * @param document the document to validate + */ +export declare const diagnose: ({ version, kind, document, debug, mode, }: { + version: Version; + kind: Kind; + document: any; + debug?: boolean | undefined; + mode: Mode; +}) => DiagnoseError[]; +export {}; diff --git a/dist/types/shared/utils/guard.d.ts b/dist/types/shared/utils/guard.d.ts new file mode 100644 index 00000000..004f47b4 --- /dev/null +++ b/dist/types/shared/utils/guard.d.ts @@ -0,0 +1,35 @@ +import { OpenAttestationDocument as OpenAttestationDocumentV3, WrappedDocument as WrappedDocumentV3 } from "../../3.0/types"; +import { OpenAttestationDocument as OpenAttestationDocumentV2, WrappedDocument as WrappedDocumentV2 } from "../../2.0/types"; +import { Mode } from "./diagnose"; +/** + * + * @param document + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + */ +export declare const isWrappedV3Document: (document: any, { mode }?: { + mode: Mode; +}) => document is WrappedDocumentV3; +/** + * + * @param document + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + */ +export declare const isWrappedV2Document: (document: any, { mode }?: { + mode: Mode; +}) => document is WrappedDocumentV2; +/** + * + * @param document + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + */ +export declare const isSignedWrappedV2Document: (document: any, { mode }?: { + mode: Mode; +}) => document is import("../../2.0/types").SignedWrappedDocument; +/** + * + * @param document + * @param mode strict or non-strict. Strict will perform additional check on the data. For instance strict validation will ensure that a target hash is a 32 bytes hex string while non strict validation will just check that target hash is a string. + */ +export declare const isSignedWrappedV3Document: (document: any, { mode }?: { + mode: Mode; +}) => document is import("../../3.0/types").SignedWrappedDocument; diff --git a/dist/types/shared/utils/index.d.ts b/dist/types/shared/utils/index.d.ts new file mode 100644 index 00000000..ee2c0318 --- /dev/null +++ b/dist/types/shared/utils/index.d.ts @@ -0,0 +1,3 @@ +export * from "./utils"; +export * from "./guard"; +export * from "./diagnose"; diff --git a/dist/types/shared/utils/utils.d.ts b/dist/types/shared/utils/utils.d.ts new file mode 100644 index 00000000..b179f519 --- /dev/null +++ b/dist/types/shared/utils/utils.d.ts @@ -0,0 +1,46 @@ +/// +import { OpenAttestationDocument as OpenAttestationDocumentV2 } from "../../__generated__/schema.2.0"; +import { OpenAttestationDocument as OpenAttestationDocumentV3 } from "../../__generated__/schema.3.0"; +import { WrappedDocument as WrappedDocumentV2 } from "../../2.0/types"; +import { WrappedDocument as WrappedDocumentV3 } from "../../3.0/types"; +import { ErrorObject } from "ajv"; +export declare type Hash = string | Buffer; +declare type Extract

= P extends WrappedDocumentV2 ? T : never; +export declare const getData: >(document: T) => Extract; +/** + * Sorts the given Buffers lexicographically and then concatenates them to form one continuous Buffer + */ +export declare function bufSortJoin(...args: Buffer[]): Buffer; +export declare function hashToBuffer(hash: Hash): Buffer; +export declare function toBuffer(element: any): Buffer; +/** + * Turns array of data into sorted array of hashes + */ +export declare function hashArray(arr: any[]): Buffer[]; +/** + * Returns the keccak hash of two buffers after concatenating them and sorting them + * If either hash is not given, the input is returned + */ +export declare function combineHashBuffers(first?: Buffer, second?: Buffer): Buffer; +/** + * Returns the keccak hash of two string after concatenating them and sorting them + * If either hash is not given, the input is returned + * @param first A string to be hashed (without 0x) + * @param second A string to be hashed (without 0x) + * @returns Resulting string after the hash is combined (without 0x) + */ +export declare function combineHashString(first?: string, second?: string): string; +export declare function getIssuerAddress(document: any): any; +export declare const getMerkleRoot: (document: any) => string; +export declare const getTargetHash: (document: any) => string; +export declare const isTransferableAsset: (document: any) => boolean; +export declare const getAssetId: (document: any) => string; +export declare class SchemaValidationError extends Error { + validationErrors: ErrorObject[]; + document: any; + constructor(message: string, validationErrors: ErrorObject[], document: any); +} +export declare const isSchemaValidationError: (error: any) => error is SchemaValidationError; +export { keccak256 } from "js-sha3"; +export declare const isObfuscated: (document: WrappedDocumentV3 | WrappedDocumentV2) => boolean; +export declare const getObfuscatedData: (document: WrappedDocumentV3 | WrappedDocumentV2) => string[]; diff --git a/dist/types/shared/validate/index.d.ts b/dist/types/shared/validate/index.d.ts new file mode 100644 index 00000000..13529009 --- /dev/null +++ b/dist/types/shared/validate/index.d.ts @@ -0,0 +1 @@ +export * from "./validate"; diff --git a/dist/types/shared/validate/validate.d.ts b/dist/types/shared/validate/validate.d.ts new file mode 100644 index 00000000..6be48863 --- /dev/null +++ b/dist/types/shared/validate/validate.d.ts @@ -0,0 +1,2 @@ +import { ErrorObject, ValidateFunction } from "ajv"; +export declare const validateSchema: (document: any, validator: ValidateFunction) => ErrorObject[];