Skip to content

Commit 101952a

Browse files
refactor(BagPeaks): Commit to leaf count
Synchronize with upstream behavior in twenty-first. Co-authored-by: Thorkil Schmidiger <[email protected]>
1 parent f618759 commit 101952a

File tree

1 file changed

+96
-97
lines changed

1 file changed

+96
-97
lines changed

tasm-lib/src/mmr/bag_peaks.rs

Lines changed: 96 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,51 @@
11
use std::collections::HashMap;
22

33
use triton_vm::prelude::*;
4-
use twenty_first::util_types::shared::bag_peaks;
4+
use triton_vm::twenty_first::util_types::mmr::mmr_accumulator::MmrAccumulator;
55

6-
use crate::mmr::MAX_MMR_HEIGHT;
6+
use crate::arithmetic;
77
use crate::prelude::*;
88
use crate::traits::basic_snippet::Reviewer;
99
use crate::traits::basic_snippet::SignOffFingerprint;
1010

11-
/// [Bag peaks](bag_peaks) into a single [`Digest`].
11+
/// [Bag peaks](bag_peaks) of an MMR into a single [`Digest`].
1212
///
1313
/// # Behavior
1414
///
1515
/// ```text
16-
/// BEFORE: _ *peaks
16+
/// BEFORE: _ *mmr_accumulator
1717
/// AFTER: _ [bagged_peaks: Digest]
1818
/// ```
1919
///
2020
/// # Preconditions
2121
///
2222
/// - the input argument points to a properly [`BFieldCodec`]-encoded list of
2323
/// [`Digest`]s in memory
24-
/// - the pointed-to list contains fewer than [`MAX_MMR_HEIGHT`] elements
24+
/// - the pointed-to MMR accumulator is consistent, *i.e.*, the number of peaks
25+
/// matches with the number of set bits in the leaf count.
2526
///
2627
/// # Postconditions
2728
///
2829
/// - the output is a single [`Digest`] computed like in [`bag_peaks`]
2930
/// - the output is properly [`BFieldCodec`] encoded
3031
///
32+
/// # Crashes
33+
///
34+
/// - if the MMR accumulator is inconsistent, *i.e.*, if the number of peaks
35+
/// does not match the number of set bits in the leaf count
36+
///
3137
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
3238
pub struct BagPeaks;
3339

