Suppose we have two chimpanzees: Flint and Goliath. Flint wants to send Goliath a message
- No ape other than Goliath should be able to figure out what
$m$ is; and - Goliath should be sure that it was indeed Flint who sent him the message.
There is a well-accepted solution to this type of problem: combining public key encryption schemes and digital signature schemes. More precisely: Goliath has a public encryption key
Flint->Goliath: $x:=Enc(m, pk_{G,enc})$
Flint->Goliath: $\sigma:=Sign(x, sk_{F,sign})$
Goliath simply performs the following two algorithms:
Unfortunately, Bob is not as careful as his great ape cousins, and does not implement the "encrypt and then sign" protocol correctly.
Suppose we have a non-degenerate bilinear pairing
struct Sender {
pub sk: Fr,
pub pk: G1Affine,
}
pub struct Receiver {
pk: G1Affine,
}
pub struct Auditor {}
impl Sender {
pub fn send(&self, m: Message, r: &Receiver) -> ElGamal {
let c_2: G1Affine = (r.pk.mul(&self.sk) + m.0).into_affine();
ElGamal(self.pk, c_2)
}
pub fn authenticate(&self, c: &ElGamal) -> G2Affine {
let hash_c = c.hash_to_curve();
hash_c.mul(&self.sk).into_affine()
}
}
Here, the ElGamal data type is: pub struct ElGamal(G1Affine, G1Affine);
, and the struct Message
is a wrapper for G1Affine
. Furthermore, we assume that we have a hash function:
ElGamal
and outputs an element of type G2Affine
.
In other words, we assume that
- Bob has a private signing key
$sk_{sign,B}\in \mathbb F_r$ and a public verification key$pk_{ver, B}:=g_1^{sk_{sign,B}}\in \mathbb G_1$ ; and - Alice has a private decryption key
$sk_{dec,A}\in \mathbb F_r$ and a public encryption key$pk_{enc,A}:=g_1^{sk_{dec,A}}\in \mathbb G_1$ .
Given a message
$pk_{ver,B}\in \mathbb G_1$ -
$\text{Enc}(pk_{enc,A},m):={(pk_{enc,A})^{sk_{sign,B}}}m=g_1^{sk_{dec,A}sk_{sign,B}}m\in \mathbb G_1$ ; and -
$\sigma:=\text{Sign}(sk_{sign,B},\text{Enc}(pk_{enc,A},m))$ $$=H(pk_{ver,B},\text{Enc}(pk_{enc,A},m))^{sk_{sign,B}}\in \mathbb G_2$$
(The first two are packaged together in type ElGamal
.)
Verification works as follows:
pub fn check_auth(sender_pk: G1Affine, c: &ElGamal, s: G2Affine) -> bool {
let lhs = { Bls12_381::pairing(G1Projective::generator(), s) };
let hash_c = c.hash_to_curve();
let rhs = { Bls12_381::pairing(sender_pk, hash_c) };
lhs == rhs
}
In other words, the verifier computes the following two pairings and checks they are equal:
ElGamal
.
We claim that given
Indeed, let