Skip to content

Commit

Permalink
feat: modify accept share method
Browse files Browse the repository at this point in the history
Ticket: PX-3296
We are modifying the accept share method for go account wallet invitations
  • Loading branch information
ravneet-bitgo committed Apr 12, 2024
1 parent 45bb7b6 commit a87b65e
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 12 deletions.
4 changes: 3 additions & 1 deletion modules/bitgo/test/v2/unit/wallets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,9 @@ describe('V2 Wallets:', function () {
nock(bgUrl).get(`/api/v2/tbtc/walletshare/${shareId}`).reply(200, {});
const acceptShareNock = nock(bgUrl)
.post(`/api/v2/tbtc/walletshare/${shareId}`, { walletShareId: shareId, state: 'accepted' })
.reply(200, {});
.reply(200, {
result: () => ({ changed: true }),
});

await wallets.acceptShare({ walletShareId: shareId });
acceptShareNock.done();
Expand Down
1 change: 1 addition & 0 deletions modules/sdk-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@bitgo/bls-dkg": "^1.3.1",
"@bitgo/public-types": "2.1.0",
"@bitgo/sdk-lib-mpc": "^9.2.0",
"@bitgo/sjcl": "^1.0.1",
"@bitgo/statics": "^48.5.0",
"@bitgo/utxo-lib": "^9.35.0",
"@noble/secp256k1": "1.6.3",
Expand Down
1 change: 1 addition & 0 deletions modules/sdk-core/src/bitgo/wallet/iWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,7 @@ export interface WalletData {
tokens?: Record<string, any>[];
nfts?: { [contractAddressOrToken: string]: NftBalance };
unsupportedNfts?: { [contractAddress: string]: NftBalance };
users?: any[];
}

export interface RecoverTokenOptions {
Expand Down
1 change: 1 addition & 0 deletions modules/sdk-core/src/bitgo/wallet/iWallets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export interface UpdateShareOptions {
walletShareId?: string;
state?: string;
encryptedPrv?: string;
keyId?: string;
}

export interface AcceptShareOptions {
Expand Down
112 changes: 101 additions & 11 deletions modules/sdk-core/src/bitgo/wallet/wallets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import {
WalletWithKeychains,
} from './iWallets';
import { Wallet } from './wallet';
import * as sjcl from '@bitgo/sjcl';
import * as bs58 from 'bs58';

export class Wallets implements IWallets {
private readonly bitgo: BitGoBase;
Expand Down Expand Up @@ -506,10 +508,7 @@ export class Wallets implements IWallets {
async updateShare(params: UpdateShareOptions = {}): Promise<any> {
common.validateParams(params, ['walletShareId'], []);

return await this.bitgo
.post(this.baseCoin.url('/walletshare/' + params.walletShareId))
.send(params)
.result();
return await this.bitgo.post(this.baseCoin.url('/walletshare/' + params.walletShareId)).send(params);
}

/**
Expand Down Expand Up @@ -538,6 +537,77 @@ export class Wallets implements IWallets {
.result();
}

/**
* Re-share wallet with existing spenders of the wallet
* @param walletId
* @param userPassword
*/
async reshareOfcAccountWithSpenders(walletId: string, userPassword: string): Promise<void> {
const wallet = await this.bitgo.coin('ofc').wallets().get({ id: walletId });
const enterpriseUsersResponse = (await this.bitgo
.get(`/api/v2/enterprise/${wallet?._wallet?.enterprise}/user`)
.result()) as any;
// create a map of users for easy lookup - we need the user email id to share the wallet
const usersMap = new Map(
[...enterpriseUsersResponse?.adminUsers, ...enterpriseUsersResponse?.nonAdminUsers].map((obj) => [obj.id, obj])
);
wallet?._wallet?.users?.forEach(async (user) => {
try {
// user should be a spender and not an admin
if (user.permissions.includes('spend') && !user.permissions.includes('admin')) {
const userObject = usersMap.get(user.user);
const shareParams = {
walletId: walletId,
user: user.user,
permissions: user.permissions.join(','),
walletPassphrase: userPassword,
email: userObject.email.email,
coin: wallet.coin,
reshare: true,
};
wallet.shareWallet(shareParams);
}
} catch (e) {
// TODO: gracefully handle this error
console.error(e);
}
});
}

/**
* Generate a random password
* @param {Number} numWords Number of 32-bit words
* @returns {String} base58 random password
*/
generateRandomPassword(numWords = 5): string {
const bytes = sjcl.codec.bytes.fromBits(sjcl.random.randomWords(numWords));
return bs58.encode(bytes);
}

/**
* Create keychain for ofc wallet using the password
* @param userPassword
* @returns
*/
async createKeychain(userPassword: string): Promise<Keychain> {
const sdkCoin = await this.bitgo.coin('ofc');
const keychains = sdkCoin.keychains();
const newKeychain = keychains.create();
const originalPasscodeEncryptionCode = this.generateRandomPassword();

const encryptedPrv = this.bitgo.encrypt({
password: userPassword,
input: newKeychain.prv,
});

const walletKeychain = await keychains.add({
encryptedPrv,
originalPasscodeEncryptionCode,
pub: newKeychain.pub,
source: 'user',
});
return walletKeychain;
}
/**
* Accepts a wallet share, adding the wallet to the user's list
* Needs a user's password to decrypt the shared key
Expand All @@ -554,15 +624,36 @@ export class Wallets implements IWallets {
common.validateParams(params, ['walletShareId'], ['overrideEncryptedPrv', 'userPassword', 'newWalletPassphrase']);

let encryptedPrv = params.overrideEncryptedPrv;

const walletShare = (await this.getShare({ walletShareId: params.walletShareId })) as any;

// Return right away if there is no keychain to decrypt, or if explicit encryptedPrv was provided
if (!walletShare.keychain || !walletShare.keychain.encryptedPrv || encryptedPrv) {
return this.updateShare({
if (walletShare.keychainOverrideRequired && walletShare.permissions.indexOf('admin') !== -1) {
if (_.isUndefined(params.userPassword)) {
throw new Error('userPassword param must be provided to decrypt shared key');
}
const walletKeychain = await this.createKeychain(params.userPassword);
const response = await this.updateShare({
walletShareId: params.walletShareId,
state: 'accepted',
keyId: walletKeychain.id,
});
// If the wallet share was accepted successfully (changed=true), reshare the wallet with the spenders
if (response.statusCode === 200 && response.result().changed) {
try {
await this.reshareOfcAccountWithSpenders(walletShare.wallet, params.userPassword);
} catch (e) {
// TODO: gracefully handle this error
console.error(e);
}
}
return response;
}
// Return right away if there is no keychain to decrypt, or if explicit encryptedPrv was provided
if (!walletShare.keychain || !walletShare.keychain.encryptedPrv || encryptedPrv) {
return (
this.updateShare({
walletShareId: params.walletShareId,
state: 'accepted',
}) as any
).result();
}

// More than viewing was requested, so we need to process the wallet keys using the shared ecdh scheme
Expand Down Expand Up @@ -606,8 +697,7 @@ export class Wallets implements IWallets {
if (encryptedPrv) {
updateParams.encryptedPrv = encryptedPrv;
}

return this.updateShare(updateParams);
return (this.updateShare(updateParams) as any).result();
}

/**
Expand Down

0 comments on commit a87b65e

Please sign in to comment.