From 1d61e1ba46e40575688cb2c664329d1d0125eb14 Mon Sep 17 00:00:00 2001 From: Peat Bakke Date: Wed, 5 Jun 2019 12:20:02 -0700 Subject: [PATCH 1/7] Add .to_bytes() to PublicKey, so that it has similar capabilities to the ed25519 PublicKey impl. Also to SharedSecret for consistency. --- src/x25519.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/x25519.rs b/src/x25519.rs index 30f5bdb..5fcc08c 100644 --- a/src/x25519.rs +++ b/src/x25519.rs @@ -36,6 +36,12 @@ impl From<[u8; 32]> for PublicKey { } impl PublicKey { + /// Convert this public key to a byte array. + #[inline] + pub fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes() + } + /// View this public key as a byte array. #[inline] pub fn as_bytes(&self) -> &[u8; 32] { @@ -149,6 +155,12 @@ impl Drop for SharedSecret { } impl SharedSecret { + /// Convert this shared secret to a byte array. + #[inline] + pub fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes() + } + /// View this shared secret key as a byte array. #[inline] pub fn as_bytes(&self) -> &[u8; 32] { From bdc6412faa1fbbf06be41f063ae34becbbe7682d Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Tue, 25 Feb 2020 12:09:40 -0800 Subject: [PATCH 2/7] README.md: Add "See also" section with link to `crypto_box` crate The `crypto_box` crate provides a pure Rust implementation of the public-key authenticated encryption primitive from NaCl which combines X25519 + XSalsa20Poly1305 (a.k.a. "Curve25519XSalsa20Poly1305") This commit adds a link to it case x25519-dalek users are interested in using it as part of a hybrid cryptosystem. --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index be90d5a..ff34263 100644 --- a/README.md +++ b/README.md @@ -105,3 +105,11 @@ attempt to prevent software side-channels. copyright © Amy Wibowo ([@sailorhg](https://twitter.com/sailorhg)) [rfc7748]: https://tools.ietf.org/html/rfc7748 + +# See also + +- [crypto_box]: pure Rust public-key authenticated encryption compatible with + the NaCl family of encryption libraries (libsodium, TweetNaCl) which uses + `x25519-dalek` for key agreement + +[crypto_box]: https://github.com/RustCrypto/AEADs/tree/master/crypto_box From be420d4ffce2c45e4ba1d3e046cdc7cb8e731c7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Wed, 4 Mar 2020 22:35:33 -0500 Subject: [PATCH 3/7] Bump criterion version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e86eb01..a092ec0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ zeroize = { version = "1", default-features = false, features = ["zeroize_derive [dev-dependencies] bincode = "1" -criterion = "0.2" +criterion = "0.3.0" [[bench]] name = "x25519" From 5d91bd8f22f8316c114a313404975629f337f1e4 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Mon, 17 Aug 2020 18:44:48 -0700 Subject: [PATCH 4/7] Make bound on `csprng` more general. `rand_core` defines a blanket impl of `RngCore + CryptoRng` for `&mut T` where `T: RngCore + CryptoRng`, so rather than requiring a borrowed RNG, it's better to require an owned RNG, as this allows passing either owned or borrowed values. In particular, this makes `OsRng` usage much more ergonomic, because the caller is not forced to do `&mut OsRng` on the zero-sized struct. --- src/x25519.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/x25519.rs b/src/x25519.rs index e95d0e3..6b18781 100644 --- a/src/x25519.rs +++ b/src/x25519.rs @@ -62,10 +62,7 @@ impl EphemeralSecret { } /// Generate an x25519 `EphemeralSecret` key. - pub fn new(csprng: &mut T) -> Self - where - T: RngCore + CryptoRng, - { + pub fn new(mut csprng: T) -> Self { let mut bytes = [0u8; 32]; csprng.fill_bytes(&mut bytes); @@ -104,10 +101,7 @@ impl StaticSecret { } /// Generate a x25519 `StaticSecret` key. - pub fn new(csprng: &mut T) -> Self - where - T: RngCore + CryptoRng, - { + pub fn new(mut csprng: T) -> Self { let mut bytes = [0u8; 32]; csprng.fill_bytes(&mut bytes); From 8287798aa12ef2147f785429a8f7325a4f2f0513 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Mon, 17 Aug 2020 18:47:31 -0700 Subject: [PATCH 5/7] Update doc examples to remove deprecated code and restore testing. Closes #59. The doc examples have code interspersed with text explaining the API. Because each doctest executes independently, when these code examples are run as doctests, they have to include parts of the previous examples with # lines. These lines are hidden from Rustdoc output and do not appear in the rendered docs, but they do appear when viewing the README.md on Github. In order to hide these on Github, the code blocks were made non-executable, with their content moved to a unit test. However, this meant that the example API usage was not tested, and so when the unit test was updated to remove the deprecated `rand_os`, there was no check that the examples stayed in sync with the test, causing #59. To prevent this from reocurring in the future, go back to executable tests of the API examples. --- README.md | 51 +++++++++++++++++++++++++++++++++------------------ src/x25519.rs | 15 --------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index ff34263..28289be 100644 --- a/README.md +++ b/README.md @@ -31,45 +31,60 @@ the rest of the afternoon nomming some yummy pie! First, Alice uses `EphemeralSecret::new()` and then `PublicKey::from()` to produce her secret and public keys: -```rust,ignore -extern crate rand_os; -extern crate x25519_dalek; +```rust +use rand_core::OsRng; +use x25519_dalek::{EphemeralSecret, PublicKey}; -use rand_os::OsRng; - -use x25519_dalek::EphemeralSecret; -use x25519_dalek::PublicKey; - -let mut alice_csprng = OsRng::new().unwrap(); -let alice_secret = EphemeralSecret::new(&mut alice_csprng); -let alice_public = PublicKey::from(&alice_secret); +let alice_secret = EphemeralSecret::new(OsRng); +let alice_public = PublicKey::from(&alice_secret); ``` Bob does the same: -```rust,ignore -let mut bob_csprng = OsRng::new().unwrap(); -let bob_secret = EphemeralSecret::new(&mut bob_csprng); -let bob_public = PublicKey::from(&bob_secret); +```rust +# use rand_core::OsRng; +# use x25519_dalek::{EphemeralSecret, PublicKey}; +let bob_secret = EphemeralSecret::new(OsRng); +let bob_public = PublicKey::from(&bob_secret); ``` Alice meows across the room, telling `alice_public` to Bob, and Bob loudly meows `bob_public` back to Alice. Alice now computes her shared secret with Bob by doing: -```rust,ignore +```rust +# use rand_core::OsRng; +# use x25519_dalek::{EphemeralSecret, PublicKey}; +# let alice_secret = EphemeralSecret::new(OsRng); +# let alice_public = PublicKey::from(&alice_secret); +# let bob_secret = EphemeralSecret::new(OsRng); +# let bob_public = PublicKey::from(&bob_secret); let alice_shared_secret = alice_secret.diffie_hellman(&bob_public); ``` Similarly, Bob computes a shared secret by doing: -```rust,ignore +```rust +# use rand_core::OsRng; +# use x25519_dalek::{EphemeralSecret, PublicKey}; +# let alice_secret = EphemeralSecret::new(OsRng); +# let alice_public = PublicKey::from(&alice_secret); +# let bob_secret = EphemeralSecret::new(OsRng); +# let bob_public = PublicKey::from(&bob_secret); let bob_shared_secret = bob_secret.diffie_hellman(&alice_public); ``` These secrets are the same: -```rust,ignore +```rust +# use rand_core::OsRng; +# use x25519_dalek::{EphemeralSecret, PublicKey}; +# let alice_secret = EphemeralSecret::new(OsRng); +# let alice_public = PublicKey::from(&alice_secret); +# let bob_secret = EphemeralSecret::new(OsRng); +# let bob_public = PublicKey::from(&bob_secret); +# let alice_shared_secret = alice_secret.diffie_hellman(&bob_public); +# let bob_shared_secret = bob_secret.diffie_hellman(&alice_public); assert_eq!(alice_shared_secret.as_bytes(), bob_shared_secret.as_bytes()); ``` diff --git a/src/x25519.rs b/src/x25519.rs index 6b18781..8866cd5 100644 --- a/src/x25519.rs +++ b/src/x25519.rs @@ -199,21 +199,6 @@ mod test { use rand_core::OsRng; - // This was previously a doctest but it got moved to the README to - // avoid duplication where it then wasn't being run, so now it - // lives here. - #[test] - fn alice_and_bob() { - let alice_secret = EphemeralSecret::new(&mut OsRng); - let alice_public = PublicKey::from(&alice_secret); - let bob_secret = EphemeralSecret::new(&mut OsRng); - let bob_public = PublicKey::from(&bob_secret); - let alice_shared_secret = alice_secret.diffie_hellman(&bob_public); - let bob_shared_secret = bob_secret.diffie_hellman(&alice_public); - - assert_eq!(alice_shared_secret.as_bytes(), bob_shared_secret.as_bytes()); - } - #[test] fn byte_basepoint_matches_edwards_scalar_mul() { let mut scalar_bytes = [0x37; 32]; From fb92cd82da40bf97aa905eed413794e27069c6e3 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Mon, 17 Aug 2020 19:24:49 -0700 Subject: [PATCH 6/7] Clarify Ephemeral/StaticSecret docs. Also does a pass through the docs converting `TypeNames` to Rustdoc links, and making sure that all the items have consistent summaries. Closes #58 Closes $56 --- src/x25519.rs | 53 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/src/x25519.rs b/src/x25519.rs index 0d6b557..9fbda07 100644 --- a/src/x25519.rs +++ b/src/x25519.rs @@ -23,8 +23,7 @@ use rand_core::RngCore; use zeroize::Zeroize; -/// A `PublicKey` is the corresponding public key converted from -/// an `EphemeralSecret` or a `StaticSecret` key. +/// A Diffie-Hellman public key, corresponding to an [`EphemeralSecret`] or [`StaticSecret`] key. #[cfg_attr(feature = "serde", serde(crate = "our_serde"))] #[cfg_attr( feature = "serde", @@ -54,20 +53,26 @@ impl PublicKey { } } -/// A `EphemeralSecret` is a short lived Diffie-Hellman secret key -/// used to create a `SharedSecret` when given their `PublicKey`. +/// A short-lived Diffie-Hellman secret key that can only be used to compute a single +/// [`SharedSecret`]. +/// +/// This type is identical to the [`StaticSecret`] type, except that the +/// [`EphemeralSecret::diffie_hellman`] method consumes and then wipes the secret key, and there +/// are no serialization methods defined. This means that [`EphemeralSecret`]s can only be +/// generated from fresh randomness by [`EphemeralSecret::new`] and the compiler statically checks +/// that the resulting secret is used at most once. #[derive(Zeroize)] #[zeroize(drop)] pub struct EphemeralSecret(pub(crate) Scalar); impl EphemeralSecret { /// Perform a Diffie-Hellman key agreement between `self` and - /// `their_public` key to produce a `SharedSecret`. + /// `their_public` key to produce a [`SharedSecret`]. pub fn diffie_hellman(self, their_public: &PublicKey) -> SharedSecret { SharedSecret(self.0 * their_public.0) } - /// Generate an x25519 `EphemeralSecret` key. + /// Generate an x25519 [`EphemeralSecret`] key. pub fn new(mut csprng: T) -> Self { let mut bytes = [0u8; 32]; @@ -78,16 +83,27 @@ impl EphemeralSecret { } impl<'a> From<&'a EphemeralSecret> for PublicKey { - /// Given an x25519 `EphemeralSecret` key, compute its corresponding - /// `PublicKey` key. + /// Given an x25519 [`EphemeralSecret`] key, compute its corresponding [`PublicKey`]. fn from(secret: &'a EphemeralSecret) -> PublicKey { PublicKey((&ED25519_BASEPOINT_TABLE * &secret.0).to_montgomery()) } } -/// A `StaticSecret` is a static Diffie-Hellman secret key that -/// can be saved and loaded to create a `SharedSecret` when given -/// their `PublicKey`. +/// A Diffie-Hellman secret key that can be used to compute multiple [`SharedSecret`]s. +/// +/// This type is identical to the [`EphemeralSecret`] type, except that the +/// [`StaticSecret::diffie_hellman`] method does not consume the secret key, and the type provides +/// serialization methods to save and load key material. This means that the secret may be used +/// multiple times (but does not *have to be*). +/// +/// Some protocols, such as Noise, already handle the static/ephemeral distinction, so the +/// additional guarantees provided by [`EphemeralSecret`] are not helpful or would cause duplicate +/// code paths. In this case, it may be useful to +/// ```rust,ignore +/// use x25519_dalek::StaticSecret as SecretKey; +/// ``` +/// since the only difference between the two is that [`StaticSecret`] does not enforce at +/// compile-time that the key is only used once. #[cfg_attr(feature = "serde", serde(crate = "our_serde"))] #[cfg_attr( feature = "serde", @@ -106,7 +122,7 @@ impl StaticSecret { SharedSecret(&self.0 * their_public.0) } - /// Generate a x25519 `StaticSecret` key. + /// Generate an x25519 key. pub fn new(mut csprng: T) -> Self { let mut bytes = [0u8; 32]; @@ -115,29 +131,30 @@ impl StaticSecret { StaticSecret(clamp_scalar(bytes)) } - /// Save a x25519 `StaticSecret` key's bytes. + /// Extract this key's bytes for serialization. pub fn to_bytes(&self) -> [u8; 32] { self.0.to_bytes() } } impl From<[u8; 32]> for StaticSecret { - /// Load a `StaticSecret` from a byte array. + /// Load a secret key from a byte array. fn from(bytes: [u8; 32]) -> StaticSecret { StaticSecret(clamp_scalar(bytes)) } } impl<'a> From<&'a StaticSecret> for PublicKey { - /// Given an x25519 `StaticSecret` key, compute its corresponding - /// `PublicKey` key. + /// Given an x25519 [`StaticSecret`] key, compute its corresponding [`PublicKey`]. fn from(secret: &'a StaticSecret) -> PublicKey { PublicKey((&ED25519_BASEPOINT_TABLE * &secret.0).to_montgomery()) } } -/// A `SharedSecret` is a Diffie-Hellman shared secret that’s generated -/// from your `EphemeralSecret` or `StaticSecret` and their `PublicKey`. +/// The result of a Diffie-Hellman key exchange. +/// +/// Each party computes this using their [`EphemeralSecret`] or [`StaticSecret`] and their +/// counterparty's [`PublicKey`]. #[derive(Zeroize)] #[zeroize(drop)] pub struct SharedSecret(pub(crate) MontgomeryPoint); From 1b01d597ca0e7e3c1182d1642c31303b387f62e1 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Mon, 17 Aug 2020 19:43:22 -0700 Subject: [PATCH 7/7] Bump version to 1.0.0 and update CHANGELOG.md --- CHANGELOG.md | 9 +++++++++ Cargo.toml | 7 +++++-- README.md | 4 ++-- src/lib.rs | 1 + 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66b1ed1..6313216 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ Entries are listed in reverse chronological order. +## 1.0.0 + +* Widen generic bound on `EphemeralSecret::new` and `StaticSecret::new` to + allow owned as well as borrowed RNGs. +* Add `PublicKey::to_bytes` and `SharedSecret::to_bytes`, returning owned byte + arrays, complementing the existing `as_bytes` methods returning references. +* Remove mention of deprecated `rand_os` crate from examples. +* Clarify `EphemeralSecret`/`StaticSecret` distinction in documentation. + ## 0.6.0 * Updates `rand_core` version to `0.5`. diff --git a/Cargo.toml b/Cargo.toml index a092ec0..31e4457 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,11 @@ [package] name = "x25519-dalek" edition = "2018" -# Be sure to update the version in README.md -version = "0.6.0" +# Before changing this: +# - update version in README.md +# - update html_root_url +# - update CHANGELOG +version = "1.0.0" authors = [ "Isis Lovecruft ", "DebugSteven ", diff --git a/README.md b/README.md index 28289be..a05c1f5 100644 --- a/README.md +++ b/README.md @@ -101,8 +101,8 @@ and load a long-term secret key. To install, add the following to your project's `Cargo.toml`: ```toml -[dependencies.x25519-dalek] -version = "0.6" +[dependencies] +x25519-dalek = "1" ``` # Documentation diff --git a/src/lib.rs b/src/lib.rs index 0f80f7e..8a16049 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,7 @@ #![cfg_attr(feature = "nightly", deny(missing_docs))] #![cfg_attr(feature = "nightly", doc(include = "../README.md"))] #![doc(html_logo_url = "https://doc.dalek.rs/assets/dalek-logo-clear.png")] +#![doc(html_root_url = "https://docs.rs/x25519-dalek/1.0.0")] //! Note that docs will only build on nightly Rust until //! `feature(external_doc)` is stabilized.