Creation-Time Proof-Usability Failure / Dead-on-Arrival Proof-Backed Object

  • Name: creation-time proof-usability failure / dead-on-arrival proof-backed object
  • Category: exploit pattern / zk-integration lifecycle failure / proof-backed object publication bug / availability-side proof-boundary break
  • Summary: This exploit family appears when a protocol lets constructors, factories, initializers, importers, or admin creation paths publish an object that looks live to the application layer even though the proving system can never later use it soundly. A poll, group, tree, claim surface, or similar object is treated as if created successfully already implied usable in the intended proof-backed lifecycle, but the protocol never actually enforced that equivalence. The recurring review mistake is to collapse three different promotions into one vague object exists moment: the object was authored into contract state, the object's embedded ids/defaults/cached proof material are actually verifier-compatible for the later lifecycle, and the rest of the system is now allowed to treat the object as operational truth. The family often begins as a field-domain mismatch or initialization omission, but the sharper exploit story is about publication of impossible proof-backed state.
  • What it is:
    • A proof-heavy application often creates long-lived objects before users ever submit a proof against them.
    • Those objects may embed group identifiers, external nullifiers, Merkle zero values, cached empty branches, roots, domain separators, or other proof-facing state at birth.
    • If creation-time code accepts values that later verifier or proof-generation paths cannot honor, the protocol authors an object that is already semantically broken.
    • The later proof failure is only the reveal. The real exploit boundary was earlier: the application published impossible proof-backed state as if it were live.
    • Sometimes the result is an availability failure rather than direct theft, but the invariant break is still security-relevant because users, integrators, and downstream modules can rely on an object the proof system will never soundly serve.
  • Typical preconditions:
    • The protocol has a constructor, factory, initializer, import path, or admin action that creates proof-backed state before the first meaningful proof-backed action occurs.
    • Some creation-time field or derived default later becomes a public input, Merkle sibling, nullifier scope, or other verifier-relevant object.
    • The later proof path enforces stricter semantics than the creation path did: field bound, canonical encoding, valid zero/default object, compatible verifier family, or lifecycle-specific usability assumptions.
    • Successful object creation has user-visible meaning: callers assume the group, poll, tree, or claim surface is now operational.
    • No creation-time invariant forces object exists and object is later proof-usable to remain the same claim.
  • Common vulnerable surfaces:
    • voting, signaling, and membership systems where groupId, pollId, scope, or externalNullifier later becomes a proof public input
    • Merkle-tree helpers whose initialization values, zero nodes, cached empty branches, or imported roots later appear inside proof verification paths
    • wrapper modules or application extensions that reuse proof-backed base primitives but forget the base contract’s proof-domain restrictions
    • migration and admin import tooling that can materialize proof-backed objects without replaying the same semantic validation as public creation
    • libraries that expose proof-facing values as generic uint256 setup parameters and only let the real proof-domain check happen much later
    • any product surface where created onchain is treated as equivalent to usable in the proving system
  • Exploit sequence:
    1. The attacker or ordinary user reaches an object-creation surface that accepts proof-facing parameters too loosely.
    2. They create a group, poll, tree, or similar object whose embedded proof-facing state is invalid for the later proof lifecycle.
    3. The contract stores the object, emits creation events, and exposes it as if it were operational.
    4. Other users or modules may add members, deposit state, or build expectations around the object.
    5. When the first meaningful proof-backed action arrives, the verifier, proof generator, or removal path discovers that the object’s birth state was impossible.
    6. The protocol either permanently bricks the object, forces recovery/migration, or discovers too late that a supposedly live proof-backed surface was never real.
  • Variants:
    • Oversized proof-facing identifier — a group, poll, or scope id is accepted as raw uint256 even though later proof verification requires a field element, as in Semaphore.
    • Poisoned zero/default ladder — a tree or cached-empty-state object is initialized with a value the later proof system cannot accept, as in ZK-Kit.
    • Wrapper/factory omission — the base verifier or shared primitive understands the invariant, but a higher-level creation surface silently widens back to raw transport semantics.
    • Imported dead state — migration, governance, or admin tooling materializes proof-backed objects that bypass ordinary creation-time usability checks.
    • Deferred semantic narrowing — engineers treat a value as ordinary setup/config at creation time and only later realize it was always a proof object.

