1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
//! The `spatial_hasher` module provides the `Spha256` struct and related functionality for encryption and decryption using spatial parameters.
//!
//! ## Structs
//!
//! - [`Point3D`]: Represents a point in 3D space.
//! - [`RotationAxis`]: Represents a rotation axis in 3D space.
//! - [`Spha256`]: Core struct for encryption and decryption.
//!
//! ## Usage
//!
//! (Include usage examples specific to this module)
//!
//! [`Point3D`]: crate::spatial_hasher::Point3D
//! [`RotationAxis`]: crate::spatial_hasher::RotationAxis
//! [`Spha256`]: crate::spatial_hasher::Spha256

use crate::{Point3D, RotationAxis};
use chacha20poly1305::{
    aead::{Aead, KeyInit, OsRng},
    ChaCha20Poly1305, Nonce,
};
use rand::RngCore;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};

/// A hasher that uses a 3D point and a rotation axis to encrypt and decrypt data.
///
/// The `Spha256` struct provides methods for encrypting and decrypting data based on a deterministic algorithm. It derives a cryptographic key from its parameters and uses the ChaCha20-Poly1305 authenticated encryption algorithm for secure encryption and decryption.
///
/// # Examples
///
/// ```
/// use spatial_hasher::{Point3D, RotationAxis, Spha256};
/// let point = Point3D { x: 1.0, y: 2.0, z: 3.0 };
/// let axis = RotationAxis { x: 0.0, y: 1.0, z: 0.0 };
/// let hasher = Spha256::new(point, axis, 10, 0.1);
/// ```
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Spha256 {
    /// The starting point in 3D space.
    point: Point3D,
    /// The rotation axis used in the hashing process.
    rotation_axis: RotationAxis,
    /// The number of iterations for the transformation.
    iterations: u32,
    /// The strength of the transformation.
    strength: f64,
}

impl Spha256 {
    /// Creates a new `Spha256` instance with the specified parameters.
    ///
    /// # Arguments
    ///
    /// * `point` - A `Point3D` specifying the starting point in 3D space.
    /// * `rotation_axis` - A `RotationAxis` specifying the axis of rotation.
    /// * `iterations` - The number of iterations to perform in the hashing process.
    /// * `strength` - A floating-point value representing the strength of the transformation.
    ///
    /// # Returns
    ///
    /// A new `Spha256` instance configured with the provided parameters.
    ///
    /// # Examples
    ///
    /// ```
    /// use spatial_hasher::{Point3D, RotationAxis, Spha256};
    /// let point = Point3D { x: 1.0, y: 2.0, z: 3.0 };
    /// let axis = RotationAxis { x: 0.0, y: 1.0, z: 0.0 };
    /// let hasher = Spha256::new(point, axis, 10, 0.1);
    /// ```
    pub fn new(
        point: Point3D,
        rotation_axis: RotationAxis,
        iterations: u32,
        strength: f64,
    ) -> Self {
        Spha256 {
            point,
            rotation_axis,
            iterations,
            strength,
        }
    }

    /// Generates a 256-bit key by hashing the hasher's parameters.
    ///
    /// This function uses the SHA-256 hash function to create a key based on the bit representations of the `point`, `rotation_axis`, `iterations`, and `strength` fields. The key is used in the [`encrypt`](#method.encrypt) and [`decrypt`](#method.decrypt) methods with the ChaCha20-Poly1305 cipher.
    ///
    /// # Returns
    ///
    /// A 32-byte array representing the encryption key.
    fn generate_key(&self) -> [u8; 32] {
        let mut hasher = Sha256::new();
        hasher.update(&self.point.x.to_bits().to_ne_bytes());
        hasher.update(&self.point.y.to_bits().to_ne_bytes());
        hasher.update(&self.point.z.to_bits().to_ne_bytes());

        hasher.update(&self.rotation_axis.x.to_bits().to_ne_bytes());
        hasher.update(&self.rotation_axis.y.to_bits().to_ne_bytes());
        hasher.update(&self.rotation_axis.z.to_bits().to_ne_bytes());

        hasher.update(&self.iterations.to_ne_bytes());
        hasher.update(&self.strength.to_bits().to_ne_bytes());

        let result = hasher.finalize();
        let mut key = [0u8; 32];
        key.copy_from_slice(&result[..32]);
        key
    }

