-
Couldn't load subscription status.
- Fork 300
feat(sdk-core): add automatic signature cleanup for Express TSS signing #7256
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
base: master
Are you sure you want to change the base?
Conversation
|
@claude review |
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 fixes an API gap in the Express TSS signing endpoint where re-signing partially signed Full TxRequests would fail due to stale signature shares. The solution adds automatic signature cleanup before signing operations.
- Adds signature share cleanup logic to detect and clean partial signatures before signing
- Updates Express TSS signing endpoint to use the new cleanup method
- Provides comprehensive test coverage for the signature cleanup scenarios
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| modules/sdk-core/src/bitgo/wallet/wallet.ts | Adds ensureCleanSigSharesAndSignTransaction method with signature cleanup logic |
| modules/express/src/clientRoutes.ts | Updates Express TSS signing to use new cleanup method |
| modules/bitgo/test/v2/unit/wallet.ts | Adds comprehensive unit tests for signature cleanup functionality |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| isPartiallySigned = txRequest.transactions.some((tx) => (tx.signatureShares ?? []).length > 0); | ||
| } else if (txRequest.messages && txRequest.messages.length > 0) { | ||
| isPartiallySigned = txRequest.messages.some((msg) => (msg.signatureShares ?? []).length > 0); |
Copilot
AI
Oct 16, 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 partial signature detection logic is duplicated between transactions and messages. Consider extracting this into a helper function to reduce code duplication and improve maintainability.
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.
+1,
can do something like
const signabeResources: { signatureShares: [] } = txRequest.transactions?.length ? txRequest.transactions : txRequest.messages ?? [];
| ): Promise<SignedTransaction | TxRequest> { | ||
| const txRequestId = params.txRequestId || params.txPrebuild?.txRequestId; | ||
|
|
||
| if (txRequestId && this.tssUtils && this.multisigType() === 'tss') { |
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 we also separate the cleaning part in different method. It think it will make the method ensureCleanSigSharesAndSignTransaction more readable.
b8a43f0 to
ae9c08f
Compare
ae9c08f to
f8fcd9e
Compare
| let isPartiallySigned = false; | ||
|
|
||
| if (txRequest.transactions && txRequest.transactions.length > 0) { | ||
| isPartiallySigned = txRequest.transactions.some((tx) => (tx.signatureShares ?? []).length > 0); |
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.
Are we not considering any transaction state for this?
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 saw something similar in wallet-platform also, would there be any case where sigShares are there but state is postSign or something where we should not delete sig shares?
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 state could be readyToCombineShares which means it has been fully signed, but has not been broadcasted yet (the end state of v2.wallet.txrequest.sign is readyToCombineShares, and this is validated in the send api)
f8fcd9e to
71b889c
Compare
| if (isPartiallySigned) { | ||
| await this.tssUtils.deleteSignatureShares(txRequestId); | ||
| } |
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't we always just rebuild/delete sig shares before signing? Or are we avoiding this for latency reasons?
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.
that looks cleaner but we would make additional delete sigshares API call even in case of fresh transaction. If it looks fine to your team then I can make it default for txRequest full
| let isPartiallySigned = false; | ||
|
|
||
| if (txRequest.transactions && txRequest.transactions.length > 0) { | ||
| isPartiallySigned = txRequest.transactions.some((tx) => (tx.signatureShares ?? []).length > 0); |
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 state could be readyToCombineShares which means it has been fully signed, but has not been broadcasted yet (the end state of v2.wallet.txrequest.sign is readyToCombineShares, and this is validated in the send api)
| isPartiallySigned = txRequest.transactions.some((tx) => (tx.signatureShares ?? []).length > 0); | ||
| } else if (txRequest.messages && txRequest.messages.length > 0) { | ||
| isPartiallySigned = txRequest.messages.some((msg) => (msg.signatureShares ?? []).length > 0); |
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.
+1,
can do something like
const signabeResources: { signatureShares: [] } = txRequest.transactions?.length ? txRequest.transactions : txRequest.messages ?? [];
Fixes Express API gap where re-signing partially signed Full TxRequests would fail. The /signtxtss route now always cleans up signature shares for txRequest full before attempting to sign. Changes: - Add ensureCleanSigSharesAndSignTransaction() to Wallet class - Update Express handleV2SignTSSWalletTx() to use new method - Add comprehensive unit tests for signature cleanup logic Ticket: COIN-5989
b17eb7d to
c222e83
Compare
| const txRequestId = params.txRequestId || params.txPrebuild?.txRequestId; | ||
|
|
||
| if (txRequestId && this.tssUtils && this.multisigType() === 'tss') { | ||
| const txRequest = await this.tssUtils.getTxRequest(txRequestId); |
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.
Would it make sense to move this whole logic to backend via a params.rebuild flag in signTransaction call? This way we can abstract the logic for multiple wallet types (future requirement) and control the implementation details.
Fixes Express API gap where re-signing partially signed Full TxRequests would fail. The /signtxtss route now automatically detects and cleans up partial signature shares before attempting to sign.
Changes:
Benefits:
Ticket: COIN-5989