Skip to content

Commit c2a8ed7

Browse files
committed
feat: add async counterparts for public encrypt/decrypt methods
Ticket: WCN-284
1 parent 676e584 commit c2a8ed7

12 files changed

Lines changed: 1493 additions & 118 deletions

File tree

modules/bitgo/test/unit/bitgo.ts

Lines changed: 86 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -237,41 +237,85 @@ describe('BitGo Prototype Methods', function () {
237237
'xpub661MyMwAqRbcEusRjkJ64BXgR8ddYsXbuDJfbRc3eZcZVEa2ygswDiFZQpHFsA5N211YDvi2N898h4KrcXcfsR8PLhjJaPUwCUqg1ptBBHN';
238238
const passwords = ['mickey', 'mouse', 'donald', 'duck'];
239239

240-
it('should fail to split secret with wrong m', () => {
241-
(() =>
242-
bitgo.splitSecret({
240+
it('should fail to split secret with wrong m', async () => {
241+
await bitgo
242+
.splitSecretAsync({
243243
seed,
244244
passwords: ['abc'],
245245
m: 0,
246-
})).should.throw('m must be a positive integer greater than or equal to 2');
246+
})
247+
.should.be.rejectedWith('m must be a positive integer greater than or equal to 2');
247248
});
248249

249-
it('should fail to split secret with bad password count', () => {
250-
(() =>
251-
bitgo.splitSecret({
250+
it('should fail to split secret with bad password count', async () => {
251+
await bitgo
252+
.splitSecretAsync({
252253
seed,
253254
passwords: ['abc'],
254255
m: 2,
255-
})).should.throw('passwords array length cannot be less than m');
256+
})
257+
.should.be.rejectedWith('passwords array length cannot be less than m');
256258
});
257259

258-
it('should split and fail to reconstitute secret with bad passwords', () => {
259-
const splitSecret = bitgo.splitSecret({ seed, passwords: passwords, m: 3 });
260+
it('should split and fail to reconstitute secret with bad passwords', async () => {
261+
const splitSecret = await bitgo.splitSecretAsync({ seed, passwords: passwords, m: 3 });
260262
const shards = _.at(splitSecret.seedShares, [0, 2]);
261263
const subsetPasswords = _.at(passwords, [0, 3]);
262-
(() =>
263-
bitgo.reconstituteSecret({
264+
await bitgo
265+
.reconstituteSecretAsync({
264266
shards,
265267
passwords: subsetPasswords,
266268
xpub,
267-
} as any)).should.throw(/ccm: tag doesn't match/);
269+
} as any)
270+
.should.be.rejectedWith('incorrect password');
271+
});
272+
273+
it('should split and reconstitute secret', async () => {
274+
const splitSecret = await bitgo.splitSecret({ seed, passwords: passwords, m: 2 });
275+
const shards = _.at(splitSecret.seedShares, [0, 2]);
276+
const subsetPasswords = _.at(passwords, [0, 2]);
277+
const reconstitutedSeed = await bitgo.reconstituteSecret({ shards, passwords: subsetPasswords });
278+
reconstitutedSeed.seed.should.equal(seed);
279+
reconstitutedSeed.xpub.should.equal(
280+
'xpub661MyMwAqRbcEusRjkJ64BXgR8ddYsXbuDJfbRc3eZcZVEa2ygswDiFZQpHFsA5N211YDvi2N898h4KrcXcfsR8PLhjJaPUwCUqg1ptBBHN'
281+
);
282+
reconstitutedSeed.xprv.should.equal(
283+
'xprv9s21ZrQH143K2Rnxdim5h3aws6o99QokXzP4o3CS6E5acSEtS9Zgfuw5ZWujhTHTWEAZDfmP3yxA1Ccn6myVkGEpRrT4xWgaEpoW7YiBAtC'
284+
);
285+
});
286+
287+
it('should split and incorrectly verify secret', async () => {
288+
const splitSecret = await bitgo.splitSecret({ seed, passwords: passwords, m: 3 });
289+
const isValid = await bitgo.verifyShards({ shards: splitSecret.seedShares, passwords, m: 2 } as any);
290+
isValid.should.equal(false);
291+
});
292+
293+
it('should split and verify secret', async () => {
294+
const splitSecret = await bitgo.splitSecret({ seed, passwords: passwords, m: 2 });
295+
const isValid = await bitgo.verifyShards({ shards: splitSecret.seedShares, passwords, m: 2, xpub });
296+
isValid.should.equal(true);
297+
});
298+
299+
it('should split and verify secret with many parts', async () => {
300+
const allPws = ['0', '1', '2', '3', '4', '5', '6', '7'];
301+
const splitSecret = await bitgo.splitSecret({ seed, passwords: allPws, m: 3 });
302+
const isValid = await bitgo.verifyShards({ shards: splitSecret.seedShares, passwords: allPws, m: 3, xpub });
303+
isValid.should.equal(true);
268304
});
305+
});
269306

270-
it('should split and reconstitute secret', () => {
271-
const splitSecret = bitgo.splitSecret({ seed, passwords: passwords, m: 2 });
307+
describe('Shamir Secret Sharing Async', () => {
308+
const bitgo = TestBitGo.decorate(BitGo);
309+
const seed = '8cc57dac9cdae42bf7848a2d12f2874d31eca1f9de8fe3f8fa13e7857b545d59';
310+
const xpub =
311+
'xpub661MyMwAqRbcEusRjkJ64BXgR8ddYsXbuDJfbRc3eZcZVEa2ygswDiFZQpHFsA5N211YDvi2N898h4KrcXcfsR8PLhjJaPUwCUqg1ptBBHN';
312+
const passwords = ['mickey', 'mouse', 'donald', 'duck'];
313+
314+
it('should split and reconstitute secret using async methods', async () => {
315+
const splitSecret = await bitgo.splitSecretAsync({ seed, passwords: passwords, m: 2 });
272316
const shards = _.at(splitSecret.seedShares, [0, 2]);
273317
const subsetPasswords = _.at(passwords, [0, 2]);
274-
const reconstitutedSeed = bitgo.reconstituteSecret({ shards, passwords: subsetPasswords });
318+
const reconstitutedSeed = await bitgo.reconstituteSecretAsync({ shards, passwords: subsetPasswords });
275319
reconstitutedSeed.seed.should.equal(seed);
276320
reconstitutedSeed.xpub.should.equal(
277321
'xpub661MyMwAqRbcEusRjkJ64BXgR8ddYsXbuDJfbRc3eZcZVEa2ygswDiFZQpHFsA5N211YDvi2N898h4KrcXcfsR8PLhjJaPUwCUqg1ptBBHN'
@@ -281,22 +325,22 @@ describe('BitGo Prototype Methods', function () {
281325
);
282326
});
283327

284-
it('should split and incorrectly verify secret', () => {
285-
const splitSecret = bitgo.splitSecret({ seed, passwords: passwords, m: 3 });
286-
const isValid = bitgo.verifyShards({ shards: splitSecret.seedShares, passwords, m: 2 } as any);
328+
it('should split and incorrectly verify secret using async methods', async () => {
329+
const splitSecret = await bitgo.splitSecretAsync({ seed, passwords: passwords, m: 3 });
330+
const isValid = await bitgo.verifyShardsAsync({ shards: splitSecret.seedShares, passwords, m: 2 } as any);
287331
isValid.should.equal(false);
288332
});
289333

290-
it('should split and verify secret', () => {
291-
const splitSecret = bitgo.splitSecret({ seed, passwords: passwords, m: 2 });
292-
const isValid = bitgo.verifyShards({ shards: splitSecret.seedShares, passwords, m: 2, xpub });
334+
it('should split and verify secret using async methods', async () => {
335+
const splitSecret = await bitgo.splitSecretAsync({ seed, passwords: passwords, m: 2 });
336+
const isValid = await bitgo.verifyShardsAsync({ shards: splitSecret.seedShares, passwords, m: 2, xpub });
293337
isValid.should.equal(true);
294338
});
295339

296-
it('should split and verify secret with many parts', () => {
340+
it('should split and verify secret with many parts using async methods', async () => {
297341
const allPws = ['0', '1', '2', '3', '4', '5', '6', '7'];
298-
const splitSecret = bitgo.splitSecret({ seed, passwords: allPws, m: 3 });
299-
const isValid = bitgo.verifyShards({ shards: splitSecret.seedShares, passwords: allPws, m: 3, xpub });
342+
const splitSecret = await bitgo.splitSecretAsync({ seed, passwords: allPws, m: 3 });
343+
const isValid = await bitgo.verifyShardsAsync({ shards: splitSecret.seedShares, passwords: allPws, m: 3, xpub });
300344
isValid.should.equal(true);
301345
});
302346
});
@@ -436,15 +480,30 @@ describe('BitGo Prototype Methods', function () {
436480
requestHeaders.hmac.should.equal('6de77d5a5446a3e5649456c11480706a71071b15639c3c787af65bdb02ecf1ec');
437481
});
438482

439-
it('should correctly handle authentication response', () => {
483+
it('should correctly handle authentication response', async () => {
484+
const responseJson = {
485+
encryptedToken:
486+
'{"iv":"EqxVaGTLY4naAYkuBaTz0w==","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4S4dBYcgL4s=","ct":"FgBRJljb8iSYxnAjMi4Qotr7sTKbSmWnlfHZShMSi8YeeE3kiS8bpHNUwAPhY8tgouh3UsEwrJnY+54MvqFD7yd19pG1V4CVssr8"}',
487+
derivationPath: 'm/999999/104490948/173846667',
488+
encryptedECDHXprv:
489+
'{"iv":"QKHEF2GNcwOJwy6+pwANRA==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"W2sVFvXDlOw=","ct":"8BTCqS25X37kLzmzQdGenhXH6znn9qEmkszAeS8kLnRdqKSiUiC7bTAVgg/Np5yrV7F7Jyiq+MTpVT76EoUT+PMJzArv0gUQKC2JPB3JuVKeAAVWBQmhWfkEwRfyv4hq4WMxwZtocwBqThvd2pJm9HE51GX4/Wo="}',
490+
};
491+
const parsedAuthenticationData = await bitgo.handleTokenIssuance(responseJson, 'test@bitgo.com');
492+
parsedAuthenticationData.token.should.equal(token);
493+
parsedAuthenticationData.ecdhXprv.should.equal(
494+
'xprv9s21ZrQH143K3si1bKGp7KqgCQv39ttQ7aUwWzVdytgHd8HtDCHyEp14mxfhiT3qHTq4BaSrA7uUkG6AJTfPJBsRu63drvBqYuMZyTxepH7'
495+
);
496+
});
497+
498+
it('should correctly handle authentication response using handleTokenIssuanceAsync', async () => {
440499
const responseJson = {
441500
encryptedToken:
442501
'{"iv":"EqxVaGTLY4naAYkuBaTz0w==","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4S4dBYcgL4s=","ct":"FgBRJljb8iSYxnAjMi4Qotr7sTKbSmWnlfHZShMSi8YeeE3kiS8bpHNUwAPhY8tgouh3UsEwrJnY+54MvqFD7yd19pG1V4CVssr8"}',
443502
derivationPath: 'm/999999/104490948/173846667',
444503
encryptedECDHXprv:
445504
'{"iv":"QKHEF2GNcwOJwy6+pwANRA==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"W2sVFvXDlOw=","ct":"8BTCqS25X37kLzmzQdGenhXH6znn9qEmkszAeS8kLnRdqKSiUiC7bTAVgg/Np5yrV7F7Jyiq+MTpVT76EoUT+PMJzArv0gUQKC2JPB3JuVKeAAVWBQmhWfkEwRfyv4hq4WMxwZtocwBqThvd2pJm9HE51GX4/Wo="}',
446505
};
447-
const parsedAuthenticationData = bitgo.handleTokenIssuance(responseJson, 'test@bitgo.com');
506+
const parsedAuthenticationData = await bitgo.handleTokenIssuanceAsync(responseJson, 'test@bitgo.com');
448507
parsedAuthenticationData.token.should.equal(token);
449508
parsedAuthenticationData.ecdhXprv.should.equal(
450509
'xprv9s21ZrQH143K3si1bKGp7KqgCQv39ttQ7aUwWzVdytgHd8HtDCHyEp14mxfhiT3qHTq4BaSrA7uUkG6AJTfPJBsRu63drvBqYuMZyTxepH7'

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

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4272,8 +4272,8 @@ describe('V2 Wallets:', function () {
42724272
});
42734273

42744274
const encryptPrvForUserStub = sinon
4275-
.stub(wallet, 'encryptPrvForUser')
4276-
.callsFake((prv, pubKey, userPubKey, path) => {
4275+
.stub(wallet, 'encryptPrvForUserAsync')
4276+
.callsFake(async (prv, pubKey, userPubKey, path) => {
42774277
return {
42784278
pub: pubKey,
42794279
encryptedPrv: 'dummyEncryptedPrv',
@@ -4328,6 +4328,80 @@ describe('V2 Wallets:', function () {
43284328
});
43294329
});
43304330

4331+
describe('downloadKeycardAsync', () => {
4332+
const localBitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
4333+
const walletData = {
4334+
id: '5b34252f1bf349930e34020a00000002',
4335+
coin: 'tbtc',
4336+
keys: [
4337+
'5b3424f91bf349930e34017500000000',
4338+
'5b3424f91bf349930e34017600000000',
4339+
'5b3424f91bf349930e34017700000000',
4340+
],
4341+
coinSpecific: {},
4342+
multisigType: 'onchain',
4343+
type: 'hot',
4344+
};
4345+
const tbtc = localBitgo.coin('tbtc');
4346+
const wallet = new Wallet(localBitgo, tbtc, walletData);
4347+
4348+
it('should throw when called in Node.js (no browser window)', async () => {
4349+
// In Node.js, accessing `window` throws ReferenceError; the method rejects.
4350+
await wallet.downloadKeycardAsync().should.be.rejected();
4351+
});
4352+
4353+
it('downloadKeycard (sync) should throw when called in Node.js (no browser window)', () => {
4354+
should.throws(() => wallet.downloadKeycard());
4355+
});
4356+
});
4357+
4358+
describe('encryptPrvForUserAsync', () => {
4359+
const localBitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
4360+
const walletData = {
4361+
id: '5b34252f1bf349930e34020a00000001',
4362+
coin: 'tbtc',
4363+
keys: [
4364+
'5b3424f91bf349930e34017500000000',
4365+
'5b3424f91bf349930e34017600000000',
4366+
'5b3424f91bf349930e34017700000000',
4367+
],
4368+
coinSpecific: {},
4369+
multisigType: 'onchain',
4370+
type: 'hot',
4371+
};
4372+
const tbtc = localBitgo.coin('tbtc');
4373+
const wallet = new Wallet(localBitgo, tbtc, walletData);
4374+
4375+
before(function () {
4376+
nock('https://bitgo.fakeurl').persist().get('/api/v1/client/constants').reply(200, { ttl: 3600, constants: {} });
4377+
localBitgo.initializeTestVars();
4378+
});
4379+
4380+
afterEach(function () {
4381+
sinon.restore();
4382+
});
4383+
4384+
it('should encrypt prv for user and return the correct output shape', async () => {
4385+
const decryptedPrv =
4386+
'xprv9s21ZrQH143K2fJ91S4BRsupcYrE6mmY96fcX5HkhoTrrwmwjd16Cn87cWinJjByrfpojjx7ezsJLx7TAKLT8m8hM5Kax9YcoxnBeJZ3t2k';
4387+
const pub =
4388+
'xpub661MyMwAqRbcF9Nc7TbBo1rZAagiWEVPWKbDKThNG8zqjk76HAKLkaSbTn6dK2dQPfuD7xjicxCZVWvj67fP5nQ9W7QURmoMVAX8m6jZsGp';
4389+
// A valid 33-byte compressed EC point on secp256k1
4390+
const userPubkey = '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798';
4391+
const path = 'm/999999/0/1';
4392+
4393+
sinon.stub(localBitgo, 'encryptAsync').resolves('encryptedPrvForUser');
4394+
4395+
const result = await wallet.encryptPrvForUserAsync(decryptedPrv, pub, userPubkey, path);
4396+
4397+
result.should.have.property('pub', pub);
4398+
result.should.have.property('encryptedPrv', 'encryptedPrvForUser');
4399+
result.should.have.property('fromPubKey').which.is.a.String();
4400+
result.should.have.property('toPubKey', userPubkey);
4401+
result.should.have.property('path', path);
4402+
});
4403+
});
4404+
43314405
describe('List Wallets:', function () {
43324406
it('should list wallets with skipReceiveAddress = true', async function () {
43334407
const bitgo = TestBitGo.decorate(BitGo, { env: 'mock' });

0 commit comments

Comments
 (0)