Three object-lifecycle promotions reviewers should not collapse

  1. The object was authored into contract state at all

    • A constructor returned, a factory emitted Created, a tree initialized, or a group/poll was stored.
    • This is only the publication question: the application has made a proof-backed object visible.
  2. That object is actually verifier-compatible for the lifecycle it claims to support

    • The object’s embedded ids, defaults, roots, zero values, scopes, and cached proof material must already satisfy the same semantics the later proof flow will enforce.
    • This is the promotion vulnerable systems skip. They treat creation succeeded as if it already meant later proof-backed use is possible.
  3. That verifier-compatible object deserves downstream operational authority

    • Even a well-formed object may still need separate promotions into user-visible availability, payout entitlement, governance finality, or withdrawal authority.
    • Healthy systems do not let created object exists stand in for safe to rely on this object operationally without making that later authority explicit.

The recurring failure is that protocols collapse all three promotions into one UI- and event-level truth called created successfully.

  • Detection heuristics:
    • Trace every proof-backed object from creation to first meaningful proof-backed action and ask where later proof usability is actually guaranteed.
    • Review creation-time parameters that later become public inputs, path elements, scopes, or replay-sensitive identifiers; ask whether their semantic narrowing happens at birth or only later.
    • Build lifecycle tests, not only verifier tests: create object -> perform ordinary proof-backed action should be a first-class invariant.
    • Treat emitted creation events as dangerous if the same code path could still publish objects the verifier would reject on first real use.
    • Audit import, migration, and admin object-authoring paths separately; they often bypass the safer public constructor while still publishing supposedly live objects.
    • Look for phrases like the verifier catches it later or this is just configuration; they often mark the exact place where proof usability drift begins.
  • Defenses:
    • Make creation-time code enforce the same semantic domain the later proof flow expects.
    • Centralize object-usability checks in the earliest shared creation primitive so wrappers and sibling modules inherit one rule.
    • Treat zero/default values, cached ladders, and imported proof state as first-class proof material rather than harmless setup.
    • Refuse to emit or persist user-visible proof-backed objects until later proof usability has already been established.
    • Apply the same checks to public creation, admin imports, migrations, and recovery tooling.
    • Test the stronger invariant bad object cannot become live state at all, not only later verifier rejects it.

What sound design looks like

  • Created proof-backed objects are already lifecycle-valid objects

  • Proof-facing creation parameters are treated as proof objects from birth

  • One shared creation primitive owns the lifecycle promise

    • The best place for the invariant is the common _create, init, or import helper, not a scattered collection of downstream consumers.
    • This keeps sibling wrappers, voting modules, tree helpers, and factories from drifting into different creation truths.
  • Publication and operational authority are separate promotions

    • A system can require additional checks before a created object becomes user-visible, payout-relevant, or relied on by downstream modules.
    • This matters most when an object can exist locally before the broader protocol should trust it as live workflow infrastructure.
  • Imported and migrated objects get the same birth guarantee

