Proof-Element Admission Boundary Discipline

  • Name: proof-element admission boundary discipline
  • Category: zk-integration reference / verifier-admission checklist / malformed-proof object fail-closed note / parser-and-consumer boundary discipline
  • Summary: Healthy zk systems do not let malformed proof objects cross an ambiguous parser boundary and hope later math or downstream authorization code will clean things up. They enforce one explicit admission boundary for proof-supplied points, commitments, openings, scalars, and note objects before any pairing, normalization, ownership recovery, or payout logic runs. This note exists to keep the positive-design target explicit for curve-point-validation-failure, spanning both the pure-verifier malformed point survived long enough to acquire infinity semantics shape preserved by the Aztec Plonk C++ W_z / W_zw zero bug and the later skipped validator + downstream default-value authority shape preserved by the AZTEC v1 demo exploit.
  • Why this note exists:
    • curve-point-validation-failure preserves the exploit family where malformed proof elements, malformed curve points, or malformed scalar values were allowed into verification arithmetic or into the first downstream authorization surface.
    • The exploit writeup is useful, but the healthy-side control question is different: where exactly should malformed proof objects die, and what downstream default value must never be allowed to rescue them into spend authority?
    • In practice, teams often review pairing equations, trusted setup artifacts, and circuit constraints more carefully than the parser, point-validation, normalization, and application-consumer boundaries that decide whether the verifier ever saw a valid object in the first place.
    • This note keeps that admission-boundary discipline explicit instead of forcing readers to reconstruct it from either the pure-verifier Aztec Plonk case or the downstream-cash-out AZTEC v1 case alone.

Fast classifier: what kind of proof-element admission boundary are you trying to protect?

  • the verifier helper existed, but the live path never called it — the codebase contained the right point or commitment validator, yet the deployed path skipped it and let malformed objects reach arithmetic anyway. zkDAI demo is the clean exemplar.
  • the parser accepted an object the protocol never meant to exist — (0,0), point-at-infinity encodings, low-order points, malformed compressed inputs, or out-of-range scalars remained live long enough to affect normalization, pairing inputs, or note logic. [[../incidents/proofs-verifiers/aztec-plonk-cpp-verifier-wz-wzw-zero-point-at-infinity-proof-forgery-near-miss-2021-12-14|Aztec Plonk C++ verifier W_z / W_zw zero bug]] is the clean pure-verifier exemplar.
  • verification should have failed, but downstream default semantics made the malformed object look authorized — the proof-side bug alone did not move funds until some consumer path also treated a failure value such as address(0) or a missing mapping entry as acceptable authority. zkDAI demo again preserves the sharpest exemplar.
  • Use this note when the sharper design question is what exact parser, validator, and downstream fail-closed rules should make malformed proof objects dead on arrival before they become arithmetic inputs or spend authority?

What this note means by proof-element admission

  • Proof-element admission means the protocol can point to one explicit boundary where proof-supplied objects become machine-checked as valid members of the proof system’s expected domain before any economically meaningful logic runs.
  • The useful positive-design claim is not merely the verifier should reject bad proofs. It is the protocol should reject malformed proof objects before they can influence arithmetic, transcript state, note ownership checks, or payout logic at all.
  • The design target is stronger than just on-curve checks exist somewhere in source. A healthy system also guarantees that failed recovery, missing note ownership, absent commitments, or invalid parser results cannot alias to usable downstream defaults.

The six joints a healthy proof-element admission boundary should keep fused

1. Parser and deserializer truth

  • The first code that turns calldata or proof bytes into curve points, commitments, scalars, or note objects should already know the exact allowed encodings.
  • ABI decoding succeeded is not proof-object validation.
  • Reviewers should be able to say which encodings are impossible to carry past deserialization at all.

2. Curve, subgroup, and identity semantics

  • The admission boundary should make explicit whether each proof element must be on-curve, in-subgroup, non-zero, and non-infinity.
  • If a point at infinity, (0,0), or other identity-like value is never meaningful for that object type, the verifier should reject it before arithmetic rather than letting later code reinterpret it.
  • This is where the protocol prevents malformed objects from changing the meaning of pairings, additions, or normalization.

