UNPKG

@oraichain/multifactors.js

Version:

Handle communication with multifactors nodes

558 lines (529 loc) 20.9 kB
/******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ // The require scope /******/ var __webpack_require__ = {}; /******/ /************************************************************************/ /******/ /* webpack/runtime/compat get default export */ /******/ (() => { /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = (module) => { /******/ var getter = module && module.__esModule ? /******/ () => (module['default']) : /******/ () => (module); /******/ __webpack_require__.d(getter, { a: getter }); /******/ return getter; /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /******/ /* webpack/runtime/make namespace object */ /******/ (() => { /******/ // define __esModule on exports /******/ __webpack_require__.r = (exports) => { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ })(); /******/ /************************************************************************/ var __webpack_exports__ = {}; // ESM COMPAT FLAG __webpack_require__.r(__webpack_exports__); // EXPORTS __webpack_require__.d(__webpack_exports__, { GetOrSetNonceError: () => (/* reexport */ GetOrSetNonceError), Some: () => (/* reexport */ Some), SomeError: () => (/* reexport */ SomeError), allSettled: () => (/* reexport */ allSettled), "default": () => (/* reexport */ multifactors), kCombinations: () => (/* reexport */ kCombinations), keccak256: () => (/* reexport */ keccak256), keyLookup: () => (/* reexport */ keyLookup), thresholdSame: () => (/* reexport */ thresholdSame), waitKeyLookup: () => (/* reexport */ waitKeyLookup) }); ;// CONCATENATED MODULE: external "@babel/runtime/helpers/defineProperty" const defineProperty_namespaceObject = require("@babel/runtime/helpers/defineProperty"); var defineProperty_default = /*#__PURE__*/__webpack_require__.n(defineProperty_namespaceObject); ;// CONCATENATED MODULE: ./src/some.ts function capitalizeFirstLetter(str) { return str.charAt(0).toUpperCase() + str.slice(1); } class SomeError extends Error { constructor(_ref) { let { errors, responses, predicate } = _ref; super("Unable to resolve enough promises."); defineProperty_default()(this, "errors", void 0); defineProperty_default()(this, "responses", void 0); defineProperty_default()(this, "predicate", void 0); this.errors = errors; this.responses = responses; this.predicate = predicate; } } const Some = (promises, predicate) => new Promise((resolve, reject) => { let finishedCount = 0; const sharedState = { resolved: false }; const errorArr = new Array(promises.length).fill(undefined); const resultArr = new Array(promises.length).fill(undefined); let predicateError; return promises.forEach((x, index) => { return x.then(resp => { resultArr[index] = resp; return undefined; }).catch(error => { errorArr[index] = error; }).finally(() => { if (sharedState.resolved) return; return predicate(resultArr.slice(0), sharedState).then(data => { sharedState.resolved = true; resolve(data); return undefined; }).catch(error => { // log only the last predicate error predicateError = error; }).finally(() => { finishedCount += 1; if (finishedCount === promises.length) { const errors = Object.values(resultArr.reduce((acc, z) => { if (z) { const { id, error } = z; if (error?.data?.length > 0) { if (error.data.startsWith("Error occurred while verifying params")) acc[id] = capitalizeFirstLetter(error.data);else acc[id] = error.data; } } return acc; }, {})); if (errors.length > 0) { // Format-able errors const msg = errors.length > 1 ? `\n${errors.map(it => `• ${it}`).join("\n")}` : errors[0]; reject(new Error(msg)); } else { reject(new SomeError({ errors: errorArr, responses: resultArr, predicate: predicateError?.message || predicateError })); } } }); }); }); }); ;// CONCATENATED MODULE: external "@toruslabs/eccrypto" const eccrypto_namespaceObject = require("@toruslabs/eccrypto"); ;// CONCATENATED MODULE: external "@toruslabs/http-helpers" const http_helpers_namespaceObject = require("@toruslabs/http-helpers"); ;// CONCATENATED MODULE: external "bn.js" const external_bn_js_namespaceObject = require("bn.js"); var external_bn_js_default = /*#__PURE__*/__webpack_require__.n(external_bn_js_namespaceObject); ;// CONCATENATED MODULE: external "elliptic" const external_elliptic_namespaceObject = require("elliptic"); ;// CONCATENATED MODULE: external "json-stable-stringify" const external_json_stable_stringify_namespaceObject = require("json-stable-stringify"); var external_json_stable_stringify_default = /*#__PURE__*/__webpack_require__.n(external_json_stable_stringify_namespaceObject); ;// CONCATENATED MODULE: external "loglevel" const external_loglevel_namespaceObject = require("loglevel"); var external_loglevel_default = /*#__PURE__*/__webpack_require__.n(external_loglevel_namespaceObject); ;// CONCATENATED MODULE: ./src/loglevel.ts const log = external_loglevel_default().getLogger("orai.js"); log.disableAll(); /* harmony default export */ const loglevel = (log); ;// CONCATENATED MODULE: external "keccak" const external_keccak_namespaceObject = require("keccak"); var external_keccak_default = /*#__PURE__*/__webpack_require__.n(external_keccak_namespaceObject); ;// CONCATENATED MODULE: ./src/utils.ts class GetOrSetNonceError extends Error {} const kCombinations = (s, k) => { let set = s; if (typeof set === "number") { set = Array.from({ length: set }, (_, i) => i); } if (k > set.length || k <= 0) { return []; } if (k === set.length) { return [set]; } if (k === 1) { return set.reduce((acc, cur) => [...acc, [cur]], []); } const combs = []; let tailCombs = []; for (let i = 0; i <= set.length - k + 1; i += 1) { tailCombs = kCombinations(set.slice(i + 1), k - 1); for (let j = 0; j < tailCombs.length; j += 1) { combs.push([set[i], ...tailCombs[j]]); } } return combs; }; const thresholdSame = (arr, t) => { const hashMap = {}; for (let i = 0; i < arr.length; i += 1) { const str = external_json_stable_stringify_default()(arr[i]); hashMap[str] = hashMap[str] ? hashMap[str] + 1 : 1; if (hashMap[str] === t) { return arr[i]; } } return undefined; }; const keyLookup = async (endpoints, verifier, verifierId) => { const lookupPromises = endpoints.map(x => (0,http_helpers_namespaceObject.post)(x, (0,http_helpers_namespaceObject.generateJsonRPCObject)("VerifierLookupRequest", { verifier, verifier_id: verifierId.toString() })).catch(err => loglevel.error("lookup request failed", err))); return Some(lookupPromises, lookupResults => { const lookupShares = lookupResults.filter(x1 => x1); const errorResult = thresholdSame(lookupShares.map(x2 => x2 && x2.error), ~~(endpoints.length / 2) + 1); const keyResult = thresholdSame(lookupShares.map(x3 => x3 && x3.result), ~~(endpoints.length / 2) + 1); if (keyResult || errorResult) { return Promise.resolve({ keyResult, errorResult }); } return Promise.reject(new Error(`invalid results ${JSON.stringify(lookupResults)}`)); }); }; const waitKeyLookup = (endpoints, verifierId, timeout) => new Promise((resolve, reject) => { setTimeout(() => { keyLookup(endpoints, "test", verifierId).then(resolve).catch(reject); // TODO: remove test }, timeout); }); function keccak256(a) { const hash = external_keccak_default()("keccak256").update(a).digest().toString("hex"); return `0x${hash}`; } const allSettled = Promise.allSettled.bind(Promise) || (promises => Promise.all(promises.map(p => p.then(value => ({ status: "fulfilled", value })).catch(reason => ({ status: "rejected", reason }))))); ;// CONCATENATED MODULE: ./src/multifactors.ts function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { defineProperty_default()(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } // Implement threshold logic wrappers around public APIs // of Multifactors nodes to handle malicious node responses class Multifactors { constructor() { let { enableOneKey = false, metadataHost = "https://metadata.social-login.orai.io", serverTimeOffset = 0, network = "mainnet", blsdkg } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; defineProperty_default()(this, "metadataHost", void 0); defineProperty_default()(this, "serverTimeOffset", void 0); defineProperty_default()(this, "enableOneKey", void 0); defineProperty_default()(this, "network", void 0); defineProperty_default()(this, "ec", void 0); defineProperty_default()(this, "blsdkg", void 0); this.ec = new external_elliptic_namespaceObject.ec("secp256k1"); this.metadataHost = metadataHost; this.enableOneKey = enableOneKey; this.serverTimeOffset = serverTimeOffset || 0; // ms this.network = network; this.blsdkg = blsdkg; } static enableLogging() { let v = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; if (v) loglevel.enableAll();else loglevel.disableAll(); } static setEmbedHost(embedHost) { (0,http_helpers_namespaceObject.setEmbedHost)(embedHost); } static isGetOrSetNonceError(err) { return err instanceof GetOrSetNonceError; } /* istanbul ignore next @preserve */ async setCustomKey(_ref) { let { privKeyHex, metadataNonce, oraiKeyHex, customKeyHex } = _ref; let oraiKey; if (oraiKeyHex) { oraiKey = new (external_bn_js_default())(oraiKeyHex, 16); } else { const privKey = new (external_bn_js_default())(privKeyHex, 16); oraiKey = privKey.sub(metadataNonce).umod(this.ec.curve.n); } const customKey = new (external_bn_js_default())(customKeyHex, 16); const newMetadataNonce = customKey.sub(oraiKey).umod(this.ec.curve.n); const data = this.generateMetadataParams(newMetadataNonce.toString(16), oraiKey); await this.setMetadata(data); } async retrieveShares(args) { if (!this.blsdkg) throw new Error("Undefined blsdkg"); const { sharesIndexes, shares, thresholdPublicKey } = await this._retrieveShares(args); const privateKey = await new Promise((resolve, reject) => { this.blsdkg.init().then(() => { const blsPrivKey = this.blsdkg.interpolate(sharesIndexes.slice(0, 3), shares.slice(0, 3)); const blsPubKey = Buffer.from(this.blsdkg.get_pk(blsPrivKey)); loglevel.info(blsPubKey.toString("hex")); loglevel.info("ThresholdPubkey", thresholdPublicKey); if (blsPubKey.toString("hex") !== thresholdPublicKey) { reject("Public key not same"); } resolve(blsPrivKey); }).catch(() => reject(new Error("Blsdkg process has been failed"))); }); return { privKey: Buffer.from(privateKey).toString("hex") }; } async retrieveSharesMobile(args) { return this._retrieveShares(args); } async _retrieveShares(_ref2) { let { typeOfLogin, endpoints, verifierParams, idToken, extraParams } = _ref2; // generate temporary private and public key that is used to secure receive shares const tmpKey = (0,eccrypto_namespaceObject.generatePrivate)(); const pubKey = (0,eccrypto_namespaceObject.getPublic)(tmpKey).toString("hex"); const pubKeyX = pubKey.slice(2, 66); const pubKeyY = pubKey.slice(66); const tokenCommitment = keccak256(idToken); const commitmentResponses = await this.getSharesCommitment({ endpoints, tokencommitment: tokenCommitment.slice(2), temppubx: pubKeyX, temppuby: pubKeyY }); if (commitmentResponses.length < ~~(endpoints.length / 2) + 1) { throw new Error("Insufficient commitment responses"); } const shareResponsesAndIndex = await this.getShareRequestResponseWithIndex({ endpoints, typeOfLogin, verifierParams, idtoken: idToken, nodesignatures: commitmentResponses, extraParams }); if (shareResponsesAndIndex.length < ~~(endpoints.length / 2) + 1) { throw new Error("Insufficient share responses"); } const thresholdPublicKey = thresholdSame(shareResponsesAndIndex.map(x => x && x.share.PublicKey), ~~(endpoints.length / 2) + 1); if (!thresholdPublicKey) throw new Error("Threshold pubkey is undefined"); const sharePromises = shareResponsesAndIndex.map(async x => { const key = x.share; if (key.Metadata) { const metadata = { ephemPublicKey: Buffer.from(key.Metadata.ephemPublicKey, "hex"), iv: Buffer.from(key.Metadata.iv, "hex"), mac: Buffer.from(key.Metadata.mac, "hex") }; return (0,eccrypto_namespaceObject.decrypt)(tmpKey, _objectSpread(_objectSpread({}, metadata), {}, { ciphertext: Buffer.from(key.Share, "base64") })).catch(err => loglevel.debug("share decryption", err)); } return Promise.resolve(Buffer.from(key.Share, "hex")); }); const sharesIndexes = shareResponsesAndIndex.map(x => Buffer.from([x.index + 1])); const shareResolved = (await Promise.all(sharePromises)).filter(x => x); return { shares: shareResolved, sharesIndexes, thresholdPublicKey }; } async getSharesCommitment(_ref3) { let { endpoints, tokencommitment, temppubx, temppuby } = _ref3; const commitmentRequestPromises = endpoints.map(endpoint => (0,http_helpers_namespaceObject.post)(endpoint, (0,http_helpers_namespaceObject.generateJsonRPCObject)("CommitmentRequest", { tokencommitment, temppubx, temppuby }))); const commitmentResponses = await allSettled(commitmentRequestPromises); const fulfillRespone = commitmentResponses.filter(x => x.status === "fulfilled"); return fulfillRespone.map(x => x.value.result).filter(x => x); } async getShareRequestResponseWithIndex(_ref4) { let { endpoints, typeOfLogin, verifierParams, nodesignatures, idtoken, extraParams } = _ref4; const shareRequestPromises = endpoints.map(endpoint => (0,http_helpers_namespaceObject.post)(endpoint, (0,http_helpers_namespaceObject.generateJsonRPCObject)("ShareRequest", _objectSpread(_objectSpread({}, verifierParams), {}, { typeOfLogin, idtoken, nodesignatures }, extraParams)))); const shareResponses = await allSettled(shareRequestPromises); const fulfillRespones = shareResponses.map((x, index) => { if (x.status === "rejected") return undefined; return { share: x.value.result, index }; }); return fulfillRespones.filter(x => x); } /* istanbul ignore next @preserve */ async getPrivKey(key) { const privateKey = key; if (!privateKey) throw new Error("Invalid private key returned"); return { privKey: privateKey.toString("hex", 64) }; } /* istanbul ignore next @preserve */ async getMetadata(data) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; try { const metadataResponse = await (0,http_helpers_namespaceObject.post)(`${this.metadataHost}/get`, data, options, { useAPIKey: true }); if (!metadataResponse || !metadataResponse.message) { return new (external_bn_js_default())(0); } return new (external_bn_js_default())(metadataResponse.message, 16); // nonce } catch (error) { loglevel.error("get metadata error", error); return new (external_bn_js_default())(0); } } /* istanbul ignore next @preserve */ generateMetadataParams(message, privateKey) { const key = this.ec.keyFromPrivate(privateKey.toString("hex", 64)); const setData = { data: message, timestamp: new (external_bn_js_default())(~~(this.serverTimeOffset + Date.now() / 1000)).toString(16) }; const sig = key.sign(keccak256(external_json_stable_stringify_default()(setData)).slice(2)); return { pub_key_X: key.getPublic().getX().toString("hex"), pub_key_Y: key.getPublic().getY().toString("hex"), set_data: setData, signature: Buffer.from(sig.r.toString(16, 64) + sig.s.toString(16, 64) + new (external_bn_js_default())("").toString(16, 2), "hex").toString("base64") }; } /* istanbul ignore next @preserve */ async setMetadata(data) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; try { const metadataResponse = await (0,http_helpers_namespaceObject.post)(`${this.metadataHost}/set`, data, options, { useAPIKey: true }); return metadataResponse.message; // IPFS hash } catch (error) { loglevel.error("set metadata error", error); return ""; } } /* istanbul ignore next @preserve */ lagrangeInterpolation(shares, nodeIndex) { if (shares.length !== nodeIndex.length) { return null; } let secret = new (external_bn_js_default())(0); for (let i = 0; i < shares.length; i += 1) { let upper = new (external_bn_js_default())(1); let lower = new (external_bn_js_default())(1); for (let j = 0; j < shares.length; j += 1) { if (i !== j) { upper = upper.mul(nodeIndex[j].neg()); upper = upper.umod(this.ec.curve.n); let temp = nodeIndex[i].sub(nodeIndex[j]); temp = temp.umod(this.ec.curve.n); lower = lower.mul(temp).umod(this.ec.curve.n); } } let delta = upper.mul(lower.invm(this.ec.curve.n)).umod(this.ec.curve.n); delta = delta.mul(shares[i]).umod(this.ec.curve.n); secret = secret.add(delta); } return secret.umod(this.ec.curve.n); } /* istanbul ignore next @preserve */ async getOrSetNonce(X, Y, privKey) { let getOnly = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; let data; const msg = getOnly ? "getNonce" : "getOrSetNonce"; if (privKey) { data = this.generateMetadataParams(msg, privKey); } else { data = { pub_key_X: X, pub_key_Y: Y, set_data: { data: msg } }; } return (0,http_helpers_namespaceObject.post)(`${this.metadataHost}/get_or_set_nonce`, data, undefined, { useAPIKey: true }); } /* istanbul ignore next @preserve */ async getNonce(X, Y, privKey) { return this.getOrSetNonce(X, Y, privKey, true); } /* istanbul ignore next @preserve */ getPostboxKeyFrom1OutOf1(privKey, nonce) { const privKeyBN = new (external_bn_js_default())(privKey, 16); const nonceBN = new (external_bn_js_default())(nonce, 16); return privKeyBN.sub(nonceBN).umod(this.ec.curve.n).toString("hex"); } } /* harmony default export */ const multifactors = (Multifactors); ;// CONCATENATED MODULE: ./src/index.ts module.exports = __webpack_exports__; /******/ })() ; //# sourceMappingURL=multifactorsUtils.cjs.js.map