Skip to content

Commit

Permalink
feat(rust/catalyst-voting): Jormungandr tx building (#60)
Browse files Browse the repository at this point in the history
* move vote protocol under the vote_protocol mod

* add jormungandr tx struct

* add CipherText serde

* add EncryptedVote decoding functionality

* add new deserializers

* refactor

* wip

* wip

* replace thiserror with anyhow

* move decoding functionalities to separate module

* wip

* add test

* fix

* refactor

* fix tests

* wip

* wip

* fix spelling

* add v1::Tx generation functions

* add test

* refactor, add ElectionPublicKey, ElectionSecretKey

* fix

* fix with must_use

* fix docs

* refactor digest crate imports

* add ed25519 impl

* add ed25519 decoding functionality

* update v1::Tx

* add Tx signing

* wip

* fix

* wip

* add Blake2b-256 hash impl, update v1::Tx sign

* add txs::v1 doc test

* update rust docs

* make rng optional

* wip

* update v1::Tx decoding

* add signature and proof verification

* update verification

* update decoding test

* add decrypt_vote function

* add private_choice, public_choice methods

* fix spelling
  • Loading branch information
Mr-Leshiy authored Oct 16, 2024
1 parent 78a75d5 commit 143174b
Show file tree
Hide file tree
Showing 23 changed files with 1,040 additions and 320 deletions.
1 change: 1 addition & 0 deletions .config/dictionaries/project.dic
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ CBOR
cbork
cdylib
CEST
chacha
CHAINCODE
chainsync
childs
Expand Down
4 changes: 3 additions & 1 deletion rust/catalyst-voting/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ workspace = true
[dependencies]
anyhow = "1.0.89"
rand_core = "0.6.4"
curve25519-dalek = { version = "4.1.3", features = ["digest"] }
rand_chacha = "0.3.1"
curve25519-dalek = { version = "4.1.3", features = ["digest", "rand_core"] }
ed25519-dalek = { version = "2.1.1", features = ["rand_core"] }
blake2b_simd = "1.0.2"

[dev-dependencies]
Expand Down
42 changes: 42 additions & 0 deletions rust/catalyst-voting/src/crypto/ed25519/decoding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//! `Ed25519` objects decoding implementation

use ed25519_dalek::{
Signature as Ed25519Signature, VerifyingKey, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH,
};

use super::{PublicKey, Signature};

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

/// 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 public key.
pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result<Self> {
Ok(Self(VerifyingKey::from_bytes(bytes)?))
}
}

impl Signature {
/// `Signature` bytes size
pub const BYTES_SIZE: usize = SIGNATURE_LENGTH;

/// Convert this `Signature` 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 `Signature` from a byte representation.
pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> Self {
Self(Ed25519Signature::from_bytes(bytes))
}
}
72 changes: 72 additions & 0 deletions rust/catalyst-voting/src/crypto/ed25519/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//! `EdDSA` digital signature scheme over Curve25519.

mod decoding;

use ed25519_dalek::{
ed25519::signature::Signer, Signature as Ed25519Signature, SigningKey, VerifyingKey,
};
use rand_core::CryptoRngCore;

/// `Ed25519` private key struct.
#[must_use]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PrivateKey(SigningKey);

impl PrivateKey {
/// Randomly generate the `Ed25519` private key.
pub fn random<R: CryptoRngCore>(rng: &mut R) -> Self {
Self(SigningKey::generate(rng))
}

/// Get associated `Ed25519` public key.
pub fn public_key(&self) -> PublicKey {
PublicKey(self.0.verifying_key())
}
}

/// `Ed25519` public key struct.
#[must_use]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublicKey(VerifyingKey);

/// `Ed25519` signature struct.
#[must_use]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Signature(Ed25519Signature);

/// Sign a message using the `Ed25519` private key.
pub fn sign(sk: &PrivateKey, msg: &[u8]) -> Signature {
Signature(sk.0.sign(msg))
}

/// Verify a `Ed25519` signature using the `Ed25519` public key.
#[must_use]
pub fn verify_signature(pk: &PublicKey, msg: &[u8], sig: &Signature) -> bool {
pk.0.verify_strict(msg, &sig.0).is_ok()
}

#[cfg(test)]
mod tests {
use proptest::prelude::{any, Arbitrary, BoxedStrategy, Strategy};
use test_strategy::proptest;

use super::*;

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

fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
any::<[u8; 32]>()
.prop_map(|b| PrivateKey(SigningKey::from_bytes(&b)))
.boxed()
}
}

#[proptest]
fn sign_test(private_key: PrivateKey, msg: Vec<u8>) {
let public_key = private_key.public_key();
let signature = sign(&private_key, &msg);
assert!(verify_signature(&public_key, &msg, &signature));
}
}
55 changes: 4 additions & 51 deletions rust/catalyst-voting/src/crypto/elgamal/decoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,14 @@

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)
}
}
use super::{Ciphertext, GroupElement};

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

