diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 6517335f..90c9fcb2 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -66,6 +66,7 @@ import { lagrangeInterpolation, } from "./lagrangeInterpolatePolynomial"; import Metadata from "./metadata"; +import { eqSet, isSome } from "./util"; // TODO: handle errors for get and set with retries @@ -1216,7 +1217,41 @@ class ThresholdKey implements ITKey { } else { throw CoreError.default("can only add type ShareStore into shares"); } + + // get current metadata poly id + // const existingPolyShares = this.metadata.polyIDList; + // const existingPolyIDs = []; + // existingPolyShares.map((polyList) => existingPolyIDs.push(polyList[0])); + + // // get input metadata's poly id + // const newShareMetadata = await this.storageLayer.getMetadata({ privKey: ss.share.share as BN }); + // const parseNewMeta = Metadata.fromJSON(JSON.parse(stringify((newShareMetadata as any).data))) as Metadata; + // const newPolyIDs = []; + // const newShareIndexes = new Set(); + // parseNewMeta.polyIDList.map((polyList) => { + // newPolyIDs.push(polyList[0]); + // return polyList[1].forEach((item) => newShareIndexes.add(item)); + // }); + + // // check if poly id list is outdated + // if (existingPolyIDs.length !== newPolyIDs.length && isSome(existingPolyIDs, newPolyIDs)) { + // // catchup to latest tKey + // await this.updateSDK(); + // } else { + // const existingShareIndexes = new Set(); + // this.metadata.polyIDList.map((polyList) => polyList[1].forEach((item) => existingShareIndexes.add(item))); + + // // make sure share indexes of input share's metadata and existing are same + // if (!eqSet(newShareIndexes, existingShareIndexes)) { + // // throw error if share index is random + // throw CoreError.fromCode(1307); + // } + // } + const latestShareRes = await this.catchupToLatestShare({ shareStore: ss, includeLocalMetadataTransitions: true }); + if (!latestShareRes.shareMetadata.polyIDList.find((tuple) => tuple[0] === this.metadata.getLatestPublicPolynomial().getPolynomialID())) { + throw CoreError.fromCode(1307); + } // if not in poly id list, metadata is probably outdated // is !this.metadata.polyIDList.includes(latestShareRes.latestShare.polynomialID) if (!this.metadata.polyIDList.find((tuple) => tuple[0] === latestShareRes.latestShare.polynomialID)) { diff --git a/packages/core/src/util.ts b/packages/core/src/util.ts new file mode 100644 index 00000000..33f5c990 --- /dev/null +++ b/packages/core/src/util.ts @@ -0,0 +1,10 @@ +// compare two arrays of string without order +export function isSome(array1: string[], array2: string[]) { + return array1.some(function (element, index) { + return element === array2[index]; + }); +} + +export function eqSet(xs: Set, ys: Set) { + return xs.size === ys.size && [...xs].every((x) => ys.has(x)); +} diff --git a/packages/default/test/shared.js b/packages/default/test/shared.js index 3859729b..ac7d53aa 100644 --- a/packages/default/test/shared.js +++ b/packages/default/test/shared.js @@ -105,7 +105,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { const tb2 = new ThresholdKey({ serviceProvider: sp, storageLayer, manualSync: mode }); await tb2.initialize({ useTSS: true, factorPub }); - tb2.inputShareStore(newShare.newShareStores[newShare.newShareIndex.toString("hex")]); + 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(); @@ -739,7 +739,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { const tb2 = new ThresholdKey({ serviceProvider: customSP, storageLayer: customSL, manualSync: mode }); await tb2.initialize({ neverInitializeNewKey: true }); - tb2.inputShareStore(resp1.deviceShare); + await tb2.inputShareStoreSafe(resp1.deviceShare); const reconstructedKey = await tb2.reconstructKey(); if (resp1.privKey.cmp(reconstructedKey.privKey) !== 0) { fail("key should be able to be reconstructed"); @@ -753,7 +753,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { const tb2 = new ThresholdKey({ serviceProvider: customSP, storageLayer: customSL, manualSync: mode }); await tb2.initialize(); - tb2.inputShareStore(resp1.userShare); + await tb2.inputShareStoreSafe(resp1.userShare); const reconstructedKey = await tb2.reconstructKey(); // compareBNArray(resp1.privKey, reconstructedKey, "key should be able to be reconstructed"); if (resp1.privKey.cmp(reconstructedKey.privKey) !== 0) { @@ -767,12 +767,13 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { const tb2 = new ThresholdKey({ serviceProvider: customSP, storageLayer: customSL, manualSync: mode }); await tb2.initialize(); - tb2.inputShareStore(resp1.deviceShare); + await tb2.inputShareStoreSafe(resp1.deviceShare); const reconstructedKey = await tb2.reconstructKey(); if (importedKey.cmp(reconstructedKey.privKey) !== 0) { fail("key should be able to be reconstructed"); } }); + it(`#should be able to reconstruct key when initializing a with a share, manualSync=${mode}`, async function () { let userInput = new BN(keccak256("user answer blublu").slice(2), "hex"); userInput = userInput.umod(ecCurve.curve.n); @@ -781,7 +782,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { const tb2 = new ThresholdKey({ serviceProvider: customSP, storageLayer: customSL, manualSync: mode }); await tb2.initialize({ withShare: resp1.userShare }); - tb2.inputShareStore(resp1.deviceShare); + await tb2.inputShareStoreSafe(resp1.deviceShare); const reconstructedKey = await tb2.reconstructKey(); if (resp1.privKey.cmp(reconstructedKey.privKey) !== 0) { fail("key should be able to be reconstructed"); @@ -796,7 +797,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { const tb2 = new ThresholdKey({ serviceProvider: customSP, storageLayer: customSL, manualSync: mode }); await tb2.initialize({ withShare: resp1.userShare }); - tb2.inputShareStore(newShares.newShareStores[newShares.newShareIndex.toString("hex")]); + await tb2.inputShareStoreSafe(newShares.newShareStores[newShares.newShareIndex.toString("hex")]); const reconstructedKey = await tb2.reconstructKey(); // compareBNArray(resp1.privKey, reconstructedKey, "key should be able to be reconstructed"); if (resp1.privKey.cmp(reconstructedKey.privKey) !== 0) { @@ -812,7 +813,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { const tb2 = new ThresholdKey({ serviceProvider: customSP, storageLayer: customSL, manualSync: mode }); await tb2.initialize(); - tb2.inputShareStore(newShares.newShareStores[newShares.newShareIndex.toString("hex")]); + await tb2.inputShareStoreSafe(newShares.newShareStores[newShares.newShareIndex.toString("hex")]); const reconstructedKey = await tb2.reconstructKey(); if (resp1.privKey.cmp(reconstructedKey.privKey) !== 0) { fail("key should be able to be reconstructed"); @@ -828,7 +829,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { await tb.generateNewShare(); // generate new share to update metadata await tb.syncLocalMetadataTransitions(); - tb2.inputShareStore(resp1.deviceShare); + await tb2.inputShareStoreSafe(resp1.deviceShare, true); const reconstructedKey = await tb2.reconstructKey(); // reconstruct key with old metadata should work to poly if (resp1.privKey.cmp(reconstructedKey.privKey) !== 0) { fail("key should be able to be reconstructed"); @@ -848,7 +849,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { const tb2 = new ThresholdKey({ serviceProvider: customSP, storageLayer: customSL, manualSync: mode }); await tb2.initialize({ neverInitializeNewKey: true }); - tb2.inputShareStore(resp1.deviceShare); + await tb2.inputShareStoreSafe(resp1.deviceShare); const reconstructedKey = await tb2.reconstructKey(); const shareStore = tb2.outputShareStore(newShareIndex); strictEqual(newShareStores[newShareIndex.toString("hex")].share.share.toString("hex"), shareStore.share.share.toString("hex")); @@ -857,6 +858,88 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { fail("key should be able to be reconstructed"); } }); + it(`#should be able to insert shares from existing tkey using _initializeNewKey, manualSync=${mode}`, async function () { + const resp1 = await tb._initializeNewKey({ initializeModules: true }); + const { newShareStores: tbShareStore, newShareIndex: tbShareIndex } = await tb.generateNewShare(); + await tb.syncLocalMetadataTransitions(); + + const tb3 = new ThresholdKey({ serviceProvider: customSP, storageLayer: customSL, manualSync: mode }); + + await tb3.initialize({ neverInitializeNewKey: true }); + try { + await tb3.inputShareStoreSafe(resp1.deviceShare, true); + } catch (err) { + throw new Error(err); + } + const reconstructedKey = await tb3.reconstructKey(); + + if (resp1.privKey.cmp(reconstructedKey.privKey) !== 0) { + fail("key should be able to be reconstructed"); + } + const shareStore = tb3.outputShareStore(tbShareIndex); + strictEqual(tbShareStore[tbShareIndex.toString("hex")].share.share.toString("hex"), shareStore.share.share.toString("hex")); + }); + it(`#should be able to insert shares from existing tkey using new TKey Instance, manualSync=${mode}`, async function () { + const tb2 = new ThresholdKey({ serviceProvider: customSP, storageLayer: customSL, manualSync: mode }); + const resp2 = await tb2._initializeNewKey({ initializeModules: true }); + await tb2.syncLocalMetadataTransitions(); + + const tb3 = new ThresholdKey({ serviceProvider: customSP, storageLayer: customSL, manualSync: mode }); + + await tb3.initialize({ neverInitializeNewKey: true }); + await tb3.inputShareStoreSafe(resp2.deviceShare, true); + const reconstructedKey = await tb3.reconstructKey(); + if (resp2.privKey.cmp(reconstructedKey.privKey) !== 0) { + fail("key should be able to be reconstructed"); + } + }); + it(`#shouldn't be able to insert shares from random threshold key, manualSync=${mode}`, async function () { + // wrong tkey instance + const resp1 = await tb._initializeNewKey({ initializeModules: true }); + const { newShareStores: tbShareStore, newShareIndex: tbShareIndex } = await tb.generateNewShare(); + await tb.syncLocalMetadataTransitions(); + + // tkey instance with correct share stores and index + const tb2 = new ThresholdKey({ serviceProvider: customSP, storageLayer: customSL, manualSync: mode }); + const resp2 = await tb2._initializeNewKey({ initializeModules: true }); + await tb2.syncLocalMetadataTransitions(); + + const tb3 = new ThresholdKey({ serviceProvider: customSP, storageLayer: customSL, manualSync: mode }); + await tb3.initialize({ neverInitializeNewKey: true }); + await tb3.syncLocalMetadataTransitions(); + + // throws since share doesn't + await rejects( + async () => { + await tb3.inputShareStoreSafe(tbShareStore[tbShareIndex.toString("hex")], true); + }, + (err) => { + strictEqual(err.code, 1307, "CoreError: Share doesn't exist"); + return true; + } + ); + await rejects( + async () => { + await tb3.inputShareStoreSafe(resp1.deviceShare, true); + }, + (err) => { + strictEqual(err.code, 1307, "CoreError: Share doesn't exist"); + return true; + } + ); + // should be able to insert if correct share store and index + await tb3.inputShareStoreSafe(resp2.deviceShare, true); + await tb3.reconstructKey(); + const { newShareStores: tb3ShareStore, newShareIndex: tb3ShareIndex } = await tb3.generateNewShare(); + await tb3.syncLocalMetadataTransitions(); + await tb3.reconstructKey(); + + await tb2.inputShareStoreSafe(tb3ShareStore[tb3ShareIndex.toString("hex")], true); + const reconstructedKey2 = await tb2.reconstructKey(); + if (resp2.privKey.cmp(reconstructedKey2.privKey) !== 0) { + fail("key should be able to be reconstructed"); + } + }); it(`#should be able to update metadata, manualSync=${mode}`, async function () { const resp1 = await tb._initializeNewKey({ initializeModules: true }); await tb.syncLocalMetadataTransitions(); @@ -864,7 +947,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { const tb2 = new ThresholdKey({ serviceProvider: customSP, storageLayer: customSL, manualSync: mode }); await tb2.initialize(); - tb2.inputShareStore(resp1.deviceShare); + await tb2.inputShareStoreSafe(resp1.deviceShare); await tb2.reconstructKey(); // try creating new shares @@ -1066,7 +1149,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { await tb.syncLocalMetadataTransitions(); const tb3 = new ThresholdKey({ serviceProvider: customSP, storageLayer: customSL, manualSync: mode }); await tb3.initialize(); - tb3.inputShareStore(newShareStores[newShareIndex.toString("hex")]); + await tb3.inputShareStoreSafe(newShareStores[newShareIndex.toString("hex")]); const stringified = JSON.stringify(tb3); const tb4 = await ThresholdKey.fromJSON(JSON.parse(stringified), { serviceProvider: customSP, storageLayer: customSL, manualSync: mode }); @@ -1085,7 +1168,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { customSL2.serviceProvider = customSP3; const tb2 = new ThresholdKey({ serviceProvider: customSP2, storageLayer: customSL2, manualSync: mode }); await tb2.initialize({ withShare: resp1.deviceShare }); - tb2.inputShareStore(newShareStores1[newShareIndex1.toString("hex")]); + await tb2.inputShareStoreSafe(newShareStores1[newShareIndex1.toString("hex")]); await tb2.reconstructKey(); const stringified = JSON.stringify(tb2); @@ -1335,6 +1418,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { }); it(`#should be able to transfer share via the module, manualSync=${mode}`, async function () { const resp1 = await tb._initializeNewKey({ initializeModules: true }); + const result = await tb.generateNewShare(); await tb.syncLocalMetadataTransitions(); const tb2 = new ThresholdKey({ @@ -1348,8 +1432,6 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { // usually should be called in callback, but mocha does not allow const pubkey = await tb2.modules.shareTransfer.requestNewShare(); - const result = await tb.generateNewShare(); - await tb.modules.shareTransfer.approveRequest(pubkey, result.newShareStores[result.newShareIndex.toString("hex")]); await tb.syncLocalMetadataTransitions(); @@ -1544,7 +1626,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { modules: { seedPhrase: new SeedPhraseModule([metamaskSeedPhraseFormat2]) }, }); await tb2.initialize(); - tb2.inputShareStore(resp1.deviceShare); + await tb2.inputShareStoreSafe(resp1.deviceShare); const reconstuctedKey = await tb2.reconstructKey(); await tb.modules.seedPhrase.getSeedPhrasesWithAccounts(); @@ -1699,7 +1781,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { modules: { seedPhrase: new SeedPhraseModule([metamaskSeedPhraseFormat2]), privateKeyModule: new PrivateKeyModule([secp256k1Format]) }, }); await tb2.initialize(); - tb2.inputShareStore(resp1.deviceShare); + await tb2.inputShareStoreSafe(resp1.deviceShare); const reconstructedKey = await tb2.reconstructKey(); compareReconstructedKeys(reconstructedKey, { @@ -1737,7 +1819,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { const tb2 = new ThresholdKey({ serviceProvider: customSP, manualSync: mode, storageLayer: customSL }); await tb2.initialize(); - tb2.inputShareStore(resp1.deviceShare); + await tb2.inputShareStoreSafe(resp1.deviceShare); const reconstructedKey = await tb2.reconstructKey(); if (resp1.privKey.cmp(reconstructedKey.privKey) !== 0) { fail("key should be able to be reconstructed"); @@ -1764,7 +1846,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { const tb2 = new ThresholdKey({ serviceProvider: customSP, manualSync: mode, storageLayer: customSL }); await tb2.initialize(); - tb2.inputShareStore(resp1.deviceShare); + await tb2.inputShareStoreSafe(resp1.deviceShare); const reconstructedKey = await tb2.reconstructKey(); if (resp1.privKey.cmp(reconstructedKey.privKey) !== 0) { fail("key should be able to be reconstructed"); @@ -1774,7 +1856,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { for (let i = 0; i < 5; i += 1) { const temp = new ThresholdKey({ serviceProvider: customSP, manualSync: mode, storageLayer: customSL }); await temp.initialize(); - temp.inputShareStore(resp1.deviceShare); + await temp.inputShareStoreSafe(resp1.deviceShare); await temp.reconstructKey(); alltbs.push(temp); } @@ -1919,7 +2001,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { it(`#should throw error code 1103 if metadata post failed, in manualSync: ${mode}`, async function () { const tb2 = new ThresholdKey({ serviceProvider: customSP, storageLayer: customSL, manualSync: mode }); await tb2.initialize({ neverInitializeNewKey: true }); - tb2.inputShareStore(resp1.deviceShare); + await tb2.inputShareStoreSafe(resp1.deviceShare); await tb2.reconstructKey(); await tb2.syncLocalMetadataTransitions(); sandbox.stub(tb2.storageLayer, "setMetadataStream").throws(new Error("failed to set metadata"));