Skip to content

Commit 14aeaae

Browse files
feat(sdk-core): add webauthnInfo support to createMpc [WAL-761]
What changed: - Thread webauthnInfo through createMpc/createKeychains so hardware authenticator (PRF-derived) encryption can be stored alongside the standard encryptedPrv on user keychains - Use encryptAsync instead of encrypt for webauthnDevices entries so v2 encryption is applied consistently - Replace ad-hoc literal 1 with ShareKeyPosition.USER in ecdsa.ts webauthn guard - Remove AddKeychainOptions annotation that was inadvertently excluding prv from the type - Rename WebAuthn passphrase param type to WebauthnKeyEncryptionInfo, move canonical definition to iWallets.ts, consolidate duplicate AcceptShareWebauthnInfo, and re-export from iKeychains.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 8f1038a commit 14aeaae

12 files changed

Lines changed: 158 additions & 34 deletions

File tree

modules/bitgo/test/v2/unit/keychains.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,51 @@ describe('V2 Keychains', function () {
434434
keychains.should.deepEqual(stubbedKeychainsTriplet);
435435
});
436436
});
437+
438+
['tsol'].forEach((coin) => {
439+
it('should pass webauthnInfo to createKeychains for EDDSA TSS', async function () {
440+
const webauthnInfo = {
441+
otpDeviceId: 'device-1',
442+
prfSalt: 'salt-1',
443+
passphrase: 'prf-derived-passphrase',
444+
};
445+
const createKeychains = sandbox
446+
.stub(EDDSAUtils.default.prototype, 'createKeychains')
447+
.resolves(stubbedKeychainsTriplet);
448+
await bitgo.coin(coin).keychains().createMpc({
449+
multisigType: 'tss',
450+
passphrase: 'password',
451+
enterprise: 'enterprise',
452+
webauthnInfo,
453+
});
454+
createKeychains.calledOnce.should.be.true();
455+
createKeychains.firstCall.args[0].should.have.property('webauthnInfo', webauthnInfo);
456+
});
457+
});
458+
459+
['tbsc'].forEach((coin) => {
460+
it('should pass webauthnInfo to createKeychains for ECDSA TSS', async function () {
461+
nock(bgUrl).get('/api/v2/tss/settings').reply(200, {
462+
coinSettings: {},
463+
});
464+
const webauthnInfo = {
465+
otpDeviceId: 'device-1',
466+
prfSalt: 'salt-1',
467+
passphrase: 'prf-derived-passphrase',
468+
};
469+
const createKeychains = sandbox
470+
.stub(ECDSAUtils.EcdsaUtils.prototype, 'createKeychains')
471+
.resolves(stubbedKeychainsTriplet);
472+
await bitgo.coin(coin).keychains().createMpc({
473+
multisigType: 'tss',
474+
passphrase: 'password',
475+
enterprise: 'enterprise',
476+
webauthnInfo,
477+
});
478+
createKeychains.calledOnce.should.be.true();
479+
createKeychains.firstCall.args[0].should.have.property('webauthnInfo', webauthnInfo);
480+
});
481+
});
437482
});
438483

