Author: Muhammad Zidan Fatonie (@mzf11125), Faisal Firdani (@zexoverz), Maulana Asykari Muhammad (@WeissCurry), Venkata ramana Komari (@Venkat5599)
ERC number: 1953, assigned by the pull request number convention.
Proposal: Add ERC: Unclonable Agent Execution Credentials by mzf11125 · Pull Request #1953 · ethereum/ERCs · GitHub (draft)
Reference Implementation: GitHub - mzf11125/unclonable-agent-execution-credentials · GitHub
Update Log
- 2026-08-05: Initial idea draft opened for discussion on this thread.
- 2026-08-06:
chainIdremoved from the nullifier preimage. Split into a chain independent nullifier and a chain bound capability commitment. - 2026-08-11: Design agreed for implementation with issuance binding, two sided observability, and at most once with no ordering.
- 2026-08-16: Coupled Guard merged into the reference implementation, together with the adversarial vectors contributed on this thread.
- 2026-08-17: Coupling promoted from a recommended profile to the normative core. Salt removed from the on chain struct. Relayed submission removed. ERC PR opened as a draft and domain separation tags settled to the assigned number.
External Reviews
- 2026-08-05: @babyblueviper1, post 2. Unclonability and authorization soundness are orthogonal properties a system needs both of, not substitutes. On mirroring, a nullifier registry is a spend problem where the answer has to be unique rather than merely available, so redundancy without ordering is precisely what an attacker wants. Folded in. Orthogonality now leads Security Considerations, and the mirroring framing is used as written.
- 2026-08-05: @zexoverz, post 4. The exactly once unit is per issuance, not per agent, and an orchestrator MUST NOT reissue a spent salt. Folded in as a normative salt derivation.
- 2026-08-08: @cedricbrown, post 7. A clone carries the same salt, the same agent identity, and under executor binding the same executor key, so the Guard cannot rank the two by construction. A collision is the only on chain evidence a clone exists, and a bare revert makes a live compromise look identical to a scheduling bug. Folded in as the ordering disclaimer and collision classification.
- 2026-08-09: @zexoverz, post 8. A revert cannot emit, so observability has to be a burn event on the accepting path plus a named error on the rejecting one. Folded in as a normative requirement.
- 2026-08-11: @helmymekaoui-web, post 10. Introduces a fourth layer, an identity scoped cumulative bound. The draft bounds how many times a credential executes and says nothing about what consumption is worth in total. Folded in as an explicit out of scope layer.
- 2026-08-11: @cedricbrown, posts 11 and 12, with adversarial test vectors. The cheapest attack is not winning the race for the valuable action, it is spending the credential at all. Whether the Guard checks the commitment against an issuance record is a security property, not an implementation detail. Folded in. This changed the design, not the wording.
- 2026-08-11: @WeissCurry, post 13. Separates issuer binding from execution coupling, and flags two implementation discrepancies. Folded in. Both discrepancies are resolved below.
- 2026-08-12: @zexoverz, posts 14 and 15. If a clone holds both salt and executor key, no interface check can distinguish them. The fixable part is preventing a burn that leaves the intended execution unavailable, so consumption and execution belong in one call. Folded in as the normative core.
Resolved Since the Original Post
- Nullifier derivation edge cases. No legitimate recurring action can be bricked. The unit of the guarantee is the issuance, not the action, so two intentional runs of the same task receive two capabilities with distinct salts and therefore distinct nullifiers. A scheduled reissue is safe as long as the index advances, which the salt derivation now requires.
- Nullifier preimage. Settled as
keccak256(NULLIFIER_TAG, salt). Posts 8 and 9 discussed adding the agent and domain identifiers. Both are already inputs to the salt derivation and already bound by the capability commitment, so including them a second time changes no security property and creates a second place for a circuit and a Guard to disagree. - Hash choice. keccak256, not Poseidon. Poseidon is cheaper in circuit, but pinning a field, round counts and constants in prose is a large normative surface, and a Guard and circuit that disagree on any of it accept nothing. Proving cost is paid once per capability by the agent. A parity bug is paid by every integrator.
Outstanding Issues
-
Cross chain nullifier synchronization. A capability deliberately issued as spendable on either of two chains, with no designated home, needs real consensus on spentness. The draft binds each capability to a home chain and expects an explicit burn and reissue to move across domains. Declared out of scope rather than solved. Because the chain identifier is excluded from the nullifier preimage, a future mirroring scheme can compare nullifiers across domains without redefining the derivation.
-
Proof generation latency. Proving sits in the execution path, so an attacker who has already cloned the agent memory can begin proving at the same moment as the honest agent. Per post 7, a secondary time lock moves the race later without changing who wins it, so the draft specifies no mitigation and states the limit instead.
-
Aggregate consumption. At most once per issuance is a count on one credential. It does not bound what one identity consumes in total. A compromised agent that keeps receiving fresh capabilities spends each of them once, legitimately, and drains value without ever triggering a collision. Named as a separate layer in the draft, but nothing implements it yet.
-
Relayed submission. The Guard requires
msg.sender == executor, so an agent holding no gas cannot spend through a relayer. A proper path needs an EIP-712 signature argument onexecutewith a domain separator over the Guard address and chain identifier, which changes the interface. Deferred until a concrete integrator asks.
Summary
Agent authorization on Ethereum typically relies on function scoped boundaries or static permissions. Both assume the credential holder is the party the credential was issued to.
That assumption breaks in multi agent swarms. When building the LadingLogic autonomous trade finance network, we hit a security wall. When an orchestrator delegates a high stakes task to a specialized off chain agent, it issues an authorization credential, and a compromised agent can be cloned. A bad actor copies the memory state and replays that authorization to drain funds or duplicate actions. Nothing in a scoped permission distinguishes the original agent from its clone, because both present the same valid credential for the same permitted call.
Existing primitives do not close this. Nonces belong to the account, not to the delegated agent, so an orchestrator handing the same account to two workers gets no separation. Expiry windows shrink the replay window without removing it. Session keys authorize a class of calls rather than one call.
The property needed is that an execution capability fundamentally breaks after one use. By adapting the unconditional unclonable encryption result of Ananth and Sahai (arXiv 2607.21551), this draft brings quantum inspired unclonability to classical EVM environments through zero-knowledge nullifiers.
How It Works
-
The orchestrator derives a per issuance salt, computes a capability commitment binding that salt to the agent identity, home chain, issuing domain, a monotonic index, and a canonical hash of the authorized action, then calls
issueon chain. The capability travels to the agent off chain. The salt never reaches the chain. -
The agent generates a zero-knowledge proof that it knows a salt opening the commitment. Proof generation forces the exposure of a nullifier derived from the salt.
-
A Guard contract verifies the proof, checks that the action being performed is the one the commitment binds, permanently logs the nullifier, and performs the action in the same call.
-
If a cloned credential attempts to execute, it produces the identical nullifier. The Guard sees the duplicate and rejects the transaction with a named error.
The standard defines only the capability envelope, the nullifier derivation, and the verification interface. It does not define the proving system, the capability transport, or the policy that decided the capability should be issued.
Three Choices Worth Surfacing Early
Nullifiers derive from a hidden salt, not the transaction payload. If the nullifier were bound only to the payload, an identical legitimate subsequent task would be blocked, which breaks any recurring agent action. A settlement agent sweeping the same amount to the same address every week would brick itself on week two. Binding to a per issuance salt ensures intentional duplicate tasks receive unique capability tokens, while cloned tokens produce colliding nullifiers. The unit of the guarantee is the issuance, not the agent and not the action.
Unclonability moves from the storage layer to the execution layer. The original arXiv 2607.21551 result relies on quantum states. EVM environments are classical and data is infinitely replicable, so no property of the credential at rest can be made unclonable. This draft relocates the unclonable property to the proof of execution, where the collision is detectable on chain. The credential is still cloneable. The execution is not.
The burn is fused to the action. This is the change the thread forced, and it is the one worth arguing with. A separable spend primitive was specified first and withdrawn. Under it, any party satisfying the executor check could burn a nullifier on an action of its choosing and leave the authorized work permanently undone. At most once held perfectly and the deployment still lost. Mandatory issuance narrows that. Fusing the burn to the call closes what remains, so a burned nullifier now always means the authorized action ran.
Scope of the Security Claim
This standard guarantees at most once execution with no ordering. It is not exactly once, and it is not correct agent wins.
A clone that holds the agent memory holds the salt, and under executor binding the same executor key, so it can construct the same valid proof. Both parties can submit and the guarantee is only that one of them lands. The Guard cannot rank them by construction. Deployments that need the honest agent to win must keep the salt outside cloneable memory, in a hardware backed enclave or an external signer.
It is not an access control framework. The verifier learns that a specific single use capability was consumed, and the Guard ensures no identical capability can ever execute again.
It hides nothing. A permitted action executes on a public chain and is public. The claim is about how many times a credential can be spent, not about what the credential authorizes or who can see it.
Relationship to Neighbouring Standards
| Standard | Layer | Relationship |
|---|---|---|
| Function-scoped delegation drafts | Boundary | Define what an agent may ever be authorized to touch. This proposal solves a different attack vector. Scoped delegation defines what an agent can reach, and this draft guarantees that a specific authorized payload executes at most once. |
| ERC-8354 Confidential Agent Policy Verdicts | Soundness | Defines how a particular authorization decision was reached, while keeping the ruleset confidential. A domain could plausibly use both, keeping the policy secret via ERC-8354 while ensuring the resulting credential cannot be replayed using this draft. Neither requires the other to function. |
| This draft | Consumption | Guarantees the resulting credential is spent at most once, and that a spend performs the issued action. |
| Identity-scoped cumulative bound | Budget | Meters total spend per agent identity so that N clones share one budget rather than multiplying it. Raised in post 10. Not defined here, and not substitutable by this draft. |
| ERC-8004 | Identity | Supplies the agent identity the capability token binds to. |
| ERC-7579, ERC-6900 | Integration surface | The Guard operates as a pre-execution hook or validation module. Dispatch must route through the Guard, for the reason in the third choice above. |
Unclonability and authorization soundness are orthogonal. This standard guarantees that a specific authorized payload executes at most once. It makes no claim about whether that payload should have been authorized in the first place. A replayed credential from a compromised agent and a correctly issued credential encoding a genuinely bad decision are indistinguishable to the Guard, because both present a valid, previously unseen nullifier. A deployment needs boundary, soundness, consumption, and budget independently, and none of the four substitutes for another. Adopting this draft alone does not make an agent safe.
What Changed Since the Original Post
For anyone returning to the thread, three things are materially different from the version at the top of this history.
The separable spend primitive is gone. consume(cap, proof) burned a nullifier on its own call. It is replaced by mandatory issuance plus execute(cap, proof, target, callData), which checks the action against the commitment, burns, and performs the call atomically. A Guard must not expose any path that marks a nullifier consumed without performing the committed action. The residual, stated plainly, is a clone holding both the salt and the executor key triggering the authorized action early. That is a key management boundary, and coupling only turns the outcome from task dead into task done early.
The salt is out of calldata. It was described as a private witness while sitting in the Solidity struct, which made it public in the mempool ahead of inclusion and handed any observer the ability to destroy the capability before the honest transaction was mined. Strictly worse than a leaked salt, because it leaked on every spend. It is now a private witness of the circuit only.
Observability is normative and two sided. The first and only successful spend emits a burn event. Every subsequent attempt reverts with CredentialAlreadySpent. The highest issued index per agent is exposed so a collision can be classified: one at an index the orchestrator never issued indicates a clone, one at an index it did issue indicates a reissue bug on the orchestrator side.
Twenty tests in the reference implementation, including every adversarial vector contributed here.
Feedback I Am Specifically Looking For
-
Integration shape. The draft requires dispatch to route through the Guard so the burn cannot be separated from the action. That may conflict with how ERC-7579 and ERC-6900 modules expect to return a verdict and let the account dispatch. If you build on either, does this create real friction, and is there a formulation that keeps the coupling guarantee without the Guard owning dispatch?
-
Cross-chain mirroring. For anyone working on cross-chain agent execution, how would you handle nullifier registry mirroring where a capability is deliberately issued without a designated home domain? Post 2 framed this as a spend problem rather than an availability problem, which is why the draft declines to solve it. Is there an ordering guarantee here that is not simply a bridge with extra steps?
-
The relocation itself. Is binding unclonability to the proof of execution rather than the credential the right move in a classical setting, or is there a construction that gets closer to the original quantum property?
-
The residual. Coupling bounds a clone to performing the authorized action early rather than destroying the capability. Is bounding the damage the right stopping point for a standard at this layer, or should the draft say more about detecting a premature but valid spend?
Reviews on the PR or here are equally welcome. Thanks to everyone who pushed on this, particularly for the adversarial vectors, which changed the design rather than the wording.
