Skip to content

Commit affc67f

Browse files
authored
Merge pull request #8588 from BitGo/WCI-223
feat(sdk-core): add dedicated EdDSA MPCv2 BitGo GPG public key support
2 parents 6d653da + 4d9f626 commit affc67f

12 files changed

Lines changed: 130 additions & 22 deletions

File tree

modules/bitgo/test/v2/unit/internal/opengpgUtils.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,14 +285,25 @@ describe('OpenGPG Utils Tests', function () {
285285
should.exist(gpgKey.publicKey);
286286
});
287287

288-
it('should generate a a GPG key for with random name and email', async function () {
288+
it('should generate a a GPG key for ed25519 with random name and email', async function () {
289289
const gpgKey = await openpgpUtils.generateGPGKeyPair('ed25519');
290290

291291
should.exist(gpgKey);
292292
should.exist(gpgKey.privateKey);
293293
should.exist(gpgKey.publicKey);
294294
});
295295

296+
it('should generate an ed25519 GPG key with a dedicated signing subkey', async function () {
297+
const gpgKey = await openpgpUtils.generateGPGKeyPair('ed25519');
298+
const parsedKey = await openpgp.readKey({ armoredKey: gpgKey.publicKey });
299+
300+
const signingKey = await parsedKey.getSigningKey();
301+
// The signing key must be a subkey (has bindingSignatures), not the primary key.
302+
// The HSM verifies the binding signature during MPS keygen round 1.
303+
assert.ok('bindingSignatures' in signingKey, 'signing key should be a subkey with bindingSignatures');
304+
assert.ok(signingKey.bindingSignatures.length > 0, 'signing subkey should have at least one binding signature');
305+
});
306+
296307
it('should generate a a GPG key with provided name and email', async function () {
297308
const userName = 'John Doe';
298309
const userEmail = 'john.doe@example.com';

modules/bitgo/test/v2/unit/internal/tssUtils/bitgoMpcGpgPubKeys.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,4 +224,20 @@ describe('TSS MPC Pick BitGo GPG Pub Key Utils:', function () {
224224
// Also verify it's the same as what's returned by testPickBitgoPubGpgKeyForSigning
225225
gpgKey.armor().should.equal(capturedKey);
226226
});
227+
228+
describe('BitgoMpcGpgPubKeys.isBitgoEddsaMpcv2PubKey', function () {
229+
it('should return true for the hardcoded on-prem test EdDSA MPCv2 key', function () {
230+
const key = BitgoMpcGpgPubKeys.bitgoMpcGpgPubKeys['eddsaMpcv2']['onprem']['test'];
231+
assert.ok(BitgoMpcGpgPubKeys.isBitgoEddsaMpcv2PubKey(key));
232+
});
233+
234+
it('should return false for the ECDSA MPCv2 on-prem test key', function () {
235+
const key = BitgoMpcGpgPubKeys.bitgoMpcGpgPubKeys['mpcv2']['onprem']['test'];
236+
assert.ok(!BitgoMpcGpgPubKeys.isBitgoEddsaMpcv2PubKey(key));
237+
});
238+
239+
it('should return false for an arbitrary string', function () {
240+
assert.ok(!BitgoMpcGpgPubKeys.isBitgoEddsaMpcv2PubKey('not-a-known-key'));
241+
});
242+
});
227243
});

modules/bitgo/test/v2/unit/internal/tssUtils/ecdsa.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,9 @@ describe('TSS Ecdsa Utils:', async function () {
239239
],
240240
});
241241
const nockGPGKey = await nockGetBitgoPublicKeyBasedOnFeatureFlags(coinName, 'enterprise_id', nitroGPGKeypair);
242-
const bitgoGpgPublicKey = await tssUtils.getBitgoGpgPubkeyBasedOnFeatureFlags('enterprise_id');
242+
const { mpcv2PublicKey: bitgoGpgPublicKey } = await tssUtils.getBitgoGpgPubkeyBasedOnFeatureFlags(
243+
'enterprise_id'
244+
);
243245
should.equal(nockGPGKey.publicKey, bitgoGpgPublicKey.armor());
244246
});
245247

