1use miden_core::{Felt, Word, crypto::hash::Poseidon2};
23use miden_crypto::{
24 field::ExtensionField,
25 merkle::{MerklePath, MerkleTree, NodeIndex},
26};
27
28use crate::{
29 AceError,
30 factory::{FactoredCircuitFactory, PackedLeafScratch},
31};
32
33const PADDING_DOMAIN: u64 = 0xace;
35
36pub const MAX_REGISTRY_AIRS: usize = 12;
41
42pub fn padding_leaf() -> Word {
49 Poseidon2::hash_elements(&[Felt::new_unchecked(PADDING_DOMAIN)])
50}
51
52pub const fn factorial(n: usize) -> usize {
54 let mut result: usize = 1;
55 let mut factor: usize = 2;
56 while factor <= n {
57 result = match result.checked_mul(factor) {
58 Some(value) => value,
59 None => panic!("factorial overflows usize"),
60 };
61 factor += 1;
62 }
63 result
64}
65
66pub const fn ceil_log2(value: usize) -> usize {
68 assert!(value > 0, "ceil_log2 is undefined for zero");
69 let mut value = value - 1;
70 let mut result = 0;
71 while value > 0 {
72 value >>= 1;
73 result += 1;
74 }
75 result
76}
77
78pub fn order_tag(proof_order: &[usize]) -> u32 {
85 let num_airs = proof_order.len();
86 assert!(
87 (1..=MAX_REGISTRY_AIRS).contains(&num_airs),
88 "registry order must contain 1..={MAX_REGISTRY_AIRS} AIRs"
89 );
90 assert!(is_permutation(proof_order), "proof order must be a permutation");
91 let mut rank: u64 = 0;
92 for i in 0..num_airs {
93 let smaller_after =
94 proof_order[i + 1..].iter().filter(|&&index| index < proof_order[i]).count();
95 rank += smaller_after as u64 * factorial(num_airs - 1 - i) as u64;
96 }
97 u32::try_from(rank).expect("tags of a supported AIR count fit in u32")
98}
99
100pub fn order_from_tag(tag: u32, num_airs: usize) -> Option<Vec<usize>> {
104 if !(1..=MAX_REGISTRY_AIRS).contains(&num_airs) {
105 return None;
106 }
107 if tag as usize >= factorial(num_airs) {
108 return None;
109 }
110 let mut rank = tag as usize;
111 let mut remaining: Vec<usize> = (0..num_airs).collect();
112 let mut order = Vec::with_capacity(num_airs);
113 for i in 0..num_airs {
114 let factor = factorial(num_airs - 1 - i);
115 order.push(remaining.remove(rank / factor));
117 rank %= factor;
118 }
119 Some(order)
120}
121
122fn is_permutation(proof_order: &[usize]) -> bool {
123 let mut seen = vec![false; proof_order.len()];
124 proof_order
125 .iter()
126 .all(|&index| index < seen.len() && !core::mem::replace(&mut seen[index], true))
127}
128
129#[derive(Clone, Copy, Debug, Eq, PartialEq)]
137pub struct RegistryLayout {
138 num_airs: usize,
139 row_depth: usize,
140}
141
142impl RegistryLayout {
143 pub const fn new(num_airs: usize, row_depth: usize) -> Option<Self> {
149 if num_airs < 2 || num_airs > MAX_REGISTRY_AIRS {
150 return None;
151 }
152 if row_depth >= ceil_log2(factorial(num_airs)) {
153 return None;
154 }
155 Some(Self { num_airs, row_depth })
156 }
157
158 pub const fn num_airs(&self) -> usize {
160 self.num_airs
161 }
162
163 pub const fn order_count(&self) -> usize {
165 factorial(self.num_airs)
166 }
167
168 pub const fn tree_depth(&self) -> usize {
170 ceil_log2(self.order_count())
171 }
172
173 pub const fn leaf_count(&self) -> usize {
175 1 << self.tree_depth()
176 }
177
178 pub const fn row_depth(&self) -> usize {
180 self.row_depth
181 }
182
183 pub const fn row_len(&self) -> usize {
185 1 << self.row_depth
186 }
187
188 pub const fn leaves_per_subtree(&self) -> usize {
190 1 << (self.tree_depth() - self.row_depth)
191 }
192}
193
194pub fn subtree_leaves<EF>(
203 factory: &FactoredCircuitFactory<EF>,
204 layout: &RegistryLayout,
205 subtree_index: usize,
206 scratch: &mut PackedLeafScratch,
207) -> Result<Vec<Word>, AceError>
208where
209 EF: ExtensionField<Felt>,
210{
211 let start = subtree_start(layout, subtree_index)?;
212 let realizable = layout.order_count().saturating_sub(start).min(layout.leaves_per_subtree());
213 let orders: Vec<Vec<usize>> = (0..realizable)
214 .map(|offset| {
215 order_from_tag((start + offset) as u32, layout.num_airs())
216 .expect("tag below the order count is realizable")
217 })
218 .collect();
219 let order_refs: Vec<&[usize]> = orders.iter().map(Vec::as_slice).collect();
220
221 let mut leaves = Vec::with_capacity(layout.leaves_per_subtree());
222 if !order_refs.is_empty() {
223 factory.leaves_for_orders(&order_refs, scratch, &mut leaves)?;
224 }
225 leaves.resize(layout.leaves_per_subtree(), padding_leaf());
226 Ok(leaves)
227}
228
229fn subtree_start(layout: &RegistryLayout, subtree_index: usize) -> Result<usize, AceError> {
230 if subtree_index >= layout.row_len() {
231 return Err(AceError::InvalidInputLayout {
232 message: format!(
233 "registry subtree index {subtree_index} is outside 0..{}",
234 layout.row_len()
235 ),
236 });
237 }
238 subtree_index.checked_mul(layout.leaves_per_subtree()).ok_or_else(|| {
239 AceError::InvalidInputLayout {
240 message: "registry subtree offset overflowed".into(),
241 }
242 })
243}
244
245pub fn fold_row_to_root(row: &[Word]) -> Word {
247 assert!(row.len().is_power_of_two(), "a node row has a power-of-two length");
248 fold_levels(row).last().expect("root level")[0]
249}
250
251fn fold_levels(row: &[Word]) -> Vec<Vec<Word>> {
253 let mut levels: Vec<Vec<Word>> = Vec::new();
254 levels.push(row.to_vec());
255 while levels.last().expect("at least the row").len() > 1 {
256 let below = levels.last().expect("level exists");
257 #[allow(clippy::chunks_exact_to_as_chunks)]
258 let above: Vec<Word> = below
259 .as_chunks::<2>()
260 .0
261 .iter()
262 .map(|pair| Poseidon2::merge(&[pair[0], pair[1]]))
263 .collect();
264 levels.push(above);
265 }
266 levels
267}
268
269pub fn verify_row(
277 layout: &RegistryLayout,
278 row: &[Word],
279 expected_root: Word,
280 mismatch_hint: &str,
281) -> Vec<Vec<Word>> {
282 assert_eq!(
283 row.len(),
284 layout.row_len(),
285 "checked-in node row length does not match the registry layout"
286 );
287 let mut levels = fold_levels(row);
288 levels.reverse();
289 assert_eq!(
290 levels[0][0], expected_root,
291 "checked-in ACE registry node row does not hash to the registry root. {mismatch_hint}",
292 );
293 levels
294}
295
296pub fn path_in_verified_tree(
304 layout: &RegistryLayout,
305 pyramid: &[Vec<Word>],
306 subtree: &MerkleTree,
307 tag: u32,
308 mismatch_hint: &str,
309) -> Result<(Word, MerklePath), AceError> {
310 if tag as usize >= layout.leaf_count() {
311 return Err(AceError::InvalidInputLayout {
312 message: format!("registry tag {tag} is outside the tree"),
313 });
314 }
315 if pyramid.len() != layout.row_depth() + 1
316 || pyramid.iter().enumerate().any(|(depth, level)| level.len() != 1 << depth)
317 {
318 return Err(AceError::InvalidInputLayout {
319 message: "registry pyramid does not match the layout".into(),
320 });
321 }
322
323 let subtree_index = tag as usize / layout.leaves_per_subtree();
324 assert_eq!(
325 subtree.root(),
326 pyramid[layout.row_depth()][subtree_index],
327 "recomputed ACE registry subtree {subtree_index} does not match the checked-in \
328 node row. {mismatch_hint}",
329 );
330
331 let index = NodeIndex::new(
332 (layout.tree_depth() - layout.row_depth()) as u8,
333 (tag as usize % layout.leaves_per_subtree()) as u64,
334 )
335 .map_err(|_| AceError::InvalidInputLayout {
336 message: "registry tag does not fit the subtree".into(),
337 })?;
338 let leaf = subtree.get_node(index).map_err(|_| AceError::InvalidInputLayout {
339 message: "registry subtree does not contain the selected leaf".into(),
340 })?;
341 let mut nodes = subtree
342 .get_path(index)
343 .map_err(|_| AceError::InvalidInputLayout {
344 message: "registry subtree cannot authenticate the selected leaf".into(),
345 })?
346 .nodes()
347 .to_vec();
348
349 for depth in (1..=layout.row_depth()).rev() {
352 let ancestor = subtree_index >> (layout.row_depth() - depth);
353 nodes.push(pyramid[depth][ancestor ^ 1]);
354 }
355 Ok((leaf, MerklePath::new(nodes)))
356}
357
358#[cfg(test)]
359mod tests {
360 use proptest::prelude::*;
361
362 use super::*;
363
364 fn registry_path_case() -> impl Strategy<Value = (RegistryLayout, u32, u32)> {
365 (2usize..=6).prop_flat_map(|num_airs| {
366 let tree_depth = ceil_log2(factorial(num_airs));
367 (0..tree_depth).prop_flat_map(move |row_depth| {
368 let layout = RegistryLayout::new(num_airs, row_depth).expect("valid layout");
369 let mut boundary_tags = vec![0, layout.order_count() as u32 - 1];
370 if layout.order_count() < layout.leaf_count() {
371 boundary_tags.push(layout.order_count() as u32);
372 }
373 boundary_tags.push(layout.leaf_count() as u32 - 1);
374 (
375 Just(layout),
376 prop_oneof![
377 3 => proptest::sample::select(boundary_tags),
378 5 => 0..layout.leaf_count() as u32,
379 ],
380 any::<u32>(),
381 )
382 })
383 })
384 }
385
386 #[test]
387 fn order_tags_round_trip_over_the_whole_range() {
388 for num_airs in 1..=6 {
389 for tag in 0..factorial(num_airs) as u32 {
390 let order = order_from_tag(tag, num_airs).expect("tag in range");
391 assert_eq!(order_tag(&order), tag, "round trip fails at {num_airs} AIRs, {tag}");
392 }
393 assert_eq!(order_from_tag(factorial(num_airs) as u32, num_airs), None);
394 let identity: Vec<usize> = (0..num_airs).collect();
395 assert_eq!(order_tag(&identity), 0, "the identity ordering must be tag 0");
396 }
397 }
398
399 proptest! {
400 #![proptest_config(ProptestConfig::with_cases(32))]
401
402 #[test]
403 fn larger_order_tags_round_trip(raw_tags in any::<[u32; 6]>()) {
404 for (num_airs, raw_tag) in (7..=MAX_REGISTRY_AIRS).zip(raw_tags) {
405 let tag = raw_tag % factorial(num_airs) as u32;
406 let order = order_from_tag(tag, num_airs).expect("tag in range");
407 prop_assert_eq!(order_tag(&order), tag);
408 }
409 }
410
411 #[test]
412 fn spliced_paths_match_varied_registry_layouts(
413 (layout, tag, salt) in registry_path_case(),
414 ) {
415 let mut leaves: Vec<Word> = (0..layout.order_count())
416 .map(|index| {
417 Poseidon2::hash_elements(&[
418 Felt::new_unchecked(u64::from(salt)),
419 Felt::new_unchecked(index as u64),
420 ])
421 })
422 .collect();
423 leaves.resize(layout.leaf_count(), padding_leaf());
424
425 let tree = MerkleTree::new(&leaves).expect("complete tree");
426 let row: Vec<Word> = if layout.row_depth() == 0 {
427 vec![tree.root()]
428 } else {
429 (0..layout.row_len())
430 .map(|index| {
431 tree.get_node(
432 NodeIndex::new(layout.row_depth() as u8, index as u64)
433 .expect("row index"),
434 )
435 .expect("row node")
436 })
437 .collect()
438 };
439 let pyramid = verify_row(&layout, &row, tree.root(), "toy row must authenticate");
440 let subtree_index = tag as usize / layout.leaves_per_subtree();
441 let start = subtree_index * layout.leaves_per_subtree();
442 let subtree = MerkleTree::new(&leaves[start..start + layout.leaves_per_subtree()])
443 .expect("complete subtree");
444
445 let (leaf, path) =
446 path_in_verified_tree(&layout, &pyramid, &subtree, tag, "toy path")
447 .expect("valid path");
448 prop_assert_eq!(leaf, leaves[tag as usize]);
449 prop_assert_eq!(
450 path.compute_root(u64::from(tag), leaf).expect("path root"),
451 tree.root(),
452 );
453 }
454 }
455
456 #[test]
457 fn layout_derives_its_shape_from_the_air_count() {
458 let layout = RegistryLayout::new(10, 12).expect("valid layout");
459 assert_eq!(layout.order_count(), 3_628_800);
460 assert_eq!(layout.tree_depth(), 22);
461 assert_eq!(layout.leaf_count(), 1 << 22);
462 assert_eq!(layout.row_len(), 4096);
463 assert_eq!(layout.leaves_per_subtree(), 1024);
464
465 let whole = RegistryLayout::new(3, 0).expect("valid layout");
467 assert_eq!(whole.row_len(), 1);
468 assert_eq!(whole.leaves_per_subtree(), whole.leaf_count());
469
470 assert!(RegistryLayout::new(3, 3).is_none(), "row must sit above the leaves");
471 assert!(RegistryLayout::new(3, 4).is_none(), "row cannot sit below the leaves");
472 assert!(RegistryLayout::new(0, 0).is_none(), "a registry needs at least two AIRs");
473 assert!(RegistryLayout::new(1, 0).is_none(), "a registry needs at least two leaves");
474 assert!(
475 RegistryLayout::new(MAX_REGISTRY_AIRS + 1, 0).is_none(),
476 "the full permutation set must fit in u32 tags"
477 );
478 assert_eq!(order_from_tag(0, MAX_REGISTRY_AIRS + 1), None);
479 }
480
481 #[test]
482 fn subtree_offsets_reject_indices_outside_the_row() {
483 let layout = RegistryLayout::new(3, 1).expect("valid layout");
484 assert!(subtree_start(&layout, layout.row_len()).is_err());
485 assert!(subtree_start(&layout, usize::MAX).is_err());
486 }
487
488 #[test]
489 #[should_panic(expected = "proof order must be a permutation")]
490 fn order_tag_rejects_invalid_permutations_in_all_builds() {
491 let _ = order_tag(&[0, 0, 2]);
492 }
493
494 #[test]
495 #[should_panic(expected = "node row length does not match the registry layout")]
496 fn verified_rows_are_bound_to_the_layout() {
497 let layout = RegistryLayout::new(3, 1).expect("valid layout");
498 let row = vec![padding_leaf()];
499 let _ = verify_row(&layout, &row, row[0], "test row must be complete");
500 }
501
502 #[test]
503 fn spliced_paths_match_a_materialised_tree_for_every_slot() {
504 for (num_airs, row_depth) in [(3, 1), (4, 2)] {
508 assert_spliced_paths_match_a_materialised_tree(num_airs, row_depth);
509 }
510 }
511
512 fn assert_spliced_paths_match_a_materialised_tree(num_airs: usize, row_depth: usize) {
513 let layout = RegistryLayout::new(num_airs, row_depth).expect("valid layout");
514 let mut leaves: Vec<Word> = (0..layout.order_count())
515 .map(|tag| Poseidon2::hash_elements(&[Felt::new_unchecked(0x1000 + tag as u64)]))
516 .collect();
517 leaves.resize(layout.leaf_count(), padding_leaf());
518
519 let tree = MerkleTree::new(&leaves).expect("complete tree");
520 let row: Vec<Word> = (0..layout.row_len())
521 .map(|index| {
522 tree.get_node(
523 NodeIndex::new(layout.row_depth() as u8, index as u64).expect("row index"),
524 )
525 .expect("row node")
526 })
527 .collect();
528 let pyramid = verify_row(&layout, &row, tree.root(), "toy row must authenticate");
529
530 for tag in 0..layout.leaf_count() {
531 let subtree_index = tag / layout.leaves_per_subtree();
532 let start = subtree_index * layout.leaves_per_subtree();
533 let subtree = MerkleTree::new(&leaves[start..start + layout.leaves_per_subtree()])
534 .expect("complete subtree");
535 let (leaf, path) =
536 path_in_verified_tree(&layout, &pyramid, &subtree, tag as u32, "toy path")
537 .expect("valid path");
538 assert_eq!(leaf, leaves[tag]);
539 assert_eq!(
540 path.compute_root(tag as u64, leaf).expect("path root"),
541 tree.root(),
542 "path does not verify at tag {tag}"
543 );
544 }
545
546 let subtree =
547 MerkleTree::new(&leaves[..layout.leaves_per_subtree()]).expect("complete subtree");
548 assert!(
549 path_in_verified_tree(
550 &layout,
551 &pyramid,
552 &subtree,
553 layout.leaf_count() as u32,
554 "toy path",
555 )
556 .is_err(),
557 "a tag outside the tree must be rejected"
558 );
559 assert!(
560 path_in_verified_tree(&layout, &pyramid[..1], &subtree, 0, "toy path").is_err(),
561 "a pyramid that does not match the layout must be rejected"
562 );
563 }
564}