Skip to content

Commit 74c77f9

Browse files
Merge pull request #200 from Web3Auth/feat/import-sfa
import sfa key
2 parents a14138b + 8c02c84 commit 74c77f9

File tree

5 files changed

+255
-33
lines changed

5 files changed

+255
-33
lines changed

src/interfaces.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,18 @@ export type MPCKeyDetails = {
8585
tssPubKey?: TkeyPoint;
8686
};
8787

88-
export type OAuthLoginParams = (SubVerifierDetailsParams | AggregateVerifierLoginParams) & { importTssKey?: string };
88+
export type OAuthLoginParams = (SubVerifierDetailsParams | AggregateVerifierLoginParams) & {
89+
/**
90+
* Key to import key into Tss during first time login.
91+
*/
92+
importTssKey?: string;
93+
94+
/**
95+
* For new users, use SFA key if user was registered with SFA before.
96+
* Useful when you created the user with SFA before and now want to convert it to TSS.
97+
*/
98+
registerExistingSFAKey?: boolean;
99+
};
89100
export type UserInfo = TorusVerifierResponse & LoginWindowResponse;
90101

91102
export interface EnableMFAParams {
@@ -146,6 +157,12 @@ export interface JWTLoginParams {
146157
*/
147158
importTssKey?: string;
148159

160+
/**
161+
* For new users, use SFA key if user was registered with SFA before.
162+
* Useful when you created the user with SFA before and now want to convert it to TSS.
163+
*/
164+
registerExistingSFAKey?: boolean;
165+
149166
/**
150167
* Number of TSS public keys to prefetch. For the best performance, set it to
151168
* the number of factors you want to create. Set it to 0 for an existing user.

src/mpcCoreKit.ts

Lines changed: 72 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BNString, KeyType, Point, secp256k1, SHARE_DELETED, ShareStore, StringifiedType } from "@tkey/common-types";
1+
import { BNString, KeyType, ONE_KEY_DELETE_NONCE, Point, secp256k1, SHARE_DELETED, ShareStore, StringifiedType } from "@tkey/common-types";
22
import { CoreError } from "@tkey/core";
33
import { ShareSerializationModule } from "@tkey/share-serialization";
44
import { TorusStorageLayer } from "@tkey/storage-layer-torus";
@@ -318,16 +318,24 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
318318
if (this.isNodejsOrRN(this.options.uxMode)) {
319319
throw CoreKitError.oauthLoginUnsupported(`Oauth login is NOT supported in ${this.options.uxMode} mode.`);
320320
}
321-
const { importTssKey } = params;
321+
const { importTssKey, registerExistingSFAKey } = params;
322322
const tkeyServiceProvider = this.torusSp;
323323

324+
if (registerExistingSFAKey && importTssKey) {
325+
throw CoreKitError.invalidConfig("Cannot import TSS key and register SFA key at the same time.");
326+
}
327+
328+
if (this.isRedirectMode && (importTssKey || registerExistingSFAKey)) {
329+
throw CoreKitError.invalidConfig("key import is not supported in redirect mode");
330+
}
324331
try {
325332
// oAuth login.
326333
const verifierParams = params as SubVerifierDetailsParams;
327334
const aggregateParams = params as AggregateVerifierLoginParams;
335+
let loginResponse: TorusLoginResponse | TorusAggregateLoginResponse;
328336
if (verifierParams.subVerifierDetails) {
329337
// single verifier login.
330-
const loginResponse = await tkeyServiceProvider.triggerLogin((params as SubVerifierDetailsParams).subVerifierDetails);
338+
loginResponse = await tkeyServiceProvider.triggerLogin((params as SubVerifierDetailsParams).subVerifierDetails);
331339

332340
if (this.isRedirectMode) return;
333341

@@ -338,7 +346,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
338346
signatures: this._getSignatures(loginResponse.sessionData.sessionTokenData),
339347
});
340348
} else if (aggregateParams.subVerifierDetailsArray) {
341-
const loginResponse = await tkeyServiceProvider.triggerAggregateLogin({
349+
loginResponse = await tkeyServiceProvider.triggerAggregateLogin({
342350
aggregateVerifierType: aggregateParams.aggregateVerifierType || AGGREGATE_VERIFIER.SINGLE_VERIFIER_ID,
343351
verifierIdentifier: aggregateParams.aggregateVerifierIdentifier as string,
344352
subVerifierDetailsArray: aggregateParams.subVerifierDetailsArray,
@@ -354,7 +362,15 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
354362
});
355363
}
356364

357-
await this.setupTkey(importTssKey);
365+
if (loginResponse && registerExistingSFAKey && loginResponse.finalKeyData.privKey) {
366+
if (loginResponse.metadata.typeOfUser === "v1") {
367+
throw CoreKitError.invalidConfig("Cannot register existing SFA key for v1 users, please contact web3auth support.");
368+
}
369+
const existingSFAKey = loginResponse.finalKeyData.privKey.padStart(64, "0");
370+
await this.setupTkey(existingSFAKey, loginResponse, true);
371+
} else {
372+
await this.setupTkey(importTssKey, loginResponse, false);
373+
}
358374
} catch (err: unknown) {
359375
log.error("login error", err);
360376
if (err instanceof CoreError) {
@@ -373,10 +389,14 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
373389
throw CoreKitError.prefetchValueExceeded(`The prefetch value '${prefetchTssPublicKeys}' exceeds the maximum allowed limit of 3.`);
374390
}
375391

376-
const { verifier, verifierId, idToken, importTssKey } = params;
392+
const { verifier, verifierId, idToken, importTssKey, registerExistingSFAKey } = params;
377393
this.torusSp.verifierName = verifier;
378394
this.torusSp.verifierId = verifierId;
379395

396+
if (registerExistingSFAKey && importTssKey) {
397+
throw CoreKitError.invalidConfig("Cannot import TSS key and register SFA key at the same time.");
398+
}
399+
380400
try {
381401
// prefetch tss pub keys.
382402
const prefetchTssPubs = [];
@@ -412,7 +432,15 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
412432
userInfo: { ...parseToken(idToken), verifier, verifierId },
413433
signatures: this._getSignatures(loginResponse.sessionData.sessionTokenData),
414434
});
415-
await this.setupTkey(importTssKey);
435+
if (registerExistingSFAKey && loginResponse.finalKeyData.privKey) {
436+
if (loginResponse.metadata.typeOfUser === "v1") {
437+
throw CoreKitError.invalidConfig("Cannot register existing SFA key for v1 users, please contact web3auth support.");
438+
}
439+
const existingSFAKey = loginResponse.finalKeyData.privKey.padStart(64, "0");
440+
await this.setupTkey(existingSFAKey, loginResponse, true);
441+
} else {
442+
await this.setupTkey(importTssKey, loginResponse, false);
443+
}
416444
} catch (err: unknown) {
417445
log.error("login error", err);
418446
if (err instanceof CoreError) {
@@ -433,30 +461,30 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
433461

434462
try {
435463
const result = await this.torusSp.customAuthInstance.getRedirectResult();
436-
464+
let loginResponse: TorusLoginResponse | TorusAggregateLoginResponse;
437465
if (result.method === TORUS_METHOD.TRIGGER_LOGIN) {
438-
const data = result.result as TorusLoginResponse;
439-
if (!data) {
466+
loginResponse = result.result as TorusLoginResponse;
467+
if (!loginResponse) {
440468
throw CoreKitError.invalidTorusLoginResponse();
441469
}
442470
this.updateState({
443-
postBoxKey: this._getPostBoxKey(data),
444-
postboxKeyNodeIndexes: data.nodesData?.nodeIndexes || [],
445-
userInfo: data.userInfo,
446-
signatures: this._getSignatures(data.sessionData.sessionTokenData),
471+
postBoxKey: this._getPostBoxKey(loginResponse),
472+
postboxKeyNodeIndexes: loginResponse.nodesData?.nodeIndexes || [],
473+
userInfo: loginResponse.userInfo,
474+
signatures: this._getSignatures(loginResponse.sessionData.sessionTokenData),
447475
});
448476
const userInfo = this.getUserInfo();
449477
this.torusSp.verifierName = userInfo.verifier;
450478
} else if (result.method === TORUS_METHOD.TRIGGER_AGGREGATE_LOGIN) {
451-
const data = result.result as TorusAggregateLoginResponse;
452-
if (!data) {
479+
loginResponse = result.result as TorusAggregateLoginResponse;
480+
if (!loginResponse) {
453481
throw CoreKitError.invalidTorusAggregateLoginResponse();
454482
}
455483
this.updateState({
456-
postBoxKey: this._getPostBoxKey(data),
457-
postboxKeyNodeIndexes: data.nodesData?.nodeIndexes || [],
458-
userInfo: data.userInfo[0],
459-
signatures: this._getSignatures(data.sessionData.sessionTokenData),
484+
postBoxKey: this._getPostBoxKey(loginResponse),
485+
postboxKeyNodeIndexes: loginResponse.nodesData?.nodeIndexes || [],
486+
userInfo: loginResponse.userInfo[0],
487+
signatures: this._getSignatures(loginResponse.sessionData.sessionTokenData),
460488
});
461489
const userInfo = this.getUserInfo();
462490
this.torusSp.verifierName = userInfo.aggregateVerifier;
@@ -984,35 +1012,45 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
9841012
return tssNonce;
9851013
}
9861014

987-
private async setupTkey(providedImportTssKey?: string): Promise<void> {
1015+
private async setupTkey(
1016+
providedImportKey?: string,
1017+
sfaLoginResponse?: TorusKey | TorusLoginResponse | TorusAggregateLoginResponse,
1018+
importingSFAKey?: boolean
1019+
): Promise<void> {
1020+
if (importingSFAKey && !sfaLoginResponse) {
1021+
throw CoreKitError.default("SFA key registration requires SFA login response");
1022+
}
9881023
if (!this.state.postBoxKey) {
9891024
throw CoreKitError.userNotLoggedIn();
9901025
}
9911026
const existingUser = await this.isMetadataPresent(this.state.postBoxKey);
992-
let importTssKey = providedImportTssKey;
1027+
let importKey = providedImportKey;
9931028
if (!existingUser) {
994-
if (!importTssKey && this.useClientGeneratedTSSKey) {
1029+
if (!importKey && this.useClientGeneratedTSSKey) {
9951030
if (this.keyType === KeyType.ed25519) {
9961031
const k = generateEd25519Seed();
997-
importTssKey = k.toString("hex");
1032+
importKey = k.toString("hex");
9981033
} else if (this.keyType === KeyType.secp256k1) {
9991034
const k = secp256k1.genKeyPair().getPrivate();
1000-
importTssKey = scalarBNToBufferSEC1(k).toString("hex");
1035+
importKey = scalarBNToBufferSEC1(k).toString("hex");
10011036
} else {
10021037
throw CoreKitError.default("Unsupported key type");
10031038
}
10041039
}
1005-
await this.handleNewUser(importTssKey);
1040+
if (importingSFAKey && sfaLoginResponse && sfaLoginResponse.metadata.upgraded) {
1041+
throw CoreKitError.default("SFA key registration is not allowed for already upgraded users");
1042+
}
1043+
await this.handleNewUser(importKey, importingSFAKey);
10061044
} else {
1007-
if (importTssKey) {
1045+
if (importKey) {
10081046
throw CoreKitError.tssKeyImportNotAllowed();
10091047
}
10101048
await this.handleExistingUser();
10111049
}
10121050
}
10131051

10141052
// mutation function
1015-
private async handleNewUser(importTssKey?: string) {
1053+
private async handleNewUser(importTssKey?: string, isSfaKey?: boolean) {
10161054
await this.atomicSync(async () => {
10171055
// Generate or use hash factor and initialize tkey with it.
10181056
let factorKey: BN;
@@ -1055,6 +1093,12 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
10551093
updateMetadata: false,
10561094
});
10571095
}
1096+
if (importTssKey && isSfaKey) {
1097+
await this.tkey.addLocalMetadataTransitions({
1098+
input: [{ message: ONE_KEY_DELETE_NONCE }],
1099+
privKey: [new BN(this.state.postBoxKey, "hex")],
1100+
});
1101+
}
10581102
});
10591103
}
10601104

tests/importRecovery.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import test from "node:test";
44
import { tssLib as tssLibDKLS } from "@toruslabs/tss-dkls-lib";
55
import { tssLib as tssLibFROST } from "@toruslabs/tss-frost-lib";
66

7-
import { AsyncStorage, MemoryStorage, TssLib, TssShareType, WEB3AUTH_NETWORK } from "../src";
7+
import { AsyncStorage, MemoryStorage, TssLibType, TssShareType, WEB3AUTH_NETWORK } from "../src";
88
import { bufferToElliptic, criticalResetAccount, newCoreKitLogInInstance } from "./setup";
99

1010
type ImportKeyTestVariable = {
1111
manualSync?: boolean;
1212
email: string;
1313
importKeyEmail: string;
14-
tssLib: TssLib;
14+
tssLib: TssLibType;
1515
};
1616

1717
const storageInstance = new MemoryStorage();

tests/setup.ts

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import BN from "bn.js";
44
import jwt, { Algorithm } from "jsonwebtoken";
55
import { tssLib as tssLibDKLS } from "@toruslabs/tss-dkls-lib";
66

7-
import { IAsyncStorage, IStorage, parseToken, TssLib, WEB3AUTH_NETWORK_TYPE, Web3AuthMPCCoreKit } from "../src";
7+
import { IAsyncStorage, IStorage, parseToken, TssLibType, WEB3AUTH_NETWORK_TYPE, Web3AuthMPCCoreKit } from "../src";
88

99
export const mockLogin2 = async (email: string) => {
1010
const req = new Request("https://li6lnimoyrwgn2iuqtgdwlrwvq0upwtr.lambda-url.eu-west-1.on.aws/", {
@@ -91,14 +91,16 @@ export const newCoreKitLogInInstance = async ({
9191
email,
9292
storageInstance,
9393
importTssKey,
94+
registerExistingSFAKey,
9495
login,
9596
}: {
9697
network: WEB3AUTH_NETWORK_TYPE;
9798
manualSync: boolean;
9899
email: string;
99100
storageInstance: IStorage | IAsyncStorage;
100-
tssLib?: TssLib;
101+
tssLib?: TssLibType;
101102
importTssKey?: string;
103+
registerExistingSFAKey?: boolean;
102104
login?: LoginFunc;
103105
}) => {
104106
const instance = new Web3AuthMPCCoreKit({
@@ -118,11 +120,55 @@ export const newCoreKitLogInInstance = async ({
118120
verifierId: parsedToken.email,
119121
idToken,
120122
importTssKey,
123+
registerExistingSFAKey
121124
});
122125

123126
return instance;
124127
};
125128

129+
export const loginWithSFA = async ({
130+
network,
131+
manualSync,
132+
email,
133+
storageInstance,
134+
login,
135+
}: {
136+
network: WEB3AUTH_NETWORK_TYPE;
137+
manualSync: boolean;
138+
email: string;
139+
storageInstance: IStorage | IAsyncStorage;
140+
tssLib?: TssLibType;
141+
login?: LoginFunc;
142+
}) => {
143+
const instance = new Web3AuthMPCCoreKit({
144+
web3AuthClientId: "torus-key-test",
145+
web3AuthNetwork: network,
146+
baseUrl: "http://localhost:3000",
147+
uxMode: "nodejs",
148+
tssLib: tssLib || tssLibDKLS,
149+
storage: storageInstance,
150+
manualSync,
151+
});
152+
153+
const { idToken, parsedToken } = login ? await login(email) : await mockLogin(email);
154+
await instance.init();
155+
const nodeDetails = await instance.torusSp.customAuthInstance.nodeDetailManager.getNodeDetails({
156+
verifier: "torus-test-health",
157+
verifierId: parsedToken.email,
158+
})
159+
return await instance.torusSp.customAuthInstance.torus.retrieveShares({
160+
idToken,
161+
nodePubkeys: nodeDetails.torusNodePub,
162+
verifier: "torus-test-health",
163+
verifierParams: {
164+
verifier_id: parsedToken.email,
165+
},
166+
endpoints: nodeDetails.torusNodeEndpoints,
167+
indexes: nodeDetails.torusIndexes,
168+
})
169+
170+
}
171+
126172
export class AsyncMemoryStorage implements IAsyncStorage {
127173
private _store: Record<string, string> = {};
128174

@@ -138,3 +184,12 @@ export class AsyncMemoryStorage implements IAsyncStorage {
138184
export function bufferToElliptic(p: Buffer, ec = secp256k1): EllipticPoint {
139185
return ec.keyFromPublic(p).getPublic();
140186
}
187+
188+
189+
export function generateRandomEmail(): string {
190+
const username = stringGen(10);
191+
const domain = stringGen(5);
192+
const tld = stringGen(3);
193+
return `${username}@${domain}.${tld}`;
194+
}
195+

0 commit comments

Comments
 (0)