From 7dda747f22dc30eb24461da3acfc137fa8605686 Mon Sep 17 00:00:00 2001 From: Islam Amin Date: Thu, 17 Oct 2024 15:54:12 -0400 Subject: [PATCH 1/3] feat(sdk-core): support hardcoded gpg keys for non-express hot wallet flow TICKET: HSM-432 --- .../sdk-core/src/bitgo/keychain/iKeychains.ts | 2 + .../sdk-core/src/bitgo/tss/bitgoPubKeys.ts | 56 ++++++++++++++++ modules/sdk-core/src/bitgo/utils/mpcUtils.ts | 5 ++ .../src/bitgo/utils/tss/baseTSSUtils.ts | 64 ++++++++++++++++++- .../sdk-core/src/bitgo/utils/tss/baseTypes.ts | 2 + .../src/bitgo/utils/tss/ecdsa/base.ts | 20 ------ .../src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts | 16 +++-- .../src/bitgo/utils/tss/eddsa/eddsa.ts | 2 +- 8 files changed, 138 insertions(+), 29 deletions(-) create mode 100644 modules/sdk-core/src/bitgo/tss/bitgoPubKeys.ts diff --git a/modules/sdk-core/src/bitgo/keychain/iKeychains.ts b/modules/sdk-core/src/bitgo/keychain/iKeychains.ts index 44c0f8320a..53527a79be 100644 --- a/modules/sdk-core/src/bitgo/keychain/iKeychains.ts +++ b/modules/sdk-core/src/bitgo/keychain/iKeychains.ts @@ -1,5 +1,6 @@ import { IRequestTracer } from '../../api'; import { KeychainsTriplet, KeyPair } from '../baseCoin'; +import { BitgoPubKeyType } from '../utils/tss/baseTypes'; import { BackupProvider, IWallet } from '../wallet'; import { BitGoKeyFromOvcShares, OvcToBitGoJSON } from './ovcJsonCodec'; @@ -39,6 +40,7 @@ export interface Keychain { commonKeychain?: string; keyShares?: ApiKeyShare[]; walletHSMGPGPublicKeySigs?: string; + hsmType?: BitgoPubKeyType; type: KeyType; source?: SourceType; coinSpecific?: { [coinName: string]: unknown }; diff --git a/modules/sdk-core/src/bitgo/tss/bitgoPubKeys.ts b/modules/sdk-core/src/bitgo/tss/bitgoPubKeys.ts new file mode 100644 index 0000000000..5a06b7096a --- /dev/null +++ b/modules/sdk-core/src/bitgo/tss/bitgoPubKeys.ts @@ -0,0 +1,56 @@ +import assert from 'assert'; +import { EnvironmentName } from '../environments'; + +export const bitgoMpcGpgPubKeys = { + mpcv1: { + nitro: { + test: '-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxk8EYqEU5hMFK4EEAAoCAwQDdbAIZrsblEXIavyg2go6p9oG0SqWTgFsdHTc\nBhqdIS/WjQ8pj75q+vLqFtV9hlImYGInsIWh97fsigzB2owyzRhoc20gPGhz\nbUB0ZXN0LmJpdGdvLmNvbT7ChAQTEwgAFQUCYqEU5wILCQIVCAIWAAIbAwIe\nAQAhCRCJNRsIDGunexYhBHRL5D/8nRM3opQnXok1GwgMa6d7tg8A/24A9awq\nSCJx7RddiUzFHcKhVvvo3R5N7bHaOGP3TP79AP0TavF2WzhUXmZSjt3IK23O\n7/aknbijVeq52ghbWb1SwsJ1BBATCAAGBQJioRTnACEJEAWuA35KJgtgFiEE\nZttLPR0KcYvjgvJCBa4DfkomC2BsrwD/Z+43zOw+WpfPHxe+ypyVog5fnOKl\nXwleH6zDvqUWmWkA/iaHC6ullYkSG4Mv68k6qbtgR/pms/X7rkfa0QQFJy5p\nzlMEYqEU5hIFK4EEAAoCAwSsLqmfonjMF3o0nZ5JHvLpmfTA1RIVDsAEoRON\ntZA6rAA23pGl6s3Iyt4/fX9Adzoh3EElOjMsgi8Aj3dFpuqiAwEIB8J4BBgT\nCAAJBQJioRTnAhsMACEJEIk1GwgMa6d7FiEEdEvkP/ydEzeilCdeiTUbCAxr\np3vM7AD9GPp6HhYNEh2VVCDtFSt14Bni5FVM5icpVDo6w9ibvWAA/2Ti3Jv4\nIhIxl81/wqAgqigIblrz6vjtagr9/ykXQCW3\n=skCo\n-----END PGP PUBLIC KEY BLOCK-----\n', + prod: '-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxk8EY4m6ZBMFK4EEAAoCAwRSTwdXgiY+EBNj2JgNzisUygcVGVxp1Fv+pT64\nTsJ64y9Fr5h9ljqMIsmM0MWn9hczpmdAEHpkSg264wAPNcIWzQtCaXRHbyBO\naXRyb8KEBBMTCAA2BQJjibpmAgsJCRDHgvrWqx65HwIVCAIWAAIbAwIeARYh\nBLgnzI9Cn6UamNlJ2MeC+tarHrkfAABEwgD/W0+LXpHEMtSnShf7rSg7tQfG\n1Bb6be2Y1utd+auj/EcA/jGJO8MtejxcVGBpH/ZrODL+D0yS/I2YD3nveLtD\nD5z3wnUEEBMIACcFAmOJumkJEHuS1voAd5fJFiEE1Xxbfbbr5zLGqNJ7e5LW\n+gB3l8kAAPtmAP0WZnW/cgGCWzG1NYbAU1sJUwYdspM1WDLByjmo5JkCrQD+\nOK/6U8zvmQEcoOq0YXArhb+yWQDDHDEkLxRptB+KO8nOUwRjibpkEgUrgQQA\nCgIDBOUvn/oNKZnjEMtnAbB6hoos8vDf8mqyIbtGRjDil1T3t19q2Ke6xFFo\nJ+U2w4gtFxjDER8igas+ja4P3u7EFlMDAQgHwngEGBMIACoFAmOJumgJEMeC\n+tarHrkfAhsMFiEEuCfMj0KfpRqY2UnYx4L61qseuR8AANHPAP96lvwGT3A0\nNNz1WAr+Sn13mR3k8arfeqcvZ1FCmioMogD9GzJIaJlbAbdsRB4QnLkRcKJO\nnMH13PKq9qM6tg4UQFM=\n=SD0h\n-----END PGP PUBLIC KEY BLOCK-----\n', + }, + onprem: { + test: '-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxk8EYqEU5hMFK4EEAAoCAwQDdbAIZrsblEXIavyg2go6p9oG0SqWTgFsdHTc\nBhqdIS/WjQ8pj75q+vLqFtV9hlImYGInsIWh97fsigzB2owyzRhoc20gPGhz\nbUB0ZXN0LmJpdGdvLmNvbT7ChAQTEwgAFQUCYqEU5wILCQIVCAIWAAIbAwIe\nAQAhCRCJNRsIDGunexYhBHRL5D/8nRM3opQnXok1GwgMa6d7tg8A/24A9awq\nSCJx7RddiUzFHcKhVvvo3R5N7bHaOGP3TP79AP0TavF2WzhUXmZSjt3IK23O\n7/aknbijVeq52ghbWb1SwsJ1BBATCAAGBQJioRTnACEJEAWuA35KJgtgFiEE\nZttLPR0KcYvjgvJCBa4DfkomC2BsrwD/Z+43zOw+WpfPHxe+ypyVog5fnOKl\nXwleH6zDvqUWmWkA/iaHC6ullYkSG4Mv68k6qbtgR/pms/X7rkfa0QQFJy5p\nzlMEYqEU5hIFK4EEAAoCAwSsLqmfonjMF3o0nZ5JHvLpmfTA1RIVDsAEoRON\ntZA6rAA23pGl6s3Iyt4/fX9Adzoh3EElOjMsgi8Aj3dFpuqiAwEIB8J4BBgT\nCAAJBQJioRTnAhsMACEJEIk1GwgMa6d7FiEEdEvkP/ydEzeilCdeiTUbCAxr\np3vM7AD9GPp6HhYNEh2VVCDtFSt14Bni5FVM5icpVDo6w9ibvWAA/2Ti3Jv4\nIhIxl81/wqAgqigIblrz6vjtagr9/ykXQCW3\n=skCo\n-----END PGP PUBLIC KEY BLOCK-----\n', + prod: '-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmE8EYqKKQRMFK4EEAAoCAwROWJbH3UCPdZTPEJXpPZcktwtDJwil4QHlXZELcUbF\nETboq/cY22w+uG0IlRypdbo6+sDuaeg3dfja2ioq6TtJtAVCaXRHb4iEBBMTCAAV\nBQJioopDAgsJAhUIAhYAAhsDAh4BACEJEFXOMjZat5vMFiEEFYS/Xvdht8iMtmyP\nVc4yNlq3m8ym1AD+P9clE3kj764YmrHDOcRPl/+tX2CoUD0rbdSYyJyfCwAA/As0\nF0UFbPzlxPSaZhV/jQxB+PsF4LViwDdh4V4pUtn9iQIzBBABCgAdFiEEycUshFXI\nDdIAN2jlMSDsLY9HGToFAmKqV5gACgkQMSDsLY9HGToeiA/+MQXgfcLlzvNwHhGJ\nkgpSsW3aj2GOA+lZPHAbLVRomltn5GI/5jtLLi0hdVrNr14yuI1K954+ezuAz2DN\nHfDWVihEQaaQ+54fzoy9w8RjGqL+lhgQa/3BrAvIRP/WPFyo9eZcaW+T35x+Es7Z\nqyK50Sc/vpkHtNJ8rVHW1Y1wwAZzuHqdsY2xp1bKeS1hn2NHJ4QRqXZTNUY2zu0M\n3jzCvFdgnqUJC9d/t92QdUAAsENgAIIrVn7qCHoQIrXKrGAO+DTVmSqJp3kkbDI8\nbhR6EeionE6uualIsOR8bx4FlBHLk5JGCtzLVQ8XO/a7+hbJK8eFGUWSRQWiuq/1\nm11+MEjT6jkYDALajbrwYgQSNnqri9kHnGl1jnkNm7r+1PS9lB+J2F/c/t0jQd1C\nsQwIDmrD9adrs6yczbuEB+1kjt8SzJq8SGGPI84hzubQBhIr15mHNw5PK/1zaXlR\nWI0Sc8gOBcQsZshIRmOAvdhQzjU1uV8rYa19WOxpHB/Bss59Gk8oZsQu5aQ7qQ2l\nFBlUnewgPPIkljTrUBZLHSSExoUsoobOTGjs59VvDS5Jdy7rvA7roY1qYG/Dca2t\n1fwTwYTehI/u98qr+4N4d472vdCOqKhmcnbmSYNH35Ig8640yuhq/0IYKWmL8cBS\nO+5E4uMK4iBxSGFjLpGLRK9McMq4UwRioopBEgUrgQQACgIDBCHnAGs6S2ol7gbm\nxtI53LDWufX/US+Q7iGPxHgLdQArsCnPn31duLqO3ekPauF24vUWdLnOHOJhIGSS\nj8HYjDwDAQgHiHgEGBMIAAkFAmKiikQCGwwAIQkQVc4yNlq3m8wWIQQVhL9e92G3\nyIy2bI9VzjI2WrebzKZXAPwPAp119kb5WBC1evOHnbdHCTSOX4TB1xXaHoCIqhX/\ncAD+NX7JLf4pMeTpDz1MZHZB+2lyTBxRMo432m4Z7366Vqc=\n=yDyI\n-----END PGP PUBLIC KEY BLOCK-----', + }, + }, + mpcv2: { + nitro: { + test: '-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxk8EZiF3CBMFK4EEAAoCAwQWD7Pa752fAl4z0PxfWVC05d89vfo80PyUQ3Er\nLXlhGLkik+NkAl/DBd8diN7i4kTvRoIo0xrHU+lZgdgt+ct5zRhoc20gPGhz\nbUB0ZXN0LmJpdGdvLmNvbT7ChAQTEwgANgUCZiF3CAILCQkQ5ycuezbbVOkC\nFQgCFgACGwMCHgEWIQRPr6GNiE7tRv0p4afnJy57NttU6QAAbAYA+wRvSLOa\ne0iREOx00HhYWP030GhN98BcZtehT9iTZMV8AP97Otkrtq6jby2f7PdEV7uv\nd4aikTa5BgnpKvl8yqL4ccKEBBATCAA2BQJmIXcKAgsJCRCZRBfch5MUcwIV\nCAIWAAIbAwIeARYhBAmXBS0TYEvmC/3L9JlEF9yHkxRzAABJ1wD+KyI1j9nu\nYWvDxwDB+JBGMt7mic77ajBOgaCabEZ0j1MA/2RCOiV2cOL3x1AOzosqofsh\niA1s9BpS14xAwrKJPwY+zlMEZiF3CBIFK4EEAAoCAwSgLs60kLzhHD3o1sDg\n0fQ/QHw6hgq9PQ5LvilUvuIGYDR79sPwrMuwy7wUcOQgJvwIOJHommDq5nj+\nKfgAtE6uAwEIB8KEBBgTCAA2BQJmIXcJAgsJCRDnJy57NttU6QIVCAIWAAIb\nDAIeARYhBE+voY2ITu1G/Snhp+cnLns221TpAADWmQD/bV9sBkwyYfYfJYTS\nqvTmubCesQDY5Ranv9wYvv7RiLQA/iwX6ZHwdbvQFVui0GrvV2iFaCHut1pn\nF4YCDqpUKidwzk8EZiF3CBMFK4EEAAoCAwTfm/HZxwvubP/rr2KOU88mkDL9\njcWjfQx1uFZ9mlIgMBV3++OgtkVE0eEe+lNWpwgksGOGrBWeQ3K0XRF0YlUp\nwsBKBBgTCAC8BQJmIXcJAgsJCRDnJy57NttU6QIVCAIWAAIbAgIeAYUgBBgT\nCAA2BQJmIXcJAgsJCRBrEMTq2oOYhgIVCAIWAAIbAgIeARYhBLFg1zIcwAmc\nRhGdOmsQxOrag5iGAAAxoAD/YNPhMmf3l4Qh7fprkmOjoU0CvFiiP+kcxTr9\nm9luVhUA/RvhIB4sqrAcSD7ZGVIQcEI14rdAFeok4Higz2cGf9R6FiEET6+h\njYhO7Ub9KeGn5ycuezbbVOkAAPnaAP0dYpya7EzvN5Q6RpIzqLFN9izyGt4Q\n6keZsvnVbW9qJAD9Fj7tAAMUbbstz/Kx9RY8qoIOFTuSwaeDXnJMrI9v84w=\n=uzVB\n-----END PGP PUBLIC KEY BLOCK-----\n', + prod: '', + }, + onprem: { + test: '-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxk8EZiF3CBMFK4EEAAoCAwQWD7Pa752fAl4z0PxfWVC05d89vfo80PyUQ3Er\nLXlhGLkik+NkAl/DBd8diN7i4kTvRoIo0xrHU+lZgdgt+ct5zRhoc20gPGhz\nbUB0ZXN0LmJpdGdvLmNvbT7ChAQTEwgANgUCZiF3CAILCQkQ5ycuezbbVOkC\nFQgCFgACGwMCHgEWIQRPr6GNiE7tRv0p4afnJy57NttU6QAAbAYA+wRvSLOa\ne0iREOx00HhYWP030GhN98BcZtehT9iTZMV8AP97Otkrtq6jby2f7PdEV7uv\nd4aikTa5BgnpKvl8yqL4ccKEBBATCAA2BQJmIXcKAgsJCRCZRBfch5MUcwIV\nCAIWAAIbAwIeARYhBAmXBS0TYEvmC/3L9JlEF9yHkxRzAABJ1wD+KyI1j9nu\nYWvDxwDB+JBGMt7mic77ajBOgaCabEZ0j1MA/2RCOiV2cOL3x1AOzosqofsh\niA1s9BpS14xAwrKJPwY+zlMEZiF3CBIFK4EEAAoCAwSgLs60kLzhHD3o1sDg\n0fQ/QHw6hgq9PQ5LvilUvuIGYDR79sPwrMuwy7wUcOQgJvwIOJHommDq5nj+\nKfgAtE6uAwEIB8KEBBgTCAA2BQJmIXcJAgsJCRDnJy57NttU6QIVCAIWAAIb\nDAIeARYhBE+voY2ITu1G/Snhp+cnLns221TpAADWmQD/bV9sBkwyYfYfJYTS\nqvTmubCesQDY5Ranv9wYvv7RiLQA/iwX6ZHwdbvQFVui0GrvV2iFaCHut1pn\nF4YCDqpUKidwzk8EZiF3CBMFK4EEAAoCAwTfm/HZxwvubP/rr2KOU88mkDL9\njcWjfQx1uFZ9mlIgMBV3++OgtkVE0eEe+lNWpwgksGOGrBWeQ3K0XRF0YlUp\nwsBKBBgTCAC8BQJmIXcJAgsJCRDnJy57NttU6QIVCAIWAAIbAgIeAYUgBBgT\nCAA2BQJmIXcJAgsJCRBrEMTq2oOYhgIVCAIWAAIbAgIeARYhBLFg1zIcwAmc\nRhGdOmsQxOrag5iGAAAxoAD/YNPhMmf3l4Qh7fprkmOjoU0CvFiiP+kcxTr9\nm9luVhUA/RvhIB4sqrAcSD7ZGVIQcEI14rdAFeok4Higz2cGf9R6FiEET6+h\njYhO7Ub9KeGn5ycuezbbVOkAAPnaAP0dYpya7EzvN5Q6RpIzqLFN9izyGt4Q\n6keZsvnVbW9qJAD9Fj7tAAMUbbstz/Kx9RY8qoIOFTuSwaeDXnJMrI9v84w=\n=uzVB\n-----END PGP PUBLIC KEY BLOCK-----\n', + 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', + }, + }, +}; + +export function getBitgoMpcGpgPubKey(env: EnvironmentName, pubKeyType: 'nitro' | 'onprem', mpcVersion: 'mpcv1' | 'mpcv2'): string { + assert(mpcVersion in bitgoMpcGpgPubKeys, `Invalid mpcVersion in getBitgoMpcGpgPubKey, got: ${mpcVersion}, expected: mpcv1 or mpcv2`); + assert(pubKeyType in bitgoMpcGpgPubKeys[mpcVersion], `Invalid pubKeyType in getBitgoMpcGpgPubKey, got: ${pubKeyType}, expected: nitro or onprem`); + if (env !== 'prod' && env !== 'test' && env !== 'staging' && env !== 'adminProd' && env !== 'adminTest'){ + throw new Error('Invalid environment to get a BitGo MPC GPG public key'); + } + if (env !== 'prod' && env !== 'adminProd') { + // default to test gpg keys if not in prod + env = 'test'; + } + if (env === 'adminProd') { + env = 'prod'; + } + if (pubKeyType === 'nitro' && env === 'prod') { + throw new Error('Nitro mpcv2 pub key is not available in production environments yet.'); + } + if (pubKeyType !== 'nitro') { + // This will be the default key type + pubKeyType = 'onprem'; + } + return bitgoMpcGpgPubKeys[mpcVersion][pubKeyType][env]; +} + +export function isBitgoMpcPubKey(key: string, mpcvVersion: 'mpcv1' | 'mpcv2'): boolean { + return Object.values(bitgoMpcGpgPubKeys[mpcvVersion]).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/mpcUtils.ts b/modules/sdk-core/src/bitgo/utils/mpcUtils.ts index 4bfabb4d0a..4864699df3 100644 --- a/modules/sdk-core/src/bitgo/utils/mpcUtils.ts +++ b/modules/sdk-core/src/bitgo/utils/mpcUtils.ts @@ -9,6 +9,7 @@ import { AddKeychainOptions, Keychain, KeyType } from '../keychain'; import { BackupProvider } from '../wallet'; import { encryptText, getBitgoGpgPubKey } from './opengpgUtils'; import { IntentRecipient, PopulatedIntent, PrebuildTransactionWithIntentOptions } from './tss/baseTypes'; +import { envRequiresBitgoPubGpgKeyConfig, isBitgoMpcPubKey } from '../tss/bitgoPubKeys'; export interface MpcKeyShare { publicShare: string; @@ -52,6 +53,10 @@ export abstract class MpcUtils { enterprise?: string ): Promise { const bitgoKey = (await getBitgoGpgPubKey(this.bitgo)).mpcV1; + if (envRequiresBitgoPubGpgKeyConfig(this.bitgo.getEnv())) { + // Ensure the public key is one of the expected BitGo public keys when in test or prod. + assert(isBitgoMpcPubKey(bitgoKey.armor(), 'mpcv1'), 'Invalid BitGo GPG public key'); + } const encUserToBitGoMessage = await encryptText(userKeyShare.privateShare, bitgoKey); const encBackupToBitGoMessage = await encryptText(backupKeyShare.privateShare, bitgoKey); diff --git a/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts b/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts index f074f131be..a2eca1a15d 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts @@ -2,7 +2,7 @@ import { IRequestTracer } from '../../../api'; import { Key, readKey, SerializedKeyPair } from 'openpgp'; import { IBaseCoin, KeychainsTriplet } from '../../baseCoin'; import { BitGoBase } from '../../bitgoBase'; -import { Keychain } from '../../keychain'; +import { Keychain, KeyIndices } from '../../keychain'; import { getTxRequest } from '../../tss'; import { IWallet, BackupProvider } from '../../wallet'; import { MpcUtils } from '../mpcUtils'; @@ -40,12 +40,17 @@ import { } from './baseTypes'; import { GShare, SignShare } from '../../../account-lib/mpc/tss'; import { RequestTracer } from '../util'; +import * as openpgp from 'openpgp'; +import { envRequiresBitgoPubGpgKeyConfig, getBitgoMpcGpgPubKey } from '../../tss/bitgoPubKeys'; +import { getBitgoGpgPubKey } from '../opengpgUtils'; /** * BaseTssUtil class which different signature schemes have to extend */ export default class BaseTssUtils extends MpcUtils implements ITssUtils { private _wallet?: IWallet; + protected bitgoPublicGpgKey: openpgp.Key; + protected bitgoMPCv2PublicGpgKey: openpgp.Key | undefined; constructor(bitgo: BitGoBase, baseCoin: IBaseCoin, wallet?: IWallet) { super(bitgo, baseCoin); @@ -59,6 +64,63 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil return this._wallet; } + protected async setBitgoGpgPubKey(bitgo) { + const { mpcV1, mpcV2 } = await getBitgoGpgPubKey(bitgo); + this.bitgoPublicGpgKey = mpcV1; + this.bitgoMPCv2PublicGpgKey = mpcV2; + } + + protected async pickBitgoPubGpgKeyForSigning(isMpcv2: boolean, reqId: IRequestTracer, enterpriseId?: string): Promise { + let bitgoGpgPubKey; + try { + const bitgoKeyChain = await this.baseCoin.keychains().get({ id: this.wallet.keyIds()[KeyIndices.BITGO], reqId }); + if (!bitgoKeyChain || !bitgoKeyChain.hsmType) { + throw new Error('Missing Bitgo GPG Pub Key Type.'); + } + bitgoGpgPubKey = await openpgp.readKey({ + armoredKey: getBitgoMpcGpgPubKey(this.bitgo.getEnv(), bitgoKeyChain.hsmType === 'nitro' ? 'nitro' : 'onprem', isMpcv2 ? 'mpcv2' : 'mpcv1'), + }); + } catch (e) { + if (!envRequiresBitgoPubGpgKeyConfig(this.bitgo.getEnv())) { + console.warn( + `Unable to get BitGo GPG key based on key data with error: ${e}. Fetching BitGo GPG key based on feature flags.` + ); + bitgoGpgPubKey = await this.getBitgoGpgPubkeyBasedOnFeatureFlags( + enterpriseId, + isMpcv2, + reqId + ).then(async (pubKey) => pubKey ?? (isMpcv2 ? await this.getBitgoMpcv2PublicGpgKey() : await this.getBitgoPublicGpgKey())); + } else { + throw new Error(`Environment "${this.bitgo.getEnv()}" requires a BitGo GPG Pub Key Config in BitGoJS for TSS. Error thrown while getting the key from config: ${e}`); + } + } + return bitgoGpgPubKey; + } + + async getBitgoPublicGpgKey(): Promise { + if (!this.bitgoPublicGpgKey) { + // retry getting bitgo's gpg key + await this.setBitgoGpgPubKey(this.bitgo); + if (!this.bitgoPublicGpgKey) { + throw new Error("Failed to get Bitgo's gpg key"); + } + } + + return this.bitgoPublicGpgKey; + } + + async getBitgoMpcv2PublicGpgKey(): Promise { + if (!this.bitgoMPCv2PublicGpgKey) { + // retry getting bitgo's gpg key + await this.setBitgoGpgPubKey(this.bitgo); + if (!this.bitgoMPCv2PublicGpgKey) { + throw new Error("Failed to get Bitgo's gpg key"); + } + } + + return this.bitgoMPCv2PublicGpgKey; + } + async createBitgoHeldBackupKeyShare( userGpgKey: SerializedKeyPair, enterprise: string | undefined diff --git a/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts b/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts index a423e2ed6a..fa80054199 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts @@ -408,6 +408,8 @@ export type TSSParamsForMessageWithPrv = TSSParamsForMessage & { mpcv2PartyId?: 0 | 1; }; +export type BitgoPubKeyType = 'nitro' | 'onprem'; + export type TSSParams = { txRequest: string | TxRequest; // can be either a string or TxRequest reqId: IRequestTracer; diff --git a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/base.ts b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/base.ts index d2e628ce36..c6f90c3265 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/base.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/base.ts @@ -12,32 +12,12 @@ import { IWallet } from '../../../wallet'; /** @inheritdoc */ export class BaseEcdsaUtils extends baseTSSUtils { // We do not have full support for 3-party verification (w/ external source) of key shares and signature shares. There is no 3rd party key service support with this release. - protected bitgoPublicGpgKey: openpgp.Key; - protected bitgoMPCv2PublicGpgKey: openpgp.Key | undefined; constructor(bitgo: BitGoBase, baseCoin: IBaseCoin, wallet?: IWallet) { super(bitgo, baseCoin, wallet); this.setBitgoGpgPubKey(bitgo); } - private async setBitgoGpgPubKey(bitgo) { - const { mpcV1, mpcV2 } = await getBitgoGpgPubKey(bitgo); - this.bitgoPublicGpgKey = mpcV1; - this.bitgoMPCv2PublicGpgKey = mpcV2; - } - - async getBitgoPublicGpgKey(): Promise { - if (!this.bitgoPublicGpgKey) { - // retry getting bitgo's gpg key - await this.setBitgoGpgPubKey(this.bitgo); - if (!this.bitgoPublicGpgKey) { - throw new Error("Failed to get Bitgo's gpg key"); - } - } - - return this.bitgoPublicGpgKey; - } - /** * Gets backup pub gpg key string * if a third party provided then get from trust 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 52563aa40a..f5006fcdef 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts @@ -48,6 +48,7 @@ import { } from '../baseTypes'; import { BaseEcdsaUtils } from './base'; import { EcdsaMPCv2KeyGenSendFn, KeyGenSenderForEnterprise } from './ecdsaMPCv2KeyGenSender'; +import { envRequiresBitgoPubGpgKeyConfig, isBitgoMpcPubKey } from '../../../tss/bitgoPubKeys'; export class EcdsaMPCv2Utils extends BaseEcdsaUtils { /** @inheritdoc */ @@ -66,6 +67,11 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils { const bitgoPublicGpgKey = ( (await this.getBitgoGpgPubkeyBasedOnFeatureFlags(params.enterprise, true)) ?? this.bitgoMPCv2PublicGpgKey ).armor(); + + if (envRequiresBitgoPubGpgKeyConfig(this.bitgo.getEnv())) { + // Ensure the public key is one of the expected BitGo public keys when in test or prod. + assert(isBitgoMpcPubKey(bitgoPublicGpgKey, 'mpcv2'), 'Invalid BitGo GPG public key'); + } const userGpgPrvKey: DklsTypes.PartyGpgKey = { partyId: MPCv2PartiesEnum.USER, @@ -711,16 +717,12 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils { typeof params.txRequest === 'string' ? await getTxRequest(this.bitgo, this.wallet.id(), params.txRequest, params.reqId) : params.txRequest; - let txOrMessageToSign; let derivationPath; let bufferContent; - const [userGpgKey, bitgoGpgPubKey] = await Promise.all([ - generateGPGKeyPair('secp256k1'), - this.getBitgoGpgPubkeyBasedOnFeatureFlags(txRequest.enterpriseId, true, params.reqId).then( - (pubKey) => pubKey ?? this.bitgoMPCv2PublicGpgKey - ), - ]); + const userGpgKey = await generateGPGKeyPair('secp256k1'); + const bitgoGpgPubKey = await this.pickBitgoPubGpgKeyForSigning(true, params.reqId, txRequest.enterpriseId); + if (!bitgoGpgPubKey) { throw new Error('Missing BitGo GPG key for MPCv2'); } diff --git a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts index 8553e561c1..6e9c670db0 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts @@ -588,7 +588,7 @@ export class EddsaUtils extends baseTSSUtils { const bitgoIndex = ShareKeyPosition.BITGO; const signerShare = signingKey.yShares[bitgoIndex].u + signingKey.yShares[bitgoIndex].chaincode; - const bitgoGpgKey = (await getBitgoGpgPubKey(this.bitgo)).mpcV1; + const bitgoGpgKey = await this.pickBitgoPubGpgKeyForSigning(false, params.reqId, txRequestResolved.enterpriseId); const userToBitgoEncryptedSignerShare = await encryptText(signerShare, bitgoGpgKey); const userGpgKey = await generateGPGKeyPair('secp256k1'); From f2db18ffeab5194e31eab0280e0039ca9c380985 Mon Sep 17 00:00:00 2001 From: Islam Amin Date: Thu, 24 Oct 2024 00:15:04 +0300 Subject: [PATCH 2/3] feat(sdk-core): support hardcoding bitgo pgp keys in external signer TICKET: HSM-432 --- .../src/bitgo/utils/tss/baseTSSUtils.ts | 41 +++++++++++++------ .../sdk-core/src/bitgo/utils/tss/baseTypes.ts | 9 +++- .../src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts | 11 +++-- .../src/bitgo/utils/tss/eddsa/eddsa.ts | 11 +++-- 4 files changed, 51 insertions(+), 21 deletions(-) diff --git a/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts b/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts index a2eca1a15d..d53304f6d3 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts @@ -69,29 +69,41 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil this.bitgoPublicGpgKey = mpcV1; this.bitgoMPCv2PublicGpgKey = mpcV2; } - - protected async pickBitgoPubGpgKeyForSigning(isMpcv2: boolean, reqId: IRequestTracer, enterpriseId?: string): Promise { + + protected async pickBitgoPubGpgKeyForSigning( + isMpcv2: boolean, + reqId?: IRequestTracer, + enterpriseId?: string + ): Promise { let bitgoGpgPubKey; try { - const bitgoKeyChain = await this.baseCoin.keychains().get({ id: this.wallet.keyIds()[KeyIndices.BITGO], reqId }); + const bitgoKeyChain = await this.baseCoin.keychains().get({ id: this.wallet.keyIds()[KeyIndices.BITGO], reqId }); if (!bitgoKeyChain || !bitgoKeyChain.hsmType) { throw new Error('Missing Bitgo GPG Pub Key Type.'); } bitgoGpgPubKey = await openpgp.readKey({ - armoredKey: getBitgoMpcGpgPubKey(this.bitgo.getEnv(), bitgoKeyChain.hsmType === 'nitro' ? 'nitro' : 'onprem', isMpcv2 ? 'mpcv2' : 'mpcv1'), + armoredKey: getBitgoMpcGpgPubKey( + this.bitgo.getEnv(), + bitgoKeyChain.hsmType === 'nitro' ? 'nitro' : 'onprem', + isMpcv2 ? 'mpcv2' : 'mpcv1' + ), }); } catch (e) { if (!envRequiresBitgoPubGpgKeyConfig(this.bitgo.getEnv())) { console.warn( `Unable to get BitGo GPG key based on key data with error: ${e}. Fetching BitGo GPG key based on feature flags.` ); - bitgoGpgPubKey = await this.getBitgoGpgPubkeyBasedOnFeatureFlags( - enterpriseId, - isMpcv2, - reqId - ).then(async (pubKey) => pubKey ?? (isMpcv2 ? await this.getBitgoMpcv2PublicGpgKey() : await this.getBitgoPublicGpgKey())); + // 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()) + ) + .catch(async (e) => (isMpcv2 ? await this.getBitgoMpcv2PublicGpgKey() : await this.getBitgoPublicGpgKey())); } else { - throw new Error(`Environment "${this.bitgo.getEnv()}" requires a BitGo GPG Pub Key Config in BitGoJS for TSS. Error thrown while getting the key from config: ${e}`); + throw new Error( + `Environment "${this.bitgo.getEnv()}" requires a BitGo GPG Pub Key Config in BitGoJS for TSS. Error thrown while getting the key from config: ${e}` + ); } } return bitgoGpgPubKey; @@ -119,7 +131,7 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil } return this.bitgoMPCv2PublicGpgKey; - } + } async createBitgoHeldBackupKeyShare( userGpgKey: SerializedKeyPair, @@ -251,7 +263,12 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil * * @returns {Promise<{ userToBitgoCommitment: CommitmentShareRecor, encryptedSignerShare: EncryptedSignerShareRecord }>} - Commitment Share and the Encrypted Signer Share to BitGo */ - createCommitmentShareFromTxRequest(params: { txRequest: TxRequest; prv: string; walletPassphrase: string }): Promise<{ + createCommitmentShareFromTxRequest(params: { + txRequest: TxRequest; + prv: string; + walletPassphrase: string; + bitgoGpgPubKey: string; + }): Promise<{ userToBitgoCommitment: CommitmentShareRecord; encryptedSignerShare: EncryptedSignerShareRecord; encryptedUserToBitgoRShare: EncryptedSignerShareRecord; diff --git a/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts b/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts index fa80054199..9a3db1b473 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts @@ -89,7 +89,7 @@ export interface CustomSShareGeneratingFunction { } export interface CustomCommitmentGeneratingFunction { - (params: { txRequest: TxRequest }): Promise<{ + (params: { txRequest: TxRequest; bitgoGpgPubKey?: string }): Promise<{ userToBitgoCommitment: CommitmentShareRecord; encryptedSignerShare: EncryptedSignerShareRecord; encryptedUserToBitgoRShare: EncryptedSignerShareRecord; @@ -592,7 +592,12 @@ export interface ITssUtils { externalSignerMuDeltaShareGenerator: CustomMuDeltaShareGeneratingFunction, externalSignerSShareGenerator: CustomSShareGeneratingFunction ): Promise; - createCommitmentShareFromTxRequest(params: { txRequest: TxRequest; prv: string; walletPassphrase: string }): Promise<{ + createCommitmentShareFromTxRequest(params: { + txRequest: TxRequest; + prv: string; + walletPassphrase: string; + bitgoGpgPubKey: string; + }): Promise<{ userToBitgoCommitment: CommitmentShareRecord; encryptedSignerShare: EncryptedSignerShareRecord; encryptedUserToBitgoRShare: EncryptedSignerShareRecord; 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 f5006fcdef..b7d3f3a0c6 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts @@ -67,7 +67,7 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils { const bitgoPublicGpgKey = ( (await this.getBitgoGpgPubkeyBasedOnFeatureFlags(params.enterprise, true)) ?? this.bitgoMPCv2PublicGpgKey ).armor(); - + if (envRequiresBitgoPubGpgKeyConfig(this.bitgo.getEnv())) { // Ensure the public key is one of the expected BitGo public keys when in test or prod. assert(isBitgoMpcPubKey(bitgoPublicGpgKey, 'mpcv2'), 'Invalid BitGo GPG public key'); @@ -1010,9 +1010,12 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils { txRequestResolved = txRequest; } - const bitgoPublicGpgKey = - (await this.getBitgoGpgPubkeyBasedOnFeatureFlags(txRequestResolved.enterpriseId, true, reqId)) ?? - this.bitgoMPCv2PublicGpgKey; + const bitgoPublicGpgKey = await this.pickBitgoPubGpgKeyForSigning( + true, + params.reqId, + txRequestResolved.enterpriseId + ); + if (!bitgoPublicGpgKey) { throw new Error('Missing BitGo GPG key for MPCv2'); } diff --git a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts index 6e9c670db0..0181c0b77d 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts @@ -371,6 +371,7 @@ export class EddsaUtils extends baseTSSUtils { txRequest: TxRequest; prv: string; walletPassphrase: string; + bitgoGpgPubKey: string; }): Promise<{ userToBitgoCommitment: CommitmentShareRecord; encryptedSignerShare: EncryptedSignerShareRecord; @@ -408,8 +409,11 @@ export class EddsaUtils extends baseTSSUtils { const userToBitgoCommitment = this.createUserToBitgoCommitmentShare(commitment); const signerShare = signingKey.yShares[bitgoIndex].u + signingKey.yShares[bitgoIndex].chaincode; - const bitgoGpgKey = (await getBitgoGpgPubKey(this.bitgo)).mpcV1; - const userToBitgoEncryptedSignerShare = await encryptText(signerShare, bitgoGpgKey); + + const userToBitgoEncryptedSignerShare = await encryptText( + signerShare, + await openpgp.readKey({ armoredKey: params.bitgoGpgPubKey }) + ); const encryptedSignerShare = this.createUserToBitgoEncryptedSignerShare(userToBitgoEncryptedSignerShare); const stringifiedRShare = JSON.stringify(userSignShare); @@ -496,9 +500,10 @@ export class EddsaUtils extends baseTSSUtils { } const { apiVersion } = txRequestResolved; + const bitgoGpgKey = await this.pickBitgoPubGpgKeyForSigning(false, reqId, txRequestResolved.enterpriseId); const { userToBitgoCommitment, encryptedSignerShare, encryptedUserToBitgoRShare } = - await externalSignerCommitmentGenerator({ txRequest: txRequestResolved }); + await externalSignerCommitmentGenerator({ txRequest: txRequestResolved, bitgoGpgPubKey: bitgoGpgKey.armor() }); const { commitmentShare: bitgoToUserCommitment } = await exchangeEddsaCommitments( this.bitgo, From fbcfcbf58b852c466c8e49c35acc77119348ee50 Mon Sep 17 00:00:00 2001 From: Islam Amin Date: Fri, 25 Oct 2024 17:13:28 +0300 Subject: [PATCH 3/3] feat(sdk-core): add tests for new pick mpc gpg pub key function TICKET: HSM-432 --- .../internal/tssUtils/bitgoMpcGpgPubKeys.ts | 169 ++++++++++++++++++ .../test/unit/clientRoutes/externalSign.ts | 1 + .../sdk-core/src/bitgo/tss/bitgoPubKeys.ts | 22 ++- modules/sdk-core/src/bitgo/tss/index.ts | 1 + .../src/bitgo/utils/tss/baseTSSUtils.ts | 10 +- .../src/bitgo/utils/tss/ecdsa/base.ts | 3 +- 6 files changed, 196 insertions(+), 10 deletions(-) create mode 100644 modules/bitgo/test/v2/unit/internal/tssUtils/bitgoMpcGpgPubKeys.ts diff --git a/modules/bitgo/test/v2/unit/internal/tssUtils/bitgoMpcGpgPubKeys.ts b/modules/bitgo/test/v2/unit/internal/tssUtils/bitgoMpcGpgPubKeys.ts new file mode 100644 index 0000000000..5a4fd1e454 --- /dev/null +++ b/modules/bitgo/test/v2/unit/internal/tssUtils/bitgoMpcGpgPubKeys.ts @@ -0,0 +1,169 @@ +import { + BitgoMpcGpgPubKeys, + common, + ECDSAUtils, + EddsaUtils, + EnvironmentName, + IRequestTracer, + Wallet, +} from '@bitgo/sdk-core'; +import { TestBitGo } from '@bitgo/sdk-test'; + +import { BitGo } from '../../../../../src'; +import * as openpgp from 'openpgp'; +import nock = require('nock'); +import assert = require('assert'); + +class TestEcdsaMpcv2Utils extends ECDSAUtils.EcdsaMPCv2Utils { + public async testPickBitgoPubGpgKeyForSigning( + isMpcv2: boolean, + reqId?: IRequestTracer, + enterpriseId?: string + ): Promise { + return this.pickBitgoPubGpgKeyForSigning(isMpcv2, reqId, enterpriseId); + } +} + +class TestEddsaMpcv1Utils extends EddsaUtils { + public async testPickBitgoPubGpgKeyForSigning( + isMpcv2: boolean, + reqId?: IRequestTracer, + enterpriseId?: string + ): Promise { + return this.pickBitgoPubGpgKeyForSigning(isMpcv2, reqId, enterpriseId); + } +} + +describe('TSS MPC Pick BitGo GPG Pub Key Utils:', function () { + const walletId = '5b34252f1bf349930e34020a00000000'; + const enterpriseId = '6449153a6f6bc20006d66771cdbe15d3'; + const ecdsaCoinName = 'hteth'; + const eddsaCoinName = 'tsol'; + const ecdsaWalletData = { + id: walletId, + enterprise: enterpriseId, + coin: ecdsaCoinName, + coinSpecific: {}, + multisigType: 'tss', + keys: ['key1', 'key2', 'key3'], + }; + const eddsaWalletData = { + id: walletId, + enterprise: enterpriseId, + coin: eddsaCoinName, + coinSpecific: {}, + multisigType: 'tss', + keys: ['key1', 'key2', 'key3'], + }; + const envs: EnvironmentName[] = ['test', 'staging', 'prod']; + const ecdsaMpcv2Utils: TestEcdsaMpcv2Utils[] = []; + const eddsaMpcv1Utils: TestEddsaMpcv1Utils[] = []; + + before(async function () { + nock.cleanAll(); + for (const env of envs) { + const bitgoInstance = TestBitGo.decorate(BitGo, { env }); + bitgoInstance.initializeTestVars(); + let coinInstance = bitgoInstance.coin(ecdsaCoinName); + ecdsaMpcv2Utils.push( + new TestEcdsaMpcv2Utils(bitgoInstance, coinInstance, new Wallet(bitgoInstance, coinInstance, ecdsaWalletData)) + ); + coinInstance = bitgoInstance.coin(eddsaCoinName); + eddsaMpcv1Utils.push( + new TestEddsaMpcv1Utils(bitgoInstance, coinInstance, new Wallet(bitgoInstance, coinInstance, eddsaWalletData)) + ); + } + }); + + beforeEach(async function () { + for (const env of envs) { + const bgUrl = common.Environments[env].uri; + nock(bgUrl).get(`/api/v2/${ecdsaCoinName}/key/key3`).times(envs.length).reply(200, { hsmType: 'onprem' }); + nock(bgUrl).get(`/api/v2/${eddsaCoinName}/key/key3`).times(envs.length).reply(200, { hsmType: 'nitro' }); + } + }); + + envs.forEach(async function (env, index) { + it(`should pick correct Mpcv2 BitGo GPG Pub Key for ${env} env`, async function () { + const bitgoGpgPubKey = await ecdsaMpcv2Utils[index].testPickBitgoPubGpgKeyForSigning(true); + bitgoGpgPubKey + .armor() + .should.equal(BitgoMpcGpgPubKeys.bitgoMpcGpgPubKeys['mpcv2']['onprem'][env === 'staging' ? 'test' : env]); + }); + }); + + envs.forEach(async function (env, index) { + it(`should pick correct Mpcv1 BitGo GPG Pub Key for ${env} env`, async function () { + const bitgoGpgPubKey = await eddsaMpcv1Utils[index].testPickBitgoPubGpgKeyForSigning(false); + bitgoGpgPubKey + .armor() + .should.equal(BitgoMpcGpgPubKeys.bitgoMpcGpgPubKeys['mpcv1']['nitro'][env === 'staging' ? 'test' : env]); + }); + }); + + it(`should pick BitGo GPG Pub Key based on enterprise flag for mock env`, async function () { + const bgUrl = common.Environments['mock'].uri; + const testBitgo = TestBitGo.decorate(BitGo, { env: 'mock' }); + const testCoin = testBitgo.coin(ecdsaCoinName); + const bitgoGPGKey = await openpgp.generateKey({ + userIDs: [ + { + name: 'bitgo', + email: 'bitgo@test.com', + }, + ], + }); + nock(bgUrl) + .get(`/api/v2/${ecdsaCoinName}/tss/pubkey`) + .query({ enterpriseId }) + .reply(200, { mpcv2PublicKey: bitgoGPGKey.publicKey }); + const ecdsaMpcv2Util = new TestEcdsaMpcv2Utils( + testBitgo, + testCoin, + new Wallet(testBitgo, testCoin, ecdsaWalletData) + ); + const bitgoGpgPubKey = await ecdsaMpcv2Util.testPickBitgoPubGpgKeyForSigning(true, undefined, enterpriseId); + bitgoGpgPubKey.armor().should.equal(bitgoGPGKey.publicKey); + }); + + it(`should pick BitGo GPG Pub Key based on constants api for mock env if enterprise flag based fetch fails`, async function () { + nock.cleanAll(); + const bgUrl = common.Environments['mock'].uri; + const testBitgo = TestBitGo.decorate(BitGo, { env: 'mock' }); + const testCoin = testBitgo.coin(ecdsaCoinName); + const bitgoGPGKey = await openpgp.generateKey({ + userIDs: [ + { + name: 'bitgo', + email: 'bitgo@test.com', + }, + ], + }); + const constants = { + mpc: { + bitgoMPCv2PublicKey: bitgoGPGKey.publicKey, + bitgoPublicKey: bitgoGPGKey.publicKey, + }, + }; + nock(bgUrl).get('/api/v1/client/constants').times(2).reply(200, { ttl: 3600, constants }); + const ecdsaMpcv2Util = new TestEcdsaMpcv2Utils( + testBitgo, + testCoin, + new Wallet(testBitgo, testCoin, ecdsaWalletData) + ); + const bitgoGpgPubKey = await ecdsaMpcv2Util.testPickBitgoPubGpgKeyForSigning(true, undefined, enterpriseId); + bitgoGpgPubKey.armor().should.equal(bitgoGPGKey.publicKey); + }); + + it(`should throw an error if config is not available in one of test, staging, or prod`, async function () { + nock.cleanAll(); + const testBitgo = TestBitGo.decorate(BitGo, { env: 'test' }); + const testCoin = testBitgo.coin(ecdsaCoinName); + const ecdsaMpcv2Util = new TestEcdsaMpcv2Utils( + testBitgo, + testCoin, + new Wallet(testBitgo, testCoin, ecdsaWalletData) + ); + await assert.rejects(async () => await ecdsaMpcv2Util.testPickBitgoPubGpgKeyForSigning(true)); + }); +}); diff --git a/modules/express/test/unit/clientRoutes/externalSign.ts b/modules/express/test/unit/clientRoutes/externalSign.ts index 9c4aed4360..077c08ba05 100644 --- a/modules/express/test/unit/clientRoutes/externalSign.ts +++ b/modules/express/test/unit/clientRoutes/externalSign.ts @@ -367,6 +367,7 @@ describe('External signer', () => { const reqCommitment = { bitgo: bgTest, body: { + bitgoGpgPubKey: bitgoGpgKey.public, txRequest: { apiVersion: 'full', walletId: walletID, diff --git a/modules/sdk-core/src/bitgo/tss/bitgoPubKeys.ts b/modules/sdk-core/src/bitgo/tss/bitgoPubKeys.ts index 5a06b7096a..168df44df6 100644 --- a/modules/sdk-core/src/bitgo/tss/bitgoPubKeys.ts +++ b/modules/sdk-core/src/bitgo/tss/bitgoPubKeys.ts @@ -24,10 +24,20 @@ export const bitgoMpcGpgPubKeys = { }, }; -export function getBitgoMpcGpgPubKey(env: EnvironmentName, pubKeyType: 'nitro' | 'onprem', mpcVersion: 'mpcv1' | 'mpcv2'): string { - assert(mpcVersion in bitgoMpcGpgPubKeys, `Invalid mpcVersion in getBitgoMpcGpgPubKey, got: ${mpcVersion}, expected: mpcv1 or mpcv2`); - assert(pubKeyType in bitgoMpcGpgPubKeys[mpcVersion], `Invalid pubKeyType in getBitgoMpcGpgPubKey, got: ${pubKeyType}, expected: nitro or onprem`); - if (env !== 'prod' && env !== 'test' && env !== 'staging' && env !== 'adminProd' && env !== 'adminTest'){ +export function getBitgoMpcGpgPubKey( + env: EnvironmentName, + pubKeyType: 'nitro' | 'onprem', + mpcVersion: 'mpcv1' | 'mpcv2' +): string { + assert( + mpcVersion in bitgoMpcGpgPubKeys, + `Invalid mpcVersion in getBitgoMpcGpgPubKey, got: ${mpcVersion}, expected: mpcv1 or mpcv2` + ); + assert( + pubKeyType in bitgoMpcGpgPubKeys[mpcVersion], + `Invalid pubKeyType in getBitgoMpcGpgPubKey, got: ${pubKeyType}, expected: nitro or onprem` + ); + if (env !== 'prod' && env !== 'test' && env !== 'staging' && env !== 'adminProd' && env !== 'adminTest') { throw new Error('Invalid environment to get a BitGo MPC GPG public key'); } if (env !== 'prod' && env !== 'adminProd') { @@ -37,7 +47,7 @@ export function getBitgoMpcGpgPubKey(env: EnvironmentName, pubKeyType: 'nitro' | if (env === 'adminProd') { env = 'prod'; } - if (pubKeyType === 'nitro' && env === 'prod') { + if (pubKeyType === 'nitro' && env === 'prod' && mpcVersion === 'mpcv2') { throw new Error('Nitro mpcv2 pub key is not available in production environments yet.'); } if (pubKeyType !== 'nitro') { @@ -52,5 +62,5 @@ export function isBitgoMpcPubKey(key: string, mpcvVersion: 'mpcv1' | 'mpcv2'): b } export function envRequiresBitgoPubGpgKeyConfig(env: EnvironmentName): boolean { - return env === 'prod' || env === 'test' || env === 'staging' || env === 'adminProd' || env === 'adminTest'; + return env === 'prod' || env === 'test' || env === 'staging' || env === 'adminProd' || env === 'adminTest'; } diff --git a/modules/sdk-core/src/bitgo/tss/index.ts b/modules/sdk-core/src/bitgo/tss/index.ts index 629d887581..a5c6331efc 100644 --- a/modules/sdk-core/src/bitgo/tss/index.ts +++ b/modules/sdk-core/src/bitgo/tss/index.ts @@ -3,6 +3,7 @@ import ECDSAMethods, { ECDSAMethodTypes, DKLSMethods } from './ecdsa'; export { EDDSAMethods, EDDSAMethodTypes, ECDSAMethods, ECDSAMethodTypes, DKLSMethods }; export { ShareKeyPosition } from './types'; +export * as BitgoMpcGpgPubKeys from './bitgoPubKeys'; // exporting this types for backward compatibility. /** @deprecated Use EDDSAMethods */ diff --git a/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts b/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts index d53304f6d3..9146ba1de4 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts @@ -66,8 +66,14 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil protected async setBitgoGpgPubKey(bitgo) { const { mpcV1, mpcV2 } = await getBitgoGpgPubKey(bitgo); - this.bitgoPublicGpgKey = mpcV1; - this.bitgoMPCv2PublicGpgKey = mpcV2; + // 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; + } + // Do not unset the MPCv2 key if it is already set + if (mpcV2 !== undefined) { + this.bitgoMPCv2PublicGpgKey = mpcV2; + } } protected async pickBitgoPubGpgKeyForSigning( diff --git a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/base.ts b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/base.ts index c6f90c3265..82ad8ac9f7 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/base.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/base.ts @@ -1,11 +1,10 @@ -import * as openpgp from 'openpgp'; import { ec } from 'elliptic'; import { IBaseCoin } from '../../../baseCoin'; import baseTSSUtils from '../baseTSSUtils'; import { KeyShare } from './types'; import { BackupGpgKey } from '../baseTypes'; -import { generateGPGKeyPair, getBitgoGpgPubKey, getTrustGpgPubKey } from '../../opengpgUtils'; +import { generateGPGKeyPair, getTrustGpgPubKey } from '../../opengpgUtils'; import { BitGoBase } from '../../../bitgoBase'; import { IWallet } from '../../../wallet';