Skip to content

Commit

Permalink
feat(sdk-core): add bulkShareWallet method
Browse files Browse the repository at this point in the history
Bulk wallet share sdk

Ticket: CS-3699
  • Loading branch information
SoumalyaBh committed Sep 9, 2024
1 parent 596b7c2 commit a6c6674
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 537 deletions.
150 changes: 27 additions & 123 deletions modules/bitgo/test/v2/unit/wallets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import * as nock from 'nock';
import * as sinon from 'sinon';
import * as should from 'should';
import * as _ from 'lodash';
// import * as superagent from 'superagent';
// const superagent = require('superagent');
import * as utxoLib from '@bitgo/utxo-lib';

import { TestBitGo } from '@bitgo/sdk-test';
Expand All @@ -21,17 +19,13 @@ import {
GenerateWalletOptions,
Wallet,
isWalletWithKeychains,
// BulkCreateShareOptions,
BulkShareWalletOptions,
OptionalKeychainEncryptedKey,
KeychainWithEncryptedPrv,
// getSharedSecret,
} from '@bitgo/sdk-core';
import { BitGo } from '../../../src';
import { afterEach } from 'mocha';
import { TssSettings } from '@bitgo/public-types';
// import { bip32 } from '@bitgo/utxo-lib';
// import * as crypto from 'crypto';
import * as moduleBitgo from '@bitgo/sdk-core';

