Skip to content

Commit 57472c3

Browse files
committed
fix: add support for frost signing
1 parent b24d599 commit 57472c3

2 files changed

Lines changed: 138 additions & 7 deletions

File tree

src/interfaces.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BNString, KeyType, Point as TkeyPoint, ShareDescriptionMap } from "@tkey/common-types";
1+
import { BNString, FactorEnc, KeyType, Point as TkeyPoint, ShareDescriptionMap } from "@tkey/common-types";
22
import { IRemoteClientState, TKeyTSS } from "@tkey/tss";
33
import type {
44
AGGREGATE_VERIFIER_TYPE,
@@ -9,6 +9,7 @@ import type {
99
TorusVerifierResponse,
1010
UX_MODE_TYPE,
1111
} from "@toruslabs/customauth";
12+
import { PointHex } from "@toruslabs/tss-client";
1213
// TODO: move the types to a base class for both dkls and frost in future
1314
import type { tssLib as TssDklsLib } from "@toruslabs/tss-dkls-lib";
1415
import type { tssLib as TssFrostLib } from "@toruslabs/tss-frost-lib";
@@ -472,3 +473,41 @@ export interface EthereumSigner {
472473
sign: (msgHash: Buffer) => Promise<EthSig>;
473474
getPublic: () => Promise<Buffer>;
474475
}
476+
477+
type SupportedCurve = "secp256k1" | "ed25519";
478+
// remote signer interface
479+
export type RemoteDklsSignParams = {
480+
factorEnc?: FactorEnc;
481+
sessionId: string;
482+
tssNonce: number;
483+
accountNonce: string;
484+
tssPubKeyHex: string;
485+
486+
nodeIndexes: number[];
487+
tssCommits: PointHex[];
488+
489+
signatures: string[];
490+
491+
serverEndpoints: {
492+
endpoints: string[];
493+
tssWSEndpoints: string[];
494+
partyIndexes: number[];
495+
};
496+
497+
curve: SupportedCurve;
498+
};
499+
500+
export type RemoteFrostSignParams = {
501+
sessionId: string;
502+
signatures: string[];
503+
tssCommits: PointHex[];
504+
factorEnc: FactorEnc;
505+
serverXCoords: number[];
506+
clientXCoord: number;
507+
serverCoefficients: string[];
508+
clientCoefficient: string;
509+
tssPubKeyHex: string;
510+
serverURLs: string[];
511+
512+
curve: SupportedCurve;
513+
};

src/mpcCoreKit.ts

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,16 @@ import { BNString, KeyType, Point, secp256k1, SHARE_DELETED, ShareStore, Stringi
22
import { CoreError } from "@tkey/core";
33
import { ShareSerializationModule } from "@tkey/share-serialization";
44
import { TorusStorageLayer } from "@tkey/storage-layer-torus";
5-
import { DELIMITERS, factorKeyCurve, getPubKeyPoint, IRemoteClientState, lagrangeInterpolation, TKeyTSS, TSSTorusServiceProvider } from "@tkey/tss";
5+
import {
6+
DELIMITERS,
7+
factorKeyCurve,
8+
getPubKeyPoint,
9+
IRemoteClientState,
10+
lagrangeInterpolation,
11+
pointToHex,
12+
TKeyTSS,
13+
TSSTorusServiceProvider,
14+
} from "@tkey/tss";
615
import { KEY_TYPE, SIGNER_MAP } from "@toruslabs/constants";
716
import { AGGREGATE_VERIFIER, TORUS_METHOD, TorusAggregateLoginResponse, TorusLoginResponse, UX_MODE } from "@toruslabs/customauth";
817
import type { UX_MODE_TYPE } from "@toruslabs/customauth/dist/types/utils/enums";
@@ -44,6 +53,8 @@ import {
4453
JWTLoginParams,
4554
MPCKeyDetails,
4655
OAuthLoginParams,
56+
RemoteDklsSignParams,
57+
RemoteFrostSignParams,
4758
SessionData,
4859
SubVerifierDetailsParams,
4960
TkeyLocalStoreData,
@@ -591,7 +602,9 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
591602
const { shareType } = createFactorParams;
592603

593604
let { factorKey, shareDescription, additionalMetadata } = createFactorParams;
594-
factorKey = factorKey ? new BN(factorKey, "hex") : undefined;
605+
if (typeof factorKey === "string") {
606+
factorKey = new BN(factorKey, "hex");
607+
}
595608

596609
if (!VALID_SHARE_INDICES.includes(shareType)) {
597610
throw CoreKitError.newShareIndexInvalid(`Invalid share type provided (${shareType}). Valid share types are ${VALID_SHARE_INDICES}.`);
@@ -670,6 +683,9 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
670683
const sig = await this.sign_ECDSA_secp256k1(data, hashed);
671684
return Buffer.concat([sig.r, sig.s, Buffer.from([sig.v])]);
672685
} else if (this.keyType === KeyType.ed25519) {
686+
if (this.state.remoteClient) {
687+
return this.remoteSignFrostEd25519(data, hashed);
688+
}
673689
return this.sign_ed25519(data, hashed);
674690
}
675691
throw CoreKitError.default(`sign not supported for key type ${this.keyType}`);
@@ -925,23 +941,27 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
925941
}
926942
const { endpoints, tssWSEndpoints, partyIndexes } = generateTSSEndpoints(torusNodeTSSEndpoints, parties, clientIndex, nodeIndexes);
927943

944+
const accountNonce = this.tkey.computeAccountNonce(this.state.accountIndex);
928945
const factor = Point.fromSEC1(secp256k1, this.state.remoteClient.remoteFactorPub);
929946
const factorEnc = this.tKey.getFactorEncs(factor);
930947

931-
const data = {
932-
dataRequired: {
948+
const data: { remoteSignParams: RemoteDklsSignParams; msgHash: string } = {
949+
remoteSignParams: {
933950
factorEnc,
934951
sessionId,
935952
tssNonce,
953+
accountNonce: accountNonce.toString("hex"),
936954
nodeIndexes: nodeIndexes.slice(0, parties - 1),
937-
tssCommits: tssCommits.map((commit) => commit.toJSON()),
955+
tssCommits: tssCommits.map((commit) => pointToHex(commit)),
938956
signatures: this.signatures,
939957
serverEndpoints: { endpoints, tssWSEndpoints, partyIndexes },
958+
tssPubKeyHex: this.state.tssPubKey.toString("hex"),
959+
curve: this.tkey.tssKeyType,
940960
},
941961
msgHash: msgData.toString("hex"),
942962
};
943963

944-
const result = await post<{ data?: Record<string, string> }>(`${this.state.remoteClient.remoteClientUrl}/api/v3/mpc/sign`, data, {
964+
const result = await post<{ data?: Record<string, string> }>(`${this.state.remoteClient.remoteClientUrl}/api/v3/mpc/sign/dkls`, data, {
945965
headers: {
946966
Authorization: `Bearer ${this.state.remoteClient.remoteClientToken}`,
947967
},
@@ -950,6 +970,76 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
950970
return { v: parseInt(v), r: Buffer.from(r, "hex"), s: Buffer.from(s, "hex") };
951971
}
952972

973+
public async remoteSignFrostEd25519(data: Buffer, hashed: boolean = false): Promise<Buffer> {
974+
if (hashed) {
975+
throw CoreKitError.default("hashed data not supported for ed25519");
976+
}
977+
978+
const nodeDetails = fetchLocalConfig(this.options.web3AuthNetwork, "ed25519");
979+
if (!nodeDetails.torusNodeTSSEndpoints) {
980+
throw CoreKitError.default("could not fetch tss node endpoints");
981+
}
982+
983+
// Endpoints must end with backslash, but URLs returned by
984+
// `fetch-node-details` don't have it.
985+
const ED25519_ENDPOINTS = nodeDetails.torusNodeTSSEndpoints.map((ep, i) => ({ index: nodeDetails.torusIndexes[i], url: `${ep}/` }));
986+
987+
// Select endpoints and derive party indices.
988+
const serverThreshold = Math.floor(ED25519_ENDPOINTS.length / 2) + 1;
989+
const endpoints = sampleEndpoints(ED25519_ENDPOINTS, serverThreshold);
990+
const serverXCoords = endpoints.map((x) => x.index);
991+
const clientXCoord = Math.max(...endpoints.map((ep) => ep.index)) + 1;
992+
993+
// Derive share coefficients for flat hierarchy.
994+
const ec = new Ed25519Curve();
995+
const { serverCoefficients, clientCoefficient } = deriveShareCoefficients(ec, serverXCoords, clientXCoord, this.state.tssShareIndex);
996+
997+
// Get pub key.
998+
const tssPubKey = await this.getPubKey();
999+
const tssPubKeyPoint = ec.keyFromPublic(tssPubKey).getPublic();
1000+
1001+
// Get client key share and adjust by coefficient.
1002+
if (this.state.accountIndex !== 0) {
1003+
throw CoreKitError.default("Account index not supported for ed25519");
1004+
}
1005+
1006+
// Generate session identifier.
1007+
const tssNonce = this.getTssNonce();
1008+
const sessionNonce = generateSessionNonce();
1009+
const session = getSessionId(this.verifier, this.verifierId, this.tKey.tssTag, tssNonce, sessionNonce);
1010+
1011+
// Run signing protocol.
1012+
1013+
const serverURLs = endpoints.map((x) => x.url);
1014+
const tssPubKeyHex = ec.pointToBuffer(tssPubKeyPoint, Buffer).toString("hex");
1015+
1016+
const factorPub = Point.fromSEC1(secp256k1, this.state.remoteClient.remoteFactorPub);
1017+
const params: { remoteSignParams: RemoteFrostSignParams; msgHash: string } = {
1018+
remoteSignParams: {
1019+
sessionId: session,
1020+
signatures: this.signatures,
1021+
tssCommits: this.tKey.getTSSCommits().map((commit) => pointToHex(commit)),
1022+
factorEnc: this.tKey.getFactorEncs(factorPub),
1023+
serverXCoords,
1024+
clientXCoord,
1025+
serverCoefficients: serverCoefficients.map((sc) => sc.toString("hex")),
1026+
clientCoefficient: clientCoefficient.toString("hex"),
1027+
tssPubKeyHex,
1028+
serverURLs,
1029+
curve: this.tkey.tssKeyType,
1030+
},
1031+
msgHash: data.toString("hex"),
1032+
};
1033+
1034+
const result = await post<{ data?: Record<string, string> }>(`${this.state.remoteClient.remoteClientUrl}/api/v3/mpc/sign/frost`, params, {
1035+
headers: {
1036+
Authorization: `Bearer ${this.state.remoteClient.remoteClientToken}`,
1037+
},
1038+
});
1039+
1040+
return Buffer.from(result.data.signature, "hex");
1041+
}
1042+
9531043
public updateState(newState: Partial<Web3AuthState>): void {
9541044
this.state = { ...this.state, ...newState };
9551045
}
@@ -966,6 +1056,8 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
9661056
}
9671057
}
9681058
return r;
1059+
} catch (e) {
1060+
throw e as Error;
9691061
} finally {
9701062
this.atomicCallStackCounter -= 1;
9711063
if (this.atomicCallStackCounter === 0) {

0 commit comments

Comments
 (0)