    /// Encrypts the provided data using the ChaCha20-Poly1305 authenticated encryption algorithm.
    ///
    /// This method encrypts the input data using the ChaCha20-Poly1305 cipher, with a key derived from the hasher's parameters via the [`generate_key`](#method.generate_key) method. A random nonce is generated for each encryption operation to ensure uniqueness and security. The nonce is prepended to the ciphertext for use during decryption.
    ///
    /// # Arguments
    ///
    /// * `data` - A slice of bytes representing the data to encrypt.
    ///
    /// # Returns
    ///
    /// A `Vec<u8>` containing the encrypted data, with the nonce prepended.
    ///
    /// # Examples
    ///
    /// ```
    /// use spatial_hasher::{Spha256, Point3D, RotationAxis};
    /// let point = Point3D { x: 1.0, y: 2.0, z: 3.0 };
    /// let axis = RotationAxis { x: 0.0, y: 1.0, z: 0.0 };
    /// let hasher = Spha256::new(point, axis, 10, 0.1);
    ///
    /// let data = b"Secret Message";
    /// let encrypted = hasher.encrypt(data);
    /// ```
    pub fn encrypt(&self, data: &[u8]) -> Vec<u8> {
        // Derive key from parameters
        let key = self.generate_key();
        let cipher = ChaCha20Poly1305::new(&key.into());

        // Generate a random nonce (12 bytes for ChaCha20-Poly1305)
        let mut nonce_bytes = [0u8; 12];
        OsRng.fill_bytes(&mut nonce_bytes);
        let nonce = Nonce::from_slice(&nonce_bytes);

        // Encrypt the data
        let ciphertext = cipher.encrypt(nonce, data).expect("Encryption failed");

        // Prepend nonce to ciphertext
        let mut encrypted = Vec::with_capacity(nonce_bytes.len() + ciphertext.len());
        encrypted.extend_from_slice(&nonce_bytes);
        encrypted.extend_from_slice(&ciphertext);

        encrypted
    }

    /// Decrypts the provided data using the ChaCha20-Poly1305 authenticated decryption algorithm.
    ///
    /// This method decrypts the input data using the ChaCha20-Poly1305 cipher, with a key derived from the hasher's parameters via the [`generate_key`](#method.generate_key) method. The nonce used during encryption is expected to be prepended to the encrypted data and is extracted during decryption.
    ///
    /// # Arguments
    ///
    /// * `encrypted` - A slice of bytes representing the encrypted data, with the nonce prepended.
    ///
    /// # Returns
    ///
    /// A `Result<Vec<u8>, &'static str>` containing the decrypted data on success, or an error message on failure.
    ///
    /// # Errors
    ///
    /// Returns an error if the decryption fails, such as when the ciphertext has been tampered with or the parameters do not match those used during encryption.
    ///
    /// # Examples
    ///
    /// ```
    /// use spatial_hasher::{Spha256, Point3D, RotationAxis};
    /// let point = Point3D { x: 1.0, y: 2.0, z: 3.0 };
    /// let axis = RotationAxis { x: 0.0, y: 1.0, z: 0.0 };
    /// let hasher = Spha256::new(point, axis, 10, 0.1);
    ///
    /// let encrypted = hasher.encrypt(b"Secret Message");
    /// let decrypted = hasher.decrypt(&encrypted).expect("Decryption failed");
    /// assert_eq!(decrypted, b"Secret Message");
    /// ```
    pub fn decrypt(&self, encrypted: &[u8]) -> Result<Vec<u8>, &'static str> {
        if encrypted.len() < 12 {
            return Err("Ciphertext too short to contain nonce");
        }

        let (nonce_bytes, ciphertext) = encrypted.split_at(12);
        let nonce = Nonce::from_slice(nonce_bytes);

        // Derive key from parameters
        let key = self.generate_key();
        let cipher = ChaCha20Poly1305::new(&key.into());

        // Decrypt the data
        let plaintext = cipher
            .decrypt(nonce, ciphertext)
            .map_err(|_| "Decryption failed")?;

        Ok(plaintext)
    }
}