1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
use super::engine_error;
use crate::specification::engines::AbstractEngine;
use crate::specification::entities::{
    LweBootstrapKeyEntity, LweCiphertextVectorEntity,
    LweCircuitBootstrapPrivateFunctionalPackingKeyswitchKeysEntity, PlaintextVectorEntity,
};
use crate::specification::parameters::{DecompositionBaseLog, DecompositionLevelCount};

engine_error! {
    LweCiphertextVectorDiscardingCircuitBootstrapBooleanVerticalPackingError for
    LweCiphertextVectorDiscardingCircuitBootstrapBooleanVerticalPackingEngine @
    NullDecompositionBaseLog => "The circuit bootstrap decomposition base log must be greater \
                                than zero.",
    NullDecompositionLevelCount => "The circuit bootstrap decomposition level count must be \
                                    greater than zero.",
    DecompositionTooLarge => "The decomposition precision (base log * level count) must not exceed \
                              the precision of the ciphertext.",
    KeysLweDimensionMismatch => "The bootstrap key output LWE dimension must be the same as the \
                                input LWE dimension of the circuit bootstrap private functional \
                                packing keyswitch keys.",
    InputLweDimensionMismatch => "The input ciphertexts LWE dimension must be the same as the \
                                    bootstrap key input LWE dimension.",
    OutputLweDimensionMismatch => "The output ciphertexts LWE dimension must be the same as the \
                                    `cbs_pfpksk` output GLWE dimension times its output polynomial \
                                    size.",
    MalformedLookUpTables => "The input `luts` must have a size divisible by the circuit bootstrap \
                                private functional packing keyswitch keys output polynomial size \
                                times the number of output ciphertexts. This is required to get \
                                small look-up tables of polynomials of the same size for each \
                                output ciphertext.",
    InvalidSmallLookUpTableSize => "The number of polynomials times the polynomial size in a small \
                                    look-up table must be equal to 2 to the power the number of \
                                    input ciphertexts encrypting bits."
}

impl<EngineError: std::error::Error>
    LweCiphertextVectorDiscardingCircuitBootstrapBooleanVerticalPackingError<EngineError>
{
    /// Validates the inputs
    #[allow(clippy::too_many_arguments)]
    pub fn perform_generic_checks<
        Input: LweCiphertextVectorEntity,
        Output: LweCiphertextVectorEntity,
        BootstrapKey: LweBootstrapKeyEntity,
        LUTs: PlaintextVectorEntity,
        CBSPFPKSK: LweCircuitBootstrapPrivateFunctionalPackingKeyswitchKeysEntity,
    >(
        input: &Input,
        output: &Output,
        bsk: &BootstrapKey,
        luts: &LUTs,
        cbs_decomposition_level_count: DecompositionLevelCount,
        cbs_decomposition_base_log: DecompositionBaseLog,
        cbs_pfpksk: &CBSPFPKSK,
        ciphertext_modulus_log: usize,
    ) -> Result<(), Self> {
        if bsk.output_lwe_dimension() != cbs_pfpksk.input_lwe_dimension() {
            return Err(Self::KeysLweDimensionMismatch);
        }
        if input.lwe_dimension() != bsk.input_lwe_dimension() {
            return Err(Self::InputLweDimensionMismatch);
        }
        if output.lwe_dimension().0
            != cbs_pfpksk.output_glwe_dimension().0 * cbs_pfpksk.output_polynomial_size().0
        {
            return Err(Self::OutputLweDimensionMismatch);
        }

        let lut_polynomial_size = cbs_pfpksk.output_polynomial_size().0;
        if luts.plaintext_count().0 % (lut_polynomial_size * output.lwe_ciphertext_count().0) != 0 {
            return Err(Self::MalformedLookUpTables);
        }

        let small_lut_size = luts.plaintext_count().0 / output.lwe_ciphertext_count().0;
        if small_lut_size < lut_polynomial_size {
            return Err(Self::InvalidSmallLookUpTableSize);
        }

        if cbs_decomposition_level_count.0 == 0 {
            return Err(Self::NullDecompositionBaseLog);
        }
        if cbs_decomposition_level_count.0 == 0 {
            return Err(Self::NullDecompositionLevelCount);
        }
        if cbs_decomposition_base_log.0 * cbs_decomposition_level_count.0 > ciphertext_modulus_log {
            return Err(Self::DecompositionTooLarge);
        }
        Ok(())
    }
}

