Fix multi_sig sending a different vault address than it signs - #314
Open
BhariGowda wants to merge 1 commit into
Open
Fix multi_sig sending a different vault address than it signs#314BhariGowda wants to merge 1 commit into
BhariGowda wants to merge 1 commit into
Conversation
Exchange.multi_sig takes a vault_address argument and passes it to
sign_multi_sig_action, which feeds it into action_hash, so the outer
signature commits to it. _post_action then ignored it and put
self.vault_address in the payload instead. Whenever the two differ the
server recomputes the multi-sig action hash over a different vault
address, recovers a different outer signer, and rejects the action.
Two configurations are affected:
Exchange(vault_address=None) + multi_sig(vault_address=V)
signs V, posts null
Exchange(vault_address=V) + multi_sig()
signs null, posts V
Only the case where both happen to be the same value works today, which
is why the existing examples (both None) are unaffected.
Resolved the vault address once at the top of multi_sig, falling back to
self.vault_address when the argument is omitted, and threaded it through
to _post_action so the payload states what was signed. _post_action
keeps its previous behaviour for every other caller: the new parameter
defaults to self.vault_address, and the usdClassTransfer/sendAsset
exception is unchanged.
Added a test that recovers the outer signer from the posted payload the
way a verifier would, across all four combinations of constructor and
argument vault address. It fails on master for the two mismatched cases.
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.
Exchange.multi_sigtakes avault_addressargument and passes it tosign_multi_sig_action, which feeds it intoaction_hash, so the outer signature commits to it._post_actionthen ignored that argument and wroteself.vault_addressinto the payload instead.Whenever the two differ, the server recomputes the multi-sig action hash over a different vault address, recovers a different outer signer, and rejects the action. Two configurations are affected:
Exchange(vault_address=)multi_sig(vault_address=)NoneVVnullVNonenullVVVVVNoneNonenullnullOnly the last two work. The existing examples pass
Noneon both sides, which is why this has not shown up there.Recovering the outer signer from the payload that actually goes out, on master:
The recovery above does not go through the SDK's own
action_hashorrecover_user_from_user_signed_action. It is reimplemented directly from the wire format, msgpack of the action with itstypetag removed, then the nonce, vault marker and expiry bytes, then keccak, then theHyperliquidTransaction:SendMultiSigEIP-712 struct, then ecrecover. That was deliberate: the conclusion holds even if every recovery and verification helper elsewhere in this repo is wrong, so a bug in the SDK's own verification path cannot be what produces the apparent mismatch. Both implementations agree on the two rows that already work, which is what makes the two mismatching rows meaningful.The same resolution order shows up in the third-party TypeScript SDK
nktkas/hyperliquid. ItsexecuteL1Actionresolves one value,options?.vaultAddress ?? config.defaultVaultAddress, and uses that single value both for the signature and for thevaultAddressfield merged into the request body. That is the behaviour this change givesmulti_sig.The fix resolves the vault address once at the top of
multi_sig, falling back toself.vault_addresswhen the argument is omitted, and threads it through to_post_actionso the payload states what was signed._post_actionis unchanged for its other callers: the new parameter defaults toself.vault_address, and theusdClassTransfer/sendAssetexception is untouched. To check that rather than assert it, I captured the outgoing payload for 35 methods against both a plainExchangeand one constructed with a vault address, with the nonce clock frozen so the runs are comparable. All 70 payloads are byte-identical before and after this change.The test recovers the outer signer from the posted payload the way a verifier would, across all four combinations above. It fails on master for the two mismatched rows.
Ran
pytest tests/signing_test.py(14 passed) plus black, isort and flake8 with the repo's configured settings.