Repository navigation
feat: validate proposed transactions against local payloads - #36
Merged
Merged
Conversation
…zz#5) Add getSafeTxHashes, which computes the EIP-712 domain separator, SafeTx message hash and safeTxHash locally (the triple a hardware wallet shows), and logSafeTxHashes, which logs them as [safe-utils] lines. Add validateProposedTransaction and validateProposedTransactions, which fetch a proposal from the Transaction Service (GET /v2/multisig-transactions/{safeTxHash}/, as api-kit does), compare safe, to, value, data, operation, nonce and the zeroed gas fields with the locally built transaction, and recompute the safeTxHash. They revert with ProposedTransactionMismatch(field, expected, actual) on the first difference. validateProposedTransactionJson checks an already-fetched response, so tests run against a fixture instead of the live service. proposeTransaction now also logs the three hashes after a successful proposal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator
|
Thanks, this covers #5. The field comparisons and hash calculations look good. I’ll merge once CI passes and follow up with more malformed-response tests and README notes about the Safe v1.3+ and zero gas/refund requirements. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #5.
A Ledger or Trezor shows only hashes when signing a Safe transaction. This PR lets a co-signer check, from the same Foundry script that built the transaction, that a proposal on the Transaction Service is the transaction they mean to sign.
Hashes
getSafeTxHashes(safe, chainId, to, value, data, operation, nonce)(pure) andsafe.getSafeTxHashes(to, value, data, operation, nonce)(current Safe and chain) returnSafeTxHashes { domainSeparator, messageHash, safeTxHash }, computed locally rather than with the Safe'sgetTransactionHash. This is the same triple the Cyfrin safe-hash tool and OpenZeppelin's Safe utils show.logSafeTxHashes(hashes)logs them as[safe-utils] domain hash: ...,[safe-utils] message hash: ...and[safe-utils] safeTxHash: ....proposeTransactionnow logs them too after a successful proposal, so the proposer can share them with co-signers.Validation
safe.validateProposedTransaction(safeTxHash, to, value, data, operation, nonce)fetchesGET {apiKitUrl}/v2/multisig-transactions/{safeTxHash}/, the endpoint api-kit'sgetTransactionuses. It compares safeTxHash, safe, to, value, data, operation, nonce, safeTxGas, baseGas, gasPrice, gasToken and refundReceiver, then recomputes the safeTxHash. On the first difference it reverts withProposedTransactionMismatch(field, expected, actual), for example("nonce", "4", "3"). On success it logs the three hashes.safe.validateProposedTransactions(safeTxHash, targets, datas, nonce)rebuilds the MultiSend payload withgetProposeTransactionsTargetAndDataand expects DelegateCall, the same wayproposeTransactionsdoes.safe.validateProposedTransactionJson(json, ...)validates a response that has already been fetched.getProposedTransactionandgetProposedTransactionUrlexpose the fetch and the URL. A non-2xx response reverts withGetProposedTransactionFailed(safeTxHash, status, response).All new functions are additions. No existing signature changed.
Limits
chainId, which is correct for Safe v1.3.0 and later. Older Safes use a domain withoutchainIdand are not supported.Testing
test/SafeValidation.t.soladds 15 tests:hashDomain,hashStruct,hashTypedData).domainSeparator()andgetTransactionHash, for a single call, for a batch, and in a fuzz test with 256 runs.test/fixtures/base-multisig-transaction.json, a real Transaction Service response saved on 2026-10-31. It covers a match, wrong data, wrong nonce, wrong to, wrong operation, wrong Safe, wrong chain, a different safeTxHash,nulldata, and the fetch URL.foundry.tomlgains read-onlyfs_permissionsfor./test/fixtures.data, and an unknown hash reverted with a 404. That check is not in the suite. When I added it, the existing propose tests hit HTTP 429 ("Too many requests per second") in parallel runs.src/stashed, the new tests do not compile. Changing the0x01EIP-712 prefix byte makes 6 of the 15 tests fail.forge testpasses 44/44 (29 before).forge fmt --checkis clean. Checked with forge 1.8.7.🤖 Generated with Claude Code