use crate::primitives::private_key::PrivateKey;
use crate::primitives::public_key::PublicKey;
use crate::primitives::random::random_bytes;
use crate::primitives::symmetric_key::SymmetricKey;
use crate::services::ServicesError;
const ENCRYPTED_MESSAGE_VERSION: [u8; 4] = [0x42, 0x42, 0x10, 0x33];
pub fn encrypt(
message: &[u8],
sender: &PrivateKey,
recipient: &PublicKey,
) -> Result<Vec<u8>, ServicesError> {
let key_id = random_bytes(32);
let key_id_base64 = to_base64(&key_id);
let invoice_number = format!("2-message encryption-{}", key_id_base64);
let sender_derived_priv = sender
.derive_child(recipient, &invoice_number)
.map_err(|e| ServicesError::Messages(format!("derive sender child: {}", e)))?;
let recipient_derived_pub = recipient
.derive_child(sender, &invoice_number)
.map_err(|e| ServicesError::Messages(format!("derive recipient child: {}", e)))?;
let shared_secret = sender_derived_priv
.derive_shared_secret(&recipient_derived_pub)
.map_err(|e| ServicesError::Messages(format!("shared secret: {}", e)))?;
let shared_compressed = shared_secret.to_der(true);
let sym_key = SymmetricKey::from_bytes(&shared_compressed[1..])
.map_err(|e| ServicesError::Messages(format!("symmetric key: {}", e)))?;
let encrypted = sym_key
.encrypt(message)
.map_err(|e| ServicesError::Messages(format!("encrypt: {}", e)))?;
let sender_pubkey = sender.to_public_key().to_der();
let recipient_pubkey = recipient.to_der();
let mut result = Vec::with_capacity(4 + 33 + 33 + 32 + encrypted.len());
result.extend_from_slice(&ENCRYPTED_MESSAGE_VERSION);
result.extend_from_slice(&sender_pubkey);
result.extend_from_slice(&recipient_pubkey);
result.extend_from_slice(&key_id);
result.extend_from_slice(&encrypted);
Ok(result)
}
pub fn decrypt(
encrypted_message: &[u8],
recipient: &PrivateKey,
) -> Result<(Vec<u8>, PublicKey), ServicesError> {
if encrypted_message.len() < 4 + 33 + 33 + 32 {
return Err(ServicesError::Messages(
"encrypted message too short".to_string(),
));
}
let version = &encrypted_message[0..4];
if version != ENCRYPTED_MESSAGE_VERSION {
return Err(ServicesError::Messages(format!(
"Message version mismatch: expected {:02x}{:02x}{:02x}{:02x}, received {:02x}{:02x}{:02x}{:02x}",
ENCRYPTED_MESSAGE_VERSION[0], ENCRYPTED_MESSAGE_VERSION[1],
ENCRYPTED_MESSAGE_VERSION[2], ENCRYPTED_MESSAGE_VERSION[3],
version[0], version[1], version[2], version[3]
)));
}
let mut pos = 4;
let sender_pub = PublicKey::from_der_bytes(&encrypted_message[pos..pos + 33])
.map_err(|e| ServicesError::Messages(format!("invalid sender pubkey: {}", e)))?;
pos += 33;
let expected_recipient_bytes = &encrypted_message[pos..pos + 33];
pos += 33;
let actual_recipient_pubkey = recipient.to_public_key();
let actual_recipient_bytes = actual_recipient_pubkey.to_der();
if expected_recipient_bytes != actual_recipient_bytes.as_slice() {
return Err(ServicesError::Messages(format!(
"Recipient public key mismatch: expected {}, got {}",
hex_encode(expected_recipient_bytes),
hex_encode(&actual_recipient_bytes),
)));
}
let key_id = &encrypted_message[pos..pos + 32];
pos += 32;
let encrypted_data = &encrypted_message[pos..];
let key_id_base64 = to_base64(key_id);
let invoice_number = format!("2-message encryption-{}", key_id_base64);
let sender_derived_pub = sender_pub
.derive_child(recipient, &invoice_number)
.map_err(|e| ServicesError::Messages(format!("derive sender child pub: {}", e)))?;
let recipient_derived_priv = recipient
.derive_child(&sender_pub, &invoice_number)
.map_err(|e| ServicesError::Messages(format!("derive recipient child priv: {}", e)))?;
let shared_secret = recipient_derived_priv
.derive_shared_secret(&sender_derived_pub)
.map_err(|e| ServicesError::Messages(format!("shared secret: {}", e)))?;
let shared_compressed = shared_secret.to_der(true);
let sym_key = SymmetricKey::from_bytes(&shared_compressed[1..])
.map_err(|e| ServicesError::Messages(format!("symmetric key: {}", e)))?;
let plaintext = sym_key
.decrypt(encrypted_data)
.map_err(|e| ServicesError::Messages(format!("decrypt: {}", e)))?;
Ok((plaintext, sender_pub))
}
fn to_base64(data: &[u8]) -> String {
const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut result = String::new();
let chunks = data.chunks(3);
for chunk in chunks {
let b0 = chunk[0] as u32;
let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
let triple = (b0 << 16) | (b1 << 8) | b2;
result.push(ALPHABET[((triple >> 18) & 0x3F) as usize] as char);
result.push(ALPHABET[((triple >> 12) & 0x3F) as usize] as char);
if chunk.len() > 1 {
result.push(ALPHABET[((triple >> 6) & 0x3F) as usize] as char);
} else {
result.push('=');
}
if chunk.len() > 2 {
result.push(ALPHABET[(triple & 0x3F) as usize] as char);
} else {
result.push('=');
}
}
result
}
fn hex_encode(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{:02x}", b)).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encrypt_decrypt_round_trip() {
let sender = PrivateKey::from_hex(
"e6baf19a8b0f6d2ab882bc22e53ef18587e1b2a35ce998d4dbeb3e5f97647e0e",
)
.unwrap();
let recipient_priv = PrivateKey::from_hex(
"c8bc2c2575f2cf350b5aa0f3497b4e38f70270cd53d23b29da54ea3a685bbf39",
)
.unwrap();
let recipient_pub = recipient_priv.to_public_key();
let message = b"Hello BRC-78!";
let encrypted = encrypt(message, &sender, &recipient_pub).unwrap();
let (decrypted, sender_back) = decrypt(&encrypted, &recipient_priv).unwrap();
assert_eq!(decrypted, message);
let expected_sender = sender.to_public_key();
assert_eq!(sender_back.to_der(), expected_sender.to_der());
}
#[test]
fn test_decrypt_wrong_recipient_fails() {
let sender = PrivateKey::from_hex(
"e6baf19a8b0f6d2ab882bc22e53ef18587e1b2a35ce998d4dbeb3e5f97647e0e",
)
.unwrap();
let recipient_priv = PrivateKey::from_hex(
"c8bc2c2575f2cf350b5aa0f3497b4e38f70270cd53d23b29da54ea3a685bbf39",
)
.unwrap();
let wrong_recipient = PrivateKey::from_hex("1").unwrap();
let recipient_pub = recipient_priv.to_public_key();
let message = b"Secret!";
let encrypted = encrypt(message, &sender, &recipient_pub).unwrap();
let result = decrypt(&encrypted, &wrong_recipient);
assert!(result.is_err());
}
#[test]
fn test_decrypt_rejects_tampered_ciphertext() {
let sender = PrivateKey::from_hex(
"e6baf19a8b0f6d2ab882bc22e53ef18587e1b2a35ce998d4dbeb3e5f97647e0e",
)
.unwrap();
let recipient_priv = PrivateKey::from_hex(
"c8bc2c2575f2cf350b5aa0f3497b4e38f70270cd53d23b29da54ea3a685bbf39",
)
.unwrap();
let recipient_pub = recipient_priv.to_public_key();
let message = b"Tamper test";
let mut encrypted = encrypt(message, &sender, &recipient_pub).unwrap();
if encrypted.len() > 110 {
encrypted[110] ^= 0xff;
}
let result = decrypt(&encrypted, &recipient_priv);
assert!(result.is_err());
}
#[test]
fn test_encrypted_message_format() {
let sender = PrivateKey::from_hex("1").unwrap();
let recipient_priv = PrivateKey::from_hex("2").unwrap();
let recipient_pub = recipient_priv.to_public_key();
let message = b"test";
let encrypted = encrypt(message, &sender, &recipient_pub).unwrap();
assert!(encrypted.len() >= 4 + 33 + 33 + 32);
assert_eq!(&encrypted[0..4], &ENCRYPTED_MESSAGE_VERSION);
}
}