Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rust/catalyst-voting): Jormungandr transaction serde #58

Merged
merged 21 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from 19 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
2 changes: 1 addition & 1 deletion rust/catalyst-voting/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ license.workspace = true
workspace = true

[dependencies]
thiserror = "1.0.64"
anyhow = "1.0.89"
rand_core = "0.6.4"
curve25519-dalek = { version = "4.1.3", features = ["digest"] }
blake2b_simd = "1.0.2"
Expand Down
36 changes: 15 additions & 21 deletions rust/catalyst-voting/src/crypto/babystep_giantstep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

use std::collections::HashMap;

use anyhow::{bail, ensure};

use crate::crypto::group::{GroupElement, Scalar};

/// Default balance value.
Expand All @@ -22,16 +24,6 @@ pub struct BabyStepGiantStep {
giant_step: GroupElement,
}

#[derive(thiserror::Error, Debug)]
pub enum BabyStepError {
/// Invalid max value or balance
#[error("Maximum value and balance must be greater than zero, provided max value: {0} and balance: {1}.")]
InvalidMaxValueOrBalance(u64, u64),
/// Max value exceeded
#[error("Max log value exceeded. Means that the actual discrete log for the provided group element is higher than the provided `max_log_value`.")]
MaxLogExceeded,
}

impl BabyStepGiantStep {
/// Creates a new setup for the baby-step giant-step algorithm.
///
Expand All @@ -47,16 +39,15 @@ impl BabyStepGiantStep {
/// `baby_step_giant_step` function for the same `max_value`.
///
/// # Errors
/// - `BabyStepError`
pub fn new(max_log_value: u64, balance: Option<u64>) -> Result<Self, BabyStepError> {
/// - Maximum value and balance must be greater than zero.
pub fn new(max_log_value: u64, balance: Option<u64>) -> anyhow::Result<Self> {
let balance = balance.unwrap_or(DEFAULT_BALANCE);

if balance == 0 || max_log_value == 0 {
return Err(BabyStepError::InvalidMaxValueOrBalance(
max_log_value,
balance,
));
}
ensure!(
balance != 0 && max_log_value != 0,
"Maximum value and balance must be greater than zero,
provided max value: {max_log_value} and balance: {balance}."
);

#[allow(
clippy::cast_possible_truncation,
Expand Down Expand Up @@ -85,18 +76,21 @@ impl BabyStepGiantStep {
/// Solve the discrete log using baby step giant step algorithm.
///
/// # Errors
/// - `BabyStepError`
pub fn discrete_log(&self, mut point: GroupElement) -> Result<u64, BabyStepError> {
/// - Max log value exceeded.
pub fn discrete_log(&self, mut point: GroupElement) -> anyhow::Result<u64> {
for baby_step in 0..=self.baby_step_size {
if let Some(x) = self.table.get(&point) {
let r = baby_step * self.baby_step_size + x;
return Ok(r);
}
point = &point + &self.giant_step;
}

// If we get here, the point is not in the table
// So we exceeded the maximum value of the discrete log
Err(BabyStepError::MaxLogExceeded)
bail!("Max log value exceeded.
Means that the actual discrete log for the provided group element is higher than the provided `max_log_value`."
)
}
}

Expand Down
96 changes: 96 additions & 0 deletions rust/catalyst-voting/src/crypto/elgamal/decoding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//! Elgamal objects decoding implementation

use anyhow::anyhow;

use super::{Ciphertext, GroupElement, PublicKey, Scalar, SecretKey};

impl PublicKey {
/// `PublicKey` bytes size
pub const BYTES_SIZE: usize = GroupElement::BYTES_SIZE;

/// Convert this `PublicKey` to its underlying sequence of bytes.
#[must_use]
pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] {
self.0.to_bytes()
}

/// Attempt to construct a `PublicKey` from a byte representation.
///
/// # Errors
/// - Cannot decode group element field.
pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result<Self> {
GroupElement::from_bytes(bytes).map(Self)
}
}

impl SecretKey {
/// `SecretKey` bytes size
pub const BYTES_SIZE: usize = Scalar::BYTES_SIZE;

/// Convert this `SecretKey` to its underlying sequence of bytes.
#[must_use]
pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] {
self.0.to_bytes()
}

/// Attempt to construct a `SecretKey` from a byte representation.
///
/// # Errors
/// - Cannot decode scalar field.
pub fn from_bytes(bytes: [u8; Self::BYTES_SIZE]) -> anyhow::Result<Self> {
Scalar::from_bytes(bytes).map(Self)
}
}

impl Ciphertext {
/// `Ciphertext` bytes size
pub const BYTES_SIZE: usize = GroupElement::BYTES_SIZE * 2;

/// Convert this `Ciphertext` to its underlying sequence of bytes.
pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] {
let mut res = [0; Self::BYTES_SIZE];
res[0..32].copy_from_slice(&self.0.to_bytes());
res[32..64].copy_from_slice(&self.1.to_bytes());
res
}

/// Attempt to construct a `Ciphertext` from a byte representation.
///
/// # Errors
/// - Cannot decode group element field.
#[allow(clippy::unwrap_used)]
pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result<Self> {
Ok(Self(
GroupElement::from_bytes(bytes[0..32].try_into().unwrap())
.map_err(|_| anyhow!("Cannot decode first group element field."))?,
GroupElement::from_bytes(bytes[32..64].try_into().unwrap())
.map_err(|_| anyhow!("Cannot decode second group element field."))?,
))
}
}

#[cfg(test)]
mod tests {
use test_strategy::proptest;

use super::*;

#[proptest]
fn keys_to_bytes_from_bytes_test(s1: SecretKey) {
let bytes = s1.to_bytes();
let s2 = SecretKey::from_bytes(bytes).unwrap();
assert_eq!(s1, s2);

let p1 = s1.public_key();
let bytes = p1.to_bytes();
let p2 = PublicKey::from_bytes(&bytes).unwrap();
assert_eq!(p1, p2);
}

#[proptest]
fn ciphertext_to_bytes_from_bytes_test(c1: Ciphertext) {
let bytes = c1.to_bytes();
let c2 = Ciphertext::from_bytes(&bytes).unwrap();
assert_eq!(c1, c2);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Implementation of the lifted ``ElGamal`` crypto system, and combine with `ChaCha`
//! stream cipher to produce a hybrid encryption scheme.

mod decoding;

use std::ops::{Add, Deref, Mul};

use rand_core::CryptoRngCore;
Expand Down Expand Up @@ -116,6 +118,17 @@ mod tests {
}
}

impl Arbitrary for Ciphertext {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;

fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
any::<(GroupElement, GroupElement)>()
.prop_map(|(g1, g2)| Ciphertext(g1, g2))
.boxed()
}
}

#[proptest]
fn ciphertext_add_test(e1: Scalar, e2: Scalar, e3: Scalar, e4: Scalar) {
let g1 = GroupElement::GENERATOR.mul(&e1);
Expand Down
71 changes: 71 additions & 0 deletions rust/catalyst-voting/src/crypto/group/ristretto255/decoding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//! ristretto255 objects decoding implementation

use anyhow::anyhow;
use curve25519_dalek::{ristretto::CompressedRistretto, scalar::Scalar as IScalar};

use super::{GroupElement, Scalar};

impl Scalar {
/// `Scalar` bytes size
pub const BYTES_SIZE: usize = 32;

/// Attempt to construct a `Scalar` from a canonical byte representation.
///
/// # Errors
/// - Cannot decode scalar.
pub fn from_bytes(bytes: [u8; Self::BYTES_SIZE]) -> anyhow::Result<Scalar> {
IScalar::from_canonical_bytes(bytes)
.map(Scalar)
.into_option()
.ok_or(anyhow!("Cannot decode scalar."))
}

/// Convert this `Scalar` to its underlying sequence of bytes.
pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] {
self.0.to_bytes()
}
}

impl GroupElement {
/// `Scalar` bytes size
pub const BYTES_SIZE: usize = 32;

/// Attempt to construct a `Scalar` from a compressed value byte representation.
///
/// # Errors
/// - Cannot decode group element.
pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result<Self> {
Ok(GroupElement(
CompressedRistretto::from_slice(bytes)?
.decompress()
.ok_or(anyhow!("Cannot decode group element."))?,
))
}

/// Convert this `GroupElement` to its underlying sequence of bytes.
/// Always encode the compressed value.
pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] {
self.0.compress().to_bytes()
}
}

#[cfg(test)]
mod tests {
use test_strategy::proptest;

use super::*;

#[proptest]
fn scalar_to_bytes_from_bytes_test(e1: Scalar) {
let bytes = e1.to_bytes();
let e2 = Scalar::from_bytes(bytes).unwrap();
assert_eq!(e1, e2);
}

#[proptest]
fn group_element_to_bytes_from_bytes_test(ge1: GroupElement) {
let bytes = ge1.to_bytes();
let ge2 = GroupElement::from_bytes(&bytes).unwrap();
assert_eq!(ge1, ge2);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

// cspell: words BASEPOINT

mod decoding;

use std::{
hash::Hash,
ops::{Add, Mul, Sub},
Expand All @@ -10,7 +12,7 @@ use std::{
use curve25519_dalek::{
constants::{RISTRETTO_BASEPOINT_POINT, RISTRETTO_BASEPOINT_TABLE},
digest::{consts::U64, Digest},
ristretto::{CompressedRistretto, RistrettoPoint as Point},
ristretto::RistrettoPoint as Point,
scalar::Scalar as IScalar,
traits::Identity,
};
Expand Down Expand Up @@ -69,16 +71,6 @@ impl Scalar {
Scalar(self.0.invert())
}

/// Convert this `Scalar` to its underlying sequence of bytes.
pub fn to_bytes(&self) -> [u8; 32] {
self.0.to_bytes()
}

/// Attempt to construct a `Scalar` from a canonical byte representation.
pub fn from_bytes(bytes: [u8; 32]) -> Option<Scalar> {
IScalar::from_canonical_bytes(bytes).map(Scalar).into()
}

/// Generate a `Scalar` from a hash digest.
pub fn from_hash<D>(hash: D) -> Scalar
where D: Digest<OutputSize = U64> {
Expand All @@ -94,19 +86,6 @@ impl GroupElement {
pub fn zero() -> Self {
GroupElement(Point::identity())
}

/// Convert this `GroupElement` to its underlying sequence of bytes.
/// Always encode the compressed value.
pub fn to_bytes(&self) -> [u8; 32] {
self.0.compress().to_bytes()
}

/// Attempt to construct a `Scalar` from a compressed value byte representation.
pub fn from_bytes(bytes: &[u8; 32]) -> Option<Self> {
Some(GroupElement(
CompressedRistretto::from_slice(bytes).ok()?.decompress()?,
))
}
}

// `std::ops` traits implementations
Expand Down Expand Up @@ -201,21 +180,6 @@ mod tests {
}
}

#[proptest]
fn scalar_to_bytes_from_bytes_test(e1: Scalar) {
let bytes = e1.to_bytes();
let e2 = Scalar::from_bytes(bytes).unwrap();
assert_eq!(e1, e2);
}

#[proptest]
fn group_element_to_bytes_from_bytes_test(e: Scalar) {
let ge1 = GroupElement::GENERATOR.mul(&e);
let bytes = ge1.to_bytes();
let ge2 = GroupElement::from_bytes(&bytes).unwrap();
assert_eq!(ge1, ge2);
}

#[proptest]
fn scalar_arithmetic_tests(e1: Scalar, e2: Scalar, e3: Scalar) {
assert_eq!(&(&e1 + &e2) + &e3, &e1 + &(&e2 + &e3));
Expand Down
12 changes: 6 additions & 6 deletions rust/catalyst-voting/src/crypto/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Crypto primitives which are used by voting protocol.

pub(crate) mod babystep_giantstep;
pub(crate) mod elgamal;
pub(crate) mod group;
pub(crate) mod hash;
pub(crate) mod zk_dl_equality;
pub(crate) mod zk_unit_vector;
pub mod babystep_giantstep;
pub mod elgamal;
pub mod group;
pub mod hash;
pub mod zk_dl_equality;
pub mod zk_unit_vector;
Loading