@guru_test/mpc-core-kit
Version:
MPC CoreKit SDK for web3Auth
1,291 lines (1,259 loc) • 61.9 kB
JavaScript
import _objectSpread from '@babel/runtime/helpers/objectSpread2';
import _defineProperty from '@babel/runtime/helpers/defineProperty';
import { Point, secp256k1, KeyType, ONE_KEY_DELETE_NONCE, ShareStore, SHARE_DELETED } from '@tkey/common-types';
import { CoreError } from '@tkey/core';
import { ShareSerializationModule } from '@tkey/share-serialization';
import { TorusStorageLayer } from '@tkey/storage-layer-torus';
import { factorKeyCurve, lagrangeInterpolation, TSSTorusServiceProvider, TKeyTSS, getPubKeyPoint } from '@tkey/tss';
import { SIGNER_MAP } from '@toruslabs/constants';
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 } from '@toruslabs/metadata-helpers';
import { SessionManager } from '@toruslabs/session-manager';
import { Torus } from '@toruslabs/torus.js';
import { setupSockets, getDKLSCoeff, Client } from '@toruslabs/tss-client';
import { sign } from '@toruslabs/tss-frost-client';
import { SafeEventEmitter } from '@web3auth/auth';
import BN from 'bn.js';
import bowser from 'bowser';
import { ec } from 'elliptic';
import { WEB3AUTH_NETWORK, ERRORS, TssShareType, VALID_SHARE_INDICES, FactorKeyTypeShareDescription, FIELD_ELEMENT_HEX_LEN, MAX_FACTORS, SOCIAL_TKEY_INDEX } from './constants.js';
import { AsyncStorage } from './helper/browserStorage.js';
import CoreKitError from './helper/errors.js';
import { COREKIT_STATUS } from './interfaces.js';
import { DefaultSessionSigGeneratorPlugin } from './plugins/DefaultSessionSigGenerator.js';
import { log, parseToken, getHashedPrivateKey, generateFactorKey, scalarBNToBufferSEC1, ed25519, generateSessionNonce, getSessionId, generateTSSEndpoints, generateEd25519Seed, sampleEndpoints, deriveShareCoefficients } from './utils.js';
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("tssWalletIndex", JSON.stringify(tssWalletIndex));
if (!this.tkey.manualSync) await this.tkey._syncShareMetadata();
this.updateState({
tssPubKey,
accountIndex
});
}
getTssWalletIndices() {
// Retrieve the existing general store domain data
const generalStoreDomain = this.tKey.metadata.getGeneralStoreDomain("tssWalletIndex");
let tssWalletIndex = {};
if (generalStoreDomain) {
tssWalletIndex = JSON.parse(generalStoreDomain);
}
// Convert the stored data into a list of indices, addresses, and names
const indicesAndAddresses = Object.keys(tssWalletIndex).map(index => ({
index: parseInt(index, 10),
address: tssWalletIndex[index].pubKey,
name: tssWalletIndex[index].name
}));
return indicesAndAddresses;
}
async updateTssWalletIndex(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);
}
// Update the account index
tssWalletIndex[accountIndex.toString()] = {
pubKey: tssPubKey.toString("hex"),
name: accountName
};
this.tkey.metadata.setGeneralStoreDomain("tssWalletIndex", JSON.stringify(tssWalletIndex));
if (!this.tkey.manualSync) await this.tkey._syncShareMetadata();
this.updateState({
tssPubKey,
accountIndex
});
}
async deleteTssWalletIndex(accountIndex) {
// 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 exists
if (!tssWalletIndex[accountIndex.toString()]) {
return `Account index ${accountIndex} not present`;
}
// Delete the account index
delete tssWalletIndex[accountIndex.toString()];
this.tkey.metadata.setGeneralStoreDomain("tssWalletIndex", JSON.stringify(tssWalletIndex));
if (!this.tkey.manualSync) await this.tkey._syncShareMetadata();
}
getCurrentFactorKey() {
this.checkReady();
if (!this.state.factorKey) {
throw CoreKitError.factorKeyNotPresent("factorKey not present in state when getting current factor key.");
}
if (!this.state.tssShareIndex) {
throw CoreKitError.tssShareTypeIndexMissing("TSS Share Type (Index) not present in state when getting current factor key.");
}
try {
return {
factorKey: this.state.factorKey,
shareType: this.state.tssShareIndex
};
} catch (err) {
log.error("state error", err);
throw CoreKitError.default(err.message);
}
}
async enableMFA(enableMFAParams, recoveryFactor = true) {
this.checkReady();
const {
postBoxKey
} = this.state;
const hashedFactorKey = getHashedPrivateKey(postBoxKey, this.options.hashedFactorNonce);
if (!(await this.checkIfFactorKeyValid(hashedFactorKey))) {
if (this.tKey._localMetadataTransitions[0].length) {
throw CoreKitError.commitChangesBeforeMFA();
}
throw CoreKitError.mfaAlreadyEnabled();
}
return this.atomicSync(async () => {
let browserData;
if (this.isNodejsOrRN(this.options.uxMode)) {
browserData = {
browserName: "Node Env",
browserVersion: "",
deviceName: "nodejs"
};
} else {
// try {
const browserInfo = bowser.parse(navigator.userAgent);
const browserName = `${browserInfo.browser.name}`;
browserData = {
browserName,
browserVersion: browserInfo.browser.version,
deviceName: browserInfo.os.name
};
}
const deviceFactorKey = new BN(await this.createFactor({
shareType: TssShareType.DEVICE,
additionalMetadata: browserData
}), "hex");
await this.setDeviceFactor(deviceFactorKey);
await this.inputFactorKey(new BN(deviceFactorKey, "hex"));
const hashedFactorPub = getPubKeyPoint(hashedFactorKey, factorKeyCurve);
await this.deleteFactor(hashedFactorPub, hashedFactorKey);
await this.deleteMetadataShareBackup(hashedFactorKey);
// only recovery factor = true
let backupFactorKey;
if (recoveryFactor) {
backupFactorKey = await this.createFactor(_objectSpread({
shareType: TssShareType.RECOVERY
}, enableMFAParams));
}
// update to undefined for next major release
return backupFactorKey;
}).catch(reason => {
log.error("error enabling MFA:", reason.message);
const err = CoreKitError.default(reason.message);
err.stack = reason.stack;
throw err;
});
}
// mutation function
async createFactor(createFactorParams) {
this.checkReady();
const {
shareType
} = createFactorParams;
let {
factorKey,
shareDescription,
additionalMetadata
} = createFactorParams;
if (!VALID_SHARE_INDICES.includes(shareType)) {
throw CoreKitError.newShareIndexInvalid(`Invalid share type provided (${shareType}). Valid share types are ${VALID_SHARE_INDICES}.`);
}
if (!factorKey) {
factorKey = generateFactorKey().private;
}
if (!shareDescription) {
shareDescription = FactorKeyTypeShareDescription.Other;
}
if (!additionalMetadata) {
additionalMetadata = {};
}
const factorPub = getPubKeyPoint(factorKey, factorKeyCurve);
if (this.getTssFactorPub().includes(factorPub.toSEC1(factorKeyCurve, true).toString("hex"))) {
throw CoreKitError.factorKeyAlreadyExists();
}
return this.atomicSync(async () => {
await this.copyOrCreateShare(shareType, factorPub);
await this.backupMetadataShare(factorKey);
await this.addFactorDescription({
factorKey,
shareDescription,
additionalMetadata,
updateMetadata: false
});
return scalarBNToBufferSEC1(factorKey).toString("hex");
}).catch(reason => {
log.error("error creating factor:", reason.message);
const err = CoreKitError.default(`error creating factor: ${reason.message}`);
err.stack = reason.stack;
throw err;
});
}
/**
* Get public key point in SEC1 format.
*/
getPubKey() {
const {
tssPubKey
} = this.state;
return Buffer.from(tssPubKey);
}
/**
* Get public key point.
*/
getPubKeyPoint() {
const {
tssPubKey
} = this.state;
return Point.fromSEC1(this.tkey.tssCurve, tssPubKey.toString("hex"));
}
/**
* Get public key in ed25519 format.
*
* Throws an error if signature type is not ed25519.
*/
getPubKeyEd25519() {
if (this._sigType !== "ed25519") {
throw CoreKitError.default(`getPubKeyEd25519 not supported for signature type ${this.sigType}`);
}
const p = this.tkey.tssCurve.keyFromPublic(this.getPubKey()).getPublic();
return ed25519().keyFromPublic(p).getPublic();
}
/**
* Get public key in bip340 format.
*
* Throws an error if signature type is not bip340.
*/
getPubKeyBip340() {
if (this._sigType !== "bip340") {
throw CoreKitError.default(`getPubKeyBip340 not supported for signature type ${this.sigType}`);
}
const p = this.tkey.tssCurve.keyFromPublic(this.getPubKey()).getPublic();
return p.getX().toBuffer("be", 32);
}
async precompute_secp256k1(params) {
const {
sessionSignatures
} = params || {};
this.wasmLib = await this.loadTssWasm();
// PreSetup
const {
tssShareIndex
} = this.state;
const tssPubKey = this.getPubKeyPoint();
const {
torusNodeTSSEndpoints
} = fetchLocalConfig(this.options.web3AuthNetwork, this.keyType);
if (!this.state.factorKey) {
throw CoreKitError.factorKeyNotPresent("factorKey not present in state when signing.");
}
const {
tssShare
} = await this.tKey.getTSSShare(this.state.factorKey, {
accountIndex: 0
});
const tssNonce = this.getTssNonce();
if (!tssPubKey || !torusNodeTSSEndpoints) {
throw CoreKitError.tssPublicKeyOrEndpointsMissing();
}
// session is needed for authentication to the web3auth infrastructure holding the factor 1
const randomSessionNonce = generateSessionNonce();
const currentSession = getSessionId(this.verifier, this.verifierId, this.tKey.tssTag, tssNonce, randomSessionNonce);
const parties = 4;
const clientIndex = parties - 1;
// 1. setup
// generate endpoints for servers
const {
nodeIndexes
} = await this.torusSp.getTSSPubKey(this.tKey.tssTag, this.tKey.metadata.tssNonces[this.tKey.tssTag]);
const {
endpoints,
tssWSEndpoints,
partyIndexes,
nodeIndexesReturned: participatingServerDKGIndexes
} = generateTSSEndpoints(torusNodeTSSEndpoints, parties, clientIndex, nodeIndexes);
// Setup sockets.
const sockets = await setupSockets(tssWSEndpoints, randomSessionNonce);
const dklsCoeff = getDKLSCoeff(true, participatingServerDKGIndexes, tssShareIndex);
const denormalisedShare = dklsCoeff.mul(tssShare).umod(secp256k1.curve.n);
const accountNonce = this.tkey.computeAccountNonce(this.state.accountIndex);
const derivedShare = denormalisedShare.add(accountNonce).umod(secp256k1.curve.n);
const share = scalarBNToBufferSEC1(derivedShare).toString("base64");
if (!currentSession) {
throw CoreKitError.activeSessionNotFound();
}
const signatures = sessionSignatures || (await this.getSessionSignatures());
if (!signatures) {
throw CoreKitError.signaturesNotPresent();
}
// Client lib expects pub key in XY-format, base64-encoded.
const tssPubKeyBase64 = Buffer.from(tssPubKey.toSEC1(secp256k1).subarray(1)).toString("base64");
const client = new Client(currentSession, clientIndex, partyIndexes, endpoints, sockets, share, tssPubKeyBase64, true, this.wasmLib);
// Suppress client logs if logging is disabled.
client.log = msg => {
if (!this.enableLogging) return;
// eslint-disable-next-line no-console
console.log(msg);
};
const serverCoeffs = {};
for (let i = 0; i < participatingServerDKGIndexes.length; i++) {
const serverIndex = participatingServerDKGIndexes[i];
serverCoeffs[serverIndex] = getDKLSCoeff(false, participatingServerDKGIndexes, tssShareIndex, serverIndex).toString("hex");
}
client.precompute({
signatures,
server_coeffs: serverCoeffs,
nonce: scalarBNToBufferSEC1(this.getAccountNonce()).toString("base64")
});
await client.ready().catch(err => {
client.cleanup({
signatures,
server_coeffs: serverCoeffs
});
throw err;
});
return {
client,
serverCoeffs,
signatures
};
}
async sign(data, opts) {
this.wasmLib = await this.loadTssWasm();
if (this._sigType === "ecdsa-secp256k1") {
if (opts !== null && opts !== void 0 && opts.keyTweak) {
throw CoreKitError.default("key tweaking not supported for ecdsa-secp256k1");
}
const sig = await this.sign_ECDSA_secp256k1(data, opts === null || opts === void 0 ? void 0 : opts.hashed, opts === null || opts === void 0 ? void 0 : opts.secp256k1Precompute);
return Buffer.concat([sig.r, sig.s, Buffer.from([sig.v])]);
} else if (this._sigType === "ed25519" || this._sigType === "bip340") {
if (opts !== null && opts !== void 0 && opts.hashed) {
throw CoreKitError.default(`hashed data not supported for bip340`);
} else if (opts !== null && opts !== void 0 && opts.keyTweak && this._sigType !== "bip340") {
throw CoreKitError.default("key tweaking not supported for ed25519");
}
return this.sign_frost(data, opts === null || opts === void 0 ? void 0 : opts.keyTweak);
}
throw CoreKitError.default(`sign not supported for key type ${this.keyType}`);
}
// mutation function
async deleteFactor(factorPub, factorKey) {
if (!this.state.factorKey) {
throw CoreKitError.factorKeyNotPresent("factorKey not present in state when deleting a factor.");
}
if (!this.tKey.metadata.factorPubs) {
throw CoreKitError.factorPubsMissing();
}
await this.atomicSync(async () => {
const remainingFactors = this.tKey.metadata.factorPubs[this.tKey.tssTag].length || 0;
if (remainingFactors <= 1) {
throw CoreKitError.cannotDeleteLastFactor("Cannot delete last factor");
}
const fpp = factorPub;
const stateFpp = getPubKeyPoint(this.state.factorKey, factorKeyCurve);
if (fpp.equals(stateFpp)) {
throw CoreKitError.factorInUseCannotBeDeleted("Cannot delete current active factor");
}
const authSignatures = await this.getSessionSignatures();
await this.tKey.deleteFactorPub({
factorKey: this.state.factorKey,
deleteFactorPub: factorPub,
authSignatures
});
const factorPubHex = fpp.toSEC1(factorKeyCurve, true).toString("hex");
const allDesc = this.tKey.metadata.getShareDescription();
const keyDesc = allDesc[factorPubHex];
if (keyDesc) {
await Promise.all(keyDesc.map(async desc => {
var _this$tKey;
return (_this$tKey = this.tKey) === null || _this$tKey === void 0 ? void 0 : _this$tKey.metadata.deleteShareDescription(factorPubHex, desc);
}));
}
// delete factorKey share metadata if factorkey is provided
if (factorKey) {
const factorKeyBN = new BN(factorKey, "hex");
const derivedFactorPub = getPubKeyPoint(factorKeyBN, factorKeyCurve);
// only delete if factorPub matches
if (derivedFactorPub.equals(fpp)) {
await this.deleteMetadataShareBackup(factorKeyBN);
}
}
});
}
async logout() {
var _this$sessionManager2;
if ((_this$sessionManager2 = this.sessionManager) !== null && _this$sessionManager2 !== void 0 && _this$sessionManager2.sessionId) {
await this.sessionManager.invalidateSession();
}
// to accommodate async storage
await this.currentStorage.set("sessionId", "");
this.resetState();
await this.init({
handleRedirectResult: false,
rehydrate: false
});
this.stateEmitter.emit("LOGOUT");
}
getUserInfo() {
if (!this.state.userInfo) {
throw CoreKitError.userNotLoggedIn();
}
return this.state.userInfo;
}
getKeyDetails() {
this.checkReady();
const tkeyDetails = this.tKey.getKeyDetails();
const tssPubKey = this.state.tssPubKey ? Point.fromSEC1(this.tkey.tssCurve, this.state.tssPubKey.toString("hex")) : undefined;
const factors = this.tKey.metadata.factorPubs ? this.tKey.metadata.factorPubs[this.tKey.tssTag] : [];
const keyDetails = {
// use tkey's for now
requiredFactors: tkeyDetails.requiredShares,
threshold: tkeyDetails.threshold,
totalFactors: factors.length + 1,
shareDescriptions: this.tKey.getMetadata().getShareDescription(),
metadataPubKey: tkeyDetails.pubKey,
tssPubKey,
keyType: this.keyType
};
return keyDetails;
}
async commitChanges() {
this.checkReady();
if (!this.state.factorKey) {
throw CoreKitError.factorKeyNotPresent("factorKey not present in state when committing changes.");
}
try {
// in case for manualsync = true, _syncShareMetadata will not call syncLocalMetadataTransitions()
// it will not create a new LocalMetadataTransition
// manual call syncLocalMetadataTransitions() required to sync local transitions to storage
await this.tKey._syncShareMetadata();
await this.tKey.syncLocalMetadataTransitions();
} catch (error) {
log.error("sync metadata error", error);
throw error;
}
}
async setManualSync(manualSync) {
this.checkReady();
// sync local transistion to storage before allow changes
await this.tKey.syncLocalMetadataTransitions();
this.options.manualSync = manualSync;
this.tKey.manualSync = manualSync;
}
// device factor
async setDeviceFactor(factorKey, replace = false) {
if (!replace) {
const existingFactor = await this.getDeviceFactor();
if (existingFactor) {
throw CoreKitError.default("Device factor already exists");
}
}
const metadata = this.tKey.getMetadata();
const tkeyPubX = metadata.pubKey.x.toString(16, FIELD_ELEMENT_HEX_LEN);
await this.currentStorage.set(tkeyPubX, JSON.stringify({
factorKey: factorKey.toString("hex").padStart(64, "0")
}));
}
async getDeviceFactor() {
const metadata = this.tKey.getMetadata();
const tkeyPubX = metadata.pubKey.x.toString(16, FIELD_ELEMENT_HEX_LEN);
const tKeyLocalStoreString = await this.currentStorage.get(tkeyPubX);
const tKeyLocalStore = JSON.parse(tKeyLocalStoreString || "{}");
return tKeyLocalStore.factorKey;
}
/**
* WARNING: Use with caution. This will export the private signing key.
*
* Exports the private key scalar for the current account index.
*
* For signature type ed25519, consider using _UNSAFE_exportTssEd25519Seed.
*/
async _UNSAFE_exportTssKey() {
if (!this.state.factorKey) {
throw CoreKitError.factorKeyNotPresent("factorKey not present in state when exporting tss key.");
}
if (!this.state.signatures) {
throw CoreKitError.signaturesNotPresent("Signatures not present in state when exporting tss key.");
}
const exportTssKey0 = await this.tKey._UNSAFE_exportTssKey({
factorKey: this.state.factorKey,
authSignatures: this.state.signatures
});
const accountNonce = this.getAccountNonce();
const tssKey = exportTssKey0.add(accountNonce).umod(this.tKey.tssCurve.n);
return tssKey.toString("hex", FIELD_ELEMENT_HEX_LEN);
}
/**
* WARNING: Use with caution. This will export the private signing key.
*
* Attempts to export the ed25519 private key seed. Only works if import key
* flow has been used.
*/
async _UNSAFE_exportTssEd25519Seed() {
if (this._sigType !== "ed25519") {
throw CoreKitError.default("Wrong signature type. Method can only be used when signature type is ed25519.");
}
if (!this.state.factorKey) throw CoreKitError.factorKeyNotPresent("factorKey not present in state when exporting tss ed25519 seed.");
if (!this.state.signatures) throw CoreKitError.signaturesNotPresent("Signatures not present in state when exporting tss ed25519 seed.");
try {
const exportEd25519Seed = await this.tKey._UNSAFE_exportTssEd25519Seed({
factorKey: this.state.factorKey,
authSignatures: this.state.signatures
});
return exportEd25519Seed;
} catch (error) {
throw CoreKitError.default(`Error exporting ed25519 seed: ${error}`);
}
}
updateState(newState) {
this.state = _objectSpread(_objectSpread({}, this.state), newState);
}
async setupTkey(params) {
var _sfaLoginResponse$nod;
const {
providedImportKey,
sfaLoginResponse,
userInfo,
importingSFAKey,
persistSessionSigs
} = params;
if (importingSFAKey && !sfaLoginResponse) {
throw CoreKitError.default("SFA key registration requires SFA login response");
}
const postBoxKey = this._getPostBoxKey(sfaLoginResponse);
this.torusSp.postboxKey = new BN(postBoxKey, "hex");
this.updateState({
postBoxKey,
postboxKeyNodeIndexes: (_sfaLoginResponse$nod = sfaLoginResponse.nodesData) === null || _sfaLoginResponse$nod === void 0 ? void 0 : _sfaLoginResponse$nod.nodeIndexes,
userInfo,
signatures: persistSessionSigs ? this.extractSessionSignatures(sfaLoginResponse.sessionData.sessionTokenData) : []
});
const sp = this.tkey.serviceProvider;
if (!sp) {
throw new Error("Oauth Service provider is missing in tkey");
}
if (!(sp !== null && sp !== void 0 && sp.verifierId)) {
sp.verifierId = userInfo.verifierId;
}
if (!(sp !== null && sp !== void 0 && sp.verifierName)) {
sp.verifierName = userInfo.aggregateVerifier || userInfo.verifier;
}
const existingUser = await this.isMetadataPresent(this.state.postBoxKey);
let importKey = providedImportKey;
if (!existingUser) {
if (!importKey && this.useClientGeneratedTSSKey) {
if (this.keyType === KeyType.ed25519) {
const k = generateEd25519Seed();
importKey = k.toString("hex");
} else if (this.keyType === KeyType.secp256k1) {
const k = secp256k1.genKeyPair().getPrivate();
importKey = scalarBNToBufferSEC1(k).toString("hex");
} else {
throw CoreKitError.default(`Unsupported key type and sig type combination: ${this.keyType}, ${this._sigType}`);
}
}
if (importingSFAKey && sfaLoginResponse && sfaLoginResponse.metadata.upgraded) {
throw CoreKitError.default("SFA key registration is not allowed for already upgraded users");
}
await this.handleNewUser(importKey, importingSFAKey);
} else {
if (importKey) {
throw CoreKitError.tssKeyImportNotAllowed();
}
await this.handleExistingUser();
}
}
async atomicSync(f) {
this.atomicCallStackCounter += 1;
this.tkey.manualSync = true;
try {
const r = await f();
if (this.atomicCallStackCounter === 1) {
if (!this.options.manualSync) {
await this.commitChanges();
}
}
return r;
} catch (error) {
throw error;
} finally {
this.atomicCallStackCounter -= 1;
if (this.atomicCallStackCounter === 0) {
this.tkey.manualSync = this.options.manualSync;
}
}
}
async importTssKey(tssKey, factorPub, newTSSIndex = TssShareType.DEVICE) {
if (!this.state.signatures) {
throw CoreKitError.signaturesNotPresent("Signatures not present in state when importing tss key.");
}
await this.tKey.importTssKey({
tag: this.tKey.tssTag,
importKey: Buffer.from(tssKey, "hex"),
factorPub,
newTSSIndex
}, {
authSignatures: this.state.signatures
});
}
getTssNonce() {
if (!this.tKey.metadata.tssNonces || this.tKey.metadata.tssNonces[this.tKey.tssTag] === undefined) {
throw CoreKitError.tssNoncesMissing(`tssNonce not present for tag ${this.tKey.tssTag}`);
}
const tssNonce = this.tKey.metadata.tssNonces[this.tKey.tssTag];
return tssNonce;
}
// mutation function
async handleNewUser(importTssKey, isSfaKey) {
await this.atomicSync(async () => {
// Generate or use hash factor and initialize tkey with it.
let factorKey;
if (this.options.disableHashedFactorKey) {
factorKey = generateFactorKey().private;
// delete previous hashed factorKey if present
const hashedFactorKey = getHashedPrivateKey(this.state.postBoxKey, this.options.hashedFactorNonce);
await this.deleteMetadataShareBackup(hashedFactorKey);
} else {
factorKey = getHashedPrivateKey(this.state.postBoxKey, this.options.hashedFactorNonce);
}
const deviceTSSIndex = TssShareType.DEVICE;
const factorPub = getPubKeyPoint(factorKey, factorKeyCurve);
if (!importTssKey) {
const ec$1 = new ec(this.keyType);
const deviceTSSShare = ec$1.genKeyPair().getPrivate();
await this.tKey.initialize({
factorPub,
deviceTSSShare,
deviceTSSIndex
});
} else {
await this.tKey.initialize({
skipTssInit: true
});
await this.tKey.reconstructKey();
await this.importTssKey(importTssKey, factorPub, deviceTSSIndex);
}
// Finalize initialization.
await this.tKey.reconstructKey();
await this.finalizeTkey(factorKey);
// Store factor description.
await this.backupMetadataShare(factorKey);
if (this.options.disableHashedFactorKey) {
await this.addFactorDescription({
factorKey,
shareDescription: FactorKeyTypeShareDescription.Other,
updateMetadata: false
});
} else {
await this.addFactorDescription({
factorKey,
shareDescription: FactorKeyTypeShareDescription.HashedShare,
updateMetadata: false
});
}
if (importTssKey && isSfaKey) {
await this.tkey.addLocalMetadataTransitions({
input: [{
message: ONE_KEY_DELETE_NONCE
}],
privKey: [new BN(this.state.postBoxKey, "hex")]
});
}
});
}
async handleExistingUser() {
await this.tKey.initialize({
neverInitializeNewKey: true
});
if (this.options.disableHashedFactorKey) {
return;
}
const hashedFactorKey = getHashedPrivateKey(this.state.postBoxKey, this.options.hashedFactorNonce);
this.state.factorKey = hashedFactorKey;
if (await this.checkIfFactorKeyValid(hashedFactorKey)) {
// Initialize tkey with existing hashed share if available.
const factorKeyMetadata = await this.getFactorKeyMetadata(hashedFactorKey);
try {
await this.tKey.inputShareStoreSafe(factorKeyMetadata, true);
await this.tKey.reconstructKey();
await this.finalizeTkey(hashedFactorKey);
} catch (err) {
log.error("error initializing tkey with hashed share", err);
}
} else {
var _this$tKey2;
const factorKeyMetadata = await ((_this$tKey2 = this.tKey) === null || _this$tKey2 === void 0 ? void 0 : _this$tKey2.readMetadata(hashedFactorKey));
if (factorKeyMetadata.message === "SHARE_DELETED") {
// throw CoreKitError.hashedFactorDeleted();
log.warn("hashed factor deleted");
}
}
}
async finalizeTkey(factorKey) {
if (this.state.accountIndex !== 0) {
log.warn("AccountIndex should be 0");
this.state.accountIndex = 0;
}
// Read tss meta data.
const {
tssIndex: tssShareIndex
} = await this.tKey.getTSSShare(factorKey);
const tssPubKey = this.tKey.getTSSPub().toSEC1(this.tkey.tssCurve, false);
this.updateState({
tssShareIndex,
tssPubKey,
factorKey
});
await this.createSession();
}
checkReady() {
if (!this.ready) {
throw CoreKitError.mpcCoreKitNotInitialized();
}
}
async rehydrateSession(result) {
try {
this.checkReady();
const factorKey = new BN(result.factorKey, "hex");
if (!factorKey) {
throw CoreKitError.providedFactorKeyInvalid();
}
const postBoxKey = result.postBoxKey || result.oAuthKey;
if (!postBoxKey) {
throw CoreKitError.default("postBoxKey or oAuthKey not present in session data");
}
this.torusSp.postboxKey = new BN(postBoxKey, "hex");
this.torusSp.verifierName = result.userInfo.aggregateVerifier || result.userInfo.verifier;
this.torusSp.verifierId = result.userInfo.verifierId;
const factorKeyMetadata = await this.getFactorKeyMetadata(factorKey);
await this.tKey.initialize({
neverInitializeNewKey: true
});
await this.tKey.inputShareStoreSafe(factorKeyMetadata, true);
await this.tKey.reconstructKey();
this.updateState({
factorKey: new BN(result.factorKey, "hex"),
postBoxKey,
postboxKeyNodeIndexes: result.postboxKeyNodeIndexes || [],
tssShareIndex: result.tssShareIndex,
tssPubKey: this.tkey.getTSSPub().toSEC1(this.tKey.tssCurve, false),
signatures: result.signatures,
userInfo: result.userInfo
});
} catch (err) {
log.warn("failed to authorize session", err);
}
}
async createSession() {
if (!this.options.disableSessionManager && !this.sessionManager) {
throw new Error("sessionManager is not available");
}
try {
const sessionId = SessionManager.generateRandomSessionKey();
this.sessionManager.sessionId = sessionId;
const {
postBoxKey,
factorKey,
userInfo,
tssShareIndex,
tssPubKey,
postboxKeyNodeIndexes
} = this.state;
if (!this.state.factorKey) {
throw CoreKitError.factorKeyNotPresent("factorKey not present in state when creating session.");
}
const {
tssShare
} = await this.tKey.getTSSShare(this.state.factorKey, {
accountIndex: this.state.accountIndex
});
if (!postBoxKey || !factorKey || !tssShare || !tssPubKey || !userInfo) {
throw CoreKitError.userNotLoggedIn();
}
const sessionSigs = this.state.signatures;
const payload = {
postBoxKey,
postboxKeyNodeIndexes: postboxKeyNodeIndexes || [],
factorKey: factorKey === null || factorKey === void 0 ? void 0 : factorKey.toString("hex"),
tssShareIndex: tssShareIndex,
tssPubKey: Buffer.from(tssPubKey).toString("hex"),
signatures: sessionSigs,
userInfo
};
await this.sessionManager.createSession(payload);
// to accommodate async storage
await this.currentStorage.set("sessionId", sessionId);
} catch (err) {
log.error("error creating session", err);
}
}
async isMetadataPresent(privateKey) {
var _this$tKey3;
const pri