Skip to content

Commit 84a8bd7

Browse files
Merge pull request #83 from Web3Auth/feat/importRecoverTssKey
feat: import recover tss key
2 parents d88f49b + 776a758 commit 84a8bd7

File tree

7 files changed

+170
-12
lines changed

7 files changed

+170
-12
lines changed

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
"eslint.format.enable": true,
77
"eslint.lintTask.enable": true,
88
"editor.codeActionsOnSave": {
9-
"source.fixAll": true
9+
"source.fixAll": "explicit"
1010
}
1111
}

src/interfaces.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export type MPCKeyDetails = {
7777
tssPubKey?: TkeyPoint;
7878
};
7979

80-
export type OauthLoginParams = SubVerifierDetailsParams | AggregateVerifierLoginParams;
80+
export type OauthLoginParams = (SubVerifierDetailsParams | AggregateVerifierLoginParams) & { importTssKey?: string };
8181
export type UserInfo = TorusVerifierResponse & LoginWindowResponse;
8282

8383
export interface EnableMFAParams {
@@ -132,6 +132,11 @@ export interface IdTokenLoginParams {
132132
* Any additional parameter (key value pair) you'd like to pass to the login function.
133133
*/
134134
additionalParams?: ExtraParams;
135+
136+
/**
137+
* Key to import key into Tss during first time login.
138+
*/
139+
importTssKey?: string;
135140
}
136141

137142
export interface Web3AuthState {

src/mpcCoreKit.ts

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable @typescript-eslint/member-ordering */
22
import { BNString, encrypt, getPubKeyPoint, Point as TkeyPoint, SHARE_DELETED, ShareStore, StringifiedType } from "@tkey-mpc/common-types";
3-
import ThresholdKey, { CoreError } from "@tkey-mpc/core";
3+
import ThresholdKey, { CoreError, lagrangeInterpolation } from "@tkey-mpc/core";
44
import { TorusServiceProvider } from "@tkey-mpc/service-provider-torus";
55
import { ShareSerializationModule } from "@tkey-mpc/share-serialization";
66
import { TorusStorageLayer } from "@tkey-mpc/storage-layer-torus";
@@ -207,6 +207,39 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
207207
return this.options.uxMode === UX_MODE.REDIRECT;
208208
}
209209

210+
// RecoverTssKey only valid for user that enable MFA where user has 2 type shares :
211+
// TssShareType.DEVICE and TssShareType.RECOVERY
212+
// if the factors key provided is the same type recovery will not works
213+
public async _UNSAFE_recoverTssKey(factorKey: string[]) {
214+
this.checkReady();
215+
const factorKeyBN = new BN(factorKey[0], "hex");
216+
const shareStore0 = await this.getFactorKeyMetadata(factorKeyBN);
217+
await this.tKey.initialize({ withShare: shareStore0 });
218+
219+
this.tkey.privKey = new BN(factorKey[1], "hex");
220+
221+
const tssShares: BN[] = [];
222+
const tssIndexes: number[] = [];
223+
const tssIndexesBN: BN[] = [];
224+
for (let i = 0; i < factorKey.length; i++) {
225+
const factorKeyBNInput = new BN(factorKey[i], "hex");
226+
const { tssIndex, tssShare } = await this.tKey.getTSSShare(factorKeyBNInput);
227+
if (tssIndexes.includes(tssIndex)) {
228+
// reset instance before throw error
229+
await this.init();
230+
throw new Error("Duplicate TSS Index");
231+
}
232+
tssIndexes.push(tssIndex);
233+
tssIndexesBN.push(new BN(tssIndex));
234+
tssShares.push(tssShare);
235+
}
236+
237+
const finalKey = lagrangeInterpolation(tssShares, tssIndexesBN);
238+
// reset instance after recovery completed
239+
await this.init();
240+
return finalKey.toString("hex", 64);
241+
}
242+
210243
public async init(params: InitParams = { handleRedirectResult: true }): Promise<void> {
211244
this.resetState();
212245
if (params.rehydrate === undefined) params.rehydrate = true;
@@ -274,7 +307,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
274307
public async loginWithOauth(params: OauthLoginParams): Promise<void> {
275308
this.checkReady();
276309
if (this.isNodejsOrRN(this.options.uxMode)) throw new Error(`Oauth login is NOT supported in ${this.options.uxMode}`);
277-
310+
const { importTssKey } = params;
278311
const tkeyServiceProvider = this.tKey.serviceProvider as TorusServiceProvider;
279312
try {
280313
// oAuth login.
@@ -307,7 +340,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
307340
});
308341
}
309342

310-
await this.setupTkey();
343+
await this.setupTkey(importTssKey);
311344
} catch (err: unknown) {
312345
log.error("login error", err);
313346
if (err instanceof CoreError) {
@@ -319,7 +352,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
319352

320353
public async loginWithJWT(idTokenLoginParams: IdTokenLoginParams): Promise<void> {
321354
this.checkReady();
322-
355+
const { importTssKey } = idTokenLoginParams;
323356
const { verifier, verifierId, idToken } = idTokenLoginParams;
324357
try {
325358
// oAuth login.
@@ -357,7 +390,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
357390
signatures: this._getSignatures(loginResponse.sessionData.sessionTokenData),
358391
});
359392

360-
await this.setupTkey();
393+
await this.setupTkey(importTssKey);
361394
} catch (err: unknown) {
362395
log.error("login error", err);
363396
if (err instanceof CoreError) {
@@ -758,7 +791,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
758791
if (!this.state.signatures) throw new Error("signatures not present");
759792

760793
const tssKeyBN = new BN(tssKey, "hex");
761-
this.tKey.importTssKey({ tag: this.tKey.tssTag, importKey: tssKeyBN, factorPub, newTSSIndex }, { authSignatures: this.state.signatures });
794+
await this.tKey.importTssKey({ tag: this.tKey.tssTag, importKey: tssKeyBN, factorPub, newTSSIndex }, { authSignatures: this.state.signatures });
762795
}
763796

764797
public async _UNSAFE_exportTssKey(): Promise<string> {
@@ -780,7 +813,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
780813
return tssNonce;
781814
}
782815

783-
private async setupTkey(): Promise<void> {
816+
private async setupTkey(importTssKey?: string): Promise<void> {
784817
if (!this.state.oAuthKey) {
785818
throw new Error("user not logged in");
786819
}
@@ -797,10 +830,15 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
797830
} else {
798831
factorKey = getHashedPrivateKey(this.state.oAuthKey, this.options.hashedFactorNonce);
799832
}
800-
const deviceTSSShare = new BN(generatePrivate());
801833
const deviceTSSIndex = TssShareType.DEVICE;
802834
const factorPub = getPubKeyPoint(factorKey);
803-
await this.tKey.initialize({ useTSS: true, factorPub, deviceTSSShare, deviceTSSIndex });
835+
if (!importTssKey) {
836+
const deviceTSSShare = new BN(generatePrivate());
837+
await this.tKey.initialize({ useTSS: true, factorPub, deviceTSSShare, deviceTSSIndex });
838+
} else {
839+
await this.tKey.initialize();
840+
await this.importTssKey(importTssKey, factorPub, deviceTSSIndex);
841+
}
804842

805843
// Finalize initialization.
806844
await this.tKey.reconstructKey();
@@ -814,6 +852,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
814852
await this.addFactorDescription(factorKey, FactorKeyTypeShareDescription.HashedShare);
815853
}
816854
} else {
855+
if (importTssKey) throw new Error("Cannot import tss key for existing user");
817856
await this.tKey.initialize({ neverInitializeNewKey: true });
818857
const hashedFactorKey = getHashedPrivateKey(this.state.oAuthKey, this.options.hashedFactorNonce);
819858
if ((await this.checkIfFactorKeyValid(hashedFactorKey)) && !this.options.disableHashedFactorKey) {

tests/importRecovery.spec.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/* eslint-disable mocha/handle-done-callback */
2+
import assert from "node:assert";
3+
import test from "node:test";
4+
5+
import * as TssLib from "@toruslabs/tss-lib-node";
6+
import { log } from "@web3auth/base";
7+
8+
import { BrowserStorage, IdTokenLoginParams, TssShareType, WEB3AUTH_NETWORK, Web3AuthMPCCoreKit } from "../src";
9+
import { criticalResetAccount, mockLogin, newCoreKitLogInInstance } from "./setup";
10+
11+
type ImportKeyTestVariable = {
12+
manualSync?: boolean;
13+
email: string;
14+
importKeyEmail: string;
15+
};
16+
export const ImportTest = async (testVariable: ImportKeyTestVariable) => {
17+
test(`import recover tss key : ${testVariable.manualSync}`, async function (t) {
18+
t.before(async () => {
19+
const instance = await newCoreKitLogInInstance({
20+
network: WEB3AUTH_NETWORK.DEVNET,
21+
manualSync: testVariable.manualSync,
22+
email: testVariable.email,
23+
});
24+
await criticalResetAccount(instance);
25+
await instance.logout();
26+
});
27+
28+
await t.test("#recover Tss key using 2 factors key, import tss key to new oauth login", async function () {
29+
const { idToken, parsedToken } = await mockLogin(testVariable.email);
30+
31+
const idTokenLoginParams = {
32+
verifier: "torus-test-health",
33+
verifierId: parsedToken.email,
34+
idToken,
35+
} as IdTokenLoginParams;
36+
37+
const coreKitInstance = new Web3AuthMPCCoreKit({
38+
web3AuthClientId: "torus-key-test",
39+
web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET,
40+
baseUrl: "http://localhost:3000",
41+
uxMode: "nodejs",
42+
tssLib: TssLib,
43+
storageKey: "memory",
44+
manualSync: testVariable.manualSync,
45+
});
46+
47+
await coreKitInstance.init();
48+
await coreKitInstance.loginWithJWT(idTokenLoginParams);
49+
50+
const factorKeyDevice = await coreKitInstance.createFactor({
51+
shareType: TssShareType.DEVICE,
52+
});
53+
54+
const factorKeyRecovery = await coreKitInstance.createFactor({
55+
shareType: TssShareType.RECOVERY,
56+
});
57+
58+
const exportedTssKey1 = await coreKitInstance._UNSAFE_exportTssKey();
59+
// recover key
60+
// reinitalize corekit
61+
await coreKitInstance.logout();
62+
BrowserStorage.getInstance("memory").resetStore();
63+
64+
const recoveredTssKey = await coreKitInstance._UNSAFE_recoverTssKey([factorKeyDevice, factorKeyRecovery]);
65+
assert.strictEqual(recoveredTssKey, exportedTssKey1);
66+
67+
await criticalResetAccount(coreKitInstance);
68+
BrowserStorage.getInstance("memory").resetStore();
69+
70+
// reinitialize corekit
71+
const newEmail = testVariable.importKeyEmail;
72+
const newLogin = await mockLogin(newEmail);
73+
74+
const newIdTokenLoginParams = {
75+
verifier: "torus-test-health",
76+
verifierId: newLogin.parsedToken.email,
77+
idToken: newLogin.idToken,
78+
importTssKey: recoveredTssKey,
79+
} as IdTokenLoginParams;
80+
81+
const coreKitInstance2 = new Web3AuthMPCCoreKit({
82+
web3AuthClientId: "torus-key-test",
83+
web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET,
84+
baseUrl: "http://localhost:3000",
85+
uxMode: "nodejs",
86+
tssLib: TssLib,
87+
storageKey: "memory",
88+
});
89+
90+
await coreKitInstance2.init();
91+
await coreKitInstance2.loginWithJWT(newIdTokenLoginParams);
92+
93+
const exportedTssKey = await coreKitInstance2._UNSAFE_exportTssKey();
94+
criticalResetAccount(coreKitInstance2);
95+
BrowserStorage.getInstance("memory").resetStore();
96+
97+
assert.strictEqual(exportedTssKey, recoveredTssKey);
98+
});
99+
100+
t.afterEach(function () {
101+
return log.info("finished running recovery test");
102+
});
103+
t.after(function () {
104+
return log.info("finished running recovery tests");
105+
});
106+
});
107+
};
108+
109+
const variable: ImportKeyTestVariable[] = [{ manualSync: false, email: "emailexport", importKeyEmail: "emailimport" }];
110+
111+
variable.forEach(async (testVariable) => {
112+
await ImportTest(testVariable);
113+
});

tests/login.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as TssLib from "@toruslabs/tss-lib-node";
88
import BN from "bn.js";
99
import { ec as EC } from "elliptic";
1010

11-
import { COREKIT_STATUS, WEB3AUTH_NETWORK, WEB3AUTH_NETWORK_TYPE, Web3AuthMPCCoreKit } from "../src";
11+
import { BrowserStorage, COREKIT_STATUS, WEB3AUTH_NETWORK, WEB3AUTH_NETWORK_TYPE, Web3AuthMPCCoreKit } from "../src";
1212
import { criticalResetAccount, mockLogin } from "./setup";
1313

1414
type TestVariable = {
@@ -52,6 +52,7 @@ variable.forEach((testVariable) => {
5252
test(`#Login Test with JWT + logout : ${testNameSuffix}`, async (t) => {
5353
t.before(async function () {
5454
if (coreKitInstance.status === COREKIT_STATUS.INITIALIZED) await criticalResetAccount(coreKitInstance);
55+
BrowserStorage.getInstance("memory").resetStore();
5556
});
5657

5758
t.after(async function () {

tests/mpcCoreKit.ts

Whitespace-only changes.

tests/signing.spec.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)