Validation-Output Nonconsumption / Assigned-But-Unconstrained Check Result

  • Name: validation-output nonconsumption / assigned-but-unconstrained check result
  • Category: exploit pattern / zk-circuit soundness failure / validation-gadget output omission / under-constrained admission check
  • Summary: This pattern appears when a circuit instantiates the right validation gadget — range check, bigint formatter, subgroup test, parser accept-state check, selector sanity check, or similar — but never actually constrains the gadget’s success output. In ordinary software review, the presence of the helper looks reassuring because the validation code is visibly there. In a constraint system, though, an unconsumed out signal is only commentary. The prover can satisfy the rest of the circuit while the validation gadget quietly outputs 0, so malformed or out-of-domain inputs reach security-critical logic that operators believed had already been screened.
  • What it is:
    • A circuit relies on helper gadgets that expose a boolean or small summary output such as out, isValid, lt, ok, or accepted.
    • Those helpers often also perform internal limb formatting, bit decomposition, or parser-state work that makes the surrounding code look complete.
    • The outer circuit wires attacker-controlled inputs into the helper but never constrains the helper’s success output to the required value.
    • The proof therefore authenticates a weaker statement than reviewers intended: the gadget was instantiated rather than the validation succeeded.
    • Downstream arithmetic, signature verification, state-transition logic, or identity recovery then runs under assumptions that are not actually enforced by the statement.
  • Typical preconditions:
    • The proving system uses reusable helper gadgets whose success condition is surfaced as an output signal rather than enforced internally with a hard equality.
    • The outer circuit is optimized or modularized, so validation and business logic live in separate templates.
    • Reviewers or implementers assume calling the validator is equivalent to enforcing the validator.
    • The unchecked validation concerns security-critical structure: field bounds, bigint formatting, subgroup membership, deterministic parser acceptance, selector legitimacy, or transition preconditions.
    • A successful proof directly authorizes something economically or logically meaningful, such as signature validity, identity authenticity, bridge release, or state-transition acceptance.
  • Common vulnerable surfaces:
    • bigint and field-range comparators whose out must be asserted to 1
    • parser or regex accept-state gadgets where the final accepting-state condition is computed but not bound into the statement
    • curve-point, subgroup, or coordinate checks that produce a boolean validity flag the outer circuit ignores
    • selector or branch-legitimacy gadgets whose result only influences an unconstrained intermediate signal
    • accumulated validation counters or sums that are computed but never finally constrained
    • any isValid / lessThan / belongsToField component instantiated for reviewer comfort but not consumed by the actual relation
  • Exploit sequence:
    1. The attacker identifies a security-critical validation gadget whose success output is not constrained by the outer circuit.
    2. They choose malformed, out-of-range, or semantically invalid inputs that make the helper’s success output false.
    3. Because no constraint demands out = 1, proof generation still succeeds as long as the rest of the circuit can be satisfied.
    4. The resulting proof convinces the verifier of a weaker statement that omits the intended admission or sanity check.
    5. A contract, bridge, or application consumer treats that proof as if the validation had really succeeded.
    6. The attacker converts the missing check into forged identity, signature acceptance, bad state transition, spoofed parser result, or another downstream privilege.
  • Variants:
    • Unasserted boolean validator — the helper computes the right 0/1 answer, but the outer circuit never requires 1, as in circom-pairing’s BigLessThan omission.
    • Validation-sum without final equality — several checks are accumulated into a counter, yet the final sum === expected assertion is missing.
    • Dead-end validation lane — the helper output is routed into an intermediate signal or branch that does not ultimately constrain the statement.
    • Formatting-without-admission — the helper performs limb formatting or decomposition work, creating the appearance of strong validation, but the decisive comparison or membership flag still goes unconstrained.
    • Parser-acceptance nonconsumption — automaton or regex circuits compute an accept state, but the proof statement never forces the final state to be one of the accepted states.
  • Detection heuristics:
    • Search for validation gadgets that expose an output signal and audit every path from that output to a hard constraint.
    • Treat helper names like IsValid, BigLessThan, LessThan, Check, accept, isMember, or isOnCurve as suspicious until you can point to the exact enforcing equality.
    • Diff inputs wired into the validator against outputs the final statement actually constrains; the dangerous gap is often just one missing === 1.
    • Build negative tests with obviously invalid inputs and ask whether proof generation still succeeds.
    • When a circuit comment says check X, verify that the check’s success bit is consumed by the final relation rather than merely computed.
    • Be especially suspicious when the helper also performs internal normalization or decomposition work; those internal constraints can make the gadget look more authoritative than it is.
  • Defenses:
    • Prefer fail-closed helper designs for mandatory checks: if a validation must hold, encode the requirement inside the helper or immediately constrain the output at the call site.
    • Treat validation outputs as security-critical API edges. A gadget returning out is not safe until the caller proves what out must be.
    • Where multiple checks are aggregated, always end with one explicit constraint on the aggregate result.
    • Add negative regression vectors for malformed inputs and require proof generation or verification to fail, not merely out to evaluate to 0 in debug traces.
    • Keep validation helpers and their call-site assertions together in review scope; modularity should not hide the security boundary.

