diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..c367173 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,132 @@ +name: Rust + +on: + push: + branches: [ '**' ] + pull_request: + branches: [ '**' ] + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: '-D warnings' + RUSTDOCFLAGS: '-D warnings' + +jobs: + test: + name: Test with multiple feature combinations + runs-on: ubuntu-latest + strategy: + matrix: + include: + # 32-bit target + - target: i686-unknown-linux-gnu + deps: sudo apt update && sudo apt install gcc-multilib + # 64-bit target + - target: x86_64-unknown-linux-gnu + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ matrix.target }} + - run: ${{ matrix.deps }} + - run: cargo test --target ${{ matrix.target }} --no-default-features + - run: cargo test --target ${{ matrix.target }} --no-default-features --features reusable_secrets + - run: cargo test --target ${{ matrix.target }} --no-default-features --features static_secrets + - run: cargo test --target ${{ matrix.target }} + - run: cargo test --target ${{ matrix.target }} --all-features + + build-simd: + name: Test simd backend (nightly) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@nightly + - env: + RUSTFLAGS: '--cfg curve25519_dalek_backend="simd" -C target_feature=+avx2' + run: cargo build --target x86_64-unknown-linux-gnu + - env: + RUSTFLAGS: '--cfg curve25519_dalek_backend="simd" -C target_feature=+avx512ifma' + run: cargo build --target x86_64-unknown-linux-gnu + + msrv: + name: Current MSRV is 1.60.0 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + # First delete the checked-in `Cargo.lock`. We're going to regenerate it + - run: rm Cargo.lock + # Now run `cargo +nightly -Z minimal-verisons check` in order to get a + # Cargo.lock with the oldest possible deps + - uses: dtolnay/rust-toolchain@nightly + - run: cargo -Z minimal-versions check --no-default-features --features serde + # Now check that `cargo build` works with respect to the oldest possible + # deps and the stated MSRV + - uses: dtolnay/rust-toolchain@1.60.0 + - run: cargo build + +# no_std support is pending feature, tracking: +# https://github.com/dalek-cryptography/x25519-dalek/issues/111 +# # Test no_std integration with no features +# build-nostd-base: +# name: Build on no_std target (thumbv7em-none-eabi) +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v3 +# - uses: dtolnay/rust-toolchain@master +# with: +# toolchain: stable +# targets: thumbv7em-none-eabi +# - uses: taiki-e/install-action@cargo-hack +# # No default features build +# - run: cargo build --target thumbv7em-none-eabi --release --no-default-features +# +# # Test no_std integration with all no_std features +# build-nostd-features: +# name: Build on no_std target (thumbv7em-none-eabi) +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v3 +# - uses: dtolnay/rust-toolchain@master +# with: +# toolchain: stable +# targets: thumbv7em-none-eabi +# - uses: taiki-e/install-action@cargo-hack +# # No default features build +# - run: cargo hack build --target thumbv7em-none-eabi --release --each-feature --exclude-features default,std + + bench: + name: Check that benchmarks compile + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - run: cargo build --benches + + rustfmt: + name: Check formatting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - run: cargo fmt --all -- --check + + clippy: + name: Check that clippy is happy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@1.65 + with: + components: clippy + - run: cargo clippy + + doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - run: cargo doc --all-features diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..da57a4b --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +target/ +**/*.rs.bk + +.cargo + +*~ +\#* +.\#* +*.swp +*.orig +*.bak + +*.s diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..eceb10e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,26 @@ +language: rust + +rust: + - nightly + +env: + - TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='default' + - TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='nightly' + - TEST_COMMAND=bench EXTRA_FLAGS='' FEATURES='default' + - TEST_COMMAND=build EXTRA_FLAGS='--no-default-features' FEATURES='u32_backend nightly' + - TEST_COMMAND=build EXTRA_FLAGS='--no-default-features' FEATURES='u64_backend nightly' + +matrix: + include: + - rust: stable + env: TEST_COMMAND=test EXTRA_FLAGS='--no-default-features' FEATURES='std u64_backend' + - rust: beta + env: TEST_COMMAND=test EXTRA_FLAGS='--no-default-features' FEATURES='std u64_backend' + +script: + - cargo $TEST_COMMAND --features="$FEATURES" $EXTRA_FLAGS + +notifications: + slack: + rooms: + - dalek-cryptography:Xxv9WotKYWdSoKlgKNqXiHoD#dalek-bots diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d2c337c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,98 @@ +# Changelog + +Entries are listed in reverse chronological order. + +# 2.x Series + +* Note: All `x255919-dalek` 2.x releases are in sync with the underlying `curve25519-dalek` 4.x releases. + +## 2.0.0-rc.3 + +* `StaticSecret` serialization and `to_bytes()` no longer returns clamped integers. Clamping is still always done during scalar-point multiplication. +* Update underlying `curve25519_dalek` library to `4.0.0-rc.3`. Notable changes: + * [curve25519-dalek backend] now by default auto selects `simd` backend over `serial` where supported. + + +## 2.0.0-rc.2 + +* Update MSRV to 1.60. +* Update edition to 2021 +* Add `.as_bytes()` and `AsRef<[u8]>` for `Shared/StaticSecret` +* Add `getrandom` feature to provide `random_from_rng` constructors +* Make `StaticSecrets` optional via feature `static_secrets` +* Update underlying `curve25519_dalek` library to `4.0.0-rc.2`. Notable changes: + * [curve25519-dalek backend] additive features have been removed in favor of cfg based selection. + * [curve25519-dalek backend] now by default auto selects the appropriate word size over the previous default `32`. + +## 2.0.0-pre.1 + +* Loosen restriction on zeroize dependency version from =1.3 to 1. +* Update MSRV to 1.51. + +## 2.0.0-pre.0 + +* Update `rand_core` dependency to `0.6`. + +# 1.x Series + +## 1.2 + +* Add module documentation for using the bytes-oriented `x25519()` API. +* Add implementation of `zeroize::Zeroize` for `PublicKey`. +* Move unittests to a separate directory. +* Add cargo feature flags `"fiat_u32_backend"` and `"fiat_u64_backend"` for + activating the Fiat crypto field element implementations. +* Fix issue with removed `feature(external_doc)` on nightly compilers. +* Pin `zeroize` to version 1.3 to support a wider range of MSRVs. +* Add CI via Github actions. +* Fix breakage in the serde unittests. +* MSRV is now 1.41 for production and 1.48 for development. +* Add an optional check to `SharedSecret` for contibutory behaviour. +* Add implementation of `ReusableSecret` keys which are non-ephemeral, but which + cannot be serialised to discourage long-term use. + +## 1.1.1 + +* Fix a typo in the README. + +## 1.1.0 + +* Add impls of `PartialEq`, `Eq`, and `Hash` for `PublicKey` (by @jack-michaud) + +## 1.0.1 + +* Update underlying `curve25519_dalek` library to `3.0`. + +## 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. + +# Pre-1.0.0 + +## 0.6.0 + +* Updates `rand_core` version to `0.5`. +* Adds `serde` support. +* Replaces `clear_on_drop` with `zeroize`. +* Use Rust 2018. + +## 0.5.2 + +* Implement `Clone` for `StaticSecret`. + +## 0.5.1 + +* Implement `Copy, Clone, Debug` for `PublicKey`. +* Remove doctests. + +## 0.5.0 + +* Adds support for static and ephemeral keys. + +[curve25519-dalek backend]: https://github.com/dalek-cryptography/curve25519-dalek/#backends + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..d1561b9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,28 @@ +# Contributing to curve25519-dalek + +If you have questions or comments, please feel free to email the +authors. + +For feature requests, suggestions, and bug reports, please open an issue on +[our Github](https://github.com/dalek-cryptography/x25519-dalek). (Or, send us +an email if you're opposed to using Github for whatever reason.) + +Patches are welcomed as pull requests on +[our Github](https://github.com/dalek-cryptography/x25519-dalek), as well as by +email (preferably sent to all of the authors listed in `Cargo.toml`). + +All issues on curve25519-dalek are mentored, if you want help with a bug just +ask @isislovecruft. + +Some issues are easier than others. The `easy` label can be used to find the +easy issues. If you want to work on an issue, please leave a comment so that we +can assign it to you! + +# Code of Conduct + +We follow the [Rust Code of Conduct](http://www.rust-lang.org/conduct.html), +with the following additional clauses: + +* We respect the rights to privacy and anonymity for contributors and people in + the community. If someone wishes to contribute under a pseudonym different to + their primary identity, that wish is to be respected by all contributors. diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ff401af --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,735 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ciborium" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" + +[[package]] +name = "ciborium-ll" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "3.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +dependencies = [ + "bitflags", + "clap_lex", + "indexmap", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "cpufeatures" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" +dependencies = [ + "libc", +] + +[[package]] +name = "criterion" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" +dependencies = [ + "anes", + "atty", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "curve25519-dalek" +version = "4.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436ace70fc06e06f7f689d2624dc4e2f0ea666efb5aa704215f7249ae6e047a7" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "platforms", + "rustc_version", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "fiat-crypto" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ace6ec7cc19c8ed33a32eaa9ea692d7faea05006b5356b9e2b668ec4bc3955" + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi 0.2.6", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "os_str_bytes" +version = "6.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" + +[[package]] +name = "platforms" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" + +[[package]] +name = "plotters" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" + +[[package]] +name = "plotters-svg" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "proc-macro2" +version = "1.0.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rayon" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", +] + +[[package]] +name = "serde_json" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "x25519-dalek" +version = "2.0.0-rc.3" +dependencies = [ + "bincode", + "criterion", + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.12", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..201968b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "x25519-dalek" +edition = "2021" +# Before changing this: +# - update version in README.md +# - update html_root_url +# - update CHANGELOG +# - if any changes were made to README.md, mirror them in src/lib.rs docs +version = "2.0.0-rc.3" +authors = [ + "Isis Lovecruft ", + "DebugSteven ", + "Henry de Valence ", +] +readme = "README.md" +license = "BSD-3-Clause" +repository = "https://github.com/dalek-cryptography/x25519-dalek" +homepage = "https://dalek.rs/" +documentation = "https://docs.rs/x25519-dalek" +categories = ["cryptography", "no-std"] +keywords = ["cryptography", "curve25519", "key-exchange", "x25519", "diffie-hellman"] +description = "X25519 elliptic curve Diffie-Hellman key exchange in pure-Rust, using curve25519-dalek." +exclude = [ + ".gitignore", + ".travis.yml", + "CONTRIBUTING.md", +] +rust-version = "1.60" + +[badges] +travis-ci = { repository = "dalek-cryptography/x25519-dalek", branch = "master"} + +[package.metadata.docs.rs] +rustdoc-args = [ + "--html-in-header", "docs/assets/rustdoc-include-katex-header.html", + "--cfg", "docsrs", +] +features = ["getrandom", "reusable_secrets", "serde", "static_secrets"] + +[dependencies] +curve25519-dalek = { version = "=4.0.0-rc.3", default-features = false } +rand_core = { version = "0.6", default-features = false } +serde = { version = "1", default-features = false, optional = true, features = ["derive"] } +zeroize = { version = "1", default-features = false, optional = true, features = ["zeroize_derive"] } + +[dev-dependencies] +bincode = "1" +criterion = "0.4.0" +rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } + +[[bench]] +name = "x25519" +harness = false + +[features] +default = ["alloc", "precomputed-tables", "zeroize"] +getrandom = ["rand_core/getrandom"] +zeroize = ["dep:zeroize", "curve25519-dalek/zeroize"] +serde = ["dep:serde", "curve25519-dalek/serde"] +alloc = ["curve25519-dalek/alloc", "serde?/alloc", "zeroize?/alloc"] +precomputed-tables = ["curve25519-dalek/precomputed-tables"] +reusable_secrets = [] +static_secrets = [] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6577d97 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2017-2021 isis agora lovecruft. All rights reserved. +Copyright (c) 2019-2021 DebugSteven. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a25210a --- /dev/null +++ b/README.md @@ -0,0 +1,139 @@ +# x25519-dalek [![](https://img.shields.io/crates/v/x25519-dalek.svg)](https://crates.io/crates/x25519-dalek) [![](https://docs.rs/x25519-dalek/badge.svg)](https://docs.rs/x25519-dalek) [![](https://travis-ci.org/dalek-cryptography/x25519-dalek.svg?branch=master)](https://travis-ci.org/dalek-cryptography/x25519-dalek) + +A pure-Rust implementation of x25519 elliptic curve Diffie-Hellman key exchange, +with curve operations provided by +[curve25519-dalek](https://github.com/dalek-cryptography/curve25519-dalek). + +This crate provides two levels of API: a bare byte-oriented `x25519` +function which matches the function specified in [RFC7748][rfc7748], as +well as a higher-level Rust API for static and ephemeral Diffie-Hellman. + +## Examples + + + + + +Alice and Bob are two adorable kittens who have lost their mittens, and they +wish to be able to send secret messages to each other to coordinate finding +them, otherwise—if their caretaker cat finds out—they will surely be called +naughty kittens and be given no pie! + +But the two kittens are quite clever. Even though their paws are still too big +and the rest of them is 90% fuzziness, these clever kittens have been studying +up on modern public key cryptography and have learned a nifty trick called +*elliptic curve Diffie-Hellman key exchange*. With the right incantations, the +kittens will be able to secretly organise to find their mittens, and then spend +the rest of the afternoon nomming some yummy pie! + +First, Alice uses `EphemeralSecret::random()` and then +`PublicKey::from()` to produce her secret and public keys: + +```ignore +use x25519_dalek::{EphemeralSecret, PublicKey}; + +let alice_secret = EphemeralSecret::random(); +let alice_public = PublicKey::from(&alice_secret); +``` + +Bob does the same: + +```ignore +# use x25519_dalek::{EphemeralSecret, PublicKey}; +let bob_secret = EphemeralSecret::random(); +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 +# 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 +# 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 +# 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()); +``` + +Voilà! Alice and Bob can now use their shared secret to encrypt their +meows, for example, by using it to generate a key and nonce for an +authenticated-encryption cipher. + +This example used the ephemeral DH API, which ensures that secret keys +cannot be reused; Alice and Bob could instead use the static DH API +and load a long-term secret key. + +# Installation + +To install, add the following to your project's `Cargo.toml`: + +```toml +[dependencies] +x25519-dalek = "2.0.0-rc.3" +``` + +# MSRV + +Current MSRV is 1.60. + +# Documentation + +Documentation is available [here](https://docs.rs/x25519-dalek). + +# Performance and backend selection + +Performance is a secondary goal behind correctness, safety, and clarity, but we aim to be competitive with other implementations. To this end, we allow users to choose their _backend_, i.e., the underlying implementation of elliptic curve and scalar arithmetic. Different backends have different use cases. For example, if you demand formally verified code, you want to use the `fiat` backend (as it was generated from [Fiat Crypto][fiat]). + +Further instructions and details regarding backends can be found in the [curve25519-dalek docs](https://github.com/dalek-cryptography/curve25519-dalek#backends). + +# Note + +This code matches the [RFC7748][rfc7748] test vectors. +The elliptic curve +operations are provided by `curve25519-dalek`, which makes a best-effort +attempt to prevent software side-channels. + +"Secret Messages" cover image and [zine](https://shop.bubblesort.io/products/secret-messages-zine) +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 + +[fiat]: https://github.com/mit-plv/fiat-crypto +[crypto_box]: https://github.com/RustCrypto/nacl-compat/tree/master/crypto_box diff --git a/benches/x25519.rs b/benches/x25519.rs new file mode 100644 index 0000000..77c832d --- /dev/null +++ b/benches/x25519.rs @@ -0,0 +1,41 @@ +// -*- mode: rust; -*- +// +// This file is part of x25519-dalek. +// Copyright (c) 2017-2019 isis agora lovecruft +// Copyright (c) 2019 DebugSteven +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - DebugSteven + +//! Benchmark the Diffie-Hellman operation. + +use criterion::{criterion_group, criterion_main, Criterion}; + +use rand_core::OsRng; + +use x25519_dalek::EphemeralSecret; +use x25519_dalek::PublicKey; + +fn bench_diffie_hellman(c: &mut Criterion) { + let bob_secret = EphemeralSecret::random_from_rng(OsRng); + let bob_public = PublicKey::from(&bob_secret); + + c.bench_function("diffie_hellman", move |b| { + b.iter_with_setup( + || EphemeralSecret::random_from_rng(OsRng), + |alice_secret| alice_secret.diffie_hellman(&bob_public), + ) + }); +} + +criterion_group! { + name = x25519_benches; + config = Criterion::default(); + targets = + bench_diffie_hellman, +} +criterion_main! { + x25519_benches, +} diff --git a/docs/assets/rustdoc-include-katex-header.html b/docs/assets/rustdoc-include-katex-header.html new file mode 100644 index 0000000..d240432 --- /dev/null +++ b/docs/assets/rustdoc-include-katex-header.html @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/res/bubblesort-zines-secret-messages-cover.jpeg b/res/bubblesort-zines-secret-messages-cover.jpeg new file mode 100644 index 0000000..ca33298 Binary files /dev/null and b/res/bubblesort-zines-secret-messages-cover.jpeg differ diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9a5fc19 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,33 @@ +// -*- mode: rust; -*- +// +// This file is part of x25519-dalek. +// Copyright (c) 2017-2021 isis lovecruft +// Copyright (c) 2019-2021 DebugSteven +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - DebugSteven + +// Refuse to compile if documentation is missing, but only on nightly. +// +// This means that missing docs will still fail CI, but means we can use +// README.md as the crate documentation. + +#![no_std] +#![cfg_attr(feature = "bench", feature(test))] +#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg, doc_cfg_hide))] +#![cfg_attr(docsrs, doc(cfg_hide(docsrs)))] +#![deny(missing_docs)] +#![doc( + html_logo_url = "https://cdn.jsdelivr.net/gh/dalek-cryptography/curve25519-dalek/docs/assets/dalek-logo-clear.png" +)] +#![doc = include_str!("../README.md")] + +//------------------------------------------------------------------------ +// x25519-dalek public API +//------------------------------------------------------------------------ + +mod x25519; + +pub use crate::x25519::*; diff --git a/src/x25519.rs b/src/x25519.rs new file mode 100644 index 0000000..e1c79d4 --- /dev/null +++ b/src/x25519.rs @@ -0,0 +1,377 @@ +// -*- mode: rust; -*- +// +// This file is part of x25519-dalek. +// Copyright (c) 2017-2021 isis lovecruft +// Copyright (c) 2019-2021 DebugSteven +// See LICENSE for licensing information. +// +// Authors: +// - isis agora lovecruft +// - DebugSteven + +//! x25519 Diffie-Hellman key exchange +//! +//! This implements x25519 key exchange as specified by Mike Hamburg +//! and Adam Langley in [RFC7748](https://tools.ietf.org/html/rfc7748). + +use curve25519_dalek::{edwards::EdwardsPoint, montgomery::MontgomeryPoint, traits::IsIdentity}; + +use rand_core::CryptoRng; +use rand_core::RngCore; + +#[cfg(feature = "zeroize")] +use zeroize::Zeroize; + +/// A Diffie-Hellman public key +/// +/// We implement `Zeroize` so that downstream consumers may derive it for `Drop` +/// should they wish to erase public keys from memory. Note that this erasure +/// (in this crate) does *not* automatically happen, but either must be derived +/// for Drop or explicitly called. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "zeroize", derive(Zeroize))] +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] +pub struct PublicKey(pub(crate) MontgomeryPoint); + +impl From<[u8; 32]> for PublicKey { + /// Given a byte array, construct a x25519 `PublicKey`. + fn from(bytes: [u8; 32]) -> PublicKey { + PublicKey(MontgomeryPoint(bytes)) + } +} + +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] { + self.0.as_bytes() + } +} + +impl AsRef<[u8]> for PublicKey { + /// View this public key as a byte array. + #[inline] + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +/// 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 where the compiler statically checks that the resulting +/// secret is used at most once. +#[cfg_attr(feature = "zeroize", derive(Zeroize))] +#[cfg_attr(feature = "zeroize", zeroize(drop))] +pub struct EphemeralSecret(pub(crate) [u8; 32]); + +impl EphemeralSecret { + /// Perform a Diffie-Hellman key agreement between `self` and + /// `their_public` key to produce a [`SharedSecret`]. + pub fn diffie_hellman(self, their_public: &PublicKey) -> SharedSecret { + SharedSecret(their_public.0.mul_clamped(self.0)) + } + + /// Generate a new [`EphemeralSecret`] with the supplied RNG. + #[deprecated( + since = "2.0.0", + note = "Renamed to `random_from_rng`. This will be removed in 2.1.0" + )] + pub fn new(mut csprng: T) -> Self { + Self::random_from_rng(&mut csprng) + } + + /// Generate a new [`EphemeralSecret`] with the supplied RNG. + pub fn random_from_rng(mut csprng: T) -> Self { + // The secret key is random bytes. Clamping is done later. + let mut bytes = [0u8; 32]; + csprng.fill_bytes(&mut bytes); + EphemeralSecret(bytes) + } + + /// Generate a new [`EphemeralSecret`]. + #[cfg(feature = "getrandom")] + pub fn random() -> Self { + Self::random_from_rng(&mut rand_core::OsRng) + } +} + +impl<'a> From<&'a EphemeralSecret> for PublicKey { + /// Given an x25519 [`EphemeralSecret`] key, compute its corresponding [`PublicKey`]. + fn from(secret: &'a EphemeralSecret) -> PublicKey { + PublicKey(EdwardsPoint::mul_base_clamped(secret.0).to_montgomery()) + } +} + +/// A Diffie-Hellman secret key which may be used more than once, but is +/// purposefully not serialiseable in order to discourage key-reuse. This is +/// implemented to facilitate protocols such as Noise (e.g. Noise IK key usage, +/// etc.) and X3DH which require an "ephemeral" key to conduct the +/// Diffie-Hellman operation multiple times throughout the protocol, while the +/// protocol run at a higher level is only conducted once per key. +/// +/// Similarly to [`EphemeralSecret`], this type does _not_ have serialisation +/// methods, in order to discourage long-term usage of secret key material. (For +/// long-term secret keys, see `StaticSecret`.) +/// +/// # Warning +/// +/// If you're uncertain about whether you should use this, then you likely +/// should not be using this. Our strongly recommended advice is to use +/// [`EphemeralSecret`] at all times, as that type enforces at compile-time that +/// secret keys are never reused, which can have very serious security +/// implications for many protocols. +#[cfg(feature = "reusable_secrets")] +#[cfg_attr(feature = "zeroize", derive(Zeroize))] +#[cfg_attr(feature = "zeroize", zeroize(drop))] +#[derive(Clone)] +pub struct ReusableSecret(pub(crate) [u8; 32]); + +#[cfg(feature = "reusable_secrets")] +impl ReusableSecret { + /// Perform a Diffie-Hellman key agreement between `self` and + /// `their_public` key to produce a [`SharedSecret`]. + pub fn diffie_hellman(&self, their_public: &PublicKey) -> SharedSecret { + SharedSecret(their_public.0.mul_clamped(self.0)) + } + + /// Generate a new [`ReusableSecret`] with the supplied RNG. + #[deprecated( + since = "2.0.0", + note = "Renamed to `random_from_rng`. This will be removed in 2.1.0." + )] + pub fn new(mut csprng: T) -> Self { + Self::random_from_rng(&mut csprng) + } + + /// Generate a new [`ReusableSecret`] with the supplied RNG. + pub fn random_from_rng(mut csprng: T) -> Self { + // The secret key is random bytes. Clamping is done later. + let mut bytes = [0u8; 32]; + csprng.fill_bytes(&mut bytes); + ReusableSecret(bytes) + } + + /// Generate a new [`ReusableSecret`]. + #[cfg(feature = "getrandom")] + pub fn random() -> Self { + Self::random_from_rng(&mut rand_core::OsRng) + } +} + +#[cfg(feature = "reusable_secrets")] +impl<'a> From<&'a ReusableSecret> for PublicKey { + /// Given an x25519 [`ReusableSecret`] key, compute its corresponding [`PublicKey`]. + fn from(secret: &'a ReusableSecret) -> PublicKey { + PublicKey(EdwardsPoint::mul_base_clamped(secret.0).to_montgomery()) + } +} + +/// 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*). +/// +/// # Warning +/// +/// If you're uncertain about whether you should use this, then you likely +/// should not be using this. Our strongly recommended advice is to use +/// [`EphemeralSecret`] at all times, as that type enforces at compile-time that +/// secret keys are never reused, which can have very serious security +/// implications for many protocols. +#[cfg(feature = "static_secrets")] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "zeroize", derive(Zeroize))] +#[cfg_attr(feature = "zeroize", zeroize(drop))] +#[derive(Clone)] +pub struct StaticSecret([u8; 32]); + +#[cfg(feature = "static_secrets")] +impl StaticSecret { + /// Perform a Diffie-Hellman key agreement between `self` and + /// `their_public` key to produce a `SharedSecret`. + pub fn diffie_hellman(&self, their_public: &PublicKey) -> SharedSecret { + SharedSecret(their_public.0.mul_clamped(self.0)) + } + + /// Generate a new [`StaticSecret`] with the supplied RNG. + #[deprecated( + since = "2.0.0", + note = "Renamed to `random_from_rng`. This will be removed in 2.1.0" + )] + pub fn new(mut csprng: T) -> Self { + Self::random_from_rng(&mut csprng) + } + + /// Generate a new [`StaticSecret`] with the supplied RNG. + pub fn random_from_rng(mut csprng: T) -> Self { + // The secret key is random bytes. Clamping is done later. + let mut bytes = [0u8; 32]; + csprng.fill_bytes(&mut bytes); + StaticSecret(bytes) + } + + /// Generate a new [`StaticSecret`]. + #[cfg(feature = "getrandom")] + pub fn random() -> Self { + Self::random_from_rng(&mut rand_core::OsRng) + } + + /// Extract this key's bytes for serialization. + #[inline] + pub fn to_bytes(&self) -> [u8; 32] { + self.0 + } + + /// View this key as a byte array. + #[inline] + pub fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } +} + +#[cfg(feature = "static_secrets")] +impl From<[u8; 32]> for StaticSecret { + /// Load a secret key from a byte array. + fn from(bytes: [u8; 32]) -> StaticSecret { + StaticSecret(bytes) + } +} + +#[cfg(feature = "static_secrets")] +impl<'a> From<&'a StaticSecret> for PublicKey { + /// Given an x25519 [`StaticSecret`] key, compute its corresponding [`PublicKey`]. + fn from(secret: &'a StaticSecret) -> PublicKey { + PublicKey(EdwardsPoint::mul_base_clamped(secret.0).to_montgomery()) + } +} + +#[cfg(feature = "static_secrets")] +impl AsRef<[u8]> for StaticSecret { + /// View this key as a byte array. + #[inline] + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +/// The result of a Diffie-Hellman key exchange. +/// +/// Each party computes this using their [`EphemeralSecret`] or [`StaticSecret`] and their +/// counterparty's [`PublicKey`]. +#[cfg_attr(feature = "zeroize", derive(Zeroize))] +#[cfg_attr(feature = "zeroize", zeroize(drop))] +pub struct SharedSecret(pub(crate) MontgomeryPoint); + +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] { + self.0.as_bytes() + } + + /// Ensure in constant-time that this shared secret did not result from a + /// key exchange with non-contributory behaviour. + /// + /// In some more exotic protocols which need to guarantee "contributory" + /// behaviour for both parties, that is, that each party contributed a public + /// value which increased the security of the resulting shared secret. + /// To take an example protocol attack where this could lead to undesirable + /// results [from Thái "thaidn" Dương](https://vnhacker.blogspot.com/2015/09/why-not-validating-curve25519-public.html): + /// + /// > If Mallory replaces Alice's and Bob's public keys with zero, which is + /// > a valid Curve25519 public key, he would be able to force the ECDH + /// > shared value to be zero, which is the encoding of the point at infinity, + /// > and thus get to dictate some publicly known values as the shared + /// > keys. It still requires an active man-in-the-middle attack to pull the + /// > trick, after which, however, not only Mallory can decode Alice's data, + /// > but everyone too! It is also impossible for Alice and Bob to detect the + /// > intrusion, as they still share the same keys, and can communicate with + /// > each other as normal. + /// + /// The original Curve25519 specification argues that checks for + /// non-contributory behaviour are "unnecessary for Diffie-Hellman". + /// Whether this check is necessary for any particular given protocol is + /// often a matter of debate, which we will not re-hash here, but simply + /// cite some of the [relevant] [public] [discussions]. + /// + /// # Returns + /// + /// Returns `true` if the key exchange was contributory (good), and `false` + /// otherwise (can be bad for some protocols). + /// + /// [relevant]: https://tools.ietf.org/html/rfc7748#page-15 + /// [public]: https://vnhacker.blogspot.com/2015/09/why-not-validating-curve25519-public.html + /// [discussions]: https://vnhacker.blogspot.com/2016/08/the-internet-of-broken-protocols.html + #[must_use] + pub fn was_contributory(&self) -> bool { + !self.0.is_identity() + } +} + +impl AsRef<[u8]> for SharedSecret { + /// View this shared secret key as a byte array. + #[inline] + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +/// The bare, byte-oriented x25519 function, exactly as specified in RFC7748. +/// +/// This can be used with [`X25519_BASEPOINT_BYTES`] for people who +/// cannot use the better, safer, and faster ephemeral DH API. +/// +/// # Example +#[cfg_attr(feature = "static_secrets", doc = "```")] +#[cfg_attr(not(feature = "static_secrets"), doc = "```ignore")] +/// use rand_core::OsRng; +/// use rand_core::RngCore; +/// +/// use x25519_dalek::x25519; +/// use x25519_dalek::StaticSecret; +/// use x25519_dalek::PublicKey; +/// +/// // Generate Alice's key pair. +/// let alice_secret = StaticSecret::random_from_rng(&mut OsRng); +/// let alice_public = PublicKey::from(&alice_secret); +/// +/// // Generate Bob's key pair. +/// let bob_secret = StaticSecret::random_from_rng(&mut OsRng); +/// let bob_public = PublicKey::from(&bob_secret); +/// +/// // Alice and Bob should now exchange their public keys. +/// +/// // Once they've done so, they may generate a shared secret. +/// let alice_shared = x25519(alice_secret.to_bytes(), bob_public.to_bytes()); +/// let bob_shared = x25519(bob_secret.to_bytes(), alice_public.to_bytes()); +/// +/// assert_eq!(alice_shared, bob_shared); +/// ``` +pub fn x25519(k: [u8; 32], u: [u8; 32]) -> [u8; 32] { + MontgomeryPoint(u).mul_clamped(k).to_bytes() +} + +/// The X25519 basepoint, for use with the bare, byte-oriented x25519 +/// function. This is provided for people who cannot use the typed +/// DH API for some reason. +pub const X25519_BASEPOINT_BYTES: [u8; 32] = [ + 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; diff --git a/tests/x25519_tests.rs b/tests/x25519_tests.rs new file mode 100644 index 0000000..d589b3e --- /dev/null +++ b/tests/x25519_tests.rs @@ -0,0 +1,231 @@ +use curve25519_dalek::edwards::EdwardsPoint; + +use x25519_dalek::*; + +#[test] +fn byte_basepoint_matches_edwards_scalar_mul() { + let mut scalar_bytes = [0x37; 32]; + + for i in 0..32 { + scalar_bytes[i] += 2; + + let result = x25519(scalar_bytes, X25519_BASEPOINT_BYTES); + let expected = EdwardsPoint::mul_base_clamped(scalar_bytes) + .to_montgomery() + .to_bytes(); + + assert_eq!(result, expected); + } +} + +#[test] +#[cfg(feature = "serde")] +fn serde_bincode_public_key_roundtrip() { + use bincode; + + let public_key = PublicKey::from(X25519_BASEPOINT_BYTES); + + let encoded = bincode::serialize(&public_key).unwrap(); + let decoded: PublicKey = bincode::deserialize(&encoded).unwrap(); + + assert_eq!(encoded.len(), 32); + assert_eq!(decoded.as_bytes(), public_key.as_bytes()); +} + +#[test] +#[cfg(feature = "serde")] +fn serde_bincode_public_key_matches_from_bytes() { + use bincode; + + let expected = PublicKey::from(X25519_BASEPOINT_BYTES); + let decoded: PublicKey = bincode::deserialize(&X25519_BASEPOINT_BYTES).unwrap(); + + assert_eq!(decoded.as_bytes(), expected.as_bytes()); +} + +#[test] +#[cfg(feature = "serde")] +fn serde_bincode_static_secret_roundtrip() { + use bincode; + + let static_secret = StaticSecret::from([0x24; 32]); + let encoded = bincode::serialize(&static_secret).unwrap(); + let decoded: StaticSecret = bincode::deserialize(&encoded).unwrap(); + + assert_eq!(encoded.len(), 32); + assert_eq!(decoded.to_bytes(), static_secret.to_bytes()); +} + +#[test] +#[cfg(feature = "serde")] +fn serde_bincode_static_secret_matches_from_bytes() { + use bincode; + + let expected = StaticSecret::from([0x24; 32]); + let decoded: StaticSecret = bincode::deserialize(&[0x24; 32]).unwrap(); + + assert_eq!(decoded.to_bytes(), expected.to_bytes()); +} + +fn do_rfc7748_ladder_test1(input_scalar: [u8; 32], input_point: [u8; 32], expected: [u8; 32]) { + let result = x25519(input_scalar, input_point); + + assert_eq!(result, expected); +} + +#[test] +fn rfc7748_ladder_test1_vectorset1() { + let input_scalar: [u8; 32] = [ + 0xa5, 0x46, 0xe3, 0x6b, 0xf0, 0x52, 0x7c, 0x9d, 0x3b, 0x16, 0x15, 0x4b, 0x82, 0x46, 0x5e, + 0xdd, 0x62, 0x14, 0x4c, 0x0a, 0xc1, 0xfc, 0x5a, 0x18, 0x50, 0x6a, 0x22, 0x44, 0xba, 0x44, + 0x9a, 0xc4, + ]; + let input_point: [u8; 32] = [ + 0xe6, 0xdb, 0x68, 0x67, 0x58, 0x30, 0x30, 0xdb, 0x35, 0x94, 0xc1, 0xa4, 0x24, 0xb1, 0x5f, + 0x7c, 0x72, 0x66, 0x24, 0xec, 0x26, 0xb3, 0x35, 0x3b, 0x10, 0xa9, 0x03, 0xa6, 0xd0, 0xab, + 0x1c, 0x4c, + ]; + let expected: [u8; 32] = [ + 0xc3, 0xda, 0x55, 0x37, 0x9d, 0xe9, 0xc6, 0x90, 0x8e, 0x94, 0xea, 0x4d, 0xf2, 0x8d, 0x08, + 0x4f, 0x32, 0xec, 0xcf, 0x03, 0x49, 0x1c, 0x71, 0xf7, 0x54, 0xb4, 0x07, 0x55, 0x77, 0xa2, + 0x85, 0x52, + ]; + + do_rfc7748_ladder_test1(input_scalar, input_point, expected); +} + +#[test] +fn rfc7748_ladder_test1_vectorset2() { + let input_scalar: [u8; 32] = [ + 0x4b, 0x66, 0xe9, 0xd4, 0xd1, 0xb4, 0x67, 0x3c, 0x5a, 0xd2, 0x26, 0x91, 0x95, 0x7d, 0x6a, + 0xf5, 0xc1, 0x1b, 0x64, 0x21, 0xe0, 0xea, 0x01, 0xd4, 0x2c, 0xa4, 0x16, 0x9e, 0x79, 0x18, + 0xba, 0x0d, + ]; + let input_point: [u8; 32] = [ + 0xe5, 0x21, 0x0f, 0x12, 0x78, 0x68, 0x11, 0xd3, 0xf4, 0xb7, 0x95, 0x9d, 0x05, 0x38, 0xae, + 0x2c, 0x31, 0xdb, 0xe7, 0x10, 0x6f, 0xc0, 0x3c, 0x3e, 0xfc, 0x4c, 0xd5, 0x49, 0xc7, 0x15, + 0xa4, 0x93, + ]; + let expected: [u8; 32] = [ + 0x95, 0xcb, 0xde, 0x94, 0x76, 0xe8, 0x90, 0x7d, 0x7a, 0xad, 0xe4, 0x5c, 0xb4, 0xb8, 0x73, + 0xf8, 0x8b, 0x59, 0x5a, 0x68, 0x79, 0x9f, 0xa1, 0x52, 0xe6, 0xf8, 0xf7, 0x64, 0x7a, 0xac, + 0x79, 0x57, + ]; + + do_rfc7748_ladder_test1(input_scalar, input_point, expected); +} + +#[test] +#[ignore] // Run only if you want to burn a lot of CPU doing 1,000,000 DH operations +fn rfc7748_ladder_test2() { + use curve25519_dalek::constants::X25519_BASEPOINT; + + let mut k: [u8; 32] = X25519_BASEPOINT.0; + let mut u: [u8; 32] = X25519_BASEPOINT.0; + let mut result: [u8; 32]; + + macro_rules! do_iterations { + ($n:expr) => { + for _ in 0..$n { + result = x25519(k, u); + // OBVIOUS THING THAT I'M GOING TO NOTE ANYWAY BECAUSE I'VE + // SEEN PEOPLE DO THIS WITH GOLANG'S STDLIB AND YOU SURE AS + // HELL SHOULDN'T DO HORRIBLY STUPID THINGS LIKE THIS WITH + // MY LIBRARY: + // + // NEVER EVER TREAT SCALARS AS POINTS AND/OR VICE VERSA. + // + // ↓↓ DON'T DO THIS ↓↓ + u = k.clone(); + k = result; + } + }; + } + + // After one iteration: + // 422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079 + // After 1,000 iterations: + // 684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51 + // After 1,000,000 iterations: + // 7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424 + + do_iterations!(1); + assert_eq!( + k, + [ + 0x42, 0x2c, 0x8e, 0x7a, 0x62, 0x27, 0xd7, 0xbc, 0xa1, 0x35, 0x0b, 0x3e, 0x2b, 0xb7, + 0x27, 0x9f, 0x78, 0x97, 0xb8, 0x7b, 0xb6, 0x85, 0x4b, 0x78, 0x3c, 0x60, 0xe8, 0x03, + 0x11, 0xae, 0x30, 0x79, + ] + ); + do_iterations!(999); + assert_eq!( + k, + [ + 0x68, 0x4c, 0xf5, 0x9b, 0xa8, 0x33, 0x09, 0x55, 0x28, 0x00, 0xef, 0x56, 0x6f, 0x2f, + 0x4d, 0x3c, 0x1c, 0x38, 0x87, 0xc4, 0x93, 0x60, 0xe3, 0x87, 0x5f, 0x2e, 0xb9, 0x4d, + 0x99, 0x53, 0x2c, 0x51, + ] + ); + do_iterations!(999_000); + assert_eq!( + k, + [ + 0x7c, 0x39, 0x11, 0xe0, 0xab, 0x25, 0x86, 0xfd, 0x86, 0x44, 0x97, 0x29, 0x7e, 0x57, + 0x5e, 0x6f, 0x3b, 0xc6, 0x01, 0xc0, 0x88, 0x3c, 0x30, 0xdf, 0x5f, 0x4d, 0xd2, 0xd2, + 0x4f, 0x66, 0x54, 0x24, + ] + ); +} + +mod rand_core { + + use super::*; + use ::rand_core::OsRng; + + #[test] + fn ephemeral_from_rng() { + #[allow(deprecated)] + EphemeralSecret::new(OsRng); + EphemeralSecret::random_from_rng(OsRng); + } + + #[test] + #[cfg(feature = "reusable_secrets")] + fn reusable_from_rng() { + #[allow(deprecated)] + ReusableSecret::new(OsRng); + ReusableSecret::random_from_rng(OsRng); + } + + #[test] + #[cfg(feature = "static_secrets")] + fn static_from_rng() { + #[allow(deprecated)] + StaticSecret::new(OsRng); + StaticSecret::random_from_rng(OsRng); + } +} + +#[cfg(feature = "getrandom")] +mod getrandom { + + use super::*; + + #[test] + fn ephemeral_random() { + EphemeralSecret::random(); + } + + #[test] + #[cfg(feature = "reusable_secrets")] + fn reusable_random() { + ReusableSecret::random(); + } + + #[test] + #[cfg(feature = "static_secrets")] + fn static_random() { + StaticSecret::random(); + } +}