hopr_crypto_types/
primitives.rs

1use blake2::{Blake2s256, Blake2sMac256};
2use chacha20::cipher::KeyIvInit;
3use chacha20::cipher::{IvSizeUser, KeySizeUser, StreamCipher, StreamCipherSeek};
4use chacha20::ChaCha20;
5use digest::{FixedOutputReset, KeyInit, Output, OutputSizeUser, Update};
6use generic_array::GenericArray;
7use sha3::Keccak256;
8use typenum::Unsigned;
9use zeroize::ZeroizeOnDrop;
10
11use crate::utils::SecretValue;
12
13/// Represents a secret key of fixed length.
14/// The value is auto-zeroized on drop.
15pub type SecretKey = SecretValue<typenum::U32>;
16
17/// Generalization of digest-like operation (MAC, Digest,...)
18/// Defines the `update` and `finalize` operations to produce digest value of arbitrary data.
19pub trait DigestLike<T>
20where
21    T: Update + FixedOutputReset + OutputSizeUser,
22{
23    /// Length of the digest in bytes
24    const SIZE: usize = T::OutputSize::USIZE;
25
26    /// Access to the internal state of the digest-like operation.
27    fn internal_state(&mut self) -> &mut T;
28
29    /// Update the internal state of the digest-like using the given input data.
30    fn update(&mut self, data: &[u8]) {
31        self.internal_state().update(data);
32    }
33
34    /// Retrieve the final digest value into a prepared buffer and reset this instance so it could be reused for
35    /// a new computation.
36    fn finalize_into(&mut self, out: &mut [u8]) {
37        assert_eq!(Self::SIZE, out.len(), "invalid output size");
38        let output = Output::<T>::from_mut_slice(out);
39        self.internal_state().finalize_into_reset(output);
40    }
41
42    /// Retrieve the final digest value and reset this instance so it could be reused for
43    /// a new computation.
44    fn finalize(&mut self) -> GenericArray<u8, T::OutputSize> {
45        let mut output = Output::<T>::default();
46        self.finalize_into(&mut output);
47        output
48    }
49}
50
51/// Simple digest computation wrapper.
52/// Use `new`, `update` and `finalize` triplet to produce hash of arbitrary data.
53/// Currently this instance is using Blake2s256.
54#[derive(Default, Clone)]
55pub struct SimpleDigest(Blake2s256);
56
57impl DigestLike<Blake2s256> for SimpleDigest {
58    fn internal_state(&mut self) -> &mut Blake2s256 {
59        &mut self.0
60    }
61}
62
63/// Computation wrapper for a digest that's compatible with Ethereum digests.
64/// Use `new`, `update` and `finalize` triplet to produce hash of arbitrary data.
65/// Currently this instance is using Keccak256.
66#[derive(Default, Clone)]
67pub struct EthDigest(Keccak256);
68
69impl DigestLike<Keccak256> for EthDigest {
70    fn internal_state(&mut self) -> &mut Keccak256 {
71        &mut self.0
72    }
73}
74
75/// Simple Message Authentication Code (MAC) computation wrapper
76/// Use `new`, `update` and `finalize` triplet to produce MAC of arbitrary data.
77/// Currently instantiated using Blake2s256 MAC.
78pub struct SimpleMac(Blake2sMac256); // TODO: add derive(ZeroizeOnDrop) once blake2 is updated to 0.11
79
80impl SimpleMac {
81    /// Create new instance of the MAC using the given secret key.
82    pub fn new(key: &SecretKey) -> Self {
83        Self(Blake2sMac256::new(key.into()))
84    }
85}
86
87impl DigestLike<Blake2sMac256> for SimpleMac {
88    fn internal_state(&mut self) -> &mut Blake2sMac256 {
89        &mut self.0
90    }
91}
92
93/// Simple stream cipher wrapper
94/// Use `new` and `apply` (or `apply_copy`) to XOR the keystream on the plaintext or ciphertext.
95/// Currently this instance is using ChaCha20.
96#[derive(ZeroizeOnDrop)]
97pub struct SimpleStreamCipher(ChaCha20);
98
99impl SimpleStreamCipher {
100    /// Size of the secret key
101    pub const KEY_SIZE: usize = <ChaCha20 as KeySizeUser>::KeySize::USIZE;
102
103    /// Size of the initialization vector
104    pub const IV_SIZE: usize = <ChaCha20 as IvSizeUser>::IvSize::USIZE;
105
106    /// Create new instance of the stream cipher initialized
107    /// with the given secret key and IV.
108    pub fn new(key: [u8; Self::KEY_SIZE], iv: [u8; Self::IV_SIZE]) -> Self {
109        Self(ChaCha20::new(&key.into(), &iv.into()))
110    }
111
112    /// Seeks the keystream to the given block position
113    pub fn set_block_counter(&mut self, counter: u32) {
114        self.0.seek(counter as u64 * 64u64)
115    }
116
117    /// Apply keystream to the given data in-place.
118    pub fn apply(&mut self, data: &mut [u8]) {
119        self.0.apply_keystream(data);
120    }
121
122    /// Creates copy of the given data and applies the keystream to it.
123    pub fn apply_copy(&mut self, data: &[u8]) -> Box<[u8]> {
124        let mut ret = Vec::from(data);
125        self.0.apply_keystream(ret.as_mut_slice());
126        ret.into_boxed_slice()
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133    use hex_literal::hex;
134
135    #[test]
136    fn test_chacha20() {
137        let key = [0u8; 32];
138        let mut iv = [0u8; 12];
139        iv[11] = 2u8;
140
141        let mut cipher = SimpleStreamCipher::new(key, iv);
142
143        let mut data = [0u8; 64];
144        cipher.apply(&mut data);
145
146        let expected_ct = hex!("c2c64d378cd536374ae204b9ef933fcd1a8b2288b3dfa49672ab765b54ee27c78a970e0e955c14f3a88e741b97c286f75f8fc299e8148362fa198a39531bed6d");
147        assert_eq!(expected_ct, data);
148    }
149
150    #[test]
151    fn test_chacha20_iv_block_counter() {
152        let key = hex!("a9c6632c9f76e5e4dd03203196932350a47562f816cebb810c64287ff68586f3");
153        let iv = hex!("6be504b26471dea53d688c4b");
154
155        let mut cipher = SimpleStreamCipher::new(key, iv);
156
157        cipher.set_block_counter(0xa5999171u32.to_be());
158
159        let mut data = [0u8; 68];
160        cipher.apply(&mut data);
161
162        let expected_ct = hex!("abe088c198cb0a7b2591f1472fb1d0bd529a697a58a45d4ac5dc426ba6bf207deec4a5331149f93c6629d514ece8b0f49b4bc3eda74e07b78df5ac7d7f69fa75f611c926");
163        assert_eq!(expected_ct, data);
164    }
165
166    #[test]
167    fn test_mac() {
168        let data = [0u8; 60];
169
170        let mut mac = SimpleMac::new(&Default::default());
171        mac.update(&data);
172
173        let out = mac.finalize();
174        let expected_out = hex!("29a11c3c01d102143a020fb562410902ba39966ec81b706945b8ed6d83498bb7");
175        assert_eq!(expected_out, out.as_slice());
176    }
177}