From 4d9f6269f61b04b6a4190197736f4a11e6800733 Mon Sep 17 00:00:00 2001 From: Marzooqa Naeema Kather Date: Tue, 21 Apr 2026 19:36:41 +0530 Subject: [PATCH] feat(sdk-core): add dedicated EdDSA MPCv2 BitGo GPG public key support EdDSA MPCv2 (MPS) DKG requires an ed25519 GPG key with an X25519 encryption subkey. Using the existing secp256k1 mpcv2PublicKey caused a WASM "Invalid Input" error. This adds a dedicated eddsaMpcv2PublicKey field throughout the key-fetching pipeline so EdDSA and ECDSA MPCv2 wallets each use the correct BitGo GPG key. Also fixes ed25519 GPG key generation to include a dedicated signing subkey, which is required by the HSM's mps.js validator. WCI-223 Co-Authored-By: Claude Sonnet 4.6 TICKET: WCI-223 --- .../test/v2/unit/internal/opengpgUtils.ts | 13 ++++++- .../internal/tssUtils/bitgoMpcGpgPubKeys.ts | 16 +++++++++ .../test/v2/unit/internal/tssUtils/ecdsa.ts | 5 ++- .../test/v2/unit/internal/tssUtils/eddsa.ts | 36 +++++++++++++++++++ .../tssUtils/eddsaMPCv2/createKeychains.ts | 1 + .../sdk-core/src/bitgo/tss/bitgoPubKeys.ts | 14 ++++++++ .../sdk-core/src/bitgo/utils/opengpgUtils.ts | 17 +++++++-- .../src/bitgo/utils/tss/baseTSSUtils.ts | 25 +++++++++---- .../sdk-core/src/bitgo/utils/tss/baseTypes.ts | 1 + .../src/bitgo/utils/tss/ecdsa/ecdsa.ts | 4 +-- .../src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts | 7 ++-- .../src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts | 13 +++---- 12 files changed, 130 insertions(+), 22 deletions(-) diff --git a/modules/bitgo/test/v2/unit/internal/opengpgUtils.ts b/modules/bitgo/test/v2/unit/internal/opengpgUtils.ts index b8c878935a..9127030d7e 100644 --- a/modules/bitgo/test/v2/unit/internal/opengpgUtils.ts +++ b/modules/bitgo/test/v2/unit/internal/opengpgUtils.ts @@ -285,7 +285,7 @@ describe('OpenGPG Utils Tests', function () { should.exist(gpgKey.publicKey); }); - it('should generate a a GPG key for with random name and email', async function () { + it('should generate a a GPG key for ed25519 with random name and email', async function () { const gpgKey = await openpgpUtils.generateGPGKeyPair('ed25519'); should.exist(gpgKey); @@ -293,6 +293,17 @@ describe('OpenGPG Utils Tests', function () { should.exist(gpgKey.publicKey); }); + it('should generate an ed25519 GPG key with a dedicated signing subkey', async function () { + const gpgKey = await openpgpUtils.generateGPGKeyPair('ed25519'); + const parsedKey = await openpgp.readKey({ armoredKey: gpgKey.publicKey }); + + const signingKey = await parsedKey.getSigningKey(); + // The signing key must be a subkey (has bindingSignatures), not the primary key. + // The HSM verifies the binding signature during MPS keygen round 1. + assert.ok('bindingSignatures' in signingKey, 'signing key should be a subkey with bindingSignatures'); + assert.ok(signingKey.bindingSignatures.length > 0, 'signing subkey should have at least one binding signature'); + }); + it('should generate a a GPG key with provided name and email', async function () { const userName = 'John Doe'; const userEmail = 'john.doe@example.com'; diff --git a/modules/bitgo/test/v2/unit/internal/tssUtils/bitgoMpcGpgPubKeys.ts b/modules/bitgo/test/v2/unit/internal/tssUtils/bitgoMpcGpgPubKeys.ts index 822f2e7d5d..69ca509780 100644 --- a/modules/bitgo/test/v2/unit/internal/tssUtils/bitgoMpcGpgPubKeys.ts +++ b/modules/bitgo/test/v2/unit/internal/tssUtils/bitgoMpcGpgPubKeys.ts @@ -224,4 +224,20 @@ describe('TSS MPC Pick BitGo GPG Pub Key Utils:', function () { // Also verify it's the same as what's returned by testPickBitgoPubGpgKeyForSigning gpgKey.armor().should.equal(capturedKey); }); + + describe('BitgoMpcGpgPubKeys.isBitgoEddsaMpcv2PubKey', function () { + it('should return true for the hardcoded on-prem test EdDSA MPCv2 key', function () { + const key = BitgoMpcGpgPubKeys.bitgoMpcGpgPubKeys['eddsaMpcv2']['onprem']['test']; + assert.ok(BitgoMpcGpgPubKeys.isBitgoEddsaMpcv2PubKey(key)); + }); + + it('should return false for the ECDSA MPCv2 on-prem test key', function () { + const key = BitgoMpcGpgPubKeys.bitgoMpcGpgPubKeys['mpcv2']['onprem']['test']; + assert.ok(!BitgoMpcGpgPubKeys.isBitgoEddsaMpcv2PubKey(key)); + }); + + it('should return false for an arbitrary string', function () { + assert.ok(!BitgoMpcGpgPubKeys.isBitgoEddsaMpcv2PubKey('not-a-known-key')); + }); + }); }); diff --git a/modules/bitgo/test/v2/unit/internal/tssUtils/ecdsa.ts b/modules/bitgo/test/v2/unit/internal/tssUtils/ecdsa.ts index 6e9574b491..9786e02c2a 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()); }); @@ -1691,6 +1693,7 @@ describe('TSS Ecdsa Utils:', async function () { const bitgoGPGPublicKeyResponse: BitgoGPGPublicKey = { name: 'irrelevant', publicKey: bitgoGpgKeyPair.publicKey, + mpcv2PublicKey: bitgoGpgKeyPair.publicKey, enterpriseId, }; nock(bgUrl).get(`/api/v2/${coin}/tss/pubkey`).query({ enterpriseId }).reply(200, bitgoGPGPublicKeyResponse); diff --git a/modules/bitgo/test/v2/unit/internal/tssUtils/eddsa.ts b/modules/bitgo/test/v2/unit/internal/tssUtils/eddsa.ts index 013a77f1c3..ca077e9cd2 100644 --- a/modules/bitgo/test/v2/unit/internal/tssUtils/eddsa.ts +++ b/modules/bitgo/test/v2/unit/internal/tssUtils/eddsa.ts @@ -9,6 +9,7 @@ import { TestableBG, TestBitGo } from '@bitgo/sdk-test'; import { BitGo } from '../../../../../src'; import { BaseCoin, + BitgoGPGPublicKey, CommitmentShareRecord, CommitmentType, common, @@ -1240,5 +1241,40 @@ describe('TSS Utils:', async function () { }); }); + describe('getBitgoGpgPubkeyBasedOnFeatureFlags', function () { + it('should return eddsaMpcv2PublicKey when present in feature flags response', async function () { + const ed25519KeyPair = await openpgp.generateKey({ + userIDs: [{ name: 'bitgo eddsa', email: 'bitgo@test.com' }], + curve: 'ed25519', + }); + const response: BitgoGPGPublicKey = { + name: 'irrelevant', + publicKey: ed25519KeyPair.publicKey, + mpcv2PublicKey: ed25519KeyPair.publicKey, + eddsaMpcv2PublicKey: ed25519KeyPair.publicKey, + enterpriseId: 'enterprise_id', + }; + nock(bgUrl).get(`/api/v2/${coinName}/tss/pubkey`).query({ enterpriseId: 'enterprise_id' }).reply(200, response); + const { eddsaMpcv2PublicKey } = await tssUtils.getBitgoGpgPubkeyBasedOnFeatureFlags('enterprise_id'); + should.exist(eddsaMpcv2PublicKey); + should.equal(ed25519KeyPair.publicKey, eddsaMpcv2PublicKey!.armor()); + }); + + it('should return undefined eddsaMpcv2PublicKey when absent from feature flags response', async function () { + const keyPair = await openpgp.generateKey({ + userIDs: [{ name: 'bitgo', email: 'bitgo@test.com' }], + }); + const response: BitgoGPGPublicKey = { + name: 'irrelevant', + publicKey: keyPair.publicKey, + mpcv2PublicKey: keyPair.publicKey, + enterpriseId: 'enterprise_id', + }; + nock(bgUrl).get(`/api/v2/${coinName}/tss/pubkey`).query({ enterpriseId: 'enterprise_id' }).reply(200, response); + const { eddsaMpcv2PublicKey } = await tssUtils.getBitgoGpgPubkeyBasedOnFeatureFlags('enterprise_id'); + should.not.exist(eddsaMpcv2PublicKey); + }); + }); + // #endregion Nock helpers }); diff --git a/modules/bitgo/test/v2/unit/internal/tssUtils/eddsaMPCv2/createKeychains.ts b/modules/bitgo/test/v2/unit/internal/tssUtils/eddsaMPCv2/createKeychains.ts index 1ad03f1a37..6bcc52017a 100644 --- a/modules/bitgo/test/v2/unit/internal/tssUtils/eddsaMPCv2/createKeychains.ts +++ b/modules/bitgo/test/v2/unit/internal/tssUtils/eddsaMPCv2/createKeychains.ts @@ -310,6 +310,7 @@ describe('TSS EdDSA MPCv2 Utils:', async function () { name: 'irrelevant', publicKey: bitgoKeyPair.publicKey, mpcv2PublicKey: bitgoKeyPair.publicKey, + eddsaMpcv2PublicKey: bitgoKeyPair.publicKey, enterpriseId: enterprise, }; nock(bgUrl).get(`/api/v2/${coin}/tss/pubkey`).query({ enterpriseId: enterprise }).reply(200, response); 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..8a185697c0 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, + }; } /** @@ -417,6 +426,9 @@ export async function generateGPGKeyPair( // Allow generating secp256k1 key pairs pgp.config.rejectCurves = new Set(); + + const subkeys = keyCurve === 'ed25519' ? ([{ sign: true }, { sign: false }] as pgp.SubkeyOptions[]) : undefined; + const gpgKey = await pgp.generateKey({ userIDs: [ { @@ -425,6 +437,7 @@ export async function generateGPGKeyPair( }, ], curve: keyCurve, + ...(subkeys && { subkeys }), }); return gpgKey; diff --git a/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts b/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts index de7fd8bfdd..e4dfebe65c 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 { @@ -552,8 +557,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 +568,7 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil enterpriseId: string | undefined, isMPCv2 = false, reqId?: IRequestTracer - ): Promise { + ): Promise<{ mpcv2PublicKey: Key; eddsaMpcv2PublicKey: Key | undefined }> { const reqTracer = reqId || new RequestTracer(); this.bitgo.setRequestTracer(reqTracer); const response: BitgoGPGPublicKey = await this.bitgo @@ -570,8 +576,13 @@ 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 = response.eddsaMpcv2PublicKey + ? await readKey({ armoredKey: response.eddsaMpcv2PublicKey }) + : undefined; + 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 });