fix: 7702 authorization/legacy-tx signature encoding and Simple7702 revoke guard - #220
Conversation
createAndSignLegacyRawTransaction encoded r and s via getBytes on the 32-byte zero-padded hex from the signer, so any signature whose r or s has a leading zero byte (~0.8% of transactions, deterministic per key/payload under RFC 6979) produced a raw transaction that nodes reject with 'rlp: non-canonical integer (leading zero bytes)'. Encode both through bigintToBytes like the EIP-7702 path already does. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
EIP-7702 requires s <= n/2 in authorization tuples; nodes silently skip high-s tuples during set-code processing, so a signer callback returning a standard high-s signature produced an authorization that never applied the delegation with no error surfaced. parseRawSignature now converts to the complementary low-s signature (s' = n - s, flipped parity), matching the private-key path which already signs with lowS. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
createRevokeDelegationTransaction fetched delegation status and the tx nonce for accountAddress but signed with eoaPrivateKey unconditionally. With a mismatched key the broadcast transaction's sender is the other EOA, revoking the signer's delegation instead of the account the method checked and reported on. Add the same up-front key/address check the Calibur revoke path already has. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Read-before-use ordering; no behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…wing Link the EIP-2 s-value bound and malleability rationale and the EIP-7702 per-tuple validation/skip behavior, and note that generic ECDSA signers (KMS/HSM/WebCrypto) produce high-s by design, so rejection would fail nondeterministically on valid signatures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe change canonicalizes legacy ECDSA signature encoding, normalizes EIP-7702 signatures to low-s values, adds coverage for both behaviors, and validates that revoke-delegation private keys match the account address. ChangesSignature and delegation correctness
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/utils7702.ts (1)
483-506: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winLow-s normalization logic is correct; consider bounding r/s before trusting external signer output.
The flip (
s = n - s,yParity = 1 - yParity) and the stricts > SECP256K1_HALF_Nboundary correctly implement the EIP-2/EIP-7702 canonicalization rule. One gap:rand the pre-normalizationsfrom an externalsignercallback are never checked against(0, n); a malformed compact/standard signature (e.g.s >= n) would still be "normalized" and returned as a well-formed-looking tuple instead of being rejected early, deferring the failure to silent on-chain tuple-skipping described in the docstring.Optional validation
const r = BigInt(`0x${sig.slice(0, 64)}`); + if (r <= 0n || r >= SECP256K1_N) { + throw new RangeError(`invalid signature r value: out of range`); + } let yParity: 0 | 1; let s: bigint;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/utils7702.ts` around lines 483 - 506, Validate the parsed r and pre-normalization s values in the signature parsing flow before applying low-s normalization: both must be greater than zero and less than SECP256K1_N, otherwise reject the external signer output. Preserve the existing yParity handling and complementary low-s flip for valid values, using the parsed r symbol alongside s.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/utils7702.ts`:
- Around line 483-506: Validate the parsed r and pre-normalization s values in
the signature parsing flow before applying low-s normalization: both must be
greater than zero and less than SECP256K1_N, otherwise reject the external
signer output. Preserve the existing yParity handling and complementary low-s
flip for valid values, using the parsed r symbol alongside s.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: fcf52971-4b18-40ea-acfa-b66f57c6d555
📒 Files selected for processing (3)
src/account/simple/Simple7702Account.tssrc/utils7702.tstest/utils7702.test.js
|
LGTM |
Signature-encoding fixes on the EIP-7702 paths.
createAndSignLegacyRawTransactionencoded r and s from the 32-byte zero-padded hex, so any signature whoser or s has a leading zero byte (~0.8% of transactions, deterministic per key/payload under RFC 6979) produced
rlp: non-canonical integer (leading zero bytes). Both are now encodedas minimal RLP integers via
bigintToBytes, like the EIP-7702 path.s <= n/2in authorization tuples, and nodes silently skip high-s tuples during set-code processing — a signer callback returning a standard high-s signature failed with no error surfaced.parseRawSignaturenow normalizes to the complementary low-s form (s' = n − s,flipped parity), matching the private-key path.
createRevokeDelegationTransactionfetched delegation status and the nonce foraccountAddressbut signed witheoaPrivateKeyunconditionally — with a mismatched key, the broadcast transaction revoked the signer's delegation instead. The same up-front key/address check the Calibur revokepath has is now applied.
Also moves the secp256k1 constants above
parseRawSignaturefor read-before-use ordering (no behavior change).Summary by CodeRabbit
Bug Fixes
ssignatures for EIP-7702 authorizations, improving compatibility and validation.Tests
snormalization.