From 18a642ac27014ca9b7738ea1cd91dadcc39f2725 Mon Sep 17 00:00:00 2001 From: Mohammad Al Faiyaz Date: Wed, 22 Apr 2026 15:26:35 +0000 Subject: [PATCH 1/3] feat(sdk-core): add webauthnInfo support to createMpc Add MpcWebauthnInfo parameter to CreateMpcOptions and thread it through all four createKeychains implementations (EDDSA, EdDSA MPCv2, ECDSA, ECDSA MPCv2). When provided, the user keychain is registered with an additional webauthnDevices entry containing a PRF-encrypted copy of the user private share, avoiding the need for a separate PUT /key/{id} call post-wallet-creation. Also wires webauthnInfo from generateWallet through generateMpcWallet so TSS wallet creation passes the authenticator info end-to-end. Ticket: WAL-761 --- modules/bitgo/test/v2/unit/keychains.ts | 45 +++++++++++++++++++ .../sdk-core/src/bitgo/keychain/iKeychains.ts | 9 ++++ .../sdk-core/src/bitgo/keychain/keychains.ts | 1 + modules/sdk-core/src/bitgo/utils/mpcUtils.ts | 3 +- .../src/bitgo/utils/tss/baseTSSUtils.ts | 3 +- .../sdk-core/src/bitgo/utils/tss/baseTypes.ts | 3 +- .../src/bitgo/utils/tss/ecdsa/ecdsa.ts | 26 +++++++++-- .../src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts | 32 ++++++++++--- .../src/bitgo/utils/tss/eddsa/eddsa.ts | 17 ++++++- .../src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts | 32 ++++++++++--- modules/sdk-core/src/bitgo/wallet/iWallets.ts | 1 + modules/sdk-core/src/bitgo/wallet/wallets.ts | 3 ++ 12 files changed, 155 insertions(+), 20 deletions(-) diff --git a/modules/bitgo/test/v2/unit/keychains.ts b/modules/bitgo/test/v2/unit/keychains.ts index 024d8f08c4..c55d8641e7 100644 --- a/modules/bitgo/test/v2/unit/keychains.ts +++ b/modules/bitgo/test/v2/unit/keychains.ts @@ -434,6 +434,51 @@ describe('V2 Keychains', function () { keychains.should.deepEqual(stubbedKeychainsTriplet); }); }); + + ['tsol'].forEach((coin) => { + it('should pass webauthnInfo to createKeychains for EDDSA TSS', async function () { + const webauthnInfo = { + otpDeviceId: 'device-1', + prfSalt: 'salt-1', + passphrase: 'prf-derived-passphrase', + }; + const createKeychains = sandbox + .stub(EDDSAUtils.default.prototype, 'createKeychains') + .resolves(stubbedKeychainsTriplet); + await bitgo.coin(coin).keychains().createMpc({ + multisigType: 'tss', + passphrase: 'password', + enterprise: 'enterprise', + webauthnInfo, + }); + createKeychains.calledOnce.should.be.true(); + createKeychains.firstCall.args[0].should.have.property('webauthnInfo', webauthnInfo); + }); + }); + + ['tbsc'].forEach((coin) => { + it('should pass webauthnInfo to createKeychains for ECDSA TSS', async function () { + nock(bgUrl).get('/api/v2/tss/settings').reply(200, { + coinSettings: {}, + }); + const webauthnInfo = { + otpDeviceId: 'device-1', + prfSalt: 'salt-1', + passphrase: 'prf-derived-passphrase', + }; + const createKeychains = sandbox + .stub(ECDSAUtils.EcdsaUtils.prototype, 'createKeychains') + .resolves(stubbedKeychainsTriplet); + await bitgo.coin(coin).keychains().createMpc({ + multisigType: 'tss', + passphrase: 'password', + enterprise: 'enterprise', + webauthnInfo, + }); + createKeychains.calledOnce.should.be.true(); + createKeychains.firstCall.args[0].should.have.property('webauthnInfo', webauthnInfo); + }); + }); }); describe('Recreate Keychains from MPCV1 to MPCV2', async function () { diff --git a/modules/sdk-core/src/bitgo/keychain/iKeychains.ts b/modules/sdk-core/src/bitgo/keychain/iKeychains.ts index 4876d031d3..867b30eedd 100644 --- a/modules/sdk-core/src/bitgo/keychain/iKeychains.ts +++ b/modules/sdk-core/src/bitgo/keychain/iKeychains.ts @@ -12,6 +12,14 @@ export interface WebauthnInfo { encryptedPrv: string; } +/** WebAuthn PRF-based encryption info passed to MPC key creation. The passphrase is the + * PRF-derived key used to encrypt the user private share before it is persisted. */ +export interface MpcWebauthnInfo { + otpDeviceId: string; + prfSalt: string; + passphrase: string; +} + export type SourceType = 'bitgo' | 'backup' | 'user' | 'cold'; export type WebauthnFmt = 'none' | 'packed' | 'fido-u2f'; @@ -188,6 +196,7 @@ export interface CreateMpcOptions { originalPasscodeEncryptionCode?: string; enterprise?: string; retrofit?: DecryptedRetrofitPayload; + webauthnInfo?: MpcWebauthnInfo; } export interface RecreateMpcOptions extends Omit { diff --git a/modules/sdk-core/src/bitgo/keychain/keychains.ts b/modules/sdk-core/src/bitgo/keychain/keychains.ts index e299c04225..9c98af8d90 100644 --- a/modules/sdk-core/src/bitgo/keychain/keychains.ts +++ b/modules/sdk-core/src/bitgo/keychain/keychains.ts @@ -326,6 +326,7 @@ export class Keychains implements IKeychains { enterprise: params.enterprise, originalPasscodeEncryptionCode: params.originalPasscodeEncryptionCode, retrofit: params.retrofit, + webauthnInfo: params.webauthnInfo, }); } diff --git a/modules/sdk-core/src/bitgo/utils/mpcUtils.ts b/modules/sdk-core/src/bitgo/utils/mpcUtils.ts index ee19e12ae3..af4a0165f3 100644 --- a/modules/sdk-core/src/bitgo/utils/mpcUtils.ts +++ b/modules/sdk-core/src/bitgo/utils/mpcUtils.ts @@ -5,7 +5,7 @@ import assert from 'assert'; import { decrypt, readMessage, readPrivateKey, SerializedKeyPair } from 'openpgp'; import { IBaseCoin, KeychainsTriplet } from '../baseCoin'; import { BitGoBase } from '../bitgoBase'; -import { AddKeychainOptions, Keychain, KeyType } from '../keychain'; +import { AddKeychainOptions, Keychain, KeyType, MpcWebauthnInfo } from '../keychain'; import { encryptText, getBitgoGpgPubKey } from './opengpgUtils'; import { IntentRecipient, @@ -105,6 +105,7 @@ export abstract class MpcUtils { passphrase: string; enterprise?: string; originalPasscodeEncryptionCode?: string; + webauthnInfo?: MpcWebauthnInfo; }): Promise; /** diff --git a/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts b/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts index e4dfebe65c..323a95724f 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts @@ -3,7 +3,7 @@ import * as openpgp from 'openpgp'; import { Key, readKey, SerializedKeyPair } from 'openpgp'; import { IBaseCoin, KeychainsTriplet } from '../../baseCoin'; import { BitGoBase } from '../../bitgoBase'; -import { Keychain, KeyIndices } from '../../keychain'; +import { Keychain, KeyIndices, MpcWebauthnInfo } from '../../keychain'; import { getTxRequest } from '../../tss'; import { IWallet } from '../../wallet'; import { MpcUtils } from '../mpcUtils'; @@ -194,6 +194,7 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil enterprise?: string | undefined; originalPasscodeEncryptionCode?: string | undefined; isThirdPartyBackup?: boolean; + webauthnInfo?: MpcWebauthnInfo; }): Promise { throw new Error('Method not implemented.'); } diff --git a/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts b/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts index f773e370a7..f36ae8e1af 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts @@ -1,7 +1,7 @@ import { Key, SerializedKeyPair } from 'openpgp'; import { IRequestTracer } from '../../../api'; import { KeychainsTriplet, ParsedTransaction, TransactionParams } from '../../baseCoin'; -import { ApiKeyShare, Keychain } from '../../keychain'; +import { ApiKeyShare, Keychain, MpcWebauthnInfo } from '../../keychain'; import { ApiVersion, Memo, WalletType } from '../../wallet'; import { EDDSA, GShare, Signature, SignShare } from '../../../account-lib/mpc/tss'; import { Signature as EcdsaSignature } from '../../../account-lib/mpc/tss/ecdsa/types'; @@ -482,6 +482,7 @@ export type CreateKeychainParamsBase = { passphrase?: string; enterprise?: string; originalPasscodeEncryptionCode?: string; + webauthnInfo?: MpcWebauthnInfo; }; export type CreateBitGoKeychainParamsBase = Omit; 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 41391d7232..5b6da13625 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts @@ -7,7 +7,7 @@ import { EcdsaPaillierProof, EcdsaRangeProof, EcdsaTypes, hexToBigInt, minModulu import { bip32 } from '@bitgo/utxo-lib'; import { ECDSA, Ecdsa } from '../../../../account-lib/mpc/tss'; -import { AddKeychainOptions, Keychain, KeyType } from '../../../keychain'; +import { AddKeychainOptions, Keychain, KeyType, MpcWebauthnInfo } from '../../../keychain'; import ECDSAMethods, { ECDSAMethodTypes } from '../../../tss/ecdsa'; import { KeychainsTriplet } from '../../../baseCoin'; import { @@ -106,6 +106,7 @@ export class EcdsaUtils extends BaseEcdsaUtils { passphrase: string; enterprise?: string | undefined; originalPasscodeEncryptionCode?: string | undefined; + webauthnInfo?: MpcWebauthnInfo; }): Promise { const MPC = new Ecdsa(); const m = 2; @@ -138,6 +139,7 @@ export class EcdsaUtils extends BaseEcdsaUtils { bitgoKeychain, passphrase: params.passphrase, originalPasscodeEncryptionCode: params.originalPasscodeEncryptionCode, + webauthnInfo: params.webauthnInfo, }); const backupKeychainPromise = this.createBackupKeychain({ userGpgKey, @@ -177,6 +179,7 @@ export class EcdsaUtils extends BaseEcdsaUtils { bitgoKeychain, passphrase, originalPasscodeEncryptionCode, + webauthnInfo, }: CreateEcdsaKeychainParams): Promise { if (!passphrase) { throw new Error('Please provide a wallet passphrase'); @@ -191,7 +194,8 @@ export class EcdsaUtils extends BaseEcdsaUtils { backupKeyShare.userHeldKeyShare, bitgoKeychain, passphrase, - originalPasscodeEncryptionCode + originalPasscodeEncryptionCode, + webauthnInfo ); } @@ -304,7 +308,8 @@ export class EcdsaUtils extends BaseEcdsaUtils { backupKeyShare: KeyShare, bitgoKeychain: Keychain, passphrase: string, - originalPasscodeEncryptionCode?: string + originalPasscodeEncryptionCode?: string, + webauthnInfo?: MpcWebauthnInfo ): Promise { const bitgoKeyShares = bitgoKeychain.keyShares; if (!bitgoKeyShares) { @@ -389,7 +394,7 @@ export class EcdsaUtils extends BaseEcdsaUtils { ); const prv = JSON.stringify(recipientCombinedKey.signingMaterial); - const recipientKeychainParams = { + const recipientKeychainParams: AddKeychainOptions = { source: recipient, keyType: 'tss' as KeyType, commonKeychain: bitgoKeychain.commonKeychain, @@ -401,6 +406,19 @@ export class EcdsaUtils extends BaseEcdsaUtils { originalPasscodeEncryptionCode, }; + if (webauthnInfo && recipientIndex === 1) { + recipientKeychainParams.webauthnDevices = [ + { + otpDeviceId: webauthnInfo.otpDeviceId, + prfSalt: webauthnInfo.prfSalt, + encryptedPrv: this.bitgo.encrypt({ + input: prv, + password: webauthnInfo.passphrase, + }), + }, + ]; + } + const keychains = this.baseCoin.keychains(); return recipientIndex === 1 ? await keychains.add(recipientKeychainParams) 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 129877a4af..754d87003e 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts @@ -20,7 +20,7 @@ import { } from '@bitgo/public-types'; import { Ecdsa } from '../../../../account-lib'; -import { AddKeychainOptions, Keychain, KeyType } from '../../../keychain'; +import { AddKeychainOptions, Keychain, KeyType, MpcWebauthnInfo } from '../../../keychain'; import { DecryptedRetrofitPayload } from '../../../keychain/iKeychains'; import { ECDSAMethodTypes, getTxRequest } from '../../../tss'; import { sendSignatureShareV2, sendTxRequest } from '../../../tss/common'; @@ -57,6 +57,7 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils { enterprise: string; originalPasscodeEncryptionCode?: string; retrofit?: DecryptedRetrofitPayload; + webauthnInfo?: MpcWebauthnInfo; }): Promise { const { userSession, backupSession } = this.getUserAndBackupSession(2, 3, params.retrofit); const userGpgKey = await generateGPGKeyPair('secp256k1'); @@ -318,7 +319,8 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils { userPrivateMaterial, userReducedPrivateMaterial, params.passphrase, - params.originalPasscodeEncryptionCode + params.originalPasscodeEncryptionCode, + params.webauthnInfo ); const backupKeychainPromise = this.addBackupKeychain( bitgoCommonKeychain, @@ -350,11 +352,13 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils { privateMaterial?: Buffer, reducedPrivateMaterial?: Buffer, passphrase?: string, - originalPasscodeEncryptionCode?: string + originalPasscodeEncryptionCode?: string, + webauthnInfo?: MpcWebauthnInfo ): Promise { let source: string; let encryptedPrv: string | undefined = undefined; let reducedEncryptedPrv: string | undefined = undefined; + let privateMaterialBase64: string | undefined = undefined; switch (participantIndex) { case MPCv2PartiesEnum.USER: case MPCv2PartiesEnum.BACKUP: @@ -362,8 +366,9 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils { assert(privateMaterial, `Private material is required for ${source} keychain`); assert(reducedPrivateMaterial, `Reduced private material is required for ${source} keychain`); assert(passphrase, `Passphrase is required for ${source} keychain`); + privateMaterialBase64 = privateMaterial.toString('base64'); encryptedPrv = this.bitgo.encrypt({ - input: privateMaterial.toString('base64'), + input: privateMaterialBase64, password: passphrase, }); // Encrypts the CBOR-encoded ReducedKeyShare (which contains the party's private @@ -393,6 +398,19 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils { isMPCv2: true, }; + if (webauthnInfo && participantIndex === MPCv2PartiesEnum.USER && privateMaterialBase64) { + recipientKeychainParams.webauthnDevices = [ + { + otpDeviceId: webauthnInfo.otpDeviceId, + prfSalt: webauthnInfo.prfSalt, + encryptedPrv: this.bitgo.encrypt({ + input: privateMaterialBase64, + password: webauthnInfo.passphrase, + }), + }, + ]; + } + const keychains = this.baseCoin.keychains(); return { ...(await keychains.add(recipientKeychainParams)), reducedEncryptedPrv: reducedEncryptedPrv }; } @@ -512,7 +530,8 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils { privateMaterial: Buffer, reducedPrivateMaterial: Buffer, passphrase: string, - originalPasscodeEncryptionCode?: string + originalPasscodeEncryptionCode?: string, + webauthnInfo?: MpcWebauthnInfo ): Promise { return this.createParticipantKeychain( MPCv2PartiesEnum.USER, @@ -520,7 +539,8 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils { privateMaterial, reducedPrivateMaterial, passphrase, - originalPasscodeEncryptionCode + originalPasscodeEncryptionCode, + webauthnInfo ); } 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 de4790dfd5..5c9d29198c 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts @@ -4,7 +4,7 @@ import assert from 'assert'; import * as openpgp from 'openpgp'; import Eddsa, { GShare, SignShare } from '../../../../account-lib/mpc/tss'; -import { AddKeychainOptions, CreateBackupOptions, Keychain } from '../../../keychain'; +import { AddKeychainOptions, CreateBackupOptions, Keychain, MpcWebauthnInfo } from '../../../keychain'; import { verifyWalletSignature } from '../../../tss/eddsa/eddsa'; import { createShareProof, encryptText, generateGPGKeyPair, getBitgoGpgPubKey } from '../../opengpgUtils'; import { @@ -128,6 +128,7 @@ export class EddsaUtils extends baseTSSUtils { bitgoKeychain, passphrase, originalPasscodeEncryptionCode, + webauthnInfo, }: CreateEddsaKeychainParams): Promise { const MPC = await Eddsa.initialize(); const bitgoKeyShares = bitgoKeychain.keyShares; @@ -189,6 +190,18 @@ export class EddsaUtils extends baseTSSUtils { password: passphrase, }); } + if (webauthnInfo && userKeychainParams.encryptedPrv) { + userKeychainParams.webauthnDevices = [ + { + otpDeviceId: webauthnInfo.otpDeviceId, + prfSalt: webauthnInfo.prfSalt, + encryptedPrv: this.bitgo.encrypt({ + input: JSON.stringify(userSigningMaterial), + password: webauthnInfo.passphrase, + }), + }, + ]; + } return await this.baseCoin.keychains().add(userKeychainParams); } @@ -344,6 +357,7 @@ export class EddsaUtils extends baseTSSUtils { passphrase?: string; enterprise?: string; originalPasscodeEncryptionCode?: string; + webauthnInfo?: MpcWebauthnInfo; }): Promise { const MPC = await Eddsa.initialize(); const m = 2; @@ -370,6 +384,7 @@ export class EddsaUtils extends baseTSSUtils { bitgoKeychain, passphrase: params.passphrase, originalPasscodeEncryptionCode: params.originalPasscodeEncryptionCode, + webauthnInfo: params.webauthnInfo, }); const backupKeychainPromise = this.createBackupKeychain({ userGpgKey, 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 e5af32d13b..2f84e58346 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts @@ -11,7 +11,7 @@ import { } from '@bitgo/public-types'; import { EddsaMPSDkg, MPSComms, MPSTypes } from '@bitgo/sdk-lib-mpc'; import { KeychainsTriplet } from '../../../baseCoin'; -import { AddKeychainOptions, Keychain, KeyType } from '../../../keychain'; +import { AddKeychainOptions, Keychain, KeyType, MpcWebauthnInfo } from '../../../keychain'; import { envRequiresBitgoPubGpgKeyConfig, isBitgoEddsaMpcv2PubKey } from '../../../tss/bitgoPubKeys'; import { generateGPGKeyPair } from '../../opengpgUtils'; import { MPCv2PartiesEnum } from '../ecdsa/typesMPCv2'; @@ -24,6 +24,7 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { passphrase: string; enterprise: string; originalPasscodeEncryptionCode?: string; + webauthnInfo?: MpcWebauthnInfo; }): Promise { const userKeyPair = await generateGPGKeyPair('ed25519'); const userGpgKey = await pgp.readPrivateKey({ armoredKey: userKeyPair.privateKey }); @@ -139,7 +140,8 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { userPrivateMaterial, userReducedPrivateMaterial, params.passphrase, - params.originalPasscodeEncryptionCode + params.originalPasscodeEncryptionCode, + params.webauthnInfo ); const backupKeychainPromise = this.addBackupKeychain( commonPublicKey, @@ -171,11 +173,13 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { privateMaterial?: Buffer, reducedPrivateMaterial?: Buffer, passphrase?: string, - originalPasscodeEncryptionCode?: string + originalPasscodeEncryptionCode?: string, + webauthnInfo?: MpcWebauthnInfo ): Promise { let source: string; let encryptedPrv: string | undefined = undefined; let reducedEncryptedPrv: string | undefined = undefined; + let privateMaterialBase64: string | undefined = undefined; switch (participantIndex) { case MPCv2PartiesEnum.USER: @@ -184,8 +188,9 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { assert(privateMaterial, `Private material is required for ${source} keychain`); assert(reducedPrivateMaterial, `Reduced private material is required for ${source} keychain`); assert(passphrase, `Passphrase is required for ${source} keychain`); + privateMaterialBase64 = privateMaterial.toString('base64'); encryptedPrv = this.bitgo.encrypt({ - input: privateMaterial.toString('base64'), + input: privateMaterialBase64, password: passphrase, }); // Encrypts the CBOR-encoded ReducedKeyShare (which contains the party's public @@ -215,6 +220,19 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { isMPCv2: true, }; + if (webauthnInfo && participantIndex === MPCv2PartiesEnum.USER && privateMaterialBase64) { + keychainParams.webauthnDevices = [ + { + otpDeviceId: webauthnInfo.otpDeviceId, + prfSalt: webauthnInfo.prfSalt, + encryptedPrv: this.bitgo.encrypt({ + input: privateMaterialBase64, + password: webauthnInfo.passphrase, + }), + }, + ]; + } + const keychains = this.baseCoin.keychains(); return { ...(await keychains.add(keychainParams)), reducedEncryptedPrv }; } @@ -224,7 +242,8 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { privateMaterial: Buffer, reducedPrivateMaterial: Buffer, passphrase: string, - originalPasscodeEncryptionCode?: string + originalPasscodeEncryptionCode?: string, + webauthnInfo?: MpcWebauthnInfo ): Promise { return this.createParticipantKeychain( MPCv2PartiesEnum.USER, @@ -232,7 +251,8 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { privateMaterial, reducedPrivateMaterial, passphrase, - originalPasscodeEncryptionCode + originalPasscodeEncryptionCode, + webauthnInfo ); } diff --git a/modules/sdk-core/src/bitgo/wallet/iWallets.ts b/modules/sdk-core/src/bitgo/wallet/iWallets.ts index 416f6682f8..225942f389 100644 --- a/modules/sdk-core/src/bitgo/wallet/iWallets.ts +++ b/modules/sdk-core/src/bitgo/wallet/iWallets.ts @@ -45,6 +45,7 @@ export interface GenerateBaseMpcWalletOptions { export interface GenerateMpcWalletOptions extends GenerateBaseMpcWalletOptions { passphrase: string; originalPasscodeEncryptionCode?: string; + webauthnInfo?: GenerateWalletWebauthnInfo; } export interface GenerateSMCMpcWalletOptions extends GenerateBaseMpcWalletOptions { bitgoKeyId: string; diff --git a/modules/sdk-core/src/bitgo/wallet/wallets.ts b/modules/sdk-core/src/bitgo/wallet/wallets.ts index 0c191b1670..e853d4bced 100644 --- a/modules/sdk-core/src/bitgo/wallet/wallets.ts +++ b/modules/sdk-core/src/bitgo/wallet/wallets.ts @@ -437,6 +437,7 @@ export class Wallets implements IWallets { originalPasscodeEncryptionCode: params.passcodeEncryptionCode, enterprise, walletVersion: params.walletVersion, + webauthnInfo: params.webauthnInfo, }); if (params.passcodeEncryptionCode) { walletData.encryptedWalletPassphrase = this.bitgo.encrypt({ @@ -1500,6 +1501,7 @@ export class Wallets implements IWallets { enterprise, walletVersion, originalPasscodeEncryptionCode, + webauthnInfo, }: GenerateMpcWalletOptions): Promise { if (multisigType === 'tss' && this.baseCoin.getMPCAlgorithm() === 'ecdsa') { const tssSettings: TssSettings = await this.bitgo @@ -1519,6 +1521,7 @@ export class Wallets implements IWallets { passphrase, enterprise, originalPasscodeEncryptionCode, + webauthnInfo: webauthnInfo ?? undefined, }); // Create Wallet From 4eae3e5852b6adead476bfbb8fb8d864a96241ce Mon Sep 17 00:00:00 2001 From: Mohammad Al Faiyaz Date: Wed, 22 Apr 2026 17:44:15 +0000 Subject: [PATCH 2/3] fix(sdk-core): remove AddKeychainOptions annotation that excluded prv The recipientKeychainParams object in createParticipantKeychain used a prv field not present in AddKeychainOptions. Remove the explicit type annotation so TypeScript infers the broader object literal type, and inline the webauthnDevices conditional to keep the declaration atomic. Ticket: WAL-761 --- .../src/bitgo/utils/tss/ecdsa/ecdsa.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) 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 5b6da13625..8ed0906467 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts @@ -394,7 +394,7 @@ export class EcdsaUtils extends BaseEcdsaUtils { ); const prv = JSON.stringify(recipientCombinedKey.signingMaterial); - const recipientKeychainParams: AddKeychainOptions = { + const recipientKeychainParams = { source: recipient, keyType: 'tss' as KeyType, commonKeychain: bitgoKeychain.commonKeychain, @@ -404,21 +404,21 @@ export class EcdsaUtils extends BaseEcdsaUtils { password: passphrase, }), originalPasscodeEncryptionCode, + webauthnDevices: + webauthnInfo && recipientIndex === 1 + ? [ + { + otpDeviceId: webauthnInfo.otpDeviceId, + prfSalt: webauthnInfo.prfSalt, + encryptedPrv: this.bitgo.encrypt({ + input: prv, + password: webauthnInfo.passphrase, + }), + }, + ] + : undefined, }; - if (webauthnInfo && recipientIndex === 1) { - recipientKeychainParams.webauthnDevices = [ - { - otpDeviceId: webauthnInfo.otpDeviceId, - prfSalt: webauthnInfo.prfSalt, - encryptedPrv: this.bitgo.encrypt({ - input: prv, - password: webauthnInfo.passphrase, - }), - }, - ]; - } - const keychains = this.baseCoin.keychains(); return recipientIndex === 1 ? await keychains.add(recipientKeychainParams) From 0636e4f562d60516c5585c5997ec887e16efacc9 Mon Sep 17 00:00:00 2001 From: Mohammad Al Faiyaz Date: Wed, 22 Apr 2026 19:38:32 +0000 Subject: [PATCH 3/3] refactor(sdk-core): consolidate WebAuthn passphrase type MpcWebauthnInfo and GenerateWalletWebauthnInfo were identical shapes. Promote GenerateWalletWebauthnInfo to iKeychains.ts as the single canonical type, re-export it from iWallets.ts, and drop the duplicate definition. All createKeychains implementations and CreateMpcOptions now reference the shared type. The onchain hot wallet path in generateWallet already wires webauthnInfo through userKeychainPromise; no additional changes are needed there. Ticket: WAL-761 --- modules/sdk-core/src/bitgo/keychain/iKeychains.ts | 9 +++++---- modules/sdk-core/src/bitgo/utils/mpcUtils.ts | 4 ++-- .../sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts | 4 ++-- modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts | 4 ++-- .../sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts | 6 +++--- .../src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts | 8 ++++---- .../sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts | 4 ++-- .../src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts | 8 ++++---- modules/sdk-core/src/bitgo/wallet/iWallets.ts | 14 +++----------- 9 files changed, 27 insertions(+), 34 deletions(-) diff --git a/modules/sdk-core/src/bitgo/keychain/iKeychains.ts b/modules/sdk-core/src/bitgo/keychain/iKeychains.ts index 867b30eedd..aaacd20288 100644 --- a/modules/sdk-core/src/bitgo/keychain/iKeychains.ts +++ b/modules/sdk-core/src/bitgo/keychain/iKeychains.ts @@ -12,9 +12,10 @@ export interface WebauthnInfo { encryptedPrv: string; } -/** WebAuthn PRF-based encryption info passed to MPC key creation. The passphrase is the - * PRF-derived key used to encrypt the user private share before it is persisted. */ -export interface MpcWebauthnInfo { +/** WebAuthn PRF-based encryption info for protecting the user private key with a hardware + * authenticator. The passphrase is the PRF-derived key used to encrypt the user private + * key/share before it is persisted. Never sent to the server. */ +export interface GenerateWalletWebauthnInfo { otpDeviceId: string; prfSalt: string; passphrase: string; @@ -196,7 +197,7 @@ export interface CreateMpcOptions { originalPasscodeEncryptionCode?: string; enterprise?: string; retrofit?: DecryptedRetrofitPayload; - webauthnInfo?: MpcWebauthnInfo; + webauthnInfo?: GenerateWalletWebauthnInfo; } export interface RecreateMpcOptions extends Omit { diff --git a/modules/sdk-core/src/bitgo/utils/mpcUtils.ts b/modules/sdk-core/src/bitgo/utils/mpcUtils.ts index af4a0165f3..f62cc11130 100644 --- a/modules/sdk-core/src/bitgo/utils/mpcUtils.ts +++ b/modules/sdk-core/src/bitgo/utils/mpcUtils.ts @@ -5,7 +5,7 @@ import assert from 'assert'; import { decrypt, readMessage, readPrivateKey, SerializedKeyPair } from 'openpgp'; import { IBaseCoin, KeychainsTriplet } from '../baseCoin'; import { BitGoBase } from '../bitgoBase'; -import { AddKeychainOptions, Keychain, KeyType, MpcWebauthnInfo } from '../keychain'; +import { AddKeychainOptions, Keychain, KeyType, GenerateWalletWebauthnInfo } from '../keychain'; import { encryptText, getBitgoGpgPubKey } from './opengpgUtils'; import { IntentRecipient, @@ -105,7 +105,7 @@ export abstract class MpcUtils { passphrase: string; enterprise?: string; originalPasscodeEncryptionCode?: string; - webauthnInfo?: MpcWebauthnInfo; + webauthnInfo?: GenerateWalletWebauthnInfo; }): Promise; /** diff --git a/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts b/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts index 323a95724f..0f994bc6b4 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts @@ -3,7 +3,7 @@ import * as openpgp from 'openpgp'; import { Key, readKey, SerializedKeyPair } from 'openpgp'; import { IBaseCoin, KeychainsTriplet } from '../../baseCoin'; import { BitGoBase } from '../../bitgoBase'; -import { Keychain, KeyIndices, MpcWebauthnInfo } from '../../keychain'; +import { Keychain, KeyIndices, GenerateWalletWebauthnInfo } from '../../keychain'; import { getTxRequest } from '../../tss'; import { IWallet } from '../../wallet'; import { MpcUtils } from '../mpcUtils'; @@ -194,7 +194,7 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil enterprise?: string | undefined; originalPasscodeEncryptionCode?: string | undefined; isThirdPartyBackup?: boolean; - webauthnInfo?: MpcWebauthnInfo; + webauthnInfo?: GenerateWalletWebauthnInfo; }): Promise { throw new Error('Method not implemented.'); } diff --git a/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts b/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts index f36ae8e1af..dba504c07b 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts @@ -1,7 +1,7 @@ import { Key, SerializedKeyPair } from 'openpgp'; import { IRequestTracer } from '../../../api'; import { KeychainsTriplet, ParsedTransaction, TransactionParams } from '../../baseCoin'; -import { ApiKeyShare, Keychain, MpcWebauthnInfo } from '../../keychain'; +import { ApiKeyShare, Keychain, GenerateWalletWebauthnInfo } from '../../keychain'; import { ApiVersion, Memo, WalletType } from '../../wallet'; import { EDDSA, GShare, Signature, SignShare } from '../../../account-lib/mpc/tss'; import { Signature as EcdsaSignature } from '../../../account-lib/mpc/tss/ecdsa/types'; @@ -482,7 +482,7 @@ export type CreateKeychainParamsBase = { passphrase?: string; enterprise?: string; originalPasscodeEncryptionCode?: string; - webauthnInfo?: MpcWebauthnInfo; + webauthnInfo?: GenerateWalletWebauthnInfo; }; export type CreateBitGoKeychainParamsBase = Omit; 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 8ed0906467..27586c6cf7 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts @@ -7,7 +7,7 @@ import { EcdsaPaillierProof, EcdsaRangeProof, EcdsaTypes, hexToBigInt, minModulu import { bip32 } from '@bitgo/utxo-lib'; import { ECDSA, Ecdsa } from '../../../../account-lib/mpc/tss'; -import { AddKeychainOptions, Keychain, KeyType, MpcWebauthnInfo } from '../../../keychain'; +import { AddKeychainOptions, Keychain, KeyType, GenerateWalletWebauthnInfo } from '../../../keychain'; import ECDSAMethods, { ECDSAMethodTypes } from '../../../tss/ecdsa'; import { KeychainsTriplet } from '../../../baseCoin'; import { @@ -106,7 +106,7 @@ export class EcdsaUtils extends BaseEcdsaUtils { passphrase: string; enterprise?: string | undefined; originalPasscodeEncryptionCode?: string | undefined; - webauthnInfo?: MpcWebauthnInfo; + webauthnInfo?: GenerateWalletWebauthnInfo; }): Promise { const MPC = new Ecdsa(); const m = 2; @@ -309,7 +309,7 @@ export class EcdsaUtils extends BaseEcdsaUtils { bitgoKeychain: Keychain, passphrase: string, originalPasscodeEncryptionCode?: string, - webauthnInfo?: MpcWebauthnInfo + webauthnInfo?: GenerateWalletWebauthnInfo ): Promise { const bitgoKeyShares = bitgoKeychain.keyShares; if (!bitgoKeyShares) { 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 754d87003e..c8cd47dce9 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts @@ -20,7 +20,7 @@ import { } from '@bitgo/public-types'; import { Ecdsa } from '../../../../account-lib'; -import { AddKeychainOptions, Keychain, KeyType, MpcWebauthnInfo } from '../../../keychain'; +import { AddKeychainOptions, Keychain, KeyType, GenerateWalletWebauthnInfo } from '../../../keychain'; import { DecryptedRetrofitPayload } from '../../../keychain/iKeychains'; import { ECDSAMethodTypes, getTxRequest } from '../../../tss'; import { sendSignatureShareV2, sendTxRequest } from '../../../tss/common'; @@ -57,7 +57,7 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils { enterprise: string; originalPasscodeEncryptionCode?: string; retrofit?: DecryptedRetrofitPayload; - webauthnInfo?: MpcWebauthnInfo; + webauthnInfo?: GenerateWalletWebauthnInfo; }): Promise { const { userSession, backupSession } = this.getUserAndBackupSession(2, 3, params.retrofit); const userGpgKey = await generateGPGKeyPair('secp256k1'); @@ -353,7 +353,7 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils { reducedPrivateMaterial?: Buffer, passphrase?: string, originalPasscodeEncryptionCode?: string, - webauthnInfo?: MpcWebauthnInfo + webauthnInfo?: GenerateWalletWebauthnInfo ): Promise { let source: string; let encryptedPrv: string | undefined = undefined; @@ -531,7 +531,7 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils { reducedPrivateMaterial: Buffer, passphrase: string, originalPasscodeEncryptionCode?: string, - webauthnInfo?: MpcWebauthnInfo + webauthnInfo?: GenerateWalletWebauthnInfo ): Promise { return this.createParticipantKeychain( MPCv2PartiesEnum.USER, 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 5c9d29198c..861035e772 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts @@ -4,7 +4,7 @@ import assert from 'assert'; import * as openpgp from 'openpgp'; import Eddsa, { GShare, SignShare } from '../../../../account-lib/mpc/tss'; -import { AddKeychainOptions, CreateBackupOptions, Keychain, MpcWebauthnInfo } from '../../../keychain'; +import { AddKeychainOptions, CreateBackupOptions, Keychain, GenerateWalletWebauthnInfo } from '../../../keychain'; import { verifyWalletSignature } from '../../../tss/eddsa/eddsa'; import { createShareProof, encryptText, generateGPGKeyPair, getBitgoGpgPubKey } from '../../opengpgUtils'; import { @@ -357,7 +357,7 @@ export class EddsaUtils extends baseTSSUtils { passphrase?: string; enterprise?: string; originalPasscodeEncryptionCode?: string; - webauthnInfo?: MpcWebauthnInfo; + webauthnInfo?: GenerateWalletWebauthnInfo; }): Promise { const MPC = await Eddsa.initialize(); const m = 2; 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 2f84e58346..de1d2064c8 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts @@ -11,7 +11,7 @@ import { } from '@bitgo/public-types'; import { EddsaMPSDkg, MPSComms, MPSTypes } from '@bitgo/sdk-lib-mpc'; import { KeychainsTriplet } from '../../../baseCoin'; -import { AddKeychainOptions, Keychain, KeyType, MpcWebauthnInfo } from '../../../keychain'; +import { AddKeychainOptions, Keychain, KeyType, GenerateWalletWebauthnInfo } from '../../../keychain'; import { envRequiresBitgoPubGpgKeyConfig, isBitgoEddsaMpcv2PubKey } from '../../../tss/bitgoPubKeys'; import { generateGPGKeyPair } from '../../opengpgUtils'; import { MPCv2PartiesEnum } from '../ecdsa/typesMPCv2'; @@ -24,7 +24,7 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { passphrase: string; enterprise: string; originalPasscodeEncryptionCode?: string; - webauthnInfo?: MpcWebauthnInfo; + webauthnInfo?: GenerateWalletWebauthnInfo; }): Promise { const userKeyPair = await generateGPGKeyPair('ed25519'); const userGpgKey = await pgp.readPrivateKey({ armoredKey: userKeyPair.privateKey }); @@ -174,7 +174,7 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { reducedPrivateMaterial?: Buffer, passphrase?: string, originalPasscodeEncryptionCode?: string, - webauthnInfo?: MpcWebauthnInfo + webauthnInfo?: GenerateWalletWebauthnInfo ): Promise { let source: string; let encryptedPrv: string | undefined = undefined; @@ -243,7 +243,7 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { reducedPrivateMaterial: Buffer, passphrase: string, originalPasscodeEncryptionCode?: string, - webauthnInfo?: MpcWebauthnInfo + webauthnInfo?: GenerateWalletWebauthnInfo ): Promise { return this.createParticipantKeychain( MPCv2PartiesEnum.USER, diff --git a/modules/sdk-core/src/bitgo/wallet/iWallets.ts b/modules/sdk-core/src/bitgo/wallet/iWallets.ts index 225942f389..c2a739e8d4 100644 --- a/modules/sdk-core/src/bitgo/wallet/iWallets.ts +++ b/modules/sdk-core/src/bitgo/wallet/iWallets.ts @@ -2,7 +2,7 @@ import * as t from 'io-ts'; import { IRequestTracer } from '../../api'; import { KeychainsTriplet, LightningKeychainsTriplet } from '../baseCoin'; -import { Keychain, WebauthnInfo } from '../keychain'; +import { GenerateWalletWebauthnInfo, Keychain, WebauthnInfo } from '../keychain'; import { IWallet, PaginationOptions, WalletShare } from './iWallet'; import { Wallet } from './wallet'; @@ -42,6 +42,8 @@ export interface GenerateBaseMpcWalletOptions { walletVersion?: number; } +export { GenerateWalletWebauthnInfo } from '../keychain'; + export interface GenerateMpcWalletOptions extends GenerateBaseMpcWalletOptions { passphrase: string; originalPasscodeEncryptionCode?: string; @@ -53,16 +55,6 @@ export interface GenerateSMCMpcWalletOptions extends GenerateBaseMpcWalletOption coldDerivationSeed?: string; } -/** WebAuthn PRF-based encryption info for protecting the user private key with a hardware authenticator. */ -export interface GenerateWalletWebauthnInfo { - /** The OTP device ID of the WebAuthn authenticator. */ - otpDeviceId: string; - /** The PRF salt used to derive the passphrase from the authenticator. */ - prfSalt: string; - /** PRF-derived passphrase used to encrypt the user private key. Never sent to the server. */ - passphrase: string; -} - export interface GenerateWalletOptions { label?: string; passphrase?: string;