@@ -1691,6 +1693,7 @@ describe('TSS Ecdsa Utils:', async function () {
16911693
const bitgoGPGPublicKeyResponse: BitgoGPGPublicKey = {
16921694
name: 'irrelevant',
16931695
publicKey: bitgoGpgKeyPair.publicKey,
1696+
mpcv2PublicKey: bitgoGpgKeyPair.publicKey,
16941697
enterpriseId,
16951698
};
16961699
nock(bgUrl).get(`/api/v2/${coin}/tss/pubkey`).query({ enterpriseId }).reply(200, bitgoGPGPublicKeyResponse);

modules/bitgo/test/v2/unit/internal/tssUtils/eddsa.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { TestableBG, TestBitGo } from '@bitgo/sdk-test';
99
import { BitGo } from '../../../../../src';
1010
import {
1111
BaseCoin,
12+
BitgoGPGPublicKey,
1213
CommitmentShareRecord,
1314
CommitmentType,
1415
common,
@@ -1240,5 +1241,40 @@ describe('TSS Utils:', async function () {
12401241
});
12411242
});
12421243

1244+
describe('getBitgoGpgPubkeyBasedOnFeatureFlags', function () {
1245+
it('should return eddsaMpcv2PublicKey when present in feature flags response', async function () {
1246+
const ed25519KeyPair = await openpgp.generateKey({
1247+
userIDs: [{ name: 'bitgo eddsa', email: 'bitgo@test.com' }],
1248+
curve: 'ed25519',
1249+
});
1250+
const response: BitgoGPGPublicKey = {
1251+
name: 'irrelevant',
1252+
publicKey: ed25519KeyPair.publicKey,
1253+
mpcv2PublicKey: ed25519KeyPair.publicKey,
1254+
eddsaMpcv2PublicKey: ed25519KeyPair.publicKey,
1255+
enterpriseId: 'enterprise_id',
1256+
};
1257+
nock(bgUrl).get(`/api/v2/${coinName}/tss/pubkey`).query({ enterpriseId: 'enterprise_id' }).reply(200, response);
1258+
const { eddsaMpcv2PublicKey } = await tssUtils.getBitgoGpgPubkeyBasedOnFeatureFlags('enterprise_id');
1259+
should.exist(eddsaMpcv2PublicKey);
1260+
should.equal(ed25519KeyPair.publicKey, eddsaMpcv2PublicKey!.armor());
1261+
});
1262+
1263+
it('should return undefined eddsaMpcv2PublicKey when absent from feature flags response', async function () {
1264+
const keyPair = await openpgp.generateKey({
1265+
userIDs: [{ name: 'bitgo', email: 'bitgo@test.com' }],
1266+
});
1267+
const response: BitgoGPGPublicKey = {
1268+
name: 'irrelevant',
1269+
publicKey: keyPair.publicKey,
1270+
mpcv2PublicKey: keyPair.publicKey,
1271+
enterpriseId: 'enterprise_id',
1272+
};
1273+
nock(bgUrl).get(`/api/v2/${coinName}/tss/pubkey`).query({ enterpriseId: 'enterprise_id' }).reply(200, response);
1274+
const { eddsaMpcv2PublicKey } = await tssUtils.getBitgoGpgPubkeyBasedOnFeatureFlags('enterprise_id');
1275+
should.not.exist(eddsaMpcv2PublicKey);
1276+
});
1277+
});
1278+
12431279
// #endregion Nock helpers
12441280
});

modules/bitgo/test/v2/unit/internal/tssUtils/eddsaMPCv2/createKeychains.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ describe('TSS EdDSA MPCv2 Utils:', async function () {
310310
name: 'irrelevant',
311311
publicKey: bitgoKeyPair.publicKey,
312312
mpcv2PublicKey: bitgoKeyPair.publicKey,
313+
eddsaMpcv2PublicKey: bitgoKeyPair.publicKey,
313314
enterpriseId: enterprise,
314315
};
315316
nock(bgUrl).get(`/api/v2/${coin}/tss/pubkey`).query({ enterpriseId: enterprise }).reply(200, response);

modules/sdk-core/src/bitgo/tss/bitgoPubKeys.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ export const bitgoMpcGpgPubKeys = {
2222
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',
2323
},
2424
},
25+
eddsaMpcv2: {
26+
nitro: {
27+
test: '', // TODO WCI-205: add eddsaMpcv2 key for nitro
28+
prod: '',
29+
},
30+
onprem: {
31+
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',
32+
prod: '', // TODO WCI-142: add prod eddsaMpcv2 key
33+
},
34+
},
2535
};
2636

2737
export function getBitgoMpcGpgPubKey(
@@ -61,6 +71,10 @@ export function isBitgoMpcPubKey(key: string, mpcvVersion: 'mpcv1' | 'mpcv2'): b
6171
return Object.values(bitgoMpcGpgPubKeys[mpcvVersion]).some((envKeys) => Object.values(envKeys).includes(key));
6272
}
6373

