From c9af08e12a0db4c9248d4cafc6cdf7b425f6bf0d Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 25 Oct 2024 16:30:01 +0800 Subject: [PATCH 01/16] feat: initial commit remoteSigner --- package-lock.json | 8 +- package.json | 4 +- src/interfaces.ts | 2 + src/mpcCoreKit.ts | 253 +++++++++++++++++++++- src/remoteSignInterfaces.ts | 42 ++++ src/remoteSignerServices/authenticator.ts | 127 +++++++++++ src/remoteSignerServices/smsOtp.ts | 138 ++++++++++++ 7 files changed, 567 insertions(+), 7 deletions(-) create mode 100644 src/remoteSignInterfaces.ts create mode 100644 src/remoteSignerServices/authenticator.ts create mode 100644 src/remoteSignerServices/smsOtp.ts diff --git a/package-lock.json b/package-lock.json index 8e6191e7..c7477d38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@toruslabs/elliptic-wrapper": "^0.1.0", "@toruslabs/fetch-node-details": "^14.0.1", "@toruslabs/fnd-base": "^14.0.0", + "@toruslabs/http-helpers": "^7.0.0", "@toruslabs/metadata-helpers": "^6.0.0", "@toruslabs/openlogin-utils": "^8.2.1", "@toruslabs/session-manager": "^3.1.0", @@ -29,6 +30,7 @@ "bn.js": "^5.2.1", "bowser": "^2.11.0", "elliptic": "^6.5.7", + "hi-base32": "^0.5.1", "loglevel": "^1.9.2" }, "devDependencies": { @@ -4232,7 +4234,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/@toruslabs/http-helpers/-/http-helpers-7.0.0.tgz", "integrity": "sha512-U79uCCA1EAManPmgIn+0YpCrKUxj9C7GYlGt7Ftnd3soYCsAXVqWgck+R5knrNvTSOPmot8QYkTl+ncP44Vg/A==", - "license": "MIT", "dependencies": { "deepmerge": "^4.3.1", "loglevel": "^1.9.1" @@ -9656,6 +9657,11 @@ "he": "bin/he" } }, + "node_modules/hi-base32": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/hi-base32/-/hi-base32-0.5.1.tgz", + "integrity": "sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA==" + }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", diff --git a/package.json b/package.json index 7a6cb85f..05441c26 100644 --- a/package.json +++ b/package.json @@ -53,9 +53,10 @@ "@toruslabs/elliptic-wrapper": "^0.1.0", "@toruslabs/fetch-node-details": "^14.0.1", "@toruslabs/fnd-base": "^14.0.0", + "@toruslabs/http-helpers": "^7.0.0", "@toruslabs/metadata-helpers": "^6.0.0", - "@toruslabs/session-manager": "^3.1.0", "@toruslabs/openlogin-utils": "^8.2.1", + "@toruslabs/session-manager": "^3.1.0", "@toruslabs/torus.js": "^15.1.0", "@toruslabs/tss-client": "^3.1.0", "@toruslabs/tss-frost-client": "0.3.1", @@ -63,6 +64,7 @@ "bn.js": "^5.2.1", "bowser": "^2.11.0", "elliptic": "^6.5.7", + "hi-base32": "^0.5.1", "loglevel": "^1.9.2" }, "devDependencies": { diff --git a/src/interfaces.ts b/src/interfaces.ts index e2bdf3bc..2f71e626 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -15,6 +15,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 { IRemoteClientState } from "./remoteSignInterfaces"; export type CoreKitMode = UX_MODE_TYPE | "nodejs" | "react-native"; @@ -161,6 +162,7 @@ export interface Web3AuthState { tssPubKey?: Buffer; accountIndex: number; factorKey?: BN; + remoteClient?: IRemoteClientState; } export interface ICoreKit { diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index a5fd3561..f4be390f 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -1,13 +1,14 @@ -import { BNString, KeyType, Point, secp256k1, SHARE_DELETED, ShareStore, StringifiedType } from "@tkey/common-types"; +import { BNString, EncryptedMessage, FactorEnc, KeyType, 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"; -import { factorKeyCurve, getPubKeyPoint, lagrangeInterpolation, TKeyTSS, TSSTorusServiceProvider } from "@tkey/tss"; +import { DELIMITERS, factorKeyCurve, getPubKeyPoint, lagrangeInterpolation, randomSelection, TKeyTSS, TSSTorusServiceProvider } from "@tkey/tss"; import { KEY_TYPE, SIGNER_MAP } from "@toruslabs/constants"; import { AGGREGATE_VERIFIER, TORUS_METHOD, TorusAggregateLoginResponse, TorusLoginResponse, UX_MODE } from "@toruslabs/customauth"; import type { UX_MODE_TYPE } from "@toruslabs/customauth/dist/types/utils/enums"; import { Ed25519Curve } from "@toruslabs/elliptic-wrapper"; import { fetchLocalConfig } from "@toruslabs/fnd-base"; +import { post } from "@toruslabs/http-helpers"; import { keccak256 } from "@toruslabs/metadata-helpers"; import { SessionManager } from "@toruslabs/session-manager"; import { Torus as TorusUtils, TorusKey } from "@toruslabs/torus.js"; @@ -54,6 +55,7 @@ import { Web3AuthOptionsWithDefaults, Web3AuthState, } from "./interfaces"; +import { IRemoteClientState, RefreshRemoteTssReturnType } from "./remoteSignInterfaces"; import { deriveShareCoefficients, ed25519, @@ -610,7 +612,15 @@ export class Web3AuthMPCCoreKit implements ICoreKit { } return this.atomicSync(async () => { - await this.copyOrCreateShare(shareType, factorPub); + if (this.state.remoteClient && !this.state.factorKey) { + if (shareType === this.state.tssShareIndex) { + await this.remoteCopyFactorPub(factorPub, shareType); + } else { + await this.remoteAddFactorPub(factorPub, shareType); + } + } else { + await this.copyOrCreateShare(shareType, factorPub); + } await this.backupMetadataShare(factorKey); await this.addFactorDescription({ factorKey, shareDescription, additionalMetadata, updateMetadata: false }); @@ -652,6 +662,10 @@ export class Web3AuthMPCCoreKit implements ICoreKit { public async sign(data: Buffer, hashed: boolean = false): Promise { this.wasmLib = await this.loadTssWasm(); if (this.keyType === KeyType.secp256k1) { + if (this.state.remoteClient && !this.state.factorKey) { + const sig = await this.remoteSignSecp256k1(data, hashed); + return Buffer.concat([sig.r, sig.s, Buffer.from([sig.v])]); + } const sig = await this.sign_ECDSA_secp256k1(data, hashed); return Buffer.concat([sig.r, sig.s, Buffer.from([sig.v])]); } else if (this.keyType === KeyType.ed25519) { @@ -662,7 +676,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { // mutation function async deleteFactor(factorPub: Point, factorKey?: BNString): Promise { - if (!this.state.factorKey) { + if (!this.state.factorKey && !this.state.remoteClient) { throw CoreKitError.factorKeyNotPresent("factorKey not present in state when deleting a factor."); } if (!this.tKey.metadata.factorPubs) { @@ -680,7 +694,11 @@ export class Web3AuthMPCCoreKit implements ICoreKit { throw CoreKitError.factorInUseCannotBeDeleted("Cannot delete current active factor"); } - await this.tKey.deleteFactorPub({ factorKey: this.state.factorKey, deleteFactorPub: factorPub, authSignatures: this.signatures }); + if (this.state.remoteClient && !this.state.factorKey) { + await this.remoteDeleteFactorPub(factorPub); + } else { + await this.tKey.deleteFactorPub({ factorKey: this.state.factorKey, deleteFactorPub: factorPub, authSignatures: this.signatures }); + } const factorPubHex = fpp.toSEC1(factorKeyCurve, true).toString("hex"); const allDesc = this.tKey.metadata.getShareDescription(); const keyDesc = allDesc[factorPubHex]; @@ -845,6 +863,231 @@ export class Web3AuthMPCCoreKit implements ICoreKit { } } + async setupRemoteSigning(params: IRemoteClientState): Promise> { + const { remoteClientUrl, remoteFactorPub, metadataShare, remoteClientToken, tssShareIndex } = params; + + const remoteClient: IRemoteClientState = { + remoteClientUrl: remoteClientUrl.at(-1) === "/" ? remoteClientUrl.slice(0, -1) : remoteClientUrl, + remoteFactorPub, + metadataShare, + remoteClientToken, + tssShareIndex, + }; + + const sharestore = ShareStore.fromJSON(JSON.parse(metadataShare)); + this.tkey.inputShareStoreSafe(sharestore); + await this.tKey.reconstructKey(); + const tssPubKey = this.tKey.getTSSPub().toSEC1(this.tkey.tssCurve, false); + // setup Tkey + // const tssPubKey = Point.fromTkeyPoint(this.tKey.getTSSPub()).toBufferSEC1(false); + this.updateState({ tssShareIndex, tssPubKey, remoteClient }); + + // // Finalize setup. + // setup provider + await this.createSession(); + } + + /** + * Refreshes TSS shares. Allows to change number of shares. New user shares are + * only produced for the target indices. + * @param factorPubs - Factor pub keys after refresh. + * @param tssIndices - Target tss indices to generate new shares for. + * @param remoteFactorPub - Factor Pub for remote share. + * @param signatures - Signatures for authentication against RSS servers. + */ + async remoteRefreshTssShares(params: { factorPubs: Point[]; tssIndices: number[]; signatures: string[]; remoteClient: IRemoteClientState }) { + const { factorPubs, tssIndices, signatures, remoteClient } = params; + const { tKey } = this; + const rssNodeDetails = await tKey._getRssNodeDetails(); + const { serverEndpoints, serverPubKeys, serverThreshold } = rssNodeDetails; + let finalSelectedServers = randomSelection( + new Array(rssNodeDetails.serverEndpoints.length).fill(null).map((_, i) => i + 1), + Math.ceil(rssNodeDetails.serverEndpoints.length / 2) + ); + + const verifierNameVerifierId = tKey.serviceProvider.getVerifierNameVerifierId(); + + const tssCommits = tKey.metadata.tssPolyCommits[tKey.tssTag]; + const tssNonce: number = tKey.metadata.tssNonces[tKey.tssTag] || 0; + const { pubKey: newTSSServerPub, nodeIndexes } = await tKey.serviceProvider.getTSSPubKey(tKey.tssTag, tssNonce + 1); + // move to pre-refresh + if (nodeIndexes?.length > 0) { + finalSelectedServers = nodeIndexes.slice(0, Math.min(serverEndpoints.length, nodeIndexes.length)); + } + + const factorEnc = tKey.getFactorEncs(Point.fromSEC1(secp256k1, remoteClient.remoteFactorPub)); + + const dataRequired = { + factorEnc, + factorPubs: factorPubs.map((pub) => pub.toJSON()), + targetIndexes: tssIndices, + verifierNameVerifierId, + tssTag: tKey.tssTag, + tssCommits: tssCommits.map((commit) => commit.toJSON()), + tssNonce, + newTSSServerPub: newTSSServerPub.toJSON(), + serverOpts: { + selectedServers: finalSelectedServers, + serverEndpoints, + serverPubKeys, + serverThreshold, + authSignatures: signatures, + }, + }; + + const result = ( + await post<{ data: RefreshRemoteTssReturnType }>( + `${remoteClient.remoteClientUrl}/api/mpc/refresh_tss`, + { dataRequired }, + { + headers: { + Authorization: `Bearer ${remoteClient.remoteClientToken}`, + }, + } + ) + ).data; + + tKey.metadata.updateTSSData({ + tssTag: result.tssTag, + tssNonce: result.tssNonce, + tssPolyCommits: result.tssPolyCommits.map((commit) => Point.fromJSON(commit)), + factorPubs: result.factorPubs.map((pub) => Point.fromJSON(pub)), + factorEncs: result.factorEncs, + }); + } + + async remoteCopyFactorPub(newFactorPub: Point, tssIndex: number) { + const remoteFactorPub = Point.fromSEC1(secp256k1, this.state.remoteClient.remoteFactorPub); + const factorEnc = this.tkey.getFactorEncs(remoteFactorPub); + const tssCommits = this.tkey.getTSSCommits(); + const dataRequired = { + factorEnc, + tssCommits, + factorPub: newFactorPub, + }; + + const result = ( + await post<{ data?: EncryptedMessage }>( + `${this.state.remoteClient.remoteClientUrl}/api/mpc/copy_tss_share`, + { dataRequired }, + { + headers: { + Authorization: `Bearer ${this.state.remoteClient.remoteClientToken}`, + }, + } + ) + ).data; + + const { tssTag } = this.tkey; + const updatedFactorPubs = this.tkey.metadata.factorPubs[tssTag].concat([newFactorPub]); + const factorEncs: { [key: string]: FactorEnc } = JSON.parse(JSON.stringify(this.tkey.metadata.factorEncs[tssTag])); + const factorPubID = newFactorPub.x.toString(16, 64); + factorEncs[factorPubID] = { + tssIndex, + type: "direct", + userEnc: result, + serverEncs: [], + }; + this.tkey.metadata.updateTSSData({ + tssKeyType: this.keyType, + tssTag: this.tkey.tssTag, + factorPubs: updatedFactorPubs, + factorEncs, + }); + } + + async remoteAddFactorPub(newFactorPub: Point, newFactorTSSIndex: number) { + const { tKey } = this; + const existingFactorPubs = tKey.metadata.factorPubs[tKey.tssTag]; + const updatedFactorPubs = existingFactorPubs.concat([newFactorPub]); + const existingTSSIndexes = existingFactorPubs.map((fb) => tKey.getFactorEncs(fb).tssIndex); + const updatedTSSIndexes = existingTSSIndexes.concat([newFactorTSSIndex]); + + await this.remoteRefreshTssShares({ + factorPubs: updatedFactorPubs, + tssIndices: updatedTSSIndexes, + signatures: this.state.signatures, + remoteClient: this.state.remoteClient, + }); + } + + async remoteDeleteFactorPub(factorPubToDelete: Point) { + const { tKey } = this; + const existingFactorPubs = tKey.metadata.factorPubs[tKey.tssTag]; + const factorIndex = existingFactorPubs.findIndex((p) => p.x.eq(factorPubToDelete.x)); + if (factorIndex === -1) { + throw new Error(`factorPub ${factorPubToDelete} does not exist`); + } + const updatedFactorPubs = existingFactorPubs.slice(); + updatedFactorPubs.splice(factorIndex, 1); + const updatedTSSIndexes = updatedFactorPubs.map((fb) => tKey.getFactorEncs(fb).tssIndex); + + await this.remoteRefreshTssShares({ + factorPubs: updatedFactorPubs, + tssIndices: updatedTSSIndexes, + signatures: this.state.signatures, + remoteClient: this.state.remoteClient, + }); + } + + public async remoteSignSecp256k1(msgData: Buffer, hashed: boolean = false): Promise<{ v: number; r: Buffer; s: Buffer }> { + if (!hashed) { + msgData = keccak256(msgData); + } + + if (!this.state.remoteClient.remoteClientUrl) throw new Error("remoteClientUrl not present"); + + // PreSetup + const { torusNodeTSSEndpoints } = await this.nodeDetailManager.getNodeDetails({ + verifier: "test-verifier", + verifierId: "test@example.com", + }); + + const tssCommits = this.tKey.getTSSCommits(); + + const tssNonce = this.getTssNonce() || 0; + + const vid = `${this.verifier}${DELIMITERS.Delimiter1}${this.verifierId}`; + const sessionId = `${vid}${DELIMITERS.Delimiter2}default${DELIMITERS.Delimiter3}${tssNonce}${DELIMITERS.Delimiter4}`; + + const parties = 4; + const clientIndex = parties - 1; + + const { nodeIndexes } = await (this.tKey.serviceProvider as TSSTorusServiceProvider).getTSSPubKey( + this.tKey.tssTag, + this.tKey.metadata.tssNonces[this.tKey.tssTag] + ); + + if (parties - 1 > nodeIndexes.length) { + throw new Error(`Not enough nodes to perform TSS - parties :${parties}, nodeIndexes:${nodeIndexes.length}`); + } + const { endpoints, tssWSEndpoints, partyIndexes } = generateTSSEndpoints(torusNodeTSSEndpoints, parties, clientIndex, nodeIndexes); + + const factor = Point.fromSEC1(secp256k1, this.state.remoteClient.remoteFactorPub); + const factorEnc = this.tKey.getFactorEncs(factor); + + const data = { + dataRequired: { + factorEnc, + sessionId, + tssNonce, + nodeIndexes: nodeIndexes.slice(0, parties - 1), + tssCommits: tssCommits.map((commit) => commit.toJSON()), + signatures: this.signatures, + serverEndpoints: { endpoints, tssWSEndpoints, partyIndexes }, + }, + msgHash: msgData.toString("hex"), + }; + + const result = await post<{ data?: Record }>(`${this.state.remoteClient.remoteClientUrl}/api/mpc/sign`, data, { + headers: { + Authorization: `Bearer ${this.state.remoteClient.remoteClientToken}`, + }, + }); + const { r, s, v } = result.data as { v: string; r: string; s: string }; + return { v: parseInt(v), r: Buffer.from(r, "hex"), s: Buffer.from(s, "hex") }; + } + public updateState(newState: Partial): void { this.state = { ...this.state, ...newState }; } diff --git a/src/remoteSignInterfaces.ts b/src/remoteSignInterfaces.ts new file mode 100644 index 00000000..cc02c263 --- /dev/null +++ b/src/remoteSignInterfaces.ts @@ -0,0 +1,42 @@ +import { FactorEnc, Point } from "@tkey/common-types"; +import { PointHex } from "@toruslabs/tss-client"; + +export interface IRemoteClientState { + remoteFactorPub: string; + remoteClientUrl: string; + remoteClientToken: string; + metadataShare: string; + tssShareIndex: number; +} + +export interface refreshRemoteTssType { + // from client + factorEnc: FactorEnc; + + factorPubs: Point[]; + targetIndexes: number[]; + verifierNameVerifierId: string; + + tssTag: string; + tssCommits: Point[]; + tssNonce: number; + newTSSServerPub: Point; + // nodeIndexes : number[], + + serverOpts: { + serverEndpoints: string[]; + serverPubKeys: PointHex[]; + serverThreshold: number; + selectedServers: number[]; + authSignatures: string[]; + }; +} +export interface RefreshRemoteTssReturnType { + tssTag: string; + tssNonce: number; + tssPolyCommits: Point[]; + factorPubs: Point[]; + factorEncs: { + [factorPubID: string]: FactorEnc; + }; +} diff --git a/src/remoteSignerServices/authenticator.ts b/src/remoteSignerServices/authenticator.ts new file mode 100644 index 00000000..40fa32b5 --- /dev/null +++ b/src/remoteSignerServices/authenticator.ts @@ -0,0 +1,127 @@ +import { secp256k1, ShareDescriptionMap } from "@tkey/common-types"; +import { generatePrivateBN } from "@tkey/core"; +import { post } from "@toruslabs/http-helpers"; +import { keccak256 } from "@toruslabs/metadata-helpers"; +import BN from "bn.js"; +import type { ec } from "elliptic"; +import base32 from "hi-base32"; +import log from "loglevel"; +import { IRemoteClientState } from "src/remoteSignInterfaces"; + +export class AuthenticatorService { + private backendUrl: string; + + private shareDescriptions: ShareDescriptionMap; + + private authenticatorType: string = "authenticator"; + + private factorPub: string = ""; + + private tssIndex: number; + + constructor(params: { backendUrl: string; shareDescriptions: ShareDescriptionMap; authenticatorType?: string }) { + const { backendUrl } = params; + this.backendUrl = backendUrl; + this.authenticatorType = params.authenticatorType || "authenticator"; + this.shareDescriptions = params.shareDescriptions; + // this.remoteClient = remoteClient || false; + } + + getDescriptionsAndUpdate() { + const arrayOfDescriptions = Object.entries(this.shareDescriptions).map(([key, value]) => { + const parsedDescription = (value || [])[0] ? JSON.parse(value[0]) : {}; + return { + key, + description: parsedDescription, + }; + }); + + const shareDescriptionsMobile = arrayOfDescriptions.find(({ description }) => description.authenticator === this.authenticatorType); + log.info("shareDescriptionsMobile", shareDescriptionsMobile); + + if (shareDescriptionsMobile) { + this.factorPub = shareDescriptionsMobile.key; + this.tssIndex = shareDescriptionsMobile.description.tssShareIndex; + } + + return shareDescriptionsMobile; + } + + generateSecretKey(): string { + const key = generatePrivateBN().toArray().slice(0, 20); + return base32.encode(key).toString().replace(/=/g, ""); + } + + async register(privKey: BN, secretKey: string): Promise<{ success: boolean; message?: string }> { + const privKeyPair: ec.KeyPair = secp256k1.keyFromPrivate(privKey.toString(16, 64)); + const pubKey = privKeyPair.getPublic(); + const sig = secp256k1.sign(keccak256(Buffer.from(secretKey, "utf8")), Buffer.from(privKey.toString(16, 64), "hex")); + + const data = { + pubKey: { + x: pubKey.getX().toString(16, 64), + y: pubKey.getY().toString(16, 64), + }, + sig: { + r: sig.r.toString(16, 64), + s: sig.s.toString(16, 64), + v: new BN(sig.recoveryParam as number).toString(16, 2), + }, + secretKey, + }; + + const resp = await post<{ + success: boolean; + message: string; + }>(`${this.backendUrl}/api/v1/register`, data); + + return resp; + } + + async addAuthenticatorRecovery(address: string, code: string, factorKey: BN) { + if (!factorKey) throw new Error("factorKey is not defined"); + if (!address) throw new Error("address is not defined"); + if (!code) throw new Error("code is not defined"); + + const data = { + address, + code, + data: { + // If the verification is complete, we save the factorKey for the user address. + // This factorKey is used to verify the user in the future on a new device and recover tss share. + factorKey: factorKey.toString(16, 64), + }, + }; + + await post(`${this.backendUrl}/api/v1/verify`, data); + } + + async verifyAuthenticatorRecovery(address: string, code: string): Promise { + const verificationData = { + address, + code, + }; + + const response = await post<{ data?: Record }>(`${this.backendUrl}/api/v1/verify`, verificationData); + const { data } = response; + return data ? new BN(data.factorKey, "hex") : undefined; + } + + async verifyRemoteSetup(address: string, code: string): Promise { + const verificationData = { + address, + code, + }; + + const response = await post<{ data?: Record }>(`${this.backendUrl}/api/v1/verify_remote`, verificationData); + const { data } = response; + + return { + tssShareIndex: this.tssIndex, + remoteClientUrl: this.backendUrl, + remoteFactorPub: this.factorPub, + metadataShare: data.metadataShare, + remoteClientToken: data.signature, + }; + } +} diff --git a/src/remoteSignerServices/smsOtp.ts b/src/remoteSignerServices/smsOtp.ts new file mode 100644 index 00000000..7867606d --- /dev/null +++ b/src/remoteSignerServices/smsOtp.ts @@ -0,0 +1,138 @@ +import { secp256k1, ShareDescriptionMap } from "@tkey/common-types"; +import { post } from "@toruslabs/http-helpers"; +import { keccak256 } from "@toruslabs/metadata-helpers"; +import BN from "bn.js"; +import type { ec } from "elliptic"; +import log from "loglevel"; +import { IRemoteClientState } from "src/remoteSignInterfaces"; + +export class SmsService { + private backendUrl: string; + + private shareDescriptions: ShareDescriptionMap; + + private authenticatorType: string = "sms"; + + private factorPub: string = ""; + + private tssIndex: number; + + constructor(params: { backendUrl: string; shareDescriptions: ShareDescriptionMap; authenticatorType?: string }) { + const { backendUrl } = params; + this.backendUrl = backendUrl; + this.authenticatorType = params.authenticatorType || "sms"; + this.shareDescriptions = params.shareDescriptions; + this.getDescriptionsAndUpdate(); + } + + getDescriptionsAndUpdate() { + const arrayOfDescriptions = Object.entries(this.shareDescriptions).map(([key, value]) => { + const parsedDescription = (value || [])[0] ? JSON.parse(value[0]) : {}; + return { + key, + description: parsedDescription, + }; + }); + + const shareDescriptionsMobile = arrayOfDescriptions.find(({ description }) => description.authenticator === this.authenticatorType); + log.info("shareDescriptionsMobile", shareDescriptionsMobile); + + if (shareDescriptionsMobile) { + this.factorPub = shareDescriptionsMobile.key; + this.tssIndex = shareDescriptionsMobile.description.tssShareIndex; + } + + return shareDescriptionsMobile; + } + + async registerSmsOTP(privKey: BN, number: string): Promise { + const privKeyPair: ec.KeyPair = secp256k1.keyFromPrivate(privKey.toString(16, 64)); + const pubKey = privKeyPair.getPublic(); + const sig = secp256k1.sign(keccak256(Buffer.from(number, "utf8")), Buffer.from(privKey.toString(16, 64), "hex")); + + const data = { + pubKey: { + x: pubKey.getX().toString(16, 64), + y: pubKey.getY().toString(16, 64), + }, + sig: { + r: sig.r.toString(16, 64), + s: sig.s.toString(16, 64), + v: new BN(sig.recoveryParam as number).toString(16, 2), + }, + number, + }; + + await post<{ + success: boolean; + id_token?: string; + message: string; + }>(`${this.backendUrl}/api/v1/register`, data); + + // this is to send sms to the user instantly after registration. + const startData = { + address: `${pubKey.getX().toString(16, 64)}${pubKey.getY().toString(16, 64)}`, + }; + + // Sends the user sms. + const resp2 = await post<{ success: boolean; code?: string }>(`${this.backendUrl}/api/v1/start`, startData); + // if (resp2.status !== 200) throw new Error("Error sending sms"); + return resp2.code; + } + + async addSmsRecovery(address: string, code: string, factorKey: BN) { + if (!factorKey) throw new Error("factorKey is not defined"); + if (!address) throw new Error("address is not defined"); + + const data = { + address, + code, + data: { + // If the verification is complete, we save the factorKey for the user address. + // This factorKey is used to verify the user in the future on a new device and recover tss share. + factorKey: factorKey.toString(16, 64), + }, + }; + + await post(`${this.backendUrl}/api/v1/verify`, data); + } + + async requestSMSOTP(address: string): Promise { + const startData = { + address, + }; + const resp2 = await post<{ success?: boolean; code?: string }>(`${this.backendUrl}/api/v1/start`, startData); + // eslint-disable-next-line no-console + console.log(resp2); + return resp2.code; + } + + async verifySMSOTPRecovery(address: string, code: string): Promise { + const verificationData = { + address, + code, + }; + + const response = await post<{ data?: Record }>(`${this.backendUrl}/api/v1/verify`, verificationData); + const { data } = response; + return data ? new BN(data.factorKey, "hex") : undefined; + } + + async verifyRemoteSetup(address: string, code: string): Promise { + const verificationData = { + address, + code, + }; + + const response = await post<{ data?: Record }>(`${this.backendUrl}/api/v1/verify_remote`, verificationData); + const { data } = response; + + return { + tssShareIndex: this.tssIndex, + remoteClientUrl: this.backendUrl, + remoteFactorPub: this.factorPub, + metadataShare: data.metadataShare, + remoteClientToken: data.signature, + }; + } +} From 15d846a827df16a8278d6b95304cc879262cec25 Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 29 Oct 2024 17:05:20 +0800 Subject: [PATCH 02/16] feat: working remote sign for authenticator --- src/helper/browserStorage.ts | 45 ++++++- src/index.ts | 1 + src/interfaces.ts | 5 +- src/mpcCoreKit.ts | 60 ++++++--- src/remoteFactorServices/authenticator.ts | 121 +++++++++++++++++ src/remoteFactorServices/index.ts | 23 ++++ .../remoteSignInterfaces.ts | 15 +++ .../smsOtp.ts | 56 ++------ src/remoteSignerServices/authenticator.ts | 127 ------------------ 9 files changed, 263 insertions(+), 190 deletions(-) create mode 100644 src/remoteFactorServices/authenticator.ts create mode 100644 src/remoteFactorServices/index.ts rename src/{ => remoteFactorServices}/remoteSignInterfaces.ts (73%) rename src/{remoteSignerServices => remoteFactorServices}/smsOtp.ts (62%) delete mode 100644 src/remoteSignerServices/authenticator.ts diff --git a/src/helper/browserStorage.ts b/src/helper/browserStorage.ts index 6a5555de..e5765727 100644 --- a/src/helper/browserStorage.ts +++ b/src/helper/browserStorage.ts @@ -1,4 +1,8 @@ -import { IAsyncStorage, IStorage } from "../interfaces"; +import { TKeyTSS } from "@tkey/tss"; +import BN from "bn.js"; + +import { FIELD_ELEMENT_HEX_LEN } from "../constants"; +import { IAsyncStorage, IStorage, TkeyLocalStoreData } from "../interfaces"; import CoreKitError from "./errors"; export class MemoryStorage implements IStorage { @@ -66,3 +70,42 @@ export class AsyncStorage { await this.storage.setItem(this._storeKey, JSON.stringify(store)); } } + +export class DeviceStorage { + private tKey: TKeyTSS; + + private currentStorage: AsyncStorage; + + constructor(tkeyInstance: TKeyTSS, currentStorage: AsyncStorage) { + this.tKey = tkeyInstance; + this.currentStorage = currentStorage; + } + + // device factor + async setDeviceFactor(factorKey: BN, replace = false): Promise { + 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"), + } as TkeyLocalStoreData) + ); + } + + async getDeviceFactor(): Promise { + 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 || "{}") as TkeyLocalStoreData; + return tKeyLocalStore.factorKey; + } +} diff --git a/src/index.ts b/src/index.ts index 3e41424a..5aae2b12 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,5 +2,6 @@ export * from "./constants"; export * from "./helper"; export * from "./interfaces"; export * from "./mpcCoreKit"; +export * from "./remoteFactorServices"; export * from "./utils"; export { factorKeyCurve } from "@tkey/tss"; diff --git a/src/interfaces.ts b/src/interfaces.ts index 2f71e626..ebeb6f9d 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -15,7 +15,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 { IRemoteClientState } from "./remoteSignInterfaces"; +import { IRemoteClientState } from "./remoteFactorServices/remoteSignInterfaces"; export type CoreKitMode = UX_MODE_TYPE | "nodejs" | "react-native"; @@ -100,7 +100,7 @@ export interface EnableMFAParams { /** * Additional metadata information you want to be stored alongside this factor for easy identification. */ - additionalMetadata?: Record; + additionalMetadata?: Record; } export interface CreateFactorParams extends EnableMFAParams { @@ -450,6 +450,7 @@ export interface SessionData { tssPubKey: string; signatures: string[]; userInfo: UserInfo; + remoteClientState?: IRemoteClientState; } export interface TkeyLocalStoreData { diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index f4be390f..55abe310 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -55,7 +55,7 @@ import { Web3AuthOptionsWithDefaults, Web3AuthState, } from "./interfaces"; -import { IRemoteClientState, RefreshRemoteTssReturnType } from "./remoteSignInterfaces"; +import { IRemoteClientState, RefreshRemoteTssReturnType } from "./remoteFactorServices/remoteSignInterfaces"; import { deriveShareCoefficients, ed25519, @@ -612,7 +612,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { } return this.atomicSync(async () => { - if (this.state.remoteClient && !this.state.factorKey) { + if (this.state.remoteClient) { if (shareType === this.state.tssShareIndex) { await this.remoteCopyFactorPub(factorPub, shareType); } else { @@ -662,7 +662,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { public async sign(data: Buffer, hashed: boolean = false): Promise { this.wasmLib = await this.loadTssWasm(); if (this.keyType === KeyType.secp256k1) { - if (this.state.remoteClient && !this.state.factorKey) { + if (this.state.remoteClient) { const sig = await this.remoteSignSecp256k1(data, hashed); return Buffer.concat([sig.r, sig.s, Buffer.from([sig.v])]); } @@ -694,7 +694,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { throw CoreKitError.factorInUseCannotBeDeleted("Cannot delete current active factor"); } - if (this.state.remoteClient && !this.state.factorKey) { + if (this.state.remoteClient) { await this.remoteDeleteFactorPub(factorPub); } else { await this.tKey.deleteFactorPub({ factorKey: this.state.factorKey, deleteFactorPub: factorPub, authSignatures: this.signatures }); @@ -863,8 +863,15 @@ export class Web3AuthMPCCoreKit implements ICoreKit { } } - async setupRemoteSigning(params: IRemoteClientState): Promise> { - const { remoteClientUrl, remoteFactorPub, metadataShare, remoteClientToken, tssShareIndex } = params; + async setupRemoteSigning(params: Omit): Promise { + const { remoteClientUrl, remoteFactorPub, metadataShare, remoteClientToken } = params; + const details = this.getKeyDetails().shareDescriptions[remoteFactorPub]; + if (!details) throw CoreKitError.default("factor description not found"); + + const parsedDescription = (details || [])[0] ? JSON.parse(details[0]) : {}; + const { tssShareIndex } = parsedDescription; + + if (!tssShareIndex) throw CoreKitError.default("tss share index not found"); const remoteClient: IRemoteClientState = { remoteClientUrl: remoteClientUrl.at(-1) === "/" ? remoteClientUrl.slice(0, -1) : remoteClientUrl, @@ -875,16 +882,15 @@ export class Web3AuthMPCCoreKit implements ICoreKit { }; const sharestore = ShareStore.fromJSON(JSON.parse(metadataShare)); - this.tkey.inputShareStoreSafe(sharestore); + await this.tkey.inputShareStoreSafe(sharestore); await this.tKey.reconstructKey(); const tssPubKey = this.tKey.getTSSPub().toSEC1(this.tkey.tssCurve, false); // setup Tkey // const tssPubKey = Point.fromTkeyPoint(this.tKey.getTSSPub()).toBufferSEC1(false); this.updateState({ tssShareIndex, tssPubKey, remoteClient }); - // // Finalize setup. // setup provider - await this.createSession(); + await this.createSessionRemoteClient(); } /** @@ -937,7 +943,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { const result = ( await post<{ data: RefreshRemoteTssReturnType }>( - `${remoteClient.remoteClientUrl}/api/mpc/refresh_tss`, + `${remoteClient.remoteClientUrl}/api/v3/mpc/refresh_tss`, { dataRequired }, { headers: { @@ -968,7 +974,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { const result = ( await post<{ data?: EncryptedMessage }>( - `${this.state.remoteClient.remoteClientUrl}/api/mpc/copy_tss_share`, + `${this.state.remoteClient.remoteClientUrl}/api/v3/mpc/copy_tss_share`, { dataRequired }, { headers: { @@ -1079,7 +1085,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { msgHash: msgData.toString("hex"), }; - const result = await post<{ data?: Record }>(`${this.state.remoteClient.remoteClientUrl}/api/mpc/sign`, data, { + const result = await post<{ data?: Record }>(`${this.state.remoteClient.remoteClientUrl}/api/v3/mpc/sign`, data, { headers: { Authorization: `Bearer ${this.state.remoteClient.remoteClientToken}`, }, @@ -1105,10 +1111,32 @@ export class Web3AuthMPCCoreKit implements ICoreKit { } return r; } finally { - this.atomicCallStackCounter -= 1; - if (this.atomicCallStackCounter === 0) { - this.tkey.manualSync = this.options.manualSync; + this.tkey.manualSync = this.options.manualSync; + } + } + + private async createSessionRemoteClient() { + try { + const sessionId = SessionManager.generateRandomSessionKey(); + this.sessionManager.sessionId = sessionId; + const { postBoxKey, userInfo, tssShareIndex, tssPubKey } = this.state; + if (!postBoxKey || !tssPubKey || !userInfo) { + throw CoreKitError.userNotLoggedIn(); } + const payload: SessionData = { + postBoxKey, + factorKey: "", + tssShareIndex: tssShareIndex as number, + tssPubKey: Buffer.from(tssPubKey).toString("hex"), + signatures: this.signatures, + userInfo, + remoteClientState: this.state.remoteClient, + }; + await this.sessionManager.createSession(payload); + // to accommodate async storage + await this.currentStorage.set("sessionId", sessionId); + } catch (err) { + log.error("error creating session", err); } } @@ -1420,7 +1448,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { private async addFactorDescription(args: { factorKey: BN; shareDescription: FactorKeyTypeShareDescription; - additionalMetadata?: Record; + additionalMetadata?: Record; updateMetadata?: boolean; }) { const { factorKey, shareDescription, updateMetadata } = args; diff --git a/src/remoteFactorServices/authenticator.ts b/src/remoteFactorServices/authenticator.ts new file mode 100644 index 00000000..257aa1c5 --- /dev/null +++ b/src/remoteFactorServices/authenticator.ts @@ -0,0 +1,121 @@ +import { Point, secp256k1 } from "@tkey/common-types"; +import { generatePrivateBN } from "@tkey/core"; +import { post } from "@toruslabs/http-helpers"; +import { keccak256 } from "@toruslabs/metadata-helpers"; +import BN from "bn.js"; +import base32 from "hi-base32"; + +import { WEB3AUTH_NETWORK_TYPE, Web3AuthMPCCoreKit } from "../"; +import { IRemoteClientState } from "./remoteSignInterfaces"; + +// todo, to replace with ICorekit +export function registerAuthenticatorService(_params: { backendUrl: string; secret: string; corekitInstance: Web3AuthMPCCoreKit }) { + // corekitInstance create factor + // sign secret with the corekit + // AuthenticatorService register +} + +// generate secret key for authenticator +export function generateSecretKey(): string { + const key = generatePrivateBN().toArray().slice(0, 20); + return base32.encode(key).toString().replace(/=/g, ""); +} + +export class AuthenticatorService { + public factorKey?: BN; + + // publickey + public factorPub?: string; + + private backendUrl: string; + + private web3authNetwork: WEB3AUTH_NETWORK_TYPE; + + private metadataUrl?: string; + + constructor(params: { backendUrl: string; web3authNetwork: WEB3AUTH_NETWORK_TYPE; overrideMetadataUrl?: string }) { + const { backendUrl } = params; + this.backendUrl = backendUrl; + this.web3authNetwork = params.web3authNetwork; + this.metadataUrl = params.overrideMetadataUrl; + } + + async register(secretKey: string, factorKey: string): Promise<{ success: boolean; message?: string }> { + // get pubkey + const privKeyPair = secp256k1.keyFromPrivate(factorKey); + const pubKey = privKeyPair.getPublic(); + const point = Point.fromElliptic(pubKey); + // sign secret + const sig = secp256k1.sign(keccak256(Buffer.from(secretKey, "utf8")), privKeyPair.getPrivate().toBuffer()); + + const data = { + address: point.toSEC1(secp256k1, true).toString("hex"), + sig: { + r: sig.r.toString("hex").padStart(64, "0"), + s: sig.s.toString("hex").padStart(64, "0"), + v: new BN(sig.recoveryParam).toString(16, 2), + }, + secretKey, + }; + + const resp = await post<{ + success: boolean; + message: string; + }>(`${this.backendUrl}/api/v3/register`, data); + + this.factorKey = new BN(factorKey, "hex"); + this.factorPub = point.toSEC1(secp256k1, true).toString("hex"); + + return resp; + } + + async verifyRegistration(code: string) { + if (!this.factorKey) throw new Error("factorKey is not defined"); + if (!this.factorPub) throw new Error("address is not defined"); + if (!code) throw new Error("code is not defined"); + + const data = { + address: this.factorPub.replaceAll("0x", ""), + code, + data: { + // If the verification is complete, we save the factorKey for the user address. + // This factorKey is used to verify the user in the future on a new device and recover tss share. + factorKey: this.factorKey.toString(16, 64), + }, + }; + + await post(`${this.backendUrl}/api/v3/verify`, data); + } + + // Verify the mfa code and return the factorKey + async verifyAuthenticatorRecovery(factorPub: string, code: string): Promise { + const verificationData = { + address: factorPub, + code, + }; + + const response = await post<{ data?: Record }>(`${this.backendUrl}/api/v3/verify`, verificationData); + const { data } = response; + return data ? new BN(data.factorKey, "hex") : undefined; + } + + // verify the mfa code and return the remote client setyp params + async verifyRemoteSetup(factorPub: string, code: string): Promise> { + const verificationData = { + address: factorPub, + code, + web3authNetwork: this.web3authNetwork, + metadataUrl: this.metadataUrl, + }; + + const response = await post<{ data?: Record }>(`${this.backendUrl}/api/v3/verify_remote`, verificationData); + const { data } = response; + + return { + remoteClientUrl: this.backendUrl, + remoteFactorPub: data.factorPub, + metadataShare: data.metadataShare, + remoteClientToken: data.signature, + }; + } +} diff --git a/src/remoteFactorServices/index.ts b/src/remoteFactorServices/index.ts new file mode 100644 index 00000000..711c83b8 --- /dev/null +++ b/src/remoteFactorServices/index.ts @@ -0,0 +1,23 @@ +import { ShareDescriptionMap } from "@tkey/common-types"; +import log from "loglevel"; + +import { RemoteFactorType } from "./remoteSignInterfaces"; + +export * from "./authenticator"; +export * from "./remoteSignInterfaces"; +export * from "./smsOtp"; + +export function getFactorDetailsAndDescriptions(shareDescriptions: ShareDescriptionMap, factorType: RemoteFactorType) { + const arrayOfDescriptions = Object.entries(shareDescriptions).map(([key, value]) => { + const parsedDescription = (value || [])[0] ? JSON.parse(value[0]) : {}; + return { + key, + description: parsedDescription, + }; + }); + + const shareDescriptionsMobile = arrayOfDescriptions.find(({ description }) => description.authenticator === factorType); + log.info("shareDescriptionsMobile", shareDescriptionsMobile); + + return { shareDescriptionsMobile, factorPub: shareDescriptionsMobile?.key, tssIndex: shareDescriptionsMobile?.description.tssShareIndex }; +} diff --git a/src/remoteSignInterfaces.ts b/src/remoteFactorServices/remoteSignInterfaces.ts similarity index 73% rename from src/remoteSignInterfaces.ts rename to src/remoteFactorServices/remoteSignInterfaces.ts index cc02c263..081c240a 100644 --- a/src/remoteSignInterfaces.ts +++ b/src/remoteFactorServices/remoteSignInterfaces.ts @@ -1,6 +1,20 @@ import { FactorEnc, Point } from "@tkey/common-types"; import { PointHex } from "@toruslabs/tss-client"; +import { FactorKeyTypeShareDescription, TssShareType } from "../constants"; + +export enum RemoteFactorType { + SMS = "sms", + Authenticator = "authenticator", +} + +export interface RemoteFactorDescription { + module: FactorKeyTypeShareDescription; + tssShareIndex: TssShareType; + authenticator: RemoteFactorType; + description: string; +} + export interface IRemoteClientState { remoteFactorPub: string; remoteClientUrl: string; @@ -31,6 +45,7 @@ export interface refreshRemoteTssType { authSignatures: string[]; }; } + export interface RefreshRemoteTssReturnType { tssTag: string; tssNonce: number; diff --git a/src/remoteSignerServices/smsOtp.ts b/src/remoteFactorServices/smsOtp.ts similarity index 62% rename from src/remoteSignerServices/smsOtp.ts rename to src/remoteFactorServices/smsOtp.ts index 7867606d..231e4c2e 100644 --- a/src/remoteSignerServices/smsOtp.ts +++ b/src/remoteFactorServices/smsOtp.ts @@ -1,48 +1,17 @@ -import { secp256k1, ShareDescriptionMap } from "@tkey/common-types"; +import { secp256k1 } from "@tkey/common-types"; import { post } from "@toruslabs/http-helpers"; import { keccak256 } from "@toruslabs/metadata-helpers"; import BN from "bn.js"; import type { ec } from "elliptic"; -import log from "loglevel"; -import { IRemoteClientState } from "src/remoteSignInterfaces"; + +import { IRemoteClientState } from "./remoteSignInterfaces"; export class SmsService { private backendUrl: string; - private shareDescriptions: ShareDescriptionMap; - - private authenticatorType: string = "sms"; - - private factorPub: string = ""; - - private tssIndex: number; - - constructor(params: { backendUrl: string; shareDescriptions: ShareDescriptionMap; authenticatorType?: string }) { + constructor(params: { backendUrl: string }) { const { backendUrl } = params; this.backendUrl = backendUrl; - this.authenticatorType = params.authenticatorType || "sms"; - this.shareDescriptions = params.shareDescriptions; - this.getDescriptionsAndUpdate(); - } - - getDescriptionsAndUpdate() { - const arrayOfDescriptions = Object.entries(this.shareDescriptions).map(([key, value]) => { - const parsedDescription = (value || [])[0] ? JSON.parse(value[0]) : {}; - return { - key, - description: parsedDescription, - }; - }); - - const shareDescriptionsMobile = arrayOfDescriptions.find(({ description }) => description.authenticator === this.authenticatorType); - log.info("shareDescriptionsMobile", shareDescriptionsMobile); - - if (shareDescriptionsMobile) { - this.factorPub = shareDescriptionsMobile.key; - this.tssIndex = shareDescriptionsMobile.description.tssShareIndex; - } - - return shareDescriptionsMobile; } async registerSmsOTP(privKey: BN, number: string): Promise { @@ -67,7 +36,7 @@ export class SmsService { success: boolean; id_token?: string; message: string; - }>(`${this.backendUrl}/api/v1/register`, data); + }>(`${this.backendUrl}/api/v3/register`, data); // this is to send sms to the user instantly after registration. const startData = { @@ -75,7 +44,7 @@ export class SmsService { }; // Sends the user sms. - const resp2 = await post<{ success: boolean; code?: string }>(`${this.backendUrl}/api/v1/start`, startData); + const resp2 = await post<{ success: boolean; code?: string }>(`${this.backendUrl}/api/v3/start`, startData); // if (resp2.status !== 200) throw new Error("Error sending sms"); return resp2.code; } @@ -94,14 +63,14 @@ export class SmsService { }, }; - await post(`${this.backendUrl}/api/v1/verify`, data); + await post(`${this.backendUrl}/api/v3/verify`, data); } async requestSMSOTP(address: string): Promise { const startData = { address, }; - const resp2 = await post<{ success?: boolean; code?: string }>(`${this.backendUrl}/api/v1/start`, startData); + const resp2 = await post<{ success?: boolean; code?: string }>(`${this.backendUrl}/api/v3/start`, startData); // eslint-disable-next-line no-console console.log(resp2); return resp2.code; @@ -113,24 +82,23 @@ export class SmsService { code, }; - const response = await post<{ data?: Record }>(`${this.backendUrl}/api/v1/verify`, verificationData); + const response = await post<{ data?: Record }>(`${this.backendUrl}/api/v3/verify`, verificationData); const { data } = response; return data ? new BN(data.factorKey, "hex") : undefined; } - async verifyRemoteSetup(address: string, code: string): Promise { + async verifyRemoteSetup(address: string, code: string): Promise> { const verificationData = { address, code, }; - const response = await post<{ data?: Record }>(`${this.backendUrl}/api/v1/verify_remote`, verificationData); + const response = await post<{ data?: Record }>(`${this.backendUrl}/api/v3/verify_remote`, verificationData); const { data } = response; return { - tssShareIndex: this.tssIndex, remoteClientUrl: this.backendUrl, - remoteFactorPub: this.factorPub, + remoteFactorPub: data.factorPub, metadataShare: data.metadataShare, remoteClientToken: data.signature, }; diff --git a/src/remoteSignerServices/authenticator.ts b/src/remoteSignerServices/authenticator.ts deleted file mode 100644 index 40fa32b5..00000000 --- a/src/remoteSignerServices/authenticator.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { secp256k1, ShareDescriptionMap } from "@tkey/common-types"; -import { generatePrivateBN } from "@tkey/core"; -import { post } from "@toruslabs/http-helpers"; -import { keccak256 } from "@toruslabs/metadata-helpers"; -import BN from "bn.js"; -import type { ec } from "elliptic"; -import base32 from "hi-base32"; -import log from "loglevel"; -import { IRemoteClientState } from "src/remoteSignInterfaces"; - -export class AuthenticatorService { - private backendUrl: string; - - private shareDescriptions: ShareDescriptionMap; - - private authenticatorType: string = "authenticator"; - - private factorPub: string = ""; - - private tssIndex: number; - - constructor(params: { backendUrl: string; shareDescriptions: ShareDescriptionMap; authenticatorType?: string }) { - const { backendUrl } = params; - this.backendUrl = backendUrl; - this.authenticatorType = params.authenticatorType || "authenticator"; - this.shareDescriptions = params.shareDescriptions; - // this.remoteClient = remoteClient || false; - } - - getDescriptionsAndUpdate() { - const arrayOfDescriptions = Object.entries(this.shareDescriptions).map(([key, value]) => { - const parsedDescription = (value || [])[0] ? JSON.parse(value[0]) : {}; - return { - key, - description: parsedDescription, - }; - }); - - const shareDescriptionsMobile = arrayOfDescriptions.find(({ description }) => description.authenticator === this.authenticatorType); - log.info("shareDescriptionsMobile", shareDescriptionsMobile); - - if (shareDescriptionsMobile) { - this.factorPub = shareDescriptionsMobile.key; - this.tssIndex = shareDescriptionsMobile.description.tssShareIndex; - } - - return shareDescriptionsMobile; - } - - generateSecretKey(): string { - const key = generatePrivateBN().toArray().slice(0, 20); - return base32.encode(key).toString().replace(/=/g, ""); - } - - async register(privKey: BN, secretKey: string): Promise<{ success: boolean; message?: string }> { - const privKeyPair: ec.KeyPair = secp256k1.keyFromPrivate(privKey.toString(16, 64)); - const pubKey = privKeyPair.getPublic(); - const sig = secp256k1.sign(keccak256(Buffer.from(secretKey, "utf8")), Buffer.from(privKey.toString(16, 64), "hex")); - - const data = { - pubKey: { - x: pubKey.getX().toString(16, 64), - y: pubKey.getY().toString(16, 64), - }, - sig: { - r: sig.r.toString(16, 64), - s: sig.s.toString(16, 64), - v: new BN(sig.recoveryParam as number).toString(16, 2), - }, - secretKey, - }; - - const resp = await post<{ - success: boolean; - message: string; - }>(`${this.backendUrl}/api/v1/register`, data); - - return resp; - } - - async addAuthenticatorRecovery(address: string, code: string, factorKey: BN) { - if (!factorKey) throw new Error("factorKey is not defined"); - if (!address) throw new Error("address is not defined"); - if (!code) throw new Error("code is not defined"); - - const data = { - address, - code, - data: { - // If the verification is complete, we save the factorKey for the user address. - // This factorKey is used to verify the user in the future on a new device and recover tss share. - factorKey: factorKey.toString(16, 64), - }, - }; - - await post(`${this.backendUrl}/api/v1/verify`, data); - } - - async verifyAuthenticatorRecovery(address: string, code: string): Promise { - const verificationData = { - address, - code, - }; - - const response = await post<{ data?: Record }>(`${this.backendUrl}/api/v1/verify`, verificationData); - const { data } = response; - return data ? new BN(data.factorKey, "hex") : undefined; - } - - async verifyRemoteSetup(address: string, code: string): Promise { - const verificationData = { - address, - code, - }; - - const response = await post<{ data?: Record }>(`${this.backendUrl}/api/v1/verify_remote`, verificationData); - const { data } = response; - - return { - tssShareIndex: this.tssIndex, - remoteClientUrl: this.backendUrl, - remoteFactorPub: this.factorPub, - metadataShare: data.metadataShare, - remoteClientToken: data.signature, - }; - } -} From 5b406326c4ff962f7945c3e1acb2808ca1b9a4c9 Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 1 Nov 2024 13:40:18 +0800 Subject: [PATCH 03/16] fix: remove unrelated changes --- src/helper/browserStorage.ts | 45 +----------------------------------- 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/src/helper/browserStorage.ts b/src/helper/browserStorage.ts index e5765727..6a5555de 100644 --- a/src/helper/browserStorage.ts +++ b/src/helper/browserStorage.ts @@ -1,8 +1,4 @@ -import { TKeyTSS } from "@tkey/tss"; -import BN from "bn.js"; - -import { FIELD_ELEMENT_HEX_LEN } from "../constants"; -import { IAsyncStorage, IStorage, TkeyLocalStoreData } from "../interfaces"; +import { IAsyncStorage, IStorage } from "../interfaces"; import CoreKitError from "./errors"; export class MemoryStorage implements IStorage { @@ -70,42 +66,3 @@ export class AsyncStorage { await this.storage.setItem(this._storeKey, JSON.stringify(store)); } } - -export class DeviceStorage { - private tKey: TKeyTSS; - - private currentStorage: AsyncStorage; - - constructor(tkeyInstance: TKeyTSS, currentStorage: AsyncStorage) { - this.tKey = tkeyInstance; - this.currentStorage = currentStorage; - } - - // device factor - async setDeviceFactor(factorKey: BN, replace = false): Promise { - 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"), - } as TkeyLocalStoreData) - ); - } - - async getDeviceFactor(): Promise { - 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 || "{}") as TkeyLocalStoreData; - return tKeyLocalStore.factorKey; - } -} From 318542fe5216858447f66c49eafe8349a4377c87 Mon Sep 17 00:00:00 2001 From: ieow Date: Mon, 4 Nov 2024 15:59:37 +0800 Subject: [PATCH 04/16] fix: refactor remote signer --- package-lock.json | 159 +++++------------- package.json | 4 +- src/index.ts | 1 - src/interfaces.ts | 3 +- src/mpcCoreKit.ts | 157 +---------------- src/remoteFactorServices/authenticator.ts | 121 ------------- src/remoteFactorServices/index.ts | 23 --- .../remoteSignInterfaces.ts | 57 ------- src/remoteFactorServices/smsOtp.ts | 106 ------------ 9 files changed, 48 insertions(+), 583 deletions(-) delete mode 100644 src/remoteFactorServices/authenticator.ts delete mode 100644 src/remoteFactorServices/index.ts delete mode 100644 src/remoteFactorServices/remoteSignInterfaces.ts delete mode 100644 src/remoteFactorServices/smsOtp.ts diff --git a/package-lock.json b/package-lock.json index c7477d38..f0d711ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,10 @@ "license": "ISC", "dependencies": { "@tkey/common-types": "^15.1.0", - "@tkey/core": "^15.1.0", + "@tkey/core": "^15.2.0-alpha.0", "@tkey/share-serialization": "^15.1.0", "@tkey/storage-layer-torus": "^15.1.0", - "@tkey/tss": "^15.1.0", + "@tkey/tss": "file:../tkey/packages/tss", "@toruslabs/constants": "^14.0.0", "@toruslabs/customauth": "^20.3.0", "@toruslabs/elliptic-wrapper": "^0.1.0", @@ -80,6 +80,32 @@ } } }, + "../tkey/packages/tss": { + "name": "@tkey/tss", + "version": "15.1.0", + "license": "ISC", + "dependencies": { + "@tkey/common-types": "^15.1.0", + "@tkey/core": "^15.1.0", + "@tkey/service-provider-torus": "^15.1.0", + "@toruslabs/customauth": "^20.3.0", + "@toruslabs/http-helpers": "^7.0.0", + "@toruslabs/rss-client": "^2.0.1", + "@toruslabs/torus.js": "^15.1.0", + "@types/bn.js": "^5.1.5", + "bn.js": "^5.2.1", + "elliptic": "^6.5.5", + "ethereum-cryptography": "^2.1.3" + }, + "devDependencies": { + "@tkey/storage-layer-torus": "^15.1.0", + "@types/jsrsasign": "^10.5.13", + "jsrsasign": "^11.1.0", + "ts-node": "^10.9.2", + "tsx": "^4.17.0", + "typescript": "^5.4.5" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -3646,7 +3672,6 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/@tkey/common-types/-/common-types-15.1.0.tgz", "integrity": "sha512-oA5gLoyhNNMgCKcjvwLyU31TVS5KMT+lotRrjjoBdDvS0keZwzSLrtHWbXj8jZDlSZaqbd3VlPbCoHcqpk1irA==", - "license": "MIT", "dependencies": { "@toruslabs/customauth": "^20.3.0", "@toruslabs/eccrypto": "^5.0.4", @@ -3664,10 +3689,9 @@ } }, "node_modules/@tkey/core": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@tkey/core/-/core-15.1.0.tgz", - "integrity": "sha512-JaFprczHR8fBEw1LrwKs87ASgpZagxQ9VZ6lAfAAI8jEh1yhz8djh9l2wzJbaFuLEOQskh7GoxpKfgB+YtBSmw==", - "license": "MIT", + "version": "15.2.0-alpha.0", + "resolved": "https://registry.npmjs.org/@tkey/core/-/core-15.2.0-alpha.0.tgz", + "integrity": "sha512-uBrh0RpGBME+8ZK4y1IKQYFy9j0K6pW+14Zrp1pSAgSJRNMrTypvROReBAxgHQkMWA4AdvBH9xXxaYxjc7octA==", "dependencies": { "@tkey/common-types": "^15.1.0", "@toruslabs/eccrypto": "^5.0.4", @@ -3686,45 +3710,6 @@ "@babel/runtime": "7.x" } }, - "node_modules/@tkey/service-provider-base": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@tkey/service-provider-base/-/service-provider-base-15.1.0.tgz", - "integrity": "sha512-MruUxiWwyRczZ8KlhhGJ2TQ/p+VFPMOQZ089B5SIi7UsTOBMlzRqJWP3lM2fBSyQsfJCzpzXkj9a29ecpRZe0g==", - "license": "MIT", - "dependencies": { - "@tkey/common-types": "^15.1.0", - "bn.js": "^5.2.1", - "elliptic": "^6.5.5" - }, - "engines": { - "node": ">=18.x", - "npm": ">=9.x" - }, - "peerDependencies": { - "@babel/runtime": "7.x" - } - }, - "node_modules/@tkey/service-provider-torus": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@tkey/service-provider-torus/-/service-provider-torus-15.1.0.tgz", - "integrity": "sha512-7tA/1ALPo4ToXvwTwMj9OF0wh97S3p1sCeilwRcfyxBMJGpaDW8MSbiAbPqaSkK/DT3AFxlkHwAXWYYZ4+ZueQ==", - "license": "MIT", - "dependencies": { - "@tkey/common-types": "^15.1.0", - "@tkey/service-provider-base": "^15.1.0", - "@toruslabs/customauth": "^20.3.0", - "@toruslabs/torus.js": "^15.1.0", - "bn.js": "^5.2.1", - "elliptic": "^6.5.5" - }, - "engines": { - "node": ">=18.x", - "npm": ">=9.x" - }, - "peerDependencies": { - "@babel/runtime": "7.x" - } - }, "node_modules/@tkey/share-serialization": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/@tkey/share-serialization/-/share-serialization-15.1.0.tgz", @@ -3765,22 +3750,8 @@ } }, "node_modules/@tkey/tss": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@tkey/tss/-/tss-15.1.0.tgz", - "integrity": "sha512-UcbJbWscIL83Zh1/i6M+X/xhN4EOimGV8JoWZ3D23Ji2pHPo8BgveUZA9i1DK4Y3YqqZ9aS8PvhSHt+KVnNluw==", - "license": "ISC", - "dependencies": { - "@tkey/common-types": "^15.1.0", - "@tkey/core": "^15.1.0", - "@tkey/service-provider-torus": "^15.1.0", - "@toruslabs/customauth": "^20.3.0", - "@toruslabs/rss-client": "^2.0.1", - "@toruslabs/torus.js": "^15.1.0", - "@types/bn.js": "^5.1.5", - "bn.js": "^5.2.1", - "elliptic": "^6.5.5", - "ethereum-cryptography": "^2.1.3" - } + "resolved": "../tkey/packages/tss", + "link": true }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", @@ -4303,27 +4274,6 @@ "@babel/runtime": "7.x" } }, - "node_modules/@toruslabs/rss-client": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@toruslabs/rss-client/-/rss-client-2.0.1.tgz", - "integrity": "sha512-EBqAX7LK8ZBy4fiGTGQhI3wVvLvfUyfwREbNkfAxP/iwevgCA2EKLXGWtVsvJj/wXF8HkfjqOcorVeHwP2vvWw==", - "license": "MIT", - "dependencies": { - "@toruslabs/eccrypto": "^5.0.4", - "@toruslabs/http-helpers": "^7.0.0", - "bn.js": "^5.2.1", - "elliptic": "^6.5.7", - "fetch": "^1.1.0", - "loglevel": "^1.9.2" - }, - "engines": { - "node": ">=18.x", - "npm": ">=9.x" - }, - "peerDependencies": { - "@babel/runtime": "7.x" - } - }, "node_modules/@toruslabs/session-manager": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@toruslabs/session-manager/-/session-manager-3.1.0.tgz", @@ -4551,6 +4501,7 @@ "version": "5.1.5", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -4683,6 +4634,7 @@ "version": "20.16.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.3.tgz", "integrity": "sha512-/wdGiWRkMOm53gAsSyFMXFZHbVg7C6CbkrzHNpaHoYfsUWPg7m6ZRKtvQjgvQ9i8WT540a3ydRlRQbxjY30XxQ==", + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.19.2" @@ -5856,18 +5808,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/biskviit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/biskviit/-/biskviit-1.0.1.tgz", - "integrity": "sha512-VGCXdHbdbpEkFgtjkeoBN8vRlbj1ZRX2/mxhE8asCCRalUx2nBzOomLJv8Aw/nRt5+ccDb+tPKidg4XxcfGW4w==", - "license": "MIT", - "dependencies": { - "psl": "^1.1.7" - }, - "engines": { - "node": ">=1.0.0" - } - }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -7469,15 +7409,6 @@ "dev": true, "license": "MIT" }, - "node_modules/encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha512-bl1LAgiQc4ZWr++pNYUdRe/alecaHFeHxIJ/pNciqGdKXghaTCOwKkbKp6ye7pKZGu/GcaSXFk8PBVhgs+dJdA==", - "license": "MIT", - "dependencies": { - "iconv-lite": "~0.4.13" - } - }, "node_modules/engine.io-client": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz", @@ -8714,16 +8645,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-5O8TwrGzoNblBG/jtK4NFuZwNCkZX6s5GfRNOaGtm+QGJEuNakSC/i2RW0R93KX6E0jVjNXm6O3CRN4Ql3K+yA==", - "license": "MIT", - "dependencies": { - "biskviit": "1.0.1", - "encoding": "0.1.12" - } - }, "node_modules/fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", @@ -9766,6 +9687,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" @@ -13305,12 +13227,6 @@ "dev": true, "license": "MIT" }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "license": "MIT" - }, "node_modules/public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -14395,6 +14311,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, "license": "MIT" }, "node_modules/schema-utils": { @@ -15341,7 +15258,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.3.1.tgz", "integrity": "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==", - "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -15652,6 +15568,7 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { diff --git a/package.json b/package.json index 05441c26..b6243ea3 100644 --- a/package.json +++ b/package.json @@ -44,10 +44,10 @@ }, "dependencies": { "@tkey/common-types": "^15.1.0", - "@tkey/core": "^15.1.0", + "@tkey/core": "^15.2.0-alpha.0", "@tkey/share-serialization": "^15.1.0", "@tkey/storage-layer-torus": "^15.1.0", - "@tkey/tss": "^15.1.0", + "@tkey/tss": "file:../tkey/packages/tss", "@toruslabs/constants": "^14.0.0", "@toruslabs/customauth": "^20.3.0", "@toruslabs/elliptic-wrapper": "^0.1.0", diff --git a/src/index.ts b/src/index.ts index 5aae2b12..3e41424a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,5 @@ export * from "./constants"; export * from "./helper"; export * from "./interfaces"; export * from "./mpcCoreKit"; -export * from "./remoteFactorServices"; export * from "./utils"; export { factorKeyCurve } from "@tkey/tss"; diff --git a/src/interfaces.ts b/src/interfaces.ts index ebeb6f9d..85ce1147 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,5 +1,5 @@ import { KeyType, Point as TkeyPoint, ShareDescriptionMap } from "@tkey/common-types"; -import { TKeyTSS } from "@tkey/tss"; +import { IRemoteClientState, TKeyTSS } from "@tkey/tss"; import type { AGGREGATE_VERIFIER_TYPE, ExtraParams, @@ -15,7 +15,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 { IRemoteClientState } from "./remoteFactorServices/remoteSignInterfaces"; export type CoreKitMode = UX_MODE_TYPE | "nodejs" | "react-native"; diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index 55abe310..9242c65b 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -1,8 +1,8 @@ -import { BNString, EncryptedMessage, FactorEnc, KeyType, Point, secp256k1, SHARE_DELETED, ShareStore, StringifiedType } from "@tkey/common-types"; +import { BNString, KeyType, 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"; -import { DELIMITERS, factorKeyCurve, getPubKeyPoint, lagrangeInterpolation, randomSelection, TKeyTSS, TSSTorusServiceProvider } from "@tkey/tss"; +import { DELIMITERS, factorKeyCurve, getPubKeyPoint, IRemoteClientState, lagrangeInterpolation, TKeyTSS, TSSTorusServiceProvider } from "@tkey/tss"; import { KEY_TYPE, SIGNER_MAP } from "@toruslabs/constants"; import { AGGREGATE_VERIFIER, TORUS_METHOD, TorusAggregateLoginResponse, TorusLoginResponse, UX_MODE } from "@toruslabs/customauth"; import type { UX_MODE_TYPE } from "@toruslabs/customauth/dist/types/utils/enums"; @@ -55,7 +55,6 @@ import { Web3AuthOptionsWithDefaults, Web3AuthState, } from "./interfaces"; -import { IRemoteClientState, RefreshRemoteTssReturnType } from "./remoteFactorServices/remoteSignInterfaces"; import { deriveShareCoefficients, ed25519, @@ -614,9 +613,9 @@ export class Web3AuthMPCCoreKit implements ICoreKit { return this.atomicSync(async () => { if (this.state.remoteClient) { if (shareType === this.state.tssShareIndex) { - await this.remoteCopyFactorPub(factorPub, shareType); + await this.tkey.remoteCopyFactorPub({ newFactorPub: factorPub, tssIndex: shareType, remoteClient: this.state.remoteClient }); } else { - await this.remoteAddFactorPub(factorPub, shareType); + await this.tkey.remoteAddFactorPub({ newFactorPub: factorPub, newFactorTSSIndex: shareType, remoteClient: this.state.remoteClient }); } } else { await this.copyOrCreateShare(shareType, factorPub); @@ -695,7 +694,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { } if (this.state.remoteClient) { - await this.remoteDeleteFactorPub(factorPub); + await this.tkey.remoteDeleteFactorPub({ factorPubToDelete: factorPub, remoteClient: this.state.remoteClient }); } else { await this.tKey.deleteFactorPub({ factorKey: this.state.factorKey, deleteFactorPub: factorPub, authSignatures: this.signatures }); } @@ -863,7 +862,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { } } - async setupRemoteSigning(params: Omit): Promise { + async setupRemoteSigning(params: Omit): Promise { const { remoteClientUrl, remoteFactorPub, metadataShare, remoteClientToken } = params; const details = this.getKeyDetails().shareDescriptions[remoteFactorPub]; if (!details) throw CoreKitError.default("factor description not found"); @@ -879,6 +878,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { metadataShare, remoteClientToken, tssShareIndex, + signatures: this.state.signatures, }; const sharestore = ShareStore.fromJSON(JSON.parse(metadataShare)); @@ -893,149 +893,6 @@ export class Web3AuthMPCCoreKit implements ICoreKit { await this.createSessionRemoteClient(); } - /** - * Refreshes TSS shares. Allows to change number of shares. New user shares are - * only produced for the target indices. - * @param factorPubs - Factor pub keys after refresh. - * @param tssIndices - Target tss indices to generate new shares for. - * @param remoteFactorPub - Factor Pub for remote share. - * @param signatures - Signatures for authentication against RSS servers. - */ - async remoteRefreshTssShares(params: { factorPubs: Point[]; tssIndices: number[]; signatures: string[]; remoteClient: IRemoteClientState }) { - const { factorPubs, tssIndices, signatures, remoteClient } = params; - const { tKey } = this; - const rssNodeDetails = await tKey._getRssNodeDetails(); - const { serverEndpoints, serverPubKeys, serverThreshold } = rssNodeDetails; - let finalSelectedServers = randomSelection( - new Array(rssNodeDetails.serverEndpoints.length).fill(null).map((_, i) => i + 1), - Math.ceil(rssNodeDetails.serverEndpoints.length / 2) - ); - - const verifierNameVerifierId = tKey.serviceProvider.getVerifierNameVerifierId(); - - const tssCommits = tKey.metadata.tssPolyCommits[tKey.tssTag]; - const tssNonce: number = tKey.metadata.tssNonces[tKey.tssTag] || 0; - const { pubKey: newTSSServerPub, nodeIndexes } = await tKey.serviceProvider.getTSSPubKey(tKey.tssTag, tssNonce + 1); - // move to pre-refresh - if (nodeIndexes?.length > 0) { - finalSelectedServers = nodeIndexes.slice(0, Math.min(serverEndpoints.length, nodeIndexes.length)); - } - - const factorEnc = tKey.getFactorEncs(Point.fromSEC1(secp256k1, remoteClient.remoteFactorPub)); - - const dataRequired = { - factorEnc, - factorPubs: factorPubs.map((pub) => pub.toJSON()), - targetIndexes: tssIndices, - verifierNameVerifierId, - tssTag: tKey.tssTag, - tssCommits: tssCommits.map((commit) => commit.toJSON()), - tssNonce, - newTSSServerPub: newTSSServerPub.toJSON(), - serverOpts: { - selectedServers: finalSelectedServers, - serverEndpoints, - serverPubKeys, - serverThreshold, - authSignatures: signatures, - }, - }; - - const result = ( - await post<{ data: RefreshRemoteTssReturnType }>( - `${remoteClient.remoteClientUrl}/api/v3/mpc/refresh_tss`, - { dataRequired }, - { - headers: { - Authorization: `Bearer ${remoteClient.remoteClientToken}`, - }, - } - ) - ).data; - - tKey.metadata.updateTSSData({ - tssTag: result.tssTag, - tssNonce: result.tssNonce, - tssPolyCommits: result.tssPolyCommits.map((commit) => Point.fromJSON(commit)), - factorPubs: result.factorPubs.map((pub) => Point.fromJSON(pub)), - factorEncs: result.factorEncs, - }); - } - - async remoteCopyFactorPub(newFactorPub: Point, tssIndex: number) { - const remoteFactorPub = Point.fromSEC1(secp256k1, this.state.remoteClient.remoteFactorPub); - const factorEnc = this.tkey.getFactorEncs(remoteFactorPub); - const tssCommits = this.tkey.getTSSCommits(); - const dataRequired = { - factorEnc, - tssCommits, - factorPub: newFactorPub, - }; - - const result = ( - await post<{ data?: EncryptedMessage }>( - `${this.state.remoteClient.remoteClientUrl}/api/v3/mpc/copy_tss_share`, - { dataRequired }, - { - headers: { - Authorization: `Bearer ${this.state.remoteClient.remoteClientToken}`, - }, - } - ) - ).data; - - const { tssTag } = this.tkey; - const updatedFactorPubs = this.tkey.metadata.factorPubs[tssTag].concat([newFactorPub]); - const factorEncs: { [key: string]: FactorEnc } = JSON.parse(JSON.stringify(this.tkey.metadata.factorEncs[tssTag])); - const factorPubID = newFactorPub.x.toString(16, 64); - factorEncs[factorPubID] = { - tssIndex, - type: "direct", - userEnc: result, - serverEncs: [], - }; - this.tkey.metadata.updateTSSData({ - tssKeyType: this.keyType, - tssTag: this.tkey.tssTag, - factorPubs: updatedFactorPubs, - factorEncs, - }); - } - - async remoteAddFactorPub(newFactorPub: Point, newFactorTSSIndex: number) { - const { tKey } = this; - const existingFactorPubs = tKey.metadata.factorPubs[tKey.tssTag]; - const updatedFactorPubs = existingFactorPubs.concat([newFactorPub]); - const existingTSSIndexes = existingFactorPubs.map((fb) => tKey.getFactorEncs(fb).tssIndex); - const updatedTSSIndexes = existingTSSIndexes.concat([newFactorTSSIndex]); - - await this.remoteRefreshTssShares({ - factorPubs: updatedFactorPubs, - tssIndices: updatedTSSIndexes, - signatures: this.state.signatures, - remoteClient: this.state.remoteClient, - }); - } - - async remoteDeleteFactorPub(factorPubToDelete: Point) { - const { tKey } = this; - const existingFactorPubs = tKey.metadata.factorPubs[tKey.tssTag]; - const factorIndex = existingFactorPubs.findIndex((p) => p.x.eq(factorPubToDelete.x)); - if (factorIndex === -1) { - throw new Error(`factorPub ${factorPubToDelete} does not exist`); - } - const updatedFactorPubs = existingFactorPubs.slice(); - updatedFactorPubs.splice(factorIndex, 1); - const updatedTSSIndexes = updatedFactorPubs.map((fb) => tKey.getFactorEncs(fb).tssIndex); - - await this.remoteRefreshTssShares({ - factorPubs: updatedFactorPubs, - tssIndices: updatedTSSIndexes, - signatures: this.state.signatures, - remoteClient: this.state.remoteClient, - }); - } - public async remoteSignSecp256k1(msgData: Buffer, hashed: boolean = false): Promise<{ v: number; r: Buffer; s: Buffer }> { if (!hashed) { msgData = keccak256(msgData); diff --git a/src/remoteFactorServices/authenticator.ts b/src/remoteFactorServices/authenticator.ts deleted file mode 100644 index 257aa1c5..00000000 --- a/src/remoteFactorServices/authenticator.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { Point, secp256k1 } from "@tkey/common-types"; -import { generatePrivateBN } from "@tkey/core"; -import { post } from "@toruslabs/http-helpers"; -import { keccak256 } from "@toruslabs/metadata-helpers"; -import BN from "bn.js"; -import base32 from "hi-base32"; - -import { WEB3AUTH_NETWORK_TYPE, Web3AuthMPCCoreKit } from "../"; -import { IRemoteClientState } from "./remoteSignInterfaces"; - -// todo, to replace with ICorekit -export function registerAuthenticatorService(_params: { backendUrl: string; secret: string; corekitInstance: Web3AuthMPCCoreKit }) { - // corekitInstance create factor - // sign secret with the corekit - // AuthenticatorService register -} - -// generate secret key for authenticator -export function generateSecretKey(): string { - const key = generatePrivateBN().toArray().slice(0, 20); - return base32.encode(key).toString().replace(/=/g, ""); -} - -export class AuthenticatorService { - public factorKey?: BN; - - // publickey - public factorPub?: string; - - private backendUrl: string; - - private web3authNetwork: WEB3AUTH_NETWORK_TYPE; - - private metadataUrl?: string; - - constructor(params: { backendUrl: string; web3authNetwork: WEB3AUTH_NETWORK_TYPE; overrideMetadataUrl?: string }) { - const { backendUrl } = params; - this.backendUrl = backendUrl; - this.web3authNetwork = params.web3authNetwork; - this.metadataUrl = params.overrideMetadataUrl; - } - - async register(secretKey: string, factorKey: string): Promise<{ success: boolean; message?: string }> { - // get pubkey - const privKeyPair = secp256k1.keyFromPrivate(factorKey); - const pubKey = privKeyPair.getPublic(); - const point = Point.fromElliptic(pubKey); - // sign secret - const sig = secp256k1.sign(keccak256(Buffer.from(secretKey, "utf8")), privKeyPair.getPrivate().toBuffer()); - - const data = { - address: point.toSEC1(secp256k1, true).toString("hex"), - sig: { - r: sig.r.toString("hex").padStart(64, "0"), - s: sig.s.toString("hex").padStart(64, "0"), - v: new BN(sig.recoveryParam).toString(16, 2), - }, - secretKey, - }; - - const resp = await post<{ - success: boolean; - message: string; - }>(`${this.backendUrl}/api/v3/register`, data); - - this.factorKey = new BN(factorKey, "hex"); - this.factorPub = point.toSEC1(secp256k1, true).toString("hex"); - - return resp; - } - - async verifyRegistration(code: string) { - if (!this.factorKey) throw new Error("factorKey is not defined"); - if (!this.factorPub) throw new Error("address is not defined"); - if (!code) throw new Error("code is not defined"); - - const data = { - address: this.factorPub.replaceAll("0x", ""), - code, - data: { - // If the verification is complete, we save the factorKey for the user address. - // This factorKey is used to verify the user in the future on a new device and recover tss share. - factorKey: this.factorKey.toString(16, 64), - }, - }; - - await post(`${this.backendUrl}/api/v3/verify`, data); - } - - // Verify the mfa code and return the factorKey - async verifyAuthenticatorRecovery(factorPub: string, code: string): Promise { - const verificationData = { - address: factorPub, - code, - }; - - const response = await post<{ data?: Record }>(`${this.backendUrl}/api/v3/verify`, verificationData); - const { data } = response; - return data ? new BN(data.factorKey, "hex") : undefined; - } - - // verify the mfa code and return the remote client setyp params - async verifyRemoteSetup(factorPub: string, code: string): Promise> { - const verificationData = { - address: factorPub, - code, - web3authNetwork: this.web3authNetwork, - metadataUrl: this.metadataUrl, - }; - - const response = await post<{ data?: Record }>(`${this.backendUrl}/api/v3/verify_remote`, verificationData); - const { data } = response; - - return { - remoteClientUrl: this.backendUrl, - remoteFactorPub: data.factorPub, - metadataShare: data.metadataShare, - remoteClientToken: data.signature, - }; - } -} diff --git a/src/remoteFactorServices/index.ts b/src/remoteFactorServices/index.ts deleted file mode 100644 index 711c83b8..00000000 --- a/src/remoteFactorServices/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ShareDescriptionMap } from "@tkey/common-types"; -import log from "loglevel"; - -import { RemoteFactorType } from "./remoteSignInterfaces"; - -export * from "./authenticator"; -export * from "./remoteSignInterfaces"; -export * from "./smsOtp"; - -export function getFactorDetailsAndDescriptions(shareDescriptions: ShareDescriptionMap, factorType: RemoteFactorType) { - const arrayOfDescriptions = Object.entries(shareDescriptions).map(([key, value]) => { - const parsedDescription = (value || [])[0] ? JSON.parse(value[0]) : {}; - return { - key, - description: parsedDescription, - }; - }); - - const shareDescriptionsMobile = arrayOfDescriptions.find(({ description }) => description.authenticator === factorType); - log.info("shareDescriptionsMobile", shareDescriptionsMobile); - - return { shareDescriptionsMobile, factorPub: shareDescriptionsMobile?.key, tssIndex: shareDescriptionsMobile?.description.tssShareIndex }; -} diff --git a/src/remoteFactorServices/remoteSignInterfaces.ts b/src/remoteFactorServices/remoteSignInterfaces.ts deleted file mode 100644 index 081c240a..00000000 --- a/src/remoteFactorServices/remoteSignInterfaces.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { FactorEnc, Point } from "@tkey/common-types"; -import { PointHex } from "@toruslabs/tss-client"; - -import { FactorKeyTypeShareDescription, TssShareType } from "../constants"; - -export enum RemoteFactorType { - SMS = "sms", - Authenticator = "authenticator", -} - -export interface RemoteFactorDescription { - module: FactorKeyTypeShareDescription; - tssShareIndex: TssShareType; - authenticator: RemoteFactorType; - description: string; -} - -export interface IRemoteClientState { - remoteFactorPub: string; - remoteClientUrl: string; - remoteClientToken: string; - metadataShare: string; - tssShareIndex: number; -} - -export interface refreshRemoteTssType { - // from client - factorEnc: FactorEnc; - - factorPubs: Point[]; - targetIndexes: number[]; - verifierNameVerifierId: string; - - tssTag: string; - tssCommits: Point[]; - tssNonce: number; - newTSSServerPub: Point; - // nodeIndexes : number[], - - serverOpts: { - serverEndpoints: string[]; - serverPubKeys: PointHex[]; - serverThreshold: number; - selectedServers: number[]; - authSignatures: string[]; - }; -} - -export interface RefreshRemoteTssReturnType { - tssTag: string; - tssNonce: number; - tssPolyCommits: Point[]; - factorPubs: Point[]; - factorEncs: { - [factorPubID: string]: FactorEnc; - }; -} diff --git a/src/remoteFactorServices/smsOtp.ts b/src/remoteFactorServices/smsOtp.ts deleted file mode 100644 index 231e4c2e..00000000 --- a/src/remoteFactorServices/smsOtp.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { secp256k1 } from "@tkey/common-types"; -import { post } from "@toruslabs/http-helpers"; -import { keccak256 } from "@toruslabs/metadata-helpers"; -import BN from "bn.js"; -import type { ec } from "elliptic"; - -import { IRemoteClientState } from "./remoteSignInterfaces"; - -export class SmsService { - private backendUrl: string; - - constructor(params: { backendUrl: string }) { - const { backendUrl } = params; - this.backendUrl = backendUrl; - } - - async registerSmsOTP(privKey: BN, number: string): Promise { - const privKeyPair: ec.KeyPair = secp256k1.keyFromPrivate(privKey.toString(16, 64)); - const pubKey = privKeyPair.getPublic(); - const sig = secp256k1.sign(keccak256(Buffer.from(number, "utf8")), Buffer.from(privKey.toString(16, 64), "hex")); - - const data = { - pubKey: { - x: pubKey.getX().toString(16, 64), - y: pubKey.getY().toString(16, 64), - }, - sig: { - r: sig.r.toString(16, 64), - s: sig.s.toString(16, 64), - v: new BN(sig.recoveryParam as number).toString(16, 2), - }, - number, - }; - - await post<{ - success: boolean; - id_token?: string; - message: string; - }>(`${this.backendUrl}/api/v3/register`, data); - - // this is to send sms to the user instantly after registration. - const startData = { - address: `${pubKey.getX().toString(16, 64)}${pubKey.getY().toString(16, 64)}`, - }; - - // Sends the user sms. - const resp2 = await post<{ success: boolean; code?: string }>(`${this.backendUrl}/api/v3/start`, startData); - // if (resp2.status !== 200) throw new Error("Error sending sms"); - return resp2.code; - } - - async addSmsRecovery(address: string, code: string, factorKey: BN) { - if (!factorKey) throw new Error("factorKey is not defined"); - if (!address) throw new Error("address is not defined"); - - const data = { - address, - code, - data: { - // If the verification is complete, we save the factorKey for the user address. - // This factorKey is used to verify the user in the future on a new device and recover tss share. - factorKey: factorKey.toString(16, 64), - }, - }; - - await post(`${this.backendUrl}/api/v3/verify`, data); - } - - async requestSMSOTP(address: string): Promise { - const startData = { - address, - }; - const resp2 = await post<{ success?: boolean; code?: string }>(`${this.backendUrl}/api/v3/start`, startData); - // eslint-disable-next-line no-console - console.log(resp2); - return resp2.code; - } - - async verifySMSOTPRecovery(address: string, code: string): Promise { - const verificationData = { - address, - code, - }; - - const response = await post<{ data?: Record }>(`${this.backendUrl}/api/v3/verify`, verificationData); - const { data } = response; - return data ? new BN(data.factorKey, "hex") : undefined; - } - - async verifyRemoteSetup(address: string, code: string): Promise> { - const verificationData = { - address, - code, - }; - - const response = await post<{ data?: Record }>(`${this.backendUrl}/api/v3/verify_remote`, verificationData); - const { data } = response; - - return { - remoteClientUrl: this.backendUrl, - remoteFactorPub: data.factorPub, - metadataShare: data.metadataShare, - remoteClientToken: data.signature, - }; - } -} From 765c89d19957882eab937500464f7e745cbcef11 Mon Sep 17 00:00:00 2001 From: ieow Date: Mon, 4 Nov 2024 18:13:02 +0800 Subject: [PATCH 05/16] update: use alpha tss --- package-lock.json | 105 ++++++++++++++++++++++++++++++++-------------- package.json | 2 +- 2 files changed, 74 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index f0d711ad..eb43d9cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@tkey/core": "^15.2.0-alpha.0", "@tkey/share-serialization": "^15.1.0", "@tkey/storage-layer-torus": "^15.1.0", - "@tkey/tss": "file:../tkey/packages/tss", + "@tkey/tss": "^15.2.0-alpha.0", "@toruslabs/constants": "^14.0.0", "@toruslabs/customauth": "^20.3.0", "@toruslabs/elliptic-wrapper": "^0.1.0", @@ -80,32 +80,6 @@ } } }, - "../tkey/packages/tss": { - "name": "@tkey/tss", - "version": "15.1.0", - "license": "ISC", - "dependencies": { - "@tkey/common-types": "^15.1.0", - "@tkey/core": "^15.1.0", - "@tkey/service-provider-torus": "^15.1.0", - "@toruslabs/customauth": "^20.3.0", - "@toruslabs/http-helpers": "^7.0.0", - "@toruslabs/rss-client": "^2.0.1", - "@toruslabs/torus.js": "^15.1.0", - "@types/bn.js": "^5.1.5", - "bn.js": "^5.2.1", - "elliptic": "^6.5.5", - "ethereum-cryptography": "^2.1.3" - }, - "devDependencies": { - "@tkey/storage-layer-torus": "^15.1.0", - "@types/jsrsasign": "^10.5.13", - "jsrsasign": "^11.1.0", - "ts-node": "^10.9.2", - "tsx": "^4.17.0", - "typescript": "^5.4.5" - } - }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -3710,6 +3684,43 @@ "@babel/runtime": "7.x" } }, + "node_modules/@tkey/service-provider-base": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@tkey/service-provider-base/-/service-provider-base-15.1.0.tgz", + "integrity": "sha512-MruUxiWwyRczZ8KlhhGJ2TQ/p+VFPMOQZ089B5SIi7UsTOBMlzRqJWP3lM2fBSyQsfJCzpzXkj9a29ecpRZe0g==", + "dependencies": { + "@tkey/common-types": "^15.1.0", + "bn.js": "^5.2.1", + "elliptic": "^6.5.5" + }, + "engines": { + "node": ">=18.x", + "npm": ">=9.x" + }, + "peerDependencies": { + "@babel/runtime": "7.x" + } + }, + "node_modules/@tkey/service-provider-torus": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@tkey/service-provider-torus/-/service-provider-torus-15.1.0.tgz", + "integrity": "sha512-7tA/1ALPo4ToXvwTwMj9OF0wh97S3p1sCeilwRcfyxBMJGpaDW8MSbiAbPqaSkK/DT3AFxlkHwAXWYYZ4+ZueQ==", + "dependencies": { + "@tkey/common-types": "^15.1.0", + "@tkey/service-provider-base": "^15.1.0", + "@toruslabs/customauth": "^20.3.0", + "@toruslabs/torus.js": "^15.1.0", + "bn.js": "^5.2.1", + "elliptic": "^6.5.5" + }, + "engines": { + "node": ">=18.x", + "npm": ">=9.x" + }, + "peerDependencies": { + "@babel/runtime": "7.x" + } + }, "node_modules/@tkey/share-serialization": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/@tkey/share-serialization/-/share-serialization-15.1.0.tgz", @@ -3750,8 +3761,22 @@ } }, "node_modules/@tkey/tss": { - "resolved": "../tkey/packages/tss", - "link": true + "version": "15.2.0-alpha.0", + "resolved": "https://registry.npmjs.org/@tkey/tss/-/tss-15.2.0-alpha.0.tgz", + "integrity": "sha512-Cc+Pl4XaqVr+l3/wVIv2nRmKWrAhqh8frm11dz8iqZgS6tks81mjJyZ0XHAoxiuMp+Rdkj3DqkDqjgAVUtU5Yw==", + "dependencies": { + "@tkey/common-types": "^15.1.0", + "@tkey/core": "^15.2.0-alpha.0", + "@tkey/service-provider-torus": "^15.1.0", + "@toruslabs/customauth": "^20.3.0", + "@toruslabs/http-helpers": "^7.0.0", + "@toruslabs/rss-client": "^2.0.1", + "@toruslabs/torus.js": "^15.1.0", + "@types/bn.js": "^5.1.5", + "bn.js": "^5.2.1", + "elliptic": "^6.5.5", + "ethereum-cryptography": "^2.1.3" + } }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", @@ -4274,6 +4299,25 @@ "@babel/runtime": "7.x" } }, + "node_modules/@toruslabs/rss-client": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@toruslabs/rss-client/-/rss-client-2.0.2.tgz", + "integrity": "sha512-kmtl7KaxTOdrftxtv/hTzz6k2RJbVFq6oPs5Vg4u4iybdQYdRzyP1HqQ3wMu9ADhiItbZAg80VP4gOCCmjao4w==", + "dependencies": { + "@toruslabs/eccrypto": "^5.0.4", + "@toruslabs/http-helpers": "^7.0.0", + "bn.js": "^5.2.1", + "elliptic": "^6.5.7", + "loglevel": "^1.9.2" + }, + "engines": { + "node": ">=18.x", + "npm": ">=9.x" + }, + "peerDependencies": { + "@babel/runtime": "7.x" + } + }, "node_modules/@toruslabs/session-manager": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@toruslabs/session-manager/-/session-manager-3.1.0.tgz", @@ -4501,7 +4545,6 @@ "version": "5.1.5", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -4634,7 +4677,6 @@ "version": "20.16.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.3.tgz", "integrity": "sha512-/wdGiWRkMOm53gAsSyFMXFZHbVg7C6CbkrzHNpaHoYfsUWPg7m6ZRKtvQjgvQ9i8WT540a3ydRlRQbxjY30XxQ==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.19.2" @@ -15568,7 +15610,6 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { diff --git a/package.json b/package.json index b6243ea3..2381e97b 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "@tkey/core": "^15.2.0-alpha.0", "@tkey/share-serialization": "^15.1.0", "@tkey/storage-layer-torus": "^15.1.0", - "@tkey/tss": "file:../tkey/packages/tss", + "@tkey/tss": "^15.2.0-alpha.0", "@toruslabs/constants": "^14.0.0", "@toruslabs/customauth": "^20.3.0", "@toruslabs/elliptic-wrapper": "^0.1.0", From c05cdb18a6cafd59b94570e2e46dc6144a698f4b Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 5 Nov 2024 18:10:59 +0800 Subject: [PATCH 06/16] fix: update interface for remote signing fix rehydration --- src/interfaces.ts | 4 ++-- src/mpcCoreKit.ts | 21 ++++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/interfaces.ts b/src/interfaces.ts index 85ce1147..a4024bba 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,4 +1,4 @@ -import { KeyType, Point as TkeyPoint, ShareDescriptionMap } from "@tkey/common-types"; +import { BNString, KeyType, Point as TkeyPoint, ShareDescriptionMap } from "@tkey/common-types"; import { IRemoteClientState, TKeyTSS } from "@tkey/tss"; import type { AGGREGATE_VERIFIER_TYPE, @@ -91,7 +91,7 @@ export interface EnableMFAParams { /** * A BN used for encrypting your Device/ Recovery TSS Key Share. You can generate it using `generateFactorKey()` function or use an existing one. */ - factorKey?: BN; + factorKey?: BNString; /** * Setting the Description of Share - Security Questions, Device Share, Seed Phrase, Password Share, Social Share, Other. Default is Other. */ diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index 9242c65b..e4ebc716 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -473,19 +473,20 @@ export class Web3AuthMPCCoreKit implements ICoreKit { } } - public async inputFactorKey(factorKey: BN): Promise { + public async inputFactorKey(factorKey: BNString): Promise { + const factorKeyBN = new BN(factorKey, "hex"); 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); + const factorKeyMetadata = await this.getFactorKeyMetadata(factorKeyBN); await this.tKey.inputShareStoreSafe(factorKeyMetadata, true); } // Finalize initialization. await this.tKey.reconstructKey(); - await this.finalizeTkey(factorKey); + await this.finalizeTkey(factorKeyBN); } catch (err: unknown) { log.error("login error", err); if (err instanceof CoreError) { @@ -590,6 +591,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { const { shareType } = createFactorParams; let { factorKey, shareDescription, additionalMetadata } = createFactorParams; + factorKey = factorKey ? new BN(factorKey, "hex") : undefined; if (!VALID_SHARE_INDICES.includes(shareType)) { throw CoreKitError.newShareIndexInvalid(`Invalid share type provided (${shareType}). Valid share types are ${VALID_SHARE_INDICES}.`); @@ -1142,7 +1144,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { this.checkReady(); const factorKey = new BN(result.factorKey, "hex"); - if (!factorKey) { + if (!factorKey && !result.remoteClientState?.remoteClientToken) { throw CoreKitError.providedFactorKeyInvalid(); } const postBoxKey = result.postBoxKey || result.oAuthKey; @@ -1152,18 +1154,23 @@ export class Web3AuthMPCCoreKit implements ICoreKit { 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); + + const metadataShareStore = result.remoteClientState?.metadataShare + ? ShareStore.fromJSON(JSON.parse(result.remoteClientState?.metadataShare)) + : await this.getFactorKeyMetadata(factorKey); + await this.tKey.initialize({ neverInitializeNewKey: true }); - await this.tKey.inputShareStoreSafe(factorKeyMetadata, true); + await this.tKey.inputShareStoreSafe(metadataShareStore, true); await this.tKey.reconstructKey(); this.updateState({ - factorKey: new BN(result.factorKey, "hex"), + factorKey: factorKey ? new BN(result.factorKey, "hex") : undefined, postBoxKey, tssShareIndex: result.tssShareIndex, tssPubKey: this.tkey.getTSSPub().toSEC1(this.tKey.tssCurve, false), signatures: result.signatures, userInfo: result.userInfo, + remoteClient: result.remoteClientState, }); } catch (err) { log.warn("failed to authorize session", err); From 75fb85952a0f727d6e858c57cc96e3cf35767f3a Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 6 Nov 2024 11:27:06 +0800 Subject: [PATCH 07/16] fix: merge issue --- src/mpcCoreKit.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index e4ebc716..239a443f 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -903,10 +903,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { if (!this.state.remoteClient.remoteClientUrl) throw new Error("remoteClientUrl not present"); // PreSetup - const { torusNodeTSSEndpoints } = await this.nodeDetailManager.getNodeDetails({ - verifier: "test-verifier", - verifierId: "test@example.com", - }); + const { torusNodeTSSEndpoints } = fetchLocalConfig(this.options.web3AuthNetwork, this.keyType); const tssCommits = this.tKey.getTSSCommits(); From b24d59975b61eb4981e423e2d0f8f4b728962dff Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 6 Nov 2024 13:04:41 +0800 Subject: [PATCH 08/16] fix: rebase issue --- src/mpcCoreKit.ts | 5 ++++- tests/factors.spec.ts | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index 239a443f..c8f36587 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -967,7 +967,10 @@ export class Web3AuthMPCCoreKit implements ICoreKit { } return r; } finally { - this.tkey.manualSync = this.options.manualSync; + this.atomicCallStackCounter -= 1; + if (this.atomicCallStackCounter === 0) { + this.tkey.manualSync = this.options.manualSync; + } } } diff --git a/tests/factors.spec.ts b/tests/factors.spec.ts index 969a2453..e3a75069 100644 --- a/tests/factors.spec.ts +++ b/tests/factors.spec.ts @@ -7,14 +7,14 @@ 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, TssLib, TssShareType, WEB3AUTH_NETWORK, Web3AuthMPCCoreKit } from "../src"; +import { COREKIT_STATUS, IAsyncStorage, IStorage, MemoryStorage, TssLibType, TssShareType, WEB3AUTH_NETWORK, Web3AuthMPCCoreKit } from "../src"; import { AsyncMemoryStorage, bufferToElliptic, criticalResetAccount, mockLogin } from "./setup"; type FactorTestVariable = { manualSync?: boolean; storage?: IAsyncStorage | IStorage; email: string; - tssLib?: TssLib; + tssLib?: TssLibType; }; function getPubKeys(kit: Web3AuthMPCCoreKit, indices: number[]): EllipticPoint[] { From 57472c351cff539c373a2ee0746ad62efdee1c59 Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 7 Nov 2024 16:34:42 +0800 Subject: [PATCH 09/16] fix: add support for frost signing --- src/interfaces.ts | 41 +++++++++++++++++- src/mpcCoreKit.ts | 104 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 138 insertions(+), 7 deletions(-) diff --git a/src/interfaces.ts b/src/interfaces.ts index a4024bba..12c52855 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,4 +1,4 @@ -import { BNString, KeyType, Point as TkeyPoint, ShareDescriptionMap } from "@tkey/common-types"; +import { BNString, FactorEnc, KeyType, Point as TkeyPoint, ShareDescriptionMap } from "@tkey/common-types"; import { IRemoteClientState, TKeyTSS } from "@tkey/tss"; import type { AGGREGATE_VERIFIER_TYPE, @@ -9,6 +9,7 @@ import type { TorusVerifierResponse, UX_MODE_TYPE, } from "@toruslabs/customauth"; +import { PointHex } from "@toruslabs/tss-client"; // TODO: move the types to a base class for both dkls and frost in future import type { tssLib as TssDklsLib } from "@toruslabs/tss-dkls-lib"; import type { tssLib as TssFrostLib } from "@toruslabs/tss-frost-lib"; @@ -472,3 +473,41 @@ export interface EthereumSigner { sign: (msgHash: Buffer) => Promise; getPublic: () => Promise; } + +type SupportedCurve = "secp256k1" | "ed25519"; +// remote signer interface +export type RemoteDklsSignParams = { + factorEnc?: FactorEnc; + sessionId: string; + tssNonce: number; + accountNonce: string; + tssPubKeyHex: string; + + nodeIndexes: number[]; + tssCommits: PointHex[]; + + signatures: string[]; + + serverEndpoints: { + endpoints: string[]; + tssWSEndpoints: string[]; + partyIndexes: number[]; + }; + + curve: SupportedCurve; +}; + +export type RemoteFrostSignParams = { + sessionId: string; + signatures: string[]; + tssCommits: PointHex[]; + factorEnc: FactorEnc; + serverXCoords: number[]; + clientXCoord: number; + serverCoefficients: string[]; + clientCoefficient: string; + tssPubKeyHex: string; + serverURLs: string[]; + + curve: SupportedCurve; +}; diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index c8f36587..c3c87c9d 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -2,7 +2,16 @@ import { BNString, KeyType, Point, secp256k1, SHARE_DELETED, ShareStore, Stringi import { CoreError } from "@tkey/core"; import { ShareSerializationModule } from "@tkey/share-serialization"; import { TorusStorageLayer } from "@tkey/storage-layer-torus"; -import { DELIMITERS, factorKeyCurve, getPubKeyPoint, IRemoteClientState, lagrangeInterpolation, TKeyTSS, TSSTorusServiceProvider } from "@tkey/tss"; +import { + DELIMITERS, + factorKeyCurve, + getPubKeyPoint, + IRemoteClientState, + lagrangeInterpolation, + pointToHex, + TKeyTSS, + TSSTorusServiceProvider, +} from "@tkey/tss"; import { KEY_TYPE, SIGNER_MAP } from "@toruslabs/constants"; import { AGGREGATE_VERIFIER, TORUS_METHOD, TorusAggregateLoginResponse, TorusLoginResponse, UX_MODE } from "@toruslabs/customauth"; import type { UX_MODE_TYPE } from "@toruslabs/customauth/dist/types/utils/enums"; @@ -44,6 +53,8 @@ import { JWTLoginParams, MPCKeyDetails, OAuthLoginParams, + RemoteDklsSignParams, + RemoteFrostSignParams, SessionData, SubVerifierDetailsParams, TkeyLocalStoreData, @@ -591,7 +602,9 @@ export class Web3AuthMPCCoreKit implements ICoreKit { const { shareType } = createFactorParams; let { factorKey, shareDescription, additionalMetadata } = createFactorParams; - factorKey = factorKey ? new BN(factorKey, "hex") : undefined; + if (typeof factorKey === "string") { + factorKey = new BN(factorKey, "hex"); + } if (!VALID_SHARE_INDICES.includes(shareType)) { throw CoreKitError.newShareIndexInvalid(`Invalid share type provided (${shareType}). Valid share types are ${VALID_SHARE_INDICES}.`); @@ -670,6 +683,9 @@ export class Web3AuthMPCCoreKit implements ICoreKit { const sig = await this.sign_ECDSA_secp256k1(data, hashed); return Buffer.concat([sig.r, sig.s, Buffer.from([sig.v])]); } else if (this.keyType === KeyType.ed25519) { + if (this.state.remoteClient) { + return this.remoteSignFrostEd25519(data, hashed); + } return this.sign_ed25519(data, hashed); } throw CoreKitError.default(`sign not supported for key type ${this.keyType}`); @@ -925,23 +941,27 @@ export class Web3AuthMPCCoreKit implements ICoreKit { } const { endpoints, tssWSEndpoints, partyIndexes } = generateTSSEndpoints(torusNodeTSSEndpoints, parties, clientIndex, nodeIndexes); + const accountNonce = this.tkey.computeAccountNonce(this.state.accountIndex); const factor = Point.fromSEC1(secp256k1, this.state.remoteClient.remoteFactorPub); const factorEnc = this.tKey.getFactorEncs(factor); - const data = { - dataRequired: { + const data: { remoteSignParams: RemoteDklsSignParams; msgHash: string } = { + remoteSignParams: { factorEnc, sessionId, tssNonce, + accountNonce: accountNonce.toString("hex"), nodeIndexes: nodeIndexes.slice(0, parties - 1), - tssCommits: tssCommits.map((commit) => commit.toJSON()), + tssCommits: tssCommits.map((commit) => pointToHex(commit)), signatures: this.signatures, serverEndpoints: { endpoints, tssWSEndpoints, partyIndexes }, + tssPubKeyHex: this.state.tssPubKey.toString("hex"), + curve: this.tkey.tssKeyType, }, msgHash: msgData.toString("hex"), }; - const result = await post<{ data?: Record }>(`${this.state.remoteClient.remoteClientUrl}/api/v3/mpc/sign`, data, { + const result = await post<{ data?: Record }>(`${this.state.remoteClient.remoteClientUrl}/api/v3/mpc/sign/dkls`, data, { headers: { Authorization: `Bearer ${this.state.remoteClient.remoteClientToken}`, }, @@ -950,6 +970,76 @@ export class Web3AuthMPCCoreKit implements ICoreKit { return { v: parseInt(v), r: Buffer.from(r, "hex"), s: Buffer.from(s, "hex") }; } + public async remoteSignFrostEd25519(data: Buffer, hashed: boolean = false): Promise { + if (hashed) { + throw CoreKitError.default("hashed data not supported for ed25519"); + } + + const nodeDetails = fetchLocalConfig(this.options.web3AuthNetwork, "ed25519"); + if (!nodeDetails.torusNodeTSSEndpoints) { + throw CoreKitError.default("could not fetch tss node endpoints"); + } + + // Endpoints must end with backslash, but URLs returned by + // `fetch-node-details` don't have it. + const ED25519_ENDPOINTS = nodeDetails.torusNodeTSSEndpoints.map((ep, i) => ({ index: nodeDetails.torusIndexes[i], url: `${ep}/` })); + + // Select endpoints and derive party indices. + const serverThreshold = Math.floor(ED25519_ENDPOINTS.length / 2) + 1; + const endpoints = sampleEndpoints(ED25519_ENDPOINTS, serverThreshold); + const serverXCoords = endpoints.map((x) => x.index); + const clientXCoord = Math.max(...endpoints.map((ep) => ep.index)) + 1; + + // Derive share coefficients for flat hierarchy. + const ec = new Ed25519Curve(); + const { serverCoefficients, clientCoefficient } = deriveShareCoefficients(ec, serverXCoords, clientXCoord, this.state.tssShareIndex); + + // Get pub key. + const tssPubKey = await this.getPubKey(); + const tssPubKeyPoint = ec.keyFromPublic(tssPubKey).getPublic(); + + // Get client key share and adjust by coefficient. + if (this.state.accountIndex !== 0) { + throw CoreKitError.default("Account index not supported for ed25519"); + } + + // Generate session identifier. + const tssNonce = this.getTssNonce(); + const sessionNonce = generateSessionNonce(); + const session = getSessionId(this.verifier, this.verifierId, this.tKey.tssTag, tssNonce, sessionNonce); + + // Run signing protocol. + + const serverURLs = endpoints.map((x) => x.url); + const tssPubKeyHex = ec.pointToBuffer(tssPubKeyPoint, Buffer).toString("hex"); + + const factorPub = Point.fromSEC1(secp256k1, this.state.remoteClient.remoteFactorPub); + const params: { remoteSignParams: RemoteFrostSignParams; msgHash: string } = { + remoteSignParams: { + sessionId: session, + signatures: this.signatures, + tssCommits: this.tKey.getTSSCommits().map((commit) => pointToHex(commit)), + factorEnc: this.tKey.getFactorEncs(factorPub), + serverXCoords, + clientXCoord, + serverCoefficients: serverCoefficients.map((sc) => sc.toString("hex")), + clientCoefficient: clientCoefficient.toString("hex"), + tssPubKeyHex, + serverURLs, + curve: this.tkey.tssKeyType, + }, + msgHash: data.toString("hex"), + }; + + const result = await post<{ data?: Record }>(`${this.state.remoteClient.remoteClientUrl}/api/v3/mpc/sign/frost`, params, { + headers: { + Authorization: `Bearer ${this.state.remoteClient.remoteClientToken}`, + }, + }); + + return Buffer.from(result.data.signature, "hex"); + } + public updateState(newState: Partial): void { this.state = { ...this.state, ...newState }; } @@ -966,6 +1056,8 @@ export class Web3AuthMPCCoreKit implements ICoreKit { } } return r; + } catch (e) { + throw e as Error; } finally { this.atomicCallStackCounter -= 1; if (this.atomicCallStackCounter === 0) { From 94a059c210b04172fa22313f4ff0e7a7cd4bfd9f Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 8 Nov 2024 15:45:44 +0800 Subject: [PATCH 10/16] fix: commitChanges Checking --- src/mpcCoreKit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index c3c87c9d..571ce7fb 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -774,7 +774,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { public async commitChanges(): Promise { this.checkReady(); - if (!this.state.factorKey) { + if (!this.state.factorKey && !this.state.remoteClient.remoteClientToken) { throw CoreKitError.factorKeyNotPresent("factorKey not present in state when committing changes."); } From f9af31e9a28f65f8bd57a0df76e1fdc4c51e29b0 Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 20 Nov 2024 11:26:21 +0800 Subject: [PATCH 11/16] feat: update with alpha package --- package-lock.json | 90 ++++++++++++++++++++++++++++++++++------------- package.json | 6 ++-- 2 files changed, 68 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index eb43d9cf..4effc706 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,11 @@ "version": "3.2.5", "license": "ISC", "dependencies": { - "@tkey/common-types": "^15.1.0", - "@tkey/core": "^15.2.0-alpha.0", + "@tkey/common-types": "^15.2.1-alpha.0", + "@tkey/core": "^15.2.1-alpha.0", "@tkey/share-serialization": "^15.1.0", "@tkey/storage-layer-torus": "^15.1.0", - "@tkey/tss": "^15.2.0-alpha.0", + "@tkey/tss": "^15.2.1-alpha.0", "@toruslabs/constants": "^14.0.0", "@toruslabs/customauth": "^20.3.0", "@toruslabs/elliptic-wrapper": "^0.1.0", @@ -3643,9 +3643,9 @@ } }, "node_modules/@tkey/common-types": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@tkey/common-types/-/common-types-15.1.0.tgz", - "integrity": "sha512-oA5gLoyhNNMgCKcjvwLyU31TVS5KMT+lotRrjjoBdDvS0keZwzSLrtHWbXj8jZDlSZaqbd3VlPbCoHcqpk1irA==", + "version": "15.2.1-alpha.0", + "resolved": "https://registry.npmjs.org/@tkey/common-types/-/common-types-15.2.1-alpha.0.tgz", + "integrity": "sha512-0BXtkB2PHNtV+fCmTVhWRqz/0tc975/z2onqCDQyCn0Lk36uhAPbEwCPRJ4vrccICxVizuYagZ+CazaEjk/ICA==", "dependencies": { "@toruslabs/customauth": "^20.3.0", "@toruslabs/eccrypto": "^5.0.4", @@ -3663,11 +3663,11 @@ } }, "node_modules/@tkey/core": { - "version": "15.2.0-alpha.0", - "resolved": "https://registry.npmjs.org/@tkey/core/-/core-15.2.0-alpha.0.tgz", - "integrity": "sha512-uBrh0RpGBME+8ZK4y1IKQYFy9j0K6pW+14Zrp1pSAgSJRNMrTypvROReBAxgHQkMWA4AdvBH9xXxaYxjc7octA==", + "version": "15.2.1-alpha.0", + "resolved": "https://registry.npmjs.org/@tkey/core/-/core-15.2.1-alpha.0.tgz", + "integrity": "sha512-WHpYdypVrdyv/A5BvHqA/OE2KVBrL1K2dVam39jhjjVlZIwLRiDu/hg2JMym62VVe5I5Jm6UzvTsIAi8YsUZEA==", "dependencies": { - "@tkey/common-types": "^15.1.0", + "@tkey/common-types": "^15.2.1-alpha.0", "@toruslabs/eccrypto": "^5.0.4", "@toruslabs/http-helpers": "^7.0.0", "@toruslabs/torus.js": "^15.1.0", @@ -3685,11 +3685,11 @@ } }, "node_modules/@tkey/service-provider-base": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@tkey/service-provider-base/-/service-provider-base-15.1.0.tgz", - "integrity": "sha512-MruUxiWwyRczZ8KlhhGJ2TQ/p+VFPMOQZ089B5SIi7UsTOBMlzRqJWP3lM2fBSyQsfJCzpzXkj9a29ecpRZe0g==", + "version": "15.2.1-alpha.0", + "resolved": "https://registry.npmjs.org/@tkey/service-provider-base/-/service-provider-base-15.2.1-alpha.0.tgz", + "integrity": "sha512-8CTHuAoglCfDhxk3U0VkWJTl6XcL5UXIQAjtRTo/fbFClsHZ1lfQREXe24m+185XwBsZySrgE+UpL/Qvrq9vow==", "dependencies": { - "@tkey/common-types": "^15.1.0", + "@tkey/common-types": "^15.2.1-alpha.0", "bn.js": "^5.2.1", "elliptic": "^6.5.5" }, @@ -3702,12 +3702,12 @@ } }, "node_modules/@tkey/service-provider-torus": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@tkey/service-provider-torus/-/service-provider-torus-15.1.0.tgz", - "integrity": "sha512-7tA/1ALPo4ToXvwTwMj9OF0wh97S3p1sCeilwRcfyxBMJGpaDW8MSbiAbPqaSkK/DT3AFxlkHwAXWYYZ4+ZueQ==", + "version": "15.2.1-alpha.0", + "resolved": "https://registry.npmjs.org/@tkey/service-provider-torus/-/service-provider-torus-15.2.1-alpha.0.tgz", + "integrity": "sha512-ZkVaZvf0S+wrjN7nVAwq87+X02XJSCal2aCEYoRtoYQAcqp0wgTMPzBB6r4Jo1VyWIYbXS9zl6fZ90h1I86YVQ==", "dependencies": { - "@tkey/common-types": "^15.1.0", - "@tkey/service-provider-base": "^15.1.0", + "@tkey/common-types": "^15.2.1-alpha.0", + "@tkey/service-provider-base": "^15.2.1-alpha.0", "@toruslabs/customauth": "^20.3.0", "@toruslabs/torus.js": "^15.1.0", "bn.js": "^5.2.1", @@ -3739,6 +3739,26 @@ "@babel/runtime": "7.x" } }, + "node_modules/@tkey/share-serialization/node_modules/@tkey/common-types": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@tkey/common-types/-/common-types-15.1.0.tgz", + "integrity": "sha512-oA5gLoyhNNMgCKcjvwLyU31TVS5KMT+lotRrjjoBdDvS0keZwzSLrtHWbXj8jZDlSZaqbd3VlPbCoHcqpk1irA==", + "dependencies": { + "@toruslabs/customauth": "^20.3.0", + "@toruslabs/eccrypto": "^5.0.4", + "@toruslabs/torus.js": "^15.1.0", + "bn.js": "^5.2.1", + "elliptic": "^6.5.5", + "ts-custom-error": "^3.3.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", @@ -3760,14 +3780,34 @@ "@babel/runtime": "7.x" } }, + "node_modules/@tkey/storage-layer-torus/node_modules/@tkey/common-types": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@tkey/common-types/-/common-types-15.1.0.tgz", + "integrity": "sha512-oA5gLoyhNNMgCKcjvwLyU31TVS5KMT+lotRrjjoBdDvS0keZwzSLrtHWbXj8jZDlSZaqbd3VlPbCoHcqpk1irA==", + "dependencies": { + "@toruslabs/customauth": "^20.3.0", + "@toruslabs/eccrypto": "^5.0.4", + "@toruslabs/torus.js": "^15.1.0", + "bn.js": "^5.2.1", + "elliptic": "^6.5.5", + "ts-custom-error": "^3.3.1" + }, + "engines": { + "node": ">=18.x", + "npm": ">=9.x" + }, + "peerDependencies": { + "@babel/runtime": "7.x" + } + }, "node_modules/@tkey/tss": { - "version": "15.2.0-alpha.0", - "resolved": "https://registry.npmjs.org/@tkey/tss/-/tss-15.2.0-alpha.0.tgz", - "integrity": "sha512-Cc+Pl4XaqVr+l3/wVIv2nRmKWrAhqh8frm11dz8iqZgS6tks81mjJyZ0XHAoxiuMp+Rdkj3DqkDqjgAVUtU5Yw==", + "version": "15.2.1-alpha.0", + "resolved": "https://registry.npmjs.org/@tkey/tss/-/tss-15.2.1-alpha.0.tgz", + "integrity": "sha512-DffLXqyZauva1D3fnV6kL/dta47ZyIuPV6EFS0xmkDzGWRd2frow9oZhZJ91XjOfslR/CIdpJ510ISsjcnUaAw==", "dependencies": { - "@tkey/common-types": "^15.1.0", - "@tkey/core": "^15.2.0-alpha.0", - "@tkey/service-provider-torus": "^15.1.0", + "@tkey/common-types": "^15.2.1-alpha.0", + "@tkey/core": "^15.2.1-alpha.0", + "@tkey/service-provider-torus": "^15.2.1-alpha.0", "@toruslabs/customauth": "^20.3.0", "@toruslabs/http-helpers": "^7.0.0", "@toruslabs/rss-client": "^2.0.1", diff --git a/package.json b/package.json index 2381e97b..f9bc4072 100644 --- a/package.json +++ b/package.json @@ -43,11 +43,11 @@ } }, "dependencies": { - "@tkey/common-types": "^15.1.0", - "@tkey/core": "^15.2.0-alpha.0", + "@tkey/common-types": "^15.2.1-alpha.0", + "@tkey/core": "^15.2.1-alpha.0", "@tkey/share-serialization": "^15.1.0", "@tkey/storage-layer-torus": "^15.1.0", - "@tkey/tss": "^15.2.0-alpha.0", + "@tkey/tss": "^15.2.1-alpha.0", "@toruslabs/constants": "^14.0.0", "@toruslabs/customauth": "^20.3.0", "@toruslabs/elliptic-wrapper": "^0.1.0", From ebd70fc2b907f3cc41db8f3cac701f33aa664255 Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 20 Nov 2024 12:27:12 +0800 Subject: [PATCH 12/16] fix: lints --- src/interfaces.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/interfaces.ts b/src/interfaces.ts index 3131cef7..8ae49f95 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -9,8 +9,7 @@ import type { TorusVerifierResponse, UX_MODE_TYPE, } from "@toruslabs/customauth"; -import { PointHex } from "@toruslabs/tss-client"; -import { Client } from "@toruslabs/tss-client"; +import { Client, PointHex } from "@toruslabs/tss-client"; // TODO: move the types to a base class for both dkls and frost in future import type { tssLib as TssDklsLib } from "@toruslabs/tss-dkls-lib"; import type { tssLib as TssFrostLib } from "@toruslabs/tss-frost-lib"; From 4fd31d155372b45dfa3ea6cfdc20d515e59da9f0 Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 26 Nov 2024 18:24:51 +0800 Subject: [PATCH 13/16] fix: add required function --- src/mpcCoreKit.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index 08594ab8..6c1b71c0 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -63,6 +63,7 @@ import { UserInfo, V3TSSLibType, V4TSSLibType, + WEB3AUTH_NETWORK_TYPE, Web3AuthOptions, Web3AuthOptionsWithDefaults, Web3AuthState, @@ -1133,6 +1134,18 @@ export class Web3AuthMPCCoreKit implements ICoreKit { this.state = { ...this.state, ...newState }; } + public getWeb3AuthNetwork(): WEB3AUTH_NETWORK_TYPE { + return this.options.web3AuthNetwork; + } + + public getMetadataKey(): string { + return this.tkey.secp256k1Key.toString("hex"); + } + + public getMetadataPublicKey(): string { + return this.tkey.getKeyDetails().pubKey.toSEC1(secp256k1, true).toString("hex"); + } + protected async atomicSync(f: () => Promise): Promise { this.atomicCallStackCounter += 1; From 65875e2a964512a1581677bb55d71792e7329c81 Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 13 Dec 2024 17:02:27 +0800 Subject: [PATCH 14/16] feat: update plugin setCustomSign --- src/interfaces.ts | 47 +++--- src/mpcCoreKit.ts | 270 ++++++++++++++--------------------- tests/importRecovery.spec.ts | 4 +- 3 files changed, 133 insertions(+), 188 deletions(-) diff --git a/src/interfaces.ts b/src/interfaces.ts index 8ae49f95..4dbba976 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -475,44 +475,41 @@ export interface EthereumSigner { } type SupportedCurve = "secp256k1" | "ed25519"; -// remote signer interface -export type RemoteDklsSignParams = { - factorEnc?: FactorEnc; - sessionId: string; - tssNonce: number; - accountNonce: string; - tssPubKeyHex: string; - - nodeIndexes: number[]; - tssCommits: PointHex[]; - - signatures: string[]; - serverEndpoints: { - endpoints: string[]; - tssWSEndpoints: string[]; - partyIndexes: number[]; - }; - - curve: SupportedCurve; -}; - -export type RemoteFrostSignParams = { +export type ICustomFrostSignParams = { sessionId: string; signatures: string[]; tssCommits: PointHex[]; factorEnc: FactorEnc; + tssPubKeyHex: string; + curve: SupportedCurve; + serverXCoords: number[]; clientXCoord: number; serverCoefficients: string[]; clientCoefficient: string; - tssPubKeyHex: string; serverURLs: string[]; - - curve: SupportedCurve; }; export interface Secp256k1PrecomputedClient { client: Client; serverCoeffs: Record; } + +export interface ICustomDklsSignParams { + sessionId: string; + signatures: string[]; + tssCommits: PointHex[]; + factorEnc: FactorEnc; + tssPubKeyHex: string; + curve: SupportedCurve; + + participatingServerDKGIndexes: number[]; + clientIndex: number; + tssNonce: string; + accountNonce: string; + + endpoints: string[]; + tssWSEndpoints: string[]; + partyIndexes: number[]; +} diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index 6c1b71c0..b286addd 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -17,7 +17,6 @@ import { AGGREGATE_VERIFIER, TORUS_METHOD, TorusAggregateLoginResponse, TorusLog import type { UX_MODE_TYPE } from "@toruslabs/customauth/dist/types/utils/enums"; import { Ed25519Curve } from "@toruslabs/elliptic-wrapper"; import { fetchLocalConfig } from "@toruslabs/fnd-base"; -import { post } from "@toruslabs/http-helpers"; import { keccak256 } from "@toruslabs/metadata-helpers"; import { SessionManager } from "@toruslabs/session-manager"; import { Torus as TorusUtils, TorusKey } from "@toruslabs/torus.js"; @@ -48,13 +47,13 @@ import { CreateFactorParams, EnableMFAParams, ICoreKit, + ICustomDklsSignParams, + ICustomFrostSignParams, IFactorKey, InitParams, JWTLoginParams, MPCKeyDetails, OAuthLoginParams, - RemoteDklsSignParams, - RemoteFrostSignParams, Secp256k1PrecomputedClient, SessionData, SubVerifierDetailsParams, @@ -112,6 +111,10 @@ export class Web3AuthMPCCoreKit implements ICoreKit { private atomicCallStackCounter: number = 0; + private customDklsSign: (params: ICustomDklsSignParams, msgHash: Uint8Array) => Promise<{ v: number; r: Uint8Array; s: Uint8Array }>; + + private customFrostSign: (params: ICustomFrostSignParams, data: Uint8Array) => Promise; + constructor(options: Web3AuthOptions) { if (!options.web3AuthClientId) { throw CoreKitError.clientIdInvalid(); @@ -210,6 +213,16 @@ export class Web3AuthMPCCoreKit implements ICoreKit { return this.keyType === KeyType.ed25519 && this.options.useClientGeneratedTSSKey === undefined ? true : !!this.options.useClientGeneratedTSSKey; } + public setCustomDKLSSign(customDKLSSign: { + sign: (params: ICustomDklsSignParams, msgHash: Uint8Array) => Promise<{ v: number; r: Uint8Array; s: Uint8Array }>; + }) { + this.customDklsSign = customDKLSSign.sign; + } + + public setCustomFrostSign(customFrostSign: { sign: (params: ICustomFrostSignParams, data: Uint8Array) => Promise }) { + this.customFrostSign = customFrostSign.sign; + } + // 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 @@ -675,17 +688,72 @@ export class Web3AuthMPCCoreKit implements ICoreKit { return ed25519().keyFromPublic(p).getPublic(); } + public async preSetupSigning(): Promise { + const { torusNodeTSSEndpoints } = fetchLocalConfig(this.options.web3AuthNetwork, this.keyType); + + const tssCommits = this.tKey.getTSSCommits(); + + if (!tssCommits[0] || !torusNodeTSSEndpoints) { + throw CoreKitError.tssPublicKeyOrEndpointsMissing(); + } + + const tssNonce = this.getTssNonce() || 0; + + const vid = `${this.verifier}${DELIMITERS.Delimiter1}${this.verifierId}`; + const sessionId = `${vid}${DELIMITERS.Delimiter2}default${DELIMITERS.Delimiter3}${tssNonce}${DELIMITERS.Delimiter4}`; + + const parties = 4; + const clientIndex = parties - 1; + + const { nodeIndexes } = await (this.tKey.serviceProvider as TSSTorusServiceProvider).getTSSPubKey( + this.tKey.tssTag, + this.tKey.metadata.tssNonces[this.tKey.tssTag] + ); + + if (parties - 1 > nodeIndexes.length) { + throw new Error(`Not enough nodes to perform TSS - parties :${parties}, nodeIndexes:${nodeIndexes.length}`); + } + const { + endpoints, + tssWSEndpoints, + partyIndexes, + nodeIndexesReturned: participatingServerDKGIndexes, + } = generateTSSEndpoints(torusNodeTSSEndpoints, parties, clientIndex, nodeIndexes); + + const factor = Point.fromSEC1(secp256k1, this.state.remoteClient.remoteFactorPub); + const factorEnc = this.tKey.getFactorEncs(factor); + + // Compute account nonce only supported for secp256k1 + const accountNonce = this.tkey.computeAccountNonce(this.state.accountIndex); + + return { + endpoints, + tssWSEndpoints, + partyIndexes, + factorEnc, + sessionId, + tssCommits: tssCommits.map((commit) => commit.toPointHex()), + participatingServerDKGIndexes, + clientIndex, + tssNonce: tssNonce.toString(), + accountNonce: accountNonce.toString(), + + signatures: this.state.signatures, + tssPubKeyHex: this.getPubKey().toString("hex"), + curve: this.keyType, + }; + } + public async precompute_secp256k1(): Promise<{ client: Client; serverCoeffs: Record; }> { this.wasmLib = await this.loadTssWasm(); // PreSetup + const { endpoints, tssWSEndpoints, partyIndexes, participatingServerDKGIndexes, clientIndex } = await this.preSetupSigning(); 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."); } @@ -694,32 +762,18 @@ export class Web3AuthMPCCoreKit implements ICoreKit { }); 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); + // Compute account nonce only supported for secp256k1 + const accountNonce = this.tkey.computeAccountNonce(this.state.accountIndex); + 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"); @@ -766,16 +820,9 @@ export class Web3AuthMPCCoreKit implements ICoreKit { public async sign(data: Buffer, hashed: boolean = false, secp256k1Precompute?: Secp256k1PrecomputedClient): Promise { this.wasmLib = await this.loadTssWasm(); if (this.keyType === KeyType.secp256k1) { - if (this.state.remoteClient) { - const sig = await this.remoteSignSecp256k1(data, hashed); - return Buffer.concat([sig.r, sig.s, Buffer.from([sig.v])]); - } const sig = await this.sign_ECDSA_secp256k1(data, hashed, secp256k1Precompute); return Buffer.concat([sig.r, sig.s, Buffer.from([sig.v])]); } else if (this.keyType === KeyType.ed25519) { - if (this.state.remoteClient) { - return this.remoteSignFrostEd25519(data, hashed); - } return this.sign_ed25519(data, hashed); } throw CoreKitError.default(`sign not supported for key type ${this.keyType}`); @@ -1001,135 +1048,6 @@ export class Web3AuthMPCCoreKit implements ICoreKit { await this.createSessionRemoteClient(); } - public async remoteSignSecp256k1(msgData: Buffer, hashed: boolean = false): Promise<{ v: number; r: Buffer; s: Buffer }> { - if (!hashed) { - msgData = keccak256(msgData); - } - - if (!this.state.remoteClient.remoteClientUrl) throw new Error("remoteClientUrl not present"); - - // PreSetup - const { torusNodeTSSEndpoints } = fetchLocalConfig(this.options.web3AuthNetwork, this.keyType); - - const tssCommits = this.tKey.getTSSCommits(); - - const tssNonce = this.getTssNonce() || 0; - - const vid = `${this.verifier}${DELIMITERS.Delimiter1}${this.verifierId}`; - const sessionId = `${vid}${DELIMITERS.Delimiter2}default${DELIMITERS.Delimiter3}${tssNonce}${DELIMITERS.Delimiter4}`; - - const parties = 4; - const clientIndex = parties - 1; - - const { nodeIndexes } = await (this.tKey.serviceProvider as TSSTorusServiceProvider).getTSSPubKey( - this.tKey.tssTag, - this.tKey.metadata.tssNonces[this.tKey.tssTag] - ); - - if (parties - 1 > nodeIndexes.length) { - throw new Error(`Not enough nodes to perform TSS - parties :${parties}, nodeIndexes:${nodeIndexes.length}`); - } - const { endpoints, tssWSEndpoints, partyIndexes } = generateTSSEndpoints(torusNodeTSSEndpoints, parties, clientIndex, nodeIndexes); - - const accountNonce = this.tkey.computeAccountNonce(this.state.accountIndex); - const factor = Point.fromSEC1(secp256k1, this.state.remoteClient.remoteFactorPub); - const factorEnc = this.tKey.getFactorEncs(factor); - - const data: { remoteSignParams: RemoteDklsSignParams; msgHash: string } = { - remoteSignParams: { - factorEnc, - sessionId, - tssNonce, - accountNonce: accountNonce.toString("hex"), - nodeIndexes: nodeIndexes.slice(0, parties - 1), - tssCommits: tssCommits.map((commit) => pointToHex(commit)), - signatures: this.signatures, - serverEndpoints: { endpoints, tssWSEndpoints, partyIndexes }, - tssPubKeyHex: this.state.tssPubKey.toString("hex"), - curve: this.tkey.tssKeyType, - }, - msgHash: msgData.toString("hex"), - }; - - const result = await post<{ data?: Record }>(`${this.state.remoteClient.remoteClientUrl}/api/v3/mpc/sign/dkls`, data, { - headers: { - Authorization: `Bearer ${this.state.remoteClient.remoteClientToken}`, - }, - }); - const { r, s, v } = result.data as { v: string; r: string; s: string }; - return { v: parseInt(v), r: Buffer.from(r, "hex"), s: Buffer.from(s, "hex") }; - } - - public async remoteSignFrostEd25519(data: Buffer, hashed: boolean = false): Promise { - if (hashed) { - throw CoreKitError.default("hashed data not supported for ed25519"); - } - - const nodeDetails = fetchLocalConfig(this.options.web3AuthNetwork, "ed25519"); - if (!nodeDetails.torusNodeTSSEndpoints) { - throw CoreKitError.default("could not fetch tss node endpoints"); - } - - // Endpoints must end with backslash, but URLs returned by - // `fetch-node-details` don't have it. - const ED25519_ENDPOINTS = nodeDetails.torusNodeTSSEndpoints.map((ep, i) => ({ index: nodeDetails.torusIndexes[i], url: `${ep}/` })); - - // Select endpoints and derive party indices. - const serverThreshold = Math.floor(ED25519_ENDPOINTS.length / 2) + 1; - const endpoints = sampleEndpoints(ED25519_ENDPOINTS, serverThreshold); - const serverXCoords = endpoints.map((x) => x.index); - const clientXCoord = Math.max(...endpoints.map((ep) => ep.index)) + 1; - - // Derive share coefficients for flat hierarchy. - const ec = new Ed25519Curve(); - const { serverCoefficients, clientCoefficient } = deriveShareCoefficients(ec, serverXCoords, clientXCoord, this.state.tssShareIndex); - - // Get pub key. - const tssPubKey = await this.getPubKey(); - const tssPubKeyPoint = ec.keyFromPublic(tssPubKey).getPublic(); - - // Get client key share and adjust by coefficient. - if (this.state.accountIndex !== 0) { - throw CoreKitError.default("Account index not supported for ed25519"); - } - - // Generate session identifier. - const tssNonce = this.getTssNonce(); - const sessionNonce = generateSessionNonce(); - const session = getSessionId(this.verifier, this.verifierId, this.tKey.tssTag, tssNonce, sessionNonce); - - // Run signing protocol. - - const serverURLs = endpoints.map((x) => x.url); - const tssPubKeyHex = ec.pointToBuffer(tssPubKeyPoint, Buffer).toString("hex"); - - const factorPub = Point.fromSEC1(secp256k1, this.state.remoteClient.remoteFactorPub); - const params: { remoteSignParams: RemoteFrostSignParams; msgHash: string } = { - remoteSignParams: { - sessionId: session, - signatures: this.signatures, - tssCommits: this.tKey.getTSSCommits().map((commit) => pointToHex(commit)), - factorEnc: this.tKey.getFactorEncs(factorPub), - serverXCoords, - clientXCoord, - serverCoefficients: serverCoefficients.map((sc) => sc.toString("hex")), - clientCoefficient: clientCoefficient.toString("hex"), - tssPubKeyHex, - serverURLs, - curve: this.tkey.tssKeyType, - }, - msgHash: data.toString("hex"), - }; - - const result = await post<{ data?: Record }>(`${this.state.remoteClient.remoteClientUrl}/api/v3/mpc/sign/frost`, params, { - headers: { - Authorization: `Bearer ${this.state.remoteClient.remoteClientToken}`, - }, - }); - - return Buffer.from(result.data.signature, "hex"); - } - public updateState(newState: Partial): void { this.state = { ...this.state, ...newState }; } @@ -1594,6 +1512,14 @@ export class Web3AuthMPCCoreKit implements ICoreKit { data = keccak256(data); } + // Custom Dkls Sign + if (this.customDklsSign) { + // PreSetup + const setupSigningParams = await this.preSetupSigning(); + const result = await this.customDklsSign(setupSigningParams, data); + return result; + } + const isAlreadyPrecomputed = precomputedTssClient?.client && precomputedTssClient?.serverCoeffs; const { client, serverCoeffs } = isAlreadyPrecomputed ? precomputedTssClient : await this.precompute_secp256k1(); @@ -1648,9 +1574,6 @@ export class Web3AuthMPCCoreKit implements ICoreKit { if (this.state.accountIndex !== 0) { throw CoreKitError.default("Account index not supported for ed25519"); } - const { tssShare } = await this.tKey.getTSSShare(this.state.factorKey); - const clientShareAdjusted = tssShare.mul(clientCoefficient).umod(ec.n); - const clientShareAdjustedHex = ec.scalarToBuffer(clientShareAdjusted, Buffer).toString("hex"); // Generate session identifier. const tssNonce = this.getTssNonce(); @@ -1661,6 +1584,31 @@ export class Web3AuthMPCCoreKit implements ICoreKit { const serverURLs = endpoints.map((x) => x.url); const pubKeyHex = ec.pointToBuffer(tssPubKeyPoint, Buffer).toString("hex"); const serverCoefficientsHex = serverCoefficients.map((c) => ec.scalarToBuffer(c, Buffer).toString("hex")); + + if (this.customFrostSign) { + const factorPub = Point.fromSEC1(secp256k1, this.state.remoteClient.remoteFactorPub); + const params: ICustomFrostSignParams = { + sessionId: session, + signatures: this.signatures, + tssCommits: this.tKey.getTSSCommits().map((commit) => pointToHex(commit)), + factorEnc: this.tKey.getFactorEncs(factorPub), + serverXCoords, + clientXCoord, + serverCoefficients: serverCoefficients.map((sc) => sc.toString("hex")), + clientCoefficient: clientCoefficient.toString("hex"), + tssPubKeyHex: this.getPubKey().toString("hex"), + serverURLs, + curve: this.tkey.tssKeyType, + }; + const result = await this.customFrostSign(params, data); + return Buffer.from(result); + } + + // compute client share + const { tssShare } = await this.tKey.getTSSShare(this.state.factorKey); + const clientShareAdjusted = tssShare.mul(clientCoefficient).umod(ec.n); + const clientShareAdjustedHex = ec.scalarToBuffer(clientShareAdjusted, Buffer).toString("hex"); + const signature = await signEd25519( this.wasmLib as FrostWasmLib, session, diff --git a/tests/importRecovery.spec.ts b/tests/importRecovery.spec.ts index d7bb18b3..8d3bbc8e 100644 --- a/tests/importRecovery.spec.ts +++ b/tests/importRecovery.spec.ts @@ -4,14 +4,14 @@ import test from "node:test"; import { tssLib as tssLibDKLS } from "@toruslabs/tss-dkls-lib"; import { tssLib as tssLibFROST } from "@toruslabs/tss-frost-lib"; -import { AsyncStorage, MemoryStorage, TssLib, TssShareType, WEB3AUTH_NETWORK } from "../src"; +import { AsyncStorage, MemoryStorage, TssLibType, TssShareType, WEB3AUTH_NETWORK } from "../src"; import { bufferToElliptic, criticalResetAccount, newCoreKitLogInInstance } from "./setup"; type ImportKeyTestVariable = { manualSync?: boolean; email: string; importKeyEmail: string; - tssLib: TssLib; + tssLib: TssLibType; }; const storageInstance = new MemoryStorage(); From 4ac5e62ca5a6efef20d46f081700f144816fccb3 Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 13 Dec 2024 18:28:27 +0800 Subject: [PATCH 15/16] feat: remove remote copy and refresh functionality --- src/mpcCoreKit.ts | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index b286addd..1d9fdb9b 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -641,15 +641,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { } return this.atomicSync(async () => { - if (this.state.remoteClient) { - if (shareType === this.state.tssShareIndex) { - await this.tkey.remoteCopyFactorPub({ newFactorPub: factorPub, tssIndex: shareType, remoteClient: this.state.remoteClient }); - } else { - await this.tkey.remoteAddFactorPub({ newFactorPub: factorPub, newFactorTSSIndex: shareType, remoteClient: this.state.remoteClient }); - } - } else { - await this.copyOrCreateShare(shareType, factorPub); - } + await this.copyOrCreateShare(shareType, factorPub); await this.backupMetadataShare(factorKey); await this.addFactorDescription({ factorKey, shareDescription, additionalMetadata, updateMetadata: false }); @@ -848,11 +840,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { throw CoreKitError.factorInUseCannotBeDeleted("Cannot delete current active factor"); } - if (this.state.remoteClient) { - await this.tkey.remoteDeleteFactorPub({ factorPubToDelete: factorPub, remoteClient: this.state.remoteClient }); - } else { - await this.tKey.deleteFactorPub({ factorKey: this.state.factorKey, deleteFactorPub: factorPub, authSignatures: this.signatures }); - } + await this.tKey.deleteFactorPub({ factorKey: this.state.factorKey, deleteFactorPub: factorPub, authSignatures: this.signatures }); const factorPubHex = fpp.toSEC1(factorKeyCurve, true).toString("hex"); const allDesc = this.tKey.metadata.getShareDescription(); const keyDesc = allDesc[factorPubHex]; From 96b84a91577d77d2bfcb4979bd50b23944f7d92c Mon Sep 17 00:00:00 2001 From: ieow Date: Mon, 16 Dec 2024 15:28:26 +0800 Subject: [PATCH 16/16] feat: update to match plugin interface --- src/helper/browserStorage.ts | 5 ++++- src/mpcCoreKit.ts | 37 ++++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/helper/browserStorage.ts b/src/helper/browserStorage.ts index 6a5555de..88e48805 100644 --- a/src/helper/browserStorage.ts +++ b/src/helper/browserStorage.ts @@ -21,7 +21,7 @@ export class MemoryStorage implements IStorage { } } -export class AsyncStorage { +export class AsyncStore { public storage: IAsyncStorage | IStorage; private _storeKey: string; @@ -66,3 +66,6 @@ export class AsyncStorage { await this.storage.setItem(this._storeKey, JSON.stringify(store)); } } + +// Deprecated +export class AsyncStorage extends AsyncStore {} diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index 1d9fdb9b..84f30fd2 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -899,7 +899,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit { public async commitChanges(): Promise { this.checkReady(); - if (!this.state.factorKey && !this.state.remoteClient.remoteClientToken) { + if (!this.state.factorKey && !this.state.remoteClient.metadataShare) { throw CoreKitError.factorKeyNotPresent("factorKey not present in state when committing changes."); } @@ -1005,8 +1005,8 @@ export class Web3AuthMPCCoreKit implements ICoreKit { } } - async setupRemoteSigning(params: Omit): Promise { - const { remoteClientUrl, remoteFactorPub, metadataShare, remoteClientToken } = params; + async setupRemoteSigning(params: IRemoteClientState, rehydrate: boolean = false): Promise { + const { remoteFactorPub, metadataShare } = params; const details = this.getKeyDetails().shareDescriptions[remoteFactorPub]; if (!details) throw CoreKitError.default("factor description not found"); @@ -1016,12 +1016,9 @@ export class Web3AuthMPCCoreKit implements ICoreKit { if (!tssShareIndex) throw CoreKitError.default("tss share index not found"); const remoteClient: IRemoteClientState = { - remoteClientUrl: remoteClientUrl.at(-1) === "/" ? remoteClientUrl.slice(0, -1) : remoteClientUrl, remoteFactorPub, metadataShare, - remoteClientToken, tssShareIndex, - signatures: this.state.signatures, }; const sharestore = ShareStore.fromJSON(JSON.parse(metadataShare)); @@ -1032,8 +1029,10 @@ export class Web3AuthMPCCoreKit implements ICoreKit { // const tssPubKey = Point.fromTkeyPoint(this.tKey.getTSSPub()).toBufferSEC1(false); this.updateState({ tssShareIndex, tssPubKey, remoteClient }); // // Finalize setup. - // setup provider - await this.createSessionRemoteClient(); + // skip setup provider if rehydrate is true + if (!rehydrate) { + await this.createSessionRemoteClient(); + } } public updateState(newState: Partial): void { @@ -1243,23 +1242,29 @@ export class Web3AuthMPCCoreKit implements ICoreKit { try { this.checkReady(); - const factorKey = new BN(result.factorKey, "hex"); - if (!factorKey && !result.remoteClientState?.remoteClientToken) { - throw CoreKitError.providedFactorKeyInvalid(); - } const postBoxKey = result.postBoxKey || result.oAuthKey; if (!postBoxKey) { throw CoreKitError.default("postBoxKey or oAuthKey not present in session data"); } + + const factorKey = new BN(result.factorKey, "hex"); + if (!factorKey && !result.remoteClientState?.metadataShare) { + throw CoreKitError.providedFactorKeyInvalid(); + } + this.torusSp.postboxKey = new BN(postBoxKey, "hex"); this.torusSp.verifierName = result.userInfo.aggregateVerifier || result.userInfo.verifier; this.torusSp.verifierId = result.userInfo.verifierId; - const metadataShareStore = result.remoteClientState?.metadataShare - ? ShareStore.fromJSON(JSON.parse(result.remoteClientState?.metadataShare)) - : await this.getFactorKeyMetadata(factorKey); - await this.tKey.initialize({ neverInitializeNewKey: true }); + + // skip input share store if factor key is not present + // tkey will be at state initalized + if (!factorKey) { + return; + } + + const metadataShareStore = await this.getFactorKeyMetadata(factorKey); await this.tKey.inputShareStoreSafe(metadataShareStore, true); await this.tKey.reconstructKey();