diff --git a/src/crypto.rs b/src/crypto.rs index 0bbe5f1..7fecd13 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,20 +1,5 @@ -use std::collections::HashMap; - use anyhow::{Error, Result}; -use serde::{Deserialize, Serialize}; -use sp_client::{ - bitcoin::{ - hex::{DisplayHex, FromHex}, - key::constants::SECRET_KEY_SIZE, - Txid, - }, - silentpayments::{ - bitcoin_hashes::{sha256t_hash_newtype, Hash, HashEngine}, - secp256k1::PublicKey, - utils::SilentPaymentAddress, - }, -}; -use tsify::Tsify; +use sp_client::silentpayments::{bitcoin_hashes::{sha256t_hash_newtype, Hash, HashEngine}, secp256k1::PublicKey}; use aes_gcm::aead::{Aead, Payload}; pub use aes_gcm::{AeadCore, Aes256Gcm, KeyInit}; @@ -22,43 +7,6 @@ use rand::thread_rng; pub const AAD: &[u8] = "4nk".as_bytes(); -const HALFKEYSIZE: usize = SECRET_KEY_SIZE / 2; - -const THIRTYTWO: usize = 32; - -#[derive(Debug)] -pub struct SharedPoint([u8; 64]); - -impl SharedPoint { - pub fn as_inner(&self) -> &[u8; 64] { - &self.0 - } -} - -#[derive(Debug, Serialize, Deserialize, Tsify, Clone, Default, PartialEq)] -#[tsify(from_wasm_abi, into_wasm_abi)] -pub struct AnkSharedSecret { - secret: String, -} - -impl AnkSharedSecret { - pub fn new(shared_point: PublicKey) -> Self { - let mut shared_point_bin = [0u8; 64]; - shared_point_bin.copy_from_slice(&shared_point.serialize_uncompressed()[1..]); - let secret = AnkSharedSecretHash::from_shared_point(shared_point_bin).to_byte_array(); - Self { - secret: secret.to_lower_hex_string(), - } - } - - pub fn to_byte_array(&self) -> [u8; 32] { - let bytes = Vec::from_hex(&self.secret).unwrap(); - let mut buf = [0u8; 32]; - buf.copy_from_slice(&bytes); - buf - } -} - sha256t_hash_newtype! { pub struct AnkSharedSecretTag = hash_str("4nk/AnkSharedSecret"); @@ -67,317 +15,39 @@ sha256t_hash_newtype! { } impl AnkSharedSecretHash { - pub fn from_shared_point(shared_point: [u8; 64]) -> Self { + pub fn from_shared_point(shared_point: PublicKey) -> Self { let mut eng = AnkSharedSecretHash::engine(); - eng.input(&shared_point); + eng.input(&shared_point.serialize_uncompressed()[1..]); AnkSharedSecretHash::from_engine(eng) } } -pub struct HalfKey([u8; HALFKEYSIZE]); +pub fn encrypt_with_key(key: &[u8; 32], plaintext: &[u8]) -> Result> { + let encryption_eng = Aes256Gcm::new(key.into()); + let nonce = Aes256Gcm::generate_nonce(&mut thread_rng()); + let payload = Payload { + msg: plaintext, + aad: AAD, + }; + let ciphertext = encryption_eng.encrypt(&nonce, payload) + .map_err(|e| anyhow::anyhow!(e))?; -impl TryFrom> for HalfKey { - type Error = anyhow::Error; - fn try_from(value: Vec) -> std::prelude::v1::Result { - if value.len() == HALFKEYSIZE { - let mut buf = [0u8; HALFKEYSIZE]; - buf.copy_from_slice(&value); - Ok(HalfKey(buf)) - } else { - Err(Error::msg("Invalid length for HalfKey")) - } - } + let mut res: Vec = Vec::with_capacity(nonce.len() + ciphertext.len()); + res.extend_from_slice(&nonce); + res.extend_from_slice(&ciphertext); + + Ok(res) } -impl HalfKey { - pub fn as_slice(&self) -> &[u8] { - &self.0 - } +pub fn decrypt_with_key(key: &[u8; 32], ciphertext: &[u8]) -> Result> { + let decryption_eng = Aes256Gcm::new(key.into()); + let nonce = &ciphertext[..12]; + let payload = Payload { + msg: &ciphertext[12..], + aad: AAD, + }; + let plaintext = decryption_eng.decrypt(nonce.into(), payload) + .map_err(|e| anyhow::anyhow!(e))?; - pub fn to_inner(&self) -> Vec { - self.0.to_vec() - } -} - -pub enum Purpose { - Login, - ThirtyTwoBytes, - Arbitrary, -} - -pub type CipherText = Vec; - -pub type EncryptedKey = Vec; - -pub struct Aes256Decryption { - pub purpose: Purpose, - cipher_text: CipherText, - aes_key: [u8; 32], - nonce: [u8; 12], -} - -impl Aes256Decryption { - pub fn new(purpose: Purpose, cipher_text: CipherText, aes_key: [u8; 32]) -> Result { - if cipher_text.len() <= 12 { - return Err(Error::msg("cipher_text is shorter than nonce length")); - } - let (message_nonce, message_cipher) = cipher_text.split_at(12); - let mut nonce = [0u8; 12]; - nonce.copy_from_slice(message_nonce); - Ok(Self { - purpose, - cipher_text: message_cipher.to_vec(), - aes_key, - nonce, - }) - } - - pub fn decrypt_with_key(&self) -> Result> { - match self.purpose { - Purpose::Login => { - let half_key = self.decrypt_login()?; - Ok(half_key.to_inner()) - } - Purpose::ThirtyTwoBytes => { - let thirty_two_buf = self.decrypt_thirty_two()?; - Ok(thirty_two_buf.to_vec()) - } - Purpose::Arbitrary => { - let arbitrary = self.decrypt_arbitrary()?; - Ok(arbitrary) - } - } - } - - fn decrypt_login(&self) -> Result { - let cipher = Aes256Gcm::new(&self.aes_key.into()); - let plain = cipher - .decrypt(&self.nonce.into(), &*self.cipher_text) - .map_err(|e| Error::msg(format!("{}", e)))?; - if plain.len() != SECRET_KEY_SIZE / 2 { - return Err(Error::msg("Plain text of invalid lenght for a login")); - } - let mut key_half = [0u8; SECRET_KEY_SIZE / 2]; - key_half.copy_from_slice(&plain); - Ok(HalfKey(key_half)) - } - - fn decrypt_thirty_two(&self) -> Result<[u8; THIRTYTWO]> { - let cipher = Aes256Gcm::new(&self.aes_key.into()); - let plain = cipher - .decrypt(&self.nonce.into(), &*self.cipher_text) - .map_err(|e| Error::msg(format!("{}", e)))?; - if plain.len() != THIRTYTWO { - return Err(Error::msg("Plain text of invalid length, should be 32")); - } - let mut thirty_two = [0u8; THIRTYTWO]; - thirty_two.copy_from_slice(&plain); - Ok(thirty_two) - } - - fn decrypt_arbitrary(&self) -> Result> { - let cipher = Aes256Gcm::new(&self.aes_key.into()); - let payload = Payload { - msg: &self.cipher_text, - aad: AAD, - }; - let plain = cipher - .decrypt(&self.nonce.into(), payload) - .map_err(|e| Error::msg(format!("{}", e)))?; - Ok(plain) - } -} - -pub struct Aes256Encryption { - pub purpose: Purpose, - plaintext: Vec, - aes_key: [u8; 32], - nonce: [u8; 12], - shared_secrets: HashMap>, -} - -impl Aes256Encryption { - pub fn new(purpose: Purpose, plaintext: Vec) -> Result { - let mut rng = thread_rng(); - let aes_key: [u8; 32] = Aes256Gcm::generate_key(&mut rng).into(); - let nonce: [u8; 12] = Aes256Gcm::generate_nonce(&mut rng).into(); - Self::import_key(purpose, plaintext, aes_key, nonce) - } - - pub fn set_shared_secret( - &mut self, - shared_secrets: HashMap>, - ) { - self.shared_secrets = shared_secrets; - } - - pub fn encrypt_keys_with_shared_secrets( - &self, - ) -> Result> { - let mut res = HashMap::new(); - let mut rng = thread_rng(); - - for (_, sp_address2shared_secret) in self.shared_secrets.iter() { - for (sp_address, shared_secret) in sp_address2shared_secret { - let cipher = Aes256Gcm::new_from_slice(&shared_secret.to_byte_array()) - .map_err(|e| Error::msg(format!("{}", e)))?; - let nonce = Aes256Gcm::generate_nonce(&mut rng); - let encrypted_key = cipher - .encrypt(&nonce, self.aes_key.as_slice()) - .map_err(|e| Error::msg(format!("{}", e)))?; - - let mut ciphertext = Vec::::with_capacity(nonce.len() + encrypted_key.len()); - ciphertext.extend(nonce); - ciphertext.extend(encrypted_key); - - res.insert(sp_address.to_owned(), ciphertext); - } - } - Ok(res) - } - - pub fn import_key( - purpose: Purpose, - plaintext: Vec, - aes_key: [u8; 32], - nonce: [u8; 12], - ) -> Result { - if plaintext.len() == 0 { - return Err(Error::msg("Can't create encryption for an empty message")); - } - Ok(Self { - purpose, - plaintext, - aes_key, - nonce, - shared_secrets: HashMap::new(), - }) - } - - pub fn export_key(&self) -> [u8; 32] { - self.aes_key - } - - pub fn encrypt_with_aes_key(&self) -> Result { - match self.purpose { - Purpose::Login => self.encrypt_login(), - Purpose::ThirtyTwoBytes => self.encrypt_thirty_two(), - Purpose::Arbitrary => self.encrypt_arbitrary(), - } - } - - fn encrypt_login(&self) -> Result { - let half_key: HalfKey = self.plaintext.clone().try_into()?; - let cipher = Aes256Gcm::new(&self.aes_key.into()); - let cipher_text = cipher - .encrypt(&self.nonce.into(), half_key.as_slice()) - .map_err(|e| Error::msg(format!("{}", e)))?; - let mut res = Vec::with_capacity(self.nonce.len() + cipher_text.len()); - res.extend_from_slice(&self.nonce); - res.extend_from_slice(&cipher_text); - Ok(res) - } - - fn encrypt_thirty_two(&self) -> Result { - if self.plaintext.len() != 32 { - return Err(Error::msg("Invalid length, should be 32")); - } - let mut thirty_two = [0u8; 32]; - thirty_two.copy_from_slice(&self.plaintext); - let cipher = Aes256Gcm::new(&self.aes_key.into()); - let cipher_text = cipher - .encrypt(&self.nonce.into(), thirty_two.as_slice()) - .map_err(|e| Error::msg(format!("{}", e)))?; - let mut res = Vec::with_capacity(self.nonce.len() + cipher_text.len()); - res.extend_from_slice(&self.nonce); - res.extend_from_slice(&cipher_text); - Ok(res) - } - - fn encrypt_arbitrary(&self) -> Result { - let cipher = Aes256Gcm::new(&self.aes_key.into()); - let payload = Payload { - msg: &self.plaintext, - aad: AAD, - }; - let cipher_text = cipher - .encrypt(&self.nonce.into(), payload) - .map_err(|e| Error::msg(format!("{}", e)))?; - let mut res = Vec::with_capacity(self.nonce.len() + cipher_text.len()); - res.extend_from_slice(&self.nonce); - res.extend_from_slice(&cipher_text); - Ok(res) - } -} - -#[cfg(test)] -mod tests { - use std::{io::Read, str::FromStr}; - - use sp_client::bitcoin::hex::FromHex; - - use super::*; - - const ALICE_SP_ADDRESS: &str = "tsp1qqw3lqr6xravz9nf8ntazgwwl0fqv47kfjdxsnxs6eutavqfwyv5q6qk97mmyf6dtkdyzqlu2zv6h9j2ggclk7vn705q5u2phglpq7yw3dg5rwpdz"; - const BOB_SP_ADDRESS: &str = "tsp1qq2hlsgrj0gz8kcfkf9flqw5llz0u2vr04telqndku9mcqm6dl4fhvq60t8r78srrf56w9yr7w9e9dusc2wjqc30up6fjwnh9mw3e3veqegdmtf08"; - const TRANSACTION: &str = "4e6d03dec558e1b6624f813bf2da7cd8d8fb1c2296684c08cf38724dcfd8d10b"; - const ALICE_SHARED_SECRET: &str = - "ccf02d364c2641ca129a3fdf49de57b705896e233f7ba6d738991993ea7e2106"; - const BOB_SHARED_SECRET: &str = - "15ef3e377fb842e81de52dbaaea8ba30aeb051a81043ee19264afd27353da521"; - - #[test] - fn new_aes_empty_plaintext() { - let plaintext = Vec::new(); - let aes_enc = Aes256Encryption::new(Purpose::Login, plaintext); - - assert!(aes_enc.is_err()); - } - - #[test] - fn aes_encrypt_login_invalid_length() { - let plaintext = "example"; - let aes_enc_short = Aes256Encryption::new(Purpose::Login, plaintext.as_bytes().to_vec()); - - assert!(aes_enc_short.is_ok()); - - let cipher = aes_enc_short.unwrap().encrypt_with_aes_key(); - - assert!(cipher.is_err()); - - let plaintext = [1u8; 64]; - let aes_enc_long = Aes256Encryption::new(Purpose::Login, plaintext.to_vec()); - - assert!(aes_enc_long.is_ok()); - - let cipher = aes_enc_long.unwrap().encrypt_with_aes_key(); - - assert!(cipher.is_err()); - } - - #[test] - fn aes_encrypt_login() { - let plaintext = [1u8; HALFKEYSIZE]; - let aes_key = Aes256Gcm::generate_key(&mut thread_rng()); - let nonce = Aes256Gcm::generate_nonce(&mut thread_rng()); - let aes_enc = Aes256Encryption::import_key( - Purpose::Login, - plaintext.to_vec(), - aes_key.into(), - nonce.into(), - ); - - assert!(aes_enc.is_ok()); - - let cipher = aes_enc.unwrap().encrypt_with_aes_key(); - - assert!(cipher.is_ok()); - - let mut plain_key = [0u8; 32]; - plain_key.copy_from_slice(&aes_key.to_vec()); - - let aes_dec = Aes256Decryption::new(Purpose::Login, cipher.unwrap(), plain_key); - - assert!(aes_dec.is_ok()); - } + Ok(plaintext) }