use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use crate::error::{Error, Result};
use crate::primitives::ec::{PrivateKey, PublicKey};
use crate::primitives::hash::{sha256_hmac, sha512};
use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;
type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;
type Aes256CbcDec = cbc::Decryptor<aes::Aes256>;
const ELECTRUM_MAGIC: &[u8] = b"BIE1";
pub fn electrum_encrypt(
message: &[u8],
to: &PublicKey,
from: &PrivateKey,
no_key: bool,
) -> Result<Vec<u8>> {
let shared_secret = from.derive_shared_secret(to)?;
let shared_compressed = shared_secret.to_compressed();
let key_material = sha512(&shared_compressed);
let iv: [u8; 16] = key_material[0..16].try_into().unwrap();
let aes_key: [u8; 16] = key_material[16..32].try_into().unwrap();
let hmac_key = &key_material[32..64];
let ciphertext = aes_128_cbc_encrypt(message, &aes_key, &iv)?;
let ephemeral_pubkey = from.public_key();
let mut encrypted = Vec::with_capacity(4 + 33 + ciphertext.len() + 32);
encrypted.extend_from_slice(ELECTRUM_MAGIC);
if !no_key {
encrypted.extend_from_slice(&ephemeral_pubkey.to_compressed());
}
encrypted.extend_from_slice(&ciphertext);
let mac = sha256_hmac(hmac_key, &encrypted);
encrypted.extend_from_slice(&mac);
Ok(encrypted)
}
pub fn electrum_decrypt(data: &[u8], to: &PrivateKey, from: Option<&PublicKey>) -> Result<Vec<u8>> {
if data.len() < 52 {
return Err(Error::EciesDecryptionFailed("Data too short".to_string()));
}
if &data[0..4] != ELECTRUM_MAGIC {
return Err(Error::EciesDecryptionFailed(
"Invalid magic bytes".to_string(),
));
}
let (shared_secret, ciphertext) = if let Some(from_pubkey) = from {
let shared = to.derive_shared_secret(from_pubkey)?;
let ct = if data.len() > 69 {
&data[37..data.len() - 32]
} else {
&data[4..data.len() - 32]
};
(shared, ct.to_vec())
} else {
if data.len() < 69 {
return Err(Error::EciesDecryptionFailed(
"Data too short for embedded public key".to_string(),
));
}
let ephemeral_pubkey = PublicKey::from_bytes(&data[4..37])?;
let shared = to.derive_shared_secret(&ephemeral_pubkey)?;
let ct = &data[37..data.len() - 32];
(shared, ct.to_vec())
};
let shared_compressed = shared_secret.to_compressed();
let key_material = sha512(&shared_compressed);
let iv: [u8; 16] = key_material[0..16].try_into().unwrap();
let aes_key: [u8; 16] = key_material[16..32].try_into().unwrap();
let hmac_key = &key_material[32..64];
let mac = &data[data.len() - 32..];
let data_without_mac = &data[..data.len() - 32];
let expected_mac = sha256_hmac(hmac_key, data_without_mac);
if !constant_time_eq(mac, &expected_mac) {
return Err(Error::EciesHmacMismatch);
}
aes_128_cbc_decrypt(&ciphertext, &aes_key, &iv)
}
pub fn bitcore_encrypt(
message: &[u8],
to: &PublicKey,
from: &PrivateKey,
iv: Option<&[u8; 16]>,
) -> Result<Vec<u8>> {
let iv = iv.copied().unwrap_or([0u8; 16]);
let shared_secret = from.derive_shared_secret(to)?;
let x_coord = shared_secret.x();
let key_material = sha512(&x_coord);
let key_e: [u8; 32] = key_material[0..32].try_into().unwrap();
let key_m = &key_material[32..64];
let ciphertext = aes_256_cbc_encrypt(message, &key_e, &iv)?;
let sender_pubkey = from.public_key().to_compressed();
let mut iv_cipher = Vec::with_capacity(16 + ciphertext.len());
iv_cipher.extend_from_slice(&iv);
iv_cipher.extend_from_slice(&ciphertext);
let mac = sha256_hmac(key_m, &iv_cipher);
let mut encrypted = Vec::with_capacity(33 + 16 + ciphertext.len() + 32);
encrypted.extend_from_slice(&sender_pubkey);
encrypted.extend_from_slice(&iv);
encrypted.extend_from_slice(&ciphertext);
encrypted.extend_from_slice(&mac);
Ok(encrypted)
}
pub fn bitcore_decrypt(data: &[u8], to: &PrivateKey) -> Result<Vec<u8>> {
if data.len() < 97 {
return Err(Error::EciesDecryptionFailed("Data too short".to_string()));
}
let from_pubkey = PublicKey::from_bytes(&data[0..33])?;
let iv_and_ciphertext = &data[33..data.len() - 32];
let mac = &data[data.len() - 32..];
if iv_and_ciphertext.len() < 16 {
return Err(Error::EciesDecryptionFailed(
"Data too short for IV".to_string(),
));
}
let iv: [u8; 16] = iv_and_ciphertext[0..16].try_into().unwrap();
let ciphertext = &iv_and_ciphertext[16..];
let shared_secret = to.derive_shared_secret(&from_pubkey)?;
let x_coord = shared_secret.x();
let key_material = sha512(&x_coord);
let key_e: [u8; 32] = key_material[0..32].try_into().unwrap();
let key_m = &key_material[32..64];
let expected_mac = sha256_hmac(key_m, iv_and_ciphertext);
if !constant_time_eq(mac, &expected_mac) {
return Err(Error::EciesHmacMismatch);
}
aes_256_cbc_decrypt(ciphertext, &key_e, &iv)
}
pub fn encrypt_single(message: &[u8], key: &PrivateKey) -> Result<Vec<u8>> {
electrum_encrypt(message, &key.public_key(), key, false)
}
pub fn decrypt_single(data: &[u8], key: &PrivateKey) -> Result<Vec<u8>> {
electrum_decrypt(data, key, None)
}
pub fn encrypt_single_base64(message: &[u8], key: &PrivateKey) -> Result<String> {
let encrypted = encrypt_single(message, key)?;
Ok(BASE64.encode(&encrypted))
}
pub fn decrypt_single_base64(data: &str, key: &PrivateKey) -> Result<Vec<u8>> {
let encrypted = BASE64
.decode(data)
.map_err(|e| Error::EciesDecryptionFailed(format!("invalid base64: {}", e)))?;
decrypt_single(&encrypted, key)
}
pub fn encrypt_shared_base64(
message: &[u8],
to_public_key: &PublicKey,
from_private_key: &PrivateKey,
) -> Result<String> {
let encrypted = electrum_encrypt(message, to_public_key, from_private_key, false)?;
Ok(BASE64.encode(&encrypted))
}
pub fn decrypt_shared_base64(
data: &str,
to_private_key: &PrivateKey,
from_public_key: &PublicKey,
) -> Result<Vec<u8>> {
let encrypted = BASE64
.decode(data)
.map_err(|e| Error::EciesDecryptionFailed(format!("invalid base64: {}", e)))?;
electrum_decrypt(&encrypted, to_private_key, Some(from_public_key))
}
fn aes_128_cbc_encrypt(data: &[u8], key: &[u8; 16], iv: &[u8; 16]) -> Result<Vec<u8>> {
let cipher = Aes128CbcEnc::new(key.into(), iv.into());
Ok(cipher.encrypt_padded_vec_mut::<Pkcs7>(data))
}
fn aes_128_cbc_decrypt(data: &[u8], key: &[u8; 16], iv: &[u8; 16]) -> Result<Vec<u8>> {
let cipher = Aes128CbcDec::new(key.into(), iv.into());
cipher
.decrypt_padded_vec_mut::<Pkcs7>(data)
.map_err(|_| Error::EciesDecryptionFailed("AES decryption failed".to_string()))
}
fn aes_256_cbc_encrypt(data: &[u8], key: &[u8; 32], iv: &[u8; 16]) -> Result<Vec<u8>> {
let cipher = Aes256CbcEnc::new(key.into(), iv.into());
Ok(cipher.encrypt_padded_vec_mut::<Pkcs7>(data))
}
fn aes_256_cbc_decrypt(data: &[u8], key: &[u8; 32], iv: &[u8; 16]) -> Result<Vec<u8>> {
let cipher = Aes256CbcDec::new(key.into(), iv.into());
cipher
.decrypt_padded_vec_mut::<Pkcs7>(data)
.map_err(|_| Error::EciesDecryptionFailed("AES decryption failed".to_string()))
}
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
use subtle::ConstantTimeEq;
if a.len() != b.len() {
return false;
}
a.ct_eq(b).into()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_electrum_encrypt_decrypt() {
let sender = PrivateKey::random();
let recipient = PrivateKey::random();
let message = b"Hello, ECIES!";
let encrypted = electrum_encrypt(message, &recipient.public_key(), &sender, false).unwrap();
let decrypted =
electrum_decrypt(&encrypted, &recipient, Some(&sender.public_key())).unwrap();
assert_eq!(decrypted, message);
let decrypted2 = electrum_decrypt(&encrypted, &recipient, None).unwrap();
assert_eq!(decrypted2, message);
}
#[test]
fn test_electrum_no_key_mode() {
let sender = PrivateKey::random();
let recipient = PrivateKey::random();
let message = b"Hello, ECIES!";
let encrypted = electrum_encrypt(message, &recipient.public_key(), &sender, true).unwrap();
assert!(encrypted.len() < 4 + 33 + 16 + 32);
let decrypted =
electrum_decrypt(&encrypted, &recipient, Some(&sender.public_key())).unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_bitcore_encrypt_decrypt() {
let sender = PrivateKey::random();
let recipient = PrivateKey::random();
let message = b"Hello, Bitcore ECIES!";
let encrypted = bitcore_encrypt(message, &recipient.public_key(), &sender, None).unwrap();
let decrypted = bitcore_decrypt(&encrypted, &recipient).unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_bitcore_with_fixed_iv() {
let sender = PrivateKey::random();
let recipient = PrivateKey::random();
let message = b"Test message";
let iv = [0u8; 16];
let encrypted1 =
bitcore_encrypt(message, &recipient.public_key(), &sender, Some(&iv)).unwrap();
let encrypted2 =
bitcore_encrypt(message, &recipient.public_key(), &sender, Some(&iv)).unwrap();
assert_eq!(encrypted1, encrypted2);
let decrypted = bitcore_decrypt(&encrypted1, &recipient).unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_encrypt_single() {
let key = PrivateKey::random();
let message = b"Self-encrypted message";
let encrypted = encrypt_single(message, &key).unwrap();
let decrypted = decrypt_single(&encrypted, &key).unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_electrum_empty_message() {
let sender = PrivateKey::random();
let recipient = PrivateKey::random();
let message = b"";
let encrypted = electrum_encrypt(message, &recipient.public_key(), &sender, false).unwrap();
let decrypted = electrum_decrypt(&encrypted, &recipient, None).unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_bitcore_empty_message() {
let sender = PrivateKey::random();
let recipient = PrivateKey::random();
let message = b"";
let encrypted = bitcore_encrypt(message, &recipient.public_key(), &sender, None).unwrap();
let decrypted = bitcore_decrypt(&encrypted, &recipient).unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_electrum_large_message() {
let sender = PrivateKey::random();
let recipient = PrivateKey::random();
let message = vec![0xab; 10000];
let encrypted =
electrum_encrypt(&message, &recipient.public_key(), &sender, false).unwrap();
let decrypted = electrum_decrypt(&encrypted, &recipient, None).unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_bitcore_large_message() {
let sender = PrivateKey::random();
let recipient = PrivateKey::random();
let message = vec![0xcd; 10000];
let encrypted = bitcore_encrypt(&message, &recipient.public_key(), &sender, None).unwrap();
let decrypted = bitcore_decrypt(&encrypted, &recipient).unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_electrum_wrong_recipient() {
let sender = PrivateKey::random();
let recipient = PrivateKey::random();
let wrong_recipient = PrivateKey::random();
let message = b"Secret message";
let encrypted = electrum_encrypt(message, &recipient.public_key(), &sender, false).unwrap();
let result = electrum_decrypt(&encrypted, &wrong_recipient, None);
assert!(matches!(result, Err(Error::EciesHmacMismatch)));
}
#[test]
fn test_bitcore_wrong_recipient() {
let sender = PrivateKey::random();
let recipient = PrivateKey::random();
let wrong_recipient = PrivateKey::random();
let message = b"Secret message";
let encrypted = bitcore_encrypt(message, &recipient.public_key(), &sender, None).unwrap();
let result = bitcore_decrypt(&encrypted, &wrong_recipient);
assert!(matches!(result, Err(Error::EciesHmacMismatch)));
}
#[test]
fn test_electrum_tampered_data() {
let sender = PrivateKey::random();
let recipient = PrivateKey::random();
let message = b"Original message";
let mut encrypted =
electrum_encrypt(message, &recipient.public_key(), &sender, false).unwrap();
let tamper_idx = 40; encrypted[tamper_idx] ^= 0xff;
let result = electrum_decrypt(&encrypted, &recipient, None);
assert!(matches!(result, Err(Error::EciesHmacMismatch)));
}
#[test]
fn test_bitcore_tampered_data() {
let sender = PrivateKey::random();
let recipient = PrivateKey::random();
let message = b"Original message";
let mut encrypted =
bitcore_encrypt(message, &recipient.public_key(), &sender, None).unwrap();
let tamper_idx = 50; encrypted[tamper_idx] ^= 0xff;
let result = bitcore_decrypt(&encrypted, &recipient);
assert!(matches!(result, Err(Error::EciesHmacMismatch)));
}
#[test]
fn test_electrum_invalid_magic() {
let mut data = vec![0u8; 100];
data[0..4].copy_from_slice(b"BAD!");
let recipient = PrivateKey::random();
let result = electrum_decrypt(&data, &recipient, None);
assert!(matches!(result, Err(Error::EciesDecryptionFailed(_))));
}
#[test]
fn test_electrum_too_short() {
let recipient = PrivateKey::random();
let result = electrum_decrypt(&[0u8; 20], &recipient, None);
assert!(matches!(result, Err(Error::EciesDecryptionFailed(_))));
}
#[test]
fn test_bitcore_too_short() {
let recipient = PrivateKey::random();
let result = bitcore_decrypt(&[0u8; 50], &recipient);
assert!(matches!(result, Err(Error::EciesDecryptionFailed(_))));
}
#[test]
fn test_aes_128_cbc_roundtrip() {
let key = [0x01u8; 16];
let iv = [0x02u8; 16];
let plaintext = b"Test AES-128-CBC";
let ciphertext = aes_128_cbc_encrypt(plaintext, &key, &iv).unwrap();
let decrypted = aes_128_cbc_decrypt(&ciphertext, &key, &iv).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_aes_256_cbc_roundtrip() {
let key = [0x03u8; 32];
let iv = [0x04u8; 16];
let plaintext = b"Test AES-256-CBC";
let ciphertext = aes_256_cbc_encrypt(plaintext, &key, &iv).unwrap();
let decrypted = aes_256_cbc_decrypt(&ciphertext, &key, &iv).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_aes_padding() {
let key = [0x05u8; 16];
let iv = [0x06u8; 16];
for len in 0..50 {
let plaintext = vec![0x42u8; len];
let ciphertext = aes_128_cbc_encrypt(&plaintext, &key, &iv).unwrap();
let decrypted = aes_128_cbc_decrypt(&ciphertext, &key, &iv).unwrap();
assert_eq!(decrypted, plaintext);
}
}
#[test]
fn test_known_electrum_vector() {
let sender = PrivateKey::from_hex(
"e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
)
.unwrap();
let recipient = PrivateKey::from_hex(
"0000000000000000000000000000000000000000000000000000000000000001",
)
.unwrap();
let message = b"Hello, BSV!";
let encrypted = electrum_encrypt(message, &recipient.public_key(), &sender, false).unwrap();
assert_eq!(&encrypted[0..4], b"BIE1");
let decrypted = electrum_decrypt(&encrypted, &recipient, None).unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_encrypt_single_base64_roundtrip() {
let key = PrivateKey::random();
let message = b"Hello, self-encryption!";
let encrypted = encrypt_single_base64(message, &key).unwrap();
let decrypted = decrypt_single_base64(&encrypted, &key).unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_encrypt_single_base64_is_valid_base64() {
let key = PrivateKey::random();
let message = b"Test message";
let encrypted = encrypt_single_base64(message, &key).unwrap();
let decoded = BASE64.decode(&encrypted);
assert!(decoded.is_ok());
let bytes = decoded.unwrap();
assert_eq!(&bytes[0..4], b"BIE1");
}
#[test]
fn test_decrypt_single_base64_invalid_base64() {
let key = PrivateKey::random();
let invalid_base64 = "not valid base64!!!";
let result = decrypt_single_base64(invalid_base64, &key);
assert!(matches!(result, Err(Error::EciesDecryptionFailed(_))));
}
#[test]
fn test_encrypt_shared_base64_roundtrip() {
let alice = PrivateKey::random();
let bob = PrivateKey::random();
let message = b"Hello Bob, from Alice!";
let encrypted = encrypt_shared_base64(message, &bob.public_key(), &alice).unwrap();
let decrypted = decrypt_shared_base64(&encrypted, &bob, &alice.public_key()).unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_encrypt_shared_base64_is_valid_base64() {
let alice = PrivateKey::random();
let bob = PrivateKey::random();
let message = b"Test shared message";
let encrypted = encrypt_shared_base64(message, &bob.public_key(), &alice).unwrap();
let decoded = BASE64.decode(&encrypted);
assert!(decoded.is_ok());
let bytes = decoded.unwrap();
assert_eq!(&bytes[0..4], b"BIE1");
}
#[test]
fn test_decrypt_shared_base64_invalid_base64() {
let alice = PrivateKey::random();
let bob = PrivateKey::random();
let invalid_base64 = "!!!invalid!!!";
let result = decrypt_shared_base64(invalid_base64, &bob, &alice.public_key());
assert!(matches!(result, Err(Error::EciesDecryptionFailed(_))));
}
#[test]
fn test_base64_wrappers_empty_message() {
let key = PrivateKey::random();
let message = b"";
let encrypted = encrypt_single_base64(message, &key).unwrap();
let decrypted = decrypt_single_base64(&encrypted, &key).unwrap();
assert_eq!(decrypted, message);
let alice = PrivateKey::random();
let bob = PrivateKey::random();
let encrypted = encrypt_shared_base64(message, &bob.public_key(), &alice).unwrap();
let decrypted = decrypt_shared_base64(&encrypted, &bob, &alice.public_key()).unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_base64_wrappers_large_message() {
let key = PrivateKey::random();
let message = vec![0xab; 10000];
let encrypted = encrypt_single_base64(&message, &key).unwrap();
let decrypted = decrypt_single_base64(&encrypted, &key).unwrap();
assert_eq!(decrypted, message);
let alice = PrivateKey::random();
let bob = PrivateKey::random();
let encrypted = encrypt_shared_base64(&message, &bob.public_key(), &alice).unwrap();
let decrypted = decrypt_shared_base64(&encrypted, &bob, &alice.public_key()).unwrap();
assert_eq!(decrypted, message);
}
#[test]
fn test_base64_wrapper_wrong_key() {
let key1 = PrivateKey::random();
let key2 = PrivateKey::random();
let message = b"Secret message";
let encrypted = encrypt_single_base64(message, &key1).unwrap();
let result = decrypt_single_base64(&encrypted, &key2);
assert!(matches!(result, Err(Error::EciesHmacMismatch)));
}
#[test]
fn test_cross_sdk_electrum_decrypt_known_ciphertext_alice() {
let alice_key = PrivateKey::from_hex(
"77e06abc52bf065cb5164c5deca839d0276911991a2730be4d8d0a0307de7ceb",
)
.unwrap();
let ciphertext_base64 = "QklFMQOGFyMXLo9Qv047K3BYJhmnJgt58EC8skYP/R2QU/U0yXXHOt6L3tKmrXho6yj6phfoiMkBOhUldRPnEI4fSZXbiaH4FsxKIOOvzolIFVAS0FplUmib2HnlAM1yP/iiPsU=";
let ciphertext = BASE64.decode(ciphertext_base64).unwrap();
let plaintext = electrum_decrypt(&ciphertext, &alice_key, None).unwrap();
assert_eq!(
plaintext, b"this is my test message",
"Cross-SDK Electrum ECIES decrypt (alice) plaintext mismatch"
);
}
#[test]
fn test_cross_sdk_electrum_decrypt_known_ciphertext_bob() {
let bob_key = PrivateKey::from_hex(
"2b57c7c5e408ce927eef5e2efb49cfdadde77961d342daa72284bb3d6590862d",
)
.unwrap();
let ciphertext_base64 = "QklFMQM55QTWSSsILaluEejwOXlrBs1IVcEB4kkqbxDz4Fap53XHOt6L3tKmrXho6yj6phfoiMkBOhUldRPnEI4fSZXbvZJHgyAzxA6SoujduvJXv+A9ri3po9veilrmc8p6dwo=";
let ciphertext = BASE64.decode(ciphertext_base64).unwrap();
let plaintext = electrum_decrypt(&ciphertext, &bob_key, None).unwrap();
assert_eq!(
plaintext, b"this is my test message",
"Cross-SDK Electrum ECIES decrypt (bob) plaintext mismatch"
);
}
#[test]
fn test_cross_sdk_electrum_encrypt_deterministic_alice_to_bob() {
let alice_key = PrivateKey::from_hex(
"77e06abc52bf065cb5164c5deca839d0276911991a2730be4d8d0a0307de7ceb",
)
.unwrap();
let bob_key = PrivateKey::from_hex(
"2b57c7c5e408ce927eef5e2efb49cfdadde77961d342daa72284bb3d6590862d",
)
.unwrap();
let message = b"this is my test message";
let encrypted =
electrum_encrypt(message, &bob_key.public_key(), &alice_key, false).unwrap();
let encrypted_base64 = BASE64.encode(&encrypted);
assert_eq!(
encrypted_base64,
"QklFMQM55QTWSSsILaluEejwOXlrBs1IVcEB4kkqbxDz4Fap53XHOt6L3tKmrXho6yj6phfoiMkBOhUldRPnEI4fSZXbvZJHgyAzxA6SoujduvJXv+A9ri3po9veilrmc8p6dwo=",
"Cross-SDK Electrum ECIES encrypt (alice->bob) ciphertext mismatch"
);
}
#[test]
fn test_cross_sdk_electrum_encrypt_deterministic_bob_to_alice() {
let alice_key = PrivateKey::from_hex(
"77e06abc52bf065cb5164c5deca839d0276911991a2730be4d8d0a0307de7ceb",
)
.unwrap();
let bob_key = PrivateKey::from_hex(
"2b57c7c5e408ce927eef5e2efb49cfdadde77961d342daa72284bb3d6590862d",
)
.unwrap();
let message = b"this is my test message";
let encrypted =
electrum_encrypt(message, &alice_key.public_key(), &bob_key, false).unwrap();
let encrypted_base64 = BASE64.encode(&encrypted);
assert_eq!(
encrypted_base64,
"QklFMQOGFyMXLo9Qv047K3BYJhmnJgt58EC8skYP/R2QU/U0yXXHOt6L3tKmrXho6yj6phfoiMkBOhUldRPnEI4fSZXbiaH4FsxKIOOvzolIFVAS0FplUmib2HnlAM1yP/iiPsU=",
"Cross-SDK Electrum ECIES encrypt (bob->alice) ciphertext mismatch"
);
}
#[test]
fn test_cross_sdk_electrum_ecdh_no_key_symmetry() {
let alice_key = PrivateKey::from_hex(
"77e06abc52bf065cb5164c5deca839d0276911991a2730be4d8d0a0307de7ceb",
)
.unwrap();
let bob_key = PrivateKey::from_hex(
"2b57c7c5e408ce927eef5e2efb49cfdadde77961d342daa72284bb3d6590862d",
)
.unwrap();
let message = b"this is my ECDH test message";
let encrypted_bob =
electrum_encrypt(message, &bob_key.public_key(), &alice_key, true).unwrap();
let encrypted_alice =
electrum_encrypt(message, &alice_key.public_key(), &bob_key, true).unwrap();
assert_eq!(
encrypted_bob, encrypted_alice,
"ECDH noKey mode should produce identical ciphertext in both directions"
);
let decrypted_1 =
electrum_decrypt(&encrypted_alice, &bob_key, Some(&alice_key.public_key())).unwrap();
assert_eq!(decrypted_1, message);
let decrypted_2 =
electrum_decrypt(&encrypted_bob, &alice_key, Some(&bob_key.public_key())).unwrap();
assert_eq!(decrypted_2, message);
}
#[test]
fn test_cross_sdk_electrum_roundtrip_with_known_keys() {
let alice_key = PrivateKey::from_hex(
"77e06abc52bf065cb5164c5deca839d0276911991a2730be4d8d0a0307de7ceb",
)
.unwrap();
let bob_key = PrivateKey::from_hex(
"2b57c7c5e408ce927eef5e2efb49cfdadde77961d342daa72284bb3d6590862d",
)
.unwrap();
let message = b"this is my test message";
let encrypted =
electrum_encrypt(message, &bob_key.public_key(), &alice_key, false).unwrap();
let decrypted =
electrum_decrypt(&encrypted, &bob_key, Some(&alice_key.public_key())).unwrap();
assert_eq!(decrypted, message);
let decrypted2 = electrum_decrypt(&encrypted, &bob_key, None).unwrap();
assert_eq!(decrypted2, message);
}
#[test]
fn test_shared_base64_wrapper_wrong_recipient() {
let alice = PrivateKey::random();
let bob = PrivateKey::random();
let charlie = PrivateKey::random();
let message = b"Secret for Bob";
let encrypted = encrypt_shared_base64(message, &bob.public_key(), &alice).unwrap();
let result = decrypt_shared_base64(&encrypted, &charlie, &alice.public_key());
assert!(matches!(result, Err(Error::EciesHmacMismatch)));
}
}