From 34885fafef7b05bcb1a96d1f604ae80fd5d066b2 Mon Sep 17 00:00:00 2001 From: ieow Date: Mon, 26 Feb 2024 14:31:16 +0800 Subject: [PATCH 01/10] fix: make threshold in option optional --- packages/core/src/core.ts | 2 +- .../default/test/RefreshAndAccountIndex.js | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index c6829684..2b5ea3e5 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -394,7 +394,7 @@ class ThresholdKey implements ITKey { * getTSSShare accepts a factorKey and returns the TSS share based on the factor encrypted TSS shares in the metadata * @param factorKey - factor key */ - async getTSSShare(factorKey: BN, opts?: { threshold: number; accountIndex?: number }): Promise<{ tssIndex: number; tssShare: BN }> { + async getTSSShare(factorKey: BN, opts?: { threshold?: number; accountIndex?: number }): Promise<{ tssIndex: number; tssShare: BN }> { if (!this.privKey) throw CoreError.default("tss share cannot be returned until you've reconstructed tkey"); const factorPub = getPubKeyPoint(factorKey); const factorEncs = this.getFactorEncs(factorPub); diff --git a/packages/default/test/RefreshAndAccountIndex.js b/packages/default/test/RefreshAndAccountIndex.js index f9b652f0..7854f873 100644 --- a/packages/default/test/RefreshAndAccountIndex.js +++ b/packages/default/test/RefreshAndAccountIndex.js @@ -1,7 +1,7 @@ import { ecCurve, getPubKeyPoint } from "@tkey-mpc/common-types"; // eslint-disable-next-line import/no-extraneous-dependencies import { generatePrivate } from "@toruslabs/eccrypto"; -import { fail, notEqual, rejects, strictEqual } from "assert"; +import assert, { fail, notEqual, rejects, strictEqual } from "assert"; import BN from "bn.js"; import ThresholdKey from "../src/index"; @@ -131,6 +131,22 @@ export const refreshAndAccountIndex = (customSP, manualSync, accountIndexBackwar authSignatures: signatures, }); + { + // ensure tssShare is the master share + const { tssShare } = await tb2.getTSSShare(factorKey); + const { tssShare: tssShareIndex1 } = await tb2.getTSSShare(factorKey, { accountIndex: 1 }); + const { tssShare: tssShareIndex2 } = await tb2.getTSSShare(factorKey, { accountIndex: 2 }); + const { tssShare: tssShareIndex0 } = await tb2.getTSSShare(factorKey, { accountIndex: 0 }); + + const nonce1 = tb2.computeAccountNonce(1); + const nonce2 = tb2.computeAccountNonce(2); + + assert(tb2.computeAccountNonce(0).eq(new BN(0)), "nonce for account 0 should be 0"); + assert(tssShare.eq(tssShareIndex0), "tssShareIndex0 should be equal to tssShare"); + assert(tssShare.add(nonce1).umod(ecCurve.n).eq(tssShareIndex1), "tssShareIndex1 should be equal to tssShare + nonce1"); + assert(tssShare.add(nonce2).umod(ecCurve.n).eq(tssShareIndex2), "tssShareIndex2 should be equal to tssShare + nonce2"); + assert(!tssShareIndex1.add(nonce1).umod(ecCurve.n).eq(tssShareIndex2), "tssShareIndex2 should not be equal to tssShareIndex1 + nonce1"); + } // test case to ensure nonce mechanism { // make sure derived pub key is different from the index 0 key From 7bf8e53568187c1a5a29539b45d987c19505b472 Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 27 Feb 2024 12:34:44 +0800 Subject: [PATCH 02/10] fix: not use tkeyStore --- packages/core/src/core.ts | 58 ++++++++++++++++++++++------------- packages/core/src/metadata.ts | 6 +++- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 2b5ea3e5..b1bf84c2 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -69,7 +69,7 @@ import { } from "./lagrangeInterpolatePolynomial"; import Metadata from "./metadata"; // TODO: handle errors for get and set with retries -export const TSS_MODULE = "tssModule"; +// export const TSS_MODULE = "tssModule"; class ThresholdKey implements ITKey { modules: ModuleMap; @@ -304,12 +304,13 @@ class ThresholdKey implements ITKey { }); if (useTSS) { const { factorEncs, factorPubs, tssPolyCommits } = await this._initializeNewTSSKey(this.tssTag, deviceTSSShare, factorPub, deviceTSSIndex); - const accountSalt = generateSalt(); - await this._setTKeyStoreItem(TSS_MODULE, { - id: "accountSalt", - value: accountSalt, - }); - this.metadata.addTSSData({ tssTag: this.tssTag, tssNonce: 0, tssPolyCommits, factorPubs, factorEncs }); + + const { encryptedSalt } = await this.getSaltAndEncrypted(this.privKey); + this.metadata.addTSSData({ tssTag: this.tssTag, tssNonce: 0, tssPolyCommits, factorPubs, factorEncs, encryptedSalt }); + // await this._setTKeyStoreItem(TSS_MODULE, { + // id: "accountSalt", + // value: accountSalt, + // }); } return this.getKeyDetails(); } @@ -624,16 +625,14 @@ class ThresholdKey implements ITKey { // only valid for use Tss // assign account salt from tKey store if it exists if (Object.keys(this.metadata.tssPolyCommits).length > 0) { - const accountSalt = await this.getTKeyStoreItem(TSS_MODULE, "accountSalt"); - if (accountSalt && accountSalt?.value) { - this._accountSalt = accountSalt.value; + const accountSalt = await this.getAccountSalt(); + + if (accountSalt) { + this._accountSalt = accountSalt; } else { - const newSalt = generateSalt(); - await this._setTKeyStoreItem(TSS_MODULE, { - id: "accountSalt", - value: newSalt, - }); - this._accountSalt = newSalt; + const { salt, encryptedSalt } = await this.getSaltAndEncrypted(privKey); + this.metadata.addTSSData({ tssTag: this.tssTag, encryptedSalt }); + this._accountSalt = salt; } } @@ -920,17 +919,14 @@ class ThresholdKey implements ITKey { serverEncs: refreshResponse.serverFactorEncs, }; } - const accountSalt = generateSalt(); + const { encryptedSalt } = await this.getSaltAndEncrypted(this.privKey); this.metadata.addTSSData({ tssTag: this.tssTag, tssNonce: newTssNonce, tssPolyCommits: newTSSCommits, factorPubs, factorEncs, - }); - await this._setTKeyStoreItem(TSS_MODULE, { - id: "accountSalt", - value: accountSalt, + encryptedSalt, }); } catch (error) { this.tssTag = oldTag; @@ -2026,6 +2022,26 @@ class ThresholdKey implements ITKey { private async initializeModules() { return Promise.all(Object.keys(this.modules).map((x) => this.modules[x].initialize())); } + + // + private async getSaltAndEncrypted(privKey: BN): Promise<{ salt: string; encryptedSalt: EncryptedMessage }> { + if (!privKey) { + throw CoreError.privateKeyUnavailable(); + } + + const salt = generateSalt(); + const encryptedSalt = await encrypt(getPubKeyECC(privKey), Buffer.from(salt, "hex")); + return { salt, encryptedSalt }; + } + + private async getAccountSalt() { + if (this._accountSalt) return this._accountSalt; + if (this.metadata.encryptedSalt) { + const decrypteSalt = await decrypt(this.privKey.toBuffer(), this.metadata.encryptedSalt); + return decrypteSalt.toString("hex"); + } + return undefined; + } } export default ThresholdKey; diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index 2c1cde09..8a8b63d6 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -68,6 +68,8 @@ class Metadata implements IMetadata { }; }; + encryptedSalt?: EncryptedMessage; + constructor(input: Point) { this.tssPolyCommits = {}; this.tssNonces = {}; @@ -189,12 +191,14 @@ class Metadata implements IMetadata { factorEncs?: { [factorPubID: string]: FactorEnc; }; + encryptedSalt?: EncryptedMessage; }): void { - const { tssTag, tssNonce, tssPolyCommits, factorPubs, factorEncs } = tssData; + const { tssTag, tssNonce, tssPolyCommits, factorPubs, factorEncs, encryptedSalt } = tssData; if (tssNonce !== undefined) this.tssNonces[tssTag] = tssNonce; if (tssPolyCommits) this.tssPolyCommits[tssTag] = tssPolyCommits; if (factorPubs) this.factorPubs[tssTag] = factorPubs; if (factorEncs) this.factorEncs[tssTag] = factorEncs; + if (encryptedSalt && !this.encryptedSalt) this.encryptedSalt = encryptedSalt; } // appends shares and public polynomial to metadata. From b83566c1936a49028d6779c531ee8cfba98eff66 Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 27 Feb 2024 13:28:18 +0800 Subject: [PATCH 03/10] fix: metadata for salt --- packages/core/src/core.ts | 16 +++++++++------- packages/core/src/metadata.ts | 11 ++++++++--- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index b1bf84c2..864ca64b 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -305,8 +305,9 @@ class ThresholdKey implements ITKey { if (useTSS) { const { factorEncs, factorPubs, tssPolyCommits } = await this._initializeNewTSSKey(this.tssTag, deviceTSSShare, factorPub, deviceTSSIndex); - const { encryptedSalt } = await this.getSaltAndEncrypted(this.privKey); + const { salt, encryptedSalt } = await this.generateSaltAndEncrypted(this.privKey); this.metadata.addTSSData({ tssTag: this.tssTag, tssNonce: 0, tssPolyCommits, factorPubs, factorEncs, encryptedSalt }); + this._accountSalt = salt; // await this._setTKeyStoreItem(TSS_MODULE, { // id: "accountSalt", // value: accountSalt, @@ -630,7 +631,7 @@ class ThresholdKey implements ITKey { if (accountSalt) { this._accountSalt = accountSalt; } else { - const { salt, encryptedSalt } = await this.getSaltAndEncrypted(privKey); + const { salt, encryptedSalt } = await this.generateSaltAndEncrypted(privKey); this.metadata.addTSSData({ tssTag: this.tssTag, encryptedSalt }); this._accountSalt = salt; } @@ -919,7 +920,7 @@ class ThresholdKey implements ITKey { serverEncs: refreshResponse.serverFactorEncs, }; } - const { encryptedSalt } = await this.getSaltAndEncrypted(this.privKey); + const { salt, encryptedSalt } = await this.generateSaltAndEncrypted(this.privKey); this.metadata.addTSSData({ tssTag: this.tssTag, tssNonce: newTssNonce, @@ -928,6 +929,7 @@ class ThresholdKey implements ITKey { factorEncs, encryptedSalt, }); + this._accountSalt = salt; } catch (error) { this.tssTag = oldTag; throw error; @@ -2024,7 +2026,7 @@ class ThresholdKey implements ITKey { } // - private async getSaltAndEncrypted(privKey: BN): Promise<{ salt: string; encryptedSalt: EncryptedMessage }> { + private async generateSaltAndEncrypted(privKey: BN): Promise<{ salt: string; encryptedSalt: EncryptedMessage }> { if (!privKey) { throw CoreError.privateKeyUnavailable(); } @@ -2036,9 +2038,9 @@ class ThresholdKey implements ITKey { private async getAccountSalt() { if (this._accountSalt) return this._accountSalt; - if (this.metadata.encryptedSalt) { - const decrypteSalt = await decrypt(this.privKey.toBuffer(), this.metadata.encryptedSalt); - return decrypteSalt.toString("hex"); + if (Object.keys(this.metadata.encryptedSalt).length) { + const decryptedSalt = await decrypt(this.privKey.toBuffer(), this.metadata.encryptedSalt as EncryptedMessage); + return decryptedSalt.toString("hex"); } return undefined; } diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index 8a8b63d6..11f16c2d 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -68,13 +68,14 @@ class Metadata implements IMetadata { }; }; - encryptedSalt?: EncryptedMessage; + encryptedSalt?: EncryptedMessage | Record; constructor(input: Point) { this.tssPolyCommits = {}; this.tssNonces = {}; this.factorPubs = {}; this.factorEncs = {}; + this.encryptedSalt = {}; this.publicPolynomials = {}; this.publicShares = {}; this.generalStore = {}; @@ -86,7 +87,8 @@ class Metadata implements IMetadata { } static fromJSON(value: StringifiedType): Metadata { - const { pubKey, polyIDList, generalStore, tkeyStore, scopedStore, nonce, tssNonces, tssPolyCommits, factorPubs, factorEncs } = value; + const { pubKey, polyIDList, generalStore, tkeyStore, scopedStore, nonce, tssNonces, tssPolyCommits, factorPubs, factorEncs, encryptedSalt } = + value; const point = Point.fromCompressedPub(pubKey); const metadata = new Metadata(point); const unserializedPolyIDList: PolyIDAndShares[] = []; @@ -115,6 +117,8 @@ class Metadata implements IMetadata { } if (factorEncs) metadata.factorEncs = factorEncs; + if (encryptedSalt) metadata.encryptedSalt = encryptedSalt; + for (let i = 0; i < polyIDList.length; i += 1) { const serializedPolyID: string = polyIDList[i]; const arrPolyID = serializedPolyID.split("|"); @@ -198,7 +202,7 @@ class Metadata implements IMetadata { if (tssPolyCommits) this.tssPolyCommits[tssTag] = tssPolyCommits; if (factorPubs) this.factorPubs[tssTag] = factorPubs; if (factorEncs) this.factorEncs[tssTag] = factorEncs; - if (encryptedSalt && !this.encryptedSalt) this.encryptedSalt = encryptedSalt; + if (encryptedSalt && Object.keys(this.encryptedSalt).length === 0) this.encryptedSalt = encryptedSalt; } // appends shares and public polynomial to metadata. @@ -342,6 +346,7 @@ class Metadata implements IMetadata { ...(this.tssPolyCommits && { tssPolyCommits: this.tssPolyCommits }), ...(this.factorPubs && { factorPubs: this.factorPubs }), ...(this.factorEncs && { factorEncs: this.factorEncs }), + ...(this.encryptedSalt && { encryptedSalt: this.encryptedSalt }), }; } } From 59d6c7a951d7069ad291a1eaa6014a8d4a76f41a Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 27 Feb 2024 13:54:17 +0800 Subject: [PATCH 04/10] fix: salt and importkey --- packages/core/src/core.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 864ca64b..8abeda67 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -835,7 +835,8 @@ class ThresholdKey implements ITKey { serverOpts: { selectedServers?: number[]; authSignatures: string[]; - } + }, + syncMetdata: boolean = true ): Promise { const oldTag = this.tssTag; try { @@ -920,16 +921,23 @@ class ThresholdKey implements ITKey { serverEncs: refreshResponse.serverFactorEncs, }; } - const { salt, encryptedSalt } = await this.generateSaltAndEncrypted(this.privKey); + let accountSalt = await this.getAccountSalt(); + let encryptedAccountSalt; + if (!accountSalt) { + const { salt, encryptedSalt } = await this.generateSaltAndEncrypted(this.privKey); + accountSalt = salt; + encryptedAccountSalt = encryptedSalt; + } this.metadata.addTSSData({ tssTag: this.tssTag, tssNonce: newTssNonce, tssPolyCommits: newTSSCommits, factorPubs, factorEncs, - encryptedSalt, + encryptedSalt: encryptedAccountSalt, }); - this._accountSalt = salt; + this._accountSalt = accountSalt; + if (syncMetdata) await this._syncShareMetadata(); } catch (error) { this.tssTag = oldTag; throw error; From d594d8149ebc7e46778931eacdda892d24f7c967 Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 27 Feb 2024 17:46:38 +0800 Subject: [PATCH 05/10] fix: need syncMetadata on back filling --- packages/core/src/core.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 8abeda67..b3915875 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -634,6 +634,7 @@ class ThresholdKey implements ITKey { const { salt, encryptedSalt } = await this.generateSaltAndEncrypted(privKey); this.metadata.addTSSData({ tssTag: this.tssTag, encryptedSalt }); this._accountSalt = salt; + await this._syncShareMetadata(); } } From 24cd08426f9a8bb5aec1d5bb6db6e23dc7f5df3e Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 28 Feb 2024 10:50:43 +0800 Subject: [PATCH 06/10] fix: add tests for existing account scenario fix force sync for existing account scenario --- packages/core/src/core.ts | 22 ++-- .../default/test/RefreshAndAccountIndex.js | 102 ++++++++++++++++-- 2 files changed, 107 insertions(+), 17 deletions(-) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index b3915875..d3196832 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -305,13 +305,10 @@ class ThresholdKey implements ITKey { if (useTSS) { const { factorEncs, factorPubs, tssPolyCommits } = await this._initializeNewTSSKey(this.tssTag, deviceTSSShare, factorPub, deviceTSSIndex); - const { salt, encryptedSalt } = await this.generateSaltAndEncrypted(this.privKey); + const { encryptedSalt } = await this.generateSaltAndEncrypted(this.privKey); this.metadata.addTSSData({ tssTag: this.tssTag, tssNonce: 0, tssPolyCommits, factorPubs, factorEncs, encryptedSalt }); + const salt = await this.getAccountSalt(this.privKey); this._accountSalt = salt; - // await this._setTKeyStoreItem(TSS_MODULE, { - // id: "accountSalt", - // value: accountSalt, - // }); } return this.getKeyDetails(); } @@ -626,7 +623,7 @@ class ThresholdKey implements ITKey { // only valid for use Tss // assign account salt from tKey store if it exists if (Object.keys(this.metadata.tssPolyCommits).length > 0) { - const accountSalt = await this.getAccountSalt(); + const accountSalt = await this.getAccountSalt(privKey); if (accountSalt) { this._accountSalt = accountSalt; @@ -635,6 +632,11 @@ class ThresholdKey implements ITKey { this.metadata.addTSSData({ tssTag: this.tssTag, encryptedSalt }); this._accountSalt = salt; await this._syncShareMetadata(); + // this is very specific case where exisiting user do not have salt. + // sync metadata to cloud to ensure salt is stored incase of manual sync mode + // new user or importKey should not hit this cases + // NOTE this is not mistake, we force sync for this case + if (this.manualSync) await this.syncLocalMetadataTransitions(); } } @@ -839,6 +841,8 @@ class ThresholdKey implements ITKey { }, syncMetdata: boolean = true ): Promise { + if (!this.privKey) throw CoreError.privateKeyUnavailable(); + const oldTag = this.tssTag; try { const { importKey, factorPub, newTSSIndex, tag } = params; @@ -922,7 +926,7 @@ class ThresholdKey implements ITKey { serverEncs: refreshResponse.serverFactorEncs, }; } - let accountSalt = await this.getAccountSalt(); + let accountSalt = await this.getAccountSalt(this.privKey); let encryptedAccountSalt; if (!accountSalt) { const { salt, encryptedSalt } = await this.generateSaltAndEncrypted(this.privKey); @@ -2045,10 +2049,10 @@ class ThresholdKey implements ITKey { return { salt, encryptedSalt }; } - private async getAccountSalt() { + private async getAccountSalt(privKey: BNString): Promise { if (this._accountSalt) return this._accountSalt; if (Object.keys(this.metadata.encryptedSalt).length) { - const decryptedSalt = await decrypt(this.privKey.toBuffer(), this.metadata.encryptedSalt as EncryptedMessage); + const decryptedSalt = await decrypt(toPrivKeyECC(privKey), this.metadata.encryptedSalt as EncryptedMessage); return decryptedSalt.toString("hex"); } return undefined; diff --git a/packages/default/test/RefreshAndAccountIndex.js b/packages/default/test/RefreshAndAccountIndex.js index 7854f873..7cac0d98 100644 --- a/packages/default/test/RefreshAndAccountIndex.js +++ b/packages/default/test/RefreshAndAccountIndex.js @@ -7,14 +7,98 @@ import BN from "bn.js"; import ThresholdKey from "../src/index"; import { assignTssDkgKeys, computeIndexedPrivateKey, fetchPostboxKeyAndSigs, getMetadataUrl, initStorageLayer } from "./helpers"; -const TSS_MODULE = "tssModule"; const metadataURL = getMetadataUrl(); // eslint-disable-next-line mocha/no-exports -export const refreshAndAccountIndex = (customSP, manualSync, accountIndexBackwardCompatible) => { +export const refreshAndAccountIndex = (customSP, manualSync, resetAccountSalt) => { const mode = manualSync; const { useTSS } = customSP; - describe(`RefreshAndAccountIndex : useTss ${useTSS}, manualSync ${manualSync}, bcAccountIndex ${accountIndexBackwardCompatible}`, function () { + describe(`RefreshAndAccountIndex : useTss ${useTSS}, manualSync ${manualSync}, bcAccountIndex ${resetAccountSalt}`, function () { + it("#should be able to get same key each login using differnt accountIndex", 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 { postboxkey } = await fetchPostboxKeyAndSigs({ + serviceProvider: sp, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + }); + sp.postboxKey = postboxkey; + + const storageLayer = initStorageLayer({ hostUrl: metadataURL }); + const tb0 = new ThresholdKey({ serviceProvider: sp, storageLayer, manualSync: mode }); + + // accountSalt is absent, required for nonce generation + // can be only initialize with tkey.initialize(); + rejects(async () => { + tb0.computeAccountNonce(1); + }); + // factor key needs to passed from outside of tKey + const factorKey = new BN(generatePrivate()); + const factorPub = getPubKeyPoint(factorKey); + + await tb0.initialize({ useTSS: true, factorPub, deviceTSSShare, deviceTSSIndex }); + await tb0.reconstructKey(); + // Test for backward compatibility ( accountSalt is absent for old account ) + + const tss0ShareIndex0 = await tb0.getTSSShare(factorKey); + const tss0ShareIndex1 = await tb0.getTSSShare(factorKey, { accountIndex: 1 }); + const tss0ShareIndex2 = await tb0.getTSSShare(factorKey, { accountIndex: 2 }); + const tss0ShareIndex99 = await tb0.getTSSShare(factorKey, { accountIndex: 99 }); + if (resetAccountSalt) { + // eslint-disable-next-line require-atomic-updates + tb0.metadata.encryptedSalt = {}; + } + + const newShare = await tb0.generateNewShare(); + await tb0.syncLocalMetadataTransitions(); + + const tb1 = new ThresholdKey({ serviceProvider: sp, storageLayer, manualSync: mode }); + await tb1.initialize({ useTSS: true, factorPub, deviceTSSShare, deviceTSSIndex }); + await tb1.inputShareStoreSafe(newShare.newShareStores[newShare.newShareIndex.toString("hex")]); + await tb1.reconstructKey(); + + const tss1ShareIndex0 = await tb1.getTSSShare(factorKey); + const tss1ShareIndex1 = await tb1.getTSSShare(factorKey, { accountIndex: 1 }); + const tss1ShareIndex2 = await tb1.getTSSShare(factorKey, { accountIndex: 2 }); + const tss1ShareIndex99 = await tb1.getTSSShare(factorKey, { accountIndex: 99 }); + + const tb2 = new ThresholdKey({ serviceProvider: sp, storageLayer, manualSync: mode }); + await tb2.initialize({ useTSS: true, factorPub, deviceTSSShare, deviceTSSIndex }); + await tb2.inputShareStoreSafe(newShare.newShareStores[newShare.newShareIndex.toString("hex")]); + await tb2.reconstructKey(); + + const tss2ShareIndex0 = await tb2.getTSSShare(factorKey); + const tss2ShareIndex1 = await tb2.getTSSShare(factorKey, { accountIndex: 1 }); + const tss2ShareIndex2 = await tb2.getTSSShare(factorKey, { accountIndex: 2 }); + const tss2ShareIndex99 = await tb2.getTSSShare(factorKey, { accountIndex: 99 }); + + strictEqual(tss0ShareIndex0.tssShare.toString("hex"), tss1ShareIndex0.tssShare.toString("hex")); + + // expect different account address when accountSalt is reseted ( to support existing account that do not have this features) + if (resetAccountSalt) { + notEqual(tss0ShareIndex1.tssShare.toString("hex"), tss1ShareIndex1.tssShare.toString("hex")); + notEqual(tss0ShareIndex2.tssShare.toString("hex"), tss1ShareIndex2.tssShare.toString("hex")); + notEqual(tss0ShareIndex99.tssShare.toString("hex"), tss1ShareIndex99.tssShare.toString("hex")); + } else { + strictEqual(tss0ShareIndex1.tssShare.toString("hex"), tss1ShareIndex1.tssShare.toString("hex")); + strictEqual(tss0ShareIndex2.tssShare.toString("hex"), tss1ShareIndex2.tssShare.toString("hex")); + strictEqual(tss0ShareIndex99.tssShare.toString("hex"), tss1ShareIndex99.tssShare.toString("hex")); + } + + // expect same account address after relogin + strictEqual(tss1ShareIndex0.tssShare.toString("hex"), tss2ShareIndex0.tssShare.toString("hex")); + strictEqual(tss1ShareIndex1.tssShare.toString("hex"), tss2ShareIndex1.tssShare.toString("hex")); + strictEqual(tss1ShareIndex2.tssShare.toString("hex"), tss2ShareIndex2.tssShare.toString("hex")); + strictEqual(tss1ShareIndex99.tssShare.toString("hex"), tss2ShareIndex99.tssShare.toString("hex")); + }); + it("#should be able to refresh tss shares", async function () { const sp = customSP; if (!sp.useTSS) this.skip(); @@ -52,10 +136,11 @@ export const refreshAndAccountIndex = (customSP, manualSync, accountIndexBackwar await tb0.initialize({ useTSS: true, factorPub, deviceTSSShare, deviceTSSIndex }); await tb0.reconstructKey(); - // Test for backward compatibility ( accountSalt is absent for old account ) - if (accountIndexBackwardCompatible) { - await tb0._deleteTKeyStoreItem(TSS_MODULE, "accountSalt"); + + if (resetAccountSalt) { + // eslint-disable-next-line require-atomic-updates + tb0.metadata.encryptedSalt = {}; } const newShare = await tb0.generateNewShare(); @@ -92,8 +177,9 @@ export const refreshAndAccountIndex = (customSP, manualSync, accountIndexBackwar serverPubKeys, authSignatures: signatures, }); - await tb1.syncLocalMetadataTransitions(); - + if (manualSync) { + await tb1.syncLocalMetadataTransitions(); + } const tssPrivKeyIndex1 = await computeIndexedPrivateKey(tb1, factorKey, serverDKGPrivKeys[1], 1); const tssPrivKeyIndex2 = await computeIndexedPrivateKey(tb1, factorKey, serverDKGPrivKeys[1], 2); From ed8288794977371b8cd21fc83bd0645db3454787 Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 28 Feb 2024 10:54:44 +0800 Subject: [PATCH 07/10] fix: typo --- packages/core/src/core.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index d3196832..37ddeeec 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -839,7 +839,7 @@ class ThresholdKey implements ITKey { selectedServers?: number[]; authSignatures: string[]; }, - syncMetdata: boolean = true + updateMetadata: boolean = true ): Promise { if (!this.privKey) throw CoreError.privateKeyUnavailable(); @@ -942,7 +942,7 @@ class ThresholdKey implements ITKey { encryptedSalt: encryptedAccountSalt, }); this._accountSalt = accountSalt; - if (syncMetdata) await this._syncShareMetadata(); + if (updateMetadata) await this._syncShareMetadata(); } catch (error) { this.tssTag = oldTag; throw error; From f41f00b9fbb0b217231ef7657f69144bd38c29a2 Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 29 Feb 2024 16:20:12 +0800 Subject: [PATCH 08/10] fix: use tkeyStoreItem --- packages/core/src/core.ts | 96 ++++++++++--------- packages/core/src/metadata.ts | 13 +-- .../default/test/RefreshAndAccountIndex.js | 6 +- 3 files changed, 58 insertions(+), 57 deletions(-) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 37ddeeec..aaca1c3c 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -69,7 +69,8 @@ import { } from "./lagrangeInterpolatePolynomial"; import Metadata from "./metadata"; // TODO: handle errors for get and set with retries -// export const TSS_MODULE = "tssModule"; +export const TSS_MODULE = "tssModule"; +export const ACCOUNTSALT = "accountSalt"; class ThresholdKey implements ITKey { modules: ModuleMap; @@ -305,10 +306,13 @@ class ThresholdKey implements ITKey { if (useTSS) { const { factorEncs, factorPubs, tssPolyCommits } = await this._initializeNewTSSKey(this.tssTag, deviceTSSShare, factorPub, deviceTSSIndex); - const { encryptedSalt } = await this.generateSaltAndEncrypted(this.privKey); - this.metadata.addTSSData({ tssTag: this.tssTag, tssNonce: 0, tssPolyCommits, factorPubs, factorEncs, encryptedSalt }); - const salt = await this.getAccountSalt(this.privKey); - this._accountSalt = salt; + this.metadata.addTSSData({ tssTag: this.tssTag, tssNonce: 0, tssPolyCommits, factorPubs, factorEncs }); + const accountSalt = generateSalt(); + await this._setTKeyStoreItem(TSS_MODULE, { + id: "accountSalt", + value: accountSalt, + }); + this._accountSalt = accountSalt; } return this.getKeyDetails(); } @@ -623,15 +627,16 @@ class ThresholdKey implements ITKey { // only valid for use Tss // assign account salt from tKey store if it exists if (Object.keys(this.metadata.tssPolyCommits).length > 0) { - const accountSalt = await this.getAccountSalt(privKey); - - if (accountSalt) { - this._accountSalt = accountSalt; + const accountSalt = await this.getTKeyStoreItem(TSS_MODULE, "accountSalt"); + if (accountSalt && accountSalt.value) { + this._accountSalt = accountSalt.value; } else { - const { salt, encryptedSalt } = await this.generateSaltAndEncrypted(privKey); - this.metadata.addTSSData({ tssTag: this.tssTag, encryptedSalt }); - this._accountSalt = salt; - await this._syncShareMetadata(); + const newSalt = generateSalt(); + await this._setTKeyStoreItem(TSS_MODULE, { + id: "accountSalt", + value: newSalt, + }); + this._accountSalt = newSalt; // this is very specific case where exisiting user do not have salt. // sync metadata to cloud to ensure salt is stored incase of manual sync mode // new user or importKey should not hit this cases @@ -926,23 +931,28 @@ class ThresholdKey implements ITKey { serverEncs: refreshResponse.serverFactorEncs, }; } - let accountSalt = await this.getAccountSalt(this.privKey); - let encryptedAccountSalt; - if (!accountSalt) { - const { salt, encryptedSalt } = await this.generateSaltAndEncrypted(this.privKey); - accountSalt = salt; - encryptedAccountSalt = encryptedSalt; - } this.metadata.addTSSData({ tssTag: this.tssTag, tssNonce: newTssNonce, tssPolyCommits: newTSSCommits, factorPubs, factorEncs, - encryptedSalt: encryptedAccountSalt, }); - this._accountSalt = accountSalt; - if (updateMetadata) await this._syncShareMetadata(); + if (!this._accountSalt) { + const accountSalt = generateSalt(); + // tkeyStoreItem already syncMetadata + await this._setTKeyStoreItem( + TSS_MODULE, + { + id: "accountSalt", + value: accountSalt, + }, + updateMetadata + ); + this._accountSalt = accountSalt; + } else if (updateMetadata) { + await this._syncShareMetadata(); + } } catch (error) { this.tssTag = oldTag; throw error; @@ -1830,7 +1840,7 @@ class ThresholdKey implements ITKey { return decrypt(toPrivKeyECC(this.privKey), encryptedMessage); } - async _setTKeyStoreItem(moduleName: string, data: TkeyStoreItemType): Promise { + async _setTKeyStoreItem(moduleName: string, data: TkeyStoreItemType, updateMetadata: boolean = true): Promise { if (!this.metadata) { throw CoreError.metadataUndefined(); } @@ -1851,7 +1861,7 @@ class ThresholdKey implements ITKey { // update metadataStore this.metadata.setTkeyStoreDomain(moduleName, rawTkeyStoreItems); - await this._syncShareMetadata(); + if (updateMetadata) await this._syncShareMetadata(); } async _deleteTKeyStoreItem(moduleName: string, id: string): Promise { @@ -2039,24 +2049,24 @@ class ThresholdKey implements ITKey { } // - private async generateSaltAndEncrypted(privKey: BN): Promise<{ salt: string; encryptedSalt: EncryptedMessage }> { - if (!privKey) { - throw CoreError.privateKeyUnavailable(); - } - - const salt = generateSalt(); - const encryptedSalt = await encrypt(getPubKeyECC(privKey), Buffer.from(salt, "hex")); - return { salt, encryptedSalt }; - } - - private async getAccountSalt(privKey: BNString): Promise { - if (this._accountSalt) return this._accountSalt; - if (Object.keys(this.metadata.encryptedSalt).length) { - const decryptedSalt = await decrypt(toPrivKeyECC(privKey), this.metadata.encryptedSalt as EncryptedMessage); - return decryptedSalt.toString("hex"); - } - return undefined; - } + // private async generateSaltAndEncrypted(privKey: BN): Promise<{ salt: string; encryptedSalt: EncryptedMessage }> { + // if (!privKey) { + // throw CoreError.privateKeyUnavailable(); + // } + + // const salt = generateSalt(); + // const encryptedSalt = await encrypt(getPubKeyECC(privKey), Buffer.from(salt, "hex")); + // return { salt, encryptedSalt }; + // } + + // private async getAccountSalt(privKey: BNString): Promise { + // if (this._accountSalt) return this._accountSalt; + // if (Object.keys(this.metadata.encryptedSalt).length) { + // const decryptedSalt = await decrypt(toPrivKeyECC(privKey), this.metadata.encryptedSalt as EncryptedMessage); + // return decryptedSalt.toString("hex"); + // } + // return undefined; + // } } export default ThresholdKey; diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index 11f16c2d..2c1cde09 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -68,14 +68,11 @@ class Metadata implements IMetadata { }; }; - encryptedSalt?: EncryptedMessage | Record; - constructor(input: Point) { this.tssPolyCommits = {}; this.tssNonces = {}; this.factorPubs = {}; this.factorEncs = {}; - this.encryptedSalt = {}; this.publicPolynomials = {}; this.publicShares = {}; this.generalStore = {}; @@ -87,8 +84,7 @@ class Metadata implements IMetadata { } static fromJSON(value: StringifiedType): Metadata { - const { pubKey, polyIDList, generalStore, tkeyStore, scopedStore, nonce, tssNonces, tssPolyCommits, factorPubs, factorEncs, encryptedSalt } = - value; + const { pubKey, polyIDList, generalStore, tkeyStore, scopedStore, nonce, tssNonces, tssPolyCommits, factorPubs, factorEncs } = value; const point = Point.fromCompressedPub(pubKey); const metadata = new Metadata(point); const unserializedPolyIDList: PolyIDAndShares[] = []; @@ -117,8 +113,6 @@ class Metadata implements IMetadata { } if (factorEncs) metadata.factorEncs = factorEncs; - if (encryptedSalt) metadata.encryptedSalt = encryptedSalt; - for (let i = 0; i < polyIDList.length; i += 1) { const serializedPolyID: string = polyIDList[i]; const arrPolyID = serializedPolyID.split("|"); @@ -195,14 +189,12 @@ class Metadata implements IMetadata { factorEncs?: { [factorPubID: string]: FactorEnc; }; - encryptedSalt?: EncryptedMessage; }): void { - const { tssTag, tssNonce, tssPolyCommits, factorPubs, factorEncs, encryptedSalt } = tssData; + const { tssTag, tssNonce, tssPolyCommits, factorPubs, factorEncs } = tssData; if (tssNonce !== undefined) this.tssNonces[tssTag] = tssNonce; if (tssPolyCommits) this.tssPolyCommits[tssTag] = tssPolyCommits; if (factorPubs) this.factorPubs[tssTag] = factorPubs; if (factorEncs) this.factorEncs[tssTag] = factorEncs; - if (encryptedSalt && Object.keys(this.encryptedSalt).length === 0) this.encryptedSalt = encryptedSalt; } // appends shares and public polynomial to metadata. @@ -346,7 +338,6 @@ class Metadata implements IMetadata { ...(this.tssPolyCommits && { tssPolyCommits: this.tssPolyCommits }), ...(this.factorPubs && { factorPubs: this.factorPubs }), ...(this.factorEncs && { factorEncs: this.factorEncs }), - ...(this.encryptedSalt && { encryptedSalt: this.encryptedSalt }), }; } } diff --git a/packages/default/test/RefreshAndAccountIndex.js b/packages/default/test/RefreshAndAccountIndex.js index 7cac0d98..c297597d 100644 --- a/packages/default/test/RefreshAndAccountIndex.js +++ b/packages/default/test/RefreshAndAccountIndex.js @@ -8,7 +8,7 @@ import ThresholdKey from "../src/index"; import { assignTssDkgKeys, computeIndexedPrivateKey, fetchPostboxKeyAndSigs, getMetadataUrl, initStorageLayer } from "./helpers"; const metadataURL = getMetadataUrl(); - +const TSS_MODULE = "tssModule"; // eslint-disable-next-line mocha/no-exports export const refreshAndAccountIndex = (customSP, manualSync, resetAccountSalt) => { const mode = manualSync; @@ -52,8 +52,8 @@ export const refreshAndAccountIndex = (customSP, manualSync, resetAccountSalt) = const tss0ShareIndex2 = await tb0.getTSSShare(factorKey, { accountIndex: 2 }); const tss0ShareIndex99 = await tb0.getTSSShare(factorKey, { accountIndex: 99 }); if (resetAccountSalt) { - // eslint-disable-next-line require-atomic-updates - tb0.metadata.encryptedSalt = {}; + // tb0.metadata.encryptedSalt = {}; + await tb0._deleteTKeyStoreItem(TSS_MODULE, "accountSalt"); } const newShare = await tb0.generateNewShare(); From c6357789212def7845e150376eb58bb945158665 Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 29 Feb 2024 16:22:39 +0800 Subject: [PATCH 09/10] fix: clean up --- packages/core/src/core.ts | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index aaca1c3c..8ea38c51 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -2047,26 +2047,6 @@ class ThresholdKey implements ITKey { private async initializeModules() { return Promise.all(Object.keys(this.modules).map((x) => this.modules[x].initialize())); } - - // - // private async generateSaltAndEncrypted(privKey: BN): Promise<{ salt: string; encryptedSalt: EncryptedMessage }> { - // if (!privKey) { - // throw CoreError.privateKeyUnavailable(); - // } - - // const salt = generateSalt(); - // const encryptedSalt = await encrypt(getPubKeyECC(privKey), Buffer.from(salt, "hex")); - // return { salt, encryptedSalt }; - // } - - // private async getAccountSalt(privKey: BNString): Promise { - // if (this._accountSalt) return this._accountSalt; - // if (Object.keys(this.metadata.encryptedSalt).length) { - // const decryptedSalt = await decrypt(toPrivKeyECC(privKey), this.metadata.encryptedSalt as EncryptedMessage); - // return decryptedSalt.toString("hex"); - // } - // return undefined; - // } } export default ThresholdKey; From 3f0c1afd7b0c0882cccc7b4b86d204d16b06fe7d Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 29 Feb 2024 17:45:31 +0800 Subject: [PATCH 10/10] fix: remove update param --- packages/core/src/core.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 8ea38c51..a1bc5a31 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -843,8 +843,7 @@ class ThresholdKey implements ITKey { serverOpts: { selectedServers?: number[]; authSignatures: string[]; - }, - updateMetadata: boolean = true + } ): Promise { if (!this.privKey) throw CoreError.privateKeyUnavailable(); @@ -941,16 +940,12 @@ class ThresholdKey implements ITKey { if (!this._accountSalt) { const accountSalt = generateSalt(); // tkeyStoreItem already syncMetadata - await this._setTKeyStoreItem( - TSS_MODULE, - { - id: "accountSalt", - value: accountSalt, - }, - updateMetadata - ); + await this._setTKeyStoreItem(TSS_MODULE, { + id: "accountSalt", + value: accountSalt, + }); this._accountSalt = accountSalt; - } else if (updateMetadata) { + } else { await this._syncShareMetadata(); } } catch (error) {