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.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 ## 5.0.0-pre.0
* Update edition to 2024 * Update edition to 2024

View file

@ -393,7 +393,7 @@ mod scalar_benches {
(0..size).map(|_| Scalar::random(&mut rng)).collect(); (0..size).map(|_| Scalar::random(&mut rng)).collect();
b.iter(|| { b.iter(|| {
let mut s = scalars.clone(); 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), /// 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!(allinv, Scalar::from(3*5*7*11u64).invert());
/// assert_eq!(scalars[0], Scalar::from(3u64).invert()); /// assert_eq!(scalars[0], Scalar::from(3u64).invert());
@ -779,8 +779,39 @@ impl Scalar {
/// assert_eq!(scalars[3], Scalar::from(11u64).invert()); /// 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")] #[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 // This code is essentially identical to the FieldElement
// implementation, and is documented there. Unfortunately, // implementation, and is documented there. Unfortunately,
// it's not easy to write it generically, since here we want // 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 // externally, but there's no corresponding distinction for
// field elements. // 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 // Keep an accumulator of all of the previous products
let mut acc = Scalar::ONE.unpack().as_montgomery(); let mut acc = Scalar::ONE.unpack().as_montgomery();
@ -826,7 +852,7 @@ impl Scalar {
} }
#[cfg(feature = "zeroize")] #[cfg(feature = "zeroize")]
Zeroize::zeroize(&mut scratch); Zeroize::zeroize(&mut scratch.iter_mut());
ret ret
} }
@ -1845,25 +1871,44 @@ pub(crate) mod test {
assert_eq!(X, bincode::deserialize(X.as_bytes()).unwrap(),); assert_eq!(X, bincode::deserialize(X.as_bytes()).unwrap(),);
} }
#[cfg(all(debug_assertions, feature = "alloc"))] #[cfg(debug_assertions)]
#[test] #[test]
#[should_panic] #[should_panic]
fn batch_invert_with_a_zero_input_panics() { fn invert_batch_with_a_zero_input_panics() {
let mut xs = vec![Scalar::ONE; 16]; let mut xs = [Scalar::ONE; 16];
xs[3] = Scalar::ZERO; xs[3] = Scalar::ZERO;
// This should panic in debug mode. // 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] #[test]
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
fn batch_invert_empty() { fn batch_vec_invert_consistency() {
assert_eq!(Scalar::ONE, Scalar::batch_invert(&mut []));
}
#[test]
#[cfg(feature = "alloc")]
fn batch_invert_consistency() {
let mut x = Scalar::from(1u64); let mut x = Scalar::from(1u64);
let mut v1: Vec<_> = (0..16) let mut v1: Vec<_> = (0..16)
.map(|_| { .map(|_| {
@ -1876,7 +1921,7 @@ pub(crate) mod test {
let expected: Scalar = v1.iter().product(); let expected: Scalar = v1.iter().product();
let expected = expected.invert(); let expected = expected.invert();
let ret = Scalar::batch_invert(&mut v1); let ret = Scalar::invert_batch_alloc(&mut v1);
assert_eq!(ret, expected); assert_eq!(ret, expected);
for (a, b) in v1.iter().zip(v2.iter()) { for (a, b) in v1.iter().zip(v2.iter()) {