@oraichain/multifactors.js
Version:
Handle communication with multifactors nodes
558 lines (529 loc) • 20.9 kB
JavaScript
/******/ (() => { // 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