use std::{array, sync::Arc};
use miden_air::PublicInputs;
use miden_assembly::{Assembler, testing::source_file};
use miden_core::{
Felt, WORD_SIZE,
field::{BasedVectorSpace, Field, PrimeCharacteristicRing, QuadFelt},
precompile::PrecompileTranscriptState,
proof::HashFunction,
};
use miden_mast_package::Package;
use miden_processor::{DefaultHost, ExecutionOptions, Program, ProgramInfo};
use miden_utils_testing::{
AdviceInputs, ProvingOptions, prove_sync,
recursive_verifier::{VerifierData, generate_advice_inputs},
stack_inputs_from_ints,
};
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use rstest::rstest;
mod ace_circuit;
mod ace_read_check;
mod batch_query_gen;
#[test]
fn stark_verifier_e2f4_small() {
let inputs = fib_stack_inputs();
let data = generate_recursive_verifier_data(EXAMPLE_FIB_SMALL, inputs, None);
run_recursive_verifier(&data);
}
#[test]
fn stark_verifier_e2f4_large() {
let inputs = fib_stack_inputs();
let data = generate_recursive_verifier_data(EXAMPLE_FIB_LARGE, inputs, None);
run_recursive_verifier(&data);
}
#[test]
fn stark_verifier_e2f4_with_kernel_even() {
let inputs = fib_stack_inputs();
let data = generate_recursive_verifier_data(
EXAMPLE_FIB_KERNEL_SMALL,
inputs,
Some(KERNEL_EVEN_NUM_PROC),
);
run_recursive_verifier(&data);
}
#[test]
fn stark_verifier_e2f4_with_kernel_odd() {
let inputs = fib_stack_inputs();
let data = generate_recursive_verifier_data(
EXAMPLE_FIB_KERNEL_SMALL,
inputs,
Some(KERNEL_ODD_NUM_PROC),
);
run_recursive_verifier(&data);
}
#[test]
fn stark_verifier_e2f4_with_kernel_single() {
let inputs = fib_stack_inputs();
let data = generate_recursive_verifier_data(
EXAMPLE_FIB_KERNEL_SMALL,
inputs,
Some(KERNEL_SINGLE_PROC),
);
run_recursive_verifier(&data);
}
pub fn generate_recursive_verifier_data(
source: &str,
stack_inputs: Vec<u64>,
kernel: Option<&str>,
) -> VerifierData {
let (program, kernel_lib) = {
match kernel {
Some(kernel) => {
let context = miden_assembly::testing::TestContext::new();
let kernel = context.parse_kernel(source_file!(&context, kernel)).unwrap();
let kernel_lib = Assembler::new(context.source_manager())
.assemble_kernel("kernel", kernel, None)
.map(Arc::<Package>::from)
.unwrap();
let assembler =
Assembler::with_kernel(context.source_manager(), kernel_lib.clone()).unwrap();
let program: Program =
assembler.assemble_program("program", source).unwrap().unwrap_program();
(program, Some(kernel_lib))
},
None => {
let program: Program = Assembler::default()
.assemble_program("program", source)
.unwrap()
.unwrap_program();
(program, None)
},
}
};
let stack_inputs = stack_inputs_from_ints(stack_inputs);
let advice_inputs = AdviceInputs::default();
let mut host = DefaultHost::default();
if let Some(ref kernel_lib) = kernel_lib {
host.load_library(kernel_lib.mast_forest()).unwrap();
}
let options = ProvingOptions::new(HashFunction::Poseidon2);
let (stack_outputs, proof) = prove_sync(
&program,
stack_inputs,
advice_inputs,
&mut host,
ExecutionOptions::default(),
options,
)
.unwrap();
let program_info = ProgramInfo::from(program);
let pub_inputs = PublicInputs::new(
program_info,
stack_inputs,
stack_outputs,
PrecompileTranscriptState::default(),
);
let (_, proof_bytes, _precompile_requests) = proof.into_parts();
generate_advice_inputs(&proof_bytes, pub_inputs).unwrap()
}
fn run_recursive_verifier(data: &VerifierData) {
let source = "
use miden::core::sys::vm
begin
exec.vm::verify_proof
end
";
let test = build_test!(
source,
&data.initial_stack,
&data.advice_stack,
data.store.clone(),
data.advice_map.clone()
);
let (output, _host) = test.execute_for_output().expect("recursive verifier execution failed");
ace_read_check::cross_check_ace_circuit(&output);
}
const EXAMPLE_FIB_SMALL: &str = "begin
repeat.320
swap dup.1 add
end
u32split drop
end";
const EXAMPLE_FIB_LARGE: &str = "begin
repeat.400
swap dup.1 add
end
u32split drop
end";
const EXAMPLE_FIB_KERNEL_SMALL: &str = "begin
syscall.foo
repeat.320
swap dup.1 add
end
u32split drop
end";
fn fib_stack_inputs() -> Vec<u64> {
let mut inputs = vec![0_u64; 16];
inputs[15] = 0;
inputs[14] = 1;
inputs
}
#[rstest]
#[case(0)]
#[case(1)]
#[case(2)]
#[case(3)]
#[case(8)]
#[case(255)]
fn variable_length_public_inputs(#[case] num_kernel_proc_digests: usize) {
let log_core_trace_length = 10_u64;
let log_chiplets_trace_length = 10_u64;
let rd = [1_u64, 2, 3, 4];
let initial_stack =
vec![log_core_trace_length, log_chiplets_trace_length, rd[0], rd[1], rd[2], rd[3]];
let seed = [0_u8; 32];
let mut rng = ChaCha20Rng::from_seed(seed);
let input_operand_stack: [u64; 16] = array::from_fn(|_| rng.next_u64());
let output_operand_stack: [u64; 16] = array::from_fn(|_| rng.next_u64());
let program_digest: [u64; 4] = array::from_fn(|_| rng.next_u64());
let mut fixed_length_public_inputs = input_operand_stack.to_vec();
fixed_length_public_inputs.extend_from_slice(&output_operand_stack);
fixed_length_public_inputs.extend_from_slice(&program_digest);
fixed_length_public_inputs.resize(fixed_length_public_inputs.len().next_multiple_of(8), 0);
let kernel_procedures_digests =
generate_kernel_procedures_digests(&mut rng, num_kernel_proc_digests);
let auxiliary_rand_values: [u64; 4] = array::from_fn(|_| rng.next_u64());
let mut advice_stack = fixed_length_public_inputs.clone();
advice_stack.push(num_kernel_proc_digests as u64);
advice_stack.extend_from_slice(&kernel_procedures_digests);
advice_stack.extend_from_slice(&auxiliary_rand_values);
let beta = QuadFelt::new([
Felt::new_unchecked(auxiliary_rand_values[0]),
Felt::new_unchecked(auxiliary_rand_values[1]),
]);
let alpha = QuadFelt::new([
Felt::new_unchecked(auxiliary_rand_values[2]),
Felt::new_unchecked(auxiliary_rand_values[3]),
]);
let reduced_value = reduce_kernel_procedures_digests(&kernel_procedures_digests, alpha, beta);
let coeffs: &[Felt] = reduced_value.as_basis_coefficients_slice();
let source = "
use miden::core::stark::random_coin
use miden::core::stark::constants
use miden::core::sys::vm::public_inputs
begin
exec.random_coin::init_seed
exec.public_inputs::process_public_inputs
end
";
let test = build_test!(source, &initial_stack, &advice_stack);
let (output, _host) = test.execute_for_output().expect("execution failed");
use miden_processor::ContextId;
let ctx = ContextId::root();
let var_len_addr_ptr = 3223322670_u32;
let var_len_ptr = output
.memory
.read_element(ctx, Felt::from_u32(var_len_addr_ptr))
.unwrap()
.as_canonical_u64() as u32;
let masm_0 = output.memory.read_element(ctx, Felt::from_u32(var_len_ptr)).unwrap();
let masm_1 = output.memory.read_element(ctx, Felt::from_u32(var_len_ptr + 1)).unwrap();
assert_eq!(
masm_0.as_canonical_u64(),
coeffs[0].as_canonical_u64(),
"kernel_reduced coord 0 mismatch (nk={num_kernel_proc_digests})"
);
assert_eq!(
masm_1.as_canonical_u64(),
coeffs[1].as_canonical_u64(),
"kernel_reduced coord 1 mismatch (nk={num_kernel_proc_digests})"
);
}
#[test]
fn rejects_too_many_kernel_proc_digests() {
let initial_stack = vec![10_u64, 10, 1, 2, 3, 4];
let seed = [0_u8; 32];
let mut rng = ChaCha20Rng::from_seed(seed);
let input_operand_stack: [u64; 16] = array::from_fn(|_| rng.next_u64());
let output_operand_stack: [u64; 16] = array::from_fn(|_| rng.next_u64());
let program_digest: [u64; 4] = array::from_fn(|_| rng.next_u64());
let mut fixed_length_public_inputs = input_operand_stack.to_vec();
fixed_length_public_inputs.extend_from_slice(&output_operand_stack);
fixed_length_public_inputs.extend_from_slice(&program_digest);
fixed_length_public_inputs.resize(fixed_length_public_inputs.len().next_multiple_of(8), 0);
let num_kernel_proc_digests = 256; let kernel_procedures_digests =
generate_kernel_procedures_digests(&mut rng, num_kernel_proc_digests);
let auxiliary_rand_values: [u64; 4] = array::from_fn(|_| rng.next_u64());
let mut advice_stack = fixed_length_public_inputs;
advice_stack.push(num_kernel_proc_digests as u64);
advice_stack.extend_from_slice(&kernel_procedures_digests);
advice_stack.extend_from_slice(&auxiliary_rand_values);
let source = "
use miden::core::stark::random_coin
use miden::core::stark::constants
use miden::core::sys::vm::public_inputs
begin
exec.random_coin::init_seed
exec.public_inputs::process_public_inputs
end
";
let test = build_test!(source, &initial_stack, &advice_stack);
assert!(
test.execute_for_output().is_err(),
"verifier accepted {num_kernel_proc_digests} kernel digests, exceeding max_aux_inputs"
);
}
fn generate_kernel_procedures_digests<R: Rng>(
rng: &mut R,
num_kernel_proc_digests: usize,
) -> Vec<u64> {
let num_elements_kernel_proc_digests = num_kernel_proc_digests * 2 * WORD_SIZE;
let mut kernel_proc_digests: Vec<u64> = Vec::with_capacity(num_elements_kernel_proc_digests);
(0..num_kernel_proc_digests).for_each(|_| {
let digest: [u64; WORD_SIZE] = array::from_fn(|_| rng.next_u64());
let mut digest = digest.to_vec();
digest.resize(WORD_SIZE * 2, 0);
digest.reverse();
kernel_proc_digests.extend_from_slice(&digest);
});
kernel_proc_digests
}
fn reduce_kernel_procedures_digests(
kernel_procedures_digests: &[u64],
alpha: QuadFelt,
beta: QuadFelt,
) -> QuadFelt {
kernel_procedures_digests
.chunks(2 * WORD_SIZE)
.map(|digest| reduce_digest(digest, alpha, beta))
.fold(QuadFelt::ZERO, |acc, term| {
acc + term.try_inverse().expect("zero kernel ROM denominator")
})
}
fn reduce_digest(digest: &[u64], alpha: QuadFelt, beta: QuadFelt) -> QuadFelt {
let gamma = (0..16).fold(QuadFelt::ONE, |acc, _| acc * beta);
let bus_prefix = alpha + gamma;
bus_prefix
+ digest.iter().fold(QuadFelt::ZERO, |acc, coef| {
acc * beta + QuadFelt::from(Felt::new_unchecked(*coef))
})
}
const KERNEL_SINGLE_PROC: &str = r#"
pub proc foo
add
end"#;
const KERNEL_EVEN_NUM_PROC: &str = r#"
pub proc foo
add
end
pub proc bar
div
end"#;
const KERNEL_ODD_NUM_PROC: &str = r#"
pub proc foo
add
end
pub proc bar
div
end
pub proc baz
mul
end"#;