From 8a4e614c1caba495c8af569b154d6550ee0f4503 Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 21 Jan 2025 15:26:27 +0800 Subject: [PATCH 01/22] feat: initial commit for multicurve --- package-lock.json | 13 +- .../src/baseTypes/aggregateTypes.ts | 28 + packages/core/src/metadata.ts | 176 ++++- packages/tss/package.json | 2 +- packages/tss/src/provider.ts | 6 +- packages/tss/src/tss.ts | 720 +++++++++++------- 6 files changed, 629 insertions(+), 316 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6d95a46a..131caa7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5586,20 +5586,19 @@ } }, "node_modules/@toruslabs/torus.js": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@toruslabs/torus.js/-/torus.js-15.1.0.tgz", - "integrity": "sha512-ZVQzeRl8qXbaw6jVUvPjNnuY6fJjxX4k8NoNfdp+8F2Og1rBiINnASMfRVeDajc4Bg+rfbsX5HZ+T0S+F6lv1A==", - "license": "MIT", + "version": "15.1.1", + "resolved": "file:../torus.js/toruslabs-torus.js-15.1.1.tgz", + "integrity": "sha512-kkLuZ9vpmWZTklva+RQFKozDMCR2ydhR5WBn9bbgJBZqVP75FSzLZL0LQYrFqBPMgKgV22uOQ8quXW6T+Kko/g==", "dependencies": { "@toruslabs/bs58": "^1.0.0", "@toruslabs/constants": "^14.0.0", "@toruslabs/eccrypto": "^5.0.4", "@toruslabs/http-helpers": "^7.0.0", "bn.js": "^5.2.1", - "elliptic": "^6.5.6", + "elliptic": "^6.5.7", "ethereum-cryptography": "^2.2.1", "json-stable-stringify": "^1.1.1", - "loglevel": "^1.9.1" + "loglevel": "^1.9.2" }, "engines": { "node": ">=18.x", @@ -25942,7 +25941,7 @@ "@toruslabs/customauth": "^20.3.0", "@toruslabs/http-helpers": "^7.0.0", "@toruslabs/rss-client": "^2.0.1", - "@toruslabs/torus.js": "^15.1.0", + "@toruslabs/torus.js": "file:../../../torus.js/toruslabs-torus.js-15.1.1.tgz", "@types/bn.js": "^5.1.5", "bn.js": "^5.2.1", "elliptic": "^6.5.5", diff --git a/packages/common-types/src/baseTypes/aggregateTypes.ts b/packages/common-types/src/baseTypes/aggregateTypes.ts index e802e612..f4d1082d 100644 --- a/packages/common-types/src/baseTypes/aggregateTypes.ts +++ b/packages/common-types/src/baseTypes/aggregateTypes.ts @@ -20,6 +20,7 @@ import { ISerializable, IServiceProvider, IStorageLayer, + KeyType, PolyIDAndShares, PolynomialID, ShareDescriptionMap, @@ -62,6 +63,30 @@ export type FactorEnc = { serverEncs: EncryptedMessage[]; }; +export interface ITssMetadata { + tssKeyTypes: { + [tssTag: string]: KeyType; + }; + + tssNonces: { + [tssTag: string]: number; + }; + + tssPolyCommits: { + [tssTag: string]: Point[]; + }; + + factorPubs: { + [tssTag: string]: Point[]; + }; + + factorEncs: { + [tssTag: string]: { + [factorPubID: string]: FactorEnc; + }; + }; +} + export interface IMetadata extends ISerializable { pubKey: Point; @@ -85,6 +110,8 @@ export interface IMetadata extends ISerializable { nonce: number; + version: number; + getShareIndexesForPolynomial(polyID: PolynomialID): string[]; getLatestPublicPolynomial(): PublicPolynomial; addPublicShare(polynomialID: PolynomialID, publicShare: PublicShare): void; @@ -112,6 +139,7 @@ export interface IMetadata extends ISerializable { [factorPubID: string]: FactorEnc; }; }): void; + getTssData(keyType: KeyType): ITssMetadata; } export type InitializeNewKeyResult = { diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index bb4df572..91ee2653 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -4,6 +4,8 @@ import { FactorEnc, getPubKeyPoint, IMetadata, + ISerializable, + ITssMetadata, KeyType, Point, PolyIDAndShares, @@ -27,6 +29,97 @@ import stringify from "json-stable-stringify"; import CoreError from "./errors"; import { polyCommitmentEval } from "./lagrangeInterpolatePolynomial"; +const METADATA_VERSION = 1; + +export class TssMetadata implements ITssMetadata, ISerializable { + tssKeyTypes: { + [tssTag: string]: KeyType; + }; + + tssNonces: { + [tssTag: string]: number; + }; + + tssPolyCommits: { + [tssTag: string]: Point[]; + }; + + factorPubs: { + [tssTag: string]: Point[]; + }; + + factorEncs: { + [tssTag: string]: { + [factorPubID: string]: FactorEnc; + }; + }; + + constructor() { + this.tssKeyTypes = {}; + this.tssNonces = {}; + this.tssPolyCommits = {}; + this.factorPubs = {}; + this.factorEncs = {}; + } + + static fromJSON(value: StringifiedType): TssMetadata { + const { tssKeyTypes, tssPolyCommits, tssNonces, factorPubs, factorEncs } = value; + const tssMetadata = new TssMetadata(); + + if (tssKeyTypes) { + for (const key in tssKeyTypes) { + tssMetadata.tssKeyTypes[key] = tssKeyTypes[key]; + } + } + if (tssPolyCommits) { + for (const key in tssPolyCommits) { + tssMetadata.tssPolyCommits[key] = (tssPolyCommits as Record)[key].map((obj) => new Point(obj.x, obj.y)); + } + } + if (tssNonces) { + for (const key in tssNonces) { + tssMetadata.tssNonces[key] = tssNonces[key]; + } + } + if (factorPubs) { + for (const key in factorPubs) { + tssMetadata.factorPubs[key] = (factorPubs as Record)[key].map((obj) => new Point(obj.x, obj.y)); + } + } + if (factorEncs) tssMetadata.factorEncs = factorEncs; + + return tssMetadata; + } + + toJSON(): StringifiedType { + return { + tssKeyTypes: this.tssKeyTypes, + tssNonces: this.tssNonces, + tssPolyCommits: this.tssPolyCommits, + factorPubs: this.factorPubs, + factorEncs: this.factorEncs, + }; + } + + update(tssData: { + tssTag: string; + tssKeyType?: KeyType; + tssNonce?: number; + tssPolyCommits?: Point[]; + factorPubs?: Point[]; + factorEncs?: { + [factorPubID: string]: FactorEnc; + }; + }) { + const { tssKeyType, tssTag, tssNonce, tssPolyCommits, factorPubs, factorEncs } = tssData; + if (tssKeyType) this.tssKeyTypes[tssTag] = tssKeyType; + 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; + } +} + class Metadata implements IMetadata { pubKey: Point; @@ -73,6 +166,13 @@ class Metadata implements IMetadata { }; }; + tss?: { + secp256k1: TssMetadata; + ed25519: TssMetadata; + }; + + version = METADATA_VERSION; + constructor(input: Point) { this.publicPolynomials = {}; this.publicShares = {}; @@ -90,9 +190,25 @@ class Metadata implements IMetadata { } static fromJSON(value: StringifiedType): Metadata { - const { pubKey, polyIDList, generalStore, tkeyStore, scopedStore, nonce, tssKeyTypes, tssPolyCommits, tssNonces, factorPubs, factorEncs } = value; + const { + pubKey, + polyIDList, + generalStore, + tkeyStore, + scopedStore, + nonce, + tssKeyTypes, + tssPolyCommits, + tssNonces, + factorPubs, + factorEncs, + tss, + version, + } = value; const point = Point.fromSEC1(secp256k1, pubKey); const metadata = new Metadata(point); + metadata.version = version || METADATA_VERSION; + const unserializedPolyIDList: PolyIDAndShares[] = []; if (generalStore) metadata.generalStore = generalStore; @@ -100,31 +216,20 @@ class Metadata implements IMetadata { if (scopedStore) metadata.scopedStore = scopedStore; if (nonce) metadata.nonce = nonce; - if (tssKeyTypes) { - metadata.tssKeyTypes = {}; - for (const key in tssKeyTypes) { - metadata.tssKeyTypes[key] = tssKeyTypes[key]; - } - } - if (tssPolyCommits) { - metadata.tssPolyCommits = {}; - for (const key in tssPolyCommits) { - metadata.tssPolyCommits[key] = (tssPolyCommits as Record)[key].map((obj) => new Point(obj.x, obj.y)); - } - } - if (tssNonces) { - metadata.tssNonces = {}; - for (const key in tssNonces) { - metadata.tssNonces[key] = tssNonces[key]; - } - } - if (factorPubs) { - metadata.factorPubs = {}; - for (const key in factorPubs) { - metadata.factorPubs[key] = (factorPubs as Record)[key].map((obj) => new Point(obj.x, obj.y)); + if (version === 1) { + metadata.tss = tss; + } else if (tssKeyTypes) { + const tssData = TssMetadata.fromJSON({ + tssKeyTypes, + tssPolyCommits, + tssNonces, + factorPubs, + factorEncs, + }); + if (tssData.tssKeyTypes.default) { + metadata.tss[tssData.tssKeyTypes.default] = tssData; } } - if (factorEncs) metadata.factorEncs = factorEncs; for (let i = 0; i < polyIDList.length; i += 1) { const serializedPolyID: string = polyIDList[i]; @@ -327,11 +432,8 @@ class Metadata implements IMetadata { generalStore: this.generalStore, tkeyStore: this.tkeyStore, nonce: this.nonce, - ...(this.tssKeyTypes && { tssKeyTypes: this.tssKeyTypes }), - ...(this.tssNonces && { tssNonces: this.tssNonces }), - ...(this.tssPolyCommits && { tssPolyCommits: this.tssPolyCommits }), - ...(this.factorPubs && { factorPubs: this.factorPubs }), - ...(this.factorEncs && { factorEncs: this.factorEncs }), + tss: this.tss, + version: this.version, }; } @@ -348,12 +450,16 @@ class Metadata implements IMetadata { [factorPubID: string]: FactorEnc; }; }): void { - const { tssKeyType, tssTag, tssNonce, tssPolyCommits, factorPubs, factorEncs } = tssData; - if (tssKeyType) this.tssKeyTypes[tssTag] = tssKeyType; - 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 (tssData.tssKeyType === KeyType.ed25519) this.tss.ed25519.update(tssData); + else if (tssData.tssKeyType === KeyType.secp256k1) this.tss.secp256k1.update(tssData); + } + + getTssData(tssKeyType: KeyType): ITssMetadata { + if (tssKeyType === KeyType.secp256k1) { + return this.tss?.secp256k1; + } else if (tssKeyType === KeyType.ed25519) { + return this.tss?.ed25519; + } } } diff --git a/packages/tss/package.json b/packages/tss/package.json index 99515116..a078c377 100644 --- a/packages/tss/package.json +++ b/packages/tss/package.json @@ -27,7 +27,7 @@ "@toruslabs/customauth": "^20.3.0", "@toruslabs/http-helpers": "^7.0.0", "@toruslabs/rss-client": "^2.0.1", - "@toruslabs/torus.js": "^15.1.0", + "@toruslabs/torus.js": "file:../../../torus.js/toruslabs-torus.js-15.1.1.tgz", "@types/bn.js": "^5.1.5", "bn.js": "^5.2.1", "elliptic": "^6.5.5", diff --git a/packages/tss/src/provider.ts b/packages/tss/src/provider.ts index beaa8f07..09fd1888 100644 --- a/packages/tss/src/provider.ts +++ b/packages/tss/src/provider.ts @@ -1,4 +1,4 @@ -import { Point, StringifiedType } from "@tkey/common-types"; +import { KeyType, Point, StringifiedType } from "@tkey/common-types"; import { TorusServiceProvider } from "@tkey/service-provider-torus"; import { AggregateLoginParams, SubVerifierDetails, TorusAggregateLoginResponse, TorusLoginResponse } from "@toruslabs/customauth"; import { PointHex } from "@toruslabs/rss-client"; @@ -57,7 +57,8 @@ export class TSSTorusServiceProvider extends TorusServiceProvider { async getTSSPubKey( tssTag: string, - tssNonce: number + tssNonce: number, + keyType: KeyType ): Promise<{ pubKey: Point; nodeIndexes?: number[]; @@ -69,6 +70,7 @@ export class TSSTorusServiceProvider extends TorusServiceProvider { verifier: this.verifierName, verifierId: this.verifierId, extendedVerifierId: getExtendedVerifierId(this.verifierId, tssTag, tssNonce), + keyType, }); return { diff --git a/packages/tss/src/tss.ts b/packages/tss/src/tss.ts index d4d78c88..20a77f09 100644 --- a/packages/tss/src/tss.ts +++ b/packages/tss/src/tss.ts @@ -4,19 +4,17 @@ import { EllipticPoint, encrypt, EncryptedMessage, - KeyDetails, KeyType, Point, ReconstructedKeyResult, secp256k1, StringifiedType, TKeyArgs, - TKeyInitArgs, } from "@tkey/common-types"; import { CoreError, TKey } from "@tkey/core"; import { post } from "@toruslabs/http-helpers"; import { dotProduct, ecPoint, hexPoint, PointHex, randomSelection, RSSClient } from "@toruslabs/rss-client"; -import { getEd25519ExtendedPublicKey as getEd25519KeyPairFromSeed, getSecpKeyFromEd25519 } from "@toruslabs/torus.js"; +import { getEd25519ExtendedPublicKey as getEd25519KeyPairFromSeed, getKeyCurve, getSecpKeyFromEd25519 } from "@toruslabs/torus.js"; import BN from "bn.js"; import { ec as EC } from "elliptic"; import { keccak256 } from "ethereum-cryptography/keccak"; @@ -55,11 +53,15 @@ export interface TSSTKeyArgs extends TKeyArgs { tssTag?: string; } -export interface TKeyTSSInitArgs extends TKeyInitArgs { +export interface TKeyTSSInitArgs { deviceTSSShare?: BN; deviceTSSIndex?: number; factorPub?: Point; - skipTssInit?: boolean; + importKey?: Buffer; + serverOpts: { + selectedServers?: number[]; + authSignatures: string[]; + }; } export class TKeyTSS extends TKey { @@ -103,20 +105,13 @@ export class TKeyTSS extends TKey { return this._tssCurve; } - public set tssTag(tag: string) { - if ((this.metadata.tssKeyTypes[this.tssTag] || LEGACY_KEY_TYPE) !== this.tssKeyType) { - throw CoreError.default(`tssKeyType mismatch: ${this.metadata.tssKeyTypes[this.tssTag]} !== ${this.tssKeyType}`); - } - this._tssTag = tag; - } - static async fromJSON(value: StringifiedType, args: TSSTKeyArgs): Promise { const tbTss = new TKeyTSS(args); const tb = await super.fromJSON(value, args); const { tssTag, tssKeyType, accountSalt } = value; - if (tssTag !== tbTss.tssTag) { + if (tssTag !== tbTss._tssTag) { throw CoreError.default(`tssTag mismatch: ${tssTag} !== ${tbTss.tssTag}`); } @@ -141,7 +136,7 @@ export class TKeyTSS extends TKey { toJSON(): StringifiedType { const tbJson = super.toJSON(); - tbJson.tssTag = this.tssTag; + tbJson.tssTag = this._tssTag; tbJson.tssKeyType = this.tssKeyType; tbJson.accountSalt = this._accountSalt; return tbJson; @@ -151,49 +146,145 @@ export class TKeyTSS extends TKey { * Initializes this instance. If a TSS account does not exist, creates one * under the given factor key. `skipTssInit` skips TSS account creation and * can be used with `importTssKey` to just import an existing account instead. - * @returns The key details of TKey core. */ - async initialize(params?: TKeyTSSInitArgs): Promise { - const keyDetails = await super.initialize(params); - - if (!this.metadata.tssPolyCommits[this.tssTag] && !(params?.skipTssInit || params?.neverInitializeNewKey)) { - // if tss shares have not been created for this tssTag, create new tss sharing - const { factorEncs, factorPubs, tssPolyCommits } = await this._initializeNewTSSKey( - this.tssTag, - params.deviceTSSShare, - params.factorPub, - params.deviceTSSIndex - ); - this.metadata.updateTSSData({ tssKeyType: this._tssKeyType, tssTag: this.tssTag, tssNonce: 0, tssPolyCommits, factorPubs, factorEncs }); - const accountSalt = generateSalt(this._tssCurve); - await this._setTKeyStoreItem(TSS_MODULE, { - id: "accountSalt", - value: accountSalt, - } as IAccountSaltStore); - this._accountSalt = accountSalt; + async initializeTssSecp256k1(params: TKeyTSSInitArgs): Promise { + const secp256k1TssData = this.metadata.getTssData(KeyType.secp256k1); + const ed25519TssData = this.metadata.getTssData(KeyType.ed25519); + + const secp256k1Exist = !!secp256k1TssData && Object.keys(secp256k1TssData).length > 0; + const ed25519Exist = !!ed25519TssData && Object.keys(ed25519TssData).length > 0; + + const { factorPub, importKey, serverOpts } = params; + if (secp256k1Exist) { + throw CoreError.default("TSS account already exists for secp256k1 key type"); } + if (!serverOpts) { + throw CoreError.default("serverOpts is required for import key flow"); + } + const backupMetadata = this.metadata.clone(); + + try { + if (!importKey) { + // if tss shares have not been created for this tssTag, create new tss sharing + const { factorEncs, factorPubs, tssPolyCommits, tss2 } = await this._initializeNewTSSKey( + KeyType.secp256k1, + this.tssTag, + params.deviceTSSShare, + factorPub, + params.deviceTSSIndex + ); + + this.metadata.updateTSSData({ tssKeyType: KeyType.secp256k1, tssTag: this.tssTag, tssNonce: 0, tssPolyCommits, factorPubs, factorEncs }); - if (this.metadata.tssPolyCommits[this.tssTag] && (this.metadata.tssKeyTypes[this.tssTag] || LEGACY_KEY_TYPE) !== this.tssKeyType) { - throw CoreError.default(`tssKeyType mismatch: ${this.metadata.tssKeyTypes[this.tssTag]} !== ${this.tssKeyType}`); + const accountSalt = generateSalt(this._tssCurve); + await this._setTKeyStoreItem(TSS_MODULE, { + id: "accountSalt", + value: accountSalt, + } as IAccountSaltStore); + this._accountSalt = accountSalt; + + if (ed25519Exist) { + const tssIndexes = ed25519TssData.factorPubs[this._tssTag].map((point) => { + return ed25519TssData.factorEncs[this._tssTag][point.x.toString("hex", 64)].tssIndex; + }); + // refresh with factorPubs + this._refreshTSSSharesWithFactorPubs( + { + updateMetadata: false, + tssShare: tss2, + tssIndex: params.deviceTSSIndex ?? 2, + factorPubs: ed25519TssData.factorPubs[this._tssTag], + tssIndexes, + }, + serverOpts + ); + } + } else { + let factorPubs = [factorPub]; + let newTSSIndexes = [2]; + if (ed25519Exist) { + // refresh with factorPubs + factorPubs = ed25519TssData.factorPubs[this._tssTag]; + newTSSIndexes = ed25519TssData.factorPubs[this._tssTag].map((point) => { + return ed25519TssData.factorEncs[this._tssTag][point.x.toString("hex", 64)].tssIndex; + }); + } + + this.importTssKey( + { + tag: this._tssTag, + importKey, + factorPubs, + newTSSIndexes, + tssKeyType: KeyType.secp256k1, + }, + params.serverOpts + ); + } + } finally { + this.metadata = backupMetadata; + } + } + + /** + * Initializes this instance. If a TSS account does not exist, creates one + * under the given factor key. `skipTssInit` skips TSS account creation and + * can be used with `importTssKey` to just import an existing account instead. + */ + async initializeTssEd25519(params: TKeyTSSInitArgs & { importKey: Buffer }): Promise { + const { importKey } = params; + if (!importKey) { + throw CoreError.default("importKey is required"); } - return keyDetails; + const secp256k1TssData = this.metadata.getTssData(KeyType.secp256k1); + const ed25519TssData = this.metadata.getTssData(KeyType.ed25519); + + const secp256k1Exist = !!secp256k1TssData && Object.keys(secp256k1TssData).length > 0; + const ed25519Exist = !!ed25519TssData && Object.keys(ed25519TssData).length > 0; + + const { factorPub } = params; + if (ed25519Exist) { + throw CoreError.default("TSS account already exists for secp256k1 key type"); + } + + let factorPubs = [factorPub]; + let newTSSIndexes = [2]; + if (secp256k1Exist) { + // refresh with factorPubs + factorPubs = secp256k1TssData.factorPubs[this._tssTag]; + newTSSIndexes = secp256k1TssData.factorPubs[this._tssTag].map((point) => { + return secp256k1TssData.factorEncs[this._tssTag][point.x.toString("hex", 64)].tssIndex; + }); + } + this.importTssKey( + { + tag: this._tssTag, + importKey, + factorPubs, + newTSSIndexes, + tssKeyType: KeyType.ed25519, + }, + params.serverOpts + ); } /** * Returns the encrypted data associated with the given factor public key. */ - getFactorEncs(factorPub: Point): FactorEnc { + getFactorEncs(factorPub: Point, keyType: KeyType): FactorEnc { if (!this.metadata) throw CoreError.metadataUndefined(); - if (!this.metadata.factorEncs) throw CoreError.default("no factor encs mapping"); - if (!this.metadata.factorPubs) throw CoreError.default("no factor pubs mapping"); - const factorPubs = this.metadata.factorPubs[this.tssTag]; + const tssData = this.metadata.getTssData(keyType); + + if (!tssData) throw CoreError.default("no factor encs mapping"); + + const factorPubs = tssData.factorPubs[this.tssTag]; if (!factorPubs) throw CoreError.default(`no factor pubs for this tssTag ${this.tssTag}`); if (factorPubs.filter((f) => f.x.cmp(factorPub.x) === 0 && f.y.cmp(factorPub.y) === 0).length === 0) throw CoreError.default(`factor pub ${factorPub} not found for tssTag ${this.tssTag}`); - if (!this.metadata.factorEncs[this.tssTag]) throw CoreError.default(`no factor encs for tssTag ${this.tssTag}`); + if (!tssData.factorEncs[this.tssTag]) throw CoreError.default(`no factor encs for tssTag ${this.tssTag}`); const factorPubID = factorPub.x.toString(16, 64); - return this.metadata.factorEncs[this.tssTag][factorPubID]; + return tssData.factorEncs[this.tssTag][factorPubID]; } /** @@ -201,7 +292,8 @@ export class TKeyTSS extends TKey { */ async getTSSShare( factorKey: BN, - opts?: { + opts: { + keyType: KeyType; threshold?: number; accountIndex?: number; coefficient?: BN; @@ -211,7 +303,7 @@ export class TKeyTSS extends TKey { tssShare: BN; }> { const factorPub = getPubKeyPoint(factorKey, factorKeyCurve); - const factorEncs = this.getFactorEncs(factorPub); + const factorEncs = this.getFactorEncs(factorPub, opts.keyType); const { userEnc, serverEncs, tssIndex, type } = factorEncs; const userDecryption = await decrypt(Buffer.from(factorKey.toString(16, 64), "hex"), userEnc); const serverDecryptions = await Promise.all( @@ -227,22 +319,22 @@ export class TKeyTSS extends TKey { return new BN(buf); }); - const ec = this._tssCurve; - const tssCommits = this.getTSSCommits().map((p) => { + const ec = getKeyCurve(opts.keyType); + const tssCommits = this.getTSSCommits(opts.keyType).map((p) => { return ec.keyFromPublic({ x: p.x.toString(16, 64), y: p.y.toString(16, 64) }).getPublic(); }); const userDec = tssShareBNs[0]; - const accountIndex = opts?.accountIndex || 0; - const coefficient = opts?.coefficient || new BN(1); + const accountIndex = opts.accountIndex || 0; + const coefficient = opts.coefficient || new BN(1); if (type === "direct") { const tssSharePub = ec.g.mul(userDec); const tssCommitA0 = tssCommits[0]; const tssCommitA1 = tssCommits[1]; const _tssSharePub = tssCommitA0.add(tssCommitA1.mul(new BN(tssIndex))); if (tssSharePub.eq(_tssSharePub)) { - const adjustedShare = this.adjustTssShare(userDec, accountIndex, coefficient); + const adjustedShare = this.adjustTssShare({ share: userDec, accountIndex, coefficient, keyType: opts.keyType }); return { tssIndex, tssShare: adjustedShare }; } throw new Error("user decryption does not match tss commitments..."); @@ -272,7 +364,7 @@ export class TKeyTSS extends TKey { _tssSharePub = _tssSharePub.add(tssCommitA1); } if (tssSharePub.eq(_tssSharePub)) { - const adjustedShare = this.adjustTssShare(tssShare, accountIndex, coefficient); + const adjustedShare = this.adjustTssShare({ share: tssShare, accountIndex, coefficient, keyType: opts.keyType }); return { tssIndex, tssShare: adjustedShare }; } } @@ -283,10 +375,13 @@ export class TKeyTSS extends TKey { * Returns the TSS public key and the curve points corresponding to secret key * shares, as stored in Metadata. */ - getTSSCommits(): Point[] { + getTSSCommits(tssKeyType: KeyType): Point[] { if (!this.metadata) throw CoreError.metadataUndefined(); - const tssPolyCommits = this.metadata.tssPolyCommits[this.tssTag]; - if (!tssPolyCommits) throw CoreError.default(`tss poly commits not found for tssTag ${this.tssTag}`); + + const tssData = this.metadata.getTssData(tssKeyType); + if (!tssData) throw CoreError.default("no tss data"); + const tssPolyCommits = tssData.tssPolyCommits[this._tssTag]; + if (!tssPolyCommits) throw CoreError.default(`tss poly commits not found for tssTag ${this._tssTag}`); if (tssPolyCommits.length === 0) throw CoreError.default("tss poly commits is empty"); return tssPolyCommits; } @@ -294,9 +389,9 @@ export class TKeyTSS extends TKey { /** * Returns the TSS public key. */ - getTSSPub(accountIndex?: number): Point { - const ec = this._tssCurve; - const tssCommits = this.getTSSCommits(); + getTSSPub(tssKeyType: KeyType, accountIndex?: number): Point { + const ec = getKeyCurve(tssKeyType); + const tssCommits = this.getTSSCommits(tssKeyType); if (accountIndex && accountIndex > 0) { // Add account nonce to pub key. const nonce = this.computeAccountNonce(accountIndex); @@ -326,157 +421,10 @@ export class TKeyTSS extends TKey { }; } - /** - * Imports an existing private key for threshold signing. A corresponding user - * key share will be stored under the specified factor key. - */ - async importTssKey( - params: { - tag: string; - importKey: Buffer; - factorPub: Point; - newTSSIndex: number; - }, - serverOpts: { - selectedServers?: number[]; - authSignatures: string[]; - } - ): Promise { - const ec = this._tssCurve; - if (!this.secp256k1Key) throw CoreError.privateKeyUnavailable(); - if (!this.metadata) { - throw CoreError.metadataUndefined(); - } - const { importKey, factorPub, newTSSIndex, tag } = params; - - const oldTag = this.tssTag; - this._tssTag = tag; - - try { - const { selectedServers = [], authSignatures = [] } = serverOpts || {}; - - if (!tag) throw CoreError.default(`invalid param, tag is required`); - if (!factorPub) throw CoreError.default(`invalid param, newFactorPub is required`); - if (!newTSSIndex) throw CoreError.default(`invalid param, newTSSIndex is required`); - if (authSignatures.length === 0) throw CoreError.default(`invalid param, authSignatures is required`); - - const existingFactorPubs = this.metadata.factorPubs[tag]; - if (existingFactorPubs?.length > 0) { - throw CoreError.default(`Duplicate account tag, please use a unique tag for importing key`); - } - const factorPubs = [factorPub]; - - const importScalar = await (async () => { - if (this._tssKeyType === KeyType.secp256k1) { - return new BN(importKey); - } else if (this._tssKeyType === KeyType.ed25519) { - // Store seed in metadata. - const domainKey = getEd25519SeedStoreDomainKey(this.tssTag || TSS_TAG_DEFAULT); - const result = this.metadata.getGeneralStoreDomain(domainKey) as Record; - if (result) { - throw new Error("Seed already exists"); - } - - const { scalar } = getEd25519KeyPairFromSeed(importKey); - const encKey = Buffer.from(getSecpKeyFromEd25519(scalar).point.encodeCompressed("hex"), "hex"); - const msg = await encrypt(encKey, importKey); - this.metadata.setGeneralStoreDomain(domainKey, { message: msg }); - - return scalar; - } - throw new Error("Invalid key type"); - })(); - - if (!importScalar || importScalar.eq(new BN("0"))) { - throw new Error("Invalid importedKey"); - } - - const tssIndexes = [newTSSIndex]; - const existingNonce = this.metadata.tssNonces[this.tssTag]; - const newTssNonce: number = existingNonce && existingNonce > 0 ? existingNonce + 1 : 0; - const verifierAndVerifierID = this.serviceProvider.getVerifierNameVerifierId(); - const label = `${verifierAndVerifierID}\u0015${this.tssTag}\u0016${newTssNonce}`; - const tssPubKey = hexPoint(ec.g.mul(importScalar)); - const rssNodeDetails = await this._getRssNodeDetails(); - const { pubKey: newTSSServerPub, nodeIndexes } = await this.serviceProvider.getTSSPubKey(this.tssTag, newTssNonce); - let finalSelectedServers = selectedServers; - - if (nodeIndexes?.length > 0) { - if (selectedServers.length) { - finalSelectedServers = nodeIndexes.slice(0, Math.min(selectedServers.length, nodeIndexes.length)); - } else { - finalSelectedServers = nodeIndexes.slice(0, 3); - } - } else if (selectedServers?.length === 0) { - finalSelectedServers = randomSelection( - new Array(rssNodeDetails.serverEndpoints.length).fill(null).map((_, i) => i + 1), - Math.ceil(rssNodeDetails.serverEndpoints.length / 2) - ); - } - - const { serverEndpoints, serverPubKeys, serverThreshold } = rssNodeDetails; - - const rssClient = new RSSClient({ - serverEndpoints, - serverPubKeys, - serverThreshold, - tssPubKey, - keyType: this._tssKeyType, - }); - - const refreshResponses = await rssClient.import({ - importKey: importScalar, - dkgNewPub: pointToHex(newTSSServerPub), - selectedServers: finalSelectedServers, - factorPubs: factorPubs.map((f) => pointToHex(f)), - targetIndexes: tssIndexes, - newLabel: label, - sigs: authSignatures, - }); - const secondCommit = newTSSServerPub.toEllipticPoint(ec).add(ecPoint(ec, tssPubKey).neg()); - const newTSSCommits = [ - Point.fromJSON(tssPubKey), - Point.fromJSON({ x: secondCommit.getX().toString(16, 64), y: secondCommit.getY().toString(16, 64) }), - ]; - const factorEncs: { - [factorPubID: string]: FactorEnc; - } = {}; - for (let i = 0; i < refreshResponses.length; i++) { - const refreshResponse = refreshResponses[i]; - factorEncs[refreshResponse.factorPub.x.padStart(64, "0")] = { - type: "hierarchical", - tssIndex: refreshResponse.targetIndex, - userEnc: refreshResponse.userFactorEnc, - serverEncs: refreshResponse.serverFactorEncs, - }; - } - this.metadata.updateTSSData({ - tssKeyType: this._tssKeyType, - tssTag: this.tssTag, - tssNonce: newTssNonce, - tssPolyCommits: newTSSCommits, - factorPubs, - factorEncs, - }); - if (!this._accountSalt) { - const accountSalt = generateSalt(this._tssCurve); - await this._setTKeyStoreItem(TSS_MODULE, { - id: "accountSalt", - value: accountSalt, - } as IAccountSaltStore); - this._accountSalt = accountSalt; - } - await this._syncShareMetadata(); - } catch (error) { - this._tssTag = oldTag; - throw error; - } - } - /** * UNSAFE: USE WITH CAUTION * - * Reconstructs and exports the TSS private key. + * Reconstructs and exports the TSS private key. Secp256k1 only. */ async _UNSAFE_exportTssKey(tssOptions: { factorKey: BN; @@ -486,11 +434,12 @@ export class TKeyTSS extends TKey { }): Promise { if (!this.metadata) throw CoreError.metadataUndefined("metadata is undefined"); if (!this.secp256k1Key) throw new Error("Tkey is not reconstructed"); - if (!this.metadata.tssPolyCommits[this.tssTag]) throw new Error(`tss key has not been initialized for tssTag ${this.tssTag}`); + const tssData = this.metadata.getTssData(KeyType.secp256k1); + if (!tssData.tssPolyCommits[this.tssTag]) throw new Error(`tss key has not been initialized for tssTag ${this.tssTag}`); const { factorKey, selectedServers, authSignatures, accountIndex } = tssOptions; - const { tssIndex } = await this.getTSSShare(factorKey); + const { tssIndex } = await this.getTSSShare(factorKey, { keyType: KeyType.secp256k1 }); // Assumption that there are only index 2 and 3 for tss shares // create complement index share const tempShareIndex = tssIndex === 2 ? 3 : 2; @@ -506,8 +455,8 @@ export class TKeyTSS extends TKey { refreshShares: true, }); - const { tssShare: factorShare, tssIndex: factorIndex } = await this.getTSSShare(factorKey); - const { tssShare: tempShare, tssIndex: tempIndex } = await this.getTSSShare(tempFactorKey); + const { tssShare: factorShare, tssIndex: factorIndex } = await this.getTSSShare(factorKey, { keyType: KeyType.secp256k1 }); + const { tssShare: tempShare, tssIndex: tempIndex } = await this.getTSSShare(tempFactorKey, { keyType: KeyType.secp256k1 }); // reconstruct final key using sss const ec = this._tssCurve; @@ -556,8 +505,8 @@ export class TKeyTSS extends TKey { * @param remoteFactorPub - Factor Pub for remote share. * @param signatures - Signatures for authentication against RSS servers. */ - async remoteRefreshTssShares(params: { factorPubs: Point[]; tssIndices: number[]; remoteClient: IRemoteClientState }) { - const { factorPubs, tssIndices, remoteClient } = params; + async remoteRefreshTssShares(params: { factorPubs: Point[]; tssIndices: number[]; remoteClient: IRemoteClientState; keyType: KeyType }) { + const { factorPubs, tssIndices, remoteClient, keyType } = params; const rssNodeDetails = await this._getRssNodeDetails(); const { serverEndpoints, serverPubKeys, serverThreshold } = rssNodeDetails; let finalSelectedServers = randomSelection( @@ -566,23 +515,25 @@ export class TKeyTSS extends TKey { ); const verifierNameVerifierId = this.serviceProvider.getVerifierNameVerifierId(); + const tssData = this.metadata.getTssData(this.tssKeyType); + if (!tssData) throw CoreError.default("no tss data"); - const tssCommits = this.metadata.tssPolyCommits[this.tssTag]; - const tssNonce: number = this.metadata.tssNonces[this.tssTag] || 0; - const { pubKey: newTSSServerPub, nodeIndexes } = await this.serviceProvider.getTSSPubKey(this.tssTag, tssNonce + 1); + const tssCommits = tssData.tssPolyCommits[this._tssTag]; + const tssNonce: number = tssData.tssNonces[this._tssTag] || 0; + const { pubKey: newTSSServerPub, nodeIndexes } = await this.serviceProvider.getTSSPubKey(this.tssTag, tssNonce + 1, keyType); // move to pre-refresh if (nodeIndexes?.length > 0) { finalSelectedServers = nodeIndexes.slice(0, Math.min(serverEndpoints.length, nodeIndexes.length)); } - const factorEnc = this.getFactorEncs(Point.fromSEC1(secp256k1, remoteClient.remoteFactorPub)); + const factorEnc = this.getFactorEncs(Point.fromSEC1(secp256k1, remoteClient.remoteFactorPub), keyType); const dataRequired: RefreshRemoteTssParams = { factorEnc, factorPubs: factorPubs.map((pub) => pub.toPointHex()), targetIndexes: tssIndices, verifierNameVerifierId, - tssTag: this.tssTag, + tssTag: this._tssTag, tssCommits: tssCommits.map((commit) => commit.toPointHex()), tssNonce, newTSSServerPub: newTSSServerPub.toPointHex(), @@ -617,43 +568,48 @@ export class TKeyTSS extends TKey { }); } - async remoteAddFactorPub(params: { newFactorPub: Point; newFactorTSSIndex: number; remoteClient: IRemoteClientState }) { - const { newFactorPub, newFactorTSSIndex, remoteClient } = params; - const existingFactorPubs = this.metadata.factorPubs[this.tssTag]; + async remoteAddFactorPub(params: { newFactorPub: Point; newFactorTSSIndex: number; remoteClient: IRemoteClientState; keyType: KeyType }) { + const { newFactorPub, newFactorTSSIndex, remoteClient, keyType } = params; + // const ed25519TssMetadata = this.metadata.getTssData(KeyType.ed25519); + const secp256k1TssMetadata = this.metadata.getTssData(KeyType.secp256k1); + + const existingFactorPubs = secp256k1TssMetadata.factorPubs[this._tssTag]; const updatedFactorPubs = existingFactorPubs.concat([newFactorPub]); - const existingTSSIndexes = existingFactorPubs.map((fb) => this.getFactorEncs(fb).tssIndex); + const existingTSSIndexes = existingFactorPubs.map((fb) => this.getFactorEncs(fb, keyType).tssIndex); const updatedTSSIndexes = existingTSSIndexes.concat([newFactorTSSIndex]); await this.remoteRefreshTssShares({ factorPubs: updatedFactorPubs, tssIndices: updatedTSSIndexes, remoteClient, + keyType, }); } - async remoteDeleteFactorPub(params: { factorPubToDelete: Point; remoteClient: IRemoteClientState }) { - const { factorPubToDelete, remoteClient } = params; - const existingFactorPubs = this.metadata.factorPubs[this.tssTag]; + async remoteDeleteFactorPub(params: { factorPubToDelete: Point; remoteClient: IRemoteClientState; keyType: KeyType }) { + const { factorPubToDelete, remoteClient, keyType } = params; + const existingFactorPubs = this.metadata.factorPubs[this._tssTag]; const factorIndex = existingFactorPubs.findIndex((p) => p.x.eq(factorPubToDelete.x)); if (factorIndex === -1) { throw new Error(`factorPub ${factorPubToDelete} does not exist`); } const updatedFactorPubs = existingFactorPubs.slice(); updatedFactorPubs.splice(factorIndex, 1); - const updatedTSSIndexes = updatedFactorPubs.map((fb) => this.getFactorEncs(fb).tssIndex); + const updatedTSSIndexes = updatedFactorPubs.map((fb) => this.getFactorEncs(fb, keyType).tssIndex); await this.remoteRefreshTssShares({ factorPubs: updatedFactorPubs, tssIndices: updatedTSSIndexes, remoteClient, + keyType, }); } - async remoteCopyFactorPub(params: { newFactorPub: Point; tssIndex: number; remoteClient: IRemoteClientState }) { - const { newFactorPub, tssIndex, remoteClient } = params; + async remoteCopyFactorPub(params: { newFactorPub: Point; tssIndex: number; remoteClient: IRemoteClientState; keyType: KeyType }) { + const { newFactorPub, tssIndex, remoteClient, keyType } = params; const remoteFactorPub = Point.fromSEC1(secp256k1, remoteClient.remoteFactorPub); - const factorEnc = this.getFactorEncs(remoteFactorPub); - const tssCommits = this.getTSSCommits().map((commit) => commit.toPointHex()); + const factorEnc = this.getFactorEncs(remoteFactorPub, keyType); + const tssCommits = this.getTSSCommits(keyType).map((commit) => commit.toPointHex()); const dataRequired: CopyRemoteTssParams = { factorEnc, tssCommits, @@ -673,7 +629,7 @@ export class TKeyTSS extends TKey { ) ).data; - const updatedFactorPubs = this.metadata.factorPubs[this.tssTag].concat([newFactorPub]); + const updatedFactorPubs = this.metadata.factorPubs[this._tssTag].concat([newFactorPub]); const factorEncs: { [key: string]: FactorEnc } = JSON.parse(JSON.stringify(this.metadata.factorEncs[this.tssTag])); const factorPubID = newFactorPub.x.toString(16, 64); factorEncs[factorPubID] = { @@ -709,12 +665,16 @@ export class TKeyTSS extends TKey { serverThreshold: number; selectedServers: number[]; authSignatures: string[]; + keyType: KeyType; } ): Promise { if (!this.metadata) throw CoreError.metadataUndefined(); - if (!this.metadata.tssPolyCommits) throw CoreError.default(`tss poly commits obj not found`); - const tssCommits = this.metadata.tssPolyCommits[this.tssTag]; - if (!tssCommits) throw CoreError.default(`tss commits not found for tssTag ${this.tssTag}`); + const { keyType } = serverOpts; + const tssData = this.metadata.getTssData(keyType); + + const { tssTag } = this; + const tssCommits = tssData?.tssPolyCommits[tssTag]; + if (!tssCommits) throw CoreError.default(`tss commits not found for tssTag ${tssTag}`); if (tssCommits.length === 0) throw CoreError.default(`tssCommits is empty`); const tssPubKeyPoint = tssCommits[0]; const tssPubKey = pointToHex(tssPubKeyPoint); @@ -725,20 +685,20 @@ export class TKeyTSS extends TKey { serverPubKeys, serverThreshold, tssPubKey, - keyType: this._tssKeyType, + keyType, }); - if (!this.metadata.factorPubs) throw CoreError.default(`factorPubs obj not found`); + if (!tssData.factorPubs) throw CoreError.default(`factorPubs obj not found`); if (!factorPubs) throw CoreError.default(`factorPubs not found for tssTag ${this.tssTag}`); if (factorPubs.length === 0) throw CoreError.default(`factorPubs is empty`); - if (!this.metadata.tssNonces) throw CoreError.default(`tssNonces obj not found`); - const tssNonce: number = this.metadata.tssNonces[this.tssTag] || 0; + if (!tssData.tssNonces) throw CoreError.default(`tssNonces obj not found`); + const tssNonce: number = tssData.tssNonces[tssTag] || 0; - const oldLabel = `${verifierNameVerifierId}\u0015${this.tssTag}\u0016${tssNonce}`; - const newLabel = `${verifierNameVerifierId}\u0015${this.tssTag}\u0016${tssNonce + 1}`; + const oldLabel = `${verifierNameVerifierId}\u0015${tssTag}\u0016${tssNonce}`; + const newLabel = `${verifierNameVerifierId}\u0015${tssTag}\u0016${tssNonce + 1}`; - const { pubKey: newTSSServerPub, nodeIndexes } = await this.serviceProvider.getTSSPubKey(this.tssTag, tssNonce + 1); + const { pubKey: newTSSServerPub, nodeIndexes } = await this.serviceProvider.getTSSPubKey(tssTag, tssNonce + 1, keyType); let finalSelectedServers = selectedServers; if (nodeIndexes?.length > 0) { @@ -756,7 +716,8 @@ export class TKeyTSS extends TKey { selectedServers: finalSelectedServers, }); - const secondCommit = newTSSServerPub.toEllipticPoint(this._tssCurve).add(ecPoint(this._tssCurve, tssPubKey).neg()); + const ecCurve = getKeyCurve(keyType); + const secondCommit = newTSSServerPub.toEllipticPoint(ecCurve).add(ecPoint(ecCurve, tssPubKey).neg()); const newTSSCommits = [ Point.fromJSON(tssPubKey), Point.fromJSON({ x: secondCommit.getX().toString(16, 64), y: secondCommit.getY().toString(16, 64) }), @@ -775,8 +736,8 @@ export class TKeyTSS extends TKey { } this.metadata.updateTSSData({ - tssKeyType: this._tssKeyType, - tssTag: this.tssTag, + tssKeyType: keyType, + tssTag, tssNonce: tssNonce + 1, tssPolyCommits: newTSSCommits, factorPubs, @@ -785,6 +746,37 @@ export class TKeyTSS extends TKey { if (updateMetadata) await this._syncShareMetadata(); } + async _refreshTSSSharesWithFactorPubs( + params: { updateMetadata: boolean; tssShare: BN; tssIndex: number; factorPubs: Point[]; tssIndexes: number[] }, + serverOpts: { + selectedServers?: number[]; + authSignatures: string[]; + } + ) { + const { factorPubs, tssIndexes, tssShare, tssIndex } = params; + const { selectedServers, authSignatures } = serverOpts; + + const verifierId = this.serviceProvider.getVerifierNameVerifierId(); + const rssNodeDetails = await this._getRssNodeDetails(); + const randomSelectedServers = randomSelection( + new Array(rssNodeDetails.serverEndpoints.length).fill(null).map((_, i) => i + 1), + Math.ceil(rssNodeDetails.serverEndpoints.length / 2) + ); + + const finalServer = selectedServers || randomSelectedServers; + + // sync metadata by default + // create a localMetadataTransition if manual sync + const updateMetadata = params.updateMetadata !== undefined ? params.updateMetadata : true; + + await this._refreshTSSShares(updateMetadata, tssShare, tssIndex, factorPubs, tssIndexes, verifierId, { + ...rssNodeDetails, + selectedServers: finalServer, + authSignatures, + keyType: this.tssKeyType, + }); + } + /** * Derives the account nonce for the specified account index. */ @@ -793,9 +785,7 @@ export class TKeyTSS extends TKey { return new BN(0); } - if (this._tssKeyType === KeyType.ed25519) { - throw new Error("account index not supported with ed25519"); - } + const ec = getKeyCurve(KeyType.secp256k1); // generation should occur during tkey.init, fails if accountSalt is absent if (!this._accountSalt) { @@ -803,7 +793,7 @@ export class TKeyTSS extends TKey { } let accountHash = keccak256(Buffer.from(`${index}${this._accountSalt}`)); if (accountHash.length === 66) accountHash = accountHash.slice(2); - return new BN(accountHash, "hex").umod(this._tssCurve.n); + return new BN(accountHash, "hex").umod(ec.n); } /** @@ -816,7 +806,7 @@ export class TKeyTSS extends TKey { if (accountSalt && accountSalt.value) { this._accountSalt = accountSalt.value; } else { - const newSalt = generateSalt(this._tssCurve); + const newSalt = generateSalt(getKeyCurve(KeyType.secp256k1)); await this._setTKeyStoreItem(TSS_MODULE, { id: "accountSalt", value: newSalt, @@ -832,13 +822,27 @@ export class TKeyTSS extends TKey { return k; } + public async addFactorPub(args: { + existingFactorKey: BN; + newFactorPub: Point; + newTSSIndex: number; + selectedServers?: number[]; + authSignatures: string[]; + refreshShares?: boolean; + updateMetadata?: boolean; + }) { + const addSecp256k1 = this._addFactorPub({ ...args, keyType: KeyType.secp256k1 }); + const addEd25519 = this._addFactorPub({ ...args, keyType: KeyType.ed25519 }); + await Promise.all([addSecp256k1, addEd25519]); + } + /** * Adds a factor key to the set of authorized keys. * * `refreshShares` - If this is true, then refresh the shares. If this is * false, `newTSSIndex` must be the same as current factor key index. */ - public async addFactorPub(args: { + public async _addFactorPub(args: { existingFactorKey: BN; newFactorPub: Point; newTSSIndex: number; @@ -846,13 +850,16 @@ export class TKeyTSS extends TKey { authSignatures: string[]; refreshShares?: boolean; updateMetadata?: boolean; + keyType: KeyType; }) { if (!this.metadata) throw CoreError.metadataUndefined("metadata is undefined"); if (!this.secp256k1Key) throw new Error("Tkey is not reconstructed"); - if (!this.metadata.tssPolyCommits[this.tssTag]) throw new Error(`tss key has not been initialized for tssTag ${this.tssTag}`); - const { existingFactorKey, newFactorPub, newTSSIndex, selectedServers, authSignatures, refreshShares } = args; + const { existingFactorKey, newFactorPub, newTSSIndex, selectedServers, authSignatures, refreshShares, keyType } = args; + const tssData = this.metadata.getTssData(keyType); + + if (!tssData.tssPolyCommits[this._tssTag]) throw new Error(`tss key has not been initialized for tssTag ${this._tssTag}`); - const { tssShare, tssIndex } = await this.getTSSShare(existingFactorKey); + const { tssShare, tssIndex } = await this.getTSSShare(existingFactorKey, { keyType }); if (tssIndex !== newTSSIndex && !refreshShares) { throw CoreError.default("newTSSIndex does not match existing tssIndex, set refreshShares to true to refresh shares"); @@ -864,8 +871,8 @@ export class TKeyTSS extends TKey { throw CoreError.default("newTSSIndex does not match existing tssIndex, set refreshShares to true to refresh shares"); } - const updatedFactorPubs = this.metadata.factorPubs[this.tssTag].concat([newFactorPub]); - const factorEncs = JSON.parse(JSON.stringify(this.metadata.factorEncs[this.tssTag])); + const updatedFactorPubs = tssData.factorPubs[this.tssTag].concat([newFactorPub]); + const factorEncs = JSON.parse(JSON.stringify(tssData.factorEncs[this.tssTag])); const factorPubID = newFactorPub.x.toString(16, 64); factorEncs[factorPubID] = { tssIndex, @@ -874,14 +881,14 @@ export class TKeyTSS extends TKey { serverEncs: [], }; this.metadata.updateTSSData({ - tssKeyType: this.tssKeyType, + tssKeyType: args.keyType, tssTag: this.tssTag, factorPubs: updatedFactorPubs, factorEncs, }); } else { // Use RSS to create new TSS share and store it under new factor key. - const existingFactorPubs = this.metadata.factorPubs[this.tssTag]; + const existingFactorPubs = tssData.factorPubs[this.tssTag]; const updatedFactorPubs = existingFactorPubs.concat([newFactorPub]); const verifierId = this.serviceProvider.getVerifierNameVerifierId(); @@ -893,7 +900,7 @@ export class TKeyTSS extends TKey { const finalServer = selectedServers || randomSelectedServers; - const existingTSSIndexes = existingFactorPubs.map((fb) => this.getFactorEncs(fb).tssIndex); + const existingTSSIndexes = existingFactorPubs.map((fb) => this.getFactorEncs(fb, keyType).tssIndex); const updatedTSSIndexes = existingTSSIndexes.concat([newTSSIndex]); // sync metadata by default @@ -904,33 +911,50 @@ export class TKeyTSS extends TKey { ...rssNodeDetails, selectedServers: finalServer, authSignatures, + keyType: this.tssKeyType, }); } } + public async deleteFactorPub(args: { + factorKey: BN; + deleteFactorPub: Point; + selectedServers?: number[]; + authSignatures: string[]; + updateMetadata?: boolean; + }): Promise { + const addSecp256k1 = this._deleteFactorPub({ ...args, keyType: KeyType.secp256k1 }); + const addEd25519 = this._deleteFactorPub({ ...args, keyType: KeyType.ed25519 }); + await Promise.all([addSecp256k1, addEd25519]); + } + /** * Removes a factor key from the set of authorized keys and refreshes the TSS * key shares. */ - public async deleteFactorPub(args: { + public async _deleteFactorPub(args: { factorKey: BN; deleteFactorPub: Point; selectedServers?: number[]; authSignatures: string[]; updateMetadata?: boolean; + keyType: KeyType; }): Promise { if (!this.metadata) throw CoreError.metadataUndefined("metadata is undefined"); if (!this.secp256k1Key) throw new Error("Tkey is not reconstructed"); - if (!this.metadata.tssPolyCommits[this.tssTag]) throw new Error(`tss key has not been initialized for tssTag ${this.tssTag}`); - const { factorKey, deleteFactorPub, selectedServers, authSignatures } = args; - const existingFactorPubs = this.metadata.factorPubs[this.tssTag]; - const { tssShare, tssIndex } = await this.getTSSShare(factorKey); + const { factorKey, deleteFactorPub, selectedServers, authSignatures, keyType } = args; + + const tssData = this.metadata.getTssData(keyType); + if (!tssData.tssPolyCommits[this.tssTag]) throw new Error(`tss key has not been initialized for tssTag ${this.tssTag}`); + const existingFactorPubs = tssData.factorPubs[this.tssTag]; + const { tssShare, tssIndex } = await this.getTSSShare(factorKey, { keyType }); const found = existingFactorPubs.filter((f) => f.x.eq(deleteFactorPub.x) && f.y.eq(deleteFactorPub.y)); if (found.length === 0) throw CoreError.default("could not find factorPub to delete"); if (found.length > 1) throw CoreError.default("found two or more factorPubs that match, error in metadata"); const updatedFactorPubs = existingFactorPubs.filter((f) => !f.x.eq(deleteFactorPub.x) || !f.y.eq(deleteFactorPub.y)); - this.metadata.updateTSSData({ tssKeyType: this._tssKeyType, tssTag: this.tssTag, factorPubs: updatedFactorPubs }); + + this.metadata.updateTSSData({ tssKeyType: keyType, tssTag: this.tssTag, factorPubs: updatedFactorPubs }); const rssNodeDetails = await this._getRssNodeDetails(); const randomSelectedServers = randomSelection( new Array(rssNodeDetails.serverEndpoints.length).fill(null).map((_, i) => i + 1), @@ -938,7 +962,7 @@ export class TKeyTSS extends TKey { ); const finalServer = selectedServers || randomSelectedServers; - const updatedTSSIndexes = updatedFactorPubs.map((fb) => this.getFactorEncs(fb).tssIndex); + const updatedTSSIndexes = updatedFactorPubs.map((fb) => this.getFactorEncs(fb, args.keyType).tssIndex); const updateMetadata = args.updateMetadata !== undefined ? args.updateMetadata : true; @@ -953,6 +977,7 @@ export class TKeyTSS extends TKey { ...rssNodeDetails, selectedServers: finalServer, authSignatures, + keyType: this.tssKeyType, } ); } @@ -960,9 +985,10 @@ export class TKeyTSS extends TKey { /** * Adjusts a TSS key share based on account index and share coefficient. */ - protected adjustTssShare(share: BN, accountIndex: number, coefficient: BN): BN { + protected adjustTssShare(args: { share: BN; accountIndex: number; coefficient: BN; keyType: KeyType }): BN { + const { share, accountIndex, coefficient, keyType } = args; const nonce = this.computeAccountNonce(accountIndex); - return share.mul(coefficient).add(nonce).umod(this._tssCurve.n); + return share.mul(coefficient).add(nonce).umod(getKeyCurve(keyType).n); } /** @@ -970,26 +996,27 @@ export class TKeyTSS extends TKey { * provided user share. */ protected async _initializeNewTSSKey( + keyType: KeyType, tssTag: string, deviceTSSShare: BN, factorPub: Point, deviceTSSIndex?: number ): Promise { - const ec = this._tssCurve; + const localEc = getKeyCurve(keyType); let tss2: BN; const _tssIndex = deviceTSSIndex || 2; // TODO: fix if (deviceTSSShare) { tss2 = deviceTSSShare; } else { - tss2 = this._tssCurve.genKeyPair().getPrivate(); + tss2 = localEc.genKeyPair().getPrivate(); } - const { pubKey: tss1Pub } = await this.serviceProvider.getTSSPubKey(tssTag, 0); - const tss1PubKey = tss1Pub.toEllipticPoint(ec); - const tss2PubKey = (this._tssCurve.g as EllipticPoint).mul(tss2); + const { pubKey: tss1Pub } = await this.serviceProvider.getTSSPubKey(tssTag, 0, keyType); + const tss1PubKey = tss1Pub.toEllipticPoint(localEc); + const tss2PubKey = (localEc.g as EllipticPoint).mul(tss2); - const L1_0 = getLagrangeCoeffs(ec, [1, _tssIndex], 1, 0); + const L1_0 = getLagrangeCoeffs(localEc, [1, _tssIndex], 1, 0); - const LIndex_0 = getLagrangeCoeffs(ec, [1, _tssIndex], _tssIndex, 0); + const LIndex_0 = getLagrangeCoeffs(localEc, [1, _tssIndex], _tssIndex, 0); const a0Pub = tss1PubKey.mul(L1_0).add(tss2PubKey.mul(LIndex_0)); const a1Pub = tss1PubKey.add(a0Pub.neg()); @@ -1016,4 +1043,155 @@ export class TKeyTSS extends TKey { tssPolyCommits, }; } + + /** + * Imports an existing private key for threshold signing. A corresponding user + * key share will be stored under the specified factor key. + */ + protected async importTssKey( + params: { + tag: string; + importKey: Buffer; + factorPubs: Point[]; + newTSSIndexes: number[]; + tssKeyType: KeyType; + }, + serverOpts: { + selectedServers?: number[]; + authSignatures: string[]; + } + ): Promise { + if (!this.secp256k1Key) throw CoreError.privateKeyUnavailable(); + if (!this.metadata) { + throw CoreError.metadataUndefined(); + } + + const tssData = this.metadata.getTssData(params.tssKeyType); + if (!tssData) throw CoreError.default("no tss data found"); + + const { importKey, factorPubs, newTSSIndexes, tag, tssKeyType } = params; + const ec = getKeyCurve(tssKeyType); + + const oldTag = this._tssTag; + this._tssTag = tag; + + try { + const { selectedServers = [], authSignatures = [] } = serverOpts || {}; + + if (!tag) throw CoreError.default(`invalid param, tag is required`); + if (!factorPubs || factorPubs.length === 0) throw CoreError.default(`invalid param, newFactorPub is required`); + if (!newTSSIndexes || newTSSIndexes.length === 0) throw CoreError.default(`invalid param, newTSSIndex is required`); + if (authSignatures.length === 0) throw CoreError.default(`invalid param, authSignatures is required`); + + const existingFactorPubs = tssData.factorPubs[tag]; + if (existingFactorPubs?.length > 0) { + throw CoreError.default(`Duplicate account tag, please use a unique tag for importing key`); + } + + const importScalar = await (async () => { + if (tssKeyType === KeyType.secp256k1) { + return new BN(importKey); + } else if (tssKeyType === KeyType.ed25519) { + // Store seed in metadata. + const domainKey = getEd25519SeedStoreDomainKey(this._tssTag || TSS_TAG_DEFAULT); + const result = this.metadata.getGeneralStoreDomain(domainKey) as Record; + if (result) { + throw new Error("Seed already exists"); + } + + const { scalar } = getEd25519KeyPairFromSeed(importKey); + const encKey = Buffer.from(getSecpKeyFromEd25519(scalar).point.encodeCompressed("hex"), "hex"); + const msg = await encrypt(encKey, importKey); + this.metadata.setGeneralStoreDomain(domainKey, { message: msg }); + + return scalar; + } + throw new Error("Invalid key type"); + })(); + + if (!importScalar || importScalar.toString("hex") === "0") { + throw new Error("Invalid importedKey"); + } + + const tssIndexes = newTSSIndexes; + const existingNonce = tssData.tssNonces[this._tssTag]; + const newTssNonce: number = existingNonce && existingNonce > 0 ? existingNonce + 1 : 0; + const verifierAndVerifierID = this.serviceProvider.getVerifierNameVerifierId(); + const label = `${verifierAndVerifierID}\u0015${this._tssTag}\u0016${newTssNonce}`; + const tssPubKey = hexPoint(ec.g.mul(importScalar)); + const rssNodeDetails = await this._getRssNodeDetails(); + const { pubKey: newTSSServerPub, nodeIndexes } = await this.serviceProvider.getTSSPubKey(this._tssTag, newTssNonce, tssKeyType); + let finalSelectedServers = selectedServers; + + if (nodeIndexes?.length > 0) { + if (selectedServers.length) { + finalSelectedServers = nodeIndexes.slice(0, Math.min(selectedServers.length, nodeIndexes.length)); + } else { + finalSelectedServers = nodeIndexes.slice(0, 3); + } + } else if (selectedServers?.length === 0) { + finalSelectedServers = randomSelection( + new Array(rssNodeDetails.serverEndpoints.length).fill(null).map((_, i) => i + 1), + Math.ceil(rssNodeDetails.serverEndpoints.length / 2) + ); + } + + const { serverEndpoints, serverPubKeys, serverThreshold } = rssNodeDetails; + + const rssClient = new RSSClient({ + serverEndpoints, + serverPubKeys, + serverThreshold, + tssPubKey, + keyType: tssKeyType, + }); + + const refreshResponses = await rssClient.import({ + importKey: importScalar, + dkgNewPub: pointToHex(newTSSServerPub), + selectedServers: finalSelectedServers, + factorPubs: factorPubs.map((f) => pointToHex(f)), + targetIndexes: tssIndexes, + newLabel: label, + sigs: authSignatures, + }); + const secondCommit = newTSSServerPub.toEllipticPoint(ec).add(ecPoint(ec, tssPubKey).neg()); + const newTSSCommits = [ + Point.fromJSON(tssPubKey), + Point.fromJSON({ x: secondCommit.getX().toString(16, 64), y: secondCommit.getY().toString(16, 64) }), + ]; + const factorEncs: { + [factorPubID: string]: FactorEnc; + } = {}; + for (let i = 0; i < refreshResponses.length; i++) { + const refreshResponse = refreshResponses[i]; + factorEncs[refreshResponse.factorPub.x.padStart(64, "0")] = { + type: "hierarchical", + tssIndex: refreshResponse.targetIndex, + userEnc: refreshResponse.userFactorEnc, + serverEncs: refreshResponse.serverFactorEncs, + }; + } + this.metadata.updateTSSData({ + tssKeyType, + tssTag: this.tssTag, + tssNonce: newTssNonce, + tssPolyCommits: newTSSCommits, + factorPubs, + factorEncs, + }); + if (!this._accountSalt) { + const accountSalt = generateSalt(this._tssCurve); + await this._setTKeyStoreItem(TSS_MODULE, { + id: "accountSalt", + value: accountSalt, + } as IAccountSaltStore); + this._accountSalt = accountSalt; + } + await this._syncShareMetadata(); + } catch (error) { + this._tssTag = oldTag; + throw error; + } + } } From af85c8e18d91dc88fc5af1ddfadab815420eb421 Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 23 Jan 2025 13:41:15 +0800 Subject: [PATCH 02/22] fix: update metadata format --- .../src/baseTypes/aggregateTypes.ts | 22 +-- packages/core/src/metadata.ts | 132 ++++++++++-------- packages/tss/src/tss.ts | 79 ++++++----- 3 files changed, 123 insertions(+), 110 deletions(-) diff --git a/packages/common-types/src/baseTypes/aggregateTypes.ts b/packages/common-types/src/baseTypes/aggregateTypes.ts index f4d1082d..c29b8dc3 100644 --- a/packages/common-types/src/baseTypes/aggregateTypes.ts +++ b/packages/common-types/src/baseTypes/aggregateTypes.ts @@ -64,26 +64,18 @@ export type FactorEnc = { }; export interface ITssMetadata { - tssKeyTypes: { - [tssTag: string]: KeyType; - }; + tssTag: string; - tssNonces: { - [tssTag: string]: number; - }; + tssKeyType: KeyType; - tssPolyCommits: { - [tssTag: string]: Point[]; - }; + tssNonce: number; - factorPubs: { - [tssTag: string]: Point[]; - }; + tssPolyCommits: Point[]; + + factorPubs: Point[]; factorEncs: { - [tssTag: string]: { - [factorPubID: string]: FactorEnc; - }; + [factorPubID: string]: FactorEnc; }; } diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index 91ee2653..1e50b234 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -29,61 +29,52 @@ import stringify from "json-stable-stringify"; import CoreError from "./errors"; import { polyCommitmentEval } from "./lagrangeInterpolatePolynomial"; +export type SupportedCurve = "ed25519" | "sec"; const METADATA_VERSION = 1; export class TssMetadata implements ITssMetadata, ISerializable { - tssKeyTypes: { - [tssTag: string]: KeyType; - }; + tssTag: string; - tssNonces: { - [tssTag: string]: number; - }; + tssKeyType: KeyType; - tssPolyCommits: { - [tssTag: string]: Point[]; - }; + tssNonce: number; - factorPubs: { - [tssTag: string]: Point[]; - }; + tssPolyCommits: Point[]; + + factorPubs: Point[]; factorEncs: { - [tssTag: string]: { - [factorPubID: string]: FactorEnc; - }; + [factorPubID: string]: FactorEnc; }; - constructor() { - this.tssKeyTypes = {}; - this.tssNonces = {}; - this.tssPolyCommits = {}; - this.factorPubs = {}; - this.factorEncs = {}; + constructor(params: ITssMetadata) { + this.tssTag = params.tssTag; + this.tssKeyType = params.tssKeyType; + this.tssNonce = params.tssNonce; + this.tssPolyCommits = params.tssPolyCommits; + this.factorPubs = params.factorPubs; + this.factorEncs = params.factorEncs; } static fromJSON(value: StringifiedType): TssMetadata { - const { tssKeyTypes, tssPolyCommits, tssNonces, factorPubs, factorEncs } = value; - const tssMetadata = new TssMetadata(); + const { tssTag, tssKeyType, tssPolyCommits, tssNonce, factorPubs, factorEncs } = value; + const tssMetadata = new TssMetadata({ + tssTag, + tssKeyType, + tssNonce, + tssPolyCommits, + factorEncs, + factorPubs, + }); - if (tssKeyTypes) { - for (const key in tssKeyTypes) { - tssMetadata.tssKeyTypes[key] = tssKeyTypes[key]; - } - } if (tssPolyCommits) { for (const key in tssPolyCommits) { - tssMetadata.tssPolyCommits[key] = (tssPolyCommits as Record)[key].map((obj) => new Point(obj.x, obj.y)); - } - } - if (tssNonces) { - for (const key in tssNonces) { - tssMetadata.tssNonces[key] = tssNonces[key]; + tssMetadata.tssPolyCommits = (tssPolyCommits as Record)[key].map((obj) => new Point(obj.x, obj.y)); } } if (factorPubs) { for (const key in factorPubs) { - tssMetadata.factorPubs[key] = (factorPubs as Record)[key].map((obj) => new Point(obj.x, obj.y)); + tssMetadata.factorPubs = (factorPubs as Record)[key].map((obj) => new Point(obj.x, obj.y)); } } if (factorEncs) tssMetadata.factorEncs = factorEncs; @@ -93,8 +84,8 @@ export class TssMetadata implements ITssMetadata, ISerializable { toJSON(): StringifiedType { return { - tssKeyTypes: this.tssKeyTypes, - tssNonces: this.tssNonces, + tssKeyTypes: this.tssKeyType, + tssNonces: this.tssNonce, tssPolyCommits: this.tssPolyCommits, factorPubs: this.factorPubs, factorEncs: this.factorEncs, @@ -112,11 +103,12 @@ export class TssMetadata implements ITssMetadata, ISerializable { }; }) { const { tssKeyType, tssTag, tssNonce, tssPolyCommits, factorPubs, factorEncs } = tssData; - if (tssKeyType) this.tssKeyTypes[tssTag] = tssKeyType; - 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 (tssTag) this.tssTag = tssTag; + if (tssKeyType) this.tssKeyType = tssKeyType; + if (tssNonce !== undefined) this.tssNonce = tssNonce; + if (tssPolyCommits) this.tssPolyCommits = tssPolyCommits; + if (factorPubs) this.factorPubs = factorPubs; + if (factorEncs) this.factorEncs = factorEncs; } } @@ -167,8 +159,12 @@ class Metadata implements IMetadata { }; tss?: { - secp256k1: TssMetadata; - ed25519: TssMetadata; + [tssTag: string]: { + [curveType: string]: TssMetadata; + }; + // [tssTag: string]: Record<"secp256k1" "ed25519", TssMetadata>; + // secp256k1: TssMetadata; + // ed25519: TssMetadata; }; version = METADATA_VERSION; @@ -197,13 +193,14 @@ class Metadata implements IMetadata { tkeyStore, scopedStore, nonce, + tss, + version, + // v0 metadata tssKeyTypes, tssPolyCommits, tssNonces, factorPubs, factorEncs, - tss, - version, } = value; const point = Point.fromSEC1(secp256k1, pubKey); const metadata = new Metadata(point); @@ -219,16 +216,19 @@ class Metadata implements IMetadata { if (version === 1) { metadata.tss = tss; } else if (tssKeyTypes) { - const tssData = TssMetadata.fromJSON({ - tssKeyTypes, - tssPolyCommits, - tssNonces, - factorPubs, - factorEncs, + metadata.tss = {}; + Object.keys(tssKeyTypes).forEach((tssTag) => { + metadata.tss[tssTag] = { + [tssKeyTypes[tssTag]]: TssMetadata.fromJSON({ + tssTag, + tssKeyType: tssKeyTypes[tssTag], + tssNonce: tssNonces[tssTag], + tssPolyCommits: tssPolyCommits[tssTag], + factorPubs: factorPubs[tssTag], + factorEncs: factorEncs[tssTag], + }), + }; }); - if (tssData.tssKeyTypes.default) { - metadata.tss[tssData.tssKeyTypes.default] = tssData; - } } for (let i = 0; i < polyIDList.length; i += 1) { @@ -450,15 +450,27 @@ class Metadata implements IMetadata { [factorPubID: string]: FactorEnc; }; }): void { - if (tssData.tssKeyType === KeyType.ed25519) this.tss.ed25519.update(tssData); - else if (tssData.tssKeyType === KeyType.secp256k1) this.tss.secp256k1.update(tssData); + const { tssKeyType, tssTag, tssNonce, tssPolyCommits, factorPubs, factorEncs } = tssData; + if (!this.tss) this.tss = {}; + if (!this.tss[tssData.tssTag]) { + if (!tssKeyType) throw Error("Missing tssKeyType"); + this.tss[tssData.tssTag] = { + [tssKeyType]: new TssMetadata({ tssTag, tssKeyType, tssNonce, tssPolyCommits, factorPubs, factorEncs }), + }; + } + if (tssData.tssKeyType === KeyType.ed25519) this.tss[tssTag].ed25519.update(tssData); + else if (tssData.tssKeyType === KeyType.secp256k1) this.tss[tssTag].secp256k1.update(tssData); } - getTssData(tssKeyType: KeyType): ITssMetadata { + getTssData(tssKeyType: KeyType, tssTag: string = "default"): ITssMetadata { + // const tssDataList = this.tss?[tssTag]; + if (!this.tss) throw Error("Missing TssData"); + if (!this.tss[tssTag]) throw Error("Invalid Tss Tag"); + if (tssKeyType === KeyType.secp256k1) { - return this.tss?.secp256k1; + return this.tss[tssTag].secp256k1; } else if (tssKeyType === KeyType.ed25519) { - return this.tss?.ed25519; + return this.tss[tssTag].ed25519; } } } diff --git a/packages/tss/src/tss.ts b/packages/tss/src/tss.ts index 20a77f09..738dfe41 100644 --- a/packages/tss/src/tss.ts +++ b/packages/tss/src/tss.ts @@ -184,8 +184,8 @@ export class TKeyTSS extends TKey { this._accountSalt = accountSalt; if (ed25519Exist) { - const tssIndexes = ed25519TssData.factorPubs[this._tssTag].map((point) => { - return ed25519TssData.factorEncs[this._tssTag][point.x.toString("hex", 64)].tssIndex; + const tssIndexes = ed25519TssData.factorPubs.map((point) => { + return ed25519TssData.factorEncs[point.x.toString("hex", 64)].tssIndex; }); // refresh with factorPubs this._refreshTSSSharesWithFactorPubs( @@ -193,7 +193,7 @@ export class TKeyTSS extends TKey { updateMetadata: false, tssShare: tss2, tssIndex: params.deviceTSSIndex ?? 2, - factorPubs: ed25519TssData.factorPubs[this._tssTag], + factorPubs: ed25519TssData.factorPubs, tssIndexes, }, serverOpts @@ -204,9 +204,9 @@ export class TKeyTSS extends TKey { let newTSSIndexes = [2]; if (ed25519Exist) { // refresh with factorPubs - factorPubs = ed25519TssData.factorPubs[this._tssTag]; - newTSSIndexes = ed25519TssData.factorPubs[this._tssTag].map((point) => { - return ed25519TssData.factorEncs[this._tssTag][point.x.toString("hex", 64)].tssIndex; + factorPubs = ed25519TssData.factorPubs; + newTSSIndexes = ed25519TssData.factorPubs.map((point) => { + return ed25519TssData.factorEncs[point.x.toString("hex", 64)].tssIndex; }); } @@ -252,9 +252,9 @@ export class TKeyTSS extends TKey { let newTSSIndexes = [2]; if (secp256k1Exist) { // refresh with factorPubs - factorPubs = secp256k1TssData.factorPubs[this._tssTag]; - newTSSIndexes = secp256k1TssData.factorPubs[this._tssTag].map((point) => { - return secp256k1TssData.factorEncs[this._tssTag][point.x.toString("hex", 64)].tssIndex; + factorPubs = secp256k1TssData.factorPubs; + newTSSIndexes = secp256k1TssData.factorPubs.map((point) => { + return secp256k1TssData.factorEncs[point.x.toString("hex", 64)].tssIndex; }); } this.importTssKey( @@ -278,13 +278,13 @@ export class TKeyTSS extends TKey { if (!tssData) throw CoreError.default("no factor encs mapping"); - const factorPubs = tssData.factorPubs[this.tssTag]; + const { factorPubs } = tssData; if (!factorPubs) throw CoreError.default(`no factor pubs for this tssTag ${this.tssTag}`); if (factorPubs.filter((f) => f.x.cmp(factorPub.x) === 0 && f.y.cmp(factorPub.y) === 0).length === 0) throw CoreError.default(`factor pub ${factorPub} not found for tssTag ${this.tssTag}`); - if (!tssData.factorEncs[this.tssTag]) throw CoreError.default(`no factor encs for tssTag ${this.tssTag}`); + if (!tssData.factorEncs) throw CoreError.default(`no factor encs for tssTag ${this.tssTag}`); const factorPubID = factorPub.x.toString(16, 64); - return tssData.factorEncs[this.tssTag][factorPubID]; + return tssData.factorEncs[factorPubID]; } /** @@ -380,7 +380,7 @@ export class TKeyTSS extends TKey { const tssData = this.metadata.getTssData(tssKeyType); if (!tssData) throw CoreError.default("no tss data"); - const tssPolyCommits = tssData.tssPolyCommits[this._tssTag]; + const { tssPolyCommits } = tssData; if (!tssPolyCommits) throw CoreError.default(`tss poly commits not found for tssTag ${this._tssTag}`); if (tssPolyCommits.length === 0) throw CoreError.default("tss poly commits is empty"); return tssPolyCommits; @@ -435,7 +435,7 @@ export class TKeyTSS extends TKey { if (!this.metadata) throw CoreError.metadataUndefined("metadata is undefined"); if (!this.secp256k1Key) throw new Error("Tkey is not reconstructed"); const tssData = this.metadata.getTssData(KeyType.secp256k1); - if (!tssData.tssPolyCommits[this.tssTag]) throw new Error(`tss key has not been initialized for tssTag ${this.tssTag}`); + if (!tssData?.tssPolyCommits) throw new Error(`tss key has not been initialized for tssTag ${this.tssTag}`); const { factorKey, selectedServers, authSignatures, accountIndex } = tssOptions; @@ -518,8 +518,8 @@ export class TKeyTSS extends TKey { const tssData = this.metadata.getTssData(this.tssKeyType); if (!tssData) throw CoreError.default("no tss data"); - const tssCommits = tssData.tssPolyCommits[this._tssTag]; - const tssNonce: number = tssData.tssNonces[this._tssTag] || 0; + const tssCommits = tssData.tssPolyCommits; + const tssNonce: number = tssData.tssNonce || 0; const { pubKey: newTSSServerPub, nodeIndexes } = await this.serviceProvider.getTSSPubKey(this.tssTag, tssNonce + 1, keyType); // move to pre-refresh if (nodeIndexes?.length > 0) { @@ -573,7 +573,7 @@ export class TKeyTSS extends TKey { // const ed25519TssMetadata = this.metadata.getTssData(KeyType.ed25519); const secp256k1TssMetadata = this.metadata.getTssData(KeyType.secp256k1); - const existingFactorPubs = secp256k1TssMetadata.factorPubs[this._tssTag]; + const existingFactorPubs = secp256k1TssMetadata.factorPubs; const updatedFactorPubs = existingFactorPubs.concat([newFactorPub]); const existingTSSIndexes = existingFactorPubs.map((fb) => this.getFactorEncs(fb, keyType).tssIndex); const updatedTSSIndexes = existingTSSIndexes.concat([newFactorTSSIndex]); @@ -588,7 +588,8 @@ export class TKeyTSS extends TKey { async remoteDeleteFactorPub(params: { factorPubToDelete: Point; remoteClient: IRemoteClientState; keyType: KeyType }) { const { factorPubToDelete, remoteClient, keyType } = params; - const existingFactorPubs = this.metadata.factorPubs[this._tssTag]; + const tssData = this.metadata.getTssData(keyType); + const existingFactorPubs = tssData.factorPubs; const factorIndex = existingFactorPubs.findIndex((p) => p.x.eq(factorPubToDelete.x)); if (factorIndex === -1) { throw new Error(`factorPub ${factorPubToDelete} does not exist`); @@ -629,8 +630,10 @@ export class TKeyTSS extends TKey { ) ).data; - const updatedFactorPubs = this.metadata.factorPubs[this._tssTag].concat([newFactorPub]); - const factorEncs: { [key: string]: FactorEnc } = JSON.parse(JSON.stringify(this.metadata.factorEncs[this.tssTag])); + const tssData = this.metadata.getTssData(keyType); + + const updatedFactorPubs = tssData.factorPubs.concat([newFactorPub]); + const factorEncs: { [key: string]: FactorEnc } = JSON.parse(JSON.stringify(tssData.factorEncs)); const factorPubID = newFactorPub.x.toString(16, 64); factorEncs[factorPubID] = { tssIndex, @@ -673,7 +676,7 @@ export class TKeyTSS extends TKey { const tssData = this.metadata.getTssData(keyType); const { tssTag } = this; - const tssCommits = tssData?.tssPolyCommits[tssTag]; + const tssCommits = tssData?.tssPolyCommits; if (!tssCommits) throw CoreError.default(`tss commits not found for tssTag ${tssTag}`); if (tssCommits.length === 0) throw CoreError.default(`tssCommits is empty`); const tssPubKeyPoint = tssCommits[0]; @@ -692,8 +695,8 @@ export class TKeyTSS extends TKey { if (!factorPubs) throw CoreError.default(`factorPubs not found for tssTag ${this.tssTag}`); if (factorPubs.length === 0) throw CoreError.default(`factorPubs is empty`); - if (!tssData.tssNonces) throw CoreError.default(`tssNonces obj not found`); - const tssNonce: number = tssData.tssNonces[tssTag] || 0; + if (!tssData.tssNonce) throw CoreError.default(`tssNonces obj not found`); + const tssNonce: number = tssData.tssNonce || 0; const oldLabel = `${verifierNameVerifierId}\u0015${tssTag}\u0016${tssNonce}`; const newLabel = `${verifierNameVerifierId}\u0015${tssTag}\u0016${tssNonce + 1}`; @@ -831,9 +834,18 @@ export class TKeyTSS extends TKey { refreshShares?: boolean; updateMetadata?: boolean; }) { - const addSecp256k1 = this._addFactorPub({ ...args, keyType: KeyType.secp256k1 }); - const addEd25519 = this._addFactorPub({ ...args, keyType: KeyType.ed25519 }); - await Promise.all([addSecp256k1, addEd25519]); + const secp256k1Data = this.metadata.getTssData(KeyType.secp256k1); + const ed25519Data = this.metadata.getTssData(KeyType.ed25519); + + const allPromise = []; + + if (secp256k1Data.tssTag) { + allPromise.push(this._addFactorPub({ ...args, keyType: KeyType.secp256k1 })); + } + if (ed25519Data.tssTag) { + allPromise.push(this._addFactorPub({ ...args, keyType: KeyType.secp256k1 })); + } + await Promise.all(allPromise); } /** @@ -857,8 +869,6 @@ export class TKeyTSS extends TKey { const { existingFactorKey, newFactorPub, newTSSIndex, selectedServers, authSignatures, refreshShares, keyType } = args; const tssData = this.metadata.getTssData(keyType); - if (!tssData.tssPolyCommits[this._tssTag]) throw new Error(`tss key has not been initialized for tssTag ${this._tssTag}`); - const { tssShare, tssIndex } = await this.getTSSShare(existingFactorKey, { keyType }); if (tssIndex !== newTSSIndex && !refreshShares) { @@ -871,8 +881,8 @@ export class TKeyTSS extends TKey { throw CoreError.default("newTSSIndex does not match existing tssIndex, set refreshShares to true to refresh shares"); } - const updatedFactorPubs = tssData.factorPubs[this.tssTag].concat([newFactorPub]); - const factorEncs = JSON.parse(JSON.stringify(tssData.factorEncs[this.tssTag])); + const updatedFactorPubs = tssData.factorPubs.concat([newFactorPub]); + const factorEncs = JSON.parse(JSON.stringify(tssData.factorEncs)); const factorPubID = newFactorPub.x.toString(16, 64); factorEncs[factorPubID] = { tssIndex, @@ -888,7 +898,7 @@ export class TKeyTSS extends TKey { }); } else { // Use RSS to create new TSS share and store it under new factor key. - const existingFactorPubs = tssData.factorPubs[this.tssTag]; + const existingFactorPubs = tssData.factorPubs; const updatedFactorPubs = existingFactorPubs.concat([newFactorPub]); const verifierId = this.serviceProvider.getVerifierNameVerifierId(); @@ -945,8 +955,7 @@ export class TKeyTSS extends TKey { const { factorKey, deleteFactorPub, selectedServers, authSignatures, keyType } = args; const tssData = this.metadata.getTssData(keyType); - if (!tssData.tssPolyCommits[this.tssTag]) throw new Error(`tss key has not been initialized for tssTag ${this.tssTag}`); - const existingFactorPubs = tssData.factorPubs[this.tssTag]; + const existingFactorPubs = tssData.factorPubs; const { tssShare, tssIndex } = await this.getTSSShare(factorKey, { keyType }); const found = existingFactorPubs.filter((f) => f.x.eq(deleteFactorPub.x) && f.y.eq(deleteFactorPub.y)); @@ -1083,7 +1092,7 @@ export class TKeyTSS extends TKey { if (!newTSSIndexes || newTSSIndexes.length === 0) throw CoreError.default(`invalid param, newTSSIndex is required`); if (authSignatures.length === 0) throw CoreError.default(`invalid param, authSignatures is required`); - const existingFactorPubs = tssData.factorPubs[tag]; + const existingFactorPubs = tssData.factorPubs; if (existingFactorPubs?.length > 0) { throw CoreError.default(`Duplicate account tag, please use a unique tag for importing key`); } @@ -1114,7 +1123,7 @@ export class TKeyTSS extends TKey { } const tssIndexes = newTSSIndexes; - const existingNonce = tssData.tssNonces[this._tssTag]; + const existingNonce = tssData.tssNonce; const newTssNonce: number = existingNonce && existingNonce > 0 ? existingNonce + 1 : 0; const verifierAndVerifierID = this.serviceProvider.getVerifierNameVerifierId(); const label = `${verifierAndVerifierID}\u0015${this._tssTag}\u0016${newTssNonce}`; From 17b4e4025128d6ebfaea7145be927fd27993f59d Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 23 Jan 2025 17:49:27 +0800 Subject: [PATCH 03/22] feat: LegacyMetadata serialization --- packages/core/src/metadata.ts | 176 ++++++++++++------------------- packages/core/src/tssMetadata.ts | 81 ++++++++++++++ packages/tss/test/tss.ts | 100 ++++++++++++------ 3 files changed, 215 insertions(+), 142 deletions(-) create mode 100644 packages/core/src/tssMetadata.ts diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index 1e50b234..3bdb4ed2 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -4,7 +4,6 @@ import { FactorEnc, getPubKeyPoint, IMetadata, - ISerializable, ITssMetadata, KeyType, Point, @@ -28,90 +27,11 @@ import stringify from "json-stable-stringify"; import CoreError from "./errors"; import { polyCommitmentEval } from "./lagrangeInterpolatePolynomial"; +import { TssMetadata } from "./tssMetadata"; export type SupportedCurve = "ed25519" | "sec"; const METADATA_VERSION = 1; -export class TssMetadata implements ITssMetadata, ISerializable { - tssTag: string; - - tssKeyType: KeyType; - - tssNonce: number; - - tssPolyCommits: Point[]; - - factorPubs: Point[]; - - factorEncs: { - [factorPubID: string]: FactorEnc; - }; - - constructor(params: ITssMetadata) { - this.tssTag = params.tssTag; - this.tssKeyType = params.tssKeyType; - this.tssNonce = params.tssNonce; - this.tssPolyCommits = params.tssPolyCommits; - this.factorPubs = params.factorPubs; - this.factorEncs = params.factorEncs; - } - - static fromJSON(value: StringifiedType): TssMetadata { - const { tssTag, tssKeyType, tssPolyCommits, tssNonce, factorPubs, factorEncs } = value; - const tssMetadata = new TssMetadata({ - tssTag, - tssKeyType, - tssNonce, - tssPolyCommits, - factorEncs, - factorPubs, - }); - - if (tssPolyCommits) { - for (const key in tssPolyCommits) { - tssMetadata.tssPolyCommits = (tssPolyCommits as Record)[key].map((obj) => new Point(obj.x, obj.y)); - } - } - if (factorPubs) { - for (const key in factorPubs) { - tssMetadata.factorPubs = (factorPubs as Record)[key].map((obj) => new Point(obj.x, obj.y)); - } - } - if (factorEncs) tssMetadata.factorEncs = factorEncs; - - return tssMetadata; - } - - toJSON(): StringifiedType { - return { - tssKeyTypes: this.tssKeyType, - tssNonces: this.tssNonce, - tssPolyCommits: this.tssPolyCommits, - factorPubs: this.factorPubs, - factorEncs: this.factorEncs, - }; - } - - update(tssData: { - tssTag: string; - tssKeyType?: KeyType; - tssNonce?: number; - tssPolyCommits?: Point[]; - factorPubs?: Point[]; - factorEncs?: { - [factorPubID: string]: FactorEnc; - }; - }) { - const { tssKeyType, tssTag, tssNonce, tssPolyCommits, factorPubs, factorEncs } = tssData; - if (tssTag) this.tssTag = tssTag; - if (tssKeyType) this.tssKeyType = tssKeyType; - if (tssNonce !== undefined) this.tssNonce = tssNonce; - if (tssPolyCommits) this.tssPolyCommits = tssPolyCommits; - if (factorPubs) this.factorPubs = factorPubs; - if (factorEncs) this.factorEncs = factorEncs; - } -} - class Metadata implements IMetadata { pubKey: Point; @@ -136,28 +56,6 @@ class Metadata implements IMetadata { nonce: number; - tssKeyTypes?: { - [tssTag: string]: KeyType; - }; - - tssNonces?: { - [tssTag: string]: number; - }; - - tssPolyCommits?: { - [tssTag: string]: Point[]; - }; - - factorPubs?: { - [tssTag: string]: Point[]; - }; - - factorEncs?: { - [tssTag: string]: { - [factorPubID: string]: FactorEnc; - }; - }; - tss?: { [tssTag: string]: { [curveType: string]: TssMetadata; @@ -178,11 +76,6 @@ class Metadata implements IMetadata { this.pubKey = input; this.polyIDList = []; this.nonce = 0; - this.tssKeyTypes = {}; - this.tssPolyCommits = {}; - this.tssNonces = {}; - this.factorPubs = {}; - this.factorEncs = {}; } static fromJSON(value: StringifiedType): Metadata { @@ -475,4 +368,71 @@ class Metadata implements IMetadata { } } +export class LegacyMetadata extends Metadata { + toJSON(): StringifiedType { + // squash data to serialized polyID according to spec + const serializedPolyIDList = []; + for (let i = 0; i < this.polyIDList.length; i += 1) { + const polyID = this.polyIDList[i][0]; + const shareIndexes = this.polyIDList[i][1]; + const sortedShareIndexes = shareIndexes.sort((a: string, b: string) => new BN(a, "hex").cmp(new BN(b, "hex"))); + const serializedPolyID = polyID + .split(`|`) + .concat("0x0") + .concat(...sortedShareIndexes) + .join("|"); + serializedPolyIDList.push(serializedPolyID); + } + + const tsstags = Object.keys(this.tss); + + // return if tss data is not available + if (tsstags.length > 0) { + return { + pubKey: this.pubKey.toSEC1(secp256k1, true).toString("hex"), + polyIDList: serializedPolyIDList, + scopedStore: this.scopedStore, + generalStore: this.generalStore, + tkeyStore: this.tkeyStore, + nonce: this.nonce, + }; + } + + // if there is tssdata, try serialize to legacy format + + const tssKeyTypes: Record = {}; + const tssNonces: Record = {}; + const tssPolyCommits: Record = {}; + const factorPubs: Record = {}; + const factorEncs: Record> = {}; + + tsstags.forEach((tag) => { + const allTssData = this.tss[tag]; + const allKeyTypes = Object.keys(allTssData); + if (allKeyTypes.length > 1) throw Error("Metadata Error: Do not support multicurve serialization"); + + const tssData = this.getTssData(allKeyTypes[0] as KeyType, tag); + tssKeyTypes[tag] = tssData.tssKeyType; + tssNonces[tag] = tssData.tssNonce; + tssPolyCommits[tag] = tssData.tssPolyCommits.map((pt) => pt.toJSON()); + factorPubs[tag] = tssData.factorPubs.map((pt) => pt.toJSON()); + factorEncs[tag] = tssData.factorEncs; + }); + + return { + pubKey: this.pubKey.toSEC1(secp256k1, true).toString("hex"), + polyIDList: serializedPolyIDList, + scopedStore: this.scopedStore, + generalStore: this.generalStore, + tkeyStore: this.tkeyStore, + nonce: this.nonce, + tssKeyTypes, + tssNonces, + tssPolyCommits, + factorPubs, + factorEncs, + }; + } +} + export default Metadata; diff --git a/packages/core/src/tssMetadata.ts b/packages/core/src/tssMetadata.ts new file mode 100644 index 00000000..a48ed8a3 --- /dev/null +++ b/packages/core/src/tssMetadata.ts @@ -0,0 +1,81 @@ +import { FactorEnc, ISerializable, ITssMetadata, KeyType, Point, StringifiedType } from "@tkey/common-types"; + +export class TssMetadata implements ITssMetadata, ISerializable { + tssTag: string; + + tssKeyType: KeyType; + + tssNonce: number; + + tssPolyCommits: Point[]; + + factorPubs: Point[]; + + factorEncs: { + [factorPubID: string]: FactorEnc; + }; + + constructor(params: ITssMetadata) { + this.tssTag = params.tssTag; + this.tssKeyType = params.tssKeyType; + this.tssNonce = params.tssNonce; + this.tssPolyCommits = params.tssPolyCommits; + this.factorPubs = params.factorPubs; + this.factorEncs = params.factorEncs; + } + + static fromJSON(value: StringifiedType): TssMetadata { + const { tssTag, tssKeyType, tssPolyCommits, tssNonce, factorPubs, factorEncs } = value; + const tssMetadata = new TssMetadata({ + tssTag, + tssKeyType, + tssNonce, + tssPolyCommits, + factorEncs, + factorPubs, + }); + + if (tssPolyCommits) { + for (const key in tssPolyCommits) { + tssMetadata.tssPolyCommits = (tssPolyCommits as Record)[key].map((obj) => new Point(obj.x, obj.y)); + } + } + if (factorPubs) { + for (const key in factorPubs) { + tssMetadata.factorPubs = (factorPubs as Record)[key].map((obj) => new Point(obj.x, obj.y)); + } + } + if (factorEncs) tssMetadata.factorEncs = factorEncs; + + return tssMetadata; + } + + toJSON(): StringifiedType { + return { + tssKeyTypes: this.tssKeyType, + tssNonces: this.tssNonce, + tssPolyCommits: this.tssPolyCommits, + factorPubs: this.factorPubs, + factorEncs: this.factorEncs, + }; + } + + update(tssData: { + tssTag: string; + tssKeyType?: KeyType; + tssNonce?: number; + tssPolyCommits?: Point[]; + factorPubs?: Point[]; + factorEncs?: { + [factorPubID: string]: FactorEnc; + }; + }) { + const { tssKeyType, tssTag, tssNonce, tssPolyCommits, factorPubs, factorEncs } = tssData; + if (tssTag) this.tssTag = tssTag; + if (tssKeyType) this.tssKeyType = tssKeyType; + if (tssNonce !== undefined) this.tssNonce = tssNonce; + if (tssPolyCommits) this.tssPolyCommits = tssPolyCommits; + if (factorPubs) this.factorPubs = factorPubs; + if (factorEncs) this.factorEncs = factorEncs; + } +} diff --git a/packages/tss/test/tss.ts b/packages/tss/test/tss.ts index 6a94c06a..ece06115 100644 --- a/packages/tss/test/tss.ts +++ b/packages/tss/test/tss.ts @@ -36,7 +36,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { sp.verifierName = "torus-test-health"; sp.verifierId = "test@example.com"; - const { postboxkey } = await fetchPostboxKeyAndSigs({ + const { postboxkey, signatures } = await fetchPostboxKeyAndSigs({ serviceProvider: sp, verifierName: sp.verifierName, verifierId: sp.verifierId, @@ -56,15 +56,38 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { const factorKey = factorKeyPair.getPrivate(); const factorPub = Point.fromElliptic(factorKeyPair.getPublic()); - await tb1.initialize({ factorPub, deviceTSSShare, deviceTSSIndex }); + await tb1.initialize(); + if (TSS_KEY_TYPE === KeyType.secp256k1) { + tb1.initializeTssSecp256k1({ + factorPub, + deviceTSSShare, + deviceTSSIndex, + serverOpts: { + // selectedServers: [], + authSignatures: signatures, + }, + }); + } else { + tb1.initializeTssEd25519({ + factorPub, + deviceTSSShare, + deviceTSSIndex, + serverOpts: { + // selectedServers: [], + authSignatures: signatures, + }, + importKey: undefined, + }); + } + const reconstructedKey = await tb1.reconstructKey(); await tb1.syncLocalMetadataTransitions(); if (tb1.secp256k1Key.cmp(reconstructedKey.secp256k1Key) !== 0) { fail("key should be able to be reconstructed"); } - const { tssShare: tss2 } = await tb1.getTSSShare(factorKey); + const { tssShare: tss2 } = await tb1.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); - const tssCommits = tb1.getTSSCommits(); + const tssCommits = tb1.getTSSCommits(TSS_KEY_TYPE); const tss2Pub = ecTSS.g.mul(tss2); const tssCommitA0 = tssCommits[0].toEllipticPoint(ecTSS); const tssCommitA1 = tssCommits[1].toEllipticPoint(ecTSS); @@ -87,7 +110,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { const tss1 = new BN(serverDKGPrivKeys[0], "hex"); const deviceTSSShare = ecTSS.genKeyPair().getPrivate(); const deviceTSSIndex = 2; - const { postboxkey } = await fetchPostboxKeyAndSigs({ + const { postboxkey, signatures } = await fetchPostboxKeyAndSigs({ serviceProvider: sp, verifierName: sp.verifierName, verifierId: sp.verifierId, @@ -101,15 +124,23 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { const factorKey = factorKeyPair.getPrivate(); const factorPub = Point.fromElliptic(factorKeyPair.getPublic()); - await tb1.initialize({ factorPub, deviceTSSShare, deviceTSSIndex }); + await tb1.initialize(); + await tb1.initializeTssSecp256k1({ + factorPub, + deviceTSSShare, + deviceTSSIndex, + serverOpts: { + authSignatures: signatures, + }, + }); const reconstructedKey = await tb1.reconstructKey(); await tb1.syncLocalMetadataTransitions(); if (tb1.secp256k1Key.cmp(reconstructedKey.secp256k1Key) !== 0) { fail("key should be able to be reconstructed"); } - const { tssShare: tss2 } = await tb1.getTSSShare(factorKey); - const tssCommits = tb1.getTSSCommits(); + const { tssShare: tss2 } = await tb1.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); + const tssCommits = tb1.getTSSCommits(TSS_KEY_TYPE); const tssPrivKey = getLagrangeCoeffs(ecTSS, [1, deviceTSSIndex], 1) .mul(tss1) @@ -118,7 +149,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { const tssPubKey = (ecTSS.g as EllipticPoint).mul(tssPrivKey); const tssCommits0 = tssCommits[0].toEllipticPoint(ecTSS); - const tssPub = tb1.getTSSPub().toEllipticPoint(ecTSS); + const tssPub = tb1.getTSSPub(TSS_KEY_TYPE).toEllipticPoint(ecTSS); equal(tssPubKey.eq(tssCommits0), true); equal(tssPub.eq(tssPubKey), true); @@ -130,14 +161,14 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { const nonce = tb1.computeAccountNonce(accountIndex); return share.add(nonce).umod(ecTSS.n); })(); - const { tssShare: tss2Account } = await tb1.getTSSShare(factorKey, { accountIndex }); + const { tssShare: tss2Account } = await tb1.getTSSShare(factorKey, { accountIndex, keyType: TSS_KEY_TYPE }); const coefficient1 = getLagrangeCoeffs(ecTSS, [1, deviceTSSIndex], 1); const coefficient2 = getLagrangeCoeffs(ecTSS, [1, deviceTSSIndex], deviceTSSIndex); const tssKey = coefficient1.mul(tss1Account).add(coefficient2.mul(tss2Account)).umod(ecTSS.n); const tssKeyPub = (ecTSS.g as EllipticPoint).mul(tssKey); - const tssPubAccount = tb1.getTSSPub(accountIndex).toEllipticPoint(ecTSS); + const tssPubAccount = tb1.getTSSPub(TSS_KEY_TYPE, accountIndex).toEllipticPoint(ecTSS); equal(tssPubAccount.eq(tssKeyPub), true, "should equal account pub key"); } }); @@ -162,7 +193,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { const importTssKey = generateKey(TSS_KEY_TYPE); const newTSSIndex = 2; - await tb.initialize({ skipTssInit: true }); + await tb.initialize(); await tb.reconstructKey(); await tb.importTssKey( { tag: "imported", importKey: importTssKey.raw, factorPub, newTSSIndex }, @@ -175,7 +206,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { // Check pub key. const importTssKeyPub = Point.fromScalar(importTssKey.scalar, tb.tssCurve); - const tssPub = await tb.getTSSPub(); + const tssPub = await tb.getTSSPub(TSS_KEY_TYPE); assert(tssPub.equals(importTssKeyPub)); // Check exported key. @@ -198,7 +229,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { accountIndex: 2, }); const exportedPubKeyIndex2 = Point.fromScalar(exportedKeyIndex2, tb.tssCurve); - const pubKeyIndex2 = tb.getTSSPub(2); + const pubKeyIndex2 = tb.getTSSPub(TSS_KEY_TYPE, 2); assert(exportedPubKeyIndex2.equals(pubKeyIndex2)); } }); @@ -232,7 +263,8 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { const factorKey = factorKeyPair.getPrivate(); const factorPub = Point.fromElliptic(factorKeyPair.getPublic()); // 2/2 - await tb.initialize({ factorPub, deviceTSSShare, deviceTSSIndex }); + await tb.initialize(); + await tb.initializeTssSecp256k1({ factorPub, deviceTSSShare, deviceTSSIndex }); const newShare = await tb.generateNewShare(); const reconstructedKey = await tb.reconstructKey(); @@ -241,8 +273,8 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { if (tb.secp256k1Key.cmp(reconstructedKey.secp256k1Key) !== 0) { fail("key should be able to be reconstructed"); } - const { tssShare: retrievedTSS, tssIndex: retrievedTSSIndex } = await tb.getTSSShare(factorKey); - const tssCommits = tb.getTSSCommits(); + const { tssShare: retrievedTSS, tssIndex: retrievedTSSIndex } = await tb.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); + const tssCommits = tb.getTSSCommits(TSS_KEY_TYPE); const tssPrivKey = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], 1) .mul(serverDKGPrivKeys[0]) .add(getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], retrievedTSSIndex).mul(retrievedTSS)) @@ -271,9 +303,9 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { // tag is switched to imported await tb.syncLocalMetadataTransitions(); // for imported key - const { tssShare: retrievedTSS1, tssIndex: retrievedTSSIndex1 } = await tb.getTSSShare(factorKey); + const { tssShare: retrievedTSS1, tssIndex: retrievedTSSIndex1 } = await tb.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); - const tssCommits1 = tb.getTSSCommits(); + const tssCommits1 = tb.getTSSCommits(TSS_KEY_TYPE); const tssPrivKey1 = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex1], 1) .mul(serverDKGPrivKeys1[0]) .add(getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex1], retrievedTSSIndex1).mul(retrievedTSS1)) @@ -303,15 +335,15 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { if (tb2.secp256k1Key.cmp(reconstructedKey2.secp256k1Key) !== 0) { fail("key should be able to be reconstructed"); } - const tssCommits2 = tb2.getTSSCommits(); + const tssCommits2 = tb2.getTSSCommits(TSS_KEY_TYPE); const tssCommits20 = tssCommits2[0].toEllipticPoint(ecTSS); equal(tssPubKey.eq(tssCommits20), true); // switch to imported account tb2.tssTag = "imported"; - const { tssShare: retrievedTSSImported, tssIndex: retrievedTSSIndexImported } = await tb2.getTSSShare(factorKey); + const { tssShare: retrievedTSSImported, tssIndex: retrievedTSSIndexImported } = await tb2.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); - const tssCommitsImported = tb2.getTSSCommits(); + const tssCommitsImported = tb2.getTSSCommits(TSS_KEY_TYPE); const tssPrivKeyImported = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndexImported], 1) .mul(serverDKGPrivKeys1[0]) @@ -363,8 +395,8 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { if (tb.secp256k1Key.cmp(reconstructedKey.secp256k1Key) !== 0) { fail("key should be able to be reconstructed"); } - const { tssShare: retrievedTSS, tssIndex: retrievedTSSIndex } = await tb.getTSSShare(factorKey); - const tssCommits = tb.getTSSCommits(); + const { tssShare: retrievedTSS, tssIndex: retrievedTSSIndex } = await tb.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); + const tssCommits = tb.getTSSCommits(TSS_KEY_TYPE); const tssPrivKey = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], 1) .mul(serverDKGPrivKeys[0]) .add(getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], retrievedTSSIndex).mul(retrievedTSS)) @@ -394,7 +426,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { await tb.syncLocalMetadataTransitions(); // for imported key { - const finalPubKey = tb.getTSSCommits()[0].toEllipticPoint(ecTSS); + const finalPubKey = tb.getTSSCommits(TSS_KEY_TYPE)[0].toEllipticPoint(ecTSS); const finalTssKey = await tb._UNSAFE_exportTssKey({ factorKey, @@ -418,7 +450,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { { tb.tssTag = "default"; - const finalPubKey = tb.getTSSCommits()[0].toEllipticPoint(ecTSS); + const finalPubKey = tb.getTSSCommits(TSS_KEY_TYPE)[0].toEllipticPoint(ecTSS); const finalTssKey = await tb._UNSAFE_exportTssKey({ factorKey, @@ -438,7 +470,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { await tb2.syncLocalMetadataTransitions(); { tb2.tssTag = "imported"; - const finalPubKey = tb2.getTSSCommits()[0].toEllipticPoint(ecTSS); + const finalPubKey = tb2.getTSSCommits(TSS_KEY_TYPE)[0].toEllipticPoint(ecTSS); const finalTssKey = await tb2._UNSAFE_exportTssKey({ factorKey, @@ -453,7 +485,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { { tb2.tssTag = "default"; - const finalPubKey = tb2.getTSSCommits()[0].toEllipticPoint(ecTSS); + const finalPubKey = tb2.getTSSCommits(TSS_KEY_TYPE)[0].toEllipticPoint(ecTSS); const finalTssKey = await tb2._UNSAFE_exportTssKey({ factorKey, @@ -546,7 +578,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { }); it("should no longer be able to access key share with removed factor (same index)", async function () { - await rejects(tb.getTSSShare(newFactorKeySameIndex)); + await rejects(tb.getTSSShare(newFactorKeySameIndex, { keyType: TSS_KEY_TYPE })); }); it("should be able to remove factor for different index", async function () { @@ -560,7 +592,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { }); it("should no longer be able to access key share with removed factor (different index)", async function () { - await rejects(tb.getTSSShare(newFactorKeyNewIndex)); + await rejects(tb.getTSSShare(newFactorKeyNewIndex, { keyType: TSS_KEY_TYPE })); }); }); @@ -627,7 +659,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { await tbJson.reconstructKey(); // try refresh share - await tbJson.getTSSShare(factorKey); + await tbJson.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); const newFactorKeyPair = ecFactor.genKeyPair(); await tbJson.addFactorPub({ @@ -638,7 +670,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { refreshShares: true, }); - await tbJson.getTSSShare(newFactorKeyPair.getPrivate()); + await tbJson.getTSSShare(newFactorKeyPair.getPrivate(), { keyType: TSS_KEY_TYPE }); const serialized2 = JSON.stringify(tbJson); @@ -651,8 +683,8 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { await tbJson2.reconstructKey(); - await tbJson2.getTSSShare(factorKey); - await tbJson2.getTSSShare(newFactorKeyPair.getPrivate()); + await tbJson2.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); + await tbJson2.getTSSShare(newFactorKeyPair.getPrivate(), { keyType: TSS_KEY_TYPE }); }); }); }); From 3aa18161a5f5ed0e09f8db971aa9b3a4995c6fda Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 24 Jan 2025 18:29:32 +0800 Subject: [PATCH 04/22] fix: update tkey with legacyMetadata feature --- packages/core/src/authMetadata.ts | 7 ++- packages/core/src/core.ts | 26 +++++--- packages/core/src/metadata.ts | 98 +++++++++++++++++++++++++++++-- 3 files changed, 117 insertions(+), 14 deletions(-) diff --git a/packages/core/src/authMetadata.ts b/packages/core/src/authMetadata.ts index 7f8a4c0f..ad67ad99 100644 --- a/packages/core/src/authMetadata.ts +++ b/packages/core/src/authMetadata.ts @@ -4,7 +4,7 @@ import BN from "bn.js"; import stringify from "json-stable-stringify"; import CoreError from "./errors"; -import Metadata from "./metadata"; +import Metadata, { createMetadataFromJson } from "./metadata"; class AuthMetadata implements IAuthMetadata { metadata: Metadata; @@ -17,10 +17,11 @@ class AuthMetadata implements IAuthMetadata { } static fromJSON(value: StringifiedType): AuthMetadata { - const { data, sig } = value; + // need to inject legacyMetadata flag + const { data, sig, legacyMetadataFlag } = value; if (!data) throw CoreError.metadataUndefined(); - const m = Metadata.fromJSON(data); + const m = createMetadataFromJson(legacyMetadataFlag, data); if (!m.pubKey) throw CoreError.metadataPubKeyUnavailable(); const keyPair = secp256k1.keyFromPublic(m.pubKey.toSEC1(secp256k1)); diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index e5aadfe0..1583bc5b 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -53,7 +53,7 @@ import stringify from "json-stable-stringify"; import AuthMetadata from "./authMetadata"; import CoreError from "./errors"; import { generatePrivateBN, generateRandomPolynomial, lagrangeInterpolatePolynomial, lagrangeInterpolation } from "./lagrangeInterpolatePolynomial"; -import Metadata from "./metadata"; +import Metadata, { createMetadataFromJson, createMetadataInstance } from "./metadata"; const ed25519SeedConst = "ed25519Seed"; @@ -90,6 +90,8 @@ class ThresholdKey implements ITKey { serverTimeOffset?: number = 0; + legacyMetadataFlag: boolean = false; + // secp256k1 key private privKey: BN; @@ -112,6 +114,7 @@ class ThresholdKey implements ITKey { this.setModuleReferences(); // Providing ITKeyApi access to modules this.haveWriteMetadataLock = ""; this.serverTimeOffset = serverTimeOffset; + this.legacyMetadataFlag = false; } get secp256k1Key(): BN | null { @@ -148,6 +151,9 @@ class ThresholdKey implements ITKey { manualSync, lastFetchedCloudMetadata, serverTimeOffset, + + // legacyMetadata flag + legacyMetadataFlag, } = value; const { storageLayer, serviceProvider, modules } = args; @@ -167,7 +173,9 @@ class ThresholdKey implements ITKey { tb.shares = shares; // switch to deserialize local metadata transition based on Object.keys() of authMetadata, ShareStore's and, IMessageMetadata - const AuthMetadataKeys = Object.keys(JSON.parse(stringify(new AuthMetadata(new Metadata(new Point("0", "0")), new BN("0", "hex"))))); + const AuthMetadataKeys = Object.keys( + JSON.parse(stringify(new AuthMetadata(createMetadataInstance(legacyMetadataFlag, new Point("0", "0")), new BN("0", "hex")))) + ); const ShareStoreKeys = Object.keys(JSON.parse(stringify(new ShareStore(new Share("0", "0"), "")))); const sampleMessageMetadata: IMessageMetadata = { message: "Sample message", dateAdded: Date.now() }; const MessageMetadataKeys = Object.keys(sampleMessageMetadata); @@ -184,7 +192,7 @@ class ThresholdKey implements ITKey { const keys = Object.keys(_localMetadataTransitions[1][index]); if (keys.length === AuthMetadataKeys.length && keys.every((val) => AuthMetadataKeys.includes(val))) { - const tempAuth = AuthMetadata.fromJSON(_localMetadataTransitions[1][index]); + const tempAuth = AuthMetadata.fromJSON({ ..._localMetadataTransitions[1][index], legacyMetadataFlag }); tempAuth.privKey = privKey; localTransitionData.push(tempAuth); } else if (keys.length === ShareStoreKeys.length && keys.every((val) => ShareStoreKeys.includes(val))) { @@ -200,8 +208,9 @@ class ThresholdKey implements ITKey { let tempMetadata: Metadata; let tempCloud: Metadata; - if (metadata) tempMetadata = Metadata.fromJSON(metadata); - if (lastFetchedCloudMetadata) tempCloud = Metadata.fromJSON(lastFetchedCloudMetadata); + // check configuration before decide the metadata + if (metadata) tempMetadata = createMetadataFromJson(legacyMetadataFlag, metadata); + if (lastFetchedCloudMetadata) tempCloud = createMetadataFromJson(legacyMetadataFlag, lastFetchedCloudMetadata); // check if cloud metadata is updated before tb.metadata = tempMetadata; @@ -911,7 +920,10 @@ class ThresholdKey implements ITKey { if ((raw as IMessageMetadata).message === SHARE_DELETED) { throw CoreError.fromCode(1308); } - return params.fromJSONConstructor.fromJSON(raw); + + // inject legacyMetadata flag for AuthMetadata deserialization + // it wont affect other fromJSONConstruct as it is just extra parameter + return params.fromJSONConstructor.fromJSON({ ...raw, legacyMetadataFlag: this.legacyMetadataFlag }); } // Lock functions @@ -1409,7 +1421,7 @@ class ThresholdKey implements ITKey { const shares = poly.generateShares(shareIndexes); // create metadata to be stored - const metadata = new Metadata(getPubKeyPoint(this.privKey)); + const metadata = createMetadataInstance(this.legacyMetadataFlag, getPubKeyPoint(this.privKey)); metadata.addFromPolynomialAndShares(poly, shares); const serviceProviderShare = shares[shareIndexes[0].toString("hex")]; const shareStore = new ShareStore(serviceProviderShare, poly.getPolynomialID()); diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index 3bdb4ed2..1eff1efa 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -30,7 +30,9 @@ import { polyCommitmentEval } from "./lagrangeInterpolatePolynomial"; import { TssMetadata } from "./tssMetadata"; export type SupportedCurve = "ed25519" | "sec"; -const METADATA_VERSION = 1; + +const LEGACY_METADATA_VERSION = "0.0.1"; +const METADATA_VERSION = "1.0.0"; class Metadata implements IMetadata { pubKey: Point; @@ -97,7 +99,7 @@ class Metadata implements IMetadata { } = value; const point = Point.fromSEC1(secp256k1, pubKey); const metadata = new Metadata(point); - metadata.version = version || METADATA_VERSION; + const localVersion = version || LEGACY_METADATA_VERSION; const unserializedPolyIDList: PolyIDAndShares[] = []; @@ -106,11 +108,15 @@ class Metadata implements IMetadata { if (scopedStore) metadata.scopedStore = scopedStore; if (nonce) metadata.nonce = nonce; - if (version === 1) { + if (localVersion === METADATA_VERSION) { metadata.tss = tss; + // else would be legacy version, migrate for secp version } else if (tssKeyTypes) { metadata.tss = {}; Object.keys(tssKeyTypes).forEach((tssTag) => { + if (tssKeyTypes[tssTag] === KeyType.ed25519) { + throw new Error(`ed25519 is not supported for migration for metadata from v${localVersion} to ${METADATA_VERSION}`); + } metadata.tss[tssTag] = { [tssKeyTypes[tssTag]]: TssMetadata.fromJSON({ tssTag, @@ -123,6 +129,7 @@ class Metadata implements IMetadata { }; }); } + metadata.version = localVersion; for (let i = 0; i < polyIDList.length; i += 1) { const serializedPolyID: string = polyIDList[i]; @@ -326,7 +333,8 @@ class Metadata implements IMetadata { tkeyStore: this.tkeyStore, nonce: this.nonce, tss: this.tss, - version: this.version, + // will be updated to current version + version: METADATA_VERSION, }; } @@ -369,6 +377,76 @@ class Metadata implements IMetadata { } export class LegacyMetadata extends Metadata { + version = LEGACY_METADATA_VERSION; + + static fromJSON(value: StringifiedType): LegacyMetadata { + const { + pubKey, + polyIDList, + generalStore, + tkeyStore, + scopedStore, + nonce, + version, + // v0 metadata + tssKeyTypes, + tssPolyCommits, + tssNonces, + factorPubs, + factorEncs, + } = value; + const point = Point.fromSEC1(secp256k1, pubKey); + const metadata = new LegacyMetadata(point); + metadata.version = version || LEGACY_METADATA_VERSION; + + if (metadata.version !== LEGACY_METADATA_VERSION) { + throw new Error(`Incompatible version, version ${metadata.version} is not supported in current configuration`); + } + + const unserializedPolyIDList: PolyIDAndShares[] = []; + + if (generalStore) metadata.generalStore = generalStore; + if (tkeyStore) metadata.tkeyStore = tkeyStore; + if (scopedStore) metadata.scopedStore = scopedStore; + if (nonce) metadata.nonce = nonce; + + metadata.tss = {}; + Object.keys(tssKeyTypes).forEach((tssTag) => { + metadata.tss[tssTag] = { + [tssKeyTypes[tssTag]]: TssMetadata.fromJSON({ + tssTag, + tssKeyType: tssKeyTypes[tssTag], + tssNonce: tssNonces[tssTag], + tssPolyCommits: tssPolyCommits[tssTag], + factorPubs: factorPubs[tssTag], + factorEncs: factorEncs[tssTag], + }), + }; + }); + + for (let i = 0; i < polyIDList.length; i += 1) { + const serializedPolyID: string = polyIDList[i]; + const arrPolyID = serializedPolyID.split("|"); + const zeroIndex = arrPolyID.findIndex((v) => v === "0x0"); + const firstHalf = arrPolyID.slice(0, zeroIndex); + const secondHalf = arrPolyID.slice(zeroIndex + 1, arrPolyID.length); + // for publicPolynomials + const pubPolyID = firstHalf.join("|"); + const pointCommitments: Point[] = []; + firstHalf.forEach((compressedCommitment) => { + pointCommitments.push(Point.fromCompressedPub(compressedCommitment)); + }); + const publicPolynomial = new PublicPolynomial(pointCommitments); + metadata.publicPolynomials[pubPolyID] = publicPolynomial; + + // for polyIDList + unserializedPolyIDList.push([pubPolyID, secondHalf]); + } + + metadata.polyIDList = unserializedPolyIDList; + return metadata; + } + toJSON(): StringifiedType { // squash data to serialized polyID according to spec const serializedPolyIDList = []; @@ -431,8 +509,20 @@ export class LegacyMetadata extends Metadata { tssPolyCommits, factorPubs, factorEncs, + // Legacy Metadata version + version: this.version, }; } + + clone(): LegacyMetadata { + return LegacyMetadata.fromJSON(JSON.parse(stringify(this))); + } } +export const createMetadataInstance = (legacyMetadataFlag: boolean, input: Point) => + legacyMetadataFlag ? new LegacyMetadata(input) : new Metadata(input); + +export const createMetadataFromJson = (legacyMetadataFlag: boolean, args: StringifiedType) => + legacyMetadataFlag ? LegacyMetadata.fromJSON(args) : Metadata.fromJSON(args); + export default Metadata; From 5bb7e6d03942913ecf7bc5ea274e28b89da4b97f Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 4 Feb 2025 16:29:17 +0800 Subject: [PATCH 05/22] fix: current tests passed --- .../src/baseTypes/aggregateTypes.ts | 4 +- packages/core/src/metadata.ts | 35 +- packages/core/src/tssMetadata.ts | 15 +- packages/tss/src/tss.ts | 628 ++++++++++-------- packages/tss/test/helpers.d.ts | 25 + packages/tss/test/tss.ts | 213 ++++-- 6 files changed, 564 insertions(+), 356 deletions(-) create mode 100644 packages/tss/test/helpers.d.ts diff --git a/packages/common-types/src/baseTypes/aggregateTypes.ts b/packages/common-types/src/baseTypes/aggregateTypes.ts index c29b8dc3..7ca11eec 100644 --- a/packages/common-types/src/baseTypes/aggregateTypes.ts +++ b/packages/common-types/src/baseTypes/aggregateTypes.ts @@ -102,7 +102,7 @@ export interface IMetadata extends ISerializable { nonce: number; - version: number; + version: string; getShareIndexesForPolynomial(polyID: PolynomialID): string[]; getLatestPublicPolynomial(): PublicPolynomial; @@ -131,7 +131,7 @@ export interface IMetadata extends ISerializable { [factorPubID: string]: FactorEnc; }; }): void; - getTssData(keyType: KeyType): ITssMetadata; + getTssData(keyType: KeyType, tssTag: string): ITssMetadata; } export type InitializeNewKeyResult = { diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index 1eff1efa..ffc55e49 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -109,7 +109,20 @@ class Metadata implements IMetadata { if (nonce) metadata.nonce = nonce; if (localVersion === METADATA_VERSION) { - metadata.tss = tss; + metadata.tss = {}; + + if (tss) { + Object.keys(tss).forEach((tsstag) => { + if (tss[tsstag]) { + Object.keys(tss[tsstag]).forEach((tssKeyType) => { + metadata.tss[tsstag] = { + [tssKeyType]: TssMetadata.fromJSON(tss[tsstag][tssKeyType]), + }; + }); + } + }); + } + // else would be legacy version, migrate for secp version } else if (tssKeyTypes) { metadata.tss = {}; @@ -325,17 +338,18 @@ class Metadata implements IMetadata { serializedPolyIDList.push(serializedPolyID); } - return { + const jsonObject = { pubKey: this.pubKey.toSEC1(secp256k1, true).toString("hex"), polyIDList: serializedPolyIDList, scopedStore: this.scopedStore, generalStore: this.generalStore, tkeyStore: this.tkeyStore, nonce: this.nonce, - tss: this.tss, // will be updated to current version version: METADATA_VERSION, }; + + return Object.keys(this.tss ?? {}).length > 0 ? { ...jsonObject, tss: this.tss } : jsonObject; } /** @@ -353,26 +367,25 @@ class Metadata implements IMetadata { }): void { const { tssKeyType, tssTag, tssNonce, tssPolyCommits, factorPubs, factorEncs } = tssData; if (!this.tss) this.tss = {}; - if (!this.tss[tssData.tssTag]) { - if (!tssKeyType) throw Error("Missing tssKeyType"); - this.tss[tssData.tssTag] = { - [tssKeyType]: new TssMetadata({ tssTag, tssKeyType, tssNonce, tssPolyCommits, factorPubs, factorEncs }), - }; + if (!this.tss[tssData.tssTag]) this.tss[tssData.tssTag] = {}; + if (!this.tss[tssData.tssTag][tssKeyType]) { + this.tss[tssData.tssTag][tssKeyType] = new TssMetadata({ tssTag, tssKeyType, tssNonce, tssPolyCommits, factorPubs, factorEncs }); } if (tssData.tssKeyType === KeyType.ed25519) this.tss[tssTag].ed25519.update(tssData); else if (tssData.tssKeyType === KeyType.secp256k1) this.tss[tssTag].secp256k1.update(tssData); } - getTssData(tssKeyType: KeyType, tssTag: string = "default"): ITssMetadata { + getTssData(tssKeyType: KeyType, tssTag: string): ITssMetadata { // const tssDataList = this.tss?[tssTag]; - if (!this.tss) throw Error("Missing TssData"); - if (!this.tss[tssTag]) throw Error("Invalid Tss Tag"); + if (!this.tss) return undefined; + if (!this.tss[tssTag]) return undefined; if (tssKeyType === KeyType.secp256k1) { return this.tss[tssTag].secp256k1; } else if (tssKeyType === KeyType.ed25519) { return this.tss[tssTag].ed25519; } + return undefined; } } diff --git a/packages/core/src/tssMetadata.ts b/packages/core/src/tssMetadata.ts index a48ed8a3..31fc70f5 100644 --- a/packages/core/src/tssMetadata.ts +++ b/packages/core/src/tssMetadata.ts @@ -26,6 +26,7 @@ export class TssMetadata implements ITssMetadata, ISerializable { static fromJSON(value: StringifiedType): TssMetadata { const { tssTag, tssKeyType, tssPolyCommits, tssNonce, factorPubs, factorEncs } = value; + const tssMetadata = new TssMetadata({ tssTag, tssKeyType, @@ -36,15 +37,12 @@ export class TssMetadata implements ITssMetadata, ISerializable { }); if (tssPolyCommits) { - for (const key in tssPolyCommits) { - tssMetadata.tssPolyCommits = (tssPolyCommits as Record)[key].map((obj) => new Point(obj.x, obj.y)); - } + tssMetadata.tssPolyCommits = (tssPolyCommits as Point[]).map((obj) => new Point(obj.x, obj.y)); } if (factorPubs) { - for (const key in factorPubs) { - tssMetadata.factorPubs = (factorPubs as Record)[key].map((obj) => new Point(obj.x, obj.y)); - } + tssMetadata.factorPubs = (factorPubs as Point[]).map((obj) => new Point(obj.x, obj.y)); } + if (factorEncs) tssMetadata.factorEncs = factorEncs; return tssMetadata; @@ -52,8 +50,9 @@ export class TssMetadata implements ITssMetadata, ISerializable { toJSON(): StringifiedType { return { - tssKeyTypes: this.tssKeyType, - tssNonces: this.tssNonce, + tssTag: this.tssTag, + tssKeyType: this.tssKeyType, + tssNonce: this.tssNonce, tssPolyCommits: this.tssPolyCommits, factorPubs: this.factorPubs, factorEncs: this.factorEncs, diff --git a/packages/tss/src/tss.ts b/packages/tss/src/tss.ts index 738dfe41..c92f6029 100644 --- a/packages/tss/src/tss.ts +++ b/packages/tss/src/tss.ts @@ -148,33 +148,38 @@ export class TKeyTSS extends TKey { * can be used with `importTssKey` to just import an existing account instead. */ async initializeTssSecp256k1(params: TKeyTSSInitArgs): Promise { - const secp256k1TssData = this.metadata.getTssData(KeyType.secp256k1); - const ed25519TssData = this.metadata.getTssData(KeyType.ed25519); + const secp256k1TssData = this.metadata.getTssData(KeyType.secp256k1, TSS_TAG_DEFAULT); + const ed25519TssData = this.metadata.getTssData(KeyType.ed25519, TSS_TAG_DEFAULT); const secp256k1Exist = !!secp256k1TssData && Object.keys(secp256k1TssData).length > 0; const ed25519Exist = !!ed25519TssData && Object.keys(ed25519TssData).length > 0; - const { factorPub, importKey, serverOpts } = params; + const { factorPub, deviceTSSShare, deviceTSSIndex, importKey, serverOpts } = params; if (secp256k1Exist) { throw CoreError.default("TSS account already exists for secp256k1 key type"); } if (!serverOpts) { throw CoreError.default("serverOpts is required for import key flow"); } - const backupMetadata = this.metadata.clone(); + if (ed25519Exist) { + if (factorPub !== undefined || deviceTSSShare !== undefined || deviceTSSIndex !== undefined) + throw CoreError.default("factorPub and deviceTSSIndex are not allowed when existing tss account exists"); + } + const backupMetadata = this.metadata.clone(); + const localTssTag = TSS_TAG_DEFAULT; try { if (!importKey) { // if tss shares have not been created for this tssTag, create new tss sharing const { factorEncs, factorPubs, tssPolyCommits, tss2 } = await this._initializeNewTSSKey( KeyType.secp256k1, - this.tssTag, - params.deviceTSSShare, + localTssTag, + deviceTSSShare, factorPub, - params.deviceTSSIndex + deviceTSSIndex ); - this.metadata.updateTSSData({ tssKeyType: KeyType.secp256k1, tssTag: this.tssTag, tssNonce: 0, tssPolyCommits, factorPubs, factorEncs }); + this.metadata.updateTSSData({ tssKeyType: KeyType.secp256k1, tssTag: localTssTag, tssNonce: 0, tssPolyCommits, factorPubs, factorEncs }); const accountSalt = generateSalt(this._tssCurve); await this._setTKeyStoreItem(TSS_MODULE, { @@ -192,16 +197,17 @@ export class TKeyTSS extends TKey { { updateMetadata: false, tssShare: tss2, - tssIndex: params.deviceTSSIndex ?? 2, + tssIndex: deviceTSSIndex ?? 2, factorPubs: ed25519TssData.factorPubs, tssIndexes, + tssTag: localTssTag, }, serverOpts ); } } else { let factorPubs = [factorPub]; - let newTSSIndexes = [2]; + let newTSSIndexes = [params.deviceTSSIndex ?? 2]; if (ed25519Exist) { // refresh with factorPubs factorPubs = ed25519TssData.factorPubs; @@ -210,9 +216,9 @@ export class TKeyTSS extends TKey { }); } - this.importTssKey( + await this.importTssKey( { - tag: this._tssTag, + tssTag: TSS_TAG_DEFAULT, importKey, factorPubs, newTSSIndexes, @@ -221,8 +227,10 @@ export class TKeyTSS extends TKey { params.serverOpts ); } - } finally { + } catch (err) { + // Error happend, restore metadata this.metadata = backupMetadata; + throw err; } } @@ -237,52 +245,89 @@ export class TKeyTSS extends TKey { throw CoreError.default("importKey is required"); } - const secp256k1TssData = this.metadata.getTssData(KeyType.secp256k1); - const ed25519TssData = this.metadata.getTssData(KeyType.ed25519); + const backupMetadata = this.metadata.clone(); + try { + const secp256k1TssData = this.metadata.getTssData(KeyType.secp256k1, TSS_TAG_DEFAULT); + const ed25519TssData = this.metadata.getTssData(KeyType.ed25519, TSS_TAG_DEFAULT); - const secp256k1Exist = !!secp256k1TssData && Object.keys(secp256k1TssData).length > 0; - const ed25519Exist = !!ed25519TssData && Object.keys(ed25519TssData).length > 0; + const secp256k1Exist = !!secp256k1TssData && Object.keys(secp256k1TssData).length > 0; + const ed25519Exist = !!ed25519TssData && Object.keys(ed25519TssData).length > 0; - const { factorPub } = params; - if (ed25519Exist) { - throw CoreError.default("TSS account already exists for secp256k1 key type"); + const { factorPub, deviceTSSIndex, deviceTSSShare } = params; + if (ed25519Exist) { + throw CoreError.default("TSS account already exists for secp256k1 key type"); + } + + let factorPubs = [factorPub]; + let newTSSIndexes = [deviceTSSIndex ?? 2]; + + // secp256k1Exist, + if (secp256k1Exist) { + // refresh with factorPubs + if (factorPub !== undefined || deviceTSSIndex !== undefined || deviceTSSShare !== undefined) + throw CoreError.default("factorPub and deviceTSSIndex are not allowed when existing tss account exists"); + factorPubs = secp256k1TssData.factorPubs; + newTSSIndexes = secp256k1TssData.factorPubs.map((point) => { + return secp256k1TssData.factorEncs[point.x.toString("hex", 64)].tssIndex; + }); + } + await this.importTssKey( + { + tssTag: TSS_TAG_DEFAULT, + importKey, + factorPubs, + newTSSIndexes, + tssKeyType: KeyType.ed25519, + }, + params.serverOpts + ); + } catch (err) { + // Error happend, restore metadata + this.metadata = backupMetadata; + throw err; } + } - let factorPubs = [factorPub]; - let newTSSIndexes = [2]; - if (secp256k1Exist) { - // refresh with factorPubs - factorPubs = secp256k1TssData.factorPubs; - newTSSIndexes = secp256k1TssData.factorPubs.map((point) => { - return secp256k1TssData.factorEncs[point.x.toString("hex", 64)].tssIndex; - }); + /** + * Initializes a new TSS account under the given factor key. + */ + async initializeTss(params: TKeyTSSInitArgs & { tssKeyType: KeyType }): Promise { + const { tssKeyType } = params; + if (tssKeyType === KeyType.secp256k1) { + await this.initializeTssSecp256k1(params); + } else if (tssKeyType === KeyType.ed25519) { + let { importKey } = params; + if (!importKey) { + // should we use randomValue for seed? + const buf = new Uint32Array(32); + crypto.getRandomValues(buf); + importKey = Buffer.from(buf); + } + if (importKey) { + await this.initializeTssEd25519({ ...params, importKey }); + } else { + throw CoreError.default("importKey is required for ed25519 key type"); + } + } else { + throw CoreError.default("unsupported tssKeyType"); } - this.importTssKey( - { - tag: this._tssTag, - importKey, - factorPubs, - newTSSIndexes, - tssKeyType: KeyType.ed25519, - }, - params.serverOpts - ); } /** * Returns the encrypted data associated with the given factor public key. */ - getFactorEncs(factorPub: Point, keyType: KeyType): FactorEnc { + getFactorEncs(factorPub: Point, keyType: KeyType, tssTag: string = TSS_TAG_DEFAULT): FactorEnc { + const localTssTag = tssTag; if (!this.metadata) throw CoreError.metadataUndefined(); - const tssData = this.metadata.getTssData(keyType); + const tssData = this.metadata.getTssData(keyType, localTssTag); if (!tssData) throw CoreError.default("no factor encs mapping"); const { factorPubs } = tssData; - if (!factorPubs) throw CoreError.default(`no factor pubs for this tssTag ${this.tssTag}`); + if (!factorPubs) throw CoreError.default(`no factor pubs for this tssTag ${localTssTag}`); if (factorPubs.filter((f) => f.x.cmp(factorPub.x) === 0 && f.y.cmp(factorPub.y) === 0).length === 0) - throw CoreError.default(`factor pub ${factorPub} not found for tssTag ${this.tssTag}`); - if (!tssData.factorEncs) throw CoreError.default(`no factor encs for tssTag ${this.tssTag}`); + throw CoreError.default(`factor pub ${factorPub} not found for tssTag ${localTssTag}`); + if (!tssData.factorEncs) throw CoreError.default(`no factor encs for tssTag ${localTssTag}`); const factorPubID = factorPub.x.toString(16, 64); return tssData.factorEncs[factorPubID]; } @@ -294,6 +339,7 @@ export class TKeyTSS extends TKey { factorKey: BN, opts: { keyType: KeyType; + tssTag: string; threshold?: number; accountIndex?: number; coefficient?: BN; @@ -303,7 +349,9 @@ export class TKeyTSS extends TKey { tssShare: BN; }> { const factorPub = getPubKeyPoint(factorKey, factorKeyCurve); - const factorEncs = this.getFactorEncs(factorPub, opts.keyType); + const localTssTag = opts.tssTag; + + const factorEncs = this.getFactorEncs(factorPub, opts.keyType, localTssTag); const { userEnc, serverEncs, tssIndex, type } = factorEncs; const userDecryption = await decrypt(Buffer.from(factorKey.toString(16, 64), "hex"), userEnc); const serverDecryptions = await Promise.all( @@ -320,7 +368,7 @@ export class TKeyTSS extends TKey { }); const ec = getKeyCurve(opts.keyType); - const tssCommits = this.getTSSCommits(opts.keyType).map((p) => { + const tssCommits = this.getTSSCommits(opts.keyType, localTssTag).map((p) => { return ec.keyFromPublic({ x: p.x.toString(16, 64), y: p.y.toString(16, 64) }).getPublic(); }); @@ -375,13 +423,14 @@ export class TKeyTSS extends TKey { * Returns the TSS public key and the curve points corresponding to secret key * shares, as stored in Metadata. */ - getTSSCommits(tssKeyType: KeyType): Point[] { + getTSSCommits(tssKeyType: KeyType, tssTag: string): Point[] { if (!this.metadata) throw CoreError.metadataUndefined(); + const localTssTag = tssTag; - const tssData = this.metadata.getTssData(tssKeyType); + const tssData = this.metadata.getTssData(tssKeyType, localTssTag); if (!tssData) throw CoreError.default("no tss data"); const { tssPolyCommits } = tssData; - if (!tssPolyCommits) throw CoreError.default(`tss poly commits not found for tssTag ${this._tssTag}`); + if (!tssPolyCommits) throw CoreError.default(`tss poly commits not found for tssTag ${localTssTag}`); if (tssPolyCommits.length === 0) throw CoreError.default("tss poly commits is empty"); return tssPolyCommits; } @@ -389,9 +438,9 @@ export class TKeyTSS extends TKey { /** * Returns the TSS public key. */ - getTSSPub(tssKeyType: KeyType, accountIndex?: number): Point { + getTSSPub(tssKeyType: KeyType, tssTag: string, accountIndex?: number): Point { const ec = getKeyCurve(tssKeyType); - const tssCommits = this.getTSSCommits(tssKeyType); + const tssCommits = this.getTSSCommits(tssKeyType, tssTag); if (accountIndex && accountIndex > 0) { // Add account nonce to pub key. const nonce = this.computeAccountNonce(accountIndex); @@ -421,6 +470,160 @@ export class TKeyTSS extends TKey { }; } + /** + * Imports an existing private key for threshold signing. A corresponding user + * key share will be stored under the specified factor key. + */ + async importTssKey( + params: { + tssTag: string; + importKey: Buffer; + factorPubs: Point[]; + newTSSIndexes: number[]; + tssKeyType: KeyType; + }, + serverOpts: { + selectedServers?: number[]; + authSignatures: string[]; + } + ): Promise { + if (!this.secp256k1Key) throw CoreError.privateKeyUnavailable(); + if (!this.metadata) { + throw CoreError.metadataUndefined(); + } + + const { importKey, factorPubs, newTSSIndexes, tssTag, tssKeyType } = params; + + const oldTag = this._tssTag; + const localTssTag = tssTag ?? TSS_TAG_DEFAULT; + + const tssData = this.metadata.getTssData(params.tssKeyType, localTssTag); + if (tssData) { + throw CoreError.default("TSS account already exists"); + } + + const ec = getKeyCurve(tssKeyType); + + try { + const { selectedServers = [], authSignatures = [] } = serverOpts || {}; + + if (!localTssTag) throw CoreError.default(`invalid param, tag is required`); + if (!factorPubs || factorPubs.length === 0) throw CoreError.default(`invalid param, newFactorPub is required`); + if (!newTSSIndexes || newTSSIndexes.length === 0) throw CoreError.default(`invalid param, newTSSIndex is required`); + if (authSignatures.length === 0) throw CoreError.default(`invalid param, authSignatures is required`); + + // const existingFactorPubs = tssData.factorPubs; + // if (existingFactorPubs?.length > 0) { + // throw CoreError.default(`Duplicate account tag, please use a unique tag for importing key`); + // } + + const importScalar = await (async () => { + if (tssKeyType === KeyType.secp256k1) { + return new BN(importKey); + } else if (tssKeyType === KeyType.ed25519) { + // Store seed in metadata. + const domainKey = getEd25519SeedStoreDomainKey(localTssTag); + const result = this.metadata.getGeneralStoreDomain(domainKey) as Record; + if (result) { + throw new Error("Seed already exists"); + } + + const { scalar } = getEd25519KeyPairFromSeed(importKey); + const encKey = Buffer.from(getSecpKeyFromEd25519(scalar).point.encodeCompressed("hex"), "hex"); + const msg = await encrypt(encKey, importKey); + this.metadata.setGeneralStoreDomain(domainKey, { message: msg }); + + return scalar; + } + throw new Error("Invalid key type"); + })(); + + if (!importScalar || importScalar.toString("hex") === "0") { + throw new Error("Invalid importedKey"); + } + + const tssIndexes = newTSSIndexes; + const existingNonce = tssData?.tssNonce ?? 0; + const newTssNonce: number = existingNonce && existingNonce > 0 ? existingNonce + 1 : 0; + const verifierAndVerifierID = this.serviceProvider.getVerifierNameVerifierId(); + const label = `${verifierAndVerifierID}\u0015${localTssTag}\u0016${newTssNonce}`; + const tssPubKey = hexPoint(ec.g.mul(importScalar)); + const rssNodeDetails = await this._getRssNodeDetails(); + const { pubKey: newTSSServerPub, nodeIndexes } = await this.serviceProvider.getTSSPubKey(localTssTag, newTssNonce, tssKeyType); + let finalSelectedServers = selectedServers; + + if (nodeIndexes?.length > 0) { + if (selectedServers.length) { + finalSelectedServers = nodeIndexes.slice(0, Math.min(selectedServers.length, nodeIndexes.length)); + } else { + finalSelectedServers = nodeIndexes.slice(0, 3); + } + } else if (selectedServers?.length === 0) { + finalSelectedServers = randomSelection( + new Array(rssNodeDetails.serverEndpoints.length).fill(null).map((_, i) => i + 1), + Math.ceil(rssNodeDetails.serverEndpoints.length / 2) + ); + } + + const { serverEndpoints, serverPubKeys, serverThreshold } = rssNodeDetails; + + const rssClient = new RSSClient({ + serverEndpoints, + serverPubKeys, + serverThreshold, + tssPubKey, + keyType: tssKeyType, + }); + + const refreshResponses = await rssClient.import({ + importKey: importScalar, + dkgNewPub: pointToHex(newTSSServerPub), + selectedServers: finalSelectedServers, + factorPubs: factorPubs.map((f) => pointToHex(f)), + targetIndexes: tssIndexes, + newLabel: label, + sigs: authSignatures, + }); + const secondCommit = newTSSServerPub.toEllipticPoint(ec).add(ecPoint(ec, tssPubKey).neg()); + const newTSSCommits = [ + Point.fromJSON(tssPubKey), + Point.fromJSON({ x: secondCommit.getX().toString(16, 64), y: secondCommit.getY().toString(16, 64) }), + ]; + const factorEncs: { + [factorPubID: string]: FactorEnc; + } = {}; + for (let i = 0; i < refreshResponses.length; i++) { + const refreshResponse = refreshResponses[i]; + factorEncs[refreshResponse.factorPub.x.padStart(64, "0")] = { + type: "hierarchical", + tssIndex: refreshResponse.targetIndex, + userEnc: refreshResponse.userFactorEnc, + serverEncs: refreshResponse.serverFactorEncs, + }; + } + this.metadata.updateTSSData({ + tssKeyType, + tssTag: localTssTag, + tssNonce: newTssNonce, + tssPolyCommits: newTSSCommits, + factorPubs, + factorEncs, + }); + if (!this._accountSalt) { + const accountSalt = generateSalt(this._tssCurve); + await this._setTKeyStoreItem(TSS_MODULE, { + id: "accountSalt", + value: accountSalt, + } as IAccountSaltStore); + this._accountSalt = accountSalt; + } + await this._syncShareMetadata(); + } catch (error) { + this._tssTag = oldTag; + throw error; + } + } + /** * UNSAFE: USE WITH CAUTION * @@ -431,15 +634,18 @@ export class TKeyTSS extends TKey { selectedServers?: number[]; authSignatures: string[]; accountIndex?: number; + keyType: KeyType; + tssTag: string; }): Promise { + const { factorKey, selectedServers, authSignatures, accountIndex, keyType, tssTag } = tssOptions; + if (!this.metadata) throw CoreError.metadataUndefined("metadata is undefined"); if (!this.secp256k1Key) throw new Error("Tkey is not reconstructed"); - const tssData = this.metadata.getTssData(KeyType.secp256k1); - if (!tssData?.tssPolyCommits) throw new Error(`tss key has not been initialized for tssTag ${this.tssTag}`); - const { factorKey, selectedServers, authSignatures, accountIndex } = tssOptions; + const tssData = this.metadata.getTssData(keyType, tssTag); + if (!tssData?.tssPolyCommits?.length) throw new Error(`tss key has not been initialized for tssTag ${this.tssTag}`); - const { tssIndex } = await this.getTSSShare(factorKey, { keyType: KeyType.secp256k1 }); + const { tssIndex } = await this.getTSSShare(factorKey, { keyType, tssTag }); // Assumption that there are only index 2 and 3 for tss shares // create complement index share const tempShareIndex = tssIndex === 2 ? 3 : 2; @@ -453,10 +659,11 @@ export class TKeyTSS extends TKey { authSignatures, selectedServers, refreshShares: true, + tssTag, }); - const { tssShare: factorShare, tssIndex: factorIndex } = await this.getTSSShare(factorKey, { keyType: KeyType.secp256k1 }); - const { tssShare: tempShare, tssIndex: tempIndex } = await this.getTSSShare(tempFactorKey, { keyType: KeyType.secp256k1 }); + const { tssShare: factorShare, tssIndex: factorIndex } = await this.getTSSShare(factorKey, { keyType, tssTag }); + const { tssShare: tempShare, tssIndex: tempIndex } = await this.getTSSShare(tempFactorKey, { keyType, tssTag }); // reconstruct final key using sss const ec = this._tssCurve; @@ -468,6 +675,7 @@ export class TKeyTSS extends TKey { deleteFactorPub: tempFactorPub, authSignatures, selectedServers, + tssTag, }); // Derive key for account index. @@ -482,11 +690,16 @@ export class TKeyTSS extends TKey { * * Reconstructs the TSS private key and exports the ed25519 private key seed. */ - async _UNSAFE_exportTssEd25519Seed(tssOptions: { factorKey: BN; selectedServers?: number[]; authSignatures: string[] }): Promise { - const edScalar = await this._UNSAFE_exportTssKey(tssOptions); + async _UNSAFE_exportTssEd25519Seed(tssOptions: { + factorKey: BN; + selectedServers?: number[]; + authSignatures: string[]; + tssTag: string; + }): Promise { + const edScalar = await this._UNSAFE_exportTssKey({ ...tssOptions, keyType: KeyType.ed25519 }); // Try to export ed25519 seed. This is only available if import key was being used. - const domainKey = getEd25519SeedStoreDomainKey(this.tssTag || TSS_TAG_DEFAULT); + const domainKey = getEd25519SeedStoreDomainKey(tssOptions.tssTag); const result = this.metadata.getGeneralStoreDomain(domainKey) as Record; const decKey = getSecpKeyFromEd25519(edScalar).scalar; @@ -513,9 +726,9 @@ export class TKeyTSS extends TKey { new Array(rssNodeDetails.serverEndpoints.length).fill(null).map((_, i) => i + 1), Math.ceil(rssNodeDetails.serverEndpoints.length / 2) ); - + const localTssTag = TSS_TAG_DEFAULT; const verifierNameVerifierId = this.serviceProvider.getVerifierNameVerifierId(); - const tssData = this.metadata.getTssData(this.tssKeyType); + const tssData = this.metadata.getTssData(this.tssKeyType, localTssTag); if (!tssData) throw CoreError.default("no tss data"); const tssCommits = tssData.tssPolyCommits; @@ -526,14 +739,14 @@ export class TKeyTSS extends TKey { finalSelectedServers = nodeIndexes.slice(0, Math.min(serverEndpoints.length, nodeIndexes.length)); } - const factorEnc = this.getFactorEncs(Point.fromSEC1(secp256k1, remoteClient.remoteFactorPub), keyType); + const factorEnc = this.getFactorEncs(Point.fromSEC1(secp256k1, remoteClient.remoteFactorPub), keyType, localTssTag); const dataRequired: RefreshRemoteTssParams = { factorEnc, factorPubs: factorPubs.map((pub) => pub.toPointHex()), targetIndexes: tssIndices, verifierNameVerifierId, - tssTag: this._tssTag, + tssTag: TSS_TAG_DEFAULT, tssCommits: tssCommits.map((commit) => commit.toPointHex()), tssNonce, newTSSServerPub: newTSSServerPub.toPointHex(), @@ -571,11 +784,11 @@ export class TKeyTSS extends TKey { async remoteAddFactorPub(params: { newFactorPub: Point; newFactorTSSIndex: number; remoteClient: IRemoteClientState; keyType: KeyType }) { const { newFactorPub, newFactorTSSIndex, remoteClient, keyType } = params; // const ed25519TssMetadata = this.metadata.getTssData(KeyType.ed25519); - const secp256k1TssMetadata = this.metadata.getTssData(KeyType.secp256k1); - + const localTssTag = TSS_TAG_DEFAULT; + const secp256k1TssMetadata = this.metadata.getTssData(KeyType.secp256k1, localTssTag); const existingFactorPubs = secp256k1TssMetadata.factorPubs; const updatedFactorPubs = existingFactorPubs.concat([newFactorPub]); - const existingTSSIndexes = existingFactorPubs.map((fb) => this.getFactorEncs(fb, keyType).tssIndex); + const existingTSSIndexes = existingFactorPubs.map((fb) => this.getFactorEncs(fb, keyType, localTssTag).tssIndex); const updatedTSSIndexes = existingTSSIndexes.concat([newFactorTSSIndex]); await this.remoteRefreshTssShares({ @@ -588,7 +801,8 @@ export class TKeyTSS extends TKey { async remoteDeleteFactorPub(params: { factorPubToDelete: Point; remoteClient: IRemoteClientState; keyType: KeyType }) { const { factorPubToDelete, remoteClient, keyType } = params; - const tssData = this.metadata.getTssData(keyType); + const localTssTag = TSS_TAG_DEFAULT; + const tssData = this.metadata.getTssData(keyType, localTssTag); const existingFactorPubs = tssData.factorPubs; const factorIndex = existingFactorPubs.findIndex((p) => p.x.eq(factorPubToDelete.x)); if (factorIndex === -1) { @@ -596,7 +810,7 @@ export class TKeyTSS extends TKey { } const updatedFactorPubs = existingFactorPubs.slice(); updatedFactorPubs.splice(factorIndex, 1); - const updatedTSSIndexes = updatedFactorPubs.map((fb) => this.getFactorEncs(fb, keyType).tssIndex); + const updatedTSSIndexes = updatedFactorPubs.map((fb) => this.getFactorEncs(fb, keyType, localTssTag).tssIndex); await this.remoteRefreshTssShares({ factorPubs: updatedFactorPubs, @@ -609,8 +823,9 @@ export class TKeyTSS extends TKey { async remoteCopyFactorPub(params: { newFactorPub: Point; tssIndex: number; remoteClient: IRemoteClientState; keyType: KeyType }) { const { newFactorPub, tssIndex, remoteClient, keyType } = params; const remoteFactorPub = Point.fromSEC1(secp256k1, remoteClient.remoteFactorPub); - const factorEnc = this.getFactorEncs(remoteFactorPub, keyType); - const tssCommits = this.getTSSCommits(keyType).map((commit) => commit.toPointHex()); + const localTssTag = TSS_TAG_DEFAULT; + const factorEnc = this.getFactorEncs(remoteFactorPub, keyType, localTssTag); + const tssCommits = this.getTSSCommits(keyType, localTssTag).map((commit) => commit.toPointHex()); const dataRequired: CopyRemoteTssParams = { factorEnc, tssCommits, @@ -630,7 +845,7 @@ export class TKeyTSS extends TKey { ) ).data; - const tssData = this.metadata.getTssData(keyType); + const tssData = this.metadata.getTssData(keyType, localTssTag); const updatedFactorPubs = tssData.factorPubs.concat([newFactorPub]); const factorEncs: { [key: string]: FactorEnc } = JSON.parse(JSON.stringify(tssData.factorEncs)); @@ -642,7 +857,7 @@ export class TKeyTSS extends TKey { serverEncs: [], }; this.metadata.updateTSSData({ - tssTag: this.tssTag, + tssTag: localTssTag, factorPubs: updatedFactorPubs, factorEncs, }); @@ -669,15 +884,16 @@ export class TKeyTSS extends TKey { selectedServers: number[]; authSignatures: string[]; keyType: KeyType; - } + }, + tssTag: string ): Promise { if (!this.metadata) throw CoreError.metadataUndefined(); const { keyType } = serverOpts; - const tssData = this.metadata.getTssData(keyType); + const localTssTag = tssTag ?? TSS_TAG_DEFAULT; + const tssData = this.metadata.getTssData(keyType, localTssTag); - const { tssTag } = this; const tssCommits = tssData?.tssPolyCommits; - if (!tssCommits) throw CoreError.default(`tss commits not found for tssTag ${tssTag}`); + if (!tssCommits) throw CoreError.default(`tss commits not found for tssTag ${localTssTag}`); if (tssCommits.length === 0) throw CoreError.default(`tssCommits is empty`); const tssPubKeyPoint = tssCommits[0]; const tssPubKey = pointToHex(tssPubKeyPoint); @@ -692,16 +908,16 @@ export class TKeyTSS extends TKey { }); if (!tssData.factorPubs) throw CoreError.default(`factorPubs obj not found`); - if (!factorPubs) throw CoreError.default(`factorPubs not found for tssTag ${this.tssTag}`); + if (!factorPubs) throw CoreError.default(`factorPubs not found for tssTag ${localTssTag}`); if (factorPubs.length === 0) throw CoreError.default(`factorPubs is empty`); - if (!tssData.tssNonce) throw CoreError.default(`tssNonces obj not found`); + if (tssData.tssNonce === undefined) throw CoreError.default(`tssNonces obj not found`); const tssNonce: number = tssData.tssNonce || 0; - const oldLabel = `${verifierNameVerifierId}\u0015${tssTag}\u0016${tssNonce}`; - const newLabel = `${verifierNameVerifierId}\u0015${tssTag}\u0016${tssNonce + 1}`; + const oldLabel = `${verifierNameVerifierId}\u0015${localTssTag}\u0016${tssNonce}`; + const newLabel = `${verifierNameVerifierId}\u0015${localTssTag}\u0016${tssNonce + 1}`; - const { pubKey: newTSSServerPub, nodeIndexes } = await this.serviceProvider.getTSSPubKey(tssTag, tssNonce + 1, keyType); + const { pubKey: newTSSServerPub, nodeIndexes } = await this.serviceProvider.getTSSPubKey(localTssTag, tssNonce + 1, keyType); let finalSelectedServers = selectedServers; if (nodeIndexes?.length > 0) { @@ -740,7 +956,7 @@ export class TKeyTSS extends TKey { this.metadata.updateTSSData({ tssKeyType: keyType, - tssTag, + tssTag: localTssTag, tssNonce: tssNonce + 1, tssPolyCommits: newTSSCommits, factorPubs, @@ -750,13 +966,13 @@ export class TKeyTSS extends TKey { } async _refreshTSSSharesWithFactorPubs( - params: { updateMetadata: boolean; tssShare: BN; tssIndex: number; factorPubs: Point[]; tssIndexes: number[] }, + params: { updateMetadata: boolean; tssShare: BN; tssIndex: number; factorPubs: Point[]; tssIndexes: number[]; tssTag: string }, serverOpts: { selectedServers?: number[]; authSignatures: string[]; } ) { - const { factorPubs, tssIndexes, tssShare, tssIndex } = params; + const { factorPubs, tssIndexes, tssShare, tssIndex, tssTag } = params; const { selectedServers, authSignatures } = serverOpts; const verifierId = this.serviceProvider.getVerifierNameVerifierId(); @@ -772,12 +988,21 @@ export class TKeyTSS extends TKey { // create a localMetadataTransition if manual sync const updateMetadata = params.updateMetadata !== undefined ? params.updateMetadata : true; - await this._refreshTSSShares(updateMetadata, tssShare, tssIndex, factorPubs, tssIndexes, verifierId, { - ...rssNodeDetails, - selectedServers: finalServer, - authSignatures, - keyType: this.tssKeyType, - }); + await this._refreshTSSShares( + updateMetadata, + tssShare, + tssIndex, + factorPubs, + tssIndexes, + verifierId, + { + ...rssNodeDetails, + selectedServers: finalServer, + authSignatures, + keyType: this.tssKeyType, + }, + tssTag ?? TSS_TAG_DEFAULT + ); } /** @@ -833,18 +1058,20 @@ export class TKeyTSS extends TKey { authSignatures: string[]; refreshShares?: boolean; updateMetadata?: boolean; + tssTag: string; }) { - const secp256k1Data = this.metadata.getTssData(KeyType.secp256k1); - const ed25519Data = this.metadata.getTssData(KeyType.ed25519); + const secp256k1Data = this.metadata.getTssData(KeyType.secp256k1, args.tssTag); + const ed25519Data = this.metadata.getTssData(KeyType.ed25519, args.tssTag); const allPromise = []; - if (secp256k1Data.tssTag) { + if (secp256k1Data) { allPromise.push(this._addFactorPub({ ...args, keyType: KeyType.secp256k1 })); } - if (ed25519Data.tssTag) { - allPromise.push(this._addFactorPub({ ...args, keyType: KeyType.secp256k1 })); + if (ed25519Data) { + allPromise.push(this._addFactorPub({ ...args, keyType: KeyType.ed25519 })); } + await Promise.all(allPromise); } @@ -863,17 +1090,19 @@ export class TKeyTSS extends TKey { refreshShares?: boolean; updateMetadata?: boolean; keyType: KeyType; + tssTag: string; }) { if (!this.metadata) throw CoreError.metadataUndefined("metadata is undefined"); if (!this.secp256k1Key) throw new Error("Tkey is not reconstructed"); - const { existingFactorKey, newFactorPub, newTSSIndex, selectedServers, authSignatures, refreshShares, keyType } = args; - const tssData = this.metadata.getTssData(keyType); + const { existingFactorKey, newFactorPub, newTSSIndex, selectedServers, authSignatures, refreshShares, keyType, tssTag } = args; + const tssData = this.metadata.getTssData(keyType, tssTag); - const { tssShare, tssIndex } = await this.getTSSShare(existingFactorKey, { keyType }); + const { tssShare, tssIndex } = await this.getTSSShare(existingFactorKey, { keyType, tssTag }); if (tssIndex !== newTSSIndex && !refreshShares) { throw CoreError.default("newTSSIndex does not match existing tssIndex, set refreshShares to true to refresh shares"); } + const localTssTag = tssTag ?? TSS_TAG_DEFAULT; if (!refreshShares) { // Just copy data stored under factor key. @@ -890,9 +1119,10 @@ export class TKeyTSS extends TKey { userEnc: await encrypt(newFactorPub.toSEC1(secp256k1, false), tssShare.toArrayLike(Buffer, "be", 32)), serverEncs: [], }; + this.metadata.updateTSSData({ tssKeyType: args.keyType, - tssTag: this.tssTag, + tssTag: localTssTag, factorPubs: updatedFactorPubs, factorEncs, }); @@ -910,32 +1140,46 @@ export class TKeyTSS extends TKey { const finalServer = selectedServers || randomSelectedServers; - const existingTSSIndexes = existingFactorPubs.map((fb) => this.getFactorEncs(fb, keyType).tssIndex); + const existingTSSIndexes = existingFactorPubs.map((fb) => this.getFactorEncs(fb, keyType, localTssTag).tssIndex); const updatedTSSIndexes = existingTSSIndexes.concat([newTSSIndex]); // sync metadata by default // create a localMetadataTransition if manual sync const updateMetadata = args.updateMetadata !== undefined ? args.updateMetadata : true; - await this._refreshTSSShares(updateMetadata, tssShare, tssIndex, updatedFactorPubs, updatedTSSIndexes, verifierId, { - ...rssNodeDetails, - selectedServers: finalServer, - authSignatures, - keyType: this.tssKeyType, - }); + await this._refreshTSSShares( + updateMetadata, + tssShare, + tssIndex, + updatedFactorPubs, + updatedTSSIndexes, + verifierId, + { + ...rssNodeDetails, + selectedServers: finalServer, + authSignatures, + keyType: this.tssKeyType, + }, + localTssTag + ); } } public async deleteFactorPub(args: { + tssTag: string; factorKey: BN; deleteFactorPub: Point; selectedServers?: number[]; authSignatures: string[]; updateMetadata?: boolean; }): Promise { - const addSecp256k1 = this._deleteFactorPub({ ...args, keyType: KeyType.secp256k1 }); - const addEd25519 = this._deleteFactorPub({ ...args, keyType: KeyType.ed25519 }); - await Promise.all([addSecp256k1, addEd25519]); + const { updateMetadata, ...otherArgs } = args; + const allPromise = []; + if (this.metadata.getTssData(KeyType.secp256k1, args.tssTag)) + allPromise.push(this._deleteFactorPub({ ...otherArgs, keyType: KeyType.secp256k1 })); + if (this.metadata.getTssData(KeyType.ed25519, args.tssTag)) allPromise.push(this._deleteFactorPub({ ...otherArgs, keyType: KeyType.ed25519 })); + await Promise.all(allPromise); + if (updateMetadata) await this._syncShareMetadata(); } /** @@ -949,14 +1193,15 @@ export class TKeyTSS extends TKey { authSignatures: string[]; updateMetadata?: boolean; keyType: KeyType; + tssTag: string; }): Promise { if (!this.metadata) throw CoreError.metadataUndefined("metadata is undefined"); if (!this.secp256k1Key) throw new Error("Tkey is not reconstructed"); - const { factorKey, deleteFactorPub, selectedServers, authSignatures, keyType } = args; + const { factorKey, deleteFactorPub, selectedServers, authSignatures, keyType, tssTag } = args; - const tssData = this.metadata.getTssData(keyType); + const tssData = this.metadata.getTssData(keyType, tssTag); const existingFactorPubs = tssData.factorPubs; - const { tssShare, tssIndex } = await this.getTSSShare(factorKey, { keyType }); + const { tssShare, tssIndex } = await this.getTSSShare(factorKey, { keyType, tssTag }); const found = existingFactorPubs.filter((f) => f.x.eq(deleteFactorPub.x) && f.y.eq(deleteFactorPub.y)); if (found.length === 0) throw CoreError.default("could not find factorPub to delete"); @@ -969,9 +1214,8 @@ export class TKeyTSS extends TKey { new Array(rssNodeDetails.serverEndpoints.length).fill(null).map((_, i) => i + 1), Math.ceil(rssNodeDetails.serverEndpoints.length / 2) ); - const finalServer = selectedServers || randomSelectedServers; - const updatedTSSIndexes = updatedFactorPubs.map((fb) => this.getFactorEncs(fb, args.keyType).tssIndex); + const updatedTSSIndexes = updatedFactorPubs.map((fb) => this.getFactorEncs(fb, keyType, tssTag).tssIndex); const updateMetadata = args.updateMetadata !== undefined ? args.updateMetadata : true; @@ -986,8 +1230,9 @@ export class TKeyTSS extends TKey { ...rssNodeDetails, selectedServers: finalServer, authSignatures, - keyType: this.tssKeyType, - } + keyType, + }, + tssTag ); } @@ -1052,155 +1297,4 @@ export class TKeyTSS extends TKey { tssPolyCommits, }; } - - /** - * Imports an existing private key for threshold signing. A corresponding user - * key share will be stored under the specified factor key. - */ - protected async importTssKey( - params: { - tag: string; - importKey: Buffer; - factorPubs: Point[]; - newTSSIndexes: number[]; - tssKeyType: KeyType; - }, - serverOpts: { - selectedServers?: number[]; - authSignatures: string[]; - } - ): Promise { - if (!this.secp256k1Key) throw CoreError.privateKeyUnavailable(); - if (!this.metadata) { - throw CoreError.metadataUndefined(); - } - - const tssData = this.metadata.getTssData(params.tssKeyType); - if (!tssData) throw CoreError.default("no tss data found"); - - const { importKey, factorPubs, newTSSIndexes, tag, tssKeyType } = params; - const ec = getKeyCurve(tssKeyType); - - const oldTag = this._tssTag; - this._tssTag = tag; - - try { - const { selectedServers = [], authSignatures = [] } = serverOpts || {}; - - if (!tag) throw CoreError.default(`invalid param, tag is required`); - if (!factorPubs || factorPubs.length === 0) throw CoreError.default(`invalid param, newFactorPub is required`); - if (!newTSSIndexes || newTSSIndexes.length === 0) throw CoreError.default(`invalid param, newTSSIndex is required`); - if (authSignatures.length === 0) throw CoreError.default(`invalid param, authSignatures is required`); - - const existingFactorPubs = tssData.factorPubs; - if (existingFactorPubs?.length > 0) { - throw CoreError.default(`Duplicate account tag, please use a unique tag for importing key`); - } - - const importScalar = await (async () => { - if (tssKeyType === KeyType.secp256k1) { - return new BN(importKey); - } else if (tssKeyType === KeyType.ed25519) { - // Store seed in metadata. - const domainKey = getEd25519SeedStoreDomainKey(this._tssTag || TSS_TAG_DEFAULT); - const result = this.metadata.getGeneralStoreDomain(domainKey) as Record; - if (result) { - throw new Error("Seed already exists"); - } - - const { scalar } = getEd25519KeyPairFromSeed(importKey); - const encKey = Buffer.from(getSecpKeyFromEd25519(scalar).point.encodeCompressed("hex"), "hex"); - const msg = await encrypt(encKey, importKey); - this.metadata.setGeneralStoreDomain(domainKey, { message: msg }); - - return scalar; - } - throw new Error("Invalid key type"); - })(); - - if (!importScalar || importScalar.toString("hex") === "0") { - throw new Error("Invalid importedKey"); - } - - const tssIndexes = newTSSIndexes; - const existingNonce = tssData.tssNonce; - const newTssNonce: number = existingNonce && existingNonce > 0 ? existingNonce + 1 : 0; - const verifierAndVerifierID = this.serviceProvider.getVerifierNameVerifierId(); - const label = `${verifierAndVerifierID}\u0015${this._tssTag}\u0016${newTssNonce}`; - const tssPubKey = hexPoint(ec.g.mul(importScalar)); - const rssNodeDetails = await this._getRssNodeDetails(); - const { pubKey: newTSSServerPub, nodeIndexes } = await this.serviceProvider.getTSSPubKey(this._tssTag, newTssNonce, tssKeyType); - let finalSelectedServers = selectedServers; - - if (nodeIndexes?.length > 0) { - if (selectedServers.length) { - finalSelectedServers = nodeIndexes.slice(0, Math.min(selectedServers.length, nodeIndexes.length)); - } else { - finalSelectedServers = nodeIndexes.slice(0, 3); - } - } else if (selectedServers?.length === 0) { - finalSelectedServers = randomSelection( - new Array(rssNodeDetails.serverEndpoints.length).fill(null).map((_, i) => i + 1), - Math.ceil(rssNodeDetails.serverEndpoints.length / 2) - ); - } - - const { serverEndpoints, serverPubKeys, serverThreshold } = rssNodeDetails; - - const rssClient = new RSSClient({ - serverEndpoints, - serverPubKeys, - serverThreshold, - tssPubKey, - keyType: tssKeyType, - }); - - const refreshResponses = await rssClient.import({ - importKey: importScalar, - dkgNewPub: pointToHex(newTSSServerPub), - selectedServers: finalSelectedServers, - factorPubs: factorPubs.map((f) => pointToHex(f)), - targetIndexes: tssIndexes, - newLabel: label, - sigs: authSignatures, - }); - const secondCommit = newTSSServerPub.toEllipticPoint(ec).add(ecPoint(ec, tssPubKey).neg()); - const newTSSCommits = [ - Point.fromJSON(tssPubKey), - Point.fromJSON({ x: secondCommit.getX().toString(16, 64), y: secondCommit.getY().toString(16, 64) }), - ]; - const factorEncs: { - [factorPubID: string]: FactorEnc; - } = {}; - for (let i = 0; i < refreshResponses.length; i++) { - const refreshResponse = refreshResponses[i]; - factorEncs[refreshResponse.factorPub.x.padStart(64, "0")] = { - type: "hierarchical", - tssIndex: refreshResponse.targetIndex, - userEnc: refreshResponse.userFactorEnc, - serverEncs: refreshResponse.serverFactorEncs, - }; - } - this.metadata.updateTSSData({ - tssKeyType, - tssTag: this.tssTag, - tssNonce: newTssNonce, - tssPolyCommits: newTSSCommits, - factorPubs, - factorEncs, - }); - if (!this._accountSalt) { - const accountSalt = generateSalt(this._tssCurve); - await this._setTKeyStoreItem(TSS_MODULE, { - id: "accountSalt", - value: accountSalt, - } as IAccountSaltStore); - this._accountSalt = accountSalt; - } - await this._syncShareMetadata(); - } catch (error) { - this._tssTag = oldTag; - throw error; - } - } } diff --git a/packages/tss/test/helpers.d.ts b/packages/tss/test/helpers.d.ts new file mode 100644 index 00000000..235ccd8d --- /dev/null +++ b/packages/tss/test/helpers.d.ts @@ -0,0 +1,25 @@ +import { IStorageLayer, KeyType } from "@tkey/common-types"; +import BN from "bn.js"; +import { TSSTorusServiceProvider } from "../src"; +export declare function initStorageLayer(): IStorageLayer; +export declare function fetchPostboxKeyAndSigs(opts: { + serviceProvider: TSSTorusServiceProvider; + verifierName: string; + verifierId: string; +}): Promise<{ + signatures: string[]; + postboxkey: BN; +}>; +export declare function assignTssDkgKeys(opts: { + serviceProvider: TSSTorusServiceProvider; + verifierName: string; + verifierId: string; + maxTSSNonceToSimulate: number; + tssTag?: string; +}): Promise<{ + serverDKGPrivKeys: BN[]; +}>; +export declare function generateKey(keyType: KeyType): { + raw: Buffer; + scalar: BN; +}; diff --git a/packages/tss/test/tss.ts b/packages/tss/test/tss.ts index ece06115..9fdde929 100644 --- a/packages/tss/test/tss.ts +++ b/packages/tss/test/tss.ts @@ -1,10 +1,11 @@ import { EllipticPoint, KeyType, Point } from "@tkey/common-types"; +import { Metadata } from "@tkey/core/"; import assert, { equal, fail, rejects } from "assert"; import BN from "bn.js"; import { ec as EC } from "elliptic"; import { TKeyTSS as ThresholdKey, TKeyTSS, TSSTorusServiceProvider } from "../src"; -import { factorKeyCurve } from "../src/tss"; +import { factorKeyCurve, TSS_TAG_DEFAULT } from "../src/tss"; import { getLagrangeCoeffs } from "../src/util"; import { assignTssDkgKeys, fetchPostboxKeyAndSigs, generateKey, initStorageLayer } from "./helpers"; @@ -57,37 +58,24 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { const factorPub = Point.fromElliptic(factorKeyPair.getPublic()); await tb1.initialize(); - if (TSS_KEY_TYPE === KeyType.secp256k1) { - tb1.initializeTssSecp256k1({ - factorPub, - deviceTSSShare, - deviceTSSIndex, - serverOpts: { - // selectedServers: [], - authSignatures: signatures, - }, - }); - } else { - tb1.initializeTssEd25519({ - factorPub, - deviceTSSShare, - deviceTSSIndex, - serverOpts: { - // selectedServers: [], - authSignatures: signatures, - }, - importKey: undefined, - }); - } + await tb1.initializeTss({ + factorPub, + deviceTSSShare, + deviceTSSIndex, + serverOpts: { + authSignatures: signatures, + }, + tssKeyType: TSS_KEY_TYPE, + }); const reconstructedKey = await tb1.reconstructKey(); await tb1.syncLocalMetadataTransitions(); if (tb1.secp256k1Key.cmp(reconstructedKey.secp256k1Key) !== 0) { fail("key should be able to be reconstructed"); } - const { tssShare: tss2 } = await tb1.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); + const { tssShare: tss2 } = await tb1.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); - const tssCommits = tb1.getTSSCommits(TSS_KEY_TYPE); + const tssCommits = tb1.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); const tss2Pub = ecTSS.g.mul(tss2); const tssCommitA0 = tssCommits[0].toEllipticPoint(ecTSS); const tssCommitA1 = tssCommits[1].toEllipticPoint(ecTSS); @@ -99,7 +87,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { const sp = torusSP; sp.verifierName = "torus-test-health"; - sp.verifierId = "test18@example.com"; + sp.verifierId = "test181@example.com"; const { serverDKGPrivKeys } = await assignTssDkgKeys({ serviceProvider: sp, verifierName: sp.verifierName, @@ -125,13 +113,14 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { const factorPub = Point.fromElliptic(factorKeyPair.getPublic()); await tb1.initialize(); - await tb1.initializeTssSecp256k1({ + await tb1.initializeTss({ factorPub, deviceTSSShare, deviceTSSIndex, serverOpts: { authSignatures: signatures, }, + tssKeyType: TSS_KEY_TYPE, }); const reconstructedKey = await tb1.reconstructKey(); await tb1.syncLocalMetadataTransitions(); @@ -139,8 +128,8 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { fail("key should be able to be reconstructed"); } - const { tssShare: tss2 } = await tb1.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); - const tssCommits = tb1.getTSSCommits(TSS_KEY_TYPE); + const { tssShare: tss2 } = await tb1.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); + const tssCommits = tb1.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); const tssPrivKey = getLagrangeCoeffs(ecTSS, [1, deviceTSSIndex], 1) .mul(tss1) @@ -149,7 +138,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { const tssPubKey = (ecTSS.g as EllipticPoint).mul(tssPrivKey); const tssCommits0 = tssCommits[0].toEllipticPoint(ecTSS); - const tssPub = tb1.getTSSPub(TSS_KEY_TYPE).toEllipticPoint(ecTSS); + const tssPub = tb1.getTSSPub(TSS_KEY_TYPE, TSS_TAG_DEFAULT).toEllipticPoint(ecTSS); equal(tssPubKey.eq(tssCommits0), true); equal(tssPub.eq(tssPubKey), true); @@ -161,19 +150,20 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { const nonce = tb1.computeAccountNonce(accountIndex); return share.add(nonce).umod(ecTSS.n); })(); - const { tssShare: tss2Account } = await tb1.getTSSShare(factorKey, { accountIndex, keyType: TSS_KEY_TYPE }); + const { tssShare: tss2Account } = await tb1.getTSSShare(factorKey, { accountIndex, keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); const coefficient1 = getLagrangeCoeffs(ecTSS, [1, deviceTSSIndex], 1); const coefficient2 = getLagrangeCoeffs(ecTSS, [1, deviceTSSIndex], deviceTSSIndex); const tssKey = coefficient1.mul(tss1Account).add(coefficient2.mul(tss2Account)).umod(ecTSS.n); const tssKeyPub = (ecTSS.g as EllipticPoint).mul(tssKey); - const tssPubAccount = tb1.getTSSPub(TSS_KEY_TYPE, accountIndex).toEllipticPoint(ecTSS); + const tssPubAccount = tb1.getTSSPub(TSS_KEY_TYPE, TSS_TAG_DEFAULT, accountIndex).toEllipticPoint(ecTSS); equal(tssPubAccount.eq(tssKeyPub), true, "should equal account pub key"); } }); - it(`#should be able to import a tss key for new account, manualSync=${manualSync}`, async function () { + // should always start with default account + it.skip(`#should be able to import a tss key for new account, manualSync=${manualSync}`, async function () { const sp = torusSP; sp.verifierName = "torus-test-health"; sp.verifierId = `importeduserfresh${TSS_KEY_TYPE}@example.com`; @@ -194,9 +184,19 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { const newTSSIndex = 2; await tb.initialize(); + await tb.initializeTss({ + factorPub, + importKey: importTssKey.raw, + deviceTSSIndex: newTSSIndex, + serverOpts: { + authSignatures: signatures, + }, + tssKeyType: TSS_KEY_TYPE, + }); await tb.reconstructKey(); + await tb.importTssKey( - { tag: "imported", importKey: importTssKey.raw, factorPub, newTSSIndex }, + { tssTag: "imported", importKey: importTssKey.raw, factorPubs: [factorPub], newTSSIndexes: [newTSSIndex], tssKeyType: TSS_KEY_TYPE }, { authSignatures: signatures, } @@ -206,19 +206,22 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { // Check pub key. const importTssKeyPub = Point.fromScalar(importTssKey.scalar, tb.tssCurve); - const tssPub = await tb.getTSSPub(TSS_KEY_TYPE); + const tssPub = await tb.getTSSPub(TSS_KEY_TYPE, "imported"); assert(tssPub.equals(importTssKeyPub)); // Check exported key. const exportedKey = await tb._UNSAFE_exportTssKey({ factorKey, authSignatures: signatures, + keyType: TSS_KEY_TYPE, + tssTag: TSS_TAG_DEFAULT, }); assert(exportedKey.eq(importTssKey.scalar)); if (TSS_KEY_TYPE === KeyType.ed25519) { const seed = await tb._UNSAFE_exportTssEd25519Seed({ factorKey, authSignatures: signatures, + tssTag: TSS_TAG_DEFAULT, }); assert(seed.equals(importTssKey.raw)); } else { @@ -227,9 +230,11 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { factorKey, authSignatures: signatures, accountIndex: 2, + keyType: TSS_KEY_TYPE, + tssTag: TSS_TAG_DEFAULT, }); const exportedPubKeyIndex2 = Point.fromScalar(exportedKeyIndex2, tb.tssCurve); - const pubKeyIndex2 = tb.getTSSPub(TSS_KEY_TYPE, 2); + const pubKeyIndex2 = tb.getTSSPub(TSS_KEY_TYPE, TSS_TAG_DEFAULT, 2); assert(exportedPubKeyIndex2.equals(pubKeyIndex2)); } }); @@ -264,7 +269,21 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { const factorPub = Point.fromElliptic(factorKeyPair.getPublic()); // 2/2 await tb.initialize(); - await tb.initializeTssSecp256k1({ factorPub, deviceTSSShare, deviceTSSIndex }); + + // import key + const { raw: importedKey0, scalar: importedScalar0 } = generateKey(TSS_KEY_TYPE); + + await tb.initializeTss({ + tssKeyType: TSS_KEY_TYPE, + factorPub, + deviceTSSShare, + deviceTSSIndex, + importKey: TSS_KEY_TYPE === KeyType.ed25519 ? importedKey0 : undefined, + serverOpts: { + authSignatures: signatures, + }, + }); + const newShare = await tb.generateNewShare(); const reconstructedKey = await tb.reconstructKey(); @@ -273,8 +292,11 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { if (tb.secp256k1Key.cmp(reconstructedKey.secp256k1Key) !== 0) { fail("key should be able to be reconstructed"); } - const { tssShare: retrievedTSS, tssIndex: retrievedTSSIndex } = await tb.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); - const tssCommits = tb.getTSSCommits(TSS_KEY_TYPE); + const { tssShare: retrievedTSS, tssIndex: retrievedTSSIndex } = await tb.getTSSShare(factorKey, { + keyType: TSS_KEY_TYPE, + tssTag: TSS_TAG_DEFAULT, + }); + const tssCommits = tb.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); const tssPrivKey = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], 1) .mul(serverDKGPrivKeys[0]) .add(getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], retrievedTSSIndex).mul(retrievedTSS)) @@ -295,17 +317,22 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { // import key const { raw: importedKey, scalar: importedScalar } = generateKey(TSS_KEY_TYPE); await tb.importTssKey( - { tag: "imported", importKey: importedKey, factorPub, newTSSIndex }, + { tssTag: "imported", importKey: importedKey, factorPubs: [factorPub], newTSSIndexes: [newTSSIndex], tssKeyType: TSS_KEY_TYPE }, { authSignatures: signatures, } ); + // tag is switched to imported await tb.syncLocalMetadataTransitions(); + // for imported key - const { tssShare: retrievedTSS1, tssIndex: retrievedTSSIndex1 } = await tb.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); + const { tssShare: retrievedTSS1, tssIndex: retrievedTSSIndex1 } = await tb.getTSSShare(factorKey, { + keyType: TSS_KEY_TYPE, + tssTag: "imported", + }); - const tssCommits1 = tb.getTSSCommits(TSS_KEY_TYPE); + const tssCommits1 = tb.getTSSCommits(TSS_KEY_TYPE, "imported"); const tssPrivKey1 = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex1], 1) .mul(serverDKGPrivKeys1[0]) .add(getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex1], retrievedTSSIndex1).mul(retrievedTSS1)) @@ -321,13 +348,15 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { factorKey, selectedServers: [1, 2, 3], authSignatures: signatures, + tssTag: "imported", }); equal(seed.equals(importedKey), true); } const tb2 = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, tssKeyType: TSS_KEY_TYPE }); - await tb2.initialize({ factorPub }); + await tb2.initialize(); + tb2.inputShareStore(newShare.newShareStores[newShare.newShareIndex.toString("hex")]); const reconstructedKey2 = await tb2.reconstructKey(); await tb2.syncLocalMetadataTransitions(); @@ -335,15 +364,18 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { if (tb2.secp256k1Key.cmp(reconstructedKey2.secp256k1Key) !== 0) { fail("key should be able to be reconstructed"); } - const tssCommits2 = tb2.getTSSCommits(TSS_KEY_TYPE); + const tssCommits2 = tb2.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); const tssCommits20 = tssCommits2[0].toEllipticPoint(ecTSS); equal(tssPubKey.eq(tssCommits20), true); // switch to imported account - tb2.tssTag = "imported"; - const { tssShare: retrievedTSSImported, tssIndex: retrievedTSSIndexImported } = await tb2.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); + // tb2.tssTag = "imported"; + const { tssShare: retrievedTSSImported, tssIndex: retrievedTSSIndexImported } = await tb2.getTSSShare(factorKey, { + keyType: TSS_KEY_TYPE, + tssTag: "imported", + }); - const tssCommitsImported = tb2.getTSSCommits(TSS_KEY_TYPE); + const tssCommitsImported = tb2.getTSSCommits(TSS_KEY_TYPE, "imported"); const tssPrivKeyImported = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndexImported], 1) .mul(serverDKGPrivKeys1[0]) @@ -386,7 +418,8 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { const factorPub = Point.fromElliptic(factorKeyPair.getPublic()); // 2/2 - await tb.initialize({ factorPub, deviceTSSShare, deviceTSSIndex }); + await tb.initialize(); + await tb.initializeTss({ factorPub, deviceTSSShare, deviceTSSIndex, tssKeyType: TSS_KEY_TYPE, serverOpts: { authSignatures: signatures } }); const newShare = await tb.generateNewShare(); const reconstructedKey = await tb.reconstructKey(); @@ -395,8 +428,11 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { if (tb.secp256k1Key.cmp(reconstructedKey.secp256k1Key) !== 0) { fail("key should be able to be reconstructed"); } - const { tssShare: retrievedTSS, tssIndex: retrievedTSSIndex } = await tb.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); - const tssCommits = tb.getTSSCommits(TSS_KEY_TYPE); + const { tssShare: retrievedTSS, tssIndex: retrievedTSSIndex } = await tb.getTSSShare(factorKey, { + keyType: TSS_KEY_TYPE, + tssTag: TSS_TAG_DEFAULT, + }); + const tssCommits = tb.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); const tssPrivKey = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], 1) .mul(serverDKGPrivKeys[0]) .add(getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], retrievedTSSIndex).mul(retrievedTSS)) @@ -416,8 +452,20 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { // import key const { raw: importedKey, scalar: importedScalar } = generateKey(TSS_KEY_TYPE); const importedIndex = 2; + tb.initializeTss({ + factorPub, + deviceTSSIndex: importedIndex, + tssKeyType: TSS_KEY_TYPE, + serverOpts: { authSignatures: signatures }, + }); await tb.importTssKey( - { tag: "imported", importKey: importedKey, factorPub, newTSSIndex: importedIndex }, + { + tssTag: "imported", + importKey: importedKey, + factorPubs: [factorPub], + newTSSIndexes: [importedIndex], + tssKeyType: TSS_KEY_TYPE, + }, { authSignatures: signatures, } @@ -426,12 +474,14 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { await tb.syncLocalMetadataTransitions(); // for imported key { - const finalPubKey = tb.getTSSCommits(TSS_KEY_TYPE)[0].toEllipticPoint(ecTSS); + const finalPubKey = tb.getTSSCommits(TSS_KEY_TYPE, "imported")[0].toEllipticPoint(ecTSS); const finalTssKey = await tb._UNSAFE_exportTssKey({ factorKey, selectedServers: [1, 2, 3], authSignatures: signatures, + keyType: TSS_KEY_TYPE, + tssTag: "imported", }); const tssPubKeyImported = (ecTSS.g as EllipticPoint).mul(importedScalar); @@ -443,39 +493,45 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { factorKey, selectedServers: [3, 4, 5], authSignatures: signatures, + tssTag: "imported", }); equal(seed.equals(importedKey), true); } } { - tb.tssTag = "default"; + // tb.tssTag = "default"; - const finalPubKey = tb.getTSSCommits(TSS_KEY_TYPE)[0].toEllipticPoint(ecTSS); + const finalPubKey = tb.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT)[0].toEllipticPoint(ecTSS); const finalTssKey = await tb._UNSAFE_exportTssKey({ factorKey, selectedServers: [1, 2, 3], authSignatures: signatures, + keyType: TSS_KEY_TYPE, + tssTag: TSS_TAG_DEFAULT, }); + const tssPubKeyImported = (ecTSS.g as EllipticPoint).mul(finalTssKey); equal(tssPubKeyImported.eq(finalPubKey), true); } - + // login to new instance const tb2 = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, tssKeyType: TSS_KEY_TYPE }); - await tb2.initialize({ factorPub }); + await tb2.initialize(); tb2.inputShareStore(newShare.newShareStores[newShare.newShareIndex.toString("hex")]); await tb2.reconstructKey(); await tb2.syncLocalMetadataTransitions(); { - tb2.tssTag = "imported"; - const finalPubKey = tb2.getTSSCommits(TSS_KEY_TYPE)[0].toEllipticPoint(ecTSS); + // tb2.tssTag = "imported"; + const finalPubKey = tb2.getTSSCommits(TSS_KEY_TYPE, "imported")[0].toEllipticPoint(ecTSS); const finalTssKey = await tb2._UNSAFE_exportTssKey({ factorKey, selectedServers: [1, 2, 3], authSignatures: signatures, + keyType: TSS_KEY_TYPE, + tssTag: "imported", }); const tssPubKeyImported = (ecTSS.g as EllipticPoint).mul(finalTssKey); @@ -483,14 +539,16 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { equal(tssPubKeyImported.eq(finalPubKey), true); } { - tb2.tssTag = "default"; + // tb2.tssTag = "default"; - const finalPubKey = tb2.getTSSCommits(TSS_KEY_TYPE)[0].toEllipticPoint(ecTSS); + const finalPubKey = tb2.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT)[0].toEllipticPoint(ecTSS); const finalTssKey = await tb2._UNSAFE_exportTssKey({ factorKey, selectedServers: [1, 2, 3], authSignatures: signatures, + keyType: TSS_KEY_TYPE, + tssTag: TSS_TAG_DEFAULT, }); const tssPubKeyImported = (ecTSS.g as EllipticPoint).mul(finalTssKey); @@ -533,7 +591,14 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { factorKey = factorKeyPair.getPrivate(); const factorPub = Point.fromElliptic(factorKeyPair.getPublic()); const deviceTSSShare = ecTSS.genKeyPair().getPrivate(); - await tb.initialize({ factorPub, deviceTSSShare, deviceTSSIndex }); + await tb.initialize(); + await tb.initializeTss({ + factorPub, + deviceTSSShare, + deviceTSSIndex, + tssKeyType: TSS_KEY_TYPE, + serverOpts: { authSignatures }, + }); const reconstructedKey = await tb.reconstructKey(); await tb.syncLocalMetadataTransitions(); @@ -550,6 +615,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { existingFactorKey: factorKey, newFactorPub: Point.fromElliptic(newFactorPub), newTSSIndex: deviceTSSIndex, + tssTag: TSS_TAG_DEFAULT, }); await tb.syncLocalMetadataTransitions(); }); @@ -563,6 +629,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { newFactorPub: Point.fromElliptic(newFactorPub), newTSSIndex, refreshShares: true, + tssTag: TSS_TAG_DEFAULT, }); await tb.syncLocalMetadataTransitions(); }); @@ -573,12 +640,13 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { factorKey, deleteFactorPub: Point.fromElliptic(newFactorPub), authSignatures: signatures, + tssTag: TSS_TAG_DEFAULT, }); await tb.syncLocalMetadataTransitions(); }); it("should no longer be able to access key share with removed factor (same index)", async function () { - await rejects(tb.getTSSShare(newFactorKeySameIndex, { keyType: TSS_KEY_TYPE })); + await rejects(tb.getTSSShare(newFactorKeySameIndex, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT })); }); it("should be able to remove factor for different index", async function () { @@ -587,12 +655,13 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { factorKey, deleteFactorPub: Point.fromElliptic(newFactorPub), authSignatures: signatures, + tssTag: TSS_TAG_DEFAULT, }); await tb.syncLocalMetadataTransitions(); }); it("should no longer be able to access key share with removed factor (different index)", async function () { - await rejects(tb.getTSSShare(newFactorKeyNewIndex, { keyType: TSS_KEY_TYPE })); + await rejects(tb.getTSSShare(newFactorKeyNewIndex, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT })); }); }); @@ -632,7 +701,14 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { factorKey = factorKeyPair.getPrivate(); const factorPub = Point.fromElliptic(factorKeyPair.getPublic()); const deviceTSSShare = ecTSS.genKeyPair().getPrivate(); - await tb.initialize({ factorPub, deviceTSSShare, deviceTSSIndex }); + await tb.initialize(); + await tb.initializeTss({ + factorPub, + deviceTSSShare, + deviceTSSIndex, + tssKeyType: TSS_KEY_TYPE, + serverOpts: { authSignatures }, + }); const reconstructedKey = await tb.reconstructKey(); await tb.syncLocalMetadataTransitions(); @@ -659,7 +735,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { await tbJson.reconstructKey(); // try refresh share - await tbJson.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); + await tbJson.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); const newFactorKeyPair = ecFactor.genKeyPair(); await tbJson.addFactorPub({ @@ -668,9 +744,10 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { newFactorPub: Point.fromElliptic(newFactorKeyPair.getPublic()), newTSSIndex, refreshShares: true, + tssTag: TSS_TAG_DEFAULT, }); - await tbJson.getTSSShare(newFactorKeyPair.getPrivate(), { keyType: TSS_KEY_TYPE }); + await tbJson.getTSSShare(newFactorKeyPair.getPrivate(), { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); const serialized2 = JSON.stringify(tbJson); @@ -683,8 +760,8 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { await tbJson2.reconstructKey(); - await tbJson2.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); - await tbJson2.getTSSShare(newFactorKeyPair.getPrivate(), { keyType: TSS_KEY_TYPE }); + await tbJson2.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); + await tbJson2.getTSSShare(newFactorKeyPair.getPrivate(), { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); }); }); }); From c08c487bef8f52b57dabb131fa36a9a36ae68758 Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 4 Feb 2025 17:57:42 +0800 Subject: [PATCH 06/22] fix: add unit test for metadata formats --- packages/core/src/index.ts | 2 +- packages/core/src/metadata.ts | 18 ++++---- packages/core/test/metadataFormat.test.js | 56 +++++++++++++++++++++++ 3 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 packages/core/test/metadataFormat.test.js diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 74ed98ee..7c7426a5 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -2,4 +2,4 @@ export { default as AuthMetadata } from "./authMetadata"; export { default as TKey } from "./core"; export { default as CoreError } from "./errors"; export * from "./lagrangeInterpolatePolynomial"; -export { default as Metadata } from "./metadata"; +export { LegacyMetadata, Metadata } from "./metadata"; diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index ffc55e49..44692b40 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -31,10 +31,10 @@ import { TssMetadata } from "./tssMetadata"; export type SupportedCurve = "ed25519" | "sec"; -const LEGACY_METADATA_VERSION = "0.0.1"; -const METADATA_VERSION = "1.0.0"; +export const LEGACY_METADATA_VERSION = "0.0.1"; +export const METADATA_VERSION = "1.0.0"; -class Metadata implements IMetadata { +export class Metadata implements IMetadata { pubKey: Point; publicPolynomials: PublicPolynomialMap; @@ -99,7 +99,6 @@ class Metadata implements IMetadata { } = value; const point = Point.fromSEC1(secp256k1, pubKey); const metadata = new Metadata(point); - const localVersion = version || LEGACY_METADATA_VERSION; const unserializedPolyIDList: PolyIDAndShares[] = []; @@ -108,7 +107,7 @@ class Metadata implements IMetadata { if (scopedStore) metadata.scopedStore = scopedStore; if (nonce) metadata.nonce = nonce; - if (localVersion === METADATA_VERSION) { + if (version === METADATA_VERSION) { metadata.tss = {}; if (tss) { @@ -128,7 +127,7 @@ class Metadata implements IMetadata { metadata.tss = {}; Object.keys(tssKeyTypes).forEach((tssTag) => { if (tssKeyTypes[tssTag] === KeyType.ed25519) { - throw new Error(`ed25519 is not supported for migration for metadata from v${localVersion} to ${METADATA_VERSION}`); + throw new Error(`ed25519 is not supported for migration for metadata from v${version ?? LEGACY_METADATA_VERSION} to ${METADATA_VERSION}`); } metadata.tss[tssTag] = { [tssKeyTypes[tssTag]]: TssMetadata.fromJSON({ @@ -142,7 +141,8 @@ class Metadata implements IMetadata { }; }); } - metadata.version = localVersion; + // updated to latest version since using latest Metadata deserialization + metadata.version = METADATA_VERSION; for (let i = 0; i < polyIDList.length; i += 1) { const serializedPolyID: string = polyIDList[i]; @@ -478,7 +478,7 @@ export class LegacyMetadata extends Metadata { const tsstags = Object.keys(this.tss); // return if tss data is not available - if (tsstags.length > 0) { + if (tsstags.length <= 0) { return { pubKey: this.pubKey.toSEC1(secp256k1, true).toString("hex"), polyIDList: serializedPolyIDList, @@ -523,7 +523,7 @@ export class LegacyMetadata extends Metadata { factorPubs, factorEncs, // Legacy Metadata version - version: this.version, + // version: this.version, }; } diff --git a/packages/core/test/metadataFormat.test.js b/packages/core/test/metadataFormat.test.js new file mode 100644 index 00000000..52c3c5b7 --- /dev/null +++ b/packages/core/test/metadataFormat.test.js @@ -0,0 +1,56 @@ +import { deepEqual, equal, fail } from "assert"; + +import { LegacyMetadata, Metadata } from "../src"; +import { LEGACY_METADATA_VERSION, METADATA_VERSION } from "../src/metadata"; + +const legacyJSONSecp256k1 = + '{"pubKey":"03eac52f1c8c0c14664aa18eb51e74e3075010774dfbd5609912ccb00d80f887c9","polyIDList":["03eac52f1c8c0c14664aa18eb51e74e3075010774dfbd5609912ccb00d80f887c9|029be6a4725c1015f26ad3c9e2d99a85d22ab7ccd9ad475bcb2bb8d2d3d780f12e|0x0|1|dced44048f8eb1ce6b9c33ded4ccc755f1324db1e038f1f6062ad19cd05ea92f"],"scopedStore":{},"generalStore":{"ed25519Seed":{"message":{"ciphertext":"1ee343ad59a4640906a9ed1e8d6685c58cdc23fcc61d067ab0d19b6bdef87608a06e6926de3ee68cd57e8bdba3985f76","ephemPublicKey":"04693440ffeddc206f4533aae9b1a383ff6d157c3607dae31aeb7a1d882f3f09c0777107a5d9522f056b0512b72d38bca1d993ca48552c360b2e0f6bb103999d97","iv":"abefdacaaad4fb0c6ff097ef244d21a2","mac":"d35e93fc593c5fa87b57a1da4217fdd2ff798551196c34ab86c2eaf7c63e78c2"},"publicKey":"04527505afd3992141247eb4443d0db646e1b1fe675d8047183510681e3e62d75e220cc092558775b41b8a6fb1218e11c892a0b5db9c39c6c46c3e4489cfe348c8"}},"tkeyStore":{"tssModule":[{"ciphertext":"8d1fa8ee530ac2f6648cf0876c024f03838463fcf695a60191cf24e637cb0f086e0ed3fdf85b0715d4bde6cca8982ebd53bd098e37c8b290dde0a4cc2bc216bd6b4563a35ead70728faa75dad8cba08c8858340e1f130ed1e53b3dc36e896675","ephemPublicKey":"04e7fbbff48c90dfb7b889825197a0caf1f0b16f1e60904da43ed8fdd88be6e67216b032cafb5209dab3099eb6ae9c020aa2687b54940d0f287d566e4774ffab1f","iv":"63bfe91464311dce7a31ea2e9125517b","mac":"4106dc6e65eb194d51ab3e7f384f651cf1045b8beeb59110e9bbf10ecace214c"}]},"nonce":1,"tssKeyTypes":{"default":"secp256k1"},"tssNonces":{"default":0},"tssPolyCommits":{"default":[{"x":"246a392cfd37aaa4fefe1a67094d3a47421ab44b2826261cec0b2b276ebba0aa","y":"983dc77eca2cd1d24b3af2b7d5b012dd39fc72f309e9c8a0959f1e59c0c78fe2"},{"x":"6c48e72b3216356cf2323d39715438cecf3ead5029b50d8f17e0a1e4dd529d07","y":"eb503a9a5cdab363b555b58ea15bf4ce6b529f91d29e88b0c576afd2b9595b84"}]},"factorPubs":{"default":[{"x":"a3f5b8bfab2139a8d3f26779a6e506d1f189c49cad0f3706cfbd226e8625edf9","y":"97d94f2efe23ce915d8a642708347160b931fcc328dd195244e3cb1ffefbe0f2"}]},"factorEncs":{"default":{"a3f5b8bfab2139a8d3f26779a6e506d1f189c49cad0f3706cfbd226e8625edf9":{"tssIndex":2,"type":"direct","userEnc":{"ciphertext":"a9f5c5884e124766c1a7822455d609068efb39406deabb6f04066b31f6f6f8327d8ddd6d9e22ac5790e389b961b8fc0f","ephemPublicKey":"041252d978c5f6802b1f4c4e839b3aaae85ddb4c69b901b301add422006a8a25b3187f3a087e34f5645ee92d1743320f98a8251e0d0178df90ae1736812c7dcaa5","iv":"b6565049616254a9f804b7a8feab4a5b","mac":"4f47a3d935cc5b12c817f146f86f5f1a9bae3dca2e73ca1e0de98aec2fd4891e"},"serverEncs":[]}}}}'; + +const legacyJSONEd25519 = + '{"pubKey":"029e5d3a06bbe4659c7433403be59da2c51c99ed330e8f3e6225414769f865dcf3","polyIDList":["029e5d3a06bbe4659c7433403be59da2c51c99ed330e8f3e6225414769f865dcf3|03105e9ec1becf75eaa9e5a9888e3c0ab604982ffe1b1dea006bc6ed9ab43d079b|0x0|1|2d705790acf48c9801de266e335c2739ce0586c173d1f7d831d25b8219f64ca0"],"scopedStore":{},"generalStore":{"ed25519Seed":{"message":{"ciphertext":"166c8f4956886f32a097d864936e7abd01a286dd37f963566a7fb59b0333a244045aa3416c8e90e955d064a0504dcdb1","ephemPublicKey":"0430fe9ccd25f8a7c8f8cca21088ba26f64f33b318b0fcd8b9700a11a437bb615b7bbf319ac26db17e7a0a081f5b62538aea0b0ffe6b0d1601a80b6a097ee5b741","iv":"76cfbb21ef3d5bcf4501c7b6d041a4c5","mac":"00aaf75774260066c06ee7a06405865b3b899bfb5b7fb7f7ff3f52b219c8d4e7"},"publicKey":"044f4dcadc2941fcc76f6746993f900b8b737e3c6b56c14e0e39f4bec43cc555af6ebe794f7dc54ab326b3cf22400115d89b5d2d5ed220f79d876170981963c09c"}},"tkeyStore":{"tssModule":[{"ciphertext":"a8c50c080af2e182b67fbcc7f910332727cde888d929eb9a365e2e6937c68bb395b857ce3b535bd2805c899a8d1976e08578b2946409d124863238ea39cc67b82a4c29a7af9aa6ada36017f8e89cb46df6633b57401d66eb362f5c2a251aa296","ephemPublicKey":"0406f773c20a70b50cbd8a3f3b3b668819a5eb6fb047c6d732adcbac3940c6f216423e342147567a8b4c9eb8709745200974aa5ce9eb3c0b715cae81699b3bb788","iv":"28f4806fec2cbcc1ca831a49dbaf05f2","mac":"d3601afad4f69578e9647b8d93792abb498ae954cd5ce44e8bd156d4efbbb4b0"}]},"nonce":1,"tssKeyTypes":{"default":"ed25519"},"tssNonces":{"default":0},"tssPolyCommits":{"default":[{"x":"2f963f06cb9d1e03b3f1654a4bed5b0674a31e64b3b488a63647fc380444b974","y":"703cd9ad9cf68ce8f50b1211a7e47ea1713077727ced731a2d833ae97f75f55c"},{"x":"25b411f18db3111060c9d4c26d1af7a0457812b597f855878ccbf4f3e3f65b79","y":"4448c04126cfee3feb6960781382d2896f9909bb3fac7aac6d9a7c6104059467"}]},"factorPubs":{"default":[{"x":"a24ec82353cff644b308c76f60703683d3a63fe845ba44e98b674aa865304fa","y":"dee6d45e36a85535a1e59c7c4820127f7dbd30900ca8b4052157e309ff72ab1e"}]},"factorEncs":{"default":{"0a24ec82353cff644b308c76f60703683d3a63fe845ba44e98b674aa865304fa":{"tssIndex":2,"type":"direct","userEnc":{"ciphertext":"ec6234ec911339b26c68bb20ee5fb092fe834340472aa37746bc5c3884267188d2cfdddca886a88b52624ba03c43578d","ephemPublicKey":"04f3bc24184daf6ffb0a399952d547af94d51ef3748cae212e1d6d7dbd4c7191fc7e234cbf78bfef90fbf8944e921967916cd936359802369ea4243bfeefdfbcc3","iv":"5c48e8efb94490970619c3497f41282e","mac":"472df1907d85a0493bd74a84fab33de4c9a3383318098638fd0a0f334527eed5"},"serverEncs":[]}}}}'; + +describe("Metadata new Formatting tests", function () { + it("#should serialize and deserialize into JSON seamlessly", async function () { + const instance1 = Metadata.fromJSON(JSON.parse(legacyJSONSecp256k1)); + const instance1Serialized = instance1.toJSON(); + const instance2 = Metadata.fromJSON(instance1Serialized); + deepEqual(instance1, instance2); + + try { + LegacyMetadata.fromJSON(instance1Serialized); + fail("LegacyMetadata should not able to deserialize latest format"); + } catch (e) {} + }); + + it("#should not able to serialize for legacy ed25510 metadata format", async function () { + try { + Metadata.fromJSON(JSON.parse(legacyJSONEd25519)); + // should not able to serialize ed25519 keyType as the postboxkey is from ed25519 network + // multicurve only support postboxkey from secp258k1 + fail("should not reach here, new Metadata should not able to serialize legacy ed25519 metadata format "); + } catch (e) {} + }); + + it("#should able to deserialize legacy JSON", async function () { + const legacyInstance = LegacyMetadata.fromJSON(JSON.parse(legacyJSONEd25519)); + const legacyInstanceSerialized = legacyInstance.toJSON(); + equal(JSON.stringify(legacyInstanceSerialized), legacyJSONEd25519); + + const legacyInstance1 = LegacyMetadata.fromJSON(JSON.parse(legacyJSONSecp256k1)); + const legacyInstanceSerialized1 = legacyInstance1.toJSON(); + + equal(JSON.stringify(legacyInstanceSerialized1), legacyJSONSecp256k1); + + const instance1 = Metadata.fromJSON(JSON.parse(legacyJSONSecp256k1)); + + equal(instance1.version, METADATA_VERSION); + equal(legacyInstance1.version, LEGACY_METADATA_VERSION); + + delete instance1.version; + delete legacyInstance1.version; + + deepEqual(instance1, legacyInstance1); + }); + + // to add: add tests for different and multple tags and keytypes\ +}); From 23f565a9c36c8875d301c9fe404e0bcaf72fbfa7 Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 6 Feb 2025 12:23:47 +0800 Subject: [PATCH 07/22] fix: multiCurve tests passed --- packages/core/src/core.ts | 4 +- packages/core/src/metadata.ts | 39 +- packages/core/test/metadataFormat.test.js | 8 + packages/tss/.mocharc.json | 2 +- packages/tss/src/tss.ts | 103 +- packages/tss/test/helpers.ts | 3 + packages/tss/test/tssMultiCurveTestCases.ts | 903 ++++++++++++++++++ .../{tss.ts => tssSingleCurveTestCases.ts} | 90 +- packages/tss/test/tssUnsupportedTestCases.ts | 172 ++++ 9 files changed, 1239 insertions(+), 85 deletions(-) create mode 100644 packages/tss/test/tssMultiCurveTestCases.ts rename packages/tss/test/{tss.ts => tssSingleCurveTestCases.ts} (92%) create mode 100644 packages/tss/test/tssUnsupportedTestCases.ts diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 1583bc5b..ab633fdb 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -53,7 +53,7 @@ import stringify from "json-stable-stringify"; import AuthMetadata from "./authMetadata"; import CoreError from "./errors"; import { generatePrivateBN, generateRandomPolynomial, lagrangeInterpolatePolynomial, lagrangeInterpolation } from "./lagrangeInterpolatePolynomial"; -import Metadata, { createMetadataFromJson, createMetadataInstance } from "./metadata"; +import { createMetadataFromJson, createMetadataInstance, Metadata } from "./metadata"; const ed25519SeedConst = "ed25519Seed"; @@ -165,6 +165,8 @@ class ThresholdKey implements ITKey { manualSync, serverTimeOffset, }); + // overwrite legacyMetadataFlag + tb.legacyMetadataFlag = legacyMetadataFlag ?? false; // this will computed during reconstructKey should we restore here? if (privKey) tb.privKey = new BN(privKey, "hex"); diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index 44692b40..cd8bdb58 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -113,10 +113,9 @@ export class Metadata implements IMetadata { if (tss) { Object.keys(tss).forEach((tsstag) => { if (tss[tsstag]) { + metadata.tss[tsstag] = {}; Object.keys(tss[tsstag]).forEach((tssKeyType) => { - metadata.tss[tsstag] = { - [tssKeyType]: TssMetadata.fromJSON(tss[tsstag][tssKeyType]), - }; + metadata.tss[tsstag][tssKeyType] = TssMetadata.fromJSON(tss[tsstag][tssKeyType]); }); } }); @@ -423,20 +422,6 @@ export class LegacyMetadata extends Metadata { if (scopedStore) metadata.scopedStore = scopedStore; if (nonce) metadata.nonce = nonce; - metadata.tss = {}; - Object.keys(tssKeyTypes).forEach((tssTag) => { - metadata.tss[tssTag] = { - [tssKeyTypes[tssTag]]: TssMetadata.fromJSON({ - tssTag, - tssKeyType: tssKeyTypes[tssTag], - tssNonce: tssNonces[tssTag], - tssPolyCommits: tssPolyCommits[tssTag], - factorPubs: factorPubs[tssTag], - factorEncs: factorEncs[tssTag], - }), - }; - }); - for (let i = 0; i < polyIDList.length; i += 1) { const serializedPolyID: string = polyIDList[i]; const arrPolyID = serializedPolyID.split("|"); @@ -457,6 +442,24 @@ export class LegacyMetadata extends Metadata { } metadata.polyIDList = unserializedPolyIDList; + + // tss related data + if (!tssKeyTypes) return metadata; + if (Object.keys(tssKeyTypes).length === 0) return metadata; + + metadata.tss = {}; + Object.keys(tssKeyTypes).forEach((tssTag) => { + metadata.tss[tssTag] = { + [tssKeyTypes[tssTag]]: TssMetadata.fromJSON({ + tssTag, + tssKeyType: tssKeyTypes[tssTag], + tssNonce: tssNonces[tssTag], + tssPolyCommits: tssPolyCommits[tssTag], + factorPubs: factorPubs[tssTag], + factorEncs: factorEncs[tssTag], + }), + }; + }); return metadata; } @@ -475,7 +478,7 @@ export class LegacyMetadata extends Metadata { serializedPolyIDList.push(serializedPolyID); } - const tsstags = Object.keys(this.tss); + const tsstags = Object.keys(this.tss ?? {}); // return if tss data is not available if (tsstags.length <= 0) { diff --git a/packages/core/test/metadataFormat.test.js b/packages/core/test/metadataFormat.test.js index 52c3c5b7..7dc5c1c6 100644 --- a/packages/core/test/metadataFormat.test.js +++ b/packages/core/test/metadataFormat.test.js @@ -9,6 +9,9 @@ const legacyJSONSecp256k1 = const legacyJSONEd25519 = '{"pubKey":"029e5d3a06bbe4659c7433403be59da2c51c99ed330e8f3e6225414769f865dcf3","polyIDList":["029e5d3a06bbe4659c7433403be59da2c51c99ed330e8f3e6225414769f865dcf3|03105e9ec1becf75eaa9e5a9888e3c0ab604982ffe1b1dea006bc6ed9ab43d079b|0x0|1|2d705790acf48c9801de266e335c2739ce0586c173d1f7d831d25b8219f64ca0"],"scopedStore":{},"generalStore":{"ed25519Seed":{"message":{"ciphertext":"166c8f4956886f32a097d864936e7abd01a286dd37f963566a7fb59b0333a244045aa3416c8e90e955d064a0504dcdb1","ephemPublicKey":"0430fe9ccd25f8a7c8f8cca21088ba26f64f33b318b0fcd8b9700a11a437bb615b7bbf319ac26db17e7a0a081f5b62538aea0b0ffe6b0d1601a80b6a097ee5b741","iv":"76cfbb21ef3d5bcf4501c7b6d041a4c5","mac":"00aaf75774260066c06ee7a06405865b3b899bfb5b7fb7f7ff3f52b219c8d4e7"},"publicKey":"044f4dcadc2941fcc76f6746993f900b8b737e3c6b56c14e0e39f4bec43cc555af6ebe794f7dc54ab326b3cf22400115d89b5d2d5ed220f79d876170981963c09c"}},"tkeyStore":{"tssModule":[{"ciphertext":"a8c50c080af2e182b67fbcc7f910332727cde888d929eb9a365e2e6937c68bb395b857ce3b535bd2805c899a8d1976e08578b2946409d124863238ea39cc67b82a4c29a7af9aa6ada36017f8e89cb46df6633b57401d66eb362f5c2a251aa296","ephemPublicKey":"0406f773c20a70b50cbd8a3f3b3b668819a5eb6fb047c6d732adcbac3940c6f216423e342147567a8b4c9eb8709745200974aa5ce9eb3c0b715cae81699b3bb788","iv":"28f4806fec2cbcc1ca831a49dbaf05f2","mac":"d3601afad4f69578e9647b8d93792abb498ae954cd5ce44e8bd156d4efbbb4b0"}]},"nonce":1,"tssKeyTypes":{"default":"ed25519"},"tssNonces":{"default":0},"tssPolyCommits":{"default":[{"x":"2f963f06cb9d1e03b3f1654a4bed5b0674a31e64b3b488a63647fc380444b974","y":"703cd9ad9cf68ce8f50b1211a7e47ea1713077727ced731a2d833ae97f75f55c"},{"x":"25b411f18db3111060c9d4c26d1af7a0457812b597f855878ccbf4f3e3f65b79","y":"4448c04126cfee3feb6960781382d2896f9909bb3fac7aac6d9a7c6104059467"}]},"factorPubs":{"default":[{"x":"a24ec82353cff644b308c76f60703683d3a63fe845ba44e98b674aa865304fa","y":"dee6d45e36a85535a1e59c7c4820127f7dbd30900ca8b4052157e309ff72ab1e"}]},"factorEncs":{"default":{"0a24ec82353cff644b308c76f60703683d3a63fe845ba44e98b674aa865304fa":{"tssIndex":2,"type":"direct","userEnc":{"ciphertext":"ec6234ec911339b26c68bb20ee5fb092fe834340472aa37746bc5c3884267188d2cfdddca886a88b52624ba03c43578d","ephemPublicKey":"04f3bc24184daf6ffb0a399952d547af94d51ef3748cae212e1d6d7dbd4c7191fc7e234cbf78bfef90fbf8944e921967916cd936359802369ea4243bfeefdfbcc3","iv":"5c48e8efb94490970619c3497f41282e","mac":"472df1907d85a0493bd74a84fab33de4c9a3383318098638fd0a0f334527eed5"},"serverEncs":[]}}}}'; +const multiCurveJson = + '{"pubKey":"022dd7f0a0b54953304e94e861373ab9fe9c36930f9bc4804efa06b1d472f62ade","polyIDList":["022dd7f0a0b54953304e94e861373ab9fe9c36930f9bc4804efa06b1d472f62ade|0341267f01008fc085e2c2bb3f508422e1410e641a5f297fa14bd57d32d7e97a87|0x0|1|731d22b16ae27306e045e6121dc9c43754dfbdb6e28a59879e054bb0772673d","022dd7f0a0b54953304e94e861373ab9fe9c36930f9bc4804efa06b1d472f62ade|034f25e3aeea954b8ddf033d00a8657c227c3e243177795c303476ffa2fd104c9b|0x0|1|731d22b16ae27306e045e6121dc9c43754dfbdb6e28a59879e054bb0772673d|969cf4076b10ba9276e44964915cc97c2984ff29cb1d2de8f2c10fb9dfa35279"],"scopedStore":{},"generalStore":{"ed25519Seed":{"message":{"ciphertext":"afde6d2044edce2f9e106bfe7b03bc716b1f4f9d765c70aa04d10089b07141e5ab2eff80a2c74798e6e44129403f4fc4","ephemPublicKey":"041a719598c138c0b5d2f1cc12b4348bed2c5c253360bb1f855f1eb7df53746047892f69655727f672f3a81bcdcc8e5816cc1f2b2b822e4e79a88cac83c3a30dfc","iv":"c846c938f752825764f0bd0fbbceaf50","mac":"013adaa31e31ab19300e1a5979a2728a53e324689b92677cbd55f0cb938ac138"},"publicKey":"047ecfc14f54a87dc25a4c839d71d9e80046f71141e42b339faa60c2f28601fa1870f68c19c5004f52ddf2f3d17f779ad1ece0d13d981afd294645d4f29a2d20c5"},"ed25519Seed/default":{"message":{"ciphertext":"9a2667945f75aa35c25358439d8f8f578fac22e99d31342d11eef32879a66c286cfae76218d16bdd3b881260f40353ff","ephemPublicKey":"046a6fccfabb06d73580143981d61e2a8d9fc4a02da99421dddef57248cd64976606a79d19cdafc42129ab39b3c80a64ccde7bd805a36cea519b57da43c6d0f195","iv":"179e3c25c68cbd5d4be06c32b7d12a6a","mac":"f4e6e500fd60c275544857ed4750487267394bf7002eed13aa4816ebbcd457b2"}}},"tkeyStore":{"tssModule":[{"ciphertext":"57ed48f042da6bac2774b48a6815476f75d09d16043bb800c92b3b04ca6ccc28fd79adfcfa11f96a7a27e28645376428f9b02c8bc0e454d190ec3880e9a9387d7dcc7300d4adef4080a847e648170b551473297b200c6781a2ea265b8db75ca3","ephemPublicKey":"04e440f6d8990c148c750bb21dbdec7dabf4f6ffe1c5c30b5640e42b2b81b02ce40073dd0ca43cee74a95fb90a25588ba05ca65da83e9377829c563b10fa000e5e","iv":"701b9f3f8f790588e47af25eff5db67e","mac":"453977ff17be129f2f17bee349e7b1bf74ae8da436b79e70f236ba470e4d5d40"}]},"nonce":5,"version":"1.0.0","tss":{"default":{"secp256k1":{"tssTag":"default","tssKeyType":"secp256k1","tssNonce":0,"tssPolyCommits":[{"x":"75e2914f29baefcadb5aea5ada6aa8ca0f2edef47f090bd55ec87fd1c68ef6a","y":"3be3fd7188f951bab6af03445c16642e70bfe720995f8d7d406f198ea451339a"},{"x":"860dcf8b742ebd9c35a43adc0a54ad5ca122f1df20f0c8cf65ae01637d988f9c","y":"96b5669497209e94b3f75d658a17a19bba66a6ea5337b77dec0920c9279999f0"}],"factorPubs":[{"x":"62bdf332bd2c64e38ea6085ddb16133a293f59d0f2efc7e89388e250b232da74","y":"ea06f717886022b9592b5504c0fbd1272080a39d57ca869bac34b05abeba9090"}],"factorEncs":{"62bdf332bd2c64e38ea6085ddb16133a293f59d0f2efc7e89388e250b232da74":{"serverEncs":[],"tssIndex":3,"type":"direct","userEnc":{"ciphertext":"60f413ae1985ff6abd10995bc9d75707d76f1bcde659b60f9f78f37549d346156209d74ec672403d01911b246db05769","ephemPublicKey":"04c7ee61df6af27917edacd194be2429575a173eb77fdde5e351a3a7ad30cacdfbb889242e689f5eb6bdd11deacadbc3c592c9881eac8551b7c792bb993a1b0321","iv":"305724b2455de8e516cd90a01c5e7adb","mac":"719ac67e41fa159e9736e3d1cb4b858e9c38c7faff3b9c2fe63ca99d82ac2452"}}}},"ed25519":{"tssTag":"default","tssKeyType":"ed25519","tssNonce":0,"tssPolyCommits":[{"x":"202c3557a7cad9102c0cfbcaab765277018f3e680c9c3eb35c0cb69fa07788b8","y":"45f1cb0bffba15dd562811694864a228ea1908eb1c3ca9cd90899c1a92ca85e3"},{"x":"7e271298f3c9c9386dc611e9c57a0031fdd17aeea17423073961f815b95fa833","y":"dbe5d48e51e01df6b236315568b60dc3e513b72cb07b9613f7aaed2c1bff5fb"}],"factorPubs":[{"x":"62bdf332bd2c64e38ea6085ddb16133a293f59d0f2efc7e89388e250b232da74","y":"ea06f717886022b9592b5504c0fbd1272080a39d57ca869bac34b05abeba9090"}],"factorEncs":{"62bdf332bd2c64e38ea6085ddb16133a293f59d0f2efc7e89388e250b232da74":{"type":"hierarchical","tssIndex":3,"userEnc":{"ciphertext":"d57f7f23f71348ea8a7479e10e085d074119c12bab5df45283e69ba3ef2a7e3dd9ecb7c962eceea265b302509ed67bad","ephemPublicKey":"048627e20c599e4fe3c5dd4fc3af7b45111f4a991f8e6ffb5c92da33f4b2fbfbacfc064b7abf27fb602b4fff8af0e01ed1ac1beaaaed373f954dde914237a27cca","iv":"6f0c006ea1ceea25167304a4d2a9c80e","mac":"31932b2647b821fc833ae625b54c8085dab9791d534457c92e220e1e00fbb73c"},"serverEncs":[{"ciphertext":"ebc855e5c04ca52f3a03a704f66fdaa6aa503760a267a8c0311554f89954096ea5bb16016037bcc9f86157ef8fd115f4","ephemPublicKey":"045fc8623ed91c3b11013d2a43da4ea1c82a46ad25158941c711397d2bfac8224a37287a93c882e55cab5ab14f45dcd82d0ee8f20ee41ffed11120820fabbd4863","iv":"30bc8a9549fdb55682e2a8dec4411884","mac":"f375aff236cb31763fb61e8bd242069a6e193b1e036b811daf2d4a354c021df7"},{"ciphertext":"eae57b0956202e6ccbd53bd5c6df568fc8b6b33242fad894333f27461e28021ba01599757109a9879526ff15a465e7d2","ephemPublicKey":"049f78eb19d31f29905596a2371ebdcc98b1beee9ab15f700974e994e254884b5df30c6bd14b3cba9f8fdcd5e36846b7fc52cf89012ebb8c9fbdee2cdd164db7c8","iv":"4d1372010b4fd0312b0bb1c089d5e2b4","mac":"70f1f77be28f2be39191e0f3af5be330776563428c469a8072af2704173d42df"},{"ciphertext":"bba1839361e43a86545d37bfcb76290b20981822cc83e6701468b89fa5c4a4dae3fb2f9130972749aaf0f1c378800347","ephemPublicKey":"04256e36cd09417399f2cff9ea02769bb102710ad7ac8704aa30b18c062256eab7f6337eb2269525fd3e96cc606e1288fe02d3b0e2ae99d4c1f556b93d6f9bfc83","iv":"2491273bae809df6df55df024726ebfd","mac":"571d7ff4e44433c67b9aff92457e241cdad281034075dfffcd48250fb4a78d78"},{"ciphertext":"e3dad5f1b33dc9ddadb2255e2864ad07573d2e0f38e2d2462380329e04cdf7cfeca1059754493be96b2c51a3464210dd","ephemPublicKey":"040ce0a9bf20b79ebca6f1de32021d575d86a4526e1df1ab67e7552e37969bceefba9983cc3c29cb4e41916359e7416ec4c0ebd385203a2f6ae77ae6aa5405ba73","iv":"8d1f802b158a8cdebc8c4157b099a0d3","mac":"a00163b3e7ccd900dd12b7c28e3e999ae018324155dac298eb3038e656982876"},{"ciphertext":"3945f770ce1057e31e64951815922be8577cbc3135b3b9f2488090b86898a579a466a2f792680f0329e9f75ef252cb41","ephemPublicKey":"041f4517c1c8932d13d564f04d0ab735a57f79ca415aeac4b7c1d6c08e4d9cac7111df7dc39df122d80b6e5be9b734bc2c89a3598a8b7138368300ebc4604c29a2","iv":"42ee1817d55cd8e3c8c76b1d82df5d81","mac":"3ad2fe6595bbf40235ae62d53debee60428bf25da6b3e8fabd14238bcb5bfebb"}]}}}},"imported":{"secp256k1":{"tssTag":"imported","tssKeyType":"secp256k1","tssNonce":0,"tssPolyCommits":[{"x":"8a41efc52c76684c21f7898a9035a5f64d14e184a70d55bf28adfefc8fc75b3","y":"8422d404ebe92f25ac0725a32e39c1c6e4a2108e535f3615a7d7348e9e843e8e"},{"x":"14c77d1851981790e379c5545c7712b5f9fc9d566e1d9cdb20bc2a0ebe5910b6","y":"b08f3294a70f3e966c3ab194314c31dcbe9a5ea02e0bb24ab4b47487f8b085db"}],"factorPubs":[{"x":"62bdf332bd2c64e38ea6085ddb16133a293f59d0f2efc7e89388e250b232da74","y":"ea06f717886022b9592b5504c0fbd1272080a39d57ca869bac34b05abeba9090"}],"factorEncs":{"62bdf332bd2c64e38ea6085ddb16133a293f59d0f2efc7e89388e250b232da74":{"type":"hierarchical","tssIndex":2,"userEnc":{"ciphertext":"9eb4d12aa6f30aa297ffcf17826826251238458e1712b75af80db4265e5a7fe4b9f5bc4e79f73255ba92bab108a98a4d","ephemPublicKey":"043408f0f43963c2fb5c6da759113a9f12616eb435cb181e565a9a66cf898589b3fec9b597abe0fffd4d8f9b10def94795dc40b43e9ef3dfa7c3761be63326a3fc","iv":"359b79f10ca00470d13756f96463eeb8","mac":"1fc2f9842b9e86117b2d53f95f8d69433308589620628aa66c50d210181ee950"},"serverEncs":[{"ciphertext":"80dc41ca3cb42187ace1fa2553daf456ea80c845158af21dce9c2722ad781f942551b8854eb0cdb686980f0cfe6ccd64","ephemPublicKey":"042179cb3af6d8b625cf410026f8c9b74c3dd1d6e2c25db7060507e58ae46a30c8f8adb3d4f7c47bcc3d031ed795c76736eb8879f02b5f9862b06de2c0c83ad74f","iv":"4ee3ba08a424a212a53faff89f40a35e","mac":"02cfff72863b1ef3b217a7d868ed812ac2fc948abdaec7f8c77a7383afcef413"},{"ciphertext":"cb51445f28f518c371637d182954dfb2dfffd1e3a58b9c5089557db269cea8983359f8b4012c9965d2c6368598044256","ephemPublicKey":"04abec93a4d9405512d6b0fe3c9f6283744a9175c2b5e82a90bba0fedecbb22583dd526cf1858b07690f53ca7e621a81a3217635a9739e79aba0fba6e332eeec4f","iv":"e89e8bb153becbc3f2c304be0d8b02f8","mac":"770c3274a8d968897f518f8a9a019e47500e91c35461ab44735de72adef3494f"},{"ciphertext":"3b98c46a54419fa2a6419cef43f505c9fccdb928fcb2f70179d751c3e155124dcf8ee4ad6b536367bc635856c52e8f66","ephemPublicKey":"048c05e675a6465044b9c5b8acb32226707af9e04f972703a18ec75920960bdca813dc0a1a4632afd74ccdebcdd9a1d8ea6f1b3b599d8f2a841301fd19c4ee0ffe","iv":"b0cfb6ec0ac4f150590b02ec2442e19d","mac":"adc65ed18f982ce229ed3ba404a357c9f4e6fb89d40b926e6b791cbcd205304d"},{"ciphertext":"1050f582da2d571016552385cbd8d5db9b7b2b024dfd406ebcf8b422b5c9b32ab5b4af3c921e8ed0bb6244f56a39de9b","ephemPublicKey":"046a44e351f2b9fb45b114457d7afd505837065d0c3d76d6af0cc831e7ee46c93bac9849ad56f8f131ec1f934836dfba23367f590e1aa2bf312195f4d1aed1ba4a","iv":"149473b0ca24ede8663e5e0a404a8832","mac":"9859c6e77a1cb2951b955449448a81f3c7f8b14b03b981b461fdf2c2857b1527"},{"ciphertext":"4a26f1e6dafeaa2a03189f0f0dbae8aa3cf3bfc6b5da88ad145ca4b3a2d7284d0ed086d1e2910b3f6cdd23dd64987e46","ephemPublicKey":"04694c715117852d1dc1d90504b1ca1b920cbb9c107ef9ee9929440e2e21b4d4fba14bac27524a43bc33cfb9b3d2f3c93973ccb20c322fa933e9e9631c3c904122","iv":"ed8adfd3806b4e57e9e384a4a308dbca","mac":"d053605e9c44083a128897d5deb600764f8ca104c57a4597bd1c143ccdbb963b"}]}}}}}}'; + describe("Metadata new Formatting tests", function () { it("#should serialize and deserialize into JSON seamlessly", async function () { const instance1 = Metadata.fromJSON(JSON.parse(legacyJSONSecp256k1)); @@ -53,4 +56,9 @@ describe("Metadata new Formatting tests", function () { }); // to add: add tests for different and multple tags and keytypes\ + it("#should able to deserialize new JSON format with multicurve", async function () { + const instance = Metadata.fromJSON(JSON.parse(multiCurveJson)); + const instanceSerialized = instance.toJSON(); + equal(JSON.stringify(instanceSerialized), multiCurveJson); + }); }); diff --git a/packages/tss/.mocharc.json b/packages/tss/.mocharc.json index 62e530da..ba75f7a0 100644 --- a/packages/tss/.mocharc.json +++ b/packages/tss/.mocharc.json @@ -1,6 +1,6 @@ { "require": ["tsx"], - "spec": ["./test/tss.ts"], + "spec": ["./test/*.ts"], "extension": ["js", "ts"], "timeout": 0, "exit": true diff --git a/packages/tss/src/tss.ts b/packages/tss/src/tss.ts index c92f6029..13b8a7d1 100644 --- a/packages/tss/src/tss.ts +++ b/packages/tss/src/tss.ts @@ -1,20 +1,21 @@ import { decrypt, - EllipticCurve, EllipticPoint, encrypt, EncryptedMessage, + KeyDetails, KeyType, Point, ReconstructedKeyResult, secp256k1, StringifiedType, TKeyArgs, + TKeyInitArgs, } from "@tkey/common-types"; import { CoreError, TKey } from "@tkey/core"; import { post } from "@toruslabs/http-helpers"; import { dotProduct, ecPoint, hexPoint, PointHex, randomSelection, RSSClient } from "@toruslabs/rss-client"; -import { getEd25519ExtendedPublicKey as getEd25519KeyPairFromSeed, getKeyCurve, getSecpKeyFromEd25519 } from "@toruslabs/torus.js"; +import { getEcCurve, getEd25519ExtendedPublicKey as getEd25519KeyPairFromSeed, getKeyCurve, getSecpKeyFromEd25519 } from "@toruslabs/torus.js"; import BN from "bn.js"; import { ec as EC } from "elliptic"; import { keccak256 } from "ethereum-cryptography/keccak"; @@ -49,8 +50,9 @@ export const LEGACY_KEY_TYPE = "secp256k1"; export interface TSSTKeyArgs extends TKeyArgs { serviceProvider: TSSTorusServiceProvider; - tssKeyType: KeyType; + // tssKeyType: KeyType; tssTag?: string; + legacyMetadataFlag?: boolean; } export interface TKeyTSSInitArgs { @@ -67,9 +69,9 @@ export interface TKeyTSSInitArgs { export class TKeyTSS extends TKey { serviceProvider: TSSTorusServiceProvider = null; - private _tssKeyType: KeyType; + // private _tssKeyType: KeyType; - private _tssCurve: EC; + // private _tssCurve: EC; private _tssTag: string; @@ -80,44 +82,48 @@ export class TKeyTSS extends TKey { */ constructor(args: TSSTKeyArgs) { super(args); - const { serviceProvider, storageLayer, tssTag = "default", tssKeyType } = args; + const { serviceProvider, storageLayer, tssTag = "default", legacyMetadataFlag } = args; - if (serviceProvider.customAuthArgs.keyType !== tssKeyType) { - throw CoreError.default(`service provider keyType mismatch: ${serviceProvider.customAuthArgs.keyType} !== ${tssKeyType}`); + if (serviceProvider.customAuthArgs.keyType === KeyType.ed25519 && !legacyMetadataFlag) { + throw CoreError.default( + `service provider keyType mismatch: ${serviceProvider.customAuthArgs.keyType} require legacyMetadataFlag to be set to true ");` + ); } this.serviceProvider = serviceProvider; this.storageLayer = storageLayer; this._tssTag = tssTag; - this._tssKeyType = tssKeyType; - this._tssCurve = new EC(tssKeyType); + // this._tssKeyType = tssKeyType; + // this._tssCurve = new EC(tssKeyType); + this.legacyMetadataFlag = legacyMetadataFlag; } public get tssTag(): string { return this._tssTag; } - public get tssKeyType(): KeyType { - return this._tssKeyType; - } + // public get tssKeyType(): KeyType { + // return this._tssKeyType; + // } - public get tssCurve(): EllipticCurve { - return this._tssCurve; - } + // public get tssCurve(): EllipticCurve { + // return this._tssCurve; + // } static async fromJSON(value: StringifiedType, args: TSSTKeyArgs): Promise { - const tbTss = new TKeyTSS(args); + // legacyMetadataFlag need to be provided during constructor as tkey's fromJson is depending on the flag + const tbTss = new TKeyTSS({ ...args, legacyMetadataFlag: value.legacyMetadataFlag ?? false }); const tb = await super.fromJSON(value, args); - const { tssTag, tssKeyType, accountSalt } = value; + const { tssTag, accountSalt } = value; if (tssTag !== tbTss._tssTag) { throw CoreError.default(`tssTag mismatch: ${tssTag} !== ${tbTss.tssTag}`); } - if (tssKeyType !== tbTss.tssKeyType) { - throw CoreError.default(`tssKeyType mismatch: ${tssKeyType} !== ${tbTss.tssKeyType}`); - } + // if (tssKeyType !== tbTss.tssKeyType) { + // throw CoreError.default(`tssKeyType mismatch: ${tssKeyType} !== ${tbTss.tssKeyType}`); + // } // copy over tkey to tkeyTss tbTss.shares = tb.shares; @@ -137,24 +143,38 @@ export class TKeyTSS extends TKey { toJSON(): StringifiedType { const tbJson = super.toJSON(); tbJson.tssTag = this._tssTag; - tbJson.tssKeyType = this.tssKeyType; + // tbJson.tssKeyType = this.tssKeyType; tbJson.accountSalt = this._accountSalt; + tbJson.legacyMetadataFlag = this.legacyMetadataFlag; return tbJson; } + async initialize(params?: TKeyInitArgs): Promise { + if (this.serviceProvider.customAuthArgs.keyType === KeyType.ed25519 && !this.legacyMetadataFlag) { + throw CoreError.default("legacyMetadataFlag need to be set for ed25519 network's postboxkey "); + } + return super.initialize(params); + } + /** * Initializes this instance. If a TSS account does not exist, creates one * under the given factor key. `skipTssInit` skips TSS account creation and * can be used with `importTssKey` to just import an existing account instead. */ async initializeTssSecp256k1(params: TKeyTSSInitArgs): Promise { + // only support service provider with secp256k1 key type + if (this.serviceProvider.customAuthArgs.keyType === KeyType.ed25519) { + throw CoreError.default("Multiple Curve do not support for ed25519 network's postboxkey "); + } + const secp256k1TssData = this.metadata.getTssData(KeyType.secp256k1, TSS_TAG_DEFAULT); const ed25519TssData = this.metadata.getTssData(KeyType.ed25519, TSS_TAG_DEFAULT); const secp256k1Exist = !!secp256k1TssData && Object.keys(secp256k1TssData).length > 0; const ed25519Exist = !!ed25519TssData && Object.keys(ed25519TssData).length > 0; - const { factorPub, deviceTSSShare, deviceTSSIndex, importKey, serverOpts } = params; + const { deviceTSSShare, deviceTSSIndex, importKey, serverOpts } = params; + let { factorPub } = params; if (secp256k1Exist) { throw CoreError.default("TSS account already exists for secp256k1 key type"); } @@ -164,6 +184,9 @@ export class TKeyTSS extends TKey { if (ed25519Exist) { if (factorPub !== undefined || deviceTSSShare !== undefined || deviceTSSIndex !== undefined) throw CoreError.default("factorPub and deviceTSSIndex are not allowed when existing tss account exists"); + factorPub = ed25519TssData.factorPubs[0]; + } else if (!factorPub) { + throw CoreError.default("factorPub is required"); } const backupMetadata = this.metadata.clone(); @@ -181,7 +204,8 @@ export class TKeyTSS extends TKey { this.metadata.updateTSSData({ tssKeyType: KeyType.secp256k1, tssTag: localTssTag, tssNonce: 0, tssPolyCommits, factorPubs, factorEncs }); - const accountSalt = generateSalt(this._tssCurve); + const ecCurve = getEcCurve(KeyType.secp256k1); + const accountSalt = generateSalt(ecCurve); await this._setTKeyStoreItem(TSS_MODULE, { id: "accountSalt", value: accountSalt, @@ -201,6 +225,7 @@ export class TKeyTSS extends TKey { factorPubs: ed25519TssData.factorPubs, tssIndexes, tssTag: localTssTag, + keyType: KeyType.secp256k1, }, serverOpts ); @@ -227,6 +252,7 @@ export class TKeyTSS extends TKey { params.serverOpts ); } + await this._syncShareMetadata(); } catch (err) { // Error happend, restore metadata this.metadata = backupMetadata; @@ -240,6 +266,13 @@ export class TKeyTSS extends TKey { * can be used with `importTssKey` to just import an existing account instead. */ async initializeTssEd25519(params: TKeyTSSInitArgs & { importKey: Buffer }): Promise { + // only support service provider with secp256k1 key type + if (this.serviceProvider.customAuthArgs.keyType === KeyType.ed25519) { + if (this.metadata.getTssData(KeyType.ed25519, TSS_TAG_DEFAULT)) { + throw CoreError.default("Multiple Curve do not support for postboxKey Ed"); + } + } + const { importKey } = params; if (!importKey) { throw CoreError.default("importKey is required"); @@ -610,7 +643,7 @@ export class TKeyTSS extends TKey { factorEncs, }); if (!this._accountSalt) { - const accountSalt = generateSalt(this._tssCurve); + const accountSalt = generateSalt(getEcCurve(tssKeyType)); await this._setTKeyStoreItem(TSS_MODULE, { id: "accountSalt", value: accountSalt, @@ -666,7 +699,7 @@ export class TKeyTSS extends TKey { const { tssShare: tempShare, tssIndex: tempIndex } = await this.getTSSShare(tempFactorKey, { keyType, tssTag }); // reconstruct final key using sss - const ec = this._tssCurve; + const ec = getEcCurve(keyType); const tssKey = lagrangeInterpolation(ec, [tempShare, factorShare], [new BN(tempIndex), new BN(factorIndex)]); // delete created tss share @@ -680,7 +713,7 @@ export class TKeyTSS extends TKey { // Derive key for account index. const nonce = this.computeAccountNonce(accountIndex); - const derivedKey = tssKey.add(nonce).umod(this._tssCurve.n); + const derivedKey = tssKey.add(nonce).umod(ec.n); return derivedKey; } @@ -728,7 +761,7 @@ export class TKeyTSS extends TKey { ); const localTssTag = TSS_TAG_DEFAULT; const verifierNameVerifierId = this.serviceProvider.getVerifierNameVerifierId(); - const tssData = this.metadata.getTssData(this.tssKeyType, localTssTag); + const tssData = this.metadata.getTssData(keyType, localTssTag); if (!tssData) throw CoreError.default("no tss data"); const tssCommits = tssData.tssPolyCommits; @@ -757,7 +790,7 @@ export class TKeyTSS extends TKey { serverThreshold, authSignatures: remoteClient.signatures, }, - curve: this.tssKeyType, + curve: keyType, }; const result = ( @@ -830,7 +863,7 @@ export class TKeyTSS extends TKey { factorEnc, tssCommits, factorPub: newFactorPub.toPointHex(), - curve: this.tssKeyType, + curve: keyType, }; const result = ( @@ -966,13 +999,13 @@ export class TKeyTSS extends TKey { } async _refreshTSSSharesWithFactorPubs( - params: { updateMetadata: boolean; tssShare: BN; tssIndex: number; factorPubs: Point[]; tssIndexes: number[]; tssTag: string }, + params: { updateMetadata: boolean; tssShare: BN; tssIndex: number; factorPubs: Point[]; tssIndexes: number[]; tssTag: string; keyType: KeyType }, serverOpts: { selectedServers?: number[]; authSignatures: string[]; } ) { - const { factorPubs, tssIndexes, tssShare, tssIndex, tssTag } = params; + const { factorPubs, tssIndexes, tssShare, tssIndex, tssTag, keyType } = params; const { selectedServers, authSignatures } = serverOpts; const verifierId = this.serviceProvider.getVerifierNameVerifierId(); @@ -986,7 +1019,7 @@ export class TKeyTSS extends TKey { // sync metadata by default // create a localMetadataTransition if manual sync - const updateMetadata = params.updateMetadata !== undefined ? params.updateMetadata : true; + const updateMetadata = params.updateMetadata === undefined ? true : params.updateMetadata; await this._refreshTSSShares( updateMetadata, @@ -999,7 +1032,7 @@ export class TKeyTSS extends TKey { ...rssNodeDetails, selectedServers: finalServer, authSignatures, - keyType: this.tssKeyType, + keyType, }, tssTag ?? TSS_TAG_DEFAULT ); @@ -1158,7 +1191,7 @@ export class TKeyTSS extends TKey { ...rssNodeDetails, selectedServers: finalServer, authSignatures, - keyType: this.tssKeyType, + keyType, }, localTssTag ); diff --git a/packages/tss/test/helpers.ts b/packages/tss/test/helpers.ts index 90f83917..0a3bfd93 100644 --- a/packages/tss/test/helpers.ts +++ b/packages/tss/test/helpers.ts @@ -72,6 +72,9 @@ export async function assignTssDkgKeys(opts: { }) { const { serviceProvider, verifierName: verifier, verifierId } = opts; + serviceProvider.verifierName = verifier; + serviceProvider.verifierId = verifierId; + let { tssTag, maxTSSNonceToSimulate } = opts; tssTag = tssTag || "default"; maxTSSNonceToSimulate = maxTSSNonceToSimulate || 1; diff --git a/packages/tss/test/tssMultiCurveTestCases.ts b/packages/tss/test/tssMultiCurveTestCases.ts new file mode 100644 index 00000000..4abe3a9e --- /dev/null +++ b/packages/tss/test/tssMultiCurveTestCases.ts @@ -0,0 +1,903 @@ +import { EllipticPoint, KeyType, Point } from "@tkey/common-types"; +import { getEcCurve } from "@toruslabs/torus.js"; +import assert, { equal, fail, rejects } from "assert"; +import BN from "bn.js"; +import { ec as EC } from "elliptic"; + +import { TKeyTSS as ThresholdKey, TKeyTSS, TSSTorusServiceProvider } from "../src"; +import { factorKeyCurve, TKeyTSSInitArgs, TSS_TAG_DEFAULT } from "../src/tss"; +import { getLagrangeCoeffs } from "../src/util"; +import { assignTssDkgKeys, fetchPostboxKeyAndSigs, generateKey, initStorageLayer } from "./helpers"; + +const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolean; spSecp256k1: boolean }) => { + const { TSS_KEY_TYPE, legacyFlag, spSecp256k1 } = params; + const ecFactor = factorKeyCurve; + const ecTSS = new EC(TSS_KEY_TYPE); + + const torusSPSecp256k1 = new TSSTorusServiceProvider({ + customAuthArgs: { + network: "sapphire_devnet", + web3AuthClientId: "YOUR_CLIENT_ID", + baseUrl: "http://localhost:3000", + // keyType: TSS_KEY_TYPE, + keyType: KeyType.secp256k1, + }, + }); + + const torusSPKeyType = new TSSTorusServiceProvider({ + customAuthArgs: { + network: "sapphire_devnet", + web3AuthClientId: "YOUR_CLIENT_ID", + baseUrl: "http://localhost:3000", + keyType: TSS_KEY_TYPE, + }, + }); + + const torusSP = spSecp256k1 ? torusSPSecp256k1 : torusSPKeyType; + + const torusSL = initStorageLayer(); + + const manualSync = true; + + const initializeTssFailedScenario = async ( + tb: TKeyTSS, + initParams: TKeyTSSInitArgs & { + tssKeyType: KeyType; + } + ) => { + const { factorPub, importKey, deviceTSSShare, deviceTSSIndex, serverOpts, tssKeyType } = initParams; + try { + await tb.initializeTss({ + importKey, + factorPub, + deviceTSSShare, + deviceTSSIndex, + serverOpts, + tssKeyType, + }); + fail("should not able to initialize tss"); + } catch (e) {} + try { + await tb.initializeTss({ + importKey, + deviceTSSShare, + serverOpts, + tssKeyType, + }); + fail("should not able to initialize tss"); + } catch (e) {} + try { + await tb.initializeTss({ + importKey, + deviceTSSIndex, + serverOpts, + tssKeyType, + }); + fail("should not able to initialize tss"); + } catch (e) {} + try { + await tb.initializeTss({ + importKey, + factorPub, + serverOpts, + tssKeyType, + }); + fail("should not able to initialize tss"); + } catch (e) {} + }; + + describe(`TSS MultiCurve tests, keyType=${TSS_KEY_TYPE}, legacyMetadata=${legacyFlag}, spSecp256k1=${spSecp256k1}`, function () { + it("#should be able to reconstruct tss share from factor key", async function () { + const sp = torusSP; + + const deviceTSSShare = ecTSS.genKeyPair().getPrivate(); + const deviceTSSIndex = 3; + + sp.verifierName = "torus-test-health"; + sp.verifierId = "testTss@example.com"; + const { postboxkey, signatures } = await fetchPostboxKeyAndSigs({ + serviceProvider: sp, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + }); + sp.postboxKey = postboxkey; + + const storageLayer2 = initStorageLayer(); + const tb1 = new ThresholdKey({ + serviceProvider: sp, + storageLayer: storageLayer2, + manualSync, + legacyMetadataFlag: legacyFlag, + }); + + // factor key needs to passed from outside of tKey + const factorKeyPair = ecFactor.genKeyPair(); + const factorKey = factorKeyPair.getPrivate(); + const factorPub = Point.fromElliptic(factorKeyPair.getPublic()); + + await tb1.initialize(); + await tb1.initializeTss({ + factorPub, + deviceTSSShare, + deviceTSSIndex, + serverOpts: { + authSignatures: signatures, + }, + tssKeyType: TSS_KEY_TYPE, + }); + + await initializeTssFailedScenario(tb1, { + factorPub, + deviceTSSShare, + deviceTSSIndex, + serverOpts: { + authSignatures: signatures, + }, + tssKeyType: TSS_KEY_TYPE === KeyType.secp256k1 ? KeyType.ed25519 : KeyType.secp256k1, + }); + + await tb1.initializeTss({ + serverOpts: { + authSignatures: signatures, + }, + tssKeyType: TSS_KEY_TYPE === KeyType.secp256k1 ? KeyType.ed25519 : KeyType.secp256k1, + }); + + const reconstructedKey = await tb1.reconstructKey(); + await tb1.syncLocalMetadataTransitions(); + if (tb1.secp256k1Key.cmp(reconstructedKey.secp256k1Key) !== 0) { + fail("key should be able to be reconstructed"); + } + const { tssShare: tss2 } = await tb1.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); + + // compute public key of the tss share + const tss2Pub = ecTSS.g.mul(tss2); + + // get tss pub key from tss commits + const tssCommits = tb1.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); + const tssCommitA0 = tssCommits[0].toEllipticPoint(ecTSS); + const tssCommitA1 = tssCommits[1].toEllipticPoint(ecTSS); + + // compute public key of the tss share using tss commits and tss index + const _tss2Pub = tssCommitA0.add(tssCommitA1.mul(new BN(deviceTSSIndex))); + equal(tss2Pub.eq(_tss2Pub), true); + }); + + it("#should be able to reconstruct tss key from factor key", async function () { + const sp = torusSP; + + sp.verifierName = "torus-test-health"; + sp.verifierId = "test181@example.com"; + + const { serverDKGPrivKeys } = await assignTssDkgKeys({ + serviceProvider: torusSPKeyType, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + maxTSSNonceToSimulate: 2, + }); + + const tss1 = new BN(serverDKGPrivKeys[0], "hex"); + const deviceTSSShare = ecTSS.genKeyPair().getPrivate(); + const deviceTSSIndex = 2; + const { postboxkey, signatures } = await fetchPostboxKeyAndSigs({ + serviceProvider: sp, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + }); + sp.postboxKey = postboxkey; + const storageLayer = initStorageLayer(); + const tb1 = new ThresholdKey({ serviceProvider: sp, storageLayer, manualSync, legacyMetadataFlag: legacyFlag }); + + // factor key needs to passed from outside of tKey + const factorKeyPair = ecFactor.genKeyPair(); + const factorKey = factorKeyPair.getPrivate(); + const factorPub = Point.fromElliptic(factorKeyPair.getPublic()); + + await tb1.initialize(); + await tb1.initializeTss({ + factorPub, + deviceTSSShare, + deviceTSSIndex, + serverOpts: { + authSignatures: signatures, + }, + tssKeyType: TSS_KEY_TYPE, + }); + + await initializeTssFailedScenario(tb1, { + factorPub, + deviceTSSShare, + deviceTSSIndex, + serverOpts: { + authSignatures: signatures, + }, + tssKeyType: TSS_KEY_TYPE === KeyType.secp256k1 ? KeyType.ed25519 : KeyType.secp256k1, + }); + + await tb1.initializeTss({ + serverOpts: { + authSignatures: signatures, + }, + tssKeyType: TSS_KEY_TYPE === KeyType.secp256k1 ? KeyType.ed25519 : KeyType.secp256k1, + }); + + const reconstructedKey = await tb1.reconstructKey(); + await tb1.syncLocalMetadataTransitions(); + if (tb1.secp256k1Key.cmp(reconstructedKey.secp256k1Key) !== 0) { + fail("key should be able to be reconstructed"); + } + + const { tssShare: tss2 } = await tb1.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); + const tssCommits = tb1.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); + + const tssPrivKey = getLagrangeCoeffs(ecTSS, [1, deviceTSSIndex], 1) + .mul(tss1) + .add(getLagrangeCoeffs(ecTSS, [1, deviceTSSIndex], deviceTSSIndex).mul(tss2)) + .umod(ecTSS.n); + + const tssPubKey = (ecTSS.g as EllipticPoint).mul(tssPrivKey); + const tssCommits0 = tssCommits[0].toEllipticPoint(ecTSS); + const tssPub = tb1.getTSSPub(TSS_KEY_TYPE, TSS_TAG_DEFAULT).toEllipticPoint(ecTSS); + equal(tssPubKey.eq(tssCommits0), true); + equal(tssPub.eq(tssPubKey), true); + + // With account index. + if (TSS_KEY_TYPE !== KeyType.ed25519) { + const accountIndex = Math.floor(Math.random() * 99) + 1; + const tss1Account = (() => { + const share = new BN(serverDKGPrivKeys[0], "hex"); + const nonce = tb1.computeAccountNonce(accountIndex); + return share.add(nonce).umod(ecTSS.n); + })(); + const { tssShare: tss2Account } = await tb1.getTSSShare(factorKey, { accountIndex, keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); + + const coefficient1 = getLagrangeCoeffs(ecTSS, [1, deviceTSSIndex], 1); + const coefficient2 = getLagrangeCoeffs(ecTSS, [1, deviceTSSIndex], deviceTSSIndex); + const tssKey = coefficient1.mul(tss1Account).add(coefficient2.mul(tss2Account)).umod(ecTSS.n); + + const tssKeyPub = (ecTSS.g as EllipticPoint).mul(tssKey); + const tssPubAccount = tb1.getTSSPub(TSS_KEY_TYPE, TSS_TAG_DEFAULT, accountIndex).toEllipticPoint(ecTSS); + equal(tssPubAccount.eq(tssKeyPub), true, "should equal account pub key"); + } + }); + + // should always start with default account + it.skip(`#should be able to import a tss key for new account, manualSync=${manualSync}`, async function () { + const sp = torusSP; + sp.verifierName = "torus-test-health"; + sp.verifierId = `importeduserfresh${TSS_KEY_TYPE}@example.com`; + const { signatures, postboxkey } = await fetchPostboxKeyAndSigs({ + serviceProvider: sp, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + }); + sp.postboxKey = postboxkey; + + const tb = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, legacyMetadataFlag: legacyFlag }); + + const factorKeyPair = ecFactor.genKeyPair(); + const factorKey = factorKeyPair.getPrivate(); + const factorPub = Point.fromElliptic(factorKeyPair.getPublic()); + + const importTssKey = generateKey(TSS_KEY_TYPE); + const newTSSIndex = 2; + + await tb.initialize(); + await tb.initializeTss({ + factorPub, + importKey: importTssKey.raw, + deviceTSSIndex: newTSSIndex, + serverOpts: { + authSignatures: signatures, + }, + tssKeyType: TSS_KEY_TYPE, + }); + await tb.reconstructKey(); + + await tb.importTssKey( + { tssTag: "imported", importKey: importTssKey.raw, factorPubs: [factorPub], newTSSIndexes: [newTSSIndex], tssKeyType: TSS_KEY_TYPE }, + { + authSignatures: signatures, + } + ); + + await tb.syncLocalMetadataTransitions(); + + // Check pub key. + const ec = getEcCurve(TSS_KEY_TYPE); + const importTssKeyPub = Point.fromScalar(importTssKey.scalar, ec); + const tssPub = await tb.getTSSPub(TSS_KEY_TYPE, "imported"); + assert(tssPub.equals(importTssKeyPub)); + + // Check exported key. + const exportedKey = await tb._UNSAFE_exportTssKey({ + factorKey, + authSignatures: signatures, + keyType: TSS_KEY_TYPE, + tssTag: TSS_TAG_DEFAULT, + }); + assert(exportedKey.eq(importTssKey.scalar)); + if (TSS_KEY_TYPE === KeyType.ed25519) { + const seed = await tb._UNSAFE_exportTssEd25519Seed({ + factorKey, + authSignatures: signatures, + tssTag: TSS_TAG_DEFAULT, + }); + assert(seed.equals(importTssKey.raw)); + } else { + // If not ed25519, then also check exporting with account index. + const exportedKeyIndex2 = await tb._UNSAFE_exportTssKey({ + factorKey, + authSignatures: signatures, + accountIndex: 2, + keyType: TSS_KEY_TYPE, + tssTag: TSS_TAG_DEFAULT, + }); + const exportedPubKeyIndex2 = Point.fromScalar(exportedKeyIndex2, ec); + const pubKeyIndex2 = tb.getTSSPub(TSS_KEY_TYPE, TSS_TAG_DEFAULT, 2); + assert(exportedPubKeyIndex2.equals(pubKeyIndex2)); + } + }); + + it(`#should be able to import a tss key for existing account, manualSync=${manualSync}`, async function () { + const sp = torusSP; + + const deviceTSSShare = ecTSS.genKeyPair().getPrivate(); + const deviceTSSIndex = 2; + const newTSSIndex = 3; + + sp.verifierName = "torus-test-health"; + sp.verifierId = `importeduser${TSS_KEY_TYPE}@example.com`; + const { signatures, postboxkey } = await fetchPostboxKeyAndSigs({ + serviceProvider: sp, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + }); + sp.postboxKey = postboxkey; + const { serverDKGPrivKeys } = await assignTssDkgKeys({ + serviceProvider: torusSPKeyType, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + maxTSSNonceToSimulate: 1, + }); + + const tb = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, legacyMetadataFlag: legacyFlag }); + + // factor key needs to passed from outside of tKey + const factorKeyPair = ecFactor.genKeyPair(); + const factorKey = factorKeyPair.getPrivate(); + const factorPub = Point.fromElliptic(factorKeyPair.getPublic()); + // 2/2 + await tb.initialize(); + + // import key + const { raw: importedKey0 } = generateKey(TSS_KEY_TYPE); + + await tb.initializeTss({ + tssKeyType: TSS_KEY_TYPE, + factorPub, + deviceTSSShare, + deviceTSSIndex, + importKey: TSS_KEY_TYPE === KeyType.ed25519 ? importedKey0 : undefined, + serverOpts: { + authSignatures: signatures, + }, + }); + + await initializeTssFailedScenario(tb, { + tssKeyType: TSS_KEY_TYPE === KeyType.secp256k1 ? KeyType.ed25519 : KeyType.secp256k1, + factorPub, + deviceTSSShare, + deviceTSSIndex, + serverOpts: { + authSignatures: signatures, + }, + }); + + await tb.initializeTss({ + serverOpts: { + authSignatures: signatures, + }, + tssKeyType: TSS_KEY_TYPE === KeyType.secp256k1 ? KeyType.ed25519 : KeyType.secp256k1, + }); + + const newShare = await tb.generateNewShare(); + + const reconstructedKey = await tb.reconstructKey(); + await tb.syncLocalMetadataTransitions(); + + if (tb.secp256k1Key.cmp(reconstructedKey.secp256k1Key) !== 0) { + fail("key should be able to be reconstructed"); + } + const { tssShare: retrievedTSS, tssIndex: retrievedTSSIndex } = await tb.getTSSShare(factorKey, { + keyType: TSS_KEY_TYPE, + tssTag: TSS_TAG_DEFAULT, + }); + const tssCommits = tb.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); + const tssPrivKey = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], 1) + .mul(serverDKGPrivKeys[0]) + .add(getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], retrievedTSSIndex).mul(retrievedTSS)) + .umod(ecTSS.n); + const tssPubKey = (ecTSS.g as EllipticPoint).mul(tssPrivKey); + + const tssCommits0 = tssCommits[0].toEllipticPoint(ecTSS); + equal(tssPubKey.eq(tssCommits0), true); + + const { serverDKGPrivKeys: serverDKGPrivKeys1 } = await assignTssDkgKeys({ + tssTag: "imported", + serviceProvider: torusSPKeyType, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + maxTSSNonceToSimulate: 1, + }); + + // import key + const { raw: importedKey, scalar: importedScalar } = generateKey(TSS_KEY_TYPE); + await tb.importTssKey( + { tssTag: "imported", importKey: importedKey, factorPubs: [factorPub], newTSSIndexes: [newTSSIndex], tssKeyType: TSS_KEY_TYPE }, + { + authSignatures: signatures, + } + ); + + // tag is switched to imported + await tb.syncLocalMetadataTransitions(); + + // for imported key + const { tssShare: retrievedTSS1, tssIndex: retrievedTSSIndex1 } = await tb.getTSSShare(factorKey, { + keyType: TSS_KEY_TYPE, + tssTag: "imported", + }); + + const tssCommits1 = tb.getTSSCommits(TSS_KEY_TYPE, "imported"); + const tssPrivKey1 = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex1], 1) + .mul(serverDKGPrivKeys1[0]) + .add(getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex1], retrievedTSSIndex1).mul(retrievedTSS1)) + .umod(ecTSS.n); + const tssPubKey1 = (ecTSS.g as EllipticPoint).mul(tssPrivKey1); + + const tssCommits10 = tssCommits1[0].toEllipticPoint(ecTSS); + equal(tssPubKey1.eq(tssCommits10), true); + equal(tssPrivKey1.toString("hex"), importedScalar.toString("hex")); + + if (TSS_KEY_TYPE === KeyType.ed25519) { + const seed = await tb._UNSAFE_exportTssEd25519Seed({ + factorKey, + selectedServers: [1, 2, 3], + authSignatures: signatures, + tssTag: "imported", + }); + equal(seed.equals(importedKey), true); + } + + const tb2 = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, legacyMetadataFlag: legacyFlag }); + + await tb2.initialize(); + + tb2.inputShareStore(newShare.newShareStores[newShare.newShareIndex.toString("hex")]); + const reconstructedKey2 = await tb2.reconstructKey(); + await tb2.syncLocalMetadataTransitions(); + + if (tb2.secp256k1Key.cmp(reconstructedKey2.secp256k1Key) !== 0) { + fail("key should be able to be reconstructed"); + } + const tssCommits2 = tb2.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); + const tssCommits20 = tssCommits2[0].toEllipticPoint(ecTSS); + equal(tssPubKey.eq(tssCommits20), true); + + // switch to imported account + // tb2.tssTag = "imported"; + const { tssShare: retrievedTSSImported, tssIndex: retrievedTSSIndexImported } = await tb2.getTSSShare(factorKey, { + keyType: TSS_KEY_TYPE, + tssTag: "imported", + }); + + const tssCommitsImported = tb2.getTSSCommits(TSS_KEY_TYPE, "imported"); + + const tssPrivKeyImported = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndexImported], 1) + .mul(serverDKGPrivKeys1[0]) + .add(getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndexImported], retrievedTSSIndexImported).mul(retrievedTSSImported)) + .umod(ecTSS.n); + + const tssPubKeyImported = (ecTSS.g as EllipticPoint).mul(tssPrivKeyImported); + + const tssCommitsImported0 = tssCommitsImported[0].toEllipticPoint(ecTSS); + equal(tssPubKeyImported.eq(tssCommitsImported0), true); + equal(tssPrivKeyImported.toString("hex"), importedScalar.toString("hex")); + }); + + it(`#should be able to unsafe export final tss key, manualSync=${manualSync}`, async function () { + const sp = torusSP; + + const deviceTSSShare = ecTSS.genKeyPair().getPrivate(); + const deviceTSSIndex = 3; + + sp.verifierName = "torus-test-health"; + sp.verifierId = `exportUser${TSS_KEY_TYPE}@example.com`; + const { signatures, postboxkey } = await fetchPostboxKeyAndSigs({ + serviceProvider: sp, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + }); + sp.postboxKey = postboxkey; + const { serverDKGPrivKeys } = await assignTssDkgKeys({ + serviceProvider: torusSPKeyType, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + maxTSSNonceToSimulate: 1, + }); + + const tb = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, legacyMetadataFlag: legacyFlag }); + + // factor key needs to passed from outside of tKey + const factorKeyPair = ecFactor.genKeyPair(); + const factorKey = factorKeyPair.getPrivate(); + const factorPub = Point.fromElliptic(factorKeyPair.getPublic()); + + // 2/2 + await tb.initialize(); + await tb.initializeTss({ factorPub, deviceTSSShare, deviceTSSIndex, tssKeyType: TSS_KEY_TYPE, serverOpts: { authSignatures: signatures } }); + await initializeTssFailedScenario(tb, { + tssKeyType: TSS_KEY_TYPE === KeyType.secp256k1 ? KeyType.ed25519 : KeyType.secp256k1, + factorPub, + deviceTSSShare, + deviceTSSIndex, + serverOpts: { + authSignatures: signatures, + }, + }); + + // multicurve - + await tb.initializeTss({ + serverOpts: { + authSignatures: signatures, + }, + tssKeyType: TSS_KEY_TYPE === KeyType.secp256k1 ? KeyType.ed25519 : KeyType.secp256k1, + }); + + const newShare = await tb.generateNewShare(); + + const reconstructedKey = await tb.reconstructKey(); + await tb.syncLocalMetadataTransitions(); + + if (tb.secp256k1Key.cmp(reconstructedKey.secp256k1Key) !== 0) { + fail("key should be able to be reconstructed"); + } + const { tssShare: retrievedTSS, tssIndex: retrievedTSSIndex } = await tb.getTSSShare(factorKey, { + keyType: TSS_KEY_TYPE, + tssTag: TSS_TAG_DEFAULT, + }); + const tssCommits = tb.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); + const tssPrivKey = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], 1) + .mul(serverDKGPrivKeys[0]) + .add(getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], retrievedTSSIndex).mul(retrievedTSS)) + .umod(ecTSS.n); + const tssPubKey = (ecTSS.g as EllipticPoint).mul(tssPrivKey); + + const tssCommits0 = tssCommits[0].toEllipticPoint(ecTSS); + equal(tssPubKey.eq(tssCommits0), true); + + await assignTssDkgKeys({ + tssTag: "imported", + serviceProvider: torusSPKeyType, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + maxTSSNonceToSimulate: 1, + }); + // import key + const { raw: importedKey, scalar: importedScalar } = generateKey(TSS_KEY_TYPE); + const importedIndex = 2; + // import ?? + await tb.importTssKey( + { tssTag: "imported", importKey: importedKey, factorPubs: [factorPub], newTSSIndexes: [importedIndex], tssKeyType: TSS_KEY_TYPE }, + { + authSignatures: signatures, + } + ); + + // tag is switched to imported + await tb.syncLocalMetadataTransitions(); + + // for imported key + { + const finalPubKey = tb.getTSSCommits(TSS_KEY_TYPE, "imported")[0].toEllipticPoint(ecTSS); + + const finalTssKey = await tb._UNSAFE_exportTssKey({ + factorKey, + selectedServers: [1, 2, 3], + authSignatures: signatures, + keyType: TSS_KEY_TYPE, + tssTag: "imported", + }); + const tssPubKeyImported = (ecTSS.g as EllipticPoint).mul(importedScalar); + + equal(finalTssKey.toString("hex"), importedScalar.toString("hex")); + equal(tssPubKeyImported.eq(finalPubKey), true); + + if (TSS_KEY_TYPE === KeyType.ed25519) { + const seed = await tb._UNSAFE_exportTssEd25519Seed({ + factorKey, + selectedServers: [3, 4, 5], + authSignatures: signatures, + tssTag: "imported", + }); + equal(seed.equals(importedKey), true); + } + } + { + // tb.tssTag = "default"; + + const finalPubKey = tb.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT)[0].toEllipticPoint(ecTSS); + + const finalTssKey = await tb._UNSAFE_exportTssKey({ + factorKey, + selectedServers: [1, 2, 3], + authSignatures: signatures, + keyType: TSS_KEY_TYPE, + tssTag: TSS_TAG_DEFAULT, + }); + + const tssPubKeyImported = (ecTSS.g as EllipticPoint).mul(finalTssKey); + + equal(tssPubKeyImported.eq(finalPubKey), true); + } + // login to new instance + const tb2 = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, legacyMetadataFlag: legacyFlag }); + + await tb2.initialize(); + tb2.inputShareStore(newShare.newShareStores[newShare.newShareIndex.toString("hex")]); + await tb2.reconstructKey(); + + { + // tb2.tssTag = "imported"; + const finalPubKey = tb2.getTSSCommits(TSS_KEY_TYPE, "imported")[0].toEllipticPoint(ecTSS); + + const finalTssKey = await tb2._UNSAFE_exportTssKey({ + factorKey, + selectedServers: [1, 2, 3], + authSignatures: signatures, + keyType: TSS_KEY_TYPE, + tssTag: "imported", + }); + const tssPubKeyImported = (ecTSS.g as EllipticPoint).mul(finalTssKey); + + equal(finalTssKey.toString("hex"), importedScalar.toString("hex")); + equal(tssPubKeyImported.eq(finalPubKey), true); + } + { + // tb2.tssTag = "default"; + + const finalPubKey = tb2.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT)[0].toEllipticPoint(ecTSS); + + const finalTssKey = await tb2._UNSAFE_exportTssKey({ + factorKey, + selectedServers: [1, 2, 3], + authSignatures: signatures, + keyType: TSS_KEY_TYPE, + tssTag: TSS_TAG_DEFAULT, + }); + const tssPubKeyImported = (ecTSS.g as EllipticPoint).mul(finalTssKey); + + equal(tssPubKeyImported.eq(finalPubKey), true); + } + }); + + describe(`factor addition and removal, manualSync=${manualSync}`, function () { + let tb: TKeyTSS; + let factorKey: BN; + let newFactorKeySameIndex: BN; + let newFactorKeyNewIndex: BN; + let signatures: string[]; + + const deviceTSSIndex = 2; + const newTSSIndex = 3; + + before("setup", async function () { + const sp = torusSP; + sp.verifierName = "torus-test-health"; + sp.verifierId = `test192${TSS_KEY_TYPE}@example.com`; + const { signatures: authSignatures, postboxkey } = await fetchPostboxKeyAndSigs({ + serviceProvider: sp, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + }); + signatures = authSignatures; + sp.postboxKey = postboxkey; + await assignTssDkgKeys({ + serviceProvider: torusSPKeyType, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + maxTSSNonceToSimulate: 4, + }); + + tb = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, legacyMetadataFlag: legacyFlag }); + + // factor key needs to passed from outside of tKey + const factorKeyPair = ecFactor.genKeyPair(); + factorKey = factorKeyPair.getPrivate(); + const factorPub = Point.fromElliptic(factorKeyPair.getPublic()); + const deviceTSSShare = ecTSS.genKeyPair().getPrivate(); + await tb.initialize(); + await tb.initializeTss({ + factorPub, + deviceTSSShare, + deviceTSSIndex, + tssKeyType: TSS_KEY_TYPE, + serverOpts: { authSignatures }, + }); + const reconstructedKey = await tb.reconstructKey(); + await tb.syncLocalMetadataTransitions(); + + if (tb.secp256k1Key.cmp(reconstructedKey.secp256k1Key) !== 0) { + fail("key should be able to be reconstructed"); + } + }); + + it("should be able to add factor for same index", async function () { + newFactorKeySameIndex = ecFactor.genKeyPair().getPrivate(); + const newFactorPub = (ecFactor.g as EllipticPoint).mul(newFactorKeySameIndex); + await tb.addFactorPub({ + authSignatures: signatures, + existingFactorKey: factorKey, + newFactorPub: Point.fromElliptic(newFactorPub), + newTSSIndex: deviceTSSIndex, + tssTag: TSS_TAG_DEFAULT, + }); + await tb.syncLocalMetadataTransitions(); + }); + + it("should be able to add factor for different index", async function () { + newFactorKeyNewIndex = ecFactor.genKeyPair().getPrivate(); + const newFactorPub = (ecFactor.g as EllipticPoint).mul(newFactorKeyNewIndex); + await tb.addFactorPub({ + authSignatures: signatures, + existingFactorKey: factorKey, + newFactorPub: Point.fromElliptic(newFactorPub), + newTSSIndex, + refreshShares: true, + tssTag: TSS_TAG_DEFAULT, + }); + await tb.syncLocalMetadataTransitions(); + }); + + it("should be able to remove factor for same index", async function () { + const newFactorPub = (ecFactor.g as EllipticPoint).mul(newFactorKeySameIndex); + await tb.deleteFactorPub({ + factorKey, + deleteFactorPub: Point.fromElliptic(newFactorPub), + authSignatures: signatures, + tssTag: TSS_TAG_DEFAULT, + }); + await tb.syncLocalMetadataTransitions(); + }); + + it("should no longer be able to access key share with removed factor (same index)", async function () { + await rejects(tb.getTSSShare(newFactorKeySameIndex, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT })); + }); + + it("should be able to remove factor for different index", async function () { + const newFactorPub = (ecFactor.g as EllipticPoint).mul(newFactorKeyNewIndex); + await tb.deleteFactorPub({ + factorKey, + deleteFactorPub: Point.fromElliptic(newFactorPub), + authSignatures: signatures, + tssTag: TSS_TAG_DEFAULT, + }); + await tb.syncLocalMetadataTransitions(); + }); + + it("should no longer be able to access key share with removed factor (different index)", async function () { + await rejects(tb.getTSSShare(newFactorKeyNewIndex, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT })); + }); + }); + + describe(`TSS serialization and deserialization tests, manualSync=${manualSync}`, function () { + let tb: TKeyTSS; + let factorKey: BN; + let signatures: string[]; + + const deviceTSSIndex = 2; + const newTSSIndex = 3; + + const sp = torusSP; + + before("setup", async function () { + sp.verifierName = "torus-test-health"; + sp.verifierId = `test193${TSS_KEY_TYPE}@example.com`; + const { signatures: authSignatures, postboxkey } = await fetchPostboxKeyAndSigs({ + serviceProvider: sp, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + }); + signatures = authSignatures; + // eslint-disable-next-line require-atomic-updates + sp.postboxKey = postboxkey; + + await assignTssDkgKeys({ + serviceProvider: torusSPKeyType, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + maxTSSNonceToSimulate: 4, + }); + + tb = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, legacyMetadataFlag: legacyFlag }); + + // factor key needs to passed from outside of tKey + const factorKeyPair = ecFactor.genKeyPair(); + factorKey = factorKeyPair.getPrivate(); + const factorPub = Point.fromElliptic(factorKeyPair.getPublic()); + const deviceTSSShare = ecTSS.genKeyPair().getPrivate(); + await tb.initialize(); + await tb.initializeTss({ + factorPub, + deviceTSSShare, + deviceTSSIndex, + tssKeyType: TSS_KEY_TYPE, + serverOpts: { authSignatures }, + }); + const reconstructedKey = await tb.reconstructKey(); + await tb.syncLocalMetadataTransitions(); + + if (tb.secp256k1Key.cmp(reconstructedKey.secp256k1Key) !== 0) { + fail("key should be able to be reconstructed"); + } + }); + + it("should be able to serialize and deserialize", async function () { + const serialized = JSON.stringify(tb); + const serializedSp = JSON.stringify(tb.serviceProvider); + const spJson = TSSTorusServiceProvider.fromJSON(JSON.parse(serializedSp)); + + // we are using mocked storage layer for local testing + // const slJson = TorusStorageLayer.fromJSON(JSON.parse(serializedTorusSL)); + + const tbJson = await ThresholdKey.fromJSON(JSON.parse(serialized), { + serviceProvider: spJson, + storageLayer: torusSL, + manualSync, + }); + // reconstruct metdata key + await tbJson.reconstructKey(); + + // try refresh share + await tbJson.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); + + const newFactorKeyPair = ecFactor.genKeyPair(); + await tbJson.addFactorPub({ + authSignatures: signatures, + existingFactorKey: factorKey, + newFactorPub: Point.fromElliptic(newFactorKeyPair.getPublic()), + newTSSIndex, + refreshShares: true, + tssTag: TSS_TAG_DEFAULT, + }); + + await tbJson.getTSSShare(newFactorKeyPair.getPrivate(), { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); + + const serialized2 = JSON.stringify(tbJson); + + const tbJson2 = await ThresholdKey.fromJSON(JSON.parse(serialized2), { + serviceProvider: sp, + storageLayer: torusSL, + manualSync, + }); + + await tbJson2.reconstructKey(); + + await tbJson2.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); + await tbJson2.getTSSShare(newFactorKeyPair.getPrivate(), { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); + }); + }); + }); +}; + +const TEST_KEY_TYPES = [KeyType.secp256k1, KeyType.ed25519]; + +// MultiCurve only supported for secp256k1 service provider and legacy flag is false +TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { + multiCurveTestCases({ + TSS_KEY_TYPE, + legacyFlag: false, + spSecp256k1: true, + }); +}); diff --git a/packages/tss/test/tss.ts b/packages/tss/test/tssSingleCurveTestCases.ts similarity index 92% rename from packages/tss/test/tss.ts rename to packages/tss/test/tssSingleCurveTestCases.ts index 9fdde929..d52e157d 100644 --- a/packages/tss/test/tss.ts +++ b/packages/tss/test/tssSingleCurveTestCases.ts @@ -1,5 +1,5 @@ import { EllipticPoint, KeyType, Point } from "@tkey/common-types"; -import { Metadata } from "@tkey/core/"; +import { getEcCurve } from "@toruslabs/torus.js"; import assert, { equal, fail, rejects } from "assert"; import BN from "bn.js"; import { ec as EC } from "elliptic"; @@ -9,13 +9,22 @@ import { factorKeyCurve, TSS_TAG_DEFAULT } from "../src/tss"; import { getLagrangeCoeffs } from "../src/util"; import { assignTssDkgKeys, fetchPostboxKeyAndSigs, generateKey, initStorageLayer } from "./helpers"; -const TEST_KEY_TYPES = [KeyType.secp256k1, KeyType.ed25519]; - -TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { +const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolean; spSecp256k1: boolean }) => { + const { TSS_KEY_TYPE, legacyFlag, spSecp256k1 } = params; const ecFactor = factorKeyCurve; const ecTSS = new EC(TSS_KEY_TYPE); - const torusSP = new TSSTorusServiceProvider({ + const torusSPSecp256k1 = new TSSTorusServiceProvider({ + customAuthArgs: { + network: "sapphire_devnet", + web3AuthClientId: "YOUR_CLIENT_ID", + baseUrl: "http://localhost:3000", + // keyType: TSS_KEY_TYPE, + keyType: KeyType.secp256k1, + }, + }); + + const torusSPKeyType = new TSSTorusServiceProvider({ customAuthArgs: { network: "sapphire_devnet", web3AuthClientId: "YOUR_CLIENT_ID", @@ -24,11 +33,13 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { }, }); + const torusSP = spSecp256k1 ? torusSPSecp256k1 : torusSPKeyType; + const torusSL = initStorageLayer(); const manualSync = true; - describe(`TSS tests, keyType=${TSS_KEY_TYPE}`, function () { + describe(`TSS Single Curve tests, keyType=${TSS_KEY_TYPE}, legacyMetadata=${legacyFlag}, spSecp256k1=${spSecp256k1}`, function () { it("#should be able to reconstruct tss share from factor key", async function () { const sp = torusSP; @@ -36,7 +47,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { const deviceTSSIndex = 3; sp.verifierName = "torus-test-health"; - sp.verifierId = "test@example.com"; + sp.verifierId = "testTss@example.com"; const { postboxkey, signatures } = await fetchPostboxKeyAndSigs({ serviceProvider: sp, verifierName: sp.verifierName, @@ -49,7 +60,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { serviceProvider: sp, storageLayer: storageLayer2, manualSync, - tssKeyType: TSS_KEY_TYPE, + legacyMetadataFlag: legacyFlag, }); // factor key needs to passed from outside of tKey @@ -75,10 +86,15 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { } const { tssShare: tss2 } = await tb1.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); - const tssCommits = tb1.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); + // compute public key of the tss share const tss2Pub = ecTSS.g.mul(tss2); + + // get tss pub key from tss commits + const tssCommits = tb1.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); const tssCommitA0 = tssCommits[0].toEllipticPoint(ecTSS); const tssCommitA1 = tssCommits[1].toEllipticPoint(ecTSS); + + // compute public key of the tss share using tss commits and tss index const _tss2Pub = tssCommitA0.add(tssCommitA1.mul(new BN(deviceTSSIndex))); equal(tss2Pub.eq(_tss2Pub), true); }); @@ -88,8 +104,9 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { sp.verifierName = "torus-test-health"; sp.verifierId = "test181@example.com"; + const { serverDKGPrivKeys } = await assignTssDkgKeys({ - serviceProvider: sp, + serviceProvider: torusSPKeyType, verifierName: sp.verifierName, verifierId: sp.verifierId, maxTSSNonceToSimulate: 2, @@ -105,7 +122,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { }); sp.postboxKey = postboxkey; const storageLayer = initStorageLayer(); - const tb1 = new ThresholdKey({ serviceProvider: sp, storageLayer, manualSync, tssKeyType: TSS_KEY_TYPE }); + const tb1 = new ThresholdKey({ serviceProvider: sp, storageLayer, manualSync, legacyMetadataFlag: legacyFlag }); // factor key needs to passed from outside of tKey const factorKeyPair = ecFactor.genKeyPair(); @@ -143,7 +160,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { equal(tssPub.eq(tssPubKey), true); // With account index. - if (tb1.tssKeyType !== KeyType.ed25519) { + if (TSS_KEY_TYPE !== KeyType.ed25519) { const accountIndex = Math.floor(Math.random() * 99) + 1; const tss1Account = (() => { const share = new BN(serverDKGPrivKeys[0], "hex"); @@ -174,7 +191,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { }); sp.postboxKey = postboxkey; - const tb = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, tssKeyType: TSS_KEY_TYPE }); + const tb = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, legacyMetadataFlag: legacyFlag }); const factorKeyPair = ecFactor.genKeyPair(); const factorKey = factorKeyPair.getPrivate(); @@ -205,7 +222,8 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { await tb.syncLocalMetadataTransitions(); // Check pub key. - const importTssKeyPub = Point.fromScalar(importTssKey.scalar, tb.tssCurve); + const ec = getEcCurve(TSS_KEY_TYPE); + const importTssKeyPub = Point.fromScalar(importTssKey.scalar, ec); const tssPub = await tb.getTSSPub(TSS_KEY_TYPE, "imported"); assert(tssPub.equals(importTssKeyPub)); @@ -233,7 +251,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT, }); - const exportedPubKeyIndex2 = Point.fromScalar(exportedKeyIndex2, tb.tssCurve); + const exportedPubKeyIndex2 = Point.fromScalar(exportedKeyIndex2, ec); const pubKeyIndex2 = tb.getTSSPub(TSS_KEY_TYPE, TSS_TAG_DEFAULT, 2); assert(exportedPubKeyIndex2.equals(pubKeyIndex2)); } @@ -255,13 +273,13 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { }); sp.postboxKey = postboxkey; const { serverDKGPrivKeys } = await assignTssDkgKeys({ - serviceProvider: sp, + serviceProvider: torusSPKeyType, verifierName: sp.verifierName, verifierId: sp.verifierId, maxTSSNonceToSimulate: 1, }); - const tb = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, tssKeyType: TSS_KEY_TYPE }); + const tb = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, legacyMetadataFlag: legacyFlag }); // factor key needs to passed from outside of tKey const factorKeyPair = ecFactor.genKeyPair(); @@ -271,7 +289,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { await tb.initialize(); // import key - const { raw: importedKey0, scalar: importedScalar0 } = generateKey(TSS_KEY_TYPE); + const { raw: importedKey0 } = generateKey(TSS_KEY_TYPE); await tb.initializeTss({ tssKeyType: TSS_KEY_TYPE, @@ -308,7 +326,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { const { serverDKGPrivKeys: serverDKGPrivKeys1 } = await assignTssDkgKeys({ tssTag: "imported", - serviceProvider: sp, + serviceProvider: torusSPKeyType, verifierName: sp.verifierName, verifierId: sp.verifierId, maxTSSNonceToSimulate: 1, @@ -353,7 +371,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { equal(seed.equals(importedKey), true); } - const tb2 = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, tssKeyType: TSS_KEY_TYPE }); + const tb2 = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, legacyMetadataFlag: legacyFlag }); await tb2.initialize(); @@ -404,13 +422,13 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { }); sp.postboxKey = postboxkey; const { serverDKGPrivKeys } = await assignTssDkgKeys({ - serviceProvider: sp, + serviceProvider: torusSPKeyType, verifierName: sp.verifierName, verifierId: sp.verifierId, maxTSSNonceToSimulate: 1, }); - const tb = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, tssKeyType: TSS_KEY_TYPE }); + const tb = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, legacyMetadataFlag: legacyFlag }); // factor key needs to passed from outside of tKey const factorKeyPair = ecFactor.genKeyPair(); @@ -444,7 +462,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { await assignTssDkgKeys({ tssTag: "imported", - serviceProvider: sp, + serviceProvider: torusSPKeyType, verifierName: sp.verifierName, verifierId: sp.verifierId, maxTSSNonceToSimulate: 1, @@ -516,7 +534,7 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { equal(tssPubKeyImported.eq(finalPubKey), true); } // login to new instance - const tb2 = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, tssKeyType: TSS_KEY_TYPE }); + const tb2 = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, legacyMetadataFlag: legacyFlag }); await tb2.initialize(); tb2.inputShareStore(newShare.newShareStores[newShare.newShareIndex.toString("hex")]); @@ -578,13 +596,13 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { signatures = authSignatures; sp.postboxKey = postboxkey; await assignTssDkgKeys({ - serviceProvider: sp, + serviceProvider: torusSPKeyType, verifierName: sp.verifierName, verifierId: sp.verifierId, maxTSSNonceToSimulate: 4, }); - tb = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, tssKeyType: TSS_KEY_TYPE }); + tb = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, legacyMetadataFlag: legacyFlag }); // factor key needs to passed from outside of tKey const factorKeyPair = ecFactor.genKeyPair(); @@ -688,13 +706,13 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { sp.postboxKey = postboxkey; await assignTssDkgKeys({ - serviceProvider: sp, + serviceProvider: torusSPKeyType, verifierName: sp.verifierName, verifierId: sp.verifierId, maxTSSNonceToSimulate: 4, }); - tb = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, tssKeyType: TSS_KEY_TYPE }); + tb = new ThresholdKey({ serviceProvider: sp, storageLayer: torusSL, manualSync, legacyMetadataFlag: legacyFlag }); // factor key needs to passed from outside of tKey const factorKeyPair = ecFactor.genKeyPair(); @@ -729,7 +747,6 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { serviceProvider: spJson, storageLayer: torusSL, manualSync, - tssKeyType: TSS_KEY_TYPE, }); // reconstruct metdata key await tbJson.reconstructKey(); @@ -755,7 +772,6 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { serviceProvider: sp, storageLayer: torusSL, manualSync, - tssKeyType: TSS_KEY_TYPE, }); await tbJson2.reconstructKey(); @@ -765,4 +781,18 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { }); }); }); +}; + +const TEST_KEY_TYPES = [KeyType.secp256k1, KeyType.ed25519]; +const LegacyMetadataFlag = [true, false]; + +// testcases with secp256k1 service provider with single tss curve +LegacyMetadataFlag.forEach((legacyFlag) => { + TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { + singleCurveTestCases({ + TSS_KEY_TYPE, + legacyFlag, + spSecp256k1: true, + }); + }); }); diff --git a/packages/tss/test/tssUnsupportedTestCases.ts b/packages/tss/test/tssUnsupportedTestCases.ts new file mode 100644 index 00000000..87c2e9e6 --- /dev/null +++ b/packages/tss/test/tssUnsupportedTestCases.ts @@ -0,0 +1,172 @@ +import { KeyType, Point } from "@tkey/common-types"; +import { fail } from "assert"; +import { ec as EC } from "elliptic"; + +import { TKeyTSS as ThresholdKey, TSSTorusServiceProvider } from "../src"; +import { factorKeyCurve } from "../src/tss"; +import { fetchPostboxKeyAndSigs, initStorageLayer } from "./helpers"; + +// fail case - sp ed25519 us not supported for new format +// legacy flag is false, key type is ed25519, service provider is ed25519 +// legacy flag is false, key type is secp256k1, service provider is ed25519 +// legacy flag is true, key type mismatched with service provider + +const failTestCases = (params: { TSS_KEY_TYPE: KeyType }) => { + const { TSS_KEY_TYPE } = params; + const ecFactor = factorKeyCurve; + const ecTSS = new EC(TSS_KEY_TYPE); + + const torusSPSecp256k1 = new TSSTorusServiceProvider({ + customAuthArgs: { + network: "sapphire_devnet", + web3AuthClientId: "YOUR_CLIENT_ID", + baseUrl: "http://localhost:3000", + // keyType: TSS_KEY_TYPE, + keyType: KeyType.secp256k1, + }, + }); + + const torusSPEd25519 = new TSSTorusServiceProvider({ + customAuthArgs: { + network: "sapphire_devnet", + web3AuthClientId: "YOUR_CLIENT_ID", + baseUrl: "http://localhost:3000", + keyType: TSS_KEY_TYPE, + }, + }); + + const manualSync = true; + + describe(`TSS Unsupported tests, unsupported tests cases, keyType = ${TSS_KEY_TYPE}`, function () { + it("#should not able to instantiate new instance when legacyFlag=false with ed25519 service provider", async function () { + const sp = torusSPEd25519; + + sp.verifierName = "torus-test-health"; + sp.verifierId = "testTss@example.com"; + const { postboxkey } = await fetchPostboxKeyAndSigs({ + serviceProvider: sp, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + }); + sp.postboxKey = postboxkey; + + const storageLayer2 = initStorageLayer(); + + try { + new ThresholdKey({ + serviceProvider: sp, + storageLayer: storageLayer2, + manualSync, + legacyMetadataFlag: false, + }); + fail("should not be able to instantiate new instance with ed25519 service provider when legacyFlag=false"); + } catch (e) {} + }); + + it("#should not able to initialize on mismatch service provider key type when legacy flag is true", async function () { + const sp = TSS_KEY_TYPE === KeyType.secp256k1 ? torusSPEd25519 : torusSPSecp256k1; + + const deviceTSSShare = ecTSS.genKeyPair().getPrivate(); + const deviceTSSIndex = 3; + + sp.verifierName = "torus-test-health"; + sp.verifierId = "testTss@example.com"; + const { postboxkey, signatures } = await fetchPostboxKeyAndSigs({ + serviceProvider: sp, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + }); + sp.postboxKey = postboxkey; + + const storageLayer2 = initStorageLayer(); + const tb1 = new ThresholdKey({ + serviceProvider: sp, + storageLayer: storageLayer2, + manualSync, + legacyMetadataFlag: true, + }); + + // factor key needs to passed from outside of tKey + const factorKeyPair = ecFactor.genKeyPair(); + const factorPub = Point.fromElliptic(factorKeyPair.getPublic()); + + await tb1.initialize(); + try { + await tb1.initializeTss({ + factorPub, + deviceTSSShare, + deviceTSSIndex, + serverOpts: { + authSignatures: signatures, + }, + tssKeyType: TSS_KEY_TYPE, + }); + fail("should not able to initialize tss"); + } catch (e) {} + }); + + it("#should not able to support multicurve when legacy flag is true", async function () { + const sp = TSS_KEY_TYPE === KeyType.secp256k1 ? torusSPSecp256k1 : torusSPEd25519; + + const deviceTSSShare = ecTSS.genKeyPair().getPrivate(); + const deviceTSSIndex = 3; + + sp.verifierName = "torus-test-health"; + sp.verifierId = "testTss@example.com"; + const { postboxkey, signatures } = await fetchPostboxKeyAndSigs({ + serviceProvider: sp, + verifierName: sp.verifierName, + verifierId: sp.verifierId, + }); + sp.postboxKey = postboxkey; + + const storageLayer2 = initStorageLayer(); + const tb1 = new ThresholdKey({ + serviceProvider: sp, + storageLayer: storageLayer2, + manualSync, + legacyMetadataFlag: true, + }); + + // factor key needs to passed from outside of tKey + const factorKeyPair = ecFactor.genKeyPair(); + const factorPub = Point.fromElliptic(factorKeyPair.getPublic()); + + await tb1.initialize(); + + // initialize with matching curve + await tb1.initializeTss({ + factorPub, + deviceTSSShare, + deviceTSSIndex, + serverOpts: { + authSignatures: signatures, + }, + tssKeyType: TSS_KEY_TYPE, + }); + + // try to enable multicurve + try { + await tb1.initializeTss({ + factorPub, + deviceTSSShare, + deviceTSSIndex, + serverOpts: { + authSignatures: signatures, + }, + tssKeyType: TSS_KEY_TYPE === KeyType.secp256k1 ? KeyType.ed25519 : KeyType.secp256k1, + }); + fail("should not able to initialize tss"); + } catch (e) {} + }); + }); +}; + +failTestCases({ + TSS_KEY_TYPE: KeyType.ed25519, +}); +failTestCases({ + TSS_KEY_TYPE: KeyType.secp256k1, +}); + +// multicurve tests that fail on legacy flag is true From bcc2b4272db084d9f22fc45a2d32e9f40ed5e5c8 Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 7 Feb 2025 19:45:42 +0800 Subject: [PATCH 08/22] fix: handle edge cases tssKeyType from json is undefined --- packages/core/src/metadata.ts | 38 ++++++++++----- packages/core/test/authMetadata.test.js | 15 +++++- packages/core/test/metadataFormat.test.js | 58 +++++++++++++++++++++++ 3 files changed, 98 insertions(+), 13 deletions(-) diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index cd8bdb58..57ed0bfb 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -91,7 +91,7 @@ export class Metadata implements IMetadata { tss, version, // v0 metadata - tssKeyTypes, + tssKeyTypes: tssKeyTypesJson, tssPolyCommits, tssNonces, factorPubs, @@ -122,16 +122,22 @@ export class Metadata implements IMetadata { } // else would be legacy version, migrate for secp version - } else if (tssKeyTypes) { + } else if (factorEncs) { metadata.tss = {}; - Object.keys(tssKeyTypes).forEach((tssTag) => { - if (tssKeyTypes[tssTag] === KeyType.ed25519) { + // some tests case on backward compatbility tests having serialized metadata with empty tssKeyTypes + const tssKeyTypes: Record = tssKeyTypesJson ?? {}; + + Object.keys(factorEncs).forEach((tssTag) => { + // incase fo tssKeyType is empty, then fill it with secp256k1 + const tssKeyType = tssKeyTypes[tssTag] ?? KeyType.secp256k1; + + if (tssKeyType === KeyType.ed25519) { throw new Error(`ed25519 is not supported for migration for metadata from v${version ?? LEGACY_METADATA_VERSION} to ${METADATA_VERSION}`); } metadata.tss[tssTag] = { - [tssKeyTypes[tssTag]]: TssMetadata.fromJSON({ + [tssKeyType]: TssMetadata.fromJSON({ tssTag, - tssKeyType: tssKeyTypes[tssTag], + tssKeyType, tssNonce: tssNonces[tssTag], tssPolyCommits: tssPolyCommits[tssTag], factorPubs: factorPubs[tssTag], @@ -401,7 +407,8 @@ export class LegacyMetadata extends Metadata { nonce, version, // v0 metadata - tssKeyTypes, + + tssKeyTypes: tssKeyTypesJson, tssPolyCommits, tssNonces, factorPubs, @@ -409,6 +416,7 @@ export class LegacyMetadata extends Metadata { } = value; const point = Point.fromSEC1(secp256k1, pubKey); const metadata = new LegacyMetadata(point); + metadata.version = version || LEGACY_METADATA_VERSION; if (metadata.version !== LEGACY_METADATA_VERSION) { @@ -444,15 +452,21 @@ export class LegacyMetadata extends Metadata { metadata.polyIDList = unserializedPolyIDList; // tss related data - if (!tssKeyTypes) return metadata; - if (Object.keys(tssKeyTypes).length === 0) return metadata; + if (!factorEncs) return metadata; + if (Object.keys(factorEncs).length === 0) return metadata; metadata.tss = {}; - Object.keys(tssKeyTypes).forEach((tssTag) => { + // some tests case on backward compatbility tests having serialized metadata with empty tssKeyType + const tssKeyTypes: Record = tssKeyTypesJson ?? {}; + + Object.keys(factorEncs).forEach((tssTag) => { + // incase fo tssKeyType is empty, then fill it with secp256k1 + const tssKeyType = tssKeyTypes[tssTag] ?? KeyType.secp256k1; + metadata.tss[tssTag] = { - [tssKeyTypes[tssTag]]: TssMetadata.fromJSON({ + [tssKeyType]: TssMetadata.fromJSON({ tssTag, - tssKeyType: tssKeyTypes[tssTag], + tssKeyType, tssNonce: tssNonces[tssTag], tssPolyCommits: tssPolyCommits[tssTag], factorPubs: factorPubs[tssTag], diff --git a/packages/core/test/authMetadata.test.js b/packages/core/test/authMetadata.test.js index c8a8e144..c38e0849 100644 --- a/packages/core/test/authMetadata.test.js +++ b/packages/core/test/authMetadata.test.js @@ -1,6 +1,6 @@ import { generatePrivateExcludingIndexes, getPubKeyPoint } from "@tkey/common-types"; import { generatePrivate } from "@toruslabs/eccrypto"; -import { deepStrictEqual } from "assert"; +import { deepEqual, deepStrictEqual } from "assert"; import BN from "bn.js"; import stringify from "json-stable-stringify"; @@ -8,6 +8,9 @@ import { AuthMetadata, generateRandomPolynomial, Metadata } from "../src/index"; const PRIVATE_KEY = generatePrivate().toString("hex"); +const mpcBackwardCompebilityTestJson = + ' {"data":{"factorEncs":{"default":{"f6126a36291f7bf6a10d5df11af4ba184adb8a1212e233846f872034a06a7a87":{"serverEncs":[],"tssIndex":2,"type":"direct","userEnc":{"ciphertext":"efa604982f337f7bf5034cb8f3996117b9a13e4e4f4a3cb37f06b55722eb1cf052456c24ff2a2d2ae3ffe01ebc4e63f9","ephemPublicKey":"04a280a81a81f8bddef7d052f740dce5ff1d123ba5c208eb2b4bd8d9d9a94e585d0d1b0b25c1624f38dac7fb196ef85fa2eecd47a363bd2c1b00ce3ccc64e1fdeb","iv":"1121328b857d206af5686021bc2a7baf","mac":"026c5633e36b69d59a11a487045cc99107440bf2da049c339beb0745fcdcaf5a"}}}},"factorPubs":{"default":[{"x":"f6126a36291f7bf6a10d5df11af4ba184adb8a1212e233846f872034a06a7a87","y":"b05c5a1dbb89939335fddac27d90495342987555e75942460d3fca0571829cf"}]},"generalStore":{"shareDescriptions":{"03f6126a36291f7bf6a10d5df11af4ba184adb8a1212e233846f872034a06a7a87":["{\\"module\\":\\"hashedShare\\",\\"dateAdded\\":1705918192036,\\"tssShareIndex\\":2}"]}},"nonce":91,"polyIDList":["03422a3a45805c131e1f046ede6a7292c999993f86dbb6b4d4c3ab21f6e278fc11|02479427749ab0b468e5192d9c41698af3175b3a0588c426e7b72cdca30f4edd84|0x0|1|5c5d460d75a271853d4e00e4738aac6f7cb7e5e13fe85848edf5b3aec92368e5"],"pubKey":"03422a3a45805c131e1f046ede6a7292c999993f86dbb6b4d4c3ab21f6e278fc11","scopedStore":{},"tkeyStore":{"tssModule":[{"ciphertext":"69c19b5c7f531e049f5e35039dfbe2ad42f1a39670790ab918b01bc40e38c4da6b83cbfc0a39eb2cf9b102ce8ae0a46feb5c8719075c27f4e5edb05758eff22ff0b782e88ae1d15394b9dc596f52b4da27ace18b2db060c08dd7ab301d9f1345","ephemPublicKey":"046beade3380418cffa450b7602d21b484999a851b098d6a2c99d7e4a47a5cbcdff9938b81348caa8ba16b0a8d48485679ccbb5e5b010690854ef9ff173dd7709c","iv":"3e07dd44e31f584af30e7fc8dc12c83a","mac":"26f686bf7bae162ee0adb670c9beb8c9729984fbfb16f8bd7c886ccc4837a490"}]},"tssKeyTypes":{},"tssNonces":{"default":0},"tssPolyCommits":{"default":[{"x":"23764a46f13a2b40f3a2e9d5ddfaac25690a84cc36c07efec0e61c723b4b5dc2","y":"5d7589199bfefd17bc0594f6e597a82388e68365d72403c3930ea594680e5d79"},{"x":"639929d09bbbe5dcfd684f4c348d8716c4d99347f7759a5b002cd3fdba2f2147","y":"a032c871a6c57a1af43bb043fcdae00dffea682eca6963a67906e892623abce5"}]}},"sig":"304502200701aab1420ba046b72817c69093d2cab43d4bbe7809b40ead658b09f12191f9022100969468307ab7ac29b64f427bd59af58ce39a8d006a22b7e260400acc329b7d90"}'; + describe("AuthMetadata", function () { it("#should authenticate and serialize and deserialize into JSON seamlessly", async function () { const privKeyBN = new BN(PRIVATE_KEY, 16); @@ -25,4 +28,14 @@ describe("AuthMetadata", function () { const final = AuthMetadata.fromJSON(JSON.parse(stringified)); deepStrictEqual(final.metadata, metadataSerialized, "Must be equal"); }); + + it("#should authenticate and serialize and deserialize into JSON seamlessly (backward compatibility)", async function () { + const instance1 = AuthMetadata.fromJSON({ ...JSON.parse(mpcBackwardCompebilityTestJson), legacyMetadataFlag: false }); + const instance2 = AuthMetadata.fromJSON({ ...JSON.parse(mpcBackwardCompebilityTestJson), legacyMetadataFlag: true }); + + delete instance1.metadata.version; + delete instance2.metadata.version; + + deepEqual(instance1, instance2); + }); }); diff --git a/packages/core/test/metadataFormat.test.js b/packages/core/test/metadataFormat.test.js index 7dc5c1c6..014c066f 100644 --- a/packages/core/test/metadataFormat.test.js +++ b/packages/core/test/metadataFormat.test.js @@ -1,4 +1,6 @@ +import { KeyType } from "@tkey/common-types"; import { deepEqual, equal, fail } from "assert"; +import stringify from "json-stable-stringify"; import { LegacyMetadata, Metadata } from "../src"; import { LEGACY_METADATA_VERSION, METADATA_VERSION } from "../src/metadata"; @@ -12,6 +14,12 @@ const legacyJSONEd25519 = const multiCurveJson = '{"pubKey":"022dd7f0a0b54953304e94e861373ab9fe9c36930f9bc4804efa06b1d472f62ade","polyIDList":["022dd7f0a0b54953304e94e861373ab9fe9c36930f9bc4804efa06b1d472f62ade|0341267f01008fc085e2c2bb3f508422e1410e641a5f297fa14bd57d32d7e97a87|0x0|1|731d22b16ae27306e045e6121dc9c43754dfbdb6e28a59879e054bb0772673d","022dd7f0a0b54953304e94e861373ab9fe9c36930f9bc4804efa06b1d472f62ade|034f25e3aeea954b8ddf033d00a8657c227c3e243177795c303476ffa2fd104c9b|0x0|1|731d22b16ae27306e045e6121dc9c43754dfbdb6e28a59879e054bb0772673d|969cf4076b10ba9276e44964915cc97c2984ff29cb1d2de8f2c10fb9dfa35279"],"scopedStore":{},"generalStore":{"ed25519Seed":{"message":{"ciphertext":"afde6d2044edce2f9e106bfe7b03bc716b1f4f9d765c70aa04d10089b07141e5ab2eff80a2c74798e6e44129403f4fc4","ephemPublicKey":"041a719598c138c0b5d2f1cc12b4348bed2c5c253360bb1f855f1eb7df53746047892f69655727f672f3a81bcdcc8e5816cc1f2b2b822e4e79a88cac83c3a30dfc","iv":"c846c938f752825764f0bd0fbbceaf50","mac":"013adaa31e31ab19300e1a5979a2728a53e324689b92677cbd55f0cb938ac138"},"publicKey":"047ecfc14f54a87dc25a4c839d71d9e80046f71141e42b339faa60c2f28601fa1870f68c19c5004f52ddf2f3d17f779ad1ece0d13d981afd294645d4f29a2d20c5"},"ed25519Seed/default":{"message":{"ciphertext":"9a2667945f75aa35c25358439d8f8f578fac22e99d31342d11eef32879a66c286cfae76218d16bdd3b881260f40353ff","ephemPublicKey":"046a6fccfabb06d73580143981d61e2a8d9fc4a02da99421dddef57248cd64976606a79d19cdafc42129ab39b3c80a64ccde7bd805a36cea519b57da43c6d0f195","iv":"179e3c25c68cbd5d4be06c32b7d12a6a","mac":"f4e6e500fd60c275544857ed4750487267394bf7002eed13aa4816ebbcd457b2"}}},"tkeyStore":{"tssModule":[{"ciphertext":"57ed48f042da6bac2774b48a6815476f75d09d16043bb800c92b3b04ca6ccc28fd79adfcfa11f96a7a27e28645376428f9b02c8bc0e454d190ec3880e9a9387d7dcc7300d4adef4080a847e648170b551473297b200c6781a2ea265b8db75ca3","ephemPublicKey":"04e440f6d8990c148c750bb21dbdec7dabf4f6ffe1c5c30b5640e42b2b81b02ce40073dd0ca43cee74a95fb90a25588ba05ca65da83e9377829c563b10fa000e5e","iv":"701b9f3f8f790588e47af25eff5db67e","mac":"453977ff17be129f2f17bee349e7b1bf74ae8da436b79e70f236ba470e4d5d40"}]},"nonce":5,"version":"1.0.0","tss":{"default":{"secp256k1":{"tssTag":"default","tssKeyType":"secp256k1","tssNonce":0,"tssPolyCommits":[{"x":"75e2914f29baefcadb5aea5ada6aa8ca0f2edef47f090bd55ec87fd1c68ef6a","y":"3be3fd7188f951bab6af03445c16642e70bfe720995f8d7d406f198ea451339a"},{"x":"860dcf8b742ebd9c35a43adc0a54ad5ca122f1df20f0c8cf65ae01637d988f9c","y":"96b5669497209e94b3f75d658a17a19bba66a6ea5337b77dec0920c9279999f0"}],"factorPubs":[{"x":"62bdf332bd2c64e38ea6085ddb16133a293f59d0f2efc7e89388e250b232da74","y":"ea06f717886022b9592b5504c0fbd1272080a39d57ca869bac34b05abeba9090"}],"factorEncs":{"62bdf332bd2c64e38ea6085ddb16133a293f59d0f2efc7e89388e250b232da74":{"serverEncs":[],"tssIndex":3,"type":"direct","userEnc":{"ciphertext":"60f413ae1985ff6abd10995bc9d75707d76f1bcde659b60f9f78f37549d346156209d74ec672403d01911b246db05769","ephemPublicKey":"04c7ee61df6af27917edacd194be2429575a173eb77fdde5e351a3a7ad30cacdfbb889242e689f5eb6bdd11deacadbc3c592c9881eac8551b7c792bb993a1b0321","iv":"305724b2455de8e516cd90a01c5e7adb","mac":"719ac67e41fa159e9736e3d1cb4b858e9c38c7faff3b9c2fe63ca99d82ac2452"}}}},"ed25519":{"tssTag":"default","tssKeyType":"ed25519","tssNonce":0,"tssPolyCommits":[{"x":"202c3557a7cad9102c0cfbcaab765277018f3e680c9c3eb35c0cb69fa07788b8","y":"45f1cb0bffba15dd562811694864a228ea1908eb1c3ca9cd90899c1a92ca85e3"},{"x":"7e271298f3c9c9386dc611e9c57a0031fdd17aeea17423073961f815b95fa833","y":"dbe5d48e51e01df6b236315568b60dc3e513b72cb07b9613f7aaed2c1bff5fb"}],"factorPubs":[{"x":"62bdf332bd2c64e38ea6085ddb16133a293f59d0f2efc7e89388e250b232da74","y":"ea06f717886022b9592b5504c0fbd1272080a39d57ca869bac34b05abeba9090"}],"factorEncs":{"62bdf332bd2c64e38ea6085ddb16133a293f59d0f2efc7e89388e250b232da74":{"type":"hierarchical","tssIndex":3,"userEnc":{"ciphertext":"d57f7f23f71348ea8a7479e10e085d074119c12bab5df45283e69ba3ef2a7e3dd9ecb7c962eceea265b302509ed67bad","ephemPublicKey":"048627e20c599e4fe3c5dd4fc3af7b45111f4a991f8e6ffb5c92da33f4b2fbfbacfc064b7abf27fb602b4fff8af0e01ed1ac1beaaaed373f954dde914237a27cca","iv":"6f0c006ea1ceea25167304a4d2a9c80e","mac":"31932b2647b821fc833ae625b54c8085dab9791d534457c92e220e1e00fbb73c"},"serverEncs":[{"ciphertext":"ebc855e5c04ca52f3a03a704f66fdaa6aa503760a267a8c0311554f89954096ea5bb16016037bcc9f86157ef8fd115f4","ephemPublicKey":"045fc8623ed91c3b11013d2a43da4ea1c82a46ad25158941c711397d2bfac8224a37287a93c882e55cab5ab14f45dcd82d0ee8f20ee41ffed11120820fabbd4863","iv":"30bc8a9549fdb55682e2a8dec4411884","mac":"f375aff236cb31763fb61e8bd242069a6e193b1e036b811daf2d4a354c021df7"},{"ciphertext":"eae57b0956202e6ccbd53bd5c6df568fc8b6b33242fad894333f27461e28021ba01599757109a9879526ff15a465e7d2","ephemPublicKey":"049f78eb19d31f29905596a2371ebdcc98b1beee9ab15f700974e994e254884b5df30c6bd14b3cba9f8fdcd5e36846b7fc52cf89012ebb8c9fbdee2cdd164db7c8","iv":"4d1372010b4fd0312b0bb1c089d5e2b4","mac":"70f1f77be28f2be39191e0f3af5be330776563428c469a8072af2704173d42df"},{"ciphertext":"bba1839361e43a86545d37bfcb76290b20981822cc83e6701468b89fa5c4a4dae3fb2f9130972749aaf0f1c378800347","ephemPublicKey":"04256e36cd09417399f2cff9ea02769bb102710ad7ac8704aa30b18c062256eab7f6337eb2269525fd3e96cc606e1288fe02d3b0e2ae99d4c1f556b93d6f9bfc83","iv":"2491273bae809df6df55df024726ebfd","mac":"571d7ff4e44433c67b9aff92457e241cdad281034075dfffcd48250fb4a78d78"},{"ciphertext":"e3dad5f1b33dc9ddadb2255e2864ad07573d2e0f38e2d2462380329e04cdf7cfeca1059754493be96b2c51a3464210dd","ephemPublicKey":"040ce0a9bf20b79ebca6f1de32021d575d86a4526e1df1ab67e7552e37969bceefba9983cc3c29cb4e41916359e7416ec4c0ebd385203a2f6ae77ae6aa5405ba73","iv":"8d1f802b158a8cdebc8c4157b099a0d3","mac":"a00163b3e7ccd900dd12b7c28e3e999ae018324155dac298eb3038e656982876"},{"ciphertext":"3945f770ce1057e31e64951815922be8577cbc3135b3b9f2488090b86898a579a466a2f792680f0329e9f75ef252cb41","ephemPublicKey":"041f4517c1c8932d13d564f04d0ab735a57f79ca415aeac4b7c1d6c08e4d9cac7111df7dc39df122d80b6e5be9b734bc2c89a3598a8b7138368300ebc4604c29a2","iv":"42ee1817d55cd8e3c8c76b1d82df5d81","mac":"3ad2fe6595bbf40235ae62d53debee60428bf25da6b3e8fabd14238bcb5bfebb"}]}}}},"imported":{"secp256k1":{"tssTag":"imported","tssKeyType":"secp256k1","tssNonce":0,"tssPolyCommits":[{"x":"8a41efc52c76684c21f7898a9035a5f64d14e184a70d55bf28adfefc8fc75b3","y":"8422d404ebe92f25ac0725a32e39c1c6e4a2108e535f3615a7d7348e9e843e8e"},{"x":"14c77d1851981790e379c5545c7712b5f9fc9d566e1d9cdb20bc2a0ebe5910b6","y":"b08f3294a70f3e966c3ab194314c31dcbe9a5ea02e0bb24ab4b47487f8b085db"}],"factorPubs":[{"x":"62bdf332bd2c64e38ea6085ddb16133a293f59d0f2efc7e89388e250b232da74","y":"ea06f717886022b9592b5504c0fbd1272080a39d57ca869bac34b05abeba9090"}],"factorEncs":{"62bdf332bd2c64e38ea6085ddb16133a293f59d0f2efc7e89388e250b232da74":{"type":"hierarchical","tssIndex":2,"userEnc":{"ciphertext":"9eb4d12aa6f30aa297ffcf17826826251238458e1712b75af80db4265e5a7fe4b9f5bc4e79f73255ba92bab108a98a4d","ephemPublicKey":"043408f0f43963c2fb5c6da759113a9f12616eb435cb181e565a9a66cf898589b3fec9b597abe0fffd4d8f9b10def94795dc40b43e9ef3dfa7c3761be63326a3fc","iv":"359b79f10ca00470d13756f96463eeb8","mac":"1fc2f9842b9e86117b2d53f95f8d69433308589620628aa66c50d210181ee950"},"serverEncs":[{"ciphertext":"80dc41ca3cb42187ace1fa2553daf456ea80c845158af21dce9c2722ad781f942551b8854eb0cdb686980f0cfe6ccd64","ephemPublicKey":"042179cb3af6d8b625cf410026f8c9b74c3dd1d6e2c25db7060507e58ae46a30c8f8adb3d4f7c47bcc3d031ed795c76736eb8879f02b5f9862b06de2c0c83ad74f","iv":"4ee3ba08a424a212a53faff89f40a35e","mac":"02cfff72863b1ef3b217a7d868ed812ac2fc948abdaec7f8c77a7383afcef413"},{"ciphertext":"cb51445f28f518c371637d182954dfb2dfffd1e3a58b9c5089557db269cea8983359f8b4012c9965d2c6368598044256","ephemPublicKey":"04abec93a4d9405512d6b0fe3c9f6283744a9175c2b5e82a90bba0fedecbb22583dd526cf1858b07690f53ca7e621a81a3217635a9739e79aba0fba6e332eeec4f","iv":"e89e8bb153becbc3f2c304be0d8b02f8","mac":"770c3274a8d968897f518f8a9a019e47500e91c35461ab44735de72adef3494f"},{"ciphertext":"3b98c46a54419fa2a6419cef43f505c9fccdb928fcb2f70179d751c3e155124dcf8ee4ad6b536367bc635856c52e8f66","ephemPublicKey":"048c05e675a6465044b9c5b8acb32226707af9e04f972703a18ec75920960bdca813dc0a1a4632afd74ccdebcdd9a1d8ea6f1b3b599d8f2a841301fd19c4ee0ffe","iv":"b0cfb6ec0ac4f150590b02ec2442e19d","mac":"adc65ed18f982ce229ed3ba404a357c9f4e6fb89d40b926e6b791cbcd205304d"},{"ciphertext":"1050f582da2d571016552385cbd8d5db9b7b2b024dfd406ebcf8b422b5c9b32ab5b4af3c921e8ed0bb6244f56a39de9b","ephemPublicKey":"046a44e351f2b9fb45b114457d7afd505837065d0c3d76d6af0cc831e7ee46c93bac9849ad56f8f131ec1f934836dfba23367f590e1aa2bf312195f4d1aed1ba4a","iv":"149473b0ca24ede8663e5e0a404a8832","mac":"9859c6e77a1cb2951b955449448a81f3c7f8b14b03b981b461fdf2c2857b1527"},{"ciphertext":"4a26f1e6dafeaa2a03189f0f0dbae8aa3cf3bfc6b5da88ad145ca4b3a2d7284d0ed086d1e2910b3f6cdd23dd64987e46","ephemPublicKey":"04694c715117852d1dc1d90504b1ca1b920cbb9c107ef9ee9929440e2e21b4d4fba14bac27524a43bc33cfb9b3d2f3c93973ccb20c322fa933e9e9631c3c904122","iv":"ed8adfd3806b4e57e9e384a4a308dbca","mac":"d053605e9c44083a128897d5deb600764f8ca104c57a4597bd1c143ccdbb963b"}]}}}}}}'; +const mpcBackwardCompebilityTestJson = + '{"factorEncs":{"default":{"16e9809395ce9078a8a510ddf613cea148a3a23049fd3948587b4fec28d68f92":{"serverEncs":[],"tssIndex":2,"type":"direct","userEnc":{"ciphertext":"ee307c87b733170222ef93048201c7c995544ac51eb648fbdea992a77d71f46e8a62c617caf20f3208d59ebc7f0a9e91","ephemPublicKey":"041c158020f3341b911d5b3a9cb70b29b7a7d327ab6b92134634bb0ed8e0461b4dfa7e0eeb8185365faefbbd37fea5e72540ed9a0bea65b3a4e6008f89c8bf796e","iv":"5a8ddd80c74dac65d1a8bd8aee110d4c","mac":"bf25a4d8411a2d3b432bd8806e699ac144fccf7f52f937b5657365a06d9d97ec"}}}},"factorPubs":{"default":[{"x":"16e9809395ce9078a8a510ddf613cea148a3a23049fd3948587b4fec28d68f92","y":"212e94e3b9e811802f1a47212a3cbc7ada4a7f993fc07e6be5340af40dd0d852"}]},"generalStore":{"shareDescriptions":{"0216e9809395ce9078a8a510ddf613cea148a3a23049fd3948587b4fec28d68f92":["{\\"module\\":\\"hashedShare\\",\\"dateAdded\\":1709020898217,\\"tssShareIndex\\":2}"]}},"nonce":172,"polyIDList":["03b3951a441f87ecea4672edc82894ac023316723cf164a93adec72b58a27a1f06|022909d9743f792d6adc3e16ce53d825230ad9b0b35574a34965a2e73a0f53b608|0x0|1|51b0891f3fed9f9f4ce7dde0710770a3d5b597d4c575460df431f907c9ee4e0a"],"pubKey":"03b3951a441f87ecea4672edc82894ac023316723cf164a93adec72b58a27a1f06","scopedStore":{},"tkeyStore":{"tssModule":[{"ciphertext":"04463425d186330813e4abefcdbcb786a0d22bd0067c4b81a584aec3989412ac3cb9a928c8be454245f60f08de08f7277a96c8d9ca292bdef4c4d26f0c6142d6618d8d1b0049812f8a95c372bc9e0eb87cf348152cf56239a289721c2c2ae2a3","ephemPublicKey":"0479cb39633e70992c3748447cd532982618ee89f451f4d2d123dd86871b62101bdf9ab98370f4ab46fde938777ae81bf05efe425e52a6ee375df825016720af76","iv":"211b9ecef309e477566a629b4c5e6e04","mac":"8277407d112e9e369408d1bc2b5086553d5d5a141373381aea06e1b0e5809676"}]},"tssKeyTypes":{},"tssNonces":{"default":0},"tssPolyCommits":{"default":[{"x":"d2869f27c3e226d90b275b008f7dc67b8f4b208900a7b98ecc4e5266807d382c","y":"15860fd569413eb7f177e655c4bf855f37920b800235de344fdd518196becfe0"},{"x":"15901123759ec65786e6a4059c6763c1da26c64f4d89b733e9d4676f29168697","y":"18d33eb0bc491ad401352b1980fba1fa864850dd895ae470b69577a6a0451719"}]}}'; + +const mpcBackwardCompebilityTestJson1 = + '{"factorEncs":{"default":{"e8e9f13e3552e7c8e4241f4c6f30f6461ce1ad5913a0d506a662abf8611d5398":{"serverEncs":[],"tssIndex":2,"type":"direct","userEnc":{"ciphertext":"a112e64ecb7396593486b0c4e4ca89ffec92ca01c59bbb89d4ca171519e36c4ccd3837096c2160482a0f455241ea71cf","ephemPublicKey":"049abfb15a3d8d8de9ce3d16afdb2dccf1c5002b8a794ec001852c33d8b58a02ff73657862225adde1c691d3e453f41eee83190804dc3004d8d6eb38c994c1e84f","iv":"6a1a9f32e748e6b35a6e4e6ad16eba17","mac":"ff11055118d8cb7feedabab07dd56d132a7d81461eacbe097a0378bf1302f39e"}}}},"factorPubs":{"default":[{"x":"e8e9f13e3552e7c8e4241f4c6f30f6461ce1ad5913a0d506a662abf8611d5398","y":"5ebe4e2c5b7b63161e1720c74391a7371a272b97b59ad2090f086fac74fd9a1a"}]},"generalStore":{"shareDescriptions":{"02e8e9f13e3552e7c8e4241f4c6f30f6461ce1ad5913a0d506a662abf8611d5398":["{\\"module\\":\\"hashedShare\\",\\"dateAdded\\":1720420195095,\\"tssShareIndex\\":2}"]}},"nonce":3,"polyIDList":["03bb90f3505fc1b059a941b60d94ba6c3eefba9e425aa4056729e7b3ab6a545735|025d18273218bf7b1a418f6d10f9dc2ba09303f10c39597684d261068b1063c867|0x0|1|8bb341daba92bfbf500578c1bbd2a05178513470c3dc6e9704996636b33fdf4"],"pubKey":"03bb90f3505fc1b059a941b60d94ba6c3eefba9e425aa4056729e7b3ab6a545735","scopedStore":{},"tkeyStore":{"tssModule":[{"ciphertext":"b1f975cc8ad6b8a3c6d47bf5a9699ae610c6130a903d6d652635d6592c2b80ab971f15d7b443320926fbac51dd33b160c0ccc5e91d4acff6d2645e2b25be8b7c4c9e989bd777893f6e251cf5b5251f9909ffb2b91b644a576ba02aede66bd2f1","ephemPublicKey":"04591474bd11b87545942e6bd0453464f39435c4c2733926b6d5e34c74c0a8e9cb56f27abaa3703e9488e34c915a523709dc7894866a1dce6e212ea4438452db4b","iv":"b2621fa8ba8212ebcc6e245440c251d6","mac":"446a459f68aa47fb3531f1c45cea98f6eceafab69d6a4f6e39d973ad38650a3e"}]},"tssNonces":{"default":0},"tssPolyCommits":{"default":[{"x":"37d65458d3161aeaa6fa76452384b87d34cf1de6bc2d42503e5f9060ce7c7818","y":"f8b4d3fe1c4bd727f0ce6195c3487251f9f37885fddc2636a6fdc2c2fa605bda"},{"x":"c9cbede6afbbf78844dbad83746ad02cee0ada30d784017053ffdd3f88259219","y":"c4650c915d10010ba079b80c2f5594eb4607c17802e07b5c13b32e7fab29ed9d"}]}}'; + describe("Metadata new Formatting tests", function () { it("#should serialize and deserialize into JSON seamlessly", async function () { const instance1 = Metadata.fromJSON(JSON.parse(legacyJSONSecp256k1)); @@ -61,4 +69,54 @@ describe("Metadata new Formatting tests", function () { const instanceSerialized = instance.toJSON(); equal(JSON.stringify(instanceSerialized), multiCurveJson); }); + // to add: add tests for different and multple tags and keytypes\ + it("#should able to deserialize new JSON format with backwardcompability test", async function () { + const instance = LegacyMetadata.fromJSON(JSON.parse(mpcBackwardCompebilityTestJson)); + const instanceSerialized = instance.toJSON(); + + // delete for matching purpose ( jsonstring provide do no have tssKeyTypes.default keytype) + delete instanceSerialized.tssKeyTypes.default; + equal(stringify(instanceSerialized), mpcBackwardCompebilityTestJson); + + // try to deserialize using new metadata format + const instance1 = Metadata.fromJSON(JSON.parse(mpcBackwardCompebilityTestJson)); + const instance1Serialized = instance1.toJSON(); + + const instance2 = Metadata.fromJSON(instance1Serialized); + // check deserialized new format is deep equal + deepEqual(instance1, instance2); + + // check legacy and new format is deep equal + deepEqual(instance1.getTssData(KeyType.secp256k1, "default"), instance.getTssData(KeyType.secp256k1, "default")); + + delete instance1.version; + delete instance.version; + + deepEqual(instance1, instance); + }); + + it("#should able to deserialize new JSON format with backwardcompability1 test", async function () { + const instance = LegacyMetadata.fromJSON(JSON.parse(mpcBackwardCompebilityTestJson1)); + const instanceSerialized = instance.toJSON(); + + // delete for matching purpose ( jsonstring provide do no have tssKeyTypes.default keytype) + delete instanceSerialized.tssKeyTypes; + equal(stringify(instanceSerialized), mpcBackwardCompebilityTestJson1); + + // try to deserialize using new metadata format + const instance1 = Metadata.fromJSON(JSON.parse(mpcBackwardCompebilityTestJson1)); + const instance1Serialized = instance1.toJSON(); + + const instance2 = Metadata.fromJSON(instance1Serialized); + // check deserialized new format is deep equal + deepEqual(instance1, instance2); + + // check legacy and new format is deep equal + deepEqual(instance1.getTssData(KeyType.secp256k1, "default"), instance.getTssData(KeyType.secp256k1, "default")); + + delete instance1.version; + delete instance.version; + + deepEqual(instance1, instance); + }); }); From 30ada0cf35e4801579e8af72c985c5b83b2598c5 Mon Sep 17 00:00:00 2001 From: ieow Date: Mon, 10 Feb 2025 15:18:02 +0800 Subject: [PATCH 09/22] fix: add random verifierId to the tests --- packages/tss/src/tss.ts | 7 ++++--- packages/tss/test/tssMultiCurveTestCases.ts | 8 +++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/tss/src/tss.ts b/packages/tss/src/tss.ts index 13b8a7d1..328b273c 100644 --- a/packages/tss/src/tss.ts +++ b/packages/tss/src/tss.ts @@ -56,6 +56,7 @@ export interface TSSTKeyArgs extends TKeyArgs { } export interface TKeyTSSInitArgs { + tssKeyType: KeyType; deviceTSSShare?: BN; deviceTSSIndex?: number; factorPub?: Point; @@ -161,7 +162,7 @@ export class TKeyTSS extends TKey { * under the given factor key. `skipTssInit` skips TSS account creation and * can be used with `importTssKey` to just import an existing account instead. */ - async initializeTssSecp256k1(params: TKeyTSSInitArgs): Promise { + async initializeTssSecp256k1(params: Omit): Promise { // only support service provider with secp256k1 key type if (this.serviceProvider.customAuthArgs.keyType === KeyType.ed25519) { throw CoreError.default("Multiple Curve do not support for ed25519 network's postboxkey "); @@ -265,7 +266,7 @@ export class TKeyTSS extends TKey { * under the given factor key. `skipTssInit` skips TSS account creation and * can be used with `importTssKey` to just import an existing account instead. */ - async initializeTssEd25519(params: TKeyTSSInitArgs & { importKey: Buffer }): Promise { + async initializeTssEd25519(params: Omit & { importKey: Buffer }): Promise { // only support service provider with secp256k1 key type if (this.serviceProvider.customAuthArgs.keyType === KeyType.ed25519) { if (this.metadata.getTssData(KeyType.ed25519, TSS_TAG_DEFAULT)) { @@ -324,7 +325,7 @@ export class TKeyTSS extends TKey { /** * Initializes a new TSS account under the given factor key. */ - async initializeTss(params: TKeyTSSInitArgs & { tssKeyType: KeyType }): Promise { + async initializeTss(params: TKeyTSSInitArgs): Promise { const { tssKeyType } = params; if (tssKeyType === KeyType.secp256k1) { await this.initializeTssSecp256k1(params); diff --git a/packages/tss/test/tssMultiCurveTestCases.ts b/packages/tss/test/tssMultiCurveTestCases.ts index 4abe3a9e..8c46459a 100644 --- a/packages/tss/test/tssMultiCurveTestCases.ts +++ b/packages/tss/test/tssMultiCurveTestCases.ts @@ -1,4 +1,5 @@ import { EllipticPoint, KeyType, Point } from "@tkey/common-types"; +import { randomId } from "@toruslabs/customauth"; import { getEcCurve } from "@toruslabs/torus.js"; import assert, { equal, fail, rejects } from "assert"; import BN from "bn.js"; @@ -9,8 +10,8 @@ import { factorKeyCurve, TKeyTSSInitArgs, TSS_TAG_DEFAULT } from "../src/tss"; import { getLagrangeCoeffs } from "../src/util"; import { assignTssDkgKeys, fetchPostboxKeyAndSigs, generateKey, initStorageLayer } from "./helpers"; -const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolean; spSecp256k1: boolean }) => { - const { TSS_KEY_TYPE, legacyFlag, spSecp256k1 } = params; +const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolean; spSecp256k1: boolean; verifierId: string }) => { + const { TSS_KEY_TYPE, legacyFlag, spSecp256k1, verifierId } = params; const ecFactor = factorKeyCurve; const ecTSS = new EC(TSS_KEY_TYPE); @@ -94,7 +95,7 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea const deviceTSSIndex = 3; sp.verifierName = "torus-test-health"; - sp.verifierId = "testTss@example.com"; + sp.verifierId = verifierId; const { postboxkey, signatures } = await fetchPostboxKeyAndSigs({ serviceProvider: sp, verifierName: sp.verifierName, @@ -899,5 +900,6 @@ TEST_KEY_TYPES.forEach((TSS_KEY_TYPE) => { TSS_KEY_TYPE, legacyFlag: false, spSecp256k1: true, + verifierId: randomId(), }); }); From 5a42797dc4f3c4b9cd182348b0a645c1ae1a0bc5 Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 21 Feb 2025 21:07:37 +0800 Subject: [PATCH 10/22] fix: remove unsued this.tssTag --- packages/tss/src/tss.ts | 257 ++++++++++++++++++++-------------------- 1 file changed, 130 insertions(+), 127 deletions(-) diff --git a/packages/tss/src/tss.ts b/packages/tss/src/tss.ts index 328b273c..45e83337 100644 --- a/packages/tss/src/tss.ts +++ b/packages/tss/src/tss.ts @@ -99,10 +99,6 @@ export class TKeyTSS extends TKey { this.legacyMetadataFlag = legacyMetadataFlag; } - public get tssTag(): string { - return this._tssTag; - } - // public get tssKeyType(): KeyType { // return this._tssKeyType; // } @@ -119,7 +115,7 @@ export class TKeyTSS extends TKey { const { tssTag, accountSalt } = value; if (tssTag !== tbTss._tssTag) { - throw CoreError.default(`tssTag mismatch: ${tssTag} !== ${tbTss.tssTag}`); + throw CoreError.default(`tssTag mismatch: ${tssTag} !== ${tbTss._tssTag}`); } // if (tssKeyType !== tbTss.tssKeyType) { @@ -528,7 +524,6 @@ export class TKeyTSS extends TKey { const { importKey, factorPubs, newTSSIndexes, tssTag, tssKeyType } = params; - const oldTag = this._tssTag; const localTssTag = tssTag ?? TSS_TAG_DEFAULT; const tssData = this.metadata.getTssData(params.tssKeyType, localTssTag); @@ -538,124 +533,119 @@ export class TKeyTSS extends TKey { const ec = getKeyCurve(tssKeyType); - try { - const { selectedServers = [], authSignatures = [] } = serverOpts || {}; - - if (!localTssTag) throw CoreError.default(`invalid param, tag is required`); - if (!factorPubs || factorPubs.length === 0) throw CoreError.default(`invalid param, newFactorPub is required`); - if (!newTSSIndexes || newTSSIndexes.length === 0) throw CoreError.default(`invalid param, newTSSIndex is required`); - if (authSignatures.length === 0) throw CoreError.default(`invalid param, authSignatures is required`); - - // const existingFactorPubs = tssData.factorPubs; - // if (existingFactorPubs?.length > 0) { - // throw CoreError.default(`Duplicate account tag, please use a unique tag for importing key`); - // } - - const importScalar = await (async () => { - if (tssKeyType === KeyType.secp256k1) { - return new BN(importKey); - } else if (tssKeyType === KeyType.ed25519) { - // Store seed in metadata. - const domainKey = getEd25519SeedStoreDomainKey(localTssTag); - const result = this.metadata.getGeneralStoreDomain(domainKey) as Record; - if (result) { - throw new Error("Seed already exists"); - } - - const { scalar } = getEd25519KeyPairFromSeed(importKey); - const encKey = Buffer.from(getSecpKeyFromEd25519(scalar).point.encodeCompressed("hex"), "hex"); - const msg = await encrypt(encKey, importKey); - this.metadata.setGeneralStoreDomain(domainKey, { message: msg }); - - return scalar; - } - throw new Error("Invalid key type"); - })(); + const { selectedServers = [], authSignatures = [] } = serverOpts || {}; - if (!importScalar || importScalar.toString("hex") === "0") { - throw new Error("Invalid importedKey"); - } + if (!localTssTag) throw CoreError.default(`invalid param, tag is required`); + if (!factorPubs || factorPubs.length === 0) throw CoreError.default(`invalid param, newFactorPub is required`); + if (!newTSSIndexes || newTSSIndexes.length === 0) throw CoreError.default(`invalid param, newTSSIndex is required`); + if (authSignatures.length === 0) throw CoreError.default(`invalid param, authSignatures is required`); - const tssIndexes = newTSSIndexes; - const existingNonce = tssData?.tssNonce ?? 0; - const newTssNonce: number = existingNonce && existingNonce > 0 ? existingNonce + 1 : 0; - const verifierAndVerifierID = this.serviceProvider.getVerifierNameVerifierId(); - const label = `${verifierAndVerifierID}\u0015${localTssTag}\u0016${newTssNonce}`; - const tssPubKey = hexPoint(ec.g.mul(importScalar)); - const rssNodeDetails = await this._getRssNodeDetails(); - const { pubKey: newTSSServerPub, nodeIndexes } = await this.serviceProvider.getTSSPubKey(localTssTag, newTssNonce, tssKeyType); - let finalSelectedServers = selectedServers; - - if (nodeIndexes?.length > 0) { - if (selectedServers.length) { - finalSelectedServers = nodeIndexes.slice(0, Math.min(selectedServers.length, nodeIndexes.length)); - } else { - finalSelectedServers = nodeIndexes.slice(0, 3); + // const existingFactorPubs = tssData.factorPubs; + // if (existingFactorPubs?.length > 0) { + // throw CoreError.default(`Duplicate account tag, please use a unique tag for importing key`); + // } + + const importScalar = await (async () => { + if (tssKeyType === KeyType.secp256k1) { + return new BN(importKey); + } else if (tssKeyType === KeyType.ed25519) { + // Store seed in metadata. + const domainKey = getEd25519SeedStoreDomainKey(localTssTag); + const result = this.metadata.getGeneralStoreDomain(domainKey) as Record; + if (result) { + throw new Error("Seed already exists"); } - } else if (selectedServers?.length === 0) { - finalSelectedServers = randomSelection( - new Array(rssNodeDetails.serverEndpoints.length).fill(null).map((_, i) => i + 1), - Math.ceil(rssNodeDetails.serverEndpoints.length / 2) - ); + + const { scalar } = getEd25519KeyPairFromSeed(importKey); + const encKey = Buffer.from(getSecpKeyFromEd25519(scalar).point.encodeCompressed("hex"), "hex"); + const msg = await encrypt(encKey, importKey); + this.metadata.setGeneralStoreDomain(domainKey, { message: msg }); + + return scalar; } + throw new Error("Invalid key type"); + })(); - const { serverEndpoints, serverPubKeys, serverThreshold } = rssNodeDetails; + if (!importScalar || importScalar.toString("hex") === "0") { + throw new Error("Invalid importedKey"); + } - const rssClient = new RSSClient({ - serverEndpoints, - serverPubKeys, - serverThreshold, - tssPubKey, - keyType: tssKeyType, - }); + const tssIndexes = newTSSIndexes; + const existingNonce = tssData?.tssNonce ?? 0; + const newTssNonce: number = existingNonce && existingNonce > 0 ? existingNonce + 1 : 0; + const verifierAndVerifierID = this.serviceProvider.getVerifierNameVerifierId(); + const label = `${verifierAndVerifierID}\u0015${localTssTag}\u0016${newTssNonce}`; + const tssPubKey = hexPoint(ec.g.mul(importScalar)); + const rssNodeDetails = await this._getRssNodeDetails(); + const { pubKey: newTSSServerPub, nodeIndexes } = await this.serviceProvider.getTSSPubKey(localTssTag, newTssNonce, tssKeyType); + let finalSelectedServers = selectedServers; - const refreshResponses = await rssClient.import({ - importKey: importScalar, - dkgNewPub: pointToHex(newTSSServerPub), - selectedServers: finalSelectedServers, - factorPubs: factorPubs.map((f) => pointToHex(f)), - targetIndexes: tssIndexes, - newLabel: label, - sigs: authSignatures, - }); - const secondCommit = newTSSServerPub.toEllipticPoint(ec).add(ecPoint(ec, tssPubKey).neg()); - const newTSSCommits = [ - Point.fromJSON(tssPubKey), - Point.fromJSON({ x: secondCommit.getX().toString(16, 64), y: secondCommit.getY().toString(16, 64) }), - ]; - const factorEncs: { - [factorPubID: string]: FactorEnc; - } = {}; - for (let i = 0; i < refreshResponses.length; i++) { - const refreshResponse = refreshResponses[i]; - factorEncs[refreshResponse.factorPub.x.padStart(64, "0")] = { - type: "hierarchical", - tssIndex: refreshResponse.targetIndex, - userEnc: refreshResponse.userFactorEnc, - serverEncs: refreshResponse.serverFactorEncs, - }; - } - this.metadata.updateTSSData({ - tssKeyType, - tssTag: localTssTag, - tssNonce: newTssNonce, - tssPolyCommits: newTSSCommits, - factorPubs, - factorEncs, - }); - if (!this._accountSalt) { - const accountSalt = generateSalt(getEcCurve(tssKeyType)); - await this._setTKeyStoreItem(TSS_MODULE, { - id: "accountSalt", - value: accountSalt, - } as IAccountSaltStore); - this._accountSalt = accountSalt; + if (nodeIndexes?.length > 0) { + if (selectedServers.length) { + finalSelectedServers = nodeIndexes.slice(0, Math.min(selectedServers.length, nodeIndexes.length)); + } else { + finalSelectedServers = nodeIndexes.slice(0, 3); } - await this._syncShareMetadata(); - } catch (error) { - this._tssTag = oldTag; - throw error; + } else if (selectedServers?.length === 0) { + finalSelectedServers = randomSelection( + new Array(rssNodeDetails.serverEndpoints.length).fill(null).map((_, i) => i + 1), + Math.ceil(rssNodeDetails.serverEndpoints.length / 2) + ); } + + const { serverEndpoints, serverPubKeys, serverThreshold } = rssNodeDetails; + + const rssClient = new RSSClient({ + serverEndpoints, + serverPubKeys, + serverThreshold, + tssPubKey, + keyType: tssKeyType, + }); + + const refreshResponses = await rssClient.import({ + importKey: importScalar, + dkgNewPub: pointToHex(newTSSServerPub), + selectedServers: finalSelectedServers, + factorPubs: factorPubs.map((f) => pointToHex(f)), + targetIndexes: tssIndexes, + newLabel: label, + sigs: authSignatures, + }); + const secondCommit = newTSSServerPub.toEllipticPoint(ec).add(ecPoint(ec, tssPubKey).neg()); + const newTSSCommits = [ + Point.fromJSON(tssPubKey), + Point.fromJSON({ x: secondCommit.getX().toString(16, 64), y: secondCommit.getY().toString(16, 64) }), + ]; + const factorEncs: { + [factorPubID: string]: FactorEnc; + } = {}; + for (let i = 0; i < refreshResponses.length; i++) { + const refreshResponse = refreshResponses[i]; + factorEncs[refreshResponse.factorPub.x.padStart(64, "0")] = { + type: "hierarchical", + tssIndex: refreshResponse.targetIndex, + userEnc: refreshResponse.userFactorEnc, + serverEncs: refreshResponse.serverFactorEncs, + }; + } + this.metadata.updateTSSData({ + tssKeyType, + tssTag: localTssTag, + tssNonce: newTssNonce, + tssPolyCommits: newTSSCommits, + factorPubs, + factorEncs, + }); + if (!this._accountSalt) { + const accountSalt = generateSalt(getEcCurve(tssKeyType)); + await this._setTKeyStoreItem(TSS_MODULE, { + id: "accountSalt", + value: accountSalt, + } as IAccountSaltStore); + this._accountSalt = accountSalt; + } + await this._syncShareMetadata(); } /** @@ -677,7 +667,7 @@ export class TKeyTSS extends TKey { if (!this.secp256k1Key) throw new Error("Tkey is not reconstructed"); const tssData = this.metadata.getTssData(keyType, tssTag); - if (!tssData?.tssPolyCommits?.length) throw new Error(`tss key has not been initialized for tssTag ${this.tssTag}`); + if (!tssData?.tssPolyCommits?.length) throw new Error(`tss key has not been initialized for tssTag ${tssTag}`); const { tssIndex } = await this.getTSSShare(factorKey, { keyType, tssTag }); // Assumption that there are only index 2 and 3 for tss shares @@ -752,8 +742,14 @@ export class TKeyTSS extends TKey { * @param remoteFactorPub - Factor Pub for remote share. * @param signatures - Signatures for authentication against RSS servers. */ - async remoteRefreshTssShares(params: { factorPubs: Point[]; tssIndices: number[]; remoteClient: IRemoteClientState; keyType: KeyType }) { - const { factorPubs, tssIndices, remoteClient, keyType } = params; + async remoteRefreshTssShares(params: { + factorPubs: Point[]; + tssIndices: number[]; + remoteClient: IRemoteClientState; + keyType: KeyType; + tssTag: string; + }) { + const { factorPubs, tssIndices, remoteClient, keyType, tssTag } = params; const rssNodeDetails = await this._getRssNodeDetails(); const { serverEndpoints, serverPubKeys, serverThreshold } = rssNodeDetails; let finalSelectedServers = randomSelection( @@ -767,7 +763,7 @@ export class TKeyTSS extends TKey { const tssCommits = tssData.tssPolyCommits; const tssNonce: number = tssData.tssNonce || 0; - const { pubKey: newTSSServerPub, nodeIndexes } = await this.serviceProvider.getTSSPubKey(this.tssTag, tssNonce + 1, keyType); + const { pubKey: newTSSServerPub, nodeIndexes } = await this.serviceProvider.getTSSPubKey(tssTag, tssNonce + 1, keyType); // move to pre-refresh if (nodeIndexes?.length > 0) { finalSelectedServers = nodeIndexes.slice(0, Math.min(serverEndpoints.length, nodeIndexes.length)); @@ -815,8 +811,14 @@ export class TKeyTSS extends TKey { }); } - async remoteAddFactorPub(params: { newFactorPub: Point; newFactorTSSIndex: number; remoteClient: IRemoteClientState; keyType: KeyType }) { - const { newFactorPub, newFactorTSSIndex, remoteClient, keyType } = params; + async remoteAddFactorPub(params: { + newFactorPub: Point; + newFactorTSSIndex: number; + remoteClient: IRemoteClientState; + keyType: KeyType; + tssTag: string; + }) { + const { newFactorPub, newFactorTSSIndex, remoteClient, keyType, tssTag } = params; // const ed25519TssMetadata = this.metadata.getTssData(KeyType.ed25519); const localTssTag = TSS_TAG_DEFAULT; const secp256k1TssMetadata = this.metadata.getTssData(KeyType.secp256k1, localTssTag); @@ -830,13 +832,13 @@ export class TKeyTSS extends TKey { tssIndices: updatedTSSIndexes, remoteClient, keyType, + tssTag, }); } - async remoteDeleteFactorPub(params: { factorPubToDelete: Point; remoteClient: IRemoteClientState; keyType: KeyType }) { - const { factorPubToDelete, remoteClient, keyType } = params; - const localTssTag = TSS_TAG_DEFAULT; - const tssData = this.metadata.getTssData(keyType, localTssTag); + async remoteDeleteFactorPub(params: { factorPubToDelete: Point; remoteClient: IRemoteClientState; keyType: KeyType; tssTag: string }) { + const { factorPubToDelete, remoteClient, keyType, tssTag } = params; + const tssData = this.metadata.getTssData(keyType, tssTag); const existingFactorPubs = tssData.factorPubs; const factorIndex = existingFactorPubs.findIndex((p) => p.x.eq(factorPubToDelete.x)); if (factorIndex === -1) { @@ -844,13 +846,14 @@ export class TKeyTSS extends TKey { } const updatedFactorPubs = existingFactorPubs.slice(); updatedFactorPubs.splice(factorIndex, 1); - const updatedTSSIndexes = updatedFactorPubs.map((fb) => this.getFactorEncs(fb, keyType, localTssTag).tssIndex); + const updatedTSSIndexes = updatedFactorPubs.map((fb) => this.getFactorEncs(fb, keyType, tssTag).tssIndex); await this.remoteRefreshTssShares({ factorPubs: updatedFactorPubs, tssIndices: updatedTSSIndexes, remoteClient, keyType, + tssTag, }); } @@ -1242,7 +1245,7 @@ export class TKeyTSS extends TKey { if (found.length > 1) throw CoreError.default("found two or more factorPubs that match, error in metadata"); const updatedFactorPubs = existingFactorPubs.filter((f) => !f.x.eq(deleteFactorPub.x) || !f.y.eq(deleteFactorPub.y)); - this.metadata.updateTSSData({ tssKeyType: keyType, tssTag: this.tssTag, factorPubs: updatedFactorPubs }); + this.metadata.updateTSSData({ tssKeyType: keyType, tssTag, factorPubs: updatedFactorPubs }); const rssNodeDetails = await this._getRssNodeDetails(); const randomSelectedServers = randomSelection( new Array(rssNodeDetails.serverEndpoints.length).fill(null).map((_, i) => i + 1), From 0189290177509d3ebd19c3bda8a797580d724b1c Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 26 Feb 2025 02:38:49 +0800 Subject: [PATCH 11/22] fix: comments --- packages/core/src/core.ts | 2 -- packages/core/src/metadata.ts | 5 ----- packages/tss/src/tss.ts | 20 ++++++++------------ packages/tss/test/helpers.ts | 3 --- 4 files changed, 8 insertions(+), 22 deletions(-) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index ab633fdb..709adb6c 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -151,8 +151,6 @@ class ThresholdKey implements ITKey { manualSync, lastFetchedCloudMetadata, serverTimeOffset, - - // legacyMetadata flag legacyMetadataFlag, } = value; const { storageLayer, serviceProvider, modules } = args; diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index 57ed0bfb..72cbfdf4 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -29,8 +29,6 @@ import CoreError from "./errors"; import { polyCommitmentEval } from "./lagrangeInterpolatePolynomial"; import { TssMetadata } from "./tssMetadata"; -export type SupportedCurve = "ed25519" | "sec"; - export const LEGACY_METADATA_VERSION = "0.0.1"; export const METADATA_VERSION = "1.0.0"; @@ -62,9 +60,6 @@ export class Metadata implements IMetadata { [tssTag: string]: { [curveType: string]: TssMetadata; }; - // [tssTag: string]: Record<"secp256k1" "ed25519", TssMetadata>; - // secp256k1: TssMetadata; - // ed25519: TssMetadata; }; version = METADATA_VERSION; diff --git a/packages/tss/src/tss.ts b/packages/tss/src/tss.ts index 45e83337..efd4a169 100644 --- a/packages/tss/src/tss.ts +++ b/packages/tss/src/tss.ts @@ -146,6 +146,9 @@ export class TKeyTSS extends TKey { return tbJson; } + /** + * Initializes tkey instance and added checking of invalid config. + */ async initialize(params?: TKeyInitArgs): Promise { if (this.serviceProvider.customAuthArgs.keyType === KeyType.ed25519 && !this.legacyMetadataFlag) { throw CoreError.default("legacyMetadataFlag need to be set for ed25519 network's postboxkey "); @@ -154,9 +157,8 @@ export class TKeyTSS extends TKey { } /** - * Initializes this instance. If a TSS account does not exist, creates one - * under the given factor key. `skipTssInit` skips TSS account creation and - * can be used with `importTssKey` to just import an existing account instead. + * Initializes Tss Curve with Secp256k1 keyType + * will use same factorPubs from Ed24459 curves if existing Ed25519 curve present */ async initializeTssSecp256k1(params: Omit): Promise { // only support service provider with secp256k1 key type @@ -258,9 +260,8 @@ export class TKeyTSS extends TKey { } /** - * Initializes this instance. If a TSS account does not exist, creates one - * under the given factor key. `skipTssInit` skips TSS account creation and - * can be used with `importTssKey` to just import an existing account instead. + * Initializes Tss Curve with Ed25519 keyType + * will use same factorPubs from Secp256k1 if existing Secp256k1 curve present */ async initializeTssEd25519(params: Omit & { importKey: Buffer }): Promise { // only support service provider with secp256k1 key type @@ -528,7 +529,7 @@ export class TKeyTSS extends TKey { const tssData = this.metadata.getTssData(params.tssKeyType, localTssTag); if (tssData) { - throw CoreError.default("TSS account already exists"); + throw CoreError.default("Duplicate account tag, please use a unique tag for importing key"); } const ec = getKeyCurve(tssKeyType); @@ -540,11 +541,6 @@ export class TKeyTSS extends TKey { if (!newTSSIndexes || newTSSIndexes.length === 0) throw CoreError.default(`invalid param, newTSSIndex is required`); if (authSignatures.length === 0) throw CoreError.default(`invalid param, authSignatures is required`); - // const existingFactorPubs = tssData.factorPubs; - // if (existingFactorPubs?.length > 0) { - // throw CoreError.default(`Duplicate account tag, please use a unique tag for importing key`); - // } - const importScalar = await (async () => { if (tssKeyType === KeyType.secp256k1) { return new BN(importKey); diff --git a/packages/tss/test/helpers.ts b/packages/tss/test/helpers.ts index 0a3bfd93..90f83917 100644 --- a/packages/tss/test/helpers.ts +++ b/packages/tss/test/helpers.ts @@ -72,9 +72,6 @@ export async function assignTssDkgKeys(opts: { }) { const { serviceProvider, verifierName: verifier, verifierId } = opts; - serviceProvider.verifierName = verifier; - serviceProvider.verifierId = verifierId; - let { tssTag, maxTSSNonceToSimulate } = opts; tssTag = tssTag || "default"; maxTSSNonceToSimulate = maxTSSNonceToSimulate || 1; From fc2f29883a83d4bbe42dc3633ef2f5f879bd0c9f Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 26 Feb 2025 02:55:12 +0800 Subject: [PATCH 12/22] fix: comment remove getEcCurve --- packages/tss/src/tss.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/tss/src/tss.ts b/packages/tss/src/tss.ts index efd4a169..f9a95445 100644 --- a/packages/tss/src/tss.ts +++ b/packages/tss/src/tss.ts @@ -15,7 +15,7 @@ import { import { CoreError, TKey } from "@tkey/core"; import { post } from "@toruslabs/http-helpers"; import { dotProduct, ecPoint, hexPoint, PointHex, randomSelection, RSSClient } from "@toruslabs/rss-client"; -import { getEcCurve, getEd25519ExtendedPublicKey as getEd25519KeyPairFromSeed, getKeyCurve, getSecpKeyFromEd25519 } from "@toruslabs/torus.js"; +import { getEd25519ExtendedPublicKey as getEd25519KeyPairFromSeed, getKeyCurve, getSecpKeyFromEd25519 } from "@toruslabs/torus.js"; import BN from "bn.js"; import { ec as EC } from "elliptic"; import { keccak256 } from "ethereum-cryptography/keccak"; @@ -203,7 +203,7 @@ export class TKeyTSS extends TKey { this.metadata.updateTSSData({ tssKeyType: KeyType.secp256k1, tssTag: localTssTag, tssNonce: 0, tssPolyCommits, factorPubs, factorEncs }); - const ecCurve = getEcCurve(KeyType.secp256k1); + const ecCurve = getKeyCurve(KeyType.secp256k1); const accountSalt = generateSalt(ecCurve); await this._setTKeyStoreItem(TSS_MODULE, { id: "accountSalt", @@ -634,7 +634,7 @@ export class TKeyTSS extends TKey { factorEncs, }); if (!this._accountSalt) { - const accountSalt = generateSalt(getEcCurve(tssKeyType)); + const accountSalt = generateSalt(getKeyCurve(tssKeyType)); await this._setTKeyStoreItem(TSS_MODULE, { id: "accountSalt", value: accountSalt, @@ -686,7 +686,7 @@ export class TKeyTSS extends TKey { const { tssShare: tempShare, tssIndex: tempIndex } = await this.getTSSShare(tempFactorKey, { keyType, tssTag }); // reconstruct final key using sss - const ec = getEcCurve(keyType); + const ec = getKeyCurve(keyType); const tssKey = lagrangeInterpolation(ec, [tempShare, factorShare], [new BN(tempIndex), new BN(factorIndex)]); // delete created tss share From 6ff795da005f44ca198d34df2357558aa8f2e58c Mon Sep 17 00:00:00 2001 From: ieow Date: Sat, 22 Feb 2025 04:19:36 +0800 Subject: [PATCH 13/22] feat: refactor to use setTssTag --- packages/tss/src/tss.ts | 123 ++++++++++--------- packages/tss/test/tssMultiCurveTestCases.ts | 75 +++++------ packages/tss/test/tssSingleCurveTestCases.ts | 72 +++++------ 3 files changed, 122 insertions(+), 148 deletions(-) diff --git a/packages/tss/src/tss.ts b/packages/tss/src/tss.ts index f9a95445..da0d963f 100644 --- a/packages/tss/src/tss.ts +++ b/packages/tss/src/tss.ts @@ -156,6 +156,10 @@ export class TKeyTSS extends TKey { return super.initialize(params); } + public setTssTag(tssTag: string) { + this._tssTag = tssTag; + } + /** * Initializes Tss Curve with Secp256k1 keyType * will use same factorPubs from Ed24459 curves if existing Ed25519 curve present @@ -189,7 +193,7 @@ export class TKeyTSS extends TKey { } const backupMetadata = this.metadata.clone(); - const localTssTag = TSS_TAG_DEFAULT; + const localTssTag = this.tssTag; try { if (!importKey) { // if tss shares have not been created for this tssTag, create new tss sharing @@ -347,18 +351,18 @@ export class TKeyTSS extends TKey { /** * Returns the encrypted data associated with the given factor public key. */ - getFactorEncs(factorPub: Point, keyType: KeyType, tssTag: string = TSS_TAG_DEFAULT): FactorEnc { - const localTssTag = tssTag; + getFactorEncs(factorPub: Point, keyType: KeyType): FactorEnc { + const { tssTag } = this; if (!this.metadata) throw CoreError.metadataUndefined(); - const tssData = this.metadata.getTssData(keyType, localTssTag); + const tssData = this.metadata.getTssData(keyType, tssTag); if (!tssData) throw CoreError.default("no factor encs mapping"); const { factorPubs } = tssData; - if (!factorPubs) throw CoreError.default(`no factor pubs for this tssTag ${localTssTag}`); + if (!factorPubs) throw CoreError.default(`no factor pubs for this tssTag ${tssTag}`); if (factorPubs.filter((f) => f.x.cmp(factorPub.x) === 0 && f.y.cmp(factorPub.y) === 0).length === 0) - throw CoreError.default(`factor pub ${factorPub} not found for tssTag ${localTssTag}`); - if (!tssData.factorEncs) throw CoreError.default(`no factor encs for tssTag ${localTssTag}`); + throw CoreError.default(`factor pub ${factorPub} not found for tssTag ${tssTag}`); + if (!tssData.factorEncs) throw CoreError.default(`no factor encs for tssTag ${tssTag}`); const factorPubID = factorPub.x.toString(16, 64); return tssData.factorEncs[factorPubID]; } @@ -370,7 +374,6 @@ export class TKeyTSS extends TKey { factorKey: BN, opts: { keyType: KeyType; - tssTag: string; threshold?: number; accountIndex?: number; coefficient?: BN; @@ -380,9 +383,8 @@ export class TKeyTSS extends TKey { tssShare: BN; }> { const factorPub = getPubKeyPoint(factorKey, factorKeyCurve); - const localTssTag = opts.tssTag; - const factorEncs = this.getFactorEncs(factorPub, opts.keyType, localTssTag); + const factorEncs = this.getFactorEncs(factorPub, opts.keyType); const { userEnc, serverEncs, tssIndex, type } = factorEncs; const userDecryption = await decrypt(Buffer.from(factorKey.toString(16, 64), "hex"), userEnc); const serverDecryptions = await Promise.all( @@ -399,7 +401,7 @@ export class TKeyTSS extends TKey { }); const ec = getKeyCurve(opts.keyType); - const tssCommits = this.getTSSCommits(opts.keyType, localTssTag).map((p) => { + const tssCommits = this.getTSSCommits(opts.keyType).map((p) => { return ec.keyFromPublic({ x: p.x.toString(16, 64), y: p.y.toString(16, 64) }).getPublic(); }); @@ -454,14 +456,14 @@ export class TKeyTSS extends TKey { * Returns the TSS public key and the curve points corresponding to secret key * shares, as stored in Metadata. */ - getTSSCommits(tssKeyType: KeyType, tssTag: string): Point[] { + getTSSCommits(tssKeyType: KeyType): Point[] { if (!this.metadata) throw CoreError.metadataUndefined(); - const localTssTag = tssTag; - const tssData = this.metadata.getTssData(tssKeyType, localTssTag); + const { tssTag } = this; + const tssData = this.metadata.getTssData(tssKeyType, tssTag); if (!tssData) throw CoreError.default("no tss data"); const { tssPolyCommits } = tssData; - if (!tssPolyCommits) throw CoreError.default(`tss poly commits not found for tssTag ${localTssTag}`); + if (!tssPolyCommits) throw CoreError.default(`tss poly commits not found for tssTag ${tssTag}`); if (tssPolyCommits.length === 0) throw CoreError.default("tss poly commits is empty"); return tssPolyCommits; } @@ -469,9 +471,9 @@ export class TKeyTSS extends TKey { /** * Returns the TSS public key. */ - getTSSPub(tssKeyType: KeyType, tssTag: string, accountIndex?: number): Point { + getTSSPub(tssKeyType: KeyType, accountIndex?: number): Point { const ec = getKeyCurve(tssKeyType); - const tssCommits = this.getTSSCommits(tssKeyType, tssTag); + const tssCommits = this.getTSSCommits(tssKeyType); if (accountIndex && accountIndex > 0) { // Add account nonce to pub key. const nonce = this.computeAccountNonce(accountIndex); @@ -525,7 +527,11 @@ export class TKeyTSS extends TKey { const { importKey, factorPubs, newTSSIndexes, tssTag, tssKeyType } = params; - const localTssTag = tssTag ?? TSS_TAG_DEFAULT; + const oldTag = this._tssTag; + + // update current tssTag + this._tssTag = tssTag; + const localTssTag = this._tssTag; const tssData = this.metadata.getTssData(params.tssKeyType, localTssTag); if (tssData) { @@ -655,9 +661,10 @@ export class TKeyTSS extends TKey { authSignatures: string[]; accountIndex?: number; keyType: KeyType; - tssTag: string; }): Promise { - const { factorKey, selectedServers, authSignatures, accountIndex, keyType, tssTag } = tssOptions; + const { factorKey, selectedServers, authSignatures, accountIndex, keyType } = tssOptions; + + const { tssTag } = this; if (!this.metadata) throw CoreError.metadataUndefined("metadata is undefined"); if (!this.secp256k1Key) throw new Error("Tkey is not reconstructed"); @@ -665,7 +672,7 @@ export class TKeyTSS extends TKey { const tssData = this.metadata.getTssData(keyType, tssTag); if (!tssData?.tssPolyCommits?.length) throw new Error(`tss key has not been initialized for tssTag ${tssTag}`); - const { tssIndex } = await this.getTSSShare(factorKey, { keyType, tssTag }); + const { tssIndex } = await this.getTSSShare(factorKey, { keyType }); // Assumption that there are only index 2 and 3 for tss shares // create complement index share const tempShareIndex = tssIndex === 2 ? 3 : 2; @@ -679,11 +686,10 @@ export class TKeyTSS extends TKey { authSignatures, selectedServers, refreshShares: true, - tssTag, }); - const { tssShare: factorShare, tssIndex: factorIndex } = await this.getTSSShare(factorKey, { keyType, tssTag }); - const { tssShare: tempShare, tssIndex: tempIndex } = await this.getTSSShare(tempFactorKey, { keyType, tssTag }); + const { tssShare: factorShare, tssIndex: factorIndex } = await this.getTSSShare(factorKey, { keyType }); + const { tssShare: tempShare, tssIndex: tempIndex } = await this.getTSSShare(tempFactorKey, { keyType }); // reconstruct final key using sss const ec = getKeyCurve(keyType); @@ -695,7 +701,6 @@ export class TKeyTSS extends TKey { deleteFactorPub: tempFactorPub, authSignatures, selectedServers, - tssTag, }); // Derive key for account index. @@ -710,16 +715,12 @@ export class TKeyTSS extends TKey { * * Reconstructs the TSS private key and exports the ed25519 private key seed. */ - async _UNSAFE_exportTssEd25519Seed(tssOptions: { - factorKey: BN; - selectedServers?: number[]; - authSignatures: string[]; - tssTag: string; - }): Promise { + async _UNSAFE_exportTssEd25519Seed(tssOptions: { factorKey: BN; selectedServers?: number[]; authSignatures: string[] }): Promise { const edScalar = await this._UNSAFE_exportTssKey({ ...tssOptions, keyType: KeyType.ed25519 }); + const { tssTag } = this; // Try to export ed25519 seed. This is only available if import key was being used. - const domainKey = getEd25519SeedStoreDomainKey(tssOptions.tssTag); + const domainKey = getEd25519SeedStoreDomainKey(tssTag); const result = this.metadata.getGeneralStoreDomain(domainKey) as Record; const decKey = getSecpKeyFromEd25519(edScalar).scalar; @@ -752,7 +753,7 @@ export class TKeyTSS extends TKey { new Array(rssNodeDetails.serverEndpoints.length).fill(null).map((_, i) => i + 1), Math.ceil(rssNodeDetails.serverEndpoints.length / 2) ); - const localTssTag = TSS_TAG_DEFAULT; + const localTssTag = this.tssTag; const verifierNameVerifierId = this.serviceProvider.getVerifierNameVerifierId(); const tssData = this.metadata.getTssData(keyType, localTssTag); if (!tssData) throw CoreError.default("no tss data"); @@ -765,7 +766,7 @@ export class TKeyTSS extends TKey { finalSelectedServers = nodeIndexes.slice(0, Math.min(serverEndpoints.length, nodeIndexes.length)); } - const factorEnc = this.getFactorEncs(Point.fromSEC1(secp256k1, remoteClient.remoteFactorPub), keyType, localTssTag); + const factorEnc = this.getFactorEncs(Point.fromSEC1(secp256k1, remoteClient.remoteFactorPub), keyType); const dataRequired: RefreshRemoteTssParams = { factorEnc, @@ -816,11 +817,11 @@ export class TKeyTSS extends TKey { }) { const { newFactorPub, newFactorTSSIndex, remoteClient, keyType, tssTag } = params; // const ed25519TssMetadata = this.metadata.getTssData(KeyType.ed25519); - const localTssTag = TSS_TAG_DEFAULT; + const localTssTag = this.tssTag; const secp256k1TssMetadata = this.metadata.getTssData(KeyType.secp256k1, localTssTag); const existingFactorPubs = secp256k1TssMetadata.factorPubs; const updatedFactorPubs = existingFactorPubs.concat([newFactorPub]); - const existingTSSIndexes = existingFactorPubs.map((fb) => this.getFactorEncs(fb, keyType, localTssTag).tssIndex); + const existingTSSIndexes = existingFactorPubs.map((fb) => this.getFactorEncs(fb, keyType).tssIndex); const updatedTSSIndexes = existingTSSIndexes.concat([newFactorTSSIndex]); await this.remoteRefreshTssShares({ @@ -832,9 +833,10 @@ export class TKeyTSS extends TKey { }); } - async remoteDeleteFactorPub(params: { factorPubToDelete: Point; remoteClient: IRemoteClientState; keyType: KeyType; tssTag: string }) { - const { factorPubToDelete, remoteClient, keyType, tssTag } = params; - const tssData = this.metadata.getTssData(keyType, tssTag); + async remoteDeleteFactorPub(params: { factorPubToDelete: Point; remoteClient: IRemoteClientState; keyType: KeyType }) { + const { factorPubToDelete, remoteClient, keyType } = params; + const localTssTag = this.tssTag; + const tssData = this.metadata.getTssData(keyType, localTssTag); const existingFactorPubs = tssData.factorPubs; const factorIndex = existingFactorPubs.findIndex((p) => p.x.eq(factorPubToDelete.x)); if (factorIndex === -1) { @@ -842,7 +844,7 @@ export class TKeyTSS extends TKey { } const updatedFactorPubs = existingFactorPubs.slice(); updatedFactorPubs.splice(factorIndex, 1); - const updatedTSSIndexes = updatedFactorPubs.map((fb) => this.getFactorEncs(fb, keyType, tssTag).tssIndex); + const updatedTSSIndexes = updatedFactorPubs.map((fb) => this.getFactorEncs(fb, keyType).tssIndex); await this.remoteRefreshTssShares({ factorPubs: updatedFactorPubs, @@ -856,9 +858,9 @@ export class TKeyTSS extends TKey { async remoteCopyFactorPub(params: { newFactorPub: Point; tssIndex: number; remoteClient: IRemoteClientState; keyType: KeyType }) { const { newFactorPub, tssIndex, remoteClient, keyType } = params; const remoteFactorPub = Point.fromSEC1(secp256k1, remoteClient.remoteFactorPub); - const localTssTag = TSS_TAG_DEFAULT; - const factorEnc = this.getFactorEncs(remoteFactorPub, keyType, localTssTag); - const tssCommits = this.getTSSCommits(keyType, localTssTag).map((commit) => commit.toPointHex()); + const localTssTag = this.tssTag; + const factorEnc = this.getFactorEncs(remoteFactorPub, keyType); + const tssCommits = this.getTSSCommits(keyType).map((commit) => commit.toPointHex()); const dataRequired: CopyRemoteTssParams = { factorEnc, tssCommits, @@ -922,7 +924,7 @@ export class TKeyTSS extends TKey { ): Promise { if (!this.metadata) throw CoreError.metadataUndefined(); const { keyType } = serverOpts; - const localTssTag = tssTag ?? TSS_TAG_DEFAULT; + const localTssTag = tssTag; const tssData = this.metadata.getTssData(keyType, localTssTag); const tssCommits = tssData?.tssPolyCommits; @@ -1091,10 +1093,10 @@ export class TKeyTSS extends TKey { authSignatures: string[]; refreshShares?: boolean; updateMetadata?: boolean; - tssTag: string; }) { - const secp256k1Data = this.metadata.getTssData(KeyType.secp256k1, args.tssTag); - const ed25519Data = this.metadata.getTssData(KeyType.ed25519, args.tssTag); + const tssTag = this._tssTag; + const secp256k1Data = this.metadata.getTssData(KeyType.secp256k1, tssTag); + const ed25519Data = this.metadata.getTssData(KeyType.ed25519, tssTag); const allPromise = []; @@ -1123,19 +1125,19 @@ export class TKeyTSS extends TKey { refreshShares?: boolean; updateMetadata?: boolean; keyType: KeyType; - tssTag: string; }) { if (!this.metadata) throw CoreError.metadataUndefined("metadata is undefined"); if (!this.secp256k1Key) throw new Error("Tkey is not reconstructed"); - const { existingFactorKey, newFactorPub, newTSSIndex, selectedServers, authSignatures, refreshShares, keyType, tssTag } = args; + const { existingFactorKey, newFactorPub, newTSSIndex, selectedServers, authSignatures, refreshShares, keyType } = args; + const tssTag = this._tssTag; const tssData = this.metadata.getTssData(keyType, tssTag); - const { tssShare, tssIndex } = await this.getTSSShare(existingFactorKey, { keyType, tssTag }); + const { tssShare, tssIndex } = await this.getTSSShare(existingFactorKey, { keyType }); if (tssIndex !== newTSSIndex && !refreshShares) { throw CoreError.default("newTSSIndex does not match existing tssIndex, set refreshShares to true to refresh shares"); } - const localTssTag = tssTag ?? TSS_TAG_DEFAULT; + const localTssTag = tssTag; if (!refreshShares) { // Just copy data stored under factor key. @@ -1173,7 +1175,7 @@ export class TKeyTSS extends TKey { const finalServer = selectedServers || randomSelectedServers; - const existingTSSIndexes = existingFactorPubs.map((fb) => this.getFactorEncs(fb, keyType, localTssTag).tssIndex); + const existingTSSIndexes = existingFactorPubs.map((fb) => this.getFactorEncs(fb, keyType).tssIndex); const updatedTSSIndexes = existingTSSIndexes.concat([newTSSIndex]); // sync metadata by default @@ -1199,7 +1201,6 @@ export class TKeyTSS extends TKey { } public async deleteFactorPub(args: { - tssTag: string; factorKey: BN; deleteFactorPub: Point; selectedServers?: number[]; @@ -1207,10 +1208,10 @@ export class TKeyTSS extends TKey { updateMetadata?: boolean; }): Promise { const { updateMetadata, ...otherArgs } = args; + const { tssTag } = this; const allPromise = []; - if (this.metadata.getTssData(KeyType.secp256k1, args.tssTag)) - allPromise.push(this._deleteFactorPub({ ...otherArgs, keyType: KeyType.secp256k1 })); - if (this.metadata.getTssData(KeyType.ed25519, args.tssTag)) allPromise.push(this._deleteFactorPub({ ...otherArgs, keyType: KeyType.ed25519 })); + if (this.metadata.getTssData(KeyType.secp256k1, tssTag)) allPromise.push(this._deleteFactorPub({ ...otherArgs, keyType: KeyType.secp256k1 })); + if (this.metadata.getTssData(KeyType.ed25519, tssTag)) allPromise.push(this._deleteFactorPub({ ...otherArgs, keyType: KeyType.ed25519 })); await Promise.all(allPromise); if (updateMetadata) await this._syncShareMetadata(); } @@ -1226,15 +1227,15 @@ export class TKeyTSS extends TKey { authSignatures: string[]; updateMetadata?: boolean; keyType: KeyType; - tssTag: string; }): Promise { if (!this.metadata) throw CoreError.metadataUndefined("metadata is undefined"); if (!this.secp256k1Key) throw new Error("Tkey is not reconstructed"); - const { factorKey, deleteFactorPub, selectedServers, authSignatures, keyType, tssTag } = args; + const { factorKey, deleteFactorPub, selectedServers, authSignatures, keyType } = args; + const tssTag = this._tssTag; const tssData = this.metadata.getTssData(keyType, tssTag); const existingFactorPubs = tssData.factorPubs; - const { tssShare, tssIndex } = await this.getTSSShare(factorKey, { keyType, tssTag }); + const { tssShare, tssIndex } = await this.getTSSShare(factorKey, { keyType }); const found = existingFactorPubs.filter((f) => f.x.eq(deleteFactorPub.x) && f.y.eq(deleteFactorPub.y)); if (found.length === 0) throw CoreError.default("could not find factorPub to delete"); @@ -1248,7 +1249,7 @@ export class TKeyTSS extends TKey { Math.ceil(rssNodeDetails.serverEndpoints.length / 2) ); const finalServer = selectedServers || randomSelectedServers; - const updatedTSSIndexes = updatedFactorPubs.map((fb) => this.getFactorEncs(fb, keyType, tssTag).tssIndex); + const updatedTSSIndexes = updatedFactorPubs.map((fb) => this.getFactorEncs(fb, keyType).tssIndex); const updateMetadata = args.updateMetadata !== undefined ? args.updateMetadata : true; @@ -1308,6 +1309,8 @@ export class TKeyTSS extends TKey { const a0Pub = tss1PubKey.mul(L1_0).add(tss2PubKey.mul(LIndex_0)); const a1Pub = tss1PubKey.add(a0Pub.neg()); + // y = mx +c + // a0Pub = c, a1Pub = y0 -y1 = m const tssPolyCommits = [Point.fromElliptic(a0Pub), Point.fromElliptic(a1Pub)]; const factorPubs = [factorPub]; const factorEncs: { [factorPubID: string]: FactorEnc } = {}; diff --git a/packages/tss/test/tssMultiCurveTestCases.ts b/packages/tss/test/tssMultiCurveTestCases.ts index 8c46459a..68592043 100644 --- a/packages/tss/test/tssMultiCurveTestCases.ts +++ b/packages/tss/test/tssMultiCurveTestCases.ts @@ -149,13 +149,13 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea if (tb1.secp256k1Key.cmp(reconstructedKey.secp256k1Key) !== 0) { fail("key should be able to be reconstructed"); } - const { tssShare: tss2 } = await tb1.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); + const { tssShare: tss2 } = await tb1.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); // compute public key of the tss share const tss2Pub = ecTSS.g.mul(tss2); // get tss pub key from tss commits - const tssCommits = tb1.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); + const tssCommits = tb1.getTSSCommits(TSS_KEY_TYPE); const tssCommitA0 = tssCommits[0].toEllipticPoint(ecTSS); const tssCommitA1 = tssCommits[1].toEllipticPoint(ecTSS); @@ -228,8 +228,8 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea fail("key should be able to be reconstructed"); } - const { tssShare: tss2 } = await tb1.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); - const tssCommits = tb1.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); + const { tssShare: tss2 } = await tb1.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); + const tssCommits = tb1.getTSSCommits(TSS_KEY_TYPE); const tssPrivKey = getLagrangeCoeffs(ecTSS, [1, deviceTSSIndex], 1) .mul(tss1) @@ -238,7 +238,7 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea const tssPubKey = (ecTSS.g as EllipticPoint).mul(tssPrivKey); const tssCommits0 = tssCommits[0].toEllipticPoint(ecTSS); - const tssPub = tb1.getTSSPub(TSS_KEY_TYPE, TSS_TAG_DEFAULT).toEllipticPoint(ecTSS); + const tssPub = tb1.getTSSPub(TSS_KEY_TYPE).toEllipticPoint(ecTSS); equal(tssPubKey.eq(tssCommits0), true); equal(tssPub.eq(tssPubKey), true); @@ -250,14 +250,14 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea const nonce = tb1.computeAccountNonce(accountIndex); return share.add(nonce).umod(ecTSS.n); })(); - const { tssShare: tss2Account } = await tb1.getTSSShare(factorKey, { accountIndex, keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); + const { tssShare: tss2Account } = await tb1.getTSSShare(factorKey, { accountIndex, keyType: TSS_KEY_TYPE }); const coefficient1 = getLagrangeCoeffs(ecTSS, [1, deviceTSSIndex], 1); const coefficient2 = getLagrangeCoeffs(ecTSS, [1, deviceTSSIndex], deviceTSSIndex); const tssKey = coefficient1.mul(tss1Account).add(coefficient2.mul(tss2Account)).umod(ecTSS.n); const tssKeyPub = (ecTSS.g as EllipticPoint).mul(tssKey); - const tssPubAccount = tb1.getTSSPub(TSS_KEY_TYPE, TSS_TAG_DEFAULT, accountIndex).toEllipticPoint(ecTSS); + const tssPubAccount = tb1.getTSSPub(TSS_KEY_TYPE, accountIndex).toEllipticPoint(ecTSS); equal(tssPubAccount.eq(tssKeyPub), true, "should equal account pub key"); } }); @@ -307,7 +307,8 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea // Check pub key. const ec = getEcCurve(TSS_KEY_TYPE); const importTssKeyPub = Point.fromScalar(importTssKey.scalar, ec); - const tssPub = await tb.getTSSPub(TSS_KEY_TYPE, "imported"); + // tb.tssTag = "imported"; + const tssPub = await tb.getTSSPub(TSS_KEY_TYPE); assert(tssPub.equals(importTssKeyPub)); // Check exported key. @@ -315,14 +316,13 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea factorKey, authSignatures: signatures, keyType: TSS_KEY_TYPE, - tssTag: TSS_TAG_DEFAULT, }); assert(exportedKey.eq(importTssKey.scalar)); if (TSS_KEY_TYPE === KeyType.ed25519) { + tb.setTssTag(TSS_TAG_DEFAULT); const seed = await tb._UNSAFE_exportTssEd25519Seed({ factorKey, authSignatures: signatures, - tssTag: TSS_TAG_DEFAULT, }); assert(seed.equals(importTssKey.raw)); } else { @@ -332,10 +332,9 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea authSignatures: signatures, accountIndex: 2, keyType: TSS_KEY_TYPE, - tssTag: TSS_TAG_DEFAULT, }); const exportedPubKeyIndex2 = Point.fromScalar(exportedKeyIndex2, ec); - const pubKeyIndex2 = tb.getTSSPub(TSS_KEY_TYPE, TSS_TAG_DEFAULT, 2); + const pubKeyIndex2 = tb.getTSSPub(TSS_KEY_TYPE, 2); assert(exportedPubKeyIndex2.equals(pubKeyIndex2)); } }); @@ -412,9 +411,8 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea } const { tssShare: retrievedTSS, tssIndex: retrievedTSSIndex } = await tb.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, - tssTag: TSS_TAG_DEFAULT, }); - const tssCommits = tb.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); + const tssCommits = tb.getTSSCommits(TSS_KEY_TYPE); const tssPrivKey = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], 1) .mul(serverDKGPrivKeys[0]) .add(getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], retrievedTSSIndex).mul(retrievedTSS)) @@ -447,10 +445,9 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea // for imported key const { tssShare: retrievedTSS1, tssIndex: retrievedTSSIndex1 } = await tb.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, - tssTag: "imported", }); - const tssCommits1 = tb.getTSSCommits(TSS_KEY_TYPE, "imported"); + const tssCommits1 = tb.getTSSCommits(TSS_KEY_TYPE); const tssPrivKey1 = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex1], 1) .mul(serverDKGPrivKeys1[0]) .add(getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex1], retrievedTSSIndex1).mul(retrievedTSS1)) @@ -466,7 +463,6 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea factorKey, selectedServers: [1, 2, 3], authSignatures: signatures, - tssTag: "imported", }); equal(seed.equals(importedKey), true); } @@ -482,18 +478,17 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea if (tb2.secp256k1Key.cmp(reconstructedKey2.secp256k1Key) !== 0) { fail("key should be able to be reconstructed"); } - const tssCommits2 = tb2.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); + const tssCommits2 = tb2.getTSSCommits(TSS_KEY_TYPE); const tssCommits20 = tssCommits2[0].toEllipticPoint(ecTSS); equal(tssPubKey.eq(tssCommits20), true); // switch to imported account - // tb2.tssTag = "imported"; + tb2.setTssTag("imported"); const { tssShare: retrievedTSSImported, tssIndex: retrievedTSSIndexImported } = await tb2.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, - tssTag: "imported", }); - const tssCommitsImported = tb2.getTSSCommits(TSS_KEY_TYPE, "imported"); + const tssCommitsImported = tb2.getTSSCommits(TSS_KEY_TYPE); const tssPrivKeyImported = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndexImported], 1) .mul(serverDKGPrivKeys1[0]) @@ -566,9 +561,8 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea } const { tssShare: retrievedTSS, tssIndex: retrievedTSSIndex } = await tb.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, - tssTag: TSS_TAG_DEFAULT, }); - const tssCommits = tb.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); + const tssCommits = tb.getTSSCommits(TSS_KEY_TYPE); const tssPrivKey = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], 1) .mul(serverDKGPrivKeys[0]) .add(getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], retrievedTSSIndex).mul(retrievedTSS)) @@ -601,14 +595,14 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea // for imported key { - const finalPubKey = tb.getTSSCommits(TSS_KEY_TYPE, "imported")[0].toEllipticPoint(ecTSS); + tb.setTssTag("imported"); + const finalPubKey = tb.getTSSCommits(TSS_KEY_TYPE)[0].toEllipticPoint(ecTSS); const finalTssKey = await tb._UNSAFE_exportTssKey({ factorKey, selectedServers: [1, 2, 3], authSignatures: signatures, keyType: TSS_KEY_TYPE, - tssTag: "imported", }); const tssPubKeyImported = (ecTSS.g as EllipticPoint).mul(importedScalar); @@ -620,22 +614,20 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea factorKey, selectedServers: [3, 4, 5], authSignatures: signatures, - tssTag: "imported", }); equal(seed.equals(importedKey), true); } } { - // tb.tssTag = "default"; + tb.setTssTag(TSS_TAG_DEFAULT); - const finalPubKey = tb.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT)[0].toEllipticPoint(ecTSS); + const finalPubKey = tb.getTSSCommits(TSS_KEY_TYPE)[0].toEllipticPoint(ecTSS); const finalTssKey = await tb._UNSAFE_exportTssKey({ factorKey, selectedServers: [1, 2, 3], authSignatures: signatures, keyType: TSS_KEY_TYPE, - tssTag: TSS_TAG_DEFAULT, }); const tssPubKeyImported = (ecTSS.g as EllipticPoint).mul(finalTssKey); @@ -650,15 +642,14 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea await tb2.reconstructKey(); { - // tb2.tssTag = "imported"; - const finalPubKey = tb2.getTSSCommits(TSS_KEY_TYPE, "imported")[0].toEllipticPoint(ecTSS); + tb2.setTssTag("imported"); + const finalPubKey = tb2.getTSSCommits(TSS_KEY_TYPE)[0].toEllipticPoint(ecTSS); const finalTssKey = await tb2._UNSAFE_exportTssKey({ factorKey, selectedServers: [1, 2, 3], authSignatures: signatures, keyType: TSS_KEY_TYPE, - tssTag: "imported", }); const tssPubKeyImported = (ecTSS.g as EllipticPoint).mul(finalTssKey); @@ -668,14 +659,13 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea { // tb2.tssTag = "default"; - const finalPubKey = tb2.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT)[0].toEllipticPoint(ecTSS); + const finalPubKey = tb2.getTSSCommits(TSS_KEY_TYPE)[0].toEllipticPoint(ecTSS); const finalTssKey = await tb2._UNSAFE_exportTssKey({ factorKey, selectedServers: [1, 2, 3], authSignatures: signatures, keyType: TSS_KEY_TYPE, - tssTag: TSS_TAG_DEFAULT, }); const tssPubKeyImported = (ecTSS.g as EllipticPoint).mul(finalTssKey); @@ -742,7 +732,6 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea existingFactorKey: factorKey, newFactorPub: Point.fromElliptic(newFactorPub), newTSSIndex: deviceTSSIndex, - tssTag: TSS_TAG_DEFAULT, }); await tb.syncLocalMetadataTransitions(); }); @@ -756,7 +745,6 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea newFactorPub: Point.fromElliptic(newFactorPub), newTSSIndex, refreshShares: true, - tssTag: TSS_TAG_DEFAULT, }); await tb.syncLocalMetadataTransitions(); }); @@ -767,13 +755,12 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea factorKey, deleteFactorPub: Point.fromElliptic(newFactorPub), authSignatures: signatures, - tssTag: TSS_TAG_DEFAULT, }); await tb.syncLocalMetadataTransitions(); }); it("should no longer be able to access key share with removed factor (same index)", async function () { - await rejects(tb.getTSSShare(newFactorKeySameIndex, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT })); + await rejects(tb.getTSSShare(newFactorKeySameIndex, { keyType: TSS_KEY_TYPE })); }); it("should be able to remove factor for different index", async function () { @@ -782,13 +769,12 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea factorKey, deleteFactorPub: Point.fromElliptic(newFactorPub), authSignatures: signatures, - tssTag: TSS_TAG_DEFAULT, }); await tb.syncLocalMetadataTransitions(); }); it("should no longer be able to access key share with removed factor (different index)", async function () { - await rejects(tb.getTSSShare(newFactorKeyNewIndex, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT })); + await rejects(tb.getTSSShare(newFactorKeyNewIndex, { keyType: TSS_KEY_TYPE })); }); }); @@ -861,7 +847,7 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea await tbJson.reconstructKey(); // try refresh share - await tbJson.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); + await tbJson.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); const newFactorKeyPair = ecFactor.genKeyPair(); await tbJson.addFactorPub({ @@ -870,10 +856,9 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea newFactorPub: Point.fromElliptic(newFactorKeyPair.getPublic()), newTSSIndex, refreshShares: true, - tssTag: TSS_TAG_DEFAULT, }); - await tbJson.getTSSShare(newFactorKeyPair.getPrivate(), { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); + await tbJson.getTSSShare(newFactorKeyPair.getPrivate(), { keyType: TSS_KEY_TYPE }); const serialized2 = JSON.stringify(tbJson); @@ -885,8 +870,8 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea await tbJson2.reconstructKey(); - await tbJson2.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); - await tbJson2.getTSSShare(newFactorKeyPair.getPrivate(), { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); + await tbJson2.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); + await tbJson2.getTSSShare(newFactorKeyPair.getPrivate(), { keyType: TSS_KEY_TYPE }); }); }); }); diff --git a/packages/tss/test/tssSingleCurveTestCases.ts b/packages/tss/test/tssSingleCurveTestCases.ts index d52e157d..f87e9fa1 100644 --- a/packages/tss/test/tssSingleCurveTestCases.ts +++ b/packages/tss/test/tssSingleCurveTestCases.ts @@ -84,13 +84,13 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole if (tb1.secp256k1Key.cmp(reconstructedKey.secp256k1Key) !== 0) { fail("key should be able to be reconstructed"); } - const { tssShare: tss2 } = await tb1.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); + const { tssShare: tss2 } = await tb1.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); // compute public key of the tss share const tss2Pub = ecTSS.g.mul(tss2); // get tss pub key from tss commits - const tssCommits = tb1.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); + const tssCommits = tb1.getTSSCommits(TSS_KEY_TYPE); const tssCommitA0 = tssCommits[0].toEllipticPoint(ecTSS); const tssCommitA1 = tssCommits[1].toEllipticPoint(ecTSS); @@ -145,8 +145,8 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole fail("key should be able to be reconstructed"); } - const { tssShare: tss2 } = await tb1.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); - const tssCommits = tb1.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); + const { tssShare: tss2 } = await tb1.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); + const tssCommits = tb1.getTSSCommits(TSS_KEY_TYPE); const tssPrivKey = getLagrangeCoeffs(ecTSS, [1, deviceTSSIndex], 1) .mul(tss1) @@ -155,7 +155,7 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole const tssPubKey = (ecTSS.g as EllipticPoint).mul(tssPrivKey); const tssCommits0 = tssCommits[0].toEllipticPoint(ecTSS); - const tssPub = tb1.getTSSPub(TSS_KEY_TYPE, TSS_TAG_DEFAULT).toEllipticPoint(ecTSS); + const tssPub = tb1.getTSSPub(TSS_KEY_TYPE).toEllipticPoint(ecTSS); equal(tssPubKey.eq(tssCommits0), true); equal(tssPub.eq(tssPubKey), true); @@ -167,14 +167,14 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole const nonce = tb1.computeAccountNonce(accountIndex); return share.add(nonce).umod(ecTSS.n); })(); - const { tssShare: tss2Account } = await tb1.getTSSShare(factorKey, { accountIndex, keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); + const { tssShare: tss2Account } = await tb1.getTSSShare(factorKey, { accountIndex, keyType: TSS_KEY_TYPE }); const coefficient1 = getLagrangeCoeffs(ecTSS, [1, deviceTSSIndex], 1); const coefficient2 = getLagrangeCoeffs(ecTSS, [1, deviceTSSIndex], deviceTSSIndex); const tssKey = coefficient1.mul(tss1Account).add(coefficient2.mul(tss2Account)).umod(ecTSS.n); const tssKeyPub = (ecTSS.g as EllipticPoint).mul(tssKey); - const tssPubAccount = tb1.getTSSPub(TSS_KEY_TYPE, TSS_TAG_DEFAULT, accountIndex).toEllipticPoint(ecTSS); + const tssPubAccount = tb1.getTSSPub(TSS_KEY_TYPE, accountIndex).toEllipticPoint(ecTSS); equal(tssPubAccount.eq(tssKeyPub), true, "should equal account pub key"); } }); @@ -224,7 +224,7 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole // Check pub key. const ec = getEcCurve(TSS_KEY_TYPE); const importTssKeyPub = Point.fromScalar(importTssKey.scalar, ec); - const tssPub = await tb.getTSSPub(TSS_KEY_TYPE, "imported"); + const tssPub = await tb.getTSSPub(TSS_KEY_TYPE); assert(tssPub.equals(importTssKeyPub)); // Check exported key. @@ -232,14 +232,12 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole factorKey, authSignatures: signatures, keyType: TSS_KEY_TYPE, - tssTag: TSS_TAG_DEFAULT, }); assert(exportedKey.eq(importTssKey.scalar)); if (TSS_KEY_TYPE === KeyType.ed25519) { const seed = await tb._UNSAFE_exportTssEd25519Seed({ factorKey, authSignatures: signatures, - tssTag: TSS_TAG_DEFAULT, }); assert(seed.equals(importTssKey.raw)); } else { @@ -249,10 +247,9 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole authSignatures: signatures, accountIndex: 2, keyType: TSS_KEY_TYPE, - tssTag: TSS_TAG_DEFAULT, }); const exportedPubKeyIndex2 = Point.fromScalar(exportedKeyIndex2, ec); - const pubKeyIndex2 = tb.getTSSPub(TSS_KEY_TYPE, TSS_TAG_DEFAULT, 2); + const pubKeyIndex2 = tb.getTSSPub(TSS_KEY_TYPE, 2); assert(exportedPubKeyIndex2.equals(pubKeyIndex2)); } }); @@ -312,9 +309,8 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole } const { tssShare: retrievedTSS, tssIndex: retrievedTSSIndex } = await tb.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, - tssTag: TSS_TAG_DEFAULT, }); - const tssCommits = tb.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); + const tssCommits = tb.getTSSCommits(TSS_KEY_TYPE); const tssPrivKey = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], 1) .mul(serverDKGPrivKeys[0]) .add(getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], retrievedTSSIndex).mul(retrievedTSS)) @@ -341,16 +337,16 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole } ); + // after import, tkey tss instance is on tssTag = "imported" // tag is switched to imported await tb.syncLocalMetadataTransitions(); // for imported key const { tssShare: retrievedTSS1, tssIndex: retrievedTSSIndex1 } = await tb.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, - tssTag: "imported", }); - const tssCommits1 = tb.getTSSCommits(TSS_KEY_TYPE, "imported"); + const tssCommits1 = tb.getTSSCommits(TSS_KEY_TYPE); const tssPrivKey1 = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex1], 1) .mul(serverDKGPrivKeys1[0]) .add(getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex1], retrievedTSSIndex1).mul(retrievedTSS1)) @@ -366,7 +362,6 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole factorKey, selectedServers: [1, 2, 3], authSignatures: signatures, - tssTag: "imported", }); equal(seed.equals(importedKey), true); } @@ -382,18 +377,17 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole if (tb2.secp256k1Key.cmp(reconstructedKey2.secp256k1Key) !== 0) { fail("key should be able to be reconstructed"); } - const tssCommits2 = tb2.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); + const tssCommits2 = tb2.getTSSCommits(TSS_KEY_TYPE); const tssCommits20 = tssCommits2[0].toEllipticPoint(ecTSS); equal(tssPubKey.eq(tssCommits20), true); // switch to imported account - // tb2.tssTag = "imported"; + tb2.setTssTag("imported"); const { tssShare: retrievedTSSImported, tssIndex: retrievedTSSIndexImported } = await tb2.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, - tssTag: "imported", }); - const tssCommitsImported = tb2.getTSSCommits(TSS_KEY_TYPE, "imported"); + const tssCommitsImported = tb2.getTSSCommits(TSS_KEY_TYPE); const tssPrivKeyImported = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndexImported], 1) .mul(serverDKGPrivKeys1[0]) @@ -448,9 +442,8 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole } const { tssShare: retrievedTSS, tssIndex: retrievedTSSIndex } = await tb.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, - tssTag: TSS_TAG_DEFAULT, }); - const tssCommits = tb.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT); + const tssCommits = tb.getTSSCommits(TSS_KEY_TYPE); const tssPrivKey = getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], 1) .mul(serverDKGPrivKeys[0]) .add(getLagrangeCoeffs(ecTSS, [1, retrievedTSSIndex], retrievedTSSIndex).mul(retrievedTSS)) @@ -492,14 +485,13 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole await tb.syncLocalMetadataTransitions(); // for imported key { - const finalPubKey = tb.getTSSCommits(TSS_KEY_TYPE, "imported")[0].toEllipticPoint(ecTSS); + const finalPubKey = tb.getTSSCommits(TSS_KEY_TYPE)[0].toEllipticPoint(ecTSS); const finalTssKey = await tb._UNSAFE_exportTssKey({ factorKey, selectedServers: [1, 2, 3], authSignatures: signatures, keyType: TSS_KEY_TYPE, - tssTag: "imported", }); const tssPubKeyImported = (ecTSS.g as EllipticPoint).mul(importedScalar); @@ -511,22 +503,21 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole factorKey, selectedServers: [3, 4, 5], authSignatures: signatures, - tssTag: "imported", }); equal(seed.equals(importedKey), true); } } { // tb.tssTag = "default"; + tb.setTssTag(TSS_TAG_DEFAULT); - const finalPubKey = tb.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT)[0].toEllipticPoint(ecTSS); + const finalPubKey = tb.getTSSCommits(TSS_KEY_TYPE)[0].toEllipticPoint(ecTSS); const finalTssKey = await tb._UNSAFE_exportTssKey({ factorKey, selectedServers: [1, 2, 3], authSignatures: signatures, keyType: TSS_KEY_TYPE, - tssTag: TSS_TAG_DEFAULT, }); const tssPubKeyImported = (ecTSS.g as EllipticPoint).mul(finalTssKey); @@ -542,14 +533,14 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole await tb2.syncLocalMetadataTransitions(); { // tb2.tssTag = "imported"; - const finalPubKey = tb2.getTSSCommits(TSS_KEY_TYPE, "imported")[0].toEllipticPoint(ecTSS); + tb2.setTssTag("imported"); + const finalPubKey = tb2.getTSSCommits(TSS_KEY_TYPE)[0].toEllipticPoint(ecTSS); const finalTssKey = await tb2._UNSAFE_exportTssKey({ factorKey, selectedServers: [1, 2, 3], authSignatures: signatures, keyType: TSS_KEY_TYPE, - tssTag: "imported", }); const tssPubKeyImported = (ecTSS.g as EllipticPoint).mul(finalTssKey); @@ -558,15 +549,15 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole } { // tb2.tssTag = "default"; + tb2.setTssTag(TSS_TAG_DEFAULT); - const finalPubKey = tb2.getTSSCommits(TSS_KEY_TYPE, TSS_TAG_DEFAULT)[0].toEllipticPoint(ecTSS); + const finalPubKey = tb2.getTSSCommits(TSS_KEY_TYPE)[0].toEllipticPoint(ecTSS); const finalTssKey = await tb2._UNSAFE_exportTssKey({ factorKey, selectedServers: [1, 2, 3], authSignatures: signatures, keyType: TSS_KEY_TYPE, - tssTag: TSS_TAG_DEFAULT, }); const tssPubKeyImported = (ecTSS.g as EllipticPoint).mul(finalTssKey); @@ -633,7 +624,6 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole existingFactorKey: factorKey, newFactorPub: Point.fromElliptic(newFactorPub), newTSSIndex: deviceTSSIndex, - tssTag: TSS_TAG_DEFAULT, }); await tb.syncLocalMetadataTransitions(); }); @@ -647,7 +637,6 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole newFactorPub: Point.fromElliptic(newFactorPub), newTSSIndex, refreshShares: true, - tssTag: TSS_TAG_DEFAULT, }); await tb.syncLocalMetadataTransitions(); }); @@ -658,13 +647,12 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole factorKey, deleteFactorPub: Point.fromElliptic(newFactorPub), authSignatures: signatures, - tssTag: TSS_TAG_DEFAULT, }); await tb.syncLocalMetadataTransitions(); }); it("should no longer be able to access key share with removed factor (same index)", async function () { - await rejects(tb.getTSSShare(newFactorKeySameIndex, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT })); + await rejects(tb.getTSSShare(newFactorKeySameIndex, { keyType: TSS_KEY_TYPE })); }); it("should be able to remove factor for different index", async function () { @@ -673,13 +661,12 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole factorKey, deleteFactorPub: Point.fromElliptic(newFactorPub), authSignatures: signatures, - tssTag: TSS_TAG_DEFAULT, }); await tb.syncLocalMetadataTransitions(); }); it("should no longer be able to access key share with removed factor (different index)", async function () { - await rejects(tb.getTSSShare(newFactorKeyNewIndex, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT })); + await rejects(tb.getTSSShare(newFactorKeyNewIndex, { keyType: TSS_KEY_TYPE })); }); }); @@ -752,7 +739,7 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole await tbJson.reconstructKey(); // try refresh share - await tbJson.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); + await tbJson.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); const newFactorKeyPair = ecFactor.genKeyPair(); await tbJson.addFactorPub({ @@ -761,10 +748,9 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole newFactorPub: Point.fromElliptic(newFactorKeyPair.getPublic()), newTSSIndex, refreshShares: true, - tssTag: TSS_TAG_DEFAULT, }); - await tbJson.getTSSShare(newFactorKeyPair.getPrivate(), { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); + await tbJson.getTSSShare(newFactorKeyPair.getPrivate(), { keyType: TSS_KEY_TYPE }); const serialized2 = JSON.stringify(tbJson); @@ -776,8 +762,8 @@ const singleCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boole await tbJson2.reconstructKey(); - await tbJson2.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); - await tbJson2.getTSSShare(newFactorKeyPair.getPrivate(), { keyType: TSS_KEY_TYPE, tssTag: TSS_TAG_DEFAULT }); + await tbJson2.getTSSShare(factorKey, { keyType: TSS_KEY_TYPE }); + await tbJson2.getTSSShare(newFactorKeyPair.getPrivate(), { keyType: TSS_KEY_TYPE }); }); }); }); From 057f7949a90d25ebcfaf3a70c528b9106d8df5da Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 26 Feb 2025 04:21:14 +0800 Subject: [PATCH 14/22] fix: rebase issue --- packages/tss/src/tss.ts | 223 ++++++++++++++++++++-------------------- 1 file changed, 112 insertions(+), 111 deletions(-) diff --git a/packages/tss/src/tss.ts b/packages/tss/src/tss.ts index da0d963f..2c764df2 100644 --- a/packages/tss/src/tss.ts +++ b/packages/tss/src/tss.ts @@ -99,13 +99,9 @@ export class TKeyTSS extends TKey { this.legacyMetadataFlag = legacyMetadataFlag; } - // public get tssKeyType(): KeyType { - // return this._tssKeyType; - // } - - // public get tssCurve(): EllipticCurve { - // return this._tssCurve; - // } + public get tssTag(): string { + return this._tssTag; + } static async fromJSON(value: StringifiedType, args: TSSTKeyArgs): Promise { // legacyMetadataFlag need to be provided during constructor as tkey's fromJson is depending on the flag @@ -533,121 +529,126 @@ export class TKeyTSS extends TKey { this._tssTag = tssTag; const localTssTag = this._tssTag; - const tssData = this.metadata.getTssData(params.tssKeyType, localTssTag); - if (tssData) { - throw CoreError.default("Duplicate account tag, please use a unique tag for importing key"); - } + try { + const tssData = this.metadata.getTssData(params.tssKeyType, localTssTag); + if (tssData) { + throw CoreError.default("Duplicate account tag, please use a unique tag for importing key"); + } - const ec = getKeyCurve(tssKeyType); + const ec = getKeyCurve(tssKeyType); - const { selectedServers = [], authSignatures = [] } = serverOpts || {}; - - if (!localTssTag) throw CoreError.default(`invalid param, tag is required`); - if (!factorPubs || factorPubs.length === 0) throw CoreError.default(`invalid param, newFactorPub is required`); - if (!newTSSIndexes || newTSSIndexes.length === 0) throw CoreError.default(`invalid param, newTSSIndex is required`); - if (authSignatures.length === 0) throw CoreError.default(`invalid param, authSignatures is required`); - - const importScalar = await (async () => { - if (tssKeyType === KeyType.secp256k1) { - return new BN(importKey); - } else if (tssKeyType === KeyType.ed25519) { - // Store seed in metadata. - const domainKey = getEd25519SeedStoreDomainKey(localTssTag); - const result = this.metadata.getGeneralStoreDomain(domainKey) as Record; - if (result) { - throw new Error("Seed already exists"); - } + const { selectedServers = [], authSignatures = [] } = serverOpts || {}; - const { scalar } = getEd25519KeyPairFromSeed(importKey); - const encKey = Buffer.from(getSecpKeyFromEd25519(scalar).point.encodeCompressed("hex"), "hex"); - const msg = await encrypt(encKey, importKey); - this.metadata.setGeneralStoreDomain(domainKey, { message: msg }); + if (!localTssTag) throw CoreError.default(`invalid param, tag is required`); + if (!factorPubs || factorPubs.length === 0) throw CoreError.default(`invalid param, newFactorPub is required`); + if (!newTSSIndexes || newTSSIndexes.length === 0) throw CoreError.default(`invalid param, newTSSIndex is required`); + if (authSignatures.length === 0) throw CoreError.default(`invalid param, authSignatures is required`); - return scalar; - } - throw new Error("Invalid key type"); - })(); + const importScalar = await (async () => { + if (tssKeyType === KeyType.secp256k1) { + return new BN(importKey); + } else if (tssKeyType === KeyType.ed25519) { + // Store seed in metadata. + const domainKey = getEd25519SeedStoreDomainKey(localTssTag); + const result = this.metadata.getGeneralStoreDomain(domainKey) as Record; + if (result) { + throw new Error("Seed already exists"); + } - if (!importScalar || importScalar.toString("hex") === "0") { - throw new Error("Invalid importedKey"); - } + const { scalar } = getEd25519KeyPairFromSeed(importKey); + const encKey = Buffer.from(getSecpKeyFromEd25519(scalar).point.encodeCompressed("hex"), "hex"); + const msg = await encrypt(encKey, importKey); + this.metadata.setGeneralStoreDomain(domainKey, { message: msg }); - const tssIndexes = newTSSIndexes; - const existingNonce = tssData?.tssNonce ?? 0; - const newTssNonce: number = existingNonce && existingNonce > 0 ? existingNonce + 1 : 0; - const verifierAndVerifierID = this.serviceProvider.getVerifierNameVerifierId(); - const label = `${verifierAndVerifierID}\u0015${localTssTag}\u0016${newTssNonce}`; - const tssPubKey = hexPoint(ec.g.mul(importScalar)); - const rssNodeDetails = await this._getRssNodeDetails(); - const { pubKey: newTSSServerPub, nodeIndexes } = await this.serviceProvider.getTSSPubKey(localTssTag, newTssNonce, tssKeyType); - let finalSelectedServers = selectedServers; + return scalar; + } + throw new Error("Invalid key type"); + })(); - if (nodeIndexes?.length > 0) { - if (selectedServers.length) { - finalSelectedServers = nodeIndexes.slice(0, Math.min(selectedServers.length, nodeIndexes.length)); - } else { - finalSelectedServers = nodeIndexes.slice(0, 3); + if (!importScalar || importScalar.toString("hex") === "0") { + throw new Error("Invalid importedKey"); } - } else if (selectedServers?.length === 0) { - finalSelectedServers = randomSelection( - new Array(rssNodeDetails.serverEndpoints.length).fill(null).map((_, i) => i + 1), - Math.ceil(rssNodeDetails.serverEndpoints.length / 2) - ); - } - const { serverEndpoints, serverPubKeys, serverThreshold } = rssNodeDetails; + const tssIndexes = newTSSIndexes; + const existingNonce = tssData?.tssNonce ?? 0; + const newTssNonce: number = existingNonce && existingNonce > 0 ? existingNonce + 1 : 0; + const verifierAndVerifierID = this.serviceProvider.getVerifierNameVerifierId(); + const label = `${verifierAndVerifierID}\u0015${localTssTag}\u0016${newTssNonce}`; + const tssPubKey = hexPoint(ec.g.mul(importScalar)); + const rssNodeDetails = await this._getRssNodeDetails(); + const { pubKey: newTSSServerPub, nodeIndexes } = await this.serviceProvider.getTSSPubKey(localTssTag, newTssNonce, tssKeyType); + let finalSelectedServers = selectedServers; + + if (nodeIndexes?.length > 0) { + if (selectedServers.length) { + finalSelectedServers = nodeIndexes.slice(0, Math.min(selectedServers.length, nodeIndexes.length)); + } else { + finalSelectedServers = nodeIndexes.slice(0, 3); + } + } else if (selectedServers?.length === 0) { + finalSelectedServers = randomSelection( + new Array(rssNodeDetails.serverEndpoints.length).fill(null).map((_, i) => i + 1), + Math.ceil(rssNodeDetails.serverEndpoints.length / 2) + ); + } - const rssClient = new RSSClient({ - serverEndpoints, - serverPubKeys, - serverThreshold, - tssPubKey, - keyType: tssKeyType, - }); + const { serverEndpoints, serverPubKeys, serverThreshold } = rssNodeDetails; - const refreshResponses = await rssClient.import({ - importKey: importScalar, - dkgNewPub: pointToHex(newTSSServerPub), - selectedServers: finalSelectedServers, - factorPubs: factorPubs.map((f) => pointToHex(f)), - targetIndexes: tssIndexes, - newLabel: label, - sigs: authSignatures, - }); - const secondCommit = newTSSServerPub.toEllipticPoint(ec).add(ecPoint(ec, tssPubKey).neg()); - const newTSSCommits = [ - Point.fromJSON(tssPubKey), - Point.fromJSON({ x: secondCommit.getX().toString(16, 64), y: secondCommit.getY().toString(16, 64) }), - ]; - const factorEncs: { - [factorPubID: string]: FactorEnc; - } = {}; - for (let i = 0; i < refreshResponses.length; i++) { - const refreshResponse = refreshResponses[i]; - factorEncs[refreshResponse.factorPub.x.padStart(64, "0")] = { - type: "hierarchical", - tssIndex: refreshResponse.targetIndex, - userEnc: refreshResponse.userFactorEnc, - serverEncs: refreshResponse.serverFactorEncs, - }; - } - this.metadata.updateTSSData({ - tssKeyType, - tssTag: localTssTag, - tssNonce: newTssNonce, - tssPolyCommits: newTSSCommits, - factorPubs, - factorEncs, - }); - if (!this._accountSalt) { - const accountSalt = generateSalt(getKeyCurve(tssKeyType)); - await this._setTKeyStoreItem(TSS_MODULE, { - id: "accountSalt", - value: accountSalt, - } as IAccountSaltStore); - this._accountSalt = accountSalt; + const rssClient = new RSSClient({ + serverEndpoints, + serverPubKeys, + serverThreshold, + tssPubKey, + keyType: tssKeyType, + }); + + const refreshResponses = await rssClient.import({ + importKey: importScalar, + dkgNewPub: pointToHex(newTSSServerPub), + selectedServers: finalSelectedServers, + factorPubs: factorPubs.map((f) => pointToHex(f)), + targetIndexes: tssIndexes, + newLabel: label, + sigs: authSignatures, + }); + const secondCommit = newTSSServerPub.toEllipticPoint(ec).add(ecPoint(ec, tssPubKey).neg()); + const newTSSCommits = [ + Point.fromJSON(tssPubKey), + Point.fromJSON({ x: secondCommit.getX().toString(16, 64), y: secondCommit.getY().toString(16, 64) }), + ]; + const factorEncs: { + [factorPubID: string]: FactorEnc; + } = {}; + for (let i = 0; i < refreshResponses.length; i++) { + const refreshResponse = refreshResponses[i]; + factorEncs[refreshResponse.factorPub.x.padStart(64, "0")] = { + type: "hierarchical", + tssIndex: refreshResponse.targetIndex, + userEnc: refreshResponse.userFactorEnc, + serverEncs: refreshResponse.serverFactorEncs, + }; + } + this.metadata.updateTSSData({ + tssKeyType, + tssTag: localTssTag, + tssNonce: newTssNonce, + tssPolyCommits: newTSSCommits, + factorPubs, + factorEncs, + }); + if (!this._accountSalt) { + const accountSalt = generateSalt(getKeyCurve(tssKeyType)); + await this._setTKeyStoreItem(TSS_MODULE, { + id: "accountSalt", + value: accountSalt, + } as IAccountSaltStore); + this._accountSalt = accountSalt; + } + await this._syncShareMetadata(); + } catch (error) { + this._tssTag = oldTag; + throw error; } - await this._syncShareMetadata(); } /** @@ -851,7 +852,7 @@ export class TKeyTSS extends TKey { tssIndices: updatedTSSIndexes, remoteClient, keyType, - tssTag, + tssTag: this.tssTag, }); } From e4f22fb568d2cf08aab1def407a767b797ebcb91 Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 26 Feb 2025 04:23:22 +0800 Subject: [PATCH 15/22] fix: clean up --- packages/core/src/metadata.ts | 17 +++++++++-------- packages/tss/src/tss.ts | 15 ++------------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index 72cbfdf4..330a2b1f 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -31,7 +31,6 @@ import { TssMetadata } from "./tssMetadata"; export const LEGACY_METADATA_VERSION = "0.0.1"; export const METADATA_VERSION = "1.0.0"; - export class Metadata implements IMetadata { pubKey: Point; @@ -58,7 +57,7 @@ export class Metadata implements IMetadata { tss?: { [tssTag: string]: { - [curveType: string]: TssMetadata; + [curveType in KeyType]?: TssMetadata; }; }; @@ -105,19 +104,23 @@ export class Metadata implements IMetadata { if (version === METADATA_VERSION) { metadata.tss = {}; - if (tss) { + if (tss instanceof Object) { Object.keys(tss).forEach((tsstag) => { - if (tss[tsstag]) { + if (tss[tsstag] instanceof Object) { metadata.tss[tsstag] = {}; Object.keys(tss[tsstag]).forEach((tssKeyType) => { - metadata.tss[tsstag][tssKeyType] = TssMetadata.fromJSON(tss[tsstag][tssKeyType]); + if (tssKeyType in KeyType) { + metadata.tss[tsstag][tssKeyType as KeyType] = TssMetadata.fromJSON(tss[tsstag][tssKeyType]); + } else { + throw new Error(`tssKeyType not supported ${tssKeyType}`); + } }); } }); } // else would be legacy version, migrate for secp version - } else if (factorEncs) { + } else if (factorEncs instanceof Object) { metadata.tss = {}; // some tests case on backward compatbility tests having serialized metadata with empty tssKeyTypes const tssKeyTypes: Record = tssKeyTypesJson ?? {}; @@ -534,8 +537,6 @@ export class LegacyMetadata extends Metadata { tssPolyCommits, factorPubs, factorEncs, - // Legacy Metadata version - // version: this.version, }; } diff --git a/packages/tss/src/tss.ts b/packages/tss/src/tss.ts index 2c764df2..ff54e61d 100644 --- a/packages/tss/src/tss.ts +++ b/packages/tss/src/tss.ts @@ -50,7 +50,6 @@ export const LEGACY_KEY_TYPE = "secp256k1"; export interface TSSTKeyArgs extends TKeyArgs { serviceProvider: TSSTorusServiceProvider; - // tssKeyType: KeyType; tssTag?: string; legacyMetadataFlag?: boolean; } @@ -70,10 +69,6 @@ export interface TKeyTSSInitArgs { export class TKeyTSS extends TKey { serviceProvider: TSSTorusServiceProvider = null; - // private _tssKeyType: KeyType; - - // private _tssCurve: EC; - private _tssTag: string; private _accountSalt: string; @@ -94,8 +89,6 @@ export class TKeyTSS extends TKey { this.serviceProvider = serviceProvider; this.storageLayer = storageLayer; this._tssTag = tssTag; - // this._tssKeyType = tssKeyType; - // this._tssCurve = new EC(tssKeyType); this.legacyMetadataFlag = legacyMetadataFlag; } @@ -114,10 +107,6 @@ export class TKeyTSS extends TKey { throw CoreError.default(`tssTag mismatch: ${tssTag} !== ${tbTss._tssTag}`); } - // if (tssKeyType !== tbTss.tssKeyType) { - // throw CoreError.default(`tssKeyType mismatch: ${tssKeyType} !== ${tbTss.tssKeyType}`); - // } - // copy over tkey to tkeyTss tbTss.shares = tb.shares; tbTss.metadata = tb.metadata; @@ -526,7 +515,7 @@ export class TKeyTSS extends TKey { const oldTag = this._tssTag; // update current tssTag - this._tssTag = tssTag; + this.setTssTag(tssTag); const localTssTag = this._tssTag; try { @@ -646,7 +635,7 @@ export class TKeyTSS extends TKey { } await this._syncShareMetadata(); } catch (error) { - this._tssTag = oldTag; + this.setTssTag(oldTag); throw error; } } From 93f266b1f7c71f08bbb86870d7bd63f9706e8e33 Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 26 Feb 2025 05:46:15 +0800 Subject: [PATCH 16/22] fix: address comments add tests failing reason remove `secp256k1` comment fix typo --- packages/core/test/authMetadata.test.js | 6 ++--- packages/core/test/metadataFormat.test.js | 16 ++++++------- packages/tss/src/tss.ts | 2 +- packages/tss/test/helpers.d.ts | 25 --------------------- packages/tss/test/tssMultiCurveTestCases.ts | 10 ++++----- 5 files changed, 16 insertions(+), 43 deletions(-) delete mode 100644 packages/tss/test/helpers.d.ts diff --git a/packages/core/test/authMetadata.test.js b/packages/core/test/authMetadata.test.js index c38e0849..e00430f3 100644 --- a/packages/core/test/authMetadata.test.js +++ b/packages/core/test/authMetadata.test.js @@ -8,7 +8,7 @@ import { AuthMetadata, generateRandomPolynomial, Metadata } from "../src/index"; const PRIVATE_KEY = generatePrivate().toString("hex"); -const mpcBackwardCompebilityTestJson = +const mpcBackwardCompatibilityTestJson = ' {"data":{"factorEncs":{"default":{"f6126a36291f7bf6a10d5df11af4ba184adb8a1212e233846f872034a06a7a87":{"serverEncs":[],"tssIndex":2,"type":"direct","userEnc":{"ciphertext":"efa604982f337f7bf5034cb8f3996117b9a13e4e4f4a3cb37f06b55722eb1cf052456c24ff2a2d2ae3ffe01ebc4e63f9","ephemPublicKey":"04a280a81a81f8bddef7d052f740dce5ff1d123ba5c208eb2b4bd8d9d9a94e585d0d1b0b25c1624f38dac7fb196ef85fa2eecd47a363bd2c1b00ce3ccc64e1fdeb","iv":"1121328b857d206af5686021bc2a7baf","mac":"026c5633e36b69d59a11a487045cc99107440bf2da049c339beb0745fcdcaf5a"}}}},"factorPubs":{"default":[{"x":"f6126a36291f7bf6a10d5df11af4ba184adb8a1212e233846f872034a06a7a87","y":"b05c5a1dbb89939335fddac27d90495342987555e75942460d3fca0571829cf"}]},"generalStore":{"shareDescriptions":{"03f6126a36291f7bf6a10d5df11af4ba184adb8a1212e233846f872034a06a7a87":["{\\"module\\":\\"hashedShare\\",\\"dateAdded\\":1705918192036,\\"tssShareIndex\\":2}"]}},"nonce":91,"polyIDList":["03422a3a45805c131e1f046ede6a7292c999993f86dbb6b4d4c3ab21f6e278fc11|02479427749ab0b468e5192d9c41698af3175b3a0588c426e7b72cdca30f4edd84|0x0|1|5c5d460d75a271853d4e00e4738aac6f7cb7e5e13fe85848edf5b3aec92368e5"],"pubKey":"03422a3a45805c131e1f046ede6a7292c999993f86dbb6b4d4c3ab21f6e278fc11","scopedStore":{},"tkeyStore":{"tssModule":[{"ciphertext":"69c19b5c7f531e049f5e35039dfbe2ad42f1a39670790ab918b01bc40e38c4da6b83cbfc0a39eb2cf9b102ce8ae0a46feb5c8719075c27f4e5edb05758eff22ff0b782e88ae1d15394b9dc596f52b4da27ace18b2db060c08dd7ab301d9f1345","ephemPublicKey":"046beade3380418cffa450b7602d21b484999a851b098d6a2c99d7e4a47a5cbcdff9938b81348caa8ba16b0a8d48485679ccbb5e5b010690854ef9ff173dd7709c","iv":"3e07dd44e31f584af30e7fc8dc12c83a","mac":"26f686bf7bae162ee0adb670c9beb8c9729984fbfb16f8bd7c886ccc4837a490"}]},"tssKeyTypes":{},"tssNonces":{"default":0},"tssPolyCommits":{"default":[{"x":"23764a46f13a2b40f3a2e9d5ddfaac25690a84cc36c07efec0e61c723b4b5dc2","y":"5d7589199bfefd17bc0594f6e597a82388e68365d72403c3930ea594680e5d79"},{"x":"639929d09bbbe5dcfd684f4c348d8716c4d99347f7759a5b002cd3fdba2f2147","y":"a032c871a6c57a1af43bb043fcdae00dffea682eca6963a67906e892623abce5"}]}},"sig":"304502200701aab1420ba046b72817c69093d2cab43d4bbe7809b40ead658b09f12191f9022100969468307ab7ac29b64f427bd59af58ce39a8d006a22b7e260400acc329b7d90"}'; describe("AuthMetadata", function () { @@ -30,8 +30,8 @@ describe("AuthMetadata", function () { }); it("#should authenticate and serialize and deserialize into JSON seamlessly (backward compatibility)", async function () { - const instance1 = AuthMetadata.fromJSON({ ...JSON.parse(mpcBackwardCompebilityTestJson), legacyMetadataFlag: false }); - const instance2 = AuthMetadata.fromJSON({ ...JSON.parse(mpcBackwardCompebilityTestJson), legacyMetadataFlag: true }); + const instance1 = AuthMetadata.fromJSON({ ...JSON.parse(mpcBackwardCompatibilityTestJson), legacyMetadataFlag: false }); + const instance2 = AuthMetadata.fromJSON({ ...JSON.parse(mpcBackwardCompatibilityTestJson), legacyMetadataFlag: true }); delete instance1.metadata.version; delete instance2.metadata.version; diff --git a/packages/core/test/metadataFormat.test.js b/packages/core/test/metadataFormat.test.js index 014c066f..101554be 100644 --- a/packages/core/test/metadataFormat.test.js +++ b/packages/core/test/metadataFormat.test.js @@ -14,10 +14,10 @@ const legacyJSONEd25519 = const multiCurveJson = '{"pubKey":"022dd7f0a0b54953304e94e861373ab9fe9c36930f9bc4804efa06b1d472f62ade","polyIDList":["022dd7f0a0b54953304e94e861373ab9fe9c36930f9bc4804efa06b1d472f62ade|0341267f01008fc085e2c2bb3f508422e1410e641a5f297fa14bd57d32d7e97a87|0x0|1|731d22b16ae27306e045e6121dc9c43754dfbdb6e28a59879e054bb0772673d","022dd7f0a0b54953304e94e861373ab9fe9c36930f9bc4804efa06b1d472f62ade|034f25e3aeea954b8ddf033d00a8657c227c3e243177795c303476ffa2fd104c9b|0x0|1|731d22b16ae27306e045e6121dc9c43754dfbdb6e28a59879e054bb0772673d|969cf4076b10ba9276e44964915cc97c2984ff29cb1d2de8f2c10fb9dfa35279"],"scopedStore":{},"generalStore":{"ed25519Seed":{"message":{"ciphertext":"afde6d2044edce2f9e106bfe7b03bc716b1f4f9d765c70aa04d10089b07141e5ab2eff80a2c74798e6e44129403f4fc4","ephemPublicKey":"041a719598c138c0b5d2f1cc12b4348bed2c5c253360bb1f855f1eb7df53746047892f69655727f672f3a81bcdcc8e5816cc1f2b2b822e4e79a88cac83c3a30dfc","iv":"c846c938f752825764f0bd0fbbceaf50","mac":"013adaa31e31ab19300e1a5979a2728a53e324689b92677cbd55f0cb938ac138"},"publicKey":"047ecfc14f54a87dc25a4c839d71d9e80046f71141e42b339faa60c2f28601fa1870f68c19c5004f52ddf2f3d17f779ad1ece0d13d981afd294645d4f29a2d20c5"},"ed25519Seed/default":{"message":{"ciphertext":"9a2667945f75aa35c25358439d8f8f578fac22e99d31342d11eef32879a66c286cfae76218d16bdd3b881260f40353ff","ephemPublicKey":"046a6fccfabb06d73580143981d61e2a8d9fc4a02da99421dddef57248cd64976606a79d19cdafc42129ab39b3c80a64ccde7bd805a36cea519b57da43c6d0f195","iv":"179e3c25c68cbd5d4be06c32b7d12a6a","mac":"f4e6e500fd60c275544857ed4750487267394bf7002eed13aa4816ebbcd457b2"}}},"tkeyStore":{"tssModule":[{"ciphertext":"57ed48f042da6bac2774b48a6815476f75d09d16043bb800c92b3b04ca6ccc28fd79adfcfa11f96a7a27e28645376428f9b02c8bc0e454d190ec3880e9a9387d7dcc7300d4adef4080a847e648170b551473297b200c6781a2ea265b8db75ca3","ephemPublicKey":"04e440f6d8990c148c750bb21dbdec7dabf4f6ffe1c5c30b5640e42b2b81b02ce40073dd0ca43cee74a95fb90a25588ba05ca65da83e9377829c563b10fa000e5e","iv":"701b9f3f8f790588e47af25eff5db67e","mac":"453977ff17be129f2f17bee349e7b1bf74ae8da436b79e70f236ba470e4d5d40"}]},"nonce":5,"version":"1.0.0","tss":{"default":{"secp256k1":{"tssTag":"default","tssKeyType":"secp256k1","tssNonce":0,"tssPolyCommits":[{"x":"75e2914f29baefcadb5aea5ada6aa8ca0f2edef47f090bd55ec87fd1c68ef6a","y":"3be3fd7188f951bab6af03445c16642e70bfe720995f8d7d406f198ea451339a"},{"x":"860dcf8b742ebd9c35a43adc0a54ad5ca122f1df20f0c8cf65ae01637d988f9c","y":"96b5669497209e94b3f75d658a17a19bba66a6ea5337b77dec0920c9279999f0"}],"factorPubs":[{"x":"62bdf332bd2c64e38ea6085ddb16133a293f59d0f2efc7e89388e250b232da74","y":"ea06f717886022b9592b5504c0fbd1272080a39d57ca869bac34b05abeba9090"}],"factorEncs":{"62bdf332bd2c64e38ea6085ddb16133a293f59d0f2efc7e89388e250b232da74":{"serverEncs":[],"tssIndex":3,"type":"direct","userEnc":{"ciphertext":"60f413ae1985ff6abd10995bc9d75707d76f1bcde659b60f9f78f37549d346156209d74ec672403d01911b246db05769","ephemPublicKey":"04c7ee61df6af27917edacd194be2429575a173eb77fdde5e351a3a7ad30cacdfbb889242e689f5eb6bdd11deacadbc3c592c9881eac8551b7c792bb993a1b0321","iv":"305724b2455de8e516cd90a01c5e7adb","mac":"719ac67e41fa159e9736e3d1cb4b858e9c38c7faff3b9c2fe63ca99d82ac2452"}}}},"ed25519":{"tssTag":"default","tssKeyType":"ed25519","tssNonce":0,"tssPolyCommits":[{"x":"202c3557a7cad9102c0cfbcaab765277018f3e680c9c3eb35c0cb69fa07788b8","y":"45f1cb0bffba15dd562811694864a228ea1908eb1c3ca9cd90899c1a92ca85e3"},{"x":"7e271298f3c9c9386dc611e9c57a0031fdd17aeea17423073961f815b95fa833","y":"dbe5d48e51e01df6b236315568b60dc3e513b72cb07b9613f7aaed2c1bff5fb"}],"factorPubs":[{"x":"62bdf332bd2c64e38ea6085ddb16133a293f59d0f2efc7e89388e250b232da74","y":"ea06f717886022b9592b5504c0fbd1272080a39d57ca869bac34b05abeba9090"}],"factorEncs":{"62bdf332bd2c64e38ea6085ddb16133a293f59d0f2efc7e89388e250b232da74":{"type":"hierarchical","tssIndex":3,"userEnc":{"ciphertext":"d57f7f23f71348ea8a7479e10e085d074119c12bab5df45283e69ba3ef2a7e3dd9ecb7c962eceea265b302509ed67bad","ephemPublicKey":"048627e20c599e4fe3c5dd4fc3af7b45111f4a991f8e6ffb5c92da33f4b2fbfbacfc064b7abf27fb602b4fff8af0e01ed1ac1beaaaed373f954dde914237a27cca","iv":"6f0c006ea1ceea25167304a4d2a9c80e","mac":"31932b2647b821fc833ae625b54c8085dab9791d534457c92e220e1e00fbb73c"},"serverEncs":[{"ciphertext":"ebc855e5c04ca52f3a03a704f66fdaa6aa503760a267a8c0311554f89954096ea5bb16016037bcc9f86157ef8fd115f4","ephemPublicKey":"045fc8623ed91c3b11013d2a43da4ea1c82a46ad25158941c711397d2bfac8224a37287a93c882e55cab5ab14f45dcd82d0ee8f20ee41ffed11120820fabbd4863","iv":"30bc8a9549fdb55682e2a8dec4411884","mac":"f375aff236cb31763fb61e8bd242069a6e193b1e036b811daf2d4a354c021df7"},{"ciphertext":"eae57b0956202e6ccbd53bd5c6df568fc8b6b33242fad894333f27461e28021ba01599757109a9879526ff15a465e7d2","ephemPublicKey":"049f78eb19d31f29905596a2371ebdcc98b1beee9ab15f700974e994e254884b5df30c6bd14b3cba9f8fdcd5e36846b7fc52cf89012ebb8c9fbdee2cdd164db7c8","iv":"4d1372010b4fd0312b0bb1c089d5e2b4","mac":"70f1f77be28f2be39191e0f3af5be330776563428c469a8072af2704173d42df"},{"ciphertext":"bba1839361e43a86545d37bfcb76290b20981822cc83e6701468b89fa5c4a4dae3fb2f9130972749aaf0f1c378800347","ephemPublicKey":"04256e36cd09417399f2cff9ea02769bb102710ad7ac8704aa30b18c062256eab7f6337eb2269525fd3e96cc606e1288fe02d3b0e2ae99d4c1f556b93d6f9bfc83","iv":"2491273bae809df6df55df024726ebfd","mac":"571d7ff4e44433c67b9aff92457e241cdad281034075dfffcd48250fb4a78d78"},{"ciphertext":"e3dad5f1b33dc9ddadb2255e2864ad07573d2e0f38e2d2462380329e04cdf7cfeca1059754493be96b2c51a3464210dd","ephemPublicKey":"040ce0a9bf20b79ebca6f1de32021d575d86a4526e1df1ab67e7552e37969bceefba9983cc3c29cb4e41916359e7416ec4c0ebd385203a2f6ae77ae6aa5405ba73","iv":"8d1f802b158a8cdebc8c4157b099a0d3","mac":"a00163b3e7ccd900dd12b7c28e3e999ae018324155dac298eb3038e656982876"},{"ciphertext":"3945f770ce1057e31e64951815922be8577cbc3135b3b9f2488090b86898a579a466a2f792680f0329e9f75ef252cb41","ephemPublicKey":"041f4517c1c8932d13d564f04d0ab735a57f79ca415aeac4b7c1d6c08e4d9cac7111df7dc39df122d80b6e5be9b734bc2c89a3598a8b7138368300ebc4604c29a2","iv":"42ee1817d55cd8e3c8c76b1d82df5d81","mac":"3ad2fe6595bbf40235ae62d53debee60428bf25da6b3e8fabd14238bcb5bfebb"}]}}}},"imported":{"secp256k1":{"tssTag":"imported","tssKeyType":"secp256k1","tssNonce":0,"tssPolyCommits":[{"x":"8a41efc52c76684c21f7898a9035a5f64d14e184a70d55bf28adfefc8fc75b3","y":"8422d404ebe92f25ac0725a32e39c1c6e4a2108e535f3615a7d7348e9e843e8e"},{"x":"14c77d1851981790e379c5545c7712b5f9fc9d566e1d9cdb20bc2a0ebe5910b6","y":"b08f3294a70f3e966c3ab194314c31dcbe9a5ea02e0bb24ab4b47487f8b085db"}],"factorPubs":[{"x":"62bdf332bd2c64e38ea6085ddb16133a293f59d0f2efc7e89388e250b232da74","y":"ea06f717886022b9592b5504c0fbd1272080a39d57ca869bac34b05abeba9090"}],"factorEncs":{"62bdf332bd2c64e38ea6085ddb16133a293f59d0f2efc7e89388e250b232da74":{"type":"hierarchical","tssIndex":2,"userEnc":{"ciphertext":"9eb4d12aa6f30aa297ffcf17826826251238458e1712b75af80db4265e5a7fe4b9f5bc4e79f73255ba92bab108a98a4d","ephemPublicKey":"043408f0f43963c2fb5c6da759113a9f12616eb435cb181e565a9a66cf898589b3fec9b597abe0fffd4d8f9b10def94795dc40b43e9ef3dfa7c3761be63326a3fc","iv":"359b79f10ca00470d13756f96463eeb8","mac":"1fc2f9842b9e86117b2d53f95f8d69433308589620628aa66c50d210181ee950"},"serverEncs":[{"ciphertext":"80dc41ca3cb42187ace1fa2553daf456ea80c845158af21dce9c2722ad781f942551b8854eb0cdb686980f0cfe6ccd64","ephemPublicKey":"042179cb3af6d8b625cf410026f8c9b74c3dd1d6e2c25db7060507e58ae46a30c8f8adb3d4f7c47bcc3d031ed795c76736eb8879f02b5f9862b06de2c0c83ad74f","iv":"4ee3ba08a424a212a53faff89f40a35e","mac":"02cfff72863b1ef3b217a7d868ed812ac2fc948abdaec7f8c77a7383afcef413"},{"ciphertext":"cb51445f28f518c371637d182954dfb2dfffd1e3a58b9c5089557db269cea8983359f8b4012c9965d2c6368598044256","ephemPublicKey":"04abec93a4d9405512d6b0fe3c9f6283744a9175c2b5e82a90bba0fedecbb22583dd526cf1858b07690f53ca7e621a81a3217635a9739e79aba0fba6e332eeec4f","iv":"e89e8bb153becbc3f2c304be0d8b02f8","mac":"770c3274a8d968897f518f8a9a019e47500e91c35461ab44735de72adef3494f"},{"ciphertext":"3b98c46a54419fa2a6419cef43f505c9fccdb928fcb2f70179d751c3e155124dcf8ee4ad6b536367bc635856c52e8f66","ephemPublicKey":"048c05e675a6465044b9c5b8acb32226707af9e04f972703a18ec75920960bdca813dc0a1a4632afd74ccdebcdd9a1d8ea6f1b3b599d8f2a841301fd19c4ee0ffe","iv":"b0cfb6ec0ac4f150590b02ec2442e19d","mac":"adc65ed18f982ce229ed3ba404a357c9f4e6fb89d40b926e6b791cbcd205304d"},{"ciphertext":"1050f582da2d571016552385cbd8d5db9b7b2b024dfd406ebcf8b422b5c9b32ab5b4af3c921e8ed0bb6244f56a39de9b","ephemPublicKey":"046a44e351f2b9fb45b114457d7afd505837065d0c3d76d6af0cc831e7ee46c93bac9849ad56f8f131ec1f934836dfba23367f590e1aa2bf312195f4d1aed1ba4a","iv":"149473b0ca24ede8663e5e0a404a8832","mac":"9859c6e77a1cb2951b955449448a81f3c7f8b14b03b981b461fdf2c2857b1527"},{"ciphertext":"4a26f1e6dafeaa2a03189f0f0dbae8aa3cf3bfc6b5da88ad145ca4b3a2d7284d0ed086d1e2910b3f6cdd23dd64987e46","ephemPublicKey":"04694c715117852d1dc1d90504b1ca1b920cbb9c107ef9ee9929440e2e21b4d4fba14bac27524a43bc33cfb9b3d2f3c93973ccb20c322fa933e9e9631c3c904122","iv":"ed8adfd3806b4e57e9e384a4a308dbca","mac":"d053605e9c44083a128897d5deb600764f8ca104c57a4597bd1c143ccdbb963b"}]}}}}}}'; -const mpcBackwardCompebilityTestJson = +const mpcBackwardCompatibilityTestJson = '{"factorEncs":{"default":{"16e9809395ce9078a8a510ddf613cea148a3a23049fd3948587b4fec28d68f92":{"serverEncs":[],"tssIndex":2,"type":"direct","userEnc":{"ciphertext":"ee307c87b733170222ef93048201c7c995544ac51eb648fbdea992a77d71f46e8a62c617caf20f3208d59ebc7f0a9e91","ephemPublicKey":"041c158020f3341b911d5b3a9cb70b29b7a7d327ab6b92134634bb0ed8e0461b4dfa7e0eeb8185365faefbbd37fea5e72540ed9a0bea65b3a4e6008f89c8bf796e","iv":"5a8ddd80c74dac65d1a8bd8aee110d4c","mac":"bf25a4d8411a2d3b432bd8806e699ac144fccf7f52f937b5657365a06d9d97ec"}}}},"factorPubs":{"default":[{"x":"16e9809395ce9078a8a510ddf613cea148a3a23049fd3948587b4fec28d68f92","y":"212e94e3b9e811802f1a47212a3cbc7ada4a7f993fc07e6be5340af40dd0d852"}]},"generalStore":{"shareDescriptions":{"0216e9809395ce9078a8a510ddf613cea148a3a23049fd3948587b4fec28d68f92":["{\\"module\\":\\"hashedShare\\",\\"dateAdded\\":1709020898217,\\"tssShareIndex\\":2}"]}},"nonce":172,"polyIDList":["03b3951a441f87ecea4672edc82894ac023316723cf164a93adec72b58a27a1f06|022909d9743f792d6adc3e16ce53d825230ad9b0b35574a34965a2e73a0f53b608|0x0|1|51b0891f3fed9f9f4ce7dde0710770a3d5b597d4c575460df431f907c9ee4e0a"],"pubKey":"03b3951a441f87ecea4672edc82894ac023316723cf164a93adec72b58a27a1f06","scopedStore":{},"tkeyStore":{"tssModule":[{"ciphertext":"04463425d186330813e4abefcdbcb786a0d22bd0067c4b81a584aec3989412ac3cb9a928c8be454245f60f08de08f7277a96c8d9ca292bdef4c4d26f0c6142d6618d8d1b0049812f8a95c372bc9e0eb87cf348152cf56239a289721c2c2ae2a3","ephemPublicKey":"0479cb39633e70992c3748447cd532982618ee89f451f4d2d123dd86871b62101bdf9ab98370f4ab46fde938777ae81bf05efe425e52a6ee375df825016720af76","iv":"211b9ecef309e477566a629b4c5e6e04","mac":"8277407d112e9e369408d1bc2b5086553d5d5a141373381aea06e1b0e5809676"}]},"tssKeyTypes":{},"tssNonces":{"default":0},"tssPolyCommits":{"default":[{"x":"d2869f27c3e226d90b275b008f7dc67b8f4b208900a7b98ecc4e5266807d382c","y":"15860fd569413eb7f177e655c4bf855f37920b800235de344fdd518196becfe0"},{"x":"15901123759ec65786e6a4059c6763c1da26c64f4d89b733e9d4676f29168697","y":"18d33eb0bc491ad401352b1980fba1fa864850dd895ae470b69577a6a0451719"}]}}'; -const mpcBackwardCompebilityTestJson1 = +const mpcBackwardCompatibilityTestJson1 = '{"factorEncs":{"default":{"e8e9f13e3552e7c8e4241f4c6f30f6461ce1ad5913a0d506a662abf8611d5398":{"serverEncs":[],"tssIndex":2,"type":"direct","userEnc":{"ciphertext":"a112e64ecb7396593486b0c4e4ca89ffec92ca01c59bbb89d4ca171519e36c4ccd3837096c2160482a0f455241ea71cf","ephemPublicKey":"049abfb15a3d8d8de9ce3d16afdb2dccf1c5002b8a794ec001852c33d8b58a02ff73657862225adde1c691d3e453f41eee83190804dc3004d8d6eb38c994c1e84f","iv":"6a1a9f32e748e6b35a6e4e6ad16eba17","mac":"ff11055118d8cb7feedabab07dd56d132a7d81461eacbe097a0378bf1302f39e"}}}},"factorPubs":{"default":[{"x":"e8e9f13e3552e7c8e4241f4c6f30f6461ce1ad5913a0d506a662abf8611d5398","y":"5ebe4e2c5b7b63161e1720c74391a7371a272b97b59ad2090f086fac74fd9a1a"}]},"generalStore":{"shareDescriptions":{"02e8e9f13e3552e7c8e4241f4c6f30f6461ce1ad5913a0d506a662abf8611d5398":["{\\"module\\":\\"hashedShare\\",\\"dateAdded\\":1720420195095,\\"tssShareIndex\\":2}"]}},"nonce":3,"polyIDList":["03bb90f3505fc1b059a941b60d94ba6c3eefba9e425aa4056729e7b3ab6a545735|025d18273218bf7b1a418f6d10f9dc2ba09303f10c39597684d261068b1063c867|0x0|1|8bb341daba92bfbf500578c1bbd2a05178513470c3dc6e9704996636b33fdf4"],"pubKey":"03bb90f3505fc1b059a941b60d94ba6c3eefba9e425aa4056729e7b3ab6a545735","scopedStore":{},"tkeyStore":{"tssModule":[{"ciphertext":"b1f975cc8ad6b8a3c6d47bf5a9699ae610c6130a903d6d652635d6592c2b80ab971f15d7b443320926fbac51dd33b160c0ccc5e91d4acff6d2645e2b25be8b7c4c9e989bd777893f6e251cf5b5251f9909ffb2b91b644a576ba02aede66bd2f1","ephemPublicKey":"04591474bd11b87545942e6bd0453464f39435c4c2733926b6d5e34c74c0a8e9cb56f27abaa3703e9488e34c915a523709dc7894866a1dce6e212ea4438452db4b","iv":"b2621fa8ba8212ebcc6e245440c251d6","mac":"446a459f68aa47fb3531f1c45cea98f6eceafab69d6a4f6e39d973ad38650a3e"}]},"tssNonces":{"default":0},"tssPolyCommits":{"default":[{"x":"37d65458d3161aeaa6fa76452384b87d34cf1de6bc2d42503e5f9060ce7c7818","y":"f8b4d3fe1c4bd727f0ce6195c3487251f9f37885fddc2636a6fdc2c2fa605bda"},{"x":"c9cbede6afbbf78844dbad83746ad02cee0ada30d784017053ffdd3f88259219","y":"c4650c915d10010ba079b80c2f5594eb4607c17802e07b5c13b32e7fab29ed9d"}]}}'; describe("Metadata new Formatting tests", function () { @@ -71,15 +71,15 @@ describe("Metadata new Formatting tests", function () { }); // to add: add tests for different and multple tags and keytypes\ it("#should able to deserialize new JSON format with backwardcompability test", async function () { - const instance = LegacyMetadata.fromJSON(JSON.parse(mpcBackwardCompebilityTestJson)); + const instance = LegacyMetadata.fromJSON(JSON.parse(mpcBackwardCompatibilityTestJson)); const instanceSerialized = instance.toJSON(); // delete for matching purpose ( jsonstring provide do no have tssKeyTypes.default keytype) delete instanceSerialized.tssKeyTypes.default; - equal(stringify(instanceSerialized), mpcBackwardCompebilityTestJson); + equal(stringify(instanceSerialized), mpcBackwardCompatibilityTestJson); // try to deserialize using new metadata format - const instance1 = Metadata.fromJSON(JSON.parse(mpcBackwardCompebilityTestJson)); + const instance1 = Metadata.fromJSON(JSON.parse(mpcBackwardCompatibilityTestJson)); const instance1Serialized = instance1.toJSON(); const instance2 = Metadata.fromJSON(instance1Serialized); @@ -96,15 +96,15 @@ describe("Metadata new Formatting tests", function () { }); it("#should able to deserialize new JSON format with backwardcompability1 test", async function () { - const instance = LegacyMetadata.fromJSON(JSON.parse(mpcBackwardCompebilityTestJson1)); + const instance = LegacyMetadata.fromJSON(JSON.parse(mpcBackwardCompatibilityTestJson1)); const instanceSerialized = instance.toJSON(); // delete for matching purpose ( jsonstring provide do no have tssKeyTypes.default keytype) delete instanceSerialized.tssKeyTypes; - equal(stringify(instanceSerialized), mpcBackwardCompebilityTestJson1); + equal(stringify(instanceSerialized), mpcBackwardCompatibilityTestJson1); // try to deserialize using new metadata format - const instance1 = Metadata.fromJSON(JSON.parse(mpcBackwardCompebilityTestJson1)); + const instance1 = Metadata.fromJSON(JSON.parse(mpcBackwardCompatibilityTestJson1)); const instance1Serialized = instance1.toJSON(); const instance2 = Metadata.fromJSON(instance1Serialized); diff --git a/packages/tss/src/tss.ts b/packages/tss/src/tss.ts index ff54e61d..2cf6f701 100644 --- a/packages/tss/src/tss.ts +++ b/packages/tss/src/tss.ts @@ -643,7 +643,7 @@ export class TKeyTSS extends TKey { /** * UNSAFE: USE WITH CAUTION * - * Reconstructs and exports the TSS private key. Secp256k1 only. + * Reconstructs and exports the TSS private key. */ async _UNSAFE_exportTssKey(tssOptions: { factorKey: BN; diff --git a/packages/tss/test/helpers.d.ts b/packages/tss/test/helpers.d.ts deleted file mode 100644 index 235ccd8d..00000000 --- a/packages/tss/test/helpers.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { IStorageLayer, KeyType } from "@tkey/common-types"; -import BN from "bn.js"; -import { TSSTorusServiceProvider } from "../src"; -export declare function initStorageLayer(): IStorageLayer; -export declare function fetchPostboxKeyAndSigs(opts: { - serviceProvider: TSSTorusServiceProvider; - verifierName: string; - verifierId: string; -}): Promise<{ - signatures: string[]; - postboxkey: BN; -}>; -export declare function assignTssDkgKeys(opts: { - serviceProvider: TSSTorusServiceProvider; - verifierName: string; - verifierId: string; - maxTSSNonceToSimulate: number; - tssTag?: string; -}): Promise<{ - serverDKGPrivKeys: BN[]; -}>; -export declare function generateKey(keyType: KeyType): { - raw: Buffer; - scalar: BN; -}; diff --git a/packages/tss/test/tssMultiCurveTestCases.ts b/packages/tss/test/tssMultiCurveTestCases.ts index 68592043..9033dbe0 100644 --- a/packages/tss/test/tssMultiCurveTestCases.ts +++ b/packages/tss/test/tssMultiCurveTestCases.ts @@ -56,7 +56,7 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea serverOpts, tssKeyType, }); - fail("should not able to initialize tss"); + fail("should not able to initialize tss using factorPub, deviceTssShare and deviceTssIndes with existing tss "); } catch (e) {} try { await tb.initializeTss({ @@ -65,7 +65,7 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea serverOpts, tssKeyType, }); - fail("should not able to initialize tss"); + fail("should not able to initialize tss using deviceTssShare with existing tss "); } catch (e) {} try { await tb.initializeTss({ @@ -74,7 +74,7 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea serverOpts, tssKeyType, }); - fail("should not able to initialize tss"); + fail("should not able to initialize tss using deviceTssIndes with existing tss "); } catch (e) {} try { await tb.initializeTss({ @@ -83,7 +83,7 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea serverOpts, tssKeyType, }); - fail("should not able to initialize tss"); + fail("should not able to initialize tss using factorPub with existing tss "); } catch (e) {} }; @@ -657,8 +657,6 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea equal(tssPubKeyImported.eq(finalPubKey), true); } { - // tb2.tssTag = "default"; - const finalPubKey = tb2.getTSSCommits(TSS_KEY_TYPE)[0].toEllipticPoint(ecTSS); const finalTssKey = await tb2._UNSAFE_exportTssKey({ From f14236c58ccde2bbcf25f84b0e143d4fcf98c4e5 Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 26 Feb 2025 23:08:46 +0800 Subject: [PATCH 17/22] fix: move legacyMetadataFlag to tkey constructor args --- .../common-types/src/baseTypes/aggregateTypes.ts | 9 +++++++++ packages/core/src/core.ts | 14 +++++++++++--- packages/tss/src/tss.ts | 3 --- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/common-types/src/baseTypes/aggregateTypes.ts b/packages/common-types/src/baseTypes/aggregateTypes.ts index 7ca11eec..a924201e 100644 --- a/packages/common-types/src/baseTypes/aggregateTypes.ts +++ b/packages/common-types/src/baseTypes/aggregateTypes.ts @@ -72,8 +72,16 @@ export interface ITssMetadata { tssPolyCommits: Point[]; + /** + * Public Key Points of all factor keys + */ factorPubs: Point[]; + /** + * Encrypted threshold signature scheme (TSS) share managed by FactorKey. + * This share is securely encrypted to ensure confidentiality and is used in multi-party + * cryptographic signing processes without exposing the full private key. + */ factorEncs: { [factorPubID: string]: FactorEnc; }; @@ -184,6 +192,7 @@ export type TKeyArgs = { customAuthArgs?: CustomAuthArgs; manualSync?: boolean; serverTimeOffset?: number; + legacyMetadataFlag?: boolean; }; export interface SecurityQuestionStoreArgs { diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 709adb6c..06a0b03f 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -90,7 +90,7 @@ class ThresholdKey implements ITKey { serverTimeOffset?: number = 0; - legacyMetadataFlag: boolean = false; + protected legacyMetadataFlag: boolean = false; // secp256k1 key private privKey: BN; @@ -98,7 +98,15 @@ class ThresholdKey implements ITKey { private _ed25519Seed?: Buffer; constructor(args?: TKeyArgs) { - const { enableLogging = false, modules = {}, serviceProvider, storageLayer, manualSync = false, serverTimeOffset } = args || {}; + const { + enableLogging = false, + modules = {}, + serviceProvider, + storageLayer, + manualSync = false, + serverTimeOffset, + legacyMetadataFlag, + } = args || {}; this.enableLogging = enableLogging; this.serviceProvider = serviceProvider; this.storageLayer = storageLayer; @@ -114,7 +122,7 @@ class ThresholdKey implements ITKey { this.setModuleReferences(); // Providing ITKeyApi access to modules this.haveWriteMetadataLock = ""; this.serverTimeOffset = serverTimeOffset; - this.legacyMetadataFlag = false; + this.legacyMetadataFlag = legacyMetadataFlag; } get secp256k1Key(): BN | null { diff --git a/packages/tss/src/tss.ts b/packages/tss/src/tss.ts index 2cf6f701..dada7162 100644 --- a/packages/tss/src/tss.ts +++ b/packages/tss/src/tss.ts @@ -51,7 +51,6 @@ export const LEGACY_KEY_TYPE = "secp256k1"; export interface TSSTKeyArgs extends TKeyArgs { serviceProvider: TSSTorusServiceProvider; tssTag?: string; - legacyMetadataFlag?: boolean; } export interface TKeyTSSInitArgs { @@ -89,7 +88,6 @@ export class TKeyTSS extends TKey { this.serviceProvider = serviceProvider; this.storageLayer = storageLayer; this._tssTag = tssTag; - this.legacyMetadataFlag = legacyMetadataFlag; } public get tssTag(): string { @@ -127,7 +125,6 @@ export class TKeyTSS extends TKey { tbJson.tssTag = this._tssTag; // tbJson.tssKeyType = this.tssKeyType; tbJson.accountSalt = this._accountSalt; - tbJson.legacyMetadataFlag = this.legacyMetadataFlag; return tbJson; } From 5187baaaa1a6e9493399e5aa9c9f4829a06a36fd Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 27 Feb 2025 00:24:21 +0800 Subject: [PATCH 18/22] fix: deserialization issue --- 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 06a0b03f..c5df5c18 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -1201,6 +1201,7 @@ class ThresholdKey implements ITKey { manualSync: this.manualSync, serviceProvider: this.serviceProvider, storageLayer: this.storageLayer, + legacyMetadataFlag: this.legacyMetadataFlag, }; } From 35dc228c98ab8f631189ca87f5a7ecbcb4024b08 Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 27 Feb 2025 00:27:12 +0800 Subject: [PATCH 19/22] fix: make legacyMetadataFlag private --- packages/core/src/core.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index c5df5c18..734f26cb 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -90,7 +90,7 @@ class ThresholdKey implements ITKey { serverTimeOffset?: number = 0; - protected legacyMetadataFlag: boolean = false; + private _legacyMetadataFlag: boolean = false; // secp256k1 key private privKey: BN; @@ -122,7 +122,7 @@ class ThresholdKey implements ITKey { this.setModuleReferences(); // Providing ITKeyApi access to modules this.haveWriteMetadataLock = ""; this.serverTimeOffset = serverTimeOffset; - this.legacyMetadataFlag = legacyMetadataFlag; + this._legacyMetadataFlag = legacyMetadataFlag; } get secp256k1Key(): BN | null { @@ -140,6 +140,10 @@ class ThresholdKey implements ITKey { return null; } + get legacyMetadataFlag() { + return this._legacyMetadataFlag; + } + protected set secp256k1Key(privKey: BN) { this.privKey = privKey; } @@ -172,7 +176,7 @@ class ThresholdKey implements ITKey { serverTimeOffset, }); // overwrite legacyMetadataFlag - tb.legacyMetadataFlag = legacyMetadataFlag ?? false; + tb._legacyMetadataFlag = legacyMetadataFlag ?? false; // this will computed during reconstructKey should we restore here? if (privKey) tb.privKey = new BN(privKey, "hex"); @@ -931,7 +935,7 @@ class ThresholdKey implements ITKey { // inject legacyMetadata flag for AuthMetadata deserialization // it wont affect other fromJSONConstruct as it is just extra parameter - return params.fromJSONConstructor.fromJSON({ ...raw, legacyMetadataFlag: this.legacyMetadataFlag }); + return params.fromJSONConstructor.fromJSON({ ...raw, legacyMetadataFlag: this._legacyMetadataFlag }); } // Lock functions @@ -1201,7 +1205,7 @@ class ThresholdKey implements ITKey { manualSync: this.manualSync, serviceProvider: this.serviceProvider, storageLayer: this.storageLayer, - legacyMetadataFlag: this.legacyMetadataFlag, + legacyMetadataFlag: this._legacyMetadataFlag, }; } @@ -1430,7 +1434,7 @@ class ThresholdKey implements ITKey { const shares = poly.generateShares(shareIndexes); // create metadata to be stored - const metadata = createMetadataInstance(this.legacyMetadataFlag, getPubKeyPoint(this.privKey)); + const metadata = createMetadataInstance(this._legacyMetadataFlag, getPubKeyPoint(this.privKey)); metadata.addFromPolynomialAndShares(poly, shares); const serviceProviderShare = shares[shareIndexes[0].toString("hex")]; const shareStore = new ShareStore(serviceProviderShare, poly.getPolynomialID()); From 3efc4e8eb81dbf98df294e464227d8510684027a Mon Sep 17 00:00:00 2001 From: ieow Date: Thu, 27 Feb 2025 00:55:40 +0800 Subject: [PATCH 20/22] fix: fail test message remove not needed check --- packages/tss/src/tss.ts | 10 ++-------- packages/tss/test/tssMultiCurveTestCases.ts | 8 ++++---- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/tss/src/tss.ts b/packages/tss/src/tss.ts index dada7162..73e0eefb 100644 --- a/packages/tss/src/tss.ts +++ b/packages/tss/src/tss.ts @@ -248,15 +248,9 @@ export class TKeyTSS extends TKey { /** * Initializes Tss Curve with Ed25519 keyType * will use same factorPubs from Secp256k1 if existing Secp256k1 curve present + * require import seed for ed25519 */ async initializeTssEd25519(params: Omit & { importKey: Buffer }): Promise { - // only support service provider with secp256k1 key type - if (this.serviceProvider.customAuthArgs.keyType === KeyType.ed25519) { - if (this.metadata.getTssData(KeyType.ed25519, TSS_TAG_DEFAULT)) { - throw CoreError.default("Multiple Curve do not support for postboxKey Ed"); - } - } - const { importKey } = params; if (!importKey) { throw CoreError.default("importKey is required"); @@ -272,7 +266,7 @@ export class TKeyTSS extends TKey { const { factorPub, deviceTSSIndex, deviceTSSShare } = params; if (ed25519Exist) { - throw CoreError.default("TSS account already exists for secp256k1 key type"); + throw CoreError.default("TSS account already exists for ed25519 key type"); } let factorPubs = [factorPub]; diff --git a/packages/tss/test/tssMultiCurveTestCases.ts b/packages/tss/test/tssMultiCurveTestCases.ts index 9033dbe0..13f5e12c 100644 --- a/packages/tss/test/tssMultiCurveTestCases.ts +++ b/packages/tss/test/tssMultiCurveTestCases.ts @@ -56,7 +56,7 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea serverOpts, tssKeyType, }); - fail("should not able to initialize tss using factorPub, deviceTssShare and deviceTssIndes with existing tss "); + fail("should not able to initialize new tss curve using factorPub, deviceTssShare and deviceTssIndes with existing tss "); } catch (e) {} try { await tb.initializeTss({ @@ -65,7 +65,7 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea serverOpts, tssKeyType, }); - fail("should not able to initialize tss using deviceTssShare with existing tss "); + fail("should not able to initialize new tss curve using deviceTssShare with existing tss "); } catch (e) {} try { await tb.initializeTss({ @@ -74,7 +74,7 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea serverOpts, tssKeyType, }); - fail("should not able to initialize tss using deviceTssIndes with existing tss "); + fail("should not able to initialize new tss curve using deviceTssIndes with existing tss "); } catch (e) {} try { await tb.initializeTss({ @@ -83,7 +83,7 @@ const multiCurveTestCases = (params: { TSS_KEY_TYPE: KeyType; legacyFlag: boolea serverOpts, tssKeyType, }); - fail("should not able to initialize tss using factorPub with existing tss "); + fail("should not able to initialize new tss curve using factorPub with existing tss "); } catch (e) {} }; From 5e037afa97d705f798c09f8fd48f60c570537700 Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 28 Feb 2025 03:01:54 +0800 Subject: [PATCH 21/22] fix: update comment remove unsued comment or code --- packages/core/src/metadata.ts | 8 ++++++-- packages/tss/src/tss.ts | 1 - 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index 330a2b1f..1ffe09ef 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -122,7 +122,9 @@ export class Metadata implements IMetadata { // else would be legacy version, migrate for secp version } else if (factorEncs instanceof Object) { metadata.tss = {}; - // some tests case on backward compatbility tests having serialized metadata with empty tssKeyTypes + + // tssKeyTypeJson might be undefined, tssKeyTypes need to fallback to `{}` + // - some tests case on backward compatbility tests having tssKeyTypeJson undefined const tssKeyTypes: Record = tssKeyTypesJson ?? {}; Object.keys(factorEncs).forEach((tssTag) => { @@ -454,7 +456,9 @@ export class LegacyMetadata extends Metadata { if (Object.keys(factorEncs).length === 0) return metadata; metadata.tss = {}; - // some tests case on backward compatbility tests having serialized metadata with empty tssKeyType + + // tssKeyTypeJson might be undefined, tssKeyTypes need to fallback to `{}` + // - some tests case on backward compatbility tests having tssKeyTypeJson undefined const tssKeyTypes: Record = tssKeyTypesJson ?? {}; Object.keys(factorEncs).forEach((tssTag) => { diff --git a/packages/tss/src/tss.ts b/packages/tss/src/tss.ts index 73e0eefb..b1df81e9 100644 --- a/packages/tss/src/tss.ts +++ b/packages/tss/src/tss.ts @@ -123,7 +123,6 @@ export class TKeyTSS extends TKey { toJSON(): StringifiedType { const tbJson = super.toJSON(); tbJson.tssTag = this._tssTag; - // tbJson.tssKeyType = this.tssKeyType; tbJson.accountSalt = this._accountSalt; return tbJson; } From a7ecc464d5adb9e25332fec15e9f6e3df79097a6 Mon Sep 17 00:00:00 2001 From: ieow Date: Tue, 11 Mar 2025 17:54:11 +0800 Subject: [PATCH 22/22] fix: add stringified value check serialize metadata.tss --- packages/core/src/metadata.ts | 2 +- packages/core/src/tssMetadata.ts | 65 ++++++++++++++++++----- packages/core/test/metadataFormat.test.js | 9 ++-- 3 files changed, 58 insertions(+), 18 deletions(-) diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index 1ffe09ef..40af9baf 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -354,7 +354,7 @@ export class Metadata implements IMetadata { version: METADATA_VERSION, }; - return Object.keys(this.tss ?? {}).length > 0 ? { ...jsonObject, tss: this.tss } : jsonObject; + return Object.keys(this.tss ?? {}).length > 0 ? { ...jsonObject, tss: JSON.parse(JSON.stringify(this.tss)) } : jsonObject; } /** diff --git a/packages/core/src/tssMetadata.ts b/packages/core/src/tssMetadata.ts index 31fc70f5..65b427d4 100644 --- a/packages/core/src/tssMetadata.ts +++ b/packages/core/src/tssMetadata.ts @@ -1,4 +1,29 @@ -import { FactorEnc, ISerializable, ITssMetadata, KeyType, Point, StringifiedType } from "@tkey/common-types"; +import { EncryptedMessage, FactorEnc, FactorEncType, ISerializable, ITssMetadata, KeyType, Point, StringifiedType } from "@tkey/common-types"; + +export const FromJsonEncryptedMessage = (value: StringifiedType): EncryptedMessage => { + const { ciphertext, ephemPublicKey, iv, mac } = value; + if (typeof ciphertext !== "string") throw new Error("ciphertext is not a string"); + if (typeof ephemPublicKey !== "string") throw new Error("ephemPublicKey is not a string"); + if (typeof iv !== "string") throw new Error("iv is not a string"); + if (typeof mac !== "string") throw new Error("mac is not a string"); + + return { + ciphertext, + ephemPublicKey, + iv, + mac, + }; +}; + +export const FromJsonFactorEnc = (value: StringifiedType): FactorEnc => { + const { tssIndex, type, userEnc, serverEncs } = value; + if (typeof tssIndex !== "number") throw new Error("tssIndex is not a number"); + if (typeof type !== "string") throw new Error("type is not a string"); + if (typeof userEnc !== "object") throw new Error("userEnc is not a string"); + if (!Array.isArray(serverEncs)) throw new Error("serverEncs is not an array"); + + return { type: type as FactorEncType, tssIndex, userEnc: FromJsonEncryptedMessage(userEnc), serverEncs: serverEncs.map(FromJsonEncryptedMessage) }; +}; export class TssMetadata implements ITssMetadata, ISerializable { tssTag: string; @@ -27,24 +52,36 @@ export class TssMetadata implements ITssMetadata, ISerializable { static fromJSON(value: StringifiedType): TssMetadata { const { tssTag, tssKeyType, tssPolyCommits, tssNonce, factorPubs, factorEncs } = value; + if (typeof tssTag !== "string") throw new Error("tssTag is not a string"); + if (typeof tssNonce !== "number") throw new Error("tssNonce is not a number"); + if (typeof factorEncs !== "object") throw new Error("factorEncs is not an object"); + + if (!(tssKeyType in KeyType)) { + throw new Error("tssKeyType is not a valid KeyType"); + } + + if (!Array.isArray(tssPolyCommits)) { + throw new Error("tssPolyCommits is not an array"); + } + + if (!Array.isArray(factorPubs)) { + throw new Error("factorPubs is not an array"); + } + + for (const key in factorEncs) { + const factorEnc = factorEncs[key]; + factorEncs[key] = FromJsonFactorEnc(factorEnc); + } + const tssMetadata = new TssMetadata({ tssTag, tssKeyType, tssNonce, - tssPolyCommits, + tssPolyCommits: (tssPolyCommits as Point[]).map((obj) => Point.fromJSON(obj)), + factorPubs: (factorPubs as Point[]).map((obj) => Point.fromJSON(obj)), factorEncs, - factorPubs, }); - if (tssPolyCommits) { - tssMetadata.tssPolyCommits = (tssPolyCommits as Point[]).map((obj) => new Point(obj.x, obj.y)); - } - if (factorPubs) { - tssMetadata.factorPubs = (factorPubs as Point[]).map((obj) => new Point(obj.x, obj.y)); - } - - if (factorEncs) tssMetadata.factorEncs = factorEncs; - return tssMetadata; } @@ -53,8 +90,8 @@ export class TssMetadata implements ITssMetadata, ISerializable { tssTag: this.tssTag, tssKeyType: this.tssKeyType, tssNonce: this.tssNonce, - tssPolyCommits: this.tssPolyCommits, - factorPubs: this.factorPubs, + tssPolyCommits: this.tssPolyCommits.map((pub) => pub.toJSON()), + factorPubs: this.factorPubs.map((pub) => pub.toJSON()), factorEncs: this.factorEncs, }; } diff --git a/packages/core/test/metadataFormat.test.js b/packages/core/test/metadataFormat.test.js index 101554be..336a9bfc 100644 --- a/packages/core/test/metadataFormat.test.js +++ b/packages/core/test/metadataFormat.test.js @@ -45,12 +45,14 @@ describe("Metadata new Formatting tests", function () { it("#should able to deserialize legacy JSON", async function () { const legacyInstance = LegacyMetadata.fromJSON(JSON.parse(legacyJSONEd25519)); const legacyInstanceSerialized = legacyInstance.toJSON(); - equal(JSON.stringify(legacyInstanceSerialized), legacyJSONEd25519); + deepEqual(legacyInstanceSerialized, JSON.parse(legacyJSONEd25519)); + // equal(JSON.stringify(legacyInstanceSerialized), JSON.stringify(JSON.parse(legacyJSONEd25519))); const legacyInstance1 = LegacyMetadata.fromJSON(JSON.parse(legacyJSONSecp256k1)); const legacyInstanceSerialized1 = legacyInstance1.toJSON(); - equal(JSON.stringify(legacyInstanceSerialized1), legacyJSONSecp256k1); + deepEqual(legacyInstanceSerialized1, JSON.parse(legacyJSONSecp256k1)); + // equal(JSON.stringify(legacyInstanceSerialized1), legacyJSONSecp256k1); const instance1 = Metadata.fromJSON(JSON.parse(legacyJSONSecp256k1)); @@ -67,7 +69,8 @@ describe("Metadata new Formatting tests", function () { it("#should able to deserialize new JSON format with multicurve", async function () { const instance = Metadata.fromJSON(JSON.parse(multiCurveJson)); const instanceSerialized = instance.toJSON(); - equal(JSON.stringify(instanceSerialized), multiCurveJson); + deepEqual(instanceSerialized, JSON.parse(multiCurveJson)); + // equal(JSON.stringify(instanceSerialized), multiCurveJson); }); // to add: add tests for different and multple tags and keytypes\ it("#should able to deserialize new JSON format with backwardcompability test", async function () {