Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 38 additions & 40 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest, windows-latest, macOS-latest ]
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- uses: actions/checkout@v4
- name: Run tests
Expand All @@ -24,7 +24,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest, windows-latest, macOS-latest ]
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
Expand All @@ -37,43 +37,42 @@ jobs:
- name: Verify working directory is clean (excluding lockfile)
run: git diff --exit-code ':!Cargo.lock'

# The scripts embedded in this job are having trouble parsing our branch names
# build-nodefault:
# name: Build target ${{ matrix.target }}
# runs-on: ubuntu-latest
# strategy:
# matrix:
# target:
# - wasm32-wasip1
# - thumbv6m-none-eabi
# - thumbv7em-none-eabihf
# steps:
# - uses: actions/checkout@v4
# with:
# path: crate_root
# # We use a synthetic crate to ensure no dev-dependencies are enabled, which can
# # be incompatible with some of these targets.
# - name: Create synthetic crate for testing
# run: cargo init --edition 2021 --lib ci-build
# - name: Copy Rust version into synthetic crate
# run: cp crate_root/rust-toolchain.toml ci-build/
# - name: Copy patch directives into synthetic crate
# run: |
# echo "[patch.crates-io]" >> ./ci-build/Cargo.toml
# cat ./crate_root/Cargo.toml | sed "0,/.\+\(patch.crates.\+\)/d" >> ./ci-build/Cargo.toml
# - name: Add no_std pragma to lib.rs
# run: |
# echo "#![no_std]" > ./ci-build/src/lib.rs
# - name: Add group as a dependency of the synthetic crate
# working-directory: ./ci-build
# # run: cargo add --no-default-features --path ../crate_root
# run: sed -i 's;\[dependencies\];\[dependencies\]\nrustcrypto-group = { path = "../crate_root", default-features = false };g' ./Cargo.toml
# - name: Add target
# working-directory: ./ci-build
# run: rustup target add ${{ matrix.target }}
# - name: Build for target
# working-directory: ./ci-build
# run: cargo build --verbose --target ${{ matrix.target }}
build-nodefault:
name: Build target ${{ matrix.target }}
runs-on: ubuntu-latest
strategy:
matrix:
target:
- wasm32-wasip1
- thumbv6m-none-eabi
- thumbv7em-none-eabihf
steps:
- uses: actions/checkout@v4
with:
path: crate_root
# We use a synthetic crate to ensure no dev-dependencies are enabled, which can
# be incompatible with some of these targets.
- name: Create synthetic crate for testing
run: cargo init --edition 2021 --lib ci-build
- name: Copy Rust version into synthetic crate
run: cp crate_root/rust-toolchain.toml ci-build/
- name: Copy patch directives into synthetic crate
run: |
echo "[patch.crates-io]" >> ./ci-build/Cargo.toml
cat ./crate_root/Cargo.toml | sed "0,/.\+\(patch.crates.\+\)/d" >> ./ci-build/Cargo.toml
- name: Add no_std pragma to lib.rs
run: |
echo "#![no_std]" > ./ci-build/src/lib.rs
- name: Add group as a dependency of the synthetic crate
working-directory: ./ci-build
# run: cargo add --no-default-features --path ../crate_root
run: sed -i 's;\[dependencies\];\[dependencies\]\ngroup = { path = "../crate_root", default-features = false };g' ./Cargo.toml
- name: Add target
working-directory: ./ci-build
run: rustup target add ${{ matrix.target }}
- name: Build for target
working-directory: ./ci-build
run: cargo build --verbose --target ${{ matrix.target }}

doc-links:
name: Intra-doc links
Expand All @@ -82,7 +81,6 @@ jobs:
- uses: actions/checkout@v4
- run: cargo fetch
# Requires #![deny(rustdoc::broken_intra_doc_links)] in crates.
- run: sudo apt-get -y install libfontconfig1-dev
- name: Check intra-doc links
run: cargo doc --all-features --document-private-items

Expand Down
36 changes: 30 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,38 @@ and this library adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.14.0] - 2026-06-01
### Added
- `group::CurveAffine`, an affine-representation trait that the curve-specific
affine traits are now built on top of.
- `group::Group::mul_by_generator`, with a default implementation. Implementors
can override it to take advantage of precomputed tables.
- `group::Group::try_random<R: TryRng + ?Sized>(rng: &mut R) -> Result<Self, R::Error>`,
a new trait method that must be implemented by downstreams. It samples a
non-identity group element using a fallible RNG and propagates the RNG's error.