439484
describe('Recreate Keychains from MPCV1 to MPCV2', async function () {

modules/sdk-core/src/bitgo/keychain/iKeychains.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ export interface WebauthnInfo {
1212
encryptedPrv: string;
1313
}
1414

15+
import type { WebauthnKeyEncryptionInfo } from '../wallet/iWallets';
16+
export type { WebauthnKeyEncryptionInfo };
17+
1518
export type SourceType = 'bitgo' | 'backup' | 'user' | 'cold';
1619

1720
export type WebauthnFmt = 'none' | 'packed' | 'fido-u2f';
@@ -189,6 +192,7 @@ export interface CreateMpcOptions {
189192
originalPasscodeEncryptionCode?: string;
190193
enterprise?: string;
191194
retrofit?: DecryptedRetrofitPayload;
195+
webauthnInfo?: WebauthnKeyEncryptionInfo;
192196
encryptionVersion?: EncryptionVersion;
193197
}
194198

modules/sdk-core/src/bitgo/keychain/keychains.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ export class Keychains implements IKeychains {
332332
enterprise: params.enterprise,
333333
originalPasscodeEncryptionCode: params.originalPasscodeEncryptionCode,
334334
retrofit: params.retrofit,
335+
webauthnInfo: params.webauthnInfo,
335336
encryptionVersion: params.encryptionVersion,
336337
});
337338
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import assert from 'assert';
55
import { decrypt, readMessage, readPrivateKey, SerializedKeyPair } from 'openpgp';
66
import { IBaseCoin, KeychainsTriplet } from '../baseCoin';
77
import { BitGoBase } from '../bitgoBase';
8-
import { AddKeychainOptions, Keychain, KeyType } from '../keychain';
8+
import { AddKeychainOptions, Keychain, KeyType, WebauthnKeyEncryptionInfo } from '../keychain';
99
import { encryptText, getBitgoGpgPubKey } from './opengpgUtils';
1010
import {
1111
IntentRecipient,
@@ -105,6 +105,7 @@ export abstract class MpcUtils {
105105
passphrase: string;
106106
enterprise?: string;
107107
originalPasscodeEncryptionCode?: string;
108+
webauthnInfo?: WebauthnKeyEncryptionInfo;
108109
}): Promise<KeychainsTriplet>;
109110

110111
/**

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as openpgp from 'openpgp';
33
import { Key, readKey, SerializedKeyPair } from 'openpgp';
44
import { IBaseCoin, KeychainsTriplet } from '../../baseCoin';
55
import { BitGoBase } from '../../bitgoBase';
6-
import { Keychain, KeyIndices } from '../../keychain';
6+
import { Keychain, KeyIndices, WebauthnKeyEncryptionInfo } from '../../keychain';
77
import { getTxRequest } from '../../tss';
88
import { IWallet } from '../../wallet';
99
import { MpcUtils } from '../mpcUtils';
@@ -216,6 +216,7 @@ export default class BaseTssUtils<KeyShare> extends MpcUtils implements ITssUtil
216216
enterprise?: string | undefined;
217217
originalPasscodeEncryptionCode?: string | undefined;
218218
isThirdPartyBackup?: boolean;
219+
webauthnInfo?: WebauthnKeyEncryptionInfo;
219220
encryptionVersion?: EncryptionVersion;
220221
}): Promise<KeychainsTriplet> {
221222
throw new Error('Method not implemented.');

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Key, SerializedKeyPair } from 'openpgp';
22
import { EncryptionVersion, IEncryptionSession, IRequestTracer } from '../../../api';
33
import { KeychainsTriplet, ParsedTransaction, TransactionParams } from '../../baseCoin';
4-
import { ApiKeyShare, Keychain } from '../../keychain';
4+
import { ApiKeyShare, Keychain, WebauthnKeyEncryptionInfo } from '../../keychain';
55
import { ApiVersion, Memo, WalletType } from '../../wallet';
66
import { EDDSA, GShare, Signature, SignShare } from '../../../account-lib/mpc/tss';
77
import { Signature as EcdsaSignature } from '../../../account-lib/mpc/tss/ecdsa/types';
@@ -482,6 +482,7 @@ export type CreateKeychainParamsBase = {
482482
passphrase?: string;
483483
enterprise?: string;
484484
originalPasscodeEncryptionCode?: string;
485+
webauthnInfo?: WebauthnKeyEncryptionInfo;
485486
encryptionVersion?: EncryptionVersion;
486487
encryptionSession?: IEncryptionSession;
487488
};

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

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { EcdsaPaillierProof, EcdsaRangeProof, EcdsaTypes, hexToBigInt, minModulu
77
import { bip32 } from '@bitgo/utxo-lib';
88

99
import { ECDSA, Ecdsa } from '../../../../account-lib/mpc/tss';
10-
import { AddKeychainOptions, Keychain, KeyType } from '../../../keychain';
10+
import { AddKeychainOptions, Keychain, KeyType, WebauthnKeyEncryptionInfo } from '../../../keychain';
1111
import ECDSAMethods, { ECDSAMethodTypes } from '../../../tss/ecdsa';
1212
import { KeychainsTriplet } from '../../../baseCoin';
1313
import {
@@ -106,6 +106,7 @@ export class EcdsaUtils extends BaseEcdsaUtils {
106106
passphrase: string;
107107
enterprise?: string | undefined;
108108
originalPasscodeEncryptionCode?: string | undefined;
109+
webauthnInfo?: WebauthnKeyEncryptionInfo;
109110
}): Promise<KeychainsTriplet> {
110111
const MPC = new Ecdsa();
111112
const m = 2;
@@ -138,6 +139,7 @@ export class EcdsaUtils extends BaseEcdsaUtils {
138139
bitgoKeychain,
139140
passphrase: params.passphrase,
140141
originalPasscodeEncryptionCode: params.originalPasscodeEncryptionCode,
142+
webauthnInfo: params.webauthnInfo,
141143
});
142144
const backupKeychainPromise = this.createBackupKeychain({
143145
userGpgKey,
@@ -177,6 +179,7 @@ export class EcdsaUtils extends BaseEcdsaUtils {
177179
bitgoKeychain,
178180
passphrase,
179181
originalPasscodeEncryptionCode,
182+
webauthnInfo,
180183
}: CreateEcdsaKeychainParams): Promise<Keychain> {
181184
if (!passphrase) {
182185
throw new Error('Please provide a wallet passphrase');
@@ -191,7 +194,8 @@ export class EcdsaUtils extends BaseEcdsaUtils {
191194
backupKeyShare.userHeldKeyShare,
192195
bitgoKeychain,
193196
passphrase,
194-
originalPasscodeEncryptionCode
197+
originalPasscodeEncryptionCode,
198+
webauthnInfo
195199
);
196200
}
197201

@@ -304,7 +308,8 @@ export class EcdsaUtils extends BaseEcdsaUtils {
304308
backupKeyShare: KeyShare,
305309
bitgoKeychain: Keychain,
306310
passphrase: string,
307-
originalPasscodeEncryptionCode?: string
311+
originalPasscodeEncryptionCode?: string,
312+
webauthnInfo?: WebauthnKeyEncryptionInfo
308313
): Promise<Keychain> {
309314
const bitgoKeyShares = bitgoKeychain.keyShares;
310315
if (!bitgoKeyShares) {
@@ -399,6 +404,19 @@ export class EcdsaUtils extends BaseEcdsaUtils {
399404
password: passphrase,
400405
}),
401406
originalPasscodeEncryptionCode,
407+
webauthnDevices:
408+
webauthnInfo && recipientIndex === ShareKeyPosition.USER
409+
? [
410+
{
411+
otpDeviceId: webauthnInfo.otpDeviceId,
412+
prfSalt: webauthnInfo.prfSalt,
413+
encryptedPrv: await this.bitgo.encryptAsync({
414+
input: prv,
415+
password: webauthnInfo.passphrase,
416+
}),
417+
},
418+
]
419+
: undefined,
402420
};
403421

404422
const keychains = this.baseCoin.keychains();

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

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
} from '@bitgo/public-types';
2222

2323
import { Ecdsa } from '../../../../account-lib';
24-
import { AddKeychainOptions, Keychain, KeyType } from '../../../keychain';
24+
import { AddKeychainOptions, Keychain, KeyType, WebauthnKeyEncryptionInfo } from '../../../keychain';
2525
import { DecryptedRetrofitPayload } from '../../../keychain/iKeychains';
2626
import { ECDSAMethodTypes, getTxRequest } from '../../../tss';
2727
import { sendSignatureShareV2, sendTxRequest } from '../../../tss/common';
@@ -63,6 +63,7 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils {
6363
enterprise: string;
6464
originalPasscodeEncryptionCode?: string;
6565
retrofit?: DecryptedRetrofitPayload;
66+
webauthnInfo?: WebauthnKeyEncryptionInfo;
6667
encryptionVersion?: EncryptionVersion;
6768
}): Promise<KeychainsTriplet> {
6869
const { userSession, backupSession } = this.getUserAndBackupSession(2, 3, params.retrofit);
@@ -329,6 +330,7 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils {
329330
userReducedPrivateMaterial,
330331
params.passphrase,
331332
params.originalPasscodeEncryptionCode,
333+
params.webauthnInfo,
332334
encryptionSession
333335
);
334336
const backupKeychainPromise = this.addBackupKeychain(
@@ -366,6 +368,7 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils {
366368
reducedPrivateMaterial?: Buffer,
367369
passphrase?: string,
368370
originalPasscodeEncryptionCode?: string,
371+
webauthnInfo?: WebauthnKeyEncryptionInfo,
369372
encryptionSession?: {
370373
encrypt(plaintext: string): Promise<string>;
371374
decrypt(ciphertext: string): Promise<string>;
@@ -375,21 +378,23 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils {
375378
let source: string;
376379
let encryptedPrv: string | undefined = undefined;
377380
let reducedEncryptedPrv: string | undefined = undefined;
381+
let privateMaterialBase64: string | undefined = undefined;
378382
switch (participantIndex) {
379383
case MPCv2PartiesEnum.USER:
380384
case MPCv2PartiesEnum.BACKUP:
381385
source = participantIndex === MPCv2PartiesEnum.USER ? 'user' : 'backup';
382386
assert(privateMaterial, `Private material is required for ${source} keychain`);
383387
assert(reducedPrivateMaterial, `Reduced private material is required for ${source} keychain`);
384388
assert(passphrase, `Passphrase is required for ${source} keychain`);
389+
privateMaterialBase64 = privateMaterial.toString('base64');
385390
if (encryptionSession) {
386-
encryptedPrv = await encryptionSession.encrypt(privateMaterial.toString('base64'));
391+
encryptedPrv = await encryptionSession.encrypt(privateMaterialBase64);
387392
reducedEncryptedPrv = await encryptionSession.encrypt(
388393
btoa(String.fromCharCode.apply(null, Array.from(new Uint8Array(reducedPrivateMaterial))))
389394
);
390395
} else {
391396
encryptedPrv = this.bitgo.encrypt({
392-
input: privateMaterial.toString('base64'),
397+
input: privateMaterialBase64,
393398
password: passphrase,
394399
});
395400
// Encrypts the CBOR-encoded ReducedKeyShare (which contains the party's private
@@ -420,6 +425,19 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils {
420425
isMPCv2: true,
421426
};
422427

428+
if (webauthnInfo && participantIndex === MPCv2PartiesEnum.USER && privateMaterialBase64) {
429+
recipientKeychainParams.webauthnDevices = [
430+
{
431+
otpDeviceId: webauthnInfo.otpDeviceId,
432+
prfSalt: webauthnInfo.prfSalt,
433+
encryptedPrv: await this.bitgo.encryptAsync({
434+
input: privateMaterialBase64,
435+
password: webauthnInfo.passphrase,
436+
}),
437+
},
438+
];
439+
}
440+
423441
const keychains = this.baseCoin.keychains();
424442
return { ...(await keychains.add(recipientKeychainParams)), reducedEncryptedPrv: reducedEncryptedPrv };
425443
}
@@ -540,6 +558,7 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils {
540558
reducedPrivateMaterial: Buffer,
541559
passphrase: string,
542560
originalPasscodeEncryptionCode?: string,
561+
webauthnInfo?: WebauthnKeyEncryptionInfo,
543562
encryptionSession?: {
544563
encrypt(plaintext: string): Promise<string>;
545564
decrypt(ciphertext: string): Promise<string>;
@@ -553,6 +572,7 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils {
553572
reducedPrivateMaterial,
554573
passphrase,
555574
originalPasscodeEncryptionCode,
575+
webauthnInfo,
556576
encryptionSession
557577
);
558578
}
@@ -576,6 +596,7 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils {
576596
reducedPrivateMaterial,
577597
passphrase,
578598
originalPasscodeEncryptionCode,
599+
undefined,
579600
encryptionSession
580601
);
581602
}

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import assert from 'assert';
55
import * as openpgp from 'openpgp';
66
import Eddsa, { GShare, SignShare } from '../../../../account-lib/mpc/tss';
7-
import { AddKeychainOptions, CreateBackupOptions, Keychain } from '../../../keychain';
7+
import { AddKeychainOptions, CreateBackupOptions, Keychain, WebauthnKeyEncryptionInfo } from '../../../keychain';
88
import { verifyWalletSignature } from '../../../tss/eddsa/eddsa';
99
import { createShareProof, encryptText, generateGPGKeyPair, getBitgoGpgPubKey } from '../../opengpgUtils';
1010
import {
@@ -129,6 +129,7 @@ export class EddsaUtils extends baseTSSUtils<KeyShare> {
129129
bitgoKeychain,
130130
passphrase,
131131
originalPasscodeEncryptionCode,
132+
webauthnInfo,
132133
encryptionSession,
133134
}: CreateEddsaKeychainParams): Promise<Keychain> {
134135
const MPC = await Eddsa.initialize();
@@ -195,6 +196,18 @@ export class EddsaUtils extends baseTSSUtils<KeyShare> {
195196
});
196197
}
197198
}
199+
if (webauthnInfo && userKeychainParams.encryptedPrv) {
200+
userKeychainParams.webauthnDevices = [
201+
{
202+
otpDeviceId: webauthnInfo.otpDeviceId,
203+
prfSalt: webauthnInfo.prfSalt,
204+
encryptedPrv: await this.bitgo.encryptAsync({
205+
input: JSON.stringify(userSigningMaterial),
206+
password: webauthnInfo.passphrase,
207+
}),
208+
},
209+
];
210+
}
198211

199212
return await this.baseCoin.keychains().add(userKeychainParams);
200213
}
@@ -355,6 +368,7 @@ export class EddsaUtils extends baseTSSUtils<KeyShare> {
355368
passphrase?: string;
356369
enterprise?: string;
357370
originalPasscodeEncryptionCode?: string;
371+
webauthnInfo?: WebauthnKeyEncryptionInfo;
358372
encryptionVersion?: EncryptionVersion;
359373
}): Promise<KeychainsTriplet> {
360374
const MPC = await Eddsa.initialize();
@@ -388,6 +402,7 @@ export class EddsaUtils extends baseTSSUtils<KeyShare> {
388402
bitgoKeychain,
389403
passphrase: params.passphrase,
390404
originalPasscodeEncryptionCode: params.originalPasscodeEncryptionCode,
405+
webauthnInfo: params.webauthnInfo,
391406
encryptionSession,
392407
});
393408
const backupKeychainPromise = this.createBackupKeychain({

0 commit comments

Comments
 (0)