Skip to content

fix: normalize zero-tuple fee payer placeholder for Rust/alloy interop - #104

Closed
brendanjryan wants to merge 2 commits into
mainfrom
fix/cosign-fee-payer-zero-tuple
Closed

fix: normalize zero-tuple fee payer placeholder for Rust/alloy interop#104
brendanjryan wants to merge 2 commits into
mainfrom
fix/cosign-fee-payer-zero-tuple

Conversation

@brendanjryan

Copy link
Copy Markdown
Collaborator

Problem

When a Rust/alloy client sends a fee-payer-sponsored transaction, it encodes the placeholder as Some(Signature::default()) — an RLP list of zeros [0x80, 0x80, 0x80] (c3 80 80 80). This is the spec-compliant encoding.

However, ox's TxEnvelopeTempo.deserialize only recognises its own 0x00 sentinel and parses the zero-tuple as a real signature object { r: 0n, s: 0n, yParity: 0 }. This causes getSignPayload to produce a 4-byte RLP list instead of the 1-byte 0x00 placeholder, breaking:

  1. Sender recovery (wrong address recovered from mismatched hash)
  2. Fee payer sign payload computation
  3. Fee payer signature verification on the Tempo node
  4. Node falls back to charging the sender (who has 0 balance) → insufficient funds

Fix

Adds a shared cosignFeePayer helper (src/tempo/internal/cosignFeePayer.ts) that:

  1. Deserializes via ox and normalizes zero-tuple (r === 0n && s === 0n) → null
  2. Recovers sender from the user's signature (handles all 4 sig types)
  3. Computes fee payer sign payload with 0x78 magic byte
  4. Re-serializes with the real fee payer signature

Replaces the previous viem.signTransaction approach in Charge.ts and Chain.ts.

Upstream

The root cause is in ox's TxEnvelopeTempo.deserialize — it should recognise the zero-valued signature tuple as a placeholder. Will file upstream fix at wevm/ox.

Spec Reference

Rust/alloy encodes the fee payer placeholder as Some(Signature::default()),
which RLP-serializes to [0x80, 0x80, 0x80]. ox's deserialize only recognizes
its own 0x00 sentinel and parses the zero-tuple as a real signature object
{ r: 0n, s: 0n }, causing getSignPayload to produce a different hash than
what the sender signed over. This breaks sender recovery and fee payer
co-signing for cross-client transactions.

Adds a shared cosignFeePayer helper that:
1. Deserializes via ox and normalizes zero-tuple → null
2. Recovers sender from the user's signature (all 4 sig types)
3. Computes fee payer sign payload with 0x78 magic byte
4. Re-serializes with the real fee payer signature

Replaces the previous viem signTransaction approach in Charge and Chain,
which re-serialized the transaction and could produce mismatched payloads.
@pkg-pr-new

pkg-pr-new Bot commented Feb 18, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/wevm/mppx@104

commit: b890712

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant