Compare commits

...

61 Commits

Author SHA1 Message Date
Jordan Rose
7c6d347563 bench: Don't depend on FieldElement and elligator_ristretto_flavor
Some checks failed
All / Check formatting (push) Has been cancelled
All / Check docs (push) Has been cancelled
Cross / Test (stable, aarch64-unknown-linux-gnu) (push) Has been cancelled
Cross / Test (stable, armv7-unknown-linux-gnueabihf) (push) Has been cancelled
Cross / Test (stable, powerpc-unknown-linux-gnu) (push) Has been cancelled
curve25519 Rust / Test fiat backend (sudo apt update && sudo apt install gcc-multilib, i686-unknown-linux-gnu) (push) Has been cancelled
curve25519 Rust / Test fiat backend (x86_64-unknown-linux-gnu) (push) Has been cancelled
curve25519 Rust / Build fiat on no_std target (thumbv7em-none-eabi) (curve25519-dalek) (push) Has been cancelled
All / Check that clippy is happy (push) Has been cancelled
curve25519 Rust / Test serial backend (sudo apt update && sudo apt install gcc-multilib, i686-unknown-linux-gnu) (push) Has been cancelled
curve25519 Rust / Test serial backend (x86_64-unknown-linux-gnu) (push) Has been cancelled
curve25519 Rust / Test Build Script (push) Has been cancelled
curve25519 Rust / Test simd backend (nightly) (push) Has been cancelled
curve25519 Rust / Test simd backend (stable) (push) Has been cancelled
curve25519 Rust / Current MSRV is 1.60.0 (push) Has been cancelled
All / Test 32/64 bit stable (sudo apt update && sudo apt install gcc-multilib, i686-unknown-linux-gnu) (push) Has been cancelled
All / Test 32/64 bit stable (x86_64-unknown-linux-gnu) (push) Has been cancelled
All / Test Nightly (push) Has been cancelled
All / Check that benchmarks compile (push) Has been cancelled
All / Build serial on no_std target (thumbv7em-none-eabi) (curve25519-dalek) (push) Has been cancelled
All / Build serial on no_std target (thumbv7em-none-eabi) (ed25519-dalek) (push) Has been cancelled
All / Build serial on no_std target (thumbv7em-none-eabi) (x25519-dalek) (push) Has been cancelled
An Elligator benchmark was added for the "lizard" extensions, and it
made sure it was only measuring the Elligator part of the work by
expanding a set of bytes into a FieldElement ahead of time. However,
this requires the FieldElement alias to be pub, and depends on the
elligator_ristretto_flavor helper function being pub as well. Changing
it to test the lizard-based entry point means we're diverging less
from upstream, and it's more realistic anyway.
2024-06-18 15:18:14 -07:00
Jordan Rose
fa03398012 Merge upstream tag 'curve25519-4.1.3' 2024-06-18 15:17:10 -07:00
Michael Rosenberg
5312a0311e
curve: Bump version to 4.1.3 (#660)
* Bumped to v4.1.3

* Added recent PRs to changelog
2024-06-18 21:18:51 +02:00
Tony Arcieri
b4f9e4df92
SECURITY: fix timing variability in backend/serial/u32/scalar.rs (#661)
Similar security fix to #659, but for the 32-bit backend. See that PR
for more information about the problem. Relevant compiler outputs (thanks to @tarcieri):

Without fix
https://godbolt.org/z/zvaWxzvqv
Notice the `jns` ("jump if not sign") instruction on line 106.

With fix
https://godbolt.org/z/jc9j7eb8E
2024-06-18 21:02:37 +02:00
Michael Rosenberg
415892acf1
SECURITY: fix timing variability in backend/serial/u64/scalar.rs (#659)
Timing variability of any kind is problematic when working with
potentially secret values such as elliptic curve scalars, and such
issues can potentially leak private keys and other secrets. Such a
problem was recently discovered in `curve25519-dalek`.

The `Scalar52::sub` function contained usage of a mask value inside of a
loop where LLVM saw an opportunity to insert a branch instruction
(`jns` on x86) to conditionally bypass this code section when the mask
value is set to zero, as can be seen in godbolt:

https://godbolt.org/z/PczYj7Pda

A similar problem was recently discovered in the Kyber reference
implementation:

https://groups.google.com/a/list.nist.gov/g/pqc-forum/c/hqbtIGFKIpU/m/cnE3pbueBgAJ

As discussed on that thread, one portable solution, which is also used
in this PR, is to introduce a volatile read as an optimization barrier,
which prevents the compiler from optimizing it away.

The fix can be validated in godbolt here:

https://godbolt.org/z/x8d46Yfah

The problem was discovered and the solution independently verified by
Alexander Wagner <alexander.wagner@aisec.fraunhofer.de> and
Lea Themint <lea.thiemt@tum.de> using their DATA tool:

https://github.com/Fraunhofer-AISEC/DATA

Co-authored-by: Tony Arcieri <bascule@gmail.com>
2024-06-18 19:49:31 +02:00
Isaiah Becker-Mayer
56bf398d0c
Updates license field to valid SPDX format (#647) 2024-06-03 14:30:13 -06:00
pinkforest(she/her)
9252fa5c0d
Mitigate check-cfg until MSRV 1.77 (#652) 2024-05-09 07:24:16 -06:00
Hiroki Kobayashi
1efe6a93b1
Fix a minor typo in signing.rs (#649)
an -> a
2024-04-13 19:37:33 -06:00
Boyd Kane
cc3421a22f
Indicate that the rand_core feature is required (#641) 2024-03-16 07:43:25 -06:00
pinkforest(she/her)
858c4ca8ae
Address new nightly clippy unnecessary qualifications (#639) 2024-03-07 16:58:20 -07:00
pinkforest(she/her)
31ccb67050
Remove platforms in favor using CARGO_CFG_TARGET_POINTER_WIDTH (#636) 2024-03-01 07:35:23 -07:00
pinkforest(she/her)
19c7f4a5d5
Fix new nightly redundant import lint warns (#638) 2024-02-29 18:56:52 -07:00
Flori
a62e4a5c57
Fix minor spelling mistakes (#629) 2024-02-14 12:01:05 -07:00
Jack Lloyd
17eab3d6c1
ed: Make it possible to convert between VerifyingKey and EdwardsPoint (#624)
Adds VerifyingKey::to_edwards and a From conversion

See #623
2024-02-12 14:36:43 -05:00
Tony Arcieri
50401ab430
curve: mark ValidityCheck trait as allow(dead_code) (#625)
Recent nightlies have started emitting a dead code lint
2024-02-12 11:56:06 -05:00
Michael Rosenberg
4ac84dd066
curve,ed,x: Bump patch version to reflect fix to nightly SIMD build (#621) 2024-02-06 20:09:18 -05:00
Jimmy Chen
ff1c309b23
Fix nightly build (#619)
* Fix nightly build

* Add nightly feature constraint so AVX-512 requires either x86 or x86_64

Co-authored-by: Tony Arcieri <bascule@gmail.com>

* fmt

---------

Co-authored-by: Michael Rosenberg <micro@fastmail.com>
Co-authored-by: Tony Arcieri <bascule@gmail.com>
Co-authored-by: Michael Rosenberg <michael@mrosenberg.pub>
2024-02-06 15:09:29 -05:00
Ford
ba737a3790
Update README.md (#613) 2024-01-21 08:27:47 -07:00
Pioua
0b45e00ad5
chore: typo fix (#608) 2023-12-13 08:10:05 -07:00
Alex Konradi
a12ab4e584
Update to upstream v4.1.1 2023-12-06 13:07:48 -05:00
Alex Konradi
7051bd8dcf Merge upstream tag 'curve25519-4.1.1' 2023-12-06 09:41:54 -05:00
Wiktor Kwapisiewicz
ba7a073487
doc: Fix markdown PR reference (#605) 2023-11-22 06:21:20 -07:00
Bram Westerbaan
a2ff6ba9e4
{Signing,Verifying}KeyVisitor: visit_borrowed_bytes -> visit_bytes (#602) 2023-11-17 02:44:28 -05:00
Michael Rosenberg
f08bbb7f57
ed: Prep to release v2.1.0 (#600) 2023-11-14 15:35:42 -05:00
Michael Rosenberg
04f811ad21
ed: Add back SigningKey::to_scalar_bytes (#599)
* Brought back SigningKey::to_scalar_bytes; added regression test

* Updated SigningKey::to_scalar docs and tests
2023-11-14 13:23:48 -05:00
Tony Arcieri
ac51ef6ecf
ed25519: loosen signature crate dependency again (#598)
Like #582, there is a new release of `signature` (v2.2.0) which contains
no breaking changes from ed25519-dalek's perspective. The main notable
one is it bumps MSRV to 1.60, which so also happens to also be
ed25519-dalek's MSRV.

This commit loosens the version requirement to allow `>=2.0, <2.3` to
allow the `signature` 2.2 series.
2023-11-14 00:09:16 -05:00
Tony Arcieri
89aabac235
README.md: remove broken image (#595)
This image duplicates the `curve25519-dalek` table entry below.

It also doesn't actually link to anything, making README.md look broken.
2023-11-01 13:33:43 -04:00
Michael Rosenberg
72761ca6b4
derive: Bump version to 0.1.1 (#594)
* derive: Bump version to 0.1.1

* Added changelog
2023-10-31 13:40:12 -04:00
Tony Arcieri
3c85f778b3
CI: fix minimal-versions resolution (#593)
To avoid nightly regressions breaking the build, the CI configuration
has been updated to *only* use nightly for resolving Cargo.lock by using
`cargo update -Z minimal-versions`.

Previously, it was running `cargo check` which would attempt to compile
all of the dependencies and the code, which is why the diagnostic bug
was triggered. By avoiding any kind of code compilation using nightly we
can avoid such regressions in the future.

Additionally, the clippy job has been changed to run on the latest
stable release (1.73.0) rather than nightly, which will prevent future
clippy lints from breaking the build. Instead, they can be addressed
when clippy is updated.
2023-10-31 12:04:34 -04:00
Tony Arcieri
78a86f1c49
ed25519-dalek: hide secret in SigningKey's Debug impl (#592)
Uses `finish_non_exhaustive` in lieu of printing the `secret_key`
component of a `SigningKey`, only showing the corresponding
`verifying_key` field which can be used to identify the public key.

Closes #591
2023-10-31 12:01:09 -04:00
Michael Rosenberg
f4cd43f606
Merge pull request #590 from dalek-cryptography/derive-license
Fix licensing on -derive repo
2023-10-31 11:35:38 -04:00
Michael Rosenberg
81d0756bdc
Made unnecessarily pub contents of field.rs pub(crate) 2023-10-29 22:06:47 -04:00
Michael Rosenberg
cd9378e6fd
Removed unnecessary 'pub use' 2023-10-29 21:53:08 -04:00
Michael Rosenberg
8a41a29939
Forgot the license files 2023-10-29 10:50:17 -04:00
Michael Rosenberg
b92421916d
Copy licensing from previous repo 2023-10-29 10:47:45 -04:00
Tony Arcieri
598695c400
ed25519: loosen signature crate dependency (#582)
The `signature` crate contains unstable, minor version-gated
functionality.

The v2.1 release did not change any of that, and only added new
functionality. So it's safe to relax the requirement for `signature` to
`>=2.0, <2.2`.
2023-10-27 00:29:56 -04:00
Victor Graf
e6675c67ce
add cfg statements to only build doctest on x86 (#585) 2023-10-03 12:51:05 -06:00
Michael Rosenberg
0cd099a9fb
curve: Bump version to 4.1.1 (#584) 2023-09-20 17:42:22 -05:00
Luke Parker
76a8b2a081
Add PrimeFieldBits support to Scalar (#579)
Co-authored-by: Michael Rosenberg <micro@fastmail.com>
Co-authored-by: pinkforest(she/her) <36498018+pinkforest@users.noreply.github.com>
2023-09-19 23:21:43 -04:00
pinkforest(she/her)
533b53a0ec
Deprecate BASEPOINT_ORDER from pub API consts (#581)
* Mark constants::BASEPOINT_ORDER_PRIVATE deprecated from pub API

* Move all BASEPOINT_ORDER use private internally

Co-authored-by: Tony Arcieri <bascule@gmail.com>

* Fix CHANGELOG for 4.1.1

---------

Co-authored-by: Tony Arcieri <bascule@gmail.com>
2023-09-17 23:59:05 -04:00
Luke Parker
c157a1ed6d
Add group to documented features (#578) 2023-09-12 07:41:15 -06:00
Michael Rosenberg
e94a5fe5ab
curve: README typos 2023-09-06 00:53:30 -04:00
pinkforest(she/her)
9db51a6bf7
curve: Release 4.1.0 (#574)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2023-09-06 00:51:15 -04:00
Michael Rosenberg
8ed1666b97
ed,x: updated repo links 2023-09-06 00:49:26 -04:00
Tony Arcieri
1ec4a36a80
curve: update repository in Cargo.toml (#575)
Point to the subdirectory which contains the crate
2023-09-06 00:08:06 -04:00
David Cook
a3a08b01ab
Adapt to new types introduced in fiat-crypto 0.2 (#566) 2023-09-05 10:07:49 -06:00
Wiktor Kwapisiewicz
135476c9f5
Fix variable names in the invariant description (#573)
Previously the variable names referred to `public` and `secret` which do
not exist. Update them to `verifying_key` and `secret_key`.
2023-09-05 08:50:10 -06:00
pinkforest(she/her)
5c5a32057c
curve: Fix no_std for fiat backend and add test for it (#572) 2023-09-04 13:49:58 -06:00
Rob Ede
c8d1d400f1
curve,ed: chore: update dev deps (#569) 2023-08-28 09:46:38 -04:00
Tony Arcieri
60dd3100c0
curve: add doc(hidden) to serial backend modules (#568)
We have a lot of backend types leaking via the public API, including
e.g. `FieldElement51`:

https://docs.rs/curve25519-dalek/latest/curve25519_dalek/backend/serial/u64/field/struct.FieldElement51.html

At the very least, these types shouldn't be visible in the rustdoc.

This PR hides them from the docs, but ideally we would hide them
completely from the public API (which might technically be considered a
breaking change, but IMO leaking them at all is a bug).
2023-08-28 02:38:11 -04:00
Michael Rosenberg
594b1f9ffe
Updated Cargo.toml repo and homepage links to the Github monorepo 2023-08-28 02:36:14 -04:00
Tony Arcieri
c058cd9057
curve: Expand lints (#530)
Adds a lints section to the top of lib.rs with the following:

    #![warn(
        clippy::unwrap_used,
        missing_docs,
        rust_2018_idioms,
        unused_lifetimes,
        unused_qualifications
    )]

`warn` is used instead of `deny` to prevent the lints from firing during
local development, however we already configure `-D warnings` in CI so
if any lint fails on checked-in code, it will cause a CI failure.

This commit also fixes or explicitly allows any current violations of
these lints. The main ones were:

- `clippy::unwrap_used`: replaces usages of `unwrap` with `expect`
- `rust_2018_idioms`: no implicit lifetimes, which were present on
  usages of `core::fmt::Formatter`
2023-08-28 02:32:31 -04:00
Michael Rosenberg
8e0cef5b72
curve: Add arbitrary integer multiplication with MontgomeryPoint::mul_bits_be (#555)
There is occasionally [a need](https://github.com/dalek-cryptography/curve25519-dalek/pull/519#issuecomment-1637770888) to multiply a non-prime-order Montgomery point by an integer. There's currently no way to do this, since our only methods are multiplication by `Scalar` (doesn't make sense in the non-prime-order case), and `MontgomeryPoint::mul_base_clamped` clamps the integer before multiplying.

This defines `MontgomeryPoint::mul_bits_be`, which takes a big-endian representation of an integer and multiplies the point by that integer. Its usage is not recommended by default, but it is also not so unsafe as to be gated behind a `hazmat` feature.
2023-08-28 01:58:41 -04:00
pinkforest(she/her)
4373695c50
curve: implement ff and group traits (#562)
Originally authored by @str4d as #473
2023-08-27 14:41:06 -06:00
Sören Meier
098658dc8b
ed: Add SigningKey::as_bytes (#561)
Allows to get a reference to the secret bytes without making a copy.
2023-08-27 14:28:06 -06:00
Rob Ede
b93ace8c7f
Address Clippy lints (#543) 2023-08-27 12:47:12 -06:00
Matt Johnston
c66973c823
ed: ConstantTimeEq and PartialEq for SigningKey (#557) 2023-08-12 01:49:16 -04:00
moiseev-signal
bf2c4eea23
curve: Mark scalar::clamp_integer as must_use (#558) 2023-08-12 01:44:09 -04:00
Elichai Turkel
6dd17b2836
x: Mark x25519-dalek version 2 as stable (#554) 2023-08-11 18:18:15 -04:00
Michael Rosenberg
42b55fd117
ed: Bump ed25519-dalek to 2.0.0 (#559)
* Made clippy happy
2023-08-11 11:38:43 -04:00
Tony Arcieri
345364d4ec
Update README.md
Use non-breaking hyphens in crate names in table
2023-07-27 18:17:00 -06:00
64 changed files with 3801 additions and 2618 deletions

View File

@ -3,10 +3,14 @@ name: curve25519 Rust
on:
push:
branches: [ '**' ]
paths: 'curve25519-dalek/**'
paths:
- 'curve25519-dalek/**'
- '.github/workflows/curve25519-dalek.yml'
pull_request:
branches: [ '**' ]
paths: 'curve25519-dalek/**'
paths:
- 'curve25519-dalek/**'
- '.github/workflows/curve25519-dalek.yml'
defaults:
run:
@ -39,6 +43,31 @@ jobs:
RUSTFLAGS: '--cfg curve25519_dalek_backend="fiat"'
run: cargo test --target ${{ matrix.target }}
# Default no_std test only tests using serial across all crates
build-nostd-fiat:
name: Build fiat on no_std target (thumbv7em-none-eabi)
runs-on: ubuntu-latest
strategy:
matrix:
include:
- crate: curve25519-dalek
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
- name: no_std fiat / no feat ${{ matrix.crate }}
env:
RUSTFLAGS: '--cfg curve25519_dalek_backend="fiat"'
run: cargo build -p ${{ matrix.crate }} --target thumbv7em-none-eabi --release --no-default-features
- name: no_std fiat / cargo hack ${{ matrix.crate }}
env:
RUSTFLAGS: '--cfg curve25519_dalek_backend="fiat"'
run: cargo hack build -p ${{ matrix.crate }} --target thumbv7em-none-eabi --release --each-feature --exclude-features default,std,getrandom
test-serial:
name: Test serial backend
runs-on: ubuntu-latest
@ -98,17 +127,16 @@ jobs:
# This should automatically pick up the simd backend in a x86_64 runner
# It should pick AVX2 due to stable toolchain used since AVX512 requires nigthly
RUSTFLAGS: '-C target_feature=+avx2'
run: cargo test --no-default-features --features alloc,precomputed-tables,zeroize --target x86_64-unknown-linux-gnu
run: cargo test --no-default-features --features alloc,precomputed-tables,zeroize,group-bits --target x86_64-unknown-linux-gnu
msrv:
name: Current MSRV is 1.60.0
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# First run `cargo +nightly -Z minimal-verisons check` in order to get a
# Cargo.lock with the oldest possible deps
# Re-resolve Cargo.lock with minimal versions
- uses: dtolnay/rust-toolchain@nightly
- run: cargo -Z minimal-versions check --no-default-features --features serde
- run: cargo update -Z minimal-versions
# Now check that `cargo build` works with respect to the oldest possible
# deps and the stated MSRV
- uses: dtolnay/rust-toolchain@1.60.0

View File

@ -24,10 +24,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# Now run `cargo +nightly -Z minimal-verisons check` in order to get a
# Cargo.lock with the oldest possible deps
# Re-resolve Cargo.lock with minimal versions
- uses: dtolnay/rust-toolchain@nightly
- run: cargo -Z minimal-versions check --no-default-features --features serde
- run: cargo update -Z minimal-versions
# Now check that `cargo build` works with respect to the oldest possible
# deps and the stated MSRV
- uses: dtolnay/rust-toolchain@1.60.0

View File

@ -1,35 +0,0 @@
name: no_std
on:
push:
branches: [ '**' ]
pull_request:
branches: [ '**' ]
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: '-D warnings'
jobs:
build-nostd:
name: Build on no_std target (thumbv7em-none-eabi)
runs-on: ubuntu-latest
strategy:
matrix:
include:
- crate: curve25519-dalek
- crate: ed25519-dalek
- crate: x25519-dalek
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
- name: no_std / no feat ${{ matrix.crate }}
run: cargo build -p ${{ matrix.crate }} --target thumbv7em-none-eabi --release --no-default-features
- name: no_std / cargo hack ${{ matrix.crate }}
run: cargo hack build -p ${{ matrix.crate }} --target thumbv7em-none-eabi --release --each-feature --exclude-features default,std,getrandom

View File

@ -57,15 +57,38 @@ jobs:
- name: Build default (host native) bench
run: cargo build --benches
# Test no_std with serial (default)
build-nostd-serial:
name: Build serial on no_std target (thumbv7em-none-eabi)
runs-on: ubuntu-latest
strategy:
matrix:
include:
- crate: curve25519-dalek
- crate: ed25519-dalek
- crate: x25519-dalek
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
- name: no_std / no feat ${{ matrix.crate }}
run: cargo build -p ${{ matrix.crate }} --target thumbv7em-none-eabi --release --no-default-features
- name: no_std / cargo hack ${{ matrix.crate }}
run: cargo hack build -p ${{ matrix.crate }} --target thumbv7em-none-eabi --release --each-feature --exclude-features default,std,getrandom
clippy:
name: Check that clippy is happy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@nightly
- uses: dtolnay/rust-toolchain@1.73.0
with:
components: clippy
- run: cargo clippy --target x86_64-unknown-linux-gnu
- run: cargo clippy --target x86_64-unknown-linux-gnu --all-features
rustfmt:
name: Check formatting

View File

@ -24,10 +24,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# Now run `cargo +nightly -Z minimal-verisons check` in order to get a
# Cargo.lock with the oldest possible deps
# Re-resolve Cargo.lock with minimal versions
- uses: dtolnay/rust-toolchain@nightly
- run: cargo -Z minimal-versions check --no-default-features --features serde
- run: cargo update -Z minimal-versions
# Now check that `cargo build` works with respect to the oldest possible
# deps and the stated MSRV
- uses: dtolnay/rust-toolchain@1.60.0

View File

@ -8,13 +8,12 @@
# Dalek elliptic curve cryptography
This repo contains pure-Rust crates for elliptic curve cryptography:
[![curve25519 Rust]()](https://github.com/dalek-cryptography/curve25519-dalek/actions/workflows/curve25519-dalek.yml)
| Crate | Description | Crates.io | Docs | CI |
-------------------------------------------|----------------|-----------|------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| [`curve25519-dalek`](./curve25519-dalek) | A library for arithmetic over the Curve25519 and Ristretto elliptic curves and their associated scalars. | [![](https://img.shields.io/crates/v/curve25519-dalek.svg)](https://crates.io/crates/curve25519-dalek) | [![](https://img.shields.io/docsrs/curve25519-dalek)](https://docs.rs/curve25519-dalek) | [![CI](https://github.com/dalek-cryptography/curve25519-dalek/actions/workflows/curve25519-dalek.yml/badge.svg?branch=main)](https://github.com/dalek-cryptography/curve25519-dalek/actions/workflows/curve25519-dalek.yml) |
| [`ed25519-dalek`](./ed25519-dalek) | An implementation of the EdDSA digital signature scheme over Curve25519. | [![](https://img.shields.io/crates/v/ed25519-dalek.svg)](https://crates.io/crates/ed25519-dalek) | [![](https://docs.rs/ed25519-dalek/badge.svg)](https://docs.rs/ed25519-dalek) | [![CI](https://github.com/dalek-cryptography/curve25519-dalek/actions/workflows/ed25519-dalek.yml/badge.svg?branch=main)](https://github.com/dalek-cryptography/curve25519-dalek/actions/workflows/ed25519-dalek.yml) |
| [`x25519-dalek`](./x25519-dalek) | An implementation of elliptic curve Diffie-Hellman key exchange over Curve25519. | [![](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) | [![CI](https://github.com/dalek-cryptography/curve25519-dalek/actions/workflows/x25519-dalek.yml/badge.svg?branch=main)](https://github.com/dalek-cryptography/curve25519-dalek/actions/workflows/x25519-dalek.yml) |
| [`curve25519dalek`](./curve25519-dalek) | A library for arithmetic over the Curve25519 and Ristretto elliptic curves and their associated scalars. | [![](https://img.shields.io/crates/v/curve25519-dalek.svg)](https://crates.io/crates/curve25519-dalek) | [![](https://img.shields.io/docsrs/curve25519-dalek)](https://docs.rs/curve25519-dalek) | [![CI](https://github.com/dalek-cryptography/curve25519-dalek/actions/workflows/curve25519-dalek.yml/badge.svg?branch=main)](https://github.com/dalek-cryptography/curve25519-dalek/actions/workflows/curve25519-dalek.yml) |
| [`ed25519dalek`](./ed25519-dalek) | An implementation of the EdDSA digital signature scheme over Curve25519. | [![](https://img.shields.io/crates/v/ed25519-dalek.svg)](https://crates.io/crates/ed25519-dalek) | [![](https://docs.rs/ed25519-dalek/badge.svg)](https://docs.rs/ed25519-dalek) | [![CI](https://github.com/dalek-cryptography/curve25519-dalek/actions/workflows/ed25519-dalek.yml/badge.svg?branch=main)](https://github.com/dalek-cryptography/curve25519-dalek/actions/workflows/ed25519-dalek.yml) |
| [`x25519dalek`](./x25519-dalek) | An implementation of elliptic curve Diffie-Hellman key exchange over Curve25519. | [![](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) | [![CI](https://github.com/dalek-cryptography/curve25519-dalek/actions/workflows/x25519-dalek.yml/badge.svg?branch=main)](https://github.com/dalek-cryptography/curve25519-dalek/actions/workflows/x25519-dalek.yml) |
There is also the [`curve25519-dalek-derive`](./curve25519-dalek-derive) crate, which is just a helper crate with some macros that make curve25519-dalek easier to write.

View File

@ -0,0 +1,8 @@
# Changelog
Entries are listed in reverse chronological order per undeprecated
major series.
### 0.1.1
* Copied over license files from [original](https://github.com/koute/unsafe_target_feature/tree/389ae00d34cf0ff589cb8d9b38a85ae1b05ebfdc) repo

View File

@ -1,12 +1,12 @@
[package]
name = "curve25519-dalek-derive"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
repository = "https://github.com/dalek-cryptography/curve25519-dalek"
homepage = "https://github.com/dalek-cryptography/curve25519-dalek"
documentation = "https://docs.rs/curve25519-dalek-derive"
license = "MIT/Apache-2.0"
license = "MIT OR Apache-2.0"
readme = "README.md"
description = "curve25519-dalek Derives"

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -44,6 +44,7 @@ to build out more elaborate abstractions it starts to become painful to use.
}
struct AVX;
# #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
impl Backend for AVX {
#[target_feature(enable = "avx")]
unsafe fn sum(xs: &[u32]) -> u32 {
@ -53,6 +54,7 @@ to build out more elaborate abstractions it starts to become painful to use.
}
struct AVX2;
# #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
impl Backend for AVX2 {
#[target_feature(enable = "avx2")]
unsafe fn sum(xs: &[u32]) -> u32 {
@ -87,6 +89,7 @@ fn func() {}
```rust
// It works, but must be `unsafe`
# #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
unsafe fn func() {}
```
@ -95,6 +98,7 @@ unsafe fn func() {}
use curve25519_dalek_derive::unsafe_target_feature;
// No `unsafe` on the function itself!
# #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[unsafe_target_feature("avx2")]
fn func() {}
```
@ -119,6 +123,7 @@ use curve25519_dalek_derive::unsafe_target_feature;
struct S;
# #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[unsafe_target_feature("avx2")]
impl core::ops::Add for S {
type Output = S;
@ -135,6 +140,7 @@ impl core::ops::Add for S {
```rust
use curve25519_dalek_derive::unsafe_target_feature_specialize;
# #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[unsafe_target_feature_specialize("sse2", "avx2", conditional("avx512ifma", nightly))]
mod simd {
#[for_target_feature("sse2")]
@ -149,6 +155,7 @@ mod simd {
pub fn func() { /* ... */ }
}
# #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn entry_point() {
#[cfg(nightly)]
if std::is_x86_feature_detected!("avx512ifma") {
@ -179,8 +186,8 @@ fn entry_point() {
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>)
* Apache License, Version 2.0, [LICENSE-APACHE](LICENSE-APACHE)
* MIT license ([LICENSE-MIT](LICENSE-MIT))
at your option.

View File

@ -23,10 +23,6 @@ where
a - b
}
#[unsafe_target_feature("sse2")]
#[cfg(feature = "dummy")]
fn function_with_cfg() {}
#[unsafe_target_feature("sse2")]
#[rustfmt::skip]
fn function_with_rustfmt_skip() {}
@ -45,9 +41,6 @@ impl Struct {
fn member_function_with_const_arg<const N: u32>(self) -> u32 {
self.a - N
}
#[cfg(feature = "dummy")]
fn member_function_with_cfg() {}
}
struct StructWithGenerics<T>
@ -83,9 +76,7 @@ impl<T: Copy + core::ops::Sub> StructWithGenericsNoWhere<T> {
#[unsafe_target_feature("sse2")]
#[allow(dead_code)]
impl<'a> From<&'a Struct> for () {
fn from(_: &'a Struct) -> Self {
()
}
fn from(_: &'a Struct) -> Self {}
}
#[unsafe_target_feature("sse2")]
@ -95,10 +86,8 @@ mod inner {
}
}
#[unsafe_target_feature_specialize("sse2", "avx2", conditional("avx512ifma", disabled))]
#[unsafe_target_feature_specialize("sse2", "avx2")]
mod inner_spec {
use std;
#[for_target_feature("sse2")]
const CONST: u32 = 1;

View File

@ -5,6 +5,29 @@ major series.
## 4.x series
### 4.1.3
* Security: Fix timing leak in Scalar subtraction on u32, u64, fiat_u32, and fiat_u64 backends
* Fix assorted new warnings and lints from rustc and clippy
### 4.1.2
* Fix nightly SIMD build
### 4.1.1
* Mark `constants::BASEPOINT_ORDER` deprecated from pub API
* Add implementation for `PrimeFieldBits`, behind the `group-bits` feature flag.
### 4.1.0
* Add arbitrary integer multiplication with `MontgomeryPoint::mul_bits_be`
* Add implementations of the `ff` and `group` traits, behind the `group` feature flag
* Adapt to new types introduced in `fiat-crypto` 0.2 in `fiat` backend
* Fix `no_std` for `fiat` backend
* Mark `Scalar::clamp_integer` as `#[must_use]`
* Various documentation fixes
### 4.0.0
#### Breaking changes
@ -109,7 +132,7 @@ major series.
### 2.1.2
* Multiple documenation typo fixes.
* Multiple documentation typo fixes.
* Fix `alloc` feature working with stable rust.
### 2.1.1

View File

@ -4,14 +4,14 @@ name = "curve25519-dalek"
# - update CHANGELOG
# - update README if required by semver
# - if README was updated, also update module documentation in src/lib.rs
version = "4.0.0"
version = "4.1.3"
edition = "2021"
rust-version = "1.60.0"
authors = ["Isis Lovecruft <isis@patternsinthevoid.net>",
"Henry de Valence <hdevalence@hdevalence.ca>"]
readme = "README.md"
license = "BSD-3-Clause"
repository = "https://github.com/dalek-cryptography/curve25519-dalek"
repository = "https://github.com/dalek-cryptography/curve25519-dalek/tree/main/curve25519-dalek"
homepage = "https://github.com/dalek-cryptography/curve25519-dalek"
documentation = "https://docs.rs/curve25519-dalek"
categories = ["cryptography", "no-std"]
@ -27,18 +27,17 @@ rustdoc-args = [
"--html-in-header", "docs/assets/rustdoc-include-katex-header.html",
"--cfg", "docsrs",
]
features = ["serde", "rand_core", "digest", "legacy_compatibility"]
features = ["serde", "rand_core", "digest", "legacy_compatibility", "group-bits"]
[dev-dependencies]
sha2 = { version = "0.10", default-features = false }
bincode = "1"
criterion = { version = "0.4.0", features = ["html_reports"] }
criterion = { version = "0.5", features = ["html_reports"] }
hex = "0.4.2"
rand = "0.8"
rand_core = { version = "0.6", default-features = false, features = ["getrandom"] }
[build-dependencies]
platforms = "3.0.2"
rustc_version = "0.4.0"
[[bench]]
@ -48,6 +47,8 @@ required-features = ["alloc", "rand_core"]
[dependencies]
cfg-if = "1"
ff = { version = "0.13", default-features = false, optional = true }
group = { version = "0.13", default-features = false, optional = true }
rand_core = { version = "0.6.4", default-features = false, optional = true }
digest = { version = "0.10", default-features = false, optional = true }
subtle = { version = "2.3.0", default-features = false }
@ -58,13 +59,15 @@ zeroize = { version = "1", default-features = false, optional = true }
cpufeatures = "0.2.6"
[target.'cfg(curve25519_dalek_backend = "fiat")'.dependencies]
fiat-crypto = "0.1.19"
fiat-crypto = { version = "0.2.1", default-features = false }
[features]
default = ["alloc", "precomputed-tables", "zeroize", "lizard"]
alloc = ["zeroize?/alloc"]
precomputed-tables = []
legacy_compatibility = []
group = ["dep:group", "rand_core"]
group-bits = ["group", "ff/bits"]
lizard = ["digest"]
[target.'cfg(all(not(curve25519_dalek_backend = "fiat"), not(curve25519_dalek_backend = "serial"), target_arch = "x86_64"))'.dependencies]

View File

@ -38,6 +38,12 @@ your project's `Cargo.toml`:
curve25519-dalek = "4"
```
If opting into [SemVer-exempted features](#public-api-semver-exemptions) a range
can be used to scope the tested compatible version range e.g.:
```toml
curve25519-dalek = ">= 4.0, < 4.2"
```
## Feature Flags
| Feature | Default? | Description |
@ -49,6 +55,7 @@ curve25519-dalek = "4"
| `digest` | | Enables `RistrettoPoint::{from_hash, hash_from_bytes}` and `Scalar::{from_hash, hash_from_bytes}`. This is an optional dependency whose version is not subject to SemVer. See [below](#public-api-semver-exemptions) for more details. |
| `serde` | | Enables `serde` serialization/deserialization for all the point and scalar types. |
| `legacy_compatibility`| | Enables `Scalar::from_bits`, which allows the user to build unreduced scalars whose arithmetic is broken. Do not use this unless you know what you're doing. |
| `group` | | Enables external `group` and `ff` crate traits |
To disable the default features when using `curve25519-dalek` as a dependency,
add `default-features = false` to the dependency in your `Cargo.toml`. To
@ -187,12 +194,12 @@ From 4.x and on, MSRV changes will be accompanied by a minor version bump.
## Public API SemVer Exemptions
Breaking changes to SemVer exempted components affecting the public API will be accompanied by
Breaking changes to SemVer-exempted components affecting the public API will be accompanied by
_some_ version bump. Below are the specific policies:
| Releases | Public API Component(s) | Policy |
| :--- | :--- | :--- |
| 4.x | Dependencies `digest` and `rand_core` | Minor SemVer bump |
| Releases | Public API Component(s) | Policy |
| :--- | :--- | :--- |
| 4.x | Dependencies `group`, `digest` and `rand_core` | Minor SemVer bump |
# Safety
@ -302,8 +309,8 @@ Thanks also to Ashley Hauck, Lucas Salibian, Manish Goregaokar, Jack Grigg,
Pratyush Mishra, Michael Rosenberg, @pinkforest, and countless others for their
contributions.
[ed25519-dalek]: https://github.com/dalek-cryptography/ed25519-dalek
[x25519-dalek]: https://github.com/dalek-cryptography/x25519-dalek
[ed25519-dalek]: https://github.com/dalek-cryptography/curve25519-dalek/tree/main/ed25519-dalek
[x25519-dalek]: https://github.com/dalek-cryptography/curve25519-dalek/tree/main/x25519-dalek
[docs]: https://docs.rs/curve25519-dalek/
[contributing]: https://github.com/dalek-cryptography/curve25519-dalek/blob/master/CONTRIBUTING.md
[criterion]: https://github.com/japaric/criterion.rs

View File

@ -7,7 +7,6 @@ use criterion::{
};
use curve25519_dalek::constants;
use curve25519_dalek::field::FieldElement;
use curve25519_dalek::scalar::Scalar;
static BATCH_SIZES: [usize; 5] = [1, 2, 4, 8, 16];
@ -148,14 +147,14 @@ mod multiscalar_benches {
let static_size = total_size;
let static_points = construct_points(static_size);
let precomp = VartimeEdwardsPrecomputation::new(&static_points);
let precomp = VartimeEdwardsPrecomputation::new(static_points);
// Rerandomize the scalars for every call to prevent
// false timings from better caching (e.g., the CPU
// cache lifts exactly the right table entries for the
// benchmark into the highest cache levels).
b.iter_batched(
|| construct_scalars(static_size),
|scalars| precomp.vartime_multiscalar_mul(&scalars),
|scalars| precomp.vartime_multiscalar_mul(scalars),
BatchSize::SmallInput,
);
},
@ -183,7 +182,7 @@ mod multiscalar_benches {
let static_points = construct_points(static_size);
let dynamic_points = construct_points(dynamic_size);
let precomp = VartimeEdwardsPrecomputation::new(&static_points);
let precomp = VartimeEdwardsPrecomputation::new(static_points);
// Rerandomize the scalars for every call to prevent
// false timings from better caching (e.g., the CPU
// cache lifts exactly the right table entries for the
@ -245,11 +244,8 @@ mod ristretto_benches {
}
fn elligator<M: Measurement>(c: &mut BenchmarkGroup<M>) {
let fe_bytes = [0u8; 32];
let fe = FieldElement::from_bytes(&fe_bytes);
c.bench_function("RistrettoPoint Elligator", |b| {
b.iter(|| RistrettoPoint::elligator_ristretto_flavor(&fe));
b.iter(|| RistrettoPoint::from_uniform_bytes_single_elligator(&[0u8; 32]));
});
}

View File

@ -9,17 +9,31 @@ enum DalekBits {
Dalek64,
}
use std::fmt::Formatter;
impl std::fmt::Display for DalekBits {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
let w_bits = match self {
DalekBits::Dalek32 => "32",
DalekBits::Dalek64 => "64",
};
write!(f, "{}", w_bits)
}
}
fn main() {
let target_arch = match std::env::var("CARGO_CFG_TARGET_ARCH") {
Ok(arch) => arch,
_ => "".to_string(),
};
let curve25519_dalek_bits = match std::env::var("CARGO_CFG_CURVE25519_DALEK_BITS").as_deref() {
Ok("32") => DalekBits::Dalek32,
Ok("64") => DalekBits::Dalek64,
_ => deterministic::determine_curve25519_dalek_bits(),
_ => deterministic::determine_curve25519_dalek_bits(&target_arch),
};
match curve25519_dalek_bits {
DalekBits::Dalek64 => println!("cargo:rustc-cfg=curve25519_dalek_bits=\"64\""),
DalekBits::Dalek32 => println!("cargo:rustc-cfg=curve25519_dalek_bits=\"32\""),
}
println!("cargo:rustc-cfg=curve25519_dalek_bits=\"{curve25519_dalek_bits}\"");
if rustc_version::version_meta()
.expect("failed to detect rustc version")
@ -36,11 +50,6 @@ fn main() {
println!("cargo:rustc-cfg=allow_unused_unsafe");
}
let target_arch = match std::env::var("CARGO_CFG_TARGET_ARCH") {
Ok(arch) => arch,
_ => "".to_string(),
};
// Backend overrides / defaults
let curve25519_dalek_backend =
match std::env::var("CARGO_CFG_CURVE25519_DALEK_BACKEND").as_deref() {
@ -74,11 +83,12 @@ mod deterministic {
use super::*;
// Standard Cargo TARGET environment variable of triplet is required
static ERR_MSG_NO_TARGET: &str = "Standard Cargo TARGET environment variable is not set";
// Custom Rust non-cargo build tooling needs to set CARGO_CFG_TARGET_POINTER_WIDTH
static ERR_MSG_NO_POINTER_WIDTH: &str =
"Standard Cargo TARGET_POINTER_WIDTH environment variable is not set.";
// Custom Non-Rust standard target platforms require explicit settings.
static ERR_MSG_NO_PLATFORM: &str = "Unknown Rust target platform.";
// When either non-32 or 64 TARGET_POINTER_WIDTH detected
static ERR_MSG_UNKNOWN_POINTER_WIDTH: &str = "Unknown TARGET_POINTER_WIDTH detected.";
// Warning when the curve25519_dalek_bits cannot be determined
fn determine_curve25519_dalek_bits_warning(cause: &str) {
@ -86,41 +96,30 @@ mod deterministic {
}
// Determine the curve25519_dalek_bits based on Rust standard TARGET triplet
pub(super) fn determine_curve25519_dalek_bits() -> DalekBits {
use platforms::target::PointerWidth;
// TARGET environment is supplied by Cargo
// https://doc.rust-lang.org/cargo/reference/environment-variables.html
let target_triplet = match std::env::var("TARGET") {
Ok(t) => t,
pub(super) fn determine_curve25519_dalek_bits(target_arch: &String) -> DalekBits {
let target_pointer_width = match std::env::var("CARGO_CFG_TARGET_POINTER_WIDTH") {
Ok(pw) => pw,
Err(_) => {
determine_curve25519_dalek_bits_warning(ERR_MSG_NO_TARGET);
return DalekBits::Dalek32;
}
};
// platforms crate is the source of truth used to determine the platform
let platform = match platforms::Platform::find(&target_triplet) {
Some(p) => p,
None => {
determine_curve25519_dalek_bits_warning(ERR_MSG_NO_PLATFORM);
determine_curve25519_dalek_bits_warning(ERR_MSG_NO_POINTER_WIDTH);
return DalekBits::Dalek32;
}
};
#[allow(clippy::match_single_binding)]
match platform.target_arch {
match &target_arch {
//Issues: 449 and 456
//TODO: When adding arch defaults use proper types not String match
//TODO(Arm): Needs tests + benchmarks to back this up
//platforms::target::Arch::Arm => DalekBits::Dalek64,
//TODO(Wasm32): Needs tests + benchmarks to back this up
//platforms::target::Arch::Wasm32 => DalekBits::Dalek64,
_ => match platform.target_pointer_width {
PointerWidth::U64 => DalekBits::Dalek64,
PointerWidth::U32 => DalekBits::Dalek32,
_ => match target_pointer_width.as_ref() {
"64" => DalekBits::Dalek64,
"32" => DalekBits::Dalek32,
// Intended default solely for non-32/64 target pointer widths
// Otherwise known target platforms only.
_ => DalekBits::Dalek32,
_ => {
determine_curve25519_dalek_bits_warning(ERR_MSG_UNKNOWN_POINTER_WIDTH);
DalekBits::Dalek32
}
},
}
}

View File

@ -351,7 +351,7 @@ This computation requires 25 `vpmadd52luq` and 25 `vpmadd52huq`
operations. For 256-bit vectors, IFMA operations execute on an
i3-8121U with latency 4 cycles, throughput 0.5 cycles, so executing 50
instructions requires 25 cycles' worth of throughput. Accumulating
terms with coefficient \\(1\\) and \\(2\\) seperately means that the
terms with coefficient \\(1\\) and \\(2\\) separately means that the
longest dependency chain has length 5, so the critical path has length
20 cycles and the bottleneck is throughput.

View File

@ -87,24 +87,24 @@ where
match get_selected_backend() {
#[cfg(curve25519_dalek_backend = "simd")]
BackendKind::Avx2 =>
self::vector::scalar_mul::pippenger::spec_avx2::Pippenger::optional_multiscalar_mul::<I, J>(scalars, points),
vector::scalar_mul::pippenger::spec_avx2::Pippenger::optional_multiscalar_mul::<I, J>(scalars, points),
#[cfg(all(curve25519_dalek_backend = "simd", nightly))]
BackendKind::Avx512 =>
self::vector::scalar_mul::pippenger::spec_avx512ifma_avx512vl::Pippenger::optional_multiscalar_mul::<I, J>(scalars, points),
vector::scalar_mul::pippenger::spec_avx512ifma_avx512vl::Pippenger::optional_multiscalar_mul::<I, J>(scalars, points),
BackendKind::Serial =>
self::serial::scalar_mul::pippenger::Pippenger::optional_multiscalar_mul::<I, J>(scalars, points),
serial::scalar_mul::pippenger::Pippenger::optional_multiscalar_mul::<I, J>(scalars, points),
}
}
#[cfg(feature = "alloc")]
pub(crate) enum VartimePrecomputedStraus {
#[cfg(curve25519_dalek_backend = "simd")]
Avx2(self::vector::scalar_mul::precomputed_straus::spec_avx2::VartimePrecomputedStraus),
Avx2(vector::scalar_mul::precomputed_straus::spec_avx2::VartimePrecomputedStraus),
#[cfg(all(curve25519_dalek_backend = "simd", nightly))]
Avx512ifma(
self::vector::scalar_mul::precomputed_straus::spec_avx512ifma_avx512vl::VartimePrecomputedStraus,
vector::scalar_mul::precomputed_straus::spec_avx512ifma_avx512vl::VartimePrecomputedStraus,
),
Scalar(self::serial::scalar_mul::precomputed_straus::VartimePrecomputedStraus),
Scalar(serial::scalar_mul::precomputed_straus::VartimePrecomputedStraus),
}
#[cfg(feature = "alloc")]
@ -119,12 +119,12 @@ impl VartimePrecomputedStraus {
match get_selected_backend() {
#[cfg(curve25519_dalek_backend = "simd")]
BackendKind::Avx2 =>
VartimePrecomputedStraus::Avx2(self::vector::scalar_mul::precomputed_straus::spec_avx2::VartimePrecomputedStraus::new(static_points)),
VartimePrecomputedStraus::Avx2(vector::scalar_mul::precomputed_straus::spec_avx2::VartimePrecomputedStraus::new(static_points)),
#[cfg(all(curve25519_dalek_backend = "simd", nightly))]
BackendKind::Avx512 =>
VartimePrecomputedStraus::Avx512ifma(self::vector::scalar_mul::precomputed_straus::spec_avx512ifma_avx512vl::VartimePrecomputedStraus::new(static_points)),
VartimePrecomputedStraus::Avx512ifma(vector::scalar_mul::precomputed_straus::spec_avx512ifma_avx512vl::VartimePrecomputedStraus::new(static_points)),
BackendKind::Serial =>
VartimePrecomputedStraus::Scalar(self::serial::scalar_mul::precomputed_straus::VartimePrecomputedStraus::new(static_points))
VartimePrecomputedStraus::Scalar(serial::scalar_mul::precomputed_straus::VartimePrecomputedStraus::new(static_points))
}
}
@ -179,19 +179,16 @@ where
match get_selected_backend() {
#[cfg(curve25519_dalek_backend = "simd")]
BackendKind::Avx2 => {
self::vector::scalar_mul::straus::spec_avx2::Straus::multiscalar_mul::<I, J>(
scalars, points,
)
vector::scalar_mul::straus::spec_avx2::Straus::multiscalar_mul::<I, J>(scalars, points)
}
#[cfg(all(curve25519_dalek_backend = "simd", nightly))]
BackendKind::Avx512 => {
self::vector::scalar_mul::straus::spec_avx512ifma_avx512vl::Straus::multiscalar_mul::<
I,
J,
>(scalars, points)
vector::scalar_mul::straus::spec_avx512ifma_avx512vl::Straus::multiscalar_mul::<I, J>(
scalars, points,
)
}
BackendKind::Serial => {
self::serial::scalar_mul::straus::Straus::multiscalar_mul::<I, J>(scalars, points)
serial::scalar_mul::straus::Straus::multiscalar_mul::<I, J>(scalars, points)
}
}
}
@ -209,21 +206,19 @@ where
match get_selected_backend() {
#[cfg(curve25519_dalek_backend = "simd")]
BackendKind::Avx2 => {
self::vector::scalar_mul::straus::spec_avx2::Straus::optional_multiscalar_mul::<I, J>(
vector::scalar_mul::straus::spec_avx2::Straus::optional_multiscalar_mul::<I, J>(
scalars, points,
)
}
#[cfg(all(curve25519_dalek_backend = "simd", nightly))]
BackendKind::Avx512 => {
self::vector::scalar_mul::straus::spec_avx512ifma_avx512vl::Straus::optional_multiscalar_mul::<
vector::scalar_mul::straus::spec_avx512ifma_avx512vl::Straus::optional_multiscalar_mul::<
I,
J,
>(scalars, points)
}
BackendKind::Serial => {
self::serial::scalar_mul::straus::Straus::optional_multiscalar_mul::<I, J>(
scalars, points,
)
serial::scalar_mul::straus::Straus::optional_multiscalar_mul::<I, J>(scalars, points)
}
}
}
@ -232,12 +227,12 @@ where
pub fn variable_base_mul(point: &EdwardsPoint, scalar: &Scalar) -> EdwardsPoint {
match get_selected_backend() {
#[cfg(curve25519_dalek_backend = "simd")]
BackendKind::Avx2 => self::vector::scalar_mul::variable_base::spec_avx2::mul(point, scalar),
BackendKind::Avx2 => vector::scalar_mul::variable_base::spec_avx2::mul(point, scalar),
#[cfg(all(curve25519_dalek_backend = "simd", nightly))]
BackendKind::Avx512 => {
self::vector::scalar_mul::variable_base::spec_avx512ifma_avx512vl::mul(point, scalar)
vector::scalar_mul::variable_base::spec_avx512ifma_avx512vl::mul(point, scalar)
}
BackendKind::Serial => self::serial::scalar_mul::variable_base::mul(point, scalar),
BackendKind::Serial => serial::scalar_mul::variable_base::mul(point, scalar),
}
}
@ -246,11 +241,11 @@ pub fn variable_base_mul(point: &EdwardsPoint, scalar: &Scalar) -> EdwardsPoint
pub fn vartime_double_base_mul(a: &Scalar, A: &EdwardsPoint, b: &Scalar) -> EdwardsPoint {
match get_selected_backend() {
#[cfg(curve25519_dalek_backend = "simd")]
BackendKind::Avx2 => self::vector::scalar_mul::vartime_double_base::spec_avx2::mul(a, A, b),
BackendKind::Avx2 => vector::scalar_mul::vartime_double_base::spec_avx2::mul(a, A, b),
#[cfg(all(curve25519_dalek_backend = "simd", nightly))]
BackendKind::Avx512 => {
self::vector::scalar_mul::vartime_double_base::spec_avx512ifma_avx512vl::mul(a, A, b)
vector::scalar_mul::vartime_double_base::spec_avx512ifma_avx512vl::mul(a, A, b)
}
BackendKind::Serial => self::serial::scalar_mul::vartime_double_base::mul(a, A, b),
BackendKind::Serial => serial::scalar_mul::vartime_double_base::mul(a, A, b),
}
}

View File

@ -527,7 +527,7 @@ impl<'a> Neg for &'a AffineNielsPoint {
// ------------------------------------------------------------------------
impl Debug for ProjectivePoint {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"ProjectivePoint{{\n\tX: {:?},\n\tY: {:?},\n\tZ: {:?}\n}}",
@ -537,7 +537,7 @@ impl Debug for ProjectivePoint {
}
impl Debug for CompletedPoint {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"CompletedPoint{{\n\tX: {:?},\n\tY: {:?},\n\tZ: {:?},\n\tT: {:?}\n}}",
@ -547,7 +547,7 @@ impl Debug for CompletedPoint {
}
impl Debug for AffineNielsPoint {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"AffineNielsPoint{{\n\ty_plus_x: {:?},\n\ty_minus_x: {:?},\n\txy2d: {:?}\n}}",
@ -557,7 +557,7 @@ impl Debug for AffineNielsPoint {
}
impl Debug for ProjectiveNielsPoint {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "ProjectiveNielsPoint{{\n\tY_plus_X: {:?},\n\tY_minus_X: {:?},\n\tZ: {:?},\n\tT2d: {:?}\n}}",
&self.Y_plus_X, &self.Y_minus_X, &self.Z, &self.T2d)
}

View File

@ -55,73 +55,78 @@ use fiat_crypto::curve25519_32::*;
/// The backend-specific type `FieldElement2625` should not be used
/// outside of the `curve25519_dalek::field` module.
#[derive(Copy, Clone)]
pub struct FieldElement2625(pub(crate) [u32; 10]);
pub struct FieldElement2625(pub(crate) fiat_25519_tight_field_element);
impl Debug for FieldElement2625 {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
write!(f, "FieldElement2625({:?})", &self.0[..])
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "FieldElement2625({:?})", &(self.0).0[..])
}
}
#[cfg(feature = "zeroize")]
impl Zeroize for FieldElement2625 {
fn zeroize(&mut self) {
self.0.zeroize();
(self.0).0.zeroize();
}
}
impl<'b> AddAssign<&'b FieldElement2625> for FieldElement2625 {
fn add_assign(&mut self, _rhs: &'b FieldElement2625) {
let input = self.0;
fiat_25519_add(&mut self.0, &input, &_rhs.0);
let input = self.0;
fiat_25519_carry(&mut self.0, &input);
fn add_assign(&mut self, rhs: &'b FieldElement2625) {
let mut result_loose = fiat_25519_loose_field_element([0; 10]);
fiat_25519_add(&mut result_loose, &self.0, &rhs.0);
fiat_25519_carry(&mut self.0, &result_loose);
}
}
impl<'a, 'b> Add<&'b FieldElement2625> for &'a FieldElement2625 {
type Output = FieldElement2625;
fn add(self, _rhs: &'b FieldElement2625) -> FieldElement2625 {
let mut output = *self;
fiat_25519_add(&mut output.0, &self.0, &_rhs.0);
let input = output.0;
fiat_25519_carry(&mut output.0, &input);
fn add(self, rhs: &'b FieldElement2625) -> FieldElement2625 {
let mut result_loose = fiat_25519_loose_field_element([0; 10]);
fiat_25519_add(&mut result_loose, &self.0, &rhs.0);
let mut output = FieldElement2625::ZERO;
fiat_25519_carry(&mut output.0, &result_loose);
output
}
}
impl<'b> SubAssign<&'b FieldElement2625> for FieldElement2625 {
fn sub_assign(&mut self, _rhs: &'b FieldElement2625) {
let input = self.0;
fiat_25519_sub(&mut self.0, &input, &_rhs.0);
let input = self.0;
fiat_25519_carry(&mut self.0, &input);
fn sub_assign(&mut self, rhs: &'b FieldElement2625) {
let mut result_loose = fiat_25519_loose_field_element([0; 10]);
fiat_25519_sub(&mut result_loose, &self.0, &rhs.0);
fiat_25519_carry(&mut self.0, &result_loose);
}
}
impl<'a, 'b> Sub<&'b FieldElement2625> for &'a FieldElement2625 {
type Output = FieldElement2625;
fn sub(self, _rhs: &'b FieldElement2625) -> FieldElement2625 {
let mut output = *self;
fiat_25519_sub(&mut output.0, &self.0, &_rhs.0);
let input = output.0;
fiat_25519_carry(&mut output.0, &input);
fn sub(self, rhs: &'b FieldElement2625) -> FieldElement2625 {
let mut result_loose = fiat_25519_loose_field_element([0; 10]);
fiat_25519_sub(&mut result_loose, &self.0, &rhs.0);
let mut output = FieldElement2625::ZERO;
fiat_25519_carry(&mut output.0, &result_loose);
output
}
}
impl<'b> MulAssign<&'b FieldElement2625> for FieldElement2625 {
fn mul_assign(&mut self, _rhs: &'b FieldElement2625) {
let input = self.0;
fiat_25519_carry_mul(&mut self.0, &input, &_rhs.0);
fn mul_assign(&mut self, rhs: &'b FieldElement2625) {
let mut self_loose = fiat_25519_loose_field_element([0; 10]);
fiat_25519_relax(&mut self_loose, &self.0);
let mut rhs_loose = fiat_25519_loose_field_element([0; 10]);
fiat_25519_relax(&mut rhs_loose, &rhs.0);
fiat_25519_carry_mul(&mut self.0, &self_loose, &rhs_loose);
}
}
impl<'a, 'b> Mul<&'b FieldElement2625> for &'a FieldElement2625 {
type Output = FieldElement2625;
fn mul(self, _rhs: &'b FieldElement2625) -> FieldElement2625 {
let mut output = *self;
fiat_25519_carry_mul(&mut output.0, &self.0, &_rhs.0);
fn mul(self, rhs: &'b FieldElement2625) -> FieldElement2625 {
let mut self_loose = fiat_25519_loose_field_element([0; 10]);
fiat_25519_relax(&mut self_loose, &self.0);
let mut rhs_loose = fiat_25519_loose_field_element([0; 10]);
fiat_25519_relax(&mut rhs_loose, &rhs.0);
let mut output = FieldElement2625::ZERO;
fiat_25519_carry_mul(&mut output.0, &self_loose, &rhs_loose);
output
}
}
@ -129,10 +134,10 @@ impl<'a, 'b> Mul<&'b FieldElement2625> for &'a FieldElement2625 {
impl<'a> Neg for &'a FieldElement2625 {
type Output = FieldElement2625;
fn neg(self) -> FieldElement2625 {
let mut output = *self;
fiat_25519_opp(&mut output.0, &self.0);
let input = output.0;
fiat_25519_carry(&mut output.0, &input);
let mut output_loose = fiat_25519_loose_field_element([0; 10]);
fiat_25519_opp(&mut output_loose, &self.0);
let mut output = FieldElement2625::ZERO;
fiat_25519_carry(&mut output.0, &output_loose);
output
}
}
@ -143,8 +148,13 @@ impl ConditionallySelectable for FieldElement2625 {
b: &FieldElement2625,
choice: Choice,
) -> FieldElement2625 {
let mut output = [0u32; 10];
fiat_25519_selectznz(&mut output, choice.unwrap_u8() as fiat_25519_u1, &a.0, &b.0);
let mut output = fiat_25519_tight_field_element([0u32; 10]);
fiat_25519_selectznz(
&mut output.0,
choice.unwrap_u8() as fiat_25519_u1,
&(a.0).0,
&(b.0).0,
);
FieldElement2625(output)
}
@ -161,7 +171,7 @@ impl ConditionallySelectable for FieldElement2625 {
fiat_25519_cmovznz_u32(&mut output[7], choicebit, self.0[7], other.0[7]);
fiat_25519_cmovznz_u32(&mut output[8], choicebit, self.0[8], other.0[8]);
fiat_25519_cmovznz_u32(&mut output[9], choicebit, self.0[9], other.0[9]);
*self = FieldElement2625(output);
*self = FieldElement2625::from_limbs(output);
}
fn conditional_swap(a: &mut FieldElement2625, b: &mut FieldElement2625, choice: Choice) {
@ -179,12 +189,16 @@ impl ConditionallySelectable for FieldElement2625 {
}
impl FieldElement2625 {
pub(crate) const fn from_limbs(limbs: [u32; 10]) -> FieldElement2625 {
FieldElement2625(fiat_25519_tight_field_element(limbs))
}
/// The scalar \\( 0 \\).
pub const ZERO: FieldElement2625 = FieldElement2625([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
pub const ZERO: FieldElement2625 = FieldElement2625::from_limbs([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
/// The scalar \\( 1 \\).
pub const ONE: FieldElement2625 = FieldElement2625([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
pub const ONE: FieldElement2625 = FieldElement2625::from_limbs([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
/// The scalar \\( -1 \\).
pub const MINUS_ONE: FieldElement2625 = FieldElement2625([
pub const MINUS_ONE: FieldElement2625 = FieldElement2625::from_limbs([
0x3ffffec, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff,
0x3ffffff, 0x1ffffff,
]);
@ -220,7 +234,7 @@ impl FieldElement2625 {
let mut temp = [0u8; 32];
temp.copy_from_slice(data);
temp[31] &= 127u8;
let mut output = [0u32; 10];
let mut output = fiat_25519_tight_field_element([0u32; 10]);
fiat_25519_from_bytes(&mut output, &temp);
FieldElement2625(output)
}
@ -235,20 +249,23 @@ impl FieldElement2625 {
/// Compute `self^2`.
pub fn square(&self) -> FieldElement2625 {
let mut output = *self;
fiat_25519_carry_square(&mut output.0, &self.0);
let mut self_loose = fiat_25519_loose_field_element([0; 10]);
fiat_25519_relax(&mut self_loose, &self.0);
let mut output = FieldElement2625::ZERO;
fiat_25519_carry_square(&mut output.0, &self_loose);
output
}
/// Compute `2*self^2`.
pub fn square2(&self) -> FieldElement2625 {
let mut output = *self;
let mut temp = *self;
// Void vs return type, measure cost of copying self
fiat_25519_carry_square(&mut temp.0, &self.0);
fiat_25519_add(&mut output.0, &temp.0, &temp.0);
let input = output.0;
fiat_25519_carry(&mut output.0, &input);
let mut self_loose = fiat_25519_loose_field_element([0; 10]);
fiat_25519_relax(&mut self_loose, &self.0);
let mut square = fiat_25519_tight_field_element([0; 10]);
fiat_25519_carry_square(&mut square, &self_loose);
let mut output_loose = fiat_25519_loose_field_element([0; 10]);
fiat_25519_add(&mut output_loose, &square, &square);
let mut output = FieldElement2625::ZERO;
fiat_25519_carry(&mut output.0, &output_loose);
output
}
}

View File

@ -44,73 +44,78 @@ use fiat_crypto::curve25519_64::*;
/// The backend-specific type `FieldElement51` should not be used
/// outside of the `curve25519_dalek::field` module.
#[derive(Copy, Clone)]
pub struct FieldElement51(pub(crate) [u64; 5]);
pub struct FieldElement51(pub(crate) fiat_25519_tight_field_element);
impl Debug for FieldElement51 {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
write!(f, "FieldElement51({:?})", &self.0[..])
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "FieldElement51({:?})", &(self.0).0[..])
}
}
#[cfg(feature = "zeroize")]
impl Zeroize for FieldElement51 {
fn zeroize(&mut self) {
self.0.zeroize();
(self.0).0.zeroize();
}
}
impl<'b> AddAssign<&'b FieldElement51> for FieldElement51 {
fn add_assign(&mut self, _rhs: &'b FieldElement51) {
let input = self.0;
fiat_25519_add(&mut self.0, &input, &_rhs.0);
let input = self.0;
fiat_25519_carry(&mut self.0, &input);
fn add_assign(&mut self, rhs: &'b FieldElement51) {
let mut result_loose = fiat_25519_loose_field_element([0; 5]);
fiat_25519_add(&mut result_loose, &self.0, &rhs.0);
fiat_25519_carry(&mut self.0, &result_loose);
}
}
impl<'a, 'b> Add<&'b FieldElement51> for &'a FieldElement51 {
type Output = FieldElement51;
fn add(self, _rhs: &'b FieldElement51) -> FieldElement51 {
let mut output = *self;
fiat_25519_add(&mut output.0, &self.0, &_rhs.0);
let input = output.0;
fiat_25519_carry(&mut output.0, &input);
fn add(self, rhs: &'b FieldElement51) -> FieldElement51 {
let mut result_loose = fiat_25519_loose_field_element([0; 5]);
fiat_25519_add(&mut result_loose, &self.0, &rhs.0);
let mut output = FieldElement51::ZERO;
fiat_25519_carry(&mut output.0, &result_loose);
output
}
}
impl<'b> SubAssign<&'b FieldElement51> for FieldElement51 {
fn sub_assign(&mut self, _rhs: &'b FieldElement51) {
let input = self.0;
fiat_25519_sub(&mut self.0, &input, &_rhs.0);
let input = self.0;
fiat_25519_carry(&mut self.0, &input);
fn sub_assign(&mut self, rhs: &'b FieldElement51) {
let mut result_loose = fiat_25519_loose_field_element([0; 5]);
fiat_25519_sub(&mut result_loose, &self.0, &rhs.0);
fiat_25519_carry(&mut self.0, &result_loose);
}
}
impl<'a, 'b> Sub<&'b FieldElement51> for &'a FieldElement51 {
type Output = FieldElement51;
fn sub(self, _rhs: &'b FieldElement51) -> FieldElement51 {
let mut output = *self;
fiat_25519_sub(&mut output.0, &self.0, &_rhs.0);
let input = output.0;
fiat_25519_carry(&mut output.0, &input);
fn sub(self, rhs: &'b FieldElement51) -> FieldElement51 {
let mut result_loose = fiat_25519_loose_field_element([0; 5]);
fiat_25519_sub(&mut result_loose, &self.0, &rhs.0);
let mut output = FieldElement51::ZERO;
fiat_25519_carry(&mut output.0, &result_loose);
output
}
}
impl<'b> MulAssign<&'b FieldElement51> for FieldElement51 {
fn mul_assign(&mut self, _rhs: &'b FieldElement51) {
let input = self.0;
fiat_25519_carry_mul(&mut self.0, &input, &_rhs.0);
fn mul_assign(&mut self, rhs: &'b FieldElement51) {
let mut self_loose = fiat_25519_loose_field_element([0; 5]);
fiat_25519_relax(&mut self_loose, &self.0);
let mut rhs_loose = fiat_25519_loose_field_element([0; 5]);
fiat_25519_relax(&mut rhs_loose, &rhs.0);
fiat_25519_carry_mul(&mut self.0, &self_loose, &rhs_loose);
}
}
impl<'a, 'b> Mul<&'b FieldElement51> for &'a FieldElement51 {
type Output = FieldElement51;
fn mul(self, _rhs: &'b FieldElement51) -> FieldElement51 {
let mut output = *self;
fiat_25519_carry_mul(&mut output.0, &self.0, &_rhs.0);
fn mul(self, rhs: &'b FieldElement51) -> FieldElement51 {
let mut self_loose = fiat_25519_loose_field_element([0; 5]);
fiat_25519_relax(&mut self_loose, &self.0);
let mut rhs_loose = fiat_25519_loose_field_element([0; 5]);
fiat_25519_relax(&mut rhs_loose, &rhs.0);
let mut output = FieldElement51::ZERO;
fiat_25519_carry_mul(&mut output.0, &self_loose, &rhs_loose);
output
}
}
@ -118,10 +123,10 @@ impl<'a, 'b> Mul<&'b FieldElement51> for &'a FieldElement51 {
impl<'a> Neg for &'a FieldElement51 {
type Output = FieldElement51;
fn neg(self) -> FieldElement51 {
let mut output = *self;
fiat_25519_opp(&mut output.0, &self.0);
let input = output.0;
fiat_25519_carry(&mut output.0, &input);
let mut output_loose = fiat_25519_loose_field_element([0; 5]);
fiat_25519_opp(&mut output_loose, &self.0);
let mut output = FieldElement51::ZERO;
fiat_25519_carry(&mut output.0, &output_loose);
output
}
}
@ -132,8 +137,13 @@ impl ConditionallySelectable for FieldElement51 {
b: &FieldElement51,
choice: Choice,
) -> FieldElement51 {
let mut output = [0u64; 5];
fiat_25519_selectznz(&mut output, choice.unwrap_u8() as fiat_25519_u1, &a.0, &b.0);
let mut output = fiat_25519_tight_field_element([0u64; 5]);
fiat_25519_selectznz(
&mut output.0,
choice.unwrap_u8() as fiat_25519_u1,
&(a.0).0,
&(b.0).0,
);
FieldElement51(output)
}
@ -145,25 +155,29 @@ impl ConditionallySelectable for FieldElement51 {
u64::conditional_swap(&mut a.0[4], &mut b.0[4], choice);
}
fn conditional_assign(&mut self, _rhs: &FieldElement51, choice: Choice) {
fn conditional_assign(&mut self, rhs: &FieldElement51, choice: Choice) {
let mut output = [0u64; 5];
let choicebit = choice.unwrap_u8() as fiat_25519_u1;
fiat_25519_cmovznz_u64(&mut output[0], choicebit, self.0[0], _rhs.0[0]);
fiat_25519_cmovznz_u64(&mut output[1], choicebit, self.0[1], _rhs.0[1]);
fiat_25519_cmovznz_u64(&mut output[2], choicebit, self.0[2], _rhs.0[2]);
fiat_25519_cmovznz_u64(&mut output[3], choicebit, self.0[3], _rhs.0[3]);
fiat_25519_cmovznz_u64(&mut output[4], choicebit, self.0[4], _rhs.0[4]);
*self = FieldElement51(output);
fiat_25519_cmovznz_u64(&mut output[0], choicebit, self.0[0], rhs.0[0]);
fiat_25519_cmovznz_u64(&mut output[1], choicebit, self.0[1], rhs.0[1]);
fiat_25519_cmovznz_u64(&mut output[2], choicebit, self.0[2], rhs.0[2]);
fiat_25519_cmovznz_u64(&mut output[3], choicebit, self.0[3], rhs.0[3]);
fiat_25519_cmovznz_u64(&mut output[4], choicebit, self.0[4], rhs.0[4]);
*self = FieldElement51::from_limbs(output);
}
}
impl FieldElement51 {
pub(crate) const fn from_limbs(limbs: [u64; 5]) -> FieldElement51 {
FieldElement51(fiat_25519_tight_field_element(limbs))
}
/// The scalar \\( 0 \\).
pub const ZERO: FieldElement51 = FieldElement51([0, 0, 0, 0, 0]);
pub const ZERO: FieldElement51 = FieldElement51::from_limbs([0, 0, 0, 0, 0]);
/// The scalar \\( 1 \\).
pub const ONE: FieldElement51 = FieldElement51([1, 0, 0, 0, 0]);
pub const ONE: FieldElement51 = FieldElement51::from_limbs([1, 0, 0, 0, 0]);
/// The scalar \\( -1 \\).
pub const MINUS_ONE: FieldElement51 = FieldElement51([
pub const MINUS_ONE: FieldElement51 = FieldElement51::from_limbs([
2251799813685228,
2251799813685247,
2251799813685247,
@ -174,10 +188,11 @@ impl FieldElement51 {
/// Given 64-bit input limbs, reduce to enforce the bound 2^(51 + epsilon).
#[inline(always)]
#[allow(dead_code)] // Need this to not complain about reduce not being used
fn reduce(mut limbs: [u64; 5]) -> FieldElement51 {
let input = limbs;
fiat_25519_carry(&mut limbs, &input);
FieldElement51(limbs)
fn reduce(limbs: [u64; 5]) -> FieldElement51 {
let input = fiat_25519_loose_field_element(limbs);
let mut output = fiat_25519_tight_field_element([0; 5]);
fiat_25519_carry(&mut output, &input);
FieldElement51(output)
}
/// Load a `FieldElement51` from the low 255 bits of a 256-bit
@ -196,7 +211,7 @@ impl FieldElement51 {
let mut temp = [0u8; 32];
temp.copy_from_slice(bytes);
temp[31] &= 127u8;
let mut output = [0u64; 5];
let mut output = fiat_25519_tight_field_element([0u64; 5]);
fiat_25519_from_bytes(&mut output, &temp);
FieldElement51(output)
}
@ -213,7 +228,8 @@ impl FieldElement51 {
pub fn pow2k(&self, mut k: u32) -> FieldElement51 {
let mut output = *self;
loop {
let input = output.0;
let mut input = fiat_25519_loose_field_element([0; 5]);
fiat_25519_relax(&mut input, &output.0);
fiat_25519_carry_square(&mut output.0, &input);
k -= 1;
if k == 0 {
@ -224,20 +240,23 @@ impl FieldElement51 {
/// Returns the square of this field element.
pub fn square(&self) -> FieldElement51 {
let mut output = *self;
fiat_25519_carry_square(&mut output.0, &self.0);
let mut self_loose = fiat_25519_loose_field_element([0; 5]);
fiat_25519_relax(&mut self_loose, &self.0);
let mut output = FieldElement51::ZERO;
fiat_25519_carry_square(&mut output.0, &self_loose);
output
}
/// Returns 2 times the square of this field element.
pub fn square2(&self) -> FieldElement51 {
let mut output = *self;
let mut temp = *self;
// Void vs return type, measure cost of copying self
fiat_25519_carry_square(&mut temp.0, &self.0);
fiat_25519_add(&mut output.0, &temp.0, &temp.0);
let input = output.0;
fiat_25519_carry(&mut output.0, &input);
let mut self_loose = fiat_25519_loose_field_element([0; 5]);
fiat_25519_relax(&mut self_loose, &self.0);
let mut square = fiat_25519_tight_field_element([0; 5]);
fiat_25519_carry_square(&mut square, &self_loose);
let mut output_loose = fiat_25519_loose_field_element([0; 5]);
fiat_25519_add(&mut output_loose, &square, &square);
let mut output = FieldElement51::ZERO;
fiat_25519_carry(&mut output.0, &output_loose);
output
}
}

View File

@ -24,17 +24,21 @@ cfg_if! {
if #[cfg(curve25519_dalek_backend = "fiat")] {
#[cfg(curve25519_dalek_bits = "32")]
#[doc(hidden)]
pub mod fiat_u32;
#[cfg(curve25519_dalek_bits = "64")]
#[doc(hidden)]
pub mod fiat_u64;
} else {
#[cfg(curve25519_dalek_bits = "32")]
#[doc(hidden)]
pub mod u32;
#[cfg(curve25519_dalek_bits = "64")]
#[doc(hidden)]
pub mod u64;
}

View File

@ -154,8 +154,7 @@ impl VartimeMultiscalarMul for Pippenger {
});
// Take the high column as an initial value to avoid wasting time doubling the identity element in `fold()`.
// `unwrap()` always succeeds because we know we have more than zero digits.
let hi_column = columns.next().unwrap();
let hi_column = columns.next().expect("should have more than zero digits");
Some(columns.fold(hi_column, |total, p| total.mul_by_pow_2(w as u32) + p))
}
@ -165,7 +164,6 @@ impl VartimeMultiscalarMul for Pippenger {
mod test {
use super::*;
use crate::constants;
use crate::scalar::Scalar;
#[test]
fn test_vartime_pippenger() {

File diff suppressed because it is too large Load Diff

View File

@ -54,7 +54,7 @@ use zeroize::Zeroize;
pub struct FieldElement2625(pub(crate) [u32; 10]);
impl Debug for FieldElement2625 {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "FieldElement2625({:?})", &self.0[..])
}
}
@ -284,12 +284,16 @@ impl ConditionallySelectable for FieldElement2625 {
}
impl FieldElement2625 {
pub(crate) const fn from_limbs(limbs: [u32; 10]) -> FieldElement2625 {
FieldElement2625(limbs)
}
/// The scalar \\( 0 \\).
pub const ZERO: FieldElement2625 = FieldElement2625([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
pub const ZERO: FieldElement2625 = FieldElement2625::from_limbs([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
/// The scalar \\( 1 \\).
pub const ONE: FieldElement2625 = FieldElement2625([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
pub const ONE: FieldElement2625 = FieldElement2625::from_limbs([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
/// The scalar \\( -1 \\).
pub const MINUS_ONE: FieldElement2625 = FieldElement2625([
pub const MINUS_ONE: FieldElement2625 = FieldElement2625::from_limbs([
0x3ffffec, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff,
0x3ffffff, 0x1ffffff,
]);

View File

@ -24,7 +24,7 @@ use crate::constants;
pub struct Scalar29(pub [u32; 9]);
impl Debug for Scalar29 {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Scalar29: {:?}", &self.0[..])
}
}
@ -185,6 +185,14 @@ impl Scalar29 {
/// Compute `a - b` (mod l).
pub fn sub(a: &Scalar29, b: &Scalar29) -> Scalar29 {
// Optimization barrier to prevent compiler from inserting branch instructions
// TODO(tarcieri): find a better home (or abstraction) for this
fn black_box(value: u32) -> u32 {
// SAFETY: `u32` is a simple integer `Copy` type and `value` lives on the stack so
// a pointer to it will be valid.
unsafe { core::ptr::read_volatile(&value) }
}
let mut difference = Scalar29::ZERO;
let mask = (1u32 << 29) - 1;
@ -199,7 +207,7 @@ impl Scalar29 {
let underflow_mask = ((borrow >> 31) ^ 1).wrapping_sub(1);
let mut carry: u32 = 0;
for i in 0..9 {
carry = (carry >> 29) + difference[i] + (constants::L[i] & underflow_mask);
carry = (carry >> 29) + difference[i] + (constants::L[i] & black_box(underflow_mask));
difference[i] = carry & mask;
}

File diff suppressed because it is too large Load Diff

View File

@ -43,7 +43,7 @@ use zeroize::Zeroize;
pub struct FieldElement51(pub(crate) [u64; 5]);
impl Debug for FieldElement51 {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "FieldElement51({:?})", &self.0[..])
}
}
@ -255,12 +255,16 @@ impl ConditionallySelectable for FieldElement51 {
}
impl FieldElement51 {
pub(crate) const fn from_limbs(limbs: [u64; 5]) -> FieldElement51 {
FieldElement51(limbs)
}
/// The scalar \\( 0 \\).
pub const ZERO: FieldElement51 = FieldElement51([0, 0, 0, 0, 0]);
pub const ZERO: FieldElement51 = FieldElement51::from_limbs([0, 0, 0, 0, 0]);
/// The scalar \\( 1 \\).
pub const ONE: FieldElement51 = FieldElement51([1, 0, 0, 0, 0]);
pub const ONE: FieldElement51 = FieldElement51::from_limbs([1, 0, 0, 0, 0]);
/// The scalar \\( -1 \\).
pub const MINUS_ONE: FieldElement51 = FieldElement51([
pub const MINUS_ONE: FieldElement51 = FieldElement51::from_limbs([
2251799813685228,
2251799813685247,
2251799813685247,

View File

@ -25,7 +25,7 @@ use crate::constants;
pub struct Scalar52(pub [u64; 5]);
impl Debug for Scalar52 {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Scalar52: {:?}", &self.0[..])
}
}
@ -174,6 +174,14 @@ impl Scalar52 {
/// Compute `a - b` (mod l)
pub fn sub(a: &Scalar52, b: &Scalar52) -> Scalar52 {
// Optimization barrier to prevent compiler from inserting branch instructions
// TODO(tarcieri): find a better home (or abstraction) for this
fn black_box(value: u64) -> u64 {
// SAFETY: `u64` is a simple integer `Copy` type and `value` lives on the stack so
// a pointer to it will be valid.
unsafe { core::ptr::read_volatile(&value) }
}
let mut difference = Scalar52::ZERO;
let mask = (1u64 << 52) - 1;
@ -188,7 +196,9 @@ impl Scalar52 {
let underflow_mask = ((borrow >> 63) ^ 1).wrapping_sub(1);
let mut carry: u64 = 0;
for i in 0..5 {
carry = (carry >> 52) + difference[i] + (constants::L[i] & underflow_mask);
// SECURITY: `black_box` prevents LLVM from inserting a `jns` conditional on x86(_64)
// which can be used to bypass this section when `underflow_mask` is zero.
carry = (carry >> 52) + difference[i] + (constants::L[i] & black_box(underflow_mask));
difference[i] = carry & mask;
}

View File

@ -35,7 +35,6 @@
#![allow(non_snake_case)]
use core::convert::From;
use core::ops::{Add, Neg, Sub};
use subtle::Choice;
@ -240,7 +239,7 @@ impl ConditionallySelectable for CachedPoint {
}
#[unsafe_target_feature("avx2")]
impl<'a> Neg for &'a CachedPoint {
impl Neg for &CachedPoint {
type Output = CachedPoint;
/// Lazily negate the point.
///
@ -255,11 +254,11 @@ impl<'a> Neg for &'a CachedPoint {
}
#[unsafe_target_feature("avx2")]
impl<'a, 'b> Add<&'b CachedPoint> for &'a ExtendedPoint {
impl Add<&CachedPoint> for &ExtendedPoint {
type Output = ExtendedPoint;
/// Add an `ExtendedPoint` and a `CachedPoint`.
fn add(self, other: &'b CachedPoint) -> ExtendedPoint {
fn add(self, other: &CachedPoint) -> ExtendedPoint {
// The coefficients of an `ExtendedPoint` are reduced after
// every operation. If the `CachedPoint` was negated, its
// coefficients grow by one bit. So on input, `self` is
@ -293,7 +292,7 @@ impl<'a, 'b> Add<&'b CachedPoint> for &'a ExtendedPoint {
}
#[unsafe_target_feature("avx2")]
impl<'a, 'b> Sub<&'b CachedPoint> for &'a ExtendedPoint {
impl Sub<&CachedPoint> for &ExtendedPoint {
type Output = ExtendedPoint;
/// Implement subtraction by negating the point and adding.
@ -301,14 +300,14 @@ impl<'a, 'b> Sub<&'b CachedPoint> for &'a ExtendedPoint {
/// Empirically, this seems about the same cost as a custom
/// subtraction impl (maybe because the benefit is cancelled by
/// increased code size?)
fn sub(self, other: &'b CachedPoint) -> ExtendedPoint {
fn sub(self, other: &CachedPoint) -> ExtendedPoint {
self + &(-other)
}
}
#[unsafe_target_feature("avx2")]
impl<'a> From<&'a edwards::EdwardsPoint> for LookupTable<CachedPoint> {
fn from(point: &'a edwards::EdwardsPoint) -> Self {
impl From<&edwards::EdwardsPoint> for LookupTable<CachedPoint> {
fn from(point: &edwards::EdwardsPoint) -> Self {
let P = ExtendedPoint::from(*point);
let mut points = [CachedPoint::from(P); 8];
for i in 0..7 {
@ -319,8 +318,8 @@ impl<'a> From<&'a edwards::EdwardsPoint> for LookupTable<CachedPoint> {
}
#[unsafe_target_feature("avx2")]
impl<'a> From<&'a edwards::EdwardsPoint> for NafLookupTable5<CachedPoint> {
fn from(point: &'a edwards::EdwardsPoint) -> Self {
impl From<&edwards::EdwardsPoint> for NafLookupTable5<CachedPoint> {
fn from(point: &edwards::EdwardsPoint) -> Self {
let A = ExtendedPoint::from(*point);
let mut Ai = [CachedPoint::from(A); 8];
let A2 = A.double();
@ -334,8 +333,8 @@ impl<'a> From<&'a edwards::EdwardsPoint> for NafLookupTable5<CachedPoint> {
#[cfg(any(feature = "precomputed-tables", feature = "alloc"))]
#[unsafe_target_feature("avx2")]
impl<'a> From<&'a edwards::EdwardsPoint> for NafLookupTable8<CachedPoint> {
fn from(point: &'a edwards::EdwardsPoint) -> Self {
impl From<&edwards::EdwardsPoint> for NafLookupTable8<CachedPoint> {
fn from(point: &edwards::EdwardsPoint) -> Self {
let A = ExtendedPoint::from(*point);
let mut Ai = [CachedPoint::from(A); 64];
let A2 = A.double();

View File

@ -760,7 +760,7 @@ impl Mul<(u32, u32, u32, u32)> for FieldElement2625x4 {
}
#[unsafe_target_feature("avx2")]
impl<'a, 'b> Mul<&'b FieldElement2625x4> for &'a FieldElement2625x4 {
impl Mul<&FieldElement2625x4> for &FieldElement2625x4 {
type Output = FieldElement2625x4;
/// Multiply `self` by `rhs`.
///
@ -776,7 +776,7 @@ impl<'a, 'b> Mul<&'b FieldElement2625x4> for &'a FieldElement2625x4 {
///
#[rustfmt::skip] // keep alignment of z* calculations
#[inline]
fn mul(self, rhs: &'b FieldElement2625x4) -> FieldElement2625x4 {
fn mul(self, rhs: &FieldElement2625x4) -> FieldElement2625x4 {
#[inline(always)]
fn m(x: u32x8, y: u32x8) -> u64x4 {
x.mul32(y)

View File

@ -123,8 +123,7 @@ pub mod spec {
});
// Take the high column as an initial value to avoid wasting time doubling the identity element in `fold()`.
// `unwrap()` always succeeds because we know we have more than zero digits.
let hi_column = columns.next().unwrap();
let hi_column = columns.next().expect("should have more than zero digits");
Some(
columns

View File

@ -8,24 +8,7 @@
// Authors:
// - isis agora lovecruft <isis@patternsinthevoid.net>
// - Henry de Valence <hdevalence@hdevalence.ca>
//! Various constants, such as the Ristretto and Ed25519 basepoints.
//!
//! Most of the constants are given with
//! `LONG_DESCRIPTIVE_UPPER_CASE_NAMES`, but they can be brought into
//! scope using a `let` binding:
//!
#![cfg_attr(feature = "precomputed-tables", doc = "```")]
#![cfg_attr(not(feature = "precomputed-tables"), doc = "```ignore")]
//! use curve25519_dalek::constants;
//! use curve25519_dalek::traits::IsIdentity;
//!
//! let B = constants::RISTRETTO_BASEPOINT_TABLE;
//! let l = &constants::BASEPOINT_ORDER;
//!
//! let A = l * B;
//! assert!(A.is_identity());
//! ```
#![allow(non_snake_case)]
@ -86,7 +69,10 @@ pub const RISTRETTO_BASEPOINT_POINT: RistrettoPoint = RistrettoPoint(ED25519_BAS
/// $$
/// \ell = 2^\{252\} + 27742317777372353535851937790883648493.
/// $$
pub const BASEPOINT_ORDER: Scalar = Scalar {
#[deprecated(since = "4.1.1", note = "Should not have been in public API")]
pub const BASEPOINT_ORDER: Scalar = BASEPOINT_ORDER_PRIVATE;
pub(crate) const BASEPOINT_ORDER_PRIVATE: Scalar = Scalar {
bytes: [
0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde,
0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

View File

@ -96,7 +96,6 @@
use core::array::TryFromSliceError;
use core::borrow::Borrow;
use core::fmt::Debug;
use core::iter::Iterator;
use core::iter::Sum;
use core::ops::{Add, Neg, Sub};
use core::ops::{AddAssign, SubAssign};
@ -107,6 +106,15 @@ use cfg_if::cfg_if;
#[cfg(feature = "digest")]
use digest::{generic_array::typenum::U64, Digest};
#[cfg(feature = "group")]
use {
group::{cofactor::CofactorGroup, prime::PrimeGroup, GroupEncoding},
subtle::CtOption,
};
#[cfg(feature = "group")]
use rand_core::RngCore;
use subtle::Choice;
use subtle::ConditionallyNegatable;
use subtle::ConditionallySelectable;
@ -163,7 +171,7 @@ impl ConstantTimeEq for CompressedEdwardsY {
}
impl Debug for CompressedEdwardsY {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "CompressedEdwardsY: {:?}", self.as_bytes())
}
}
@ -183,25 +191,52 @@ impl CompressedEdwardsY {
///
/// Returns `None` if the input is not the \\(y\\)-coordinate of a
/// curve point.
#[rustfmt::skip] // keep alignment of explanatory comments
pub fn decompress(&self) -> Option<EdwardsPoint> {
let Y = FieldElement::from_bytes(self.as_bytes());
let (is_valid_y_coord, X, Y, Z) = decompress::step_1(self);
if is_valid_y_coord.into() {
Some(decompress::step_2(self, X, Y, Z))
} else {
None
}
}
}
mod decompress {
use super::*;
#[rustfmt::skip] // keep alignment of explanatory comments
pub(super) fn step_1(
repr: &CompressedEdwardsY,
) -> (Choice, FieldElement, FieldElement, FieldElement) {
let Y = FieldElement::from_bytes(repr.as_bytes());
let Z = FieldElement::ONE;
let YY = Y.square();
let u = &YY - &Z; // u = y²-1
let v = &(&YY * &constants::EDWARDS_D) + &Z; // v = dy²+1
let (is_valid_y_coord, mut X) = FieldElement::sqrt_ratio_i(&u, &v);
let (is_valid_y_coord, X) = FieldElement::sqrt_ratio_i(&u, &v);
if (!is_valid_y_coord).into() {
return None;
}
(is_valid_y_coord, X, Y, Z)
}
#[rustfmt::skip]
pub(super) fn step_2(
repr: &CompressedEdwardsY,
mut X: FieldElement,
Y: FieldElement,
Z: FieldElement,
) -> EdwardsPoint {
// FieldElement::sqrt_ratio_i always returns the nonnegative square root,
// so we negate according to the supplied sign bit.
let compressed_sign_bit = Choice::from(self.as_bytes()[31] >> 7);
let compressed_sign_bit = Choice::from(repr.as_bytes()[31] >> 7);
X.conditional_negate(compressed_sign_bit);
Some(EdwardsPoint{ X, Y, Z, T: &X * &Y })
EdwardsPoint {
X,
Y,
Z,
T: &X * &Y,
}
}
}
@ -224,7 +259,7 @@ impl TryFrom<&[u8]> for CompressedEdwardsY {
#[cfg(feature = "serde")]
use serde::de::Visitor;
#[cfg(feature = "serde")]
use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[cfg(feature = "serde")]
impl Serialize for EdwardsPoint {
@ -267,7 +302,7 @@ impl<'de> Deserialize<'de> for EdwardsPoint {
impl<'de> Visitor<'de> for EdwardsPointVisitor {
type Value = EdwardsPoint;
fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter.write_str("a valid point in Edwards y + sign format")
}
@ -276,6 +311,7 @@ impl<'de> Deserialize<'de> for EdwardsPoint {
A: serde::de::SeqAccess<'de>,
{
let mut bytes = [0u8; 32];
#[allow(clippy::needless_range_loop)]
for i in 0..32 {
bytes[i] = seq
.next_element()?
@ -302,7 +338,7 @@ impl<'de> Deserialize<'de> for CompressedEdwardsY {
impl<'de> Visitor<'de> for CompressedEdwardsYVisitor {
type Value = CompressedEdwardsY;
fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter.write_str("32 bytes of data")
}
@ -311,6 +347,7 @@ impl<'de> Deserialize<'de> for CompressedEdwardsY {
A: serde::de::SeqAccess<'de>,
{
let mut bytes = [0u8; 32];
#[allow(clippy::needless_range_loop)]
for i in 0..32 {
bytes[i] = seq
.next_element()?
@ -1016,7 +1053,7 @@ macro_rules! impl_basepoint_table {
}
impl Debug for $name {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:?}([\n", stringify!($name))?;
for i in 0..32 {
write!(f, "\t{:?},\n", &self.0[i])?;
@ -1218,7 +1255,7 @@ impl EdwardsPoint {
/// assert_eq!((P+Q).is_torsion_free(), false);
/// ```
pub fn is_torsion_free(&self) -> bool {
(self * constants::BASEPOINT_ORDER).is_identity()
(self * constants::BASEPOINT_ORDER_PRIVATE).is_identity()
}
}
@ -1227,7 +1264,7 @@ impl EdwardsPoint {
// ------------------------------------------------------------------------
impl Debug for EdwardsPoint {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"EdwardsPoint{{\n\tX: {:?},\n\tY: {:?},\n\tZ: {:?},\n\tT: {:?}\n}}",
@ -1236,6 +1273,318 @@ impl Debug for EdwardsPoint {
}
}
// ------------------------------------------------------------------------
// group traits
// ------------------------------------------------------------------------
// Use the full trait path to avoid Group::identity overlapping Identity::identity in the
// rest of the module (e.g. tests).
#[cfg(feature = "group")]
impl group::Group for EdwardsPoint {
type Scalar = Scalar;
fn random(mut rng: impl RngCore) -> Self {
let mut repr = CompressedEdwardsY([0u8; 32]);
loop {
rng.fill_bytes(&mut repr.0);
if let Some(p) = repr.decompress() {
if !IsIdentity::is_identity(&p) {
break p;
}
}
}
}
fn identity() -> Self {
Identity::identity()
}
fn generator() -> Self {
constants::ED25519_BASEPOINT_POINT
}
fn is_identity(&self) -> Choice {
self.ct_eq(&Identity::identity())
}
fn double(&self) -> Self {
self.double()
}
}
#[cfg(feature = "group")]
impl GroupEncoding for EdwardsPoint {
type Repr = [u8; 32];
fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
let repr = CompressedEdwardsY(*bytes);
let (is_valid_y_coord, X, Y, Z) = decompress::step_1(&repr);
CtOption::new(decompress::step_2(&repr, X, Y, Z), is_valid_y_coord)
}
fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
// Just use the checked API; there are no checks we can skip.
Self::from_bytes(bytes)
}
fn to_bytes(&self) -> Self::Repr {
self.compress().to_bytes()
}
}
/// A `SubgroupPoint` represents a point on the Edwards form of Curve25519, that is
/// guaranteed to be in the prime-order subgroup.
#[cfg(feature = "group")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SubgroupPoint(EdwardsPoint);
#[cfg(feature = "group")]
impl From<SubgroupPoint> for EdwardsPoint {
fn from(p: SubgroupPoint) -> Self {
p.0
}
}
#[cfg(feature = "group")]
impl Neg for SubgroupPoint {
type Output = Self;
fn neg(self) -> Self::Output {
SubgroupPoint(-self.0)
}
}
#[cfg(feature = "group")]
impl Add<&SubgroupPoint> for &SubgroupPoint {
type Output = SubgroupPoint;
fn add(self, other: &SubgroupPoint) -> SubgroupPoint {
SubgroupPoint(self.0 + other.0)
}
}
#[cfg(feature = "group")]
define_add_variants!(
LHS = SubgroupPoint,
RHS = SubgroupPoint,
Output = SubgroupPoint
);
#[cfg(feature = "group")]
impl Add<&SubgroupPoint> for &EdwardsPoint {
type Output = EdwardsPoint;
fn add(self, other: &SubgroupPoint) -> EdwardsPoint {
self + other.0
}
}
#[cfg(feature = "group")]
define_add_variants!(
LHS = EdwardsPoint,
RHS = SubgroupPoint,
Output = EdwardsPoint
);
#[cfg(feature = "group")]
impl AddAssign<&SubgroupPoint> for SubgroupPoint {
fn add_assign(&mut self, rhs: &SubgroupPoint) {
self.0 += rhs.0
}
}
#[cfg(feature = "group")]
define_add_assign_variants!(LHS = SubgroupPoint, RHS = SubgroupPoint);
#[cfg(feature = "group")]
impl AddAssign<&SubgroupPoint> for EdwardsPoint {
fn add_assign(&mut self, rhs: &SubgroupPoint) {
*self += rhs.0
}
}
#[cfg(feature = "group")]
define_add_assign_variants!(LHS = EdwardsPoint, RHS = SubgroupPoint);
#[cfg(feature = "group")]
impl Sub<&SubgroupPoint> for &SubgroupPoint {
type Output = SubgroupPoint;
fn sub(self, other: &SubgroupPoint) -> SubgroupPoint {
SubgroupPoint(self.0 - other.0)
}
}
#[cfg(feature = "group")]
define_sub_variants!(
LHS = SubgroupPoint,
RHS = SubgroupPoint,
Output = SubgroupPoint
);
#[cfg(feature = "group")]
impl Sub<&SubgroupPoint> for &EdwardsPoint {
type Output = EdwardsPoint;
fn sub(self, other: &SubgroupPoint) -> EdwardsPoint {
self - other.0
}
}
#[cfg(feature = "group")]
define_sub_variants!(
LHS = EdwardsPoint,
RHS = SubgroupPoint,
Output = EdwardsPoint
);
#[cfg(feature = "group")]
impl SubAssign<&SubgroupPoint> for SubgroupPoint {
fn sub_assign(&mut self, rhs: &SubgroupPoint) {
self.0 -= rhs.0;
}
}
#[cfg(feature = "group")]
define_sub_assign_variants!(LHS = SubgroupPoint, RHS = SubgroupPoint);
#[cfg(feature = "group")]
impl SubAssign<&SubgroupPoint> for EdwardsPoint {
fn sub_assign(&mut self, rhs: &SubgroupPoint) {
*self -= rhs.0;
}
}
#[cfg(feature = "group")]
define_sub_assign_variants!(LHS = EdwardsPoint, RHS = SubgroupPoint);
#[cfg(feature = "group")]
impl<T> Sum<T> for SubgroupPoint
where
T: Borrow<SubgroupPoint>,
{
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = T>,
{
use group::Group;
iter.fold(SubgroupPoint::identity(), |acc, item| acc + item.borrow())
}
}
#[cfg(feature = "group")]
impl Mul<&Scalar> for &SubgroupPoint {
type Output = SubgroupPoint;
/// Scalar multiplication: compute `scalar * self`.
///
/// For scalar multiplication of a basepoint,
/// `EdwardsBasepointTable` is approximately 4x faster.
fn mul(self, scalar: &Scalar) -> SubgroupPoint {
SubgroupPoint(self.0 * scalar)
}
}
#[cfg(feature = "group")]
define_mul_variants!(LHS = Scalar, RHS = SubgroupPoint, Output = SubgroupPoint);
#[cfg(feature = "group")]
impl Mul<&SubgroupPoint> for &Scalar {
type Output = SubgroupPoint;
/// Scalar multiplication: compute `scalar * self`.
///
/// For scalar multiplication of a basepoint,
/// `EdwardsBasepointTable` is approximately 4x faster.
fn mul(self, point: &SubgroupPoint) -> SubgroupPoint {
point * self
}
}
#[cfg(feature = "group")]
define_mul_variants!(LHS = SubgroupPoint, RHS = Scalar, Output = SubgroupPoint);
#[cfg(feature = "group")]
impl MulAssign<&Scalar> for SubgroupPoint {
fn mul_assign(&mut self, scalar: &Scalar) {
self.0 *= scalar;
}
}
#[cfg(feature = "group")]
define_mul_assign_variants!(LHS = SubgroupPoint, RHS = Scalar);
#[cfg(feature = "group")]
impl group::Group for SubgroupPoint {
type Scalar = Scalar;
fn random(mut rng: impl RngCore) -> Self {
use group::ff::Field;
// This will almost never loop, but `Group::random` is documented as returning a
// non-identity element.
let s = loop {
let s: Scalar = Field::random(&mut rng);
if !s.is_zero_vartime() {
break s;
}
};
// This gives an element of the prime-order subgroup.
Self::generator() * s
}
fn identity() -> Self {
SubgroupPoint(Identity::identity())
}
fn generator() -> Self {
SubgroupPoint(EdwardsPoint::generator())
}
fn is_identity(&self) -> Choice {
self.0.ct_eq(&Identity::identity())
}
fn double(&self) -> Self {
SubgroupPoint(self.0.double())
}
}
#[cfg(feature = "group")]
impl GroupEncoding for SubgroupPoint {
type Repr = <EdwardsPoint as GroupEncoding>::Repr;
fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
EdwardsPoint::from_bytes(bytes).and_then(|p| p.into_subgroup())
}
fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
EdwardsPoint::from_bytes_unchecked(bytes).and_then(|p| p.into_subgroup())
}
fn to_bytes(&self) -> Self::Repr {
self.0.compress().to_bytes()
}
}
#[cfg(feature = "group")]
impl PrimeGroup for SubgroupPoint {}
/// Ristretto has a cofactor of 1.
#[cfg(feature = "group")]
impl CofactorGroup for EdwardsPoint {
type Subgroup = SubgroupPoint;
fn clear_cofactor(&self) -> Self::Subgroup {
SubgroupPoint(self.mul_by_cofactor())
}
fn into_subgroup(self) -> CtOption<Self::Subgroup> {
CtOption::new(SubgroupPoint(self), CofactorGroup::is_torsion_free(&self))
}
fn is_torsion_free(&self) -> Choice {
(self * constants::BASEPOINT_ORDER_PRIVATE).ct_eq(&Self::identity())
}
}
// ------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------
@ -1243,8 +1592,10 @@ impl Debug for EdwardsPoint {
#[cfg(test)]
mod test {
use super::*;
use crate::{field::FieldElement, scalar::Scalar};
use subtle::ConditionallySelectable;
// If `group` is set, then this is already imported in super
#[cfg(not(feature = "group"))]
use rand_core::RngCore;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
@ -1252,8 +1603,6 @@ mod test {
#[cfg(feature = "precomputed-tables")]
use crate::constants::ED25519_BASEPOINT_TABLE;
use rand_core::RngCore;
/// X coordinate of the basepoint.
/// = 15112221349535400772501151409588531511454012693041857206046113283949847762202
static BASE_X_COORD_BYTES: [u8; 32] = [
@ -1421,7 +1770,7 @@ mod test {
/// Test that multiplication by the basepoint order kills the basepoint
#[test]
fn basepoint_mult_by_basepoint_order() {
let should_be_id = EdwardsPoint::mul_base(&constants::BASEPOINT_ORDER);
let should_be_id = EdwardsPoint::mul_base(&constants::BASEPOINT_ORDER_PRIVATE);
assert!(should_be_id.is_identity());
}

View File

@ -23,7 +23,7 @@
//! Field operations defined in terms of other field operations, such as
//! field inversion or square roots, are defined here.
use core::cmp::{Eq, PartialEq};
#![allow(unused_qualifications)]
use cfg_if::cfg_if;
@ -37,11 +37,6 @@ use crate::constants;
cfg_if! {
if #[cfg(curve25519_dalek_backend = "fiat")] {
#[cfg(curve25519_dalek_bits = "32")]
pub use backend::serial::fiat_u32::field::*;
#[cfg(curve25519_dalek_bits = "64")]
pub use backend::serial::fiat_u64::field::*;
/// A `FieldElement` represents an element of the field
/// \\( \mathbb Z / (2\^{255} - 19)\\).
///
@ -50,7 +45,7 @@ cfg_if! {
///
/// Using formally-verified field arithmetic from fiat-crypto.
#[cfg(curve25519_dalek_bits = "32")]
pub type FieldElement = backend::serial::fiat_u32::field::FieldElement2625;
pub(crate) type FieldElement = backend::serial::fiat_u32::field::FieldElement2625;
/// A `FieldElement` represents an element of the field
/// \\( \mathbb Z / (2\^{255} - 19)\\).
@ -60,25 +55,21 @@ cfg_if! {
///
/// Using formally-verified field arithmetic from fiat-crypto.
#[cfg(curve25519_dalek_bits = "64")]
pub type FieldElement = backend::serial::fiat_u64::field::FieldElement51;
pub(crate) type FieldElement = backend::serial::fiat_u64::field::FieldElement51;
} else if #[cfg(curve25519_dalek_bits = "64")] {
pub use crate::backend::serial::u64::field::*;
/// A `FieldElement` represents an element of the field
/// \\( \mathbb Z / (2\^{255} - 19)\\).
///
/// The `FieldElement` type is an alias for one of the platform-specific
/// implementations.
pub type FieldElement = backend::serial::u64::field::FieldElement51;
pub(crate) type FieldElement = backend::serial::u64::field::FieldElement51;
} else {
pub use backend::serial::u32::field::*;
/// A `FieldElement` represents an element of the field
/// \\( \mathbb Z / (2\^{255} - 19)\\).
///
/// The `FieldElement` type is an alias for one of the platform-specific
/// implementations.
pub type FieldElement = backend::serial::u32::field::FieldElement2625;
pub(crate) type FieldElement = backend::serial::u32::field::FieldElement2625;
}
}
@ -107,7 +98,7 @@ impl FieldElement {
/// # Return
///
/// If negative, return `Choice(1)`. Otherwise, return `Choice(0)`.
pub fn is_negative(&self) -> Choice {
pub(crate) fn is_negative(&self) -> Choice {
let bytes = self.as_bytes();
(bytes[0] & 1).into()
}
@ -117,7 +108,7 @@ impl FieldElement {
/// # Return
///
/// If zero, return `Choice(1)`. Otherwise, return `Choice(0)`.
pub fn is_zero(&self) -> Choice {
pub(crate) fn is_zero(&self) -> Choice {
let zero = [0u8; 32];
let bytes = self.as_bytes();
@ -163,11 +154,11 @@ impl FieldElement {
(t19, t3)
}
/// Given a slice of public `FieldElements`, replace each with its inverse.
/// Given a slice of pub(crate)lic `FieldElements`, replace each with its inverse.
///
/// When an input `FieldElement` is zero, its value is unchanged.
#[cfg(feature = "alloc")]
pub fn batch_invert(inputs: &mut [FieldElement]) {
pub(crate) fn batch_invert(inputs: &mut [FieldElement]) {
// Montgomerys Trick and Fast Implementation of Masked AES
// Genelle, Prouff and Quisquater
// Section 3.2
@ -212,7 +203,7 @@ impl FieldElement {
/// This function returns zero on input zero.
#[rustfmt::skip] // keep alignment of explanatory comments
#[allow(clippy::let_and_return)]
pub fn invert(&self) -> FieldElement {
pub(crate) fn invert(&self) -> FieldElement {
// The bits of p-2 = 2^255 -19 -2 are 11010111111...11.
//
// nonzero bits of exponent
@ -249,7 +240,7 @@ impl FieldElement {
/// - `(Choice(0), zero) ` if `v` is zero and `u` is nonzero;
/// - `(Choice(0), +sqrt(i*u/v))` if `u/v` is nonsquare (so `i*u/v` is square).
///
pub fn sqrt_ratio_i(u: &FieldElement, v: &FieldElement) -> (Choice, FieldElement) {
pub(crate) fn sqrt_ratio_i(u: &FieldElement, v: &FieldElement) -> (Choice, FieldElement) {
// Using the same trick as in ed25519 decoding, we merge the
// inversion, the square root, and the square test as follows.
//
@ -309,7 +300,7 @@ impl FieldElement {
/// - `(Choice(0), zero) ` if `self` is zero;
/// - `(Choice(0), +sqrt(i/self)) ` if `self` is a nonzero nonsquare;
///
pub fn invsqrt(&self) -> (Choice, FieldElement) {
pub(crate) fn invsqrt(&self) -> (Choice, FieldElement) {
FieldElement::sqrt_ratio_i(&FieldElement::ONE, self)
}
}
@ -317,7 +308,6 @@ impl FieldElement {
#[cfg(test)]
mod test {
use crate::field::*;
use subtle::ConditionallyNegatable;
/// Random element a of GF(2^255-19), from Sage
/// a = 1070314506888354081329385823235218444233221\

View File

@ -10,22 +10,40 @@
// - Henry de Valence <hdevalence@hdevalence.ca>
#![no_std]
#![cfg_attr(all(curve25519_dalek_backend = "simd", nightly), feature(stdsimd))]
#![cfg_attr(
all(
curve25519_dalek_backend = "simd",
nightly,
any(target_arch = "x86", target_arch = "x86_64")
),
feature(stdarch_x86_avx512)
)]
#![cfg_attr(
all(curve25519_dalek_backend = "simd", nightly),
feature(avx512_target_feature)
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg, doc_cfg_hide))]
#![cfg_attr(docsrs, doc(cfg_hide(docsrs)))]
#![cfg_attr(allow_unused_unsafe, allow(unused_unsafe))]
//------------------------------------------------------------------------
// Documentation:
//------------------------------------------------------------------------
#![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")]
//------------------------------------------------------------------------
// Linting:
//------------------------------------------------------------------------
#![cfg_attr(allow_unused_unsafe, allow(unused_unsafe))]
#![warn(
clippy::unwrap_used,
missing_docs,
rust_2018_idioms,
unused_lifetimes,
unused_qualifications
)]
// Requires MSRV 1.77 as it does not allow build.rs gating
#![allow(unexpected_cfgs)]
//------------------------------------------------------------------------
// External dependencies:

View File

@ -237,7 +237,7 @@ impl RistrettoPoint {
#[cfg(test)]
mod test {
extern crate sha2;
use sha2;
use self::sha2::Sha256;
use super::*;

View File

@ -3,36 +3,44 @@ use cfg_if::cfg_if;
cfg_if! {
if #[cfg(curve25519_dalek_backend = "fiat")] {
pub use crate::backend::serial::fiat_u32::field::FieldElement2625;
const fn field_element(element: [u32; 10]) -> FieldElement2625 {
FieldElement2625(fiat_crypto::curve25519_32::fiat_25519_tight_field_element(element))
}
} else {
pub use crate::backend::serial::u32::field::FieldElement2625;
const fn field_element(element: [u32; 10]) -> FieldElement2625 {
FieldElement2625(element)
}
}
}
/// `= sqrt(i*d)`, where `i = +sqrt(-1)` and `d` is the Edwards curve parameter.
pub const SQRT_ID: FieldElement2625 = FieldElement2625([
pub const SQRT_ID: FieldElement2625 = field_element([
39590824, 701138, 28659366, 23623507, 53932708, 32206357, 36326585, 24309414, 26167230, 1494357,
]);
/// `= (d+1)/(d-1)`, where `d` is the Edwards curve parameter.
pub const DP1_OVER_DM1: FieldElement2625 = FieldElement2625([
pub const DP1_OVER_DM1: FieldElement2625 = field_element([
58833708, 32184294, 62457071, 26110240, 19032991, 27203620, 7122892, 18068959, 51019405,
3776288,
]);
/// `= -2/sqrt(a-d)`, where `a = -1 (mod p)`, `d` are the Edwards curve parameters.
pub const MDOUBLE_INVSQRT_A_MINUS_D: FieldElement2625 = FieldElement2625([
pub const MDOUBLE_INVSQRT_A_MINUS_D: FieldElement2625 = field_element([
54885894, 25242303, 55597453, 9067496, 51808079, 33312638, 25456129, 14121551, 54921728,
3972023,
]);
/// `= -2i/sqrt(a-d)`, where `a = -1 (mod p)`, `d` are the Edwards curve parameters
/// and `i = +sqrt(-1)`.
pub const MIDOUBLE_INVSQRT_A_MINUS_D: FieldElement2625 = FieldElement2625([
pub const MIDOUBLE_INVSQRT_A_MINUS_D: FieldElement2625 = field_element([
58178520, 23970840, 26444491, 29801899, 41064376, 743696, 2900628, 27920316, 41968995, 5270573,
]);
/// `= -1/sqrt(1+d)`, where `d` is the Edwards curve parameters.
pub const MINVSQRT_ONE_PLUS_D: FieldElement2625 = FieldElement2625([
pub const MINVSQRT_ONE_PLUS_D: FieldElement2625 = field_element([
38019585, 4791795, 20332186, 18653482, 46576675, 33182583, 65658549, 2817057, 12569934,
30919145,
]);

View File

@ -3,13 +3,21 @@ use cfg_if::cfg_if;
cfg_if! {
if #[cfg(curve25519_dalek_backend = "fiat")] {
pub use crate::backend::serial::fiat_u64::field::FieldElement51;
const fn field_element(element: [u64; 5]) -> FieldElement51 {
FieldElement51(fiat_crypto::curve25519_64::fiat_25519_tight_field_element(element))
}
} else {
pub use crate::backend::serial::u64::field::FieldElement51;
const fn field_element(element: [u64; 5]) -> FieldElement51 {
FieldElement51(element)
}
}
}
/// `= sqrt(i*d)`, where `i = +sqrt(-1)` and `d` is the Edwards curve parameter.
pub const SQRT_ID: FieldElement51 = FieldElement51([
pub const SQRT_ID: FieldElement51 = field_element([
2298852427963285,
3837146560810661,
4413131899466403,
@ -18,7 +26,7 @@ pub const SQRT_ID: FieldElement51 = FieldElement51([
]);
/// `= (d+1)/(d-1)`, where `d` is the Edwards curve parameter.
pub const DP1_OVER_DM1: FieldElement51 = FieldElement51([
pub const DP1_OVER_DM1: FieldElement51 = field_element([
2159851467815724,
1752228607624431,
1825604053920671,
@ -27,7 +35,7 @@ pub const DP1_OVER_DM1: FieldElement51 = FieldElement51([
]);
/// `= -2/sqrt(a-d)`, where `a = -1 (mod p)`, `d` are the Edwards curve parameters.
pub const MDOUBLE_INVSQRT_A_MINUS_D: FieldElement51 = FieldElement51([
pub const MDOUBLE_INVSQRT_A_MINUS_D: FieldElement51 = field_element([
1693982333959686,
608509411481997,
2235573344831311,
@ -37,7 +45,7 @@ pub const MDOUBLE_INVSQRT_A_MINUS_D: FieldElement51 = FieldElement51([
/// `= -2i/sqrt(a-d)`, where `a = -1 (mod p)`, `d` are the Edwards curve parameters
/// and `i = +sqrt(-1)`.
pub const MIDOUBLE_INVSQRT_A_MINUS_D: FieldElement51 = FieldElement51([
pub const MIDOUBLE_INVSQRT_A_MINUS_D: FieldElement51 = field_element([
1608655899704280,
1999971613377227,
49908634785720,
@ -46,7 +54,7 @@ pub const MIDOUBLE_INVSQRT_A_MINUS_D: FieldElement51 = FieldElement51([
]);
/// `= -1/sqrt(1+d)`, where `d` is the Edwards curve parameters.
pub const MINVSQRT_ONE_PLUS_D: FieldElement51 = FieldElement51([
pub const MINVSQRT_ONE_PLUS_D: FieldElement51 = field_element([
321571956990465,
1251814006996634,
2226845496292387,

View File

@ -151,6 +151,43 @@ impl MontgomeryPoint {
Self::mul_base(&s)
}
/// Given `self` \\( = u\_0(P) \\), and a big-endian bit representation of an integer
/// \\(n\\), return \\( u\_0(\[n\]P) \\). This is constant time in the length of `bits`.
///
/// **NOTE:** You probably do not want to use this function. Almost every protocol built on
/// Curve25519 uses _clamped multiplication_, explained
/// [here](https://neilmadden.blog/2020/05/28/whats-the-curve25519-clamping-all-about/).
/// When in doubt, use [`Self::mul_clamped`].
pub fn mul_bits_be(&self, bits: impl Iterator<Item = bool>) -> MontgomeryPoint {
// Algorithm 8 of Costello-Smith 2017
let affine_u = FieldElement::from_bytes(&self.0);
let mut x0 = ProjectivePoint::identity();
let mut x1 = ProjectivePoint {
U: affine_u,
W: FieldElement::ONE,
};
// Go through the bits from most to least significant, using a sliding window of 2
let mut prev_bit = false;
for cur_bit in bits {
let choice: u8 = (prev_bit ^ cur_bit) as u8;
debug_assert!(choice == 0 || choice == 1);
ProjectivePoint::conditional_swap(&mut x0, &mut x1, choice.into());
differential_add_and_double(&mut x0, &mut x1, &affine_u);
prev_bit = cur_bit;
}
// The final value of prev_bit above is scalar.bits()[0], i.e., the LSB of scalar
ProjectivePoint::conditional_swap(&mut x0, &mut x1, Choice::from(prev_bit as u8));
// Don't leave the bit in the stack
#[cfg(feature = "zeroize")]
prev_bit.zeroize();
x0.as_affine()
}
/// View this `MontgomeryPoint` as an array of bytes.
pub const fn as_bytes(&self) -> &[u8; 32] {
&self.0
@ -357,55 +394,27 @@ define_mul_variants!(
);
/// Multiply this `MontgomeryPoint` by a `Scalar`.
impl<'a, 'b> Mul<&'b Scalar> for &'a MontgomeryPoint {
impl Mul<&Scalar> for &MontgomeryPoint {
type Output = MontgomeryPoint;
/// Given `self` \\( = u\_0(P) \\), and a `Scalar` \\(n\\), return \\( u\_0(\[n\]P) \\).
fn mul(self, scalar: &'b Scalar) -> MontgomeryPoint {
// Algorithm 8 of Costello-Smith 2017
let affine_u = FieldElement::from_bytes(&self.0);
let mut x0 = ProjectivePoint::identity();
let mut x1 = ProjectivePoint {
U: affine_u,
W: FieldElement::ONE,
};
// NOTE: The below swap-double-add routine skips the first iteration, i.e., it assumes the
// MSB of `scalar` is 0. This is allowed, since it follows from Scalar invariant #1.
// Go through the bits from most to least significant, using a sliding window of 2
let mut bits = scalar.bits_le().rev();
let mut prev_bit = bits.next().unwrap();
for cur_bit in bits {
let choice: u8 = (prev_bit ^ cur_bit) as u8;
debug_assert!(choice == 0 || choice == 1);
ProjectivePoint::conditional_swap(&mut x0, &mut x1, choice.into());
differential_add_and_double(&mut x0, &mut x1, &affine_u);
prev_bit = cur_bit;
}
// The final value of prev_bit above is scalar.bits()[0], i.e., the LSB of scalar
ProjectivePoint::conditional_swap(&mut x0, &mut x1, Choice::from(prev_bit as u8));
// Don't leave the bit in the stack
#[cfg(feature = "zeroize")]
prev_bit.zeroize();
x0.as_affine()
/// Given `self` \\( = u\_0(P) \\), and a `Scalar` \\(n\\), return \\( u\_0(\[n\]P) \\)
fn mul(self, scalar: &Scalar) -> MontgomeryPoint {
// We multiply by the integer representation of the given Scalar. By scalar invariant #1,
// the MSB is 0, so we can skip it.
self.mul_bits_be(scalar.bits_le().rev().skip(1))
}
}
impl<'b> MulAssign<&'b Scalar> for MontgomeryPoint {
fn mul_assign(&mut self, scalar: &'b Scalar) {
impl MulAssign<&Scalar> for MontgomeryPoint {
fn mul_assign(&mut self, scalar: &Scalar) {
*self = (self as &MontgomeryPoint) * scalar;
}
}
impl<'a, 'b> Mul<&'b MontgomeryPoint> for &'a Scalar {
impl Mul<&MontgomeryPoint> for &Scalar {
type Output = MontgomeryPoint;
fn mul(self, point: &'b MontgomeryPoint) -> MontgomeryPoint {
fn mul(self, point: &MontgomeryPoint) -> MontgomeryPoint {
point * self
}
}
@ -422,7 +431,7 @@ mod test {
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use rand_core::RngCore;
use rand_core::{CryptoRng, RngCore};
#[test]
fn identity_in_different_coordinates() {
@ -505,15 +514,33 @@ mod test {
assert_eq!(u18, u18_unred);
}
/// Returns a random point on the prime-order subgroup
fn rand_prime_order_point(mut rng: impl RngCore + CryptoRng) -> EdwardsPoint {
let s: Scalar = Scalar::random(&mut rng);
EdwardsPoint::mul_base(&s)
}
/// Given a bytestring that's little-endian at the byte level, return an iterator over all the
/// bits, in little-endian order.
fn bytestring_bits_le(x: &[u8]) -> impl DoubleEndedIterator<Item = bool> + Clone + '_ {
let bitlen = x.len() * 8;
(0..bitlen).map(|i| {
// As i runs from 0..256, the bottom 3 bits index the bit, while the upper bits index
// the byte. Since self.bytes is little-endian at the byte level, this iterator is
// little-endian on the bit level
((x[i >> 3] >> (i & 7)) & 1u8) == 1
})
}
#[test]
fn montgomery_ladder_matches_edwards_scalarmult() {
let mut csprng = rand_core::OsRng;
for _ in 0..100 {
let s: Scalar = Scalar::random(&mut csprng);
let p_edwards = EdwardsPoint::mul_base(&s);
let p_edwards = rand_prime_order_point(&mut csprng);
let p_montgomery: MontgomeryPoint = p_edwards.to_montgomery();
let s: Scalar = Scalar::random(&mut csprng);
let expected = s * p_edwards;
let result = s * p_montgomery;
@ -521,6 +548,65 @@ mod test {
}
}
// Tests that, on the prime-order subgroup, MontgomeryPoint::mul_bits_be is the same as
// multiplying by the Scalar representation of the same bits
#[test]
fn montgomery_mul_bits_be() {
let mut csprng = rand_core::OsRng;
for _ in 0..100 {
// Make a random prime-order point P
let p_edwards = rand_prime_order_point(&mut csprng);
let p_montgomery: MontgomeryPoint = p_edwards.to_montgomery();
// Make a random integer b
let mut bigint = [0u8; 64];
csprng.fill_bytes(&mut bigint[..]);
let bigint_bits_be = bytestring_bits_le(&bigint).rev();
// Check that bP is the same whether calculated as scalar-times-edwards or
// integer-times-montgomery.
let expected = Scalar::from_bytes_mod_order_wide(&bigint) * p_edwards;
let result = p_montgomery.mul_bits_be(bigint_bits_be);
assert_eq!(result, expected.to_montgomery())
}
}
// Tests that MontgomeryPoint::mul_bits_be is consistent on any point, even ones that might be
// on the curve's twist. Specifically, this tests that b₁(b₂P) == b₂(b₁P) for random
// integers b₁, b₂ and random (curve or twist) point P.
#[test]
fn montgomery_mul_bits_be_twist() {
let mut csprng = rand_core::OsRng;
for _ in 0..100 {
// Make a random point P on the curve or its twist
let p_montgomery = {
let mut buf = [0u8; 32];
csprng.fill_bytes(&mut buf);
MontgomeryPoint(buf)
};
// Compute two big integers b₁ and b₂
let mut bigint1 = [0u8; 64];
let mut bigint2 = [0u8; 64];
csprng.fill_bytes(&mut bigint1[..]);
csprng.fill_bytes(&mut bigint2[..]);
// Compute b₁P and b₂P
let bigint1_bits_be = bytestring_bits_le(&bigint1).rev();
let bigint2_bits_be = bytestring_bits_le(&bigint2).rev();
let prod1 = p_montgomery.mul_bits_be(bigint1_bits_be.clone());
let prod2 = p_montgomery.mul_bits_be(bigint2_bits_be.clone());
// Check that b₁(b₂P) == b₂(b₁P)
assert_eq!(
prod1.mul_bits_be(bigint2_bits_be),
prod2.mul_bits_be(bigint1_bits_be)
);
}
}
/// Check that mul_base_clamped and mul_clamped agree
#[test]
fn mul_base_clamped() {

View File

@ -180,6 +180,13 @@ use digest::Digest;
use crate::constants;
use crate::field::FieldElement;
#[cfg(feature = "group")]
use {
group::{cofactor::CofactorGroup, prime::PrimeGroup, GroupEncoding},
rand_core::RngCore,
subtle::CtOption,
};
use subtle::Choice;
use subtle::ConditionallyNegatable;
use subtle::ConditionallySelectable;
@ -246,6 +253,26 @@ impl CompressedRistretto {
///
/// - `None` if `self` was not the canonical encoding of a point.
pub fn decompress(&self) -> Option<RistrettoPoint> {
let (s_encoding_is_canonical, s_is_negative, s) = decompress::step_1(self);
if (!s_encoding_is_canonical | s_is_negative).into() {
return None;
}
let (ok, t_is_negative, y_is_zero, res) = decompress::step_2(s);
if (!ok | t_is_negative | y_is_zero).into() {
None
} else {
Some(res)
}
}
}
mod decompress {
use super::*;
pub(super) fn step_1(repr: &CompressedRistretto) -> (Choice, Choice, FieldElement) {
// Step 1. Check s for validity:
// 1.a) s must be 32 bytes (we get this from the type system)
// 1.b) s < p
@ -257,15 +284,15 @@ impl CompressedRistretto {
// converting back to bytes, and checking that we get the
// original input, since our encoding routine is canonical.
let s = FieldElement::from_bytes(self.as_bytes());
let s = FieldElement::from_bytes(repr.as_bytes());
let s_bytes_check = s.as_bytes();
let s_encoding_is_canonical = s_bytes_check[..].ct_eq(self.as_bytes());
let s_encoding_is_canonical = s_bytes_check[..].ct_eq(repr.as_bytes());
let s_is_negative = s.is_negative();
if (!s_encoding_is_canonical | s_is_negative).into() {
return None;
}
(s_encoding_is_canonical, s_is_negative, s)
}
pub(super) fn step_2(s: FieldElement) -> (Choice, Choice, Choice, RistrettoPoint) {
// Step 2. Compute (X:Y:Z:T).
let one = FieldElement::ONE;
let ss = s.square();
@ -292,16 +319,17 @@ impl CompressedRistretto {
// t == ((1+as²) sqrt(4s²/(ad(1+as²)² - (1-as²)²)))/(1-as²)
let t = &x * &y;
if (!ok | t.is_negative() | y.is_zero()).into() {
None
} else {
Some(RistrettoPoint(EdwardsPoint {
(
ok,
t.is_negative(),
y.is_zero(),
RistrettoPoint(EdwardsPoint {
X: x,
Y: y,
Z: one,
T: t,
}))
}
}),
)
}
}
@ -336,7 +364,7 @@ impl TryFrom<&[u8]> for CompressedRistretto {
#[cfg(feature = "serde")]
use serde::de::Visitor;
#[cfg(feature = "serde")]
use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[cfg(feature = "serde")]
impl Serialize for RistrettoPoint {
@ -379,7 +407,7 @@ impl<'de> Deserialize<'de> for RistrettoPoint {
impl<'de> Visitor<'de> for RistrettoPointVisitor {
type Value = RistrettoPoint;
fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter.write_str("a valid point in Ristretto format")
}
@ -388,6 +416,7 @@ impl<'de> Deserialize<'de> for RistrettoPoint {
A: serde::de::SeqAccess<'de>,
{
let mut bytes = [0u8; 32];
#[allow(clippy::needless_range_loop)]
for i in 0..32 {
bytes[i] = seq
.next_element()?
@ -414,7 +443,7 @@ impl<'de> Deserialize<'de> for CompressedRistretto {
impl<'de> Visitor<'de> for CompressedRistrettoVisitor {
type Value = CompressedRistretto;
fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter.write_str("32 bytes of data")
}
@ -423,6 +452,7 @@ impl<'de> Deserialize<'de> for CompressedRistretto {
A: serde::de::SeqAccess<'de>,
{
let mut bytes = [0u8; 32];
#[allow(clippy::needless_range_loop)]
for i in 0..32 {
bytes[i] = seq
.next_element()?
@ -623,7 +653,7 @@ impl RistrettoPoint {
///
/// This method is not public because it's just used for hashing
/// to a point -- proper elligator support is deferred for now.
pub fn elligator_ristretto_flavor(r_0: &FieldElement) -> RistrettoPoint {
pub(crate) fn elligator_ristretto_flavor(r_0: &FieldElement) -> RistrettoPoint {
let i = &constants::SQRT_M1;
let d = &constants::EDWARDS_D;
let one_minus_d_sq = &constants::ONE_MINUS_EDWARDS_D_SQUARED;
@ -1125,13 +1155,13 @@ impl ConditionallySelectable for RistrettoPoint {
// ------------------------------------------------------------------------
impl Debug for CompressedRistretto {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "CompressedRistretto: {:?}", self.as_bytes())
}
}
impl Debug for RistrettoPoint {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let coset = self.coset4();
write!(
f,
@ -1141,6 +1171,86 @@ impl Debug for RistrettoPoint {
}
}
// ------------------------------------------------------------------------
// group traits
// ------------------------------------------------------------------------
// Use the full trait path to avoid Group::identity overlapping Identity::identity in the
// rest of the module (e.g. tests).
#[cfg(feature = "group")]
impl group::Group for RistrettoPoint {
type Scalar = Scalar;
fn random(mut rng: impl RngCore) -> Self {
// NOTE: this is duplicated due to different `rng` bounds
let mut uniform_bytes = [0u8; 64];
rng.fill_bytes(&mut uniform_bytes);
RistrettoPoint::from_uniform_bytes(&uniform_bytes)
}
fn identity() -> Self {
Identity::identity()
}
fn generator() -> Self {
constants::RISTRETTO_BASEPOINT_POINT
}
fn is_identity(&self) -> Choice {
self.ct_eq(&Identity::identity())
}
fn double(&self) -> Self {
self + self
}
}
#[cfg(feature = "group")]
impl GroupEncoding for RistrettoPoint {
type Repr = [u8; 32];
fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
let (s_encoding_is_canonical, s_is_negative, s) =
decompress::step_1(&CompressedRistretto(*bytes));
let s_is_valid = s_encoding_is_canonical & !s_is_negative;
let (ok, t_is_negative, y_is_zero, res) = decompress::step_2(s);
CtOption::new(res, s_is_valid & ok & !t_is_negative & !y_is_zero)
}
fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
// Just use the checked API; the checks we could skip aren't expensive.
Self::from_bytes(bytes)
}
fn to_bytes(&self) -> Self::Repr {
self.compress().to_bytes()
}
}
#[cfg(feature = "group")]
impl PrimeGroup for RistrettoPoint {}
/// Ristretto has a cofactor of 1.
#[cfg(feature = "group")]
impl CofactorGroup for RistrettoPoint {
type Subgroup = Self;
fn clear_cofactor(&self) -> Self::Subgroup {
*self
}
fn into_subgroup(self) -> CtOption<Self::Subgroup> {
CtOption::new(self, Choice::from(1))
}
fn is_torsion_free(&self) -> Choice {
Choice::from(1)
}
}
// ------------------------------------------------------------------------
// Zeroize traits
// ------------------------------------------------------------------------
@ -1167,8 +1277,6 @@ impl Zeroize for RistrettoPoint {
mod test {
use super::*;
use crate::edwards::CompressedEdwardsY;
use crate::scalar::Scalar;
use crate::traits::Identity;
use rand_core::OsRng;
@ -1270,7 +1378,7 @@ mod test {
let bp_compressed_ristretto = constants::RISTRETTO_BASEPOINT_POINT.compress();
let bp_recaf = bp_compressed_ristretto.decompress().unwrap().0;
// Check that bp_recaf differs from bp by a point of order 4
let diff = &constants::RISTRETTO_BASEPOINT_POINT.0 - bp_recaf;
let diff = constants::RISTRETTO_BASEPOINT_POINT.0 - bp_recaf;
let diff4 = diff.mul_by_pow_2(2);
assert_eq!(diff4.compress(), CompressedEdwardsY::identity());
}
@ -1681,7 +1789,7 @@ mod test {
];
// Check that onewaymap(input) == output for all the above vectors
for (input, output) in test_vectors {
let Q = RistrettoPoint::from_uniform_bytes(&input);
let Q = RistrettoPoint::from_uniform_bytes(input);
assert_eq!(&Q.compress(), output);
}
}

View File

@ -112,8 +112,6 @@
//! has been enabled.
use core::borrow::Borrow;
use core::cmp::{Eq, PartialEq};
use core::convert::TryInto;
use core::fmt::Debug;
use core::iter::{Product, Sum};
use core::ops::Index;
@ -124,6 +122,14 @@ use core::ops::{Sub, SubAssign};
use cfg_if::cfg_if;
#[cfg(feature = "group")]
use group::ff::{Field, FromUniformBytes, PrimeField};
#[cfg(feature = "group-bits")]
use group::ff::{FieldBits, PrimeFieldBits};
#[cfg(any(test, feature = "group"))]
use rand_core::RngCore;
#[cfg(any(test, feature = "rand_core"))]
use rand_core::CryptoRngCore;
@ -279,7 +285,7 @@ impl Scalar {
}
impl Debug for Scalar {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Scalar{{\n\tbytes: {:?},\n}}", &self.bytes)
}
}
@ -394,7 +400,7 @@ impl ConditionallySelectable for Scalar {
#[cfg(feature = "serde")]
use serde::de::Visitor;
#[cfg(feature = "serde")]
use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
@ -424,7 +430,7 @@ impl<'de> Deserialize<'de> for Scalar {
impl<'de> Visitor<'de> for ScalarVisitor {
type Value = Scalar;
fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter.write_str(
"a sequence of 32 bytes whose little-endian interpretation is less than the \
basepoint order ",
@ -436,13 +442,14 @@ impl<'de> Deserialize<'de> for Scalar {
A: serde::de::SeqAccess<'de>,
{
let mut bytes = [0u8; 32];
#[allow(clippy::needless_range_loop)]
for i in 0..32 {
bytes[i] = seq
.next_element()?
.ok_or_else(|| serde::de::Error::invalid_length(i, &"expected 32 bytes"))?;
}
Option::from(Scalar::from_canonical_bytes(bytes))
.ok_or_else(|| serde::de::Error::custom(&"scalar was not canonically encoded"))
.ok_or_else(|| serde::de::Error::custom("scalar was not canonically encoded"))
}
}
@ -824,7 +831,7 @@ impl Scalar {
}
#[cfg(feature = "zeroize")]
zeroize::Zeroize::zeroize(&mut scratch);
Zeroize::zeroize(&mut scratch);
ret
}
@ -1201,6 +1208,141 @@ impl UnpackedScalar {
}
}
#[cfg(feature = "group")]
impl Field for Scalar {
const ZERO: Self = Self::ZERO;
const ONE: Self = Self::ONE;
fn random(mut rng: impl RngCore) -> Self {
// NOTE: this is duplicated due to different `rng` bounds
let mut scalar_bytes = [0u8; 64];
rng.fill_bytes(&mut scalar_bytes);
Self::from_bytes_mod_order_wide(&scalar_bytes)
}
fn square(&self) -> Self {
self * self
}
fn double(&self) -> Self {
self + self
}
fn invert(&self) -> CtOption<Self> {
CtOption::new(self.invert(), !self.is_zero())
}
fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) {
#[allow(unused_qualifications)]
group::ff::helpers::sqrt_ratio_generic(num, div)
}
fn sqrt(&self) -> CtOption<Self> {
#[allow(unused_qualifications)]
group::ff::helpers::sqrt_tonelli_shanks(
self,
[
0xcb02_4c63_4b9e_ba7d,
0x029b_df3b_d45e_f39a,
0x0000_0000_0000_0000,
0x0200_0000_0000_0000,
],
)
}
}
#[cfg(feature = "group")]
impl PrimeField for Scalar {
type Repr = [u8; 32];
fn from_repr(repr: Self::Repr) -> CtOption<Self> {
Self::from_canonical_bytes(repr)
}
fn from_repr_vartime(repr: Self::Repr) -> Option<Self> {
// Check that the high bit is not set
if (repr[31] >> 7) != 0u8 {
return None;
}
let candidate = Scalar { bytes: repr };
if candidate == candidate.reduce() {
Some(candidate)
} else {
None
}
}
fn to_repr(&self) -> Self::Repr {
self.to_bytes()
}
fn is_odd(&self) -> Choice {
Choice::from(self.as_bytes()[0] & 1)
}
const MODULUS: &'static str =
"0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed";
const NUM_BITS: u32 = 253;
const CAPACITY: u32 = 252;
const TWO_INV: Self = Self {
bytes: [
0xf7, 0xe9, 0x7a, 0x2e, 0x8d, 0x31, 0x09, 0x2c, 0x6b, 0xce, 0x7b, 0x51, 0xef, 0x7c,
0x6f, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x08,
],
};
const MULTIPLICATIVE_GENERATOR: Self = Self {
bytes: [
2, 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,
],
};
const S: u32 = 2;
const ROOT_OF_UNITY: Self = Self {
bytes: [
0xd4, 0x07, 0xbe, 0xeb, 0xdf, 0x75, 0x87, 0xbe, 0xfe, 0x83, 0xce, 0x42, 0x53, 0x56,
0xf0, 0x0e, 0x7a, 0xc2, 0xc1, 0xab, 0x60, 0x6d, 0x3d, 0x7d, 0xe7, 0x81, 0x79, 0xe0,
0x10, 0x73, 0x4a, 0x09,
],
};
const ROOT_OF_UNITY_INV: Self = Self {
bytes: [
0x19, 0xcc, 0x37, 0x71, 0x3a, 0xed, 0x8a, 0x99, 0xd7, 0x18, 0x29, 0x60, 0x8b, 0xa3,
0xee, 0x05, 0x86, 0x3d, 0x3e, 0x54, 0x9f, 0x92, 0xc2, 0x82, 0x18, 0x7e, 0x86, 0x1f,
0xef, 0x8c, 0xb5, 0x06,
],
};
const DELTA: Self = Self {
bytes: [
16, 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,
],
};
}
#[cfg(feature = "group-bits")]
impl PrimeFieldBits for Scalar {
type ReprBits = [u8; 32];
fn to_le_bits(&self) -> FieldBits<Self::ReprBits> {
self.to_repr().into()
}
fn char_le_bits() -> FieldBits<Self::ReprBits> {
constants::BASEPOINT_ORDER_PRIVATE.to_bytes().into()
}
}
#[cfg(feature = "group")]
impl FromUniformBytes<64> for Scalar {
fn from_uniform_bytes(bytes: &[u8; 64]) -> Self {
Scalar::from_bytes_mod_order_wide(bytes)
}
}
/// Read one or more u64s stored as little endian bytes.
///
/// ## Panics
@ -1240,6 +1382,7 @@ fn read_le_u64_into(src: &[u8], dst: &mut [u64]) {
///
/// See [here](https://neilmadden.blog/2020/05/28/whats-the-curve25519-clamping-all-about/) for
/// more details.
#[must_use]
pub const fn clamp_integer(mut bytes: [u8; 32]) -> [u8; 32] {
bytes[0] &= 0b1111_1000;
bytes[31] &= 0b0111_1111;
@ -1250,13 +1393,10 @@ pub const fn clamp_integer(mut bytes: [u8; 32]) -> [u8; 32] {
#[cfg(test)]
pub(crate) mod test {
use super::*;
use crate::constants;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use rand::RngCore;
/// x = 2238329342913194256032495932344128051776374960164957527413114840482143558222
pub static X: Scalar = Scalar {
bytes: [
@ -1475,13 +1615,13 @@ pub(crate) mod test {
#[cfg(feature = "alloc")]
fn impl_product() {
// Test that product works for non-empty iterators
let X_Y_vector = vec![X, Y];
let X_Y_vector = [X, Y];
let should_be_X_times_Y: Scalar = X_Y_vector.iter().product();
assert_eq!(should_be_X_times_Y, X_TIMES_Y);
// Test that product works for the empty iterator
let one = Scalar::ONE;
let empty_vector = vec![];
let empty_vector = [];
let should_be_one: Scalar = empty_vector.iter().product();
assert_eq!(should_be_one, one);
@ -1506,13 +1646,13 @@ pub(crate) mod test {
fn impl_sum() {
// Test that sum works for non-empty iterators
let two = Scalar::from(2u64);
let one_vector = vec![Scalar::ONE, Scalar::ONE];
let one_vector = [Scalar::ONE, Scalar::ONE];
let should_be_two: Scalar = one_vector.iter().sum();
assert_eq!(should_be_two, two);
// Test that sum works for the empty iterator
let zero = Scalar::ZERO;
let empty_vector = vec![];
let empty_vector = [];
let should_be_zero: Scalar = empty_vector.iter().sum();
assert_eq!(should_be_zero, zero);
@ -1812,6 +1952,56 @@ pub(crate) mod test {
assert_eq!(sx + s1, Scalar::from(x + 1));
}
#[cfg(feature = "group")]
#[test]
fn ff_constants() {
assert_eq!(Scalar::from(2u64) * Scalar::TWO_INV, Scalar::ONE);
assert_eq!(
Scalar::ROOT_OF_UNITY * Scalar::ROOT_OF_UNITY_INV,
Scalar::ONE,
);
// ROOT_OF_UNITY^{2^s} mod m == 1
assert_eq!(
Scalar::ROOT_OF_UNITY.pow(&[1u64 << Scalar::S, 0, 0, 0]),
Scalar::ONE,
);
// DELTA^{t} mod m == 1
assert_eq!(
Scalar::DELTA.pow(&[
0x9604_98c6_973d_74fb,
0x0537_be77_a8bd_e735,
0x0000_0000_0000_0000,
0x0400_0000_0000_0000,
]),
Scalar::ONE,
);
}
#[cfg(feature = "group")]
#[test]
fn ff_impls() {
assert!(bool::from(Scalar::ZERO.is_even()));
assert!(bool::from(Scalar::ONE.is_odd()));
assert!(bool::from(Scalar::from(2u64).is_even()));
assert!(bool::from(Scalar::DELTA.is_even()));
assert!(bool::from(Field::invert(&Scalar::ZERO).is_none()));
assert_eq!(Field::invert(&X).unwrap(), XINV);
let x_sq = X.square();
// We should get back either the positive or negative root.
assert!([X, -X].contains(&x_sq.sqrt().unwrap()));
assert_eq!(Scalar::from_repr_vartime(X.to_repr()), Some(X));
assert_eq!(Scalar::from_repr_vartime([0xff; 32]), None);
assert_eq!(Scalar::from_repr(X.to_repr()).unwrap(), X);
assert!(bool::from(Scalar::from_repr([0xff; 32]).is_none()));
}
#[test]
#[should_panic]
fn test_read_le_u64_into_should_panic_on_bad_input() {

View File

@ -15,9 +15,8 @@
use core::borrow::Borrow;
use subtle;
use crate::scalar::{clamp_integer, Scalar};
use subtle::ConstantTimeEq;
// ------------------------------------------------------------------------
// Public Traits
@ -41,7 +40,7 @@ pub trait IsIdentity {
/// constructor.
impl<T> IsIdentity for T
where
T: subtle::ConstantTimeEq + Identity,
T: ConstantTimeEq + Identity,
{
fn is_identity(&self) -> bool {
self.ct_eq(&T::identity()).into()
@ -259,7 +258,7 @@ pub trait VartimeMultiscalarMul {
scalars,
points.into_iter().map(|P| Some(P.borrow().clone())),
)
.unwrap()
.expect("should return some point")
}
}
@ -365,7 +364,7 @@ pub trait VartimePrecomputedMultiscalarMul: Sized {
dynamic_scalars,
dynamic_points.into_iter().map(|P| Some(P.borrow().clone())),
)
.unwrap()
.expect("should return some point")
}
/// Given `static_scalars`, an iterator of public scalars
@ -409,6 +408,7 @@ pub trait VartimePrecomputedMultiscalarMul: Sized {
/// This trait is only for debugging/testing, since it should be
/// impossible for a `curve25519-dalek` user to construct an invalid
/// point.
#[allow(dead_code)]
pub(crate) trait ValidityCheck {
/// Checks whether the point is on the curve. Not CT.
fn is_valid(&self) -> bool;

View File

@ -83,7 +83,7 @@ macro_rules! impl_lookup_table {
}
impl<T: Debug> Debug for $name<T> {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:?}(", stringify!($name))?;
for x in self.0.iter() {
@ -193,7 +193,7 @@ impl<T: Copy> NafLookupTable5<T> {
}
impl<T: Debug> Debug for NafLookupTable5<T> {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "NafLookupTable5({:?})", self.0)
}
}
@ -240,7 +240,7 @@ impl<T: Copy> NafLookupTable8<T> {
#[cfg(any(feature = "precomputed-tables", feature = "alloc"))]
impl<T: Debug> Debug for NafLookupTable8<T> {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
writeln!(f, "NafLookupTable8([")?;
for i in 0..64 {
writeln!(f, "\t{:?},", &self.0[i])?;

View File

@ -6,8 +6,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Entries are listed in reverse chronological order per undeprecated major series.
# Unreleased
# 2.x series
## 2.1.1
* Fix nightly SIMD build
## 2.1.0
* Add `SigningKey::to_scalar_bytes` for getting the unclamped scalar from a signing key
* Loosened `signature` dependency to allow version 2.2
## 2.0.0
### Breaking changes

View File

@ -1,6 +1,6 @@
[package]
name = "ed25519-dalek"
version = "2.0.0-rc.3"
version = "2.1.1"
edition = "2021"
authors = [
"isis lovecruft <isis@patternsinthevoid.net>",
@ -9,7 +9,8 @@ authors = [
]
readme = "README.md"
license = "BSD-3-Clause"
repository = "https://github.com/dalek-cryptography/ed25519-dalek"
repository = "https://github.com/dalek-cryptography/curve25519-dalek/tree/main/ed25519-dalek"
homepage = "https://github.com/dalek-cryptography/curve25519-dalek"
documentation = "https://docs.rs/ed25519-dalek"
keywords = ["cryptography", "ed25519", "curve25519", "signature", "ECC"]
categories = ["cryptography", "no-std"]
@ -27,8 +28,9 @@ features = ["batch", "digest", "hazmat", "pem", "serde"]
[dependencies]
curve25519-dalek = { version = "4", path = "../curve25519-dalek", default-features = false, features = ["digest"] }
ed25519 = { version = ">=2.2, <2.3", default-features = false }
signature = { version = ">=2.0, <2.1", optional = true, default-features = false }
signature = { version = ">=2.0, <2.3", optional = true, default-features = false }
sha2 = { version = "0.10", default-features = false }
subtle = { version = "2.3.0", default-features = false }
# optional features
merlin = { version = "3", default-features = false, optional = true }
@ -38,17 +40,18 @@ zeroize = { version = "1.5", default-features = false, optional = true }
[dev-dependencies]
curve25519-dalek = { version = "4", path = "../curve25519-dalek", default-features = false, features = ["digest", "rand_core"] }
x25519-dalek = { version = "2", path = "../x25519-dalek", default-features = false, features = ["static_secrets"] }
blake2 = "0.10"
sha3 = "0.10"
hex = "0.4"
bincode = "1.0"
serde_json = "1.0"
criterion = { version = "0.4", features = ["html_reports"] }
hex-literal = "0.3"
criterion = { version = "0.5", features = ["html_reports"] }
hex-literal = "0.4"
rand = "0.8"
rand_core = { version = "0.6.4", default-features = false }
serde = { version = "1.0", features = ["derive"] }
toml = { version = "0.5" }
toml = { version = "0.7" }
[[bench]]
name = "ed25519_benchmarks"

View File

@ -5,20 +5,10 @@ verification.
# Use
## Stable
To import `ed25519-dalek`, add the following to the dependencies section of
your project's `Cargo.toml`:
```toml
ed25519-dalek = "1"
```
## Beta
To use the latest prerelease (see changes [below](#breaking-changes-in-200)),
use the following line in your project's `Cargo.toml`:
```toml
ed25519-dalek = "2.0.0-rc.3"
ed25519-dalek = "2"
```
# Feature Flags
@ -41,7 +31,7 @@ This crate is `#[no_std]` compatible with `default-features = false`.
# Major Changes
See [CHANGELOG.md](CHANGELOG.md) for a list of changes made in past version of this crate.
See [CHANGELOG.md](CHANGELOG.md) for a list of changes made in past versions of this crate.
## Breaking Changes in 2.0.0
@ -53,7 +43,7 @@ See [CHANGELOG.md](CHANGELOG.md) for a list of changes made in past version of t
* Make `rand_core` an optional dependency
* Adopt [curve25519-backend selection](https://github.com/dalek-cryptography/curve25519-dalek/#backends) over features
* Make all batch verification deterministic remove `batch_deterministic` ([#256](https://github.com/dalek-cryptography/ed25519-dalek/pull/256))
* Remove `ExpandedSecretKey` API ((#205)[https://github.com/dalek-cryptography/ed25519-dalek/pull/205])
* Remove `ExpandedSecretKey` API ([#205](https://github.com/dalek-cryptography/ed25519-dalek/pull/205))
* Rename `Keypair``SigningKey` and `PublicKey``VerifyingKey`
* Make `hazmat` feature to expose, `ExpandedSecretKey`, `raw_sign()`, `raw_sign_prehashed()`, `raw_verify()`, and `raw_verify_prehashed()`
@ -73,7 +63,7 @@ SemVer exemptions are outlined below for MSRV and public API.
| 2.x | 1.60 |
| 1.x | 1.41 |
From 2.x and on, MSRV changes will be accompanied by a minor version bump.
From 2.x onwards, MSRV changes will be accompanied by a minor version bump.
## Public API SemVer Exemptions
@ -140,7 +130,7 @@ Backend selection details and instructions can be found in the [curve25519-dalek
# Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md)
See [CONTRIBUTING.md](../CONTRIBUTING.md)
# Batch Signature Verification

View File

@ -64,8 +64,7 @@ mod ed25519_benches {
.collect();
let msg: &[u8] = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
let messages: Vec<&[u8]> = (0..size).map(|_| msg).collect();
let signatures: Vec<Signature> =
keypairs.iter().map(|key| key.sign(&msg)).collect();
let signatures: Vec<Signature> = keypairs.iter().map(|key| key.sign(msg)).collect();
let verifying_keys: Vec<_> =
keypairs.iter().map(|key| key.verifying_key()).collect();

View File

@ -11,7 +11,6 @@
use alloc::vec::Vec;
use core::convert::TryFrom;
use core::iter::once;
use curve25519_dalek::constants;
@ -176,7 +175,7 @@ pub fn verify_batch(
let mut h: Sha512 = Sha512::default();
h.update(signatures[i].r_bytes());
h.update(verifying_keys[i].as_bytes());
h.update(&messages[i]);
h.update(messages[i]);
*h.finalize().as_ref()
})
.collect();

View File

@ -80,6 +80,8 @@ impl<'k, 'v, K> Context<'k, 'v, K> {
#[cfg(all(test, feature = "digest"))]
mod test {
#![allow(clippy::unwrap_used)]
use crate::{Signature, SigningKey, VerifyingKey};
use curve25519_dalek::digest::Digest;
use ed25519::signature::{DigestSigner, DigestVerifier};

View File

@ -156,11 +156,11 @@ where
/// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1
#[cfg(feature = "digest")]
#[allow(non_snake_case)]
pub fn raw_sign_prehashed<'a, CtxDigest, MsgDigest>(
pub fn raw_sign_prehashed<CtxDigest, MsgDigest>(
esk: &ExpandedSecretKey,
prehashed_message: MsgDigest,
verifying_key: &VerifyingKey,
context: Option<&'a [u8]>,
context: Option<&[u8]>,
) -> Result<Signature, SignatureError>
where
MsgDigest: Digest<OutputSize = U64>,
@ -204,6 +204,8 @@ where
#[cfg(test)]
mod test {
#![allow(clippy::unwrap_used)]
use super::*;
use rand::{rngs::OsRng, CryptoRng, RngCore};
@ -226,8 +228,8 @@ mod test {
#[test]
fn sign_verify_nonspec() {
// Generate the keypair
let mut rng = OsRng;
let esk = ExpandedSecretKey::random(&mut rng);
let rng = OsRng;
let esk = ExpandedSecretKey::random(rng);
let vk = VerifyingKey::from(&esk);
let msg = b"Then one day, a piano fell on my head";
@ -245,8 +247,8 @@ mod test {
use curve25519_dalek::digest::Digest;
// Generate the keypair
let mut rng = OsRng;
let esk = ExpandedSecretKey::random(&mut rng);
let rng = OsRng;
let esk = ExpandedSecretKey::random(rng);
let vk = VerifyingKey::from(&esk);
// Hash the message

View File

@ -21,6 +21,7 @@
#![cfg_attr(feature = "rand_core", doc = "```")]
#![cfg_attr(not(feature = "rand_core"), doc = "```ignore")]
//! # fn main() {
//! // $ cargo add ed25519_dalek --features rand_core
//! use rand::rngs::OsRng;
//! use ed25519_dalek::SigningKey;
//! use ed25519_dalek::Signature;

View File

@ -9,7 +9,6 @@
//! An ed25519 signature.
use core::convert::TryFrom;
use core::fmt::Debug;
use curve25519_dalek::edwards::CompressedEdwardsY;
@ -58,7 +57,7 @@ impl Clone for InternalSignature {
}
impl Debug for InternalSignature {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Signature( R: {:?}, s: {:?} )", &self.R, &self.s)
}
}

View File

@ -9,6 +9,8 @@
//! ed25519 signing keys.
use core::fmt::Debug;
#[cfg(feature = "pkcs8")]
use ed25519::pkcs8;
@ -19,6 +21,7 @@ use rand_core::CryptoRngCore;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use sha2::Sha512;
use subtle::{Choice, ConstantTimeEq};
use curve25519_dalek::{
digest::{generic_array::typenum::U64, Digest},
@ -54,9 +57,10 @@ use crate::{
pub type SecretKey = [u8; SECRET_KEY_LENGTH];
/// ed25519 signing key which can be used to produce signatures.
// Invariant: `public` is always the public key of `secret`. This prevents the signing function
// oracle attack described in https://github.com/MystenLabs/ed25519-unsafe-libs
#[derive(Clone, Debug)]
// Invariant: `verifying_key` is always the public key of
// `secret_key`. This prevents the signing function oracle attack
// described in https://github.com/MystenLabs/ed25519-unsafe-libs
#[derive(Clone)]
pub struct SigningKey {
/// The secret half of this signing key.
pub(crate) secret_key: SecretKey,
@ -109,6 +113,12 @@ impl SigningKey {
self.secret_key
}
/// Convert this [`SigningKey`] into a [`SecretKey`] reference
#[inline]
pub fn as_bytes(&self) -> &SecretKey {
&self.secret_key
}
/// Construct a [`SigningKey`] from the bytes of a `VerifyingKey` and `SecretKey`.
///
/// # Inputs
@ -121,7 +131,7 @@ impl SigningKey {
/// # Returns
///
/// A `Result` whose okay value is an EdDSA [`SigningKey`] or whose error value
/// is an `SignatureError` describing the error that occurred.
/// is a `SignatureError` describing the error that occurred.
#[inline]
pub fn from_keypair_bytes(bytes: &[u8; 64]) -> Result<SigningKey, SignatureError> {
let (secret_key, verifying_key) = bytes.split_at(SECRET_KEY_LENGTH);
@ -473,15 +483,43 @@ impl SigningKey {
self.verifying_key.verify_strict(message, signature)
}
/// Convert this signing key into a byte representation of a(n) (unreduced) Curve25519 scalar.
/// Convert this signing key into a byte representation of an unreduced, unclamped Curve25519
/// scalar. This is NOT the same thing as `self.to_scalar().to_bytes()`, since `to_scalar()`
/// performs a clamping step, which changes the value of the resulting scalar.
///
/// This can be used for performing X25519 Diffie-Hellman using Ed25519 keys. The bytes output
/// by this function are a valid secret key for the X25519 public key given by
/// `self.verifying_key().to_montgomery()`.
/// by this function are a valid corresponding [`StaticSecret`](https://docs.rs/x25519-dalek/2.0.0/x25519_dalek/struct.StaticSecret.html#impl-From%3C%5Bu8;+32%5D%3E-for-StaticSecret)
/// for the X25519 public key given by `self.verifying_key().to_montgomery()`.
///
/// # Note
///
/// We do NOT recommend this usage of a signing/verifying key. Signing keys are usually
/// We do NOT recommend using a signing/verifying key for encryption. Signing keys are usually
/// long-term keys, while keys used for key exchange should rather be ephemeral. If you can
/// help it, use a separate key for encryption.
///
/// For more information on the security of systems which use the same keys for both signing
/// and Diffie-Hellman, see the paper
/// [On using the same key pair for Ed25519 and an X25519 based KEM](https://eprint.iacr.org/2021/509).
pub fn to_scalar_bytes(&self) -> [u8; 32] {
// Per the spec, the ed25519 secret key sk is expanded to
// (scalar_bytes, hash_prefix) = SHA-512(sk)
// where the two outputs are both 32 bytes. scalar_bytes is what we return. Its clamped and
// reduced form is what we use for signing (see impl ExpandedSecretKey)
let mut buf = [0u8; 32];
let scalar_and_hash_prefix = Sha512::default().chain_update(self.secret_key).finalize();
buf.copy_from_slice(&scalar_and_hash_prefix[..32]);
buf
}
/// Convert this signing key into a Curve25519 scalar. This is computed by clamping and
/// reducing the output of [`Self::to_scalar_bytes`].
///
/// This can be used anywhere where a Curve25519 scalar is used as a private key, e.g., in
/// [`crypto_box`](https://docs.rs/crypto_box/0.9.1/crypto_box/struct.SecretKey.html#impl-From%3CScalar%3E-for-SecretKey).
///
/// # Note
///
/// We do NOT recommend using a signing/verifying key for encryption. Signing keys are usually
/// long-term keys, while keys used for key exchange should rather be ephemeral. If you can
/// help it, use a separate key for encryption.
///
@ -489,6 +527,11 @@ impl SigningKey {
/// and Diffie-Hellman, see the paper
/// [On using the same key pair for Ed25519 and an X25519 based KEM](https://eprint.iacr.org/2021/509).
pub fn to_scalar(&self) -> Scalar {
// Per the spec, the ed25519 secret key sk is expanded to
// (scalar_bytes, hash_prefix) = SHA-512(sk)
// where the two outputs are both 32 bytes. To use for signing, scalar_bytes must be
// clamped and reduced (see ExpandedSecretKey::from_bytes). We return the clamped and
// reduced form.
ExpandedSecretKey::from(&self.secret_key).scalar
}
}
@ -499,6 +542,14 @@ impl AsRef<VerifyingKey> for SigningKey {
}
}
impl Debug for SigningKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SigningKey")
.field("verifying_key", &self.verifying_key)
.finish_non_exhaustive() // avoids printing `secret_key`
}
}
impl KeypairRef for SigningKey {
type VerifyingKey = VerifyingKey;
}
@ -583,6 +634,20 @@ impl TryFrom<&[u8]> for SigningKey {
}
}
impl ConstantTimeEq for SigningKey {
fn ct_eq(&self, other: &Self) -> Choice {
self.secret_key.ct_eq(&other.secret_key)
}
}
impl PartialEq for SigningKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl Eq for SigningKey {}
#[cfg(feature = "zeroize")]
impl Drop for SigningKey {
fn drop(&mut self) {
@ -677,15 +742,12 @@ impl<'d> Deserialize<'d> for SigningKey {
impl<'de> serde::de::Visitor<'de> for SigningKeyVisitor {
type Value = SigningKey;
fn expecting(&self, formatter: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(formatter, concat!("An ed25519 signing (private) key"))
}
fn visit_borrowed_bytes<E: serde::de::Error>(
self,
bytes: &'de [u8],
) -> Result<Self::Value, E> {
SigningKey::try_from(bytes.as_ref()).map_err(E::custom)
fn visit_bytes<E: serde::de::Error>(self, bytes: &[u8]) -> Result<Self::Value, E> {
SigningKey::try_from(bytes).map_err(E::custom)
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
@ -693,6 +755,7 @@ impl<'d> Deserialize<'d> for SigningKey {
A: serde::de::SeqAccess<'de>,
{
let mut bytes = [0u8; 32];
#[allow(clippy::needless_range_loop)]
for i in 0..32 {
bytes[i] = seq
.next_element()?
@ -782,11 +845,11 @@ impl ExpandedSecretKey {
#[cfg(feature = "digest")]
#[allow(non_snake_case)]
#[inline(always)]
pub(crate) fn raw_sign_prehashed<'a, CtxDigest, MsgDigest>(
pub(crate) fn raw_sign_prehashed<CtxDigest, MsgDigest>(
&self,
prehashed_message: MsgDigest,
verifying_key: &VerifyingKey,
context: Option<&'a [u8]>,
context: Option<&[u8]>,
) -> Result<Signature, SignatureError>
where
CtxDigest: Digest<OutputSize = U64>,

View File

@ -9,7 +9,6 @@
//! ed25519 public keys.
use core::convert::TryFrom;
use core::fmt::Debug;
use core::hash::{Hash, Hasher};
@ -505,6 +504,11 @@ impl VerifyingKey {
pub fn to_montgomery(&self) -> MontgomeryPoint {
self.point.to_montgomery()
}
/// Return this verifying key in Edwards form.
pub fn to_edwards(&self) -> EdwardsPoint {
self.point
}
}
impl Verifier<ed25519::Signature> for VerifyingKey {
@ -563,6 +567,12 @@ impl TryFrom<&[u8]> for VerifyingKey {
}
}
impl From<VerifyingKey> for EdwardsPoint {
fn from(vk: VerifyingKey) -> EdwardsPoint {
vk.point
}
}
#[cfg(all(feature = "alloc", feature = "pkcs8"))]
impl pkcs8::EncodePublicKey for VerifyingKey {
fn to_public_key_der(&self) -> pkcs8::spki::Result<pkcs8::Document> {
@ -632,15 +642,12 @@ impl<'d> Deserialize<'d> for VerifyingKey {
impl<'de> serde::de::Visitor<'de> for VerifyingKeyVisitor {
type Value = VerifyingKey;
fn expecting(&self, formatter: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(formatter, concat!("An ed25519 verifying (public) key"))
}
fn visit_borrowed_bytes<E: serde::de::Error>(
self,
bytes: &'de [u8],
) -> Result<Self::Value, E> {
VerifyingKey::try_from(bytes.as_ref()).map_err(E::custom)
fn visit_bytes<E: serde::de::Error>(self, bytes: &[u8]) -> Result<Self::Value, E> {
VerifyingKey::try_from(bytes).map_err(E::custom)
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
@ -649,6 +656,7 @@ impl<'d> Deserialize<'d> for VerifyingKey {
{
let mut bytes = [0u8; 32];
#[allow(clippy::needless_range_loop)]
for i in 0..32 {
bytes[i] = seq
.next_element()?

View File

@ -9,7 +9,7 @@
//! Integration tests for ed25519-dalek.
use curve25519_dalek;
#![allow(clippy::items_after_test_module)]
use ed25519_dalek::*;
@ -27,10 +27,11 @@ mod vectors {
scalar::Scalar,
traits::IsIdentity,
};
#[cfg(not(feature = "digest"))]
use sha2::{digest::Digest, Sha512};
use std::{
convert::TryFrom,
fs::File,
io::{BufRead, BufReader},
ops::Neg,
@ -63,10 +64,10 @@ mod vectors {
let parts: Vec<&str> = line.split(':').collect();
assert_eq!(parts.len(), 5, "wrong number of fields in line {}", lineno);
let sec_bytes: Vec<u8> = FromHex::from_hex(&parts[0]).unwrap();
let pub_bytes: Vec<u8> = FromHex::from_hex(&parts[1]).unwrap();
let msg_bytes: Vec<u8> = FromHex::from_hex(&parts[2]).unwrap();
let sig_bytes: Vec<u8> = FromHex::from_hex(&parts[3]).unwrap();
let sec_bytes: Vec<u8> = FromHex::from_hex(parts[0]).unwrap();
let pub_bytes: Vec<u8> = FromHex::from_hex(parts[1]).unwrap();
let msg_bytes: Vec<u8> = FromHex::from_hex(parts[2]).unwrap();
let sig_bytes: Vec<u8> = FromHex::from_hex(parts[3]).unwrap();
let sec_bytes = &sec_bytes[..SECRET_KEY_LENGTH].try_into().unwrap();
let pub_bytes = &pub_bytes[..PUBLIC_KEY_LENGTH].try_into().unwrap();
@ -161,13 +162,13 @@ mod vectors {
let mut h = Sha512::default();
if let Some(c) = context {
h.update(b"SigEd25519 no Ed25519 collisions");
h.update(&[1]);
h.update(&[c.len() as u8]);
h.update([1]);
h.update([c.len() as u8]);
h.update(c);
}
h.update(&signature_r.compress().as_bytes());
h.update(signature_r.compress().as_bytes());
h.update(&pub_key.compress().as_bytes()[..]);
h.update(&message);
h.update(message);
Scalar::from_hash(h)
}
@ -223,7 +224,7 @@ mod vectors {
// = R + H(R || A || M₂) · A
// Check that this is true
let signature = serialize_signature(&r, &s);
let vk = VerifyingKey::from_bytes(&pubkey.compress().as_bytes()).unwrap();
let vk = VerifyingKey::from_bytes(pubkey.compress().as_bytes()).unwrap();
let sig = Signature::try_from(&signature[..]).unwrap();
assert!(vk.verify(message1, &sig).is_ok());
assert!(vk.verify(message2, &sig).is_ok());
@ -265,7 +266,7 @@ mod vectors {
// Check that verify_prehashed succeeds on both sigs
let signature = serialize_signature(&r, &s);
let vk = VerifyingKey::from_bytes(&pubkey.compress().as_bytes()).unwrap();
let vk = VerifyingKey::from_bytes(pubkey.compress().as_bytes()).unwrap();
let sig = Signature::try_from(&signature[..]).unwrap();
assert!(vk
.verify_prehashed(message1.clone(), context_str, &sig)
@ -288,52 +289,47 @@ mod vectors {
mod integrations {
use super::*;
use rand::rngs::OsRng;
#[cfg(feature = "digest")]
use sha2::Sha512;
use std::collections::HashMap;
#[test]
fn sign_verify() {
// TestSignVerify
let signing_key: SigningKey;
let good_sig: Signature;
let bad_sig: Signature;
let good: &[u8] = "test message".as_bytes();
let bad: &[u8] = "wrong message".as_bytes();
let mut csprng = OsRng;
signing_key = SigningKey::generate(&mut csprng);
let signing_key: SigningKey = SigningKey::generate(&mut csprng);
let verifying_key = signing_key.verifying_key();
good_sig = signing_key.sign(&good);
bad_sig = signing_key.sign(&bad);
let good_sig: Signature = signing_key.sign(good);
let bad_sig: Signature = signing_key.sign(bad);
// Check that an honestly generated public key is not weak
assert!(!verifying_key.is_weak());
assert!(
signing_key.verify(&good, &good_sig).is_ok(),
signing_key.verify(good, &good_sig).is_ok(),
"Verification of a valid signature failed!"
);
assert!(
verifying_key.verify_strict(&good, &good_sig).is_ok(),
verifying_key.verify_strict(good, &good_sig).is_ok(),
"Strict verification of a valid signature failed!"
);
assert!(
signing_key.verify(&good, &bad_sig).is_err(),
signing_key.verify(good, &bad_sig).is_err(),
"Verification of a signature on a different message passed!"
);
assert!(
verifying_key.verify_strict(&good, &bad_sig).is_err(),
verifying_key.verify_strict(good, &bad_sig).is_err(),
"Strict verification of a signature on a different message passed!"
);
assert!(
signing_key.verify(&bad, &good_sig).is_err(),
signing_key.verify(bad, &good_sig).is_err(),
"Verification of a signature on a different message passed!"
);
assert!(
verifying_key.verify_strict(&bad, &good_sig).is_err(),
verifying_key.verify_strict(bad, &good_sig).is_err(),
"Strict verification of a signature on a different message passed!"
);
}
@ -341,10 +337,6 @@ mod integrations {
#[cfg(feature = "digest")]
#[test]
fn ed25519ph_sign_verify() {
let signing_key: SigningKey;
let good_sig: Signature;
let bad_sig: Signature;
let good: &[u8] = b"test message";
let bad: &[u8] = b"wrong message";
@ -365,12 +357,12 @@ mod integrations {
let context: &[u8] = b"testing testing 1 2 3";
signing_key = SigningKey::generate(&mut csprng);
let signing_key: SigningKey = SigningKey::generate(&mut csprng);
let verifying_key = signing_key.verifying_key();
good_sig = signing_key
let good_sig: Signature = signing_key
.sign_prehashed(prehashed_good1, Some(context))
.unwrap();
bad_sig = signing_key
let bad_sig: Signature = signing_key
.sign_prehashed(prehashed_bad1, Some(context))
.unwrap();
@ -427,9 +419,9 @@ mod integrations {
let mut signing_keys: Vec<SigningKey> = Vec::new();
let mut signatures: Vec<Signature> = Vec::new();
for i in 0..messages.len() {
for msg in messages {
let signing_key: SigningKey = SigningKey::generate(&mut csprng);
signatures.push(signing_key.sign(&messages[i]));
signatures.push(signing_key.sign(msg));
signing_keys.push(signing_key);
}
let verifying_keys: Vec<VerifyingKey> =
@ -466,6 +458,29 @@ mod integrations {
assert_eq!(v, "Second public key");
assert_eq!(m.len(), 2usize);
}
#[test]
fn montgomery_and_edwards_conversion() {
let mut rng = rand::rngs::OsRng;
let signing_key = SigningKey::generate(&mut rng);
let verifying_key = signing_key.verifying_key();
let ed = verifying_key.to_edwards();
// Check that to_edwards and From return same result:
assert_eq!(ed, curve25519_dalek::EdwardsPoint::from(verifying_key));
// The verifying key serialization is simply the compressed Edwards point
assert_eq!(verifying_key.to_bytes(), ed.compress().0);
// Check that modulo sign, to_montgomery().to_edwards() returns the original point
let monty = verifying_key.to_montgomery();
let via_monty0 = monty.to_edwards(0).unwrap();
let via_monty1 = monty.to_edwards(1).unwrap();
assert!(via_monty0 != via_monty1);
assert!(ed == via_monty0 || ed == via_monty1);
}
}
#[cfg(all(test, feature = "serde"))]
@ -477,6 +492,8 @@ struct Demo {
#[cfg(all(test, feature = "serde"))]
mod serialisation {
#![allow(clippy::zero_prefixed_literal)]
use super::*;
// The size for bincode to serialize the length of a byte array.
@ -547,7 +564,7 @@ mod serialisation {
// derived from `serialize_deserialize_verifying_key_json` test
// trailing zero elements makes key too long (34 bytes)
let encoded_verifying_key_too_long = "[130,39,155,15,62,76,188,63,124,122,26,251,233,253,225,220,14,41,166,120,108,35,254,77,160,83,172,58,219,42,86,120,0,0]";
let de_err = serde_json::from_str::<VerifyingKey>(&encoded_verifying_key_too_long)
let de_err = serde_json::from_str::<VerifyingKey>(encoded_verifying_key_too_long)
.unwrap_err()
.to_string();
assert!(
@ -560,7 +577,7 @@ mod serialisation {
fn serialize_deserialize_verifying_key_json_too_short() {
// derived from `serialize_deserialize_verifying_key_json` test
let encoded_verifying_key_too_long = "[130,39,155,15]";
let de_err = serde_json::from_str::<VerifyingKey>(&encoded_verifying_key_too_long)
let de_err = serde_json::from_str::<VerifyingKey>(encoded_verifying_key_too_long)
.unwrap_err()
.to_string();
assert!(
@ -575,6 +592,7 @@ mod serialisation {
let encoded_signing_key: Vec<u8> = bincode::serialize(&signing_key).unwrap();
let decoded_signing_key: SigningKey = bincode::deserialize(&encoded_signing_key).unwrap();
#[allow(clippy::needless_range_loop)]
for i in 0..SECRET_KEY_LENGTH {
assert_eq!(SECRET_KEY_BYTES[i], decoded_signing_key.to_bytes()[i]);
}
@ -586,6 +604,7 @@ mod serialisation {
let encoded_signing_key = serde_json::to_string(&signing_key).unwrap();
let decoded_signing_key: SigningKey = serde_json::from_str(&encoded_signing_key).unwrap();
#[allow(clippy::needless_range_loop)]
for i in 0..SECRET_KEY_LENGTH {
assert_eq!(SECRET_KEY_BYTES[i], decoded_signing_key.to_bytes()[i]);
}
@ -596,7 +615,7 @@ mod serialisation {
// derived from `serialize_deserialize_signing_key_json` test
// trailing zero elements makes key too long (34 bytes)
let encoded_signing_key_too_long = "[62,70,27,163,92,182,11,3,77,234,98,4,11,127,79,228,243,187,150,73,201,137,76,22,85,251,152,2,241,42,72,54,0,0]";
let de_err = serde_json::from_str::<SigningKey>(&encoded_signing_key_too_long)
let de_err = serde_json::from_str::<SigningKey>(encoded_signing_key_too_long)
.unwrap_err()
.to_string();
assert!(
@ -609,7 +628,7 @@ mod serialisation {
fn serialize_deserialize_signing_key_json_too_short() {
// derived from `serialize_deserialize_signing_key_json` test
let encoded_signing_key_too_long = "[62,70,27,163]";
let de_err = serde_json::from_str::<SigningKey>(&encoded_signing_key_too_long)
let de_err = serde_json::from_str::<SigningKey>(encoded_signing_key_too_long)
.unwrap_err()
.to_string();
assert!(

View File

@ -22,7 +22,7 @@ const VERIFY_ALLOWED_EDGECASES: &[Flag] = &[
const VERIFY_STRICT_ALLOWED_EDGECASES: &[Flag] =
&[Flag::LowOrderComponentA, Flag::LowOrderComponentR];
/// Each variant describes a specfiic edge case that can occur in an Ed25519 signature. Refer to
/// Each variant describes a specific edge case that can occur in an Ed25519 signature. Refer to
/// the test vector [README][] for more info.
///
/// [README]: https://github.com/C2SP/CCTV/blob/5ea85644bd035c555900a2f707f7e4c31ea65ced/ed25519vectors/README.md
@ -89,7 +89,7 @@ impl From<IntermediateTestVector> for TestVector {
let msg = tv.msg.as_bytes().to_vec();
// Unwrap the Option<Set<Flag>>
let flags = tv.flags.unwrap_or_else(Default::default);
let flags = tv.flags.unwrap_or_default();
Self {
number,

View File

@ -4,63 +4,77 @@ use curve25519_dalek::scalar::{clamp_integer, Scalar};
use ed25519_dalek::SigningKey;
use hex_literal::hex;
use sha2::{Digest, Sha512};
/// Helper function to return the bytes corresponding to the input bytes after being clamped and
/// reduced mod 2^255 - 19
fn clamp_and_reduce(bytes: &[u8]) -> [u8; 32] {
assert_eq!(bytes.len(), 32);
Scalar::from_bytes_mod_order(clamp_integer(bytes.try_into().unwrap())).to_bytes()
}
use x25519_dalek::{PublicKey as XPublicKey, StaticSecret as XStaticSecret};
/// Tests that X25519 Diffie-Hellman works when using keys converted from Ed25519.
// TODO: generate test vectors using another implementation of Ed25519->X25519
#[test]
fn ed25519_to_x25519_dh() {
// Keys from RFC8032 test vectors (from section 7.1)
let ed25519_secret_key_a =
hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60");
let ed25519_secret_key_b =
hex!("4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb");
let ed_secret_key_a = hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60");
let ed_secret_key_b = hex!("4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb");
let ed25519_signing_key_a = SigningKey::from_bytes(&ed25519_secret_key_a);
let ed25519_signing_key_b = SigningKey::from_bytes(&ed25519_secret_key_b);
let ed_signing_key_a = SigningKey::from_bytes(&ed_secret_key_a);
let ed_signing_key_b = SigningKey::from_bytes(&ed_secret_key_b);
let scalar_a = ed25519_signing_key_a.to_scalar();
let scalar_b = ed25519_signing_key_b.to_scalar();
// Create an x25519 static secret from the ed25519 signing key
let scalar_bytes_a = ed_signing_key_a.to_scalar_bytes();
let scalar_bytes_b = ed_signing_key_b.to_scalar_bytes();
let x_static_secret_a = XStaticSecret::from(scalar_bytes_a);
let x_static_secret_b = XStaticSecret::from(scalar_bytes_b);
// Compute the secret scalars too
let scalar_a = ed_signing_key_a.to_scalar();
let scalar_b = ed_signing_key_b.to_scalar();
// Compare the scalar bytes to the first 32 bytes of SHA-512(secret_key). We have to clamp and
// reduce the SHA-512 output because that's what the spec does before using the scalars for
// anything.
assert_eq!(scalar_bytes_a, &Sha512::digest(ed_secret_key_a)[..32]);
assert_eq!(scalar_bytes_b, &Sha512::digest(ed_secret_key_b)[..32]);
// Compare the scalar with the clamped and reduced scalar bytes
assert_eq!(
scalar_a.to_bytes(),
clamp_and_reduce(&Sha512::digest(ed25519_secret_key_a)[..32]),
scalar_a,
Scalar::from_bytes_mod_order(clamp_integer(scalar_bytes_a))
);
assert_eq!(
scalar_b.to_bytes(),
clamp_and_reduce(&Sha512::digest(ed25519_secret_key_b)[..32]),
scalar_b,
Scalar::from_bytes_mod_order(clamp_integer(scalar_bytes_b))
);
let x25519_public_key_a = ed25519_signing_key_a.verifying_key().to_montgomery();
let x25519_public_key_b = ed25519_signing_key_b.verifying_key().to_montgomery();
let x_public_key_a = XPublicKey::from(&x_static_secret_a);
let x_public_key_b = XPublicKey::from(&x_static_secret_b);
assert_eq!(
x25519_public_key_a.to_bytes(),
x_public_key_a.to_bytes(),
hex!("d85e07ec22b0ad881537c2f44d662d1a143cf830c57aca4305d85c7a90f6b62e")
);
assert_eq!(
x25519_public_key_b.to_bytes(),
x_public_key_b.to_bytes(),
hex!("25c704c594b88afc00a76b69d1ed2b984d7e22550f3ed0802d04fbcd07d38d47")
);
// Test the claim made in the comments of SigningKey::to_scalar_bytes, i.e., that the resulting
// scalar is a valid private key for the x25519 pubkey represented by
// `sk.verifying_key().to_montgomery()`
assert_eq!(
ed_signing_key_a.verifying_key().to_montgomery().as_bytes(),
x_public_key_a.as_bytes()
);
assert_eq!(
ed_signing_key_b.verifying_key().to_montgomery().as_bytes(),
x_public_key_b.as_bytes()
);
// Check that Diffie-Hellman works
let expected_shared_secret =
hex!("5166f24a6918368e2af831a4affadd97af0ac326bdf143596c045967cc00230e");
assert_eq!(
(x25519_public_key_a * scalar_b).to_bytes(),
expected_shared_secret
x_static_secret_a.diffie_hellman(&x_public_key_b).to_bytes(),
expected_shared_secret,
);
assert_eq!(
(x25519_public_key_b * scalar_a).to_bytes(),
expected_shared_secret
x_static_secret_b.diffie_hellman(&x_public_key_a).to_bytes(),
expected_shared_secret,
);
}

View File

@ -6,6 +6,10 @@ Entries are listed in reverse chronological order.
* Note: All `x255919-dalek` 2.x releases are in sync with the underlying `curve25519-dalek` 4.x releases.
## 2.0.1
* Fix nightly SIMD build
## 2.0.0-rc.3
* `StaticSecret` serialization and `to_bytes()` no longer returns clamped integers. Clamping is still always done during scalar-point multiplication.

View File

@ -6,7 +6,7 @@ edition = "2021"
# - 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"
version = "2.0.1"
authors = [
"Isis Lovecruft <isis@patternsinthevoid.net>",
"DebugSteven <debugsteven@gmail.com>",
@ -14,8 +14,8 @@ authors = [
]
readme = "README.md"
license = "BSD-3-Clause"
repository = "https://github.com/dalek-cryptography/x25519-dalek"
homepage = "https://dalek.rs/"
repository = "https://github.com/dalek-cryptography/curve25519-dalek/tree/main/x25519-dalek"
homepage = "https://github.com/dalek-cryptography/curve25519-dalek"
documentation = "https://docs.rs/x25519-dalek"
categories = ["cryptography", "no-std"]
keywords = ["cryptography", "curve25519", "key-exchange", "x25519", "diffie-hellman"]
@ -45,7 +45,7 @@ zeroize = { version = "1", default-features = false, optional = true, features =
[dev-dependencies]
bincode = "1"
criterion = "0.4.0"
criterion = "0.5"
rand_core = { version = "0.6", default-features = false, features = ["getrandom"] }
[[bench]]

View File

@ -53,9 +53,9 @@ shared secret with Bob by doing:
```rust
# use rand_core::OsRng;
# use x25519_dalek::{EphemeralSecret, PublicKey};
# let alice_secret = EphemeralSecret::new(OsRng);
# let alice_secret = EphemeralSecret::random_from_rng(OsRng);
# let alice_public = PublicKey::from(&alice_secret);
# let bob_secret = EphemeralSecret::new(OsRng);
# let bob_secret = EphemeralSecret::random_from_rng(OsRng);
# let bob_public = PublicKey::from(&bob_secret);
let alice_shared_secret = alice_secret.diffie_hellman(&bob_public);
```
@ -65,9 +65,9 @@ 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_secret = EphemeralSecret::random_from_rng(OsRng);
# let alice_public = PublicKey::from(&alice_secret);
# let bob_secret = EphemeralSecret::new(OsRng);
# let bob_secret = EphemeralSecret::random_from_rng(OsRng);
# let bob_public = PublicKey::from(&bob_secret);
let bob_shared_secret = bob_secret.diffie_hellman(&alice_public);
```
@ -77,9 +77,9 @@ These secrets are the same:
```rust
# use rand_core::OsRng;
# use x25519_dalek::{EphemeralSecret, PublicKey};
# let alice_secret = EphemeralSecret::new(OsRng);
# let alice_secret = EphemeralSecret::random_from_rng(OsRng);
# let alice_public = PublicKey::from(&alice_secret);
# let bob_secret = EphemeralSecret::new(OsRng);
# let bob_secret = EphemeralSecret::random_from_rng(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);
@ -100,7 +100,7 @@ To install, add the following to your project's `Cargo.toml`:
```toml
[dependencies]
x25519-dalek = "2.0.0-rc.3"
x25519-dalek = "2"
```
# MSRV

View File

@ -15,7 +15,6 @@
// 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)]

View File

@ -101,7 +101,7 @@ impl EphemeralSecret {
/// Generate a new [`EphemeralSecret`].
#[cfg(feature = "getrandom")]
pub fn random() -> Self {
Self::random_from_rng(&mut rand_core::OsRng)
Self::random_from_rng(rand_core::OsRng)
}
}
@ -164,7 +164,7 @@ impl ReusableSecret {
/// Generate a new [`ReusableSecret`].
#[cfg(feature = "getrandom")]
pub fn random() -> Self {
Self::random_from_rng(&mut rand_core::OsRng)
Self::random_from_rng(rand_core::OsRng)
}
}
@ -225,7 +225,7 @@ impl StaticSecret {
/// Generate a new [`StaticSecret`].
#[cfg(feature = "getrandom")]
pub fn random() -> Self {
Self::random_from_rng(&mut rand_core::OsRng)
Self::random_from_rng(rand_core::OsRng)
}
/// Extract this key's bytes for serialization.