Skip to content

Fix CS::one() bug in solver.#24

Merged
srinathsetty merged 4 commits into
microsoft:mainfrom
porcuquine:solver-cs-one
Apr 8, 2022
Merged

Fix CS::one() bug in solver.#24
srinathsetty merged 4 commits into
microsoft:mainfrom
porcuquine:solver-cs-one

Conversation

@porcuquine

@porcuquine porcuquine commented Apr 8, 2022

Copy link
Copy Markdown
Contributor

This PR adds tests that @srinathsetty provided in tests/num.rs. Prior to the fixes here, test_use_cs_one was failing.

running 2 tests
test test_use_cs_one ... FAILED
test test_use_cs_one_after_inputize ... ok

failures:

---- test_use_cs_one stdout ----
thread 'test_use_cs_one' panicked at 'index out of bounds: the len is 0 but the index is 0', /Users/xxx/.cargo/registry/src/github.com-1ecc6299db9ec823/bellperson-0.19.1/src/lc.rs:257:27
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    test_use_cs_one

It passes now, and I believe the fix is correct. The problem was that the 'one' input was not being added, so constraints referencing it were trying to index into the 0th position of a 0-element vector if no other inputs had yet been added. test_use_cs_one_after_inputize was not failing because inputize incidentally allocates an input.

The fix is to add the 'one' input when creating the constraint system. We then discard it when creating the witness, since Nova doesn't require it to be there.

This leaves the question: why or how was this correct before? The answer is that the enforce function was actually doing unnecessary work in computing unused linear-combination evaluations. Nova only needs the witness assignments from the constraint system, so I also removed them. This would also fix the problem we notice, but for the sake of remaining as consistent with bellperson as possible, we should also apply the other fixes.

@porcuquine porcuquine changed the title Solver cs one Fix CS::one() bug in solver. Apr 8, 2022
@srinathsetty
srinathsetty merged commit 648eb0b into microsoft:main Apr 8, 2022
@porcuquine
porcuquine deleted the solver-cs-one branch April 8, 2022 16:52
marcus-sa added a commit to marcus-sa/Nova that referenced this pull request May 15, 2026
…working tree — IdentityStepCircuit + Corrigendum microsoft#22 F2 fixture + 1000-iter empirical close + Falsifier G record

Preserves the working-tree state authored at the previous M.GH7.5 third
PREPARE (Corrigendum microsoft#21 Path 2 + Corrigendum microsoft#22 F2 single-setup-with-
post-mutation fixture-construction algebra). Capture-only commit by the
Corrigendum microsoft#23 experiment-protocol crafter dispatch — none of this
content is authored by the dispatch crafter; preserved verbatim so the
Experiment A/B diagnostic fixtures can stack cleanly on top.

Content captured:

- `IdentityStepCircuit` + `gh75_lookup_shape` + `gh75_build_honest_fixture`
  helpers (`compressed_snark.rs` lines ~4191-4472) — Corrigendum microsoft#21
  Path 2 fixture algebra implemented via Corrigendum microsoft#22's surgical
  single-setup-with-post-construction-mutation fix.