40+
impl BagPeaks {
41+
const INCONSISTENT_PEAKS_NUM: usize = 11;
42+
}
43+
3444
impl BasicSnippet for BagPeaks {
3545
fn inputs(&self) -> Vec<(DataType, String)> {
36-
let list_of_digests = DataType::List(Box::new(DataType::Digest));
46+
let mmr_accumulator = DataType::List(Box::new(DataType::Digest));
3747

38-
vec![(list_of_digests, "*peaks".to_string())]
48+
vec![(mmr_accumulator, "*mmra".to_string())]
3949
}
4050

4151
fn outputs(&self) -> Vec<(DataType, String)> {
@@ -46,107 +56,77 @@ impl BasicSnippet for BagPeaks {
4656
"tasmlib_mmr_bag_peaks".to_string()
4757
}
4858

49-
fn code(&self, _: &mut Library) -> Vec<LabelledInstruction> {
59+
fn code(&self, library: &mut Library) -> Vec<LabelledInstruction> {
5060
let entrypoint = self.entrypoint();
51-
52-
let length_is_zero = format!("{entrypoint}_length_is_zero");
53-
let length_is_not_zero = format!("{entrypoint}_length_is_not_zero");
54-
let length_is_one = format!("{entrypoint}_length_is_one");
55-
let length_is_gt_one = format!("{entrypoint}_length_is_gt_one");
5661
let bagging_loop = format!("{entrypoint}_loop");
5762

58-
let Digest(zero_digest) = bag_peaks(&[]);
63+
let destructure_mmra = MmrAccumulator::destructure();
64+
let pop_count = library.import(Box::new(arithmetic::u64::popcount::PopCount));
5965

6066
triton_asm!(
61-
// BEFORE: _ *peaks
67+
// BEFORE: _ *mmra
6268
// AFTER: _ [bagged_peaks: Digest]
6369
{entrypoint}:
64-
dup 0 read_mem 1 pop 1
65-
// _ *peaks length
70+
{&destructure_mmra}
71+
// _ *peaks *leaf_count
6672

67-
push {MAX_MMR_HEIGHT}
68-
dup 1
69-
lt
70-
assert
73+
addi 1 read_mem 2 pop 1
74+
hint leaf_count : u64 = stack[0..2]
75+
// _ *peaks [leaf_count]
7176

72-
/* special case: length is 0 */
73-
push 1
74-
dup 1 push 0 eq
75-
// _ *peaks length 1 (length==0)
77+
dup 1 dup 1
78+
call {pop_count}
79+
// _ *peaks [leaf_count] popcount
7680

77-
skiz call {length_is_zero}
78-
skiz call {length_is_not_zero}
81+
dup 3 read_mem 1 pop 1
82+
// _ *peaks [leaf_count] popcount num_peaks
7983

80-
// _ [bagged_peaks: Digest]
81-
return
84+
dup 1 eq
85+
// _ *peaks [leaf_count] pop_count (num_peaks==pop_count)
8286

83-
// BEFORE: _ *peaks length 1
84-
// AFTER: _ [bagged_peaks: Digest] 0
85-
{length_is_zero}:
86-
pop 3
87+
assert error_id {Self::INCONSISTENT_PEAKS_NUM}
88+
hint num_peaks: u32 = stack[0]
89+
// _ *peaks [leaf_count] num_peaks
8790

88-
push {zero_digest[4]}
89-
push {zero_digest[3]}
90-
push {zero_digest[2]}
91-
push {zero_digest[1]}
92-
push {zero_digest[0]}
93-
hint bag_of_no_peaks: Digest = stack[0..5]
91+
place 2
92+
// _ *peaks num_peaks [leaf_count]
93+
// _ *peaks len [leaf_count] <-- rename
9494

9595
push 0
96-
return
97-
98-
// BEFORE: _ *peaks length
99-
// AFTER: _ [bagged_peaks: Digest]
100-
{length_is_not_zero}:
101-
/* special case: length is 1 */
102-
push 1
103-
dup 1 push 1 eq
104-
// _ *peaks length 1 (length==1)
105-
106-
skiz call {length_is_one}
107-
skiz call {length_is_gt_one}
108-
return
109-
110-
// BEFORE: _ *peaks length 1
111-
// AFTER: _ [bagged_peaks: Digest] 0
112-
{length_is_one}:
113-
pop 2
114-
115-
push {Digest::LEN} add
116-
// _ *peaks[0]_lw
117-
118-
read_mem {Digest::LEN}
119-
pop 1
120-
12196
push 0
122-
return
97+
push 0
98+
push 0
99+
push 0
100+
push 0
101+
push 0
102+
push 0
103+
pick 9
104+
pick 9
105+
// _ *peaks len 0 0 0 0 0 0 0 0 [leaf_count; 2]
123106

124-
// BEFORE: _ *peaks length
125-
// AFTER: _ [bagged_peaks: Digest]
126-
{length_is_gt_one}:
127-
/* Get pointer to last word of peaks list */
107+
hash
108+
// _ *peaks len [hash_of_leaf_count]
109+
110+
pick 5
128111
push {Digest::LEN}
129112
mul
130-
// _ *peaks offset
113+
// _ *peaks [hash_of_leaf_count] size_of_peaks_list
131114

132-
dup 1
115+
dup 6
133116
add
134-
// _ *peaks *peaks[last]_lw
135-
136-
read_mem {Digest::LEN}
137-
// _ *peaks [peaks[last]: Digest] *peaks[last-1]_lw
117+
// _ *peaks [hash_of_leaf_count] *peaks[last]_lw
138118

139119
place 5
140-
// _ *peaks *peaks[last-1]_lw [peaks[last]: Digest]
141-
// _ *peaks *peaks[last-1]_lw [acc: Digest]
120+
// _ *peaks *peaks[last]_lw [hash_of_leaf_count]
121+
122+
dup 6 dup 6 eq push 0 eq
123+
// _ *peaks *peaks[last]_lw [hash_of_leaf_count] (num_peaks == 0)
142124

143-
call {bagging_loop}
144-
// *peaks *peaks [acc: Digest]
145-
// *peaks *peaks [bagged_peaks: Digest]
125+
skiz call {bagging_loop}
126+
// _ *peaks *peaks [bag_hash]
146127

147-
pick 6
148-
pick 6
149-
pop 2
128+
pick 6 pick 6 pop 2
129+
// _ [bagged_peaks: Digest]
150130

151131
return
152132

@@ -165,29 +145,31 @@ impl BasicSnippet for BagPeaks {
165145
}
166146

167147
fn sign_offs(&self) -> HashMap<Reviewer, SignOffFingerprint> {
168-
let mut sign_offs = HashMap::new();
169-
sign_offs.insert(Reviewer("ferdinand"), 0xaf79abb21cb46bf.into());
170-
sign_offs
148+
[].into()
171149
}
172150
}
173151

174152
#[cfg(test)]
175153
mod tests {
176154
use std::collections::HashMap;
177155

156+
use triton_vm::twenty_first::prelude::Mmr;
178157
use twenty_first::math::other::random_elements;
179158

180159
use super::*;
181160
use crate::test_prelude::*;
182161

183162
impl BagPeaks {
184-
fn set_up_initial_state(&self, num_peaks: usize) -> FunctionInitialState {
185-
let address = random();
163+
fn set_up_initial_state(&self, leaf_count: u64) -> FunctionInitialState {
164+
let address = rand::random();
186165
let mut stack = self.init_stack_for_isolated_run();
187166
push_encodable(&mut stack, &address);
188167

189168
let mut memory = HashMap::new();
190-
encode_to_memory(&mut memory, address, &random_elements::<Digest>(num_peaks));
169+
let num_peaks = leaf_count.count_ones();
170+
let mmra =
171+
MmrAccumulator::init(random_elements::<Digest>(num_peaks as usize), leaf_count);
172+
encode_to_memory(&mut memory, address, &mmra);
191173

192174
FunctionInitialState { stack, memory }
193175
}
@@ -200,28 +182,45 @@ mod tests {
200182
memory: &mut HashMap<BFieldElement, BFieldElement>,
201183
) {
202184
let address = pop_encodable(stack);
203-
let peaks = *Vec::<Digest>::decode_from_memory(memory, address).unwrap();
204-
push_encodable(stack, &bag_peaks(&peaks));
185+
let mmra = *MmrAccumulator::decode_from_memory(memory, address).unwrap();
186+
187+
fn bag_peaks(peaks: &[Digest], leaf_count: u64) -> Digest {
188+
// use `hash_10` over `hash` or `hash_varlen` to simplify hashing in Triton VM
189+
let [lo_limb, hi_limb] = leaf_count.encode()[..] else {
190+
panic!("internal error: unknown encoding of type `u64`")
191+
};
192+
let padded_leaf_count = bfe_array![lo_limb, hi_limb, 0, 0, 0, 0, 0, 0, 0, 0];
193+
let hashed_leaf_count = Digest::new(Tip5::hash_10(&padded_leaf_count));
194+
195+
peaks
196+
.iter()
197+
.rev()
198+
.fold(hashed_leaf_count, |acc, &peak| Tip5::hash_pair(peak, acc))
199+
}
200+
201+
let bag = bag_peaks(&mmra.peaks(), mmra.num_leafs());
202+
println!("bag: {bag}");
203+
push_encodable(stack, &bag);
205204
}
206205

207206
fn pseudorandom_initial_state(
208207
&self,
209208
seed: [u8; 32],
210209
bench_case: Option<BenchmarkCase>,
211210
) -> FunctionInitialState {
212-
let num_peaks = match bench_case {
213-
Some(BenchmarkCase::CommonCase) => 30,
214-
Some(BenchmarkCase::WorstCase) => 60,
215-
None => StdRng::from_seed(seed).gen_range(0..=63),
211+
let num_leafs = match bench_case {
212+
Some(BenchmarkCase::CommonCase) => 348753,
213+
Some(BenchmarkCase::WorstCase) => 843759843768,
214+
None => StdRng::from_seed(seed).random_range(0u64..(u64::MAX >> 1)),
216215
};
217216

218-
self.set_up_initial_state(num_peaks)
217+
self.set_up_initial_state(num_leafs)
219218
}
220219

221220
fn corner_case_initial_states(&self) -> Vec<FunctionInitialState> {
222221
(0..=5)
223222
.chain([63])
224-
.map(|num_peaks| self.set_up_initial_state(num_peaks))
223+
.map(|num_peaks| self.set_up_initial_state((1 << num_peaks) - 1))
225224
.collect()
226225
}
227226
}

0 commit comments

Comments
 (0)