tasm_lib/hashing/
absorb_multiple_static_size.rs

1use triton_vm::prelude::*;
2use twenty_first::tip5::RATE;
3
4use crate::memory::load_words_from_memory_pop_pointer;
5use crate::prelude::*;
6
7/// Absorb a sequence of field elements stored in memory, into the Sponge.
8#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
9pub struct AbsorbMultipleStaticSize {
10    pub size: usize,
11}
12
13impl AbsorbMultipleStaticSize {
14    fn padded_length(&self) -> usize {
15        (self.size + 1).next_multiple_of(RATE)
16    }
17
18    fn num_absorbs_before_pad(&self) -> usize {
19        self.padded_length() / RATE - 1
20    }
21
22    fn num_remaining_words(&self) -> usize {
23        self.size - self.num_absorbs_before_pad() * RATE
24    }
25
26    fn read_remainder_and_pad(&self) -> Vec<LabelledInstruction> {
27        let num_zeros = self.padded_length() - self.size - 1;
28        let adjust_read_pointer = match self.num_remaining_words() {
29            0 | 1 => triton_asm!(),
30            n => triton_asm!(
31                // _ *first_unread_word
32                push {n-1}
33                add
34                // _ *last_unread_word
35            ),
36        };
37        [
38            triton_asm![push 0; num_zeros],
39            triton_asm!(push 1),
40            triton_asm!(dup {num_zeros + 1}),
41            // _ *first_unread_word [0] 1 *first_unread_word
42            adjust_read_pointer,
43            // _ *first_unread_word [0] 1 *last_unread_word
44            load_words_from_memory_pop_pointer(self.num_remaining_words()),
45            // _ *first_unread_word [0] 1 [un-absorbed-words]
46        ]
47        .concat()
48    }
49}
50
51impl BasicSnippet for AbsorbMultipleStaticSize {
52    fn inputs(&self) -> Vec<(DataType, String)> {
53        vec![(DataType::VoidPointer, "*sequence".to_string())]
54    }
55
56    fn outputs(&self) -> Vec<(DataType, String)> {
57        vec![(DataType::VoidPointer, "(sequence + size)".to_string())]
58    }
59
60    fn entrypoint(&self) -> String {
61        format!("tasmlib_hashing_absorb_multiple_static_size_{}", self.size)
62    }
63
64    fn code(&self, _: &mut Library) -> Vec<LabelledInstruction> {
65        let entrypoint = self.entrypoint();
66
67        let absorb_all_non_padded = triton_asm![sponge_absorb_mem; self.num_absorbs_before_pad()];
68        let read_remainder_and_pad = self.read_remainder_and_pad();
69
70        triton_asm! {
71            // BEFORE: _ *bfe_sequence
72            // AFTER:  _ (*bfe_sequence + self.size)
73            {entrypoint}:
74                // _ *bfe_sequence
75
76                push 0
77                push 0
78                push 0
79                push 0
80                swap 4
81                // _ 0 0 0 0 *bfe_sequence
82
83                {&absorb_all_non_padded}
84                // _ g0 g1 g2 g3 *next_unread_word
85
86                swap 4
87                pop 4
88                // _ *next_unread_word
89
90                {&read_remainder_and_pad}
91
92                sponge_absorb
93                push {self.size % RATE}
94                add
95
96                // _ (*address + self.size)
97
98                return
99        }
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use twenty_first::prelude::Sponge;
106
107    use super::*;
108    use crate::test_prelude::*;
109
110    impl Procedure for AbsorbMultipleStaticSize {
111        fn rust_shadow(
112            &self,
113            stack: &mut Vec<BFieldElement>,
114            memory: &mut HashMap<BFieldElement, BFieldElement>,
115            _: &NonDeterminism,
116            _: &[BFieldElement],
117            sponge: &mut Option<Tip5>,
118        ) -> Vec<BFieldElement> {
119            // read arguments
120            let address = stack.pop().unwrap();
121
122            // read sequence from memory
123            let mut sequence = vec![];
124            for i in 0..self.size {
125                sequence.push(
126                    memory
127                        .get(&(address + BFieldElement::new(i as u64)))
128                        .copied()
129                        .unwrap(),
130                )
131            }
132
133            let sponge = sponge.as_mut().expect("sponge must be initialized");
134            sponge.pad_and_absorb_all(&sequence);
135
136            stack.push(address + BFieldElement::new(self.size.try_into().unwrap()));
137
138            // output empty
139            vec![]
140        }
141
142        fn pseudorandom_initial_state(
143            &self,
144            seed: [u8; 32],
145            _bench_case: Option<BenchmarkCase>,
146        ) -> ProcedureInitialState {
147            let mut rng = StdRng::from_seed(seed);
148
149            // sample address
150            let address = BFieldElement::new(rng.next_u64() % (1 << 20));
151
152            let sequence = (0..self.size)
153                .map(|_| rng.random::<BFieldElement>())
154                .collect_vec();
155
156            // write to memory
157            let mut memory = HashMap::new();
158            for (i, s) in sequence.into_iter().enumerate() {
159                memory.insert(address + BFieldElement::new(i as u64), s);
160            }
161            let nondeterminism = NonDeterminism::default().with_ram(memory);
162
163            let stack = [self.init_stack_for_isolated_run(), vec![address]].concat();
164
165            let vm_hasher_state = Tip5 {
166                state: rng.random(),
167            };
168
169            ProcedureInitialState {
170                stack,
171                nondeterminism,
172                public_input: vec![],
173                sponge: Some(vm_hasher_state),
174            }
175        }
176    }
177
178    #[test]
179    fn absorb_multiple_static_size_0() {
180        ShadowedProcedure::new(AbsorbMultipleStaticSize { size: 0 }).test();
181    }
182
183    #[test]
184    fn absorb_multiple_static_size_1() {
185        ShadowedProcedure::new(AbsorbMultipleStaticSize { size: 1 }).test();
186    }
187
188    #[test]
189    fn absorb_multiple_static_size_9() {
190        ShadowedProcedure::new(AbsorbMultipleStaticSize { size: 9 }).test();
191    }
192
193    #[test]
194    fn absorb_multiple_static_size_small_pbt() {
195        for size in 0..30 {
196            println!("Testing size {size}");
197            ShadowedProcedure::new(AbsorbMultipleStaticSize { size }).test();
198        }
199    }
200
201    #[proptest(cases = 40)]
202    fn absorb_multiple_static_size_pbt_pbt(#[strategy(arb())] size: u8) {
203        ShadowedProcedure::new(AbsorbMultipleStaticSize {
204            size: size as usize,
205        })
206        .test();
207    }
208}
209
210#[cfg(test)]
211mod benches {
212    use super::*;
213    use crate::test_prelude::*;
214
215    #[test]
216    fn absorb_multiple_static_size_benchmark_102() {
217        ShadowedProcedure::new(AbsorbMultipleStaticSize { size: 102 }).bench();
218    }
219
220    #[test]
221    fn absorb_multiple_static_size_benchmark_400() {
222        ShadowedProcedure::new(AbsorbMultipleStaticSize { size: 400 }).bench();
223    }
224
225    #[test]
226    fn absorb_multiple_static_size_benchmark_2002() {
227        ShadowedProcedure::new(AbsorbMultipleStaticSize { size: 2002 }).bench();
228    }
229}