Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions modules/bitgo/test/v2/unit/keychains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down
10 changes: 10 additions & 0 deletions modules/sdk-core/src/bitgo/keychain/iKeychains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ export interface WebauthnInfo {
encryptedPrv: string;
}

/** 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;
}

export type SourceType = 'bitgo' | 'backup' | 'user' | 'cold';

export type WebauthnFmt = 'none' | 'packed' | 'fido-u2f';
Expand Down Expand Up @@ -188,6 +197,7 @@ export interface CreateMpcOptions {
originalPasscodeEncryptionCode?: string;
enterprise?: string;
retrofit?: DecryptedRetrofitPayload;
webauthnInfo?: GenerateWalletWebauthnInfo;
}

export interface RecreateMpcOptions extends Omit<CreateMpcOptions, 'retrofit' | 'multisigType'> {
Expand Down
1 change: 1 addition & 0 deletions modules/sdk-core/src/bitgo/keychain/keychains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ export class Keychains implements IKeychains {
enterprise: params.enterprise,
originalPasscodeEncryptionCode: params.originalPasscodeEncryptionCode,
retrofit: params.retrofit,
webauthnInfo: params.webauthnInfo,
});
}

Expand Down
3 changes: 2 additions & 1 deletion modules/sdk-core/src/bitgo/utils/mpcUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, GenerateWalletWebauthnInfo } from '../keychain';
import { encryptText, getBitgoGpgPubKey } from './opengpgUtils';
import {
IntentRecipient,
Expand Down Expand Up @@ -105,6 +105,7 @@ export abstract class MpcUtils {
passphrase: string;
enterprise?: string;
originalPasscodeEncryptionCode?: string;
webauthnInfo?: GenerateWalletWebauthnInfo;
}): Promise<KeychainsTriplet>;

/**
Expand Down
3 changes: 2 additions & 1 deletion modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, GenerateWalletWebauthnInfo } from '../../keychain';
import { getTxRequest } from '../../tss';
import { IWallet } from '../../wallet';
import { MpcUtils } from '../mpcUtils';
Expand Down Expand Up @@ -194,6 +194,7 @@ export default class BaseTssUtils<KeyShare> extends MpcUtils implements ITssUtil
enterprise?: string | undefined;
originalPasscodeEncryptionCode?: string | undefined;
isThirdPartyBackup?: boolean;
webauthnInfo?: GenerateWalletWebauthnInfo;
}): Promise<KeychainsTriplet> {
throw new Error('Method not implemented.');
}
Expand Down
3 changes: 2 additions & 1 deletion modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts
Original file line number Diff line number Diff line change
@@ -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, 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';
Expand Down Expand Up @@ -482,6 +482,7 @@ export type CreateKeychainParamsBase = {
passphrase?: string;
enterprise?: string;
originalPasscodeEncryptionCode?: string;
webauthnInfo?: GenerateWalletWebauthnInfo;
};

export type CreateBitGoKeychainParamsBase = Omit<CreateKeychainParamsBase, 'bitgoKeychain'>;
Expand Down
24 changes: 21 additions & 3 deletions modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, GenerateWalletWebauthnInfo } from '../../../keychain';
import ECDSAMethods, { ECDSAMethodTypes } from '../../../tss/ecdsa';
import { KeychainsTriplet } from '../../../baseCoin';
import {
Expand Down Expand Up @@ -106,6 +106,7 @@ export class EcdsaUtils extends BaseEcdsaUtils {
passphrase: string;
enterprise?: string | undefined;
originalPasscodeEncryptionCode?: string | undefined;
webauthnInfo?: GenerateWalletWebauthnInfo;
}): Promise<KeychainsTriplet> {
const MPC = new Ecdsa();
const m = 2;
Expand Down Expand Up @@ -138,6 +139,7 @@ export class EcdsaUtils extends BaseEcdsaUtils {
bitgoKeychain,
passphrase: params.passphrase,
originalPasscodeEncryptionCode: params.originalPasscodeEncryptionCode,
webauthnInfo: params.webauthnInfo,
});
const backupKeychainPromise = this.createBackupKeychain({
userGpgKey,
Expand Down Expand Up @@ -177,6 +179,7 @@ export class EcdsaUtils extends BaseEcdsaUtils {
bitgoKeychain,
passphrase,
originalPasscodeEncryptionCode,
webauthnInfo,
}: CreateEcdsaKeychainParams): Promise<Keychain> {
if (!passphrase) {
throw new Error('Please provide a wallet passphrase');
Expand All @@ -191,7 +194,8 @@ export class EcdsaUtils extends BaseEcdsaUtils {
backupKeyShare.userHeldKeyShare,
bitgoKeychain,
passphrase,
originalPasscodeEncryptionCode
originalPasscodeEncryptionCode,
webauthnInfo
);
}

Expand Down Expand Up @@ -304,7 +308,8 @@ export class EcdsaUtils extends BaseEcdsaUtils {
backupKeyShare: KeyShare,
bitgoKeychain: Keychain,
passphrase: string,
originalPasscodeEncryptionCode?: string
originalPasscodeEncryptionCode?: string,
webauthnInfo?: GenerateWalletWebauthnInfo
): Promise<Keychain> {
const bitgoKeyShares = bitgoKeychain.keyShares;
if (!bitgoKeyShares) {
Expand Down Expand Up @@ -399,6 +404,19 @@ 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,
};

const keychains = this.baseCoin.keychains();
Expand Down
32 changes: 26 additions & 6 deletions modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
} from '@bitgo/public-types';

import { Ecdsa } from '../../../../account-lib';
import { AddKeychainOptions, Keychain, KeyType } 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';
Expand Down Expand Up @@ -57,6 +57,7 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils {
enterprise: string;
originalPasscodeEncryptionCode?: string;
retrofit?: DecryptedRetrofitPayload;
webauthnInfo?: GenerateWalletWebauthnInfo;
}): Promise<KeychainsTriplet> {
const { userSession, backupSession } = this.getUserAndBackupSession(2, 3, params.retrofit);
const userGpgKey = await generateGPGKeyPair('secp256k1');
Expand Down Expand Up @@ -318,7 +319,8 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils {
userPrivateMaterial,
userReducedPrivateMaterial,
params.passphrase,
params.originalPasscodeEncryptionCode
params.originalPasscodeEncryptionCode,
params.webauthnInfo
);
const backupKeychainPromise = this.addBackupKeychain(
bitgoCommonKeychain,
Expand Down Expand Up @@ -350,20 +352,23 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils {
privateMaterial?: Buffer,
reducedPrivateMaterial?: Buffer,
passphrase?: string,
originalPasscodeEncryptionCode?: string
originalPasscodeEncryptionCode?: string,
webauthnInfo?: GenerateWalletWebauthnInfo
): Promise<Keychain> {
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:
source = participantIndex === MPCv2PartiesEnum.USER ? 'user' : 'backup';
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
Expand Down Expand Up @@ -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 };
}
Expand Down Expand Up @@ -512,15 +530,17 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils {
privateMaterial: Buffer,
reducedPrivateMaterial: Buffer,
passphrase: string,
originalPasscodeEncryptionCode?: string
originalPasscodeEncryptionCode?: string,
webauthnInfo?: GenerateWalletWebauthnInfo
): Promise<Keychain> {
return this.createParticipantKeychain(
MPCv2PartiesEnum.USER,
commonKeychain,
privateMaterial,
reducedPrivateMaterial,
passphrase,
originalPasscodeEncryptionCode
originalPasscodeEncryptionCode,
webauthnInfo
);
}

Expand Down
17 changes: 16 additions & 1 deletion modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, GenerateWalletWebauthnInfo } from '../../../keychain';
import { verifyWalletSignature } from '../../../tss/eddsa/eddsa';
import { createShareProof, encryptText, generateGPGKeyPair, getBitgoGpgPubKey } from '../../opengpgUtils';
import {
Expand Down Expand Up @@ -128,6 +128,7 @@ export class EddsaUtils extends baseTSSUtils<KeyShare> {
bitgoKeychain,
passphrase,
originalPasscodeEncryptionCode,
webauthnInfo,
}: CreateEddsaKeychainParams): Promise<Keychain> {
const MPC = await Eddsa.initialize();
const bitgoKeyShares = bitgoKeychain.keyShares;
Expand Down Expand Up @@ -189,6 +190,18 @@ export class EddsaUtils extends baseTSSUtils<KeyShare> {
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);
}
Expand Down Expand Up @@ -344,6 +357,7 @@ export class EddsaUtils extends baseTSSUtils<KeyShare> {
passphrase?: string;
enterprise?: string;
originalPasscodeEncryptionCode?: string;
webauthnInfo?: GenerateWalletWebauthnInfo;
}): Promise<KeychainsTriplet> {
const MPC = await Eddsa.initialize();
const m = 2;
Expand All @@ -370,6 +384,7 @@ export class EddsaUtils extends baseTSSUtils<KeyShare> {
bitgoKeychain,
passphrase: params.passphrase,
originalPasscodeEncryptionCode: params.originalPasscodeEncryptionCode,
webauthnInfo: params.webauthnInfo,
});
const backupKeychainPromise = this.createBackupKeychain({
userGpgKey,
Expand Down
Loading
Loading