use std::mem::size_of;
use chacha20poly1305::{
XChaCha20Poly1305,
XNonce,
aead::{Aead, Payload},
};
use rand::Rng;
use tari_utilities::{ByteArray, Hidden};
pub trait Encryptable<C> {
const KEY_MANAGER: &'static [u8] = b"KEY_MANAGER";
const OUTPUT: &'static [u8] = b"OUTPUT";
const WALLET_SETTING_MASTER_SEED: &'static [u8] = b"MASTER_SEED";
const WALLET_SETTING_TOR_ID: &'static [u8] = b"TOR_ID";
const INBOUND_TRANSACTION: &'static [u8] = b"INBOUND_TRANSACTION";
const OUTBOUND_TRANSACTION: &'static [u8] = b"OUTBOUND_TRANSACTION";
const COMPLETED_TRANSACTION: &'static [u8] = b"COMPLETED_TRANSACTION";
const KNOWN_ONESIDED_PAYMENT_SCRIPT: &'static [u8] = b"KNOWN_ONESIDED_PAYMENT_SCRIPT";
const CLIENT_KEY_VALUE: &'static [u8] = b"CLIENT_KEY_VALUE";
const BURNT_PROOF: &'static [u8] = b"BURNT_PROOF";
fn domain(&self, field_name: &'static str) -> Vec<u8>;
fn encrypt(self, cipher: &C) -> Result<Self, String>
where Self: Sized;
fn decrypt(self, cipher: &C) -> Result<Self, String>
where Self: Sized;
}
pub fn decrypt_bytes_integral_nonce(
cipher: &XChaCha20Poly1305,
domain: Vec<u8>,
ciphertext: &[u8],
) -> Result<Vec<u8>, String> {
let (nonce, ciphertext) = ciphertext
.split_at_checked(size_of::<XNonce>())
.ok_or("Ciphertext is too short".to_string())?;
let nonce_ga = XNonce::from_slice(nonce);
let payload = Payload {
msg: ciphertext,
aad: domain.as_bytes(),
};
let plaintext = cipher
.decrypt(nonce_ga, payload)
.map_err(|e| format!("Decryption failed: {e}"))?;
Ok(plaintext)
}
pub fn encrypt_bytes_integral_nonce(
cipher: &XChaCha20Poly1305,
domain: Vec<u8>,
plaintext: Hidden<Vec<u8>>,
) -> Result<Vec<u8>, String> {
let mut nonce = [0u8; size_of::<XNonce>()];
rand::rng().fill_bytes(&mut nonce);
let nonce_ga = XNonce::from_slice(&nonce);
let payload = Payload {
msg: plaintext.reveal(),
aad: domain.as_slice(),
};
let mut ciphertext = cipher
.encrypt(nonce_ga, payload)
.map_err(|e| format!("Failed to encrypt: {e}"))?;
let mut ciphertext_integral_nonce = nonce.to_vec();
ciphertext_integral_nonce.append(&mut ciphertext);
Ok(ciphertext_integral_nonce)
}
#[cfg(test)]
mod test {
#![allow(clippy::indexing_slicing)]
use chacha20poly1305::{Key, KeyInit, Tag};
use super::*;
#[test]
fn test_encrypt_decrypt() {
let plaintext = b"The quick brown fox was annoying".to_vec();
let mut key = [0u8; size_of::<Key>()];
rand::rng().fill_bytes(&mut key);
let key_ga = Key::from_slice(&key);
let cipher = XChaCha20Poly1305::new(key_ga);
let ciphertext =
encrypt_bytes_integral_nonce(&cipher, b"correct_domain".to_vec(), Hidden::hide(plaintext.clone())).unwrap();
assert_eq!(
ciphertext.len(),
size_of::<XNonce>() + plaintext.len() + size_of::<Tag>()
);
let decrypted_text = decrypt_bytes_integral_nonce(&cipher, b"correct_domain".to_vec(), &ciphertext).unwrap();
assert_eq!(decrypted_text, plaintext);
assert!(decrypt_bytes_integral_nonce(&cipher, b"wrong_domain".to_vec(), &ciphertext).is_err());
let ciphertext_with_evil_nonce = ciphertext
.clone()
.splice(0..size_of::<XNonce>(), [0u8; size_of::<XNonce>()])
.collect::<Vec<_>>();
assert!(
decrypt_bytes_integral_nonce(&cipher, b"correct_domain".to_vec(), &ciphertext_with_evil_nonce).is_err()
);
let ciphertext_with_evil_ciphertext = ciphertext
.clone()
.splice(
size_of::<XNonce>()..(ciphertext.len() - size_of::<Tag>()),
vec![0u8; plaintext.len()],
)
.collect::<Vec<_>>();
assert!(
decrypt_bytes_integral_nonce(&cipher, b"correct_domain".to_vec(), &ciphertext_with_evil_ciphertext)
.is_err()
);
let ciphertext_with_evil_tag = ciphertext
.clone()
.splice((ciphertext.len() - size_of::<Tag>())..ciphertext.len(), vec![
0u8;
size_of::<
Tag,
>(
)
])
.collect::<Vec<_>>();
assert!(decrypt_bytes_integral_nonce(&cipher, b"correct_domain".to_vec(), &ciphertext_with_evil_tag).is_err());
assert!(
decrypt_bytes_integral_nonce(
&cipher,
b"correct_domain".to_vec(),
&ciphertext[0..(size_of::<XNonce>() + size_of::<Tag>() - 1)]
)
.is_err()
);
}
}