group_threshold_cryptography_pre_release/
ciphertext.rs1use std::ops::Mul;
2
3use ark_ec::{pairing::Pairing, AffineRepr};
4use ark_ff::{One, UniformRand};
5use ark_serialize::CanonicalSerialize;
6use chacha20poly1305::{
7 aead::{generic_array::GenericArray, Aead, KeyInit, Payload},
8 ChaCha20Poly1305,
9};
10use ferveo_common::serialization;
11use serde::{Deserialize, Serialize};
12use serde_with::serde_as;
13use sha2::{digest::Digest, Sha256};
14use zeroize::ZeroizeOnDrop;
15
16use crate::{htp_bls12381_g2, Error, Result, SecretBox, SharedSecret};
17
18#[serde_as]
19#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
20pub struct Ciphertext<E: Pairing> {
21 #[serde_as(as = "serialization::SerdeAs")]
23 pub commitment: E::G1Affine,
24
25 #[serde_as(as = "serialization::SerdeAs")]
27 pub auth_tag: E::G2Affine,
28
29 #[serde(with = "serde_bytes")]
31 pub ciphertext: Vec<u8>,
32}
33
34impl<E: Pairing> Ciphertext<E> {
35 pub fn check(&self, aad: &[u8], g_inv: &E::G1Prepared) -> Result<bool> {
36 self.header()?.check(aad, g_inv)
37 }
38
39 pub fn ciphertext_hash(&self) -> [u8; 32] {
40 sha256(&self.ciphertext)
41 }
42
43 pub fn header(&self) -> Result<CiphertextHeader<E>> {
44 Ok(CiphertextHeader {
45 commitment: self.commitment,
46 auth_tag: self.auth_tag,
47 ciphertext_hash: self.ciphertext_hash(),
48 })
49 }
50 pub fn payload(&self) -> Vec<u8> {
51 self.ciphertext.clone()
52 }
53}
54
55#[serde_as]
56#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
57pub struct CiphertextHeader<E: Pairing> {
58 #[serde_as(as = "serialization::SerdeAs")]
59 pub commitment: E::G1Affine,
60 #[serde_as(as = "serialization::SerdeAs")]
61 pub auth_tag: E::G2Affine,
62 pub ciphertext_hash: [u8; 32],
63}
64
65impl<E: Pairing> CiphertextHeader<E> {
66 pub fn check(&self, aad: &[u8], g_inv: &E::G1Prepared) -> Result<bool> {
67 let hash_g2 = E::G2Prepared::from(construct_tag_hash::<E>(
74 self.commitment,
75 &self.ciphertext_hash,
76 aad,
77 )?);
78
79 let is_ciphertext_valid = E::multi_pairing(
80 [self.commitment.into(), g_inv.to_owned()],
83 [hash_g2, self.auth_tag.into()],
84 )
85 .0 == E::TargetField::one();
86
87 if is_ciphertext_valid {
88 Ok(true)
89 } else {
90 Err(Error::CiphertextVerificationFailed)
91 }
92 }
93}
94
95pub fn encrypt<E: Pairing>(
96 message: SecretBox<Vec<u8>>,
97 aad: &[u8],
98 pubkey: &E::G1Affine,
99 rng: &mut impl rand::Rng,
100) -> Result<Ciphertext<E>> {
101 let rand_element = E::ScalarField::rand(rng);
103 let g_gen = E::G1Affine::generator();
105 let h_gen = E::G2Affine::generator();
107
108 let ry_prep = E::G1Prepared::from(pubkey.mul(rand_element).into());
109 let product = E::pairing(ry_prep, h_gen).0;
111 let commitment = g_gen.mul(rand_element).into();
113
114 let nonce = Nonce::from_commitment::<E>(commitment)?;
115 let shared_secret = SharedSecret::<E>(product);
116
117 let payload = Payload {
118 msg: message.as_secret().as_ref(),
119 aad,
120 };
121 let ciphertext = shared_secret_to_chacha(&shared_secret)?
122 .encrypt(&nonce.0, payload)
123 .map_err(Error::SymmetricEncryptionError)?
124 .to_vec();
125 let ciphertext_hash = sha256(&ciphertext);
126
127 let auth_tag = construct_tag_hash::<E>(commitment, &ciphertext_hash, aad)?
129 .mul(rand_element)
130 .into();
131
132 Ok(Ciphertext::<E> {
134 commitment,
135 ciphertext,
136 auth_tag,
137 })
138}
139
140pub fn decrypt_symmetric<E: Pairing>(
141 ciphertext: &Ciphertext<E>,
142 aad: &[u8],
143 private_key: &E::G2Affine,
144 g_inv: &E::G1Prepared,
145) -> Result<Vec<u8>> {
146 ciphertext.check(aad, g_inv)?;
147 let shared_secret = E::pairing(
148 E::G1Prepared::from(ciphertext.commitment),
149 E::G2Prepared::from(*private_key),
150 )
151 .0;
152 let shared_secret = SharedSecret(shared_secret);
153 decrypt_with_shared_secret_unchecked(ciphertext, aad, &shared_secret)
154}
155
156fn decrypt_with_shared_secret_unchecked<E: Pairing>(
157 ciphertext: &Ciphertext<E>,
158 aad: &[u8],
159 shared_secret: &SharedSecret<E>,
160) -> Result<Vec<u8>> {
161 let nonce = Nonce::from_commitment::<E>(ciphertext.commitment)?;
162 let ctxt = ciphertext.ciphertext.to_vec();
163 let payload = Payload {
164 msg: ctxt.as_ref(),
165 aad,
166 };
167 let plaintext = shared_secret_to_chacha(shared_secret)?
168 .decrypt(&nonce.0, payload)
169 .map_err(|_| Error::CiphertextVerificationFailed)?
170 .to_vec();
171
172 Ok(plaintext)
173}
174
175pub fn decrypt_with_shared_secret<E: Pairing>(
176 ciphertext: &Ciphertext<E>,
177 aad: &[u8],
178 shared_secret: &SharedSecret<E>,
179 g_inv: &E::G1Prepared,
180) -> Result<Vec<u8>> {
181 ciphertext.check(aad, g_inv)?;
182 decrypt_with_shared_secret_unchecked(ciphertext, aad, shared_secret)
183}
184
185fn sha256(input: &[u8]) -> [u8; 32] {
186 let mut hasher = Sha256::new();
187 hasher.update(input);
188 let result = hasher.finalize();
189 result.into()
190}
191
192pub fn shared_secret_to_chacha<E: Pairing>(
193 shared_secret: &SharedSecret<E>,
194) -> Result<ChaCha20Poly1305> {
195 let mut prf_key = SecretBox::new(Vec::new());
196 shared_secret
197 .0
198 .serialize_compressed(prf_key.as_mut_secret())?;
199 Ok(ChaCha20Poly1305::new(GenericArray::from_slice(&sha256(
200 prf_key.as_secret(),
201 ))))
202}
203
204#[derive(ZeroizeOnDrop)]
208pub struct Nonce(pub(crate) chacha20poly1305::Nonce);
209
210impl Nonce {
211 pub fn from_commitment<E: Pairing>(
212 commitment: E::G1Affine,
213 ) -> Result<Self> {
214 let mut commitment_bytes = Vec::new();
215 commitment.serialize_compressed(&mut commitment_bytes)?;
216 let commitment_hash = sha256(&commitment_bytes);
217 Ok(Nonce(*chacha20poly1305::Nonce::from_slice(
218 &commitment_hash[..12],
219 )))
220 }
221}
222
223fn hash_to_g2<T: ark_serialize::CanonicalDeserialize>(
224 message: &[u8],
225) -> Result<T> {
226 let point = htp_bls12381_g2(message);
227 let mut point_ser: Vec<u8> = Vec::new();
228 point.serialize_compressed(&mut point_ser)?;
229 T::deserialize_compressed(&point_ser[..]).map_err(Error::ArkSerializeError)
230}
231
232fn construct_tag_hash<E: Pairing>(
233 commitment: E::G1Affine,
234 ciphertext_hash: &[u8],
235 aad: &[u8],
236) -> Result<E::G2Affine> {
237 let mut hash_input = Vec::<u8>::new();
238 commitment.serialize_compressed(&mut hash_input)?;
239 hash_input.extend_from_slice(ciphertext_hash);
240 hash_input.extend_from_slice(aad);
241 hash_to_g2(&hash_input)
242}
243
244#[cfg(test)]
245mod tests {
246 use ark_std::test_rng;
247
248 use crate::{test_common::*, *};
249
250 type E = ark_bls12_381::Bls12_381;
251
252 #[test]
253 fn symmetric_encryption() {
254 let rng = &mut test_rng();
255 let shares_num = 16;
256 let threshold = shares_num * 2 / 3;
257 let msg = "my-msg".as_bytes().to_vec();
258 let aad: &[u8] = "my-aad".as_bytes();
259
260 let (pubkey, privkey, contexts) =
261 setup_fast::<E>(threshold, shares_num, rng);
262 let g_inv = &contexts[0].setup_params.g_inv;
263
264 let ciphertext =
265 encrypt::<E>(SecretBox::new(msg.clone()), aad, &pubkey, rng)
266 .unwrap();
267
268 let plaintext =
269 decrypt_symmetric(&ciphertext, aad, &privkey, g_inv).unwrap();
270
271 assert_eq!(msg, plaintext);
272
273 let bad: &[u8] = "bad-aad".as_bytes();
274
275 assert!(decrypt_symmetric(&ciphertext, bad, &privkey, g_inv).is_err());
276 }
277
278 #[test]
279 fn ciphertext_validity_check() {
280 let rng = &mut test_rng();
281 let shares_num = 16;
282 let threshold = shares_num * 2 / 3;
283 let msg = "my-msg".as_bytes().to_vec();
284 let aad: &[u8] = "my-aad".as_bytes();
285 let (pubkey, _, contexts) = setup_fast::<E>(threshold, shares_num, rng);
286 let g_inv = contexts[0].setup_params.g_inv.clone();
287 let mut ciphertext =
288 encrypt::<E>(SecretBox::new(msg), aad, &pubkey, rng).unwrap();
289
290 assert!(ciphertext.check(aad, &g_inv).is_ok());
292
293 ciphertext.ciphertext[0] += 1;
295 assert!(ciphertext.check(aad, &g_inv).is_err());
296
297 let aad = "bad aad".as_bytes();
299 assert!(ciphertext.check(aad, &g_inv).is_err());
300 }
301}