I think that RIP-7212 has a unfortunate flaw–as specified after this update from two weeks ago and deployed on Polygon PoS: it now returns empty data for an invalid signature.
Why is this an issue?
It means there’s no clean way to implement a fallback
Contracts cannot distinguish between (invalid signature) and (7212 not deployed). On a chain without the precompile, calling 0x100
with any calldata will return (success=true, return value=0x).
On a chain with the precompile, an invalid signature will return exactly the same.
You can still implement a fallback, but the fallback contract will run on invalid signatures even on chains that have the precompile. In practice, this means that invalid signatures will often fail with an out-of-gas instead of a descriptive revert message.
This was changed after Final
Both EIP-7212 and RIP-7212 originally specified that the return value should always be 32 bytes.
Here’s my reconstruction of what happened:
- 2023 Summer - EIP-7212 created, specifying 32-byte (uint) return value.
- 2023 Aug - we (Daimo) implement a fallback contract matching the spec.
- 2023 Oct - zkSync implements 7212, also following the spec: returns uint 0 or 1.
- 2023 Nov - PR in Polygon to implement 7212. This had a bug, where invalid signatures would return empty data. This behavior deviates from zkSync, Daimo, & EIP–but wasn’t caught at the time.
- 2023 Dec 23 - RIP-7212 created. It specifies a 32-byte return value, same as the EIP it replaced.
- 2024 Jan 8 - RIP-7212 marked Final
- 2024 Mar 21 - PIP-27 / RIP-7212 deployed on Polygon PoS, with invalid signatures returning empty data. Bug is now in production.
- 2024 Apr 25 - RIP-7212 spec changed retroactively to match the deployed implementation. Instead of
Output data: 32 bytes of result data and error
, it now specifies a 32-byte uint(1) for valid signatures, and empty data for invalid.
I’m not sure if this is worth fixing, given that it’s now live on Polygon, but it warrants a retrospective on how we can avoid this kind of outcome in the future.
I propose a rule that no new precompile, going forward, should return empty data, since this makes a clean fallback implementation impossible.
Finally, I’d challenge the community to come up with a good process to verify new RIP implementations before they ship. I don’t think this story would’ve happened with an EIP: L1 client implementations have a lot of careful eyes on them. RIPs, by contrast, will land in production on the currently-fastest-shipping L2. This can happen in just a few weeks, as it did here. Once in production, if there are any discrepancies, it’s more feasible to update the spec than the implementation, locking them in forever.