3. Scalar and field-range discipline

  • Proof-facing integers should be range-checked before they become arithmetic inputs, transcript challenges, or note fields.
  • A healthy design treats out-of-range scalars as malformed proof objects, not as benign values that downstream reduction or helper libraries can quietly reinterpret.
  • This is the scalar-side analogue of rejecting off-curve points.

4. Arithmetic-entrypoint discipline

  • Pairing calls, normalization helpers, inversion helpers, and note-validation routines should only ever see already-admitted objects.
  • The clean positive property is all arithmetic runs on valid proof elements, not our arithmetic probably behaves okay on weird edge cases.
  • This is the joint that keeps parser validation and algebraic verification from drifting into separate security stories.

5. Consumer fail-closed semantics

  • Downstream authorization surfaces must not convert malformed-proof weirdness into usable authority.
  • Failed signature recovery, nonexistent note ownership, invalid note hashes, missing commitments, or parser errors must not alias to defaults like address(0), zero commitments, or empty mapping values that can satisfy a spend check.
  • This is where proof parser bug stops being a zk-only issue and becomes a treasury issue.

6. Path-level regression proof

  • Healthy systems do not only unit-test the validator helper. They prove the deployed execution path actually calls it for every relevant entrypoint.
  • The permanent regression corpus should include malformed curve points, zero encodings, identity-like points, off-curve inputs, low-order points, and out-of-range scalars.
  • The test target is malformed proof objects never reach arithmetic or spend logic, not merely normal proofs still work.

What sound design looks like

  • One explicit proof-object admission boundary
    • Reviewers should be able to point to one place where proof-supplied objects become admissible or dead.
    • The healthy answer to where do malformed proof objects die? should be a line of code, not a hand-wave about later verifier behavior.
  • Arithmetic only consumes admitted objects
    • Pairing precompiles, point addition, batch normalization, inversion helpers, and note checks should never have to guess what a malformed object means.
    • The parser boundary and the arithmetic boundary should describe the same object family.
    • A value that was not an admissible proof point at admission must never later acquire a second, more permissive meaning such as acceptable point at infinity inside verifier math.
  • Default values are not fallback authority
    • A healthy application never lets failed cryptographic recovery, missing note, or unset mapping entry become equivalent to a valid owner, signer, or proof-backed claim.
    • Failures stay failures all the way through the consumer path.
  • Validator wiring is treated as live custody logic
    • A correct helper that is commented out or skipped is not a partial success. It is a missing security boundary.
    • Deployment and path tests should prove the live verifier still hits the same admission checks reviewers relied on.
  • Malformed-input testing is permanent, not one-off
    • The system keeps a small malicious corpus for zero encodings, identity-like points, off-curve inputs, low-order points, and bad scalars, and reruns it across refactors, compiler upgrades, and library swaps.

Useful positive design shapes

1. Centralized proof-object validators

  • Keep one shared admission helper for each proof-object family instead of letting each entrypoint improvise its own partial validation.
  • The design goal is not abstraction for its own sake; it is making every live caller hits the same checks auditable.
  • This is the direct positive-design answer to AZTEC-style the helper existed, but this path skipped it failures.

2. Parser-to-arithmetic handoff contracts

  • Make the parser boundary explicit in code structure: decode, validate, then and only then hand admitted objects to arithmetic or ownership logic.
  • Avoid interleaving decode some fields, start arithmetic, then maybe validate one property later.
  • The cleanest systems make malformed-object death happen before algebra starts.

3. Default-value deauthorization

  • Design downstream consumers so invalid parser or recovery results have no spendable representation.
  • If ownership lookup fails, signature recovery fails, or note lookup misses, the code should revert rather than comparing two failure-shaped defaults.
  • This is the positive-design answer to address(0) or zero-like defaults becoming accidental authority.

