1use core::{ops::Deref, fmt};
2use std::{io, collections::HashMap};
3
4use thiserror::Error;
5
6use zeroize::{Zeroize, Zeroizing};
7use rand_core::{RngCore, CryptoRng};
8
9use chacha20::{
10 cipher::{crypto_common::KeyIvInit, StreamCipher},
11 Key as Cc20Key, Nonce as Cc20Iv, ChaCha20,
12};
13
14use transcript::{Transcript, RecommendedTranscript};
15
16#[cfg(test)]
17use ciphersuite::group::ff::Field;
18use ciphersuite::{group::GroupEncoding, Ciphersuite};
19use multiexp::BatchVerifier;
20
21use schnorr::SchnorrSignature;
22use dleq::DLEqProof;
23
24use crate::{Participant, ThresholdParams};
25
26mod sealed {
27 use super::*;
28
29 pub trait ReadWrite: Sized {
30 fn read<R: io::Read>(reader: &mut R, params: ThresholdParams) -> io::Result<Self>;
31 fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()>;
32
33 fn serialize(&self) -> Vec<u8> {
34 let mut buf = vec![];
35 self.write(&mut buf).unwrap();
36 buf
37 }
38 }
39
40 pub trait Message: Clone + PartialEq + Eq + fmt::Debug + Zeroize + ReadWrite {}
41 impl<M: Clone + PartialEq + Eq + fmt::Debug + Zeroize + ReadWrite> Message for M {}
42
43 pub trait Encryptable: Clone + AsRef<[u8]> + AsMut<[u8]> + Zeroize + ReadWrite {}
44 impl<E: Clone + AsRef<[u8]> + AsMut<[u8]> + Zeroize + ReadWrite> Encryptable for E {}
45}
46pub(crate) use sealed::*;
47
48#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
50pub struct EncryptionKeyMessage<C: Ciphersuite, M: Message> {
51 msg: M,
52 enc_key: C::G,
53}
54
55impl<C: Ciphersuite, M: Message> EncryptionKeyMessage<C, M> {
57 pub fn read<R: io::Read>(reader: &mut R, params: ThresholdParams) -> io::Result<Self> {
58 Ok(Self { msg: M::read(reader, params)?, enc_key: C::read_G(reader)? })
59 }
60
61 pub fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
62 self.msg.write(writer)?;
63 writer.write_all(self.enc_key.to_bytes().as_ref())
64 }
65
66 pub fn serialize(&self) -> Vec<u8> {
67 let mut buf = vec![];
68 self.write(&mut buf).unwrap();
69 buf
70 }
71
72 #[cfg(any(test, feature = "tests"))]
73 pub(crate) fn enc_key(&self) -> C::G {
74 self.enc_key
75 }
76}
77
78#[derive(Clone, Zeroize)]
81pub struct EncryptedMessage<C: Ciphersuite, E: Encryptable> {
82 key: C::G,
83 pop: SchnorrSignature<C>,
92 msg: Zeroizing<E>,
93}
94
95fn ecdh<C: Ciphersuite>(private: &Zeroizing<C::F>, public: C::G) -> Zeroizing<C::G> {
96 Zeroizing::new(public * private.deref())
97}
98
99fn cipher<C: Ciphersuite>(context: &str, ecdh: &Zeroizing<C::G>) -> ChaCha20 {
102 let mut transcript = RecommendedTranscript::new(b"DKG Encryption v0.2");
105 transcript.append_message(b"context", context.as_bytes());
106
107 transcript.domain_separate(b"encryption_key");
108
109 let mut ecdh = ecdh.to_bytes();
110 transcript.append_message(b"shared_key", ecdh.as_ref());
111 ecdh.as_mut().zeroize();
112
113 let zeroize = |buf: &mut [u8]| buf.zeroize();
114
115 let mut key = Cc20Key::default();
116 let mut challenge = transcript.challenge(b"key");
117 key.copy_from_slice(&challenge[.. 32]);
118 zeroize(challenge.as_mut());
119
120 let mut iv = Cc20Iv::default();
125 iv.copy_from_slice(b"DKG IV v0.2\0");
127
128 let res = ChaCha20::new(&key, &iv);
131 zeroize(key.as_mut());
132 res
133}
134
135fn encrypt<R: RngCore + CryptoRng, C: Ciphersuite, E: Encryptable>(
136 rng: &mut R,
137 context: &str,
138 from: Participant,
139 to: C::G,
140 mut msg: Zeroizing<E>,
141) -> EncryptedMessage<C, E> {
142 let key = Zeroizing::new(C::random_nonzero_F(rng));
154 cipher::<C>(context, &ecdh::<C>(&key, to)).apply_keystream(msg.as_mut().as_mut());
155
156 let pub_key = C::generator() * key.deref();
157 let nonce = Zeroizing::new(C::random_nonzero_F(rng));
158 let pub_nonce = C::generator() * nonce.deref();
159 EncryptedMessage {
160 key: pub_key,
161 pop: SchnorrSignature::sign(
162 &key,
163 nonce,
164 pop_challenge::<C>(context, pub_nonce, pub_key, from, msg.deref().as_ref()),
165 ),
166 msg,
167 }
168}
169
170impl<C: Ciphersuite, E: Encryptable> EncryptedMessage<C, E> {
171 pub fn read<R: io::Read>(reader: &mut R, params: ThresholdParams) -> io::Result<Self> {
172 Ok(Self {
173 key: C::read_G(reader)?,
174 pop: SchnorrSignature::<C>::read(reader)?,
175 msg: Zeroizing::new(E::read(reader, params)?),
176 })
177 }
178
179 pub fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
180 writer.write_all(self.key.to_bytes().as_ref())?;
181 self.pop.write(writer)?;
182 self.msg.write(writer)
183 }
184
185 pub fn serialize(&self) -> Vec<u8> {
186 let mut buf = vec![];
187 self.write(&mut buf).unwrap();
188 buf
189 }
190
191 #[cfg(test)]
192 pub(crate) fn invalidate_pop(&mut self) {
193 self.pop.s += C::F::ONE;
194 }
195
196 #[cfg(test)]
197 pub(crate) fn invalidate_msg<R: RngCore + CryptoRng>(
198 &mut self,
199 rng: &mut R,
200 context: &str,
201 from: Participant,
202 ) {
203 let key = Zeroizing::new(C::random_nonzero_F(rng));
206 let pub_key = C::generator() * key.deref();
207 let nonce = Zeroizing::new(C::random_nonzero_F(rng));
208 let pub_nonce = C::generator() * nonce.deref();
209 self.key = pub_key;
210 self.pop = SchnorrSignature::sign(
211 &key,
212 nonce,
213 pop_challenge::<C>(context, pub_nonce, pub_key, from, self.msg.deref().as_ref()),
214 );
215 }
216
217 #[cfg(test)]
219 pub(crate) fn invalidate_share_serialization<R: RngCore + CryptoRng>(
220 &mut self,
221 rng: &mut R,
222 context: &str,
223 from: Participant,
224 to: C::G,
225 ) {
226 use ciphersuite::group::ff::PrimeField;
227
228 let mut repr = <C::F as PrimeField>::Repr::default();
229 for b in repr.as_mut() {
230 *b = 255;
231 }
232 assert_eq!(repr.as_ref().len(), self.msg.as_ref().len());
234 assert!(!bool::from(C::F::from_repr(repr).is_some()));
236
237 self.msg.as_mut().as_mut().copy_from_slice(repr.as_ref());
238 *self = encrypt(rng, context, from, to, self.msg.clone());
239 }
240
241 #[cfg(test)]
243 pub(crate) fn invalidate_share_value<R: RngCore + CryptoRng>(
244 &mut self,
245 rng: &mut R,
246 context: &str,
247 from: Participant,
248 to: C::G,
249 ) {
250 use ciphersuite::group::ff::PrimeField;
251
252 let repr = C::F::ONE.to_repr();
254 self.msg.as_mut().as_mut().copy_from_slice(repr.as_ref());
255 *self = encrypt(rng, context, from, to, self.msg.clone());
256 }
257}
258
259#[derive(Clone, PartialEq, Eq, Debug, Zeroize)]
261pub struct EncryptionKeyProof<C: Ciphersuite> {
262 key: Zeroizing<C::G>,
263 dleq: DLEqProof<C::G>,
264}
265
266impl<C: Ciphersuite> EncryptionKeyProof<C> {
267 pub fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
268 Ok(Self { key: Zeroizing::new(C::read_G(reader)?), dleq: DLEqProof::read(reader)? })
269 }
270
271 pub fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
272 writer.write_all(self.key.to_bytes().as_ref())?;
273 self.dleq.write(writer)
274 }
275
276 pub fn serialize(&self) -> Vec<u8> {
277 let mut buf = vec![];
278 self.write(&mut buf).unwrap();
279 buf
280 }
281
282 #[cfg(test)]
283 pub(crate) fn invalidate_key(&mut self) {
284 *self.key += C::generator();
285 }
286
287 #[cfg(test)]
288 pub(crate) fn invalidate_dleq(&mut self) {
289 let mut buf = vec![];
290 self.dleq.write(&mut buf).unwrap();
291 buf[0] = buf[0].wrapping_add(1);
295 self.dleq = DLEqProof::read::<&[u8]>(&mut buf.as_ref()).unwrap();
296 }
297}
298
299fn pop_challenge<C: Ciphersuite>(
303 context: &str,
304 nonce: C::G,
305 key: C::G,
306 sender: Participant,
307 msg: &[u8],
308) -> C::F {
309 let mut transcript = RecommendedTranscript::new(b"DKG Encryption Key Proof of Possession v0.2");
310 transcript.append_message(b"context", context.as_bytes());
311
312 transcript.domain_separate(b"proof_of_possession");
313
314 transcript.append_message(b"nonce", nonce.to_bytes());
315 transcript.append_message(b"key", key.to_bytes());
316 transcript.append_message(b"sender", sender.to_bytes());
318 transcript.append_message(b"message", msg);
320 C::hash_to_F(b"DKG-encryption-proof_of_possession", &transcript.challenge(b"schnorr"))
324}
325
326fn encryption_key_transcript(context: &str) -> RecommendedTranscript {
327 let mut transcript = RecommendedTranscript::new(b"DKG Encryption Key Correctness Proof v0.2");
328 transcript.append_message(b"context", context.as_bytes());
329 transcript
330}
331
332#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)]
333pub(crate) enum DecryptionError {
334 #[error("accused provided an invalid signature")]
335 InvalidSignature,
336 #[error("accuser provided an invalid decryption key")]
337 InvalidProof,
338}
339
340#[derive(Clone)]
342pub(crate) struct Encryption<C: Ciphersuite> {
343 context: String,
344 i: Option<Participant>,
345 enc_key: Zeroizing<C::F>,
346 enc_pub_key: C::G,
347 enc_keys: HashMap<Participant, C::G>,
348}
349
350impl<C: Ciphersuite> fmt::Debug for Encryption<C> {
351 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
352 fmt
353 .debug_struct("Encryption")
354 .field("context", &self.context)
355 .field("i", &self.i)
356 .field("enc_pub_key", &self.enc_pub_key)
357 .field("enc_keys", &self.enc_keys)
358 .finish_non_exhaustive()
359 }
360}
361
362impl<C: Ciphersuite> Zeroize for Encryption<C> {
363 fn zeroize(&mut self) {
364 self.enc_key.zeroize();
365 self.enc_pub_key.zeroize();
366 for (_, mut value) in self.enc_keys.drain() {
367 value.zeroize();
368 }
369 }
370}
371
372impl<C: Ciphersuite> Encryption<C> {
373 pub(crate) fn new<R: RngCore + CryptoRng>(
374 context: String,
375 i: Option<Participant>,
376 rng: &mut R,
377 ) -> Self {
378 let enc_key = Zeroizing::new(C::random_nonzero_F(rng));
379 Self {
380 context,
381 i,
382 enc_pub_key: C::generator() * enc_key.deref(),
383 enc_key,
384 enc_keys: HashMap::new(),
385 }
386 }
387
388 pub(crate) fn registration<M: Message>(&self, msg: M) -> EncryptionKeyMessage<C, M> {
389 EncryptionKeyMessage { msg, enc_key: self.enc_pub_key }
390 }
391
392 pub(crate) fn register<M: Message>(
393 &mut self,
394 participant: Participant,
395 msg: EncryptionKeyMessage<C, M>,
396 ) -> M {
397 assert!(
398 !self.enc_keys.contains_key(&participant),
399 "Re-registering encryption key for a participant"
400 );
401 self.enc_keys.insert(participant, msg.enc_key);
402 msg.msg
403 }
404
405 pub(crate) fn encrypt<R: RngCore + CryptoRng, E: Encryptable>(
406 &self,
407 rng: &mut R,
408 participant: Participant,
409 msg: Zeroizing<E>,
410 ) -> EncryptedMessage<C, E> {
411 encrypt(rng, &self.context, self.i.unwrap(), self.enc_keys[&participant], msg)
412 }
413
414 pub(crate) fn decrypt<R: RngCore + CryptoRng, I: Copy + Zeroize, E: Encryptable>(
415 &self,
416 rng: &mut R,
417 batch: &mut BatchVerifier<I, C::G>,
418 batch_id: I,
421 from: Participant,
422 mut msg: EncryptedMessage<C, E>,
423 ) -> (Zeroizing<E>, EncryptionKeyProof<C>) {
424 msg.pop.batch_verify(
425 rng,
426 batch,
427 batch_id,
428 msg.key,
429 pop_challenge::<C>(&self.context, msg.pop.R, msg.key, from, msg.msg.deref().as_ref()),
430 );
431
432 let key = ecdh::<C>(&self.enc_key, msg.key);
433 cipher::<C>(&self.context, &key).apply_keystream(msg.msg.as_mut().as_mut());
434 (
435 msg.msg,
436 EncryptionKeyProof {
437 key,
438 dleq: DLEqProof::prove(
439 rng,
440 &mut encryption_key_transcript(&self.context),
441 &[C::generator(), msg.key],
442 &self.enc_key,
443 ),
444 },
445 )
446 }
447
448 pub(crate) fn decrypt_with_proof<E: Encryptable>(
451 &self,
452 from: Participant,
453 decryptor: Participant,
454 mut msg: EncryptedMessage<C, E>,
455 proof: Option<EncryptionKeyProof<C>>,
457 ) -> Result<Zeroizing<E>, DecryptionError> {
458 if !msg.pop.verify(
459 msg.key,
460 pop_challenge::<C>(&self.context, msg.pop.R, msg.key, from, msg.msg.deref().as_ref()),
461 ) {
462 Err(DecryptionError::InvalidSignature)?;
463 }
464
465 if let Some(proof) = proof {
466 proof
468 .dleq
469 .verify(
470 &mut encryption_key_transcript(&self.context),
471 &[C::generator(), msg.key],
472 &[self.enc_keys[&decryptor], *proof.key],
473 )
474 .map_err(|_| DecryptionError::InvalidProof)?;
475
476 cipher::<C>(&self.context, &proof.key).apply_keystream(msg.msg.as_mut().as_mut());
477 Ok(msg.msg)
478 } else {
479 Err(DecryptionError::InvalidProof)
480 }
481 }
482}