feat(sdk-coin-sol): add verifyTransaction validation for staking authorize intent - #9463
feat(sdk-coin-sol): add verifyTransaction validation for staking authorize intent#9463bitgo-ai-agent-dev[bot] wants to merge 1 commit into
Conversation
61816b4 to
b9f9be7
Compare
b9f9be7 to
c0322c5
Compare
…orize SOL authorize transactions carry no recipients by design, so 'authorize' was added to NO_RECIPIENT_TX_TYPES in WCI-1111 to keep the signing flow working. That left sol.ts:verifyTransaction with no checks at all for these transactions: a compromised server could present a txHex that rotates a stake account's withdraw authority to an attacker key and the client would sign it without noticing. Thread the authorize intent fields through to the coin layer and validate the decoded instruction against them: - sdk-core baseTypes.ts / iBaseCoin.ts: add newWithdrawPublicKey and stakeAccount to PopulatedIntent and TransactionParams - sdk-core recipientUtils.ts: propagate both fields from the intent in resolveEffectiveTxParams, so they reach verifyTransaction via the existing txParams argument without new plumbing in signRequestBase - sdk-coin-sol explainTransactionWasm.ts / transaction.ts: populate explainedTx.stakingAuthorize, preferring the Withdrawer instruction over Staker so the security-critical newWithdrawAddress is not dropped when a tx changes both authorities - sdk-coin-sol sol.ts: validate oldWithdrawAddress against the wallet root address, newWithdrawAddress against the intended newWithdrawPublicKey, and stakingAddress against the intended stakeAccount, whenever those intent fields are present The staker/withdrawer distinction matters because verifyTransaction always explains via the legacy Transaction.explainTransaction path, never the WASM one. Neither instruction parser surfaces Solana's stakeAuthorizationType, so a Withdrawer-type instruction is identified by its custodian key; a staker-only authorize populates the staking authority fields and leaves the withdraw fields empty rather than reporting staker addresses as withdraw addresses. The authorize checks deliberately fall through to the rest of verifyTransaction rather than returning early, so authorize transactions remain subject to the fee payer, durable nonce, memo and recipient checks. TICKET: CHALO-1294
c0322c5 to
f3d445e
Compare
|
@claude Review the changes for security, correctness and adversarial effects/regression |
|
Claude finished @maheshbitgo's task —— View job Security and Correctness ReviewI've completed a comprehensive security review of the Solana staking authorize transaction validation changes. Here's my detailed analysis: Todo List:
Security Assessment: ✅ APPROVEDThis PR effectively addresses the Trail of Bits audit finding (TOB-BITGOEDMPC-1) by implementing comprehensive validation for Solana staking authorize transactions. Key Security Strengths1. Critical Vulnerability Mitigation ✅
2. Robust Parameter Validation ✅// Validates critical fields when present
if (txParams.newWithdrawPublicKey && authorizeParams.newWithdrawAddress !== txParams.newWithdrawPublicKey) {
throw new Error('StakingAuthorize newWithdrawAddress does not match intended newWithdrawPublicKey...');
}
3. Secure Intent Parameter Propagation ✅
4. Enhanced explainTransaction Logic ✅// Prioritizes Withdrawer instruction over Staker for stakingAuthorize summary
const isWithdrawerAuthorize = !!(
authorizeInstruction.params.newWithdrawAddress || authorizeInstruction.params.custodianAddress
);
Attack Vector Analysis✅ Malicious Server Attack - MITIGATED
✅ Parameter Substitution - MITIGATED
✅ Staker-Only Transaction Bypass - HANDLED
Test Coverage Analysis ✅The test suite comprehensively covers all security scenarios:
Minor Observations1. Code Quality ✅
2. Backward Compatibility ✅
3. Performance ✅
Potential Edge Cases (All Handled)
ConclusionThis implementation successfully closes the security gap identified in the Trail of Bits audit. The validation logic is robust, well-tested, and handles all identified attack vectors while maintaining backward compatibility. Recommendation: APPROVE - This PR significantly improves the security posture of Solana staking authorize transactions without introducing regressions. Job Run: Link |
davidkaplanbitgo
left a comment
There was a problem hiding this comment.
BTC related changes look fine
What
isStakingAuthorizeTxbranch insol.ts:verifyTransactionthat validates the decoded SOL staking authorize instruction against the caller's intent fields:oldWithdrawAddressmatches the wallet root address (Withdrawer authority only; skipped for staker-only txs where the field is'')newWithdrawAddressmatchestxParams.newWithdrawPublicKeyfrom the intent (when set)stakingAddressmatchestxParams.stakeAccountfrom the intent (when set)resolveEffectiveTxParamsinsdk-coreto propagatenewWithdrawPublicKeyandstakeAccountfromtxRequest.intentintotxParamsso the fields reachverifyTransactionthrough the TSS signing flownewWithdrawPublicKeyandstakeAccounttoPopulatedIntent(baseTypes.ts) andTransactionParams(iBaseCoin.ts)explainTransactionfor both the WASM (tsol) and non-WASM (sol) paths to populatestakingAuthorizewith the Withdrawer instruction's fields (the security-critical one), not the Staker instruction's, when a standard two-instruction authorize tx is presentnewWithdrawPublicKeysubstitution, wrongstakeAccount, wrongoldWithdrawAddress, non-WASM explainTransaction path, andrecipientUtilspropagationWhy
'authorize'was added toNO_RECIPIENT_TX_TYPES(WCI-1111 / PR fix(sdk-core): wire resolveEffectiveTxParams into EddsaMPCv2Utils (WCI-1111) #9394) becauseSolAuthorizeIntenthas emptyrecipientsby design. Without coin-layer validation, a compromised BitGo server could present aStakingAuthorizetxHex that rotates the stake account's withdraw authority to an attacker-controlled key, and the client would sign it with no checks.Test plan
yarn unit-test --scope @bitgo/sdk-coin-sol— 646 tests passyarn unit-test --scope @bitgo/sdk-core(recipientUtils tests) — 32 tests passsdk-coin-solandsdk-corenewWithdrawPublicKeyandstakeAccountpassesnewWithdrawPublicKeycorrectly throwsstakeAccountcorrectly throwsoldWithdrawAddress(wallet root mismatch) correctly throwsoldWithdrawAddress) does not block legitimate staker-authority changesTicket: CHALO-1294