Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ecies"
version = "0.2.9"
version = "0.2.10"
# docs
authors = ["Weiliang Li <to.be.impressive@gmail.com>"]
description = "Elliptic Curve Integrated Encryption Scheme for secp256k1"
Expand Down Expand Up @@ -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)
Expand All @@ -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",
] }
Expand All @@ -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
Expand Down Expand Up @@ -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",
] }

Expand Down
4 changes: 2 additions & 2 deletions src/elliptic/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SharedSecret, Error> {
pub fn encapsulate(sk: &SecretKey, peer_pk: &PublicKey, _compressed: bool) -> Result<SharedSecret, Error> {
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<SharedSecret, Error> {
pub fn decapsulate(pk: &PublicKey, peer_sk: &SecretKey, _compressed: bool) -> Result<SharedSecret, Error> {
let shared_point = multiply(peer_sk, pk)?;
Ok(hkdf_derive(pk, shared_point.compress().as_bytes()))
}
Expand Down
5 changes: 4 additions & 1 deletion src/elliptic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
);
}
}
40 changes: 19 additions & 21 deletions src/elliptic/secp256k1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<SharedSecret, Error> {
pub fn encapsulate(sk: &SecretKey, peer_pk: &PublicKey, compressed: bool) -> Result<SharedSecret, Error> {
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<SharedSecret, Error> {
pub fn decapsulate(pk: &PublicKey, peer_sk: &SecretKey, compressed: bool) -> Result<SharedSecret, Error> {
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
Expand Down Expand Up @@ -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")))]
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand Down
4 changes: 2 additions & 2 deletions src/elliptic/x25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SharedSecret, Error> {
pub fn encapsulate(sk: &SecretKey, peer_pk: &PublicKey, _compressed: bool) -> Result<SharedSecret, Error> {
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<SharedSecret, Error> {
pub fn decapsulate(pk: &PublicKey, peer_sk: &SecretKey, _compressed: bool) -> Result<SharedSecret, Error> {
let shared_point = peer_sk.diffie_hellman(pk);
Ok(hkdf_derive(pk.as_bytes(), shared_point.as_bytes()))
}
Expand Down
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -39,7 +39,7 @@ pub fn encrypt(receiver_pub: &[u8], msg: &[u8]) -> Result<Vec<u8>, 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();
Expand Down Expand Up @@ -71,6 +71,6 @@ pub fn decrypt(receiver_sec: &[u8], msg: &[u8]) -> Result<Vec<u8>, 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)
}
Loading