UNPKG

@guru_test/mpc-core-kit

Version:
1,323 lines (1,277 loc) 92.1 kB
import { TORUS_SAPPHIRE_NETWORK, SIGNER_MAP } from '@toruslabs/constants'; export { SIG_TYPE } from '@toruslabs/constants'; import _defineProperty from '@babel/runtime/helpers/defineProperty'; import { ShareSerializationModule } from '@tkey/share-serialization'; import BN from 'bn.js'; import _objectSpread from '@babel/runtime/helpers/objectSpread2'; import { secp256k1, Point, KeyType, ONE_KEY_DELETE_NONCE, ShareStore, SHARE_DELETED } from '@tkey/common-types'; import { getPubKeyPoint, factorKeyCurve, lagrangeInterpolation, TSSTorusServiceProvider, TKeyTSS } from '@tkey/tss'; export { factorKeyCurve } from '@tkey/tss'; import { keccak256, Torus } from '@toruslabs/torus.js'; import { generatePrivateBN, CoreError } from '@tkey/core'; import { TorusStorageLayer } from '@tkey/storage-layer-torus'; import { UX_MODE, AGGREGATE_VERIFIER, TORUS_METHOD } from '@toruslabs/customauth'; import { Secp256k1Curve, Ed25519Curve } from '@toruslabs/elliptic-wrapper'; import { fetchLocalConfig } from '@toruslabs/fnd-base'; import { keccak256 as keccak256$1 } from '@toruslabs/metadata-helpers'; import { SessionManager } from '@toruslabs/session-manager'; import { setupSockets, getDKLSCoeff, Client } from '@toruslabs/tss-client'; import { sign } from '@toruslabs/tss-frost-client'; import { SafeEventEmitter } from '@web3auth/auth'; import bowser from 'bowser'; import { eddsa, ec } from 'elliptic'; import { safeatob } from '@toruslabs/openlogin-utils'; import loglevel from 'loglevel'; const WEB3AUTH_NETWORK = { MAINNET: TORUS_SAPPHIRE_NETWORK.SAPPHIRE_MAINNET, DEVNET: TORUS_SAPPHIRE_NETWORK.SAPPHIRE_DEVNET }; const USER_PATH = { NEW: "NewAccount", EXISTING: "ExistingAccount", REHYDRATE: "RehydrateAccount", RECOVER: "RecoverAccount" }; let FactorKeyTypeShareDescription = /*#__PURE__*/function (FactorKeyTypeShareDescription) { FactorKeyTypeShareDescription["HashedShare"] = "hashedShare"; FactorKeyTypeShareDescription["SecurityQuestions"] = "tssSecurityQuestions"; FactorKeyTypeShareDescription["DeviceShare"] = "deviceShare"; FactorKeyTypeShareDescription["SeedPhrase"] = "seedPhrase"; FactorKeyTypeShareDescription["PasswordShare"] = "passwordShare"; FactorKeyTypeShareDescription["SocialShare"] = "socialShare"; FactorKeyTypeShareDescription["Other"] = "Other"; return FactorKeyTypeShareDescription; }({}); const DELIMITERS = { Delimiter1: "\u001c", Delimiter2: "\u0015", Delimiter3: "\u0016", Delimiter4: "\u0017" }; const ERRORS = { TKEY_SHARES_REQUIRED: "required more shares", INVALID_BACKUP_SHARE: "invalid backup share" }; const SOCIAL_FACTOR_INDEX = 1; /** * Defines the TSS Share Index in a simplified way for better implementation. **/ let TssShareType = /*#__PURE__*/function (TssShareType) { TssShareType[TssShareType["DEVICE"] = 2] = "DEVICE"; TssShareType[TssShareType["RECOVERY"] = 3] = "RECOVERY"; return TssShareType; }({}); const VALID_SHARE_INDICES = [TssShareType.DEVICE, TssShareType.RECOVERY]; const SCALAR_LEN = 32; // Length of secp256k1 scalar in bytes. const FIELD_ELEMENT_HEX_LEN = 32 * 2; // Length of secp256k1 field element in hex form. const MAX_FACTORS = 10; // Maximum number of factors that can be added to an account. const SOCIAL_TKEY_INDEX = 1; /** * Fix the prototype chain of the error * * Use Object.setPrototypeOf * Support ES6 environments * * Fallback setting __proto__ * Support IE11+, see https://docs.microsoft.com/en-us/scripting/javascript/reference/javascript-version-information */ function fixProto(target, prototype) { const { setPrototypeOf } = Object; if (setPrototypeOf) { setPrototypeOf(target, prototype); } else { // eslint-disable-next-line no-proto, @typescript-eslint/no-explicit-any target.__proto__ = prototype; } } /** * Capture and fix the error stack when available * * Use Error.captureStackTrace * Support v8 environments */ function fixStack(target, fn = target.constructor) { const { captureStackTrace } = Error; if (captureStackTrace) { captureStackTrace(target, fn); } } // copy from https://github.com/microsoft/TypeScript/blob/main/lib/lib.es2022.error.d.ts // avoid typescript isue https://github.com/adriengibrat/ts-custom-error/issues/81 /** * Allows to easily extend a base class to create custom applicative errors. * * example: * ``` * class HttpError extends CustomError { * public constructor( * public code: number, * message?: string, * cause?: Error, * ) { * super(message, { cause }) * } * } * * new HttpError(404, 'Not found') * ``` */ class CustomError extends Error { constructor(message, options) { super(message, options); // set error name as constructor name, make it not enumerable to keep native Error behavior // see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new.target#new.target_in_constructors // see https://github.com/adriengibrat/ts-custom-error/issues/30 _defineProperty(this, "name", void 0); Object.defineProperty(this, "name", { value: new.target.name, enumerable: false, configurable: true }); // fix the extended error prototype chain // because typescript __extends implementation can't // see https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work fixProto(this, new.target.prototype); // try to remove contructor from stack trace fixStack(this); } } class AbstractCoreKitError extends CustomError { constructor(code, message) { // takes care of stack and proto super(message); _defineProperty(this, "code", void 0); _defineProperty(this, "message", void 0); this.code = code; this.message = message || ""; // Set name explicitly as minification can mangle class names Object.defineProperty(this, "name", { value: "TkeyError" }); } toJSON() { return { name: this.name, code: this.code, message: this.message }; } toString() { return JSON.stringify(this.toJSON()); } } /** * CoreKitError, extension for Error using CustomError * * Usage: * 1. throw CoreKitError.factorKeyNotPresent("Required factor key missing in the operation."); // Use a predefined method to throw a common error * 2. throw CoreKitError.fromCode(1001); // Throw an error using a code for a common error * 3. throw new CoreKitError(1102, "'tkey' instance has not been initialized."); // Throw a specific error with a custom message * * Guide: * 1000 - Configuration errors * 1100 - TSS and key management errors * 1200 - Factor key and authentication errors * 1300 - Initialization and session management */ class CoreKitError extends AbstractCoreKitError { constructor(code, message) { super(code, message); Object.defineProperty(this, "name", { value: "CoreKitError" }); } static fromCode(code, extraMessage = "") { return new CoreKitError(code, `${CoreKitError.messages[code]} ${extraMessage}`); } static default(extraMessage = "") { return new CoreKitError(1000, `${CoreKitError.messages[1000]} ${extraMessage}`); } // Configuration errors static chainConfigInvalid(extraMessage = "") { return CoreKitError.fromCode(1001, extraMessage); } static clientIdInvalid(extraMessage = "") { return CoreKitError.fromCode(1002, extraMessage); } static storageTypeUnsupported(extraMessage = "") { return CoreKitError.fromCode(1003, extraMessage); } static oauthLoginUnsupported(extraMessage = "") { return CoreKitError.fromCode(1004, extraMessage); } static noValidStorageOptionFound(extraMessage = "") { return CoreKitError.fromCode(1005, extraMessage); } static noDataFoundInStorage(extraMessage = "") { return CoreKitError.fromCode(1006, extraMessage); } static invalidConfig(extraMessage = "") { return CoreKitError.fromCode(1007, extraMessage); } static invalidKeyType(extraMessage = "") { return CoreKitError.fromCode(1008, extraMessage); } static invalidSigType(extraMessage = "") { return CoreKitError.fromCode(1009, extraMessage); } // TSS and key management errors static tssLibRequired(extraMessage = "") { return CoreKitError.fromCode(1101, extraMessage); } static tkeyInstanceUninitialized(extraMessage = "") { return CoreKitError.fromCode(1102, extraMessage); } static duplicateTssIndex(extraMessage = "") { return CoreKitError.fromCode(1103, extraMessage); } static nodeDetailsRetrievalFailed(extraMessage = "") { return CoreKitError.fromCode(1104, extraMessage); } static prefetchValueExceeded(extraMessage = "") { return CoreKitError.fromCode(1105, extraMessage); } static invalidTorusLoginResponse(extraMessage = "") { return CoreKitError.fromCode(1106, extraMessage); } static invalidTorusAggregateLoginResponse(extraMessage = "") { return CoreKitError.fromCode(1107, extraMessage); } static unsupportedRedirectMethod(extraMessage = "") { return CoreKitError.fromCode(1108, extraMessage); } static postBoxKeyMissing(extraMessage = "") { return CoreKitError.fromCode(1109, extraMessage); } static tssShareTypeIndexMissing(extraMessage = "") { return CoreKitError.fromCode(1110, extraMessage); } static tssPublicKeyOrEndpointsMissing(extraMessage = "") { return CoreKitError.fromCode(1111, extraMessage); } static activeSessionNotFound(extraMessage = "") { return CoreKitError.fromCode(1112, extraMessage); } static tssNoncesMissing(extraMessage = "") { return CoreKitError.fromCode(1113, extraMessage); } static tssKeyImportNotAllowed(extraMessage = "") { return CoreKitError.fromCode(1114, extraMessage); } // Factor key and authentication errors static factorKeyNotPresent(extraMessage = "") { return CoreKitError.fromCode(1201, extraMessage); } static factorKeyAlreadyExists(extraMessage = "") { return CoreKitError.fromCode(1202, extraMessage); } static mfaAlreadyEnabled(extraMessage = "") { return CoreKitError.fromCode(1203, extraMessage); } static cannotDeleteLastFactor(extraMessage = "") { return CoreKitError.fromCode(1204, extraMessage); } static factorInUseCannotBeDeleted(extraMessage = "") { return CoreKitError.fromCode(1205, extraMessage); } static userNotLoggedIn(extraMessage = "") { return CoreKitError.fromCode(1206, extraMessage); } static providedFactorKeyInvalid(extraMessage = "") { return CoreKitError.fromCode(1207, extraMessage); } static factorEncsMissing(extraMessage = "") { return CoreKitError.fromCode(1208, extraMessage); } static noMetadataFound(extraMessage = "") { return CoreKitError.fromCode(1209, extraMessage); } static newShareIndexInvalid(extraMessage = "") { return CoreKitError.fromCode(1210, extraMessage); } static maximumFactorsReached(extraMessage = "") { return CoreKitError.fromCode(1211, extraMessage); } static noMetadataShareFound(extraMessage = "") { return CoreKitError.fromCode(1212, extraMessage); } static signaturesNotPresent(extraMessage = "") { return CoreKitError.fromCode(1213, extraMessage); } static factorPubsMissing(extraMessage = "") { return CoreKitError.fromCode(1214, extraMessage); } // Initialization and session management static commitChangesBeforeMFA(extraMessage = "") { return CoreKitError.fromCode(1301, extraMessage); } static mpcCoreKitNotInitialized(extraMessage = "") { return CoreKitError.fromCode(1302, extraMessage); } } _defineProperty(CoreKitError, "messages", { // Configuration errors 1001: "You must specify a valid eip155 chain configuration in the options.", 1002: "You must specify a web3auth clientId.", 1003: "Unsupported storage type in this UX mode.", 1004: "OAuth login is NOT supported in this UX mode.", 1005: "No valid storage option found.", 1006: "No data found in storage.", 1007: "Invalid config.", 1008: "Invalid key type.", 1009: "Invalid signature type.", // TSS and key management errors 1101: "'tssLib' is required when running in this UX mode.", 1102: "'tkey' instance has not been initialized.", 1103: "Duplicate TSS index found. Ensure that each TSS index is unique.", 1104: "Failed to retrieve node details. Please check your network connection and try again.", 1105: "The prefetch TSS public keys exceeds the maximum allowed limit of 3.", 1106: "Invalid 'TorusLoginResponse' data provided.", 1107: "Invalid 'TorusAggregateLoginResponse' data provided.", 1108: "Unsupported method type encountered in redirect result.", 1109: "OAuthKey not present in state.", 1110: "TSS Share Type (Index) not present in state when getting current factor key.", 1111: "'tssPubKey' or 'torusNodeTSSEndpoints' are missing.", 1112: "No active session found.", 1113: "tssNonces not present in metadata when getting tss nonce.", 1114: "A TSS key cannot be imported for an existing user who already has a key configured.", // Factor key and authentication errors 1201: "factorKey not present in state when required.", 1202: "A factor with the same key already exists.", 1203: "MFA is already enabled.", 1204: "Cannot delete the last remaining factor as at least one factor is required.", 1205: "The factor currently in use cannot be deleted.", 1206: "User is not logged in.", 1207: "Provided factor key is invalid.", 1208: "'factorEncs' mpt [resemt].", 1209: "No metadata found for the provided factor key. Consider resetting your account if this error persists.", 1210: "The new share index is not valid. It must be one of the valid share indices.", 1211: "The maximum number of allowable factors (10) has been reached.", 1212: "No metadata share found in the current polynomial.", 1213: "No signatures found.", 1214: "Factor public keys not present", // Initialization and session management 1301: "The 'CommitChanges' method must be called before enabling MFA.", 1302: "The MPC Core Kit is not initialized. Please ensure you call the 'init()' method to initialize the kit properly before attempting any operations." }); class MemoryStorage { constructor() { _defineProperty(this, "_store", {}); } getItem(key) { return this._store[key] || null; } setItem(key, value) { this._store[key] = value; } removeItem(key) { delete this._store[key]; } clear() { this._store = {}; } } class AsyncStorage { constructor(storeKey, storage) { _defineProperty(this, "storage", void 0); _defineProperty(this, "_storeKey", void 0); this.storage = storage; this._storeKey = storeKey; } async toJSON() { const result = await this.storage.getItem(this._storeKey); if (!result) { throw CoreKitError.noDataFoundInStorage(`No data found in storage under key '${this._storeKey}'.`); } return result; } async resetStore() { const currStore = await this.getStore(); await this.storage.setItem(this._storeKey, JSON.stringify({})); return currStore; } async getStore() { return JSON.parse((await this.storage.getItem(this._storeKey)) || "{}"); } async get(key) { const store = JSON.parse((await this.storage.getItem(this._storeKey)) || "{}"); return store[key]; } async set(key, value) { const store = JSON.parse((await this.storage.getItem(this._storeKey)) || "{}"); store[key] = value; await this.storage.setItem(this._storeKey, JSON.stringify(store)); } async remove(key) { const store = JSON.parse((await this.storage.getItem(this._storeKey)) || "{}"); delete store[key]; await this.storage.setItem(this._storeKey, JSON.stringify(store)); } } /** * Converts a mnemonic to a BN. * @param shareMnemonic - The mnemonic to convert. * @returns A BN respective to your mnemonic */ function mnemonicToKey(shareMnemonic) { const factorKey = ShareSerializationModule.deserializeMnemonic(shareMnemonic); return factorKey.toString("hex"); } /** * Converts a BN to a mnemonic. * @param shareBN - The BN to convert. * @returns A mnemonic respective to your BN */ function keyToMnemonic(shareHex) { const shareBN = new BN(shareHex, "hex"); const mnemonic = ShareSerializationModule.serializeMnemonic(shareBN); return mnemonic; } class TssSecurityQuestionStore { constructor(shareIndex, factorPublicKey, question) { _defineProperty(this, "shareIndex", void 0); _defineProperty(this, "factorPublicKey", void 0); _defineProperty(this, "question", void 0); this.shareIndex = shareIndex; this.factorPublicKey = factorPublicKey; this.question = question; } static fromJSON(json) { const { shareIndex, factorPublicKey, question } = json; return new TssSecurityQuestionStore(shareIndex, factorPublicKey, question); } toJSON() { return { shareIndex: this.shareIndex, factorPublicKey: this.factorPublicKey, question: this.question }; } } class TssSecurityQuestion { constructor() { _defineProperty(this, "storeDomainName", "tssSecurityQuestion"); } async setSecurityQuestion(params) { const { mpcCoreKit, question, answer, description } = params; let { shareType } = params; if (!mpcCoreKit.tKey) { throw new Error("Tkey not initialized, call init first."); } if (!question || !answer) { throw new Error("question and answer are required"); } const domainKey = `${this.storeDomainName}:${params.mpcCoreKit.tKey.tssTag}`; // default using recovery index if (!shareType) { shareType = TssShareType.RECOVERY; } else if (!VALID_SHARE_INDICES.includes(shareType)) { throw new Error(`invalid share type: must be one of ${VALID_SHARE_INDICES}`); } // Check for existing security question const tkey = mpcCoreKit.tKey; const storeDomain = tkey.metadata.getGeneralStoreDomain(domainKey); if (storeDomain && storeDomain.question) { throw new Error("Security question already exists"); } // const pubKey = Point.fromTkeyPoint(mpcCoreKit.tKey.getTSSPub()).toBufferSEC1(true).toString("hex"); const pubKey = tkey.getKeyDetails().pubKey.toSEC1(secp256k1, true).toString("hex") + tkey.tssTag; let hash = keccak256(Buffer.from(answer + pubKey, "utf8")); hash = hash.startsWith("0x") ? hash.slice(2) : hash; const factorKeyBN = new BN(hash, "hex"); const descriptionFinal = _objectSpread({ question }, description); await mpcCoreKit.createFactor({ factorKey: factorKeyBN, shareType, shareDescription: FactorKeyTypeShareDescription.SecurityQuestions, additionalMetadata: descriptionFinal }); // set store domain const tkeyPt = getPubKeyPoint(factorKeyBN, factorKeyCurve); const factorPub = tkeyPt.toSEC1(factorKeyCurve, true).toString("hex"); const storeData = new TssSecurityQuestionStore(shareType.toString(), factorPub, question); tkey.metadata.setGeneralStoreDomain(domainKey, storeData.toJSON()); // check for auto commit if (!tkey.manualSync) await tkey._syncShareMetadata(); return factorKeyBN.toString("hex").padStart(64, "0"); } async changeSecurityQuestion(params) { const { mpcCoreKit, newQuestion, newAnswer, answer } = params; if (!newQuestion || !newAnswer || !answer) { throw new Error("question and answer are required"); } // Check for existing security question const tkey = mpcCoreKit.tKey; // const pubKey = Point.fromTkeyPoint(mpcCoreKit.tKey.getTSSPub()).toBufferSEC1(true).toString("hex"); const pubKey = tkey.getKeyDetails().pubKey.toSEC1(secp256k1, true).toString("hex") + tkey.tssTag; const domainKey = `${this.storeDomainName}:${params.mpcCoreKit.tKey.tssTag}`; const storeDomain = tkey.metadata.getGeneralStoreDomain(domainKey); if (!storeDomain || !storeDomain.question) { throw new Error("Security question does not exists"); } const store = TssSecurityQuestionStore.fromJSON(storeDomain); const preHash = answer + pubKey; let hash = keccak256(Buffer.from(preHash, "utf8")); hash = hash.startsWith("0x") ? hash.slice(2) : hash; const factorKeyBN = new BN(hash, "hex"); const factorKeyPt = getPubKeyPoint(factorKeyBN, factorKeyCurve); if (factorKeyPt.toSEC1(factorKeyCurve, true).toString("hex") !== store.factorPublicKey) { throw new Error("Invalid answer"); } // create new factor key const prenewHash = newAnswer + pubKey; let newHash = keccak256(Buffer.from(prenewHash, "utf8")); newHash = newHash.startsWith("0x") ? newHash.slice(2) : newHash; const newAnswerBN = new BN(newHash, "hex"); const newFactorPt = Point.fromScalar(newAnswerBN, factorKeyCurve); await mpcCoreKit.createFactor({ factorKey: newAnswerBN, shareType: parseInt(store.shareIndex), shareDescription: FactorKeyTypeShareDescription.SecurityQuestions }); // update mpcCoreKit state to use new factor key during change password if mpc factor key is security question factor if (mpcCoreKit.state.factorKey.eq(factorKeyBN)) { await mpcCoreKit.inputFactorKey(newAnswerBN); } // delete after create factor to prevent last key issue // delete old factor key and device share await mpcCoreKit.deleteFactor(factorKeyPt, factorKeyBN); store.factorPublicKey = newFactorPt.toSEC1(factorKeyCurve, true).toString("hex"); store.question = newQuestion; tkey.metadata.setGeneralStoreDomain(domainKey, store.toJSON()); // check for auto commit if (!tkey.manualSync) await tkey._syncShareMetadata(); } // Should we check with answer before deleting? async deleteSecurityQuestion(mpcCoreKit, deleteFactorKey = true) { if (!mpcCoreKit.tKey) { throw new Error("Tkey not initialized, call init first."); } const domainKey = `${this.storeDomainName}:${mpcCoreKit.tKey.tssTag}`; const tkey = mpcCoreKit.tKey; if (deleteFactorKey) { const storeDomain = tkey.metadata.getGeneralStoreDomain(domainKey); if (!storeDomain || !storeDomain.question) { throw new Error("Security question does not exists"); } const store = TssSecurityQuestionStore.fromJSON(storeDomain); if (store.factorPublicKey) { await mpcCoreKit.deleteFactor(Point.fromSEC1(factorKeyCurve, store.factorPublicKey)); } } tkey.metadata.deleteGeneralStoreDomain(domainKey); // check for auto commit if (!tkey.manualSync) await tkey._syncShareMetadata(); } async recoverFactor(mpcCoreKit, answer) { if (!mpcCoreKit.tKey) { throw new Error("Tkey not initialized, call init first."); } if (!answer) { throw new Error("question and answer are required"); } const tkey = mpcCoreKit.tKey; const domainKey = `${this.storeDomainName}:${mpcCoreKit.tKey.tssTag}`; const storeDomain = tkey.metadata.getGeneralStoreDomain(domainKey); if (!storeDomain || !storeDomain.question) { throw new Error("Security question does not exists"); } const store = TssSecurityQuestionStore.fromJSON(storeDomain); // const pubKey = Point.fromTkeyPoint(mpcCoreKit.tKey.getTSSPub()).toBufferSEC1(true).toString("hex"); const pubKey = tkey.getKeyDetails().pubKey.toSEC1(secp256k1, true).toString("hex") + tkey.tssTag; let hash = keccak256(Buffer.from(answer + pubKey, "utf8")); hash = hash.startsWith("0x") ? hash.slice(2) : hash; const factorKeyBN = new BN(hash, "hex"); const factorKeyPt = Point.fromScalar(factorKeyBN, factorKeyCurve); if (factorKeyPt.toSEC1(factorKeyCurve, true).toString("hex") !== store.factorPublicKey) { throw new Error("Invalid answer"); } return hash; } getQuestion(mpcCoreKit) { if (!mpcCoreKit.tKey) { throw new Error("Tkey not initialized, call init first."); } const tkey = mpcCoreKit.tKey; const domainKey = `${this.storeDomainName}:${mpcCoreKit.tKey.tssTag}`; const storeDomain = tkey.metadata.getGeneralStoreDomain(domainKey); if (!storeDomain || !storeDomain.question) { throw new Error("Security question does not exists"); } const store = TssSecurityQuestionStore.fromJSON(storeDomain); return store.question; } } // TODO: move the types to a base class for both dkls and frost in future let COREKIT_STATUS = /*#__PURE__*/function (COREKIT_STATUS) { COREKIT_STATUS["NOT_INITIALIZED"] = "NOT_INITIALIZED"; COREKIT_STATUS["INITIALIZED"] = "INITIALIZED"; COREKIT_STATUS["REQUIRED_SHARE"] = "REQUIRED_SHARE"; COREKIT_STATUS["LOGGED_IN"] = "LOGGED_IN"; return COREKIT_STATUS; }({}); class DefaultSessionSigGeneratorPlugin { constructor(mpcCorekitContext) { this.mpcCorekitContext = mpcCorekitContext; _defineProperty(this, "context", void 0); this.context = mpcCorekitContext; } async getSessionSigs() { return this.context.state.signatures || []; } } const ed25519 = () => { return new eddsa("ed25519"); }; /** * Secure PRNG. Uses `crypto.getRandomValues`, which defers to OS. */ function randomBytes(bytesLength = 32) { // We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+. const crypto = typeof globalThis === "object" && "crypto" in globalThis ? globalThis.crypto : undefined; if (crypto && typeof crypto.getRandomValues === "function") { return crypto.getRandomValues(new Uint8Array(bytesLength)); } throw new Error("crypto.getRandomValues must be defined"); } function generateEd25519Seed() { return Buffer.from(randomBytes(32)); } const generateFactorKey = () => { const keyPair = factorKeyCurve.genKeyPair(); const pub = Point.fromElliptic(keyPair.getPublic()); return { private: keyPair.getPrivate(), pub }; }; const generateTSSEndpoints = (tssNodeEndpoints, parties, clientIndex, nodeIndexes) => { const endpoints = []; const tssWSEndpoints = []; const partyIndexes = []; const nodeIndexesReturned = []; for (let i = 0; i < parties; i++) { partyIndexes.push(i); if (i === clientIndex) { // eslint-disable-next-line @typescript-eslint/no-explicit-any endpoints.push(null); // eslint-disable-next-line @typescript-eslint/no-explicit-any tssWSEndpoints.push(null); } else { const targetNodeIndex = nodeIndexes[i] - 1; endpoints.push(tssNodeEndpoints[targetNodeIndex]); tssWSEndpoints.push(new URL(tssNodeEndpoints[targetNodeIndex]).origin); nodeIndexesReturned.push(nodeIndexes[i]); } } return { endpoints, tssWSEndpoints, partyIndexes, nodeIndexesReturned }; }; async function storageAvailable(storage) { try { const x = "__storage_test__"; const rand = Math.random().toString(); await storage.setItem(x, rand); const value = await storage.getItem(rand); if (value !== rand) { throw new Error("Value mismatch"); } return true; } catch (error) { return false; } } // TODO think which conversion functions to keep and how to export them. /** * Parses a JWT Token, without verifying the signature. * @param token - JWT Token * @returns Extracted JSON payload from the token */ function parseToken(token) { const payload = token.split(".")[1]; return JSON.parse(safeatob(payload)); } const getHashedPrivateKey = (postboxKey, clientId) => { const uid = `${postboxKey}_${clientId}`; let hashUid = keccak256(Buffer.from(uid, "utf8")); hashUid = hashUid.replace("0x", ""); return new BN(hashUid, "hex"); }; /** * Converts an elliptic curve scalar represented by a BN to a byte buffer in SEC1 * format (i.e., padded to maximum length). * @param s - The scalar of type BN. * @returns The SEC1 encoded representation of the scalar. */ function scalarBNToBufferSEC1(s) { return s.toArrayLike(Buffer, "be", SCALAR_LEN); } function sampleEndpoints(endpoints, n) { if (n > endpoints.length) { throw new Error("Invalid number of endpoints"); } const shuffledEndpoints = endpoints.slice().sort(() => Math.random() - 0.5); return shuffledEndpoints.slice(0, n).sort((a, b) => a.index - b.index); } function fraction(curve, nom, denom) { return nom.mul(denom.invm(curve.n)).umod(curve.n); } function lagrangeCoefficient(curve, xCoords, targetCoeff, targetX) { return xCoords.filter((_, i) => i !== targetCoeff).reduce((prev, cur) => { const frac = fraction(curve, targetX.sub(cur), xCoords[targetCoeff].sub(cur)); return prev.mul(frac).umod(curve.n); }, new BN(1)); } function lagrangeCoefficients(curve, xCoords, targetX) { const xCoordsBN = xCoords.map(i => new BN(i)); const targetXBN = new BN(targetX); return xCoordsBN.map((_value, i) => lagrangeCoefficient(curve, xCoordsBN, i, targetXBN)); } const SERVER_XCOORD_L1 = 1; const CLIENT_XCOORD_L1 = 2; /** * Derive share coefficients for client and servers. * * @param curve - The curve to be used. * @param serverXCoords - The source and target x-coordinates of the selected * servers. * @param targetClientXCoord - The target x-coordinate of the client. * @param sourceClientXCoord - The source x-coordinate of the client in the L1 * hierarchy. * @returns - The share coefficients for the client and the servers. */ function deriveShareCoefficients(ec, serverXCoords, targetClientXCoord, sourceClientXCoord = CLIENT_XCOORD_L1) { const l1Coefficients = lagrangeCoefficients(ec, [SERVER_XCOORD_L1, sourceClientXCoord], 0); const l2Coefficients = lagrangeCoefficients(ec, serverXCoords, 0); if (serverXCoords.includes(targetClientXCoord)) { throw new Error(`Invalid server x-coordinates: overlapping with client x-coordinate: ${serverXCoords} ${targetClientXCoord}`); } const targetCoefficients = lagrangeCoefficients(ec, [targetClientXCoord, ...serverXCoords], 0); // Derive server coefficients. const serverCoefficients = l2Coefficients.map((coeff, i) => fraction(ec, l1Coefficients[0].mul(coeff), targetCoefficients[i + 1])); // Derive client coefficient. const clientCoefficient = fraction(ec, l1Coefficients[1], targetCoefficients[0]); return { serverCoefficients, clientCoefficient }; } function generateSessionNonce() { return keccak256(Buffer.from(generatePrivateBN().toString("hex") + Date.now(), "utf8")); } function getSessionId(verifier, verifierId, tssTag, tssNonce, sessionNonce) { return `${verifier}${DELIMITERS.Delimiter1}${verifierId}${DELIMITERS.Delimiter2}${tssTag}${DELIMITERS.Delimiter3}${tssNonce}${DELIMITERS.Delimiter4}${sessionNonce}`; } function sigToRSV(sig) { if (sig.length !== 65) { throw new Error(`Invalid signature length: expected 65, got ${sig.length}`); } return { r: sig.subarray(0, 32), s: sig.subarray(32, 64), v: sig[64] }; } function makeEthereumSigner(kit) { if (kit.keyType !== KeyType.secp256k1) { throw new Error(`Invalid key type: expected secp256k1, got ${kit.keyType}`); } return { sign: async msgHash => { const sig = await kit.sign(msgHash, { hashed: true }); return sigToRSV(sig); }, getPublic: async () => { const pk = Point.fromSEC1(secp256k1, kit.getPubKey().toString("hex")); return pk.toSEC1(secp256k1).subarray(1); } }; } const log = loglevel.getLogger("mpc-core-kit"); log.disableAll(); class Web3AuthMPCCoreKit { constructor(options) { var _window; _defineProperty(this, "state", { accountIndex: 0 }); _defineProperty(this, "torusSp", null); _defineProperty(this, "stateEmitter", void 0); _defineProperty(this, "options", void 0); _defineProperty(this, "storageLayer", null); _defineProperty(this, "tkey", null); _defineProperty(this, "sessionManager", void 0); _defineProperty(this, "currentStorage", void 0); _defineProperty(this, "_storageBaseKey", "corekit_store"); _defineProperty(this, "enableLogging", false); _defineProperty(this, "ready", false); _defineProperty(this, "_tssLib", void 0); _defineProperty(this, "wasmLib", void 0); _defineProperty(this, "_keyType", void 0); _defineProperty(this, "_sigType", void 0); _defineProperty(this, "atomicCallStackCounter", 0); _defineProperty(this, "sessionSigGenerator", void 0); _defineProperty(this, "getTssFactorPub", () => { this.checkReady(); if (!this.state.factorKey) { throw CoreKitError.factorKeyNotPresent("factorKey not present in state when getting tss factor public key."); } const factorPubsList = this.tKey.metadata.factorPubs[this.tKey.tssTag]; return factorPubsList.map(factorPub => factorPub.toSEC1(factorKeyCurve, true).toString("hex")); }); if (!options.web3AuthClientId) { throw CoreKitError.clientIdInvalid(); } this._tssLib = options.tssLib; this._keyType = options.tssLib.keyType; this._sigType = options.tssLib.sigType; const isNodejsOrRN = this.isNodejsOrRN(options.uxMode); if (options.enableLogging) { log.enableAll(); this.enableLogging = true; } else log.setLevel("error"); if (typeof options.manualSync !== "boolean") options.manualSync = false; if (!options.web3AuthNetwork) options.web3AuthNetwork = WEB3AUTH_NETWORK.MAINNET; // if sessionTime is not provided, it is defaulted to 86400 if (!options.sessionTime) options.sessionTime = 86400; if (!options.serverTimeOffset) options.serverTimeOffset = 0; if (!options.uxMode) options.uxMode = UX_MODE.REDIRECT; if (!options.redirectPathName) options.redirectPathName = "redirect"; if (!options.baseUrl) options.baseUrl = isNodejsOrRN ? "https://localhost" : `${(_window = window) === null || _window === void 0 ? void 0 : _window.location.origin}/serviceworker`; if (!options.disableHashedFactorKey) options.disableHashedFactorKey = false; if (!options.hashedFactorNonce) options.hashedFactorNonce = options.web3AuthClientId; if (options.disableSessionManager === undefined) options.disableSessionManager = false; this.sessionSigGenerator = new DefaultSessionSigGeneratorPlugin(this); this.options = options; this.currentStorage = new AsyncStorage(this._storageBaseKey, options.storage); this.stateEmitter = new SafeEventEmitter(); if (!options.disableSessionManager) { this.sessionManager = new SessionManager({ sessionTime: options.sessionTime }); } Torus.setSessionTime(this.options.sessionTime); } get tKey() { if (this.tkey === null) { throw CoreKitError.tkeyInstanceUninitialized(); } return this.tkey; } get keyType() { return this._keyType; } get sigType() { return this._sigType; } get config() { return this.options; } get _storageKey() { return this._storageBaseKey; } get status() { try { // metadata will be present if tkey is initialized (1 share) // if 2 shares are present, then privKey will be present after metadatakey(tkey) reconstruction const { tkey } = this; if (!tkey) return COREKIT_STATUS.NOT_INITIALIZED; if (!tkey.metadata) return COREKIT_STATUS.INITIALIZED; if (!tkey.secp256k1Key || !this.state.factorKey) return COREKIT_STATUS.REQUIRED_SHARE; return COREKIT_STATUS.LOGGED_IN; } catch (e) {} return COREKIT_STATUS.NOT_INITIALIZED; } get sessionId() { var _this$sessionManager; return (_this$sessionManager = this.sessionManager) === null || _this$sessionManager === void 0 ? void 0 : _this$sessionManager.sessionId; } get supportsAccountIndex() { return this._sigType !== "ed25519"; } get verifier() { var _this$state$userInfo, _this$state; if ((_this$state$userInfo = this.state.userInfo) !== null && _this$state$userInfo !== void 0 && _this$state$userInfo.aggregateVerifier) { return this.state.userInfo.aggregateVerifier; } return (_this$state = this.state) !== null && _this$state !== void 0 && (_this$state = _this$state.userInfo) !== null && _this$state !== void 0 && _this$state.verifier ? this.state.userInfo.verifier : ""; } get verifierId() { var _this$state2; return (_this$state2 = this.state) !== null && _this$state2 !== void 0 && (_this$state2 = _this$state2.userInfo) !== null && _this$state2 !== void 0 && _this$state2.verifierId ? this.state.userInfo.verifierId : ""; } get isRedirectMode() { return this.options.uxMode === UX_MODE.REDIRECT; } get useClientGeneratedTSSKey() { return this._sigType === "ed25519" && this.options.useClientGeneratedTSSKey === undefined ? true : !!this.options.useClientGeneratedTSSKey; } setCustomSessionSigGenerator(sessionSigGenerator) { this.sessionSigGenerator = sessionSigGenerator; } async getSessionSignatures() { return this.sessionSigGenerator.getSessionSigs(); } // RecoverTssKey only valid for user that enable MFA where user has 2 type shares : // TssShareType.DEVICE and TssShareType.RECOVERY // if the factors key provided is the same type recovery will not works async _UNSAFE_recoverTssKey(factorKey) { this.checkReady(); const factorKeyBN = new BN(factorKey[0], "hex"); const shareStore0 = await this.getFactorKeyMetadata(factorKeyBN); await this.tKey.initialize({ withShare: shareStore0 }); const tssShares = []; const tssIndexes = []; const tssIndexesBN = []; for (let i = 0; i < factorKey.length; i++) { const factorKeyBNInput = new BN(factorKey[i], "hex"); const { tssIndex, tssShare } = await this.tKey.getTSSShare(factorKeyBNInput); if (tssIndexes.includes(tssIndex)) { // reset instance before throw error await this.init(); throw CoreKitError.duplicateTssIndex(); } tssIndexes.push(tssIndex); tssIndexesBN.push(new BN(tssIndex)); tssShares.push(tssShare); } const finalKey = lagrangeInterpolation(this.tkey.tssCurve, tssShares, tssIndexesBN); // reset instance after recovery completed await this.init(); return finalKey.toString("hex", 64); } async init(params = { handleRedirectResult: true }) { var _window2, _window3; this.resetState(); if (params.rehydrate === undefined) params.rehydrate = true; const nodeDetails = fetchLocalConfig(this.options.web3AuthNetwork, this.keyType); if (this._sigType === "ed25519" && this.options.useDKG) { throw CoreKitError.invalidConfig("DKG is not supported for ed25519 signature type"); } this.torusSp = new TSSTorusServiceProvider({ customAuthArgs: { web3AuthClientId: this.options.web3AuthClientId, baseUrl: this.options.baseUrl, uxMode: this.isNodejsOrRN(this.options.uxMode) ? UX_MODE.REDIRECT : this.options.uxMode, network: this.options.web3AuthNetwork, redirectPathName: this.options.redirectPathName, locationReplaceOnRedirect: true, serverTimeOffset: this.options.serverTimeOffset, keyType: this.keyType, useDkg: this.options.useDKG } }); this.storageLayer = new TorusStorageLayer({ hostUrl: `${new URL(nodeDetails.torusNodeEndpoints[0]).origin}/metadata`, enableLogging: this.enableLogging }); const shareSerializationModule = new ShareSerializationModule(); this.tkey = new TKeyTSS({ enableLogging: this.enableLogging, serviceProvider: this.torusSp, storageLayer: this.storageLayer, manualSync: this.options.manualSync, modules: { shareSerialization: shareSerializationModule }, tssKeyType: this.keyType }); if (this.isRedirectMode) { await this.torusSp.init({ skipSw: true, skipPrefetch: true }); } else if (this.options.uxMode === UX_MODE.POPUP) { await this.torusSp.init({}); } this.ready = true; // try handle redirect flow if enabled and return(redirect) from oauth login if (params.handleRedirectResult && this.options.uxMode === UX_MODE.REDIRECT && ((_window2 = window) !== null && _window2 !== void 0 && _window2.location.hash.includes("#state") || (_window3 = window) !== null && _window3 !== void 0 && _window3.location.hash.includes("#access_token"))) { // on failed redirect, instance is reseted. // skip check feature gating on redirection as it was check before login await this.handleRedirectResult(); // return early on successful redirect, the rest of the code will not be executed return; } else if (params.rehydrate && this.sessionManager) { // if not redirect flow try to rehydrate session if available const sessionId = await this.currentStorage.get("sessionId"); if (sessionId) { this.sessionManager.sessionId = sessionId; // swallowed, should not throw on rehydrate timed out session const sessionResult = await this.sessionManager.authorizeSession().catch(async err => { log.error("rehydrate session error", err); }); // try rehydrate session if (sessionResult) { await this.rehydrateSession(sessionResult); // return early on success rehydration return; } } } // feature gating if not redirect flow or session rehydration await this.featureRequest(); } async loginWithOAuth(params) { this.checkReady(); if (this.isNodejsOrRN(this.options.uxMode)) { throw CoreKitError.oauthLoginUnsupported(`Oauth login is NOT supported in ${this.options.uxMode} mode.`); } const { importTssKey, registerExistingSFAKey } = params; const tkeyServiceProvider = this.torusSp; if (registerExistingSFAKey && importTssKey) { throw CoreKitError.invalidConfig("Cannot import TSS key and register SFA key at the same time."); } if (this.isRedirectMode && (importTssKey || registerExistingSFAKey)) { throw CoreKitError.invalidConfig("key import is not supported in redirect mode"); } try { // oAuth login. const verifierParams = params; const aggregateParams = params; let loginResponse; let userInfo; if (verifierParams.subVerifierDetails) { // single verifier login. loginResponse = await tkeyServiceProvider.triggerLogin(params.subVerifierDetails); userInfo = loginResponse.userInfo; if (this.isRedirectMode) return; } else if (aggregateParams.subVerifierDetailsArray) { loginResponse = await tkeyServiceProvider.triggerAggregateLogin({ aggregateVerifierType: aggregateParams.aggregateVerifierType || AGGREGATE_VERIFIER.SINGLE_VERIFIER_ID, verifierIdentifier: aggregateParams.aggregateVerifierIdentifier, subVerifierDetailsArray: aggregateParams.subVerifierDetailsArray }); userInfo = loginResponse.userInfo[0]; if (this.isRedirectMode) return; } if (loginResponse && registerExistingSFAKey && loginResponse.finalKeyData.privKey) { if (loginResponse.metadata.typeOfUser === "v1") { throw CoreKitError.invalidConfig("Cannot register existing SFA key for v1 users, please contact web3auth support."); } const existingSFAKey = loginResponse.finalKeyData.privKey.padStart(64, "0"); await this.setupTkey({ providedImportKey: existingSFAKey, sfaLoginResponse: loginResponse, userInfo, importingSFAKey: true, persistSessionSigs: true }); } else { await this.setupTkey({ providedImportKey: importTssKey, sfaLoginResponse: loginResponse, userInfo, importingSFAKey: true, persistSessionSigs: true }); } } catch (err) { log.error("login error", err); if (err instanceof CoreError) { if (err.code === 1302) { throw CoreKitError.default(ERRORS.TKEY_SHARES_REQUIRED); } } throw CoreKitError.default(err.message); } } async loginWithJWT(params) { this.checkReady(); const { prefetchTssPublicKeys = 1 } = params; if (prefetchTssPublicKeys > 3) { throw CoreKitError.prefetchValueExceeded(`The prefetch value '${prefetchTssPublicKeys}' exceeds the maximum allowed limit of 3.`); } const { verifier, verifierId, idToken, importTssKey, registerExistingSFAKey } = params; this.torusSp.verifierName = verifier; this.torusSp.verifierId = verifierId; if (registerExistingSFAKey && importTssKey) { throw CoreKitError.invalidConfig("Cannot import TSS key and register SFA key at the same time."); } try { // prefetch tss pub keys. const prefetchTssPubs = []; for (let i = 0; i < prefetchTssPublicKeys; i++) { prefetchTssPubs.push(this.torusSp.getTSSPubKey(this.tkey.tssTag, i)); } // get postbox key. let loginPromise; if (!params.subVerifier) { // single verifier login. loginPromise = this.torusSp.customAuthInstance.getTorusKey(verifier, verifierId, { verifier_id: verifierId }, idToken, _objectSpread(_objectSpread({}, params.extraVerifierParams), params.additionalParams)); } else { // aggregate verifier login loginPromise = this.torusSp.customAuthInstance.getAggregateTorusKey(verifier, verifierId, [{ verifier: params.subVerifier, idToken, extraVerifierParams: params.extraVerifierParams }]); } // wait for prefetch completed before setup tkey const [loginResponse] = await Promise.all([loginPromise, ...prefetchTssPubs]); if (registerExistingSFAKey && loginResponse.finalKeyData.privKey) { if (loginResponse.metadata.typeOfUser === "v1") { throw CoreKitError.invalidConfig("Cannot register existing SFA key for v1 users, please contact web3auth support."); } const existingSFAKey = loginResponse.finalKeyData.privKey.padStart(64, "0"); await this.setupTkey({ providedImportKey: existingSFAKey, importingSFAKey: true, sfaLoginResponse: loginResponse, userInfo: _objectSpread(_objectSpread({}, parseToken(idToken)), {}, { verifier, verifierId }), persistSessionSigs: true }); } else { await this.setupTkey({ providedImportKey: importTssKey, importingSFAKey: false, sfaLoginResponse: loginResponse, userInfo: _objectSpread(_objectSpread({}, parseToken(idToken)), {}, { verifier, verifierId }), persistSessionSigs: true }); } } catch (err) { log.error("login error", err); if (err instanceof CoreError) { if (err.code === 1302) { const newError = CoreKitError.default(ERRORS.TKEY_SHARES_REQUIRED); newError.stack = err.stack; throw newError; } } const newError = CoreKitError.default(err.message); newError.stack = err.stack; throw newError; } } async handleRedirectResult() { this.checkReady(); try { const result = await this.torusSp.customAuthInstance.getRedirectResult(); let loginResponse; let userInfo; if (result.method === TORUS_METHOD.TRIGGER_LOGIN) { loginResponse = result.result; if (!loginResponse) { throw CoreKitError.invalidTorusLoginResponse(); } userInfo = loginResponse.userInfo; this.torusSp.verifierName = userInfo.verifier; } else if (result.method === TORUS_METHOD.TRIGGER_AGGREGATE_LOGIN) { loginResponse = result.result; if (!loginResponse) { throw CoreKitError.invalidTorusAggregateLoginResponse(); } userInfo = loginResponse.userInfo[0]; this.torusSp.verifierName = userInfo.aggregateVerifier; } else { throw CoreKitError.unsupportedRedirectMethod(); } this.torusSp.postboxKey = new BN(this.state.postBoxKey, "hex"); this.torusSp.verifierId = userInfo.verifierId; await this.setupTkey({ importingSFAKey: false, userInfo, sfaLoginResponse: loginResponse, persistSessionSigs: true }); } catch (error) { this.resetState(); log.error("error while handling redirect result", error); throw CoreKitError.default(error.message); } } async inputFactorKey(factorKey) { this.checkReady(); try { // input tkey device share when required share > 0 ( or not reconstructed ) // assumption tkey shares will not changed if (!this.tKey.secp256k1Key) { const factorKeyMetadata = await this.getFactorKeyMetadata(factorKey); await this.tKey.inputShareStoreSafe(factorKeyMetadata, true); } // Finalize initialization. await this.tKey.reconstructKey(); await this.finalizeTkey(factorKey); } catch (err) { log.error("login error", err); if (err instanceof CoreError) { if (err.code === 1302) { throw CoreKitError.default(ERRORS.TKEY_SHARES_REQUIRED); } } throw CoreKitError.default(err.message); } } async setTssWalletIndex(accountIndex, accountName) { const tssPubKey = this.tKey.getTSSPub(accountIndex).toSEC1(this.tkey.tssCurve, false); // Retrieve the existing general store domain data const generalStoreDomain = this.tkey.metadata.getGeneralStoreDomain("tssWalletIndex"); let tssWalletIndex = {}; if (generalStoreDomain) { tssWalletIndex = JSON.parse(generalStoreDomain); } // Check if the account index is already present if (!tssWalletIndex[accountIndex.toString()]) { tssWalletIndex[accountIndex.toString()] = { pubKey: tssPubKey.toString("hex"), name: accountName || "" }; } this.tkey.metadata.setGeneralStoreDomain("ts