Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ cipher = "0.4.4"
pbkdf2 = "0.12.2"
sha2 = "0.10.9"
scrypt = "0.11.0"
subtle = "2.6"
unicode-normalization = "0.1.25"
zeroize = "1.8.2"
uuid = { version = "1.19", features = ["serde", "v4"] }
Expand Down
3 changes: 3 additions & 0 deletions crates/frost/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ publish.workspace = true
blst.workspace = true
rand_core.workspace = true
sha2.workspace = true
subtle.workspace = true
thiserror.workspace = true
zeroize = { workspace = true, features = ["derive"] }

[dev-dependencies]
hex.workspace = true
Expand Down
115 changes: 115 additions & 0 deletions crates/frost/src/curve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use std::{

use blst::*;
use rand_core::{CryptoRng, RngCore};
use subtle::ConstantTimeEq;
use zeroize::Zeroize;

/// BLS12-381 scalar field element. Wrapper around `blst_fr` in Montgomery form.
#[derive(Copy, Clone, Default, PartialEq, Eq)]
Expand Down Expand Up @@ -78,6 +80,17 @@ impl Scalar {
Scalar(fr)
}

/// Reduce big-endian bytes modulo the scalar field order.
pub(crate) fn from_be_bytes_wide(bytes: &[u8]) -> Self {
let mut scalar = blst_scalar::default();
let mut fr = blst_fr::default();
unsafe {
blst_scalar_from_be_bytes(&mut scalar, bytes.as_ptr(), bytes.len());
blst_fr_from_scalar(&mut fr, &scalar);
}
Scalar(fr)
}

/// Generate a uniformly random scalar.
pub fn random<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
let mut wide = [0u8; 64];
Expand All @@ -94,6 +107,17 @@ impl Scalar {
unsafe { blst_fr_eucl_inverse(&mut out, &self.0) };
Some(Scalar(out))
}

/// Compare scalar limbs without early-exit equality.
pub(crate) fn constant_time_eq(&self, other: &Self) -> bool {
self.0.l.ct_eq(&other.0.l).into()
}
}

impl Zeroize for Scalar {
fn zeroize(&mut self) {
self.0.l.zeroize();
}
}

impl From<u64> for Scalar {
Expand Down Expand Up @@ -214,6 +238,7 @@ impl Mul<Scalar> for G1Projective {
let mut out = blst_p1::default();
unsafe {
blst_scalar_from_fr(&mut scalar, &rhs.0);
// BLS12-381 scalar field order has 255 significant bits.
blst_p1_mult(&mut out, &self.0, scalar.b.as_ptr(), 255);
}
G1Projective(out)
Expand Down Expand Up @@ -277,3 +302,93 @@ impl From<G1Affine> for G1Projective {
G1Projective(p)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn scalar_one_matches_blst_conversion() {
assert_eq!(Scalar::ONE, Scalar::from(1u64));
}

#[test]
fn scalar_round_trips_little_endian_bytes() {
let scalar = Scalar::from(42);
let bytes = scalar.to_bytes();

assert_eq!(Scalar::from_bytes(&bytes), Some(scalar));
}

#[test]
fn scalar_rejects_out_of_range_bytes() {
assert_eq!(Scalar::from_bytes(&[0xff; 32]), None);
}

#[test]
fn scalar_from_be_bytes_wide_matches_reversed_le_wide() {
let be = [7u8; 48];
let from_be = Scalar::from_be_bytes_wide(&be);

let mut reversed = be;
reversed.reverse();
let mut wide = [0u8; 64];
wide[..48].copy_from_slice(&reversed);

assert_eq!(from_be, Scalar::from_bytes_wide(&wide));
}

#[test]
fn scalar_constant_time_eq_matches_equality() {
let a = Scalar::from(42);
let b = Scalar::from(42);
let c = Scalar::from(43);

assert!(a.constant_time_eq(&b));
assert!(!a.constant_time_eq(&c));
}

#[test]
fn scalar_zeroize_clears_limbs() {
let mut scalar = Scalar::from(42);

scalar.zeroize();

assert_eq!(scalar, Scalar::ZERO);
}

#[test]
fn scalar_invert_returns_none_for_zero() {
assert_eq!(Scalar::ZERO.invert(), None);
}

#[test]
fn scalar_invert_returns_multiplicative_inverse() {
let scalar = Scalar::from(42);
let inverse = scalar.invert().expect("non-zero scalar should invert");

assert_eq!(scalar * inverse, Scalar::ONE);
}

#[test]
fn g1_projective_identity_reports_identity() {
assert!(G1Projective::identity().is_identity());
assert!(!G1Projective::generator().is_identity());
}

#[test]
fn g1_projective_rejects_identity_compressed_point() {
let identity = G1Affine::from(G1Projective::identity()).to_compressed();

assert_eq!(G1Projective::from_compressed(&identity), None);
}

#[test]
fn g1_affine_round_trips_generator_compressed_point() {
let generator = G1Projective::generator();
let compressed = G1Affine::from(generator).to_compressed();
let affine = G1Affine::from_compressed(&compressed).expect("generator should deserialize");

assert_eq!(G1Projective::from(affine), generator);
}
}
Loading
Loading