2022-11-08 02:03:24 -08:00
|
|
|
//! Authenticated encryption implementation.
|
|
|
|
//!
|
|
|
|
//! This module is a simple wrapper of the `Aes128GcmSiv` implementation.
|
2022-05-18 18:17:29 -07:00
|
|
|
#[cfg(not(target_os = "solana"))]
|
2021-10-17 08:37:28 -07:00
|
|
|
use {
|
2021-12-13 07:56:49 -08:00
|
|
|
aes_gcm_siv::{
|
|
|
|
aead::{Aead, NewAead},
|
|
|
|
Aes128GcmSiv,
|
|
|
|
},
|
2021-10-17 08:37:28 -07:00
|
|
|
rand::{rngs::OsRng, CryptoRng, Rng, RngCore},
|
|
|
|
};
|
2021-10-15 03:44:56 -07:00
|
|
|
use {
|
2021-10-17 08:37:28 -07:00
|
|
|
arrayref::{array_ref, array_refs},
|
2021-10-21 17:48:25 -07:00
|
|
|
solana_sdk::{
|
|
|
|
instruction::Instruction,
|
|
|
|
message::Message,
|
|
|
|
pubkey::Pubkey,
|
|
|
|
signature::Signature,
|
|
|
|
signer::{Signer, SignerError},
|
|
|
|
},
|
2022-07-23 05:59:40 -07:00
|
|
|
std::{convert::TryInto, fmt},
|
2022-08-24 02:56:55 -07:00
|
|
|
subtle::ConstantTimeEq,
|
2021-10-15 03:44:56 -07:00
|
|
|
zeroize::Zeroize,
|
2021-10-14 09:19:22 -07:00
|
|
|
};
|
2021-10-08 06:12:54 -07:00
|
|
|
|
2021-12-13 07:56:49 -08:00
|
|
|
struct AuthenticatedEncryption;
|
|
|
|
impl AuthenticatedEncryption {
|
2022-05-18 18:17:29 -07:00
|
|
|
#[cfg(not(target_os = "solana"))]
|
2021-10-11 12:16:29 -07:00
|
|
|
#[allow(clippy::new_ret_no_self)]
|
2021-12-13 07:56:49 -08:00
|
|
|
fn keygen<T: RngCore + CryptoRng>(rng: &mut T) -> AeKey {
|
|
|
|
AeKey(rng.gen::<[u8; 16]>())
|
2021-10-07 15:21:31 -07:00
|
|
|
}
|
|
|
|
|
2022-05-18 18:17:29 -07:00
|
|
|
#[cfg(not(target_os = "solana"))]
|
2021-12-13 07:56:49 -08:00
|
|
|
fn encrypt(key: &AeKey, balance: u64) -> AeCiphertext {
|
|
|
|
let mut plaintext = balance.to_le_bytes();
|
|
|
|
let nonce: Nonce = OsRng.gen::<[u8; 12]>();
|
2021-10-08 06:12:54 -07:00
|
|
|
|
2021-12-13 07:56:49 -08:00
|
|
|
// The balance and the nonce have fixed length and therefore, encryption should not fail.
|
|
|
|
let ciphertext = Aes128GcmSiv::new(&key.0.into())
|
2021-10-17 08:37:28 -07:00
|
|
|
.encrypt(&nonce.into(), plaintext.as_ref())
|
2021-12-13 07:56:49 -08:00
|
|
|
.expect("authenticated encryption");
|
|
|
|
|
|
|
|
plaintext.zeroize();
|
2021-10-08 06:12:54 -07:00
|
|
|
|
2021-12-13 07:56:49 -08:00
|
|
|
AeCiphertext {
|
2021-10-17 08:16:18 -07:00
|
|
|
nonce,
|
|
|
|
ciphertext: ciphertext.try_into().unwrap(),
|
|
|
|
}
|
2021-10-07 15:21:31 -07:00
|
|
|
}
|
|
|
|
|
2022-05-18 18:17:29 -07:00
|
|
|
#[cfg(not(target_os = "solana"))]
|
2021-12-13 07:56:49 -08:00
|
|
|
fn decrypt(key: &AeKey, ct: &AeCiphertext) -> Option<u64> {
|
2021-10-17 08:37:28 -07:00
|
|
|
let plaintext =
|
2021-12-13 07:56:49 -08:00
|
|
|
Aes128GcmSiv::new(&key.0.into()).decrypt(&ct.nonce.into(), ct.ciphertext.as_ref());
|
2021-10-08 06:12:54 -07:00
|
|
|
|
2021-10-17 08:16:18 -07:00
|
|
|
if let Ok(plaintext) = plaintext {
|
|
|
|
let amount_bytes: [u8; 8] = plaintext.try_into().unwrap();
|
|
|
|
Some(u64::from_le_bytes(amount_bytes))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
2021-10-07 15:21:31 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-11 12:16:29 -07:00
|
|
|
#[derive(Debug, Zeroize)]
|
2021-12-13 07:56:49 -08:00
|
|
|
pub struct AeKey([u8; 16]);
|
|
|
|
impl AeKey {
|
2021-10-21 17:48:25 -07:00
|
|
|
pub fn new(signer: &dyn Signer, address: &Pubkey) -> Result<Self, SignerError> {
|
|
|
|
let message = Message::new(
|
2021-12-13 07:56:49 -08:00
|
|
|
&[Instruction::new_with_bytes(*address, b"AeKey", vec![])],
|
2021-10-21 17:48:25 -07:00
|
|
|
Some(&signer.try_pubkey()?),
|
|
|
|
);
|
|
|
|
let signature = signer.try_sign_message(&message.serialize())?;
|
|
|
|
|
|
|
|
// Some `Signer` implementations return the default signature, which is not suitable for
|
|
|
|
// use as key material
|
2022-08-24 02:56:55 -07:00
|
|
|
if bool::from(signature.as_ref().ct_eq(Signature::default().as_ref())) {
|
2021-10-21 17:48:25 -07:00
|
|
|
Err(SignerError::Custom("Rejecting default signature".into()))
|
|
|
|
} else {
|
2021-12-13 07:56:49 -08:00
|
|
|
Ok(AeKey(signature.as_ref()[..16].try_into().unwrap()))
|
2021-10-21 17:48:25 -07:00
|
|
|
}
|
2021-10-17 08:16:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn random<T: RngCore + CryptoRng>(rng: &mut T) -> Self {
|
2021-12-13 07:56:49 -08:00
|
|
|
AuthenticatedEncryption::keygen(rng)
|
2021-10-17 08:16:18 -07:00
|
|
|
}
|
|
|
|
|
2021-12-13 07:56:49 -08:00
|
|
|
pub fn encrypt(&self, amount: u64) -> AeCiphertext {
|
|
|
|
AuthenticatedEncryption::encrypt(self, amount)
|
2021-10-07 15:21:31 -07:00
|
|
|
}
|
2021-10-29 10:46:29 -07:00
|
|
|
|
2021-12-13 07:56:49 -08:00
|
|
|
pub fn decrypt(&self, ct: &AeCiphertext) -> Option<u64> {
|
|
|
|
AuthenticatedEncryption::decrypt(self, ct)
|
2021-10-29 10:46:29 -07:00
|
|
|
}
|
2021-10-07 15:21:31 -07:00
|
|
|
}
|
|
|
|
|
2022-11-08 02:03:24 -08:00
|
|
|
/// For the purpose of encrypting balances for the spl token accounts, the nonce and ciphertext
|
|
|
|
/// sizes should always be fixed.
|
2021-12-13 07:56:49 -08:00
|
|
|
pub type Nonce = [u8; 12];
|
|
|
|
pub type Ciphertext = [u8; 24];
|
|
|
|
|
|
|
|
/// Authenticated encryption nonce and ciphertext
|
2022-02-22 22:47:26 -08:00
|
|
|
#[derive(Debug, Default, Clone)]
|
2021-12-13 07:56:49 -08:00
|
|
|
pub struct AeCiphertext {
|
|
|
|
pub nonce: Nonce,
|
|
|
|
pub ciphertext: Ciphertext,
|
2021-10-07 15:21:31 -07:00
|
|
|
}
|
2021-12-13 07:56:49 -08:00
|
|
|
impl AeCiphertext {
|
|
|
|
pub fn decrypt(&self, key: &AeKey) -> Option<u64> {
|
|
|
|
AuthenticatedEncryption::decrypt(key, self)
|
2021-10-17 08:37:28 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn to_bytes(&self) -> [u8; 36] {
|
|
|
|
let mut buf = [0_u8; 36];
|
|
|
|
buf[..12].copy_from_slice(&self.nonce);
|
|
|
|
buf[12..].copy_from_slice(&self.ciphertext);
|
|
|
|
buf
|
|
|
|
}
|
|
|
|
|
2021-12-13 07:56:49 -08:00
|
|
|
pub fn from_bytes(bytes: &[u8]) -> Option<AeCiphertext> {
|
2021-10-17 08:37:28 -07:00
|
|
|
if bytes.len() != 36 {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
let bytes = array_ref![bytes, 0, 36];
|
|
|
|
let (nonce, ciphertext) = array_refs![bytes, 12, 24];
|
2021-12-13 07:56:49 -08:00
|
|
|
|
|
|
|
Some(AeCiphertext {
|
2021-10-17 08:37:28 -07:00
|
|
|
nonce: *nonce,
|
|
|
|
ciphertext: *ciphertext,
|
|
|
|
})
|
2021-10-14 09:56:37 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-23 05:59:40 -07:00
|
|
|
impl fmt::Display for AeCiphertext {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
write!(f, "{}", base64::encode(self.to_bytes()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-08 06:12:54 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2021-10-21 17:48:25 -07:00
|
|
|
use {
|
|
|
|
super::*,
|
|
|
|
solana_sdk::{signature::Keypair, signer::null_signer::NullSigner},
|
|
|
|
};
|
2021-10-08 06:12:54 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_aes_encrypt_decrypt_correctness() {
|
2021-12-13 07:56:49 -08:00
|
|
|
let key = AeKey::random(&mut OsRng);
|
2021-10-08 06:12:54 -07:00
|
|
|
let amount = 55;
|
|
|
|
|
2021-10-17 08:16:18 -07:00
|
|
|
let ct = key.encrypt(amount);
|
|
|
|
let decrypted_amount = ct.decrypt(&key).unwrap();
|
2021-10-08 06:12:54 -07:00
|
|
|
|
|
|
|
assert_eq!(amount, decrypted_amount);
|
|
|
|
}
|
2021-10-21 17:48:25 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_aes_new() {
|
|
|
|
let keypair1 = Keypair::new();
|
|
|
|
let keypair2 = Keypair::new();
|
|
|
|
|
|
|
|
assert_ne!(
|
2021-12-13 07:56:49 -08:00
|
|
|
AeKey::new(&keypair1, &Pubkey::default()).unwrap().0,
|
|
|
|
AeKey::new(&keypair2, &Pubkey::default()).unwrap().0,
|
2021-10-21 17:48:25 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
let null_signer = NullSigner::new(&Pubkey::default());
|
2021-12-13 07:56:49 -08:00
|
|
|
assert!(AeKey::new(&null_signer, &Pubkey::default()).is_err());
|
2021-10-21 17:48:25 -07:00
|
|
|
}
|
2021-10-08 06:12:54 -07:00
|
|
|
}
|