Implementing Crypto
In the next section we provide a hands-on tutorial on how to implement a cryptographic scheme. Note: This section is a step-by-step tutorial and as such code-blocks only display the outline of the code. In the top right corner of every code field, there is an eye with which you can display the entire documentation. Press it to see the solution.
Private Key Encryption Scheme - Ring-LWE
We give the procedure of defining a private key encryption scheme based on ring-LWE.
Firstly, we look at the formal definition of the scheme we will implement:
Let \( R = \mathbb{Z}[X]/(X^n + 1) \), then we define the ring-based LWE private key encryption scheme as follows:
- \(sk \leftarrow Gen(1^n): sk \leftarrow R_q\)
- \((a, b) \leftarrow Enc(sk, m): \text{ where } m \in R_{0,1}, a \leftarrow R_q, e \leftarrow \chi, b:= sk \cdot a + e + m\)
- \(m \leftarrow Dec(sk, (a,b)): d:= b - a\cdot sk\) and each coefficient of \(d\) is mapped to \(0\) if it is closer to \(0\) than to \(q/2\) and to \(1\) otherwise.
The error distribution \(\chi\) samples an element from \(R_q\) and the modulus \(q\) is also chosen appropriately.
Finding the correct Trait
In order to implement a scheme, first identify the commonly shared functionality between each scheme of that category. Here, this is easy, as it is the formal definition of a private key encryption scheme:
- \(sk \leftarrow Gen(1^n)\) generate a private key for encryption
- \(c \leftarrow Enc(sk, m):\) encrypt the message
- \(m \leftarrow Dec(sk, c):\) decrypt the message
Now try to make a trait out of this:
// The trait is still missing the above described functionalities, try to add them
// manually
pub trait PrivKeyEncryption {
type SecretKey;
type Message;
type Cipher;
/// Generates a secret key that can be used for encryption and decryption.
fn gen(&self) -> Self::SecretKey;
/// Encrypts a message using the secret key.
fn enc(&self, sk: &Self::SecretKey, message: &Self::Message) -> Self::Cipher;
/// Decrypts a ciphertext using the secret key.
fn dec(&self, sk: &Self::SecretKey, cipher: &Self::Cipher) -> Self::Message;
}
Determining Public Parameters and Defining a corresponding Struct
Traits are implemented for structs. The actual implementation of our scheme corresponds to an implementation of the trait for a struct. This struct holds the public parameters needed for the computation:
// The struct is still missing the public parameters, try to identify them
pub struct PrivKRingLWE {
// the security parameter
n: Z,
// the modulus
q: ModulusPolynomialRingZq,
// the parameter for the error distribution
alpha: Q,
}
Implementing the Scheme
Now we want to implement the trait for our previously defined struct.
// The structure, now we only have to fill out each algorithm individually
impl PrivKeyEncryption for PrivKRingLWE {
type SecretKey = PolynomialRingZq;
type Message = Z;
type Cipher = (PolynomialRingZq, PolynomialRingZq);
fn gen(&self) -> Self::SecretKey {
}
fn enc(&self, sk: &Self::SecretKey, message: &Z) -> Self::Cipher {
}
fn dec(&self, sk: &Self::SecretKey, (a, b): &Self::Cipher) -> Z {
}
For the generation algorithm we have to implement
- \(sk \leftarrow Gen(1^n): sk \leftarrow R_q\) which corresponds to:
fn gen(&self) -> Self::SecretKey {
PolynomialRingZq::sample_uniform(&self.q)
}
Now we have to implement the encryption. You might have realized that as input for the scheme we have chosen an integer rather than a value of \(R*{0,1}\), this was mainly due to practicability, but we need a way to transform an integer into an element in \(R*{0,1}\). Use the following function for the transformation:
impl PrivKRingLWE {
/// Turns a [`Z`] instance into its bit representation, converts this bit representation
/// into a [`PolynomialRingZq`] with entries q/2 for any 1-bit and 0 as coefficient for any 0-bit.
fn compute_mu_half(&self, message: impl Into<Z>) -> PolynomialRingZq {
// ensure mu has at most n bits
let message: Z = message.into().abs();
let mu = message.modulo(Z::from(2).pow(&self.n).unwrap());
// set mu_q_half to polynomial with n {0,1} coefficients
let bits = mu.to_bits();
let mut mu_q_half = PolynomialRingZq::from((PolyOverZ::default(), &self.q));
let q_half = self.q.get_q().div_floor(2);
for (i, bit) in bits.iter().enumerate() {
if *bit {
mu_q_half.set_coeff(i, &q_half).unwrap();
}
}
mu_q_half
}
}
For the encryption algorithm we have to implement
- \((a, b) \leftarrow Enc(sk, m): \text{ where } m \in R_{0,1}, a \leftarrow R_q, e \leftarrow \chi, b:= sk \cdot a + e + m\) which corresponds to (note that for the error distribution you can sample from a discrete gauss):
fn enc(&self, sk: &Self::SecretKey, message: &Z) -> Self::Cipher {
let mu_q_half = self.compute_mu_half(message);
let a = PolynomialRingZq::sample_uniform(&self.q);
let e = PolynomialRingZq::sample_discrete_gauss(
&self.q,
&self.n,
0,
&self.alpha * &self.q.get_q(),
)
.unwrap();
let b = sk * &a + e + mu_q_half;
(a, b)
}
Now it only remains to decrypt the message. At this point this should probably be the easiest part, so we provide the solution directly:
fn dec(&self, sk: &Self::SecretKey, (a, b): &Self::Cipher) -> Z {
let result = b - sk * a;
let q_half = self.q.get_q().div_floor(2);
// check for each coefficient whether it's closer to 0 or q/2
// if closer to q/2 -> add 2^i to result
let mut vec = vec![];
for i in 0..self.q.get_degree() {
let coeff = Zq::from((result.get_coeff(i).unwrap(), self.q.get_q()));
if coeff.distance(&q_half) < coeff.distance(Z::ZERO) {
vec.push(true);
} else {
vec.push(false);
}
}
Z::from_bits(&vec)
}
Choosing Parameters and using the Scheme
Now we only have to use a set of parameters and we can use our construction. This could look as follows:
impl PrivKRingLWE {
pub fn secure128() -> Self {
Self::new(512, 92897729, 0.000005)
}
pub fn new(n: impl Into<Z>, q: impl Into<Modulus>, alpha: impl Into<Q>) -> Self {
let n: Z = n.into();
// mod = (X^n + 1) mod q
let mut q = PolyOverZq::from((1, q));
q.set_coeff(&n, 1).unwrap();
let q = ModulusPolynomialRingZq::from(&q);
let alpha: Q = alpha.into();
Self { n, q, alpha }
}
}
and can then be used to encrypt and decrypt messages:
fn enc_and_dec() {
let message = Z::from(17);
let scheme = PrivKRingLWE::secure128();
let sk = scheme.gen();
let c = scheme.enc(&sk, &message);
assert_eq!(message, scheme.dec(&sk, &c));
}