diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 6517335f..1d6a6c18 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -354,7 +354,7 @@ class ThresholdKey implements ITKey { if (useTSS) { if (!this.metadata.tssPolyCommits[this.tssTag]) { // if tss shares have not been created for this tssTag, create new tss sharing - await this._initializeNewTSSKey(this.tssTag, deviceTSSShare, factorPub); + await this._initializeNewTSSKey(this.tssTag, deviceTSSShare, factorPub, deviceTSSIndex); } } @@ -458,6 +458,27 @@ class ThresholdKey implements ITKey { return this.getTSSCommits()[0]; } + setTssTag(tssTag: string): void { + this.tssTag = tssTag; + } + + async createTaggedTSSShare(factorPub: Point, tssShare: BN, tssIndex: number) { + if (!this.privKey) throw CoreError.default("tkey must be reconstructed"); + if (!this.metadata) throw CoreError.metadataUndefined(); + if (!this.tssTag) throw CoreError.default("tssTag must be set"); + if (this.metadata.factorPubs[this.tssTag]) throw CoreError.default(`tssTag ${this.tssTag} already exists`); + + const { factorEncs, factorPubs, tssPolyCommits } = await this._initializeNewTSSKey(this.tssTag, tssShare, factorPub, tssIndex); + this.metadata.addTSSData({ + tssTag: this.tssTag, + tssNonce: 0, + tssPolyCommits, + factorPubs, + factorEncs, + }); + await this._syncShareMetadata(); + } + /** * catchupToLatestShare recursively loops fetches metadata of the provided share and checks if there is an encrypted share for it. * @param shareStore - share to start of with diff --git a/packages/default/test/helpers.js b/packages/default/test/helpers.js index cbbf3d8c..ff81c848 100644 --- a/packages/default/test/helpers.js +++ b/packages/default/test/helpers.js @@ -51,13 +51,14 @@ export function getServiceProvider(params) { return new ServiceProviderBase({ postboxKey: isEmptyProvider ? null : PRIVATE_KEY }); } +const MockServers = [new MockServer(), new MockServer(), new MockServer(), new MockServer(), new MockServer()]; export async function setupTSSMocks(opts) { - let { serviceProvider, verifierName, verifierId, maxTSSNonceToSimulate, tssTag } = opts; + let { serviceProvider, verifierName, verifierId, maxTSSNonceToSimulate, tssTag, postboxKey } = opts; tssTag = tssTag || "default"; serviceProvider._setVerifierNameVerifierId(verifierName, verifierId); const vid = serviceProvider.getVerifierNameVerifierId(); maxTSSNonceToSimulate = maxTSSNonceToSimulate || 1; - const serverEndpoints = [new MockServer(), new MockServer(), new MockServer(), new MockServer(), new MockServer()]; + const serverEndpoints = MockServers; const serverCount = serverEndpoints.length; const serverPrivKeys = []; for (let i = 0; i < serverCount; i++) { @@ -96,6 +97,11 @@ export async function setupTSSMocks(opts) { } serviceProvider._setTSSNodeDetails(serverEndpoints, serverPubKeys, serverThreshold); + // serviceProvider.rs = { ...serviceProvider.sssEndpoints } + if (!postboxKey) { + const postboxKeyGenerated = new BN(generatePrivate()); + serviceProvider.postboxKey = postboxKeyGenerated.toString(16, 64); + } return { serverEndpoints, @@ -182,3 +188,9 @@ export async function assignTssDkgKeys(opts) { // serverDKGPubKeys, }; } + +export async function executeAtomicAsyncTasks(tasks) { + for (const task of tasks) { + await task(); // Assuming task is an async function + } +} diff --git a/packages/default/test/shared.js b/packages/default/test/shared.js index 3859729b..de597401 100644 --- a/packages/default/test/shared.js +++ b/packages/default/test/shared.js @@ -20,7 +20,16 @@ import { createSandbox } from "sinon"; import { keccak256 } from "web3-utils"; import ThresholdKey from "../src/index"; -import { assignTssDkgKeys, fetchPostboxKeyAndSigs, getMetadataUrl, getServiceProvider, initStorageLayer, isMocked, setupTSSMocks } from "./helpers"; +import { + assignTssDkgKeys, + executeAtomicAsyncTasks, + fetchPostboxKeyAndSigs, + getMetadataUrl, + getServiceProvider, + initStorageLayer, + isMocked, + setupTSSMocks, +} from "./helpers"; const rejects = async (fn, error, msg) => { let f = () => {}; @@ -69,10 +78,8 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { it("#should be able to refresh tss shares", async function () { const sp = customSP; if (!sp.useTSS) this.skip(); - const deviceTSSShare = new BN(generatePrivate()); const deviceTSSIndex = 2; - sp.verifierName = "torus-test-health"; sp.verifierId = "test19@example.com"; const testId = sp.getVerifierNameVerifierId(); @@ -98,6 +105,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { await tb1.initialize({ useTSS: true, factorPub, deviceTSSShare, deviceTSSIndex }); const newShare = await tb1.generateNewShare(); const reconstructedKey = await tb1.reconstructKey(); + await tb1.syncLocalMetadataTransitions(); if (tb1.privKey.cmp(reconstructedKey.privKey) !== 0) { fail("key should be able to be reconstructed"); @@ -108,6 +116,123 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { tb2.inputShareStore(newShare.newShareStores[newShare.newShareIndex.toString("hex")]); await tb2.reconstructKey(); const { tssShare: retrievedTSS, tssIndex: retrievedTSSIndex } = await tb2.getTSSShare(factorKey); + const tssCommits = tb2.getTSSCommits(); + const tssPrivKey = getLagrangeCoeffs([1, retrievedTSSIndex], 1) + .mul(serverDKGPrivKeys[0]) + .add(getLagrangeCoeffs([1, retrievedTSSIndex], retrievedTSSIndex).mul(retrievedTSS)) + .umod(ecCurve.n); + const tssPubKey = getPubKeyPoint(tssPrivKey); + strictEqual(tssPubKey.x.toString(16, 64), tssCommits[0].x.toString(16, 64)); + strictEqual(tssPubKey.y.toString(16, 64), tssCommits[0].y.toString(16, 64)); + + const factorKey2 = new BN(generatePrivate()); + const factorPub2 = getPubKeyPoint(factorKey2); + + const factorPubs = [factorPub, factorPub2]; + const { serverEndpoints, serverPubKeys } = await sp.getRSSNodeDetails(); + await tb2._refreshTSSShares(true, retrievedTSS, retrievedTSSIndex, factorPubs, [2, 3], testId, { + serverThreshold: 3, + selectedServers: [1, 2, 3], + serverEndpoints, + serverPubKeys, + authSignatures: signatures, + }); + { + const { tssShare: newTSS2, tssIndex } = await tb2.getTSSShare(factorKey); + const newTSSPrivKey = getLagrangeCoeffs([1, 2], 1) + .mul(new BN(serverDKGPrivKeys[1], "hex")) + .add(getLagrangeCoeffs([1, 2], 2).mul(newTSS2)) + .umod(ecCurve.n); + strictEqual(tssPrivKey.toString(16, 64), newTSSPrivKey.toString(16, 64)); + // eslint-disable-next-line no-console + console.log("newTSS2", newTSS2.toString("hex"), tssIndex); + } + { + const { tssShare: newTSS2, tssIndex } = await tb2.getTSSShare(factorKey2); + const newTSSPrivKey = getLagrangeCoeffs([1, 3], 1) + .mul(new BN(serverDKGPrivKeys[1], "hex")) + .add(getLagrangeCoeffs([1, 3], 3).mul(newTSS2)) + .umod(ecCurve.n); + strictEqual(tssPrivKey.toString(16, 64), newTSSPrivKey.toString(16, 64)); + // eslint-disable-next-line no-console + console.log("newTSS2", newTSS2.toString("hex"), tssIndex); + } + }); + + it("#should be able to refresh with multiple tagged tss shares", async function () { + const sp = customSP; + if (!sp.useTSS) this.skip(); + + const deviceTSSShare = new BN(generatePrivate()); + const deviceTSSIndex = 2; + + sp.verifierName = "torus-test-health"; + sp.verifierId = "test19@example.com"; + const testId = sp.getVerifierNameVerifierId(); + const { signatures, postboxkey } = await fetchPostboxKeyAndSigs({ + serviceProvider: sp, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + }); + sp.postboxKey = postboxkey; + const newTag = "testTag"; + + const { serverDKGPrivKeys } = await assignTssDkgKeys({ + serviceProvider: sp, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + maxTSSNonceToSimulate: 3, + // tssTag: newTag, + }); + + // Setup tss share for newTag + const { serverDKGPrivKeys: serverKey1 } = await assignTssDkgKeys({ + serviceProvider: sp, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + maxTSSNonceToSimulate: 3, + tssTag: newTag, + }); + + const storageLayer = initStorageLayer({ hostUrl: metadataURL }); + const tb1 = new ThresholdKey({ serviceProvider: sp, storageLayer, manualSync: mode }); + // tb1.setTssTag(newTag); + + // factor key needs to passed from outside of tKey + const factorKey = new BN(generatePrivate()); + const factorPub = getPubKeyPoint(factorKey); + + // await tb1.initialize({ useTSS: true, factorPub, deviceTSSShare, deviceTSSIndex }); + await tb1.initialize(); + const reconstructedKey = await tb1.reconstructKey(); + + const am1 = tb1.generateAuthMetadata({ input: [tb1.metadata] }); + await tb1.createTaggedTSSShare(factorPub, deviceTSSShare, deviceTSSIndex); + + const am2 = tb1.generateAuthMetadata({ input: [tb1.metadata] }); + + const deviceTSSShareTag = new BN(generatePrivate()); + const deviceTSSIndexTag = 2; + const factorKeyTag = factorKey; // new BN(generatePrivate()); + const factorPubTag = getPubKeyPoint(factorKeyTag); + tb1.setTssTag(newTag); + await tb1.createTaggedTSSShare(factorPubTag, deviceTSSShareTag, deviceTSSIndexTag); + + const amNewTag = tb1.generateAuthMetadata({ input: [tb1.metadata] }); + + const newShare = await tb1.generateNewShare(); + await tb1.syncLocalMetadataTransitions(); + if (tb1.privKey.cmp(reconstructedKey.privKey) !== 0) { + fail("key should be able to be reconstructed"); + } + + const tb2 = new ThresholdKey({ serviceProvider: sp, storageLayer, manualSync: mode }); + // tb2.setTssTag(newTag); + await tb2.initialize(); + await tb2.inputShareStoreSafe(newShare.newShareStores[newShare.newShareIndex.toString("hex")]); + await tb2.reconstructKey(); + const { tssShare: retrievedTSS, tssIndex: retrievedTSSIndex } = await tb2.getTSSShare(factorKey); + const tssCommits = tb2.getTSSCommits(); const tssPrivKey = getLagrangeCoeffs([1, retrievedTSSIndex], 1) .mul(serverDKGPrivKeys[0]) @@ -118,13 +243,27 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { strictEqual(tssPubKey.x.toString(16, 64), tssCommits[0].x.toString(16, 64)); strictEqual(tssPubKey.y.toString(16, 64), tssCommits[0].y.toString(16, 64)); - // // test tss refresh + // set tkey to new TSS Tag + tb2.setTssTag(newTag); + const { tssShare: retrievedTSSTag, tssIndex: retrievedTSSIndexTag } = await tb2.getTSSShare(factorKeyTag); + + const tssCommitsTag = tb2.getTSSCommits(); + const tssPrivKeyTag = getLagrangeCoeffs([1, retrievedTSSIndexTag], 1) + .mul(serverKey1[0]) + .add(getLagrangeCoeffs([1, retrievedTSSIndexTag], retrievedTSSIndexTag).mul(retrievedTSSTag)) + .umod(ecCurve.n); + const tssPubKeyTag = getPubKeyPoint(tssPrivKeyTag); + strictEqual(tssPubKeyTag.x.toString(16, 64), tssCommitsTag[0].x.toString(16, 64)); + strictEqual(tssPubKeyTag.y.toString(16, 64), tssCommitsTag[0].y.toString(16, 64)); + + // // test tss refresh const factorKey2 = new BN(generatePrivate()); const factorPub2 = getPubKeyPoint(factorKey2); const factorPubs = [factorPub, factorPub2]; const { serverEndpoints, serverPubKeys } = await sp.getRSSNodeDetails(); + tb2.setTssTag("default"); await tb2._refreshTSSShares(true, retrievedTSS, retrievedTSSIndex, factorPubs, [2, 3], testId, { serverThreshold: 3, selectedServers: [1, 2, 3], @@ -133,6 +272,8 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { authSignatures: signatures, }); + const amRefreshed = tb2.generateAuthMetadata({ input: [tb2.metadata] }); + { const { tssShare: newTSS2, tssIndex } = await tb2.getTSSShare(factorKey); const newTSSPrivKey = getLagrangeCoeffs([1, 2], 1) @@ -154,6 +295,59 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { // eslint-disable-next-line no-console console.log("newTSS2", newTSS2.toString("hex"), tssIndex); } + + const { tssShare: retrievedTSS3, tssIndex: retrievedTSSIndex3 } = await tb2.getTSSShare(factorKey); + const factorKey3 = new BN(generatePrivate()); + const factorPub3 = getPubKeyPoint(factorKey3); + const factorPubs2 = [factorPub, factorPub2]; + + // const { serverEndpoints, serverPubKeys } = await sp.getRSSNodeDetails(); + tb2.setTssTag("default"); + await tb2._refreshTSSShares(true, retrievedTSS3, retrievedTSSIndex3, factorPubs2, [2, 3], testId, { + serverThreshold: 3, + selectedServers: [1, 2, 3], + serverEndpoints, + serverPubKeys, + authSignatures: signatures, + }); + + const amRefreshed2 = tb2.generateAuthMetadata({ input: [tb2.metadata] }); + + const factorKey2Tag = new BN(generatePrivate()); + const factorPub2Tag = getPubKeyPoint(factorKey2Tag); + const factorPubsTag = [factorPubTag, factorPub2Tag]; + + tb2.setTssTag(newTag); + await tb2._refreshTSSShares(true, retrievedTSSTag, retrievedTSSIndexTag, factorPubsTag, [2, 3], testId, { + serverThreshold: 3, + selectedServers: [1, 2, 3], + serverEndpoints, + serverPubKeys, + authSignatures: signatures, + }); + + const amTagRefreshed = tb2.generateAuthMetadata({ input: [tb2.metadata] }); + { + const { tssShare: newTSS2, tssIndex } = await tb2.getTSSShare(factorKeyTag); + const newTSSPrivKey = getLagrangeCoeffs([1, 2], 1) + .mul(new BN(serverKey1[1], "hex")) + .add(getLagrangeCoeffs([1, 2], 2).mul(newTSS2)) + .umod(ecCurve.n); + strictEqual(tssPrivKeyTag.toString(16, 64), newTSSPrivKey.toString(16, 64)); + // eslint-disable-next-line no-console + console.log("newTSS2", newTSS2.toString("hex"), tssIndex); + } + + { + const { tssShare: newTSS2, tssIndex } = await tb2.getTSSShare(factorKey2Tag); + const newTSSPrivKey = getLagrangeCoeffs([1, 3], 1) + .mul(new BN(serverKey1[1], "hex")) + .add(getLagrangeCoeffs([1, 3], 3).mul(newTSS2)) + .umod(ecCurve.n); + strictEqual(tssPrivKeyTag.toString(16, 64), newTSSPrivKey.toString(16, 64)); + // eslint-disable-next-line no-console + console.log("newTSS2", newTSS2.toString("hex"), tssIndex); + } }); it("#should be able to reconstruct tssShare from factor key (tss2) when initializing a key with useTSS true", async function () { const sp = customSP; @@ -715,6 +909,138 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { await tb3.updateSDK(); }); }); + describe("multi tag tss", function () { + it("#should be able to refresh with 4 tagged tss shares and refersh from 2/2 -> 2/3 ", async function () { + const sp = customSP; + if (!sp.useTSS) this.skip(); + + const deviceTSSShare = new BN(generatePrivate()); + const deviceTSSIndex = 2; + + sp.verifierName = "torus-test-health"; + sp.verifierId = "test19@example.com"; + const testId = sp.getVerifierNameVerifierId(); + + const { signatures, postboxkey } = await fetchPostboxKeyAndSigs({ + serviceProvider: sp, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + }); + + // eslint-disable-next-line require-atomic-updates + sp.postboxKey = postboxkey; + + const tags = ["tag1", "tag2", "tag3", "tag4"]; + + const dkgKeyAssignedPromise = tags.map(async (tag) => { + const { serverDKGPrivKeys } = await assignTssDkgKeys({ + serviceProvider: sp, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + maxTSSNonceToSimulate: 3, + tssTag: tag, + }); + return serverDKGPrivKeys; + }); + // await executeAtomicAsyncTasks(dkgKeyAssigned); + const dkgKeyAssigned = await Promise.all(dkgKeyAssignedPromise); + + const storageLayer = initStorageLayer({ hostUrl: metadataURL }); + const tb1 = new ThresholdKey({ serviceProvider: sp, storageLayer, manualSync: mode }); + // tb1.setTssTag(newTag); + + // factor key needs to passed from outside of tKey + const factorKey = new BN(generatePrivate()); + const factorPub = getPubKeyPoint(factorKey); + + // await tb1.initialize({ useTSS: true, factorPub, deviceTSSShare, deviceTSSIndex }); + await tb1.initialize(); + const reconstructedKey = await tb1.reconstructKey(); + + // const am1 = tb1.generateAuthMetadata({ input: [tb1.metadata] }); + + const tagTasks = tags.map((tag, index) => { + return async () => { + tb1.setTssTag(tag); + await tb1.createTaggedTSSShare(factorPub, deviceTSSShare, deviceTSSIndex); + // const am2 = tb1.generateAuthMetadata({ input: [tb1.metadata] }); + }; + }); + await executeAtomicAsyncTasks(tagTasks); + + // generate new share for new instance initialization + const newShare = await tb1.generateNewShare(); + await tb1.syncLocalMetadataTransitions(); + if (tb1.privKey.cmp(reconstructedKey.privKey) !== 0) { + fail("key should be able to be reconstructed"); + } + + // new instance initialization + const tb2 = new ThresholdKey({ serviceProvider: sp, storageLayer, manualSync: mode }); + await tb2.initialize(); + await tb2.inputShareStoreSafe(newShare.newShareStores[newShare.newShareIndex.toString("hex")]); + await tb2.reconstructKey(); + + // TSS share test + const testTasks = tags.map((tag, index) => { + return async () => { + tb2.setTssTag(tag); + const { tssShare: retrievedTSS, tssIndex: retrievedTSSIndex } = await tb2.getTSSShare(factorKey); + + const tssCommits = tb2.getTSSCommits(); + const tssPrivKey = getLagrangeCoeffs([1, retrievedTSSIndex], 1) + .mul(dkgKeyAssigned[index][0]) + .add(getLagrangeCoeffs([1, retrievedTSSIndex], retrievedTSSIndex).mul(retrievedTSS)) + .umod(ecCurve.n); + + const tssPubKey = getPubKeyPoint(tssPrivKey); + strictEqual(tssPubKey.x.toString(16, 64), tssCommits[0].x.toString(16, 64)); + strictEqual(tssPubKey.y.toString(16, 64), tssCommits[0].y.toString(16, 64)); + + // // test tss refresh + const factorKey2 = new BN(generatePrivate()); + const factorPub2 = getPubKeyPoint(factorKey2); + + const factorPubs = [factorPub, factorPub2]; + const { serverEndpoints, serverPubKeys } = await sp.getRSSNodeDetails(); + + await tb2._refreshTSSShares(true, retrievedTSS, retrievedTSSIndex, factorPubs, [2, 3], testId, { + serverThreshold: 3, + selectedServers: [1, 2, 3], + serverEndpoints, + serverPubKeys, + authSignatures: signatures, + }); + + // const amRefreshed = tb2.generateAuthMetadata({ input: [tb2.metadata] }); + + { + const { tssShare: newTSS2, tssIndex } = await tb2.getTSSShare(factorKey); + const newTSSPrivKey = getLagrangeCoeffs([1, 2], 1) + .mul(new BN(dkgKeyAssigned[index][1], "hex")) + .add(getLagrangeCoeffs([1, 2], 2).mul(newTSS2)) + .umod(ecCurve.n); + strictEqual(tssPrivKey.toString(16, 64), newTSSPrivKey.toString(16, 64)); + // eslint-disable-next-line no-console + console.log("newTSS2", newTSS2.toString("hex"), tssIndex); + } + + { + const { tssShare: newTSS2, tssIndex } = await tb2.getTSSShare(factorKey2); + const newTSSPrivKey = getLagrangeCoeffs([1, 3], 1) + .mul(new BN(dkgKeyAssigned[index][1], "hex")) + .add(getLagrangeCoeffs([1, 3], 3).mul(newTSS2)) + .umod(ecCurve.n); + strictEqual(tssPrivKey.toString(16, 64), newTSSPrivKey.toString(16, 64)); + // eslint-disable-next-line no-console + console.log("newTSS2", newTSS2.toString("hex"), tssIndex); + } + }; + }); + + await executeAtomicAsyncTasks(testTasks); + }); + }); }); describe("tkey", function () { let tb;