ordinal_crypto/
lib.rs

1/*
2Copyright (c) 2024 sean watters
3
4Licensed under the MIT license <LICENSE or https://opensource.org/licenses/MIT>.
5This file may not be copied, modified, or distributed except according to those terms.
6*/
7
8#![doc = include_str!("../README.md")]
9
10#[cfg(feature = "multi-thread")]
11use rayon::prelude::*;
12#[cfg(feature = "multi-thread")]
13use std::sync::mpsc::channel;
14
15use blake2::digest::{FixedOutput, Mac};
16use chacha20::{
17    cipher::{generic_array::GenericArray, typenum, StreamCipher},
18    XChaCha20,
19};
20use chacha20poly1305::{aead::Aead, AeadCore, XChaCha20Poly1305};
21use ed25519_dalek::{Signer, Verifier};
22use x25519_dalek::{PublicKey, StaticSecret};
23
24const NONCE_LEN: usize = 24;
25const KEY_LEN: usize = 32;
26const SIGNATURE_LEN: usize = 64;
27
28/// generates fingerprints and verifying keys for signing.
29///
30/// ```rust
31/// use ordinal_crypto::generate_fingerprint;
32///
33/// let (fingerprint, verifier) = generate_fingerprint();
34///
35/// assert_eq!(fingerprint.len(), 32);
36/// assert_eq!(verifier.len(), 32);
37/// ```
38pub fn generate_fingerprint() -> ([u8; 32], [u8; 32]) {
39    let fingerprint = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng);
40
41    (
42        fingerprint.to_bytes(),
43        fingerprint.verifying_key().to_bytes(),
44    )
45}
46
47/// signs content.
48#[inline]
49fn sign(fingerprint: &[u8; 32], content: &[u8]) -> [u8; 64] {
50    let fingerprint = ed25519_dalek::SigningKey::from_bytes(fingerprint);
51    let signature = fingerprint.sign(content);
52
53    signature.to_bytes()
54}
55
56/// verifies signatures.
57#[inline]
58fn verify(signature: &[u8; 64], verifier: &[u8; 32], content: &[u8]) -> Result<(), &'static str> {
59    let signature = ed25519_dalek::Signature::from_bytes(signature);
60    let verifier = match ed25519_dalek::VerifyingKey::from_bytes(verifier) {
61        Ok(vk) => vk,
62        Err(_) => return Err("failed to convert verifying key"),
63    };
64
65    match verifier.verify(content, &signature) {
66        Ok(_) => Ok(()),
67        Err(_) => return Err("failed to verify signature"),
68    }
69}
70
71/// generates `Dh` pub/priv key pairs.
72///
73/// ```rust
74/// use ordinal_crypto::generate_dh_keys;
75///
76/// let (priv_key, pub_key) = generate_dh_keys();
77///
78/// assert_eq!(priv_key.len(), 32);
79/// assert_eq!(pub_key.len(), 32);
80/// ```
81pub fn generate_dh_keys() -> ([u8; 32], [u8; 32]) {
82    let priv_key = x25519_dalek::StaticSecret::random_from_rng(rand_core::OsRng);
83    let pub_key = x25519_dalek::PublicKey::from(&priv_key);
84
85    (*priv_key.as_bytes(), *pub_key.as_bytes())
86}
87
88#[inline(always)]
89fn usize_to_bytes(num: usize) -> Vec<u8> {
90    match num {
91        0..=63 => vec![(0 << 6) | num as u8],
92        64..=318 => vec![((0 << 6) | 63), (num - 63) as u8],
93        319..=65_598 => {
94            let mut h = vec![(1 << 6) | 63];
95            h.extend_from_slice(&((num - 63) as u16).to_be_bytes());
96            h
97        }
98        65_599..=4_294_967_358 => {
99            let mut h = vec![(2 << 6) | 63];
100            h.extend_from_slice(&((num - 63) as u32).to_be_bytes());
101            h
102        }
103        _ => {
104            let mut h = vec![(3 << 6) | 63];
105            h.extend_from_slice(&((num - 63) as u64).to_be_bytes());
106            h
107        }
108    }
109}
110
111#[inline(always)]
112fn bytes_to_usize(bytes: &[u8]) -> (usize, usize) {
113    let num_size = bytes[0];
114
115    if num_size < 64 {
116        (1, num_size as usize)
117    } else {
118        match (num_size >> 6) & 0b11 {
119            0 => (2, bytes[1] as usize + 63),
120            1 => (
121                3,
122                u16::from_be_bytes(bytes[1..3].try_into().unwrap()) as usize + 63,
123            ),
124            2 => (
125                5,
126                u32::from_be_bytes(bytes[1..5].try_into().unwrap()) as usize + 63,
127            ),
128            3 => (
129                9,
130                u64::from_be_bytes(bytes[1..9].try_into().unwrap()) as usize + 63,
131            ),
132            _ => unreachable!(),
133        }
134    }
135}
136
137#[inline]
138fn encrypt_content(
139    fingerprint: [u8; 32],
140    nonce: &GenericArray<u8, typenum::U24>,
141    key: &GenericArray<u8, typenum::U32>,
142    content: &mut Vec<u8>,
143) -> Result<Vec<u8>, &'static str> {
144    use chacha20poly1305::KeyInit;
145
146    let signature = sign(&fingerprint, &content);
147    content.extend(signature);
148
149    let content_cipher = XChaCha20Poly1305::new(key);
150    match content_cipher.encrypt(nonce, content.as_ref()) {
151        Ok(encrypted_content) => Ok(encrypted_content),
152        Err(_) => Err("failed to encrypt content"),
153    }
154}
155
156#[inline]
157fn dh_encrypt_keys(
158    priv_key: [u8; KEY_LEN],
159    pub_keys: &Vec<[u8; KEY_LEN]>,
160    nonce: &GenericArray<u8, typenum::U24>,
161    content_key: &GenericArray<u8, typenum::U32>,
162) -> (Vec<u8>, Vec<u8>) {
163    use chacha20::cipher::KeyIvInit;
164
165    let keys_count = pub_keys.len();
166    let header = usize_to_bytes(keys_count);
167
168    let priv_key = StaticSecret::from(priv_key);
169
170    let mut keys = vec![0u8; KEY_LEN * keys_count];
171
172    #[cfg(feature = "multi-thread")]
173    let chunks = keys.par_chunks_mut(KEY_LEN);
174    #[cfg(not(feature = "multi-thread"))]
175    let chunks = keys.chunks_mut(KEY_LEN);
176
177    chunks.enumerate().for_each(|(i, chunk)| {
178        let shared_secret = priv_key
179            .diffie_hellman(&PublicKey::from(pub_keys[i]))
180            .to_bytes();
181
182        let mut key_cipher = XChaCha20::new(&shared_secret.into(), nonce);
183
184        let mut buf = content_key.clone();
185
186        key_cipher.apply_keystream(&mut buf);
187        chunk[0..KEY_LEN].copy_from_slice(&buf);
188    });
189
190    (header, keys)
191}
192
193/// encapsulates the parameters and mode for encryption.
194pub enum Encrypt<'a> {
195    /// generates random content key and encrypts for all
196    /// recipients with their respective DH shared secret.
197    Dh([u8; KEY_LEN], &'a Vec<[u8; KEY_LEN]>),
198
199    /// hashes the second tuple member, with the first
200    /// tuple member as the hash key.
201    Hmac([u8; KEY_LEN], [u8; KEY_LEN], usize),
202
203    /// uses the key that is passed in without modification.
204    Session([u8; KEY_LEN]),
205}
206
207/// signs and encrypts content.
208///
209/// ```rust
210/// # use ordinal_crypto::{decrypt, extract_components_mut, Components, Decrypt, generate_dh_keys, generate_fingerprint};
211/// # let (sender_priv_key, sender_pub_key) = generate_dh_keys();
212/// # let (receiver_priv_key, receiver_pub_key) = generate_dh_keys();
213/// # let (receiver_priv_key, receiver_pub_key) = generate_dh_keys();
214/// # let (hmac_key, hmac_value) = generate_dh_keys();
215/// # let (session_key, _) = generate_dh_keys();
216/// # let itr = 0;
217/// # let (fingerprint, verifier) = generate_fingerprint();
218/// #
219/// use ordinal_crypto::{encrypt, Encrypt};
220///
221/// let content = vec![0u8; 1024];
222/// # let content_clone = content.clone();
223/// # let recipient_pub_keys = vec![receiver_pub_key];
224///
225/// // Dh
226/// let (mut encrypted_content, content_key) = encrypt(
227///     fingerprint,
228///     content,
229///     Encrypt::Dh(sender_priv_key, &recipient_pub_keys)
230/// ).unwrap();
231/// # if let Components::Dh(content_key) = extract_components_mut(0, &mut encrypted_content) {
232/// #     let (decrypted_content, _) = decrypt(
233/// #         Some(&verifier),
234/// #         &encrypted_content,
235/// #         Decrypt::Dh(content_key, sender_pub_key, receiver_priv_key),
236/// #     )
237/// #     .unwrap();
238/// #
239/// #     assert_eq!(decrypted_content, content_clone);
240/// # };
241///
242/// // Hmac
243/// # let content = content_clone.clone();
244/// let (mut encrypted_content, content_key) = encrypt(
245///     fingerprint,
246///     content,
247///     Encrypt::Hmac(hmac_key, hmac_value, itr)
248/// ).unwrap();
249/// # if let Components::Hmac(iteration) = extract_components_mut(0, &mut encrypted_content) {
250/// #     assert_eq!(iteration, itr);
251/// #
252/// #     let (decrypted_content, _) = decrypt(
253/// #         Some(&verifier),
254/// #         &encrypted_content,
255/// #         Decrypt::Hmac(hmac_key, hmac_value),
256/// #     )
257/// #     .unwrap();
258/// #
259/// #     assert_eq!(decrypted_content, content_clone);
260/// # };
261///
262/// // Session
263/// # let content = content_clone.clone();
264/// let (mut encrypted_content, content_key) = encrypt(
265///     fingerprint,
266///     content,
267///     Encrypt::Session(session_key)
268/// ).unwrap();
269/// # if let Components::Session = extract_components_mut(0, &mut encrypted_content) {
270/// #     let (decrypted_content, _) = decrypt(
271/// #         Some(&verifier),
272/// #         &encrypted_content,
273/// #         Decrypt::Session(session_key),
274/// #     )
275/// #     .unwrap();
276/// #
277/// #     assert_eq!(decrypted_content, content_clone);
278/// # };
279/// ```
280pub fn encrypt(
281    fingerprint: [u8; 32],
282    mut content: Vec<u8>,
283    mode: Encrypt,
284) -> Result<(Vec<u8>, [u8; KEY_LEN]), &'static str> {
285    let nonce = XChaCha20Poly1305::generate_nonce(&mut rand_core::OsRng);
286    let mut out = nonce.to_vec();
287
288    match mode {
289        Encrypt::Session(key) => {
290            let encrypted_content =
291                encrypt_content(fingerprint, &nonce, &key.into(), &mut content)?;
292            out.extend(encrypted_content);
293
294            out.push(0);
295
296            Ok((out, key))
297        }
298        Encrypt::Hmac(hash_key, key, itr) => {
299            let key = blake2::Blake2sMac256::new_from_slice(&hash_key)
300                .unwrap()
301                .chain_update(&key)
302                .finalize_fixed();
303
304            let itr_as_bytes = usize_to_bytes(itr);
305            out.extend(itr_as_bytes);
306
307            let encrypted_content = encrypt_content(fingerprint, &nonce, &key, &mut content)?;
308            out.extend(encrypted_content);
309
310            out.push(1);
311
312            Ok((out, key.into()))
313        }
314        Encrypt::Dh(priv_key, pub_keys) => {
315            use chacha20poly1305::KeyInit;
316
317            let key = XChaCha20Poly1305::generate_key(&mut rand_core::OsRng);
318
319            #[cfg(feature = "multi-thread")]
320            let (sender, receiver) = channel();
321
322            #[cfg(feature = "multi-thread")]
323            rayon::spawn(move || {
324                let encrypted_content = encrypt_content(fingerprint, &nonce, &key, &mut content);
325                sender.send(encrypted_content).unwrap();
326            });
327
328            let (header, keys) = dh_encrypt_keys(priv_key, pub_keys, &nonce, &key);
329            out.extend(header);
330            out.extend(keys);
331
332            #[cfg(feature = "multi-thread")]
333            let encrypted_content = receiver.recv().unwrap()?;
334            #[cfg(not(feature = "multi-thread"))]
335            let encrypted_content = encrypt_content(fingerprint, &nonce, &key, &mut content)?;
336
337            out.extend(encrypted_content);
338
339            out.push(2);
340
341            Ok((out, key.into()))
342        }
343    }
344}
345
346/// facilitates mode-specific decryption component extraction.
347pub enum Components {
348    Session,
349    Hmac(usize),
350    Dh([u8; KEY_LEN]),
351}
352
353/// extract components from encrypted result.
354///
355/// ```rust
356/// # use ordinal_crypto::{encrypt, generate_dh_keys, generate_fingerprint, Encrypt};
357/// # let (fingerprint, verifier) = generate_fingerprint();
358/// # let (session_key, _) = generate_dh_keys();
359/// # let content = vec![0u8; 1024];
360/// # let (encrypted_content, _) = encrypt(fingerprint, content.clone(), Encrypt::Session(session_key)).unwrap();
361/// #
362/// use ordinal_crypto::{extract_components, Components};
363///
364/// let (components, encrypted_content) = extract_components(0, encrypted_content);
365///
366/// match components {
367///     Components::Session => { /* decrypt for session */ }
368///     Components::Hmac(itr) => { /* decrypt for HMAC */ }
369///     Components::Dh(key) => { /* decrypt for diffie-hellman */ }
370/// };
371/// ```
372#[inline(always)]
373pub fn extract_components(
374    position: usize,
375    mut encrypted_content: Vec<u8>,
376) -> (Components, Vec<u8>) {
377    let mode_meta = extract_components_mut(position, &mut encrypted_content);
378
379    (mode_meta, encrypted_content)
380}
381
382/// extract components from encrypted result, mutating the content passed in.
383///
384/// ```rust
385/// # use ordinal_crypto::{encrypt, generate_dh_keys, generate_fingerprint, Encrypt};
386/// # let (fingerprint, verifier) = generate_fingerprint();
387/// # let (session_key, _) = generate_dh_keys();
388/// # let content = vec![0u8; 1024];
389/// # let (mut encrypted_content, _) = encrypt(fingerprint, content.clone(), Encrypt::Session(session_key)).unwrap();
390/// #
391/// use ordinal_crypto::{extract_components_mut, Components};
392///
393/// match extract_components_mut(0, &mut encrypted_content) {
394///     Components::Session => { /* decrypt for session */ }
395///     Components::Hmac(itr) => { /* decrypt for HMAC */ }
396///     Components::Dh(key) => { /* decrypt for diffie-hellman */ }
397/// };
398/// ```
399pub fn extract_components_mut(position: usize, encrypted_content: &mut Vec<u8>) -> Components {
400    let mode = encrypted_content.pop().expect("at least one element");
401
402    match mode {
403        2 => {
404            let (keys_count_size, keys_count) =
405                bytes_to_usize(&encrypted_content[NONCE_LEN..NONCE_LEN + 9]);
406
407            let keys_start = NONCE_LEN + keys_count_size;
408            let encrypted_key_start = keys_start + (position as usize * KEY_LEN);
409
410            let encrypted_content_start = keys_start + (keys_count * KEY_LEN);
411
412            let content_key: [u8; KEY_LEN] = encrypted_content
413                [encrypted_key_start..encrypted_key_start + KEY_LEN]
414                .try_into()
415                .unwrap();
416
417            encrypted_content.copy_within(encrypted_content_start.., NONCE_LEN);
418            encrypted_content
419                .truncate(encrypted_content.len() - keys_count_size - (keys_count * KEY_LEN));
420
421            Components::Dh(content_key)
422        }
423        1 => {
424            let (itr_size, itr) = bytes_to_usize(&encrypted_content[NONCE_LEN..NONCE_LEN + 9]);
425
426            encrypted_content.copy_within(NONCE_LEN + itr_size.., NONCE_LEN);
427            encrypted_content.truncate(encrypted_content.len() - itr_size);
428
429            Components::Hmac(itr)
430        }
431        _ => Components::Session,
432    }
433}
434
435/// encapsulates the parameters and mode for decryption.
436pub enum Decrypt {
437    /// encrypted content key, sender pub key, receiver priv key.
438    Dh([u8; KEY_LEN], [u8; KEY_LEN], [u8; KEY_LEN]),
439
440    /// hashes the second tuple member, with the first
441    /// tuple member as the hash key.
442    Hmac([u8; KEY_LEN], [u8; KEY_LEN]),
443
444    /// uses the key that is passed in without modification.
445    Session([u8; KEY_LEN]),
446}
447
448/// decrypts and verifies content.
449///
450/// ```rust
451/// # use ordinal_crypto::{encrypt, generate_dh_keys, generate_fingerprint, Encrypt};
452/// # let (sender_priv_key, sender_pub_key) = generate_dh_keys();
453/// # let (receiver_priv_key, receiver_pub_key) = generate_dh_keys();
454/// # let (hmac_key, hmac_value) = generate_dh_keys();
455/// # let (session_key, _) = generate_dh_keys();
456/// # let (fingerprint, verifier) = generate_fingerprint();
457/// # let content = vec![0u8; 1024];
458/// # let pub_keys = vec![receiver_pub_key];
459/// # let (mut encrypted_content, _) = encrypt(fingerprint, content.clone(), Encrypt::Dh(sender_priv_key, &pub_keys)).unwrap();
460/// #
461/// use ordinal_crypto::{decrypt, extract_components_mut, Components, Decrypt};
462///
463/// match extract_components_mut(0, &mut encrypted_content) {
464///     Components::Dh(key) => {
465///         let (decrypted_content, _) = decrypt(
466///             Some(&verifier),
467///             &encrypted_content,
468///             Decrypt::Dh(key, sender_pub_key, receiver_priv_key),
469///         )
470///         .unwrap();
471/// #       assert_eq!(decrypted_content, content);
472///     }
473///     Components::Hmac(itr) => {
474///         let (decrypted_content, _) = decrypt(
475///             Some(&verifier),
476///             &encrypted_content,
477///             Decrypt::Hmac(hmac_key, hmac_value),
478///         )
479///         .unwrap();
480/// #       assert_eq!(decrypted_content, content);
481///     }
482///     Components::Session => {
483///         let (decrypted_content, _) = decrypt(
484///             Some(&verifier),
485///             &encrypted_content,
486///             Decrypt::Session(session_key),
487///         )
488///         .unwrap();
489/// #       assert_eq!(decrypted_content, content);
490///     }
491/// };
492/// ```
493pub fn decrypt(
494    verifier: Option<&[u8; 32]>,
495    encrypted_content: &[u8],
496    mode: Decrypt,
497) -> Result<(Vec<u8>, [u8; KEY_LEN]), &'static str> {
498    let nonce = &GenericArray::<u8, typenum::U24>::from_slice(&encrypted_content[0..NONCE_LEN]);
499
500    let (content_key, encrypted_content): (GenericArray<u8, typenum::U32>, &[u8]) = match mode {
501        Decrypt::Session(key) => (key.into(), &encrypted_content[NONCE_LEN..]),
502        Decrypt::Hmac(hash_key, key) => {
503            let key = blake2::Blake2sMac256::new_from_slice(&hash_key)
504                .unwrap()
505                .chain_update(&key)
506                .finalize_fixed();
507
508            (key, &encrypted_content[NONCE_LEN..])
509        }
510        Decrypt::Dh(mut content_key, pub_key, priv_key) => {
511            let priv_key = StaticSecret::from(priv_key);
512            let shared_secret = priv_key.diffie_hellman(&pub_key.into()).to_bytes();
513
514            let mut key_cipher = {
515                use chacha20::cipher::KeyIvInit;
516                XChaCha20::new(&shared_secret.into(), nonce)
517            };
518
519            key_cipher.apply_keystream(&mut content_key);
520
521            let content_key = GenericArray::from(content_key);
522
523            (content_key, &encrypted_content[NONCE_LEN..])
524        }
525    };
526
527    let content_cipher = {
528        use chacha20poly1305::KeyInit;
529        XChaCha20Poly1305::new(&content_key)
530    };
531
532    match content_cipher.decrypt(nonce, encrypted_content) {
533        Ok(mut content) => {
534            let signature = content.split_off(content.len() - SIGNATURE_LEN);
535
536            match verifier {
537                Some(verifier) => {
538                    let signature_as_bytes: [u8; SIGNATURE_LEN] = match signature.try_into() {
539                        Ok(v) => v,
540                        Err(_) => return Err("failed to convert signature to bytes"),
541                    };
542
543                    verify(&signature_as_bytes, verifier, &content)?;
544                    Ok((content, content_key.into()))
545                }
546                None => Ok((content, content_key.into())),
547            }
548        }
549        Err(_) => return Err("failed to decrypt content"),
550    }
551}