Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor elliptic #107

Merged
merged 1 commit into from
Sep 12, 2023
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: 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
28 changes: 19 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 @@ -45,23 +49,29 @@ parking_lot = "0.12.1"
[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.10", default-features = false, features = ["js"]}
once_cell = {version = "1.18.0", default-features = false, features = ["std"]}
wasm-bindgen = {version = "0.2.87", default-features = false}

[target.'cfg(all(target_arch = "wasm32", not(target_os="unknown")))'.dependencies]
# allows wasm32-wasi to build
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