4. Path-level deployment assertions

  • Treat live validator wiring as part of release verification: build or deployment checks should prove the same admission helpers are still reached in production bytecode or path-level tests.
  • The useful question is not just did the validator helper compile? but did the funded path actually keep calling it?

5. Malicious proof-object corpora in CI

  • Keep explicit regression vectors for (0,0), point-at-infinity encodings, low-order points, malformed compressed points, bad scalars, and bad note encodings.
  • A good suite proves both that the validator rejects them and that no downstream consumer can turn their failure mode into authority.

Design questions worth asking first

  • Where exactly do proof-supplied points, commitments, scalars, or note objects become admissible or rejected?
  • Can any pairing, normalization, inversion, transcript, or ownership logic run before that admission decision is finalized?
  • What identity semantics does each proof object have, and where are impossible identity-like encodings rejected?
  • Are subgroup and scalar-range checks explicit, or only implied by a library or by wishful thinking about later arithmetic?
  • If cryptographic recovery or note lookup fails, what concrete value flows downstream, and could that value accidentally satisfy an authorization comparison?
  • Do deployment tests prove the live funded path still executes the same validator helpers reviewers relied on in source?
  • If one malformed proof object slipped past the parser, what independent control would stop it from becoming real payout or spend authority?

Red flags that usually mean proof-element admission truth is still split

  • The pairing equation is sound used as if that proved malformed objects cannot enter it
  • That helper exists in the repo with no proof the funded entrypoint still calls it
  • ecrecover returns zero on failure, but that should be fine anywhere near proof-backed note spending or withdrawal logic
  • default mapping values or zero addresses being compared directly against proof- or note-derived ownership state
  • parser code and arithmetic code maintained as if they were separate trust domains
  • malformed-input tests that only check rejection in isolation, not whether the downstream consumer also stays shut
  • deployment or refactor pipelines that can ship funded bytecode even when critical path-level security tests were skipped

Failure-prevention checklist

  • Enforce one explicit decode-then-admit boundary for every proof-supplied object family before any pairing, normalization, transcript update, note-ownership recovery, or payout logic runs. We reject weird cases later is not a safe design once malformed objects can already influence state or downstream authority checks.
  • Reject impossible encodings at the first parser boundary: malformed compressed points, point-at-infinity encodings, (0,0) placeholders, low-order points, off-curve points, and out-of-range scalars should never become live arithmetic inputs.
  • Keep arithmetic helpers behind admitted types or explicit admission guards. Pairing, addition, inversion, normalization, and transcript code should not accept raw caller-provided objects that still need semantic interpretation.
  • Make consumer failure values non-authoritative by construction. Failed recovery, missing ownership, absent commitments, invalid notes, and parser errors must revert or stay unspendable rather than degrading into address(0), zero commitments, or empty mapping entries that can satisfy later checks.
  • Centralize proof-object validators and reuse them across all live entrypoints, extensions, wrappers, and migration helpers so one funded path cannot silently bypass the exact checks reviewers assumed existed.
  • Add path-level tests that prove the production entrypoint still traverses the admission gate, not just that a validator helper exists and rejects malformed objects in isolation.
  • Keep a malicious proof-object corpus in CI covering zero encodings, identity-like points, low-order points, malformed compressed points, off-curve elements, bad note encodings, and out-of-range scalars; assert both rejection and downstream fail-closed behavior.
  • Treat library swaps, compiler upgrades, optimization changes, and deserializer refactors as admission-boundary migrations. Re-run malformed-input and fail-closed regression suites before shipping new bytecode.
  • Ensure emitted events, metrics, and debug traces do not normalize malformed proof objects into apparently valid operational truth. Offchain observers should not see recovered owner = 0x0 or similar failure artifacts as a usable state transition.
  • Keep one review artifact that states the full joined invariant explicitly: malformed proof objects die before arithmetic, and any failure that escapes parsing still cannot become spend, withdrawal, or note-control authority.

