diff --git a/packages/common-types/src/baseTypes/aggregateTypes.ts b/packages/common-types/src/baseTypes/aggregateTypes.ts index 448e9ad69..54987c5b5 100644 --- a/packages/common-types/src/baseTypes/aggregateTypes.ts +++ b/packages/common-types/src/baseTypes/aggregateTypes.ts @@ -81,6 +81,7 @@ export interface IMetadata extends ISerializable { addPublicShare(polynomialID: PolynomialID, publicShare: PublicShare): void; setGeneralStoreDomain(key: string, obj: unknown): void; getGeneralStoreDomain(key: string): unknown; + deleteGeneralStoreDomain(key: string): unknown; setTkeyStoreDomain(key: string, arr: unknown): void; getTkeyStoreDomain(key: string): unknown; addFromPolynomialAndShares(polynomial: Polynomial, shares: Array | ShareMap): void; diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index c0c55d2a1..f98e77cb0 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -479,6 +479,7 @@ class ThresholdKey implements ITKey { const metadataToPush = Array(sharesToPush.length).fill(m); // run refreshShare middleware + // If a shareIndex is left out during refresh shares, we assume that it being explicitly deleted. for (const moduleName in this._refreshMiddleware) { if (Object.prototype.hasOwnProperty.call(this._refreshMiddleware, moduleName)) { const adjustedGeneralStore = this._refreshMiddleware[moduleName]( @@ -486,7 +487,8 @@ class ThresholdKey implements ITKey { oldShareStores, newShareStores ); - this.metadata.setGeneralStoreDomain(moduleName, adjustedGeneralStore); + if (!adjustedGeneralStore) this.metadata.deleteGeneralStoreDomain(moduleName); + else this.metadata.setGeneralStoreDomain(moduleName, adjustedGeneralStore); } } diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index d4e00d1cc..4d2147bd1 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -93,6 +93,10 @@ class Metadata implements IMetadata { return this.generalStore[key]; } + deleteGeneralStoreDomain(key: string): void { + delete this.generalStore[key]; + } + setTkeyStoreDomain(key: string, arr: unknown): void { this.tkeyStore[key] = arr; } diff --git a/packages/default/test/test.js b/packages/default/test/test.js index 7b0f5fcf0..0392cff01 100644 --- a/packages/default/test/test.js +++ b/packages/default/test/test.js @@ -479,7 +479,7 @@ manualSyncModes.forEach((mode) => { manualSync: mode, }); }); - it(`#should be able to reconstruct key and initialize a key with security questions, metadataSync=${mode}`, async function () { + it(`#should be able to reconstruct key and initialize a key with security questions, manualSync=${mode}`, async function () { const resp1 = await tb._initializeNewKey({ initializeModules: true }); await rejects(async function () { await tb.modules.securityQuestions.inputShareFromSecurityQuestions("blublu"); @@ -507,7 +507,34 @@ manualSyncModes.forEach((mode) => { fail("key should be able to be reconstructed"); } }); - it(`#should be able to reconstruct key and initialize a key with security questions after refresh, metadataSync=${mode}`, async function () { + it(`#should be able to delete and add security questions, manualSync=${mode}`, async function () { + const resp1 = await tb._initializeNewKey({ initializeModules: true }); + await tb.modules.securityQuestions.generateNewShareWithSecurityQuestions("blublu", "who is your cat?"); + await tb.generateNewShare(); + await tb.syncLocalMetadataTransitions(); + + // delete sq + const sqIndex = tb.metadata.generalStore.securityQuestions.shareIndex; + await tb.deleteShare(sqIndex); + + // add sq again + await tb.modules.securityQuestions.generateNewShareWithSecurityQuestions("blubluss", "who is your cat?"); + await tb.syncLocalMetadataTransitions(); + + const tb2 = new ThresholdKey({ + serviceProvider: defaultSP, + storageLayer: defaultSL, + modules: { securityQuestions: new SecurityQuestionsModule() }, + }); + await tb2.initialize(); + + await tb2.modules.securityQuestions.inputShareFromSecurityQuestions("blubluss"); + const reconstructedKey = await tb2.reconstructKey(); + if (resp1.privKey.cmp(reconstructedKey.privKey) !== 0) { + fail("key should be able to be reconstructed"); + } + }); + it(`#should be able to reconstruct key and initialize a key with security questions after refresh, manualSync=${mode}`, async function () { const resp1 = await tb._initializeNewKey({ initializeModules: true }); await tb.modules.securityQuestions.generateNewShareWithSecurityQuestions("blublu", "who is your cat?"); const tb2 = new ThresholdKey({ @@ -527,7 +554,7 @@ manualSyncModes.forEach((mode) => { fail("key should be able to be reconstructed"); } }); - it(`#should be able to change password, metadataSync=${mode}`, async function () { + it(`#should be able to change password, manualSync=${mode}`, async function () { const resp1 = await tb._initializeNewKey({ initializeModules: true }); // should throw @@ -553,7 +580,7 @@ manualSyncModes.forEach((mode) => { fail("key should be able to be reconstructed"); } }); - it(`#should be able to change password and serialize, metadataSync=${mode}`, async function () { + it(`#should be able to change password and serialize, manualSync=${mode}`, async function () { const resp1 = await tb._initializeNewKey({ initializeModules: true }); await tb.modules.securityQuestions.generateNewShareWithSecurityQuestions("blublu", "who is your cat?"); await tb.modules.securityQuestions.changeSecurityQuestionAndAnswer("dodo", "who is your cat?"); @@ -578,7 +605,7 @@ manualSyncModes.forEach((mode) => { const finalKeyPostSerialization = await tb4.reconstructKey(); strictEqual(finalKeyPostSerialization.toString("hex"), reconstructedKey.toString("hex"), "Incorrect serialization"); }); - it(`#should be able to get answers, even when they change, metadataSync=${mode}`, async function () { + it(`#should be able to get answers, even when they change, manualSync=${mode}`, async function () { tb = new ThresholdKey({ serviceProvider: defaultSP, storageLayer: defaultSL, diff --git a/packages/security-questions/src/SecurityQuestionsModule.ts b/packages/security-questions/src/SecurityQuestionsModule.ts index 7edf1dbfa..59e56b116 100644 --- a/packages/security-questions/src/SecurityQuestionsModule.ts +++ b/packages/security-questions/src/SecurityQuestionsModule.ts @@ -129,17 +129,23 @@ class SecurityQuestionsModule implements IModule { return generalStore; } const sqStore = new SecurityQuestionStore(generalStore as SecurityQuestionStoreArgs); - const sqAnswer = oldShareStores[sqStore.shareIndex.toString("hex")].share.share.sub(sqStore.nonce); - let newNonce = newShareStores[sqStore.shareIndex.toString("hex")].share.share.sub(sqAnswer); - newNonce = newNonce.umod(ecCurve.curve.n); - - return new SecurityQuestionStore({ - nonce: newNonce, - polynomialID: newShareStores[Object.keys(newShareStores)[0]].polynomialID, - sqPublicShare: newShareStores[sqStore.shareIndex.toString("hex")].share.getPublicShare(), - shareIndex: sqStore.shareIndex, - questions: sqStore.questions, - }); + const sqIndex = sqStore.shareIndex.toString("hex"); + + // Assumption: If sqIndex doesn't exist, it must have been explicitly deleted. + if (oldShareStores[sqIndex] && newShareStores[sqIndex]) { + const sqAnswer = oldShareStores[sqIndex].share.share.sub(sqStore.nonce); + let newNonce = newShareStores[sqIndex].share.share.sub(sqAnswer); + newNonce = newNonce.umod(ecCurve.curve.n); + + return new SecurityQuestionStore({ + nonce: newNonce, + polynomialID: newShareStores[Object.keys(newShareStores)[0]].polynomialID, + sqPublicShare: newShareStores[sqIndex].share.getPublicShare(), + shareIndex: sqStore.shareIndex, + questions: sqStore.questions, + }); + } + return undefined; } async saveAnswerOnTkeyStore(answerString: string): Promise {