Skip to content

Commit 5576861

Browse files
committed
feat: thread encryptionVersion in SDK
Ticket: WCN-774
1 parent ddfc0e8 commit 5576861

27 files changed

Lines changed: 704 additions & 50 deletions

File tree

modules/abstract-lightning/src/codecs/api/wallet.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export const UpdateLightningWalletClientRequest = t.intersection([
8888
signerMacaroon: t.string,
8989
signerAdminMacaroon: t.string,
9090
signerTlsKey: t.string,
91+
encryptionVersion: t.union([t.literal(1), t.literal(2)]),
9192
}),
9293
]);
9394

modules/abstract-lightning/src/wallet/selfCustodialLightning.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,23 @@ async function encryptWalletUpdateRequest(
2424
requestWithEncryption.encryptedSignerTlsKey = await wallet.bitgo.encryptAsync({
2525
password: params.passphrase,
2626
input: params.signerTlsKey,
27+
encryptionVersion: params.encryptionVersion,
2728
});
2829
}
2930

3031
if (params.signerAdminMacaroon) {
3132
requestWithEncryption.encryptedSignerAdminMacaroon = await wallet.bitgo.encryptAsync({
3233
password: params.passphrase,
3334
input: params.signerAdminMacaroon,
35+
encryptionVersion: params.encryptionVersion,
3436
});
3537
}
3638

3739
if (params.signerMacaroon) {
3840
requestWithEncryption.encryptedSignerMacaroon = await wallet.bitgo.encryptAsync({
3941
password: deriveLightningServiceSharedSecret(coinName, userAuthXprv).toString('hex'),
4042
input: params.signerMacaroon,
43+
encryptionVersion: params.encryptionVersion,
4144
});
4245
}
4346

modules/express/src/fetchEncryptedPrivKeys.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type Credentials = {
2020
walletId: string; // Id of the BitGo wallet.
2121
walletPassword: string; // Password used for the wallet.
2222
secret: string; // xprv of user key or backup key.
23+
encryptionVersion?: 1 | 2;
2324
};
2425

