diff --git a/modules/bitgo/test/v2/unit/internal/tssUtils/ecdsa.ts b/modules/bitgo/test/v2/unit/internal/tssUtils/ecdsa.ts index 6e9574b491..65dfbee84b 100644 --- a/modules/bitgo/test/v2/unit/internal/tssUtils/ecdsa.ts +++ b/modules/bitgo/test/v2/unit/internal/tssUtils/ecdsa.ts @@ -239,7 +239,9 @@ describe('TSS Ecdsa Utils:', async function () { ], }); const nockGPGKey = await nockGetBitgoPublicKeyBasedOnFeatureFlags(coinName, 'enterprise_id', nitroGPGKeypair); - const bitgoGpgPublicKey = await tssUtils.getBitgoGpgPubkeyBasedOnFeatureFlags('enterprise_id'); + const { mpcv2PublicKey: bitgoGpgPublicKey } = await tssUtils.getBitgoGpgPubkeyBasedOnFeatureFlags( + 'enterprise_id' + ); should.equal(nockGPGKey.publicKey, bitgoGpgPublicKey.armor()); }); diff --git a/modules/sdk-core/src/bitgo/tss/bitgoPubKeys.ts b/modules/sdk-core/src/bitgo/tss/bitgoPubKeys.ts index be75b194f1..83325de52b 100644 --- a/modules/sdk-core/src/bitgo/tss/bitgoPubKeys.ts +++ b/modules/sdk-core/src/bitgo/tss/bitgoPubKeys.ts @@ -22,6 +22,16 @@ export const bitgoMpcGpgPubKeys = { prod: '-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxk8EZmHyKBMFK4EEAAoCAwS+tBY/2P47G0mgYRhq90jK475f02f3f3W4VbKA\nSwd9s6aI5spk7GeYsjRvP6rBf4vFIjLj7Ty7K2V03rZPQc8bzQVCaXRHb8KE\nBBMTCAA2BQJmYfIpAgsJCRAKMB4ATA5V7QIVCAIWAAIbAwIeARYhBAIdflLB\nK4deHok+gQowHgBMDlXtAACRpAD/UUbTsFEkjt+CCJmVq2v5l6oocR9hXXkT\nzhRQKQIwSigA/RVvS2RsoZLkaL68GUHLy63XVHtG149pN3BYPwb63EcQwoQE\nEBMIADYFAmZh8ioCCwkJEFlh2DLM6IVNAhUIAhYAAhsDAh4BFiEEsb9f1VA0\n9rOLgFM+WWHYMszohU0AAFC8AP4wH0ndmzCSg2O/a+ZfqW2yA465BFvDM1ij\nvMtCJYSxzAD/RjcfDfkN4Ipjaa2LRuHxfHZbvgCgoOChsJLv4KQLTafOUwRm\nYfIoEgUrgQQACgIDBM+W01KEUaAm8a3hMBWG9EShyNrZxbtv9ryd8JIIxeEb\nEckLTVQvIer3YvDUyjeY/v83VCRdm6H5cahV92sydrIDAQgHwoQEGBMIADYF\nAmZh8ikCCwkJEAowHgBMDlXtAhUIAhYAAhsMAh4BFiEEAh1+UsErh14eiT6B\nCjAeAEwOVe0AAEcSAP9H96t/z9uKe9lAoq2d9Dt3Hrq9eM6sLQ2+cVblngP+\nDQD/dCqHYQzDdsuc9Y3HmWbhCK1Um6ewppkct1v5lmbaJ1bOTwRmYfIoEwUr\ngQQACgIDBJDIofWOLj/JkBFkZDh3a++LNEH8TBNlDZvU7tNfURXWApxV2VAb\nFBKYddN03Q1SBpMR0GkPl42rH7whYdeaEBHCwEoEGBMIALwFAmZh8ioCCwkJ\nEAowHgBMDlXtAhUIAhYAAhsCAh4BhSAEGBMIADYFAmZh8ioCCwkJEGAfBsMT\nFzVjAhUIAhYAAhsCAh4BFiEE2zAGHSaLnswqIvBrYB8GwxMXNWMAANroAP0f\ntFPumKFwQrCf7OMHQWsesrQYpKT6Z65VbewBoGaGigD/UkeeygTtlyzTV2YF\nNAjWAzaQtXWmmzRgnOj0IKub39MWIQQCHX5SwSuHXh6JPoEKMB4ATA5V7QAA\nTjMA/jDSVXJNblr/kSLNFTordgDjKP0nN1aElvFUFh/QEVT0AP9lmf2Fc/o7\nyYOGPPg4OvvU6odrTsuNgljvPqBlaCc2EA==\n=ZLkt\n-----END PGP PUBLIC KEY BLOCK-----\n', }, }, + eddsaMpcv2: { + nitro: { + test: '', // TODO WCI-205: add eddsaMpcv2 key for nitro + prod: '', + }, + onprem: { + test: '-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxjMEad+QCxYJKwYBBAHaRw8BAQdAG0FBM/1JRvo7KlLvhp1Mwi6IWmV3V9xy\nZZcByg0fDQ3NGGhzbSA8aHNtQHRlc3QuYml0Z28uY29tPsKEBBMWCgA2BYJp\n35AMAgsJCZCuV6d9sal31QIVCgIWAAKbAwIeARYhBKSiXcKo4xMT5wwfh65X\np32xqXfVAAA2sAEAgmk543UetoUoOoOvEAhOrRBbF4h6VwcH9cyR9UGSwygA\n/2KJJadiAvaepqFZxyE77rFM7ZfqhRMsoAc2MfslvuQMwoQEEBMIADYFgmnf\nkA0CCwkJkN2vJwOOuE03AhUKAhYAApsDAh4BFiEEjMSQwTRbUtG1fSvR3a8n\nA464TTcAANNVAQD1RTu/bJmPBRvWbvuIiuT1WUxYsSuoXWwki1YImN1gMAD+\nOPU+v056hkdoD8Rcd8D+HhoNlJAbRbZWg/qjxr+S6lLOOARp35AMEgorBgEE\nAZdVAQUBAQdAgqwA9UhQGuseztLr2ZM189pBjrW6sAJ5m6icDYOWMHEDAQgH\nwoUEGBYKADcFgmnfkAwCCwkJkK5Xp32xqXfVAhUKAhYAA5sECAIeARYhBKSi\nXcKo4xMT5wwfh65Xp32xqXfVAAC4uwEAlkVzGDPJYETIV4pXYpCdaeGLBjm9\ny1sRb2nx9ET7m+4BANpb0vKKBrKZTAx/+rINgWoxKPnKPsycOE8bYHY3zKAN\nzjMEad+QDBYJKwYBBAHaRw8BAQdAanwKEY5QEAPafbhM5/BIJZRyLmyNpBTo\ntntTIq0nOt/CwEoEGBYKALwFgmnfkA0CCwkJkK5Xp32xqXfVAhUKAhYAApsC\nAh4BhaAEGBYKADYFgmnfkA0CCwkJkAuXAU6A6KYvAhUKAhYAApsCAh4BFiEE\neaGtxZYsjWFFYrD6C5cBToDopi8AAEp3AQCP4bCSYbhjNJfGnCCOq24DaozR\nUFg0hNlpfSA9NYZ3bwD/fdtV5m5a1QyvcyGEnv37l1H7UGbQlG1Zp8roqZh3\nqwMWIQSkol3CqOMTE+cMH4euV6d9sal31QAAMegA/i3sBuAwvrBsp8ozp7O2\nzQlIbjuvDBMomIXj1rRmgoOkAQDRqsQpAvITd0LMFN2dCqCBIGAeIqznFX0C\nyqvU0m8sCQ==\n=/R3k\n-----END PGP PUBLIC KEY BLOCK-----\n', + prod: '', // TODO WCI-142: add prod eddsaMpcv2 key + }, + }, }; export function getBitgoMpcGpgPubKey( @@ -61,6 +71,10 @@ export function isBitgoMpcPubKey(key: string, mpcvVersion: 'mpcv1' | 'mpcv2'): b return Object.values(bitgoMpcGpgPubKeys[mpcvVersion]).some((envKeys) => Object.values(envKeys).includes(key)); } +export function isBitgoEddsaMpcv2PubKey(key: string): boolean { + return Object.values(bitgoMpcGpgPubKeys.eddsaMpcv2).some((envKeys) => Object.values(envKeys).includes(key)); +} + export function envRequiresBitgoPubGpgKeyConfig(env: EnvironmentName): boolean { return env === 'prod' || env === 'test' || env === 'staging' || env === 'adminProd' || env === 'adminTest'; } diff --git a/modules/sdk-core/src/bitgo/utils/opengpgUtils.ts b/modules/sdk-core/src/bitgo/utils/opengpgUtils.ts index 635c25e4e5..9e303004a7 100644 --- a/modules/sdk-core/src/bitgo/utils/opengpgUtils.ts +++ b/modules/sdk-core/src/bitgo/utils/opengpgUtils.ts @@ -35,7 +35,9 @@ export type AuthEncMessage = { * @param {BitGoBase} bitgo BitGo object * @return {Key} public gpg key */ -export async function getBitgoGpgPubKey(bitgo: BitGoBase): Promise<{ mpcV1: Key; mpcV2: Key | undefined }> { +export async function getBitgoGpgPubKey( + bitgo: BitGoBase +): Promise<{ mpcV1: Key; mpcV2: Key | undefined; eddsaMpcV2: Key | undefined }> { const constants = await bitgo.fetchConstants(); if (!constants.mpc || !constants.mpc.bitgoPublicKey) { throw new Error('Unable to create MPC keys - bitgoPublicKey is missing from constants'); @@ -45,7 +47,14 @@ export async function getBitgoGpgPubKey(bitgo: BitGoBase): Promise<{ mpcV1: Key; const bitgoMPCv2PublicKeyStr = constants.mpc.bitgoMPCv2PublicKey ? await readKey({ armoredKey: constants.mpc.bitgoMPCv2PublicKey as string }) : undefined; - return { mpcV1: await readKey({ armoredKey: bitgoPublicKeyStr }), mpcV2: bitgoMPCv2PublicKeyStr }; + const bitgoEddsaMpcv2PublicKeyStr = constants.mpc.bitgoEddsaMpcv2PublicKey + ? await readKey({ armoredKey: constants.mpc.bitgoEddsaMpcv2PublicKey as string }) + : undefined; + return { + mpcV1: await readKey({ armoredKey: bitgoPublicKeyStr }), + mpcV2: bitgoMPCv2PublicKeyStr, + eddsaMpcV2: bitgoEddsaMpcv2PublicKeyStr, + }; } /** diff --git a/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts b/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts index de7fd8bfdd..dffd403f78 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts @@ -53,6 +53,7 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil private _wallet?: IWallet; protected bitgoPublicGpgKey: openpgp.Key; protected bitgoMPCv2PublicGpgKey: openpgp.Key | undefined; + protected bitgoEddsaMpcv2PublicGpgKey: openpgp.Key | undefined; constructor(bitgo: BitGoBase, baseCoin: IBaseCoin, wallet?: IWallet) { super(bitgo, baseCoin); @@ -67,7 +68,7 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil } protected async setBitgoGpgPubKey(bitgo) { - const { mpcV1, mpcV2 } = await getBitgoGpgPubKey(bitgo); + const { mpcV1, mpcV2, eddsaMpcV2 } = await getBitgoGpgPubKey(bitgo); // Do not unset the MPCv1 key if it is already set. This is to avoid unsetting if extra constants api calls fail. if (mpcV1 !== undefined) { this.bitgoPublicGpgKey = mpcV1; @@ -76,6 +77,10 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil if (mpcV2 !== undefined) { this.bitgoMPCv2PublicGpgKey = mpcV2; } + // Do not unset the EdDSA MPCv2 key if it is already set + if (eddsaMpcV2 !== undefined) { + this.bitgoEddsaMpcv2PublicGpgKey = eddsaMpcV2; + } } public async pickBitgoPubGpgKeyForSigning( @@ -104,8 +109,8 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil // First try to get the key based on feature flags, if that fails, fallback to the default key from constants api. bitgoGpgPubKey = await this.getBitgoGpgPubkeyBasedOnFeatureFlags(enterpriseId, isMpcv2, reqId) .then( - async (pubKey) => - pubKey ?? (isMpcv2 ? await this.getBitgoMpcv2PublicGpgKey() : await this.getBitgoPublicGpgKey()) + async ({ mpcv2PublicKey }) => + mpcv2PublicKey ?? (isMpcv2 ? await this.getBitgoMpcv2PublicGpgKey() : await this.getBitgoPublicGpgKey()) ) .catch(async (e) => (isMpcv2 ? await this.getBitgoMpcv2PublicGpgKey() : await this.getBitgoPublicGpgKey())); } else { @@ -141,6 +146,17 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil return this.bitgoMPCv2PublicGpgKey; } + async getBitgoEddsaMpcv2PublicGpgKey(): Promise { + if (!this.bitgoEddsaMpcv2PublicGpgKey) { + // retry getting bitgo's EdDSA MPCv2 gpg key + await this.setBitgoGpgPubKey(this.bitgo); + if (!this.bitgoEddsaMpcv2PublicGpgKey) { + throw new Error("Failed to get Bitgo's EdDSA MPCv2 gpg key"); + } + } + return this.bitgoEddsaMpcv2PublicGpgKey; + } + async createBitgoHeldBackupKeyShare( userGpgKey: SerializedKeyPair, enterprise: string | undefined @@ -552,8 +568,9 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil } /** - * It gets the appropriate BitGo GPG public key for key creation based on a + * It gets the appropriate BitGo GPG public keys for key creation based on a * combination of coin and the feature flags on the user and their enterprise if set. + * Returns both the default MPCv2 key and the EdDSA-specific MPCv2 key (if present). * @param enterpriseId - enterprise under which user wants to create the wallet * @param isMPCv2 - true to get the MPCv2 GPG public key, defaults to false * @param reqId - request tracer request id @@ -562,7 +579,7 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil enterpriseId: string | undefined, isMPCv2 = false, reqId?: IRequestTracer - ): Promise { + ): Promise<{ mpcv2PublicKey: Key; eddsaMpcv2PublicKey: Key }> { const reqTracer = reqId || new RequestTracer(); this.bitgo.setRequestTracer(reqTracer); const response: BitgoGPGPublicKey = await this.bitgo @@ -570,8 +587,11 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil .query({ enterpriseId }) .retry(3) .result(); - const bitgoPublicKeyStr = isMPCv2 ? response.mpcv2PublicKey : response.publicKey; - return readKey({ armoredKey: bitgoPublicKeyStr as string }); + const mpcv2PublicKey = await readKey({ + armoredKey: (isMPCv2 ? response.mpcv2PublicKey : response.publicKey) as string, + }); + const eddsaMpcv2PublicKey = await readKey({ armoredKey: response.eddsaMpcv2PublicKey as string }); + return { mpcv2PublicKey, eddsaMpcv2PublicKey }; } /** diff --git a/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts b/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts index 719ffd4507..f773e370a7 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts @@ -572,6 +572,7 @@ export interface BitgoGPGPublicKey { name: string; publicKey: string; mpcv2PublicKey?: string; + eddsaMpcv2PublicKey?: string; enterpriseId: string; } diff --git a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts index 32e185d3b8..41391d7232 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts @@ -118,8 +118,8 @@ export class EcdsaUtils extends BaseEcdsaUtils { // Get the BitGo public key based on user/enterprise feature flags // If it doesn't work, use the default public key from the constants - const bitgoPublicGpgKey = - (await this.getBitgoGpgPubkeyBasedOnFeatureFlags(params.enterprise)) ?? this.bitgoPublicGpgKey; + const { mpcv2PublicKey: primaryGpgKey } = await this.getBitgoGpgPubkeyBasedOnFeatureFlags(params.enterprise); + const bitgoPublicGpgKey = primaryGpgKey ?? this.bitgoPublicGpgKey; const bitgoKeychain = await this.createBitgoKeychain({ userGpgKey, diff --git a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts index 55f1d9b725..129877a4af 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts @@ -64,9 +64,10 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils { // Get the BitGo public key based on user/enterprise feature flags // If it doesn't work, use the default public key from the constants - const bitgoPublicGpgKey = ( - (await this.getBitgoGpgPubkeyBasedOnFeatureFlags(params.enterprise, true)) ?? this.bitgoMPCv2PublicGpgKey - ).armor(); + const { mpcv2PublicKey } = await this.getBitgoGpgPubkeyBasedOnFeatureFlags(params.enterprise, true); + const mpcv2Key = mpcv2PublicKey ?? this.bitgoMPCv2PublicGpgKey; + assert(mpcv2Key, 'Failed to get BitGo MPCv2 GPG public key'); + const bitgoPublicGpgKey = mpcv2Key.armor(); if (envRequiresBitgoPubGpgKeyConfig(this.bitgo.getEnv())) { // Ensure the public key is one of the expected BitGo public keys when in test or prod. diff --git a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts index 6e5534bf8f..e5af32d13b 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts @@ -12,7 +12,7 @@ import { import { EddsaMPSDkg, MPSComms, MPSTypes } from '@bitgo/sdk-lib-mpc'; import { KeychainsTriplet } from '../../../baseCoin'; import { AddKeychainOptions, Keychain, KeyType } from '../../../keychain'; -import { envRequiresBitgoPubGpgKeyConfig, isBitgoMpcPubKey } from '../../../tss/bitgoPubKeys'; +import { envRequiresBitgoPubGpgKeyConfig, isBitgoEddsaMpcv2PubKey } from '../../../tss/bitgoPubKeys'; import { generateGPGKeyPair } from '../../opengpgUtils'; import { MPCv2PartiesEnum } from '../ecdsa/typesMPCv2'; import { BaseEddsaUtils } from './base'; @@ -35,14 +35,15 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { const backupGpgPublicKey = backupKeyPair.publicKey; const [backupPk, backupSk] = await MPSComms.extractEd25519KeyPair(backupGpgKey); - // Get the BitGo public key based on user/enterprise feature flags; - // fall back to the hardcoded MPCv2 public key from constants. - const bitgoPublicGpgKey = - (await this.getBitgoGpgPubkeyBasedOnFeatureFlags(params.enterprise, true)) ?? this.bitgoMPCv2PublicGpgKey; + // Get the BitGo EdDSA MPCv2 public key (ed25519). Using the default mpcv2PublicKey (secp256k1) + // here would cause a WASM "Invalid Input" error, so we require the dedicated eddsaMpcv2PublicKey. + const { eddsaMpcv2PublicKey } = await this.getBitgoGpgPubkeyBasedOnFeatureFlags(params.enterprise, true); + const bitgoPublicGpgKey = eddsaMpcv2PublicKey ?? this.bitgoEddsaMpcv2PublicGpgKey; + assert(bitgoPublicGpgKey, 'Failed to get BitGo EdDSA MPCv2 GPG public key'); const bitgoPublicGpgKeyArmored = bitgoPublicGpgKey.armor(); if (envRequiresBitgoPubGpgKeyConfig(this.bitgo.getEnv())) { - assert(isBitgoMpcPubKey(bitgoPublicGpgKeyArmored, 'mpcv2'), 'Invalid BitGo GPG public key'); + assert(isBitgoEddsaMpcv2PubKey(bitgoPublicGpgKeyArmored), 'Invalid BitGo GPG public key'); } const bitgoKeyObj = await pgp.readKey({ armoredKey: bitgoPublicGpgKeyArmored });