recovery multisig for eve and mbe#23
Conversation
Discussed/resolved all questions on meet |
8f58dd6 to
478d5e1
Compare
| const unsignedTx = await coin.recover({ | ||
| ...commonRecoveryParams, | ||
| //TODO: it's needed for keycard debugging, the walletPassphrase | ||
| //walletPassphrase: passphrase, | ||
| }); |
There was a problem hiding this comment.
This will use blockchain APIs to build, will need to be in MBE. Enclave will need to accept the unsignedSweepPrebuild
There was a problem hiding this comment.
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!
30647cc to
bc0e6bd
Compare
| // 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)) { |
There was a problem hiding this comment.
i think you can expand this to ethlike they all use AbstractEthLike as the base class iirc.
There was a problem hiding this comment.
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!
| 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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
This was added on my last commit, thanks!
| const { | ||
| userPub, | ||
| backupPub, | ||
| walletContractAddress, | ||
| recoveryDestinationAddress, | ||
| coinSpecificParams, | ||
| apiKey, | ||
| } = req.body; |
There was a problem hiding this comment.
use req.decoded
There was a problem hiding this comment.
Added the change, check it on next commit when re review requested please. Thanks Mohammad!
There was a problem hiding this comment.
Changed on my last commit, thanks!
bc0e6bd to
693c2a8
Compare
|
Moving from draft -> ready for review. |
693c2a8 to
c0be450
Compare
There was a problem hiding this comment.
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.) incoinUtils.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:
EnclavedAPiSpecshould beEnclavedApiSpecto 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 = {
| isFamily(coin, CoinFamily.BCH) || | ||
| isFamily(coin, CoinFamily.ZEC) || | ||
| isFamily(coin, CoinFamily.DASH) || | ||
| isFamily(coin, CoinFamily.DASH) || |
There was a problem hiding this comment.
There are two identical checks for CoinFamily.DASH in isUtxoCoin. Remove the duplicate or replace one with the intended family.
| isFamily(coin, CoinFamily.DASH) || |
| } | ||
|
|
||
| /** | ||
| * Recovery a multisig transaction |
There was a problem hiding this comment.
Minor grammar: change the JSDoc comment to Recover a multisig transaction.
| * Recovery a multisig transaction | |
| * Recover a multisig transaction |
mohammadalfaiyazbitgo
left a comment
There was a problem hiding this comment.
mainly nits: but lgtm.
| // recoveryDestinationAddress: string; | ||
| } |
There was a problem hiding this comment.
remove commented code.
| unsignedSweepPrebuildTx, | ||
| coinSpecificParams, | ||
| walletContractAddress, | ||
| // recoveryDestinationAddress, |
There was a problem hiding this comment.
remove commented code.
There was a problem hiding this comment.
Changed! thanks.
| // TODO: populate coinSpecificParams with things like replayProtectionOptions | ||
| // coinSpecificParams type could be "recoverOptions" |
There was a problem hiding this comment.
is this still needed?
There was a problem hiding this comment.
Yes, but not for this PR, still an usefull TODO to left for the immediate follow up on btc.
There was a problem hiding this comment.
I'll remove it to avoid any confusions and add it later, just in case. Thanks Pranav.
| walletContractAddress, | ||
| }); | ||
|
|
||
| const { halfSigned } = halfSignedTx as any; |
There was a problem hiding this comment.
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 ?? [],
There was a problem hiding this comment.
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.
In @mohammadalfaiyazbitgo initial example code, I found the same any, so I decided to try to remove it and found this ungrateful surprise : (.
There was a problem hiding this comment.
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.
| // 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({ |
There was a problem hiding this comment.
I'm sure this already exists. See kmsClient.ts -> getKey(). Please use that
There was a problem hiding this comment.
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:
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.
| 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', | ||
| ); | ||
| } |
There was a problem hiding this comment.
Rebase. All this is not needed anymore. These are checked in the middleware now
There was a problem hiding this comment.
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 !
c0be450 to
e96785c
Compare
e96785c to
bbf2bf4
Compare
| // 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 |
pranavjain97
left a comment
There was a problem hiding this comment.
Discussed offline. Approving to unblock dev.
Followup needed-
- Type out
t.anyin the response and half signed tx. - Making the kms retrieve key consistent.










Ticket: WP-4637 / WP-4632
Description: