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 15 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");
}
54 changes: 48 additions & 6 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 = await 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 = await 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];
async getTSSPub(accountIndex?: number): Promise<Point> {
const tssCommits = this.getTSSCommits();
if (accountIndex && accountIndex > 0) {
const nonce = await 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 dervicepubKeyPoint = pubKeyPoint.add(noncePub);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo derivedPubKeyPoint

return new Point(dervicepubKeyPoint.getX().toString("hex"), dervicepubKeyPoint.getY().toString("hex"));
}
return tssCommits[0];
}

/**
Expand Down Expand Up @@ -876,13 +903,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 @@ -1968,6 +2000,16 @@ class ThresholdKey implements ITKey {
private async initializeModules() {
return Promise.all(Object.keys(this.modules).map((x) => this.modules[x].initialize()));
}

private async computeAccountNonce(index: number) {
// generation should occur during tkey.init, fails if accountSalt is absent
this._accountSalt = this._accountSalt || (await this.getTKeyStoreItem(TSS_MODULE, "accountSalt")).value;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should getTkeyStore during reconstruct and assign to the accountSalt ( only get from tkey store if the accountSalt is undefined )

  • this will keep this function as sync function

if (!this._accountSalt) {
throw CoreError.accountSaltUndefined();
}
const accountHash = keccak256(Buffer.from(`${index}${this._accountSalt}`)).slice(2);
return index && index > 0 ? new BN(accountHash, "hex").umod(ecCurve.curve.n) : new BN(0);
}
}

export default ThresholdKey;
5 changes: 5 additions & 0 deletions packages/core/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ 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",
1106: "Account Salt is absent, required for nonce generation",
// tkeystore
1201: "Invalid tkeyStore",
1202: "Encryption 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
1 change: 1 addition & 0 deletions packages/core/src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ class Metadata implements IMetadata {
tssNonce?: number;
tssPolyCommits?: Point[];
factorPubs?: Point[];
accountIndex?: number;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be removed rite?

factorEncs?: {
[factorPubID: string]: FactorEnc;
};
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