diff --git a/CHANGELOG.md b/CHANGELOG.md index f3e386e..c77e204 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.2.10 + +- Add homemade rwlock for `no_std` +- Add `zeroize` feature for x25519/ed25519 +- Add `compressed` argument to encapsulate/decapsulate functions + - Note: this might break a little of client code but can be easily fixed + ## 0.2.9 - Add ed25519 support diff --git a/Cargo.toml b/Cargo.toml index 9a85422..83cf8d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ecies" -version = "0.2.9" +version = "0.2.10" # docs authors = ["Weiliang Li "] description = "Elliptic Curve Integrated Encryption Scheme for secp256k1" @@ -31,7 +31,7 @@ curve25519-dalek = { version = "4.1.3", default-features = false, features = [ x25519-dalek = { version = "2.0.1", default-features = false, features = [ "static_secrets", ], optional = true } -ed25519-dalek = { version = "~2.1.1", default-features = false, optional = true } +ed25519-dalek = { version = "~2.1.1", default-features = false, optional = true } # msrv 1.60 # symmetric ciphers # aes (openssl) @@ -49,7 +49,7 @@ hkdf = { version = "0.12.4", default-features = false } sha2 = { version = "0.10.8", default-features = false } # random number generator -getrandom = { version = "0.2.15", default-features = false } +getrandom = { version = "0.2.16", default-features = false } rand_core = { version = "0.6.4", default-features = false, features = [ "getrandom", ] } @@ -63,9 +63,9 @@ parking_lot = { version = "=0.12.4", optional = true } # msrv 1.64 [target.'cfg(all(target_arch = "wasm32", target_os="unknown"))'.dependencies] # only for js (browser or node). if it's not js, like substrate, it won't build -getrandom = { version = "0.2.15", default-features = false, features = ["js"] } +getrandom = { version = "0.2.16", default-features = false, features = ["js"] } once_cell = { version = "1.21.3", default-features = false, features = ["std"] } -wasm-bindgen = { version = "0.2.100", default-features = false } +wasm-bindgen = { version = "0.2.104", default-features = false } [target.'cfg(all(target_arch = "wasm32", not(target_os="unknown")))'.dependencies] # for wasm32-wasip2 @@ -108,7 +108,7 @@ wasm-bindgen-test = "0.3.54" [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] futures-util = "0.3.31" reqwest = "0.12.23" -tokio = { version = "1.44.1", default-features = false, features = [ +tokio = { version = "1.47.1", default-features = false, features = [ "rt-multi-thread", ] } diff --git a/src/elliptic/ed25519.rs b/src/elliptic/ed25519.rs index 007dd21..5f2024c 100644 --- a/src/elliptic/ed25519.rs +++ b/src/elliptic/ed25519.rs @@ -37,14 +37,14 @@ pub fn generate_keypair() -> (SecretKey, PublicKey) { } /// Calculate a shared symmetric key of our secret key and peer's public key by hkdf -pub fn encapsulate(sk: &SecretKey, peer_pk: &PublicKey) -> Result { +pub fn encapsulate(sk: &SecretKey, peer_pk: &PublicKey, _compressed: bool) -> Result { let sender_point = to_public_key(sk).to_bytes(); let shared_point = multiply(sk, peer_pk)?; Ok(hkdf_derive(&sender_point, shared_point.compress().as_bytes())) } /// Calculate a shared symmetric key of our public key and peer's secret key by hkdf -pub fn decapsulate(pk: &PublicKey, peer_sk: &SecretKey) -> Result { +pub fn decapsulate(pk: &PublicKey, peer_sk: &SecretKey, _compressed: bool) -> Result { let shared_point = multiply(peer_sk, pk)?; Ok(hkdf_derive(pk, shared_point.compress().as_bytes())) } diff --git a/src/elliptic/mod.rs b/src/elliptic/mod.rs index 6e9452f..fe9f2c3 100644 --- a/src/elliptic/mod.rs +++ b/src/elliptic/mod.rs @@ -27,6 +27,9 @@ mod tests { fn test_key_exchange() { let (sk1, pk1) = generate_keypair(); let (sk2, pk2) = generate_keypair(); - assert_eq!(encapsulate(&sk2, &pk1).unwrap(), decapsulate(&pk2, &sk1).unwrap()); + assert_eq!( + encapsulate(&sk2, &pk1, false).unwrap(), + decapsulate(&pk2, &sk1, false).unwrap() + ); } } diff --git a/src/elliptic/secp256k1.rs b/src/elliptic/secp256k1.rs index 5d69cb0..1afdcfc 100644 --- a/src/elliptic/secp256k1.rs +++ b/src/elliptic/secp256k1.rs @@ -3,7 +3,6 @@ use rand_core::OsRng; pub use libsecp256k1::{Error, PublicKey, SecretKey}; use crate::compat::Vec; -use crate::config::is_hkdf_key_compressed; use crate::consts::SharedSecret; use crate::symmetric::hkdf_derive; @@ -15,20 +14,18 @@ pub fn generate_keypair() -> (SecretKey, PublicKey) { } /// Calculate a shared symmetric key of our secret key and peer's public key by hkdf -pub fn encapsulate(sk: &SecretKey, peer_pk: &PublicKey) -> Result { +pub fn encapsulate(sk: &SecretKey, peer_pk: &PublicKey, compressed: bool) -> Result { let mut shared_point = *peer_pk; shared_point.tweak_mul_assign(sk)?; let sender_point = &PublicKey::from_secret_key(sk); - // TODO: move compressed: bool to arg - Ok(get_shared_secret(sender_point, &shared_point, is_hkdf_key_compressed())) + Ok(get_shared_secret(sender_point, &shared_point, compressed)) } /// Calculate a shared symmetric key of our public key and peer's secret key by hkdf -pub fn decapsulate(pk: &PublicKey, peer_sk: &SecretKey) -> Result { +pub fn decapsulate(pk: &PublicKey, peer_sk: &SecretKey, compressed: bool) -> Result { let mut shared_point = *pk; shared_point.tweak_mul_assign(peer_sk)?; - // TODO: move compressed: bool to arg - Ok(get_shared_secret(pk, &shared_point, is_hkdf_key_compressed())) + Ok(get_shared_secret(pk, &shared_point, compressed)) } /// Parse secret key bytes @@ -105,9 +102,14 @@ mod known_tests { let pk3 = PublicKey::from_secret_key(&sk3); assert_eq!( - encapsulate(&sk2, &pk3).unwrap().to_vec(), + encapsulate(&sk2, &pk3, false).unwrap().to_vec(), decode_hex("6f982d63e8590c9d9b5b4c1959ff80315d772edd8f60287c9361d548d5200f82") ); + + assert_eq!( + encapsulate(&sk2, &pk3, true).unwrap().to_vec(), + decode_hex("b192b226edb3f02da11ef9c6ce4afe1c7e40be304e05ae3b988f4834b1cb6c69") + ); } #[cfg(all(not(feature = "xchacha20"), not(feature = "aes-short-nonce")))] @@ -212,32 +214,28 @@ mod error_tests { #[cfg(test)] mod config_tests { - use super::known_tests::get_sk; - use super::{encapsulate, generate_keypair, PublicKey}; + use super::generate_keypair; use crate::config::{reset_config, update_config, Config}; - use crate::utils::tests::decode_hex; - use crate::{decrypt, encrypt}; + use crate::{decrypt, encrypt, Error}; const MSG: &str = "helloworld🌍"; #[test] - pub fn test_known_hkdf_config() { - let sk2 = get_sk(2); - let sk3 = get_sk(3); - let pk3 = PublicKey::from_secret_key(&sk3); + pub fn test_hkdf_key_config() { + let (sk, pk) = generate_keypair(); + let (sk, pk) = (&sk.serialize(), &pk.serialize_compressed()); update_config(Config { is_hkdf_key_compressed: true, ..Config::default() }); - assert_eq!( - encapsulate(&sk2, &pk3).unwrap().to_vec(), - decode_hex("b192b226edb3f02da11ef9c6ce4afe1c7e40be304e05ae3b988f4834b1cb6c69") - ); + let encrypted = encrypt(pk, MSG.as_bytes()).unwrap(); + assert_eq!(MSG.as_bytes(), &decrypt(sk, &encrypted).unwrap()); reset_config(); + assert_eq!(decrypt(sk, &encrypted).unwrap_err(), Error::InvalidMessage); } #[test] @@ -277,8 +275,8 @@ mod wasm_tests { #[wasm_bindgen_test] fn test_config() { + super::config_tests::test_hkdf_key_config(); super::config_tests::test_ephemeral_key_config(); - super::config_tests::test_known_hkdf_config(); } #[wasm_bindgen_test] diff --git a/src/elliptic/x25519.rs b/src/elliptic/x25519.rs index c423be6..967715d 100644 --- a/src/elliptic/x25519.rs +++ b/src/elliptic/x25519.rs @@ -30,14 +30,14 @@ pub fn generate_keypair() -> (SecretKey, PublicKey) { } /// Calculate a shared symmetric key of our secret key and peer's public key by hkdf -pub fn encapsulate(sk: &SecretKey, peer_pk: &PublicKey) -> Result { +pub fn encapsulate(sk: &SecretKey, peer_pk: &PublicKey, _compressed: bool) -> Result { let shared_point = sk.diffie_hellman(peer_pk); let sender_point = PublicKey::from(sk); Ok(hkdf_derive(sender_point.as_bytes(), shared_point.as_bytes())) } /// Calculate a shared symmetric key of our public key and peer's secret key by hkdf -pub fn decapsulate(pk: &PublicKey, peer_sk: &SecretKey) -> Result { +pub fn decapsulate(pk: &PublicKey, peer_sk: &SecretKey, _compressed: bool) -> Result { let shared_point = peer_sk.diffie_hellman(pk); Ok(hkdf_derive(pk.as_bytes(), shared_point.as_bytes())) } diff --git a/src/lib.rs b/src/lib.rs index c2bc31c..1f319d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ mod elliptic; #[cfg(not(feature = "std"))] mod sync; -use config::{get_ephemeral_key_size, is_ephemeral_key_compressed}; +use config::{get_ephemeral_key_size, is_ephemeral_key_compressed, is_hkdf_key_compressed}; use elliptic::{decapsulate, encapsulate, generate_keypair, parse_pk, parse_sk, pk_to_vec, Error}; use symmetric::{sym_decrypt, sym_encrypt}; @@ -39,7 +39,7 @@ pub fn encrypt(receiver_pub: &[u8], msg: &[u8]) -> Result, Error> { let receiver_pk = parse_pk(receiver_pub)?; let (ephemeral_sk, ephemeral_pk) = generate_keypair(); - let sym_key = encapsulate(&ephemeral_sk, &receiver_pk)?; + let sym_key = encapsulate(&ephemeral_sk, &receiver_pk, is_hkdf_key_compressed())?; let encrypted = sym_encrypt(&sym_key, msg).ok_or(Error::InvalidMessage)?; let is_compressed = is_ephemeral_key_compressed(); @@ -71,6 +71,6 @@ pub fn decrypt(receiver_sec: &[u8], msg: &[u8]) -> Result, Error> { let ephemeral_pk = parse_pk(&msg[..key_size])?; let encrypted = &msg[key_size..]; - let sym_key = decapsulate(&ephemeral_pk, &receiver_sk)?; + let sym_key = decapsulate(&ephemeral_pk, &receiver_sk, is_hkdf_key_compressed())?; sym_decrypt(&sym_key, encrypted).ok_or(Error::InvalidMessage) }