From bce6011bec5567fec84645bfd3fe26f77397f916 Mon Sep 17 00:00:00 2001 From: Alejandro Busse Date: Wed, 21 Aug 2024 16:52:26 -0300 Subject: [PATCH] feat(sdk-core): add encryptedWalletPassphrase to generateWallet method Added a new field in the generateWallet response that its the wallet passphrase encrypted with the passcode, this data can be used to do recover the original password, same as the box D on a keycard. Its only available for hot wallets and if passphrase and the passcode is provided WP-2525 TICKET: WP-2525 --- .../v2/unit/lightning/lightningWallets.ts | 10 +- modules/bitgo/test/v2/unit/wallets.ts | 108 +++++++++++++++++- modules/sdk-core/src/bitgo/wallet/iWallets.ts | 2 + modules/sdk-core/src/bitgo/wallet/wallets.ts | 25 +++- 4 files changed, 135 insertions(+), 10 deletions(-) diff --git a/modules/bitgo/test/v2/unit/lightning/lightningWallets.ts b/modules/bitgo/test/v2/unit/lightning/lightningWallets.ts index 5a7938a79d..2331bfafd7 100644 --- a/modules/bitgo/test/v2/unit/lightning/lightningWallets.ts +++ b/modules/bitgo/test/v2/unit/lightning/lightningWallets.ts @@ -1,3 +1,4 @@ +import * as assert from 'assert'; import { TestBitGo } from '@bitgo/sdk-test'; import * as nock from 'nock'; @@ -164,7 +165,14 @@ describe('Lightning wallets', function () { .post('/api/v2/tlnbtc/wallet', (body) => validateWalletRequest(body)) .reply(200, { id: 'walletId' }); - await wallets.generateWallet(params); + const response = await wallets.generateWallet(params); + + assert.ok(response.wallet); + assert.ok(response.encryptedWalletPassphrase); + assert.equal( + bitgo.decrypt({ input: response.encryptedWalletPassphrase, password: params.passcodeEncryptionCode }), + params.passphrase + ); }); }); }); diff --git a/modules/bitgo/test/v2/unit/wallets.ts b/modules/bitgo/test/v2/unit/wallets.ts index 963ccfcb1c..851530f892 100644 --- a/modules/bitgo/test/v2/unit/wallets.ts +++ b/modules/bitgo/test/v2/unit/wallets.ts @@ -429,9 +429,47 @@ describe('V2 Wallets:', function () { .post('/api/v2/tbtc/key', _.matches({ source: 'backup' })) .reply(200, { pub: 'backupPub' }); - await wallets.generateWallet(params); + const response = await wallets.generateWallet(params); walletNock.isDone().should.be.true(); + + assert.ok(response.encryptedWalletPassphrase === undefined); + assert.ok(response.wallet); + }); + + it('should generate hot onchain wallet', async () => { + const params: GenerateWalletOptions = { + label: 'test wallet', + passphrase: 'multisig password', + enterprise: 'enterprise', + passcodeEncryptionCode: 'originalPasscodeEncryptionCode', + }; + + const walletNock = nock(bgUrl) + .post('/api/v2/tbtc/wallet', function (body) { + body.type.should.equal('hot'); + return true; + }) + .reply(200); + + nock(bgUrl) + .post('/api/v2/tbtc/key', _.matches({ source: 'bitgo' })) + .reply(200, { pub: 'bitgoPub' }); + nock(bgUrl).post('/api/v2/tbtc/key', _.matches({})).reply(200); + nock(bgUrl) + .post('/api/v2/tbtc/key', _.matches({ source: 'backup' })) + .reply(200, { pub: 'backupPub' }); + + const response = await wallets.generateWallet(params); + + walletNock.isDone().should.be.true(); + + assert.ok(response.encryptedWalletPassphrase); + assert.ok(response.wallet); + assert.equal( + bitgo.decrypt({ input: response.encryptedWalletPassphrase, password: params.passcodeEncryptionCode }), + params.passphrase + ); }); }); @@ -490,15 +528,64 @@ describe('V2 Wallets:', function () { const wallets = new Wallets(bitgo, tsol); - await wallets.generateWallet({ + const params = { label: 'tss wallet', passphrase: 'tss password', - multisigType: 'tss', + multisigType: 'tss' as any, enterprise: 'enterprise', passcodeEncryptionCode: 'originalPasscodeEncryptionCode', + }; + + const response = await wallets.generateWallet(params); + + walletNock.isDone().should.be.true(); + + assert.ok(response.encryptedWalletPassphrase); + assert.ok(response.wallet); + assert.equal( + bitgo.decrypt({ input: response.encryptedWalletPassphrase, password: params.passcodeEncryptionCode }), + params.passphrase + ); + }); + + it('should create a new TSS wallet without providing passcodeEncryptionCode', async function () { + const stubbedKeychainsTriplet: KeychainsTriplet = { + userKeychain: { + id: '1', + pub: 'userPub', + type: 'independent', + source: 'user', + }, + backupKeychain: { + id: '2', + pub: 'userPub', + type: 'independent', + source: 'backup', + }, + bitgoKeychain: { + id: '3', + pub: 'userPub', + type: 'independent', + source: 'bitgo', + }, + }; + sandbox.stub(TssUtils.prototype, 'createKeychains').resolves(stubbedKeychainsTriplet); + + const walletNock = nock('https://bitgo.fakeurl').post('/api/v2/tsol/wallet').reply(200); + + const wallets = new Wallets(bitgo, tsol); + + const response = await wallets.generateWallet({ + label: 'tss wallet', + passphrase: 'tss password', + multisigType: 'tss', + enterprise: 'enterprise', }); walletNock.isDone().should.be.true(); + + assert.ok(response.wallet); + assert.ok(response.encryptedWalletPassphrase === undefined); }); it('should create a new ECDSA TSS wallet with BitGoTrustAsKrs as backup provider', async function () { @@ -831,17 +918,26 @@ describe('V2 Wallets:', function () { const wallets = new Wallets(bitgo, testCoin); - await wallets.generateWallet({ + const params = { label: 'tss wallet', passphrase: 'tss password', - multisigType: 'tss', + multisigType: 'tss' as const, enterprise: 'enterprise', passcodeEncryptionCode: 'originalPasscodeEncryptionCode', walletVersion: 3, - }); + }; + + const response = await wallets.generateWallet(params); walletNock.isDone().should.be.true(); stubCreateKeychains.calledOnce.should.be.true(); + + assert.ok(response.encryptedWalletPassphrase); + assert.ok(response.wallet); + assert.equal( + bitgo.decrypt({ input: response.encryptedWalletPassphrase, password: params.passcodeEncryptionCode }), + params.passphrase + ); }); }); diff --git a/modules/sdk-core/src/bitgo/wallet/iWallets.ts b/modules/sdk-core/src/bitgo/wallet/iWallets.ts index ca1ccca027..e5137ad583 100644 --- a/modules/sdk-core/src/bitgo/wallet/iWallets.ts +++ b/modules/sdk-core/src/bitgo/wallet/iWallets.ts @@ -9,12 +9,14 @@ export interface WalletWithKeychains extends KeychainsTriplet { responseType: 'WalletWithKeychains'; wallet: IWallet; warning?: string; + encryptedWalletPassphrase?: string; } export interface LightningWalletWithKeychains extends LightningKeychainsTriplet { responseType: 'LightningWalletWithKeychains'; wallet: IWallet; warning?: string; + encryptedWalletPassphrase?: string; } export interface GetWalletOptions { diff --git a/modules/sdk-core/src/bitgo/wallet/wallets.ts b/modules/sdk-core/src/bitgo/wallet/wallets.ts index 08bcb72226..4d64ca4164 100644 --- a/modules/sdk-core/src/bitgo/wallet/wallets.ts +++ b/modules/sdk-core/src/bitgo/wallet/wallets.ts @@ -247,7 +247,13 @@ export class Wallets implements IWallets { throw new Error(`error(s) parsing generate lightning wallet request params: ${errors}`); } ); - return this.generateLightningWallet(options); + + const walletData = await this.generateLightningWallet(options); + walletData.encryptedWalletPassphrase = this.bitgo.encrypt({ + input: options.passphrase, + password: options.passcodeEncryptionCode, + }); + return walletData; } common.validateParams(params, ['label'], ['passphrase', 'userKey', 'backupXpub']); @@ -325,8 +331,7 @@ export class Wallets implements IWallets { } assert(passphrase, 'cannot generate TSS keys without passphrase'); - - return this.generateMpcWallet({ + const walletData = await this.generateMpcWallet({ multisigType: 'tss', label, passphrase, @@ -335,6 +340,13 @@ export class Wallets implements IWallets { walletVersion: params.walletVersion, backupProvider: params.backupProvider, }); + if (params.passcodeEncryptionCode) { + walletData.encryptedWalletPassphrase = this.bitgo.encrypt({ + input: passphrase, + password: params.passcodeEncryptionCode, + }); + } + return walletData; } const isBlsDkg = params.multisigType ? params.multisigType === 'blsdkg' : this.baseCoin.supportsBlsDkg(); @@ -559,6 +571,13 @@ export class Wallets implements IWallets { userKeychain.derivationPath = derivationPath; } + if (canEncrypt && params.passcodeEncryptionCode) { + result.encryptedWalletPassphrase = this.bitgo.encrypt({ + input: passphrase, + password: params.passcodeEncryptionCode, + }); + } + return result; }