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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/tss/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@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",
Expand Down
45 changes: 44 additions & 1 deletion packages/tss/src/common.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Point, TkeyStoreItemType } from "@tkey/common-types";
import { EncryptedMessage } from "@toruslabs/rss-client";
import { EncryptedMessage, PointHex } from "@toruslabs/rss-client";
import BN from "bn.js";

export type FactorEncType = "direct" | "hierarchical";
Expand All @@ -23,3 +23,46 @@ export type InitializeNewTSSKeyResult = {
export type IAccountSaltStore = TkeyStoreItemType & {
value: string;
};

export interface IRemoteClientState {
remoteFactorPub: string;
remoteClientUrl: string;
remoteClientToken: string;
metadataShare: string;
tssShareIndex: number;
// Signatures for authentication against RSS servers
signatures: string[];
}

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;
};
}
144 changes: 143 additions & 1 deletion packages/tss/src/tss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ import {
TKeyInitArgs,
} from "@tkey/common-types";
import { CoreError, TKey } from "@tkey/core";
import { post } from "@toruslabs/http-helpers";
import { dotProduct, ecPoint, hexPoint, PointHex, randomSelection, RSSClient } from "@toruslabs/rss-client";
import { getEd25519ExtendedPublicKey as getEd25519KeyPairFromSeed, getSecpKeyFromEd25519 } from "@toruslabs/torus.js";
import BN from "bn.js";
import { ec as EC } from "elliptic";
import { keccak256 } from "ethereum-cryptography/keccak";

import { TSSTorusServiceProvider } from ".";
import { FactorEnc, IAccountSaltStore, InitializeNewTSSKeyResult } from "./common";
import { FactorEnc, IAccountSaltStore, InitializeNewTSSKeyResult, IRemoteClientState, RefreshRemoteTssReturnType } from "./common";
import {
generateSalt,
getEd25519SeedStoreDomainKey,
Expand Down Expand Up @@ -499,6 +500,147 @@ export class TKeyTSS extends TKey {
return seed;
}

// remote signer function

/**
* 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[]; remoteClient: IRemoteClientState }) {
const { factorPubs, tssIndices, remoteClient } = params;
const rssNodeDetails = await this._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 = this.serviceProvider.getVerifierNameVerifierId();

const tssCommits = this.metadata.tssPolyCommits[this.tssTag];
const tssNonce: number = this.metadata.tssNonces[this.tssTag] || 0;
const { pubKey: newTSSServerPub, nodeIndexes } = await this.serviceProvider.getTSSPubKey(this.tssTag, tssNonce + 1);
// move to pre-refresh
if (nodeIndexes?.length > 0) {
finalSelectedServers = nodeIndexes.slice(0, Math.min(serverEndpoints.length, nodeIndexes.length));
}

const factorEnc = this.getFactorEncs(Point.fromSEC1(secp256k1, remoteClient.remoteFactorPub));

const dataRequired = {
factorEnc,
factorPubs: factorPubs.map((pub) => pub.toJSON()),
targetIndexes: tssIndices,
verifierNameVerifierId,
tssTag: this.tssTag,
tssCommits: tssCommits.map((commit) => commit.toJSON()),
tssNonce,
newTSSServerPub: newTSSServerPub.toJSON(),
serverOpts: {
selectedServers: finalSelectedServers,
serverEndpoints,
serverPubKeys,
serverThreshold,
authSignatures: remoteClient.signatures,
},
};

const result = (
await post<{ data: RefreshRemoteTssReturnType }>(
`${remoteClient.remoteClientUrl}/api/v3/mpc/refresh_tss`,
{ dataRequired },
{
headers: {
Authorization: `Bearer ${remoteClient.remoteClientToken}`,
},
}
)
).data;

this.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 remoteAddFactorPub(params: { newFactorPub: Point; newFactorTSSIndex: number; remoteClient: IRemoteClientState }) {
const { newFactorPub, newFactorTSSIndex, remoteClient } = params;
const existingFactorPubs = this.metadata.factorPubs[this.tssTag];
const updatedFactorPubs = existingFactorPubs.concat([newFactorPub]);
const existingTSSIndexes = existingFactorPubs.map((fb) => this.getFactorEncs(fb).tssIndex);
const updatedTSSIndexes = existingTSSIndexes.concat([newFactorTSSIndex]);

await this.remoteRefreshTssShares({
factorPubs: updatedFactorPubs,
tssIndices: updatedTSSIndexes,
remoteClient,
});
}

async remoteDeleteFactorPub(params: { factorPubToDelete: Point; remoteClient: IRemoteClientState }) {
const { factorPubToDelete, remoteClient } = params;
const existingFactorPubs = this.metadata.factorPubs[this.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) => this.getFactorEncs(fb).tssIndex);

await this.remoteRefreshTssShares({
factorPubs: updatedFactorPubs,
tssIndices: updatedTSSIndexes,
remoteClient,
});
}

async remoteCopyFactorPub(params: { newFactorPub: Point; tssIndex: number; remoteClient: IRemoteClientState }) {
const { newFactorPub, tssIndex, remoteClient } = params;
const remoteFactorPub = Point.fromSEC1(secp256k1, remoteClient.remoteFactorPub);
const factorEnc = this.getFactorEncs(remoteFactorPub);
const tssCommits = this.getTSSCommits();
const dataRequired = {
factorEnc,
tssCommits,
factorPub: newFactorPub,
};

const result = (
await post<{ data?: EncryptedMessage }>(
`${remoteClient.remoteClientUrl}/api/v3/mpc/copy_tss_share`,
{ dataRequired },
{
headers: {
Authorization: `Bearer ${remoteClient.remoteClientToken}`,
},
}
)
).data;

const updatedFactorPubs = this.metadata.factorPubs[this.tssTag].concat([newFactorPub]);
const factorEncs: { [key: string]: FactorEnc } = JSON.parse(JSON.stringify(this.metadata.factorEncs[this.tssTag]));
const factorPubID = newFactorPub.x.toString(16, 64);
factorEncs[factorPubID] = {
tssIndex,
type: "direct",
userEnc: result,
serverEncs: [],
};
this.metadata.updateTSSData({
tssTag: this.tssTag,
factorPubs: updatedFactorPubs,
factorEncs,
});
}

/**
* Runs the share refresh protocol for the TSS key shares.
* @param inputShare - The current user secret share.
Expand Down