Skip to content

recovery multisig for eve and mbe#23

Merged
mohammadalfaiyazbitgo merged 8 commits intomasterfrom
WP-4637_recovery_multisig
Jun 17, 2025
Merged

recovery multisig for eve and mbe#23
mohammadalfaiyazbitgo merged 8 commits intomasterfrom
WP-4637_recovery_multisig

Conversation

@mtexeira-simtlix
Copy link
Copy Markdown
Contributor

@mtexeira-simtlix mtexeira-simtlix commented Jun 11, 2025

Ticket: WP-4637 / WP-4632

Description:

  • recovery full flow, mbe & eve

@pranavjain97
Copy link
Copy Markdown
Contributor

pranavjain97 commented Jun 11, 2025

Ticket: WP-4637.

Reviews needed in order to avoid delays: @pranavjain97 I'm missing the enclavedExpressClient.signMultisigTransaction method (eve api), mind if you let me know when merged? I need that to finish the handleRecoveryWalletOnPrem on masterBitgoExpress/recoveryWallet.ts, i left the calls but there is no method implemented currently. image

@pranavjain97 I need a hand to type the txBuild on enclavedApiSpec.ts, on a normal signing it could be a TransactionPrebuild and in a recovery it could be other types that comes from the SDK (Typescript types), the issue is that currently:

image

and because it's all io-ts, i didn't managed to find a way to export the types from the TS side to the IO-TS side so I need your advice (worst case we could leave the types as t.any and find a way later to reach the demo date on Friday). Thread about this: https://bitgo.slack.com/archives/C08RCSLF398/p1749666974629419

@mohammadalfaiyazbitgo mind checking the main body of handleRecoveryWalletOnPrem, particulary the part about ETH please? let me know if you find something weird or any kind of improvements please. In order to understand why am I doing this (on mbe): image

you need to also check side by side with this (on eve signing) image

@pranavjain97 & @mohammadalfaiyazbitgo mind checking the signMultisigTransactionRecovery.ts file please? It's an alternative version that i created for signing as i'm not sure how to integrate the recovery signing with a normal signing, It's not as trivial as it sounds and I'm presenting some alternatives and concerns that I tried to express some days ago but wasn't as visible as now for the lack of a more polished code examples. I'll need a hand in order to unity the recovery code with the normal signing, on things like verifying the tx i.e.

Also, the proposed code path that i left is pretty simple and did some refactors in order to remove some cluther:

image

@pranavjain97 @mohammadalfaiyazbitgo do we need this kind of validation? (related to the previous comment), if not please let me know to remove the extras and avoid investing time on this. Thanks guys! image

Those are my main concerns for now and things that blocks the delivery of the PR. If i noticed something else i'll let you know guys. Thanks!

Discussed/resolved all questions on meet

@mtexeira-simtlix mtexeira-simtlix force-pushed the WP-4637_recovery_multisig branch 3 times, most recently from 8f58dd6 to 478d5e1 Compare June 12, 2025 13:51
Comment on lines +51 to +55
const unsignedTx = await coin.recover({
...commonRecoveryParams,
//TODO: it's needed for keycard debugging, the walletPassphrase
//walletPassphrase: passphrase,
});
Copy link
Copy Markdown
Contributor

@pranavjain97 pranavjain97 Jun 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will use blockchain APIs to build, will need to be in MBE. Enclave will need to accept the unsignedSweepPrebuild

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solved!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @pranavjain97 thanks for the reviews! Is this one solved for you? let me know if we need more changes and if that's not the case please "Resolve conversation". Thank you!