Failure-prevention checklist

  • Make every object-authoring path prove the same claim: if a proof-backed object is created, it is immediately usable for the later proof lifecycle it advertises.
  • Identify every creation-time parameter or derived default that later enters proof semantics: ids, scopes, nullifier domains, zero values, cached empty branches, roots, commitments, and imported state.
  • Enforce the narrow semantic domain at creation time rather than relying on later verifier rejection to clean up impossible objects.
  • Centralize object-usability checks in the shared creation primitive so extensions and wrappers cannot widen the boundary back to raw transport types.
  • Treat event emission and user-visible creation as authority. Do not publish GroupCreated, PollCreated, or equivalent events for objects the proving system still may not honor.
  • Apply the same object-usability validation to migration, admin import, rescue, and governance-created paths.
  • Add lifecycle tests that start with creation and continue through ordinary proof-backed use, including maximum valid and immediate out-of-domain values.
  • Audit cached defaults and derived ladders as if they were explicit user inputs; one bad seed can poison many later proof paths.
  • Document the object-level promise explicitly for integrators: what exactly does created successfully guarantee about later proof usability?
  • Re-run creation-to-usability invariants after upgrades, verifier swaps, library updates, and migration tooling changes.

Audit prompts

  • What exact proof-backed workflow does this object claim to support after creation, and where is that lifecycle-validity guarantee enforced?
  • Which creation-time fields or derived defaults later become verifier public inputs, Merkle siblings, scopes, or replay-relevant objects?
  • Could the protocol publish an object that looks live to users but is already impossible for the later proving system to honor?
  • Where is the earliest shared creation primitive, and do all wrappers, factories, extensions, imports, and admin paths really reuse it?
  • If a created object later fails its first proof-backed action, was that impossibility already knowable at creation time?
  • Are tests proving bad object rejected at creation, or only the weaker claim later verifier refuses already-created bad object?
  • Do emitted identifiers, stored identifiers, and proof-consumed identifiers all describe the same semantic object from birth?
  • If a migration imports proof-backed state, what guarantees the imported object is not dead on arrival in the new verifier/runtime context?
  • What operational truth does created successfully currently convey to integrators, and is that truth stronger than what the proving system has actually earned?

Lessons that should already be standard

  • Constructors, factories, initializers, and import helpers are part of the proof security boundary when they publish objects meant for later proof-backed workflows.

  • The verifier rejects it later is not the same guarantee as the protocol never published impossible proof-backed state.

  • Availability failures belong in the exploit corpus. A dead-on-arrival poll, group, tree, or claim surface is a real security failure even without direct theft.

  • Defaults are often proof material in disguise. Zero values, cached ladders, and imported roots can be as security-critical as explicit proof calldata.

  • Shared creation primitives are a major safety win because they keep one object-creation truth across sibling modules.

  • Admin imports and migrations are creation events too. If they can publish impossible objects, the protocol still has a creation-time proof-usability problem.

  • The clean mental model is simple: object exists, object is lifecycle-valid for later proofs, and object now deserves operational authority are different claims.

  • High-signal incidents:

    • [[../../../incidents/proofs-verifiers/semaphore-groupid-external-nullifier-range-check-omission-inoperable-poll-near-miss-2022-03-07|Semaphore groupId / external-nullifier range-check omission / inoperable poll near miss (2022-03-07)]] — the application-object version, where poll/group creation published objects whose ids the verifier would never honor later.
    • [[../../../incidents/proofs-verifiers/zk-kit-incremental-binary-quin-tree-zero-leaf-snark-scalar-range-check-omission-near-miss-2022-03-09|ZK-Kit incremental binary/quin tree zero-leaf SNARK_SCALAR_FIELD range-check omission near miss (2022-03-09)]] — the library-object version, where initialization published trees whose seeded zero ladders later poisoned proof-backed use.

Where this pattern stops

  • Use public-input-domain-mismatch when the sharper recurring story is the finite-field aliasing itself: replayable nullifiers, raw x versus x + q ambiguity, or broad verifier/contract domain drift.
  • Use this note when the sharper recurring story is that the application published an impossible proof-backed object before the proving boundary had actually been satisfied.
  • Use witness-public-input-binding-failure when the object may have been created fine but the later proof relation itself still bound the wrong state or target object.
  • Use proof-to-effects-binding-failure when the proof and created object may both be sound, but a downstream consumer later settles a different economically authoritative effect set.

Internal linkages

Control surface