text:
\section{Introduction} \vspace{-1px}
\input{sections/intro.tex}
\section{Background Knowledge} \vspace{-1px}
\input{sections/background.tex} \vspace{-2px}
\section{Our Context for Private Inference} \vspace{-2px}
\input{sections/context.tex}
\section{Defeating Collateral Learning} \vspace{-2px}
\input{sections/adversarial.tex}
\section{Experimental Results}
\input{sections/results.tex}
\section{Conclusion}
\input{sections/conclusion.tex}

\section*{Acknowledgments}

This work was supported in part by the European Community's Seventh Framework Programme (FP7/2007-2013 Grant Agreement no. 339563 -- CryptoCloud), the European Community's Horizon 2020 Project FENTEC (Grant Agreement no. 780108), the Google PhD fellowship, and the French FUI ANBLIC Project.

\bibliographystyle{plain}

\subsection{Reducing information leakage}\label{ReduceLeak}

Our first approach is based on the observation that we leak many bits of information. We first investigate whether we can reduce the number of outputs of the privately-evaluted network, as adding extra layers on top of the private network makes it no longer necessary to keep $10$ of them.
\begin{wrapfigure}{r}{0.5\textwidth}
  \centering
  \includegraphics[width=1\linewidth]{figures/Pareto_OutputSize_noSabotage.png}
  \caption{Trade-off between main and collateral accuracies depending on the private output size. }\label{fig:pareto_nosabo}
\end{wrapfigure}
The intuition is that if the information that is relevant to the main task can fit in less than 10 neurons, then the extra neurons would leak unnecessary information.
We have therefore a trade-off between reducing too much and losing desired information or keeping a too large output and having an important leakage. We can observe this through the respective accuracies as it is shown in Figure \ref{fig:pareto_nosabo}, where the main and adversarial networks are CNNs as in Section \ref{sec:CLthreat} with 10 epochs of training using 7-fold cross validation.
What we observe here is interesting: the main task does not exhibit significant weaknesses even with size 3 where we drop to $97.1\%$ which is still very good although $2\%$ under the best accuracy. In return, the collateral accuracy starts to significantly decrease when output size is below 7. At size 4, it is only $76.4\%$ on average so $18\%$ less than the baseline. We will keep an output size of 3 or 4 for the next experiments to keep the main accuracy almost unchanged.

Another hyperparameter that we can consider is the weight compression: how many bits do we need to represent the weights on the private networks layers? This is of interest for the FE scheme as we need to convert all weights to integers and those integers will be low provided that the compression rate is high. Small weight integers mean that the output of the private network has a relatively low amplitude and can be therefore efficiently decrypted using discrete logarithm. We managed to express all weights and even the input image using 4 bit values with limited impact on the main accuracy and almost none on the collateral one. Details about compression can be found in Appendix \ref{app:Wcompression}.

\subsection{Adversarial training}

We propose a new approach to actively adapt against collateral learning. The main idea is to simulate adversaries and to try to defeat them. To do this, we use semi-adversarial training and optimize simultaneously the main classification objective and the opposite of the collateral objective of a given simulated adversary. The function that we want to minimize at each iteration step can be written:
\[  \min_{\theta_q} [
    \min_{\theta_{\mathrm{pub}}} \loss_{\mathrm{pub}}(\theta_q, \theta_{\mathrm{pub}}) - \alpha \min_{\theta_{\mathrm{pub}}} \loss_{\mathrm{priv}}(\theta_q, \theta_{\mathrm{pub}})
    ] .
\]
This approach is inspired from \cite{NIPS2017_6699} where the authors train some objective against nuisances parameters to build a classifier independent from these nuisances. Private features leaking in our scheme can indeed be considered to be a nuisance. However, our approach goes one step further as we do not just stack a network above another; our global network structure is fork-like: the common basis is the private network and the two forks are the main and collateral classifiers. This allows us to have a better classifier for the main task which is not as sensitive to the adversarial training as the scheme exposed by \cite[Figure 1]{NIPS2017_6699}. One other difference is that the collateral classifier is a specific modeling of an adversary, and we will discuss this in details in the next section.
We define in Figure \ref{fig:AdvTraining} the 3-step procedure used to implement this semi-adversarial training using partial back-propagation.

\begin{figure*}
  \begin{center}
    {\footnotesize
    \begin{tabular}{|l|}\hline
        \underline{Pre-training}: \textit{Initial phase where both tasks learn and strengthen before the joint optimization}\\
        Minimize $\loss_{\mathrm{pub}}(\theta_q, \theta_{\mathrm{pub}})$\\
        Minimize $\loss_{\mathrm{priv}}(\mathsf{Frozen}(\theta_q), \theta_{\mathrm{priv}})$\\
        \\
        \underline{Semi-adversarial training}: \textit{The joint optimization phase, where $\theta_{\mathrm{pub}}$ and $\theta_{\mathrm{priv}}$ are updated depending on}\\
        \textit{the variations of $\theta_q$ and $\theta_q$ is optimized to reduce the loss $\mathcal{L} = \loss_{\mathrm{pub}} - \alpha \loss_{\mathrm{priv}}$}\\
        Minimize $\loss_{\mathrm{pub}}(\mathsf{Frozen}(\theta_q), \theta_{\mathrm{pub}})$\\
        Minimize $\loss_{\mathrm{priv}}(\mathsf{Frozen}(\theta_q), \theta_{\mathrm{priv}})$\\
        Minimize $\mathcal{L} = \loss_{\mathrm{pub}}(\theta_q, \mathsf{Frozen}(\theta_{\mathrm{pub}})) - \alpha \loss_{\mathrm{priv}}(\theta_q, \mathsf{Frozen}(\theta_{\mathrm{pub}})) $\\
        \\
        \underline{Recover phase}: \textit{Both tasks recover from the perturbations induced by the adversarial phase, $\theta_q$ does not}\\
        \textit{change anymore}\\
        Minimize $\loss_{\mathrm{pub}}(\mathsf{Frozen}(\theta_q), \theta_{\mathrm{pub}})$\\
        Minimize $\loss_{\mathrm{priv}}(\mathsf{Frozen}(\theta_q), \theta_{\mathrm{priv}})$\\
        [1.5ex]\hline
    \end{tabular}
    }
    \caption{Our semi-adversarial training scheme.}\label{fig:AdvTraining}
  \end{center}
\end{figure*}

\section{Functional Encryption and crypto tools}\label{appendix:FEsec}

\subsection{Formal definition of Functional Encryption}\label{appendix:FE}

Functional encryption relies on a pair of keys like in public key encryption: a master secret key $\mathsf{msk}$ and a public key $\mathsf{pk}$. The public key $\mathsf{pk}$ can be shared and is used to encrypt the data, while the master secret key $\mathsf{msk}$   is used to build functional decryption keys $\mathsf{dk}_{f}$ for $f \in \mathcal{F}$. A user having access to $c$ an encryption of $x$ with $\mathsf{pk}$ and to $\mathsf{dk}_{f}$ can learn $f(x)$ but can't learn anything else about $x$.

We give the definition of Functional Encryption, originally defined in \cite{TCC:BonSahWat11,cryptoeprint:2010:556}.

\begin{definition}[Functional Encryption]
    A {\em functional encryption} scheme $\mathsf{FE}$ for a set of functions $\mathcal{F} \subseteq \mathcal{X} \rightarrow \mathcal{Y}$ is a tuple of PPT algorithms
    $\mathsf{FE} = (\mathsf{SetUp}\xspace, \allowbreak \mathsf{KeyGen}\xspace, \allowbreak  \Enc, \allowbreak  \Dec)$ defined as follows.
    \begin{description}
        \item[$\mathsf{SetUp}\xspace(1^{\lambda},\mathcal{F})$] takes as input a security parameter $1^{\lambda}$, the set of functions $\mathcal{F}$, and outputs a master secret key $\mathsf{msk}$ and a public key $\mathsf{pk}$.

        \item[$\mathsf{KeyGen}\xspace(\mathsf{msk},f)$] takes as input the master secret key and a function $f \in \mathcal{F}$, and outputs a functional decryption key $\mathsf{dk}_{f}$.

        \item[$\Enc(\mathsf{pk}, x)$] takes as input the public key $\mathsf{pk}$ and a
        message $x \in \mathcal{X}$, and outputs a ciphertext $\mathsf{ct}$.

        \item[$\Dec(\mathsf{dk}_f, \mathsf{ct})$] takes as input a functional decryption key $\mathsf{dk}_{f}$ and a ciphertext
        $\mathsf{ct}$, and returns an output $y \in \mathcal{Y} \cup \{\bot\}$, where $\bot$ is a special rejection symbol.
    \end{description}
\end{definition}

\subsection{IND-CPA security}\label{app:ind-cpa}
With notations of Appendix \ref{appendix:FE}, for any stateful adversary $\mathcal{A}$ and any functional encryption scheme $\mathsf{FE}$, we define the following advantage.

\[\mathsf{Adv}^{\mathsf{FE}}_{\mathcal{A}}(\lambda) := \Pr\left[\beta' = \beta :\begin{array}{l}
(\mathsf{pk},\mathsf{msk}) \gets \mathsf{SetUp}\xspace(1^\lambda,\mathcal{F})\\
(x_0,x_1) \gets \mathcal{A}^{\mathsf{KeyGen}\xspace(\mathsf{msk},\cdot)}(\mathsf{pk})\\
\beta \stackrel{{}_\$}{\leftarrow} \{0,1\},
\mathsf{ct} \gets \Enc(\mathsf{pk},x_\beta)\\
\beta' \gets \mathcal{A}^{\mathsf{KeyGen}\xspace(\mathsf{msk},\cdot)}(\mathsf{ct})
\end{array}\right] - \frac{1}{2},\]

with the restriction that all queries $f$ that $\mathcal{A}$ makes to key generation algorithm
$\mathsf{KeyGen}\xspace(\mathsf{msk},\cdot)$ must satisfy $f(x_0)=f(x_1)$.

We say $\mathsf{FE}$ is IND-CPA secure if for all PPT adversaries $\mathcal{A}$, $\mathsf{Adv}^{\mathsf{FE}}_{\mathcal{A}}(\lambda) = \negl(\lambda)$\footnote{In cryptography, the security parameter $\lambda$ is a measure of the probability with which an adversary can break the scheme. $\lambda$ or $1^\lambda$ means that the probability of breaking the scheme is $2^{-\lambda}$.}.

\subsection{Bilinear Groups}
\label{sec:group}

Our FE scheme uses bilinear (or \textit{pairing}) groups, whose use in cryptography has been introduced by \cite{BonFra03,JC:Joux04}.
More precisely, given $\lambda$ a security parameter, let $\mathbb{G}_1$ and $\mathbb{G}_2$ be two cyclic groups of prime order $p$ (for a $2\lambda$-bit prime $p$) and $g_1$ and $g_2$ their generators, respectively.
The application $e: \mathbb{G}_1 \times \mathbb{G}_2 \rightarrow \mathbb{G}_T$ is a pairing if it is efficiently computable, non-degenerated, and bilinear: $e(g_1^\alpha,g_2^\beta) = e(g_1,g_2)^{\alpha \beta}$ for any $\alpha,\beta\in\mathbb{Z}_p$. Additionally, we define $g_T := e(g_1, g_2)$ which spans the group $\mathbb{G}_T$ of prime order $p$.

We will denote by $\mathsf{GGen}$ a probabilistic polynomial-time (PPT) algorithm that on input $1^\lambda$ returns a description $\mathcal{PG}=(\mathbb{G}_1,\mathbb{G}_2,p,g_1,g_2,e)$ of an asymmetric bilinear group. For convenience, given $s = 1$, 2 or $T$, $n \in \mathbb{N}$ and vectors $\vec u := ( u_1 \dots  u_n )\in \mathbb{Z}^n_p$, $\vec v \in \mathbb{Z}_p^{n}$, we denote by $g_s^{\vec u} := (g^{u_1}_s \dots g^{u_n}_s)  \in \mathbb{G}_s^{n}$ and $e(g_1^{\vec u},g_2^{\vec v}) = \prod_{i=1} e(g_1,g_2)^{u_i \cdot v_i} =  e(g_1,g_2)^{\vec u \cdot \vec v} \in \mathbb{G}_T$, where $\vec u \cdot \vec v$ is the inner product, i.e. $\vec u \cdot \vec v := \sum_{i=1}^n u_i v_i$.

\section{Our Quadratic Functional Encryption Scheme}\label{appendix:qfe}

\subsection{Proofs of IND-CPA security and correctness}\label{appendix:QFEproofs}

\subsubsection*{Proof of Security}

