@@ -12,6 +12,8 @@ use std::{
1212
1313use blst:: * ;
1414use rand_core:: { CryptoRng , RngCore } ;
15+ use subtle:: ConstantTimeEq ;
16+ use zeroize:: Zeroize ;
1517
1618/// BLS12-381 scalar field element. Wrapper around `blst_fr` in Montgomery form.
1719#[ derive( Copy , Clone , Default , PartialEq , Eq ) ]
@@ -78,6 +80,17 @@ impl Scalar {
7880 Scalar ( fr)
7981 }
8082
83+ /// Reduce big-endian bytes modulo the scalar field order.
84+ pub ( crate ) fn from_be_bytes_wide ( bytes : & [ u8 ] ) -> Self {
85+ let mut scalar = blst_scalar:: default ( ) ;
86+ let mut fr = blst_fr:: default ( ) ;
87+ unsafe {
88+ blst_scalar_from_be_bytes ( & mut scalar, bytes. as_ptr ( ) , bytes. len ( ) ) ;
89+ blst_fr_from_scalar ( & mut fr, & scalar) ;
90+ }
91+ Scalar ( fr)
92+ }
93+
8194 /// Generate a uniformly random scalar.
8295 pub fn random < R : RngCore + CryptoRng > ( rng : & mut R ) -> Self {
8396 let mut wide = [ 0u8 ; 64 ] ;
@@ -94,6 +107,17 @@ impl Scalar {
94107 unsafe { blst_fr_eucl_inverse ( & mut out, & self . 0 ) } ;
95108 Some ( Scalar ( out) )
96109 }
110+
111+ /// Compare scalar limbs without early-exit equality.
112+ pub ( crate ) fn constant_time_eq ( & self , other : & Self ) -> bool {
113+ self . 0 . l . ct_eq ( & other. 0 . l ) . into ( )
114+ }
115+ }
116+
117+ impl Zeroize for Scalar {
118+ fn zeroize ( & mut self ) {
119+ self . 0 . l . zeroize ( ) ;
120+ }
97121}
98122
99123impl From < u64 > for Scalar {
@@ -214,6 +238,7 @@ impl Mul<Scalar> for G1Projective {
214238 let mut out = blst_p1:: default ( ) ;
215239 unsafe {
216240 blst_scalar_from_fr ( & mut scalar, & rhs. 0 ) ;
241+ // BLS12-381 scalar field order has 255 significant bits.
217242 blst_p1_mult ( & mut out, & self . 0 , scalar. b . as_ptr ( ) , 255 ) ;
218243 }
219244 G1Projective ( out)
@@ -277,3 +302,93 @@ impl From<G1Affine> for G1Projective {
277302 G1Projective ( p)
278303 }
279304}
305+
306+ #[ cfg( test) ]
307+ mod tests {
308+ use super :: * ;
309+
310+ #[ test]
311+ fn scalar_one_matches_blst_conversion ( ) {
312+ assert_eq ! ( Scalar :: ONE , Scalar :: from( 1u64 ) ) ;
313+ }
314+
315+ #[ test]
316+ fn scalar_round_trips_little_endian_bytes ( ) {
317+ let scalar = Scalar :: from ( 42 ) ;
318+ let bytes = scalar. to_bytes ( ) ;
319+
320+ assert_eq ! ( Scalar :: from_bytes( & bytes) , Some ( scalar) ) ;
321+ }
322+
323+ #[ test]
324+ fn scalar_rejects_out_of_range_bytes ( ) {
325+ assert_eq ! ( Scalar :: from_bytes( & [ 0xff ; 32 ] ) , None ) ;
326+ }
327+
328+ #[ test]
329+ fn scalar_from_be_bytes_wide_matches_reversed_le_wide ( ) {
330+ let be = [ 7u8 ; 48 ] ;
331+ let from_be = Scalar :: from_be_bytes_wide ( & be) ;
332+
333+ let mut reversed = be;
334+ reversed. reverse ( ) ;
335+ let mut wide = [ 0u8 ; 64 ] ;
336+ wide[ ..48 ] . copy_from_slice ( & reversed) ;
337+
338+ assert_eq ! ( from_be, Scalar :: from_bytes_wide( & wide) ) ;
339+ }
340+
341+ #[ test]
342+ fn scalar_constant_time_eq_matches_equality ( ) {
343+ let a = Scalar :: from ( 42 ) ;
344+ let b = Scalar :: from ( 42 ) ;
345+ let c = Scalar :: from ( 43 ) ;
346+
347+ assert ! ( a. constant_time_eq( & b) ) ;
348+ assert ! ( !a. constant_time_eq( & c) ) ;
349+ }
350+
351+ #[ test]
352+ fn scalar_zeroize_clears_limbs ( ) {
353+ let mut scalar = Scalar :: from ( 42 ) ;
354+
355+ scalar. zeroize ( ) ;
356+
357+ assert_eq ! ( scalar, Scalar :: ZERO ) ;
358+ }
359+
360+ #[ test]
361+ fn scalar_invert_returns_none_for_zero ( ) {
362+ assert_eq ! ( Scalar :: ZERO . invert( ) , None ) ;
363+ }
364+
365+ #[ test]
366+ fn scalar_invert_returns_multiplicative_inverse ( ) {
367+ let scalar = Scalar :: from ( 42 ) ;
368+ let inverse = scalar. invert ( ) . expect ( "non-zero scalar should invert" ) ;
369+
370+ assert_eq ! ( scalar * inverse, Scalar :: ONE ) ;
371+ }
372+
373+ #[ test]
374+ fn g1_projective_identity_reports_identity ( ) {
375+ assert ! ( G1Projective :: identity( ) . is_identity( ) ) ;
376+ assert ! ( !G1Projective :: generator( ) . is_identity( ) ) ;
377+ }
378+
379+ #[ test]
380+ fn g1_projective_rejects_identity_compressed_point ( ) {
381+ let identity = G1Affine :: from ( G1Projective :: identity ( ) ) . to_compressed ( ) ;
382+
383+ assert_eq ! ( G1Projective :: from_compressed( & identity) , None ) ;
384+ }
385+
386+ #[ test]
387+ fn g1_affine_round_trips_generator_compressed_point ( ) {
388+ let generator = G1Projective :: generator ( ) ;
389+ let compressed = G1Affine :: from ( generator) . to_compressed ( ) ;
390+ let affine = G1Affine :: from_compressed ( & compressed) . expect ( "generator should deserialize" ) ;
391+
392+ assert_eq ! ( G1Projective :: from( affine) , generator) ;
393+ }
394+ }
0 commit comments