### Changed
- MSRV is now 1.63.0.
- Migrated to `ff 0.14`, `rand_core 0.9`.
- MSRV is now 1.85.0.
- Bumped dependencies to `ff 0.14`, `rand_core 0.10`.
- `group::Group::random(rng: impl RngCore) -> Self` has been changed to
`Group::random<R: RngCore + ?Sized>(rng: &mut R) -> Self`, to enable passing a
trait object as the RNG.
- `group::Group::try_from_rng` is a new trait method that must be implemented by
downstreams. `Group::random` now has a default implementation that calls it.
`Group::random<R: Rng + ?Sized>(rng: &mut R) -> Self`, to enable passing a
trait object as the RNG. It now has a default implementation in terms of
`Group::try_random`.
- The curve-related traits have been refactored around the new `CurveAffine`
trait:
- `group::Curve::AffineRepr` has been renamed to `Curve::Affine`.
- All of the trait methods and associated types on the following traits have
been removed (use `group::Curve::Affine` or the `group::CurveAffine` trait
instead; trait implementors must implement `group::CurveAffine` instead
using the same logic):
- `group::cofactor::CofactorCurve`
- `group::cofactor::CofactorCurveAffine`
- `group::prime::PrimeCurve`
- `group::prime::PrimeCurveAffine`
- `group::cofactor::CofactorCurveAffine` and `group::prime::PrimeCurveAffine`
now have blanket implementations for all types `C: group::CurveAffine` where
`C::Curve` implements `CofactorCurve` or `PrimeCurve` respectively.

## [0.13.0] - 2022-12-06
### Changed
Expand Down
52 changes: 44 additions & 8 deletions src/wnaf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,23 +150,45 @@ pub(crate) fn wnaf_form<S: AsRef<[u8]>>(wnaf: &mut Vec<i64>, c: S, window: usize
///
/// This function must be provided a `table` and `wnaf` that were constructed with
/// the same window size; otherwise, it may panic or produce invalid results.
#[inline]
pub(crate) fn wnaf_exp<G: Group>(table: &[G], wnaf: &[i64]) -> G {
let mut result = G::identity();
wnaf_multi_exp(&[table], &[wnaf])
}

/// Performs w-NAF multi-exponentiation using the interleaved window method, also known as
/// Straus' method.
///
/// The key insight is that when computing this sum by means of additions and doublings, the
/// doublings can be shared by performing the additions within an inner loop.
///
/// This function must be provided with `tables` and `wnafs` that were constructed with
/// the same window size; otherwise, it may panic or produce invalid results.
pub(crate) fn wnaf_multi_exp<G: Group, T: AsRef<[G]>, W: AsRef<[i64]>>(
tables: &[T],
wnafs: &[W],
) -> G {
debug_assert_eq!(tables.len(), wnafs.len());
let window_size = wnafs.iter().map(|w| w.as_ref().len()).max().unwrap_or(0);

let mut result = G::identity();
let mut found_one = false;

for n in wnaf.iter().rev() {
for i in (0..window_size).rev() {
// Only double once per iteration of the loop
if found_one {
result = result.double();
}

if *n != 0 {
found_one = true;
for (table, wnaf) in tables.iter().zip(wnafs.iter()) {
let n = wnaf.as_ref().get(i).copied().unwrap_or(0);
if n != 0 {
found_one = true;

if *n > 0 {
result += &table[(n / 2) as usize];
} else {
result -= &table[((-n) / 2) as usize];
if n > 0 {
result += table.as_ref()[(n / 2) as usize];
} else {
result -= table.as_ref()[((-n) / 2) as usize];
}
}
}
}
Expand Down Expand Up @@ -499,6 +521,20 @@ impl<G: Group, const WINDOW_SIZE: usize> WnafBase<G, WINDOW_SIZE> {

WnafBase { table }
}

/// Perform a multiscalar multiplication.
///
/// Computes a sum-of-products `aA + bB + ...` in variable time with w-NAF multi-exponentiation
/// using the interleaved window method, also known as Straus' method.
pub fn multiscalar_mul<I, J>(scalars: I, bases: J) -> G
where
I: IntoIterator<Item = WnafScalar<G::Scalar, WINDOW_SIZE>>,
J: IntoIterator<Item = Self>,
{
let wnafs = scalars.into_iter().map(|s| s.wnaf).collect::<Vec<_>>();
let tables = bases.into_iter().map(|b| b.table).collect::<Vec<_>>();
wnaf_multi_exp(tables.as_slice(), wnafs.as_slice())
}
}

impl<G: Group, const WINDOW_SIZE: usize> Mul<&WnafScalar<G::Scalar, WINDOW_SIZE>>
Expand Down
Loading