From 7e655f452a2d965b7195712b7180d6b1ae9e22df Mon Sep 17 00:00:00 2001 From: Michal Bilinski Date: Thu, 6 Mar 2025 11:53:41 +0100 Subject: [PATCH 1/3] initial --- package-lock.json | 20 ++++++++++++++++++ package.json | 1 + src/interfaces.ts | 7 +++++++ src/mpcCoreKit.ts | 53 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+) diff --git a/package-lock.json b/package-lock.json index 704ec63..b124938 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@tkey/common-types": "^15.1.0", "@tkey/core": "^15.1.0", "@tkey/share-serialization": "^15.1.0", + "@tkey/share-transfer": "^15.1.0", "@tkey/storage-layer-torus": "^15.1.0", "@tkey/tss": "^15.1.0", "@toruslabs/constants": "^14.0.0", @@ -3768,6 +3769,25 @@ "@babel/runtime": "7.x" } }, + "node_modules/@tkey/share-transfer": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@tkey/share-transfer/-/share-transfer-15.1.0.tgz", + "integrity": "sha512-3a5HYs4lZinUUA3nXQ9r/WYfX6kdfO0WBIw9tMI8wvXdcDgPZ1+EOa+t86JyvKZlW6FvkFj7vrIYHZ7kK4chRw==", + "license": "MIT", + "dependencies": { + "@tkey/common-types": "^15.1.0", + "@toruslabs/eccrypto": "^5.0.4", + "@toruslabs/http-helpers": "^7.0.0", + "bn.js": "^5.2.1" + }, + "engines": { + "node": ">=18.x", + "npm": ">=9.x" + }, + "peerDependencies": { + "@babel/runtime": "7.x" + } + }, "node_modules/@tkey/storage-layer-torus": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/@tkey/storage-layer-torus/-/storage-layer-torus-15.1.0.tgz", diff --git a/package.json b/package.json index dbfdf8a..349f347 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@tkey/core": "^15.1.0", "@tkey/share-serialization": "^15.1.0", "@tkey/storage-layer-torus": "^15.1.0", + "@tkey/share-transfer": "^15.1.0", "@tkey/tss": "^15.1.0", "@toruslabs/constants": "^14.0.0", "@toruslabs/customauth": "^20.3.0", diff --git a/src/interfaces.ts b/src/interfaces.ts index d27397c..b0182d0 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -16,6 +16,7 @@ import type { tssLib as TssFrostLib } from "@toruslabs/tss-frost-lib"; import BN from "bn.js"; import { FactorKeyTypeShareDescription, TssShareType, USER_PATH, WEB3AUTH_NETWORK } from "./constants"; +import { ShareTransferStore } from "@tkey/share-transfer"; export type CoreKitMode = UX_MODE_TYPE | "nodejs" | "react-native"; @@ -262,6 +263,12 @@ export interface ICoreKit { */ createFactor(createFactorParams: CreateFactorParams): Promise; + requestShare(userAgent?: string): Promise; + waitForRequestShareResponse(currentEncPubKeyX: string): Promise; + getShareTransferStore(): Promise; + approveShareRequest(pubKey: string): Promise; + createDeviceFactor(metadata: Record): Promise; + /** * Deletes the factor identified by the given public key, including all * associated metadata. diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index 6b291d2..a9f06fa 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -70,6 +70,7 @@ import { sampleEndpoints, scalarBNToBufferSEC1, } from "./utils"; +import { ShareTransferModule, ShareTransferStore } from "@tkey/share-transfer"; export class Web3AuthMPCCoreKit implements ICoreKit { public state: Web3AuthState = { accountIndex: 0 }; @@ -260,6 +261,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { }); const shareSerializationModule = new ShareSerializationModule(); + const shareTransferModule = new ShareTransferModule(); this.tkey = new TKeyTSS({ enableLogging: this.enableLogging, @@ -268,6 +270,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { manualSync: this.options.manualSync, modules: { shareSerialization: shareSerializationModule, + shareTransfer: shareTransferModule, }, tssKeyType: this.keyType, }); @@ -670,6 +673,56 @@ export class Web3AuthMPCCoreKit implements ICoreKit { }); } + public async requestShare(userAgent?: string): Promise { + return await (this.tKey.modules.shareTransfer as ShareTransferModule).requestNewShare( + userAgent ?? navigator.userAgent, + this.tKey.getCurrentShareIndexes() + ); + } + + public async waitForRequestShareResponse(currentEncPubKeyX: string): Promise { + const shareStore = await (this.tkey!.modules.shareTransfer as ShareTransferModule).startRequestStatusCheck(currentEncPubKeyX, true); + await this.tKey.reconstructKey(); + this.tKey.inputShareStore(shareStore); + } + + public async getShareTransferStore(): Promise { + return await (this.tKey.modules.shareTransfer as ShareTransferModule).getShareTransferStore(); + } + + public async approveShareRequest(currentEncPubKeyY: string): Promise { + console.log("Approving All Share Requests"); + try { + const newShare = await this.tKey.generateNewShare(); + const shareToShare = newShare.newShareStores[newShare.newShareIndex.toString("hex")]; + await (this.tKey.modules.shareTransfer as ShareTransferModule).approveRequest(currentEncPubKeyY, shareToShare); + await this.tKey.syncLocalMetadataTransitions(); + } catch (err) { + console.error("Failed to process share transfer store:", err); + } + } + + public async createDeviceFactor(metadata: Record): Promise { + this.tKey.initialize(); + + const deviceFactorKey = new BN( + await this.createFactor({ + shareType: TssShareType.DEVICE, + additionalMetadata: metadata, + }), + "hex" + ); + + await this.atomicSync(async () => { + await this.setDeviceFactor(deviceFactorKey); + await this.inputFactorKey(new BN(deviceFactorKey, "hex")); + + await this.commitChanges(); + }); + + return deviceFactorKey; + } + /** * Get public key point in SEC1 format. */ From a5443d89ad318327cb8706798318d14a4a454f62 Mon Sep 17 00:00:00 2001 From: Michal Bilinski Date: Thu, 6 Mar 2025 13:20:07 +0100 Subject: [PATCH 2/3] test for share, create and delete factor --- tests/shareCreateDelete.spec.ts | 165 ++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 tests/shareCreateDelete.spec.ts diff --git a/tests/shareCreateDelete.spec.ts b/tests/shareCreateDelete.spec.ts new file mode 100644 index 0000000..d223fd5 --- /dev/null +++ b/tests/shareCreateDelete.spec.ts @@ -0,0 +1,165 @@ +import { factorKeyCurve, getPubKeyPoint } from "@tkey/tss"; +import { tssLib as tssLibDKLS } from "@toruslabs/tss-dkls-lib"; +import BN from "bn.js"; +import assert from "node:assert"; +import test from "node:test"; + +import { + COREKIT_STATUS, + FactorKeyTypeShareDescription, + generateFactorKey, + getHashedPrivateKey, + IAsyncStorage, + IStorage, + MemoryStorage, + TssLibType, + TssShareType, + WEB3AUTH_NETWORK, + Web3AuthMPCCoreKit, +} from "../src"; +import { criticalResetAccount, mockLogin } from "./setup"; +import { Point, secp256k1 } from "@tkey/common-types"; + +type FactorTestVariable = { + manualSync?: boolean; + storage?: IAsyncStorage | IStorage; + email: string; + tssLib?: TssLibType; + userAgent?: string; +}; + +export const FactorManipulationTest = async (testVariable: FactorTestVariable) => { + const { email, tssLib } = testVariable; + const newInstance = async () => { + const instance = new Web3AuthMPCCoreKit({ + web3AuthClientId: "torus-key-test", + web3AuthNetwork: WEB3AUTH_NETWORK.DEVNET, + baseUrl: "http://localhost:3000", + uxMode: "nodejs", + tssLib: tssLib || tssLibDKLS, + storage: testVariable.storage, + manualSync: testVariable.manualSync, + }); + + const { idToken, parsedToken } = await mockLogin(email); + await instance.init({ handleRedirectResult: false, rehydrate: false }); + await instance.loginWithJWT({ + verifier: "torus-test-health", + verifierId: parsedToken.email, + idToken, + }); + return instance; + }; + + async function beforeTest() { + const resetInstance = await newInstance(); + await criticalResetAccount(resetInstance); + await resetInstance.logout(); + } + + await test(`#Factor manipulation - manualSync ${testVariable.manualSync} `, async function (t) { + await beforeTest(); + + await t.test("should be able to create factor", async function () { + const coreKitInstance = await newInstance(); + assert.equal(coreKitInstance.status, COREKIT_STATUS.LOGGED_IN, "Instance should be logged in after initialization"); + + await coreKitInstance.commitChanges(); + + const socialFactorKey = new BN(coreKitInstance.state.postBoxKey, "hex"); + await coreKitInstance.enableMFA({ + factorKey: socialFactorKey, + shareDescription: FactorKeyTypeShareDescription.SocialShare, + }); + + // Sync if manualSync is enabled + if (testVariable.manualSync) { + await coreKitInstance.commitChanges(); + } + + // Create a new instance to simulate another device + const instance2 = await newInstance(); + assert.equal(instance2.status, COREKIT_STATUS.REQUIRED_SHARE, "Second instance should require a share"); + + const pubKeyX = await instance2.requestShare(testVariable.userAgent); + const transferStore = await coreKitInstance.getShareTransferStore(); + assert(Object.keys(transferStore).length > 0, "Share transfer store should have pending requests"); + + await coreKitInstance.approveShareRequest(Object.keys(transferStore)[0]); + assert.equal(instance2.status, COREKIT_STATUS.REQUIRED_SHARE, "Second instance should still require a share until input"); + + await instance2.waitForRequestShareResponse(pubKeyX); + await instance2.inputFactorKey(socialFactorKey); + assert.equal(instance2.status, COREKIT_STATUS.LOGGED_IN, "Second instance should be logged in after inputting factor key"); + + // Create a new factor (recovery factor) + const otherFactorKey = generateFactorKey().private; + await instance2.createFactor({ + factorKey: otherFactorKey, + shareType: TssShareType.RECOVERY, + shareDescription: FactorKeyTypeShareDescription.Other, + }); + + await instance2.inputFactorKey(otherFactorKey); + await instance2.logout(); + + // Create a third instance to verify recovery + const instance3 = await newInstance(); + await instance3.inputFactorKey(new BN(await instance3.getDeviceFactor(), "hex")); + await instance3.inputFactorKey(socialFactorKey); + assert.equal(instance3.status, COREKIT_STATUS.LOGGED_IN, "Third instance should be logged in after inputting factor keys"); + + // Verify the recovery factor exists + let factorPub: string | undefined; + for (const [key, value] of Object.entries(instance3.getKeyDetails().shareDescriptions)) { + if (value.length > 0) { + const parsedData = JSON.parse(value[0]); + if (parsedData.module === FactorKeyTypeShareDescription.Other) { + factorPub = key; + } + } + } + assert(factorPub, "Recovery factor should exist in key details"); + + // Delete the recovery factor + const pub = Point.fromSEC1(secp256k1, factorPub); + await instance3.deleteFactor(pub); + await instance3.commitChanges(); + + factorPub = undefined; + + for (const [key, value] of Object.entries(instance3.getKeyDetails().shareDescriptions)) { + if (value.length > 0) { + const parsedData = JSON.parse(value[0]); + if (parsedData.module === FactorKeyTypeShareDescription.Other) { + factorPub = key; + } + } + } + + assert(!factorPub, "Recovery factor should be deleted"); + + await coreKitInstance.logout(); + await instance3.logout(); + }); + }); +}; + +const variable: FactorTestVariable[] = [ + { + manualSync: true, + storage: new MemoryStorage(), + email: "testmail1012", + userAgent: "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36", + }, + // { manualSync: false, storage: new MemoryStorage(), email: "testmail1013" }, + + // { manualSync: true, storage: new AsyncMemoryStorage(), email: "testmail1014" }, + // { manualSync: false, storage: new AsyncMemoryStorage(), email: "testmail1015" }, + + // { manualSync: true, storage: new MemoryStorage(), email: "testmail1012ed25519", tssLib: tssLibFROST }, +]; + +variable.forEach(async (testVariable) => { + await FactorManipulationTest(testVariable); +}); From 0d02b541097d3fca23b84e8fa300b3ddf81eabe4 Mon Sep 17 00:00:00 2001 From: Michal Bilinski Date: Thu, 6 Mar 2025 13:22:18 +0100 Subject: [PATCH 3/3] linter --- src/interfaces.ts | 2 +- src/mpcCoreKit.ts | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/interfaces.ts b/src/interfaces.ts index b0182d0..03583b1 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,4 +1,5 @@ import { KeyType, Point as TkeyPoint, ShareDescriptionMap } from "@tkey/common-types"; +import { ShareTransferStore } from "@tkey/share-transfer"; import { TKeyTSS } from "@tkey/tss"; import type { AGGREGATE_VERIFIER_TYPE, @@ -16,7 +17,6 @@ import type { tssLib as TssFrostLib } from "@toruslabs/tss-frost-lib"; import BN from "bn.js"; import { FactorKeyTypeShareDescription, TssShareType, USER_PATH, WEB3AUTH_NETWORK } from "./constants"; -import { ShareTransferStore } from "@tkey/share-transfer"; export type CoreKitMode = UX_MODE_TYPE | "nodejs" | "react-native"; diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index a9f06fa..2a96658 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -1,6 +1,7 @@ import { BNString, 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 { ShareTransferModule, ShareTransferStore } from "@tkey/share-transfer"; import { TorusStorageLayer } from "@tkey/storage-layer-torus"; import { factorKeyCurve, getPubKeyPoint, lagrangeInterpolation, TKeyTSS, TSSTorusServiceProvider } from "@tkey/tss"; import { KEY_TYPE, SIGNER_MAP } from "@toruslabs/constants"; @@ -70,7 +71,6 @@ import { sampleEndpoints, scalarBNToBufferSEC1, } from "./utils"; -import { ShareTransferModule, ShareTransferStore } from "@tkey/share-transfer"; export class Web3AuthMPCCoreKit implements ICoreKit { public state: Web3AuthState = { accountIndex: 0 }; @@ -674,7 +674,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { } public async requestShare(userAgent?: string): Promise { - return await (this.tKey.modules.shareTransfer as ShareTransferModule).requestNewShare( + return (this.tKey.modules.shareTransfer as ShareTransferModule).requestNewShare( userAgent ?? navigator.userAgent, this.tKey.getCurrentShareIndexes() ); @@ -687,18 +687,17 @@ export class Web3AuthMPCCoreKit implements ICoreKit { } public async getShareTransferStore(): Promise { - return await (this.tKey.modules.shareTransfer as ShareTransferModule).getShareTransferStore(); + return (this.tKey.modules.shareTransfer as ShareTransferModule).getShareTransferStore(); } public async approveShareRequest(currentEncPubKeyY: string): Promise { - console.log("Approving All Share Requests"); try { const newShare = await this.tKey.generateNewShare(); const shareToShare = newShare.newShareStores[newShare.newShareIndex.toString("hex")]; await (this.tKey.modules.shareTransfer as ShareTransferModule).approveRequest(currentEncPubKeyY, shareToShare); await this.tKey.syncLocalMetadataTransitions(); } catch (err) { - console.error("Failed to process share transfer store:", err); + log.error("Failed to process share transfer store:", err); } }