UNPKG

bitgo

Version:
1,109 lines • 496 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); // // Tests for Wallets // const assert = require("assert"); const nock = require("nock"); const sinon = require("sinon"); const should = require("should"); const _ = require("lodash"); const utxoLib = require("@bitgo/utxo-lib"); const sdk_test_1 = require("@bitgo/sdk-test"); const sdk_core_1 = require("@bitgo/sdk-core"); const src_1 = require("../../../src"); const mocha_1 = require("mocha"); const moduleBitgo = require("@bitgo/sdk-core"); describe('V2 Wallets:', function () { const bitgo = sdk_test_1.TestBitGo.decorate(src_1.BitGo, { env: 'mock' }); let wallets; let ofcWallets; let bgUrl; before(function () { nock('https://bitgo.fakeurl').persist().get('/api/v1/client/constants').reply(200, { ttl: 3600, constants: {} }); bitgo.initializeTestVars(); const basecoin = bitgo.coin('tbtc'); wallets = basecoin.wallets(); ofcWallets = bitgo.coin('ofc').wallets(); bgUrl = sdk_core_1.common.Environments[bitgo.getEnv()].uri; }); after(function () { nock.cleanAll(); nock.pendingMocks().length.should.equal(0); }); describe('Add Wallet:', function () { it('throws on invalid arguments', async function () { // isCustodial flag is not a boolean await wallets .add({ label: 'label', enterprise: 'enterprise', keys: [], m: 2, n: 3, isCustodial: 1 }) .should.be.rejectedWith('invalid argument for isCustodial - boolean expected'); // type is not a string await wallets .add({ label: 'label', enterprise: 'enterprise', keys: [], m: 2, n: 3, type: 1 }) .should.be.rejectedWith('Expecting parameter string: type but found number'); // Address is an invalid address await wallets .add({ label: 'label', enterprise: 'enterprise', keys: [], m: 2, n: 3, address: '$' }) .should.be.rejectedWith('invalid argument for address - valid address string expected'); // gasPrice is a number await wallets .add({ label: 'label', enterprise: 'enterprise', keys: [], m: 2, n: 3, gasPrice: '17' }) .should.be.rejectedWith('invalid argument for gasPrice - number expected'); // walletVersion is a number await wallets .add({ label: 'label', enterprise: 'enterprise', keys: [], m: 2, n: 3, walletVersion: '1' }) .should.be.rejectedWith('invalid argument for walletVersion - number expected'); }); it('creates a paired custodial wallet', async function () { nock(bgUrl) .post('/api/v2/tbtc/wallet/add', function (body) { body.isCustodial.should.be.true(); body.should.have.property('keys'); body.m.should.equal(2); body.n.should.equal(3); return true; }) .reply(200, {}); await wallets.add({ label: 'label', enterprise: 'enterprise', keys: [], m: 2, n: 3, isCustodial: true }); }); it('creates an eos wallet with custom address', async function () { const eosBitGo = sdk_test_1.TestBitGo.decorate(src_1.BitGo, { env: 'mock' }); eosBitGo.initializeTestVars(); const eosWallets = eosBitGo.coin('teos').wallets(); const address = 'testeosaddre'; nock(bgUrl) .post('/api/v2/teos/wallet/add', function (body) { body.should.have.property('keys'); body.m.should.equal(2); body.n.should.equal(3); body.address.should.equal(address); return true; }) .reply(200, {}); await eosWallets.add({ label: 'label', enterprise: 'enterprise', keys: [], m: 2, n: 3, address }); }); it('creates a single custodial wallet', async function () { nock(bgUrl) .post('/api/v2/tbtc/wallet/add', function (body) { body.type.should.equal('custodial'); body.should.not.have.property('keys'); body.should.not.have.property('m'); body.should.not.have.property('n'); return true; }) .reply(200, {}); await wallets.add({ label: 'label', enterprise: 'enterprise', type: 'custodial' }); }); it('creates a wallet with custom gasPrice', async function () { const ethBitGo = sdk_test_1.TestBitGo.decorate(src_1.BitGo, { env: 'mock' }); ethBitGo.initializeTestVars(); const ethWallets = ethBitGo.coin('teth').wallets(); nock(bgUrl) .post('/api/v2/teth/wallet/add', function (body) { body.type.should.equal('custodial'); body.gasPrice.should.equal(20000000000); body.should.not.have.property('keys'); body.should.not.have.property('m'); body.should.not.have.property('n'); return true; }) .reply(200, {}); await ethWallets.add({ label: 'label', enterprise: 'enterprise', type: 'custodial', gasPrice: 20000000000, }); }); it('creates a new wallet with walletVersion', async function () { const ethBitGo = sdk_test_1.TestBitGo.decorate(src_1.BitGo, { env: 'mock' }); ethBitGo.initializeTestVars(); const ethWallets = ethBitGo.coin('teth').wallets(); nock(bgUrl) .post('/api/v2/teth/wallet/add', function (body) { body.type.should.equal('custodial'); body.walletVersion.should.equal(1); body.should.not.have.property('keys'); body.should.not.have.property('m'); body.should.not.have.property('n'); return true; }) .reply(200, {}); await ethWallets.add({ label: 'label', enterprise: 'enterprise', type: 'custodial', walletVersion: 1 }); }); it('creates a new hot wallet with userKey', async function () { nock(bgUrl) .post('/api/v2/tbtc/wallet/add', function (body) { body.type.should.equal('hot'); body.should.have.property('keys'); body.should.have.property('m'); body.should.have.property('n'); return true; }) .reply(200, {}); await wallets.add({ label: 'label', enterprise: 'enterprise', type: 'hot', keys: [], m: 2, n: 3, userKey: 'test123', }); }); }); describe('Generate wallet:', function () { const sandbox = sinon.createSandbox(); it('should validate parameters', async function () { let params = {}; await wallets.generateWallet(params).should.be.rejectedWith('Missing parameter: label'); params = { label: 'abc', backupXpub: 'backup', backupXpubProvider: 'provider', }; await wallets .generateWallet(params) .should.be.rejectedWith('Cannot provide more than one backupXpub or backupXpubProvider flag'); params = { label: 'abc', passcodeEncryptionCode: 123, }; await wallets.generateWallet(params).should.be.rejectedWith('passcodeEncryptionCode must be a string'); params = { label: 'abc', enterprise: 1234, }; await wallets.generateWallet(params).should.be.rejectedWith('invalid enterprise argument, expecting string'); params = { label: 'abc', disableTransactionNotifications: 'string', }; await wallets .generateWallet(params) .should.be.rejectedWith('invalid disableTransactionNotifications argument, expecting boolean'); params = { label: 'abc', gasPrice: 'string', }; await wallets .generateWallet(params) .should.be.rejectedWith('invalid gas price argument, expecting number or number as string'); params = { label: 'abc', gasPrice: true, }; await wallets .generateWallet(params) .should.be.rejectedWith('invalid gas price argument, expecting number or number as string'); params = { label: 'abc', gasPrice: 123, eip1559: { maxFeePerGas: 1234, maxPriorityFeePerGas: 123, }, }; await wallets.generateWallet(params).should.be.rejectedWith('can not use both eip1559 and gasPrice values'); params = { label: 'abc', eip1559: { maxFeePerGas: 'q1234', maxPriorityFeePerGas: '123', }, }; await wallets .generateWallet(params) .should.be.rejectedWith('invalid max fee argument, expecting number or number as string'); params = { label: 'abc', eip1559: { maxFeePerGas: 1234, maxPriorityFeePerGas: '123a', }, }; await wallets .generateWallet(params) .should.be.rejectedWith('invalid priority fee argument, expecting number or number as string'); params = { label: 'abc', disableKRSEmail: 'string', }; await wallets .generateWallet(params) .should.be.rejectedWith('invalid disableKRSEmail argument, expecting boolean'); params = { label: 'abc', krsSpecific: { malicious: { javascript: { code: 'bad.js', }, }, }, }; await wallets .generateWallet(params) .should.be.rejectedWith('krsSpecific object contains illegal values. values must be strings, booleans, or numbers'); }); it('should correctly disable krs emails when creating backup keychains', async function () { const params = { label: 'my_wallet', disableKRSEmail: true, backupXpubProvider: 'test', passphrase: 'test123', userKey: 'xpub123', }; // bitgo key nock(bgUrl) .post('/api/v2/tbtc/key', _.matches({ source: 'bitgo' })) .reply(200); // user key nock(bgUrl) .post('/api/v2/tbtc/key', _.conforms({ pub: (p) => p.startsWith('xpub') })) .reply(200); // backup key nock(bgUrl) .post('/api/v2/tbtc/key', _.matches({ source: 'backup', provider: params.backupXpubProvider, disableKRSEmail: true, })) .reply(200); // wallet nock(bgUrl).post('/api/v2/tbtc/wallet/add').reply(200); await wallets.generateWallet(params); }); it('should correctly pass through the krsSpecific param when creating backup keychains', async function () { const params = { label: 'my_wallet', backupXpubProvider: 'test', passphrase: 'test123', userKey: 'xpub123', krsSpecific: { coverage: 'insurance', expensive: true, howExpensive: 25 }, }; // bitgo key nock(bgUrl) .post('/api/v2/tbtc/key', _.matches({ source: 'bitgo' })) .reply(200); // user key nock(bgUrl) .post('/api/v2/tbtc/key', _.conforms({ pub: (p) => p.startsWith('xpub') })) .reply(200); // backup key nock(bgUrl) .post('/api/v2/tbtc/key', _.matches({ source: 'backup', provider: params.backupXpubProvider, krsSpecific: { coverage: 'insurance', expensive: true, howExpensive: 25 }, })) .reply(200); // wallet nock(bgUrl).post('/api/v2/tbtc/wallet/add').reply(200); await wallets.generateWallet(params); }); it('should send the cold derivation seed for a user key', async () => { const params = { label: 'my-cold-wallet', passphrase: 'test123', userKey: 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8', coldDerivationSeed: '123', }; // bitgo key const bitgoKeyNock = nock(bgUrl) .post('/api/v2/tbtc/key', _.matches({ source: 'bitgo' })) .reply(200); // user key const userKeyNock = nock(bgUrl) .post('/api/v2/tbtc/key', _.matches({ derivedFromParentWithSeed: params.coldDerivationSeed, })) .reply(200); // backup key const backupKeyNock = nock(bgUrl) .post('/api/v2/tbtc/key', _.matches({ source: 'backup' })) .reply(200); // wallet const walletNock = nock(bgUrl).post('/api/v2/tbtc/wallet/add').reply(200); await wallets.generateWallet(params); for (const scope of [bitgoKeyNock, userKeyNock, backupKeyNock, walletNock]) { scope.done(); } }); it('should generate custodial onchain wallet without passing m, n, keys, keySignatures', async () => { const params = { label: 'test wallet', enterprise: 'myenterprise', type: 'custodial', }; const walletNock = nock(bgUrl) .post('/api/v2/tbtc/wallet/add', function (body) { body.type.should.equal('custodial'); should.not.exist(body.m); should.not.exist(body.n); should.not.exist(body.keys); should.not.exist(body.keySignatures); return true; }) .reply(200, { id: '123', baseCoin: bitgo.coin('tbtc'), keys: ['123', '456', '789'] }); nock(bgUrl).get('/api/v2/tbtc/key/123').reply(200, { pub: 'bitgoPub', id: '789' }); nock(bgUrl).get('/api/v2/tbtc/key/456', _.matches({})).reply(200); nock(bgUrl).get('/api/v2/tbtc/key/789').reply(200, { pub: 'backupPub', id: '789' }); 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 = { label: 'test wallet', passphrase: 'multisig password', enterprise: 'enterprise', passcodeEncryptionCode: 'originalPasscodeEncryptionCode', }; const walletNock = nock(bgUrl) .post('/api/v2/tbtc/wallet/add', 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); }); it('should generate hot onchain wallet without passing multisig type', async () => { const params = { label: 'test wallet', passphrase: 'multisig password', enterprise: 'enterprise', passcodeEncryptionCode: 'originalPasscodeEncryptionCode', }; const walletNock = nock(bgUrl) .post('/api/v2/tbtc/wallet/add', 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 generateWalletSpy = sandbox.spy(wallets, 'generateWallet'); const response = await wallets.generateWallet(params); walletNock.isDone().should.be.true(); sinon.assert.calledOnce(generateWalletSpy); assert.equal(generateWalletSpy.firstCall?.args[0]?.multisigType, sdk_core_1.multisigTypes.onchain); assert.ok(response.encryptedWalletPassphrase); assert.ok(response.wallet); assert.equal(bitgo.decrypt({ input: response.encryptedWalletPassphrase, password: params.passcodeEncryptionCode }), params.passphrase); }); it('should generate Go Account wallet', async () => { const params = { label: 'Go Account Wallet', passphrase: 'go_account_password', enterprise: 'enterprise-id', passcodeEncryptionCode: 'originalPasscodeEncryptionCode', type: 'trading', }; const keychainId = 'user_keychain_id'; // Mock keychain creation and upload nock(bgUrl) .post('/api/v2/ofc/key', function (body) { body.should.have.property('encryptedPrv'); body.should.have.property('originalPasscodeEncryptionCode'); body.keyType.should.equal('independent'); body.source.should.equal('user'); return true; }) .reply(200, { id: keychainId, pub: 'userPub', encryptedPrv: 'encryptedPrivateKey' }); // Mock wallet creation const walletNock = nock(bgUrl) .post('/api/v2/ofc/wallet/add', function (body) { body.type.should.equal('trading'); body.m.should.equal(1); body.n.should.equal(1); body.keys.should.have.length(1); body.keys[0].should.equal(keychainId); return true; }) .reply(200, { id: 'wallet123', keys: [keychainId] }); const response = await ofcWallets.generateWallet(params); walletNock.isDone().should.be.true(); assert.ok(response.encryptedWalletPassphrase); assert.ok(response.wallet); assert.ok('userKeychain' in response); }); }); describe('Generate TSS wallet:', function () { const tsol = bitgo.coin('tsol'); const sandbox = sinon.createSandbox(); beforeEach(function () { nock('https://bitgo.fakeurl') .get(`/api/v2/tss/settings`) .times(2) .reply(200, { coinSettings: { eth: { walletCreationSettings: {}, }, bsc: { walletCreationSettings: {}, }, polygon: { walletCreationSettings: {}, }, }, }); }); (0, mocha_1.afterEach)(function () { nock.cleanAll(); sandbox.verifyAndRestore(); }); it('should create a new TSS wallet', async function () { const stubbedKeychainsTriplet = { 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(sdk_core_1.TssUtils.prototype, 'createKeychains').resolves(stubbedKeychainsTriplet); const walletNock = nock('https://bitgo.fakeurl').post('/api/v2/tsol/wallet/add').reply(200); const wallets = new sdk_core_1.Wallets(bitgo, tsol); const params = { label: 'tss wallet', passphrase: 'tss password', multisigType: 'tss', 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 passing multisig type', async function () { const stubbedKeychainsTriplet = { 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(sdk_core_1.TssUtils.prototype, 'createKeychains').resolves(stubbedKeychainsTriplet); const walletNock = nock('https://bitgo.fakeurl') .post('/api/v2/tsol/wallet/add', function (body) { body.multisigType.should.equal(sdk_core_1.multisigTypes.tss); return true; }) .reply(200); const wallets = new sdk_core_1.Wallets(bitgo, tsol); const params = { label: 'tss wallet', passphrase: 'tss password', enterprise: 'enterprise', passcodeEncryptionCode: 'originalPasscodeEncryptionCode', }; const generateWalletSpy = sandbox.spy(wallets, 'generateWallet'); const response = await wallets.generateWallet(params); walletNock.isDone().should.be.true(); sinon.assert.calledOnce(generateWalletSpy); assert.equal(generateWalletSpy.firstCall?.args[0]?.multisigType, sdk_core_1.multisigTypes.tss); 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 = { 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(sdk_core_1.TssUtils.prototype, 'createKeychains').resolves(stubbedKeychainsTriplet); const walletNock = nock('https://bitgo.fakeurl').post('/api/v2/tsol/wallet/add').reply(200); const wallets = new sdk_core_1.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 fail to create TSS wallet with invalid inputs', async function () { const tbtc = bitgo.coin('tbtc'); const params = { label: 'my-cold-wallet', passphrase: 'test123', userKey: 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8', coldDerivationSeed: '123', }; const wallets = new sdk_core_1.Wallets(bitgo, tbtc); nock(bgUrl) .post('/api/v2/tbtc/key', _.matches({ source: 'bitgo' })) .reply(200); nock(bgUrl) .post('/api/v2/tbtc/key', _.matches({ derivedFromParentWithSeed: params.coldDerivationSeed })) .reply(200); nock(bgUrl) .post('/api/v2/tbtc/key', _.matches({ source: 'backup' })) .reply(200); nock(bgUrl).post('/api/v2/tbtc/wallet/add').reply(200); // create a non tss wallet for coin that doesn't support tss even though multisigType is set to tss await wallets.generateWallet({ ...params, multisigType: 'tss' }); const tsolWallets = new sdk_core_1.Wallets(bitgo, tsol); await tsolWallets .generateWallet({ label: 'tss cold wallet', passphrase: 'passphrase', userKey: 'user key', multisigType: 'tss', }) .should.be.rejectedWith('enterprise is required for TSS wallet'); await tsolWallets .generateWallet({ label: 'tss cold wallet', userKey: 'user key', multisigType: 'tss', enterprise: 'enterpriseId', }) .should.be.rejectedWith('cannot generate TSS keys without passphrase'); }); it('should create a new TSS custodial wallet', async function () { const keys = ['1', '2', '3']; const walletParams = { label: 'tss wallet', multisigType: 'tss', enterprise: 'enterprise', type: 'custodial', }; const walletNock = nock('https://bitgo.fakeurl') .post('/api/v2/tsol/wallet/add') .times(1) .reply(200, { ...walletParams, keys }); const wallets = new sdk_core_1.Wallets(bitgo, tsol); const res = await wallets.generateWallet(walletParams); if (!(0, sdk_core_1.isWalletWithKeychains)(res)) { throw new Error('wallet missing required keychains'); } res.wallet.label().should.equal(walletParams.label); should.equal(res.wallet.type(), walletParams.type); res.wallet.toJSON().enterprise.should.equal(walletParams.enterprise); res.wallet.multisigType().should.equal(walletParams.multisigType); res.userKeychain.id.should.equal(keys[0]); res.backupKeychain.id.should.equal(keys[1]); res.bitgoKeychain.id.should.equal(keys[2]); walletNock.isDone().should.be.true(); }); it('should create a new TSS SMC wallet', async function () { const commonKeychain = 'longstring'; const seed = 'seed'; const keys = { userKeychain: { id: '1', commonKeychain, type: 'tss', derivedFromParentWithSeed: seed, source: 'user', }, backupKeychain: { id: '2', commonKeychain, type: 'tss', derivedFromParentWithSeed: seed, source: 'backup', }, bitgoKeychain: { id: '3', commonKeychain, type: 'tss', source: 'bitgo', }, }; const bitgoKeyNock = nock('https://bitgo.fakeurl').get('/api/v2/tsol/key/3').reply(200, keys.bitgoKeychain); const userKeyExpectedBody = { source: 'user', keyType: 'tss', commonKeychain, derivedFromParentWithSeed: seed, }; const userKeyNock = nock('https://bitgo.fakeurl') .post('/api/v2/tsol/key', userKeyExpectedBody) .reply(200, keys.userKeychain); const backupKeyExpectedBody = { source: 'backup', keyType: 'tss', commonKeychain, derivedFromParentWithSeed: seed, }; const backupKeyNock = nock('https://bitgo.fakeurl') .post('/api/v2/tsol/key', backupKeyExpectedBody) .reply(200, keys.backupKeychain); const walletParams = { label: 'tss wallet', multisigType: 'tss', enterprise: 'enterprise', type: 'cold', bitgoKeyId: keys.bitgoKeychain.id, commonKeychain, coldDerivationSeed: seed, }; const walletNockExpected = { label: walletParams.label, m: 2, n: 3, keys: [keys.userKeychain.id, keys.backupKeychain.id, keys.bitgoKeychain.id], type: walletParams.type, multisigType: walletParams.multisigType, enterprise: walletParams.enterprise, walletVersion: undefined, }; const walletNock = nock('https://bitgo.fakeurl') .post('/api/v2/tsol/wallet/add', walletNockExpected) .reply(200, { ...walletNockExpected, responseType: 'WalletWithKeychains' }); const wallets = new sdk_core_1.Wallets(bitgo, tsol); const res = await wallets.generateWallet(walletParams); if (!(0, sdk_core_1.isWalletWithKeychains)(res)) { throw new Error('wallet missing required keychains'); } res.responseType.should.equal('WalletWithKeychains'); res.wallet.label().should.equal(walletParams.label); should.equal(res.wallet.type(), walletParams.type); res.wallet.toJSON().enterprise.should.equal(walletParams.enterprise); res.wallet.multisigType().should.equal(walletParams.multisigType); res.userKeychain.should.deepEqual(keys.userKeychain); res.backupKeychain.should.deepEqual(keys.backupKeychain); res.bitgoKeychain.should.deepEqual(keys.bitgoKeychain); bitgoKeyNock.isDone().should.be.true(); userKeyNock.isDone().should.be.true(); backupKeyNock.isDone().should.be.true(); walletNock.isDone().should.be.true(); }); it('should throw an error for TSS SMC wallet if the bitgoKeyId is not a bitgo key ', async function () { const commonKeychain = 'longstring'; const seed = 'seed'; const keys = { userKeychain: { id: '1', commonKeychain, type: 'tss', derivedFromParentWithSeed: seed, source: 'user', }, backupKeychain: { id: '2', commonKeychain, type: 'tss', derivedFromParentWithSeed: seed, source: 'backup', }, bitgoKeychain: { id: '3', commonKeychain, type: 'tss', source: 'bitgo', }, }; const bitgoKeyNock = nock('https://bitgo.fakeurl').get('/api/v2/tsol/key/1').reply(200, keys.userKeychain); const walletParams = { label: 'tss wallet', multisigType: 'tss', enterprise: 'enterprise', type: 'cold', bitgoKeyId: keys.userKeychain.id, commonKeychain, coldDerivationSeed: seed, }; const wallets = new sdk_core_1.Wallets(bitgo, tsol); await wallets .generateWallet(walletParams) .should.be.rejectedWith('The provided bitgoKeyId is not a BitGo keychain'); bitgoKeyNock.isDone().should.be.true(); }); }); describe('Generate TSS MPCv2 wallet:', async function () { const sandbox = sinon.createSandbox(); beforeEach(function () { const tssSettings = { coinSettings: { eth: { walletCreationSettings: { multiSigTypeVersion: 'MPCv2', coldMultiSigTypeVersion: 'MPCv2', custodialMultiSigTypeVersion: 'MPCv2', }, }, bsc: { walletCreationSettings: { multiSigTypeVersion: 'MPCv2', coldMultiSigTypeVersion: 'MPCv2', custodialMultiSigTypeVersion: 'MPCv2', }, }, polygon: { walletCreationSettings: { multiSigTypeVersion: 'MPCv2', coldMultiSigTypeVersion: 'MPCv2', custodialMultiSigTypeVersion: 'MPCv2', }, }, atom: { walletCreationSettings: { multiSigTypeVersion: 'MPCv2', coldMultiSigTypeVersion: 'MPCv2', custodialMultiSigTypeVersion: 'MPCv2', }, }, tia: { walletCreationSettings: { multiSigTypeVersion: 'MPCv2', coldMultiSigTypeVersion: 'MPCv2', custodialMultiSigTypeVersion: 'MPCv2', }, }, bera: { walletCreationSettings: { multiSigTypeVersion: 'MPCv2', coldMultiSigTypeVersion: 'MPCv2', custodialMultiSigTypeVersion: 'MPCv2', }, }, arbeth: { walletCreationSettings: { multiSigTypeVersion: 'MPCv2', coldMultiSigTypeVersion: 'MPCv2', custodialMultiSigTypeVersion: 'MPCv2', }, }, opeth: { walletCreationSettings: { multiSigTypeVersion: 'MPCv2', coldMultiSigTypeVersion: 'MPCv2', custodialMultiSigTypeVersion: 'MPCv2', }, }, }, }; nock('https://bitgo.fakeurl').get(`/api/v2/tss/settings`).times(2).reply(200, tssSettings); }); (0, mocha_1.afterEach)(function () { nock.cleanAll(); sandbox.verifyAndRestore(); }); ['hteth', 'tbsc', 'tpolygon', 'ttia', 'tatom', 'tbera', 'tarbeth', 'topeth'].forEach((coin) => { it(`should create a new ${coin} TSS MPCv2 hot wallet`, async function () { const testCoin = bitgo.coin(coin); const stubbedKeychainsTriplet = { userKeychain: { id: '1', commonKeychain: 'userPub', type: 'tss', source: 'user', }, backupKeychain: { id: '2', commonKeychain: 'userPub', type: 'tss', source: 'backup', }, bitgoKeychain: { id: '3', commonKeychain: 'userPub', type: 'tss', source: 'bitgo', }, }; const stubCreateKeychains = sandbox .stub(sdk_core_1.ECDSAUtils.EcdsaMPCv2Utils.prototype, 'createKeychains') .resolves(stubbedKeychainsTriplet); const walletNock = nock('https://bitgo.fakeurl').post(`/api/v2/${coin}/wallet/add`).reply(200); const wallets = new sdk_core_1.Wallets(bitgo, testCoin); const params = { label: 'tss wallet', passphrase: 'tss password', multisigType: 'tss', 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); }); it(`should create a new ${coin} TSS MPCv2 hot wallet without passing multisig type`, async function () { const testCoin = bitgo.coin(coin); const stubbedKeychainsTriplet = { userKeychain: { id: '1', commonKeychain: 'userPub', type: 'tss', source: 'user', }, backupKeychain: { id: '2', commonKeychain: 'userPub', type: 'tss', source: 'backup', }, bitgoKeychain: { id: '3', commonKeychain: 'userPub', type: 'tss', source: 'bitgo', }, }; const stubCreateKeychains = sandbox .stub(sdk_core_1.ECDSAUtils.EcdsaMPCv2Utils.prototype, 'createKeychains') .resolves(stubbedKeychainsTriplet); const walletNock = nock('https://bitgo.fakeurl') .post(`/api/v2/${coin}/wallet/add`, function (body) { body.multisigType.should.equal(sdk_core_1.multisigTypes.tss); return true; }) .reply(200); const wallets = new sdk_core_1.Wallets(bitgo, testCoin); const params = { label: 'tss wallet', passphrase: 'tss password', 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); }); it(`should create a new ${coin} TSS MPCv2 cold wallet`, async function () { const testCoin = bitgo.coin(coin); const bitgoKeyId = 'key123'; const commonKeychain = '0xabc'; const bitgoKeyNock = nock('https://bitgo.fakeurl') .get(`/api/v2/${coin}/key/${bitgoKeyId}`) .times(1) .reply(200, { id: 'key123', pub: 'bitgoPub', type: 'tss', source: 'bitgo', commonKeychain, }); const userKeyNock = nock('https://bitgo.fakeurl') .post(`/api/v2/${coin}/key`, { source: 'user', keyType: 'tss', commonKeychain, derivedFromParentWithSeed: '37', isMPCv2: true, }) .times(1) .reply(200, { id: 'userKey123', pub: 'userPub', type: 'tss', source: 'user', }); const backupKeyNock = nock('https://bitgo.fakeurl') .post(`/api/v2/${coin}/key`, { source: 'backup', keyType: 'tss', commonKeychain, derivedFromParentWithSeed: '37', isMPCv2: true, }) .times(1) .reply(200, { id: 'backupKey123', pub: 'backupPub', type: 'tss', source: 'backup', }); const walletNock = nock('https://bitgo.fakeurl') .post(`/api/v2/${coin}/wallet/add`, { label: 'tss wallet', m: 2, n: 3, keys: ['userKey123', 'backupKey123', 'key123'], type: 'cold', multisigType: 'tss', enterprise: 'enterprise', walletVersion: 5, }) .reply(200); const wallets = new sdk_core_1.Wallets(bitgo, testCoin); const params = { label: 'tss wallet', multisigType: 'tss', enterprise: 'enterprise', passcodeEncryptionCode: 'originalPasscodeEncryptionCode', walletVersion: 5, type: 'cold', bitgoKeyId: 'key123', commonKeychain: '0xabc', coldDerivationSeed: '37', }; const response = (await wallets.generateWallet(params)); bitgoKeyNock.isDone().should.be.true(); userKeyNock.isDone().should.be.true(); backupKeyNock.isDone().should.be.true(); walletNock.isDone().should.be.true(); should.exist(response.wallet); should.exist(response.userKeychain); should.exist(response.backupKeychain); should.exist(response.bitgoKeychain); response.responseType.should.equal('WalletWithKeychains'); response.userKeychain.id.should.equal('userKey123'); response.backupKeychain.id.should.equal('backupKey123'); response.bitgoKeychain.id.should.equal('key123'); }); it(`should create a new ${coin} TSS MPCv2 custody wallet`, async function () { const testCoin = bitgo.coin(coin); const keys = ['userKey', 'backupKey', 'bitgoKey']; const params = { label: 'tss wallet', passphrase: 'tss password', multisigType: 'tss', enterprise: 'enterprise', passcodeEncryptionCode: 'originalPasscodeEncryptionCode', walletVersion: 5, type: 'custodial', }; const walletNock = nock('https://bitgo.fakeurl') .post(`/api/v2/${coin}/wallet/add`) .times(1) .reply(200, { ...params, keys }); const wallets = new sdk_core_1.Wallets(bitgo, testCoin); const response = (await wallets.generateWallet(params)); walletNock.isDone().should.be.true(); should.exist(response.wallet); should.exist(response.userKeychain); should.exist(response.backupKeychain); should.exist(response.bitgoKeychain); response.responseType.should.equal('WalletWithKeychains'); response.userKeychain.id.should.equal(keys[0]); response.backupKeychain.id.should.equal(keys[1]); response.bitgoKeychain.id.should.equal(keys[2]); }); }); it(`should create a new hteth TSS MPCv2 wallet with walletVersion 6`, async function () { const testCoin = bitgo.coin('hteth'); const stubbedKeychainsTriplet = { userKeychain: { id: '1', commonKeychain: 'userPub', type: 'tss', source: 'user', }, backupKeychain: { id: '2', commonKeychain: 'userPub', type: 'tss', source: 'backup', }, bitgoKeychain: { id: '3', commonKeychain: 'userPub', type: 'tss', source: 'bitgo', }, }; const stubCreateKeychains = sandbox .stub(sdk_core_1.ECDSAUtils.EcdsaMPCv2Utils.prototype, 'createKeychains') .resolves(stubbedKeychainsTriplet); const walletNock = nock('https://bitgo.fakeurl') .post(`/api/v2/hteth/wallet/add`, (body) => { body.walletVersion.should.equal(6); return true; }) .reply(200); const wallets = new sdk_core_1.Wallets(bitgo, testCoin); const params = { label: 'tss wallet', passphrase: 'tss password', multisigType: 'tss', enterprise: 'enterprise', passcodeEncryptionCode: 'originalPasscodeEncryptionCode', walletVersion: 6, }; 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); }); it(`should create a new MPCv2 wallet with version 5 if walletVersion passed is not 5 or 6`, async function () { const testCoin = bitgo.coin('hteth'); const stubbedKeychainsTriplet = { userKeychain: { id: '1', commonKeychain: 'userPub', type: 'tss', source: 'user', }, backupKeychain: { id: '2', commonKeychain: 'userPub', type: 'tss', source: