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
113 changes: 86 additions & 27 deletions modules/bitgo/test/unit/bitgo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,41 +237,85 @@ describe('BitGo Prototype Methods', function () {
'xpub661MyMwAqRbcEusRjkJ64BXgR8ddYsXbuDJfbRc3eZcZVEa2ygswDiFZQpHFsA5N211YDvi2N898h4KrcXcfsR8PLhjJaPUwCUqg1ptBBHN';
const passwords = ['mickey', 'mouse', 'donald', 'duck'];

it('should fail to split secret with wrong m', () => {
(() =>
bitgo.splitSecret({
it('should fail to split secret with wrong m', async () => {
await bitgo
.splitSecretAsync({
seed,
passwords: ['abc'],
m: 0,
})).should.throw('m must be a positive integer greater than or equal to 2');
})
.should.be.rejectedWith('m must be a positive integer greater than or equal to 2');
});

it('should fail to split secret with bad password count', () => {
(() =>
bitgo.splitSecret({
it('should fail to split secret with bad password count', async () => {
await bitgo
.splitSecretAsync({
seed,
passwords: ['abc'],
m: 2,
})).should.throw('passwords array length cannot be less than m');
})
.should.be.rejectedWith('passwords array length cannot be less than m');
});

it('should split and fail to reconstitute secret with bad passwords', () => {
const splitSecret = bitgo.splitSecret({ seed, passwords: passwords, m: 3 });
it('should split and fail to reconstitute secret with bad passwords', async () => {
const splitSecret = await bitgo.splitSecretAsync({ seed, passwords: passwords, m: 3 });
const shards = _.at(splitSecret.seedShares, [0, 2]);
const subsetPasswords = _.at(passwords, [0, 3]);
(() =>
bitgo.reconstituteSecret({
await bitgo
.reconstituteSecretAsync({
shards,
passwords: subsetPasswords,
xpub,
} as any)).should.throw(/ccm: tag doesn't match/);
} as any)
.should.be.rejectedWith('incorrect password');
});

it('should split and reconstitute secret', async () => {
const splitSecret = await bitgo.splitSecret({ seed, passwords: passwords, m: 2 });
const shards = _.at(splitSecret.seedShares, [0, 2]);
const subsetPasswords = _.at(passwords, [0, 2]);
const reconstitutedSeed = await bitgo.reconstituteSecret({ shards, passwords: subsetPasswords });
reconstitutedSeed.seed.should.equal(seed);
reconstitutedSeed.xpub.should.equal(
'xpub661MyMwAqRbcEusRjkJ64BXgR8ddYsXbuDJfbRc3eZcZVEa2ygswDiFZQpHFsA5N211YDvi2N898h4KrcXcfsR8PLhjJaPUwCUqg1ptBBHN'
);
reconstitutedSeed.xprv.should.equal(
'xprv9s21ZrQH143K2Rnxdim5h3aws6o99QokXzP4o3CS6E5acSEtS9Zgfuw5ZWujhTHTWEAZDfmP3yxA1Ccn6myVkGEpRrT4xWgaEpoW7YiBAtC'
);
});

it('should split and incorrectly verify secret', async () => {
const splitSecret = await bitgo.splitSecret({ seed, passwords: passwords, m: 3 });
const isValid = await bitgo.verifyShards({ shards: splitSecret.seedShares, passwords, m: 2 } as any);
isValid.should.equal(false);
});

it('should split and verify secret', async () => {
const splitSecret = await bitgo.splitSecret({ seed, passwords: passwords, m: 2 });
const isValid = await bitgo.verifyShards({ shards: splitSecret.seedShares, passwords, m: 2, xpub });
isValid.should.equal(true);
});

it('should split and verify secret with many parts', async () => {
const allPws = ['0', '1', '2', '3', '4', '5', '6', '7'];
const splitSecret = await bitgo.splitSecret({ seed, passwords: allPws, m: 3 });
const isValid = await bitgo.verifyShards({ shards: splitSecret.seedShares, passwords: allPws, m: 3, xpub });
isValid.should.equal(true);
});
});

it('should split and reconstitute secret', () => {
const splitSecret = bitgo.splitSecret({ seed, passwords: passwords, m: 2 });
describe('Shamir Secret Sharing Async', () => {
const bitgo = TestBitGo.decorate(BitGo);
const seed = '8cc57dac9cdae42bf7848a2d12f2874d31eca1f9de8fe3f8fa13e7857b545d59';
const xpub =
'xpub661MyMwAqRbcEusRjkJ64BXgR8ddYsXbuDJfbRc3eZcZVEa2ygswDiFZQpHFsA5N211YDvi2N898h4KrcXcfsR8PLhjJaPUwCUqg1ptBBHN';
const passwords = ['mickey', 'mouse', 'donald', 'duck'];

it('should split and reconstitute secret using async methods', async () => {
const splitSecret = await bitgo.splitSecretAsync({ seed, passwords: passwords, m: 2 });
const shards = _.at(splitSecret.seedShares, [0, 2]);
const subsetPasswords = _.at(passwords, [0, 2]);
const reconstitutedSeed = bitgo.reconstituteSecret({ shards, passwords: subsetPasswords });
const reconstitutedSeed = await bitgo.reconstituteSecretAsync({ shards, passwords: subsetPasswords });
reconstitutedSeed.seed.should.equal(seed);
reconstitutedSeed.xpub.should.equal(
'xpub661MyMwAqRbcEusRjkJ64BXgR8ddYsXbuDJfbRc3eZcZVEa2ygswDiFZQpHFsA5N211YDvi2N898h4KrcXcfsR8PLhjJaPUwCUqg1ptBBHN'
Expand All @@ -281,22 +325,22 @@ describe('BitGo Prototype Methods', function () {
);
});

it('should split and incorrectly verify secret', () => {
const splitSecret = bitgo.splitSecret({ seed, passwords: passwords, m: 3 });
const isValid = bitgo.verifyShards({ shards: splitSecret.seedShares, passwords, m: 2 } as any);
it('should split and incorrectly verify secret using async methods', async () => {
const splitSecret = await bitgo.splitSecretAsync({ seed, passwords: passwords, m: 3 });
const isValid = await bitgo.verifyShardsAsync({ shards: splitSecret.seedShares, passwords, m: 2 } as any);
isValid.should.equal(false);
});

it('should split and verify secret', () => {
const splitSecret = bitgo.splitSecret({ seed, passwords: passwords, m: 2 });
const isValid = bitgo.verifyShards({ shards: splitSecret.seedShares, passwords, m: 2, xpub });
it('should split and verify secret using async methods', async () => {
const splitSecret = await bitgo.splitSecretAsync({ seed, passwords: passwords, m: 2 });
const isValid = await bitgo.verifyShardsAsync({ shards: splitSecret.seedShares, passwords, m: 2, xpub });
isValid.should.equal(true);
});

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

it('should correctly handle authentication response', () => {
it('should correctly handle authentication response', async () => {
const responseJson = {
encryptedToken:
'{"iv":"EqxVaGTLY4naAYkuBaTz0w==","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4S4dBYcgL4s=","ct":"FgBRJljb8iSYxnAjMi4Qotr7sTKbSmWnlfHZShMSi8YeeE3kiS8bpHNUwAPhY8tgouh3UsEwrJnY+54MvqFD7yd19pG1V4CVssr8"}',
derivationPath: 'm/999999/104490948/173846667',
encryptedECDHXprv:
'{"iv":"QKHEF2GNcwOJwy6+pwANRA==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"W2sVFvXDlOw=","ct":"8BTCqS25X37kLzmzQdGenhXH6znn9qEmkszAeS8kLnRdqKSiUiC7bTAVgg/Np5yrV7F7Jyiq+MTpVT76EoUT+PMJzArv0gUQKC2JPB3JuVKeAAVWBQmhWfkEwRfyv4hq4WMxwZtocwBqThvd2pJm9HE51GX4/Wo="}',
};
const parsedAuthenticationData = await bitgo.handleTokenIssuance(responseJson, 'test@bitgo.com');
parsedAuthenticationData.token.should.equal(token);
parsedAuthenticationData.ecdhXprv.should.equal(
'xprv9s21ZrQH143K3si1bKGp7KqgCQv39ttQ7aUwWzVdytgHd8HtDCHyEp14mxfhiT3qHTq4BaSrA7uUkG6AJTfPJBsRu63drvBqYuMZyTxepH7'
);
});

it('should correctly handle authentication response using handleTokenIssuanceAsync', async () => {
const responseJson = {
encryptedToken:
'{"iv":"EqxVaGTLY4naAYkuBaTz0w==","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"4S4dBYcgL4s=","ct":"FgBRJljb8iSYxnAjMi4Qotr7sTKbSmWnlfHZShMSi8YeeE3kiS8bpHNUwAPhY8tgouh3UsEwrJnY+54MvqFD7yd19pG1V4CVssr8"}',
derivationPath: 'm/999999/104490948/173846667',
encryptedECDHXprv:
'{"iv":"QKHEF2GNcwOJwy6+pwANRA==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"W2sVFvXDlOw=","ct":"8BTCqS25X37kLzmzQdGenhXH6znn9qEmkszAeS8kLnRdqKSiUiC7bTAVgg/Np5yrV7F7Jyiq+MTpVT76EoUT+PMJzArv0gUQKC2JPB3JuVKeAAVWBQmhWfkEwRfyv4hq4WMxwZtocwBqThvd2pJm9HE51GX4/Wo="}',
};
const parsedAuthenticationData = bitgo.handleTokenIssuance(responseJson, 'test@bitgo.com');
const parsedAuthenticationData = await bitgo.handleTokenIssuanceAsync(responseJson, 'test@bitgo.com');
parsedAuthenticationData.token.should.equal(token);
parsedAuthenticationData.ecdhXprv.should.equal(
'xprv9s21ZrQH143K3si1bKGp7KqgCQv39ttQ7aUwWzVdytgHd8HtDCHyEp14mxfhiT3qHTq4BaSrA7uUkG6AJTfPJBsRu63drvBqYuMZyTxepH7'
Expand Down
78 changes: 76 additions & 2 deletions modules/bitgo/test/v2/unit/wallets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4272,8 +4272,8 @@ describe('V2 Wallets:', function () {
});

const encryptPrvForUserStub = sinon
.stub(wallet, 'encryptPrvForUser')
.callsFake((prv, pubKey, userPubKey, path) => {
.stub(wallet, 'encryptPrvForUserAsync')
.callsFake(async (prv, pubKey, userPubKey, path) => {
return {
pub: pubKey,
encryptedPrv: 'dummyEncryptedPrv',
Expand Down Expand Up @@ -4328,6 +4328,80 @@ describe('V2 Wallets:', function () {
});
});

describe('downloadKeycardAsync', () => {
const localBitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
const walletData = {
id: '5b34252f1bf349930e34020a00000002',
coin: 'tbtc',
keys: [
'5b3424f91bf349930e34017500000000',
'5b3424f91bf349930e34017600000000',
'5b3424f91bf349930e34017700000000',
],
coinSpecific: {},
multisigType: 'onchain',
type: 'hot',
};
const tbtc = localBitgo.coin('tbtc');
const wallet = new Wallet(localBitgo, tbtc, walletData);

it('should throw when called in Node.js (no browser window)', async () => {
// In Node.js, accessing `window` throws ReferenceError; the method rejects.
await wallet.downloadKeycardAsync().should.be.rejected();
});

it('downloadKeycard (sync) should throw when called in Node.js (no browser window)', () => {
should.throws(() => wallet.downloadKeycard());
});
});

describe('encryptPrvForUserAsync', () => {
const localBitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
const walletData = {
id: '5b34252f1bf349930e34020a00000001',
coin: 'tbtc',
keys: [
'5b3424f91bf349930e34017500000000',
'5b3424f91bf349930e34017600000000',
'5b3424f91bf349930e34017700000000',
],
coinSpecific: {},
multisigType: 'onchain',
type: 'hot',
};
const tbtc = localBitgo.coin('tbtc');
const wallet = new Wallet(localBitgo, tbtc, walletData);

before(function () {
nock('https://bitgo.fakeurl').persist().get('/api/v1/client/constants').reply(200, { ttl: 3600, constants: {} });
localBitgo.initializeTestVars();
});

afterEach(function () {
sinon.restore();
});

it('should encrypt prv for user and return the correct output shape', async () => {
const decryptedPrv =
'xprv9s21ZrQH143K2fJ91S4BRsupcYrE6mmY96fcX5HkhoTrrwmwjd16Cn87cWinJjByrfpojjx7ezsJLx7TAKLT8m8hM5Kax9YcoxnBeJZ3t2k';
const pub =
'xpub661MyMwAqRbcF9Nc7TbBo1rZAagiWEVPWKbDKThNG8zqjk76HAKLkaSbTn6dK2dQPfuD7xjicxCZVWvj67fP5nQ9W7QURmoMVAX8m6jZsGp';
// A valid 33-byte compressed EC point on secp256k1
const userPubkey = '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798';
const path = 'm/999999/0/1';

sinon.stub(localBitgo, 'encryptAsync').resolves('encryptedPrvForUser');

const result = await wallet.encryptPrvForUserAsync(decryptedPrv, pub, userPubkey, path);

result.should.have.property('pub', pub);
result.should.have.property('encryptedPrv', 'encryptedPrvForUser');
result.should.have.property('fromPubKey').which.is.a.String();
result.should.have.property('toPubKey', userPubkey);
result.should.have.property('path', path);
});
});

describe('List Wallets:', function () {
it('should list wallets with skipReceiveAddress = true', async function () {
const bitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
Expand Down
Loading
Loading