Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: soft nonce implementation #6

Merged
merged 23 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions .github/workflows/backward.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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`"
Expand All @@ -63,5 +63,4 @@ jobs:

- name: Running comp tests
run: |
cd ./backward-compatibility-tests
npm test
6 changes: 3 additions & 3 deletions karmaBaseConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/common-types/src/baseTypes/aggregateTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ export interface ShareRequestArgs {

export type TkeyStoreItemType = {
id: string;
value?: string;
};

export type ISeedPhraseStore = TkeyStoreItemType & {
Expand Down
4 changes: 4 additions & 0 deletions packages/common-types/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
65 changes: 56 additions & 9 deletions packages/core/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
FromJSONConstructor,
GenerateNewShareResult,
generatePrivateExcludingIndexes,
generateSalt,
getPubKeyECC,
getPubKeyPoint,
hexPoint,
Expand Down Expand Up @@ -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";

Expand All @@ -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;
Expand Down Expand Up @@ -98,6 +100,8 @@ class ThresholdKey implements ITKey {

_shareSerializationMiddleware: ShareSerializationMiddleware;

_accountSalt: string;

storeDeviceShare: (deviceShareStore: ShareStore, customDeviceInfo?: StringifiedType) => Promise<void>;

haveWriteMetadataLock: string;
Expand Down Expand Up @@ -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();
this._setTKeyStoreItem(TSS_MODULE, {
id: "accountSalt",
value: accountSalt,
});
this.metadata.addTSSData({ tssTag: this.tssTag, tssNonce: 0, tssPolyCommits, factorPubs, factorEncs });
}
return this.getKeyDetails();
Expand Down Expand Up @@ -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);
Expand All @@ -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();
Expand All @@ -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...");
Expand All @@ -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];
Expand All @@ -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 };
}
}
Expand All @@ -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];
}

/**
Expand Down Expand Up @@ -593,6 +620,11 @@ class ThresholdKey implements ITKey {
})
);
}

// assign account salt from tKey store if it exists
const accountSalt = await this.getTKeyStoreItem(TSS_MODULE, "accountSalt");
if (accountSalt && accountSalt?.value) this._accountSalt = accountSalt.value;

return { privKey, ...returnObject };
}

Expand Down Expand Up @@ -876,13 +908,18 @@ class ThresholdKey implements ITKey {
serverEncs: refreshResponse.serverFactorEncs,
};
}
const accountSalt = generateSalt();
this.metadata.addTSSData({
tssTag: this.tssTag,
tssNonce: newTssNonce,
tssPolyCommits: newTSSCommits,
factorPubs,
factorEncs,
});
this._setTKeyStoreItem(TSS_MODULE, {
id: "accountSalt",
value: accountSalt,
});
await this._syncShareMetadata();
} catch (error) {
this.tssTag = oldTag;
Expand Down Expand Up @@ -1638,13 +1675,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)`);
}

Expand Down Expand Up @@ -1933,6 +1970,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),
Expand Down
7 changes: 6 additions & 1 deletion packages/core/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 0 additions & 3 deletions packages/default/test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
});
}
Expand Down Expand Up @@ -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(
Expand Down
Loading
Loading