use alloc::vec;
use miden_core::{Felt, field::QuadFelt};
use miden_crypto::{
field::Field,
hash::{
blake::Blake3Hasher,
keccak::{Keccak256Hash, KeccakF, VECTOR_LEN},
poseidon2::Poseidon2Permutation256,
rpo::RpoPermutation256,
rpx::RpxPermutation256,
},
stark::{
GenericStarkConfig,
challenger::{CanObserve, DuplexChallenger, HashChallenger, SerializingChallenger64},
dft::Radix2DitParallel,
fri::PcsParams,
hasher::{ChainingHasher, SerializingStatefulSponge, StatefulSponge},
lmcs::config::LmcsConfig,
symmetric::{
CompressionFunctionFromHasher, CryptographicPermutation, PaddingFreeSponge,
TruncatedPermutation,
},
},
};
pub type MidenStarkConfig<L, Ch> =
GenericStarkConfig<Felt, QuadFelt, L, Radix2DitParallel<Felt>, Ch>;
type PackedFelt = <Felt as Field>::Packing;
const COMPRESSION_INPUTS: usize = 2;
const LOG_BLOWUP: u8 = 3;
pub const LOG_FOLDING_ARITY: u8 = 2;
const LOG_FINAL_DEGREE: u8 = 7;
pub const FOLDING_POW_BITS: usize = 4;
pub const DEEP_POW_BITS: usize = 12;
const NUM_QUERIES: usize = 27;
const QUERY_POW_BITS: usize = 16;
pub fn pcs_params() -> PcsParams {
PcsParams::new(
LOG_BLOWUP,
LOG_FOLDING_ARITY,
LOG_FINAL_DEGREE,
FOLDING_POW_BITS,
DEEP_POW_BITS,
NUM_QUERIES,
QUERY_POW_BITS,
)
.expect("invalid PCS parameters")
}
pub const RELATION_DIGEST: [Felt; 4] = [
Felt::new_unchecked(2564365500194292689),
Felt::new_unchecked(7963649451118915546),
Felt::new_unchecked(13003513905888733288),
Felt::new_unchecked(3704785727996306162),
];
pub fn observe_protocol_params(challenger: &mut impl CanObserve<Felt>) {
challenger.observe(Felt::new_unchecked(NUM_QUERIES as u64));
challenger.observe(Felt::new_unchecked(QUERY_POW_BITS as u64));
challenger.observe(Felt::new_unchecked(DEEP_POW_BITS as u64));
challenger.observe(Felt::new_unchecked(FOLDING_POW_BITS as u64));
challenger.observe(Felt::new_unchecked(LOG_BLOWUP as u64));
challenger.observe(Felt::new_unchecked(LOG_FINAL_DEGREE as u64));
challenger.observe(Felt::new_unchecked(1_u64 << LOG_FOLDING_ARITY));
challenger.observe(Felt::ZERO);
}
pub fn observe_var_len_public_inputs<C: CanObserve<Felt>>(
challenger: &mut C,
var_len_public_inputs: &[&[Felt]],
message_widths: &[usize],
) {
assert_eq!(
var_len_public_inputs.len(),
message_widths.len(),
"must provide one message width per VLPI group"
);
for (group, &msg_width) in var_len_public_inputs.iter().zip(message_widths) {
assert!(msg_width > 0, "VLPI message width must be positive");
let padded_width = msg_width.next_multiple_of(SPONGE_RATE);
for message in group.chunks(msg_width) {
assert_eq!(
message.len(),
msg_width,
"VLPI group has trailing elements that don't form a complete message"
);
let mut padded = vec![Felt::ZERO; padded_width];
for (i, &elem) in message.iter().enumerate() {
padded[padded_width - 1 - i] = elem;
}
challenger.observe_slice(&padded);
}
}
}
const SPONGE_WIDTH: usize = 12;
const SPONGE_RATE: usize = 8;
const DIGEST_WIDTH: usize = 4;
const CAPACITY_RANGE: core::ops::Range<usize> = SPONGE_RATE..SPONGE_WIDTH;
type AlgLmcs<P> = LmcsConfig<
PackedFelt,
PackedFelt,
StatefulSponge<P, SPONGE_WIDTH, SPONGE_RATE, DIGEST_WIDTH>,
TruncatedPermutation<P, COMPRESSION_INPUTS, DIGEST_WIDTH, SPONGE_WIDTH>,
SPONGE_WIDTH,
DIGEST_WIDTH,
>;
type AlgChallenger<P> = DuplexChallenger<Felt, P, SPONGE_WIDTH, SPONGE_RATE>;
pub type Poseidon2Config =
MidenStarkConfig<AlgLmcs<Poseidon2Permutation256>, AlgChallenger<Poseidon2Permutation256>>;
pub fn rpo_config(
params: PcsParams,
) -> MidenStarkConfig<AlgLmcs<RpoPermutation256>, AlgChallenger<RpoPermutation256>> {
alg_config(params, RpoPermutation256)
}
pub fn poseidon2_config(
params: PcsParams,
) -> MidenStarkConfig<AlgLmcs<Poseidon2Permutation256>, AlgChallenger<Poseidon2Permutation256>> {
alg_config(params, Poseidon2Permutation256)
}
pub fn rpx_config(
params: PcsParams,
) -> MidenStarkConfig<AlgLmcs<RpxPermutation256>, AlgChallenger<RpxPermutation256>> {
alg_config(params, RpxPermutation256)
}
fn alg_config<P>(params: PcsParams, perm: P) -> MidenStarkConfig<AlgLmcs<P>, AlgChallenger<P>>
where
P: CryptographicPermutation<[Felt; SPONGE_WIDTH]> + Copy,
{
let lmcs = LmcsConfig::new(StatefulSponge::new(perm), TruncatedPermutation::new(perm));
let mut state = [Felt::ZERO; SPONGE_WIDTH];
state[CAPACITY_RANGE].copy_from_slice(&RELATION_DIGEST);
let challenger = DuplexChallenger {
sponge_state: state,
input_buffer: vec![],
output_buffer: vec![],
permutation: perm,
};
GenericStarkConfig::new(params, lmcs, Radix2DitParallel::default(), challenger)
}
const BLAKE_DIGEST_SIZE: usize = 32;
type BlakeLmcs = LmcsConfig<
Felt,
u8,
ChainingHasher<Blake3Hasher>,
CompressionFunctionFromHasher<Blake3Hasher, COMPRESSION_INPUTS, BLAKE_DIGEST_SIZE>,
BLAKE_DIGEST_SIZE,
BLAKE_DIGEST_SIZE,
>;
type BlakeChallenger =
SerializingChallenger64<Felt, HashChallenger<u8, Blake3Hasher, BLAKE_DIGEST_SIZE>>;
pub fn blake3_256_config(params: PcsParams) -> MidenStarkConfig<BlakeLmcs, BlakeChallenger> {
let lmcs = LmcsConfig::new(
ChainingHasher::new(Blake3Hasher),
CompressionFunctionFromHasher::new(Blake3Hasher),
);
let mut challenger = SerializingChallenger64::from_hasher(vec![], Blake3Hasher);
challenger.observe_slice(&RELATION_DIGEST);
GenericStarkConfig::new(params, lmcs, Radix2DitParallel::default(), challenger)
}
const KECCAK_WIDTH: usize = 25;
const KECCAK_RATE: usize = 17;
const KECCAK_DIGEST: usize = 4;
const KECCAK_CHALLENGER_DIGEST_SIZE: usize = 32;
type KeccakMmcsSponge = PaddingFreeSponge<KeccakF, KECCAK_WIDTH, KECCAK_RATE, KECCAK_DIGEST>;
type KeccakLmcs = LmcsConfig<
[Felt; VECTOR_LEN],
[u64; VECTOR_LEN],
SerializingStatefulSponge<StatefulSponge<KeccakF, KECCAK_WIDTH, KECCAK_RATE, KECCAK_DIGEST>>,
CompressionFunctionFromHasher<KeccakMmcsSponge, COMPRESSION_INPUTS, KECCAK_DIGEST>,
KECCAK_WIDTH,
KECCAK_DIGEST,
>;
type KeccakChallenger =
SerializingChallenger64<Felt, HashChallenger<u8, Keccak256Hash, KECCAK_CHALLENGER_DIGEST_SIZE>>;
pub fn keccak_config(params: PcsParams) -> MidenStarkConfig<KeccakLmcs, KeccakChallenger> {
let mmcs_sponge = KeccakMmcsSponge::new(KeccakF {});
let compress = CompressionFunctionFromHasher::new(mmcs_sponge);
let sponge = SerializingStatefulSponge::new(StatefulSponge::new(KeccakF {}));
let lmcs = LmcsConfig::new(sponge, compress);
let mut challenger = SerializingChallenger64::from_hasher(vec![], Keccak256Hash {});
challenger.observe_slice(&RELATION_DIGEST);
GenericStarkConfig::new(params, lmcs, Radix2DitParallel::default(), challenger)
}
#[cfg(test)]
mod tests {
extern crate alloc;
use alloc::vec::Vec;
use miden_ace_codegen::{AceConfig, LayoutKind};
use miden_core::{Felt, crypto::hash::Poseidon2, field::QuadFelt};
use crate::{ProcessorAir, ace};
const PROTOCOL_ID: u64 = 0;
const REGEN_HINT: &str = "cargo run -p miden-core-lib --features constraints-tools --bin regenerate-constraints -- --write";
#[test]
fn relation_digest_matches_current_air() {
let config = AceConfig {
num_quotient_chunks: 8,
num_vlpi_groups: 1,
layout: LayoutKind::Masm,
};
let air = ProcessorAir;
let boundary_config = ace::logup_boundary_config();
let circuit =
ace::build_batched_ace_circuit::<_, QuadFelt>(&air, config, &boundary_config).unwrap();
let encoded = circuit.to_ace().unwrap();
let circuit_commitment: [Felt; 4] = encoded.circuit_hash().into();
let input: Vec<Felt> = core::iter::once(Felt::new_unchecked(PROTOCOL_ID))
.chain(circuit_commitment.iter().copied())
.collect();
let digest = Poseidon2::hash_elements(&input);
let expected: Vec<u64> = digest.as_elements().iter().map(Felt::as_canonical_u64).collect();
let snapshot = format!(
"num_inputs: {}\nnum_eval_gates: {}\nrelation_digest: {:?}",
encoded.num_vars(),
encoded.num_eval_rows(),
expected,
);
insta::assert_snapshot!(snapshot);
let actual: Vec<u64> = super::RELATION_DIGEST.iter().map(Felt::as_canonical_u64).collect();
assert_eq!(
actual, expected,
"RELATION_DIGEST in config.rs is stale. Regenerate with: {REGEN_HINT}"
);
}
}