Skip to content

Commit 2db8c59

Browse files
Merge remote-tracking branch 'origin/main' into fix/code-quality-housekeeping
# Conflicts: # src/gg.rs
2 parents 7b91a0b + 370662b commit 2db8c59

2 files changed

Lines changed: 199 additions & 8 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ zeroize = { version = "1.6.0", features = ["zeroize_derive"], optional = true }
2323
serde = { version = "1.0", default-features = false, optional = true }
2424

2525
[dev-dependencies]
26-
bincode = "1.3"
26+
bincode = { version = "2", features = ["serde"] }
2727
criterion = "0.5"
2828
rand = "0.8"
2929

src/gg.rs

Lines changed: 198 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
4141
use curve25519_dalek::{
4242
constants::RISTRETTO_BASEPOINT_POINT, constants::RISTRETTO_BASEPOINT_TABLE,
43-
ristretto::RistrettoPoint, scalar::Scalar, traits::VartimeMultiscalarMul,
43+
ristretto::CompressedRistretto, ristretto::RistrettoPoint, scalar::Scalar,
44+
traits::VartimeMultiscalarMul,
4445
};
4546
use rand_core::{CryptoRng, RngCore};
4647
use sha3::digest::{ExtendableOutput, Update};
@@ -115,6 +116,113 @@ impl<T: AsRef<[u8]>> From<T> for Identity {
115116
}
116117
}
117118

