diff --git a/.github/workflows/backward.yml b/.github/workflows/backward.yml index 41670b680..0c1bef294 100644 --- a/.github/workflows/backward.yml +++ b/.github/workflows/backward.yml @@ -26,12 +26,12 @@ jobs: steps: - name: checkout tkey repo uses: actions/checkout@v3 - with: - path: tkey + # with: + # path: tkey - - name: switch path - run: | - cd ./tkey + # - name: switch path + # run: | + # cd ./tkey - name: Set up node uses: actions/setup-node@v3 @@ -54,7 +54,7 @@ jobs: run: | cd ./backward-compatibility-tests npm i - for filename in ../tkey/packages/* ; do + for filename in ../packages/* ; do echo "installing $filename" || continue # ... install packed packages packagename="`ls ${filename}| grep tkey`" @@ -63,5 +63,4 @@ jobs: - name: Running comp tests run: | - cd ./backward-compatibility-tests npm test diff --git a/karmaBaseConfig.js b/karmaBaseConfig.js index 1512ec4c3..183518fed 100644 --- a/karmaBaseConfig.js +++ b/karmaBaseConfig.js @@ -24,9 +24,9 @@ const localBrowserConfig = (webpackConfig, karmaConfig, packageConfig) => { frameworks: ["mocha", "webpack"], webpack: { - module: webpackConfig[1].module, - resolve: webpackConfig[1].resolve, - plugins: webpackConfig[1].plugins, + module: webpackConfig[0].module, + resolve: webpackConfig[0].resolve, + plugins: webpackConfig[0].plugins, }, plugins: ["karma-mocha-reporter", "karma-webkit-launcher", "karma-chrome-launcher", "karma-firefox-launcher", "karma-mocha", "karma-webpack"], diff --git a/package-lock.json b/package-lock.json index 15360ce90..d5f9f5cd0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4827,9 +4827,9 @@ "dev": true }, "node_modules/@types/bn.js": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.2.tgz", - "integrity": "sha512-dkpZu0szUtn9UXTmw+e0AJFd4D2XAxDnsCLdc05SfqpqzPEBft8eQr8uaFitfo/dUUOZERaLec2hHMG87A4Dxg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", + "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", "dependencies": { "@types/node": "*" } @@ -24264,7 +24264,6 @@ "ts-custom-error": "^3.3.1" }, "devDependencies": { - "@types/bn.js": "^5.1.2", "@types/elliptic": "^6.4.15" }, "engines": { @@ -24290,7 +24289,6 @@ "json-stable-stringify": "^1.0.2" }, "devDependencies": { - "@types/bn.js": "^5.1.2", "@types/elliptic": "^6.4.15", "@types/json-stable-stringify": "^1.0.34" }, @@ -24345,9 +24343,7 @@ "bn.js": "^5.2.1", "randombytes": "^2.1.0" }, - "devDependencies": { - "@types/bn.js": "^5.1.2" - }, + "devDependencies": {}, "engines": { "node": ">=18.x", "npm": ">=9.x" @@ -24365,9 +24361,7 @@ "bn.js": "^5.2.1", "ethereum-cryptography": "^2.1.2" }, - "devDependencies": { - "@types/bn.js": "^5.1.2" - }, + "devDependencies": {}, "engines": { "node": ">=18.x", "npm": ">=9.x" @@ -24386,9 +24380,7 @@ "bn.js": "^5.2.1", "ethers": "^6.7.1" }, - "devDependencies": { - "@types/bn.js": "^5.1.2" - }, + "devDependencies": {}, "engines": { "node": ">=18.x", "npm": ">=9.x" @@ -24407,7 +24399,6 @@ "elliptic": "^6.5.4" }, "devDependencies": { - "@types/bn.js": "^5.1.2", "@types/elliptic": "^6.4.15" }, "engines": { @@ -24431,7 +24422,6 @@ "elliptic": "^6.5.4" }, "devDependencies": { - "@types/bn.js": "^5.1.2", "@types/elliptic": "^6.4.15" }, "engines": { @@ -24470,9 +24460,7 @@ "@toruslabs/http-helpers": "^5.0.0", "bn.js": "^5.2.1" }, - "devDependencies": { - "@types/bn.js": "^5.1.2" - }, + "devDependencies": {}, "engines": { "node": ">=18.x", "npm": ">=9.x" @@ -24494,7 +24482,6 @@ "json-stable-stringify": "^1.0.2" }, "devDependencies": { - "@types/bn.js": "^5.1.2", "@types/json-stable-stringify": "^1.0.34" }, "engines": { @@ -24511,16 +24498,13 @@ "license": "MIT", "dependencies": { "@tkey-mpc/common-types": "^9.0.2", - "@types/bn.js": "^5.1.1", "bn.js": "^5.2.1" }, "devDependencies": { "@tkey-mpc/core": "^9.0.2", "@tkey-mpc/service-provider-base": "^9.0.2", "@tkey-mpc/storage-layer-torus": "^9.0.2", - "@types/bn.js": "^5.1.2", - "@types/filesystem": "^0.0.33", - "bn.js": "^5.2.1" + "@types/filesystem": "^0.0.33" }, "engines": { "node": ">=18.x", diff --git a/packages/common-types/package.json b/packages/common-types/package.json index 7337d3bcf..f307b4932 100644 --- a/packages/common-types/package.json +++ b/packages/common-types/package.json @@ -53,7 +53,6 @@ "ts-custom-error": "^3.3.1" }, "devDependencies": { - "@types/bn.js": "^5.1.2", "@types/elliptic": "^6.4.15" }, "bugs": { diff --git a/packages/common-types/src/baseTypes/aggregateTypes.ts b/packages/common-types/src/baseTypes/aggregateTypes.ts index 71293e4db..2e94ec905 100644 --- a/packages/common-types/src/baseTypes/aggregateTypes.ts +++ b/packages/common-types/src/baseTypes/aggregateTypes.ts @@ -196,6 +196,7 @@ export interface ShareRequestArgs { export type TkeyStoreItemType = { id: string; + value?: string; }; export type ISeedPhraseStore = TkeyStoreItemType & { diff --git a/packages/common-types/src/utils.ts b/packages/common-types/src/utils.ts index f07a5ab85..1967563ac 100644 --- a/packages/common-types/src/utils.ts +++ b/packages/common-types/src/utils.ts @@ -104,3 +104,7 @@ export function generateID(): string { // after the decimal. return `${Math.random().toString(36).substr(2, 9)}`; } + +export function generateSalt() { + return generatePrivate().toString("hex").padStart(64, "0"); +} diff --git a/packages/core/package.json b/packages/core/package.json index 05f692cd4..65aa88aab 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -44,7 +44,6 @@ "json-stable-stringify": "^1.0.2" }, "devDependencies": { - "@types/bn.js": "^5.1.2", "@types/elliptic": "^6.4.15", "@types/json-stable-stringify": "^1.0.34" }, diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index de0769278..c68296848 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -11,6 +11,7 @@ import { FromJSONConstructor, GenerateNewShareResult, generatePrivateExcludingIndexes, + generateSalt, getPubKeyECC, getPubKeyPoint, hexPoint, @@ -52,6 +53,7 @@ import { toPrivKeyECC, } from "@tkey-mpc/common-types"; import { generatePrivate } from "@toruslabs/eccrypto"; +import { keccak256 } from "@toruslabs/torus.js"; import BN from "bn.js"; import stringify from "json-stable-stringify"; @@ -66,8 +68,8 @@ import { lagrangeInterpolation, } from "./lagrangeInterpolatePolynomial"; import Metadata from "./metadata"; - // TODO: handle errors for get and set with retries +export const TSS_MODULE = "tssModule"; class ThresholdKey implements ITKey { modules: ModuleMap; @@ -98,6 +100,8 @@ class ThresholdKey implements ITKey { _shareSerializationMiddleware: ShareSerializationMiddleware; + _accountSalt: string; + storeDeviceShare: (deviceShareStore: ShareStore, customDeviceInfo?: StringifiedType) => Promise; haveWriteMetadataLock: string; @@ -300,6 +304,11 @@ 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 }); } return this.getKeyDetails(); @@ -385,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 }): 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); @@ -407,6 +416,7 @@ class ThresholdKey implements ITKey { const userDec = tssShareBNs[0]; + const { threshold, accountIndex } = opts || {}; if (type === "direct") { const tssSharePub = ecCurve.g.mul(userDec); const tssCommitA0 = ecCurve.keyFromPublic({ x: tssCommits[0].x.toString(16, 64), y: tssCommits[0].y.toString(16, 64) }).getPublic(); @@ -416,6 +426,11 @@ class ThresholdKey implements ITKey { _tssSharePub = _tssSharePub.add(tssCommitA1); } if (tssSharePub.getX().cmp(_tssSharePub.getX()) === 0 && tssSharePub.getY().cmp(_tssSharePub.getY()) === 0) { + if (accountIndex && accountIndex > 0) { + const nonce = this.computeAccountNonce(accountIndex); + const derivedShare = userDec.add(nonce).umod(ecCurve.n); + return { tssIndex, tssShare: derivedShare }; + } return { tssIndex, tssShare: userDec }; } throw new Error("user decryption does not match tss commitments..."); @@ -425,8 +440,6 @@ class ThresholdKey implements ITKey { const serverDecs = tssShareBNs.slice(1); // 5 elems const serverIndexes = new Array(serverDecs.length).fill(null).map((_, i) => i + 1); - const { threshold } = opts || {}; - const combis = kCombinations(serverDecs.length, threshold || Math.ceil(serverDecs.length / 2)); for (let i = 0; i < combis.length; i++) { const combi = combis[i]; @@ -446,6 +459,11 @@ class ThresholdKey implements ITKey { _tssSharePub = _tssSharePub.add(tssCommitA1); } if (tssSharePub.getX().cmp(_tssSharePub.getX()) === 0 && tssSharePub.getY().cmp(_tssSharePub.getY()) === 0) { + if (accountIndex && accountIndex > 0) { + const nonce = this.computeAccountNonce(accountIndex); + const derivedShare = tssShare.add(nonce).umod(ecCurve.n); + return { tssIndex, tssShare: derivedShare }; + } return { tssIndex, tssShare }; } } @@ -461,8 +479,17 @@ class ThresholdKey implements ITKey { return tssPolyCommits; } - getTSSPub(): Point { - return this.getTSSCommits()[0]; + getTSSPub(accountIndex?: number): Point { + const tssCommits = this.getTSSCommits(); + if (accountIndex && accountIndex > 0) { + const nonce = this.computeAccountNonce(accountIndex); + // we need to add the pub key nonce to the tssPub + const noncePub = ecCurve.keyFromPrivate(nonce.toString("hex")).getPublic(); + const pubKeyPoint = ecCurve.keyFromPublic({ x: tssCommits[0].x.toString("hex"), y: tssCommits[0].y.toString("hex") }).getPublic(); + const devicePubKeyPoint = pubKeyPoint.add(noncePub); + return new Point(devicePubKeyPoint.getX().toString("hex"), devicePubKeyPoint.getY().toString("hex")); + } + return tssCommits[0]; } /** @@ -593,6 +620,23 @@ 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; + } else { + const newSalt = generateSalt(); + await this._setTKeyStoreItem(TSS_MODULE, { + id: "accountSalt", + value: newSalt, + }); + this._accountSalt = newSalt; + } + } + return { privKey, ...returnObject }; } @@ -876,6 +920,7 @@ class ThresholdKey implements ITKey { serverEncs: refreshResponse.serverFactorEncs, }; } + const accountSalt = generateSalt(); this.metadata.addTSSData({ tssTag: this.tssTag, tssNonce: newTssNonce, @@ -883,7 +928,10 @@ class ThresholdKey implements ITKey { factorPubs, factorEncs, }); - await this._syncShareMetadata(); + await this._setTKeyStoreItem(TSS_MODULE, { + id: "accountSalt", + value: accountSalt, + }); } catch (error) { this.tssTag = oldTag; throw error; @@ -1638,13 +1686,13 @@ class ThresholdKey implements ITKey { // read errors for what each means if (latestMetadata.nonce > this.lastFetchedCloudMetadata.nonce) { - throw CoreError.acquireLockFailed(`unable to acquire write access for metadata due to + throw CoreError.acquireLockFailed(`unable to acquire write access for metadata due to lastFetchedCloudMetadata (${this.lastFetchedCloudMetadata.nonce}) being lower than last written metadata nonce (${latestMetadata.nonce}). perhaps update metadata SDK (create new tKey and init)`); } else if (latestMetadata.nonce < this.lastFetchedCloudMetadata.nonce) { - throw CoreError.acquireLockFailed(`unable to acquire write access for metadata due to + throw CoreError.acquireLockFailed(`unable to acquire write access for metadata due to lastFetchedCloudMetadata (${this.lastFetchedCloudMetadata.nonce}) - being higher than last written metadata nonce (${latestMetadata.nonce}). this should never happen as it + being higher than last written metadata nonce (${latestMetadata.nonce}). this should never happen as it should only ever be updated by getting metadata)`); } @@ -1933,6 +1981,16 @@ class ThresholdKey implements ITKey { this.lastFetchedCloudMetadata = undefined; } + computeAccountNonce(index: number) { + // generation should occur during tkey.init, fails if accountSalt is absent + if (!this._accountSalt) { + throw CoreError.accountSaltUndefined(); + } + let accountHash = keccak256(Buffer.from(`${index}${this._accountSalt}`)); + if (accountHash.length === 66) accountHash = accountHash.slice(2); + return index && index > 0 ? new BN(accountHash, "hex").umod(ecCurve.curve.n) : new BN(0); + } + getApi(): ITKeyApi { return { getMetadata: this.getMetadata.bind(this), diff --git a/packages/core/src/errors.ts b/packages/core/src/errors.ts index 38137f8ac..03dab9f1c 100644 --- a/packages/core/src/errors.ts +++ b/packages/core/src/errors.ts @@ -32,7 +32,8 @@ class CoreError extends TkeyError { 1103: "setMetadata errored", 1104: "previouslyFetchedCloudMetadata provided in initialization is outdated", 1105: "previouslyFetchedCloudMetadata.nonce should never be higher than the latestShareDetails, please contact support", - // tkeystore + 1106: "Account Salt is absent, required for nonce generation.Make sure key is reconstructed", + // tKeystore 1201: "Invalid tkeyStore", 1202: "Encryption failed", 1203: "Decryption failed", @@ -90,6 +91,10 @@ class CoreError extends TkeyError { return CoreError.fromCode(1103, extraMessage); } + public static accountSaltUndefined(extraMessage = ""): ITkeyError { + return CoreError.fromCode(1106, extraMessage); + } + // TkeyData public static tkeyStoreInvalid(extraMessage = ""): ITkeyError { return CoreError.fromCode(1201, extraMessage); diff --git a/packages/default/test/RefreshAndAccountIndex.js b/packages/default/test/RefreshAndAccountIndex.js new file mode 100644 index 000000000..f9b652f0b --- /dev/null +++ b/packages/default/test/RefreshAndAccountIndex.js @@ -0,0 +1,184 @@ +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 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) => { + const mode = manualSync; + const { useTSS } = customSP; + describe(`RefreshAndAccountIndex : useTss ${useTSS}, manualSync ${manualSync}, bcAccountIndex ${accountIndexBackwardCompatible}`, function () { + 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(); + const { signatures, postboxkey } = await fetchPostboxKeyAndSigs({ + serviceProvider: sp, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + }); + sp.postboxKey = postboxkey; + const { serverDKGPrivKeys } = await assignTssDkgKeys({ + serviceProvider: sp, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + maxTSSNonceToSimulate: 3, + }); + + 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 ) + if (accountIndexBackwardCompatible) { + await tb0._deleteTKeyStoreItem(TSS_MODULE, "accountSalt"); + } + + 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 }); + + // const newShare = await tb1.generateNewShare(); + await tb1.inputShareStoreSafe(newShare.newShareStores[newShare.newShareIndex.toString("hex")]); + const reconstructedKey = await tb1.reconstructKey(); + + if (tb1.privKey.cmp(reconstructedKey.privKey) !== 0) { + fail("key should be able to be reconstructed"); + } + + const tssPrivKey1 = await computeIndexedPrivateKey(tb1, factorKey, serverDKGPrivKeys[0], 1); + const tssPubKeyIndex0 = ecCurve.keyFromPrivate(tssPrivKey1).getPublic(); + + const pubKey1 = tb1.getTSSPub(1); + strictEqual(tssPubKeyIndex0.x.toString(16, 64), pubKey1.x.toString(16, 64)); + strictEqual(tssPubKeyIndex0.y.toString(16, 64), pubKey1.y.toString(16, 64)); + + const factorKey1 = new BN(generatePrivate()); + const factorPub1 = getPubKeyPoint(factorKey1); + + const factorPubs1 = [factorPub, factorPub1]; + const { serverEndpoints, serverPubKeys } = await sp.getRSSNodeDetails(); + const { tssShare: retrievedTSSShare1, tssIndex: retrievedTSSIdx1 } = await tb1.getTSSShare(factorKey); + await tb1._refreshTSSShares(true, retrievedTSSShare1, retrievedTSSIdx1, factorPubs1, [2, 3], testId, { + serverThreshold: 3, + selectedServers: [1, 2, 3], + serverEndpoints, + serverPubKeys, + authSignatures: signatures, + }); + await tb1.syncLocalMetadataTransitions(); + + const tssPrivKeyIndex1 = await computeIndexedPrivateKey(tb1, factorKey, serverDKGPrivKeys[1], 1); + const tssPrivKeyIndex2 = await computeIndexedPrivateKey(tb1, factorKey, serverDKGPrivKeys[1], 2); + + const tssPubKeyIndex1 = ecCurve.keyFromPrivate(tssPrivKeyIndex1).getPublic(); + + const pubKeyIndex1 = tb1.getTSSPub(1); + strictEqual(tssPubKeyIndex1.x.toString(16, 64), pubKeyIndex1.x.toString(16, 64)); + strictEqual(tssPubKeyIndex1.y.toString(16, 64), pubKeyIndex1.y.toString(16, 64)); + + const tb2 = new ThresholdKey({ serviceProvider: sp, storageLayer, manualSync: mode }); + await tb2.initialize({ useTSS: true, factorPub }); + await tb2.inputShareStoreSafe(newShare.newShareStores[newShare.newShareIndex.toString("hex")]); + await tb2.reconstructKey(); + + const tssPrivKeytb2Index2 = await computeIndexedPrivateKey(tb2, factorKey, serverDKGPrivKeys[1], 2); + strictEqual(tssPrivKeyIndex2.toString("hex"), tssPrivKeytb2Index2.toString("hex")); + + const tssPubKeytb2Index2 = getPubKeyPoint(tssPrivKeytb2Index2); + const pubKey2Index2 = tb2.getTSSPub(2); + + strictEqual(tssPubKeytb2Index2.x.toString(16, 64), pubKey2Index2.x.toString(16, 64)); + strictEqual(tssPubKeytb2Index2.y.toString(16, 64), pubKey2Index2.y.toString(16, 64)); + + // // test tss refresh + const factorKey2 = new BN(generatePrivate()); + const factorPub2 = getPubKeyPoint(factorKey2); + + const factorPubs2 = [factorPub, factorPub2]; + const { tssShare: retrievedTSS3, tssIndex: retrievedTSSIndex3 } = await tb2.getTSSShare(factorKey); + await tb2._refreshTSSShares(true, retrievedTSS3, retrievedTSSIndex3, factorPubs2, [2, 3], testId, { + serverThreshold: 3, + selectedServers: [1, 2, 3], + serverEndpoints, + serverPubKeys, + authSignatures: signatures, + }); + + // test case to ensure nonce mechanism + { + // make sure derived pub key is different from the index 0 key + notEqual(tssPubKeyIndex0.x.toString(16, 64), tssPubKeytb2Index2.x.toString(16, 64)); + notEqual(tssPubKeyIndex0.y.toString(16, 64), tssPubKeytb2Index2.y.toString(16, 64)); + + const { tssShare: retrievedTSS } = await tb2.getTSSShare(factorKey); + const tssSharePub = ecCurve.keyFromPrivate(retrievedTSS.toString("hex")).getPublic(); + const { tssShare: retrievedTSSIndex1 } = await tb2.getTSSShare(factorKey, { accountIndex: 1 }); + const tssSharePubIndex1 = ecCurve.keyFromPrivate(retrievedTSSIndex1.toString("hex")).getPublic(); + + const nonce = tb2.computeAccountNonce(1); + const noncePub = ecCurve.keyFromPrivate(nonce.toString("hex")).getPublic(); + const tssShareDerived = tssSharePub.add(noncePub); + + strictEqual(tssShareDerived.getX().toString("hex"), tssSharePubIndex1.getX().toString("hex")); + strictEqual(tssShareDerived.getY().toString("hex"), tssSharePubIndex1.getY().toString("hex")); + + const { tssShare: retrievedTSS31 } = await tb2.getTSSShare(factorKey, { accountIndex: 2 }); + const tssSharePub3 = ecCurve.keyFromPrivate(retrievedTSS31.toString("hex")).getPublic(); + const nonce2 = tb2.computeAccountNonce(2); + const noncePub2 = ecCurve.keyFromPrivate(nonce2.toString("hex")).getPublic(); + const tssShareDerived2 = tssSharePub.add(noncePub2); + strictEqual(tssShareDerived2.getX().toString("hex"), tssSharePub3.getX().toString("hex")); + strictEqual(tssShareDerived2.getY().toString("hex"), tssSharePub3.getY().toString("hex")); + } + + // check for account 1 and 2 after refresh share ( tb1 only refresh once ) + { + const computedKey = await computeIndexedPrivateKey(tb1, factorKey, serverDKGPrivKeys[1], 1); + strictEqual(tssPrivKey1.toString(16, 64), computedKey.toString(16, 64)); + } + + { + const computedKey = await computeIndexedPrivateKey(tb1, factorKey, serverDKGPrivKeys[1], 2); + strictEqual(tssPrivKeyIndex2.toString(16, 64), computedKey.toString(16, 64)); + } + + // check for account 1 and 2 after refresh share ( tb2 only refresh twice ) + { + const computedKey = await computeIndexedPrivateKey(tb2, factorKey2, serverDKGPrivKeys[2], 1); + strictEqual(tssPrivKey1.toString(16, 64), computedKey.toString(16, 64)); + } + + { + const computedKey = await computeIndexedPrivateKey(tb2, factorKey2, serverDKGPrivKeys[2], 2); + strictEqual(tssPrivKeytb2Index2.toString(16, 64), computedKey.toString(16, 64)); + } + }); + }); +}; diff --git a/packages/default/test/helpers.js b/packages/default/test/helpers.js index 210d978fd..0f1cecf2a 100644 --- a/packages/default/test/helpers.js +++ b/packages/default/test/helpers.js @@ -3,7 +3,7 @@ import ServiceProviderBase from "@tkey-mpc/service-provider-base"; import ServiceProviderTorus from "@tkey-mpc/service-provider-torus"; import TorusStorageLayer, { MockStorageLayer } from "@tkey-mpc/storage-layer-torus"; import { generatePrivate } from "@toruslabs/eccrypto"; -import { generatePolynomial, getShare, hexPoint, MockServer, postEndpoint } from "@toruslabs/rss-client"; +import { generatePolynomial, getLagrangeCoeffs, getShare, hexPoint, MockServer, postEndpoint } from "@toruslabs/rss-client"; // eslint-disable-next-line import/no-extraneous-dependencies import Torus from "@toruslabs/torus.js"; import BN from "bn.js"; @@ -47,8 +47,6 @@ export function getServiceProvider(params) { // this url has no effect as postbox key is passed // passing it just to satisfy direct auth checks. baseUrl: "http://localhost:3000", - web3AuthClientId: "test", - network: "mainnet", }, }); } @@ -180,7 +178,6 @@ export async function assignTssDkgKeys(opts) { for (let j = 0; j < maxTSSNonceToSimulate; j++) { const token = generateIdToken(verifierId); const extendedVerifierId = `${verifierId}\u0015${tssTag}\u0016${j}`; - console.log("extendedVerifierId", extendedVerifierId); const { serverEndpoints: sssEndpoints } = await serviceProvider.getSSSNodeDetails(); const retrieveSharesResponse = await serviceProvider.customAuthInstance.torus.retrieveShares( @@ -200,3 +197,12 @@ export async function assignTssDkgKeys(opts) { // serverDKGPubKeys, }; } + +export async function computeIndexedPrivateKey(tkey, factorKey, serverDKGPrivKeys, accountIndex) { + const { tssShare: retrievedTSS1, tssIndex: retrievedTSSIndex1 } = await tkey.getTSSShare(factorKey, { accountIndex }); + const tssPrivKey1 = getLagrangeCoeffs([1, retrievedTSSIndex1], 1) + .mul(serverDKGPrivKeys.add(tkey.computeAccountNonce(accountIndex))) + .add(getLagrangeCoeffs([1, retrievedTSSIndex1], retrievedTSSIndex1).mul(retrievedTSS1)) + .umod(ecCurve.n); + return tssPrivKey1; +} diff --git a/packages/default/test/shared.js b/packages/default/test/shared.js index 2f74f32c6..25022b67a 100644 --- a/packages/default/test/shared.js +++ b/packages/default/test/shared.js @@ -3,7 +3,7 @@ /* eslint-disable mocha/no-exports */ /* eslint-disable import/no-extraneous-dependencies */ -import { ecCurve, getPubKeyPoint, KEY_NOT_FOUND, SHARE_DELETED, ShareStore } from "@tkey-mpc/common-types"; +import { ecCurve, getPubKeyPoint, KEY_NOT_FOUND, SHARE_DELETED, ShareStore, toPrivKeyEC } from "@tkey-mpc/common-types"; import { Metadata } from "@tkey-mpc/core"; import PrivateKeyModule, { ED25519Format, SECP256K1Format } from "@tkey-mpc/private-keys"; import SecurityQuestionsModule from "@tkey-mpc/security-questions"; @@ -22,7 +22,17 @@ import stringify from "json-stable-stringify"; import { createSandbox } from "sinon"; import ThresholdKey from "../src/index"; -import { assignTssDkgKeys, fetchPostboxKeyAndSigs, getMetadataUrl, getServiceProvider, initStorageLayer, isMocked, setupTSSMocks } from "./helpers"; +import { + assignTssDkgKeys, + computeIndexedPrivateKey, + fetchPostboxKeyAndSigs, + getMetadataUrl, + getServiceProvider, + initStorageLayer, + isMocked, + setupTSSMocks, +} from "./helpers"; +import { refreshAndAccountIndex } from "./RefreshAndAccountIndex"; const rejects = async (fn, error, msg) => { let f = () => {}; @@ -67,96 +77,13 @@ function compareReconstructedKeys(a, b, message) { export const sharedTestCases = (mode, torusSP, storageLayer) => { const customSP = torusSP; const customSL = storageLayer; - describe("TSS tests", function () { - 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(); - const { signatures, postboxkey } = await fetchPostboxKeyAndSigs({ - serviceProvider: sp, - verifierName: sp.verifierName, - verifierId: sp.verifierId, - }); - sp.postboxKey = postboxkey; - const { serverDKGPrivKeys } = await assignTssDkgKeys({ - serviceProvider: sp, - verifierName: sp.verifierName, - verifierId: sp.verifierId, - maxTSSNonceToSimulate: 2, - }); - const storageLayer = initStorageLayer({ hostUrl: metadataURL }); - const tb1 = new ThresholdKey({ serviceProvider: sp, storageLayer, manualSync: mode }); - - // 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 }); - 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"); - } + // backward compatibility test + refreshAndAccountIndex(customSP, mode, true); - const tb2 = new ThresholdKey({ serviceProvider: sp, storageLayer, manualSync: mode }); - await tb2.initialize({ useTSS: true, factorPub }); - 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]) - .add(getLagrangeCoeffs([1, retrievedTSSIndex], retrievedTSSIndex).mul(retrievedTSS)) - .umod(ecCurve.n); + refreshAndAccountIndex(customSP, mode, false); - 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 { 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); - } - }); + describe("TSS tests", function () { it("#should be able to reconstruct tssShare from factor key (tss2) when initializing a key with useTSS true", async function () { const sp = customSP; if (!sp.useTSS) this.skip(); @@ -561,6 +488,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { selectedServers: [1, 2, 3], serverEndpoints, serverPubKeys, + authSignatures: signatures, }); { @@ -702,7 +630,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { await tb.getTSSShare(newFactorKey); }); // it(`#should be able to delete a user and reset tss nonce, manualSync=${mode}`, async function () { - // if (!customSP.useTSS) this.skip(); + // if (!customSP.useTSS) this(); // // create 2/4 // await tb._initializeNewKey({ initializeModules: true }); // await tb.generateNewShare(); @@ -773,7 +701,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { sp.verifierName = "torus-test-health"; sp.verifierId = "test18@example.com"; - const { postboxkey } = await fetchPostboxKeyAndSigs({ + const { postboxkey, signatures } = await fetchPostboxKeyAndSigs({ serviceProvider: sp, verifierName: sp.verifierName, verifierId: sp.verifierId, @@ -806,6 +734,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { inputTSSIndex: tssIndex, newFactorPub, newTSSIndex: 3, + authSignatures: signatures, }); await tb.syncLocalMetadataTransitions(); @@ -830,7 +759,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { const sp = customSP; sp.verifierName = "torus-test-health"; sp.verifierId = "test18@example.com"; - const { postboxkey } = await fetchPostboxKeyAndSigs({ + const { postboxkey, signatures } = await fetchPostboxKeyAndSigs({ serviceProvider: sp, verifierName: sp.verifierName, verifierId: sp.verifierId, @@ -862,6 +791,7 @@ export const sharedTestCases = (mode, torusSP, storageLayer) => { inputTSSIndex: tssIndex, newFactorPub, newTSSIndex: 3, + authSignatures: signatures, }); await tb.syncLocalMetadataTransitions(); diff --git a/packages/default/test/tssTorusSPManualSync.test.js b/packages/default/test/tssTorusSPManualSync.test.js index b087f032f..c031e103b 100644 --- a/packages/default/test/tssTorusSPManualSync.test.js +++ b/packages/default/test/tssTorusSPManualSync.test.js @@ -30,7 +30,7 @@ const metadataURL = getMetadataUrl(); const torusSL = initStorageLayer({ hostUrl: metadataURL }); const MANUAL_SYNC = true; -describe.only(`TorusServiceProvider with manualSync: ${MANUAL_SYNC}`, function () { +describe(`TorusServiceProvider with manualSync: ${MANUAL_SYNC}`, function () { // eslint-disable-next-line mocha/no-setup-in-describe sharedTestCases(MANUAL_SYNC, torusSp, torusSL); }); diff --git a/packages/private-keys/package.json b/packages/private-keys/package.json index 4dba72b0b..1064bf03a 100644 --- a/packages/private-keys/package.json +++ b/packages/private-keys/package.json @@ -38,7 +38,6 @@ "@babel/runtime": "7.x" }, "devDependencies": { - "@types/bn.js": "^5.1.2" }, "dependencies": { "@tkey-mpc/common-types": "^9.0.2", diff --git a/packages/security-questions/package.json b/packages/security-questions/package.json index 64aea1bfc..3a0756168 100644 --- a/packages/security-questions/package.json +++ b/packages/security-questions/package.json @@ -46,7 +46,6 @@ "ethereum-cryptography": "^2.1.2" }, "devDependencies": { - "@types/bn.js": "^5.1.2" }, "lint-staged": { "!(*d).ts": [ diff --git a/packages/seed-phrase/package.json b/packages/seed-phrase/package.json index 0a18b34de..ec53b0612 100644 --- a/packages/seed-phrase/package.json +++ b/packages/seed-phrase/package.json @@ -47,7 +47,6 @@ "ethers": "^6.7.1" }, "devDependencies": { - "@types/bn.js": "^5.1.2" }, "lint-staged": { "!(*d).ts": [ diff --git a/packages/service-provider-base/package.json b/packages/service-provider-base/package.json index d91988d40..c4fe38d80 100644 --- a/packages/service-provider-base/package.json +++ b/packages/service-provider-base/package.json @@ -48,7 +48,6 @@ "elliptic": "^6.5.4" }, "devDependencies": { - "@types/bn.js": "^5.1.2", "@types/elliptic": "^6.4.15" }, "bugs": { diff --git a/packages/service-provider-torus/package.json b/packages/service-provider-torus/package.json index 455a99ddc..ceac3b946 100644 --- a/packages/service-provider-torus/package.json +++ b/packages/service-provider-torus/package.json @@ -46,7 +46,6 @@ "elliptic": "^6.5.4" }, "devDependencies": { - "@types/bn.js": "^5.1.2", "@types/elliptic": "^6.4.15" }, "bugs": { diff --git a/packages/share-transfer/package.json b/packages/share-transfer/package.json index 738910e80..bac94b293 100644 --- a/packages/share-transfer/package.json +++ b/packages/share-transfer/package.json @@ -44,7 +44,6 @@ "bn.js": "^5.2.1" }, "devDependencies": { - "@types/bn.js": "^5.1.2" }, "bugs": { "url": "https://github.com/tkey/tkey/issues" diff --git a/packages/storage-layer-torus/package.json b/packages/storage-layer-torus/package.json index df45fc04a..d275c1d3b 100644 --- a/packages/storage-layer-torus/package.json +++ b/packages/storage-layer-torus/package.json @@ -46,7 +46,6 @@ "json-stable-stringify": "^1.0.2" }, "devDependencies": { - "@types/bn.js": "^5.1.2", "@types/json-stable-stringify": "^1.0.34" }, "bugs": { diff --git a/packages/web-storage/package.json b/packages/web-storage/package.json index 147817323..d9229d5d0 100644 --- a/packages/web-storage/package.json +++ b/packages/web-storage/package.json @@ -44,16 +44,13 @@ }, "dependencies": { "@tkey-mpc/common-types": "^9.0.2", - "@types/bn.js": "^5.1.1", "bn.js": "^5.2.1" }, "devDependencies": { "@tkey-mpc/core": "^9.0.2", "@tkey-mpc/service-provider-base": "^9.0.2", "@tkey-mpc/storage-layer-torus": "^9.0.2", - "@types/bn.js": "^5.1.2", - "@types/filesystem": "^0.0.33", - "bn.js": "^5.2.1" + "@types/filesystem": "^0.0.33" }, "bugs": { "url": "https://github.com/tkey/tkey/issues"