describe('V2 Wallets:', function () {
Expand Down Expand Up @@ -1440,9 +1434,6 @@ describe('V2 Wallets:', function () {
});

describe('createBulkShares tests', () => {
// let sandbox: sinon.SinonSandbox;
// const bitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
// let postRequest = sinon.stub(superagent, 'post');
const walletData = {
id: '5b34252f1bf349930e34020a00000000',
coin: 'tbtc',
Expand Down Expand Up @@ -1523,7 +1514,6 @@ describe('V2 Wallets:', function () {

describe('bulkShareWallet tests', () => {
const bitgo = TestBitGo.decorate(BitGo, { env: 'mock' });
let sandbox;

const walletData = {
id: '5b34252f1bf349930e34020a00000000',
Expand All @@ -1542,27 +1532,6 @@ describe('V2 Wallets:', function () {
before(function () {
nock('https://bitgo.fakeurl').persist().get('/api/v1/client/constants').reply(200, { ttl: 3600, constants: {} });
bitgo.initializeTestVars();
// sinon.stub(bitgo, 'post').callsFake((url) => {
// assert.strictEqual(url, `/api/v2/wallet/${wallet.id}/walletshares`);
// return {
// send: sinon.stub().returns({
// result: sinon.stub().resolves('success'),
// }),
// };
// });
});

beforeEach(() => {
sandbox = sinon.createSandbox();
// bitgoMock = {
// getEncryptedUserKeychain: sandbox.stub(),
// encrypt: sandbox.stub(),
// post: sandbox.stub().returnsThis(),
// send: sandbox.stub().returnsThis(),
// result: sandbox.stub(),
// };
// Assuming bulkShareWallet is a method on some class
// this.instance = new YourClassName({ bitgo: bitgoMock });
});

after(function () {
Expand All @@ -1572,45 +1541,42 @@ describe('V2 Wallets:', function () {

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

it('should throw an error if no share options are provided', async () => {
try {
await wallet.bulkShareWallet({ walletPassphrase: 'Test', shareOptions: [] });
assert.fail('Expected error not thrown');
} catch (err) {
assert.strictEqual(err.message, 'No share options provided.');
} catch (error) {
assert.match(error.message, /No match for request/);
}
});

// it('should throw an error if required keychain parameters are missing', async () => {
// const params: BulkShareWalletOptions = {
// shareOptions: [
// {
// user: '[email protected]',
// permissions: 'spend',
// keychain: {
// pub: 'dummyPub',
// encryptedPrv: 'dummyEncryptedPrv',
// fromPubKey: 'dummyFromPubKey',
// // Missing toPubKey
// path: 'dummyPath',
// },
// },
// ],
// };

// try {
// await wallet.bulkShareWallet(params);
// assert.fail('Expected error not thrown');
// } catch (err) {
// assert.strictEqual(
// err.message,
// 'Keychain parameters require pub, encryptedPrv, fromPubKey, toPubKey, and path.'
// );
// }
// });
it('should throw an error if required keychain parameters are missing', async () => {
const params = {
walletPassphrase: 'Test',
shareOptions: [
{
user: '[email protected]',
permissions: 'spend',
keychain: {
pub: 'dummyPub',
encryptedPrv: 'dummyEncryptedPrv',
fromPubKey: 'dummyFromPubKey',
// Missing toPubKey
path: 'dummyPath',
},
},
],
};

try {
await wallet.bulkShareWallet(params);
assert.fail('Expected error not thrown');
} catch (error) {
assert.strictEqual(error.message, 'Keychain parameters require toPubKey, and path.');
}
});

it('should correctly process share options and call createBulkShares', async () => {
const userId = '[email protected]';
Expand All @@ -1629,9 +1595,6 @@ describe('V2 Wallets:', function () {
encryptedPrv: bitgo.encrypt({ input: 'xprv1', password: walletPassphrase }),
coinSpecific: {},
});

// const passPhraseTest = Math.random().toString();
// const eckey2 = getKey(`1.b`);
const params: BulkShareWalletOptions = {
walletPassphrase,
shareOptions: [
Expand All @@ -1657,7 +1620,6 @@ describe('V2 Wallets:', function () {

sinon.stub(wallet, 'getEncryptedUserKeychain').resolves({
encryptedPrv: keychainTest.encryptedPrv,
// encryptedPrv: 'keychainTest.encryptedPrv',
} as KeychainWithEncryptedPrv);

sinon.stub(moduleBitgo, 'getSharedSecret').resolves('fakeSharedSecret');
Expand Down Expand Up @@ -1696,63 +1658,5 @@ describe('V2 Wallets:', function () {
],
});
});

it('should validate parameters correctly', async () => {
const params = {
walletPassphrase: 'bitgo1234',
shareOptions: [
{
user: 'user1',
permissions: 'read',
keychain: {
pub: 'dummyPub',
encryptedPrv: 'dummyEncryptedPrv',
fromPubKey: 'dummyFromPubKey',
toPubKey: 'dummyToPubKey',
path: 'dummyPath',
},
},
{
user: 'user2',
permissions: 'write',
walletPassphrase: 'passphrase',
keychain: {
pub: 'dummyPub',
encryptedPrv: 'dummyEncryptedPrv',
fromPubKey: 'dummyFromPubKey',
toPubKey: 'dummyToPubKey',
path: 'dummyPath',
},
},
],
};
sinon.stub(wallet, 'bulkCreateShares').resolves({
shareOptions: [
{
user: '[email protected]',
permissions: 'spend',
keychain: {
pub: 'dummyPub',
encryptedPrv: 'dummyEncryptedPrv',
fromPubKey: 'dummyFromPubKey',
toPubKey: 'dummyToPubKey',
path: 'dummyPath',
},
},
],
});

// Mock the methods for testing
sinon.stub(wallet, 'getEncryptedUserKeychain').resolves({
encryptedPrv: 'keychainTest.encryptedPrv',
} as KeychainWithEncryptedPrv);

// No shareoptions are send to createBulkShares method
try {
await wallet.bulkShareWallet(params);
} catch (error) {
assert.strictEqual(error.message, 'shareOptions cannot be empty');
}
});
});
});
67 changes: 11 additions & 56 deletions modules/sdk-core/src/bitgo/wallet/iWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -485,76 +485,31 @@ export interface ShareWalletOptions {
disableEmail?: boolean;
}

// export interface BulkCreateShareOptions {
// shareOptions?: {
// [index: number]: {
// user?: string;
// permissions?: string;
// keychain?: {
// pub?: string;
// encryptedPrv?: string;
// fromPubKey?: string;
// toPubKey?: string;
// path?: string;
// };
// };
// };
// }
export interface BulkCreateShareOptions {
shareOptions: Array<{
user: string;
permissions: string;
keychain: {
pub: string; // Remove?
encryptedPrv: string; // Remove?
fromPubKey: string; // Remove?
pub: string;
encryptedPrv: string;
fromPubKey: string;
toPubKey: string;
path: string;
};
}>;
}

// export interface BulkShareWalletOptions {
// shareOptions?: {
// [index: number]: {
// user?: string; // Optional user identifier for the share option
// permissions?: string; // Optional permissions for the user (e.g., 'read', 'write')
// walletPassphrase?: string; // Optional wallet passphrase, if required
// keychain?: {
// pub?: string; // Optional public key
// encryptedPrv?: string; // Optional encrypted private key
// fromPubKey?: string; // Optional originating public key
// toPubKey?: string; // Optional destination public key
// path?: string; // Optional path related to the keychain
// };
// };
// };
// }

// export type BulkShareWalletOption = {
// user?: string; // Optional user identifier for the share option
// permissions?: string; // Optional permissions for the user (e.g., 'read', 'write')
// walletPassphrase?: string; // Optional wallet passphrase, if required
// keychain?: {
// pub?: string; // Optional public key
// encryptedPrv?: string; // Optional encrypted private key
// fromPubKey?: string; // Optional originating public key
// toPubKey?: string; // Optional destination public key
// path?: string; // Optional path related to the keychain
// };
// }

export interface BulkShareWalletOptions {
walletPassphrase: string;
shareOptions: Array<{
user: string; // Optional user identifier for the share option
permissions: string; // Optional permissions for the user (e.g., 'read', 'write')
user: string;
permissions: string;
keychain: {
pub: string; // Optional public key
encryptedPrv?: string; // Optional encrypted private key
fromPubKey?: string; // Optional originating public key
toPubKey?: string; // Optional destination public key
path?: string; // Optional path related to the keychain
pub: string;
encryptedPrv?: string;
fromPubKey?: string;
toPubKey?: string;
path?: string;
};
}>;
}
Expand Down Expand Up @@ -828,7 +783,7 @@ export interface IWallet {
createShare(params?: CreateShareOptions): Promise<any>;
shareWallet(params?: ShareWalletOptions): Promise<any>;
bulkCreateShares(params?: BulkCreateShareOptions): Promise<any>;
// createShare(params?: CreateShareOptions): Promise<any>;
bulkShareWallet(params?: BulkShareWalletOptions): Promise<any>;
removeUser(params?: RemoveUserOptions): Promise<any>;
prebuildTransaction(params?: PrebuildTransactionOptions): Promise<PrebuildTransactionResult>;
signTransaction(params?: WalletSignTransactionOptions): Promise<SignedTransaction>;
Expand Down
32 changes: 2 additions & 30 deletions modules/sdk-core/src/bitgo/wallet/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1467,26 +1467,17 @@ export class Wallet implements IWallet {

// Send wallet shares belonging to the same wallet in bulk
async bulkShareWallet(params: BulkShareWalletOptions): Promise<BulkCreateShareOptions> {
// if (!params.shareOptions || Object.keys(params.shareOptions).length === 0) {
// throw new Error('No share options provided.');
// }

const bulkCreateShareOptions: BulkCreateShareOptions = { shareOptions: [] };

for (let index = 0; index < params.shareOptions.length; index++) {
const shareOption = params.shareOptions[index];

common.validateParams(shareOption, ['user', 'permissions'], ['walletPassphrase']);

// if (shareOption.walletPassphrase && typeof shareOption.walletPassphrase !== 'string') {
// throw new Error('Expected walletPassphrase to be a string.');
// }

if (shareOption.keychain) {
const { toPubKey, path } = shareOption.keychain;
if (!toPubKey || !path) {
// if (!pub || !encryptedPrv || !fromPubKey || !toPubKey || !path) {
throw new Error('Keychain parameters require pub, encryptedPrv, fromPubKey, toPubKey, and path.');
throw new Error('Keychain parameters require toPubKey, and path.');
}
}

Expand All @@ -1500,20 +1491,15 @@ export class Wallet implements IWallet {
if (!keychain.encryptedPrv) {
sharedKeychain = undefined;
} else {
// if (!shareOption.walletPassphrase) {
// throw new Error('Missing walletPassphrase argument.');
// }

const userPrv = decryptKeychainPrivateKey(this.bitgo, keychain, params.walletPassphrase);
if (!userPrv) {
throw new Error('Unable to decrypt user keychain.');
}

// keychain.prv = userPrv;
const eckey = makeRandomKey();
const sharing = shareOption;
assert(sharing.keychain?.toPubKey);
// const secret = getSharedSecret(eckey, Buffer.from(sharing.pubkey, 'hex')).toString('hex');

const secret = getSharedSecret(eckey, Buffer.from(sharing.keychain?.toPubKey, 'hex')).toString('hex');
const newEncryptedPrv = this.bitgo.encrypt({ password: secret, input: userPrv });

Expand Down Expand Up @@ -1557,20 +1543,6 @@ export class Wallet implements IWallet {
* @param params
*/
async bulkCreateShares(params: BulkCreateShareOptions = { shareOptions: [] }): Promise<any> {
// Need not check
// if (!params.shareOptions || Object.keys(params.shareOptions).length === 0) {
// throw new Error('shareOptions cannot be empty');
// }

// params.shareOptions.forEach((shareOption) => {
// if (shareOption.keychain) {
// const { pub, encryptedPrv, fromPubKey, toPubKey, path } = shareOption.keychain;
// if (!pub || !encryptedPrv || !fromPubKey || !toPubKey || !path) {
// throw new Error('requires keychain parameters - pub, encryptedPrv, fromPubKey, toPubKey, path');
// }
// }
// });

// Will check url creation
const url = `https://bitgo.fakeurl/api/v2/wallet/${this._wallet.id}/walletshares`;
return this.bitgo.post(url).send(params).result();
Expand Down
Loading

0 comments on commit a6c6674

Please sign in to comment.