-
Notifications
You must be signed in to change notification settings - Fork 0
feat: sign eddsa recovery transaction #65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
de845fb to
cbe8005
Compare
cbe8005 to
3a3ea85
Compare
There was a problem hiding this 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 adds support for EdDSA recovery transactions, introducing a new TSS recovery flow alongside the existing multi-signature recovery mechanism. The changes enable signing recovery transactions for EdDSA-based coins (Solana, Near, Sui, Ada, Dot) through a new MPC endpoint.
- Implements EdDSA recovery transaction signing functionality with TSS keyshare support
- Adds new API endpoint
/api/{coin}/mpc/recoveryfor EdDSA recovery operations - Restructures existing recovery API to support both TSS and traditional multi-signature recovery modes
Reviewed Changes
Copilot reviewed 12 out of 15 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/shared/coinUtils.ts | Adds utility functions for Solana coin detection and EdDSA algorithm checking |
| src/enclavedBitgoExpress/routers/enclavedApiSpec.ts | Defines new MPC recovery endpoint with request/response types and handler routing |
| src/api/master/routers/masterApiSpec.ts | Updates recovery wallet API structure to support both TSS and multi-signature recovery parameters |
| src/api/master/handlers/recoveryWallet.ts | Implements dual recovery flow logic handling both EdDSA TSS and traditional multi-signature recovery |
| src/api/master/handlers/recoverEddsaWallets.ts | New module for coin-specific EdDSA wallet recovery with dynamic imports |
| src/api/master/clients/enclavedExpressClient.ts | Adds MPC recovery client method with transaction format handling |
| src/api/enclaved/mpcFinalize.ts | Updates signing material structure for user vs backup key differentiation |
| src/api/enclaved/handlers/signEddsaRecoveryTransaction.ts | New handler implementing EdDSA signature generation with TSS support |
| src/tests/api/master/recoveryWallet.test.ts | Updates test structure to match new API parameter organization |
| src/tests/api/master/musigRecovery.test.ts | Updates test cases and adds TSS recovery validation test |
| package.json | Adds EdDSA coin SDK dependencies and version resolution |
| masterBitgoExpress.json | Updates OpenAPI specification for new recovery API structure |
| recoveryDestination, | ||
| apiKey: params.apiKey, | ||
| }); | ||
| console.log('Unsigned sweep tx'); |
Copilot
AI
Jul 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Console.log statements should not be used in production code. Consider using a proper logging framework or removing this debug output.
| apiKey: params.apiKey, | ||
| }); | ||
| console.log('Unsigned sweep tx'); | ||
| console.log(JSON.stringify(unsignedSweepPrebuildTx, null, 2)); |
Copilot
AI
Jul 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Console.log statements should not be used in production code. Consider using a proper logging framework or removing this debug output.
|
|
||
| return { txBuilder, publicKey }; | ||
| } catch (error) { | ||
| console.error(error); |
Copilot
AI
Jul 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Console.error should be replaced with a proper logging framework for consistency with the rest of the codebase.
| console.error(error); | |
| logger.error(error); |
| throw new Error('Invalid user public key format'); | ||
| } else if (!sdkCoin.isValidPub(backupPub)) { | ||
| throw new Error('Invalid backup public'); | ||
| throw new Error('Invalid backup public key format'); |
Copilot
AI
Jul 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message should be 'Invalid backup public key format' to be consistent with the user key error message format.
| userPub: string; | ||
| backupPub: string; | ||
| apiKey: string; | ||
| coinSpecificParams: any; |
Copilot
AI
Jul 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using 'any' type for coinSpecificParams reduces type safety. Consider defining a proper interface or union type for coin-specific parameters.
| coinSpecificParams: any; | |
| coinSpecificParams: CoinSpecificParams; |
| coinFamily: CoinFamily, | ||
| signableHex: string, | ||
| accountId: string, | ||
| ): Promise<{ txBuilder: any; publicKey: string }> { |
Copilot
AI
Jul 11, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using 'any' type for txBuilder reduces type safety. Consider defining a proper interface or union type for transaction builders.
pranavjain97
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work! Some comments
| 'v1.mpc.recovery': { | ||
| post: httpRoute({ | ||
| method: 'POST', | ||
| path: `/api/{coin}/mpc/recovery`, | ||
| request: httpRequest({ | ||
| params: { | ||
| coin: t.string, | ||
| }, | ||
| body: RecoveryEdDSARequest, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is the body specific to EDDSA?
| }), | ||
| }; | ||
|
|
||
| const RecoveryEdDSARequest = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
woudn't it be similar for ECDSA?
| const { recoveryDestination, userKey } = commonRecoveryParams; | ||
| try { | ||
| const unsignedSweepPrebuildTx = await recoverEddsaWallets(bitgo, sdkCoin, { | ||
| bitgoKey: userKey, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would change userKey to call it commonKeychain so it isnt misleading
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lol that's how they decided to name the field on the sdk. Would just mean more transforming of the parameters. I can do it in a follow up.
| if ('txRequests' in tx && Array.isArray(tx.txRequests)) { | ||
| // MPCTxs format | ||
| const firstRequest = tx.txRequests[0]; | ||
| if (firstRequest && firstRequest.transactions && firstRequest.transactions[0]) { | ||
| const firstTx = firstRequest.transactions[0]; | ||
| txRequest.signableHex = firstTx.unsignedTx?.serializedTx || ''; | ||
| txRequest.derivationPath = firstTx.unsignedTx?.derivationPath || ''; | ||
| } | ||
| } else if ('signableHex' in tx) { | ||
| // MPCTx format | ||
| txRequest.signableHex = tx.signableHex || ''; | ||
| txRequest.derivationPath = tx.derivationPath || ''; | ||
| } else if (Array.isArray(tx) && tx.length > 0) { | ||
| // MPCSweepTxs format | ||
| const firstTx = tx[0]; | ||
| if (firstTx && firstTx.transactions && firstTx.transactions[0]) { | ||
| const transaction = firstTx.transactions[0]; | ||
| txRequest.signableHex = transaction.unsignedTx?.signableHex || ''; | ||
| txRequest.derivationPath = transaction.unsignedTx?.derivationPath || ''; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add comments in which scenario are which formats used? I'm surprised it differs so much just between MPC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this to a helper. Like prepareUnsignedSweepTxRequest
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The client is already too big; we need to start modularizing it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can do it in a follow up.
| txRequest.signableHex = tx.signableHex || ''; | ||
| txRequest.derivationPath = tx.derivationPath || ''; | ||
| } else if (Array.isArray(tx) && tx.length > 0) { | ||
| // MPCSweepTxs format |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
arent the above two sweeps as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
they are, just different transaction formats that are returned by recovery
| const hdTree = await Ed25519Bip32HdTree.initialize(); | ||
| const MPC = await Eddsa.initialize(hdTree); | ||
|
|
||
| const accountId = MPC.deriveUnhardened(request.commonKeychain, request.derivationPath).slice( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is this accountId?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/BitGo/BitGoJS/blob/52c248404bdfadf4b285f46e9585909446824f33/modules/sdk-coin-sol/src/sol.ts#L742 something like a public key I guess?
| accountId, | ||
| ); | ||
|
|
||
| const txBuilder = builder; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: prolly not needed
ac800dc to
ff6ecf8
Compare
ff6ecf8 to
88eac6b
Compare
No description provided.