Repository navigation
v0.2.1
·
33 commits
to main
since this release
Immutable
release. Only release title and notes can be modified.
Security/correctness patch. Closes a replay-protection bypass on the EIP-3009 path, tightens signature v-byte validation against silent hex coercion, and fixes a misleading production recommendation around Psr16NonceStore. Tests pass on the CI matrix (PHP 8.2 / 8.3 / 8.4 × prefer-stable / prefer-lowest).
Bug fixes
PaymentEnforcerreplay gate was bypassable via injectedpayload.authorization—guardReplay()keyed on the presence of an EIP-3009payload.authorizationblock, which Permit2/Upto'sverifyShape()does not reject as an extra payload key. A caller could ship a realpermit2Authorization(oruptoAuthorization) alongside a forged top-levelauthorizationwith attacker-chosen(from, nonce); the in-process store would claim the forged tuple and the real permit/witness signature stayed replayable. The gate now keys on the matched challenge (server-controlled):scheme === "exact"ANDnetworkstarts witheip155:ANDextra.assetTransferMethodnormalized to"eip3009"(matchingExactScheme::verifyShape's normalization, including non-string fallbacks). Permit2/Upto/Erc7710/Stellar/Svm paths route to the facilitator for nonce uniqueness as before.SignatureVerifieraccepted any bytes for thevfield —hexdec('gg')silently coerces to0, so a malformed trailingvon a 65-byte signature would pass through as recovery id 0 and proceed to public-key recovery against the wrong byte instead of being rejected. Addedctype_xdigitvalidation on the full hex string up front and replaced the implicit$v >= 27 ? $v - 27 : $varithmetic with an explicitmatchover{0, 1, 27, 28}. EIP-155 chainId-offset values (29/30/etc., used for raw transactions, not typed data) now fail with a clear error rather than tripping a downstream "invalid recovery id". Recovered public-key hex is also explicitly validated beforeKeccak::hash.Psr16NonceStorecannot satisfyNonceStoreContractatomicity — PSR-16 has no add-if-absent primitive; thehas() + set()implementation has a small race window that lets two concurrent workers both claim the same(network, from, nonce)and both settle. The contract's docblock requires atomic SETNX-EX semantics, but the in-package PSR-16 adapter never delivered them. Class is unchanged in behavior but its docblock now states it is fit for tests and single-worker dev only. The README pivots the production recommendation away fromPsr16NonceStoreand towardLaravelNonceStore(insandermuller/laravel-x402, backed byCache::add()) or a RedisSET key value NX EX ttladapter implementingNonceStoreContractdirectly. TheSurfacetable now lists the contract, not the adapter, as the production entry point.
Full Changelog: 0.2.0...0.2.1