DO NOT FORK THE REPOSITORY, AS IT WILL MAKE YOUR SOLUTION PUBLIC. INSTEAD, CLONE IT AND ADD A NEW REMOTE TO A PRIVATE REPOSITORY, OR SUBMIT A GIST
Use cargo run --release
to see it in action
|___ /| | / / | | | | | |
/ / | |/ / | |_| | __ _ ___| | __
/ / | \ | _ |/ _` |/ __| |/ /
./ /___| |\ \ | | | | (_| | (__| <
\_____/\_| \_/ \_| |_/\__,_|\___|_|\_\
Bob designed a new one time scheme, that's based on the tried and true method of encrypt + sign. He combined ElGamal encryption with BLS signatures in a clever way, such that you use pairings to verify the encrypted message was not tampered with. Alice, then, figured out a way to reveal the plaintexts...
We start with some notations
Source code consists of only 1 file, main.rs
.
ElGamal
declaration include 2 elements in group hash_to_curve
function return a hash in group
pub struct ElGamal(G1Affine, G1Affine);
impl ElGamal {
pub fn hash_to_curve(&self) -> G2Affine {
let mut data = Vec::new();
self.serialize_uncompressed(&mut data).unwrap();
hasher().hash(&data).unwrap()
}
}
Sender
's method is not called directly in the main
function, but it is what generated blob in blob.bin
file.
send()
create ElGamal encryption which includes sender's public key,
authenticate()
create a signature
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()
}
}
The Auditor check_auth()
does the check that whether data received by received is valid
impl Auditor {
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
}
}
The generate_message_space()
function return a space of message, consist of 10 points on
fn generate_message_space() -> [Message; 10] {
let g1 = G1Projective::generator();
let msgs = [
390183091831u64,
4987238947234982,
84327489279482,
8492374892742,
5894274824234,
4982748927426,
48248927348927427,
489274982749828,
99084321987189371,
8427489729843712893,
];
msgs.iter()
.map(|&msg_i| Message(g1.mul(Fr::from(msg_i)).into_affine()))
.collect::<Vec<_>>()
.try_into()
.unwrap()
}
The receiver got: public key of sender, its own key pair, the encrypted message
In our own situation, we (the attacker) have same information as receiver side EXCEPT receiver private key
Hmm, we have
Let's start with a trivial pairing equation we can think of:
We need to cancel
Equation happens iff
If
Notice that we ignored group in above statements. Because both public key are in group
But look at an existing bilinear pairing in souce code, we already have
With this, we can basically try every message in message space to find when the equation happen.
Just one main line to get the index thanks to iter of rust.
let Blob {sender_pk: _, c , s, rec_pk: receiver_pk} = blob;
let index = messages.iter()
.enumerate()
.filter(|(_, &Message(m))| Bls12_381::pairing(receiver_pk, s) == Bls12_381::pairing(c.1 + m.neg(), c.hash_to_curve()))
.map(|(i, _)| i)
.collect::<Vec<_>>()[0];
println!("Index of the encrypted message {:?}", index);
To use neg()
, we need to import Neg.
use std::ops::Neg;