Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added get or set tss pubkey #167

Merged
merged 2 commits into from
Oct 7, 2024
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
1 change: 1 addition & 0 deletions src/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from "./keyUtils";
export * from "./langrangeInterpolatePoly";
export * from "./metadataUtils";
export * from "./nodeUtils";
export * from "./tssPubKeyUtils";
112 changes: 112 additions & 0 deletions src/helpers/tssPubKeyUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Note: Endpoints should be the sss node endpoints along with path
import { JRPCResponse, KEY_TYPE } from "@toruslabs/constants";
import { generateJsonRPCObject, post } from "@toruslabs/http-helpers";
import log from "loglevel";

import { JRPC_METHODS } from "../constants";
import { GetORSetKeyResponse, KeyType } from "../interfaces";
import { Some } from "../some";
import { normalizeKeysResult, thresholdSame } from "./common";

// for ex: [https://node-1.node.web3auth.io/sss/jrpc, https://node-2.node.web3auth.io/sss/jrpc ....]
export const GetOrSetTssDKGPubKey = async (params: {
endpoints: string[];
verifier: string;
verifierId: string;
tssVerifierId: string;
keyType?: KeyType;
}): Promise<{
key: {
pubKeyX: string;
pubKeyY: string;
address: string;
createdAt?: number;
};
isNewKey: boolean;
nodeIndexes: number[];
}> => {
const { endpoints, verifier, verifierId, tssVerifierId, keyType = KEY_TYPE.SECP256K1 } = params;
const minThreshold = ~~(endpoints.length / 2) + 1;
const lookupPromises = endpoints.map((x) =>
post<JRPCResponse<GetORSetKeyResponse>>(
x,
generateJsonRPCObject(JRPC_METHODS.GET_OR_SET_KEY, {
distributed_metadata: true,
verifier,
verifier_id: verifierId,
extended_verifier_id: tssVerifierId,
one_key_flow: true,
key_type: keyType,
fetch_node_index: true,
client_time: Math.floor(Date.now() / 1000).toString(),
}),
{},
{
logTracingHeader: false,
}
).catch((err) => log.error(`${JRPC_METHODS.GET_OR_SET_KEY} request failed`, err))
);

const nodeIndexes: number[] = [];
const result = await Some<
void | JRPCResponse<GetORSetKeyResponse>,
{
keyResult: Pick<GetORSetKeyResponse, "keys" | "is_new_key">;
nodeIndexes: number[];
errorResult: JRPCResponse<GetORSetKeyResponse>["error"];
}
>(lookupPromises, async (lookupResults) => {
const lookupPubKeys = lookupResults.filter((x1) => {
if (x1 && !x1.error) {
return x1;
}
return false;
});

const errorResult = thresholdSame(
lookupResults.map((x2) => x2 && x2.error),
minThreshold
);

const keyResult = thresholdSame(
lookupPubKeys.map((x3) => x3 && normalizeKeysResult(x3.result)),
minThreshold
);

if (keyResult || errorResult) {
if (keyResult) {
lookupResults.forEach((x1) => {
if (x1 && x1.result) {
const currentNodePubKey = x1.result.keys[0].pub_key_X.toLowerCase();
const thresholdPubKey = keyResult.keys[0].pub_key_X.toLowerCase();
// push only those indexes for nodes who are returning pub key matching with threshold pub key.
// this check is important when different nodes have different keys assigned to a user.
if (currentNodePubKey === thresholdPubKey) {
const nodeIndex = Number.parseInt(x1.result.node_index);
if (nodeIndex) nodeIndexes.push(nodeIndex);
}
}
});
}

return Promise.resolve({ keyResult, nodeIndexes, errorResult });
}
return Promise.reject(new Error(`invalid public key result: ${JSON.stringify(lookupResults)} for tssVerifierId: ${tssVerifierId} `));
});

if (result.errorResult) {
throw new Error(`invalid public key result,errorResult: ${JSON.stringify(result.errorResult)}`);
}

const key = result.keyResult.keys[0];
return {
key: {
pubKeyX: key.pub_key_X,
pubKeyY: key.pub_key_Y,
address: key.address,
createdAt: key.created_at,
},
nodeIndexes: result.nodeIndexes,
isNewKey: result.keyResult.is_new_key,
};
};
88 changes: 88 additions & 0 deletions test/tssPubKey.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { TORUS_SAPPHIRE_NETWORK } from "@toruslabs/constants";
import { NodeDetailManager } from "@toruslabs/fetch-node-details";
import { expect } from "chai";
import faker from "faker";

