Skip to main content

oasis_core_runtime/common/crypto/mrae/
deoxysii.rs

1//! Deoxys-II-256-128 MRAE primitives implementation.
2use anyhow::Result;
3use hmac::{Hmac, KeyInit, Mac};
4use rand::{rand_core::UnwrapErr, rngs::SysRng};
5use sha2::Sha512_256;
6use x25519_dalek::{PublicKey, StaticSecret};
7
8pub use deoxysii::{DeoxysII, KEY_SIZE, NONCE_SIZE, TAG_SIZE};
9
10type Kdf = Hmac<Sha512_256>;
11
12/// An abstract Deoxys-II-256-128 box opener.
13pub trait Opener: Send + Sync {
14    /// Unboxes ("opens") the provided additional data and ciphertext.
15    fn box_open(
16        &self,
17        nonce: &[u8; NONCE_SIZE],
18        ciphertext: Vec<u8>,
19        additional_data: Vec<u8>,
20        peers_public_key: &PublicKey,
21    ) -> Result<Vec<u8>>;
22}
23
24/// Derives a MRAE AEAD symmetric key suitable for use with the asymmetric
25/// box primitives from the provided X25519 public and private keys.
26fn derive_symmetric_key(public: &PublicKey, private: &StaticSecret) -> [u8; KEY_SIZE] {
27    let pmk = private.diffie_hellman(public);
28
29    let mut kdf = Kdf::new_from_slice(b"MRAE_Box_Deoxys-II-256-128").expect("Hmac::new_from_slice");
30    kdf.update(pmk.as_bytes());
31    drop(pmk);
32
33    let mut derived_key = [0u8; KEY_SIZE];
34    let digest = kdf.finalize();
35    derived_key.copy_from_slice(&digest.into_bytes()[..KEY_SIZE]);
36
37    derived_key
38}
39
40/// Generates a public/private key pair suitable for use with
41/// `derive_symmetric_key`, `box_seal`, and `box_open`.
42pub fn generate_key_pair() -> (PublicKey, StaticSecret) {
43    let mut rng = UnwrapErr(SysRng);
44    let sk = StaticSecret::random_from_rng(&mut rng);
45    let pk = PublicKey::from(&sk);
46
47    (pk, sk)
48}
49
50/// Boxes ("seals") the provided additional data and plaintext via
51/// Deoxys-II-256-128 using a symmetric key derived from the provided
52/// X25519 public and private keys.
53/// The nonce should be `NONCE_SIZE` bytes long and unique for all time
54/// for a given public and private key tuple.
55pub fn box_seal(
56    nonce: &[u8; NONCE_SIZE],
57    plaintext: Vec<u8>,
58    additional_data: Vec<u8>,
59    peers_public_key: &PublicKey,
60    private_key: &StaticSecret,
61) -> Result<Vec<u8>> {
62    let key = derive_symmetric_key(peers_public_key, private_key);
63
64    let d2 = DeoxysII::new(&key);
65
66    Ok(d2.seal(nonce, plaintext, additional_data))
67}
68
69/// Unboxes ("opens") the provided additional data and ciphertext via
70/// Deoxys-II-256-128 using a symmetric key derived from the provided
71/// X25519 public and private keys.
72/// The nonce should be `NONCE_SIZE` bytes long and both it and the additional
73/// data must match the value passed to `box_seal`.
74pub fn box_open(
75    nonce: &[u8; NONCE_SIZE],
76    ciphertext: Vec<u8>,
77    additional_data: Vec<u8>,
78    peers_public_key: &PublicKey,
79    private_key: &StaticSecret,
80) -> Result<Vec<u8>> {
81    let key = derive_symmetric_key(peers_public_key, private_key);
82
83    let d2 = DeoxysII::new(&key);
84
85    d2.open(nonce, ciphertext, additional_data)
86        .map_err(|err| err.into())
87}
88
89#[cfg(test)]
90mod tests {
91    extern crate test;
92
93    use self::test::{black_box, Bencher};
94    use super::*;
95    use rand::Rng;
96
97    #[test]
98    fn test_mrae_asymmetric() {
99        let (a_pub, a_priv) = generate_key_pair(); // Alice
100        let (b_pub, b_priv) = generate_key_pair(); // Bob
101
102        // None of the generated keys should be the same.
103        assert_ne!(a_pub.to_bytes(), b_pub.to_bytes());
104        assert_ne!(a_priv.to_bytes(), b_priv.to_bytes());
105        assert_ne!(a_pub.to_bytes(), a_priv.to_bytes());
106        assert_ne!(b_pub.to_bytes(), b_priv.to_bytes());
107
108        // Should successfully seal the text in a box.
109        let nonce = [1u8; NONCE_SIZE];
110        let text = String::from("This is a test!").as_bytes().to_vec();
111        let aad = vec![42u8; 10];
112
113        let sealed = box_seal(&nonce, text.clone(), aad.clone(), &b_pub, &a_priv);
114        assert!(sealed.is_ok());
115
116        // Should successfully open the sealed box.
117        let opened = box_open(&nonce, sealed.unwrap(), aad, &a_pub, &b_priv);
118        assert!(opened.is_ok());
119
120        // The deciphered text should match the original.
121        let deciphered = opened.unwrap();
122        assert_eq!(deciphered, text);
123    }
124
125    #[bench]
126    fn bench_mrae_box_seal_4096(b: &mut Bencher) {
127        let mut rng = rand::rng();
128
129        // Set up the keys.
130        let (_a_pub, a_priv) = generate_key_pair(); // Alice
131        let (b_pub, _b_priv) = generate_key_pair(); // Bob
132
133        // Set up the payload.
134        let mut nonce = [0u8; NONCE_SIZE];
135        rng.fill_bytes(&mut nonce);
136        let mut text = [0u8; 4096];
137        rng.fill_bytes(&mut text);
138        let mut aad = [0u8; 64];
139        rng.fill_bytes(&mut aad);
140
141        // Benchmark box sealing.
142        b.iter(|| {
143            let _sealed = black_box(box_seal(
144                &nonce,
145                text.to_vec(),
146                aad.to_vec(),
147                &b_pub,
148                &a_priv,
149            ));
150        });
151    }
152
153    #[bench]
154    fn bench_mrae_box_open_4096(b: &mut Bencher) {
155        let mut rng = rand::rng();
156
157        // Set up the keys.
158        let (a_pub, a_priv) = generate_key_pair(); // Alice
159        let (b_pub, b_priv) = generate_key_pair(); // Bob
160
161        // Set up the payload.
162        let mut nonce = [0u8; NONCE_SIZE];
163        rng.fill_bytes(&mut nonce);
164        let mut text = [0u8; 4096];
165        rng.fill_bytes(&mut text);
166        let mut aad = [0u8; 64];
167        rng.fill_bytes(&mut aad);
168
169        // Seal the payload.
170        let sealed = box_seal(&nonce, text.to_vec(), aad.to_vec(), &b_pub, &a_priv);
171        let ciphertext = sealed.unwrap();
172
173        // Benchmark box opening.
174        b.iter(|| {
175            let _opened = black_box(box_open(
176                &nonce,
177                ciphertext.clone(),
178                aad.to_vec(),
179                &a_pub,
180                &b_priv,
181            ));
182        });
183    }
184}