[ed25519] improve docs (#47)

* improve docs

* more docs
This commit is contained in:
zz-sol 2026-06-11 22:10:13 -04:00 committed by GitHub
parent c41adab68f
commit eed50b4d8d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 119 additions and 85 deletions

View file

@ -5,8 +5,8 @@ accelerated Ed25519 signature verification via the **HEEA** (Half-Extended Eucli
method and a reduced set of well-tested backends.
> Original library READMEs: [README_dalek.md](README_dalek.md) (workspace) ·
> [curve25519/README_dalek.md](curve25519/README_dalek.md) ·
> [ed25519-heea/README_zebra.md](ed25519-heea/README_zebra.md)
> [solana-ed25519/README_dalek.md](solana-ed25519/README_dalek.md) ·
> [solana-ed25519/README_zebra.md](solana-ed25519/README_zebra.md)
---
@ -14,10 +14,9 @@ method and a reduced set of well-tested backends.
| Crate | Description |
|---|---|
| [`curve25519`](./curve25519) | Fork of `curve25519-dalek`. Core elliptic-curve arithmetic over Curve25519, Edwards, Ristretto, and Short-Weierstrass forms, with HEEA scalar decomposition and a narrowed backend set (removed `u32` and constraint device supports). |
| [`ed25519-heea`](./ed25519-heea) | Fork of `ed25519-zebra`. ZIP-215-compliant Ed25519 with an added `verify_heea` fast-path that uses HEEA half-size scalars. |
| [`solana-ed25519`](./solana-ed25519) | Fork of `curve25519-dalek` with ZIP-215-compliant Ed25519 from `ed25519-zebra`, HEEA-accelerated `verify` / `verify_zebra`, and a narrowed backend set (removed `u32` and constraint device supports). |
| [`curve25519-cuda`](./curve25519-cuda) | GPU-accelerated multi-scalar multiplication (MSM) via CUDA/SPPARK. Falls back to CPU when CUDA is unavailable. |
| [`curve25519-derive`](./curve25519-derive) | Helper proc-macro crate (`#[unsafe_target_feature]`) inherited from upstream; required to write clean SIMD code. Identical to the one in dalek 0.5.0 |
| [`curve25519-derive`](../curve25519-derive) | Helper proc-macro crate (`#[unsafe_target_feature]`) inherited from upstream; required to write clean SIMD code. Identical to the one in dalek 0.5.0 |
---
@ -30,7 +29,8 @@ HEEA (from the TCHES 2025 paper _"Accelerating EdDSA Signature Verification with
Size Halving"_) transforms this into a 4-point MSM with ~128-bit scalars:
```
τs_lo · B + τs_hi · (2¹²⁸·B) = τ·R + ρ·A
flip_h = false: τs_lo · B + τs_hi · (2¹²⁸·B) = τ·R + ρ·A
flip_h = true: τs_lo · B + τs_hi · (2¹²⁸·B) = τ·R - ρ·A
```
where `ρ` and `τ` are half-size (~127-bit) values derived from `h` via a half-extended
@ -39,10 +39,10 @@ two of the bases (`B` and `2¹²⁸B`) use precomputed tables. In practice this
**~15% faster** verification compared to the standard double-scalar-multiplication path.
The algorithm is implemented in:
- [`curve25519/src/scalar/heea.rs`](curve25519/src/scalar/heea.rs) `curve25519_heea_vartime`
- [`curve25519/src/traits.rs`](curve25519/src/traits.rs) `HEEADecomposition` trait
- [`curve25519/src/backend/serial/scalar_mul/vartime_triple_base.rs`](curve25519/src/backend/serial/scalar_mul/vartime_triple_base.rs) optimised 128+128+256 MSM
- [`ed25519-heea/src/verification_key.rs`](ed25519-heea/src/verification_key.rs) `VerificationKey::verify_heea`
- [`solana-ed25519/src/scalar/heea.rs`](solana-ed25519/src/scalar/heea.rs) `curve25519_heea_vartime`
- [`solana-ed25519/src/traits.rs`](solana-ed25519/src/traits.rs) `HEEADecomposition` trait
- [`solana-ed25519/src/backend/serial/scalar_mul/vartime_triple_base.rs`](solana-ed25519/src/backend/serial/scalar_mul/vartime_triple_base.rs) optimised 128+128+256 MSM
- [`solana-ed25519/src/ed_sigs/verification_key.rs`](solana-ed25519/src/ed_sigs/verification_key.rs) `VerificationKey::verify` / `VerificationKey::verify_zebra`
### Reduced Backend Set
@ -65,17 +65,13 @@ maintenance surface. If you need them, use upstream `curve25519-dalek` directly.
Add the relevant crate to `Cargo.toml`:
```toml
# Core curve arithmetic with HEEA
curve25519-sol = { git = "https://github.com/zz-sol/ed25519-sol" }
# Ed25519 signatures with fast HEEA verification
ed25519-heea = { git = "https://github.com/zz-sol/ed25519-sol" }
curve25519 = { package = "solana-ed25519", git = "https://github.com/anza-xyz/cryptography" }
```
### Standard Ed25519 verification
```rust
use ed25519_heea::{SigningKey, VerificationKey};
use curve25519::ed_sigs::{SigningKey, VerificationKey};
use rand::thread_rng;
let msg = b"hello world";
@ -87,11 +83,11 @@ let vk = VerificationKey::from(&sk);
vk.verify(&sig, msg).expect("valid signature");
```
### HEEA-accelerated verification
### Explicit HEEA-accelerated verification
```rust
// Fast path: ~15% faster via half-size scalars (same result)
vk.verify_heea(&sig, msg).expect("valid signature");
// Same ZIP-215 result as verify(), using the HEEA path explicitly.
vk.verify_zebra(&sig, msg).expect("valid signature");
```
---
@ -106,8 +102,8 @@ cargo build --release
RUSTFLAGS='-C target-feature=+avx2' cargo build --release
# Run benchmarks
cargo bench --features "rand_core" -p curve25519
cargo bench -p ed25519-heea
cargo bench --features "rand_core" -p solana-ed25519
cargo bench -p curve25519-cuda
```
---

View file

@ -24,7 +24,8 @@ A new `HEEADecomposition` trait and implementation have been added in:
Given a 256-bit hash scalar `h`, `heea_decompose` returns `(ρ, τ, flip_h)` such that:
```text
ρ ≡ ±τ·h (mod ) // ρ and τ are both ≤ 128 bits
flip_h = false: ρ ≡ τ·h (mod )
flip_h = true: ρ ≡ -τ·h (mod )
```
This allows verification of `sB = R + hA` to be rewritten as a 4-point MSM over ~128-bit
@ -58,9 +59,9 @@ verification.
### `verify_zebra`: fast-path signature verification
A new method `VerificationKey::verify_zebra` sits alongside the existing `verify`.
Both accept the same arguments and produce identical results — `verify_zebra` is a
**drop-in accelerated replacement** for `verify`.
`VerificationKey::verify_zebra` is the HEEA implementation used by the default
`VerificationKey::verify` method. Both accept the same arguments and produce identical
ZIP-215 results.
The HEEA method (TCHES 2025) transforms the standard 2-point MSM:
@ -71,10 +72,12 @@ The HEEA method (TCHES 2025) transforms the standard 2-point MSM:
into a 4-point MSM over half-size (~128-bit) scalars:
```text
τs_lo·B + τs_hi·(2¹²⁸·B) = τ·R + ρ·A (HEEA)
flip_h = false: τs_lo·B + τs_hi·(2¹²⁸·B) = τ·R + ρ·A
flip_h = true: τs_lo·B + τs_hi·(2¹²⁸·B) = τ·R - ρ·A
```
where `ρ ≡ ±τ·h (mod )` and `τs = τs_hi·2¹²⁸ + τs_lo`. All four scalars are ≤128 bits
where `ρ ≡ τ·h (mod )` when `flip_h` is false, `ρ ≡ -τ·h (mod )` when
`flip_h` is true, and `τs = τs_hi·2¹²⁸ + τs_lo`. All four scalars are ≤128 bits
and the two basepoints (`B` and `2¹²⁸B`) use precomputed lookup tables, giving approximately
**~15% faster** verification compared to the standard path.
@ -137,7 +140,8 @@ let h = Scalar::from_hash(Sha512::new().chain_update(b"some message"));
// Decompose into two ~128-bit scalars
let (rho, tau, flip_h) = h.heea_decompose();
// rho ≡ ±tau·h (mod )
// flip_h == false: rho ≡ tau·h (mod )
// flip_h == true: rho ≡ -tau·h (mod )
```
---
@ -146,10 +150,10 @@ let (rho, tau, flip_h) = h.heea_decompose();
| Feature | Default? | Description |
|---|:---:|---|
| `alloc` | ✓ | Multiscalar multiplication, batch inversion, batch compress, batch Ed25519 verification. |
| `alloc` | ✓ | Multiscalar multiplication, batch inversion, batch compress, and the Ed25519 batch module. |
| `zeroize` | ✓ | `Zeroize` for all scalar and point types. |
| `precomputed-tables` | ✓ | Precomputed basepoint tables (~400 KB, ~4× faster basepoint mul). |
| `rand_core` | ✓ | `Scalar::random`, `RistrettoPoint::random`, `SigningKey::new`. |
| `rand_core` | ✓ | `Scalar::random`, `RistrettoPoint::random`, `SigningKey::new`, and randomized batch verification. |
| `digest` | ✓ | Hash-to-curve, `Scalar::from_hash`, and Ed25519 hashing. |
| `std` | | Enables `std::error::Error` impl on `ed_sigs::Error`. |
| `serde` | | Serialization for all point, scalar, and key types. |

View file

@ -1,3 +1,7 @@
> This is an upstream `ed25519-zebra` README snapshot retained for provenance.
> It describes the upstream crate, not this fork's current `curve25519::ed_sigs`
> API. See [README.md](README.md) for current `solana-ed25519` usage.
[![Build status](https://github.com/ZcashFoundation/ed25519-zebra/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/ZcashFoundation/ed25519-zebra/actions/workflows/main.yml?query=branch%3Amain)
[![dependency status](https://deps.rs/repo/github/ZcashFoundation/ed25519-zebra/status.svg)](https://deps.rs/repo/github/ZcashFoundation/ed25519-zebra)

View file

@ -40,8 +40,13 @@ use crate::window::NafLookupTable5;
/// # Implementation
///
/// - For \\(A_1\\) and \\(A_2\\): NAF with window width 5 (8 precomputed points each)
/// - For \\(B\\): NAF with window width 8 when precomputed tables available (64 points)
/// - For \\(B'\\): NAF with window width 5 (could be optimized with precomputed table)
/// - For \\(B\\): NAF with window width 5. When precomputed tables are enabled,
/// these width-5 digits are selected from the larger width-8 basepoint table.
/// - For \\(B'\\): NAF with window width 5.
///
/// The vector backend uses width 8 for \\(b_{lo}\\) when precomputed tables are
/// enabled. This serial backend keeps width 5 for \\(b_{lo}\\) as a
/// backend-specific performance tradeoff.
///
/// The algorithm shares doublings across all four scalar multiplications, processing
/// only 128 bits instead of 256, providing approximately 2x speedup over the naive approach.
@ -70,7 +75,11 @@ pub fn mul_128_128_256(
let b_lo = Scalar::from_canonical_bytes(b_lo_bytes).unwrap();
let b_hi = Scalar::from_canonical_bytes(b_hi_bytes).unwrap();
// Compute NAF representations (all scalars are now ~128 bits)
// Compute NAF representations (all scalars are now ~128 bits).
//
// The serial backend keeps b_lo at width 5 even when table_B is the larger
// precomputed width-8 basepoint table. The vector backend uses width 8 in
// that configuration.
let a1_naf = a1.non_adjacent_form(5);
let a2_naf = a2.non_adjacent_form(5);
let b_lo_naf = b_lo.non_adjacent_form(5);

View file

@ -54,6 +54,10 @@ pub mod spec {
/// - For \\(B\\): NAF with window width 8 when precomputed tables available (64 points), otherwise width 5
/// - For \\(B'\\): NAF with window width 5
///
/// The serial backend keeps \\(b_{lo}\\) at width 5 even when precomputed
/// tables are enabled. This vector backend uses width 8 in that
/// configuration as a backend-specific performance tradeoff.
///
/// The algorithm shares doublings across all four scalar multiplications, processing
/// only 128 bits instead of 256, providing approximately 2x speedup over the naive approach.
///
@ -88,6 +92,8 @@ pub mod spec {
let a1_naf = a1.non_adjacent_form(5);
let a2_naf = a2.non_adjacent_form(5);
// With precomputed tables, the vector backend uses the larger width-8
// basepoint table for b_lo. The serial backend keeps b_lo at width 5.
#[cfg(feature = "precomputed-tables")]
let b_lo_naf = b_lo.non_adjacent_form(8);
#[cfg(not(feature = "precomputed-tables"))]

View file

@ -104,9 +104,9 @@ impl Item {
///
/// This is useful (in combination with `Item::clone`) for implementing fallback
/// logic when batch verification fails. In contrast to
/// [`VerificationKey::verify`](crate::VerificationKey::verify), which requires
/// borrowing the message data, the `Item` type is unlinked from the lifetime of
/// the message.
/// [`VerificationKey::verify`](crate::ed_sigs::VerificationKey::verify),
/// which requires borrowing the message data, the `Item` type is unlinked
/// from the lifetime of the message.
pub fn verify_single(self) -> Result<(), Error> {
VerificationKey::try_from(self.vk_bytes)
.and_then(|vk| vk.verify_zebra_prehashed(&self.sig, self.k))

View file

@ -16,7 +16,7 @@ const PKCS8_V1_PEM: &str = include_str!("../../../examples/pkcs8-v1.pem");
#[cfg(feature = "pkcs8")]
const PKCS8_V2_DER: &[u8] = include_bytes!("../../../examples/pkcs8-v2.der");
/// Ed25519 PKCS#8 v1 private key encoded as PEM.
/// Ed25519 PKCS#8 v2 private key + public key encoded as PEM.
#[cfg(feature = "pem")]
const PKCS8_V2_PEM: &str = include_str!("../../../examples/pkcs8-v2.pem");

View file

@ -19,7 +19,7 @@ const PKCS8_V1_PEM: &str = include_str!("../../../examples/pkcs8-v1.pem");
#[cfg(feature = "pkcs8")]
const PKCS8_V2_DER: &[u8] = include_bytes!("../../../examples/pkcs8-v2.der");
/// Ed25519 PKCS#8 v1 private key encoded as PEM.
/// Ed25519 PKCS#8 v2 private key + public key encoded as PEM.
#[cfg(feature = "pem")]
const PKCS8_V2_PEM: &str = include_str!("../../../examples/pkcs8-v2.pem");

View file

@ -8,7 +8,7 @@ use core::convert::TryFrom;
use ed25519::Signature;
#[test]
fn test_verify_heea_invalid_signature() {
fn test_verify_zebra_invalid_signature() {
let signing_key = SigningKey::from([1u8; 32]);
let verification_key = VerificationKey::from(&signing_key);
@ -37,7 +37,7 @@ fn test_verify_heea_invalid_signature() {
}
#[test]
fn test_verify_heea_multiple_signatures() {
fn test_verify_zebra_multiple_signatures() {
for i in 0..100 {
let mut seed = [0u8; 32];
seed[0] = i;

View file

@ -1,16 +1,16 @@
// -*- mode: rust; -*-
//
// This file is part of ed25519-heea, a fork of ed25519-zebra.
// This file is part of solana-ed25519's ed_sigs module, forked from ed25519-zebra.
// Original ed25519-zebra code: Copyright (c) Zcash Foundation contributors
// Modifications for HEEA: Copyright (c) 2025 curve25519-sol contributors
// See LICENSE-APACHE and LICENSE-MIT for licensing information.
//
// Modifications from ed25519-zebra:
// - Added `verify_heea`, an accelerated verification path using the HEEA
// - Added `verify_zebra`, an accelerated verification path using the HEEA
// scalar decomposition from curve25519-sol's `HEEADecomposition` trait.
// See "Accelerating EdDSA Signature Verification with Faster Scalar Size
// Halving" (TCHES 2025) for the algorithm.
// - `verify` and all ZIP-215 consensus logic are unchanged from ed25519-zebra.
// - `verify` dispatches to `verify_zebra`, preserving ZIP-215 semantics.
use crate::{
edwards::{CompressedEdwardsY, EdwardsPoint},
@ -105,12 +105,12 @@ const LEGACY_EXCLUDED_R_ENCODINGS: [[u8; 32]; 11] = [
],
];
/// A refinement type for `[u8; 32]` indicating that the bytes represent an
/// encoding of an Ed25519 verification key.
/// A container for the 32-byte encoded form of an Ed25519 verification key.
///
/// This is useful for representing an encoded verification key, while the
/// [`VerificationKey`] type in this library caches other decoded state used in
/// signature verification.
/// This type only checks or carries the byte length. It does not prove that the
/// bytes decompress to a valid Ed25519 verification key. Convert it to
/// [`VerificationKey`] to validate the encoded point and cache decoded state
/// used in signature verification.
///
/// A `VerificationKeyBytes` can be used to verify a single signature using the
/// following idiom:
@ -204,7 +204,7 @@ fn verification_key_bytes_from_spki(
///
/// This type holds decompressed state used in signature verification; if the
/// verification key may not be used immediately, it is probably better to use
/// [`VerificationKeyBytes`], which is a refinement type for `[u8; 32]`.
/// [`VerificationKeyBytes`], which stores only the length-checked encoded bytes.
///
/// ## Zcash-specific consensus properties
///
@ -366,17 +366,17 @@ impl VerificationKey {
/// This implements the algorithm from "Accelerating EdDSA Signature Verification
/// with Faster Scalar Size Halving" (TCHES 2025).
///
/// The standard verification equation sB = R + hA is transformed to:
/// τsB = τR + ρA where ρ ≡ τh (mod )
/// The decomposition returns ρ and τ such that either ρ ≡ τh (mod ) or
/// ρ ≡ -τh (mod ). The standard verification equation sB = R + hA is
/// multiplied by τ and the sign of A is selected according to `flip_h`.
///
/// Both ρ and τ are approximately half the size of h.
///
/// We then decompose τs into two 128-bit scalars:
/// τs = τs_hi * 2^128 + τs_lo
///
/// The verification equation becomes:
/// τs_lo B + τs_hi (2^128 B) = τR + ρA
/// which can be done via 4-variable MSM with half-size scalars.
/// The resulting equation can be checked with a 4-variable MSM with
/// half-size scalars.
#[allow(non_snake_case)]
pub fn verify_zebra(&self, signature: &Signature, msg: &[u8]) -> Result<(), Error> {
self.verify_zebra_prehashed(signature, self.challenge_scalar(signature, msg))
@ -388,12 +388,9 @@ impl VerificationKey {
signature: &Signature,
h: Scalar,
) -> Result<(), Error> {
// Generate half-size scalars ρ and τ such that ρ ≡ τh (mod )
// in order to have rho and tau approximately half the size of h
// it is possible that we compute ρ ≡ -τh (mod )
// this is indicated by `flip_h` flag being true,
// in which case we will need to negate A later
// let (rho, tau, flip_h) = crate::heea::generate_half_size_scalars(&h);
// Generate half-size scalars ρ and τ. If flip_h is false, then
// ρ ≡ τh (mod ). If flip_h is true, then ρ ≡ -τh (mod ), so the
// sign of A is flipped below.
let (rho, tau, flip_h) = h.heea_decompose();
// Extract s from the signature
@ -405,11 +402,11 @@ impl VerificationKey {
.decompress()
.ok_or(Error::InvalidSignature)?;
// Standard verification checks: sB = R + hA
// Transformed verification: -τsB + τR + ρA == 0
// Standard verification checks: sB = R + hA.
//
// We verify:
// [8] τs B + [8] τ (-R) + [8] ρ (-A) == 0
// [8] τs B + [8] τ (-R) + [8] ρ A_term == 0
// where A_term is -A when ρ ≡ τh and A when ρ ≡ -τh.
// Compute τs
let ts = tau * s;
@ -424,12 +421,18 @@ impl VerificationKey {
}
}
/// Verify a signature with exact `ed25519-dalek`-style byte-level behavior.
/// Verify a signature with dalek-style canonical-`R` byte comparison.
///
/// This recomputes the expected canonical `R` encoding and compares it to the
/// signature's `R` bytes, matching dalek's ordinary verification behavior.
/// signature's `R` bytes.
///
/// Note that exact dalek-compatible behavior is incompatible with the HEEA
/// This helper also preserves this crate's legacy compatibility filters: it
/// rejects an all-zero encoded public key and the known legacy-excluded `R`
/// encodings before running the canonical-`R` comparison. Because of those
/// extra checks, this is not a byte-for-byte clone of every `ed25519-dalek`
/// release.
///
/// Note that dalek-style canonical-`R` comparison is incompatible with the HEEA
/// transformed equation because the transformed check does not preserve the
/// original `R` encoding needed for the byte comparison.
#[allow(non_snake_case)]

View file

@ -537,12 +537,15 @@ impl Zeroize for Scalar {
}
impl HEEADecomposition for Scalar {
/// Generate half-size scalars (rho, tau) for a given hash value h
/// Generate approximately half-size non-negative scalars `(rho, tau)` for a
/// given challenge scalar `h`.
///
/// This function takes the hash value h from the signature verification equation
/// and produces two half-size scalars rho and tau such that rho = tau*h (mod ell).
/// The signed HEEA output satisfies `rho_i = tau_i * h (mod ell)`. This
/// method returns the absolute values of `rho_i` and `tau_i`, plus `flip_h`
/// to record whether exactly one of the signed values was negative.
///
/// And a flag indicating if rho is negative in its signed representation.
/// If `flip_h` is false, then `rho = tau * h (mod ell)`. If `flip_h` is
/// true, then `rho = -tau * h (mod ell)`.
fn heea_decompose(&self) -> (Scalar, Scalar, bool) {
// Convert h to I256
let v = self.into();

View file

@ -17,7 +17,10 @@
//! This module implements Algorithm 4 (hEEA_approx_q) from the paper, which generates
//! half-size scalars for faster EdDSA verification.
//!
//! For verification sB = R + hA, we find rho, tau such that rho = tau*h (mod ell)
//! For verification `sB = R + hA`, this module finds signed values `rho` and
//! `tau` such that `rho == tau * h (mod ell)`. The public
//! `HEEADecomposition` implementation converts them to non-negative scalars and
//! reports whether the sign of `h` must be flipped in the transformed equation.
use core::ops::Neg;
use crate::constants;
@ -34,9 +37,11 @@ pub(crate) struct I256 {
pub(crate) const HEEA_MAX_INDEX: usize = 129;
/// Implement curve25519_hEEA_vartime algorithm
/// Returns (rho, tau) such that rho ≡ tau * v (mod L)
/// where L is the Ed25519 group order (2^252 + 27742317777372353535851937790883648493)
/// Implement the curve25519 hEEA variable-time algorithm.
///
/// Returns signed `(rho, tau)` such that `rho == tau * v (mod L)`, where `L` is
/// the Ed25519 group order
/// `2^252 + 27742317777372353535851937790883648493`.
pub(crate) fn curve25519_heea_vartime(v: I256) -> (I256, I256) {
// Get L from the existing BASEPOINT_ORDER constant
let mut r0: I256 = (&constants::BASEPOINT_ORDER).into();
@ -427,7 +432,7 @@ mod tests {
#[test]
#[cfg(all(feature = "rand_core", feature = "digest"))]
fn test_generate_half_size_scalars() {
fn test_heea_decompose_half_size_scalars() {
use rand::thread_rng;
use sha2::{Digest, Sha512};
@ -453,10 +458,13 @@ mod tests {
// Now convert to Scalars and verify the equation
let (rho, tau, flip) = h.heea_decompose();
// Verify that rho = tau * h (mod ell)
// Verify the non-negative scalar relation reported by flip.
let computed_rho = tau * h;
let computed_rho = if flip { -computed_rho } else { computed_rho };
assert_eq!(rho, computed_rho, "rho should equal tau * h");
assert_eq!(
rho, computed_rho,
"rho should equal tau * h with flip applied"
);
// Check that they are non-zero
assert_ne!(rho, Scalar::ZERO, "rho should be non-zero");

View file

@ -422,18 +422,19 @@ pub trait VartimePrecomputedMultiscalarMul: Sized {
/// A trait for decomposing scalars for the HEEA method.
///
/// The HEEA method is a technique for accelerating scalar multiplication
/// on elliptic curves by decomposing a scalar into two smaller scalars.
/// The smaller scalars then can be multiplied with multi-scalar multiplication
/// which is in general faster than single point full multiplication.
/// The HEEA method is used by EdDSA verification to replace multiplication by
/// a full-width challenge scalar with multiplications by two shorter scalars.
/// For a challenge scalar `h`, the decomposition returns non-negative scalars
/// `rho` and `tau` that satisfy either `rho == tau * h (mod ell)` or
/// `rho == -tau * h (mod ell)`.
///
/// This trait provides a method for performing this decomposition.
pub trait HEEADecomposition {
/// Decomposes a scalar `k` into two half-size scalars `k1` and `k2`
/// such that `k = k1 + k2 * h (mod ell)`, where `h` is a fixed constant.
/// Decomposes a scalar `h` into two approximately half-size scalars.
///
/// Returns the tuple `(k1, k2, flip_h)`, where `flip_h` is a boolean
/// indicating whether the sign of `h` was flipped during the decomposition.
/// Returns `(rho, tau, flip_h)`. If `flip_h` is false, then
/// `rho == tau * h (mod ell)`. If `flip_h` is true, then
/// `rho == -tau * h (mod ell)`.
fn heea_decompose(&self) -> (Scalar, Scalar, bool);
}