Skip to content

Commit 6625d70

Browse files
committed
Merge remote-tracking branch 'origin/main' into bohdan/dkg-final
2 parents e41dc72 + a1be7a4 commit 6625d70

11 files changed

Lines changed: 865 additions & 135 deletions

File tree

Cargo.lock

Lines changed: 16 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ pbkdf2 = "0.12.2"
7070
pin-project = "1"
7171
sha2 = "0.10.9"
7272
scrypt = "0.11.0"
73+
subtle = "2.6"
7374
unicode-normalization = "0.1.25"
7475
zeroize = "1.8.2"
7576
uuid = { version = "1.19", features = ["serde", "v4"] }

crates/dkg/src/frost.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub struct MsgKey {
4949
pub enum FrostError {
5050
/// Kryptology FROST DKG algorithm failed.
5151
#[error("kryptology DKG error: {0:?}")]
52-
Kryptology(pluto_frost::kryptology::DkgError),
52+
Kryptology(pluto_frost::kryptology::KryptologyError),
5353
/// Broadcast component error.
5454
#[error("bcast error: {0}")]
5555
Bcast(#[from] crate::bcast::Error),
@@ -70,8 +70,8 @@ pub enum FrostError {
7070
SecretShareEncoding,
7171
}
7272

73-
impl From<pluto_frost::kryptology::DkgError> for FrostError {
74-
fn from(e: pluto_frost::kryptology::DkgError) -> Self {
73+
impl From<pluto_frost::kryptology::KryptologyError> for FrostError {
74+
fn from(e: pluto_frost::kryptology::KryptologyError) -> Self {
7575
FrostError::Kryptology(e)
7676
}
7777
}

crates/frost/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ publish.workspace = true
1010
blst.workspace = true
1111
rand_core.workspace = true
1212
sha2.workspace = true
13+
subtle.workspace = true
14+
thiserror.workspace = true
15+
zeroize = { workspace = true, features = ["derive"] }
1316

1417
[dev-dependencies]
1518
hex.workspace = true

crates/frost/src/curve.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use std::{
1212

1313
use blst::*;
1414
use 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

99123
impl 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

Comments
 (0)