To prove security of our scheme, we use the Generic Bilinear Group Model, which captures the fact that no attacks can make use of the representation of group elements. For convenience, we use Maurer's model~\cite{IMA:Maurer05}, where a third party implements the group and gives access to the adversary via handles, providing also equality checking. This is an alternative, but equivalent, formulation of the Generic Group Model, as originally introduced in~\cite{Nechaev94,EC:Shoup97}.

We prove security in two steps: first, we use a master theorem from \cite{C:BCFG17} that relates the security in the Generic Bilinear Group model to a security in a symbolic model. Second, we prove security in the symbolic model. Let us now explain the symbolic model (the next paragraph is taken verbatim from \cite{CCS:ABGW17}).

In the symbolic model, the third party does not implement an actual group, but keeps track of
abstract expressions. For example, consider an experiment where values $x,y$ are sampled from $\mathbb{Z}_p$ and the adversary gets handles to $g^x$ and $g^y$. In the generic model, the third party will choose a group of order $p$, for example $(\mathbb{Z}_p,+)$, will sample values $x,y \leftarrow_{R} \mathbb{Z}_p$ and will give handles to $x$ and $y$. On the other hand, in the symbolic model the sampling  won't be performed and the third party will output handles to $X$ and $Y$, where $X$ and $Y$ are abstract variables. Now, if the adversary asks for equality of the elements associated to the two handles, the answer will be negative in the symbolic model, since abstract variable $X$ is different from abstract variable $Y$, but there is a small chance the equality check succeeds in the generic model (only when the sampling of $x$ and $y$ coincides).

To apply the master theorem, we first need to change the distribution of the security game to ensure that the public key, challenge ciphertext, and functional decryption keys only contain group elements whose exponent is a polynomial evaluated on uniformly random values in $\mathbb{Z}_p$ (this is called polynomially induced distributions in \cite[Definition 10]{C:BCFG17}, and previously in \cite{C:BFFMSS14}). We show that this is possible with only a negligible statistical change in the distribution of the adversary view.

After applying the master theorem from \cite{C:BCFG17}, we prove the security in the symbolic model (cf. Appendix \ref{lem:symb}), which simply consists of checking that an algebraic condition on the scheme in satisfied.

