From 5e26511b2a4b73af8f4ed524b0ba68db287e1987 Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 25 Oct 2024 16:30:01 +0800 Subject: [PATCH 01/22] feat: initial commit remoteSigner --- package-lock.json | 7 +- package.json | 2 + src/interfaces.ts | 2 + src/mpcCoreKit.ts | 266 +++++++++++++++++++++- src/remoteSignInterfaces.ts | 42 ++++ src/remoteSignerServices/authenticator.ts | 127 +++++++++++ src/remoteSignerServices/smsOtp.ts | 138 +++++++++++ 7 files changed, 577 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 e1b05fc4..51aadb65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,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": { @@ -4444,7 +4445,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" @@ -10081,6 +10081,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 da42e46c..301b9a34 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "@toruslabs/elliptic-wrapper": "^0.1.1", "@toruslabs/fetch-node-details": "^14.2.0", "@toruslabs/fnd-base": "^14.2.0", + "@toruslabs/http-helpers": "^7.0.0", "@toruslabs/metadata-helpers": "^6.0.0", "@toruslabs/openlogin-utils": "^8.2.1", "@toruslabs/torus.js": "15.2.0-alpha.0", @@ -68,6 +69,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 ccfb484d..c738cdf9 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -22,6 +22,7 @@ import { SafeEventEmitter } from "@web3auth/auth"; import BN from "bn.js"; import { FactorKeyTypeShareDescription, TssShareType, USER_PATH, WEB3AUTH_NETWORK } from "./constants"; +import { IRemoteClientState } from "./remoteSignInterfaces"; import { ISessionSigGenerator } from "./plugins/ISessionSigGenerator"; export type CoreKitMode = UX_MODE_TYPE | "nodejs" | "react-native"; @@ -186,6 +187,7 @@ export interface Web3AuthState { tssPubKey?: Buffer; accountIndex: number; factorKey?: BN; + remoteClient?: IRemoteClientState; } export type WEB3AUTH_NETWORK_TYPE = (typeof WEB3AUTH_NETWORK)[keyof typeof WEB3AUTH_NETWORK]; diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index 44b7fc1d..9911cb89 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -1,13 +1,25 @@ -import { BNString, KeyType, ONE_KEY_DELETE_NONCE, Point, secp256k1, SHARE_DELETED, ShareStore, StringifiedType } from "@tkey/common-types"; +import { + BNString, + EncryptedMessage, + FactorEnc, + KeyType, + ONE_KEY_DELETE_NONCE, + Point, + secp256k1, + SHARE_DELETED, + ShareStore, + StringifiedType, +} from "@tkey/common-types"; import { CoreError } from "@tkey/core"; import { ShareSerializationModule } from "@tkey/share-serialization"; import { TorusStorageLayer } from "@tkey/storage-layer-torus"; -import { factorKeyCurve, getPubKeyPoint, lagrangeInterpolation, TKeyTSS, TSSTorusServiceProvider } from "@tkey/tss"; +import { DELIMITERS, factorKeyCurve, getPubKeyPoint, lagrangeInterpolation, randomSelection, TKeyTSS, TSSTorusServiceProvider } from "@tkey/tss"; import { 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, Secp256k1Curve } 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"; @@ -60,6 +72,7 @@ import { } from "./interfaces"; import { DefaultSessionSigGeneratorPlugin } from "./plugins/DefaultSessionSigGenerator"; import { ISessionSigGenerator } from "./plugins/ISessionSigGenerator"; +import { IRemoteClientState, RefreshRemoteTssReturnType } from "./remoteSignInterfaces"; import { deriveShareCoefficients, ed25519, @@ -659,7 +672,15 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { } 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 }); @@ -827,6 +848,10 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { if (opts?.keyTweak) { throw CoreKitError.default("key tweaking not supported for ecdsa-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, opts?.hashed, opts?.secp256k1Precompute); return Buffer.concat([sig.r, sig.s, Buffer.from([sig.v])]); } else if (this._sigType === "ed25519" || this._sigType === "bip340") { @@ -843,7 +868,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { // 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) { @@ -861,8 +886,12 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { throw CoreKitError.factorInUseCannotBeDeleted("Cannot delete current active factor"); } - const authSignatures = await this.getSessionSignatures(); - await this.tKey.deleteFactorPub({ factorKey: this.state.factorKey, deleteFactorPub: factorPub, authSignatures }); + if (this.state.remoteClient && !this.state.factorKey) { + await this.remoteDeleteFactorPub(factorPub); + } else { + const authSignatures = await this.getSessionSignatures(); + await this.tKey.deleteFactorPub({ factorKey: this.state.factorKey, deleteFactorPub: factorPub, authSignatures }); + } const factorPubHex = fpp.toSEC1(factorKeyCurve, true).toString("hex"); const allDesc = this.tKey.metadata.getShareDescription(); const keyDesc = allDesc[factorPubHex]; @@ -1025,6 +1054,231 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { } } + 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 3e1587d87cbce34764efd817fcbfaeb2d9944d9f Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 29 Oct 2024 17:05:20 +0800 Subject: [PATCH 02/22] 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 3a4bd8d6..c59c14a8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,5 +3,6 @@ export * from "./helper"; export * from "./interfaces"; export * from "./mpcCoreKit"; export * from "./plugins"; +export * from "./remoteFactorServices"; export * from "./utils"; export { factorKeyCurve } from "@tkey/tss"; diff --git a/src/interfaces.ts b/src/interfaces.ts index c738cdf9..3294749f 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -22,7 +22,7 @@ import { SafeEventEmitter } from "@web3auth/auth"; import BN from "bn.js"; import { FactorKeyTypeShareDescription, TssShareType, USER_PATH, WEB3AUTH_NETWORK } from "./constants"; -import { IRemoteClientState } from "./remoteSignInterfaces"; +import { IRemoteClientState } from "./remoteFactorServices/remoteSignInterfaces"; import { ISessionSigGenerator } from "./plugins/ISessionSigGenerator"; export type CoreKitMode = UX_MODE_TYPE | "nodejs" | "react-native"; @@ -118,7 +118,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 { @@ -515,6 +515,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 9911cb89..ca1671f0 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -72,7 +72,7 @@ import { } from "./interfaces"; import { DefaultSessionSigGeneratorPlugin } from "./plugins/DefaultSessionSigGenerator"; import { ISessionSigGenerator } from "./plugins/ISessionSigGenerator"; -import { IRemoteClientState, RefreshRemoteTssReturnType } from "./remoteSignInterfaces"; +import { IRemoteClientState, RefreshRemoteTssReturnType } from "./remoteFactorServices/remoteSignInterfaces"; import { deriveShareCoefficients, ed25519, @@ -672,7 +672,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { } 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 { @@ -849,7 +849,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { throw CoreKitError.default("key tweaking not supported for ecdsa-secp256k1"); } if (this.state.remoteClient && !this.state.factorKey) { - const sig = await this.remoteSignSecp256k1(data, hashed); + const sig = await this.remoteSignSecp256k1(data, opts?.hashed); return Buffer.concat([sig.r, sig.s, Buffer.from([sig.v])]); } const sig = await this.sign_ECDSA_secp256k1(data, opts?.hashed, opts?.secp256k1Precompute); @@ -886,7 +886,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { throw CoreKitError.factorInUseCannotBeDeleted("Cannot delete current active factor"); } - if (this.state.remoteClient && !this.state.factorKey) { + if (this.state.remoteClient) { await this.remoteDeleteFactorPub(factorPub); } else { const authSignatures = await this.getSessionSignatures(); @@ -1054,8 +1054,15 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { } } - 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, @@ -1066,16 +1073,15 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { }; 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(); } /** @@ -1128,7 +1134,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { const result = ( await post<{ data: RefreshRemoteTssReturnType }>( - `${remoteClient.remoteClientUrl}/api/mpc/refresh_tss`, + `${remoteClient.remoteClientUrl}/api/v3/mpc/refresh_tss`, { dataRequired }, { headers: { @@ -1159,7 +1165,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { 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: { @@ -1270,7 +1276,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { 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}`, }, @@ -1354,10 +1360,32 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { } catch (error) { throw error as Error; } 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); } } @@ -1654,7 +1682,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { 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 95c1aba6bab5b017a196749da209e1f268551625 Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 1 Nov 2024 13:40:18 +0800 Subject: [PATCH 03/22] 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 c83f7921936307a1c29912e285a5a0523eb5f07c Mon Sep 17 00:00:00 2001 From: ieow Date: Mon, 4 Nov 2024 15:59:37 +0800 Subject: [PATCH 04/22] fix: refactor remote signer --- package-lock.json | 56 ++++--- package.json | 4 +- src/index.ts | 1 - src/interfaces.ts | 3 +- src/mpcCoreKit.ts | 155 +----------------- src/remoteFactorServices/authenticator.ts | 121 -------------- src/remoteFactorServices/index.ts | 23 --- .../remoteSignInterfaces.ts | 57 ------- src/remoteFactorServices/smsOtp.ts | 106 ------------ 9 files changed, 42 insertions(+), 484 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 51aadb65..227530c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "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", @@ -85,6 +85,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", @@ -3758,7 +3784,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", @@ -3800,10 +3825,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", @@ -3949,22 +3973,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/@tkey/tss/node_modules/@toruslabs/torus.js": { "version": "15.1.1", @@ -15769,7 +15779,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" } @@ -16088,6 +16097,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 301b9a34..99845be1 100644 --- a/package.json +++ b/package.json @@ -48,10 +48,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.2.0", "@toruslabs/customauth": "^20.3.0", "@toruslabs/elliptic-wrapper": "^0.1.1", diff --git a/src/index.ts b/src/index.ts index c59c14a8..3a4bd8d6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,5 @@ export * from "./helper"; export * from "./interfaces"; export * from "./mpcCoreKit"; export * from "./plugins"; -export * from "./remoteFactorServices"; export * from "./utils"; export { factorKeyCurve } from "@tkey/tss"; diff --git a/src/interfaces.ts b/src/interfaces.ts index 3294749f..0e183a0e 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, TSSTorusServiceProvider } from "@tkey/tss"; +import { IRemoteClientState, TKeyTSS, TSSTorusServiceProvider } from "@tkey/tss"; import { WEB3AUTH_SIG_TYPE } from "@toruslabs/constants"; import type { AGGREGATE_VERIFIER_TYPE, @@ -22,7 +22,6 @@ import { SafeEventEmitter } from "@web3auth/auth"; import BN from "bn.js"; import { FactorKeyTypeShareDescription, TssShareType, USER_PATH, WEB3AUTH_NETWORK } from "./constants"; -import { IRemoteClientState } from "./remoteFactorServices/remoteSignInterfaces"; import { ISessionSigGenerator } from "./plugins/ISessionSigGenerator"; export type CoreKitMode = UX_MODE_TYPE | "nodejs" | "react-native"; diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index ca1671f0..5bf1e9cb 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -13,7 +13,7 @@ import { 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 { 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"; @@ -72,7 +72,6 @@ import { } from "./interfaces"; import { DefaultSessionSigGeneratorPlugin } from "./plugins/DefaultSessionSigGenerator"; import { ISessionSigGenerator } from "./plugins/ISessionSigGenerator"; -import { IRemoteClientState, RefreshRemoteTssReturnType } from "./remoteFactorServices/remoteSignInterfaces"; import { deriveShareCoefficients, ed25519, @@ -674,9 +673,9 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { 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); @@ -887,7 +886,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { } if (this.state.remoteClient) { - await this.remoteDeleteFactorPub(factorPub); + await this.tkey.remoteDeleteFactorPub({ factorPubToDelete: factorPub, remoteClient: this.state.remoteClient }); } else { const authSignatures = await this.getSessionSignatures(); await this.tKey.deleteFactorPub({ factorKey: this.state.factorKey, deleteFactorPub: factorPub, authSignatures }); @@ -1054,7 +1053,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { } } - 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"); @@ -1070,6 +1069,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { metadataShare, remoteClientToken, tssShareIndex, + signatures: this.state.signatures, }; const sharestore = ShareStore.fromJSON(JSON.parse(metadataShare)); @@ -1084,149 +1084,6 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { 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 fcf01966b3872c236511c546749f5ca3fa3ffb21 Mon Sep 17 00:00:00 2001 From: ieow Date: Mon, 4 Nov 2024 18:13:02 +0800 Subject: [PATCH 05/22] update: use alpha tss --- package-lock.json | 45 ++++++++++++++++----------------------------- package.json | 2 +- 2 files changed, 17 insertions(+), 30 deletions(-) diff --git a/package-lock.json b/package-lock.json index 227530c0..db045c47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -85,32 +85,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", @@ -3973,8 +3947,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/@tkey/tss/node_modules/@toruslabs/torus.js": { "version": "15.1.1", @@ -16097,7 +16085,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 99845be1..61cf9499 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,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.2.0", "@toruslabs/customauth": "^20.3.0", "@toruslabs/elliptic-wrapper": "^0.1.1", From d7f1d009210f4a25da80335295addc290fa8653d Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 5 Nov 2024 18:10:59 +0800 Subject: [PATCH 06/22] 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 0e183a0e..a7c2032c 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, TSSTorusServiceProvider } from "@tkey/tss"; import { WEB3AUTH_SIG_TYPE } from "@toruslabs/constants"; import type { @@ -109,7 +109,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 5bf1e9cb..9375d555 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -533,19 +533,20 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { } } - 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) { @@ -650,6 +651,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { 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}.`); @@ -1370,7 +1372,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { 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; @@ -1380,19 +1382,24 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { 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, postboxKeyNodeIndexes: result.postboxKeyNodeIndexes || [], 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 47c550fb3d7769826c4a845f6ec88652b42786a2 Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 6 Nov 2024 11:27:06 +0800 Subject: [PATCH 07/22] 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 9375d555..efecb1ba 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -1094,10 +1094,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { 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 5a5dfcc6f54e9d97581e2e4b8c069f51e0bf9073 Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 6 Nov 2024 13:04:41 +0800 Subject: [PATCH 08/22] fix: rebase issue --- src/mpcCoreKit.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index efecb1ba..df2e273a 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -1216,7 +1216,10 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { } catch (error) { throw error as Error; } finally { - this.tkey.manualSync = this.options.manualSync; + this.atomicCallStackCounter -= 1; + if (this.atomicCallStackCounter === 0) { + this.tkey.manualSync = this.options.manualSync; + } } } From 18cb2c00c192aac77bca6859f5890cd6894ec2ec Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 7 Nov 2024 16:34:42 +0800 Subject: [PATCH 09/22] fix: add support for frost signing --- src/interfaces.ts | 41 ++++++++++++++++++- src/mpcCoreKit.ts | 102 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 136 insertions(+), 7 deletions(-) diff --git a/src/interfaces.ts b/src/interfaces.ts index a7c2032c..52933c0e 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, TSSTorusServiceProvider } from "@tkey/tss"; import { WEB3AUTH_SIG_TYPE } from "@toruslabs/constants"; import type { @@ -14,6 +14,7 @@ import type { } from "@toruslabs/customauth"; import { TorusKey } from "@toruslabs/torus.js"; import { Client } from "@toruslabs/tss-client"; +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 TssFrostLibEd25519 } from "@toruslabs/tss-frost-lib"; @@ -544,3 +545,41 @@ export interface EthereumSigner { export type StateEmitterEvents = { LOGOUT: () => void; }; + +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 df2e273a..4d1a6f55 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -13,7 +13,16 @@ import { 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 { 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"; @@ -58,6 +67,8 @@ import { JWTLoginParams, MPCKeyDetails, OAuthLoginParams, + RemoteDklsSignParams, + RemoteFrostSignParams, Secp256k1PrecomputedClient, SessionData, SigType, @@ -651,7 +662,9 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { 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}.`); @@ -862,6 +875,9 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { throw CoreKitError.default("key tweaking not supported for ed25519"); } + if (this.state.remoteClient) { + return this.remoteSignFrostEd25519(data, hashed); + } return this.sign_frost(data, opts?.keyTweak); } throw CoreKitError.default(`sign not supported for key type ${this.keyType}`); @@ -1116,23 +1132,27 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { } 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}`, }, @@ -1141,6 +1161,76 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { 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 }; } From ff8db72a08edc902ffe2752c02c171980caa582a Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 8 Nov 2024 15:45:44 +0800 Subject: [PATCH 10/22] 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 4d1a6f55..2b103c13 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -968,7 +968,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { 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 85b0412b769db40578b188464a465b56689cf925 Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 20 Nov 2024 11:26:21 +0800 Subject: [PATCH 11/22] feat: update with alpha package --- package-lock.json | 76 ++++++++++++++++++++++++++++++++++++----------- package.json | 6 ++-- 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index db045c47..25fd0305 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,8 @@ "version": "4.1.6-alpha.0", "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.1.0", @@ -3755,9 +3755,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", @@ -3799,11 +3799,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", @@ -3850,7 +3850,7 @@ "integrity": "sha512-MruUxiWwyRczZ8KlhhGJ2TQ/p+VFPMOQZ089B5SIi7UsTOBMlzRqJWP3lM2fBSyQsfJCzpzXkj9a29ecpRZe0g==", "license": "MIT", "dependencies": { - "@tkey/common-types": "^15.1.0", + "@tkey/common-types": "^15.2.1-alpha.0", "bn.js": "^5.2.1", "elliptic": "^6.5.5" }, @@ -3868,8 +3868,8 @@ "integrity": "sha512-7tA/1ALPo4ToXvwTwMj9OF0wh97S3p1sCeilwRcfyxBMJGpaDW8MSbiAbPqaSkK/DT3AFxlkHwAXWYYZ4+ZueQ==", "license": "MIT", "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", @@ -3925,6 +3925,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", @@ -3946,14 +3966,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 61cf9499..5b039500 100644 --- a/package.json +++ b/package.json @@ -47,11 +47,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.2.0", "@toruslabs/customauth": "^20.3.0", "@toruslabs/elliptic-wrapper": "^0.1.1", From 00e925ee51f54dffd0aab33251b2ed0894356fb4 Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 26 Nov 2024 18:24:51 +0800 Subject: [PATCH 12/22] fix: add required function --- src/mpcCoreKit.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index 2b103c13..dacb5b73 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -77,6 +77,7 @@ import { TkeyLocalStoreData, TssLibType, UserInfo, + WEB3AUTH_NETWORK_TYPE, Web3AuthOptions, Web3AuthOptionsWithDefaults, Web3AuthState, @@ -1291,6 +1292,18 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { } } + 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 ea7c2684eaeaaf296ddb8ced82b4b36c5b101bd2 Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 13 Dec 2024 17:02:27 +0800 Subject: [PATCH 13/22] feat: update plugin setCustomSign --- src/interfaces.ts | 22 +++- src/mpcCoreKit.ts | 272 ++++++++++++++++++---------------------------- 2 files changed, 125 insertions(+), 169 deletions(-) diff --git a/src/interfaces.ts b/src/interfaces.ts index 52933c0e..37cf7f85 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -574,12 +574,30 @@ export type RemoteFrostSignParams = { signatures: string[]; tssCommits: PointHex[]; factorEnc: FactorEnc; + tssPubKeyHex: string; + curve: SupportedCurve; + serverXCoords: number[]; clientXCoord: number; serverCoefficients: string[]; clientCoefficient: string; - tssPubKeyHex: string; serverURLs: string[]; +}; +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 dacb5b73..340dc43d 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -28,7 +28,6 @@ import { AGGREGATE_VERIFIER, TORUS_METHOD, TorusAggregateLoginResponse, TorusLog import type { UX_MODE_TYPE } from "@toruslabs/customauth/dist/types/utils/enums"; import { Ed25519Curve, Secp256k1Curve } 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"; @@ -61,14 +60,14 @@ import { CreateFactorParams, EnableMFAParams, ICoreKit, + ICustomDklsSignParams, + ICustomFrostSignParams, IFactorKey, IMPCContext, InitParams, JWTLoginParams, MPCKeyDetails, OAuthLoginParams, - RemoteDklsSignParams, - RemoteFrostSignParams, Secp256k1PrecomputedClient, SessionData, SigType, @@ -134,6 +133,10 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { private sessionSigGenerator: ISessionSigGenerator; + 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(); @@ -245,6 +248,16 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { return this.sessionSigGenerator.getSessionSigs(); } + 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 @@ -738,18 +751,60 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { return ed25519().keyFromPublic(p).getPublic(); } - /** - * Get public key in bip340 format. - * - * Throws an error if signature type is not bip340. - */ - public getPubKeyBip340(): Buffer { - if (this._sigType !== "bip340") { - throw CoreKitError.default(`getPubKeyBip340 not supported for signature type ${this.sigType}`); + 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 p = this.tkey.tssCurve.keyFromPublic(this.getPubKey()).getPublic(); - return p.getX().toBuffer("be", 32); + 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(params?: { sessionSignatures?: string[] }): Promise<{ @@ -760,11 +815,10 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { const { sessionSignatures } = params || {}; 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."); } @@ -773,32 +827,18 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { }); 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"); @@ -876,9 +916,6 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { throw CoreKitError.default("key tweaking not supported for ed25519"); } - if (this.state.remoteClient) { - return this.remoteSignFrostEd25519(data, hashed); - } return this.sign_frost(data, opts?.keyTweak); } throw CoreKitError.default(`sign not supported for key type ${this.keyType}`); @@ -1103,135 +1140,6 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { 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 }; } @@ -1737,6 +1645,14 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { 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, signatures } = isAlreadyPrecomputed ? precomputedTssClient : await this.precompute_secp256k1(); @@ -1793,9 +1709,6 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { if (this._sigType === "ed25519" && 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(); @@ -1807,6 +1720,31 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { const pubKeyHex = ec.pointToBuffer(tssPubKeyPoint, Buffer).toString("hex"); const serverCoefficientsHex = serverCoefficients.map((c) => ec.scalarToBuffer(c, Buffer).toString("hex")); const authSignatures = await this.getSessionSignatures(); + + 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 signFrost( this.wasmLib as FrostWasmLibEd25519 | FrostWasmLibBip340, session, From 4640b669598871005f1f25f0aa0f682ab9ed4d8e Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 13 Dec 2024 18:28:27 +0800 Subject: [PATCH 14/22] feat: remove remote copy and refresh functionality --- src/mpcCoreKit.ts | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index 340dc43d..8d4cca85 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -700,15 +700,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { } 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 }); @@ -941,12 +933,8 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { throw CoreKitError.factorInUseCannotBeDeleted("Cannot delete current active factor"); } - if (this.state.remoteClient) { - await this.tkey.remoteDeleteFactorPub({ factorPubToDelete: factorPub, remoteClient: this.state.remoteClient }); - } else { - const authSignatures = await this.getSessionSignatures(); - await this.tKey.deleteFactorPub({ factorKey: this.state.factorKey, deleteFactorPub: factorPub, authSignatures }); - } + const authSignatures = await this.getSessionSignatures(); + await this.tKey.deleteFactorPub({ factorKey: this.state.factorKey, deleteFactorPub: factorPub, authSignatures }); const factorPubHex = fpp.toSEC1(factorKeyCurve, true).toString("hex"); const allDesc = this.tKey.metadata.getShareDescription(); const keyDesc = allDesc[factorPubHex]; From b19e86f9cf1f3c2582594b01afce8a00c2d7aa16 Mon Sep 17 00:00:00 2001 From: ieow Date: Mon, 16 Dec 2024 15:28:26 +0800 Subject: [PATCH 15/22] 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 8d4cca85..326edc36 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -994,7 +994,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { 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."); } @@ -1097,8 +1097,8 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { } } - 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"); @@ -1108,12 +1108,9 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { 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)); @@ -1124,8 +1121,10 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { // 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 { @@ -1370,23 +1369,29 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { 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(); From 09c07af4df7b3326a1abec05afa8338a26fb698c Mon Sep 17 00:00:00 2001 From: ieow Date: Mon, 16 Dec 2024 20:43:12 +0800 Subject: [PATCH 16/22] fix: merge issue --- package-lock.json | 64 +++++++++++++++++++++++++++++++++++++++-------- package.json | 2 +- src/interfaces.ts | 5 ++-- src/mpcCoreKit.ts | 21 ++++------------ 4 files changed, 62 insertions(+), 30 deletions(-) diff --git a/package-lock.json b/package-lock.json index 25fd0305..5e67e9ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,12 +13,13 @@ "@tkey/core": "^15.2.1-alpha.0", "@tkey/share-serialization": "^15.1.0", "@tkey/storage-layer-torus": "^15.1.0", - "@tkey/tss": "^15.1.0", + "@tkey/tss": "^15.2.1-alpha.0", "@toruslabs/constants": "^14.2.0", "@toruslabs/customauth": "^20.3.0", "@toruslabs/elliptic-wrapper": "^0.1.1", "@toruslabs/fetch-node-details": "^14.2.0", "@toruslabs/fnd-base": "^14.2.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", @@ -3845,10 +3846,9 @@ } }, "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", + "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.2.1-alpha.0", "bn.js": "^5.2.1", @@ -3863,10 +3863,9 @@ } }, "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", + "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.2.1-alpha.0", "@tkey/service-provider-base": "^15.2.1-alpha.0", @@ -3945,6 +3944,29 @@ "@babel/runtime": "7.x" } }, + "node_modules/@tkey/share-serialization/node_modules/@toruslabs/torus.js": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@toruslabs/torus.js/-/torus.js-15.1.1.tgz", + "integrity": "sha512-sLaXA1/R8KTTjU4t+teL3PPaJr2+j01QLYn5IY/t5uTD+1G2nzzfVWpkMDYrk9EfQYw0u4aKJ1lT7j9uKafMlg==", + "dependencies": { + "@toruslabs/bs58": "^1.0.0", + "@toruslabs/constants": "^14.0.0", + "@toruslabs/eccrypto": "^5.0.4", + "@toruslabs/http-helpers": "^7.0.0", + "bn.js": "^5.2.1", + "elliptic": "^6.5.7", + "ethereum-cryptography": "^2.2.1", + "json-stable-stringify": "^1.1.1", + "loglevel": "^1.9.2" + }, + "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", @@ -3986,6 +4008,29 @@ "@babel/runtime": "7.x" } }, + "node_modules/@tkey/storage-layer-torus/node_modules/@toruslabs/torus.js": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@toruslabs/torus.js/-/torus.js-15.1.1.tgz", + "integrity": "sha512-sLaXA1/R8KTTjU4t+teL3PPaJr2+j01QLYn5IY/t5uTD+1G2nzzfVWpkMDYrk9EfQYw0u4aKJ1lT7j9uKafMlg==", + "dependencies": { + "@toruslabs/bs58": "^1.0.0", + "@toruslabs/constants": "^14.0.0", + "@toruslabs/eccrypto": "^5.0.4", + "@toruslabs/http-helpers": "^7.0.0", + "bn.js": "^5.2.1", + "elliptic": "^6.5.7", + "ethereum-cryptography": "^2.2.1", + "json-stable-stringify": "^1.1.1", + "loglevel": "^1.9.2" + }, + "engines": { + "node": ">=18.x", + "npm": ">=9.x" + }, + "peerDependencies": { + "@babel/runtime": "7.x" + } + }, "node_modules/@tkey/tss": { "version": "15.2.1-alpha.0", "resolved": "https://registry.npmjs.org/@tkey/tss/-/tss-15.2.1-alpha.0.tgz", @@ -5285,7 +5330,6 @@ "version": "9.6.1", "resolved": "https://registry.npmjs.org/@web3auth/auth/-/auth-9.6.1.tgz", "integrity": "sha512-6iYmjhDtZcXGph3pPTVLShMv/xN+eLnc/+g04rqM34pfb85CK2OsvnufCxWcNpEuS/n5yo5voALPE4s/s2BXWw==", - "license": "MIT", "dependencies": { "@ethereumjs/util": "^9.1.0", "@toruslabs/constants": "^14.2.0", diff --git a/package.json b/package.json index 5b039500..36f62756 100644 --- a/package.json +++ b/package.json @@ -60,8 +60,8 @@ "@toruslabs/http-helpers": "^7.0.0", "@toruslabs/metadata-helpers": "^6.0.0", "@toruslabs/openlogin-utils": "^8.2.1", - "@toruslabs/torus.js": "15.2.0-alpha.0", "@toruslabs/session-manager": "^3.1.0", + "@toruslabs/torus.js": "15.2.0-alpha.0", "@toruslabs/tss-client": "^3.3.0-alpha.0", "@toruslabs/tss-frost-client": "^1.0.1-alpha.0", "@toruslabs/tss-frost-common": "^1.0.2-alpha.0", diff --git a/src/interfaces.ts b/src/interfaces.ts index 37cf7f85..1a696863 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -13,8 +13,7 @@ import type { UX_MODE_TYPE, } from "@toruslabs/customauth"; import { TorusKey } from "@toruslabs/torus.js"; -import { Client } from "@toruslabs/tss-client"; -import { PointHex } 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 TssFrostLibEd25519 } from "@toruslabs/tss-frost-lib"; @@ -569,7 +568,7 @@ export type RemoteDklsSignParams = { curve: SupportedCurve; }; -export type RemoteFrostSignParams = { +export type ICustomFrostSignParams = { sessionId: string; signatures: string[]; tssCommits: PointHex[]; diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index 326edc36..7e463536 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -1,15 +1,4 @@ -import { - BNString, - EncryptedMessage, - FactorEnc, - KeyType, - ONE_KEY_DELETE_NONCE, - Point, - secp256k1, - SHARE_DELETED, - ShareStore, - StringifiedType, -} from "@tkey/common-types"; +import { BNString, KeyType, ONE_KEY_DELETE_NONCE, Point, secp256k1, SHARE_DELETED, ShareStore, StringifiedType } from "@tkey/common-types"; import { CoreError } from "@tkey/core"; import { ShareSerializationModule } from "@tkey/share-serialization"; import { TorusStorageLayer } from "@tkey/storage-layer-torus"; @@ -775,7 +764,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { nodeIndexesReturned: participatingServerDKGIndexes, } = generateTSSEndpoints(torusNodeTSSEndpoints, parties, clientIndex, nodeIndexes); - const factor = Point.fromSEC1(secp256k1, this.state.remoteClient.remoteFactorPub); + const factor = Point.fromSEC1(secp256k1, this.state.remoteClient?.remoteFactorPub); const factorEnc = this.tKey.getFactorEncs(factor); // Compute account nonce only supported for secp256k1 @@ -1234,7 +1223,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { factorKey: "", tssShareIndex: tssShareIndex as number, tssPubKey: Buffer.from(tssPubKey).toString("hex"), - signatures: this.signatures, + signatures: await this.getSessionSignatures(), userInfo, remoteClientState: this.state.remoteClient, }; @@ -1387,7 +1376,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { // skip input share store if factor key is not present // tkey will be at state initalized - if (!factorKey) { + if (!result.factorKey) { return; } @@ -1718,7 +1707,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { const factorPub = Point.fromSEC1(secp256k1, this.state.remoteClient.remoteFactorPub); const params: ICustomFrostSignParams = { sessionId: session, - signatures: this.signatures, + signatures: await this.getSessionSignatures(), tssCommits: this.tKey.getTSSCommits().map((commit) => pointToHex(commit)), factorEnc: this.tKey.getFactorEncs(factorPub), serverXCoords, From d42a4d58b37c5cbad99b20aae399713bdb8f8263 Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 17 Dec 2024 14:25:28 +0800 Subject: [PATCH 17/22] feat: update interface --- src/interfaces.ts | 63 +++--------------------------------- src/mpcCoreKit.ts | 47 +++++++++++++++++++-------- src/plugins/IRemoteSigner.ts | 62 +++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 72 deletions(-) create mode 100644 src/plugins/IRemoteSigner.ts diff --git a/src/interfaces.ts b/src/interfaces.ts index 1a696863..e7ad60e1 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,4 +1,4 @@ -import { BNString, FactorEnc, KeyType, Point as TkeyPoint, ShareDescriptionMap } from "@tkey/common-types"; +import { BNString, KeyType, Point as TkeyPoint, ShareDescriptionMap } from "@tkey/common-types"; import { IRemoteClientState, TKeyTSS, TSSTorusServiceProvider } from "@tkey/tss"; import { WEB3AUTH_SIG_TYPE } from "@toruslabs/constants"; import type { @@ -13,7 +13,7 @@ import type { UX_MODE_TYPE, } from "@toruslabs/customauth"; import { TorusKey } from "@toruslabs/torus.js"; -import { Client, PointHex } from "@toruslabs/tss-client"; +import { Client } 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 TssFrostLibEd25519 } from "@toruslabs/tss-frost-lib"; @@ -22,6 +22,7 @@ import { SafeEventEmitter } from "@web3auth/auth"; import BN from "bn.js"; import { FactorKeyTypeShareDescription, TssShareType, USER_PATH, WEB3AUTH_NETWORK } from "./constants"; +import { IRemoteSignerContext } from "./plugins/IRemoteSigner"; import { ISessionSigGenerator } from "./plugins/ISessionSigGenerator"; export type CoreKitMode = UX_MODE_TYPE | "nodejs" | "react-native"; @@ -337,7 +338,7 @@ export interface Web3AuthOptions { } export type Web3AuthOptionsWithDefaults = Required; -export interface IMPCContext { +export interface IMPCContext extends IRemoteSignerContext { stateEmitter: SafeEventEmitter; config: Web3AuthOptionsWithDefaults; status: COREKIT_STATUS; @@ -544,59 +545,3 @@ export interface EthereumSigner { export type StateEmitterEvents = { LOGOUT: () => void; }; - -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 ICustomFrostSignParams = { - sessionId: string; - signatures: string[]; - tssCommits: PointHex[]; - factorEnc: FactorEnc; - tssPubKeyHex: string; - curve: SupportedCurve; - - serverXCoords: number[]; - clientXCoord: number; - serverCoefficients: string[]; - clientCoefficient: string; - serverURLs: string[]; -}; - -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 7e463536..f043b3b8 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -49,8 +49,6 @@ import { CreateFactorParams, EnableMFAParams, ICoreKit, - ICustomDklsSignParams, - ICustomFrostSignParams, IFactorKey, IMPCContext, InitParams, @@ -71,6 +69,7 @@ import { Web3AuthState, } from "./interfaces"; import { DefaultSessionSigGeneratorPlugin } from "./plugins/DefaultSessionSigGenerator"; +import { ICustomDklsSignParams, ICustomFrostSignParams } from "./plugins/IRemoteSigner"; import { ISessionSigGenerator } from "./plugins/ISessionSigGenerator"; import { deriveShareCoefficients, @@ -732,6 +731,20 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { return ed25519().keyFromPublic(p).getPublic(); } + /** + * Get public key in bip340 format. + * + * Throws an error if signature type is not bip340. + */ + public getPubKeyBip340(): Buffer { + if (this._sigType !== "bip340") { + throw CoreKitError.default(`getPubKeyBip340 not supported for signature type ${this.sigType}`); + } + + const p = this.tkey.tssCurve.keyFromPublic(this.getPubKey()).getPublic(); + return p.getX().toBuffer("be", 32); + } + public async preSetupSigning(): Promise { const { torusNodeTSSEndpoints } = fetchLocalConfig(this.options.web3AuthNetwork, this.keyType); @@ -742,7 +755,6 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { } const tssNonce = this.getTssNonce() || 0; - const vid = `${this.verifier}${DELIMITERS.Delimiter1}${this.verifierId}`; const sessionId = `${vid}${DELIMITERS.Delimiter2}default${DELIMITERS.Delimiter3}${tssNonce}${DELIMITERS.Delimiter4}`; @@ -764,7 +776,9 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { nodeIndexesReturned: participatingServerDKGIndexes, } = generateTSSEndpoints(torusNodeTSSEndpoints, parties, clientIndex, nodeIndexes); - const factor = Point.fromSEC1(secp256k1, this.state.remoteClient?.remoteFactorPub); + const factor = this.state.remoteClient?.remoteFactorPub + ? Point.fromSEC1(secp256k1, this.state.remoteClient?.remoteFactorPub) + : Point.fromScalar(this.state.factorKey, secp256k1); const factorEnc = this.tKey.getFactorEncs(factor); // Compute account nonce only supported for secp256k1 @@ -1088,6 +1102,18 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { async setupRemoteSigning(params: IRemoteClientState, rehydrate: boolean = false): Promise { const { remoteFactorPub, metadataShare } = params; + + // rehydrate session + if (rehydrate) { + this.updateState({ remoteClient: params }); + const sessionResult = await this.sessionManager.authorizeSession().catch(async (err) => { + log.error("rehydrate session error", err); + }); + if (sessionResult) { + await this.rehydrateSession(sessionResult); + } + } + const details = this.getKeyDetails().shareDescriptions[remoteFactorPub]; if (!details) throw CoreKitError.default("factor description not found"); @@ -1225,7 +1251,6 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { tssPubKey: Buffer.from(tssPubKey).toString("hex"), signatures: await this.getSessionSignatures(), userInfo, - remoteClientState: this.state.remoteClient, }; await this.sessionManager.createSession(payload); // to accommodate async storage @@ -1364,7 +1389,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { } const factorKey = new BN(result.factorKey, "hex"); - if (!factorKey && !result.remoteClientState?.metadataShare) { + if (!result.factorKey && !this.state.remoteClient.metadataShare) { throw CoreKitError.providedFactorKeyInvalid(); } @@ -1374,13 +1399,10 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { await this.tKey.initialize({ neverInitializeNewKey: true }); - // skip input share store if factor key is not present - // tkey will be at state initalized - if (!result.factorKey) { - return; - } + const metadataShareStore = this.state.remoteClient?.metadataShare + ? ShareStore.fromJSON(JSON.parse(this.state.remoteClient.metadataShare)) + : await this.getFactorKeyMetadata(factorKey); - const metadataShareStore = await this.getFactorKeyMetadata(factorKey); await this.tKey.inputShareStoreSafe(metadataShareStore, true); await this.tKey.reconstructKey(); @@ -1392,7 +1414,6 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { 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); diff --git a/src/plugins/IRemoteSigner.ts b/src/plugins/IRemoteSigner.ts new file mode 100644 index 00000000..33f34932 --- /dev/null +++ b/src/plugins/IRemoteSigner.ts @@ -0,0 +1,62 @@ +import { FactorEnc, Point, ShareDescriptionMap } from "@tkey/common-types"; +import { IRemoteClientState } from "@tkey/tss"; +import { PointHex } from "@toruslabs/tss-client"; + +import { CreateFactorParams, WEB3AUTH_NETWORK_TYPE } from "../interfaces"; + +type SupportedCurve = "secp256k1" | "ed25519"; + +export type ICustomFrostSignParams = { + sessionId: string; + signatures: string[]; + tssCommits: PointHex[]; + factorEnc: FactorEnc; + tssPubKeyHex: string; + curve: SupportedCurve; + + serverXCoords: number[]; + clientXCoord: number; + serverCoefficients: string[]; + clientCoefficient: string; + serverURLs: string[]; +}; + +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[]; +} + +export interface ICustomDKLSSign { + sign: (params: ICustomDklsSignParams, msgHash: Uint8Array) => Promise<{ v: number; r: Uint8Array; s: Uint8Array }>; +} + +export interface ICustomFrostSign { + sign: (params: ICustomFrostSignParams, msgHash: Uint8Array) => Promise; +} +export interface IRemoteSignerContext { + setupRemoteSigning(params: Omit, rehydrate?: boolean): Promise; + createFactor(createFactorParams: CreateFactorParams): Promise; + inputFactorKey(factorKey: string): Promise; + deleteFactor(factorPub: Point, factorKey?: string): Promise; + getKeyDetails(): Record & { shareDescriptions: ShareDescriptionMap }; + getMetadataKey(): string | undefined; + getMetadataPublicKey(): string | undefined; + getWeb3AuthNetwork(): WEB3AUTH_NETWORK_TYPE; + + // Added new methods + setCustomDKLSSign(customDKLSSign: ICustomDKLSSign): void; + setCustomFrostSign(customDKLSSign: ICustomFrostSign): void; +} From b9f19246873949452857c604c69a236450a5a1f3 Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 17 Dec 2024 14:51:52 +0800 Subject: [PATCH 18/22] fix: rebase issue --- src/mpcCoreKit.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index f043b3b8..dea7509e 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -898,10 +898,6 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { if (opts?.keyTweak) { throw CoreKitError.default("key tweaking not supported for ecdsa-secp256k1"); } - if (this.state.remoteClient && !this.state.factorKey) { - const sig = await this.remoteSignSecp256k1(data, opts?.hashed); - return Buffer.concat([sig.r, sig.s, Buffer.from([sig.v])]); - } const sig = await this.sign_ECDSA_secp256k1(data, opts?.hashed, opts?.secp256k1Precompute); return Buffer.concat([sig.r, sig.s, Buffer.from([sig.v])]); } else if (this._sigType === "ed25519" || this._sigType === "bip340") { From f0bc2f8ed53242e8d4c854f7eb040a1892fae348 Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 17 Dec 2024 14:56:59 +0800 Subject: [PATCH 19/22] fix: interface --- src/mpcCoreKit.ts | 13 ++----------- src/plugins/IRemoteSigner.ts | 6 +++++- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index dea7509e..9b2e750e 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -2,16 +2,7 @@ import { BNString, KeyType, ONE_KEY_DELETE_NONCE, Point, secp256k1, SHARE_DELETE import { CoreError } from "@tkey/core"; import { ShareSerializationModule } from "@tkey/share-serialization"; import { TorusStorageLayer } from "@tkey/storage-layer-torus"; -import { - DELIMITERS, - factorKeyCurve, - getPubKeyPoint, - IRemoteClientState, - lagrangeInterpolation, - pointToHex, - TKeyTSS, - TSSTorusServiceProvider, -} from "@tkey/tss"; +import { DELIMITERS, factorKeyCurve, getPubKeyPoint, lagrangeInterpolation, pointToHex, TKeyTSS, TSSTorusServiceProvider } from "@tkey/tss"; import { 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"; @@ -69,7 +60,7 @@ import { Web3AuthState, } from "./interfaces"; import { DefaultSessionSigGeneratorPlugin } from "./plugins/DefaultSessionSigGenerator"; -import { ICustomDklsSignParams, ICustomFrostSignParams } from "./plugins/IRemoteSigner"; +import { ICustomDklsSignParams, ICustomFrostSignParams, IRemoteClientState } from "./plugins/IRemoteSigner"; import { ISessionSigGenerator } from "./plugins/ISessionSigGenerator"; import { deriveShareCoefficients, diff --git a/src/plugins/IRemoteSigner.ts b/src/plugins/IRemoteSigner.ts index 33f34932..cb0b0880 100644 --- a/src/plugins/IRemoteSigner.ts +++ b/src/plugins/IRemoteSigner.ts @@ -1,10 +1,14 @@ import { FactorEnc, Point, ShareDescriptionMap } from "@tkey/common-types"; -import { IRemoteClientState } from "@tkey/tss"; import { PointHex } from "@toruslabs/tss-client"; import { CreateFactorParams, WEB3AUTH_NETWORK_TYPE } from "../interfaces"; type SupportedCurve = "secp256k1" | "ed25519"; +export interface IRemoteClientState { + remoteFactorPub: string; + metadataShare: string; + tssShareIndex: number; +} export type ICustomFrostSignParams = { sessionId: string; From 241f152e471f61a1cd5775c24f23259b5ec14133 Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 18 Dec 2024 11:59:05 +0800 Subject: [PATCH 20/22] feat: update interface add throw error for unsupport feature for remoteSigner --- src/helper/errors.ts | 4 ++++ src/interfaces.ts | 6 ++---- src/mpcCoreKit.ts | 20 +++++++++++++++++-- .../{IRemoteSigner.ts => ICustomSigner.ts} | 16 +++++++++------ src/plugins/index.ts | 1 + 5 files changed, 35 insertions(+), 12 deletions(-) rename src/plugins/{IRemoteSigner.ts => ICustomSigner.ts} (93%) diff --git a/src/helper/errors.ts b/src/helper/errors.ts index f22c3131..5cb262f3 100644 --- a/src/helper/errors.ts +++ b/src/helper/errors.ts @@ -339,6 +339,10 @@ class CoreKitError extends AbstractCoreKitError { return CoreKitError.fromCode(1214, extraMessage); } + public static notSupportedForRemoteFactor(extraMessage = ""): ICoreKitError { + return CoreKitError.fromCode(1215, extraMessage); + } + // Initialization and session management public static commitChangesBeforeMFA(extraMessage = ""): ICoreKitError { return CoreKitError.fromCode(1301, extraMessage); diff --git a/src/interfaces.ts b/src/interfaces.ts index e7ad60e1..b0cb1777 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,5 +1,5 @@ import { BNString, KeyType, Point as TkeyPoint, ShareDescriptionMap } from "@tkey/common-types"; -import { IRemoteClientState, TKeyTSS, TSSTorusServiceProvider } from "@tkey/tss"; +import { TKeyTSS, TSSTorusServiceProvider } from "@tkey/tss"; import { WEB3AUTH_SIG_TYPE } from "@toruslabs/constants"; import type { AGGREGATE_VERIFIER_TYPE, @@ -22,7 +22,7 @@ import { SafeEventEmitter } from "@web3auth/auth"; import BN from "bn.js"; import { FactorKeyTypeShareDescription, TssShareType, USER_PATH, WEB3AUTH_NETWORK } from "./constants"; -import { IRemoteSignerContext } from "./plugins/IRemoteSigner"; +import { IRemoteClientState, IRemoteSignerContext } from "./plugins/ICustomSigner"; import { ISessionSigGenerator } from "./plugins/ISessionSigGenerator"; export type CoreKitMode = UX_MODE_TYPE | "nodejs" | "react-native"; @@ -177,7 +177,6 @@ export interface JWTLoginParams { */ prefetchTssPublicKeys?: number; } - export interface Web3AuthState { postBoxKey?: string; signatures?: string[]; @@ -515,7 +514,6 @@ 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 9b2e750e..27af8e45 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -60,7 +60,7 @@ import { Web3AuthState, } from "./interfaces"; import { DefaultSessionSigGeneratorPlugin } from "./plugins/DefaultSessionSigGenerator"; -import { ICustomDklsSignParams, ICustomFrostSignParams, IRemoteClientState } from "./plugins/IRemoteSigner"; +import { ICustomDklsSignParams, ICustomFrostSignParams, IRemoteClientState } from "./plugins/ICustomSigner"; import { ISessionSigGenerator } from "./plugins/ISessionSigGenerator"; import { deriveShareCoefficients, @@ -588,6 +588,13 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { public async enableMFA(enableMFAParams: EnableMFAParams, recoveryFactor = true): Promise { this.checkReady(); + if (!this.state.factorKey) { + if (this.state.remoteClient?.remoteFactorPub) { + throw CoreKitError.notSupportedForRemoteFactor("Cannot enable MFA with remote factor."); + } + throw CoreKitError.factorKeyNotPresent("Current factorKey not present in state when enabling MFA."); + } + const { postBoxKey } = this.state; const hashedFactorKey = getHashedPrivateKey(postBoxKey, this.options.hashedFactorNonce); if (!(await this.checkIfFactorKeyValid(hashedFactorKey))) { @@ -652,6 +659,14 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { // mutation function public async createFactor(createFactorParams: CreateFactorParams): Promise { this.checkReady(); + + if (!this.state.factorKey) { + if (this.state.remoteClient?.remoteFactorPub) { + throw CoreKitError.notSupportedForRemoteFactor("Cannot create a factor with remote factor."); + } + throw CoreKitError.factorKeyNotPresent("Current factorKey not present in state when creating a factor."); + } + const { shareType } = createFactorParams; let { factorKey, shareDescription, additionalMetadata } = createFactorParams; @@ -905,7 +920,8 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { // mutation function async deleteFactor(factorPub: Point, factorKey?: BNString): Promise { - if (!this.state.factorKey && !this.state.remoteClient) { + if (!this.state.factorKey) { + if (this.state.remoteClient?.remoteFactorPub) throw CoreKitError.notSupportedForRemoteFactor("Cannot delete a remote factor."); throw CoreKitError.factorKeyNotPresent("factorKey not present in state when deleting a factor."); } if (!this.tKey.metadata.factorPubs) { diff --git a/src/plugins/IRemoteSigner.ts b/src/plugins/ICustomSigner.ts similarity index 93% rename from src/plugins/IRemoteSigner.ts rename to src/plugins/ICustomSigner.ts index cb0b0880..e648ba3f 100644 --- a/src/plugins/IRemoteSigner.ts +++ b/src/plugins/ICustomSigner.ts @@ -1,14 +1,10 @@ import { FactorEnc, Point, ShareDescriptionMap } from "@tkey/common-types"; import { PointHex } from "@toruslabs/tss-client"; +import { SafeEventEmitter } from "@web3auth/auth"; import { CreateFactorParams, WEB3AUTH_NETWORK_TYPE } from "../interfaces"; -type SupportedCurve = "secp256k1" | "ed25519"; -export interface IRemoteClientState { - remoteFactorPub: string; - metadataShare: string; - tssShareIndex: number; -} +export type SupportedCurve = "secp256k1" | "ed25519"; export type ICustomFrostSignParams = { sessionId: string; @@ -50,7 +46,15 @@ export interface ICustomDKLSSign { export interface ICustomFrostSign { sign: (params: ICustomFrostSignParams, msgHash: Uint8Array) => Promise; } + +export interface IRemoteClientState { + remoteFactorPub: string; + metadataShare: string; + tssShareIndex: number; +} + export interface IRemoteSignerContext { + stateEmitter: SafeEventEmitter; setupRemoteSigning(params: Omit, rehydrate?: boolean): Promise; createFactor(createFactorParams: CreateFactorParams): Promise; inputFactorKey(factorKey: string): Promise; diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 789c86a3..62bef675 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -1,2 +1,3 @@ export * from "./DefaultSessionSigGenerator"; +export * from "./ICustomSigner"; export * from "./ISessionSigGenerator"; From 7db5d9522a23673a8414c0386dbcbcddfff73cb0 Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 19 Dec 2024 13:15:32 +0800 Subject: [PATCH 21/22] fix: better way check valid factorKey --- src/mpcCoreKit.ts | 44 ++++++++++++++++++++---------------- src/plugins/ICustomSigner.ts | 3 ++- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index 27af8e45..bf8a5893 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -540,6 +540,10 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { public async inputFactorKey(factorKey: BNString): Promise { const factorKeyBN = new BN(factorKey, "hex"); this.checkReady(); + const point = Point.fromScalar(factorKeyBN, secp256k1); + if (!this.getTssFactorPub().includes(point.toSEC1(secp256k1, true).toString("hex"))) { + throw CoreKitError.providedFactorKeyInvalid(); + } try { // input tkey device share when required share > 0 ( or not reconstructed ) // assumption tkey shares will not changed @@ -649,9 +653,6 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { public getTssFactorPub = (): string[] => { this.checkReady(); - if (!this.state.factorKey) { - throw CoreKitError.factorKeyNotPresent("factorKey not present in state when getting tss factor public key."); - } const factorPubsList = this.tKey.metadata.factorPubs[this.tKey.tssTag]; return factorPubsList.map((factorPub) => factorPub.toSEC1(factorKeyCurve, true).toString("hex")); }; @@ -1210,7 +1211,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { } public getMetadataKey(): string { - return this.tkey.secp256k1Key.toString("hex"); + return this.tkey.secp256k1Key.toString("hex").padStart(64, "0"); } public getMetadataPublicKey(): string { @@ -1342,22 +1343,25 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { } const hashedFactorKey = getHashedPrivateKey(this.state.postBoxKey, this.options.hashedFactorNonce); - this.state.factorKey = hashedFactorKey; - if (await this.checkIfFactorKeyValid(hashedFactorKey)) { - // Initialize tkey with existing hashed share if available. - const factorKeyMetadata: ShareStore = await this.getFactorKeyMetadata(hashedFactorKey); - try { - await this.tKey.inputShareStoreSafe(factorKeyMetadata, true); - await this.tKey.reconstructKey(); - await this.finalizeTkey(hashedFactorKey); - } catch (err) { - log.error("error initializing tkey with hashed share", err); - } - } else { - const factorKeyMetadata = await this.tKey?.readMetadata(hashedFactorKey); - if (factorKeyMetadata.message === "SHARE_DELETED") { - // throw CoreKitError.hashedFactorDeleted(); - log.warn("hashed factor deleted"); + const point = Point.fromScalar(hashedFactorKey, secp256k1); + if (this.getTssFactorPub().includes(point.toSEC1(secp256k1, true).toString("hex"))) { + this.state.factorKey = hashedFactorKey; + if (await this.checkIfFactorKeyValid(hashedFactorKey)) { + // Initialize tkey with existing hashed share if available. + const factorKeyMetadata: ShareStore = await this.getFactorKeyMetadata(hashedFactorKey); + try { + await this.tKey.inputShareStoreSafe(factorKeyMetadata, true); + await this.tKey.reconstructKey(); + await this.finalizeTkey(hashedFactorKey); + } catch (err) { + log.error("error initializing tkey with hashed share", err); + } + } else { + const factorKeyMetadata = await this.tKey?.readMetadata(hashedFactorKey); + if (factorKeyMetadata.message === "SHARE_DELETED") { + // throw CoreKitError.hashedFactorDeleted(); + log.warn("hashed factor deleted"); + } } } } diff --git a/src/plugins/ICustomSigner.ts b/src/plugins/ICustomSigner.ts index e648ba3f..75097fb5 100644 --- a/src/plugins/ICustomSigner.ts +++ b/src/plugins/ICustomSigner.ts @@ -2,7 +2,7 @@ import { FactorEnc, Point, ShareDescriptionMap } from "@tkey/common-types"; import { PointHex } from "@toruslabs/tss-client"; import { SafeEventEmitter } from "@web3auth/auth"; -import { CreateFactorParams, WEB3AUTH_NETWORK_TYPE } from "../interfaces"; +import { COREKIT_STATUS, CreateFactorParams, WEB3AUTH_NETWORK_TYPE } from "../interfaces"; export type SupportedCurve = "secp256k1" | "ed25519"; @@ -54,6 +54,7 @@ export interface IRemoteClientState { } export interface IRemoteSignerContext { + status: COREKIT_STATUS; stateEmitter: SafeEventEmitter; setupRemoteSigning(params: Omit, rehydrate?: boolean): Promise; createFactor(createFactorParams: CreateFactorParams): Promise; From 874bb7c74b222484dd5d67105e9272a3ef5789b5 Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 20 Dec 2024 16:19:31 +0800 Subject: [PATCH 22/22] fix: take remoteclient in to status consideration --- src/mpcCoreKit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mpcCoreKit.ts b/src/mpcCoreKit.ts index bf8a5893..962eb960 100644 --- a/src/mpcCoreKit.ts +++ b/src/mpcCoreKit.ts @@ -186,7 +186,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit, IMPCContext { const { tkey } = this; if (!tkey) return COREKIT_STATUS.NOT_INITIALIZED; if (!tkey.metadata) return COREKIT_STATUS.INITIALIZED; - if (!tkey.secp256k1Key || !this.state.factorKey) return COREKIT_STATUS.REQUIRED_SHARE; + if (!tkey.secp256k1Key || !(this.state.factorKey || this.state.remoteClient.remoteFactorPub)) return COREKIT_STATUS.REQUIRED_SHARE; return COREKIT_STATUS.LOGGED_IN; } catch (e) {} return COREKIT_STATUS.NOT_INITIALIZED;