import { GetOrSetTssDKGPubKey } from "../src";

describe("setTssKey", function () {
const TORUS_EXTENDED_VERIFIER_EMAIL = "[email protected]";
const TORUS_TEST_VERIFIER = "torus-test-health";

let TORUS_NODE_MANAGER: NodeDetailManager;

beforeEach("one time execution before all tests", async function () {
TORUS_NODE_MANAGER = new NodeDetailManager({ network: TORUS_SAPPHIRE_NETWORK.SAPPHIRE_DEVNET });
});

it("should assign key to tss verifier id", async function () {
const email = faker.internet.email();
const nonce = 0;
const tssTag = "default";
const tssVerifierId = `${email}\u0015${tssTag}\u0016${nonce}`;
const verifierDetails = { verifier: TORUS_TEST_VERIFIER, verifierId: email };

const { torusNodeSSSEndpoints: torusNodeEndpoints } = await TORUS_NODE_MANAGER.getNodeDetails(verifierDetails);

const result = await GetOrSetTssDKGPubKey({
endpoints: torusNodeEndpoints,
verifier: TORUS_TEST_VERIFIER,
verifierId: email,
tssVerifierId,
});
expect(result.key.pubKeyX).to.not.equal(null);
});

it("should fetch pub address of tss verifier id", async function () {
const email = TORUS_EXTENDED_VERIFIER_EMAIL;
const nonce = 0;
const tssTag = "default";
const tssVerifierId = `${email}\u0015${tssTag}\u0016${nonce}`;
const verifierDetails = { verifier: TORUS_TEST_VERIFIER, verifierId: email };

const { torusNodeSSSEndpoints: torusNodeEndpoints } = await TORUS_NODE_MANAGER.getNodeDetails(verifierDetails);

const result = await GetOrSetTssDKGPubKey({
endpoints: torusNodeEndpoints,
verifier: TORUS_TEST_VERIFIER,
verifierId: email,
tssVerifierId,
});
delete result.key.createdAt;
expect(result).eql({
key: {
pubKeyX: "d45d4ad45ec643f9eccd9090c0a2c753b1c991e361388e769c0dfa90c210348c",
pubKeyY: "fdc151b136aa7df94e97cc7d7007e2b45873c4b0656147ec70aad46e178bce1e",
address: "0xBd6Bc8aDC5f2A0526078Fd2016C4335f64eD3a30",
},
isNewKey: false,
nodeIndexes: result.nodeIndexes,
});
});

it("should fail if more than one endpoints are invalid", async function () {
const email = TORUS_EXTENDED_VERIFIER_EMAIL;
const nonce = 0;
const tssTag = "default";
const tssVerifierId = `${email}\u0015${tssTag}\u0016${nonce}`;
const verifierDetails = { verifier: TORUS_TEST_VERIFIER, verifierId: email };

const { torusNodeSSSEndpoints: torusNodeEndpoints } = await TORUS_NODE_MANAGER.getNodeDetails(verifierDetails);
torusNodeEndpoints[2] = "https://invalid.torus.com";
torusNodeEndpoints[3] = "https://invalid.torus.com";
torusNodeEndpoints[4] = "https://invalid.torus.com";
try {
await GetOrSetTssDKGPubKey({
endpoints: torusNodeEndpoints,
verifier: TORUS_TEST_VERIFIER,
verifierId: email,
tssVerifierId,
});
// If the function doesn't throw an error, fail the test
expect.fail("Expected an error to be thrown");
} catch (error) {
// Test passes if an error is thrown
expect(error).to.be.instanceOf(Error);
}
});
});