Skip to content

Commit

Permalink
Refactor elliptic
Browse files Browse the repository at this point in the history
  • Loading branch information
kigawas committed Sep 11, 2023
1 parent 196729c commit 212852e
Show file tree
Hide file tree
Showing 12 changed files with 423 additions and 301 deletions.
7 changes: 6 additions & 1 deletion .cspell.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
"bindgen",
"chacha",
"Codacy",
"Codecov",
"consts",
"Ctarget",
"dalek",
"diffie",
"docsrs",
"ecies",
"eciespy",
Expand All @@ -32,7 +35,9 @@
// flagWords - list of words to be always considered incorrect
// This is useful for offensive words and common spelling errors.
// For example "hte" should be "the"
"flagWords": ["hte"],
"flagWords": [
"hte"
],
"ignorePaths": [
".git",
".github",
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:

- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.toolchain }}
toolchain: stable
target: wasm32-unknown-unknown

- run: cargo generate-lockfile
Expand Down
27 changes: 18 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
[package]
name = "ecies"
version = "0.2.6"
version = "0.2.7"
# docs
authors = ["Weiliang Li <[email protected]>"]
description = "Elliptic Curve Integrated Encryption Scheme for secp256k1 in Rust"
description = "Elliptic Curve Integrated Encryption Scheme for secp256k1/curve25519"
edition = "2021"
keywords = [
"secp256k1",
"curve25519",
"crypto",
"ecc",
"ecies",
Expand All @@ -21,11 +22,14 @@ repository = "https://github.com/ecies/rs"

[dependencies]
hkdf = {version = "0.12.3", default-features = false}
libsecp256k1 = {version = "0.7.1", default-features = false, features = ["static-context"]}
sha2 = {version = "0.10.7", default-features = false}

# elliptic curves
libsecp256k1 = {version = "0.7.1", default-features = false, features = ["static-context"], optional = true}
x25519-dalek = {version = "2.0.0", default-features = false, features = ["static_secrets"], optional = true}

# openssl aes
openssl = {version = "0.10.56", default-features = false, optional = true}
openssl = {version = "0.10.57", default-features = false, optional = true}

# pure rust aes
aes-gcm = {version = "0.10.2", default-features = false, optional = true}
Expand All @@ -52,16 +56,21 @@ wasm-bindgen = {version = "0.2.87", default-features = false}
once_cell = {version = "1.18.0", default-features = false, features = ["std"]}

[features]
default = ["openssl"]
std = ["hkdf/std", "sha2/std", "once_cell/std"]

default = ["openssl"]
# curve
secp256k1 = ["libsecp256k1"]
x25519 = ["x25519-dalek"]

aes-12bytes-nonce = [] # with feature "openssl" or "pure". default: 16 bytes
pure = ["aes-gcm/aes", "typenum"]
xchacha20 = ["chacha20poly1305"]
# cipher
aes-12bytes-nonce = ["secp256k1"] # with feature "openssl" or "pure". default: 16 bytes
openssl = ["dep:openssl", "secp256k1"]
pure = ["aes-gcm/aes", "typenum", "secp256k1"]
xchacha20 = ["chacha20poly1305", "secp256k1"]

[dev-dependencies]
criterion = {version = "0.5.1", default-features = false}
criterion = {version = "0.4.0", default-features = false}
hex = {version = "0.4.3", default-features = false, features = ["alloc"]}

[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ use ecies::{decrypt, encrypt, utils::generate_keypair};

const MSG: &str = "helloworld🌍";
let (sk, pk) = generate_keypair();
#[cfg(not(feature = "x25519"))]
let (sk, pk) = (&sk.serialize(), &pk.serialize());
#[cfg(feature = "x25519")]
let (sk, pk) = (sk.as_bytes(), pk.as_bytes());

let msg = MSG.as_bytes();
assert_eq!(
Expand Down
76 changes: 8 additions & 68 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use once_cell::sync::Lazy;
use parking_lot::RwLock;

use crate::consts::{COMPRESSED_PUBLIC_KEY_SIZE, UNCOMPRESSED_PUBLIC_KEY_SIZE};

/// ECIES config. Make sure all parties use the same config
#[derive(Default)]
pub struct Config {
Expand Down Expand Up @@ -32,81 +30,23 @@ pub fn is_ephemeral_key_compressed() -> bool {
}

/// Get ephemeral key size: compressed(33) or uncompressed(65)
#[cfg(not(feature = "x25519"))]
pub fn get_ephemeral_key_size() -> usize {
use crate::consts::{COMPRESSED_PUBLIC_KEY_SIZE, UNCOMPRESSED_PUBLIC_KEY_SIZE};

if is_ephemeral_key_compressed() {
COMPRESSED_PUBLIC_KEY_SIZE
} else {
UNCOMPRESSED_PUBLIC_KEY_SIZE
}
}

#[cfg(feature = "x25519")]
pub fn get_ephemeral_key_size() -> usize {
32
}

/// Get hkdf key derived from compressed shared point or not
pub fn is_hkdf_key_compressed() -> bool {
ECIES_CONFIG.read().is_hkdf_key_compressed
}

#[cfg(test)]
mod tests {
use super::{reset_config, update_config, Config};
use libsecp256k1::PublicKey;

use crate::{
decrypt, encrypt,
utils::{
encapsulate, generate_keypair,
tests::{decode_hex, get_sk2_sk3},
},
};

const MSG: &str = "helloworld🌍";

#[test]
pub(super) fn test_known_hkdf_config() {
let (sk2, sk3) = get_sk2_sk3();
let pk3 = PublicKey::from_secret_key(&sk3);

update_config(Config {
is_hkdf_key_compressed: true,
..Config::default()
});

let encapsulated = encapsulate(&sk2, &pk3).unwrap();

assert_eq!(
encapsulated.to_vec(),
decode_hex("b192b226edb3f02da11ef9c6ce4afe1c7e40be304e05ae3b988f4834b1cb6c69")
);

reset_config();
}

#[test]
pub(super) fn test_ephemeral_key_config() {
let (sk, pk) = generate_keypair();
let (sk, pk) = (&sk.serialize(), &pk.serialize_compressed());
let encrypted_1 = encrypt(pk, MSG.as_bytes()).unwrap();
assert_eq!(MSG.as_bytes(), &decrypt(sk, &encrypted_1).unwrap());

update_config(Config {
is_ephemeral_key_compressed: true,
..Config::default()
});

let encrypted_2 = encrypt(pk, MSG.as_bytes()).unwrap();
assert_eq!(encrypted_1.len() - encrypted_2.len(), 32);
assert_eq!(MSG.as_bytes(), &decrypt(sk, &encrypted_2).unwrap());

reset_config();
}
}

#[cfg(all(test, target_arch = "wasm32"))]
mod wasm_tests {
use wasm_bindgen_test::*;

#[wasm_bindgen_test]
fn test_wasm() {
super::tests::test_ephemeral_key_config();
super::tests::test_known_hkdf_config();
}
}
3 changes: 3 additions & 0 deletions src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ pub const NONCE_TAG_LENGTH: usize = NONCE_LENGTH + AEAD_TAG_LENGTH;

/// Empty bytes array
pub const EMPTY_BYTES: [u8; 0] = [];

/// Shared secret derived from key exchange by hkdf
pub type SharedSecret = [u8; 32];
50 changes: 50 additions & 0 deletions src/elliptic/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use hkdf::Hkdf;
use sha2::Sha256;

use crate::compat::Vec;
use crate::consts::{SharedSecret, EMPTY_BYTES};

#[cfg(not(feature = "x25519"))]
mod secp256k1;
#[cfg(not(feature = "x25519"))]
pub use secp256k1::{decapsulate, encapsulate, generate_keypair};
#[cfg(not(feature = "x25519"))]
pub(crate) use secp256k1::{parse_pk, parse_sk, pk_to_vec, Error};

#[cfg(feature = "x25519")]
mod x25519;
#[cfg(feature = "x25519")]
pub use x25519::{decapsulate, encapsulate, generate_keypair};
#[cfg(feature = "x25519")]
pub(crate) use x25519::{parse_pk, parse_sk, pk_to_vec, Error};

fn hkdf_derive(sender_point: &[u8], shared_point: &[u8]) -> SharedSecret {
let size = sender_point.len() + shared_point.len();
let mut master = Vec::with_capacity(size);
master.extend(sender_point);
master.extend(shared_point);
hkdf_sha256(&master)
}

fn hkdf_sha256(master: &[u8]) -> SharedSecret {
let h = Hkdf::<Sha256>::new(None, master);
let mut out = [0u8; 32];
// never fails because 32 < 255 * chunk_len, which is 32 on SHA256
h.expand(&EMPTY_BYTES, &mut out).unwrap();
out
}

#[cfg(test)]
mod tests {
use super::hkdf_sha256;

use crate::utils::tests::decode_hex;

#[test]
fn test_known_hkdf_vector() {
assert_eq!(
hkdf_sha256(b"secret").to_vec(),
decode_hex("2f34e5ff91ec85d53ca9b543683174d0cf550b60d5f52b24c97b386cfcf6cbbf")
);
}
}
Loading

0 comments on commit 212852e

Please sign in to comment.