/// A trait for engines performing a (discarding) boolean circuit bootstrapping followed by a
/// vertical packing on LWE ciphertext vectors. The term "boolean" refers to the fact the input
/// ciphertexts encrypt a single bit of message.
///
/// The provided "big" `luts` look-up table is expected to be divisible into the same number of
/// chunks of polynomials as there are ciphertexts in the `output` LweCiphertextVector. Each chunk
/// of polynomials is used as a look-up table to evaluate during the vertical packing operation to
/// fill an output ciphertext.
///
/// Note that there should be enough polynomials provided in each chunk to perform the vertical
/// packing given the number of boolean input ciphertexts. The number of boolean input ciphertexts
/// is in fact a number of bits. For this example let's say we have 16 input ciphertexts
/// representing 16 bits and want to output 4 ciphertexts. The "big" `luts` will need to be
/// divisible into 4 chunks of equal size. If the polynomial size used is $1024 = 2^{10}$ then each
/// chunk must contain $2^6 = 64$ polynomials ($2^6 * 2^{10} = 2^{16}$) to match the amount of
/// values representable by the 16 input ciphertexts each encrypting a bit. The "big" `luts` then
/// has a layout looking as follows:
///
/// ```text
/// small lut for 1st output ciphertext|...|small lut for 4th output ciphertext
/// |[polynomial 1] ... [polynomial 64]|...|[polynomial 1] ... [polynomial 64]|
/// ```
///
/// The polynomials in the above representation are not necessarily the same, this is just for
/// illustration purposes.
///
/// It is also possible in the above example to have a single polynomial of size $2^{16} = 65 536$
/// for each chunk if the polynomial size is supported for computation (which is not the case for 65
/// 536 at the moment for implemented backends). Chunks containing a single polynomial of size
/// $2^{10} = 1024$ would work for example for 10 input ciphertexts as that polynomial size is
/// supported for computations. The "big" `luts` layout would then look as follows for that 10 bits
/// example (still with 4 output ciphertexts):
///
/// ```text
/// small lut for 1st output ciphertext|...|small lut for 4th output ciphertext
/// |[          polynomial 1          ]|...|[          polynomial 1          ]|
/// ```
///
/// # Semantics
///
/// This [discarding](super#operation-semantics) operation first performs the circuit bootstrapping
/// on all boolean (i.e. containing only 1 bit of message) input LWE ciphertexts from the `input`
/// vector. It then fills the `output` LWE ciphertext vector with the result of the vertical packing
/// operation applied on the output of the circuit bootstrapping, using the provided look-up table.
///
/// # Formal Definition
pub trait LweCiphertextVectorDiscardingCircuitBootstrapBooleanVerticalPackingEngine<
    Input,
    Output,
    BootstrapKey,
    LUTs,
    CirctuiBootstrapFunctionalPackingKeyswitchKeys,
>: AbstractEngine where
    Input: LweCiphertextVectorEntity,
    Output: LweCiphertextVectorEntity,
    BootstrapKey: LweBootstrapKeyEntity,
    LUTs: PlaintextVectorEntity,
    CirctuiBootstrapFunctionalPackingKeyswitchKeys:
        LweCircuitBootstrapPrivateFunctionalPackingKeyswitchKeysEntity,
{
    /// Performs the circuit bootstrapping on all boolean input LWE ciphertexts followed by vertical
    /// packing using the provided look-up table.
    #[allow(clippy::too_many_arguments)]
    fn discard_circuit_bootstrap_boolean_vertical_packing_lwe_ciphertext_vector(
        &mut self,
        output: &mut Output,
        input: &Input,
        bsk: &BootstrapKey,
        luts: &LUTs,
        cbs_level_count: DecompositionLevelCount,
        cbs_base_log: DecompositionBaseLog,
        cbs_pfpksk: &CirctuiBootstrapFunctionalPackingKeyswitchKeys,
    ) -> Result<
        (),
        LweCiphertextVectorDiscardingCircuitBootstrapBooleanVerticalPackingError<Self::EngineError>,
    >;

    /// Unsafely performs the circuit bootstrapping on all boolean input LWE ciphertexts followed by
    /// vertical packing using the provided look-up table.
    ///
    /// # Safety
    /// For the _general_ safety concerns regarding this operation, refer to the different variants
    /// of [`LweCiphertextVectorDiscardingCircuitBootstrapBooleanVerticalPackingError`]. For safety
    /// concerns _specific_ to an engine, refer to the implementer safety section.
    #[allow(clippy::too_many_arguments)]
    unsafe fn discard_circuit_bootstrap_boolean_vertical_packing_lwe_ciphertext_vector_unchecked(
        &mut self,
        output: &mut Output,
        input: &Input,
        bsk: &BootstrapKey,
        luts: &LUTs,
        cbs_level_count: DecompositionLevelCount,
        cbs_base_log: DecompositionBaseLog,
        cbs_pfpksk: &CirctuiBootstrapFunctionalPackingKeyswitchKeys,
    );
}