Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 46 additions & 19 deletions src/mpcCoreKit.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { BNString, KeyType, ONE_KEY_DELETE_NONCE, Point, secp256k1, SHARE_DELETED, ShareStore, StringifiedType } from "@tkey/common-types";
import {
BNString,
KEY_NOT_FOUND,
KeyType,
ONE_KEY_DELETE_NONCE,
Point,
secp256k1,
SHARE_DELETED,
ShareStore,
StringifiedType,
} from "@tkey/common-types";
import { CoreError } from "@tkey/core";
import { ShareSerializationModule } from "@tkey/share-serialization";
import { TorusStorageLayer } from "@tkey/storage-layer-torus";
Expand Down Expand Up @@ -323,6 +333,11 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
if (this.isNodejsOrRN(this.options.uxMode)) {
throw CoreKitError.oauthLoginUnsupported(`Oauth login is NOT supported in ${this.options.uxMode} mode.`);
}

if (this.state.factorKey) {
throw CoreKitError.oauthLoginUnsupported("Instance is alreay login or rehydrated");
}

const { importTssKey, registerExistingSFAKey } = params;
const tkeyServiceProvider = this.torusSp;

Expand Down Expand Up @@ -518,15 +533,17 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
public async inputFactorKey(factorKey: BN): Promise<void> {
this.checkReady();
try {
// input tkey device share when required share > 0 ( or not reconstructed )
// assumption tkey shares will not changed
// always check for valid factor key
const factorKeyPrivate = factorKeyCurve.keyFromPrivate(factorKey.toBuffer());
const factorPubX = factorKeyPrivate.getPublic().getX().toString("hex").padStart(64, "0");
const factorEncExist = this.tkey.metadata.factorEncs?.[this.tkey.tssTag]?.[factorPubX];
if (!factorEncExist) {
throw CoreKitError.providedFactorKeyInvalid("Invalid FactorKey provided. Failed to input factor key.");
}

// input tkey device share when required share > 0 ( or tkey is not yet reconstructed )
// assumption tkey shares will never changed
if (!this.tKey.secp256k1Key) {
const factorKeyPrivate = factorKeyCurve.keyFromPrivate(factorKey.toBuffer());
const factorPubX = factorKeyPrivate.getPublic().getX().toString("hex").padStart(64, "0");
const factorEncExist = this.tkey.metadata.factorEncs?.[this.tkey.tssTag]?.[factorPubX];
if (!factorEncExist) {
throw CoreKitError.providedFactorKeyInvalid("Invalid FactorKey provided. Failed to input factor key.");
}
const factorKeyMetadata = await this.getFactorKeyMetadata(factorKey);
await this.tKey.inputShareStoreSafe(factorKeyMetadata, true);
}
Expand Down Expand Up @@ -605,7 +622,6 @@ export class Web3AuthMPCCoreKit implements ICoreKit {

const hashedFactorPub = getPubKeyPoint(hashedFactorKey, factorKeyCurve);
await this.deleteFactor(hashedFactorPub, hashedFactorKey);
await this.deleteMetadataShareBackup(hashedFactorKey);

// only recovery factor = true
let backupFactorKey;
Expand Down Expand Up @@ -1107,6 +1123,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
shareDescription: FactorKeyTypeShareDescription.Other,
updateMetadata: false,
});
await this.setDeviceFactor(factorKey);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this means the factor key was previously not persisted here, which is kind of catastrophic. We should have a test that asserts this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests added

} else {
await this.addFactorDescription({
factorKey,
Expand Down Expand Up @@ -1199,8 +1216,16 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
signatures: result.signatures,
userInfo: result.userInfo,
});

// update device factor if not present upon rehydration
if (this.options.disableHashedFactorKey) {
const deviceFactorKey = await this.getDeviceFactor();
if (!deviceFactorKey && this.state.factorKey && this.state.tssShareIndex === TssShareType.DEVICE) {
await this.setDeviceFactor(this.state.factorKey);
}
}
} catch (err) {
log.warn("failed to authorize session", err);
log.warn("failed to authorize session please use new", err);
}
}