/// Convert this `Ciphertext` to its underlying sequence of bytes.
#[must_use]
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());
Expand All @@ -58,6 +21,8 @@ impl Ciphertext {
///
/// # Errors
/// - Cannot decode group element field.
///
/// # Panics
#[allow(clippy::unwrap_used)]
pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result<Self> {
Ok(Self(
Expand All @@ -75,18 +40,6 @@ mod tests {

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();
Expand Down
78 changes: 17 additions & 61 deletions rust/catalyst-voting/src/crypto/elgamal/mod.rs
Original file line number Diff line number Diff line change
@@ -1,55 +1,17 @@
//! Implementation of the lifted ``ElGamal`` crypto system, and combine with `ChaCha`
//! 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;
use std::ops::{Add, Mul};

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

/// ``ElGamal`` secret key.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SecretKey(Scalar);

/// ``ElGamal`` public key.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PublicKey(GroupElement);

/// ``ElGamal`` ciphertext, encrypted message with the public key.
/// `ElGamal` ciphertext, encrypted message with the public key.
#[derive(Debug, Clone, PartialEq, Eq)]
#[must_use]
pub struct Ciphertext(GroupElement, GroupElement);

impl Deref for SecretKey {
type Target = Scalar;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl Deref for PublicKey {
type Target = GroupElement;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl SecretKey {
/// Generate a random `SecretKey` value from the random number generator.
pub fn random<R: CryptoRngCore>(rng: &mut R) -> Self {
Self(Scalar::random(rng))
}

/// Generate a corresponding `PublicKey`.
#[must_use]
pub fn public_key(&self) -> PublicKey {
PublicKey(GroupElement::GENERATOR.mul(&self.0))
}
}

impl Ciphertext {
/// Generate a zero `Ciphertext`.
/// The same as encrypt a `Scalar::zero()` message and `Scalar::zero()` randomness.
Expand All @@ -68,19 +30,24 @@ impl Ciphertext {
}
}

/// Generate `ElGamal` public key from the secret key value.
pub fn generate_public_key(secret_key: &Scalar) -> GroupElement {
GroupElement::GENERATOR.mul(secret_key)
}

/// Given a `message` represented as a `Scalar`, return a ciphertext using the
/// lifted ``ElGamal`` mechanism.
/// lifted `ElGamal` mechanism.
/// Returns a ciphertext of type `Ciphertext`.
pub fn encrypt(message: &Scalar, public_key: &PublicKey, randomness: &Scalar) -> Ciphertext {
pub fn encrypt(message: &Scalar, public_key: &GroupElement, randomness: &Scalar) -> Ciphertext {
let e1 = GroupElement::GENERATOR.mul(randomness);
let e2 = &GroupElement::GENERATOR.mul(message) + &public_key.0.mul(randomness);
let e2 = &GroupElement::GENERATOR.mul(message) + &public_key.mul(randomness);
Ciphertext(e1, e2)
}

/// Decrypt ``ElGamal`` `Ciphertext`, returns the original message represented as a
/// Decrypt `ElGamal` `Ciphertext`, returns the original message represented as a
/// `GroupElement`.
pub fn decrypt(cipher: &Ciphertext, secret_key: &SecretKey) -> GroupElement {
&(&cipher.0 * &secret_key.0.negate()) + &cipher.1
pub fn decrypt(cipher: &Ciphertext, secret_key: &Scalar) -> GroupElement {
&(&cipher.0 * &secret_key.negate()) + &cipher.1
}

impl Mul<&Scalar> for &Ciphertext {
Expand Down Expand Up @@ -109,15 +76,6 @@ mod tests {

use super::*;

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

fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
any::<Scalar>().prop_map(SecretKey).boxed()
}
}

impl Arbitrary for Ciphertext {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
Expand Down Expand Up @@ -152,10 +110,8 @@ mod tests {
}

#[proptest]
fn elgamal_encryption_decryption_test(
secret_key: SecretKey, message: Scalar, randomness: Scalar,
) {
let public_key = secret_key.public_key();
fn elgamal_encryption_decryption_test(secret_key: Scalar, message: Scalar, randomness: Scalar) {
let public_key = generate_public_key(&secret_key);

let cipher = encrypt(&message, &public_key, &randomness);
let decrypted = decrypt(&cipher, &secret_key);
Expand Down
21 changes: 14 additions & 7 deletions rust/catalyst-voting/src/crypto/group/ristretto255/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,23 @@ use std::{

use curve25519_dalek::{
constants::{RISTRETTO_BASEPOINT_POINT, RISTRETTO_BASEPOINT_TABLE},
digest::{consts::U64, Digest},
ristretto::RistrettoPoint as Point,
scalar::Scalar as IScalar,
traits::Identity,
RistrettoPoint,
};
use rand_core::CryptoRngCore;

use crate::crypto::hash::digest::{consts::U64, Digest};

/// Ristretto group scalar.
#[derive(Debug, Clone, PartialEq, Eq)]
#[must_use]
pub struct Scalar(IScalar);

/// Ristretto group element.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GroupElement(Point);
#[must_use]
pub struct GroupElement(RistrettoPoint);

impl From<u64> for Scalar {
fn from(value: u64) -> Self {
Expand All @@ -41,9 +44,7 @@ impl Hash for GroupElement {
impl Scalar {
/// Generate a random scalar value from the random number generator.
pub fn random<R: CryptoRngCore>(rng: &mut R) -> Self {
let mut scalar_bytes = [0u8; 64];
rng.fill_bytes(&mut scalar_bytes);
Scalar(IScalar::from_bytes_mod_order_wide(&scalar_bytes))
Scalar(IScalar::random(rng))
}

/// additive identity
Expand Down Expand Up @@ -84,7 +85,13 @@ impl GroupElement {

/// Generate a zero group element.
pub fn zero() -> Self {
GroupElement(Point::identity())
GroupElement(RistrettoPoint::identity())
}

/// Generate a `GroupElement` from a hash digest.
pub fn from_hash<D>(hash: D) -> GroupElement
where D: Digest<OutputSize = U64> + Default {
GroupElement(RistrettoPoint::from_hash(hash))
}
}

Expand Down
Loading

0 comments on commit 143174b

Please sign in to comment.