Skip to content

Commit

Permalink
test: Correctness & soundness of degree lowering
Browse files Browse the repository at this point in the history
Test the completeness and soundness of the `apply_substitution`
function, which substitutes a single node during degree lowering.

In this context, completeness means: “given a satisfying assignment to
the circuit before degree lowering, one can derive a satisfying
assignment to the circuit after degree lowering.” Soundness means the
converse.

These features are tested using random input vectors. Naturally, the
output is not the zero vector (with high probability) and so the given
input is *not* a satisfying assignment (with high probability).
However, the circuit can be extended by way of thought experiment into
one that subtracts a fixed constant from the original output. For the
right choice of subtrahend, the random input now *is* a satisfying
assignment to the circuit.

Specifically, let `input` denote the original (before degree lowering)
input, and `C` the circuit. Then `input` is a satisfying input for the
new circuit `C'(X) = C(X) - C(input)`.

After applying a substitution to obtain circuit `C || k` from `C`, where
`k = Z - some_expr(X)` and `Z` is the introduced variable, the length
of the input and output increases by 1. Moreover, if `input` is a
satisfying input to `C'` then `input || some_expr(input)` is* a
satisfying input to `C' || k`.

(*: If the transformation is complete.)

To establish the converse, we want to start from a satisfying input to
`C" || k` and reduce it to a satisfying input to `C"`. The requirement,
implied by "satisfying input", that `k(X || Z) == 0` implies `Z ==
some_expr(X)`. Therefore, in order to sample a random satisfying input
to `C" || k`, it suffices to select `input` at random, define `C"(X) =
C(X) - C(input)`, and evaluate `some_expr(input)`. Then `input ||
some_expr(input)` is a random satisfying input to `C" || k`. It
follows** that `input` is a satisfying input to `C"`.

(**: If the transformation is sound.)

This description makes use of the following commutative diagram.

         C ───── degree-lowering ────> C || k
         ╷                               ╷
subtract │                      subtract │
   fixed │                         fixed │
  output │                        output │
         │                               │
         v                               v
         C* ─── degree-lowering ────> C* || k

The point of this elaboration is that in this particular case, testing
completeness and soundness require the same code path. If `input` and
`input || some_expr(input)` work for circuits before and after degree
lowering, this fact establishes both completeness and soundness
simultaneously.
  • Loading branch information
jan-ferdinand committed Sep 25, 2024
2 parents 7d612aa + 8634d45 commit 667c910
Show file tree
Hide file tree
Showing 10 changed files with 777 additions and 61 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
uses: taiki-e/install-action@nextest

- name: Collect coverage data
run: cargo llvm-cov nextest --all-targets --lcov --output-path lcov.info
run: cargo llvm-cov nextest --lcov --output-path lcov.info

- name: Upload coverage to coveralls.io
uses: coverallsapp/github-action@v2
Expand Down
17 changes: 15 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,21 @@ jobs:
- name: Run clippy
run: cargo clippy --all-targets -- -D warnings

- name: Run tests
run: cargo nextest run --no-fail-fast --all-targets
- name: Test triton-isa
run: cargo nextest run -p triton-isa --no-fail-fast --all-targets

- name: Test triton-constraint-circuit
run: cargo nextest run -p triton-constraint-circuit --no-fail-fast --all-targets

- name: Test triton-air
run: cargo nextest run -p triton-air --no-fail-fast --all-targets

- name: Test triton-constraint-builder
# without --all-targets because the divan benchmark doesn't play ball with nextest
run: cargo nextest run -p triton-constraint-builder --no-fail-fast

- name: Test triton-vm
run: cargo nextest run -p triton-vm --no-fail-fast --all-targets

# doctests are special [^1] but this step does not incur a performance penalty [^2]
#
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ test-strategy = "0.4.0"
thiserror = "1.0"
twenty-first = "0.42.0-alpha.9"
unicode-width = "0.1"
divan = "0.1.14"

[workspace.lints.rust]
let_underscore_drop = "warn"
Expand Down
2 changes: 1 addition & 1 deletion triton-air/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Triton VM AIR

