diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 481a4a8d..9ba84546 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,22 +43,12 @@ jobs: uses: actions-rs/cargo@v1 with: command: build - args: --verbose --release --tests --features endo,experimental + args: --verbose --release --tests --features experimental,zeroize - name: Run tests uses: actions-rs/cargo@v1 with: command: test - args: --verbose --release --features endo,experimental - - name: Build tests (no endomorphism) - uses: actions-rs/cargo@v1 - with: - command: build - args: --verbose --release --tests - - name: Run tests (no endomorphism) - uses: actions-rs/cargo@v1 - with: - command: test - args: --verbose --release + args: --verbose --release --features experimental,zeroize no-std: name: Check no-std target ${{ matrix.target }} diff --git a/Cargo.toml b/Cargo.toml index 5e8e17ef..160ffded 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ homepage = "https://github.com/zkcrypto/bls12_381" license = "MIT/Apache-2.0" name = "bls12_381" repository = "https://github.com/zkcrypto/bls12_381" -version = "0.5.0" +version = "0.6.0" edition = "2018" [package.metadata.docs.rs] @@ -37,16 +37,16 @@ version = "0.9" optional = true [dependencies.ff] -version = "0.10" +version = "0.11" default-features = false [dependencies.group] -version = "0.10" +version = "0.11" default-features = false optional = true [dependencies.pairing] -version = "0.20" +version = "0.21" optional = true [dependencies.rand_core] @@ -57,15 +57,16 @@ default-features = false version = "2.2.1" default-features = false +[dependencies.zeroize] +version = "1.4" +default-features = false +optional = true + [features] -default = ["groups", "pairings", "alloc", "bits", "endo"] +default = ["groups", "pairings", "alloc", "bits"] bits = ["ff/bits"] groups = ["group"] pairings = ["groups", "pairing"] alloc = ["group/alloc"] experimental = ["digest"] nightly = ["subtle/nightly"] - -# Deprecated. -# GLV patents US7110538B2 and US7995752B2 expired in September 2020. -endo = [] diff --git a/README.md b/README.md index 135c90d5..1f68cf4b 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ This crate provides an implementation of the BLS12-381 pairing-friendly elliptic * `pairings` (on by default): Enables some APIs for performing pairings. * `alloc` (on by default): Enables APIs that require an allocator; these include pairing optimizations. * `nightly`: Enables `subtle/nightly` which tries to prevent compiler optimizations that could jeopardize constant time operations. Requires the nightly Rust compiler. -* `endo` (on by default): Enables optimizations that leverage curve endomorphisms. Deprecated, will be removed in a future release. * `experimental`: Enables experimental features. These features have no backwards-compatibility guarantees and may change at any time; users that depend on specific behaviour should pin an exact version of this crate. The current list of experimental features: * Hashing to curves ([Internet Draft v11](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11)) diff --git a/RELEASES.md b/RELEASES.md index 2ee34ea4..3906fa35 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,21 @@ +# 0.6.0 + +## Fixed +- `bls12_381::Gt::default()` now returns `Gt::identity()` instead of a nonsensical value. + +## Added +- Zeroization support for most public types, behind the `zeroize` feature flag. +- `bls12_381::MillerLoopResult` trait implementations: + - `Default` + - `AddAssign` + - `AddAssign<&MillerLoopResult>` + +## Changed +- Bumped dependencies to `ff 0.11`, `group 0.11`, `pairing 0.21`. + +## Removed +- The deprecated `endo` feature flag. + # 0.5.0 ## Added diff --git a/src/fp.rs b/src/fp.rs index 2d3992db..04f194cd 100644 --- a/src/fp.rs +++ b/src/fp.rs @@ -32,6 +32,9 @@ impl Default for Fp { } } +#[cfg(feature = "zeroize")] +impl zeroize::DefaultIsZeroes for Fp {} + impl ConstantTimeEq for Fp { fn ct_eq(&self, other: &Self) -> Choice { self.0[0].ct_eq(&other.0[0]) @@ -914,3 +917,13 @@ fn test_lexicographic_largest() { .lexicographically_largest() )); } + +#[cfg(feature = "zeroize")] +#[test] +fn test_zeroize() { + use zeroize::Zeroize; + + let mut a = Fp::one(); + a.zeroize(); + assert!(bool::from(a.is_zero())); +} diff --git a/src/fp12.rs b/src/fp12.rs index 36734760..26b48043 100644 --- a/src/fp12.rs +++ b/src/fp12.rs @@ -62,6 +62,9 @@ impl Default for Fp12 { } } +#[cfg(feature = "zeroize")] +impl zeroize::DefaultIsZeroes for Fp12 {} + impl fmt::Debug for Fp12 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?} + ({:?})*w", self.c0, self.c1) @@ -644,3 +647,13 @@ fn test_arithmetic() { .frobenius_map() ); } + +#[cfg(feature = "zeroize")] +#[test] +fn test_zeroize() { + use zeroize::Zeroize; + + let mut a = Fp12::one(); + a.zeroize(); + assert!(bool::from(a.is_zero())); +} diff --git a/src/fp2.rs b/src/fp2.rs index c6854d0e..187e48f1 100644 --- a/src/fp2.rs +++ b/src/fp2.rs @@ -25,6 +25,9 @@ impl Default for Fp2 { } } +#[cfg(feature = "zeroize")] +impl zeroize::DefaultIsZeroes for Fp2 {} + impl From for Fp2 { fn from(f: Fp) -> Fp2 { Fp2 { @@ -890,3 +893,13 @@ fn test_lexicographic_largest() { .lexicographically_largest() )); } + +#[cfg(feature = "zeroize")] +#[test] +fn test_zeroize() { + use zeroize::Zeroize; + + let mut a = Fp2::one(); + a.zeroize(); + assert!(bool::from(a.is_zero())); +} diff --git a/src/fp6.rs b/src/fp6.rs index 69af77c7..e359f60b 100644 --- a/src/fp6.rs +++ b/src/fp6.rs @@ -55,6 +55,9 @@ impl Default for Fp6 { } } +#[cfg(feature = "zeroize")] +impl zeroize::DefaultIsZeroes for Fp6 {} + impl fmt::Debug for Fp6 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?} + ({:?})*v + ({:?})*v^2", self.c0, self.c1, self.c2) @@ -514,3 +517,13 @@ fn test_arithmetic() { ); assert_eq!(a.invert().unwrap() * a, Fp6::one()); } + +#[cfg(feature = "zeroize")] +#[test] +fn test_zeroize() { + use zeroize::Zeroize; + + let mut a = Fp6::one(); + a.zeroize(); + assert!(bool::from(a.is_zero())); +} diff --git a/src/g1.rs b/src/g1.rs index 521325c4..8f5e9750 100644 --- a/src/g1.rs +++ b/src/g1.rs @@ -37,6 +37,9 @@ impl Default for G1Affine { } } +#[cfg(feature = "zeroize")] +impl zeroize::DefaultIsZeroes for G1Affine {} + impl fmt::Display for G1Affine { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) @@ -430,6 +433,9 @@ impl Default for G1Projective { } } +#[cfg(feature = "zeroize")] +impl zeroize::DefaultIsZeroes for G1Projective {} + impl fmt::Display for G1Projective { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) @@ -817,6 +823,7 @@ impl G1Projective { } } +#[derive(Clone, Copy)] pub struct G1Compressed([u8; 48]); impl fmt::Debug for G1Compressed { @@ -831,6 +838,9 @@ impl Default for G1Compressed { } } +#[cfg(feature = "zeroize")] +impl zeroize::DefaultIsZeroes for G1Compressed {} + impl AsRef<[u8]> for G1Compressed { fn as_ref(&self) -> &[u8] { &self.0 @@ -843,6 +853,21 @@ impl AsMut<[u8]> for G1Compressed { } } +impl ConstantTimeEq for G1Compressed { + fn ct_eq(&self, other: &Self) -> Choice { + self.0.ct_eq(&other.0) + } +} + +impl Eq for G1Compressed {} +impl PartialEq for G1Compressed { + #[inline] + fn eq(&self, other: &Self) -> bool { + bool::from(self.ct_eq(other)) + } +} + +#[derive(Clone, Copy)] pub struct G1Uncompressed([u8; 96]); impl fmt::Debug for G1Uncompressed { @@ -857,6 +882,9 @@ impl Default for G1Uncompressed { } } +#[cfg(feature = "zeroize")] +impl zeroize::DefaultIsZeroes for G1Uncompressed {} + impl AsRef<[u8]> for G1Uncompressed { fn as_ref(&self) -> &[u8] { &self.0 @@ -869,6 +897,20 @@ impl AsMut<[u8]> for G1Uncompressed { } } +impl ConstantTimeEq for G1Uncompressed { + fn ct_eq(&self, other: &Self) -> Choice { + self.0.ct_eq(&other.0) + } +} + +impl Eq for G1Uncompressed {} +impl PartialEq for G1Uncompressed { + #[inline] + fn eq(&self, other: &Self) -> bool { + bool::from(self.ct_eq(other)) + } +} + impl Group for G1Projective { type Scalar = Scalar; @@ -1645,3 +1687,25 @@ fn test_batch_normalize() { } } } + +#[cfg(feature = "zeroize")] +#[test] +fn test_zeroize() { + use zeroize::Zeroize; + + let mut a = G1Affine::generator(); + a.zeroize(); + assert!(bool::from(a.is_identity())); + + let mut a = G1Projective::generator(); + a.zeroize(); + assert!(bool::from(a.is_identity())); + + let mut a = GroupEncoding::to_bytes(&G1Affine::generator()); + a.zeroize(); + assert_eq!(&a, &G1Compressed::default()); + + let mut a = UncompressedEncoding::to_uncompressed(&G1Affine::generator()); + a.zeroize(); + assert_eq!(&a, &G1Uncompressed::default()); +} diff --git a/src/g2.rs b/src/g2.rs index 33b76dc0..4a2faaa9 100644 --- a/src/g2.rs +++ b/src/g2.rs @@ -38,6 +38,9 @@ impl Default for G2Affine { } } +#[cfg(feature = "zeroize")] +impl zeroize::DefaultIsZeroes for G2Affine {} + impl fmt::Display for G2Affine { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) @@ -504,6 +507,9 @@ impl Default for G2Projective { } } +#[cfg(feature = "zeroize")] +impl zeroize::DefaultIsZeroes for G2Projective {} + impl fmt::Display for G2Projective { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) @@ -821,7 +827,6 @@ impl G2Projective { acc } - #[cfg(feature = "endo")] fn psi(&self) -> G2Projective { // 1 / ((u+1) ^ ((q-1)/3)) let psi_coeff_x = Fp2 { @@ -865,7 +870,6 @@ impl G2Projective { } } - #[cfg(feature = "endo")] fn psi2(&self) -> G2Projective { // 1 / 2 ^ ((q-1)/3) let psi2_coeff_x = Fp2 { @@ -891,7 +895,6 @@ impl G2Projective { } /// Multiply `self` by `crate::BLS_X`, using double and add. - #[cfg(feature = "endo")] fn mul_by_x(&self) -> G2Projective { let mut xself = G2Projective::identity(); // NOTE: in BLS12-381 we can just skip the first bit. @@ -915,35 +918,15 @@ impl G2Projective { /// This is equivalent to multiplying by $h\_\textrm{eff} = 3(z^2 - 1) \cdot /// h_2$, where $h_2$ is the cofactor of $\mathbb{G}\_2$ and $z$ is the /// parameter of BLS12-381. - /// - /// The endomorphism is only actually used if the crate feature `endo` is - /// enabled, which it is by default. pub fn clear_cofactor(&self) -> G2Projective { - #[cfg(feature = "endo")] - fn clear_cofactor(this: &G2Projective) -> G2Projective { - let t1 = this.mul_by_x(); // [x] P - let t2 = this.psi(); // psi(P) - - this.double().psi2() // psi^2(2P) - + (t1 + t2).mul_by_x() // psi^2(2P) + [x^2] P + [x] psi(P) - - t1 // psi^2(2P) + [x^2 - x] P + [x] psi(P) - - t2 // psi^2(2P) + [x^2 - x] P + [x - 1] psi(P) - - this // psi^2(2P) + [x^2 - x - 1] P + [x - 1] psi(P) - } - - #[cfg(not(feature = "endo"))] - fn clear_cofactor(this: &G2Projective) -> G2Projective { - this.multiply(&[ - 0x51, 0x55, 0xa9, 0xaa, 0x5, 0x0, 0x2, 0xe8, 0xb4, 0xf6, 0xbb, 0xde, 0xa, 0x4c, - 0x89, 0x59, 0xa3, 0xf6, 0x89, 0x66, 0xc0, 0xcb, 0x54, 0xe9, 0x1a, 0x7c, 0x47, 0xd7, - 0x69, 0xec, 0xc0, 0x2e, 0xb0, 0x12, 0x12, 0x5d, 0x1, 0xbf, 0x82, 0x6d, 0x95, 0xdb, - 0x31, 0x87, 0x17, 0x2f, 0x9c, 0x32, 0xe1, 0xff, 0x8, 0x15, 0x3, 0xff, 0x86, 0x99, - 0x68, 0xd7, 0x5a, 0x14, 0xe9, 0xa8, 0xe2, 0x88, 0x28, 0x35, 0x1b, 0xa9, 0xe, 0x6a, - 0x4c, 0x58, 0xb3, 0x75, 0xee, 0xf2, 0x8, 0x9f, 0xc6, 0xb, - ]) - } + let t1 = self.mul_by_x(); // [x] P + let t2 = self.psi(); // psi(P) - clear_cofactor(self) + self.double().psi2() // psi^2(2P) + + (t1 + t2).mul_by_x() // psi^2(2P) + [x^2] P + [x] psi(P) + - t1 // psi^2(2P) + [x^2 - x] P + [x] psi(P) + - t2 // psi^2(2P) + [x^2 - x] P + [x - 1] psi(P) + - self // psi^2(2P) + [x^2 - x - 1] P + [x - 1] psi(P) } /// Converts a batch of `G2Projective` elements into `G2Affine` elements. This @@ -999,6 +982,7 @@ impl G2Projective { } } +#[derive(Clone, Copy)] pub struct G2Compressed([u8; 96]); impl fmt::Debug for G2Compressed { @@ -1013,6 +997,9 @@ impl Default for G2Compressed { } } +#[cfg(feature = "zeroize")] +impl zeroize::DefaultIsZeroes for G2Compressed {} + impl AsRef<[u8]> for G2Compressed { fn as_ref(&self) -> &[u8] { &self.0 @@ -1025,6 +1012,21 @@ impl AsMut<[u8]> for G2Compressed { } } +impl ConstantTimeEq for G2Compressed { + fn ct_eq(&self, other: &Self) -> Choice { + self.0.ct_eq(&other.0) + } +} + +impl Eq for G2Compressed {} +impl PartialEq for G2Compressed { + #[inline] + fn eq(&self, other: &Self) -> bool { + bool::from(self.ct_eq(other)) + } +} + +#[derive(Clone, Copy)] pub struct G2Uncompressed([u8; 192]); impl fmt::Debug for G2Uncompressed { @@ -1039,6 +1041,9 @@ impl Default for G2Uncompressed { } } +#[cfg(feature = "zeroize")] +impl zeroize::DefaultIsZeroes for G2Uncompressed {} + impl AsRef<[u8]> for G2Uncompressed { fn as_ref(&self) -> &[u8] { &self.0 @@ -1051,6 +1056,20 @@ impl AsMut<[u8]> for G2Uncompressed { } } +impl ConstantTimeEq for G2Uncompressed { + fn ct_eq(&self, other: &Self) -> Choice { + self.0.ct_eq(&other.0) + } +} + +impl Eq for G2Uncompressed {} +impl PartialEq for G2Uncompressed { + #[inline] + fn eq(&self, other: &Self) -> bool { + bool::from(self.ct_eq(other)) + } +} + impl Group for G2Projective { type Scalar = Scalar; @@ -1869,7 +1888,6 @@ fn test_is_torsion_free() { assert!(bool::from(G2Affine::generator().is_torsion_free())); } -#[cfg(feature = "endo")] #[test] fn test_mul_by_x() { // multiplying by `x` a point in G2 is the same as multiplying by @@ -1886,7 +1904,6 @@ fn test_mul_by_x() { assert_eq!(point.mul_by_x(), point * x); } -#[cfg(feature = "endo")] #[test] fn test_psi() { let generator = G2Projective::generator(); @@ -2094,3 +2111,25 @@ fn test_batch_normalize() { } } } + +#[cfg(feature = "zeroize")] +#[test] +fn test_zeroize() { + use zeroize::Zeroize; + + let mut a = G2Affine::generator(); + a.zeroize(); + assert!(bool::from(a.is_identity())); + + let mut a = G2Projective::generator(); + a.zeroize(); + assert!(bool::from(a.is_identity())); + + let mut a = GroupEncoding::to_bytes(&G2Affine::generator()); + a.zeroize(); + assert_eq!(&a, &G2Compressed::default()); + + let mut a = UncompressedEncoding::to_uncompressed(&G2Affine::generator()); + a.zeroize(); + assert_eq!(&a, &G2Uncompressed::default()); +} diff --git a/src/hash_to_curve/expand_msg.rs b/src/hash_to_curve/expand_msg.rs index adc91ebd..ee413a69 100644 --- a/src/hash_to_curve/expand_msg.rs +++ b/src/hash_to_curve/expand_msg.rs @@ -287,13 +287,13 @@ where } } +#[cfg(feature = "alloc")] #[cfg(test)] mod tests { use super::*; use sha2::{Sha256, Sha512}; use sha3::{Shake128, Shake256}; - #[cfg(feature = "alloc")] #[test] fn expand_xmd_long_dst() { const MESSAGE: &[u8] = b"test expand xmd input message"; @@ -310,7 +310,6 @@ mod tests { } } - #[cfg(feature = "alloc")] #[test] fn expand_xof_long_dst() { const MESSAGE: &[u8] = b"test expand xof input message"; @@ -333,7 +332,6 @@ mod tests { // These test vectors are consistent between draft 8 and draft 11. /// From - #[cfg(feature = "alloc")] #[test] fn expand_message_xmd_works_for_draft8_testvectors_sha256() { let dst = b"QUUX-V01-CS02-with-expander"; @@ -440,7 +438,6 @@ mod tests { } /// From - #[cfg(feature = "alloc")] #[test] fn expand_message_xmd_works_for_draft8_testvectors_sha512() { let dst = b"QUUX-V01-CS02-with-expander"; @@ -547,7 +544,6 @@ mod tests { } /// From - #[cfg(feature = "alloc")] #[test] fn expand_message_xof_works_for_draft8_testvectors_shake128() { let dst = b"QUUX-V01-CS02-with-expander"; @@ -654,7 +650,6 @@ mod tests { } /// From - #[cfg(feature = "alloc")] #[test] fn expand_message_xof_works_for_draft11_testvectors_shake256() { let dst = b"QUUX-V01-CS02-with-expander"; diff --git a/src/pairings.rs b/src/pairings.rs index 4bc570bf..23e3ceaa 100644 --- a/src/pairings.rs +++ b/src/pairings.rs @@ -25,6 +25,15 @@ use pairing::MultiMillerLoop; #[derive(Copy, Clone, Debug)] pub struct MillerLoopResult(pub(crate) Fp12); +impl Default for MillerLoopResult { + fn default() -> Self { + MillerLoopResult(Fp12::one()) + } +} + +#[cfg(feature = "zeroize")] +impl zeroize::DefaultIsZeroes for MillerLoopResult {} + impl ConditionallySelectable for MillerLoopResult { fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { MillerLoopResult(Fp12::conditional_select(&a.0, &b.0, choice)) @@ -192,15 +201,35 @@ impl<'a, 'b> Mul<&'b MillerLoopResult> for &'a MillerLoopResult { impl_add_binop_specify_output!(MillerLoopResult, MillerLoopResult, MillerLoopResult); +impl AddAssign for MillerLoopResult { + #[inline] + fn add_assign(&mut self, rhs: MillerLoopResult) { + *self = &*self + &rhs; + } +} + +impl<'b> AddAssign<&'b MillerLoopResult> for MillerLoopResult { + #[inline] + fn add_assign(&mut self, rhs: &'b MillerLoopResult) { + *self = &*self + rhs; + } +} + /// This is an element of $\mathbb{G}_T$, the target group of the pairing function. As with /// $\mathbb{G}_1$ and $\mathbb{G}_2$ this group has order $q$. /// /// Typically, $\mathbb{G}_T$ is written multiplicatively but we will write it additively to /// keep code and abstractions consistent. #[cfg_attr(docsrs, doc(cfg(feature = "pairings")))] -#[derive(Copy, Clone, Debug, Default)] +#[derive(Copy, Clone, Debug)] pub struct Gt(pub(crate) Fp12); +impl Default for Gt { + fn default() -> Self { + Self::identity() + } +} + impl fmt::Display for Gt { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) @@ -901,3 +930,52 @@ fn test_multi_miller_loop() { assert_eq!(expected, test); } + +#[test] +fn test_miller_loop_result_default() { + assert_eq!( + MillerLoopResult::default().final_exponentiation(), + Gt::identity(), + ); +} + +#[cfg(feature = "zeroize")] +#[test] +fn test_miller_loop_result_zeroize() { + use zeroize::Zeroize; + + let mut m = multi_miller_loop(&[ + (&G1Affine::generator(), &G2Affine::generator().into()), + (&-G1Affine::generator(), &G2Affine::generator().into()), + ]); + m.zeroize(); + assert_eq!(m.0, MillerLoopResult::default().0); +} + +#[test] +fn tricking_miller_loop_result() { + assert_eq!( + multi_miller_loop(&[(&G1Affine::identity(), &G2Affine::generator().into())]).0, + Fp12::one() + ); + assert_eq!( + multi_miller_loop(&[(&G1Affine::generator(), &G2Affine::identity().into())]).0, + Fp12::one() + ); + assert_ne!( + multi_miller_loop(&[ + (&G1Affine::generator(), &G2Affine::generator().into()), + (&-G1Affine::generator(), &G2Affine::generator().into()) + ]) + .0, + Fp12::one() + ); + assert_eq!( + multi_miller_loop(&[ + (&G1Affine::generator(), &G2Affine::generator().into()), + (&-G1Affine::generator(), &G2Affine::generator().into()) + ]) + .final_exponentiation(), + Gt::identity() + ); +} diff --git a/src/scalar.rs b/src/scalar.rs index 3be61de0..a7077d36 100644 --- a/src/scalar.rs +++ b/src/scalar.rs @@ -207,6 +207,9 @@ impl Default for Scalar { } } +#[cfg(feature = "zeroize")] +impl zeroize::DefaultIsZeroes for Scalar {} + impl Scalar { /// Returns zero, the additive identity. #[inline] @@ -679,10 +682,6 @@ impl Field for Scalar { Self::one() } - fn is_zero(&self) -> bool { - self.ct_eq(&Self::zero()).into() - } - #[must_use] fn square(&self) -> Self { self.square() @@ -705,21 +704,16 @@ impl Field for Scalar { impl PrimeField for Scalar { type Repr = [u8; 32]; - fn from_repr(r: Self::Repr) -> Option { - let res = Self::from_bytes(&r); - if res.is_some().into() { - Some(res.unwrap()) - } else { - None - } + fn from_repr(r: Self::Repr) -> CtOption { + Self::from_bytes(&r) } fn to_repr(&self) -> Self::Repr { self.to_bytes() } - fn is_odd(&self) -> bool { - self.to_bytes()[0] & 1 == 1 + fn is_odd(&self) -> Choice { + Choice::from(self.to_bytes()[0] & 1) } const NUM_BITS: u32 = MODULUS_BITS; @@ -1240,3 +1234,18 @@ fn test_double() { assert_eq!(a.double(), a + a); } + +#[cfg(feature = "zeroize")] +#[test] +fn test_zeroize() { + use zeroize::Zeroize; + + let mut a = Scalar::from_raw([ + 0x1fff_3231_233f_fffd, + 0x4884_b7fa_0003_4802, + 0x998c_4fef_ecbc_4ff3, + 0x1824_b159_acc5_0562, + ]); + a.zeroize(); + assert!(bool::from(a.is_zero())); +}