\begin{theorem}[IND-CPA Security in the Generic Bilinear Group Model]\label{thm:FEsec}
For any PPT adversary $\mathcal{A}$ that performs at most $Q$ group operations against the functional encryption scheme described on \ref{fig:FE}, we have, in the generic bilinear group model:
\[ \mathsf{Adv}^{\mathsf{FE}}_{\mathcal{A}}(\lambda) \leq \frac{12\cdot(6n+3+Q+Q')^2+1}{p},\] where $Q'$ is the number of queries to $\mathsf{KeyGen}\xspace(\mathsf{msk},\cdot)$.
\end{theorem}
The proof of this result is quite technical and can be found in the dedicated Appendix \ref{app:FEproof}.

\subsubsection*{Proof of Correctness}

For all $i,j\in[n]$, we have:
$$e(g_1^{\vec{a}_i},g_2^{\vec{b}_j}) = g_T^{{\vec{a}_i} \cdot \vec{b}_j} = g_T^{x_i y_j - \gamma s_i t_j}$$
since
\begin{align*}
\vec{a}_i \cdot \vec{b}_j
& = \left( (\mathbf{W}^{-1})^\top \begin{pmatrix}x_i \\ \gamma s_i\end{pmatrix} \right)^\top
  \cdot \left(\mathbf{W} \begin{pmatrix}y_j \\ -t_j\end{pmatrix}\right) \\
& = \begin{pmatrix} x_i \\ \gamma s_i \end{pmatrix}^\top \mathbf{W}^{-1} \mathbf{W} \begin{pmatrix}y_j \\ -t_j\end{pmatrix}
= {x_i y_j - \gamma s_i t_j}.
\end{align*}
Therefore we have:
\begin{align*}
out &= e(g_1^\gamma,g_2^{q(\vec{s},\vec{t})}) \cdot \prod_{i,j} e(g_1^{\vec{a}_i},g_2^{\vec{b}_i})^{q_{i,j}}
 = g_T^{\gamma q(\vec{s},\vec{t})} \cdot g_T^{\sum_{i,j} q_{i,j} x_i y_j-\gamma q_{i,j} s_i t_j} \\
& = g_T^{\gamma q(\vec{s},\vec{t})} \cdot g_T^{q(\vec{x},\vec{y})-\gamma q(\vec{s},\vec{t})} = g_T^{q(\vec{x},\vec{y})}.
\end{align*}

\subsubsection*{Proof of Complexity}

The complexity can be inferred from the decryption phase as detailed in Figure \ref{fig:FE} and we compare this with previous quadratic FE schemes in Figure  \ref{fig:compare}.

\begin{figure*}[htb!]
    \normalsize
    \centering
    \begin{tabular}{@{}l|c|c|c|c}
        FE scheme & $ct$ &  $\mathsf{dk}_f$ & $\Dec$ & Assumption \\\hline
        \rule{0pt}{1.0\normalbaselineskip}\cite[Sec. 3]{C:BCFG17} & $\mathbb{G}_1^{6n+1} \times \mathbb{G}_2^{6n+1}$ &  $\mathbb{G}_1 \times \mathbb{G}_2$ & $6n^2(E_1+P)+2P$ & SXDH, 3PDDH\\
        \cite[Sec. 4]{C:BCFG17} &  $\mathbb{G}_1^{2n+1} \times \mathbb{G}_2^{2n+1}$ &  $\mathbb{G}_1^2$ & $3n^2(E_1+P)+2P$ & GGM \\
        Ours &  $\mathbb{G}_1^{2n+1} \times \mathbb{G}_2^{2n}$  &  $\mathbb{G}_2$ & $2n^2(E_1+P)+P$  & GGM
    \end{tabular}
    \caption{Performance comparison of FE for quadratic polynomials. $E_1$ and $P$ denote exponentiation in $\mathbb{G}_1$ and pairing evaluation, respectively. Decryption additionally requires solving a discrete logarithm but this computational overhead is the same for all schemes and is therefore omitted here.} \label{fig:compare}
\end{figure*}

\subsection{Detailed equivalence of the FE scheme with a neural network}

\subsubsection*{Proof of Linear Homomorphism}\label{app:QFEequiv}

For all $(\vec x, \vec y) \in \mathbb{Z}_p^n\times\mathbb{Z}_p^n$, and $(\vec u, \vec v) \in \mathbb{Z}_p^n\times\mathbb{Z}_p^n$, given an encryption of $(\vec x, \vec y)$ under the public key $\mathsf{pk} := (g_1^{\vec s}, g_2^{\vec t})$, one can efficiently compute an encryption of $(\vec u^\top \vec x, \vec v^\top \vec y)$ under the public key $\mathsf{pk}' := (g_1^{\vec u^\top \vec s}, g_2^{\vec v^\top \vec t})$. Indeed, given \[\Enc(\mathsf{pk},(\vec x, \vec y)) := (g_1^\gamma, \{g_1^{\vec a_i}, g_2^{\vec b_i}\}_{i \in [n]}),\]
and $\vec u, \vec v \in \mathbb{Z}_p^n$, one can efficiently compute:  \[(g_1^\gamma, g_1^{\sum_{i \in [n]} u_i \cdot \vec a_i}, g_2^{\sum_{i \in [n]} v_i \cdot \vec b_i}),\]
which is $\Enc(\mathsf{pk}',(\vec u^\top \vec x, \vec v^\top \vec y))$, since:
\begin{align*}
\sum_{i\in [n]} u_i \cdot \vec a_i & = \sum_{i \in [n]} u_i \cdot (\mathbf{W}^{-1})^\top \begin{pmatrix}x_i \\ \gamma s_i\end{pmatrix}
= (\mathbf{W}^{-1})^\top \begin{pmatrix} \sum_{i \in [n]} u_i \cdot x_i \\ \gamma \sum_{i \in [n]} u_i \cdot s_i\end{pmatrix} \\
& = (\mathbf{W}^{-1})^\top \begin{pmatrix} \vec u^\top \vec x \\ \gamma \vec u^\top \vec s\end{pmatrix}.
\end{align*}
Similarly, we have:
\begin{align*}
\sum_{i\in [n]} v_i \cdot \vec b_i = \sum_{i \in [n]} v_i \cdot \mathbf{W} \begin{pmatrix} y_i \\ -t_i\end{pmatrix} =
  \mathbf{W} \begin{pmatrix} \vec v^\top \vec y \\ -\vec v^\top \vec t\end{pmatrix}.
\end{align*}

\section{Additional results}

\subsection{Influence of weight compression on the network performance}\label{app:Wcompression}

We show here that we can manage to compress significantly the network weights in order to have a very fast discrete logarithm without modifying the results and conclusions made throughout the article. The main and collateral model follow the same CNN structure as stated above, and the collateral accuracy is reported after 10 epochs of training.

\begin{table}[htb!]
  \centering
    \begin{tabular}{|l|l|}
     \hline

     Main accuracy  with compression & $97.72 \pm  0.30 ~ \%$\\
     \hline
     Collateral accuracy with compression & $55.27 \pm  0.41 ~ \%$\\
     \hline
    \end{tabular}
    \vspace{10px}
  \caption{Impact of weight compression on the main and collateral accuracies}\label{tab:Wcompression}
\end{table}

\subsection{Influence of alpha during adversarial training}\label{app:alpha}

To choose the best value for $\alpha$, we have chosen an output size of 4 which allows us to keep a very high main accuracy while reducing significantly the collateral one, as shown in Figure \ref{fig:pareto_nosabo}. We observe that the semi-adversarial training does not affect much the main accuracy for a large range of values for $\alpha$, while its impact on the collateral accuracy is decisive. Figure \ref{fig:alpha} illustrates the role of $\alpha$ and justify our choice of $\alpha = 1.7$. For this experiment, we have chosen for both networks a simple feed forward with a hidden layer of 32 neurons.

\begin{figure}[htb!]
  \centering
  \includegraphics[width=0.70\linewidth]{figures/alpha_std_size63.png}
  \caption{Trade-off between the main and collateral tasks accuracies as a function of $\alpha$}\label{fig:alpha}
\end{figure}

\subsection{Quadratic and Polynomial Neural Networks}

Polynomial neural networks are a class of networks which only use linear elements, like fully connected linear layers, convolutions but with average pooling, and model activation functions with polynomial approximations when not simply the square function. Despite these simplifications, they have proved themselves satisfactorily accurate for relatively simple tasks (\cite{cryptonets} learns on MNIST and \cite{HCNNAlexNetMoment} on CIFAR10 \cite{cifar10}). The simplicity of the operations they build on guarantees good efficiency, especially for the gradient computations, and works like \cite{DBLP:journals/corr/LivniSS14} have shown that they can achieve convergence rates similar to those of networks with non-linear activations.

In particular, they have been used for several early stage implementations in cryptography  \citep{cryptonets,FastFHE01sec,eprint:2017:1114} to demonstrate the usability of new protocols for machine learning.
However, the $\mathsf{argmax}$ or other thresholding function present at the end of a classifier network to select the class among the output neurons cannot be conveniently handled, so several protocol implementations (among which ours) run polynomial networks on encrypted inputs, but take the $\mathsf{argmax}$ over the decrypted output of the network. This results in potential information leakage which could be maliciously exploited.

\subsection{Functional Encryption}\vspace{-3px}

Functional encryption extends the notion of public key encryption where one uses a public key $\mathsf{pk}$ and a secret key $\mathsf{sk}$ to respectively encrypt and decrypt some data. More precisely, $\mathsf{pk}$ is still used to encrypt data, but for a given function $f$, $\mathsf{sk}$ can be used to derive a functional decryption key $\mathsf{dk}_{f}$ which will be shared to users so that, given a ciphertext of $x$, they can decrypt $f(x)$ but not $x$. In particular, someone having access to $\mathsf{dk}_{f}$ cannot learn anything about $x$ other than $f(x)$. Note also that functions cannot be composed, since the decryption happens within the function evaluation. Hence, only single quadratic functions can be securely evaluated. A formal definition of functional encryption is provided in Appendix \ref{appendix:FE}.

\textbf{Perfect correctness.} Perfect correctness is achieved in functional encryption: $\forall x \in \mathcal{X}$, $f \in \mathcal{F}$, $\Pr[\Dec(\mathsf{dk}_f,\mathsf{ct})=f(x)]=1$, where  $\mathsf{dk}_f \gets \mathsf{KeyGen}\xspace(\mathsf{msk},f)$ and $\mathsf{ct} \gets \Enc(\mathsf{pk},x)$. Note that this property is a very strict condition, which is not satisfied by exisiting fully homomorphic encryption schemes (FHE), such as \citep{LWE,C:GenSahWat13}.

\subsection{Indistinguishability and security}\vspace{-3px}

To assess the security of our framework, we first consider the FE scheme security and make sure that we cannot learn anything more than what the function is supposed to output given an encryption of $x$. Second, we analyze how sensitive the output $f(x)$ is with respect to the private input $x$. For both studies, we will rely on \textit{indistinguishability} \cite{GolMic84}, a classical security notion which can be summed up in the following game: an adversary provides two input items to the challenger (here our FE algorithm), and the challenger chooses one item to be encrypted, runs encryption on it before returning the output. The adversary should not be able to detect which input was used. This is known as IND-CPA security in cryptography and a formal definition of it can be found in Appendix \ref{app:ind-cpa}.

We will first prove that our quadratic FE scheme achieves IND-CPA security, then, we will use a relaxed version of indistinguishability to measure the FE output sensitivity. More precisely, we will make the hypothesis that our input data can be used to predict public labels but also sensitive private ones, respectively $y_{\mathrm{pub}}$ and $y_{\mathrm{priv}}$. Our quadratic FE scheme $q$ aims at predicting $y_{\mathrm{pub}}$ and an adversary would rather like to infer $y_{\mathrm{priv}}$. In this case, the security game consists in the adversary providing two inputs $(x_0, x_1)$ labelled with the same $y_{\mathrm{pub}}$ but a different $y_{\mathrm{priv}}$ and then trying to distinguish which one was selected by the challenger, given its output $q(x_b)$, $b \in \{0, 1\}$. One way to do this is to measure the ability of an adversary to predict $y_{\mathrm{priv}}$ for items which all belong to the same $y_{\mathrm{pub}}$ class.

In particular, note that we do not consider approaches based on input reconstruction (as done by \cite{Carpov2018IlluminatingTD}) because in many cases, the adversary is not interested in reconstructing the whole input, but rather wants to get insights into specific characteristics.

Another way to see this problem is that we want the sensitive label $y_{\mathrm{priv}}$ to be independent from the decrypted output $q(x)$ (which is a proxy to the prediction), given the true public label $y_{\mathrm{pub}}$. This independence notion is known as \textit{separation} and is used as a fairness criterion in \cite{fairnessML} if the sensitive features can be misused for discrimination.

\subsection{Classifying in two directions} \vspace{-2px}

We are interested in specific types of datasets $(\vec x_i)_{i=1,...,n}$ which have public labels $y_{\mathrm{pub}}$ but also private ones $y_{\mathrm{priv}}$. Moreover, these different types of labels should be entangled, meaning that they should not be easily separable, unlike the color and the shape of an object in an image for example which can be simply separated.
 For example, in the spam filtering use case mentioned above, $y_{\mathrm{pub}}$ would be a spam flag, and $y_{\mathrm{priv}}$ would be some marketing information highlighting areas of interest of the email recipient like technology, culture, etc. In addition, to simplify our analysis, we assume that classes are balanced for all types of labels, and that labels are independent from each other given the input: $\forall \vec x,  P(y_{\mathrm{pub}}, y_{\mathrm{priv}} | \vec x) = P(y_{\mathrm{pub}} | \vec x) P(y_{\mathrm{priv}} | \vec x)$.
To illustrate our approach in the case of image recognition, we propose a synthetic dataset inspired by MNIST which consists of 60 000 grey scaled images of $28\times 28$ pixels representing digits using two fonts and some distortion, as shown in Figure~\ref{fig:dataset}. Here, the public label $y_{\mathrm{pub}}$ is the digit on the image and the private one $y_{\mathrm{priv}}$ is the font used to draw it.

\begin{figure}[htb!]
  \centering
  \includegraphics[width=0.9\linewidth]{figures/dataset_flat.png}
  \caption{Artificial dataset inspired from MNIST with two types of labels.}\label{fig:dataset}
\end{figure}

We define two tasks: a \textit{main} task which tries to predict $y_{\mathrm{pub}}$ using a partially-encrypted polynomial neural network with functional encryption, and a \textit{collateral} task which is performed by an adversary who tries to leverage the output of the FE encrypted network at test time to predict $y_{\mathrm{priv}}$. Our goal is to perform the main task with high accuracy while making the collateral one as bad as random predictions. In terms of indistinguishability, given a dataset with the same digit drawn, it should be infeasible to detect the used font.

\subsection{Equivalence with a Quadratic Functional Encryption scheme}

We now introduce our new framework for quadratic functional encryption and show that it can be used to partially encrypt a polynomial network.

\subsubsection{Functional Encryption for Quadratic Polynomials}

We build an efficient FE scheme for the set of quadratic functions defined as $\mathcal{F}_{n,B_x,B_y,B_q} \subset \{ q: [-B_x,B_x]^n \times [-B_y,B_y]^n \rightarrow \mathbb{Z}\}$, where $q$ is described as a set of bounded coefficients $\{q_{i,j} \in [-B_q,B_q]\}_{i,j \in [n]}$ and for all vectors  $(\vec{x}, \vec y)$, we have $\textstyle q(\vec{x},\vec{y}) = \sum_{i,j \in [n]} q_{i,j} x_i y_j.$

A complete version of our scheme is given in Figure \ref{fig:FE}, but here are the main ideas and notations. First note that we use bilinear groups, \textit{i.e.}, a set of prime-order groups $\mathbb{G}_1$, $\mathbb{G}_2$ and $\mathbb{G}_T$ together with a bilinear map $e: \mathbb{G}_1 \times \mathbb{G}_2 \rightarrow \mathbb{G}_T$ called \textit{pairing} which satisfies $e(g_1^a , g_2^b) = e(g_1, g_2)^{ab}$ for any exponents $a,b \in \mathbb{Z}$: one can compute quadratic polynomials in the exponent. Here, $g_1$, $g_2$ are generators of $\mathbb{G}_1$ and $\mathbb{G}_2$ and $g_T := e(g_1, g_2)$ is a generator of the target group~$\mathbb{G}_T$.
A pair of vectors $(\vec{s},\vec{t})$ is first selected and constitutes the private key $\mathsf{msk}$, while the public key is $(g_1^{\vec{s}},g_2^{\vec{t}})$.

Encrypting $(\vec{x},\vec{y})$ roughly consists of masking $g_1^{\vec{x}}$ and $g_2^{\vec{y}}$ with $g_1^{\vec{s}}$ and $g_2^{\vec{t}}$, which allows any user to compute $g_T^{q(\vec{x},\vec{y})-q(\vec{s},\vec{t})}$ with for any quadratic function $q$, using the pairing. The functional decryption key for a specific $q$ is $g_T^{q(\vec{s},\vec{t})}$ which allows to get $g_T^{q(\vec{x},\vec{y})}$. Last, taking the discrete logarithm gives access to $q(\vec{x},\vec{y})$ (discrete logarithm for small exponents is easy). Security uses the fact that it is hard to compute $\mathsf{msk}$ from $\mathsf{pk}$ (discrete logarithm for large exponents $\vec{s},\vec{t}$ is hard to compute). More details are given in Appendix \ref{appendix:QFEproofs}\footnote{Note that we only present a simplified scheme here. In particular, the actual encryption is randomized, which is necessary to achieve IND-CPA security.}

\begin{figure*}[ht]
  \begin{center}
    \begin{tabular}{|l|}\hline
        \underline{$\mathsf{SetUp}\xspace(1^\lambda,\mathcal{F}_{n,B_x,B_y,B_f})$:}\\
        $\mathcal{PG}:=(\mathbb{G}_1,\mathbb{G}_2,p,g_1,g_2,e)\gets \mathsf{GGen}(1^\lambda)$, $\vec{s}, \vec{t} \stackrel{{}_\$}{\leftarrow} \mathbb{Z}^n_p$,
        $\mathsf{msk} := (\vec{s},\vec{t})$, $\mathsf{pk} := \left(\mathcal{PG},g_1^{\vec{s}}, g_2^{\vec{t}}\right)$\\
        Return $(\mathsf{pk},\mathsf{msk})$.\\
        \\
        \underline{$\Enc\big(\mathsf{pk}, (\vec{x},\vec{y})\big)$:}\\
        $\gamma \stackrel{{}_\$}{\leftarrow} \mathbb{Z}_p$, $\mathbf{W} \stackrel{{}_\$}{\leftarrow} \mathsf{GL}_2$, for all $i \in [n]$, $\vec{a}_i := (\mathbf{W}^{-1})^\top \begin{pmatrix}x_i \\ \gamma s_i\end{pmatrix}$, $\vec{b}_i := \mathbf{W} \begin{pmatrix} y_i \\ -t_i\end{pmatrix}$\\
        Return $\mathsf{ct} := \left(g_1^{\gamma}, \{g_1^{\vec{a}_i},g_2^{\vec{b}_i}\}_{i \in [n]}\right) \in \mathbb{G}_1 \times (\mathbb{G}_1^2 \times \mathbb{G}_2^2)^n$\\
        \\
        \underline{$\mathsf{KeyGen}\xspace(\mathsf{msk},q)$:}\\
        Return $\mathsf{dk}_f := \left(g_2^{q(\vec{s},\vec{t})},q\right) \in \mathbb{G}_2 \times \mathcal{F}_{n,B_x,B_y,B_q}$.\\
        \\
        \underline{$\Dec\left(\mathsf{pk},\mathsf{ct} := \left(g_1^\gamma, \{g_1^{\vec{a}_i},g_2^{\vec{b}_i}\}_{i \in [n]}\right) ,\mathsf{dk}_q := \left(g_2^{q(\vec{s},\vec{t})},q\right)\right)$:}\\[2ex]
        $out := e(g_1^\gamma,g_2^{q(\vec{s},\vec{t})})\cdot \prod_{i,j \in [n]} e\big(g_1^{\vec{a}_i},g_2^{\vec{b}_j}\big)^{q_{i,j}}$\\
        Return $\log(out) \in \mathbb{Z}$.
        \\[1.5ex]\hline
    \end{tabular}
    \caption{Our functional encryption scheme for quadratic polynomials.}\label{fig:FE}
  \end{center}
\end{figure*}

\begin{theorem}[Security, correctness and complexity]
    The FE scheme provided in Figure \ref{fig:FE}:
    \begin{itemize}[noitemsep,topsep=0pt,parsep=0pt,partopsep=0pt]
        \item is IND-CPA secure in the Generic Bilinear Group Model,
        \item verifies $\log(out) = q(\vec{x},\vec{y})$ and satisfies perfect correctness,
        \item has a overall decryption complexity of ~$ 2n^2(E+P) + P + D$,
    \end{itemize}
    where $E$, $P$ and $D$ respectively denote exponentiation, pairing and discrete logarithm complexities.
\end{theorem}

Our scheme outperforms previous schemes for quadratic FE with the same security assumption, like the one from \cite[Sec. 4]{C:BCFG17} which achieves $3n^2(E+P) + 2P + D$ complexity and uses larger ciphertexts and decryption keys. Note that the efficiency of the decryption can even be further optimized for those quadratic polynomials used that are relevant to our application (see Section \ref{sec:equivFENN}).

\textbf{Computing the discrete logarithm for decryption.}
Our decryption requires computing discrete logarithms of group elements in base $g_T$, but contrary to previous works like \cite{SCN:KLMMRW18} it is independent of the ciphertext and the functional decryption key used to decrypt. This allows to pre-compute values and dramatically speeds-up decryption.

\subsubsection{Equivalence of the FE scheme with a Quadratic Network}\label{sec:equivFENN}

We classify data which can be represented as a vector $\vec x \in [0,B]^n$ (in our case, the size $B=255$, and the dimension $n=784$) and we first build models $(q_i)_{i\in[\ell]}$ for each public label $i \in [\ell]$, such that our prediction $y_{\mathrm{pub}}$ for $\vec x$ is $\operatornamewithlimits{argmax}_{i\in[\ell]} q_i(\vec x)$.

\textbf{Quadratic polynomial on $\mathbb{R}^n$.} The most straightforward way to use our FE scheme would be for us to learn a model $(\mat Q_i)_{i\in[\ell]}\in {\left(\mathbb{R}^{n \times n}\right)}^{\ell}$, which we would then round onto   integers, such that $q_i(\vec x) = \vec x^\top \mat Q_i \vec x$, $\forall i \in [\ell]$. This is a unnecessarily powerful model in the case of MNIST as it has ${\ell}n^2$ parameters ($n=784$), and the resulting number of pairings to compute would be unreasonably large.

\textbf{Linear homomorphism.} The encryption algorithm of our FE scheme is linearly homomorphic with respect to the plaintext: given an encryption of $(\vec x, \vec y)$ under the secret key $\mathsf{msk} := (\vec s, \vec t)$, one can efficiently compute an encryption of $(\vec u^\top \vec x, \vec v^\top \vec y)$ under the secret key $\mathsf{msk}' := (\vec u^\top \vec s, \vec v^\top \vec t)$ for any linear combination $\vec u,\vec v$ (see proof in Appendix \ref{app:QFEequiv}). Any vector $\vec v$ is a column, and $\vec v^\top$ is a row.

Therefore, if $q$ can be written $q(\vec x, \vec y) = (\mat U \vec x)^\top \mat M (\mat V \vec y)$ for all ($\vec x$, $\vec y$), with $\mat U, \mat V \in \mathbb{Z}_p^{d \times n}$ projection matrices and $\mat M \in \mathbb{Z}_p^{d \times d}$, it is more efficient to first compute the encryption of $(\mat U \vec x, \mat V \vec y)$ from the encryption of $(\vec x, \vec y)$, and then to apply the functional decryption on these ciphertexts, because their underlying plaintexts are of reduced dimension $d < n$. This reduces the number of exponentiations from $2n^2$ to $2dn$ and the number of pairing computations from $2n^2$ to $2d^2$ for a single $q_i$. This is a major efficiency improvement for small $d$, as pairings are the main bottleneck in the computation.

\textbf{Projection and quadratic polynomial on $\mathbb{R}^d$.} We can use this and apply the quadratic polynomials on projected vectors: we learn $\mat P \in \mathbb{R}^{n\times{}d}$ and $(\mat Q_i)_{i\in[\ell]}\in {\left(\mathbb{R}^{d \times d}\right)}^{\ell}$, and our model is $q_i(\vec x) = (\mat P\vec x)^\top \mat Q_i (\mat P\vec x)$, $\forall i \in [\ell]$. We only need $2\ell d^2$ pairings and since the same $\mat P$ is used for all $q_i$, we only compute once the encryption of $\mat P\vec x$ from the encryption of $\vec x$. Better yet, we can also perform the pairings only once, and then compute the scores by exponentiating with different coefficients the same results of the pairings, thus only requiring $2d^2$ pairing evaluations, independently of $\ell$.

\textbf{Degree 2 polynomial network, with one hidden layer.} To further reduce the number of pairings, we actually limit ourselves to diagonal matrices, and thus rename $\mat Q_i$ to $\mat D_i$. We find that the gain in efficiency associated with only computing $2d$ pairings is worth the small drop in accuracy. The resulting model is actually a polynomial network of degree 2 with one hidden layer of $d$ neurons and the activation function is the square. In the following experiments we take $d = 40$.

Our final encrypted model can thus be written as
$q_i(\vec x) = (\mat P\vec x)^\top \mat D_i (\mat P\vec x), \forall i \in [\ell]$, where we add a bias term to $\vec x$ by replacing it with $\vec x = ( 1 ~ x_{1}  \dots   x_{n})$.

\textbf{Full network.} The result of the quadratic $(q_i(\vec x))_{i \in [\ell]}$ (i.e., of the private quadratic network) is now visible in clear. As mentioned above, we cannot compose this block several times as it contains decryption, so this is currently the best that we can have as an encrypted computation with FE.  Instead of simply applying the $\mathsf{argmax}$ to the cleartext output of this privately-evaluated quadratic network to get the label, we observe that adding more plaintext layers on top of it helps improving the overall accuracy of the main task. We have therefore a neural network composed of a private and a public part, as illustrated in Figure \ref{fig:encryptnet}.

\begin{figure}[!htb]
   \begin{minipage}{0.33\textwidth}
     \centering
     \includegraphics[width=1\linewidth]{figures/semi_encrypt_net.png}
      \caption{Semi-encrypted network using quadratic FE.}\label{fig:encryptnet}
   \end{minipage}\hfill
   \begin{minipage}{0.63\textwidth}
     \centering
     \includegraphics[width=1\linewidth]{figures/semi_encrypt_net_adversarial.png}
     \caption{Semi-encrypted network with an adversary trying\\ to recover private labels from the private quadratic network.}\label{fig:encryptnetadv}
   \end{minipage}
\end{figure}\vspace{-10px}

\subsection{Threat of Collateral Learning}\label{sec:CLthreat}

A typical adversary would have a read access to the main task classification process. It would leverage the output of the quadratic network to try to learn the font used on ciphered images. To do this, all that is needed is to train another network on top of the quadratic network so that it learns to predict the font, assuming some access to labeled samples (which is the case if the adversary encrypts itself images and provides them to the main task at evaluation time). Note that in this case the private network is not updated by the collateral network as we assume it is only provided in read access after the main task is trained. Figure \ref{fig:encryptnetadv} summarizes the setting.

We implemented this scenario using as adversary a neural network composed of a first layer acting as a decompression step where we increase the number of neurons from $10$ back to $28 \times 28$ and add on top of it a classical\footnote{https://github.com/pytorch/examples/blob/master/mnist/main.py} convolutional neural network (CNN). This structure is reminiscent of autoencoders \cite{autoencoders} where the bottleneck is the public output of the private net and the challenge of this autoencoder is to correctly memorize the public label while forgetting the private one. What we observed is  striking: in less than $10$ epochs, the collateral network leverages the $10$ public neurons output and achieves $93.5\%$ accuracy for the font prediction. As expected, it gets even worse when the adversary is assessed with the indistinguishability criterion because in that case the adversary can work on a dataset where only a  specific digit is represented: this reduces the variability of the samples and makes it easier to distinguish the font; the probability of success is indeed of $96.9\%$.

We call \textit{collateral learning} this phenomenon of learning unexpected features and will show in the next section how to implement counter-measures to this threat in order to improve privacy.

\subsection{Use cases}

\textbf{Spam filtering.} Consider the following scenario: Alice uses a secure email protocol which makes use of functional encryption. Bob uses Alice's public key to send her an email, which lands on Alice's email provider's server. Alice gave the server keys that enable it to process the email and take a predefined set of appropriate actions without her being online. The server could learn how urgent the email is and decide accordingly whether to alert Alice. It could also detect whether the message is spam and store it in the spam box right away.

\textbf{Privacy-preserving enforcement of content policies} Another use case could be to enable platforms, such as messaging apps, to maintain user privacy through end-to-end encryption, while filtering out content that is illegal or doesn't adhere to policies the site may have regarding, for instance, abusive speech or explicit images.

These applications are not currently feasible within a reasonable computing time, as the construction of FE for all kinds of circuits is essentially equivalent to \emph{indistinguishable obfuscation} \cite{C:BGIRSVY01,FOCS:GGHRSW13}, concrete instances of which have been shown insecure, let alone efficient.  However, there exist practical FE schemes for the inner-product functionality \citep{PKC:ABDP15,C:AgrLibSte16} and more recently for quadratic computations \citep{C:BCFG17}, that is usable for practical applications.

\subsection{Our contributions}

We introduce a new FE scheme to compute quadratic forms which outperforms that of Baltico et al.~\cite{C:BCFG17} in terms of complexity, and provide an efficient implementation of this scheme. We show how to use it to build privacy preserving neural networks, which perform well on simple image classification problems. Specifically, we show that the first layers of a polynomial network can be run on encrypted inputs using this quadratic scheme.

In addition, we present an adversarial training technique to process these first layers to improve privacy, so that their output, which is in plaintext, cannot be used by adversaries to recover specific sensitive information at test time. This adversarial procedure is generic for semi-encrypted neural networks and aims at reducing the information leakage, as the decrypted output is not directly the classification result but an intermediate layer (i.e., the neuron outputs of the neural network before thresholding). This has been overlooked in other popular encrypted classification schemes (even in FHE-based constructions like \cite{cryptonets} and \cite{FastHEofDiNN}), where the $\mathsf{argmax}$ operation used to select the class label is made in clear, as it is either not possible with FE, or quite inefficient with FHE and SMPC.

We demonstrate the practicality of our approach using a dataset inspired from MNIST \citep{mnist}, which is made of images of digits written using two different fonts. We show how to perform classification of the encrypted digit images in less than $3$ seconds with over $97.7\%$ accuracy while making the font prediction a hard task for a whole set of adversaries.

This paper builds on a preliminary version available on the \hyperlink{https://eprint.iacr.org/2018/206}{Cryptology ePrint Archive} at \url{eprint.iacr.org/2018/206}. All code and implementations can be found online at \url{github.com/LaRiffle/collateral-learning} and \url{github.com/edufoursans/reading-in-the-dark}.

\section{Security proof of our FE scheme}\label{app:FEproof}

\begin{proof} ~For any experiment $\mathsf{Exp}$, adversary $\mathcal{A}$, and security parameter $\lambda \in \mathbb{N}$, we use the notation: $\mathsf{Adv}_{\mathsf{Exp}}(\mathcal{A}) := \Pr[1 \gets \mathsf{Exp}(1^\lambda,\mathcal{A})]$, where the probability is taken over the random coins of $\mathsf{Exp}$ and $\mathcal{A}$.

\begin{figure*}
  \begin{center}
    \begin{tabular}{|l|l|}\hline
        \underline{$\mathsf{Exp}_1(1^\lambda,\mathcal{A})$:} & \underline{$\mathsf{KeyGen}\xspace(\mathsf{msk},f)$:}\\
        $(\mathbb{G}_1,\mathbb{G}_2,p,g_1,g_2,e) \gets \mathsf{GGen}(1^\lambda)$, $\vec{s},\vec{t} \stackrel{{}_\$}{\leftarrow} \mathbb{Z}^n_p$ & return $(g_2^{f(\vec{s},\vec{t})},f)$.\\
        $a,b,c,d \stackrel{{}_\$}{\leftarrow} \mathbb{Z}_p$, set $\mathcal{PG}:= (\mathbb{G}_1,\mathbb{G}_2,p,g_1^{ad-bc},g_2,e)$ & \\
        $\mathsf{msk} := (\vec{s},\vec{t})$, $\mathsf{pk} := \left(\mathcal{PG},g_1^{(ad-bc)\vec{s}}, g_2^{\vec{t}}\right)$ & \\
        $\Big((\vec x^{(0)},\vec y^{(0)}), (\vec x^{(1)}, \vec y^{(1)})\Big) \gets \mathcal{A}^{\mathsf{KeyGen}\xspace(\mathsf{msk},\cdot)}(\mathsf{pk})$ &\\
        $\beta \stackrel{{}_\$}{\leftarrow} \{0,1\}$, $\gamma \stackrel{{}_\$}{\leftarrow} \mathbb{Z}_p$ &\\
        for all $i \in [n]$, $\vec{a}_i := \begin{pmatrix}d & -c \\ -b & a \end{pmatrix} \begin{pmatrix}x^{(\beta)}_i \\ \gamma s_i\end{pmatrix}$, $\vec{b}_i := \begin{pmatrix}a & b \\ c & d\end{pmatrix} \begin{pmatrix} y^{(\beta)}_i \\ -t_j\end{pmatrix}$ &\\
        $ct =: \big(g_1^{\gamma(ad-bc)}, \{g_1^{\vec{a}_i},g_2^{\vec{b}_i}\}_{i \in [n]}\big)$ &\\
        $\beta' \gets \mathcal{A}^{\mathsf{KeyGen}\xspace(\mathsf{msk},\cdot)}(\mathsf{pk},ct)$ &\\
        Return $1$ if $\beta' = \beta$ and for all queried $f$, $f(\vec x^{(0)},\vec y^{(0)}) = f(\vec x^{(1)}, \vec y^{(1)})$.&\\\hline
        \end{tabular}
    \caption{Experiment $\mathsf{Exp}_1$, for the proof of Theorem \ref{thm:FEsec}.}\label{fig:G1}
  \end{center}
\end{figure*}

While we want to prove the security result in the real experiment $\mathsf{Exp}_0$, in which the adversary has to guess $\beta$, we slightly modify it into the hybrid experiment $\mathsf{Exp}_1$, described in \ref{fig:G1}:
we write the matrix $\mathbf{W} \stackrel{{}_\$}{\leftarrow} \mathsf{GL}_2$ used in the challenge ciphertext as $\mathbf{W} := \begin{pmatrix} a & b \\ c & d \end{pmatrix}$, chosen from the beginning. Then $\mathbf{W}^{-1} = \frac{1}{ad-bc} \begin{pmatrix} d & -b \\ -c & a \end{pmatrix}$.

The only difference with the IND-CPA security game as defined in Appendix \ref{app:ind-cpa}, is that we change the generator $g_1 \stackrel{{}_\$}{\leftarrow} \mathbb{G}_1^*$ into $g_1^{ad-bc}$ for $a,b,c,d \stackrel{{}_\$}{\leftarrow} \mathbb{Z}_p$, which only changes the distribution of the game by a statistical distance of at most $\frac{3}{p}$ (this is obtained by computing the probability that $ad-bc=0$ when $a,b,c,d \stackrel{{}_\$}{\leftarrow} \mathbb{Z}_p$).
Thus, \[\mathsf{Adv}^{\mathsf{FE}}_{\mathcal{A}}(\lambda) = \mathsf{Adv}_0(\mathcal{A}) \leq \mathsf{Adv}_1(\mathcal{A}) + \frac{3}{p}.\]

Note that in $\mathsf{Exp}_1$, the public key, the challenge ciphertext and the functional decryption keys only contain group elements whose exponents are polynomials evaluated on random inputs (as opposed to $g_1^{\mathbf{W}^{-1}}$, for instance). This is going to be helpful for the next step of the proof, which uses the generic bilinear group model.

Next, we make the generic bilinear group model assumption, which intuitively says that no PPT adversary can exploit the structure of the bilinear group to perform better attacks than generic adversaries. That is, we have (with $\mathsf{Exp}_2$  defined in \ref{fig:GGM}):
\[\max_{\mbox{\begin{scriptsize}PPT \end{scriptsize}} \mathcal{A}}\Big(\mathsf{Adv}_1(\mathcal{A})\Big) = \max_{\mbox{\begin{scriptsize}PPT \end{scriptsize}}\mathcal{A}}\Big(\mathsf{Adv}_2(\mathcal{A})\Big).\]

\begin{figure*}
    \begin{center}\fbox{
            \begin{minipage}[t]{0.95\textwidth}
                \underline{$\mathsf{Exp}_2(1^\lambda,\mathcal{A})$:}\\[0.2ex]
                $L_1=L_2= L_T:= \emptyset$, $Q_\mathsf{sk} := \emptyset$, $\vec{s},\vec{t} \stackrel{{}_\$}{\leftarrow} \mathbb{Z}_p^n$, $a,b,c,d \stackrel{{}_\$}{\leftarrow} \mathbb{Z}_p$, $\mathsf{append}(L_1,(ad-bc) \cdot \vec s)$, $\mathsf{append}(L_2,\vec t)$, $\beta \stackrel{{}_\$}{\leftarrow} \{0,1\}$\\
                $\Big((\vec x^{(0)},\vec y^{(0)}),(\vec x^{(1)},\vec y^{(1)}) \Big) \gets \mathcal{A}^{\mathcal{O}_{\mathsf{add}},\mathcal{O}_{\mathsf{pair}},\mathcal{O}_{\mathsf{sk}},\mathcal{O}_{\mathsf{eq}}}(1^\lambda,p)$\\
                $\mathcal{O}_{\mathsf{chal}}\Big((\vec x^{(0)},\vec y^{(0)}),(\vec x^{(1)},\vec y^{(1)}) \Big)$\\
                $\beta'\gets \mathcal{A}^{\mathcal{O}_{\mathsf{add}},\mathcal{O}_{\mathsf{pair}},\mathcal{O}_{\mathsf{sk}},\mathcal{O}_{\mathsf{eq}}}(1^\lambda,p)$\\
                If $\beta=\beta'$, and for all $f \in Q_\mathsf{sk}$, $f(\vec x^{(0)},\vec y^{(0)})=f(\vec x^{(1)},\vec y^{(1)})$, output $1$. Otherwise, output $0$.\\
                \\
                \underline{$\mathcal{O}_{\mathsf{add}}(s\in \{1,2,T\},i,j \in \mathbb{N})$:}\\[0.2ex]
                $\mathsf{append}(L_s,L_s[i]+ L_s[j])$.
                \\
                \\
                \underline{$\mathcal{O}_{\mathsf{pair}}(i,j\in \mathbb{N})$:}\\[0.2ex]
                $\mathsf{append}(L_T,L_1[i]\cdot L_2[j])$.\\
                \\
                \underline{$\mathcal{O}_{\mathsf{chal}}\Big((\vec x^{(0)},\vec y^{(0)}),(\vec x^{(1)},\vec y^{(1)}) \Big)$:}\\[0.2ex]
                $\gamma \stackrel{{}_\$}{\leftarrow} \mathbb{Z}_p$, $\mathsf{append}(L_1,\gamma(ad-bc))$\\
                for all $i \in [n]$, $\vec{a}_i := \begin{pmatrix} d & -c \\ -b & a\end{pmatrix} \begin{pmatrix} x^{(\beta)}_i \\ \gamma s_i \end{pmatrix}$, $\mathsf{append}(L_1,\vec{a}_i)$,
                $\vec{b}_i := \begin{pmatrix} a & b \\ c & d \end{pmatrix} \begin{pmatrix} y^{(\beta)}_i \\ - t_i \end{pmatrix}$, $\mathsf{append}(L_2,\vec{b}_i)$.\\
                \\
                \underline{$\mathcal{O}_{\mathsf{sk}}(f \in \mathcal{F}_{n,B_x,B_y,B_f})$:}\\[0.2ex]
                $\mathsf{append}(L_2,f(\vec s, \vec t))$, $Q_\mathsf{sk}:= Q_\mathsf{sk} \cup \{f\}$.\\
                \\
                \underline{$\mathcal{O}_{\mathsf{eq}}(s \in \{1,2,T\},i,j \in \mathbb{N})$:}\\[0.2ex]
                Output $1$ if $L_s[i] = L_s[j]$, $0$ otherwise
        \end{minipage}}
    \end{center}
    \caption{Experiment $\mathsf{Exp}_2$. Wlog. we assume no query contains indices $i,j\in \mathbb{N}$ that exceed the size of the involved lists.}\label{fig:GGM}
\end{figure*}

In this experiment, we denote by $\emptyset$ the empty list, by $\mathsf{append}(L,x)$ the addition of an element $x$ to the list $L$, and for any $i \in \mathbb{N}$, we denote by $L[i]$ the $i$'th element of the list $L$ if it exists (lists are indexed from index $1$ on), or $\bot$ otherwise.

Thus, it suffices to show that for any PPT adversary $\mathcal{A}$, $\mathsf{Adv}_2(\mathcal{A})$ is negligible in $\lambda$.
The experiment $\mathsf{Exp}_2$ defined in Figure \ref{fig:GGM} falls into the general class of simple interactive decisional problems from \cite[Definition 14]{C:BCFG17}. Thus, we can use their master theorem \cite[Theorem 7]{C:BCFG17}, which, for our particular case (setting the public key size $N:=2n+2$, the key size $c=1$, the ciphertext size $c^*:=4n+1$, and degree $d=6$ in \cite[Theorem 7]{C:BCFG17}) states that:
     \[ \mathsf{Adv}_2(\mathcal{A}) \leq \frac{12\cdot(6n+3+Q+Q')^2}{p},\] where $Q'$ is the number of queries to $\mathcal{O}_{\mathsf{sk}}$, and $Q$ is the number of group operations, that is, the number of calls to oracles $\mathcal{O}_{\mathsf{add}}$ and $\mathcal{O}_{\mathsf{pair}}$, provided the following algebraic condition is satisfied:
     \begin{align*}& \{\mathbf{M} \in \mathbb{Z}_p^{(3n+2) \times (3n+Q'+1)}: \mathsf{Eq}_0(\mathbf{M})\} \\
       & = \{\mathbf{M} \in \mathbb{Z}_p^{(3n+2) \times (3n+Q'+1)}: \mathsf{Eq}_1(\mathbf{M}) \},\end{align*}
     where for all $\mathbf{M}$, $b \in \{0,1\}$,
     \[\mathsf{Eq}_b(\mathbf{M}): \begin{pmatrix} 1 \\ (AD-BC) \vec S \\ (AD-BC)\Gamma \\ D \vec x^{(b)} - \Gamma C \vec S \\ -B \vec x^{(b)} + \Gamma A \vec S\end{pmatrix}^\top \mathbf{M} \begin{pmatrix} 1 \\ \vec T \\ A \vec y^{(b)} - B \vec T \\ C \vec y^{(b)} - D \vec T \\ (f(\vec S,\vec T))_{ f \in  Q_\mathsf{sk}}\end{pmatrix} = 0,\]
     where the equality is taken in the ring $\mathbb{Z}_p[\vec S, \vec T,A,B,C,D,\Gamma]$, and $0$ denotes the zero polynomial. Intuitively, this condition captures the security at a symbolic level: it holds for schemes that are not trivially broken.
     The latter means that computing a linear combination in the exponents of target group elements that can be obtained from $\mathsf{pk}$, the challenge ciphertext, and functional decryption keys, does not break the security of the scheme.
     We prove this condition is satisfied in \ref{lem:symb} below.
\end{proof}

\begin{lemma}[Symbolic Security]\label{lem:symb}
    For any $(\vec x^{(0)},\vec y^{(0)}), (\vec x^{(1)},\vec y^{(1)}) \in Z_p^{2n}$, and any set $Q_\mathsf{sk} \subseteq \mathcal{F}_{n,B_x,B_y,B_f}$ such that for all $f \in Q_\mathsf{sk}$, $f(\vec x^{(0)}, \vec y^{(0)}) = f(\vec x^{(1)}, \vec y^{(1)})$, we have:
    \begin{align*}& \{\mathbf{M} \in \mathbb{Z}_p^{(3n+2) \times (3n+Q'+1)}: \mathsf{Eq}_0(\mathbf{M})\} \\ & = \{\mathbf{M} \in \mathbb{Z}_p^{(3n+2) \times (3n+Q'+1)}: \mathsf{Eq}_1(\mathbf{M}) \},\end{align*}
    where for all $\mathbf{M}$, $b \in \{0,1\}$,
    \[\mathsf{Eq}_b(\mathbf{M}): \begin{pmatrix} 1 \\ (AD-BC) \vec S \\ (AD-BC)\Gamma \\ D \vec x^{(b)} - \Gamma C \vec S \\ -B \vec x^{(b)} + \Gamma A \vec S\end{pmatrix}^\top \mathbf{M} \begin{pmatrix} 1 \\ \vec T \\ A \vec y^{(b)} - B \vec T \\ C \vec y^{(b)} - D \vec T \\ (f(\vec S, \vec T))_{ f \in  Q_\mathsf{sk}}\end{pmatrix} = 0,\]
    where the equality is taken in the ring $\mathbb{Z}_p[\vec S, \vec T,A,B,C,D,\Gamma]$, and $0$ denotes the zero polynomial.
\end{lemma}

\begin{proof}Let $b \in \{0,1\}$, and $\mathbf{M} \in \mathbb{Z}_p^{(3n+2) \times (3n+Q'+1)}$ that satisfies $\mathsf{Eq}_b(\mathbf{M})$. We prove it also satisfies $\mathsf{Eq}_{1-b}(\mathbf{M})$. To do so, we use the following rules:
    \begin{description}
        \item[Rule 1]: for all $P,Q,R \in \mathbb{Z}_p[\vec S, \vec T,A,B,C,D,\Gamma]$, with $\deg(P) \geq 1$, if $P \cdot Q+R = 0$ and $R$ is not a multiple of $P$, then $Q=0$ and $R=0$.
        \item[Rule 2]: for all $P \in \mathbb{Z}_p[\vec S, \vec T,A,B,C,D,\Gamma]$, any variable $X$ among the set $\{\vec S, \vec T,A,B,C,D,\Gamma\}$, and any $x \in \mathbb{Z}_p$, $P=0$ implies $P(X:=x)=0$, where $P(X:=x)$ denotes the polynomial $P$ evaluated on $X=x$.
    \end{description}
    Evaluating $\mathsf{Eq}_b(\mathbf{M})$ on $B=D=0$ (using \textbf{Rule 2}), then using \textbf{Rule 1} on $P=C \Gamma S_i T_j$ for all $i,j \in [n]$, we obtain that:
    \[\mathbf{M}_{n+2+i} \begin{pmatrix} 0 \\ \vec T \\ \mathbf{0} \\ \mathbf{0} \\ (f(\vec S, \vec T))_{ f \in  Q_\mathsf{sk}}\end{pmatrix} = 0,\]
    where $\mathbf{M}_{n+2+i}$ denotes the $n+2+i$'th row of $\mathbf{M}$.

    Similarly, using \textbf{Rule 1} on $P=\Gamma A S_i T_j$ for all $i,j \in [n]$, we obtain that:
    \[\mathbf{M}_{2n+2+i} \begin{pmatrix} 0 \\ \vec T \\ \mathbf{0} \\ \mathbf{0} \\ (f(\vec S, \vec T))_{ f \in  Q_\mathsf{sk}}\end{pmatrix} = 0.\]

    Thus, we have:
    \begin{equation}\label{eqn1}
        \forall \beta \in \{0,1\}: \begin{pmatrix} 0 \\ \mathbf{0} \\ 0 \\ D \vec x^{(\beta)} - \Gamma C \vec S \\ -B \vec x^{(\beta)} + \Gamma A \vec S\end{pmatrix}^\top \mathbf{M} \begin{pmatrix} 0 \\ \vec T \\ \mathbf{0} \\ \mathbf{0} \\ (f(\vec S, \vec T))_{ f \in  Q_\mathsf{sk}}\end{pmatrix} = 0.
        \end{equation}

    Using \textbf{Rule 1} on $P=(AD-BC) S_i B T_j$ for all $i,j \in [n]$ in the equation $\mathsf{Eq}_b(\mathbf{M})$, we get that the coefficient $M_{i+1,n+1+j}=0$ for all $i ,j \in [n]$. Similarly, using \textbf{Rule 1} on $P=(AD-BC) S_i D T_j$ for all $i,j \in [n]$, we get $M_{i+1,2n+1+j}=0$ for all $i ,j \in [n]$. Then, using \textbf{Rule 1} on $P=(AD-BC) \Gamma B T_j$ for all $j \in [n]$, we get $M_{n+2,n+1+j}=0$ for all $j \in [n]$. Finally, using \textbf{Rule 1} on $P=(AD-BC) \Gamma D T_j$ for all $j \in [n]$, we get $M_{n+2,2n+1+j}=0$ for all $j \in [n]$. Overall, we obtain:
    \begin{equation}\label{eqn2}
    \forall \beta \in \{0,1\}: \begin{pmatrix} 0 \\ (AD-BC) \vec S \\ (AD-BC)\Gamma \\ \mathbf{0} \\ \mathbf{0} \end{pmatrix}^\top \mathbf{M} \begin{pmatrix} 0 \\ \mathbf{0} \\ A \vec y^{(\beta)} - B \vec T \\ C \vec y^{(\beta)} - D \vec T \\ \mathbf{0} \end{pmatrix} = 0.
    \end{equation}

    We write:
    \begin{align*}
        &\begin{pmatrix} 0 \\ \mathbf{0} \\ 0 \\ D \vec x^{(b)} - \Gamma C \vec S \\ -B \vec x^{(b)} + \Gamma A \vec S\end{pmatrix}^\top \mathbf{M} \begin{pmatrix} 0 \\ \mathbf{0} \\ A \vec y^{(b)} - B \vec T \\ C \vec y^{(b)} - D \vec T \\ \mathbf{0} \end{pmatrix} \\
        & = \sum_{i,j \in [n]} \begin{pmatrix} D x_i^{(b)} - \Gamma C S_i \\ -B x_i^{(b)} + \Gamma A S_i\end{pmatrix}^\top \\
        & \times \left( m_{i,j}^{(1)} \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix} + m_{i,j}^{(2)} \begin{pmatrix} 1 & 0 \\ 0 & 0 \end{pmatrix} + m_{i,j}^{(3)} \begin{pmatrix} 0 & 0 \\ 1 & 0 \end{pmatrix} + m_{i,j}^{(4)} \begin{pmatrix} 0 & 1 \\ 0 & 0 \end{pmatrix}\right) \\
        & \times \begin{pmatrix} A y_j^{(b)} - B T_j \\ C y_j^{(b)} - D T_j\end{pmatrix}
        \end{align*}

    Evaluating the equation $\mathsf{Eq}_b(\mathbf{M})$ on $C=D=0$ (by \textbf{Rule 2}), then using \textbf{Rule 1} on $P=\Gamma A B S_i T_j$ for all $i,j \in [n]$, we obtain $m_{i,j}^{(3)}=0$ for all $i,j \in [n]$.
    Evaluating the equation $\mathsf{Eq}_b(\mathbf{M})$ on $A=B=0$ (by \textbf{Rule 2}), then using \textbf{Rule 1} on $P=\Gamma C D S_i T_j$ for all $i,j \in [n]$, we obtain $m_{i,j}^{(4)}=0$ for all $i,j \in [n]$.
    Evaluating the equation $\mathsf{Eq}_b(\mathbf{M})$ on $A = B = C = D =1$ (using \textbf{Rule 2}), then using \textbf{Rule 1} on $P=\Gamma S_i T_j$ for all $i,j \in [n]$, using the fact that $m_{i,j}^{(3)}=m_{i,j}^{(4)}=0$ and \eqref{eqn1}, we obtain $m_{i,j}^{(2)}=0$ for all $i,j \in [n]$.
    Using \textbf{Rule 1} on $P=\Gamma (AD-BC)S_i T_j$ for all $i,j \in [n]$ in the equation $\mathsf{Eq}_b(\mathbf{M})$, we obtain that for all $i,j \in [n]$,
    $$m_{i,j}^{(1)} = \mathbf{M}_{n+2} \begin{pmatrix} 0 \\ \mathbf{0} \\ \mathbf{0} \\ \mathbf{0} \\ (f_{i,j})_{f \in Q_\mathsf{sk}}\end{pmatrix},$$
    where $\mathbf{M}_{n+2}$ is the $n+2$'th row of $\mathbf{M}$.

    Putting everything together, can write
    \begin{align*}
      \begin{pmatrix} 0 \\ \mathbf{0} \\ 0 \\ D \vec x^{(b)} - \Gamma C \vec S \\ -B \vec x^{(b)} + \Gamma A \vec S\end{pmatrix}^\top \mathbf{M} \begin{pmatrix} 0 \\ \mathbf{0} \\ A \vec y^{(b)} - B \vec T \\ C \vec y^{(b)} - D \vec T \\ \mathbf{0} \end{pmatrix}
    \end{align*}
    as
    \begin{align*}
       & (AD-BC) \mathbf{M}_{n+2} \begin{pmatrix} 0 \\ \mathbf{0} \\ \mathbf{0} \\ \mathbf{0} \\ \big(f(\vec x^{(b)},\vec y^{(b)}) - \Gamma f(\vec s,\vec t)\big)_{f \in Q_\mathsf{sk}}\end{pmatrix} \\
       & = (AD-BC) \mathbf{M}_{n+2} \begin{pmatrix} 0 \\ \mathbf{0} \\ \mathbf{0} \\ \mathbf{0} \\ \big(f(\vec x^{(1-b)},\vec y^{(1-b)}) - \Gamma f(\vec s,\vec t)\big)_{f \in Q_\mathsf{sk}}\end{pmatrix} \\
       & = \begin{pmatrix} 0 \\ \mathbf{0} \\ 0 \\ D \vec x^{(1-b)} - \Gamma C \vec S \\ -B \vec x^{(1-b)} + \Gamma A \vec S\end{pmatrix}^\top \mathbf{M} \begin{pmatrix} 0 \\ \mathbf{0} \\ A \vec y^{(b)} - B \vec T \\ C \vec y^{(b)} - D \vec T \\ \mathbf{0} \end{pmatrix} \numberthis \label{eqn3}
    \end{align*}
    where we use the fact that for all $f \in Q_\mathsf{sk}$, we have the equality
    $f(\vec x^{(b)},\vec y^{(b)})=f(\vec x^{(1-b)},\vec y^{(1-b)})$.

    Evaluating equation $\mathsf{Eq}_b(\mathbf{M})$ on $A=B=D=0$ (by \textbf{Rule 2}), then using \textbf{Rule 1} on $\Gamma S_i C$ for all $i \in [n]$, and using \eqref{eqn1} and \eqref{eqn3}, we obtain that the coefficient $M_{n+2+i,1}=0$ for all $i \in [n]$.
    Evaluating $\mathsf{Eq}_b(\mathbf{M})$ on $B=C=D=0$ (by \textbf{Rule 2}), then using \textbf{Rule 1} on $\Gamma S_i A$ for all $i \in [n]$, and using \eqref{eqn1} and \eqref{eqn3}, we obtain that the coefficient $M_{2n+2+i,1}=0$ for all $i \in [n]$. Thus, we have:
    \begin{equation}\label{eqn4}
    \forall \beta \in \{0,1\}: \begin{pmatrix} 0 \\ \mathbf{0} \\ 0 \\ D \vec x^{(\beta)} - \Gamma C \vec S \\ -B \vec x^{(\beta)} + \Gamma A \vec S\end{pmatrix}^\top \mathbf{M} \begin{pmatrix} 1 \\ \mathbf{0} \\ \mathbf{0} \\ \mathbf{0} \\ \mathbf{0} \end{pmatrix} = 0.
    \end{equation}

    Evaluating equation $\mathsf{Eq}_b(\mathbf{M})$ on $A=C=D=0$ (by \textbf{Rule 2}), then using \textbf{Rule 1} on $B T_j$ for all $i \in [n]$, and using \eqref{eqn3}, we obtain that the coefficient $M_{1,n+1+j}=0$ for all $j \in [n]$.
    Evaluating $\mathsf{Eq}_b(\mathbf{M})$ on $A=B=C=0$ (by \textbf{Rule 2}), then using \textbf{Rule 1} on $D T_j$ for all $j \in [n]$, and using \eqref{eqn3}, we obtain that the coefficient $M_{1,2n+1+j}=0$ for all $j \in [n]$. Thus, we have:
    \begin{equation}\label{eqn5}
    \forall \beta \in \{0,1\}: \begin{pmatrix} 1 \\ \mathbf{0} \\ 0 \\ \mathbf{0} \\ \mathbf{0} \end{pmatrix}^\top \mathbf{M} \begin{pmatrix} 0 \\ \mathbf{0} \\ A \vec y^{(\beta)} - B \vec T \\ C \vec y^{(\beta)} - D \vec T \\ \mathbf{0} \end{pmatrix} = 0.
    \end{equation}
    Overall, we have:
        \begin{align*}
        \mathsf{Eq}_b(\mathbf{M}): &\begin{pmatrix} 1 \\ (AD-BC) \vec S \\ (AD-BC)\Gamma \\ D \vec x^{(b)} - \Gamma C \vec S \\ -B \vec x^{(b)} + \Gamma A \vec S\end{pmatrix}^\top \mathbf{M} \begin{pmatrix} 1 \\ \vec T \\ A \vec y^{(b)} - B \vec T \\ C \vec y^{(b)} - D \vec T \\ (f(\vec S, \vec T))_{ f \in  Q_\mathsf{sk}}\end{pmatrix} = 0
        \end{align*}
        which implies the following relation, under~\eqref{eqn1},\eqref{eqn2},\eqref{eqn4},\eqref{eqn5}
        \begin{align*}
&\begin{pmatrix} 1 \\ (AD-BC) \vec S \\ (AD-BC)\Gamma \\ \mathbf{0} \\  \mathbf{0} \end{pmatrix}^\top \mathbf{M} \begin{pmatrix} 1 \\ \vec T \\ \mathbf{0} \\ \mathbf{0} \\ (f(\vec S, \vec T))_{ f \in  Q_\mathsf{sk}}\end{pmatrix} \\
        &+ \begin{pmatrix} 0 \\ \mathbf{0} \\ 0 \\ D \vec x^{(b)} - \Gamma C \vec S \\ -B \vec x^{(b)} + \Gamma A \vec S\end{pmatrix}^\top \mathbf{M} \begin{pmatrix} 0 \\ \mathbf{0} \\ A \vec y^{(b)} - B \vec T \\ C \vec y^{(b)} - D \vec T \\ \mathbf{0}\end{pmatrix} = 0 \\
        \end{align*}
        and then, under~\eqref{eqn3}
        \begin{align*}
        &\begin{pmatrix} 1 \\ (AD-BC) \vec S \\ (AD-BC)\Gamma \\ \mathbf{0} \\  \mathbf{0} \end{pmatrix}^\top \mathbf{M} \begin{pmatrix} 1 \\ \vec T \\ \mathbf{0} \\ \mathbf{0} \\ (f(\vec S, \vec T))_{ f \in  Q_\mathsf{sk}}\end{pmatrix} \\
        & + \begin{pmatrix} 0 \\ \mathbf{0} \\ 0 \\ D \vec x^{(1-b)} - \Gamma C \vec S \\ -B \vec x^{(1-b)} + \Gamma A \vec S\end{pmatrix}^\top \mathbf{M} \begin{pmatrix} 0 \\ \mathbf{0} \\ A \vec y^{(1-b)} - B \vec T \\ C \vec y^{(1-b)} - D \vec T \\ \mathbf{0}\end{pmatrix} = 0.
        \end{align*}
        Under~\eqref{eqn1},\eqref{eqn2},\eqref{eqn4},\eqref{eqn5}, this implies
        \begin{align*}
        \mathsf{Eq}_{1-b}(\mathbf{M}): &\begin{pmatrix} 1 \\ (AD-BC) \vec S \\ (AD-BC)\Gamma \\ D \vec x^{(1-b)} - \Gamma C \vec S \\ -B \vec x^{(1-b)} + \Gamma A \vec S\end{pmatrix}^\top \mathbf{M} \begin{pmatrix} 1 \\ \vec T \\ A \vec y^{(1-b)} - B \vec T \\ C \vec y^{(1-b)} - D \vec T \\ (f(\vec S, \vec T))_{ f \in  Q_\mathsf{sk}}\end{pmatrix} = 0
        \end{align*}

\end{proof}

redpajama_set_name: RedPajamaArXiv


We are passionate about responsible travel and sustainable tourism. Duma supports the Arusha community through our school donation program and re-investment in the local economy.
In 2007, we established a formal donation program for Ndatu Kindergarten, a rural school located about 20 miles from Arusha. Starting as an unfinished brick structure without windows or a roof, the kindergarten is now fully operational with 85 students.
Since 2007, we have built two classrooms, a teachers' office, kitchen, water tower and fence. We constructed a pipeline that brings water from a source four kilometers away to the school and local community. We helped two teachers complete two year courses in Montessori education. We have also purchased land adjacent to the school to build the community's first playground.
Volunteer groups arrive each summer to assist with new construction and other projects at the school. Volunteers also tour the village and visit local families. If you are interested in volunteering, visiting the school, or making a monetary or in-kind donation, please let us know.
Duma is deeply committed to supporting and preserving the economy and environment of Tanzania. We use local suppliers and renewable products whenever possible.
Our offices in Arusha run on solar power and we recycle plastic and tin.
Duma also invests heavily in employee education and development. Guides, cooks and porters attend language workshops, Wilderness First Responder courses, wildlife trainings and driving classes.
We proudly support responsible travel by partnering with the International Mountain Explorers Connection and Kilimanjaro Porters Assistance Project. One of only a few Kilimanjaro trekking companies to qualify for IMEC/KPAP partnership, we meet all requirements set by IMEC/KPAP for fair treatment of porters. We regularly meet all requirements of fair treatment of porters. Please email us for our latest porter treatment report from KPAP.
We also believe strongly in wildlife protection, and all staff are trained to follow park maintenance laws set by the Tanzania National Park Association.


text:
Philip John Densham (born 7 April 1957) is a former English cricketer.  Densham was a left-handed batsman who bowled right-arm medium.  He was born in Banbury, Oxfordshire.

Densham made his debut for Oxfordshire in the 1978 Minor Counties Championship against Berkshire.  Densham played Minor counties cricket for Oxfordshire from 1978 to 1988, which included 37 Minor Counties Championship matches and a single MCCA Knockout Trophy match.  He made his List A debut against Warwickshire in the 1980 Gillette Cup.  He played 2 further List A matches, against Glamorgan in 1981 and Warwickshire in 1983.  In his 3 List A matches, he scored 17 runs at a batting average of 8.50, with a high score of 10.

References

External links
Philip Densham at ESPNcricinfo
Philip Densham at CricketArchive

1957 births
Living people
Sportspeople from Banbury
English cricketers
Oxfordshire cricketers

redpajama_set_name: RedPajamaWikipedia


text:
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;

namespace XNode {
    [Serializable]
    public class NodePort {
        public enum IO { Input, Output }

        public int ConnectionCount { get { return connections.Count; } }
        /// <summary> Return the first non-null connection </summary>
        public NodePort Connection {
            get {
                for (int i = 0; i < connections.Count; i++) {
                    if (connections[i] != null) return connections[i].Port;
                }
                return null;
            }
        }

        public IO direction {
            get { return _direction; }
            internal set { _direction = value; }
        }
        public Node.ConnectionType connectionType {
            get { return _connectionType; }
            internal set { _connectionType = value; }
        }
        public Node.TypeConstraint typeConstraint {
            get { return _typeConstraint; }
            internal set { _typeConstraint = value; }
        }

        /// <summary> Is this port connected to anytihng? </summary>
        public bool IsConnected { get { return connections.Count != 0; } }
        public bool IsInput { get { return direction == IO.Input; } }
        public bool IsOutput { get { return direction == IO.Output; } }

        public string fieldName { get { return _fieldName; } }
        public Node node { get { return _node; } }
        public bool IsDynamic { get { return _dynamic; } }
        public bool IsStatic { get { return !_dynamic; } }
        public Type ValueType {
            get {
                if (valueType == null && !string.IsNullOrEmpty(_typeQualifiedName)) valueType = Type.GetType(_typeQualifiedName, false);
                return valueType;
            }
            set {
                valueType = value;
                if (value != null) _typeQualifiedName = value.AssemblyQualifiedName;
            }
        }
        private Type valueType;

        [SerializeField] private string _fieldName;
        [SerializeField] private Node _node;
        [SerializeField] private string _typeQualifiedName;
        [SerializeField] private List<PortConnection> connections = new List<PortConnection>();
        [SerializeField] private IO _direction;
        [SerializeField] private Node.ConnectionType _connectionType;
        [SerializeField] private Node.TypeConstraint _typeConstraint;
        [SerializeField] private bool _dynamic;

        /// <summary> Construct a static targetless nodeport. Used as a template. </summary>
        public NodePort(FieldInfo fieldInfo) {
            _fieldName = fieldInfo.Name;
            ValueType = fieldInfo.FieldType;
            _dynamic = false;
            var attribs = fieldInfo.GetCustomAttributes(false);
            for (int i = 0; i < attribs.Length; i++) {
                if (attribs[i] is Node.InputAttribute) {
                    _direction = IO.Input;
                    _connectionType = (attribs[i] as Node.InputAttribute).connectionType;
                    _typeConstraint = (attribs[i] as Node.InputAttribute).typeConstraint;
                } else if (attribs[i] is Node.OutputAttribute) {
                    _direction = IO.Output;
                    _connectionType = (attribs[i] as Node.OutputAttribute).connectionType;
                    _typeConstraint = (attribs[i] as Node.OutputAttribute).typeConstraint;
                }
            }
        }

        /// <summary> Copy a nodePort but assign it to another node. </summary>
        public NodePort(NodePort nodePort, Node node) {
            _fieldName = nodePort._fieldName;
            ValueType = nodePort.valueType;
            _direction = nodePort.direction;
            _dynamic = nodePort._dynamic;
            _connectionType = nodePort._connectionType;
            _typeConstraint = nodePort._typeConstraint;
            _node = node;
        }

        /// <summary> Construct a dynamic port. Dynamic ports are not forgotten on reimport, and is ideal for runtime-created ports. </summary>
        public NodePort(string fieldName, Type type, IO direction, Node.ConnectionType connectionType, Node.TypeConstraint typeConstraint, Node node) {
            _fieldName = fieldName;
            this.ValueType = type;
            _direction = direction;
            _node = node;
            _dynamic = true;
            _connectionType = connectionType;
            _typeConstraint = typeConstraint;
        }

        /// <summary> Checks all connections for invalid references, and removes them. </summary>
        public void VerifyConnections() {
            for (int i = connections.Count - 1; i >= 0; i--) {
                if (connections[i].node != null &&
                    !string.IsNullOrEmpty(connections[i].fieldName) &&
                    connections[i].node.GetPort(connections[i].fieldName) != null)
                    continue;
                connections.RemoveAt(i);
            }
        }

        /// <summary> Return the output value of this node through its parent nodes GetValue override method. </summary>
        /// <returns> <see cref="Node.GetValue(NodePort)"/> </returns>
        public object GetOutputValue() {
            if (direction == IO.Input) return null;
            return node.GetValue(this);
        }

        /// <summary> Return the output value of the first connected port. Returns null if none found or invalid.</summary>
        /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
        public object GetInputValue() {
            NodePort connectedPort = Connection;
            if (connectedPort == null) return null;
            return connectedPort.GetOutputValue();
        }

        /// <summary> Return the output values of all connected ports. </summary>
        /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
        public object[] GetInputValues() {
            object[] objs = new object[ConnectionCount];
            for (int i = 0; i < ConnectionCount; i++) {
                NodePort connectedPort = connections[i].Port;
                if (connectedPort == null) { // if we happen to find a null port, remove it and look again
                    connections.RemoveAt(i);
                    i--;
                    continue;
                }
                objs[i] = connectedPort.GetOutputValue();
            }
            return objs;
        }

        /// <summary> Return the output value of the first connected port. Returns null if none found or invalid. </summary>
        /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
        public T GetInputValue<T>() {
            object obj = GetInputValue();
            return obj is T ? (T) obj : default(T);
        }

        /// <summary> Return the output values of all connected ports. </summary>
        /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
        public T[] GetInputValues<T>() {
            object[] objs = GetInputValues();
            T[] ts = new T[objs.Length];
            for (int i = 0; i < objs.Length; i++) {
                if (objs[i] is T) ts[i] = (T) objs[i];
            }
            return ts;
        }

        /// <summary> Return true if port is connected and has a valid input. </summary>
        /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
        public bool TryGetInputValue<T>(out T value) {
            object obj = GetInputValue();
            if (obj is T) {
                value = (T) obj;
                return true;
            } else {
                value = default(T);
                return false;
            }
        }

        /// <summary> Return the sum of all inputs. </summary>
        /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
        public float GetInputSum(float fallback) {
            object[] objs = GetInputValues();
            if (objs.Length == 0) return fallback;
            float result = 0;
            for (int i = 0; i < objs.Length; i++) {
                if (objs[i] is float) result += (float) objs[i];
            }
            return result;
        }

        /// <summary> Return the sum of all inputs. </summary>
        /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
        public int GetInputSum(int fallback) {
            object[] objs = GetInputValues();
            if (objs.Length == 0) return fallback;
            int result = 0;
            for (int i = 0; i < objs.Length; i++) {
                if (objs[i] is int) result += (int) objs[i];
            }
            return result;
        }

        /// <summary> Connect this <see cref="NodePort"/> to another </summary>
        /// <param name="port">The <see cref="NodePort"/> to connect to</param>
        public void Connect(NodePort port) {
            if (connections == null) connections = new List<PortConnection>();
            if (port == null) { Debug.LogWarning("Cannot connect to null port"); return; }
            if (port == this) { Debug.LogWarning("Cannot connect port to self."); return; }
            if (IsConnectedTo(port)) { Debug.LogWarning("Port already connected. "); return; }
            if (direction == port.direction) { Debug.LogWarning("Cannot connect two " + (direction == IO.Input ? "input" : "output") + " connections"); return; }
#if UNITY_EDITOR
            UnityEditor.Undo.RecordObject(node, "Connect Port");
            UnityEditor.Undo.RecordObject(port.node, "Connect Port");
#endif
            if (port.connectionType == Node.ConnectionType.Override && port.ConnectionCount != 0) { port.ClearConnections(); }
            if (connectionType == Node.ConnectionType.Override && ConnectionCount != 0) { ClearConnections(); }
            connections.Add(new PortConnection(port));
            if (port.connections == null) port.connections = new List<PortConnection>();
            if (!port.IsConnectedTo(this)) port.connections.Add(new PortConnection(this));
            node.OnCreateConnection(this, port);
            port.node.OnCreateConnection(this, port);
        }

        public List<NodePort> GetConnections() {
            List<NodePort> result = new List<NodePort>();
            for (int i = 0; i < connections.Count; i++) {
                NodePort port = GetConnection(i);
                if (port != null) result.Add(port);
            }
            return result;
        }

        public NodePort GetConnection(int i) {
            //If the connection is broken for some reason, remove it.
            if (connections[i].node == null || string.IsNullOrEmpty(connections[i].fieldName)) {
                connections.RemoveAt(i);
                return null;
            }
            NodePort port = connections[i].node.GetPort(connections[i].fieldName);
            if (port == null) {
                connections.RemoveAt(i);
                return null;
            }
            return port;
        }

        /// <summary> Get index of the connection connecting this and specified ports </summary>
        public int GetConnectionIndex(NodePort port) {
            for (int i = 0; i < ConnectionCount; i++) {
                if (connections[i].Port == port) return i;
            }
            return -1;
        }

        public bool IsConnectedTo(NodePort port) {
            for (int i = 0; i < connections.Count; i++) {
                if (connections[i].Port == port) return true;
            }
            return false;
        }

        /// <summary> Returns true if this port can connect to specified port </summary>
        public bool CanConnectTo(NodePort port) {
            // Figure out which is input and which is output
            NodePort input = null, output = null;
            if (IsInput) input = this;
            else output = this;
            if (port.IsInput) input = port;
            else output = port;
            // If there isn't one of each, they can't connect
            if (input == null || output == null) return false;
            // Check input type constraints
            if (input.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false;
            if (input.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false;
            if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
            // Check output type constraints
            if (output.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false;
            if (output.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false;
            if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
            // Success
            return true;
        }

        /// <summary> Disconnect this port from another port </summary>
        public void Disconnect(NodePort port) {
            // Remove this ports connection to the other
            for (int i = connections.Count - 1; i >= 0; i--) {
                if (connections[i].Port == port) {
                    connections.RemoveAt(i);
                }
            }
            if (port != null) {
                // Remove the other ports connection to this port
                for (int i = 0; i < port.connections.Count; i++) {
                    if (port.connections[i].Port == this) {
                        port.connections.RemoveAt(i);
                    }
                }
            }
            // Trigger OnRemoveConnection
            node.OnRemoveConnection(this);
            if (port != null) port.node.OnRemoveConnection(port);
        }

        /// <summary> Disconnect this port from another port </summary>
        public void Disconnect(int i) {
            // Remove the other ports connection to this port
            NodePort otherPort = connections[i].Port;
            if (otherPort != null) {
                for (int k = 0; k < otherPort.connections.Count; k++) {
                    if (otherPort.connections[k].Port == this) {
                        otherPort.connections.RemoveAt(i);
                    }
                }
            }
            // Remove this ports connection to the other
            connections.RemoveAt(i);

            // Trigger OnRemoveConnection
            node.OnRemoveConnection(this);
            if (otherPort != null) otherPort.node.OnRemoveConnection(otherPort);
        }

        public void ClearConnections() {
            while (connections.Count > 0) {
                Disconnect(connections[0].Port);
            }
        }

        /// <summary> Get reroute points for a given connection. This is used for organization </summary>
        public List<Vector2> GetReroutePoints(int index) {
            return connections[index].reroutePoints;
        }

        /// <summary> Swap connections with another node </summary>
        public void SwapConnections(NodePort targetPort) {
            int aConnectionCount = connections.Count;
            int bConnectionCount = targetPort.connections.Count;

            List<NodePort> portConnections = new List<NodePort>();
            List<NodePort> targetPortConnections = new List<NodePort>();

            // Cache port connections
            for (int i = 0; i < aConnectionCount; i++)
                portConnections.Add(connections[i].Port);

            // Cache target port connections
            for (int i = 0; i < bConnectionCount; i++)
                targetPortConnections.Add(targetPort.connections[i].Port);

            ClearConnections();
            targetPort.ClearConnections();

            // Add port connections to targetPort
            for (int i = 0; i < portConnections.Count; i++)
                targetPort.Connect(portConnections[i]);

            // Add target port connections to this one
            for (int i = 0; i < targetPortConnections.Count; i++)
                Connect(targetPortConnections[i]);

        }

        /// <summary> Copy all connections pointing to a node and add them to this one </summary>
        public void AddConnections(NodePort targetPort) {
            int connectionCount = targetPort.ConnectionCount;
            for (int i = 0; i < connectionCount; i++) {
                PortConnection connection = targetPort.connections[i];
                NodePort otherPort = connection.Port;
                Connect(otherPort);
            }
        }

        /// <summary> Move all connections pointing to this node, to another node </summary>
        public void MoveConnections(NodePort targetPort) {
            int connectionCount = connections.Count;

            // Add connections to target port
            for (int i = 0; i < connectionCount; i++) {
                PortConnection connection = targetPort.connections[i];
                NodePort otherPort = connection.Port;
                Connect(otherPort);
            }
            ClearConnections();
        }

        /// <summary> Swap connected nodes from the old list with nodes from the new list </summary>
        public void Redirect(List<Node> oldNodes, List<Node> newNodes) {
            foreach (PortConnection connection in connections) {
                int index = oldNodes.IndexOf(connection.node);
                if (index >= 0) connection.node = newNodes[index];
            }
        }

        [Serializable]
        private class PortConnection {
            [SerializeField] public string fieldName;
            [SerializeField] public Node node;
            public NodePort Port { get { return port != null ? port : port = GetPort(); } }

            [NonSerialized] private NodePort port;
            /// <summary> Extra connection path points for organization </summary>
            [SerializeField] public List<Vector2> reroutePoints = new List<Vector2>();

            public PortConnection(NodePort port) {
                this.port = port;
                node = port.node;
                fieldName = port.fieldName;
            }

            /// <summary> Returns the port that this <see cref="PortConnection"/> points to </summary>
            private NodePort GetPort() {
                if (node == null || string.IsNullOrEmpty(fieldName)) return null;
                return node.GetPort(fieldName);
            }
        }
    }
}

redpajama_set_name: RedPajamaGithub


This document provides examples of how to work with the Calculate Discount activity. The activity is found in the CartValidate workflow, but the actual work is done with the CalculateDiscounts() method.
Begin by setting the running total to the order sub-total.
Populate a PromotionEntriesSet with the order form and its line items (only applicable if the line item does not have a gift discount associated with it). This will be the source set, which the promotion context will need to evaluate a promotion.
Entry promotions can be limited in the quantity applied. For example, if you add to cart a line item of SKU X, with quantity 3, that qualifies for an entry level promotion, you can limit the promotion to be applied to only 1 unit of the line item. This would result the customer buying 3 units of SKU X and receiving a discount for one unit of the SKU. For details, see the Marketing section of the User Guide.
The Dictionary object is used to keep track of the quantity of entries that qualify for one or more promotions. The promotion engine first evaluates all line items for any qualifying promotions, then retroactively examines the results to apply the entry limit. For example, you can have a promotion X, which SKU Y qualifies for, that limits the entry discount to quantity of 1. When you add SKU Y to the cart, the promotion engine evaluates promotion X to be valid for SKU Y. After all line items in the cart have been evaluated for valid promotions, the promotion engine takes one last step to ensure that any promotion reward for promotion X will only give a discount for one unit of SKU Y, regardless of the line item quantity for SKU Y.
The use for this is more apparent when you have multiple line items all qualifying for the same promotion. If you set a limit to 3 and you have 5 line items all qualifying for the same promotion, you will only get discounts for the 3 line items with the highest placed prices.
The line items are ordered by placed price in descending order so that entry discounts will be applied to maximize savings for the customer.
If the line item is a gift, move on to the next line item.
Do the same as in the previous step, but applied to order level discounts instead.
After each evaluation, the promotion is marked as "Committed" or "Invalid", and the running total in the promotion context is updated accordingly.
Finally, the discounts are applied to the actual order. If the discount is not a shipping discount, it is added to the line item, and the new discounted price is stored in the ExtendedPrice field).
Order level discounts look at an untouched order form from the cart in the context first saved and populated, when this activity starts running. If an entry level discount should disable any subsequent order level discount, this will not take place. All promotions are evaluated cumulatively first. Whatever promotion passes the test will then be applied consecutively and cumulatively after the evaluation.
By the time the entry level discount is applied to the original order form, there is nothing in place to prevent the order level discount that should have been invalidated from running and applying further, "unwanted" discounts.
Evaluate and (upon validation) immediately apply discounts after each type/instance of a validated promotion to the cart, so that any subsequent promotion will check its conditions against an updated context.


text:
Why MAGFAST?
MAGFAST Guides
MAGFAST News
MAGFAST Live
MAGFAST in The Press
MAGFAST News » Is USB-C the Same as Thunderbolt?
Is USB-C the Same as Thunderbolt?
Posted: 25 Dec, 2022.
If a piece of your tech needs a charge, there is every chance it receives its boost of power via USB-C or Thunderbolt cables. The thing is, with both looking fairly similar, it wouldn't be too difficult to get confused about which one is which.
Since the introduction of USB-C, and the debuts of both Thunderbolt 3 and 4, choosing your charging tech has been a little more of a challenge. With connectors that are identically shaped and cables that are physically compatible with one another, it can make knowing what charger is best for you much more difficult.
We looked at the difference between these two types of charging ports so you can keep your tech ticking over.
What is a USB-C charger?
USB-C is perhaps the most common connector and port on the market at the moment. Used in a vast majority of the tech you use day to day, it has fast become an input/output type embraced by phone manufacturers, laptop creators, and power bank designers.
Oval in shape, it has no incorrect way to connect it to a device. Likewise, the cable has exactly the same connector at both ends meaning you can never be confused about which bit goes where when attaching it to an adaptor.
Able to transmit data at speeds of up to 20Gbps and send almost 100 watts of power to a device battery, USB-C is versatile and fast.
There are some examples where USB-C can transmit audio and video signals from your device through to an external monitor. However, for this to be possible, the port will need to support something known as DisplayPort over USB.
There are limitations and not all USB-C devices can deliver the audio and visual elements. A USB hard drive, for example, is unable to output any video whereas an iPad will use USB-C to charge, sync and power a monitor.
What is Thunderbolt?
Thunderbolt has seen a few iterations and the latest, known as 3 and 4, looks just the same in both port and connector. In terms of capability, the Thunderbolt can do the same as the USB-C – it just does it faster. You can even use them combined: a USB-C device can plug into a Thunderbolt 3 or 4 port on a PC, for example, without the use being compromised.
Speed-wise, a Thunderbolt 4 device will see data transfer twice as fast as a USB-C, meaning speeds of up to 40Gbps.
As well as sending data, a Thunderbolt gives you further benefits in both a visual and audio capacity. The Thunderbolt port means just one single Thunderbolt cable is all that is needed to send both power and information to and from a computer. For example, video data could be sent to two or more 60hz 4k external monitors.
Are Thunderbolt chargers the future?
Thunderbolt 3 and 4 have both been adopted by tech giants as a way to utilize rapid data transfer, fast charging and versatile audio and visual capabilities. All late-model Apple Macs and the iPad Pro have embraced it, and some go further, offering fantastic video output functions. A selection of iMacs for example supports dual 6k monitors via the use of Thunderbolt cables.
Stepping away from Apple products, the Thunderbolt can still be found on many Windows devices but mainly just those that are Intel rather than AMD-powered. Featuring on laptops as well as a few desktop computers, the Thunderbolt port is gradually being introduced to external hard drives and expansion docks too.
Thunderbolt, as mentioned earlier, is universally compatible with USB-C tech and so, if you have a device with a Thunderbolt port but only have a USB-C cable, you'll be able to connect them. The data transfer though will only be at USB-C capabilities.
How can you tell the difference between USB-C and Thunderbolt?
Telling the difference can be a little difficult: a USB-C port that supports Thunderbolt will look pretty much the same as one that doesn't. Looking at Apple products, for example, there are some with four ports, all of which are Thunderbolt capable, but none will have a marking to indicate that.
In many ways, you will only be able to tell by checking the device spec. With cables, it is more or less the same. In some cases, the cable will show a lightning bolt on it, on others it will not. This means, like with the devices, you'll need to check the user manual to find out whether it is a Thunderbolt cable or not.
Which should I use – Thunderbolt or USB-C?
It stands to reason that many people would say a Thunderbolt is the option to go for. It is faster, and able to offer you much more but, in many cases, those benefits are not always needed.
On a laptop that charges via USB-C but has both USB-C and Thunderbolt connectors, there is normally no difference between the two ports' performance for charging.
Another example of the ability to interchange between Thunderbolt and USB-C is when you may need to connect a laptop that supports Thunderbolt to a device that doesn't. The device will work and operate normally – you just won't get the benefit of the Thunderbolt data transfer speed.
Furthermore, and making the argument for Thunderbolt perhaps a little weaker, many devices that you attach, such as a printer or a keyboard, don't even put USB to the test due to the low levels of data transfer required.
The argument for Thunderbolt lies in its speed though. If you work in an environment where rapid data transfer is necessary, or you are regularly copying large video files for example. Thunderbolt is the clear option.
Enjoy rapid charging with MAGFAST premium chargers
At MAGFAST, we know that staying connected is vital in today's digital world. That is why we have designed a range of premium power banks that allows you to keep going no matter what demands you put on your tech. Whether working in the office or on the go, our six high-performance chargers give your tech the battery boost it needs whenever and wherever it needs it. See them in action at our demo and you might just win $1,000 of high-quality tech gear for yourself.
Troubleshooting Samsung: How to Turn Off a Samsung S21
Everything You Need to Know About Pass-Through Charging
How to Screenshot on a Google Pixel Step-by-Step
Discover MAGFAST
Portable Device Chargers
Qi Charging
Charging Ports
What is a Powerbank?
Flying with MAGFAST
MAGFAST for Business
Login to MAGFAST
Legal | Unsubscribe
© 2023 MAGFAST LLC All rights reserved

redpajama_set_name: RedPajamaCommonCrawl


This project is pretty cool, it's a ballistic knife, wich means the blade shoots out out the handle !
You will need a compression spring, 10mm in diameter 30mm when compressed and something like 70mm or a bit more when uncompressed.
I used the springs in an old Well bipod (there are 2 in each "leg").
Use a bit of epoxy or hot glue to attach the spring to the bottom of the handle.
You will also need a 25mm m3 screw.
You can print the blade (use flexible filament if you want to shoot at people), or print the blade mold (m3 screws to assemble the 2 parts of the mold) and cast flexible resin inside to get a safe flexible blade. I used this method to make about 20 blades with no problem.