Add fixed-array Scalar batch inversion (#789)

This commit is contained in:
daxpedda 2025-08-26 20:54:09 +02:00 committed by GitHub
parent 015707ab4e
commit 44433757a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 71 additions and 21 deletions

View file

@ -5,6 +5,11 @@ major series.
## 5.x series
## 5.0.0-pre.1
* Rename `Scalar::batch_invert` -> `Scalar::invert_batch` for consistency. Also make it no-alloc.
* Add an allocating batch inversion called `Scalar::invert_batch_alloc`.
## 5.0.0-pre.0
* Update edition to 2024

View file

@ -393,7 +393,7 @@ mod scalar_benches {
(0..size).map(|_| Scalar::random(&mut rng)).collect();
b.iter(|| {
let mut s = scalars.clone();
Scalar::batch_invert(&mut s);
Scalar::invert_batch_alloc(&mut s);
});
},
);

View file

@ -770,7 +770,7 @@ impl Scalar {
/// Scalar::from(11u64),
/// ];
///
/// let allinv = Scalar::batch_invert(&mut scalars);
/// let allinv = Scalar::invert_batch(&mut scalars);
///
/// assert_eq!(allinv, Scalar::from(3*5*7*11u64).invert());
/// assert_eq!(scalars[0], Scalar::from(3u64).invert());
@ -779,8 +779,39 @@ impl Scalar {
/// assert_eq!(scalars[3], Scalar::from(11u64).invert());
/// # }
/// ```
pub fn invert_batch<const N: usize>(inputs: &mut [Scalar; N]) -> Scalar {
let one: UnpackedScalar = Scalar::ONE.unpack().as_montgomery();
let mut scratch = [one; N];
Self::invert_batch_internal(inputs, &mut scratch)
}
/// Given a slice of nonzero (possibly secret) `Scalar`s, compute their inverses in a batch.
/// This the allocating form of [`Self::invert_batch`]. See those docs for examples.
///
/// # Return
///
/// Each element of `inputs` is replaced by its inverse.
///
/// The product of all inverses is returned.
///
/// # Warning
///
/// All input `Scalars` **MUST** be nonzero. If you cannot
/// *prove* that this is the case, you **SHOULD NOT USE THIS
/// FUNCTION**.
#[cfg(feature = "alloc")]
pub fn batch_invert(inputs: &mut [Scalar]) -> Scalar {
pub fn invert_batch_alloc(inputs: &mut [Scalar]) -> Scalar {
let n = inputs.len();
let one: UnpackedScalar = Scalar::ONE.unpack().as_montgomery();
let mut scratch = vec![one; n];
Self::invert_batch_internal(inputs, &mut scratch)
}
fn invert_batch_internal(inputs: &mut [Scalar], scratch: &mut [UnpackedScalar]) -> Scalar {
// This code is essentially identical to the FieldElement
// implementation, and is documented there. Unfortunately,
// it's not easy to write it generically, since here we want
@ -788,11 +819,6 @@ impl Scalar {
// externally, but there's no corresponding distinction for
// field elements.
let n = inputs.len();
let one: UnpackedScalar = Scalar::ONE.unpack().as_montgomery();
let mut scratch = vec![one; n];
// Keep an accumulator of all of the previous products
let mut acc = Scalar::ONE.unpack().as_montgomery();
@ -826,7 +852,7 @@ impl Scalar {
}
#[cfg(feature = "zeroize")]
Zeroize::zeroize(&mut scratch);
Zeroize::zeroize(&mut scratch.iter_mut());
ret
}
@ -1845,25 +1871,44 @@ pub(crate) mod test {
assert_eq!(X, bincode::deserialize(X.as_bytes()).unwrap(),);
}
#[cfg(all(debug_assertions, feature = "alloc"))]
#[cfg(debug_assertions)]
#[test]
#[should_panic]
fn batch_invert_with_a_zero_input_panics() {
let mut xs = vec![Scalar::ONE; 16];
fn invert_batch_with_a_zero_input_panics() {
let mut xs = [Scalar::ONE; 16];
xs[3] = Scalar::ZERO;
// This should panic in debug mode.
Scalar::batch_invert(&mut xs);
Scalar::invert_batch(&mut xs);
}
#[test]
fn invert_batch_empty() {
assert_eq!(Scalar::ONE, Scalar::invert_batch(&mut []));
}
#[test]
fn invert_batch_consistency() {
let mut x = Scalar::from(1u64);
let mut v1: [Scalar; 16] = core::array::from_fn(|_| {
let tmp = x;
x = x + x;
tmp
});
let v2 = v1;
let expected: Scalar = v1.iter().product();
let expected = expected.invert();
let ret = Scalar::invert_batch(&mut v1);
assert_eq!(ret, expected);
for (a, b) in v1.iter().zip(v2.iter()) {
assert_eq!(a * b, Scalar::ONE);
}
}
#[test]
#[cfg(feature = "alloc")]
fn batch_invert_empty() {
assert_eq!(Scalar::ONE, Scalar::batch_invert(&mut []));
}
#[test]
#[cfg(feature = "alloc")]
fn batch_invert_consistency() {
fn batch_vec_invert_consistency() {
let mut x = Scalar::from(1u64);
let mut v1: Vec<_> = (0..16)
.map(|_| {
@ -1876,7 +1921,7 @@ pub(crate) mod test {
let expected: Scalar = v1.iter().product();
let expected = expected.invert();
let ret = Scalar::batch_invert(&mut v1);
let ret = Scalar::invert_batch_alloc(&mut v1);
assert_eq!(ret, expected);
for (a, b) in v1.iter().zip(v2.iter()) {