119+
// Helper: decode a 32-byte slice into a canonical `Scalar`.
120+
fn scalar_from_canonical(bytes: [u8; 32]) -> Option<Scalar> {
121+
Scalar::from_canonical_bytes(bytes).into()
122+
}
123+
124+
// Helper: decode a 32-byte slice into a `RistrettoPoint`.
125+
fn point_from_bytes(bytes: [u8; 32]) -> Option<RistrettoPoint> {
126+
CompressedRistretto(bytes).decompress()
127+
}
128+
129+
impl PublicKey {
130+
/// Serialize the public key to its compressed byte encoding.
131+
pub fn to_bytes(&self) -> [u8; PK_BYTES] {
132+
self.0.compress().to_bytes()
133+
}
134+
135+
/// Deserialize a public key from its compressed byte encoding.
136+
///
137+
/// Returns `None` if `bytes` is not a valid compressed Ristretto point.
138+
pub fn from_bytes(bytes: &[u8; PK_BYTES]) -> Option<Self> {
139+
point_from_bytes(*bytes).map(PublicKey)
140+
}
141+
}
142+
143+
impl SecretKey {
144+
/// Serialize the secret key to its canonical byte encoding.
145+
pub fn to_bytes(&self) -> [u8; SK_BYTES] {
146+
self.0.to_bytes()
147+
}
148+
149+
/// Deserialize a secret key from its canonical byte encoding.
150+
///
151+
/// Returns `None` if `bytes` is not a canonical scalar encoding.
152+
pub fn from_bytes(bytes: &[u8; SK_BYTES]) -> Option<Self> {
153+
scalar_from_canonical(*bytes).map(SecretKey)
154+
}
155+
}
156+
157+
impl UserSecretKey {
158+
/// Serialize the user secret key to a 96-byte encoding.
159+
///
160+
/// Layout: `y (32 bytes) || gr (32 bytes, compressed) || id (32 bytes)`.
161+
pub fn to_bytes(&self) -> [u8; USK_BYTES] {
162+
let mut out = [0u8; USK_BYTES];
163+
out[..32].copy_from_slice(&self.y.to_bytes());
164+
out[32..64].copy_from_slice(&self.gr.compress().to_bytes());
165+
out[64..96].copy_from_slice(&self.id.0);
166+
out
167+
}
168+
169+
/// Deserialize a user secret key from its 96-byte encoding.
170+
///
171+
/// Returns `None` if `y` is not a canonical scalar or if `gr` is not a
172+
/// valid compressed Ristretto point. See [`UserSecretKey::to_bytes`] for
173+
/// the encoding layout.
174+
pub fn from_bytes(bytes: &[u8; USK_BYTES]) -> Option<Self> {
175+
let mut y_bytes = [0u8; 32];
176+
let mut gr_bytes = [0u8; 32];
177+
let mut id_bytes = [0u8; IDENTITY_BYTES];
178+
y_bytes.copy_from_slice(&bytes[..32]);
179+
gr_bytes.copy_from_slice(&bytes[32..64]);
180+
id_bytes.copy_from_slice(&bytes[64..96]);
181+
182+
let y = scalar_from_canonical(y_bytes)?;
183+
let gr = point_from_bytes(gr_bytes)?;
184+
185+
Some(UserSecretKey {
186+
y,
187+
gr,
188+
id: Identity(id_bytes),
189+
})
190+
}
191+
}
192+
193+
impl Signature {
194+
/// Serialize the signature to a 96-byte encoding.
195+
///
196+
/// Layout: `ga (32 bytes, compressed) || b (32 bytes) || gr (32 bytes, compressed)`.
197+
pub fn to_bytes(&self) -> [u8; SIG_BYTES] {
198+
let mut out = [0u8; SIG_BYTES];
199+
out[..32].copy_from_slice(&self.ga.compress().to_bytes());
200+
out[32..64].copy_from_slice(&self.b.to_bytes());
201+
out[64..96].copy_from_slice(&self.gr.compress().to_bytes());
202+
out
203+
}
204+
205+
/// Deserialize a signature from its 96-byte encoding.
206+
///
207+
/// Returns `None` if `ga` or `gr` is not a valid compressed Ristretto
208+
/// point or if `b` is not a canonical scalar encoding. See
209+
/// [`Signature::to_bytes`] for the encoding layout.
210+
pub fn from_bytes(bytes: &[u8; SIG_BYTES]) -> Option<Self> {
211+
let mut ga_bytes = [0u8; 32];
212+
let mut b_bytes = [0u8; 32];
213+
let mut gr_bytes = [0u8; 32];
214+
ga_bytes.copy_from_slice(&bytes[..32]);
215+
b_bytes.copy_from_slice(&bytes[32..64]);
216+
gr_bytes.copy_from_slice(&bytes[64..96]);
217+
218+
let ga = point_from_bytes(ga_bytes)?;
219+
let b = scalar_from_canonical(b_bytes)?;
220+
let gr = point_from_bytes(gr_bytes)?;
221+
222+
Some(Signature { ga, b, gr })
223+
}
224+
}
225+
118226
// Helper function to compute H(g^r || id).
119227
fn h_helper(gr: &RistrettoPoint, id: &Identity) -> Scalar {
120228
let mut h = Sha3_512::new();
@@ -313,22 +421,26 @@ mod tests {
313421
// where all communicated messages are serialized/deserialized.
314422

315423
let (pk, usk, id) = default_setup();
424+
let cfg = bincode::config::standard();
316425

317426
// 1. PKG creates key pair and publishes the public key.
318-
let pk_serialized = bincode::serialize(&pk).unwrap();
319-
let usk_serialized = bincode::serialize(&usk).unwrap();
427+
let pk_serialized = bincode::serde::encode_to_vec(&pk, cfg).unwrap();
428+
let usk_serialized = bincode::serde::encode_to_vec(&usk, cfg).unwrap();
320429

321430
// 2. A signer retrieves the public key and signs some message,
322431
// after which it sends the signature to the verifier.
323-
let pk_recovered: PublicKey = bincode::deserialize(&pk_serialized).unwrap();
324-
let usk_recovered = bincode::deserialize(&usk_serialized).unwrap();
432+
let (pk_recovered, _): (PublicKey, usize) =
433+
bincode::serde::decode_from_slice(&pk_serialized, cfg).unwrap();
434+
let (usk_recovered, _): (UserSecretKey, usize) =
435+
bincode::serde::decode_from_slice(&usk_serialized, cfg).unwrap();
325436
let sig = Signer::new()
326437
.chain(b"some message")
327438
.sign(&usk_recovered, &mut OsRng);
328-
let sig_serialized = bincode::serialize(&sig).unwrap();
439+
let sig_serialized = bincode::serde::encode_to_vec(&sig, cfg).unwrap();
329440

330441
// 3. A verifier retrieves the signature from the signer and verifies it.
331-
let sig_recovered: Signature = bincode::deserialize(&sig_serialized).unwrap();
442+
let (sig_recovered, _): (Signature, usize) =
443+
bincode::serde::decode_from_slice(&sig_serialized, cfg).unwrap();
332444

333445
assert!(Verifier::new()
334446
.chain(b"some message")
@@ -348,6 +460,85 @@ mod tests {
348460
assert_ne!(sig, sig_other);
349461
}
350462

463+
#[test]
464+
fn test_byte_roundtrip_public_key() {
465+
let (pk, _) = setup(&mut OsRng);
466+
let bytes = pk.to_bytes();
467+
let recovered = PublicKey::from_bytes(&bytes).expect("valid pk bytes");
468+
assert_eq!(pk, recovered);
469+
assert_eq!(bytes, recovered.to_bytes());
470+
}
471+
472+
#[test]
473+
fn test_byte_roundtrip_secret_key() {
474+
let (_, sk) = setup(&mut OsRng);
475+
let bytes = sk.to_bytes();
476+
let recovered = SecretKey::from_bytes(&bytes).expect("valid sk bytes");
477+
assert_eq!(sk, recovered);
478+
assert_eq!(bytes, recovered.to_bytes());
479+
}
480+
481+
#[test]
482+
fn test_byte_roundtrip_user_secret_key() {
483+
let (_, usk, _) = default_setup();
484+
let bytes = usk.to_bytes();
485+
let recovered = UserSecretKey::from_bytes(&bytes).expect("valid usk bytes");
486+
assert_eq!(usk, recovered);
487+
assert_eq!(bytes, recovered.to_bytes());
488+
}
489+
490+
#[test]
491+
fn test_byte_roundtrip_signature() {
492+
let (_, usk, _) = default_setup();
493+
let sig = Signer::new().chain(b"msg").sign(&usk, &mut OsRng);
494+
let bytes = sig.to_bytes();
495+
let recovered = Signature::from_bytes(&bytes).expect("valid sig bytes");
496+
assert_eq!(bytes, recovered.to_bytes());
497+
}
498+
499+
#[test]
500+
fn test_byte_roundtrip_end_to_end() {
501+
// Full sign/verify across to_bytes/from_bytes on every type.
502+
let (pk, sk) = setup(&mut OsRng);
503+
let mut id_bytes = [0u8; 32];
504+
OsRng.fill_bytes(&mut id_bytes);
505+
let id: Identity = id_bytes.into();
506+
let usk = keygen(&sk, &id, &mut OsRng);
507+
508+
let pk = PublicKey::from_bytes(&pk.to_bytes()).unwrap();
509+
let usk = UserSecretKey::from_bytes(&usk.to_bytes()).unwrap();
510+
511+
let message = b"the eagle has landed";
512+
let sig = Signer::new().chain(message).sign(&usk, &mut OsRng);
513+
let sig = Signature::from_bytes(&sig.to_bytes()).unwrap();
514+
515+
assert!(Verifier::new().chain(message).verify(&pk, &sig, &id));
516+
}
517+
518+
#[test]
519+
fn test_from_bytes_rejects_invalid_point() {
520+
// 0xFF... is not a canonical compressed Ristretto encoding.
521+
let bad = [0xFFu8; PK_BYTES];
522+
assert!(PublicKey::from_bytes(&bad).is_none());
523+
}
524+
525+
#[test]
526+
fn test_from_bytes_rejects_non_canonical_scalar() {
527+
// The all-ones byte string exceeds the curve25519 scalar order.
528+
let bad = [0xFFu8; SK_BYTES];
529+
assert!(SecretKey::from_bytes(&bad).is_none());
530+
}
531+
532+
#[test]
533+
fn test_signature_from_bytes_rejects_bad_point() {
534+
let (_, usk, _) = default_setup();
535+
let sig = Signer::new().chain(b"msg").sign(&usk, &mut OsRng);
536+
let mut bytes = sig.to_bytes();
537+
// Corrupt the `ga` point to an invalid encoding.
538+
bytes[..32].copy_from_slice(&[0xFFu8; 32]);
539+
assert!(Signature::from_bytes(&bytes).is_none());
540+
}
541+
351542
#[test]
352543
fn test_clone_state() {
353544
let (pk, usk, id) = default_setup();

0 commit comments

Comments
 (0)