Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
2143ccf
feat: Add Plonky3 basic backend
Leo-Besancon Sep 17, 2025
d83b791
feat: Handle plonky3 target
Leo-Besancon Sep 17, 2025
98495ba
tests: Add E2E test codegen for binary plonky3
Leo-Besancon Sep 17, 2025
f15970d
tests: Add test of generated code in Plonky3 binary E2E tests
Leo-Besancon Sep 17, 2025
254cdcc
fix: Make docs_sync() test work on windows
Leo-Besancon Sep 17, 2025
aaa41ed
fix(codegen): differentiate integrity and transition constraints
Leo-Besancon Sep 19, 2025
8b3ed73
refactor(codegen): avoid public_+value Vec allocation
Leo-Besancon Sep 19, 2025
e628f19
chore: changelog, lint fix and removed unused IntegrityConstraintDegr…
Leo-Besancon Sep 19, 2025
df72ff4
feat: add periodic_columns
Leo-Besancon Sep 24, 2025
6c7c777
fix(codegen): Correctly handle boundary constraints domain
Leo-Besancon Sep 24, 2025
96e741c
tests: generate and test all E2E Plonky3 tests
Leo-Besancon Sep 24, 2025
0bd3b79
Merge branch 'next' into add_plonky3_backend
Leo-Besancon Sep 26, 2025
4875e6c
tests: add E2E test for selectors combine with list comprehensions
Leo-Besancon Sep 26, 2025
7a2998a
Merge branch 'next' into add_plonky3_backend
Leo-Besancon Oct 14, 2025
d8c60cc
refactor(plonky3): use AB::Expr::ZERO, ONE, from_u64 and double
Leo-Besancon Oct 14, 2025
ab4c4e8
tests: update plonky3 E2E tests following codegen refactor
Leo-Besancon Oct 14, 2025
dee6af7
refactor(tests): Use macro for plonky3 test boilerplate
Leo-Besancon Oct 14, 2025
7b11b11
docs: Add documentation for Plonky3 backend
Leo-Besancon Oct 14, 2025
3eb73ad
Merge branch 'next' into add_plonky3_backend
Leo-Besancon Oct 15, 2025
4409002
tests: Add Plonky3 E2E tests for computed indices
Leo-Besancon Oct 15, 2025
d0418fe
chore: fix fmt lint
Leo-Besancon Oct 15, 2025
e6aa421
tests(plonky3): use Goldilocks instead of Mersenne32
Leo-Besancon Oct 20, 2025
a2f9883
Merge branch 'next' into add_plonky3_backend
Leo-Besancon Oct 20, 2025
1ec834b
chore: cleanup import
Leo-Besancon Oct 20, 2025
b3a441e
Target 0xMiden Plonky3 repo and use AirScriptAir and AirScriptBuilder…
Leo-Besancon Dec 4, 2025
18a4905
Merge branch 'next' into add_plonky3_backend
Leo-Besancon Dec 8, 2025
805649f
tests: update tests after merge, add plonky3 E2E tests codegen and up…
Leo-Besancon Dec 8, 2025
13d2ae0
fix: cargo fmt
Leo-Besancon Dec 8, 2025
b72ead4
fix: prev cargo fmt failed
Leo-Besancon Dec 8, 2025
f4f6c86
feat: prove/verify and sync plonky3 (#523)
Leo-Besancon Dec 17, 2025
e979548
Merge branch 'next' into add_plonky3_backend
Leo-Besancon Jan 5, 2026
0e99eef
feat: Target p3-miden repo
Leo-Besancon Jan 6, 2026
e02364d
Add Miden VM constraint files
Al-Kindi-0 Dec 22, 2025
5798a02
test: Add failing test for cross-module constant dependencies
Al-Kindi-0 Dec 22, 2025
512e04b
fix: Handle cross-module constant dependencies in dependency graph an…
Al-Kindi-0 Dec 22, 2025
778c366
enable hasher constraints
Al-Kindi-0 Dec 22, 2025
28ced49
test: Add failing test for comprehension periodic binding scoping
Al-Kindi-0 Dec 22, 2025
0196da4
fix: Convert comprehension bindings to Local type for proper scoping
Al-Kindi-0 Dec 22, 2025
53968fe
misc. fixes
Al-Kindi-0 Dec 22, 2025
bf52a32
Add Kernel ROM chiplet constraints (#484)
Al-Kindi-0 Dec 22, 2025
578816e
chore: cleanup tests path
Leo-Besancon Jan 7, 2026
6467036
tests(compare-wf-p3): compare eval test harness on all frames for plo…
Soulthym Jan 8, 2026
38fa68f
tests(compare-wf-p3): binary test
Soulthym Jan 8, 2026
19ff379
tests(compare-wf-p3): compare wf-p3 test harness + test constants
Soulthym Jan 8, 2026
6a9ca39
tests(compare-wf-p3): add support for periodic columns in test harness
Soulthym Jan 8, 2026
91f7699
tests(compare-wf-p3): bitwise test
Soulthym Jan 8, 2026
13cd66b
tests(compare-wf-p3): fibonnaci test
Soulthym Jan 8, 2026
335539d
tests(compare-wf-p3): constraint comprehension test
Soulthym Jan 9, 2026
1a95c0c
tests(compare-wf-p3): randomized tests
Soulthym Jan 9, 2026
df15790
tests(compare-wf-p3): remove useless comments
Soulthym Jan 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@
- Removed `TraceSegmentId::index()` and replaced segment indexing with `TraceShape<T>`/`FullTraceShape<T>` across ACE codegen, MIR-to-AIR pass, and constraints (#442).
- Allow computed indices (#444).
- Fix regressions on MIR and list_comprehensions (#449).
- Add Plonky3 codegen backend (#461).
- Fixed a vector unrolling issue in nested match evaluations (#491).
- Fix evaluator argument vector slice expansion (#495).
- Fixed MIR's constant propagation to fold 0^0 to 1 (#509)
- Support importing hierarchical modules (#507).
- In Plonky3 codegen, use AirScriptAir and AirScriptBuilder traits, and generate aux constraints (#508).
- Fixed MIR's constant propagation to fold 0^0 to 1 (#509).
- Support importing hierarchical modules (#507, #513, #514).
- In Plonky3 codegen, use MidenAir and MidenAirBuilder from 0xMiden's Plonky3 fork instead of AirScriptAir and AirScriptBuilder (#515).
- In Plonky3 codegen, use prove/verify workflow for tests (#523).
- Fix MIR inlining loop on deeply nested calls (#524).

## 0.4.0 (2025-06-20)
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ members = [
"air",
"codegen/winterfell",
"codegen/ace",
"codegen/plonky3",
"constraints",
]
resolver = "2"

Expand Down
49 changes: 49 additions & 0 deletions air-script/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ path = "src/main.rs"

[dependencies]
air-codegen-winter = { package = "air-codegen-winter", path = "../codegen/winterfell", version = "0.5" }
air-codegen-plonky3 = { package = "air-codegen-plonky3", path = "../codegen/plonky3", version = "0.5" }
air-ir = { package = "air-ir", path = "../air", version = "0.5" }
air-parser = { package = "air-parser", path = "../parser", version = "0.5" }
air-pass = { package = "air-pass", path = "../pass", version = "0.5" }
Expand All @@ -27,11 +28,59 @@ log = { version = "0.4", default-features = false }
miden-diagnostics = { workspace = true }
mir = { package = "air-mir", path = "../mir", version = "0.5" }

# Upstream Plonky3 dependencies
p3-field = { version = "0.4.2", default-features = false }
p3-matrix = { version = "0.4.2", default-features = false }
# Internal p3-miden crates
#p3-miden-air = { version = "0.4.0", default-features = false }
# Internal p3-miden crates
p3-miden-air = { package = "p3-miden-air", path = "../../p3-miden/p3-miden-air", default-features = false }

# MassaLabs fork
miden-processor = { package = "miden-processor", git="https://github.com/massalabs/miden-vm", rev = "bc553af69a2543a0789830e8508b019694528181", default-features = false }
miden-air = { package = "miden-air", git="https://github.com/massalabs/miden-vm", rev = "bc553af69a2543a0789830e8508b019694528181", default-features = false }

[dev-dependencies]
expect-test = "1.4"
pretty_assertions = "1.4"

# Upstream Plonky3 dependencies
p3-air = { version = "0.4.2", default-features = false }
p3-baby-bear = { version = "0.4.2", default-features = false }
p3-challenger = { version = "0.4.2", default-features = false }
p3-circle = { version = "0.4.2", default-features = false }
p3-commit = { version = "0.4.2", default-features = false }
p3-dft = { version = "0.4.2", default-features = false }
p3-field-testing = { version = "0.4.2", default-features = false }
p3-fri = { version = "0.4.2", default-features = false }
p3-goldilocks = { version = "0.4.2", default-features = false }
p3-interpolation = { version = "0.4.2", default-features = false }
p3-keccak = { version = "0.4.2", default-features = false }
p3-matrix = { version = "0.4.2", default-features = false }
p3-maybe-rayon = { version = "0.4.2", default-features = false }
p3-mds = { version = "0.4.2", default-features = false }
p3-merkle-tree = { version = "0.4.2", default-features = false }
p3-mersenne-31 = { version = "0.4.2", default-features = false }
p3-poseidon = { version = "0.4.2", default-features = false }
p3-poseidon2 = { version = "0.4.2", default-features = false }
p3-sha256 = { version = "0.4.2", default-features = false }
p3-symmetric = { version = "0.4.2", default-features = false }
p3-uni-stark = { version = "0.4.2", default-features = false }
p3-util = { version = "0.4.2", default-features = false }
# Internal p3-miden crates
#p3-miden-fri = { package = "p3-miden-fri", git = "https://github.com/0xMiden/p3-miden", rev = "134c14d3b438e3c077cd0fba28903d41fe189e52", default-features = false }
#p3-miden-prover = { package = "p3-miden-prover", git = "https://github.com/0xMiden/p3-miden", rev = "134c14d3b438e3c077cd0fba28903d41fe189e52", default-features = false }
#p3-miden-uni-stark = { package = "p3-miden-uni-stark", git = "https://github.com/0xMiden/p3-miden", rev = "134c14d3b438e3c077cd0fba28903d41fe189e52", default-features = false }
# Internal p3-miden crates
p3-miden-fri = { package = "p3-miden-fri", path = "../../p3-miden/p3-miden-fri", default-features = false }
p3-miden-prover = { package = "p3-miden-prover", path = "../../p3-miden/p3-miden-prover", default-features = false }
p3-miden-uni-stark = { package = "p3-miden-uni-stark", path = "../../p3-miden/p3-miden-uni-stark", default-features = false }

winter-air = { package = "winter-air", version = "0.12", default-features = false }
winter-math = { package = "winter-math", version = "0.12", default-features = false }
winter-utils = { package = "winter-utils", version = "0.12", default-features = false }
winter-prover = { package = "winter-prover", version = "0.12", default-features = false }
winter-verifier = { package = "winter-verifier", version = "0.12", default-features = false }
winterfell = { package = "winterfell", version = "0.12", default-features = false }
rand = { version = "0.9", features = ["small_rng"] }
rand_chacha = "0.9"
10 changes: 10 additions & 0 deletions air-script/src/cli/transpile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ use miden_diagnostics::{
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
pub enum Target {
Winterfell,
Plonky3,
}
impl Target {
pub fn extension(&self) -> &'static str {
match self {
Self::Winterfell => "rs",
Self::Plonky3 => "rs",
}
}
}
Expand Down Expand Up @@ -55,13 +57,21 @@ impl Transpile {
let target = self.target.unwrap_or(Target::Winterfell);
let backend: Box<dyn CodeGenerator<Output = String>> = match target {
Target::Winterfell => Box::new(air_codegen_winter::CodeGenerator),
Target::Plonky3 => Box::new(air_codegen_plonky3::CodeGenerator),
};

// write transpiled output to the output path
let output_path = match &self.output {
Some(path) => path.clone(),
None => {
let mut path = input_path.clone();
if target == Target::Plonky3 {
path.set_file_name(format!(
"{}_plonky3",
path.file_stem().unwrap().display()
));
path.set_extension("air");
}
path.set_extension(target.extension());
path
},
Expand Down
8 changes: 8 additions & 0 deletions air-script/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
pub use air_codegen_winter::CodeGenerator as WinterfellCodeGenerator;
pub use air_ir::{Air, CompileError, compile};
pub use air_parser::{parse, parse_file, transforms};

/// Miden VM auxiliary trace generator
pub mod miden_vm_aux_trace_generator;

#[cfg(test)]
pub mod test_utils;
#[cfg(test)]
mod tests;
117 changes: 117 additions & 0 deletions air-script/src/miden_vm_aux_trace_generator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use std::collections::BTreeMap;

use miden_air::{Felt, FieldElement, trace::main_trace::MainTrace};
use miden_processor::{
ColMatrix, Kernel, PrecompileTranscriptState, QuadExtension,
chiplets::{AceHints, AuxTraceBuilder as ChipletsAuxTraceBuilder},
decoder::AuxTraceBuilder as DecoderAuxTraceBuilder,
range::AuxTraceBuilder as RangeAuxTraceBuilder,
stack::AuxTraceBuilder as StackAuxTraceBuilder,
};
use p3_field::{ExtensionField, Field, PrimeField64};
use p3_matrix::Matrix;
use p3_miden_air::RowMajorMatrix;

pub enum MidenModule {
Chiplets,
Decoder,
Stack,
Range,
}

impl MidenModule {
fn build_aux_columns<EF: FieldElement<BaseField = Felt>>(
&self,
main_trace: &MainTrace,
rand_elements: &[EF],
) -> Vec<Vec<EF>> {
match self {
MidenModule::Chiplets => {
let kernel = Kernel::new(&[]).unwrap();
let ace_hints = AceHints::new(0, vec![]);
let final_transcript_state = PrecompileTranscriptState::default();
let aux_trace_builder =
ChipletsAuxTraceBuilder::new(kernel, ace_hints, final_transcript_state);
aux_trace_builder.build_aux_columns(main_trace, rand_elements).to_vec()
},
MidenModule::Decoder => {
let aux_trace_builder = DecoderAuxTraceBuilder {};
aux_trace_builder.build_aux_columns(main_trace, rand_elements)
},
MidenModule::Stack => {
let aux_trace_builder = StackAuxTraceBuilder {};
aux_trace_builder.build_aux_columns(main_trace, rand_elements)
},
MidenModule::Range => {
let lookup_values = vec![];
let cycle_lookups = BTreeMap::new();
let values_start = 0;
let aux_trace_builder =
RangeAuxTraceBuilder::new(lookup_values, cycle_lookups, values_start);
aux_trace_builder.build_aux_columns(main_trace, rand_elements)
},
}
}
}

/// Builds the Miden VM auxiliary trace using the provided main trace and challenges.
pub fn build_aux_trace_with_miden_vm<F, EF>(
main: &RowMajorMatrix<F>,
challenges: &[EF],
module: MidenModule,
) -> RowMajorMatrix<F>
where
F: Field + PrimeField64,
EF: ExtensionField<F>,
{
// Convert main trace to Miden format: transposed to column-major and use `BaseElement` instead
// of `F``
let main_transposed = main.transpose();
let mut felt_columns_vec = Vec::new();
for row in main_transposed.rows() {
let col_felt = row.map(|x| Felt::new(x.as_canonical_u64())).collect::<Vec<_>>();
felt_columns_vec.push(col_felt);
}
let col_matrix = ColMatrix::new(felt_columns_vec);
let last_program_row = main.height().into();
let main_trace = MainTrace::new(col_matrix, last_program_row);

// Convert challenges to Miden format
let mut rand_elements = Vec::new();
for r in challenges {
let coeffs: Vec<Felt> = r
.as_basis_coefficients_slice()
.iter()
.map(|x| Felt::new(x.as_canonical_u64()))
.collect();
let r_fe: &[QuadExtension<Felt>] = FieldElement::slice_from_base_elements(&coeffs);
rand_elements.push(r_fe[0]);
}

// Build aux trace using Miden VM AuxTraceBuilder
let aux_trace_miden = module.build_aux_columns(&main_trace, &rand_elements);
let aux_width = aux_trace_miden.len();

// Convert aux trace back to RowMajorMatrix<EF>
let num_rows = main.height();
let trace_length = num_rows * aux_width;
let long_trace = EF::zero_vec(trace_length);
let mut aux_trace = RowMajorMatrix::new(long_trace, aux_width);

for j in 0..aux_width {
let col = aux_trace_miden.get(j).unwrap();
for i in 0..num_rows {
let value_felt = col[i];
let coeffs_f: Vec<F> = value_felt
.to_base_elements()
.iter()
.map(|x| F::from_canonical_checked(x.as_int()).unwrap())
.collect();
let value_ef = EF::from_basis_coefficients_iter(coeffs_f.iter().cloned()).unwrap();
aux_trace.row_mut(i)[j] = value_ef;
}
}

let aux_trace_f = aux_trace.flatten_to_base();
aux_trace_f
}
96 changes: 96 additions & 0 deletions air-script/src/test_utils/air_tester_macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Helper macros for Winterfell test generation

/// Generates a Winterfell AIR test function with the standard boilerplate
///
/// # Arguments
/// * `test_name` - The identifier for the test function (e.g., `test_binary_air`)
/// * `air_name` - The identifier for the AIR struct (e.g., `BinaryAir`)
/// * `tester_name` - The identifier for the `AirTester` struct (e.g., `BinaryAirTester`)
/// * `trace_length` - The length of the trace for the test (e.g., `32` or `1024`)
#[macro_export]
macro_rules! generate_air_winterfell_test {
($test_name:ident, $air_name:path, $tester_name:ident, $trace_length:expr) => {
#[test]
fn $test_name() {
use winter_math::fields::f64::BaseElement as Felt;
let air_tester = Box::new($tester_name {});
let length = $trace_length;

let main_trace = air_tester.build_main_trace(length);
let aux_trace = air_tester.build_aux_trace(length);
let pub_inputs = air_tester.public_inputs();
let trace_info = air_tester.build_trace_info(length);
let options = air_tester.build_proof_options();

let air = <$air_name>::new(trace_info, pub_inputs, options);
main_trace.validate::<$air_name, Felt>(&air, aux_trace.as_ref());
}
};
}

// Helper macros for Plonky3 test generation

/// Generates a Plonky3 AIR test function with the standard boilerplate
///
/// # Arguments
/// * `test_name` - The identifier for the test function (e.g., `test_binary_air`)
/// * `air_name` - The identifier for the AIR struct (e.g., `BinaryAir`)
#[macro_export]
macro_rules! generate_air_plonky3_test_with_airscript_traits {
($test_name:ident, $air_name:ident) => {
#[test]
fn $test_name() {
type Val = p3_goldilocks::Goldilocks;
type Challenge = p3_field::extension::BinomialExtensionField<Val, 2>;
type ByteHash = p3_sha256::Sha256;
type FieldHash = p3_symmetric::SerializingHasher<ByteHash>;
type MyCompress = p3_symmetric::CompressionFunctionFromHasher<ByteHash, 2, 32>;
type ValMmcs = p3_merkle_tree::MerkleTreeMmcs<Val, u8, FieldHash, MyCompress, 32>;
type ChallengeMmcs = p3_commit::ExtensionMmcs<Val, Challenge, ValMmcs>;
type Challenger = p3_challenger::SerializingChallenger64<
Val,
p3_challenger::HashChallenger<u8, ByteHash, 32>,
>;
type Dft = p3_dft::Radix2DitParallel<Val>;
type Pcs = p3_miden_fri::TwoAdicFriPcs<Val, Dft, ValMmcs, ChallengeMmcs>;
type MyConfig = p3_miden_prover::StarkConfig<Pcs, Challenge, Challenger>;

let byte_hash = ByteHash {};
let field_hash = FieldHash::new(p3_sha256::Sha256);
let compress = MyCompress::new(byte_hash);
let val_mmcs = ValMmcs::new(field_hash, compress);
let challenge_mmcs = ChallengeMmcs::new(val_mmcs.clone());
let challenger = Challenger::from_hasher(vec![], byte_hash);
let dft = Dft::default();
let mut fri_params = p3_miden_fri::create_recursive_miden_fri_params(challenge_mmcs);
let pcs = Pcs::new(dft, val_mmcs, fri_params);
let config = MyConfig::new(pcs, challenger);

// Generate public inputs and convert them to Goldilocks field elements
let inputs = generate_inputs();
let inputs_goldilocks = crate::test_utils::pub_inputs_conversion_utils::convert_pub_inputs_to_goldilocks(&inputs);

// Generate variable-length public inputs as a `Vec<Vec<Vec<u64>>>`.
// The outer `Vec` represents multiple tables (one for each bus in the AIR)
// The middle `Vec` represents the rows of each table
// The innermost `Vec` represents the tuple values contained by the bus, that will be combined with randomness into a single field element.
let var_len_pub_inputs = generate_var_len_pub_inputs();
// Convert variable-length public inputs to Goldilocks field elements
let var_len_pub_inputs_goldilocks_vec = crate::test_utils::pub_inputs_conversion_utils::convert_var_len_pub_inputs_to_goldilocks(var_len_pub_inputs);
let var_len_pub_inputs_goldilocks_vec_slice = crate::test_utils::pub_inputs_conversion_utils::convert_inner_vec_to_slice(&var_len_pub_inputs_goldilocks_vec);
let var_len_pub_inputs_goldilocks = crate::test_utils::pub_inputs_conversion_utils::convert_mid_vec_to_slice(&var_len_pub_inputs_goldilocks_vec_slice);

let trace = generate_trace_rows::<Val>(inputs);

let proof = p3_miden_prover::prove(&config, &$air_name {}, &trace, &inputs_goldilocks);
p3_miden_prover::verify(
&config,
&$air_name {},
&proof,
&inputs_goldilocks,
&var_len_pub_inputs_goldilocks,
)
.expect("Verification failed");
}
};
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use std::sync::Arc;

use air_ir::{CodeGenerator, CompileError};
use air_script::compile;
use miden_diagnostics::{
CodeMap, DefaultEmitter, DiagnosticsHandler, term::termcolor::ColorChoice,
};

use crate::compile;

pub enum Target {
Winterfell,
Plonky3,
}

pub struct Test {
Expand All @@ -30,6 +32,7 @@ impl Test {

let backend: Box<dyn CodeGenerator<Output = String>> = match target {
Target::Winterfell => Box::new(air_codegen_winter::CodeGenerator),
Target::Plonky3 => Box::new(air_codegen_plonky3::CodeGenerator),
};

// generate Rust code targeting Winterfell
Expand Down
Loading
Loading