Expand Down Expand Up @@ -1263,7 +1288,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
private async getFactorKeyMetadata(factorKey: BN): Promise<ShareStore> {
this.checkReady();
const factorKeyMetadata = await this.tKey?.readMetadata<StringifiedType>(factorKey);
if (!factorKeyMetadata || factorKeyMetadata.message === "KEY_NOT_FOUND") {
if (!factorKeyMetadata || factorKeyMetadata.message === KEY_NOT_FOUND || factorKeyMetadata.message === SHARE_DELETED) {
throw CoreKitError.noMetadataFound();
}
return ShareStore.fromJSON(factorKeyMetadata);
Expand Down Expand Up @@ -1325,19 +1350,21 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
}

private async deleteMetadataShareBackup(factorKey: BN): Promise<void> {
await this.tKey.addLocalMetadataTransitions({ input: [{ message: SHARE_DELETED, dateAdded: Date.now() }], privKey: [factorKey] });
if (!this.tkey?.manualSync) await this.tkey?.syncLocalMetadataTransitions();
await this.atomicSync(async () => {
await this.tKey.addLocalMetadataTransitions({ input: [{ message: SHARE_DELETED, dateAdded: Date.now() }], privKey: [factorKey] });
});
}

private async backupMetadataShare(factorKey: BN) {
const metadataShare = await this.getMetadataShare();

// Set metadata for factor key backup
await this.tKey?.addLocalMetadataTransitions({
input: [metadataShare],
privKey: [factorKey],
await this.atomicSync(async () => {
// Set metadata for factor key backup
await this.tKey?.addLocalMetadataTransitions({
input: [metadataShare],
privKey: [factorKey],
});
});
if (!this.tkey?.manualSync) await this.tkey?.syncLocalMetadataTransitions();
}

private async addFactorDescription(args: {
Expand Down
62 changes: 52 additions & 10 deletions tests/ed25519.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,27 @@ import { UX_MODE_TYPE } from "@toruslabs/customauth";
import { tssLib } from "@toruslabs/tss-frost-lib";
import BN from "bn.js";

import { AsyncStorage, COREKIT_STATUS, ed25519, MemoryStorage, WEB3AUTH_NETWORK, WEB3AUTH_NETWORK_TYPE, Web3AuthMPCCoreKit } from "../src";
import { AsyncStorage, COREKIT_STATUS, ed25519, MemoryStorage, TssShareType, WEB3AUTH_NETWORK, WEB3AUTH_NETWORK_TYPE, Web3AuthMPCCoreKit } from "../src";
import { bufferToElliptic, criticalResetAccount, mockLogin, mockLogin2 } from "./setup";

type TestVariable = {
web3AuthNetwork: WEB3AUTH_NETWORK_TYPE;
uxMode: UX_MODE_TYPE | "nodejs";
manualSync?: boolean;
email: string;
importedEmail: string
};

const defaultTestEmail = "testEmailForLoginEd25519";
const defaultTestEmail = "testEmailForLoginEd25519-01";
const importedEmail = "testEmailImportEd25519-01"

const variable: TestVariable[] = [
{ web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, uxMode: "nodejs", email: defaultTestEmail },
{ web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, uxMode: "nodejs", email: defaultTestEmail, importedEmail },
// { web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET, uxMode: UX_MODE.REDIRECT, email: defaultTestEmail },

{ web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, uxMode: "nodejs", manualSync: true, email: defaultTestEmail },
{ web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, uxMode: "nodejs", manualSync: true, email: defaultTestEmail , importedEmail },
// { web3AuthNetwork: WEB3AUTH_NETWORK.MAINNET, uxMode: UX_MODE.REDIRECT, manualSync: true, email: defaultTestEmail },

];

const checkLogin = async (coreKitInstance: Web3AuthMPCCoreKit, accountIndex = 0) => {
Expand All @@ -39,7 +43,7 @@ const storageInstance = new MemoryStorage();

variable.forEach((testVariable) => {
const { web3AuthNetwork, uxMode, manualSync, email } = testVariable;
const newCoreKitInstance = () =>
const newCoreKitInstance = ( params: {disableSessionManager : boolean } = {disableSessionManager: true}) =>
new Web3AuthMPCCoreKit({
web3AuthClientId: "torus-key-test",
web3AuthNetwork,
Expand All @@ -48,11 +52,12 @@ variable.forEach((testVariable) => {
tssLib,
storage: storageInstance,
manualSync,
disableSessionManager: params.disableSessionManager
});

async function resetAccount() {
async function resetAccount( resetEmail: string) {
const resetInstance = newCoreKitInstance();
const { idToken, parsedToken } = await mockLogin(email);
const { idToken, parsedToken } = await mockLogin(resetEmail);
await resetInstance.init({ handleRedirectResult: false, rehydrate: false });
await resetInstance.loginWithJWT({
verifier: "torus-test-health",
Expand All @@ -69,9 +74,11 @@ variable.forEach((testVariable) => {
let checkTssShare: BN;

test(`#Login Test with JWT + logout: ${testNameSuffix}`, async (t) => {
await resetAccount();
await resetAccount(email);
await resetAccount(importedEmail);

await t.test("#Login", async function () {
const coreKitInstance = newCoreKitInstance();
const coreKitInstance = newCoreKitInstance({disableSessionManager: false});

// mocklogin
const { idToken, parsedToken } = await mockLogin(email);
Expand All @@ -98,7 +105,7 @@ variable.forEach((testVariable) => {
});

await t.test("#relogin ", async function () {
const coreKitInstance = newCoreKitInstance();
const coreKitInstance = newCoreKitInstance({ disableSessionManager: false });
// rehydrate
await coreKitInstance.init({ handleRedirectResult: false });
await checkLogin(coreKitInstance);
Expand Down Expand Up @@ -147,5 +154,40 @@ variable.forEach((testVariable) => {
const valid = ed25519().verify(msgBuffer, signature, coreKitInstance.getPubKeyEd25519());
assert(valid);
});

await t.test("#able to export import", async function () {
const coreKitInstance = newCoreKitInstance();
await coreKitInstance.init({ handleRedirectResult: false, rehydrate: false });
const localToken = await mockLogin2(email);
await coreKitInstance.loginWithJWT({
verifier: "torus-test-health",
verifierId: email,
idToken: localToken.idToken,
});

await coreKitInstance.enableMFA({});
if (manualSync) {
await coreKitInstance.commitChanges();
}
const exportedSeed = await coreKitInstance._UNSAFE_exportTssEd25519Seed();

// import tss key into mpc corekit
const coreKitInstance2 = newCoreKitInstance();
await coreKitInstance2.init({ handleRedirectResult: false, rehydrate: false });
const localToken2 = await mockLogin2(importedEmail);
await coreKitInstance2.loginWithJWT({
verifier: "torus-test-health",
verifierId: importedEmail,
idToken: localToken2.idToken,
importTssKey: exportedSeed.toString("hex"),
});
if (manualSync) {
await coreKitInstance2.commitChanges();
}

const exportedSeed2 = await coreKitInstance2._UNSAFE_exportTssEd25519Seed();

assert(exportedSeed.toString("hex") === (exportedSeed2.toString("hex")));
});
});
});
44 changes: 38 additions & 6 deletions tests/factors.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import assert from "node:assert";
import test from "node:test";

import { EllipticPoint, Point } from "@tkey/common-types";
import { EllipticPoint, KeyType, Point, secp256k1 } from "@tkey/common-types";
import { factorKeyCurve } from "@tkey/tss";
import { tssLib as tssLibDKLS } from "@toruslabs/tss-dkls-lib";
import { tssLib as tssLibFROST } from "@toruslabs/tss-frost-lib";
import BN from "bn.js";

import { COREKIT_STATUS, IAsyncStorage, IStorage, MemoryStorage, TssLibType, TssShareType, WEB3AUTH_NETWORK, Web3AuthMPCCoreKit } from "../src";
import { AsyncStorage, COREKIT_STATUS, ed25519, IAsyncStorage, IStorage, MemoryStorage, sigToRSV, TssLibType, TssShareType, WEB3AUTH_NETWORK, Web3AuthMPCCoreKit } from "../src";
import { AsyncMemoryStorage, bufferToElliptic, criticalResetAccount, mockLogin } from "./setup";
import { keccak256 } from "@toruslabs/metadata-helpers";

type FactorTestVariable = {
manualSync?: boolean;
Expand All @@ -28,6 +29,26 @@ function getPubKeys(kit: Web3AuthMPCCoreKit, indices: number[]): EllipticPoint[]
return pubKeys;
}

async function signSecp256k1Data( params : { coreKitInstance: Web3AuthMPCCoreKit, msg: string, }) {
const {coreKitInstance, msg } = params
const msgBuffer1 = Buffer.from(msg);
const msgHash = keccak256(msgBuffer1);

const signature = sigToRSV(await coreKitInstance.sign(msgHash, true));

const pubkey = secp256k1.recoverPubKey(msgHash, signature, signature.v) as EllipticPoint;
const publicKeyPoint = bufferToElliptic(coreKitInstance.getPubKey());
assert(pubkey.eq(publicKeyPoint));
}

async function signEd25519Data(params: { coreKitInstance: Web3AuthMPCCoreKit, msg: string }) {
const { coreKitInstance, msg } = params;
const msgBuffer = Buffer.from(msg)
const signature = ed25519().makeSignature((await coreKitInstance.sign(msgBuffer)).toString("hex"));
const valid = ed25519().verify(msgBuffer, signature, coreKitInstance.getPubKeyEd25519());
assert(valid);
}

export const FactorManipulationTest = async (testVariable: FactorTestVariable) => {
const { email, tssLib } = testVariable;
const newInstance = async () => {
Expand All @@ -39,6 +60,7 @@ export const FactorManipulationTest = async (testVariable: FactorTestVariable) =
tssLib: tssLib || tssLibDKLS,
storage: testVariable.storage,
manualSync: testVariable.manualSync,
disableSessionManager: true
});

const { idToken, parsedToken } = await mockLogin(email);
Expand All @@ -55,6 +77,7 @@ export const FactorManipulationTest = async (testVariable: FactorTestVariable) =
const resetInstance = await newInstance();
await criticalResetAccount(resetInstance);
await resetInstance.logout();
await new AsyncStorage(resetInstance._storageKey, testVariable.storage).resetStore();
}

await test(`#Factor manipulation - manualSync ${testVariable.manualSync} `, async function (t) {
Expand Down Expand Up @@ -163,6 +186,7 @@ export const FactorManipulationTest = async (testVariable: FactorTestVariable) =
// login with mfa factor
await instance2.inputFactorKey(new BN(recoverFactor, "hex"));
assert.strictEqual(instance2.status, COREKIT_STATUS.LOGGED_IN);

await instance2.logout();

// new instance
Expand All @@ -180,16 +204,24 @@ export const FactorManipulationTest = async (testVariable: FactorTestVariable) =

await instance3.inputFactorKey(new BN(browserFactor, "hex"));
assert.strictEqual(instance3.status, COREKIT_STATUS.LOGGED_IN);

if ( tssLib && tssLib.keyType === KeyType.ed25519) {
await signEd25519Data({ coreKitInstance: instance3, msg: "hello world" });
} else {
await signSecp256k1Data({ coreKitInstance: instance3, msg: "hello world" });
}

});

});
};

const variable: FactorTestVariable[] = [
{ manualSync: true, storage: new MemoryStorage(), email: "testmail1012" },
{ manualSync: false, storage: new MemoryStorage(), email: "testmail1013" },
{ manualSync: true, storage: new MemoryStorage(), email: "testmail1012-1" },
{ manualSync: false, storage: new MemoryStorage(), email: "testmail1013-1" },

{ manualSync: true, storage: new AsyncMemoryStorage(), email: "testmail1014" },
{ manualSync: false, storage: new AsyncMemoryStorage(), email: "testmail1015" },
{ manualSync: true, storage: new AsyncMemoryStorage(), email: "testmail1014-1" },
{ manualSync: false, storage: new AsyncMemoryStorage(), email: "testmail1015-1" },

{ manualSync: true, storage: new MemoryStorage(), email: "testmail1012ed25519", tssLib: tssLibFROST },
];
Expand Down
Loading