This crate is part of the [Triton VM](https://triton-vm.org) ecosystem. It contains the definition
of the AIR constraints, which are part of the STARK proving system.
of the AIR constraints, which are part of the STARK proving system, before degree lowering.
5 changes: 5 additions & 0 deletions triton-constraint-builder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ twenty-first.workspace = true
proptest.workspace = true
proptest-arbitrary-interop.workspace = true
test-strategy.workspace = true
divan.workspace = true

[lints]
workspace = true

[[bench]]
name = "degree_lowering"
harness = false
91 changes: 91 additions & 0 deletions triton-constraint-builder/benches/degree_lowering.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use constraint_circuit::ConstraintCircuitMonad;
use divan::black_box;
use triton_constraint_builder::Constraints;

fn main() {
divan::main();
}

#[divan::bench(sample_count = 10)]
fn assemble_constraints() {
black_box(Constraints::all());
}

#[divan::bench(sample_count = 10)]
fn initial_constraints(bencher: divan::Bencher) {
bencher
.with_inputs(|| {
let degree_lowering_info = Constraints::default_degree_lowering_info();
let constraints = Constraints::initial_constraints();
(degree_lowering_info, constraints)
})
.bench_values(|(degree_lowering_info, mut constraints)| {
black_box(ConstraintCircuitMonad::lower_to_degree(
&mut constraints,
degree_lowering_info,
));
});
}

#[divan::bench(sample_count = 10)]
fn consistency_constraints(bencher: divan::Bencher) {
bencher
.with_inputs(|| {
let degree_lowering_info = Constraints::default_degree_lowering_info();
let constraints = Constraints::consistency_constraints();
(degree_lowering_info, constraints)
})
.bench_values(|(degree_lowering_info, mut constraints)| {
black_box(ConstraintCircuitMonad::lower_to_degree(
&mut constraints,
degree_lowering_info,
));
});
}

#[divan::bench(sample_count = 1)]
fn transition_constraints(bencher: divan::Bencher) {
bencher
.with_inputs(|| {
let degree_lowering_info = Constraints::default_degree_lowering_info();
let constraints = Constraints::transition_constraints();
(degree_lowering_info, constraints)
})
.bench_values(|(degree_lowering_info, mut constraints)| {
black_box(ConstraintCircuitMonad::lower_to_degree(
&mut constraints,
degree_lowering_info,
));
});
}

#[divan::bench(sample_count = 10)]
fn terminal_constraints(bencher: divan::Bencher) {
bencher
.with_inputs(|| {
let degree_lowering_info = Constraints::default_degree_lowering_info();
let constraints = Constraints::terminal_constraints();
(degree_lowering_info, constraints)
})
.bench_values(|(degree_lowering_info, mut constraints)| {
black_box(ConstraintCircuitMonad::lower_to_degree(
&mut constraints,
degree_lowering_info,
));
});
}

#[divan::bench(sample_count = 1)]
fn degree_lower_all(bencher: divan::Bencher) {
bencher
.with_inputs(|| {
let constraints = Constraints::all();
let degree_lowering_info = Constraints::default_degree_lowering_info();
(degree_lowering_info, constraints)
})
.bench_values(|(degree_lowering_info, mut constraints)| {
black_box(
constraints.lower_to_target_degree_through_substitutions(degree_lowering_info),
);
});
}
11 changes: 11 additions & 0 deletions triton-constraint-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ impl Constraints {
}
}

// Implementing Default for DegreeLoweringInfo is impossible because the
// constants are defined in crate `air` but struct `DegreeLoweringInfo` is
// defined in crate `triton-constraint-circuit`. Cfr. orphan rule.
pub fn default_degree_lowering_info() -> DegreeLoweringInfo {
constraint_circuit::DegreeLoweringInfo {
target_degree: air::TARGET_DEGREE,
num_main_cols: air::table::NUM_MAIN_COLUMNS,
num_aux_cols: air::table::NUM_AUX_COLUMNS,
}
}

pub fn initial_constraints() -> Vec<ConstraintCircuitMonad<SingleRowIndicator>> {
let circuit_builder = ConstraintCircuitBuilder::new();
vec![
Expand Down
Loading

0 comments on commit 667c910

Please sign in to comment.