Skip to content

Commit

Permalink
Added serde support
Browse files Browse the repository at this point in the history
In addition:
- Added `ConstantTimeEq`, `PartialEq`, and `dusk_bytes::Serializable` impls for `AffineNielsPoint` and `ExtendedNielsPoint`. Added `From<ExtendedPoint>` impl for `SubgroupPoint`
- Added serde feature tests to CI
- Added tests for `Serializable` impls of `ExtendedNielsPoint` and `AffineNielsPoint`
  • Loading branch information
d-sonuga committed Dec 5, 2024
1 parent 40e9c78 commit 85991d8
Show file tree
Hide file tree
Showing 6 changed files with 371 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/dusk_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,9 @@ jobs:
uses: dusk-network/.github/.github/workflows/run-tests.yml@main
with:
test_flags: --no-default-features

test_serde:
name: Serde tests
uses: dusk-network/.github/.github/workflows/run-tests.yml@main
with:
test_flags: --features=serde
13 changes: 13 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@ optional = true
default-features = false
# End Dusk dependendencies

[dependencies.serde]
version = "1.0"
optional = true

[dependencies.serde_json]
version = "1.0"
optional = true

[dependencies.hex]
version = "0.4"
optional = true

[dev-dependencies]
criterion = "0.3"
csv = ">= 1.0, < 1.2" # csv 1.2 has MSRV 1.60
Expand All @@ -86,6 +98,7 @@ default = ["alloc", "bits"]
alloc = ["ff/alloc", "group/alloc"]
bits = ["ff/bits"]
rkyv-impl = ["bytecheck", "dusk-bls12_381/rkyv-impl", "rkyv"]
serde = ["dep:serde", "serde_json", "dusk-bls12_381/serde", "hex"]

[[bench]]
name = "fq_bench"
Expand Down
101 changes: 101 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,15 @@ extern crate alloc;
#[macro_use]
extern crate std;

#[cfg(feature = "serde")]
mod serde_support;

use bitvec::{order::Lsb0, view::AsBits};
use core::borrow::Borrow;
use core::fmt;
use core::iter::Sum;
use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use dusk_bytes::{DeserializableSlice, Serializable};
use ff::{BatchInverter, Field};
use group::{
cofactor::{CofactorCurve, CofactorCurveAffine, CofactorGroup},
Expand Down Expand Up @@ -361,6 +365,49 @@ impl ConditionallySelectable for AffineNielsPoint {
}
}

impl ConstantTimeEq for AffineNielsPoint {
fn ct_eq(&self, other: &Self) -> Choice {
self.t2d.ct_eq(&other.t2d)
& self.v_minus_u.ct_eq(&other.v_minus_u)
& self.v_plus_u.ct_eq(&other.v_plus_u)
}
}

impl PartialEq for AffineNielsPoint {
fn eq(&self, other: &Self) -> bool {
bool::from(self.ct_eq(other))
}
}

impl Serializable<{ Fq::SIZE * 3 }> for AffineNielsPoint {
type Error = dusk_bytes::Error;

fn to_bytes(&self) -> [u8; 96] {
let v_plus_u = self.v_plus_u.to_bytes();
let v_minus_u = self.v_minus_u.to_bytes();
let t2d = self.t2d.to_bytes();

let mut bytes = [0; Self::SIZE];

bytes[..32].copy_from_slice(&v_plus_u);
bytes[32..64].copy_from_slice(&v_minus_u);
bytes[64..].copy_from_slice(&t2d);

bytes
}

fn from_bytes(bytes: &[u8; 96]) -> Result<Self, Self::Error> {
let v_plus_u = Fq::from_slice(&bytes[..32])?;
let v_minus_u = Fq::from_slice(&bytes[32..64])?;
let t2d = Fq::from_slice(&bytes[64..])?;
Ok(Self {
v_plus_u,
v_minus_u,
t2d,
})
}
}

/// This is a pre-processed version of an extended point `(U, V, Z, T1, T2)`
/// in the form `(V + U, V - U, Z, T1 * T2 * 2d)`.
#[derive(Clone, Copy, Debug)]
Expand Down Expand Up @@ -444,6 +491,54 @@ impl<'a, 'b> Mul<&'b Fr> for &'a ExtendedNielsPoint {
}
}

impl Serializable<{ Fq::SIZE * 4 }> for ExtendedNielsPoint {
type Error = dusk_bytes::Error;

fn to_bytes(&self) -> [u8; 128] {
let v_plus_u = self.v_plus_u.to_bytes();
let v_minus_u = self.v_minus_u.to_bytes();
let t2d = self.t2d.to_bytes();
let z = self.z.to_bytes();

let mut bytes = [0; Self::SIZE];

bytes[..32].copy_from_slice(&v_plus_u);
bytes[32..64].copy_from_slice(&v_minus_u);
bytes[64..96].copy_from_slice(&t2d);
bytes[96..].copy_from_slice(&z);

bytes
}

fn from_bytes(bytes: &[u8; 128]) -> Result<Self, Self::Error> {
let v_plus_u = Fq::from_slice(&bytes[..32])?;
let v_minus_u = Fq::from_slice(&bytes[32..64])?;
let t2d = Fq::from_slice(&bytes[64..96])?;
let z = Fq::from_slice(&bytes[96..])?;
Ok(Self {
v_plus_u,
v_minus_u,
t2d,
z,
})
}
}

impl ConstantTimeEq for ExtendedNielsPoint {
fn ct_eq(&self, other: &Self) -> Choice {
self.t2d.ct_eq(&other.t2d)
& self.v_minus_u.ct_eq(&other.v_minus_u)
& self.v_plus_u.ct_eq(&other.v_plus_u)
& self.z.ct_eq(&other.z)
}
}

impl PartialEq for ExtendedNielsPoint {
fn eq(&self, other: &Self) -> bool {
bool::from(self.ct_eq(other))
}
}

impl_binops_multiplicative_mixed!(ExtendedNielsPoint, Fr, ExtendedPoint);

/// `d = -(10240/10241)`
Expand Down Expand Up @@ -1214,6 +1309,12 @@ impl From<SubgroupPoint> for ExtendedPoint {
}
}

impl From<ExtendedPoint> for SubgroupPoint {
fn from(val: ExtendedPoint) -> Self {
Self(val)
}
}

impl<'a> From<&'a SubgroupPoint> for &'a ExtendedPoint {
fn from(val: &'a SubgroupPoint) -> &'a ExtendedPoint {
&val.0
Expand Down
167 changes: 167 additions & 0 deletions src/serde_support.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
extern crate alloc;

use alloc::format;
use alloc::string::String;

use dusk_bytes::Serializable;
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};

use crate::{
AffineNielsPoint, AffinePoint, ExtendedNielsPoint, ExtendedPoint, Fr,
SubgroupPoint,
};

impl Serialize for AffinePoint {
fn serialize<S: Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
let s = hex::encode(self.to_bytes());
serializer.serialize_str(&s)
}
}

impl<'de> Deserialize<'de> for AffinePoint {
fn deserialize<D: Deserializer<'de>>(
deserializer: D,
) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
match hex::decode(s) {
Ok(bytes) => {
let bytes: [u8; 32] = bytes.try_into().map_err(|_| Error::custom(format!("Failed to deserialize AffinePoint: invalid byte length")))?;
AffinePoint::from_bytes(bytes).into_option().ok_or(
Error::custom(format!(
"Failed to deserialize AffinePoint: invalid AffinePoint"
)),
)
}
Err(e) => Err(Error::custom(format!(
"Failed to deserialize AffinePoint with error: {e}"
))),
}
}
}

impl Serialize for ExtendedPoint {
fn serialize<S: Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
AffinePoint::from(self).serialize(serializer)
}
}

impl<'de> Deserialize<'de> for ExtendedPoint {
fn deserialize<D: Deserializer<'de>>(
deserializer: D,
) -> Result<Self, D::Error> {
AffinePoint::deserialize(deserializer).map(|point| point.into())
}
}

impl Serialize for Fr {
fn serialize<S: Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
let s = hex::encode(self.to_bytes());
serializer.serialize_str(&s)
}
}

impl<'de> Deserialize<'de> for Fr {
fn deserialize<D: Deserializer<'de>>(
deserializer: D,
) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
match hex::decode(s) {
Ok(bytes) => {
let bytes: [u8; 32] = bytes.try_into().map_err(|_| {
Error::custom(format!(
"Failed to deserialize Fr: invalid byte length"
))
})?;
Fr::from_bytes(&bytes).into_option().ok_or(Error::custom(
format!("Failed to deserialize Fr: invalid Fr"),
))
}
Err(e) => Err(Error::custom(format!(
"Failed to deserialize Fr with error: {e}"
))),
}
}
}

impl Serialize for AffineNielsPoint {
fn serialize<S: Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
let s = hex::encode(self.to_bytes());
serializer.serialize_str(&s)
}
}

impl<'de> Deserialize<'de> for AffineNielsPoint {
fn deserialize<D: Deserializer<'de>>(
deserializer: D,
) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
match hex::decode(s) {
Ok(bytes) => {
let bytes: [u8; AffineNielsPoint::SIZE] = bytes.try_into().map_err(|_| Error::custom(format!("Failed to deserialize AffineNielsPoint: invalid byte length")))?;
AffineNielsPoint::from_bytes(&bytes)
.map_err(|_| Error::custom(format!("Failed to deserialize AffineNielsPoint: invalid AffineNielsPoint")))
}
Err(e) => Err(Error::custom(format!(
"Failed to deserialize AffineNielsPoint with error: {e}"
))),
}
}
}

impl Serialize for ExtendedNielsPoint {
fn serialize<S: Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
let s = hex::encode(self.to_bytes());
serializer.serialize_str(&s)
}
}

impl<'de> Deserialize<'de> for ExtendedNielsPoint {
fn deserialize<D: Deserializer<'de>>(
deserializer: D,
) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
match hex::decode(s) {
Ok(bytes) => {
let bytes: [u8; 128] = bytes.try_into().map_err(|_| Error::custom(format!("Failed to deserialize ExtendedNielsPoint: invalid byte length")))?;
ExtendedNielsPoint::from_bytes(&bytes)
.map_err(|_| Error::custom(format!("Failed to deserialize ExtendedNielsPoint: invalid ExtendedNielsPoint")))
}
Err(e) => Err(Error::custom(format!(
"Failed to deserialize ExtendedNielsPoint with error: {e}"
))),
}
}
}

impl Serialize for SubgroupPoint {
fn serialize<S: Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}

impl<'de> Deserialize<'de> for SubgroupPoint {
fn deserialize<D: Deserializer<'de>>(
deserializer: D,
) -> Result<Self, D::Error> {
ExtendedPoint::deserialize(deserializer)
.map(|point| SubgroupPoint::from(point))
}
}
20 changes: 20 additions & 0 deletions tests/points.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use dusk_bytes::Serializable;
use dusk_jubjub::{AffineNielsPoint, ExtendedNielsPoint};

#[test]
fn affine_niels_point_encoding() {
let point = AffineNielsPoint::identity();
assert_eq!(
point,
AffineNielsPoint::from_bytes(&point.to_bytes()).unwrap()
);
}

#[test]
fn extended_niels_point_encoding() {
let point = ExtendedNielsPoint::identity();
assert_eq!(
point,
ExtendedNielsPoint::from_bytes(&point.to_bytes()).unwrap()
);
}
Loading

0 comments on commit 85991d8

Please sign in to comment.