Skip to content

Commit e1de7e8

Browse files
committed
refactor(serialization): replace curve-specific serialization with generic approach
- refactor: remove GroupSerialization feature that required per-curve implementation - feat: implement generic serialization functions applicable to any Group G - test: preserve Sage test vectors to maintain compatibility verification
1 parent 6d48643 commit e1de7e8

File tree

10 files changed

+219
-186
lines changed

10 files changed

+219
-186
lines changed

src/codec/keccak_codec.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
//! - `KeccakDuplexSponge`: Duplex sponge over 200-byte state buffer
2222
//! - `ByteSchnorrCodec`: Fiat-Shamir transcript codec compatible with Sage Schnorr proofs
2323
use crate::codec::r#trait::{Codec, DuplexSpongeInterface};
24-
use crate::serialisation::GroupSerialisation;
2524
use ff::PrimeField;
2625
use group::{Group, GroupEncoding};
2726
use num_bigint::BigUint;
@@ -178,7 +177,7 @@ fn cardinal<F: PrimeField>() -> BigUint {
178177
#[derive(Clone)]
179178
pub struct ByteSchnorrCodec<G, H>
180179
where
181-
G: Group + GroupEncoding + GroupSerialisation,
180+
G: Group + GroupEncoding,
182181
H: DuplexSpongeInterface,
183182
{
184183
hasher: H,
@@ -187,7 +186,7 @@ where
187186

188187
impl<G, H> Codec for ByteSchnorrCodec<G, H>
189188
where
190-
G: Group + GroupEncoding + GroupSerialisation,
189+
G: Group + GroupEncoding,
191190
H: DuplexSpongeInterface,
192191
{
193192
type Challenge = <G as Group>::Scalar;

src/group_serialisation.rs

Lines changed: 33 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,46 @@
1-
//! # Group Serialization Implementations
2-
//!
3-
//! This module implements the [`GroupSerialisation`] trait for specific elliptic curve.
4-
//!
5-
//! Supported groups:
6-
//! - [`RistrettoPoint`] from `curve25519-dalek`
7-
//! - [`G1Projective`] from `bls12_381`
8-
//!
9-
//! ## Trait Overview
10-
//!
11-
//! The [`GroupSerialisation`] trait defines:
12-
//! - `serialize_element` / `deserialize_element` for group points
13-
//! - `serialize_scalar` / `deserialize_scalar` for field elements (scalars)
14-
//!
15-
//! Implementations must guarantee:
16-
//! - Canonical and deterministic serialization
17-
//! - Rejection of malformed or non-canonical encodings on deserialization
18-
use std::convert::TryInto;
19-
20-
use crate::serialisation::GroupSerialisation;
21-
use bls12_381::{G1Affine, G1Projective, Scalar as BlsScalar};
22-
use curve25519_dalek::{
23-
ristretto::{CompressedRistretto, RistrettoPoint},
24-
scalar::Scalar as RistrettoScalar,
25-
};
1+
use group::{Group, GroupEncoding};
262
use ff::PrimeField;
273

28-
impl GroupSerialisation for RistrettoPoint {
29-
/// Serializes a `RistrettoPoint` to 32-byte compressed form.
30-
fn serialize_element(point: &Self) -> Vec<u8> {
31-
point.compress().to_bytes().to_vec()
32-
}
4+
pub fn serialize_element<G: Group + GroupEncoding>(element: &G) -> Vec<u8>{
5+
element.to_bytes().as_ref().to_vec()
6+
}
337

34-
/// Attempts to decompress a 32-byte slice into a `RistrettoPoint`.
35-
/// Returns `None` if the input is not a valid compressed point.
36-
fn deserialize_element(bytes: &[u8]) -> Option<Self> {
37-
let point_size = 32;
38-
if bytes.len() != point_size {
39-
return None;
40-
}
41-
let mut buf = [0u8; 32];
42-
buf.copy_from_slice(bytes);
43-
CompressedRistretto(buf).decompress()
8+
pub fn deserialize_element<G: Group + GroupEncoding>(data: &[u8]) -> Option<G> {
9+
let element_len = G::Repr::default().as_ref().len();
10+
if data.len() != element_len {
11+
return None;
4412
}
4513

46-
/// Serializes a `RistrettoScalar` to 32 bytes (little-endian).
47-
fn serialize_scalar(scalar: &Self::Scalar) -> Vec<u8> {
48-
scalar.to_bytes().to_vec()
49-
}
14+
let mut repr = G::Repr::default();
15+
repr.as_mut().copy_from_slice(data);
16+
let ct_point = G::from_bytes(&repr);
5017

51-
/// Deserializes a 32-byte little-endian encoding into a `RistrettoScalar`.
52-
/// Returns `None` if the encoding is not canonical or invalid.
53-
fn deserialize_scalar(bytes: &[u8]) -> Option<Self::Scalar> {
54-
if bytes.len() != 32 {
55-
return None;
56-
}
57-
let mut buf = [0u8; 32];
58-
buf.copy_from_slice(bytes);
59-
RistrettoScalar::from_canonical_bytes(buf).into()
18+
if ct_point.is_some().into() {
19+
let point = ct_point.unwrap();
20+
Some(point)
21+
} else {
22+
None
6023
}
6124
}
6225

63-
impl GroupSerialisation for G1Projective {
64-
/// Serializes a `G1Projective` element using compressed affine representation (48 bytes).
65-
fn serialize_element(point: &G1Projective) -> Vec<u8> {
66-
let affine = G1Affine::from(point);
67-
affine.to_compressed().as_ref().to_vec()
68-
}
26+
pub fn serialize_scalar<G: Group>(scalar: &G::Scalar) -> Vec<u8> {
27+
let mut scalar_bytes = scalar.to_repr().as_ref().to_vec();
28+
scalar_bytes.reverse();
29+
scalar_bytes
30+
}
6931

70-
/// Deserializes a 48-byte compressed affine representation into a `G1Projective`.
71-
/// Returns `None` if the point is invalid or not on the curve.
72-
fn deserialize_element(bytes: &[u8]) -> Option<G1Projective> {
73-
if bytes.len() != 48 {
74-
return None;
75-
}
76-
let mut buf = [0u8; 48];
77-
buf.copy_from_slice(bytes);
78-
let affine_ctoption = G1Affine::from_compressed(&buf);
79-
if affine_ctoption.is_some().into() {
80-
let affine = affine_ctoption.unwrap();
81-
Some(G1Projective::from(&affine))
82-
} else {
83-
None
84-
}
32+
pub fn deserialize_scalar<G: Group>(data: &[u8]) -> Option<G::Scalar> {
33+
let scalar_len = <<G as Group>::Scalar as PrimeField>::Repr::default().as_ref().len();
34+
if data.len() != scalar_len {
35+
return None;
8536
}
8637

87-
/// Serializes a `bls12_381::Scalar` using its canonical representation.
88-
fn serialize_scalar(scalar: &Self::Scalar) -> Vec<u8> {
89-
scalar.to_repr().as_ref().to_vec()
90-
}
38+
let mut repr = <<G as Group>::Scalar as PrimeField>::Repr::default();
39+
repr.as_mut().copy_from_slice(&{
40+
let mut tmp = data.to_vec();
41+
tmp.reverse();
42+
tmp
43+
});
9144

92-
/// Deserializes a canonical byte representation into a `bls12_381::Scalar`.
93-
/// Returns `None` if the scalar is malformed or out of range.
94-
fn deserialize_scalar(bytes: &[u8]) -> Option<Self::Scalar> {
95-
let repr = bytes.try_into().ok()?;
96-
let result_ctoption = BlsScalar::from_repr(repr);
97-
if result_ctoption.is_some().into() {
98-
Some(result_ctoption.unwrap())
99-
} else {
100-
None
101-
}
102-
}
103-
}
45+
G::Scalar::from_repr(repr).into()
46+
}

src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,17 @@ pub mod group_morphism;
1919
pub mod group_serialisation;
2020
pub mod proof_builder;
2121
pub mod proof_composition;
22-
pub mod schnorr_proof;
23-
pub mod serialisation;
22+
pub mod schnorr_protocol;
2423
pub mod r#trait;
2524

2625
pub use errors::*;
2726
pub use fiat_shamir::*;
2827
pub use group_morphism::*;
28+
pub use group_serialisation::*;
2929
pub use proof_builder::*;
3030
pub use proof_composition::*;
3131
pub use r#trait::*;
32-
pub use schnorr_proof::*;
32+
pub use schnorr_protocol::*;
3333

3434
pub mod codec;
3535
#[deprecated(

src/proof_builder.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
//! - Supports element assignment to statement variables
1414
//! - Offers one-shot `prove` and `verify` methods
1515
16-
use group::Group;
16+
use group::{Group, GroupEncoding};
1717
use rand::{CryptoRng, RngCore};
1818

1919
use crate::{
20-
codec::ShakeCodec, serialisation::GroupSerialisation, GroupMorphismPreimage, NISigmaProtocol,
20+
codec::ShakeCodec, GroupMorphismPreimage, NISigmaProtocol,
2121
PointVar, ProofError, ScalarVar, SchnorrProtocol,
2222
};
2323

@@ -28,25 +28,25 @@ use crate::{
2828
/// for allocating variables, defining statements, and generating proofs.
2929
///
3030
/// # Type Parameters
31-
/// - `G`: A group that implements both [`Group`] and [`GroupSerialisation`], such as `RistrettoPoint` or `G1Projective`.
31+
/// - `G`: A group that implements both [`Group`] and [`GroupEncoding`].
3232
pub struct ProofBuilder<G>
3333
where
34-
G: Group + GroupSerialisation,
34+
G: Group + GroupEncoding,
3535
{
3636
/// The underlying Sigma protocol instance with Fiat-Shamir transformation applied.
3737
pub protocol: NISigmaProtocol<SchnorrProtocol<G>, ShakeCodec<G>, G>,
3838
}
3939

4040
impl<G> ProofBuilder<G>
4141
where
42-
G: Group + GroupSerialisation,
42+
G: Group + GroupEncoding,
4343
ShakeCodec<G>: Clone,
4444
{
4545
/// Creates a new proof builder with a Schnorr protocol instance using the given domain separator.
4646
pub fn new(domain_sep: &[u8]) -> Self {
47-
let schnorr_proof = SchnorrProtocol(GroupMorphismPreimage::<G>::new());
47+
let schnorr_protocol = SchnorrProtocol(GroupMorphismPreimage::<G>::new());
4848
let protocol =
49-
NISigmaProtocol::<SchnorrProtocol<G>, ShakeCodec<G>, G>::new(domain_sep, schnorr_proof);
49+
NISigmaProtocol::<SchnorrProtocol<G>, ShakeCodec<G>, G>::new(domain_sep, schnorr_protocol);
5050
Self { protocol }
5151
}
5252

src/schnorr_proof.rs renamed to src/schnorr_protocol.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
//! through a group morphism abstraction (see Maurer09).
66
77
use crate::{
8-
serialisation::GroupSerialisation, CompactProtocol, GroupMorphismPreimage, ProofError,
8+
group_serialisation::*,
9+
CompactProtocol, GroupMorphismPreimage, ProofError,
910
SigmaProtocol,
1011
};
1112

@@ -16,13 +17,13 @@ use rand::{CryptoRng, Rng};
1617
/// A Schnorr protocol proving knowledge some discrete logarithm relation.
1718
///
1819
/// The specific proof instance is defined by a [`GroupMorphismPreimage`] over a group `G`.
19-
pub struct SchnorrProtocol<G: Group + GroupEncoding + GroupSerialisation>(
20+
pub struct SchnorrProtocol<G: Group + GroupEncoding>(
2021
pub GroupMorphismPreimage<G>,
2122
);
2223

2324
impl<G> SigmaProtocol for SchnorrProtocol<G>
2425
where
25-
G: Group + GroupEncoding + GroupSerialisation,
26+
G: Group + GroupEncoding,
2627
{
2728
type Commitment = Vec<G>;
2829
type ProverState = (Vec<<G as Group>::Scalar>, Vec<<G as Group>::Scalar>);
@@ -94,12 +95,12 @@ where
9495

9596
// Serialize commitments
9697
for commit in commitment.iter().take(commit_nb) {
97-
bytes.extend_from_slice(&G::serialize_element(commit));
98+
bytes.extend_from_slice(&serialize_element(commit));
9899
}
99100

100101
// Serialize responses
101102
for response in response.iter().take(response_nb) {
102-
bytes.extend_from_slice(&G::serialize_scalar(response));
103+
bytes.extend_from_slice(&serialize_scalar::<G>(response));
103104
}
104105
Ok(bytes)
105106
}
@@ -130,7 +131,7 @@ where
130131
let end = start + commit_size;
131132

132133
let slice = &data[start..end];
133-
let elem = G::deserialize_element(slice).ok_or(ProofError::GroupSerialisationFailure)?;
134+
let elem = deserialize_element(slice).ok_or(ProofError::GroupSerialisationFailure)?;
134135
commitments.push(elem);
135136
}
136137

@@ -139,7 +140,7 @@ where
139140
let end = start + response_size;
140141

141142
let slice = &data[start..end];
142-
let scalar = G::deserialize_scalar(slice).ok_or(ProofError::GroupSerialisationFailure)?;
143+
let scalar = deserialize_scalar::<G>(slice).ok_or(ProofError::GroupSerialisationFailure)?;
143144
responses.push(scalar);
144145
}
145146

@@ -149,7 +150,7 @@ where
149150

150151
impl<G> CompactProtocol for SchnorrProtocol<G>
151152
where
152-
G: Group + GroupEncoding + GroupSerialisation,
153+
G: Group + GroupEncoding,
153154
{
154155
fn get_commitment(
155156
&self,
@@ -177,11 +178,11 @@ where
177178
let response_nb = self.0.morphism.num_scalars;
178179

179180
// Serialize challenge
180-
bytes.extend_from_slice(&G::serialize_scalar(challenge));
181+
bytes.extend_from_slice(&serialize_scalar::<G>(challenge));
181182

182183
// Serialize responses
183184
for response in response.iter().take(response_nb) {
184-
bytes.extend_from_slice(&G::serialize_scalar(response));
185+
bytes.extend_from_slice(&serialize_scalar::<G>(response));
185186
}
186187
Ok(bytes)
187188
}
@@ -205,14 +206,14 @@ where
205206
let mut responses: Self::Response = Vec::new();
206207

207208
let slice = &data[0..response_size];
208-
let challenge = G::deserialize_scalar(slice).ok_or(ProofError::GroupSerialisationFailure)?;
209+
let challenge = deserialize_scalar::<G>(slice).ok_or(ProofError::GroupSerialisationFailure)?;
209210

210211
for i in 0..response_nb {
211212
let start = (i + 1) * response_size;
212213
let end = start + response_size;
213214

214215
let slice = &data[start..end];
215-
let scalar = G::deserialize_scalar(slice).ok_or(ProofError::GroupSerialisationFailure)?;
216+
let scalar = deserialize_scalar::<G>(slice).ok_or(ProofError::GroupSerialisationFailure)?;
216217
responses.push(scalar);
217218
}
218219

src/serialisation.rs

Lines changed: 0 additions & 57 deletions
This file was deleted.

0 commit comments

Comments
 (0)