Three validator-output promotions reviewers should not collapse

  • A validation-shaped computation exists
    • The circuit instantiated a helper such as LessThan, BigLessThan, IsValid, subgroup membership, parser acceptance, or selector sanity.
    • This is the weakest and most cosmetic step. It proves only that the code contains a validation lane reviewers can point at.
  • That computation became canonical admission truth for the live statement
    • This is the decisive step preserved by this pattern: the circuit must force out = 1, accepted = 1, allChecks == expected, or equivalent so the statement cannot be satisfied while the helper says fail.
    • If the output never hits that binding edge, the circuit proves validator was wired rather than validation succeeded.
  • That admitted statement still deserves downstream authority
    • A verifier, bridge, signature checker, identity system, or settlement consumer then treats the accepted statement as if malformed inputs were already impossible.
    • This is where the omission cashes out: one dead validator output becomes forged signature acceptance, counterfeit identity, invalid state transition, or another authority-bearing proof artifact.
  • Review should keep these three questions separate: did a helper run, did the statement require its success, and what downstream authority inherits from that accepted statement.

What sound design looks like

  • Mandatory validation should be statement-level truth, not a side computation. If the design intends all coordinates are < q or the parser ended in an accept state, then the proof should make that fact unavoidable, not merely computable. The healthy circuit does not blur helper exists, helper bound into statement truth, and accepted statement now deserves authority into one vague feeling that validation happened somewhere.
  • The cleanest shape is instantiate validator, constrain success immediately. When a helper exists only to expose a boolean, the safest call pattern is often one line later: validator.out === 1.
  • Validation-heavy circuits should make omission difficult by API design. Helpers for mandatory invariants can be written to fail closed internally or return no success bit at all, leaving no caller discretion about whether the check matters.
  • Reviewers should be able to answer a concrete question for every helper: where exactly does this result become part of the proved statement? If the answer is vague, the check likely is not real yet.
  • Healthy systems treat validation wiring as proof-authority logic. Missing one equality on a helper output can be as dangerous as omitting a withdrawal authorization check in ordinary Solidity.
  • High-signal incidents:
    • [[../../../incidents/proofs-verifiers/circom-pairing-coreverifypubkeyg1-biglessthan-output-omission-forged-signature-near-miss-2022-11-25|circom-pairing CoreVerifyPubkeyG1() BigLessThan output omission / forged-signature near miss (2022-11-25)]] — the clean current exemplar: ten BigLessThan validators were instantiated to keep BLS12-381 coordinates below q and properly formatted, but none of their success bits were asserted, so the outer signature-verification relation silently dropped the intended admission check. The primary artifact worth re-reading is tiny and brutally clear: PR #21 adds a six-line r += lt[i].out; r === 10; aggregate that finally turns those ten advisory outputs into live statement truth.