- `m_gh7_5_b_envelope_verify_ivc_trace_round_trip_byte_equivalence`
  (the M.GH7.5 acceptance test, line ~4485) — currently FAILS at vendor
  HEAD inside `gh75_build_honest_fixture`'s `recursive_snark.verify`
  call with `Err(NovaError::UnSat { sum != U.T })` at n=4. This is
  Obstruction 2 surfaced under Corrigendum microsoft#23. The test is preserved
  as-is per the dispatch ("DO NOT commit the failing existing M.GH7.5
  acceptance test — that one stays as-is in the working tree until
  Corrigendum microsoft#24 lands a fix").

- Three negative tests `m_gh7_5_b_corrupted_envelope_per_table_comm_L_rejects`,
  `m_gh7_5_b_corrupted_envelope_per_table_comm_ts_rejects`,
  `m_gh7_5_b_corrupted_snapshot_r_U_comm_L_rejects` (preserved; do not
  pass under the current Obstruction 2 fire because they call
  `gh75_build_honest_fixture` which itself panics in the verify-side
  check).

- `m_gh7_5_3_build_per_table_synthetic_data_closes_by_construction_1000_iter`
  (PASSES at vendor HEAD per the 2026-05-13 baseline re-confirmation
  by the experiment-protocol dispatch — 238s release-mode runtime, 1000
  fresh fixtures, n=2 IVC, asserts `envelope.per_table_comm_L[j] ==
  r_U.comm_L[j]` byte-equal).

- `m_gh7_5_falsifier_g_obstruction_record_two_pass_ck_non_determinism`
  (PASSES — Corrigendum microsoft#22 record-the-obstruction discipline).

- `src/neutron/mod.rs` visibility bumps to `pub(crate)` on the
  `RecursiveSNARK` fields `z0`, `ri`, `l_u`, `i`, `running_lws` and
  `#[serde(skip, default)]` on `shape_registry` — supporting the
  fixture algebra above.

Empirical state recorded by the dispatch at this commit:
- `m_gh7_5_3_build_per_table_synthetic_data_closes_by_construction_1000_iter`
  PASSES (238.50s release-mode, vendor HEAD `39aaec4`).
- `m_gh7_5_b_envelope_verify_ivc_trace_round_trip_byte_equivalence` FAILS
  with `UnSat { sum != U.T, sum: 0x0639af1c..., U.T: 0x0f73deec... }` at
  `recursive_snark.verify(&pp, GH75_N_STEPS=4, &z0)`. The specific sum/U.T
  byte values differ across runs (HyperKZG OsRng-sampled tau per Falsifier
  G obstruction) but the failure class is stable.

The Corrigendum microsoft#23 experiment fixtures (A: n=1, B: n=2) follow as
separate commits on top of this capture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
marcus-sa added a commit to marcus-sa/Nova that referenced this pull request May 15, 2026
…riment B — diagnostic fixture for Obstruction 2 (n=2 IVC verify); NOT a fix; FAILS with R1CS-unsat variant

Authors the Experiment B fixture from Corrigendum microsoft#23's Experiment
Protocol (pin lines 2995-2998). Empirically observes the verify-side
outcome at n=2 to triage the multi-step accumulation locus.

**Recorded outcome at vendor HEAD `804325b` (Experiment A parent
commit) + this commit, 2026-05-13 release-mode, 0.70s runtime**:

  test result: FAILED
  Err(NovaError::UnSat {
    reason: "R1CS is unsatisfiable"
  })

The variant is `UnSat { reason: "R1CS is unsatisfiable" }` — distinct
from both Experiment A's `ProofVerifyError` at n=1 and the existing
acceptance test's `UnSat { sum != U.T }` at n=4. The error fires
from `vendor/nova/src/r1cs/mod.rs:516-519`, which is the **plain
R1CS satisfiability check** (`Az[i] * Bz[i] == Cz[i]` for all i)
inside `R1CSShape::is_sat` — invoked at `mod.rs:800` against the
fresh `l_u/l_w` slot:

  ```rust
  // vendor/nova/src/neutron/mod.rs:798-800
  let (res_r, res_l) = rayon::join(
    || pp.structure.is_sat(&pp.ck, &self.r_U, &self.r_W),
    || pp.structure.S.is_sat(&pp.ck, &self.l_u, &self.l_w),
  );
  ```

The `res_l` arm is the fresh-instance plain-R1CS check (no
eq-weighting, no folding); it's the un-folded R1CS slot for the
LATEST step's augmented-circuit witness. At n=2 this slot
corresponds to the augmented-circuit witness produced by the SECOND
`prove_step_with_lookup_fold` call (i.e., the i=1→2 step). That
witness does NOT satisfy plain R1CS — which means
`prove_step_with_lookup_fold` at step i=1 produces an augmented-
circuit witness that fails its own augmented-circuit R1CS structure
(NOT a folded-sum identity failure).

**Routing implication for Halpert.** At n=2 the IVC-hash-chain
check (n=1's `ProofVerifyError` family) does NOT fire — so whatever
hash-chain bug surfaces at n=1, it's somehow recovered (or different
in shape) at n=2. But the plain-R1CS check now fires on the fresh
slot, suggesting:

  - The augmented circuit emits some constraint at step i≥1 that the
    `prove_step_with_lookup_fold` witness assignment violates
    structurally. Candidates: M.7 reflexive identity at i=1 reads a
    DIFFERENT `chunk_index_in_z` than at i=0 (but the
    IdentityStepCircuit's `z_next = z` keeps `z_i[F_arity-1]` constant
    at ZERO, so this should hold) — OR — some constraint family in the
    augmented circuit's `with_lookup_fold` branch (line ranges in
    `circuit/mod.rs` / `circuit/nifs.rs`) expects a witness that
    `IdentityStepCircuit` does not allocate.

Cross-reference with Experiment A's outcome: three distinct error
variants surface across n ∈ {1, 2, 4} on the SAME fixture algebra:

  - n=1: `ProofVerifyError { "Invalid output hash in R1CS instance" }`
    (hash-chain check at `mod.rs:791-794`)
  - n=2: `UnSat { "R1CS is unsatisfiable" }`
    (fresh-slot plain R1CS at `r1cs/mod.rs:516-519`)
  - n=4: `UnSat { "sum != U.T" }`
    (running-side eq-weighted sumcheck at `relation.rs:601-604`)

This variant-multiplicity is a much stronger diagnostic signal than
Corrigendum microsoft#23 anticipated. The taxonomy (i)/(ii)/(iii) in the
pin assumed a single failure-mode signature; the empirical record
shows the fixture has at LEAST THREE structurally distinct failure
surfaces, suggesting either (a) multiple latent bugs in the
fixture algebra layered on top of each other, or (b) a single
root-cause bug whose downstream manifestation depends on which
verifier check fires first at each n.

Diagnostic fixture for Corrigendum microsoft#23 Experiment B — NOT a fix for
Obstruction 2; FAILS as recorded above.

NOTE: Experiments C and D were NOT executed per the dispatch's
STOP-AND-ASK rule ("Any experiment surfaces `Err(NovaError::
ProofVerifyError)` ... different variant routes to a different root
cause family"). Experiment A triggered the rule at n=1; Experiment
B was authored as cheap additional data (it costs ~1s to run) but
authoring Halpert should consume both outcomes into Corrigendum microsoft#24
before any further empirical work proceeds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
marcus-sa added a commit to marcus-sa/Nova that referenced this pull request May 15, 2026
…riment D-prime — diagnostic fixture for Obstruction 2 (n=1 hash-chain divergence locus); NOT a fix; PASSES as documented Obstruction 2 record

Appends `m_gh7_5_obstruction2_experiment_d_prime_n1_hash_chain_divergence_locus`
to `vendor/nova/src/neutron/compressed_snark.rs` at the test-module tail
sibling to Experiments A and B. Mirrors the verifier-side hash-chain
re-derive at `mod.rs:774-789` byte-for-byte AND a prover-equivalent
reconstruction of the augmented circuit's base-case output-hash absorb
sequence (`circuit/mod.rs:939-955` composed with `circuit/relation.rs:
502-570` on `default_with_lookup_k(cs, 1, k=1)` shape). Captures partial
squeezes after each absorb step on both sides via fresh `PoseidonRO`
re-absorb per prefix (since `PoseidonRO` is not `Clone`). Identifies the
first divergence index and routes to D1/D2/D3/D4 per Corrigendum microsoft#24.

Empirical close at vendor HEAD `b233301`: **D1 — (iv.γ) constant-shape-
vs-Option-shape absorb-sequence drift at base case.** First divergence
at prefix index 7, exactly at the `T_lookup` block: native verifier
side skips (`r_U.T_lookup = None`), prover-equivalent side absorbs one
zero scalar (mirroring `Unew_base.T_lookup_per_table = Some(vec![0; 1])`
per `default_with_lookup_k`). Partials 0..=6 byte-equal across native
and prover sides (`pp.digest`, `num_steps`/`i_new`, `z0`, `zi`/`z_next`,
`comm_W`, `comm_E`, `T`). From index 7 onward streams diverge by one
absorb-shift; final hash mismatch byte-for-byte matches the n=1
`Err(NovaError::ProofVerifyError { reason: "Invalid output hash in
R1CS instance" })` reported by Experiment A.

(iv.α) ruled OUT empirically: comm_W (idx=4) and comm_E (idx=5) partials
byte-equal across native and prover sides — the `comm_E = comm_W.clone()`
shared-variable circuit pattern does not produce an RO2-byte divergence
against the native `Commitment::default()`-distinct-handle pattern at
equal values.

(iv.β) ruled OUT empirically: ri / r_next absorb (last position) is
DOWNSTREAM of the (iv.γ) divergence — the divergence reaches that step
already-corrupted by the (iv.γ) one-absorb-shift, so ri-allocation
parity cannot be the root cause.

Routes Corrigendum microsoft#25 to Fix-α: extend `FoldedInstance::default` to
mirror constant-shape allocation under `lookup_fold_k > 0` (the
strictly-more-general fix that closes the asymmetry without breaking
Corrigendum microsoft#6's constant-shape FS-schedule discipline).

Test asserts the divergence EXISTS (preferred pattern from Corrigendum
microsoft#24 dispatch): PASSES at vendor HEAD `b233301` as a documented record
of Obstruction 2's n=1 hash-chain divergence locus. If a future
Corrigendum microsoft#25 fix-α lands and the divergence disappears, this fixture
FAILS to signal that the obstruction has been resolved — at which point
the fixture should be promoted into a positive single-step n=1
acceptance test (Corrigendum microsoft#23 layer 5b deferred deliverable).

Read-only against vendor body: no modification to `RecursiveSNARK::
verify`, `FoldedInstance::default`, `AllocatedFoldedInstance::default`,
`absorb_in_ro2`, or `absorb_in_ro`. Pure additive test fixture.

Test invocation (release-mode mandatory per `.claude/rules/testing.md`):

  cargo test --release -p nova-snark --features lookup-fold --lib \
    neutron::compressed_snark::tests::\
    m_gh7_5_obstruction2_experiment_d_prime_n1_hash_chain_divergence_locus \
    -- --nocapture

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
marcus-sa added a commit to marcus-sa/Nova that referenced this pull request May 16, 2026
…perKZG opening at r_x (KPI microsoft#3 envelope restoration)

Implements Corrigendum microsoft#29 of the GH-microsoft#7 Stage K corrigenda chain per the
ratified design pin at
docs/research/cryptography/microsoftgh-7-stage-k-corrigendum-T-lookup-envelope-2026-05-16.md
(Halpert, 2026-05-16; cryptographer-reviewer RATIFY).

Mechanism (Path b ratified): replace the
per_table_T_lookup: Vec<Vec<E::Scalar>> envelope field whose inner length
num_cons is the linear-in-num_cons envelope-size regression at KPI microsoft#3
(slice-04 forecast ~12,289 bytes vs 12,288-byte ceiling; slice-06 multi-MB
under k=2 at num_cons ~1.7M) with
per_table_comm_T_lookup: Vec<Commitment<E>>. The verifier batched-opens
T_j(r_x) via a 5th per-table PolyEvalInstance in the existing u_vec
(extending 3 + 4*k -> 3 + 5*k); the claimed evaluation comes from the
prover-published per_table_outer_evals[j].eval_T (already consumed by the
unified outer-sumcheck residue identity per Corrigendum microsoft#16 Finding B).

Affected sites:
- vendor/nova/src/neutron/compressed_snark.rs:
  * Struct field per_table_T_lookup -> per_table_comm_T_lookup
  * build_per_table_commitments: 4-tuple -> 5-tuple
  * prove_with_lookup_fold: pass new commitment slice
  * prove_from_parts_with_logup: new parameter + envelope FS-absorb of
    per_table_comm_T_lookup under FS-tag b"comm_T_lookup_j" (was raw vec
    under b"T_lookup_j")
  * prove_from_parts (k=0 path): empty slice for the 5th param
  * verify body: cardinality check + FS-absorb + Spartan delegation
  * M.GH7.4c acceptance test: build the 5th commitment, drop the
    inner-length assertion (commitments have no inner length)
- vendor/nova/src/spartan/snark.rs:
  * prove_with_T_claim_split_error_with_logup: new parameter
    per_table_comm_T_lookup + cardinality assert; w_vec and u_vec
    extended from 3 + 4*k to 3 + 5*k with the 5th per-table entry
    (PolyEvalWitness {p: per_table_T[j].clone()} +
     PolyEvalInstance {c: per_table_comm_T_lookup[j], x: r_x.clone(),
                        e: per_table_outer_evals[j].eval_T})
  * verify_with_T_claim_split_error_with_logup: signature change
    per_table_T: &[Vec<E::Scalar>] -> per_table_comm_T: &[Commitment<E>];
    remove inner-length-num_cons check; remove MLE-eval cross-check (the
    binding migrates to the batched HyperKZG opening at the u_vec
    extension below); extend u_vec from 3 + 4*k to 3 + 5*k
  * Test-scope build_per_table_commitments: 4-tuple -> 5-tuple
  * M.GH7.4a + M.GH7.4b acceptance tests: build the 5th commitment;
    update call sites; verify still accepts

FS-tag substitution b"T_lookup_j" -> b"comm_T_lookup_j" at BOTH the
prover-side envelope absorb (prove_from_parts_with_logup step (7),
compressed_snark.rs:2235) AND the verifier-side envelope absorb
(CompressedSNARK::verify, compressed_snark.rs:2577). This is a
forward-incompatible breaking change per design pin §4.4: no envelope
back-compat shim. Same-tag-different-payload would silently re-derive a
different challenge stream that the prover-side did not match (a
Halpert-class forgery vector).

Pre-flight gates (per design pin §7.3 / cryptographer-reviewer):
- (a) HyperKZG batched-opening capacity: PASSED. EE::prove / EE::verify
  operate on a single combined polynomial built via batch_eval_reduce /
  batch_eval_verify (vendor/nova/src/spartan/mod.rs:393 / :452); proof
  overhead is O(log size_max), not O(k · log num_cons). 5th entry adds
  zero new opening proofs.
- (b) PolyEvalInstance::batch_diff_size at length num_cons: PASSED.
  vendor/nova/src/spartan/mod.rs:320-362 accepts num_vars: &[usize] with
  L0 Lagrange-rescale; existing 4 per-table entries already at length
  num_cons (snark.rs:1554-1573); 5th entry is mechanically identical.
- (c) Verifier cardinality check located at compressed_snark.rs:2400;
  updated to per_table_comm_T_lookup.len() != k.
- (d) FS-tag substitution sites: 2 absorb call-sites (prover at line
  2235, verifier at line 2577). Both changed. No stale b"T_lookup_j"
  references remain in any code path; only audit-trail commentary
  preserves the pre-corrigendum form.
- (e) Commitment basis: PASSED. All 5 per-table commitments use prefix-
  basis ck.ck[..num_cons] (build_per_table_commitments delegates to
  E::CE::commit(ck, ..., &ZERO) identically for all 5).

§7.3 acceptance gates:
- (b) cargo test -p nova-snark --features lookup-fold --release --lib:
  194 passed; 1 failed; 3 ignored. The single failure
  (neutron::tests::m_gh7_3a_prove_step_with_lookup_fold_sibling_threads_running_lws_witness_alloc_preserved_positive_path)
  is PRE-EXISTING on the lookup-fold baseline (verified by stashing
  Corrigendum microsoft#29 changes and re-running the same test; same failure)
  and belongs to the M.GH7.5 / Corrigendum microsoft#23 TRIAGE-ROUTED IVC-trace
  empirical investigation that is STRUCTURALLY ORTHOGONAL to the
  envelope-shape change per design pin §5.4 issue 4. Corrigendum microsoft#29 is
  the structural-correctness change against the current
  per_table_T_lookup field; Corrigendum microsoft#24+ owns the M.GH7.5
  full-R1CS-sat empirical close.
- Corrigendum microsoft#29-specific acceptance tests ALL PASS:
  * spartan::snark::tests::m_gh7_4a::m_gh7_4a_prove_with_T_claim_split_error_with_logup_round_trip ... ok
  * spartan::snark::tests::m_gh7_4a::m_gh7_4b_prove_with_T_claim_split_error_with_logup_pcs_opening_batched_round_trip ... ok
  * spartan::snark::tests::m_gh7_4a::m_gh7_4a_per_table_outer_evals_struct_field_shape ... ok
  * neutron::compressed_snark::tests::m_gh7_4c_compressed_snark_end_to_end_with_per_table_lookup_envelope_transcript ... ok
  These exercise the prover-side signature change, the verifier-side
  signature change, the 3+5*k u_vec extension, and the end-to-end
  envelope FS-tag substitution.
- (c) No regression on existing vendor-side acceptance tests naming
  per_table_T_lookup: confirmed. The only sites are the M.GH7.4c
  acceptance test (which was updated to use per_table_comm_T_lookup
  and now asserts cardinality, not inner length) and audit-trail
  commentary referencing the pre-corrigendum form.

Inumbra-side rebase + slice-04 envelope re-measurement is the
follow-up dispatch per design pin §7.4. The forecast post-corrigendum
is slice-04 ~9,810 bytes (band 9,500-10,500); the un-#[ignore] of the
ceiling-asserting tests at arity_envelope_acceptance.rs:393 and :537
lands in that follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants