Repository navigation
Description
Sub-issue of #46 (PRD-02). De-risks the EIP-7702 refusal path (#46 risk R1-b) and the shared 7702-auth chokepoint cross-linked to #33. Throwaway spike — output is a decision + test vectors, not merged code. This issue is the complete, self-contained charter: point a fresh agent at it.
Why this spike exists
#46 must "refuse EIP-7702 delegation by default" (DELEGATION_REFUSED) and show a distinct alarming screen. But a 7702 authorization is neither EIP-191 (personal_sign) nor EIP-712 (eth_signTypedData_v4) — it's a distinct (chain_id, address, nonce) tuple signed over the 0x05 magic. So it's unknown how one even reaches PRD-02's signing surface, which means the delegation_refused acceptance test has no defined trigger yet. #33 (session keys) is the same primitive's allow path; per the #46↔#33 cross-link they must share one chokepoint, not two that drift.
Context you can trust (already verified — don't re-litigate)
- ✅ No new dependency. A throwaway probe compiled clean (EXIT=0):
use alloy::eips::eip7702::Authorization; // { chain_id: U256, address: Address, nonce: u64 } let h = Authorization { chain_id, address, nonce }.signature_hash(); // keccak256(0x05 ‖ rlp([chain_id,address,nonce]))
alloy-eip7702is already inCargo.lockviaalloy-eips; theeipsfeature is on (crates/deckard-core/Cargo.toml:38). Usealloy::eips::eip7702::{Authorization, SignedAuthorization}directly — do not add a dep. - The shape we refuse: the tuple
(chain_id, address, nonce)signed overkeccak256(0x05 ‖ rlp([chain_id, address, nonce])), riding in a type-4 (SET_CODE,0x04) txauthorization_list. The auth is signed off-chain — that off-chain signature is what a malicious dapp tries to extract. - EIP-7702 session keys v1: chain-enforced per-tx cap + expiry + allowlist, no bundler #33's frozen invariants (the chokepoint must carry these; this spike only exercises the refuse subset): never sign
chain_id == 0; delegate MUST be on a hardcoded, versioned audited-delegate allowlist (user/dapp-supplied delegates rejected); self-sponsored grants sign auth nonce +1; un-delegation toaddress(0)clears code not storage. The nonce+1 / allow path is EIP-7702 session keys v1: chain-enforced per-tx cap + expiry + allowlist, no bundler #33, out of scope here. - anvil scaffolding exists (
crates/deckard-signerd/tests/common/mod.rs:148-221:start_anvil()/start_anvil_fork()); 7702 needs--hardfork prague. Skip-gracefully when anvil is absent (seeanvil_e2e.rs:33). - References:
spikes/eip1193-railgun/andspikes/helios-walkaway/— reuse their standalone-[workspace]Cargo.tomlshape + gitignore pattern.
The question to settle
Through PRD-02's signing surface (SignMessage::PersonalSign / SignMessage::TypedDataV4), can a 7702 auth reach the daemon — and in what exact representation? Then: what's the minimal backend-neutral predicate that refuses it (recognize 0x05; chain_id == 0 always; delegate ∉ allowlist always; refuse-by-default), and where does it live so #46 + #33 share it?
Tasks (in order)
- Pin the alloy 7702 surface —
Authorization,SignedAuthorization,signature_hash(), RLP encoding,0x05magic, at the locked version. Write down the exact preimage. - Survey the trigger shape (the crux) — how, if at all, does a dapp deliver a 7702 auth for signing via standard provider methods? Confirm it's not EIP-191 / EIP-712; document any experimental
wallet_*/eth_signAuthorization-style methods + their request JSON. Conclude with one of:- (a) No standard path → PRD-02's refusal is a smuggle guard: detect a
0x05-magic preimage (or raw 32-byte hash) presented throughpersonal_sign/eth_signand refuse. - (b) A method exists → PRD-02 needs an explicit
SignMessage::Authorization7702 { … }wire variant — specify its fields.
- (a) No standard path → PRD-02's refusal is a smuggle guard: detect a
- Prove the mechanism on anvil (
--hardfork prague): sign anAuthorization, send the type-4 tx, assert the EOA gains code (eth_getCodenon-empty), then un-delegate toaddress(0)and assert code clears. (Seeds EIP-7702 session keys v1: chain-enforced per-tx cap + expiry + allowlist, no bundler #33's tests later. Uses anvil dev keys only — no mainnet, no real funds.) - Build + prove the refusal predicate — throwaway
refuse_7702_authorization(auth, allowlist) -> Decision+ a vector table:chain_id == 0→ refuse; delegate ∉ empty allowlist → refuse; a MetaMask-style delegate → still refuse; a Porto-style delegate → still refuse. Prove the verdict does not depend on which backend's delegate is used (backend-neutrality). - Sketch the chokepoint API + recommend the trigger — propose the public surface of
deckard-core/src/eip7702_auth.rs(is_authorization(...),refuse(...), a versioned emptyAUDITED_DELEGATES), naming what PRD-02: Clear-signing v2 + message-signing intents #46 calls (refuse-all) vs what EIP-7702 session keys v1: chain-enforced per-tx cap + expiry + allowlist, no bundler #33 later adds (allowlist contents + allow path). Recommend thedelegation_refusedtrigger shape (2a vs 2b). - Emit
vectors.jsonof signed authorizations + expected refusal verdicts (mirrorcrates/deckard-signerd/tests/fixtures/cow/vectors.json) — seeds PRD-02: Clear-signing v2 + message-signing intents #46'sdelegation_refusedacceptance test.
Constraints
- Runnable spike code (if any) lands as a standalone crate at
spikes/eip7702-refusal/with its own[workspace];.gitignoretarget/+Cargo.lock. Do not edit anything undercrates/or the app. - Refuse/read only — never sign a real 7702 authorization with a funded key.
- No allowlist contents, no
nonce+1/grant logic, no UI — those are EIP-7702 session keys v1: chain-enforced per-tx cap + expiry + allowlist, no bundler #33 / PRD-02 proper. - Never log or
Debug-print a key or signature (Zeroizing).
Done when
Trigger answered (a vs b) with the exact request shape and a YES/NO on "does a 7702 auth reach PRD-02's surface via standard methods?"; a runnable anvil bin that sets and clears a delegation; vectors.json; a short report + the eip7702_auth.rs API sketch with the #46-refuse-all vs #33-allow-path split. No production code merged into crates/.
Report format (return this)
<spike_report>
<verdict>trigger: NO-STANDARD-PATH (smuggle guard) / METHOD-EXISTS (needs wire variant)</verdict>
<trigger_shape><!-- exact request JSON / preimage we detect + refuse --></trigger_shape>
<refusal_predicate_backend_neutral>YES / NO + evidence</refusal_predicate_backend_neutral>
<chokepoint_api><!-- eip7702_auth.rs public surface; #46 refuse-all vs #33 allow-path --></chokepoint_api>
<anvil_proof><!-- delegate set + cleared, with the cmds --></anvil_proof>
<no_new_dep>confirmed (alloy eips) / NEW DEP NEEDED (why)</no_new_dep>
<what_worked/>
<what_failed/>
<recommendation><!-- one paragraph for #46's delegation_refused test + the shared module --></recommendation>
<artifacts><!-- spike crate path + how to run + vectors.json --></artifacts>
</spike_report>
Activity