Failure-prevention checklist

  • Inventory every helper gadget that returns a success bit, acceptance flag, comparison result, or validation summary, and mark whether it is advisory or mandatory. Mandatory ones must be tied to an explicit equality in the proved statement.
  • For every call site that instantiates a mandatory validator, add the enforcing constraint immediately after wiring inputs unless the helper itself already fails closed internally.
  • Prefer helper APIs that encode mandatory checks as no-choice assertions rather than optional booleans. If a caller should never ignore a result, do not make ignore result an easy API outcome.
  • Add regression tests that attempt proof generation with invalid coordinates, malformed bigints, bad parser states, or illegal selectors. The expected result should be proof failure, not a computed 0 in some unconstrained debug lane.
  • Review comments and docs that say check, verify, validate, or ensure against the actual constraint graph. Naming is not evidence.
  • When aggregating multiple validation results, constrain the final count or conjunction explicitly and keep the expected value obvious in code review.
  • Treat helper outputs as part of the statement schema during audits. The question is not only what are the public inputs and witnesses? but also which validator outputs must be fixed for the statement to mean what the team thinks it means?
  • Separate the three promotions explicitly in review notes: validator exists, validator success became statement truth, and that accepted statement now carries bridge / signature / identity / payout authority. The dangerous bug often lives in the second promotion even when teams keep talking only about the first and third.
  • Re-run malformed-input regression suites after optimization passes, helper refactors, or inlining changes. This bug family often appears when teams separate performance work from security review.
  • Keep one negative corpus for each validator family — range, subgroup, parser, selector, formatting — so future circuits cannot quietly reintroduce the same omission under different names.
  • During audits, ask for one machine-readable or reviewer-readable map from validator outputs to final enforcing constraints. If that map does not exist, omission bugs are likely hiding in the gap.

Audit prompts

  • Which helper gadgets return a boolean or summary output rather than enforcing their own validity internally?
  • For each such gadget, where is the exact constraint that requires success instead of merely computing it?
  • Can I generate a proof with an input that should fail validation but still satisfies the rest of the circuit because the validator output is unconstrained?
  • Do any comments, audit notes, or code structure imply a check happened even though the corresponding success bit never reaches a hard equality?
  • If several validation outputs are summed or combined, what final assertion proves the aggregate actually had to pass?
  • Are there helper gadgets whose internal normalization work makes reviewers feel safe even though the decisive comparison or acceptance condition is optional?
  • Could a malformed value stay outside the intended field, subgroup, parser language, or selector set and still influence downstream arithmetic or authority?
  • Which negative tests prove invalid input => proof failure instead of merely invalid input => validator outputs 0 somewhere?
  • After optimization or modularization, who verifies that validation outputs are still consumed by the live statement rather than left as dead side computations?
  • If a verifier accepted one surprising proof today, which validator output would you inspect first to confirm the circuit actually required success there?

Lessons that should already be standard

  • In a zk circuit, computed is not enforced. A validator output that never reaches a hard constraint is the proof-equivalent of a warning log, not a security control.
  • Validator exists, statement proves validator passed, and accepted proof now deserves real authority are three different promotions. This family is exactly the bug where reviewers subconsciously jump from the first straight to the third.
  • Reusable helper gadgets create a dangerous review illusion: code can look validation-heavy while the actual proved statement omits the last inch that makes the validation matter.
  • One missing === 1 can be a soundness bug, not a style nit. Teams used to ordinary programming often underestimate how much security sits in these tiny statement-binding edges.
  • Negative proofs matter more than positive tests for this family. Ordinary happy-path proving often looks fine because the validator does compute the right answer; the exploit appears only when invalid input still proves.
  • Validation outputs are part of the protocol’s security object. Audits should map them with the same seriousness given to public inputs, nullifiers, and withdrawal conditions.
  • Performance-driven modularity often increases this risk. The more a circuit factors validation into helper templates, the easier it becomes to instantiate checks without actually consuming them.
  • The clean mantra is simple: if the system must rely on a validation result, that result must be forced into the proved statement immediately and visibly.

Where this pattern stops

  • Use public-input-domain-mismatch when the decisive failure is that the contract and verifier disagree about the numeric domain of one proof-facing value even though the verifier-side checks are real.
  • Use witness-public-input-binding-failure when the proof statement splits one semantic fact across unconstrained public-input and witness-side roles rather than merely forgetting to consume a validator output.
  • Use curve-point-validation-failure when malformed proof elements survive a parser or verifier admission boundary into arithmetic or payout logic; that family preserves the broader malformed-element story, while this note preserves the narrower the validation gadget existed, but its success bit never became statement truth subfamily.
  • In short: this pattern begins once the circuit computes a security-critical validation result without actually requiring that result to hold, and it stops once the sharper story is instead contract/verifier domain drift, split-statement soundness, or malformed-proof admission at a different boundary.

Internal linkages

Control surface