Skip to content

fix(ssa): accept tainted arguments to no_predicates calls in flatten_post_check - #13430

Open
asterite wants to merge 1 commit into
masterfrom
ab/flatten-post-check-no-predicates
Open

fix(ssa): accept tainted arguments to no_predicates calls in flatten_post_check#13430
asterite wants to merge 1 commit into
masterfrom
ab/flatten-post-check-no-predicates

Conversation

@asterite

Copy link
Copy Markdown
Collaborator

Problem

Compiling contracts in Aztec-Packages (e.g. token_contract) with a debug-built compiler aborts with 11 SSA validation errors:

--- The SSA failed to validate after 'Flattening': Set the NOIR_SHOW_INVALID_SSA env var to see the SSA.
error: Value v22018 escapes `enable_side_effects` in function __aztec_nr_internals___recurse_subtract_balance

The flatten_post_check validator (debug-assertions only, added in #13084) did not account for the enable_side_effects u1 1 wrapper that flattening itself emits around calls to #[no_predicates] functions. Aztec contracts hit this constantly: oracle (Brillig) results fetched under a branch predicate — e.g. get_public_keys_and_partial_address inside conditionally-executed note logic — are passed to #[no_predicates] poseidon2 hash helpers:

enable_side_effects v5238
v22010, ..., v22017 = call f118(v21989)   // Brillig call → results tainted by v5238
v22018 = make_array [v22011, v22012]
enable_side_effects u1 1                  // no_predicates call wrapper
v22019 = call f147(v22018, ...)           // ← flagged: "v22018 escapes"

The escape is deliberate and safe: the callee runs unpredicated by design, and a pure callee cannot be over-constrained by a disabled-branch value. This was a false positive in the validator, not a miscompilation — release builds are unaffected since the check is #[cfg(debug_assertions)].

Minimal reproduction (fails to compile with a debug nargo before this fix):

#[no_predicates]
fn hash(input: [Field; 2]) -> Field {
    std::hash::poseidon2_permutation([input[0], input[1], 0, 0])[0]
}

unconstrained fn get_values(x: Field) -> (Field, Field) {
    (x + 1, x + 2)
}

fn main(c: bool, x: Field) -> pub Field {
    let mut r = 0;
    if c {
        // Safety: test
        let (a, b) = unsafe { get_values(x) };
        r = hash([a, b]);
    }
    r
}

Solution

Exempt argument uses at no_predicates call sites from the escape check. The exemption only skips the violation: the call's results still inherit the argument's predicate and stay tracked until guarded, since they are arbitrary on the disabled branch.

Tests:

  • accepts_tainted_argument_to_no_predicates_call — the distilled Aztec shape above (fails without the fix).
  • rejects_unguarded_result_of_no_predicates_call — same SSA minus the result guard, pinning that the exemption does not leak to the call's results.

Verified that the token_contract in Aztec-Packages compiles with zero validation failures after this change (previously aborted with 11 errors).

🤖 Generated with Claude Code

…post_check

The post-flattening validator flagged predicated values flowing into
calls to `no_predicates` functions as ungated escapes. Flattening wraps
such calls in `enable_side_effects u1 1` on purpose (the callee runs
unpredicated), so the escape is deliberate and safe: a pure callee
cannot be over-constrained by a disabled-branch value. The call's
results still inherit the argument's predicate and stay tracked until
guarded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@asterite
asterite requested a review from TomAFrench July 29, 2026 16:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant