Skip to content

Commit 7e0ac0a

Browse files
feat(rust): Implement CBOR encoding/decoding for the catalyst-voting types (#705)
* Implement encoding and decoding for Ciphertext and GroupElement * Implement Hash for Ciphertext * Use proptest * Fix Clippy * Implement encode decode for UnitVectorProof * Remove proptest files * Implement Hash for more types * Partially remove old serialization * Fix typo * Fix UnitVectorProof encoding * Apply review comments
1 parent 4879953 commit 7e0ac0a

File tree

9 files changed

+344
-201
lines changed

9 files changed

+344
-201
lines changed

rust/catalyst-voting/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@ curve25519-dalek = { version = "4.1.3", features = ["digest", "rand_core"] }
2424
ed25519-dalek = { version = "2.1.1", features = ["rand_core"] }
2525
blake2b_simd = "1.0.2"
2626
rayon = "1.10.0"
27+
minicbor = { version = "0.25.1", features = ["alloc", "derive", "half"] }
28+
29+
cbork-utils = { version = "0.0.2", git = "https://github.com/input-output-hk/catalyst-libs.git", tag = "cbork-utils-v0.0.2" }
2730

2831
[dev-dependencies]
2932
criterion = "0.5.1"
30-
proptest = { version = "1.5.0" }
33+
proptest = { version = "1.6.0", features = ["attr-macro"] }
3134
# Potentially it could be replaced with using `proptest::property_test` attribute macro,
3235
# after this PR will be merged https://github.com/proptest-rs/proptest/pull/523
3336
test-strategy = "0.4.0"

rust/catalyst-voting/src/crypto/elgamal/decoding.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//! Elgamal objects decoding implementation
22
33
use anyhow::anyhow;
4+
use cbork_utils::decode_helper::decode_array_len;
5+
use minicbor::{Decode, Decoder, Encode, Encoder, encode::Write};
46

57
use super::{Ciphertext, GroupElement};
68

@@ -34,6 +36,35 @@ impl Ciphertext {
3436
}
3537
}
3638

39+
impl Encode<()> for Ciphertext {
40+
fn encode<W: Write>(
41+
&self,
42+
e: &mut Encoder<W>,
43+
ctx: &mut (),
44+
) -> Result<(), minicbor::encode::Error<W::Error>> {
45+
e.array(2)?;
46+
self.0.encode(e, ctx)?;
47+
self.1.encode(e, ctx)
48+
}
49+
}
50+
51+
impl Decode<'_, ()> for Ciphertext {
52+
fn decode(
53+
d: &mut Decoder<'_>,
54+
ctx: &mut (),
55+
) -> Result<Self, minicbor::decode::Error> {
56+
let len = decode_array_len(d, "Ciphertext")?;
57+
if len != 2 {
58+
return Err(minicbor::decode::Error::message(format!(
59+
"Unexpected Ciphertext array length: {len}, expected 2"
60+
)));
61+
}
62+
let c1 = GroupElement::decode(d, ctx)?;
63+
let c2 = GroupElement::decode(d, ctx)?;
64+
Ok(Self(c1, c2))
65+
}
66+
}
67+
3768
#[cfg(test)]
3869
mod tests {
3970
use test_strategy::proptest;
@@ -46,4 +77,14 @@ mod tests {
4677
let c2 = Ciphertext::from_bytes(&bytes).unwrap();
4778
assert_eq!(c1, c2);
4879
}
80+
81+
#[proptest]
82+
fn cbor_roundtrip(original: Ciphertext) {
83+
let mut buffer = Vec::new();
84+
original
85+
.encode(&mut Encoder::new(&mut buffer), &mut ())
86+
.unwrap();
87+
let decoded = Ciphertext::decode(&mut Decoder::new(&buffer), &mut ()).unwrap();
88+
assert_eq!(original, decoded);
89+
}
4990
}

rust/catalyst-voting/src/crypto/elgamal/mod.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@ use std::ops::{Add, Mul};
88
use crate::crypto::group::{GroupElement, Scalar};
99

1010
/// `ElGamal` ciphertext, encrypted message with the public key.
11-
#[derive(Debug, Clone, PartialEq, Eq)]
11+
///
12+
/// The CBOR CDDL schema:
13+
/// ```cddl
14+
/// elgamal-ristretto255-encrypted-choice = [
15+
/// c1: elgamal-ristretto255-group-element
16+
/// c2: elgamal-ristretto255-group-element
17+
/// ]
18+
/// ```
19+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1220
#[must_use]
1321
pub struct Ciphertext(GroupElement, GroupElement);
1422

@@ -19,6 +27,14 @@ impl Ciphertext {
1927
Ciphertext(GroupElement::zero(), GroupElement::zero())
2028
}
2129

30+
/// Creates a `Ciphertext` instance from the given elements.
31+
pub fn from_elements(
32+
first: GroupElement,
33+
second: GroupElement,
34+
) -> Self {
35+
Self(first, second)
36+
}
37+
2238
/// Get the first element of the `Ciphertext`.
2339
pub fn first(&self) -> &GroupElement {
2440
&self.0

rust/catalyst-voting/src/crypto/group/ristretto255/decoding.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use anyhow::anyhow;
44
use curve25519_dalek::{ristretto::CompressedRistretto, scalar::Scalar as IScalar};
5+
use minicbor::{Decode, Decoder, Encode, Encoder, encode::Write};
56

67
use super::{GroupElement, Scalar};
78

@@ -48,6 +49,46 @@ impl GroupElement {
4849
}
4950
}
5051

52+
impl Encode<()> for Scalar {
53+
fn encode<W: Write>(
54+
&self,
55+
e: &mut Encoder<W>,
56+
ctx: &mut (),
57+
) -> Result<(), minicbor::encode::Error<W::Error>> {
58+
self.to_bytes().encode(e, ctx)
59+
}
60+
}
61+
62+
impl Decode<'_, ()> for Scalar {
63+
fn decode(
64+
d: &mut Decoder<'_>,
65+
ctx: &mut (),
66+
) -> Result<Self, minicbor::decode::Error> {
67+
let bytes = <[u8; Scalar::BYTES_SIZE]>::decode(d, ctx)?;
68+
Self::from_bytes(bytes).map_err(minicbor::decode::Error::message)
69+
}
70+
}
71+
72+
impl Encode<()> for GroupElement {
73+
fn encode<W: Write>(
74+
&self,
75+
e: &mut Encoder<W>,
76+
ctx: &mut (),
77+
) -> Result<(), minicbor::encode::Error<W::Error>> {
78+
self.to_bytes().encode(e, ctx)
79+
}
80+
}
81+
82+
impl Decode<'_, ()> for GroupElement {
83+
fn decode(
84+
d: &mut Decoder<'_>,
85+
ctx: &mut (),
86+
) -> Result<Self, minicbor::decode::Error> {
87+
let compressed = <[u8; GroupElement::BYTES_SIZE]>::decode(d, ctx)?;
88+
Self::from_bytes(&compressed).map_err(minicbor::decode::Error::message)
89+
}
90+
}
91+
5192
#[cfg(test)]
5293
mod tests {
5394
use test_strategy::proptest;
@@ -67,4 +108,24 @@ mod tests {
67108
let ge2 = GroupElement::from_bytes(&bytes).unwrap();
68109
assert_eq!(ge1, ge2);
69110
}
111+
112+
#[proptest]
113+
fn scalar_cbor_roundtrip(original: Scalar) {
114+
let mut buffer = Vec::new();
115+
original
116+
.encode(&mut Encoder::new(&mut buffer), &mut ())
117+
.unwrap();
118+
let decoded = Scalar::decode(&mut Decoder::new(&buffer), &mut ()).unwrap();
119+
assert_eq!(original, decoded);
120+
}
121+
122+
#[proptest]
123+
fn group_element_cbor_roundtrip(original: GroupElement) {
124+
let mut buffer = Vec::new();
125+
original
126+
.encode(&mut Encoder::new(&mut buffer), &mut ())
127+
.unwrap();
128+
let decoded = GroupElement::decode(&mut Decoder::new(&buffer), &mut ()).unwrap();
129+
assert_eq!(original, decoded);
130+
}
70131
}

rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,21 @@ use crate::crypto::{
2222
};
2323

2424
/// Ristretto group scalar.
25-
#[derive(Debug, Clone, PartialEq, Eq)]
25+
///
26+
/// The CBOR CDDL schema:
27+
/// ```cddl
28+
/// zkproof-ed25519-scalar = bytes .size 32
29+
/// ```
30+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2631
#[must_use]
2732
pub struct Scalar(IScalar);
2833

2934
/// Ristretto group element.
35+
///
36+
/// The CBOR CDDL schema:
37+
/// ```cddl
38+
/// elgamal-ristretto255-group-element = bytes .size 32
39+
/// ```
3040
#[derive(Debug, Clone, PartialEq, Eq)]
3141
#[must_use]
3242
pub struct GroupElement(RistrettoPoint);

0 commit comments

Comments
 (0)