dkls23/proto/
scheme.rs

1// Copyright (c) Silence Laboratories Pte. Ltd. All Rights Reserved.
2// This software is licensed under the Silence Laboratories License Agreement.
3
4//! Cryptographic Scheme Implementation for Secure Communication
5//!
6//! This module provides the core cryptographic primitives and protocols
7//! for secure communication between parties in the DKLS23 protocol.
8//! It implements an encryption scheme that combines:
9//! - Authenticated Encryption with Associated Data (AEAD)
10//! - Key exchange using X25519
11//! - Secure key derivation
12//!
13//! # Security Properties
14//!
15//! - Forward secrecy through ephemeral key exchange
16//! - Message authentication and integrity
17//! - Nonce reuse prevention
18//! - Secure key derivation using SHA-256
19//!
20//! # Implementation Details
21//!
22//! The module provides:
23//! - `EncryptionScheme` trait defining the interface for secure communication
24//! - `AeadX25519` implementation using ChaCha20-Poly1305 for AEAD
25//! - Secure nonce generation and management
26//! - Key pair management for multiple parties
27
28#![allow(unused_imports, dead_code, unused_variables)]
29
30use std::marker::PhantomData;
31
32use aead::{
33    consts::{U10, U32},
34    generic_array::{typenum::Unsigned, GenericArray},
35    AeadCore, AeadInPlace, Key, KeyInit, KeySizeUser, Nonce, Tag,
36};
37use chacha20::hchacha;
38use rand_core::CryptoRngCore;
39use sha2::{Digest, Sha256};
40use x25519_dalek::{PublicKey, ReusableSecret};
41use zeroize::Zeroizing;
42
43use crate::pairs::Pairs;
44
45/// Error indicating invalid public key format or operation
46#[derive(Debug)]
47pub struct PublicKeyError;
48
49/// Error indicating encryption or decryption failure
50#[derive(Debug)]
51pub struct EncryptionError;
52
53/// Type alias for a shared key used in encryption
54type SharedKey = Zeroizing<GenericArray<u8, U32>>;
55
56/// Represents an encryption scheme interface for in-place AEAD and
57/// managing key exchange.
58///
59/// A type implementing EncryptionScheme encapsulates two
60/// functionalities:
61///
62/// - An AEAD algorithm such as ChaCha20 or AES-GCM and its
63///   implementation details, such as nonce generation.
64///
65/// - The derivation of encryption keys for one or more pairs of parties.
66///   The concrete type of public/private key pair and key exchange is
67///   an implementation detail.
68///
69/// # Security Considerations
70///
71/// Implementations must ensure:
72/// - Forward secrecy through proper key exchange
73/// - Nonce uniqueness for each encryption
74/// - Secure key derivation
75/// - Proper authentication of messages
76pub trait EncryptionScheme: Send {
77    /// Return external representation of own public key
78    fn public_key(&self) -> &[u8];
79
80    /// Sets or updates the public key for a specified receiver index.
81    ///
82    /// This function is used to associate a public key with a designated
83    /// receiver identified by an index. It facilitates secure communication
84    /// setup by ensuring that messages can be encrypted such that only the
85    /// receiver with the corresponding private key can decrypt them.
86    ///
87    /// # Parameters
88    ///
89    /// - `receiver_index`: An integer value that uniquely identifies the
90    ///   receiver within the system. This index is used to specify which
91    ///   receiver the public key is associated with.
92    ///
93    /// - `public_key`: A byte slice representing the public key of the receiver.
94    ///   This key is used in cryptographic operations to ensure that only
95    ///   the intended receiver can decrypt messages encrypted with this key.
96    ///
97    /// # Errors
98    ///
99    /// - `PublicKeyError`: An error is returned if the public key cannot be
100    ///   set due to invalid formatting, an invalid receiver index, or any
101    ///   other issue encountered in the operation.
102    fn receiver_public_key(
103        &mut self,
104        receiver_index: usize,
105        public_key: &[u8],
106    ) -> Result<(), PublicKeyError>;
107
108    /// Encrypts the provided data buffer using associated data and
109    /// manages an associated tail segment.
110    ///
111    /// # Parameters
112    ///
113    /// - `associated_data`: A byte slice containing additional
114    ///   authenticated data (AAD) that will be used to ensure the
115    ///   integrity and authenticity of the encrypted data. This data is
116    ///   not encrypted but is included in the integrity check.
117    ///
118    /// - `buffer`: A mutable byte slice containing the plaintext data
119    ///   that will be encrypted in place. Upon successful encryption,
120    ///   this buffer will contain the ciphertext data.
121    ///
122    /// - `tail`: A mutable byte slice representing the trailing
123    ///   segment of the data buffer that may be used to store for
124    ///   trailing portion of the data that should be considered during
125    ///   decryption.
126    ///
127    /// - `receive`: An index or identifier associated with the
128    ///   receiver of the message. This may be used for deriving
129    ///   encryption keys.
130    ///
131    /// # Errors
132    ///
133    ///   `EncryptionError` if issues arise such as missing keys,
134    ///   incorrect buffer lengths, or any other problems during the
135    ///   encryption process.
136    fn encrypt(
137        &mut self,
138        associated_data: &[u8],
139        buffer: &mut [u8],
140        tail: &mut [u8],
141        receive: usize,
142    ) -> Result<(), EncryptionError>;
143
144    /// Decrypts the provided data buffer using associated data and a
145    /// tail segment.
146    ///
147    /// # Parameters
148    ///
149    /// - `associated_data`: A byte slice containing additional
150    ///   authenticated data (AAD) that will be used along with the
151    ///   buffer to ensure the integrity and authenticity of the
152    ///   decryption process.
153    ///
154    /// - `buffer`: A mutable byte slice holding the encrypted data
155    ///   that will be decrypted in place. Upon successful decryption,
156    ///   this buffer will contain the plaintext data.
157    ///
158    /// - `tail`: A byte slice representing the trailing portion of
159    ///   the data that should be considered during decryption. This is
160    ///   typically used nonce and/or authentication tag.
161    ///
162    /// - `sender`: An index or identifier representing the sender of
163    ///   the message. This might be used to retrieve or derive
164    ///   encryption keys.
165    ///
166    /// # Errors
167    ///
168    /// - `EncryptionError`: This function may return an
169    ///   `EncryptionError` in several situations such as when the
170    ///   decryption key is not found, when the input data is tampered
171    ///   with or if the cryptographic verification of the AAD fails.
172    fn decrypt(
173        &self,
174        associated_data: &[u8],
175        buffer: &mut [u8],
176        tail: &[u8],
177        sender: usize,
178    ) -> Result<(), EncryptionError>;
179
180    /// Return size of trailing segment. See method `encrypt()` and `decrypt()`.
181    fn overhead(&self) -> usize;
182}
183
184/// Counter to create a unique nonce for encryption operations.
185///
186/// This struct maintains a counter that is used to generate unique nonces
187/// for each encryption operation. The counter is incremented for each
188/// encryption to ensure nonce uniqueness.
189///
190/// # Security Considerations
191///
192/// - The counter is 32-bit and will panic on overflow
193/// - Nonces are derived from the counter value
194/// - Each encryption operation must use a unique nonce
195#[derive(Default)]
196pub struct NonceCounter(u32);
197
198impl NonceCounter {
199    /// Creates a new counter initialized to 0.
200    pub fn new() -> Self {
201        Self(0)
202    }
203
204    /// Increments the counter and returns a new nonce.
205    ///
206    /// # Type Parameters
207    ///
208    /// * `S`: The AEAD scheme type that determines the nonce size
209    ///
210    /// # Returns
211    ///
212    /// A new nonce derived from the incremented counter value
213    ///
214    /// # Panics
215    ///
216    /// Panics if the counter overflows (exceeds u32::MAX)
217    pub fn next_nonce<S: AeadCore>(&mut self) -> Nonce<S> {
218        self.0 = self.0.checked_add(1).expect("nonce overflow");
219
220        let mut nonce = Nonce::<S>::default();
221        nonce[..4].copy_from_slice(&self.0.to_le_bytes());
222
223        nonce
224    }
225}
226
227/// Implementation of EncryptionScheme using X25519 for key exchange
228/// and any AEAD scheme for encryption.
229///
230/// This struct combines:
231/// - X25519 for key exchange
232/// - A configurable AEAD scheme for encryption
233/// - Secure nonce generation
234/// - Key pair management for multiple parties
235///
236/// # Type Parameters
237///
238/// * `S`: The AEAD scheme to use for encryption (e.g., ChaCha20Poly1305)
239pub struct AeadX25519<S> {
240    secret: ReusableSecret,
241    public_key: PublicKey,
242    counter: NonceCounter,
243    pk: Pairs<(SharedKey, PublicKey), usize>,
244    marker: PhantomData<S>,
245}
246
247impl<S> AeadX25519<S> {
248    /// Generate a new [`AeadX25519`] with the supplied RNG.
249    ///
250    /// # Arguments
251    ///
252    /// * `rng`: A cryptographically secure random number generator
253    ///
254    /// # Returns
255    ///
256    /// A new `AeadX25519` instance with:
257    /// - A randomly generated X25519 key pair
258    /// - An initialized nonce counter
259    /// - An empty key pair store
260    pub fn new(rng: &mut impl CryptoRngCore) -> Self {
261        let secret = ReusableSecret::random_from_rng(rng);
262        let public_key = PublicKey::from(&secret);
263
264        Self {
265            secret,
266            public_key,
267            counter: NonceCounter::new(),
268            pk: Pairs::new(),
269            marker: PhantomData,
270        }
271    }
272
273    /// Create a new [`AeadX25519`] from a provided `ReusableSecret`.
274    ///
275    /// # Arguments
276    ///
277    /// * `secret`: A pre-existing X25519 secret key
278    ///
279    /// # Returns
280    ///
281    /// A new `AeadX25519` instance with:
282    /// - The provided secret key and its corresponding public key
283    /// - An initialized nonce counter
284    /// - An empty key pair store
285    pub fn from_secret(secret: ReusableSecret) -> Self {
286        let public_key = PublicKey::from(&secret);
287
288        Self {
289            secret,
290            public_key,
291            counter: NonceCounter::new(),
292            pk: Pairs::new(),
293            marker: PhantomData,
294        }
295    }
296}
297
298impl<S> EncryptionScheme for AeadX25519<S>
299where
300    S: AeadInPlace + KeyInit + Send,
301{
302    fn overhead(&self) -> usize {
303        S::TagSize::USIZE + S::NonceSize::USIZE
304    }
305
306    fn encrypt(
307        &mut self,
308        associated_data: &[u8],
309        buffer: &mut [u8],
310        tail: &mut [u8],
311        receive: usize,
312    ) -> Result<(), EncryptionError> {
313        if tail.len() != self.overhead() {
314            return Err(EncryptionError);
315        }
316
317        let (key, public_key) =
318            self.pk.find_pair_or_err(receive, EncryptionError)?;
319
320        let key = Zeroizing::new(
321            Sha256::new_with_prefix(public_key)
322                .chain_update(key)
323                .finalize(),
324        );
325
326        let key = Key::<S>::from_slice(key.as_slice());
327
328        let nonce = self.counter.next_nonce::<S>();
329        let tag = S::new(key)
330            .encrypt_in_place_detached(&nonce, associated_data, buffer)
331            .map_err(|_| EncryptionError)?;
332
333        tail[..S::TagSize::USIZE].copy_from_slice(&tag);
334        tail[S::TagSize::USIZE..].copy_from_slice(&nonce);
335
336        Ok(())
337    }
338
339    fn decrypt(
340        &self,
341        associated_data: &[u8],
342        buffer: &mut [u8],
343        tail: &[u8],
344        sender: usize,
345    ) -> Result<(), EncryptionError> {
346        if tail.len() != self.overhead() {
347            return Err(EncryptionError);
348        }
349
350        let (key, public_key) =
351            self.pk.find_pair_or_err(sender, EncryptionError)?;
352
353        let key = Zeroizing::new(
354            Sha256::new_with_prefix(self.public_key)
355                .chain_update(key)
356                .finalize(),
357        );
358
359        let key = Key::<S>::from_slice(key.as_slice());
360
361        let nonce = Nonce::<S>::from_slice(&tail[S::TagSize::USIZE..]);
362        let tag = Tag::<S>::from_slice(&tail[..S::TagSize::USIZE]);
363
364        S::new(key)
365            .decrypt_in_place_detached(nonce, associated_data, buffer, tag)
366            .map_err(|_| EncryptionError)?;
367
368        Ok(())
369    }
370
371    fn public_key(&self) -> &[u8] {
372        self.public_key.as_bytes()
373    }
374
375    fn receiver_public_key(
376        &mut self,
377        receiver_index: usize,
378        pk: &[u8],
379    ) -> Result<(), PublicKeyError> {
380        let pk: [u8; 32] = pk.try_into().map_err(|_| PublicKeyError)?;
381        let pk = PublicKey::from(pk);
382
383        let shared_secret = self.secret.diffie_hellman(&pk);
384
385        if !shared_secret.was_contributory() {
386            return Err(PublicKeyError);
387        }
388
389        let shared_key = Zeroizing::new(hchacha::<U10>(
390            GenericArray::from_slice(shared_secret.as_bytes()),
391            &GenericArray::default(),
392        ));
393
394        self.pk.push(receiver_index, (shared_key, pk));
395
396        Ok(())
397    }
398}