74+
export function isBitgoEddsaMpcv2PubKey(key: string): boolean {
75+
return Object.values(bitgoMpcGpgPubKeys.eddsaMpcv2).some((envKeys) => Object.values(envKeys).includes(key));
76+
}
77+
6478
export function envRequiresBitgoPubGpgKeyConfig(env: EnvironmentName): boolean {
6579
return env === 'prod' || env === 'test' || env === 'staging' || env === 'adminProd' || env === 'adminTest';
6680
}

modules/sdk-core/src/bitgo/utils/opengpgUtils.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ export type AuthEncMessage = {
3535
* @param {BitGoBase} bitgo BitGo object
3636
* @return {Key} public gpg key
3737
*/
38-
export async function getBitgoGpgPubKey(bitgo: BitGoBase): Promise<{ mpcV1: Key; mpcV2: Key | undefined }> {
38+
export async function getBitgoGpgPubKey(
39+
bitgo: BitGoBase
40+
): Promise<{ mpcV1: Key; mpcV2: Key | undefined; eddsaMpcV2: Key | undefined }> {
3941
const constants = await bitgo.fetchConstants();
4042
if (!constants.mpc || !constants.mpc.bitgoPublicKey) {
4143
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;
4547
const bitgoMPCv2PublicKeyStr = constants.mpc.bitgoMPCv2PublicKey
4648
? await readKey({ armoredKey: constants.mpc.bitgoMPCv2PublicKey as string })
4749
: undefined;
48-
return { mpcV1: await readKey({ armoredKey: bitgoPublicKeyStr }), mpcV2: bitgoMPCv2PublicKeyStr };
50+
const bitgoEddsaMpcv2PublicKeyStr = constants.mpc.bitgoEddsaMpcv2PublicKey
51+
? await readKey({ armoredKey: constants.mpc.bitgoEddsaMpcv2PublicKey as string })
52+
: undefined;
53+
return {
54+
mpcV1: await readKey({ armoredKey: bitgoPublicKeyStr }),
55+
mpcV2: bitgoMPCv2PublicKeyStr,
56+
eddsaMpcV2: bitgoEddsaMpcv2PublicKeyStr,
57+
};
4958
}
5059

5160
/**
@@ -417,6 +426,9 @@ export async function generateGPGKeyPair(
417426

418427
// Allow generating secp256k1 key pairs
419428
pgp.config.rejectCurves = new Set();
429+
430+
const subkeys = keyCurve === 'ed25519' ? ([{ sign: true }, { sign: false }] as pgp.SubkeyOptions[]) : undefined;
431+
420432
const gpgKey = await pgp.generateKey({
421433
userIDs: [
422434
{
@@ -425,6 +437,7 @@ export async function generateGPGKeyPair(
425437
},
426438
],
427439
curve: keyCurve,
440+
...(subkeys && { subkeys }),
428441
});
429442

430443
return gpgKey;

modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export default class BaseTssUtils<KeyShare> extends MpcUtils implements ITssUtil
5353
private _wallet?: IWallet;
5454
protected bitgoPublicGpgKey: openpgp.Key;
5555
protected bitgoMPCv2PublicGpgKey: openpgp.Key | undefined;
56+
protected bitgoEddsaMpcv2PublicGpgKey: openpgp.Key | undefined;
5657

5758
constructor(bitgo: BitGoBase, baseCoin: IBaseCoin, wallet?: IWallet) {
5859
super(bitgo, baseCoin);
@@ -67,7 +68,7 @@ export default class BaseTssUtils<KeyShare> extends MpcUtils implements ITssUtil
6768
}
6869

6970
protected async setBitgoGpgPubKey(bitgo) {
70-
const { mpcV1, mpcV2 } = await getBitgoGpgPubKey(bitgo);
71+
const { mpcV1, mpcV2, eddsaMpcV2 } = await getBitgoGpgPubKey(bitgo);
7172
// Do not unset the MPCv1 key if it is already set. This is to avoid unsetting if extra constants api calls fail.
7273
if (mpcV1 !== undefined) {
7374
this.bitgoPublicGpgKey = mpcV1;
@@ -76,6 +77,10 @@ export default class BaseTssUtils<KeyShare> extends MpcUtils implements ITssUtil
7677
if (mpcV2 !== undefined) {
7778
this.bitgoMPCv2PublicGpgKey = mpcV2;
7879
}
80+
// Do not unset the EdDSA MPCv2 key if it is already set
81+
if (eddsaMpcV2 !== undefined) {
82+
this.bitgoEddsaMpcv2PublicGpgKey = eddsaMpcV2;
83+
}
7984
}
8085

8186
public async pickBitgoPubGpgKeyForSigning(
@@ -104,8 +109,8 @@ export default class BaseTssUtils<KeyShare> extends MpcUtils implements ITssUtil
104109
// First try to get the key based on feature flags, if that fails, fallback to the default key from constants api.
105110
bitgoGpgPubKey = await this.getBitgoGpgPubkeyBasedOnFeatureFlags(enterpriseId, isMpcv2, reqId)
106111
.then(
107-
async (pubKey) =>
108-
pubKey ?? (isMpcv2 ? await this.getBitgoMpcv2PublicGpgKey() : await this.getBitgoPublicGpgKey())
112+
async ({ mpcv2PublicKey }) =>
113+
mpcv2PublicKey ?? (isMpcv2 ? await this.getBitgoMpcv2PublicGpgKey() : await this.getBitgoPublicGpgKey())
109114
)
110115
.catch(async (e) => (isMpcv2 ? await this.getBitgoMpcv2PublicGpgKey() : await this.getBitgoPublicGpgKey()));
111116
} else {
@@ -552,8 +557,9 @@ export default class BaseTssUtils<KeyShare> extends MpcUtils implements ITssUtil
552557
}
553558

554559
/**
555-
* It gets the appropriate BitGo GPG public key for key creation based on a
560+
* It gets the appropriate BitGo GPG public keys for key creation based on a
556561
* combination of coin and the feature flags on the user and their enterprise if set.
562+
* Returns both the default MPCv2 key and the EdDSA-specific MPCv2 key (if present).
557563
* @param enterpriseId - enterprise under which user wants to create the wallet
558564
* @param isMPCv2 - true to get the MPCv2 GPG public key, defaults to false
559565
* @param reqId - request tracer request id
@@ -562,16 +568,21 @@ export default class BaseTssUtils<KeyShare> extends MpcUtils implements ITssUtil
562568
enterpriseId: string | undefined,
563569
isMPCv2 = false,
564570
reqId?: IRequestTracer
565-
): Promise<Key> {
571+
): Promise<{ mpcv2PublicKey: Key; eddsaMpcv2PublicKey: Key | undefined }> {
566572
const reqTracer = reqId || new RequestTracer();
567573
this.bitgo.setRequestTracer(reqTracer);
568574
const response: BitgoGPGPublicKey = await this.bitgo
569575
.get(this.baseCoin.url('/tss/pubkey'))
570576
.query({ enterpriseId })
571577
.retry(3)
572578
.result();
573-
const bitgoPublicKeyStr = isMPCv2 ? response.mpcv2PublicKey : response.publicKey;
574-
return readKey({ armoredKey: bitgoPublicKeyStr as string });
579+
const mpcv2PublicKey = await readKey({
580+
armoredKey: (isMPCv2 ? response.mpcv2PublicKey : response.publicKey) as string,
581+
});
582+
const eddsaMpcv2PublicKey = response.eddsaMpcv2PublicKey
583+
? await readKey({ armoredKey: response.eddsaMpcv2PublicKey })
584+
: undefined;
585+
return { mpcv2PublicKey, eddsaMpcv2PublicKey };
575586
}
576587

577588
/**

modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,7 @@ export interface BitgoGPGPublicKey {
572572
name: string;
573573
publicKey: string;
574574
mpcv2PublicKey?: string;
575+
eddsaMpcv2PublicKey?: string;
575576
enterpriseId: string;
576577
}
577578

modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@ export class EcdsaUtils extends BaseEcdsaUtils {
118118

119119
// Get the BitGo public key based on user/enterprise feature flags
120120
// If it doesn't work, use the default public key from the constants
121-
const bitgoPublicGpgKey =
122-
(await this.getBitgoGpgPubkeyBasedOnFeatureFlags(params.enterprise)) ?? this.bitgoPublicGpgKey;
121+
const { mpcv2PublicKey: primaryGpgKey } = await this.getBitgoGpgPubkeyBasedOnFeatureFlags(params.enterprise);
122+
const bitgoPublicGpgKey = primaryGpgKey ?? this.bitgoPublicGpgKey;
123123

124124
const bitgoKeychain = await this.createBitgoKeychain({
125125
userGpgKey,

0 commit comments

Comments
 (0)