Skip to main content

tfhe/integer/
block_decomposition.rs

1use crate::core_crypto::prelude::{CastFrom, CastInto, Numeric, SignedNumeric};
2use crate::integer::bigint::static_signed::StaticSignedBigInt;
3use crate::integer::bigint::static_unsigned::StaticUnsignedBigInt;
4use core::ops::{AddAssign, BitAnd, ShlAssign, ShrAssign};
5use std::ops::{BitOrAssign, Not, Shl, Shr, Sub};
6
7// These work for signed number as rust uses 2-Complements
8// And Arithmetic shift for signed number (logical for unsigned)
9// https://doc.rust-lang.org/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators
10
11pub trait Decomposable:
12    Numeric
13    + BitAnd<Self, Output = Self>
14    + ShrAssign<u32>
15    + Eq
16    + CastFrom<u32>
17    + Shr<u32, Output = Self>
18    + Shl<u32, Output = Self>
19    + BitOrAssign<Self>
20    + Not<Output = Self>
21{
22}
23pub trait Recomposable:
24    Numeric
25    + ShlAssign<u32>
26    + AddAssign<Self>
27    + CastFrom<u32>
28    + BitAnd<Self, Output = Self>
29    + Shl<u32, Output = Self>
30    + Sub<Self, Output = Self>
31{
32    // TODO: need for wrapping arithmetic traits
33    // This is a wrapping add but to avoid conflicts with other parts of the code using external
34    // wrapping traits definition we change the name here
35    #[must_use]
36    fn recomposable_wrapping_add(self, other: Self) -> Self;
37}
38
39// Convenience traits have simpler bounds
40pub trait RecomposableFrom<T>: Recomposable + CastFrom<T> {}
41pub trait DecomposableInto<T>: Decomposable + CastInto<T> {}
42
43macro_rules! impl_recomposable_decomposable {
44    (
45        $($type:ty),* $(,)?
46    ) => {
47        $(
48            impl Decomposable for $type { }
49            impl Recomposable for $type {
50                #[inline]
51                fn recomposable_wrapping_add(self, other: Self) -> Self {
52                    self.wrapping_add(other)
53                }
54            }
55            impl RecomposableFrom<u128> for $type { }
56            impl DecomposableInto<u128> for $type { }
57            impl RecomposableFrom<u64> for $type { }
58            impl DecomposableInto<u64> for $type { }
59            impl RecomposableFrom<u8> for $type { }
60            impl DecomposableInto<u8> for $type { }
61        )*
62    };
63}
64
65impl_recomposable_decomposable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128,);
66
67impl<const N: usize> Decomposable for StaticSignedBigInt<N> {}
68impl<const N: usize> Recomposable for StaticSignedBigInt<N> {
69    #[inline]
70    fn recomposable_wrapping_add(mut self, other: Self) -> Self {
71        self.add_assign(other);
72        self
73    }
74}
75impl<const N: usize> RecomposableFrom<u128> for StaticSignedBigInt<N> {}
76impl<const N: usize> RecomposableFrom<u64> for StaticSignedBigInt<N> {}
77impl<const N: usize> RecomposableFrom<u8> for StaticSignedBigInt<N> {}
78impl<const N: usize> DecomposableInto<u128> for StaticSignedBigInt<N> {}
79impl<const N: usize> DecomposableInto<u64> for StaticSignedBigInt<N> {}
80impl<const N: usize> DecomposableInto<u8> for StaticSignedBigInt<N> {}
81
82impl<const N: usize> Decomposable for StaticUnsignedBigInt<N> {}
83impl<const N: usize> Recomposable for StaticUnsignedBigInt<N> {
84    #[inline]
85    fn recomposable_wrapping_add(mut self, other: Self) -> Self {
86        self.add_assign(other);
87        self
88    }
89}
90impl<const N: usize> RecomposableFrom<u128> for StaticUnsignedBigInt<N> {}
91impl<const N: usize> RecomposableFrom<u64> for StaticUnsignedBigInt<N> {}
92impl<const N: usize> RecomposableFrom<u8> for StaticUnsignedBigInt<N> {}
93impl<const N: usize> DecomposableInto<u128> for StaticUnsignedBigInt<N> {}
94impl<const N: usize> DecomposableInto<u64> for StaticUnsignedBigInt<N> {}
95impl<const N: usize> DecomposableInto<u8> for StaticUnsignedBigInt<N> {}
96
97pub trait RecomposableSignedInteger:
98    RecomposableFrom<u64>
99    + std::ops::Neg<Output = Self>
100    + std::ops::Shr<u32, Output = Self>
101    + std::ops::BitOrAssign<Self>
102    + std::ops::BitOr<Self, Output = Self>
103    + std::ops::Mul<Self, Output = Self>
104    + SignedNumeric
105{
106}
107
108impl RecomposableSignedInteger for i8 {}
109impl RecomposableSignedInteger for i16 {}
110impl RecomposableSignedInteger for i32 {}
111impl RecomposableSignedInteger for i64 {}
112impl RecomposableSignedInteger for i128 {}
113
114impl<const N: usize> RecomposableSignedInteger for StaticSignedBigInt<N> {}
115
116pub trait SignExtendable:
117    std::ops::Shl<u32, Output = Self> + std::ops::Shr<u32, Output = Self> + SignedNumeric
118{
119}
120
121impl<T> SignExtendable for T where T: RecomposableSignedInteger {}
122
123/// This function takes a signed integer of type `T` for which `num_bits_set`
124/// have been set.
125///
126/// It will set the most significant bits to the value of the bit
127/// at pos `num_bits_set - 1`.
128///
129/// This is used to correctly decrypt a signed radix ciphertext into a clear type
130/// that has more bits than the original ciphertext.
131///
132/// This is like doing i8 as i16, i16 as i64, i16 as i8, etc
133pub(in crate::integer) fn sign_extend_partial_number<T>(unpadded_value: T, num_bits_set: u32) -> T
134where
135    T: SignExtendable,
136{
137    if num_bits_set >= T::BITS as u32 {
138        return unpadded_value;
139    }
140
141    // Shift to put the last set bit in the position of the sign bit of T
142    // When right shifting this will do the sign extend automatically
143    let shift = T::BITS as u32 - num_bits_set;
144    (unpadded_value << shift) >> shift
145}
146
147#[derive(Copy, Clone)]
148#[repr(u32)]
149pub enum PaddingBitValue {
150    Zero = 0,
151    One = 1,
152}
153
154#[derive(Clone)]
155pub struct BlockDecomposer<T> {
156    data: T,
157    bit_mask: T,
158    num_bits_in_mask: u32,
159    num_bits_valid: u32,
160    padding_bit: Option<PaddingBitValue>,
161    limit: Option<T>,
162}
163
164impl<T> BlockDecomposer<T>
165where
166    T: Decomposable,
167{
168    /// Creates a block decomposer that will stop when the value reaches zero
169    pub fn with_early_stop_at_zero(value: T, bits_per_block: u32) -> Self {
170        Self::new_(value, bits_per_block, Some(T::ZERO), None)
171    }
172
173    /// Creates a block decomposer that will set the surplus bits to a specific value
174    /// when bits_per_block is not a multiple of T::BITS
175    pub fn with_padding_bit(value: T, bits_per_block: u32, padding_bit: PaddingBitValue) -> Self {
176        Self::new_(value, bits_per_block, None, Some(padding_bit))
177    }
178
179    /// Creates a block decomposer that will return `block_count` blocks
180    ///
181    /// * If T is signed, extra block will be sign extended
182    pub fn with_block_count(value: T, bits_per_block: u32, block_count: usize) -> Self {
183        let mut decomposer = Self::new(value, bits_per_block);
184        let block_count: u32 = block_count.try_into().unwrap();
185        // If the new number of bits is less than the actual number of bits, it means
186        // data will be truncated
187        //
188        // If the new number of bits is greater than the actual number of bits, it means
189        // the right shift used internally will correctly sign extend for us
190        let num_bits_valid = block_count * bits_per_block;
191        decomposer.num_bits_valid = num_bits_valid;
192        decomposer
193    }
194
195    pub fn new(value: T, bits_per_block: u32) -> Self {
196        Self::new_(value, bits_per_block, None, None)
197    }
198
199    fn new_(
200        value: T,
201        bits_per_block: u32,
202        limit: Option<T>,
203        padding_bit: Option<PaddingBitValue>,
204    ) -> Self {
205        assert!(bits_per_block <= T::BITS as u32);
206        let num_bits_valid = T::BITS as u32;
207
208        let num_bits_in_mask = bits_per_block;
209        let bit_mask = 1_u32.checked_shl(bits_per_block).unwrap() - 1;
210        let bit_mask = T::cast_from(bit_mask);
211
212        Self {
213            data: value,
214            bit_mask,
215            num_bits_in_mask,
216            num_bits_valid,
217            limit,
218            padding_bit,
219        }
220    }
221
222    // We concretize the iterator type to allow usage of callbacks working on iterator for generic
223    // integer encryption
224    pub fn iter_as<V>(self) -> std::iter::Map<Self, fn(T) -> V>
225    where
226        V: Numeric,
227        T: CastInto<V>,
228    {
229        assert!(self.num_bits_in_mask <= V::BITS as u32);
230        self.map(CastInto::cast_into)
231    }
232
233    pub fn next_as<V>(&mut self) -> Option<V>
234    where
235        V: CastFrom<T>,
236    {
237        self.next().map(|masked| V::cast_from(masked))
238    }
239
240    pub fn checked_next_as<V>(&mut self) -> Option<V>
241    where
242        V: TryFrom<T>,
243    {
244        self.next().and_then(|masked| V::try_from(masked).ok())
245    }
246}
247
248impl<T> Iterator for BlockDecomposer<T>
249where
250    T: Decomposable,
251{
252    type Item = T;
253
254    fn next(&mut self) -> Option<Self::Item> {
255        // This works by using the mask to get the bits we need
256        // then shifting the source value to remove the bits
257        // we just masked to be ready for the next iteration.
258        if self.num_bits_valid == 0 {
259            return None;
260        }
261
262        if self.limit.is_some_and(|limit| limit == self.data) {
263            return None;
264        }
265
266        let mut masked = self.data & self.bit_mask;
267
268        if self.num_bits_in_mask < T::BITS as u32 {
269            self.data >>= self.num_bits_in_mask;
270        } else {
271            self.data = T::ZERO;
272        }
273
274        if self.num_bits_valid < self.num_bits_in_mask {
275            // This will be the case when self.num_bits_in_mask is not a multiple
276            // of T::BITS.
277            //
278            // We replace bits that do not come from the actual T but from the padding
279            // introduced by the shift, to a specific value, if one was provided.
280            if let Some(padding_bit) = self.padding_bit {
281                let padding_mask = (self.bit_mask >> self.num_bits_valid) << self.num_bits_valid;
282                masked = masked & !padding_mask;
283
284                let padding_bit = T::cast_from(padding_bit as u32);
285                for i in self.num_bits_valid..self.num_bits_in_mask {
286                    masked |= padding_bit << i;
287                }
288            }
289        }
290
291        self.num_bits_valid = self.num_bits_valid.saturating_sub(self.num_bits_in_mask);
292
293        Some(masked)
294    }
295
296    fn size_hint(&self) -> (usize, Option<usize>) {
297        // In the case self we constructed with an early stop value
298        // the upper bound might be higher than the actual number of iteration.
299        //
300        // The size_hint docs states that it is ok (not best thing
301        // but won't break code)
302        let max_remaining_iter = self.num_bits_valid / self.num_bits_in_mask;
303        let min_remaining_iter = if max_remaining_iter == 0 { 0 } else { 1 };
304        (min_remaining_iter, Some(max_remaining_iter as usize))
305    }
306}
307
308pub struct BlockRecomposer<T> {
309    data: T,
310    bit_mask: T,
311    num_bits_in_block: u32,
312    bit_pos: u32,
313}
314
315impl<T> BlockRecomposer<T>
316where
317    T: Recomposable,
318{
319    pub fn new(bits_per_block: u32) -> Self {
320        let num_bits_in_block = bits_per_block;
321        let bit_pos = 0;
322        let bit_mask = 1_u32.checked_shl(bits_per_block).unwrap() - 1;
323        let bit_mask = T::cast_from(bit_mask);
324
325        Self {
326            data: T::ZERO,
327            bit_mask,
328            num_bits_in_block,
329            bit_pos,
330        }
331    }
332
333    pub fn value(&self) -> T {
334        let is_signed = (T::ONE << (T::BITS as u32 - 1)) < T::ZERO;
335        if self.bit_pos >= (T::BITS as u32 - u32::from(is_signed)) {
336            self.data
337        } else {
338            let valid_mask = (T::ONE << self.bit_pos) - T::ONE;
339            self.data & valid_mask
340        }
341    }
342
343    pub fn unmasked_value(&self) -> T {
344        self.data
345    }
346
347    pub fn add_unmasked<V>(&mut self, block: V) -> bool
348    where
349        T: CastFrom<V>,
350    {
351        let casted_block = T::cast_from(block);
352        self.add(casted_block)
353    }
354
355    pub fn add_masked<V>(&mut self, block: V) -> bool
356    where
357        T: CastFrom<V>,
358    {
359        if self.bit_pos >= T::BITS as u32 {
360            return false;
361        }
362        let casted_block = T::cast_from(block);
363        self.add(casted_block & self.bit_mask)
364    }
365
366    fn add(&mut self, mut block: T) -> bool {
367        if self.bit_pos >= T::BITS as u32 {
368            return false;
369        }
370
371        block <<= self.bit_pos;
372        self.data = self.data.recomposable_wrapping_add(block);
373        self.bit_pos += self.num_bits_in_block;
374
375        true
376    }
377
378    /// Recompose an unsigned integer, assumes all limbs from input contribute `bits_in_block` bits
379    /// to the final result.
380    ///
381    /// Input is expected in little endian order.
382    pub fn recompose_unsigned<U>(input: impl Iterator<Item = U>, bits_in_block: u32) -> T
383    where
384        T: RecomposableFrom<U>,
385    {
386        let mut recomposer = Self::new(bits_in_block);
387        for limb in input {
388            if !recomposer.add_unmasked(limb) {
389                break;
390            }
391        }
392
393        recomposer.value()
394    }
395
396    /// Recompose an unsigned integer, all limbs from input are added as if contributing
397    /// `bits_in_block` bits to the result, `unsigned_integer_size` indicates which of the low bits
398    /// are actually considered as being part of the result, the bits beyond that are set to 0.
399    ///
400    /// Input is expected in little endian order.
401    pub fn recompose_unsigned_with_size<U>(
402        input: impl Iterator<Item = U>,
403        bits_in_block: u32,
404        unsigned_integer_size: u32,
405    ) -> T
406    where
407        T: RecomposableFrom<U>,
408    {
409        let mut recomposer = Self::new(bits_in_block);
410        for limb in input {
411            if !recomposer.add_unmasked(limb) {
412                break;
413            }
414        }
415
416        if T::BITS <= unsigned_integer_size as usize {
417            recomposer.value()
418        } else {
419            let mask = (T::ONE << unsigned_integer_size) - T::ONE;
420            recomposer.value() & mask
421        }
422    }
423
424    /// Recompose a signed integer, assumes all limbs from input contribute `bits_in_block` bits
425    /// to the final result.
426    ///
427    /// Input is expected in little endian order.
428    pub fn recompose_signed<U>(input: impl Iterator<Item = U>, bits_in_block: u32) -> T
429    where
430        T: RecomposableFrom<U> + SignExtendable,
431    {
432        let mut recomposer = Self::new(bits_in_block);
433        for limb in input {
434            if !recomposer.add_unmasked(limb) {
435                break;
436            }
437        }
438
439        sign_extend_partial_number(recomposer.value(), recomposer.bit_pos)
440    }
441
442    /// Recompose a signed integer, all limbs from input are added as if contributing
443    /// `bits_in_block` bits to the result, `signed_integer_size` indicates which of the low bits
444    /// are actually considered as being part of the result, this is used to decide which bit
445    /// represents the sign.
446    ///
447    /// For example with 2 limbs of 4 bits, if `signed_integer_size` is 6, then the 2 top bits from
448    /// the last limb are ignored.
449    ///
450    /// Input is expected in little endian order.
451    pub fn recompose_signed_with_size<U>(
452        input: impl Iterator<Item = U>,
453        bits_in_block: u32,
454        signed_integer_size: u32,
455    ) -> T
456    where
457        T: RecomposableFrom<U> + SignExtendable,
458    {
459        let mut recomposer = Self::new(bits_in_block);
460        for limb in input {
461            if !recomposer.add_unmasked(limb) {
462                break;
463            }
464        }
465
466        sign_extend_partial_number(recomposer.value(), signed_integer_size)
467    }
468}
469
470#[cfg(test)]
471mod tests {
472
473    use super::*;
474
475    #[test]
476    fn test_bit_block_decomposer() {
477        let value = u16::MAX as u32;
478        let bits_per_block = 2;
479        let blocks = BlockDecomposer::new(value, bits_per_block)
480            .iter_as::<u64>()
481            .collect::<Vec<_>>();
482        let expected_blocks = vec![3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0];
483        assert_eq!(expected_blocks, blocks);
484    }
485
486    #[test]
487    fn test_bit_block_decomposer_3() {
488        let bits_per_block = 3;
489
490        let value = -1i8;
491        let blocks = BlockDecomposer::new(value, bits_per_block)
492            .iter_as::<u64>()
493            .collect::<Vec<_>>();
494        // We expect the last block padded with 1s as a consequence of arithmetic shift
495        let expected_blocks = vec![7, 7, 7];
496        assert_eq!(expected_blocks, blocks);
497
498        let value = i8::MIN;
499        let blocks = BlockDecomposer::new(value, bits_per_block)
500            .iter_as::<u64>()
501            .collect::<Vec<_>>();
502        // We expect the last block padded with 1s as a consequence of arithmetic shift
503        let expected_blocks = vec![0, 0, 6];
504        assert_eq!(expected_blocks, blocks);
505
506        let value = -1i8;
507        let blocks =
508            BlockDecomposer::with_padding_bit(value, bits_per_block, PaddingBitValue::Zero)
509                .iter_as::<u64>()
510                .collect::<Vec<_>>();
511        // We expect the last block padded with 0s as we force that
512        let expected_blocks = vec![7, 7, 3];
513        assert_eq!(expected_blocks, blocks);
514    }
515
516    #[test]
517    fn test_bit_block_decomposer_with_block_count() {
518        let bits_per_block = 3;
519        let expected_blocks = [0, 0, 6, 7, 7, 7, 7, 7, 7];
520        let value = i8::MIN;
521        for block_count in 1..expected_blocks.len() {
522            let blocks = BlockDecomposer::with_block_count(value, bits_per_block, block_count)
523                .iter_as::<u64>()
524                .collect::<Vec<_>>();
525            assert_eq!(expected_blocks[..block_count], blocks);
526        }
527
528        let bits_per_block = 3;
529        let expected_blocks = [7, 7, 1, 0, 0, 0, 0, 0, 0];
530        let value = i8::MAX;
531        for block_count in 1..expected_blocks.len() {
532            let blocks = BlockDecomposer::with_block_count(value, bits_per_block, block_count)
533                .iter_as::<u64>()
534                .collect::<Vec<_>>();
535            assert_eq!(expected_blocks[..block_count], blocks);
536        }
537
538        let bits_per_block = 2;
539        let expected_blocks = [0, 0, 0, 2, 3, 3, 3, 3, 3];
540        let value = i8::MIN;
541        for block_count in 1..expected_blocks.len() {
542            let blocks = BlockDecomposer::with_block_count(value, bits_per_block, block_count)
543                .iter_as::<u64>()
544                .collect::<Vec<_>>();
545            assert_eq!(expected_blocks[..block_count], blocks);
546        }
547
548        let bits_per_block = 2;
549        let expected_blocks = [3, 3, 3, 1, 0, 0, 0, 0, 0, 0];
550        let value = i8::MAX;
551        for block_count in 1..expected_blocks.len() {
552            let blocks = BlockDecomposer::with_block_count(value, bits_per_block, block_count)
553                .iter_as::<u64>()
554                .collect::<Vec<_>>();
555            assert_eq!(expected_blocks[..block_count], blocks);
556        }
557    }
558
559    #[test]
560    fn test_bit_block_decomposer_recomposer_carry_handling_in_between() {
561        let value = u16::MAX as u32;
562        let bits_per_block = 2;
563        let mut blocks = BlockDecomposer::new(value, bits_per_block)
564            .iter_as::<u64>()
565            .collect::<Vec<_>>();
566        let expected_blocks = vec![3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0];
567        assert_eq!(expected_blocks, blocks);
568
569        // Now this block, which is not the last will have a 'carry'
570        blocks[0] += 2;
571
572        let mut recomposer = BlockRecomposer::new(bits_per_block);
573        for block in blocks {
574            recomposer.add_unmasked(block);
575        }
576        let recomposed: u32 = recomposer.value();
577        assert_eq!(recomposed, value.wrapping_add(2));
578    }
579
580    #[test]
581    fn test_bit_block_decomposer_recomposer_carry_overflow() {
582        let value = u16::MAX;
583        let bits_per_block = 2;
584        let mut blocks = BlockDecomposer::new(value, bits_per_block)
585            .iter_as::<u64>()
586            .collect::<Vec<_>>();
587        let expected_blocks = vec![3, 3, 3, 3, 3, 3, 3, 3];
588        assert_eq!(expected_blocks, blocks);
589
590        // Now this block, which is not the last will have a 'carry'
591        blocks[0] += 2;
592
593        let mut recomposer = BlockRecomposer::new(bits_per_block);
594        for block in blocks {
595            recomposer.add_unmasked(block);
596        }
597        let recomposed: u16 = recomposer.value();
598        assert_eq!(recomposed, value.wrapping_add(2));
599    }
600
601    #[test]
602    fn test_bit_block_decomposer_recomposer_carry_bigger_recomposed_type() {
603        // Test that when we use a bigger type to decompose / recompose our value
604        // (by taking a smaller number of blocks), the recomposed value is
605        // ok
606        let value = u8::MAX as u16;
607        let bits_per_block = 2;
608        let mut blocks = BlockDecomposer::new(value, bits_per_block)
609            .iter_as::<u64>()
610            .take(4)
611            .collect::<Vec<_>>();
612        let expected_blocks = vec![3, 3, 3, 3];
613        assert_eq!(expected_blocks, blocks);
614
615        // Now this block, which is not the last will have a 'carry'
616        blocks[0] += 2;
617
618        let mut recomposer = BlockRecomposer::new(bits_per_block);
619        for block in blocks {
620            recomposer.add_unmasked(block);
621        }
622        let recomposed: u16 = recomposer.value();
623        assert_eq!(recomposed, u8::MAX.wrapping_add(2) as u16);
624    }
625
626    #[test]
627    fn test_bit_block_decomposer_round_trip_unsigned() {
628        for i in 0..u32::BITS {
629            let value = (u16::MAX as u32).rotate_left(i);
630            let bits_per_block = 2;
631            let blocks = BlockDecomposer::new(value, bits_per_block)
632                .iter_as::<u64>()
633                .collect::<Vec<_>>();
634
635            let mut recomposer = BlockRecomposer::new(bits_per_block);
636            for block in blocks {
637                recomposer.add_unmasked(block);
638            }
639            let recomposed: u32 = recomposer.value();
640            assert_eq!(recomposed, value);
641        }
642    }
643
644    #[test]
645    fn test_bit_block_decomposer_round_trip_signed() {
646        for i in 0..i32::BITS {
647            let value = (i16::MAX as i32).rotate_left(i);
648            let bits_per_block = 2;
649            let blocks = BlockDecomposer::new(value, bits_per_block).collect::<Vec<_>>();
650
651            let mut recomposer = BlockRecomposer::new(bits_per_block);
652            for block in blocks {
653                recomposer.add_unmasked(block);
654            }
655            let recomposed: i32 = recomposer.value();
656            assert_eq!(recomposed, value);
657        }
658    }
659
660    /// Test that when the bits per block is not a multiple of the number of bytes
661    /// we can decompose and recompose
662    #[test]
663    fn test_bit_block_decomposer_round_trip_non_multiple_bits_per_block() {
664        for i in 0..u32::BITS {
665            let value = (u16::MAX as u32).rotate_left(i);
666            let bits_per_block = 3;
667            let blocks = BlockDecomposer::new(value, bits_per_block)
668                .iter_as::<u64>()
669                .collect::<Vec<_>>();
670
671            let mut recomposer = BlockRecomposer::new(bits_per_block);
672            for block in blocks {
673                recomposer.add_unmasked(block);
674            }
675            let recomposed: u32 = recomposer.value();
676            assert_eq!(recomposed, value);
677        }
678    }
679}