Audit prompts

  • Where exactly does each proof-supplied point, commitment, scalar, note, or opening stop being raw bytes and become an admitted protocol object?
  • Can any pairing, transcript, normalization, inversion, ownership, or payout path execute before that admission decision is final?
  • Which encodings are explicitly impossible at the parser boundary, and which malformed objects are merely hoped away by later math or library behavior?
  • If signature recovery, note lookup, or commitment lookup fails, what exact value flows downstream, and can that value accidentally satisfy an authorization comparison?
  • Do all live entrypoints, wrappers, and migration helpers reuse the same admission helpers, or is there one path where malformed objects bypass the intended gate?
  • What test proves the funded production path still calls the validator reviewers are relying on, rather than a nearby helper that is now dead code?
  • Can any emitted event, debug trace, or offchain consumer mistake a failure-shaped value for a real owner, note, or verifier-approved object?
  • During upgrades, where is the assertion that parser semantics, library expectations, and downstream fail-closed behavior still describe the same admissible object family?
  • If one malformed proof element reached the first arithmetic helper anyway, what independent downstream control would still prevent it from becoming spend authority?
  • If a reviewer asked where do malformed proof objects die?, what single boundary in code and tests would you point to?

Lessons that should already be standard

  • Verifier is sound is not the same claim as malformed proof objects cannot become authority. Parser boundaries and consumer fail-closed behavior are part of the security object too.
  • A validator helper that is not on the funded path is functionally absent. Review comfort should attach to executed wiring, not to code that merely exists in the repository.
  • Failure-shaped defaults are dangerous custody surfaces. address(0), zero commitments, empty notes, and unset mappings should never be able to stand in for cryptographic truth.
  • Proof parsing is treasury logic when later code can withdraw, mint, or release assets based on the parsed object. Teams routinely under-review this because it looks like input sanitation instead of value authorization.
  • Malformed-input corpora should be permanent regression assets, not one-off audit artifacts. The same weird objects tend to reappear after refactors, optimizer changes, or library swaps.
  • Healthy zk integrations have one joined story from bytes to money: admitted object, valid arithmetic, real owner, non-default authority. If those become separate stories, exploitation room usually appears between them.
  • The clean mantra is simple: malformed objects die early, arithmetic sees only admitted objects, and failures never get promoted into authority.

Negative exemplar cluster: what no proof-element admission boundary looks like

  • [[../incidents/proofs-verifiers/aztec-plonk-cpp-verifier-wz-wzw-zero-point-at-infinity-proof-forgery-near-miss-2021-12-14|Aztec Plonk C++ verifier W_z / W_zw zero bug]] preserves the pure-verifier side of the family: a zero value was recognized as not on-curve yet still survived long enough to acquire point-at-infinity semantics inside the pairing path.
  • zkDAI demo preserves the downstream-cash-out side: malformed note and commitment material crossed a skipped validateCommitments() gate, then a zero-address ecrecover / missing-note-owner alias turned that admission failure into real DAI withdrawal authority.
  • Curve-point validation failure preserves the broader exploit family: skipped validators, malformed proof-element acceptance, identity-point confusion, and downstream application paths that fail open on cryptographic weirdness.
  • The preserved common lesson is not only some curve checks were missing. It is that the protocol never forced proof object is well formed and consumer authority is non-default and real to remain one joined security boundary.

How this note complements the exploit corpus

  • Start with curve-point-validation-failure when the exploit-side question is did malformed proof elements get admitted into verifier arithmetic or a downstream spend path?
  • Use this note when the sharper design question becomes what exact parser, validator wiring, and default-value fail-closed discipline should have made those malformed objects dead on arrival?
  • Pair with verification-key-statement-inventory-and-asset-reachability-discipline when the parser and admission boundary are coherent but the live verifier artifact itself may be stale, malformed, or still trusted somewhere dangerous.
  • Pair with proof-facing-field-domain-canonicalization-discipline when the object is well formed as a proof element but contract and verifier still disagree about its numeric domain.
  • Pair with proof-effect-truth-security-object when the verifier may be sound and inputs well formed, but the downstream settlement consumer honors a different economically relevant effect set.

Internal linkages

Control surface