@mtexeira-simtlix mtexeira-simtlix force-pushed the WP-4637_recovery_multisig branch 2 times, most recently from 30647cc to bc0e6bd Compare June 12, 2025 16:48
Comment thread src/api/enclaved/recoveryMultisigTransaction.ts Outdated
// If you check the type of coin before and after the "if", you may see "BaseCoin" vs "AbstractEthLikeCoin"
if (coin.isEVM()) {
// Every recovery method on every coin family varies one from another so we need to ensure with a guard.
if (isEthCoin(coin)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think you can expand this to ethlike they all use AbstractEthLike as the base class iirc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure! it's an issue of bad naming of the func, it says "isEthCoin" but the implementations checks for pure eth and all the eth like coins. I'm gonna change the name.
Good catch, thanks!

Comment thread src/shared/coinUtils.ts Outdated
Comment on lines +6 to +26
const isEthPure =
isFamily(coin, 'eth', 'gteth') ||
isFamily(coin, 'eth', 'hteth') ||
isFamily(coin, 'ethw', 'tethw');

const isEthLike =
isFamily(coin, 'rbtc', 'trbtc') ||
isFamily(coin, 'etc', 'tetc') ||
isFamily(coin, 'avaxc', 'tavaxc') ||
isFamily(coin, 'polygon', 'tpolygon') ||
isFamily(coin, 'arbeth', 'tarbeth') ||
isFamily(coin, 'opeth', 'topeth') ||
isFamily(coin, 'bsc', 'tbsc') ||
isFamily(coin, 'baseeth', 'tbaseeth') ||
isFamily(coin, 'coredao', 'tcoredao') ||
isFamily(coin, 'oas', 'toas') ||
isFamily(coin, 'flr', 'tflr') ||
isFamily(coin, 'sgb', 'tsgb') ||
isFamily(coin, 'wemix', 'twemix') ||
isFamily(coin, 'xdc', 'txdc');

return isEthPure || isEthLike;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use the CoinFamily enum https://github.com/BitGo/BitGoJS/blob/f1a721be2963094c84fa14d7a4dbad22da102094/modules/statics/src/base.ts#L22, I don't think you need to type the test versions either.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need to type the test versions either.
True! I copied the "isFamily" method from some FE utility on OVC but in the description of "getFamily" it clearly states that the family test version of a coin is in fact the family of the prod version so we don't need that check.
Thanks for the catch.
image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm gonna upload an update without the test versions of the coin and using the enum. Although i was surprised when checked the family of ethw as an example and discovered that it's not coming from ETH.
I suppose that it's because ethw is a "wrapper" of ethereum in another chain.

A little preview of the code pending to be uploaded:
image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was added on my last commit, thanks!

Comment on lines +23 to +30
const {
userPub,
backupPub,
walletContractAddress,
recoveryDestinationAddress,
coinSpecificParams,
apiKey,
} = req.body;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use req.decoded

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the change, check it on next commit when re review requested please. Thanks Mohammad!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed on my last commit, thanks!

Comment thread src/masterBitgoExpress/recoveryWallet.ts
@mtexeira-simtlix mtexeira-simtlix force-pushed the WP-4637_recovery_multisig branch from bc0e6bd to 693c2a8 Compare June 12, 2025 19:27
@mtexeira-simtlix
Copy link
Copy Markdown
Contributor Author

Moving from draft -> ready for review.
Still working on yesterdays feedback but I don't wanna push and forget to change the PR state so i'm changing it now (blocked by requested-changes so no risk of merging it by mistake)

@mtexeira-simtlix mtexeira-simtlix marked this pull request as ready for review June 13, 2025 14:17
@mtexeira-simtlix mtexeira-simtlix force-pushed the WP-4637_recovery_multisig branch from 693c2a8 to c0be450 Compare June 13, 2025 15:08
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces end-to-end recovery support for multisig wallets on both the master Express and enclaved Express services, including new routes, handlers, client methods, and test mocks.

  • Added coin classification utilities (isEthLikeCoin, isUtxoCoin, etc.) in coinUtils.ts.
  • Defined and implemented recovery endpoints and handlers for master Express (v1.wallet.recovery) and enclaved Express (v1.multisig.recovery).
  • Introduced KMS key retrieval helper and recovery transaction logic in the enclaved API, along with supporting JSON mocks.

Reviewed Changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/shared/coinUtils.ts New utilities to detect coin families (ETH-like, UTXO, EOS, STX, XTZ)
src/masterBitgoExpress/routers/masterApiSpec.ts API spec and route registration for /wallet/recovery
src/masterBitgoExpress/recoveryWallet.ts handleRecoveryWalletOnPrem implementation for ETH-like recovery
src/masterBitgoExpress/enclavedExpressClient.ts Added recoveryMultisig client method
src/enclavedBitgoExpress/routers/enclavedApiSpec.ts API spec and route for /multisig/recovery
src/api/enclaved/utils.ts retrieveKmsKey helper for fetching keys from KMS
src/api/enclaved/recoveryMultisigTransaction.ts Handler logic for signing and assembling the full recovery transaction
mocks/wallet-recovery/*.json Sample request/response mocks for the recovery flow
Comments suppressed due to low confidence (2)

src/enclavedBitgoExpress/routers/enclavedApiSpec.ts:83

  • Typo in constant name: EnclavedAPiSpec should be EnclavedApiSpec to match project naming conventions.
export const EnclavedAPiSpec = apiSpec({

src/enclavedBitgoExpress/routers/enclavedApiSpec.ts:57

  • The request codec for multisig recovery omits walletContractAddress, but the handler and client expect it. Add this field to the request schema.
const RecoveryMultisigRequest = {

Comment thread src/shared/coinUtils.ts Outdated
isFamily(coin, CoinFamily.BCH) ||
isFamily(coin, CoinFamily.ZEC) ||
isFamily(coin, CoinFamily.DASH) ||
isFamily(coin, CoinFamily.DASH) ||
Copy link

Copilot AI Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two identical checks for CoinFamily.DASH in isUtxoCoin. Remove the duplicate or replace one with the intended family.

Suggested change
isFamily(coin, CoinFamily.DASH) ||

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good bot.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed

Comment thread src/api/enclaved/recoveryMultisigTransaction.ts
}

/**
* Recovery a multisig transaction
Copy link

Copilot AI Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor grammar: change the JSDoc comment to Recover a multisig transaction.

Suggested change
* Recovery a multisig transaction
* Recover a multisig transaction

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

solved!

Copy link
Copy Markdown
Contributor

@mohammadalfaiyazbitgo mohammadalfaiyazbitgo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mainly nits: but lgtm.

Comment thread src/shared/coinUtils.ts
// recoveryDestinationAddress: string;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove commented code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! ty.

unsignedSweepPrebuildTx,
coinSpecificParams,
walletContractAddress,
// recoveryDestinationAddress,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove commented code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed! thanks.

Comment on lines +31 to +32
// TODO: populate coinSpecificParams with things like replayProtectionOptions
// coinSpecificParams type could be "recoverOptions"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this still needed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but not for this PR, still an usefull TODO to left for the immediate follow up on btc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remove it to avoid any confusions and add it later, just in case. Thanks Pranav.

walletContractAddress,
});

const { halfSigned } = halfSignedTx as any;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to typecast to any here? Please use the types, especially since you seem to know the fields you're using

          signingKeyNonce: halfSigned.signingKeyNonce ?? 0,
          backupKeyNonce: halfSigned.backupKeyNonce ?? 0,
          recipients: halfSigned.recipients ?? [],

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need the typecast to any because something on the SDK types isn't finished unfortunately and halfSigned is not recognized as a prop of halfSignedTx (the output of coin.signTransaction) despite that it's there.

image

In @mohammadalfaiyazbitgo initial example code, I found the same any, so I decided to try to remove it and found this ungrateful surprise : (.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that you also got confused with what we're casting @pranavjain97 , the cast is not on halfSigned, but in halfSignedTx, for halfSigned as you say we know that it have those properties you mentioned and there is no cast.

Comment thread src/api/enclaved/utils.ts
// TODO: this function is duplicated in multisigTransactioSign.ts but as hardcoded. Replace that code later with this call (to avoid merge conflicts/duplication)
import { KmsClient } from '../../kms/kmsClient';
import { EnclavedConfig } from '../../types';
export async function retrieveKmsKey({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure this already exists. See kmsClient.ts -> getKey(). Please use that

Copy link
Copy Markdown
Contributor Author

@mtexeira-simtlix mtexeira-simtlix Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi! it's not the same code that's why I left a comment as an explanation to avoid confusions but i must admit that it's kinda short and in the rush we're a proper explanation could be better so let me proceed with that please.
The code in retrieveKmsKey doesn't do the logic inside get, what it does is calling kms.getKey and wrapping the error responses as in the already existing piece of code on signMultisigTransaction.ts:

image

Instead of throwing the piece of code that does this directly in the main flow of the code, I moved it to a function to avoid clutter and let the one reading the function behavior focus on the important parts instead of the error responses.

The comment is also a TODO for latter, the code on signMultisigTransaction.ts was worked by Alex at that time and since I didn't know how much could someone change it, I didn't wanna to start randomly moving things on code non related to my PR to avoid conflicts.

Hope that clarifies a little! Thanks.

Comment on lines +13 to +22
assert(
isMasterExpressConfig(req.config),
'Expected req.config to be of type MasterExpressConfig',
);
const enclavedExpressClient = createEnclavedExpressClient(req.config, coin);
if (!enclavedExpressClient) {
throw new Error(
'Enclaved express client not configured - enclaved express features will be disabled',
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase. All this is not needed anymore. These are checked in the middleware now

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure! sorry, my PR is in review since Friday so I suppose that this got merged before my re-review request and got out of date. I'm gonna rebase. Thanks @pranavjain97 !

@mtexeira-simtlix mtexeira-simtlix force-pushed the WP-4637_recovery_multisig branch from e96785c to bbf2bf4 Compare June 17, 2025 14:52
// Response type for /multisig/recovery endpoint
const RecoveryMultisigResponse: HttpResponse = {
// TODO: Define proper response type for recovery multisig transaction
200: t.any, // the full signed tx
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please type this out

Copy link
Copy Markdown
Contributor

@pranavjain97 pranavjain97 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed offline. Approving to unblock dev.
Followup needed-

  1. Type out t.any in the response and half signed tx.
  2. Making the kms retrieve key consistent.

@mohammadalfaiyazbitgo mohammadalfaiyazbitgo merged commit d09b586 into master Jun 17, 2025
3 checks passed
@mohammadalfaiyazbitgo mohammadalfaiyazbitgo deleted the WP-4637_recovery_multisig branch June 17, 2025 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants