# ecmqv-rs

[![crates.io](https://img.shields.io/crates/v/ecmqv-rs.svg)](https://crates.io/crates/ecmqv-rs)
[![Documentation](https://docs.rs/ecmqv-rs/badge.svg)](https://docs.rs/ecmqv-rs)
[![License: MIT/Apache-2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue)](LICENSE)

A **pure Rust** implementation of the **ECMQV** (Elliptic Curve Menezes‑Qu‑Vanstone) key exchange protocol, following **RFC 5656** and **draft‑sullivan‑tls‑ecmqv‑01**.  
It is `no_std` compatible and designed with constant‑time operations and automatic zeroization of secrets.

---

## Features

- **Two key‑exchange modes**:
  - `EphemeralOnly` – ephemeral keys only; provides perfect forward secrecy without authentication.
  - `EphemeralStatic` – ephemeral + static keys; full ECMQV with mutual authentication and forward secrecy.

- **Three elliptic curves** (feature‑gated):
  - `Curve25519` (enabled by default)
  - `secp256k1`
  - `secp256r1` (NIST P‑256)

- **Built‑in HKDF key derivation**:
  - Supports `SHA‑256`, `SHA‑512`, and `BLAKE3`.
  - Domain separation via `SharedInfo` to prevent cross‑protocol attacks.

- **Security‑first**:
  - Constant‑time operations using [`subtle`](https://crates.io/crates/subtle).
  - Secrets (scalars) are automatically zeroized on drop ([`zeroize`](https://crates.io/crates/zeroize)).
  - Public keys are validated (point on curve, not infinity, correct subgroup) to prevent small‑subgroup attacks.

- **`no_std` compatible** – ideal for embedded systems, WebAssembly, and bare‑metal environments.

- **Optional async support** – provides an asynchronous handshake helper (requires `async` feature).

---

## Usage

Add this to your `Cargo.toml`:

```toml
[dependencies]
ecmqv-rs = "0.1.0"
```

By default, only `Curve25519` is enabled. To use other curves or features, see [Crate Features](#crate-features) below.

---

## Example: Ephemeral‑only Handshake

```rust
use ecmqv_rs::{EphemeralOnly, KeyExchange, CurveParameters, KdfParams, SharedInfo, HashFunction};
use rand::rngs::OsRng;

fn main() -> Result<(), ecmqv_rs::AgreementError> {
    let mut rng = OsRng;
    let params = CurveParameters::Curve25519;

    // Generate ephemeral keys for both parties
    let (alice_eph, alice_pub) = EphemeralOnly::generate_ephemeral(&mut rng, &params)?;
    let (bob_eph, bob_pub) = EphemeralOnly::generate_ephemeral(&mut rng, &params)?;

    let alice = EphemeralOnly::new(alice_eph, params);
    let bob = EphemeralOnly::new(bob_eph, params);

    let info = SharedInfo {
        id_a: b"alice",
        id_b: b"bob",
        other_data: b"session123",
    };
    let kdf = KdfParams {
        hash_fn: HashFunction::Sha256,
        output_len: 32,
        label: None,
        salt: None,
    };

    let alice_secret = alice.agree(&bob_pub, None, &info, kdf.clone())?;
    let bob_secret = bob.agree(&alice_pub, None, &info, kdf)?;

    assert_eq!(alice_secret.as_slice(), bob_secret.as_slice());
    Ok(())
}
```

For authenticated exchange using static keys, use `EphemeralStatic`:

```rust
use ecmqv_rs::{EphemeralStatic, StaticKey, KeyExchange, CurveParameters, KdfParams, SharedInfo, HashFunction};
use rand::rngs::OsRng;

let mut rng = OsRng;
let params = CurveParameters::Curve25519;

// Load or generate static keys (e.g., from certificates)
let alice_static = StaticKey::from_seed(&[42u8; 32], &mut rng, params)?;
let bob_static = StaticKey::from_seed(&[43u8; 32], &mut rng, params)?;
let alice_static_pub = alice_static.public();
let bob_static_pub = bob_static.public();

let (alice_eph, alice_eph_pub) = EphemeralStatic::generate_ephemeral(&mut rng, &params)?;
let (bob_eph, bob_eph_pub) = EphemeralStatic::generate_ephemeral(&mut rng, &params)?;

let alice = EphemeralStatic::new(alice_eph, alice_static, params)?;
let bob = EphemeralStatic::new(bob_eph, bob_static, params)?;

let info = SharedInfo { id_a: b"alice", id_b: b"bob", other_data: b"session123" };
let kdf = KdfParams { hash_fn: HashFunction::Sha256, output_len: 32, label: None, salt: None };

let alice_secret = alice.agree(&bob_eph_pub, Some(&bob_static_pub), &info, kdf.clone())?;
let bob_secret = bob.agree(&alice_eph_pub, Some(&alice_static_pub), &info, kdf)?;
assert_eq!(alice_secret.as_slice(), bob_secret.as_slice());
```

---

## Crate Features

| Feature       | Description                                    | Default |
|---------------|------------------------------------------------|---------|
| `curve25519`  | Enable Curve25519 (Edwards25519) support       | yes     |
| `secp256k1`   | Enable secp256k1 curve                         | no      |
| `secp256r1`   | Enable secp256r1 (NIST P‑256) curve            | no      |
| `async`       | Enable asynchronous handshake helpers          | no      |
| `std`         | Enable standard library (error traits, etc.)   | yes     |
| `serde`       | Enable serialization/deserialization of keys   | no      |

> **Note about hashing**: If you disable default features, you must explicitly enable at least one of the `sha2` or `blake3` features to use the HKDF. `HashFunction::Sha256` and `Sha512` require the `sha2` feature; `HashFunction::Blake3` requires `blake3`.

To use the library in a `no_std` environment, disable default features and enable only the curves you need, along with `alloc`:

```toml
[dependencies.ecmqv-rs]
version = "0.1.0"
default-features = false
features = ["alloc", "curve25519"]   # or "secp256k1", "secp256r1"
```

---

## Performance

Benchmarks (Curve25519, SHA-256 KDF) on a typical machine:

| Operation                          | Time (μs) |
|------------------------------------|-----------|
| Key generation (ephemeral)         | ~12       |
| EphemeralOnly full handshake       | ~200      |
| EphemeralStatic full handshake     | ~350      |

Run the benchmarks yourself with:

```bash
cargo bench
```

Detailed HTML reports are generated in `target/criterion/report/index.html`.

---

## Security Considerations

- **Authentication**: This library does **not** validate certificates. The application must ensure that static public keys are authentic (e.g., via a PKI or out‑of‑band verification).

- **Domain separation**: Always provide unique `id_a` and `id_b` in `SharedInfo` (e.g., certificate hashes or DER‑encoded names) and include session‑specific `other_data` to prevent unknown key‑share attacks.

- **Secret zeroization**: All secret scalars are automatically zeroized when they go out of scope. Sensitive byte vectors returned by the KDF are wrapped in `Zeroizing<Vec<u8>>`.

- **Constant‑time**: Arithmetic operations are implemented using constant‑time techniques where possible. The library relies on underlying curve implementations that are themselves constant‑time.

---

## License

Licensed under either of

- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)

at your option.

## Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache‑2.0 license, shall be dual licensed as above, without any additional terms or conditions.
```