2526
type WalletIds = {
@@ -77,7 +78,11 @@ export async function fetchKeys(ids: WalletIds, token: string, accessToken?: str
7778

7879
if (keychain.encryptedPrv === undefined) {
7980
if (typeof credential === 'object') {
80-
const encryptedPrv = await bg.encryptAsync({ password: credential.walletPassword, input: credential.secret });
81+
const encryptedPrv = await bg.encryptAsync({
82+
password: credential.walletPassword,
83+
input: credential.secret,
84+
encryptionVersion: credential.encryptionVersion,
85+
});
8186
output[id] = encryptedPrv;
8287
} else {
8388
console.warn(`could not find a ${coinName} encrypted user private key for wallet id ${id}, skipping`);

modules/key-card/src/generateQrData.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,12 @@ function generatePasscodeQrData(passphrase: string, passcodeEncryptionCode: stri
138138
};
139139
}
140140

141-
async function generatePasscodeQrDataAsync(passphrase: string, passcodeEncryptionCode: string): Promise<QrDataEntry> {
142-
const encryptedWalletPasscode = await encryptAsync(passcodeEncryptionCode, passphrase);
141+
async function generatePasscodeQrDataAsync(
142+
passphrase: string,
143+
passcodeEncryptionCode: string,
144+
encryptionVersion?: 1 | 2
145+
): Promise<QrDataEntry> {
146+
const encryptedWalletPasscode = await encryptAsync(passcodeEncryptionCode, passphrase, { encryptionVersion });
143147
return {
144148
title: 'D: Encrypted wallet Password',
145149
description: 'This is the wallet password, encrypted client-side with a key held by BitGo.',
@@ -211,7 +215,11 @@ export async function generateQrDataAsync(params: GenerateQrDataParams): Promise
211215
const qrData = buildWalletQrData(params);
212216

213217
if (params.passphrase && params.passcodeEncryptionCode) {
214-
qrData.passcode = await generatePasscodeQrDataAsync(params.passphrase, params.passcodeEncryptionCode);
218+
qrData.passcode = await generatePasscodeQrDataAsync(
219+
params.passphrase,
220+
params.passcodeEncryptionCode,
221+
params.encryptionVersion
222+
);
215223
}
216224

217225
return qrData;
@@ -234,7 +242,11 @@ export async function generateLightningQrDataAsync(params: GenerateLightningQrDa
234242
const qrData = buildLightningQrData(params);
235243

236244
if (params.passphrase && params.passcodeEncryptionCode) {
237-
qrData.passcode = await generatePasscodeQrDataAsync(params.passphrase, params.passcodeEncryptionCode);
245+
qrData.passcode = await generatePasscodeQrDataAsync(
246+
params.passphrase,
247+
params.passcodeEncryptionCode,
248+
params.encryptionVersion
249+
);
238250
}
239251

240252
return qrData;

modules/key-card/src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Keychain } from '@bitgo/sdk-core';
1+
import { EncryptionVersion, Keychain } from '@bitgo/sdk-core';
22
import { BaseCoin, KeyCurve } from '@bitgo/statics';
33

44
export interface GenerateQrDataBaseParams {
@@ -25,6 +25,7 @@ export interface GenerateQrDataCoinParams {
2525
// If both the passphrase and passcodeEncryptionCode are passed, then this code encrypts the passphrase with the
2626
// passcodeEncryptionCode and puts the result into Box D. Allows recoveries of the wallet password.
2727
passphrase?: string;
28+
encryptionVersion?: EncryptionVersion;
2829
}
2930

3031
export interface GenerateQrDataParams extends GenerateQrDataCoinParams {

modules/key-card/test/unit/generateQrData.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,72 @@ describe('generateQrDataAsync', function () {
247247
const decryptedData = await decryptAsync(passcodeEncryptionCode, qrData.passcode.data);
248248
decryptedData.should.equal(passphrase);
249249
});
250+
251+
it('produces a v1 Box D when encryptionVersion is not set', async function () {
252+
const passphrase = 'testingIsFun';
253+
const passcodeEncryptionCode = '123456';
254+
const qrData = await generateQrDataAsync({
255+
backupKeychain: createKeychain({ encryptedPrv: 'backupPrv' }),
256+
bitgoKeychain: createKeychain({ pub: 'bitgoPub' }),
257+
coin: coins.get('btc'),
258+
passcodeEncryptionCode,
259+
passphrase,
260+
userKeychain: createKeychain({ encryptedPrv: 'userPrv' }),
261+
});
262+
263+
assert.ok(qrData.passcode);
264+
const envelope = JSON.parse(qrData.passcode.data);
265+
assert.notStrictEqual(envelope.v, 2, 'should default to v1 envelope');
266+
});
267+
268+
it('produces a v2 Box D when encryptionVersion: 2', async function () {
269+
const passphrase = 'testingIsFun';
270+
const passcodeEncryptionCode = '123456';
271+
const qrData = await generateQrDataAsync({
272+
backupKeychain: createKeychain({ encryptedPrv: 'backupPrv' }),
273+
bitgoKeychain: createKeychain({ pub: 'bitgoPub' }),
274+
coin: coins.get('btc'),
275+
passcodeEncryptionCode,
276+
passphrase,
277+
userKeychain: createKeychain({ encryptedPrv: 'userPrv' }),
278+
encryptionVersion: 2,
279+
});
280+
281+
assert.ok(qrData.passcode);
282+
const envelope = JSON.parse(qrData.passcode.data);
283+
assert.strictEqual(envelope.v, 2, 'should produce v2 envelope');
284+
const decryptedData = await decryptAsync(passcodeEncryptionCode, qrData.passcode.data);
285+
decryptedData.should.equal(passphrase);
286+
});
287+
288+
it('produces a v1 Box D when encryptionVersion: 1 is explicit', async function () {
289+
const passphrase = 'testingIsFun';
290+
const passcodeEncryptionCode = '123456';
291+
const qrData = await generateQrDataAsync({
292+
backupKeychain: createKeychain({ encryptedPrv: 'backupPrv' }),
293+
bitgoKeychain: createKeychain({ pub: 'bitgoPub' }),
294+
coin: coins.get('btc'),
295+
passcodeEncryptionCode,
296+
passphrase,
297+
userKeychain: createKeychain({ encryptedPrv: 'userPrv' }),
298+
encryptionVersion: 1,
299+
});
300+
301+
assert.ok(qrData.passcode);
302+
const envelope = JSON.parse(qrData.passcode.data);
303+
assert.notStrictEqual(envelope.v, 2, 'should produce v1 envelope');
304+
});
305+
306+
it('omits Box D when passphrase or passcodeEncryptionCode is missing', async function () {
307+
const qrData = await generateQrDataAsync({
308+
backupKeychain: createKeychain({ encryptedPrv: 'backupPrv' }),
309+
bitgoKeychain: createKeychain({ pub: 'bitgoPub' }),
310+
coin: coins.get('btc'),
311+
userKeychain: createKeychain({ encryptedPrv: 'userPrv' }),
312+
encryptionVersion: 2,
313+
});
314+
assert.strictEqual(qrData.passcode, undefined);
315+
});
250316
});
251317

252318
describe('generateLightningQrDataAsync', function () {
@@ -264,4 +330,22 @@ describe('generateLightningQrDataAsync', function () {
264330
const decryptedData = await decryptAsync(passcodeEncryptionCode, qrData.passcode.data);
265331
decryptedData.should.equal(passphrase);
266332
});
333+
334+
it('produces a v2 Box D when encryptionVersion: 2', async function () {
335+
const passphrase = 'testingIsFun';
336+
const passcodeEncryptionCode = '123456';
337+
const qrData = await generateLightningQrDataAsync({
338+
userAuthKeychain: createKeychain({ encryptedPrv: 'userAuthPrv' }),
339+
coin: coins.get('lnbtc'),
340+
passcodeEncryptionCode,
341+
passphrase,
342+
encryptionVersion: 2,
343+
});
344+
345+
assert.ok(qrData.passcode);
346+
const envelope = JSON.parse(qrData.passcode.data);
347+
assert.strictEqual(envelope.v, 2);
348+
const decryptedData = await decryptAsync(passcodeEncryptionCode, qrData.passcode.data);
349+
decryptedData.should.equal(passphrase);
350+
});
267351
});

modules/passkey-crypto/src/attachPasskeyToWallet.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BitGoBase, Keychain } from '@bitgo/sdk-core';
1+
import { BitGoBase, EncryptionVersion, Keychain } from '@bitgo/sdk-core';
22
import { base64UrlToBuffer } from './base64url';
33
import { deriveEnterpriseSalt } from './deriveEnterpriseSalt';
44
import { derivePassword } from './derivePassword';
@@ -11,8 +11,9 @@ export async function attachPasskeyToWallet(params: {
1111
device: WebAuthnOtpDevice;
1212
existingPassphrase: string;
1313
provider: WebAuthnProvider;
14+
encryptionVersion?: EncryptionVersion;
1415
}): Promise<Keychain> {
15-
const { bitgo, coin, walletId, device, existingPassphrase, provider } = params;
16+
const { bitgo, coin, walletId, device, existingPassphrase, provider, encryptionVersion } = params;
1617

1718
// Throw early if PRF extension is not supported
1819
if (!device.prfSalt) {
@@ -66,7 +67,7 @@ export async function attachPasskeyToWallet(params: {
6667
}
6768

6869
const prfPassword = derivePassword(authResult.prfResult);
69-
const encryptedPrv = await bitgo.encryptAsync({ password: prfPassword, input: privateKey, encryptionVersion: 2 });
70+
const encryptedPrv = await bitgo.encryptAsync({ password: prfPassword, input: privateKey, encryptionVersion });
7071

7172
const updatedKeychain = await bitgo
7273
.put(bitgo.url(`/${coin}/key/${keychainId}`, 2))

modules/sdk-api/src/bitgoAPI.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
DecryptOptions,
1111
defaultConstants,
1212
EcdhDerivedKeypair,
13+
EncryptionVersion,
1314
EncryptOptions,
1415
EnvironmentName,
1516
generateRandomPassword,
@@ -1125,7 +1126,7 @@ export class BitGoAPI implements BitGoBase {
11251126
* @returns {Promise<any>} - A promise that resolves with the new ECDH keychain data.
11261127
* @throws {Error} - Throws an error if there is an issue creating the keychain.
11271128
*/
1128-
public async createUserEcdhKeychain(loginPassword: string): Promise<any> {
1129+
public async createUserEcdhKeychain(loginPassword: string, encryptionVersion?: EncryptionVersion): Promise<any> {
11291130
const keyData = this.keychains().create();
11301131
const hdNode = bitcoin.HDNode.fromBase58(keyData.xprv);
11311132

@@ -1139,6 +1140,7 @@ export class BitGoAPI implements BitGoBase {
11391140
encryptedXprv: await this.encryptAsync({
11401141
password: loginPassword,
11411142
input: hdNode.toBase58(),
1143+
encryptionVersion,
11421144
}),
11431145
});
11441146
}
@@ -1932,11 +1934,11 @@ export class BitGoAPI implements BitGoBase {
19321934
* @param passwords
19331935
* @param m
19341936
*/
1935-
async splitSecretAsync({ seed, passwords, m }: SplitSecretOptions): Promise<SplitSecret> {
1937+
async splitSecretAsync({ seed, passwords, m, encryptionVersion }: SplitSecretOptions): Promise<SplitSecret> {
19361938
const n = validateSplitSecretInputs({ seed, passwords, m });
19371939
const secrets: string[] = shamir.share(seed, n, m);
19381940
const shards = await Promise.all(
1939-
secrets.map((shard, i) => this.encryptAsync({ input: shard, password: passwords[i] }))
1941+
secrets.map((shard, i) => this.encryptAsync({ input: shard, password: passwords[i], encryptionVersion }))
19401942
);
19411943
return buildSplitSecretResult(seed, shards, m, n);
19421944
}

modules/sdk-api/src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { EnvironmentName, IRequestTracer, V1Network } from '@bitgo/sdk-core';
1+
import { EncryptionVersion, EnvironmentName, IRequestTracer, V1Network } from '@bitgo/sdk-core';
22
import { ECPairInterface } from '@bitgo/utxo-lib';
33
import { type Agent } from 'http';
44

@@ -226,6 +226,7 @@ export interface SplitSecretOptions {
226226
seed: string;
227227
passwords: string[];
228228
m: number;
229+
encryptionVersion?: EncryptionVersion;
229230
}
230231

231232
export interface SplitSecret {

modules/sdk-api/src/v1/keychains.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,11 @@ Keychains.prototype.updatePassword = function (params, callback) {
194194
input: oldEncryptedXprv as string,
195195
password: params.oldPassword,
196196
});
197-
const newEncryptedPrv = await self.bitgo.encryptAsync({ input: decryptedPrv, password: params.newPassword });
197+
const newEncryptedPrv = await self.bitgo.encryptAsync({
198+
input: decryptedPrv,
199+
password: params.newPassword,
200+
encryptionVersion: params.encryptionVersion,
201+
});
198202
newKeychains[xpub] = newEncryptedPrv;
199203
} catch (e) {
200204
// decrypting the keychain with the old password didn't work so we just keep it the way it is

0 commit comments

Comments
 (0)