Skip to content

Commit 143174b

Browse files
authored
feat(rust/catalyst-voting): Jormungandr tx building (#60)
* 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
1 parent 78a75d5 commit 143174b

File tree

23 files changed

+1040
-320
lines changed

23 files changed

+1040
-320
lines changed

.config/dictionaries/project.dic

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ CBOR
2929
cbork
3030
cdylib
3131
CEST
32+
chacha
3233
CHAINCODE
3334
chainsync
3435
childs

rust/catalyst-voting/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ workspace = true
1313
[dependencies]
1414
anyhow = "1.0.89"
1515
rand_core = "0.6.4"
16-
curve25519-dalek = { version = "4.1.3", features = ["digest"] }
16+
rand_chacha = "0.3.1"
17+
curve25519-dalek = { version = "4.1.3", features = ["digest", "rand_core"] }
18+
ed25519-dalek = { version = "2.1.1", features = ["rand_core"] }
1719
blake2b_simd = "1.0.2"
1820

1921
[dev-dependencies]
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//! `Ed25519` objects decoding implementation
2+
3+
use ed25519_dalek::{
4+
Signature as Ed25519Signature, VerifyingKey, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH,
5+
};
6+
7+
use super::{PublicKey, Signature};
8+
9+
impl PublicKey {
10+
/// `PublicKey` bytes size
11+
pub const BYTES_SIZE: usize = PUBLIC_KEY_LENGTH;
12+
13+
/// Convert this `PublicKey` to its underlying sequence of bytes.
14+
#[must_use]
15+
pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] {
16+
self.0.to_bytes()
17+
}
18+
19+
/// Attempt to construct a `PublicKey` from a byte representation.
20+
///
21+
/// # Errors
22+
/// - Cannot decode public key.
23+
pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result<Self> {
24+
Ok(Self(VerifyingKey::from_bytes(bytes)?))
25+
}
26+
}
27+
28+
impl Signature {
29+
/// `Signature` bytes size
30+
pub const BYTES_SIZE: usize = SIGNATURE_LENGTH;
31+
32+
/// Convert this `Signature` to its underlying sequence of bytes.
33+
#[must_use]
34+
pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] {
35+
self.0.to_bytes()
36+
}
37+
38+
/// Attempt to construct a `Signature` from a byte representation.
39+
pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> Self {
40+
Self(Ed25519Signature::from_bytes(bytes))
41+
}
42+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//! `EdDSA` digital signature scheme over Curve25519.
2+
3+
mod decoding;
4+
5+
use ed25519_dalek::{
6+
ed25519::signature::Signer, Signature as Ed25519Signature, SigningKey, VerifyingKey,
7+
};
8+
use rand_core::CryptoRngCore;
9+
10+
/// `Ed25519` private key struct.
11+
#[must_use]
12+
#[derive(Debug, Clone, PartialEq, Eq)]
13+
pub struct PrivateKey(SigningKey);
14+
15+
impl PrivateKey {
16+
/// Randomly generate the `Ed25519` private key.
17+
pub fn random<R: CryptoRngCore>(rng: &mut R) -> Self {
18+
Self(SigningKey::generate(rng))
19+
}
20+
21+
/// Get associated `Ed25519` public key.
22+
pub fn public_key(&self) -> PublicKey {
23+
PublicKey(self.0.verifying_key())
24+
}
25+
}
26+
27+
/// `Ed25519` public key struct.
28+
#[must_use]
29+
#[derive(Debug, Clone, PartialEq, Eq)]
30+
pub struct PublicKey(VerifyingKey);
31+
32+
/// `Ed25519` signature struct.
33+
#[must_use]
34+
#[derive(Debug, Clone, PartialEq, Eq)]
35+
pub struct Signature(Ed25519Signature);
36+
37+
/// Sign a message using the `Ed25519` private key.
38+
pub fn sign(sk: &PrivateKey, msg: &[u8]) -> Signature {
39+
Signature(sk.0.sign(msg))
40+
}
41+
42+
/// Verify a `Ed25519` signature using the `Ed25519` public key.
43+
#[must_use]
44+
pub fn verify_signature(pk: &PublicKey, msg: &[u8], sig: &Signature) -> bool {
45+
pk.0.verify_strict(msg, &sig.0).is_ok()
46+
}
47+
48+
#[cfg(test)]
49+
mod tests {
50+
use proptest::prelude::{any, Arbitrary, BoxedStrategy, Strategy};
51+
use test_strategy::proptest;
52+
53+
use super::*;
54+
55+
impl Arbitrary for PrivateKey {
56+
type Parameters = ();
57+
type Strategy = BoxedStrategy<Self>;
58+
59+
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
60+
any::<[u8; 32]>()
61+
.prop_map(|b| PrivateKey(SigningKey::from_bytes(&b)))
62+
.boxed()
63+
}
64+
}
65+
66+
#[proptest]
67+
fn sign_test(private_key: PrivateKey, msg: Vec<u8>) {
68+
let public_key = private_key.public_key();
69+
let signature = sign(&private_key, &msg);
70+
assert!(verify_signature(&public_key, &msg, &signature));
71+
}
72+
}

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

Lines changed: 4 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,14 @@
22
33
use anyhow::anyhow;
44

5-
use super::{Ciphertext, GroupElement, PublicKey, Scalar, SecretKey};
6-
7-
impl PublicKey {
8-
/// `PublicKey` bytes size
9-
pub const BYTES_SIZE: usize = GroupElement::BYTES_SIZE;
10-
11-
/// Convert this `PublicKey` to its underlying sequence of bytes.
12-
#[must_use]
13-
pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] {
14-
self.0.to_bytes()
15-
}
16-
17-
/// Attempt to construct a `PublicKey` from a byte representation.
18-
///
19-
/// # Errors
20-
/// - Cannot decode group element field.
21-
pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result<Self> {
22-
GroupElement::from_bytes(bytes).map(Self)
23-
}
24-
}
25-
26-
impl SecretKey {
27-
/// `SecretKey` bytes size
28-
pub const BYTES_SIZE: usize = Scalar::BYTES_SIZE;
29-
30-
/// Convert this `SecretKey` to its underlying sequence of bytes.
31-
#[must_use]
32-
pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] {
33-
self.0.to_bytes()
34-
}
35-
36-
/// Attempt to construct a `SecretKey` from a byte representation.
37-
///
38-
/// # Errors
39-
/// - Cannot decode scalar field.
40-
pub fn from_bytes(bytes: [u8; Self::BYTES_SIZE]) -> anyhow::Result<Self> {
41-
Scalar::from_bytes(bytes).map(Self)
42-
}
43-
}
5+
use super::{Ciphertext, GroupElement};
446

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

4911
/// Convert this `Ciphertext` to its underlying sequence of bytes.
12+
#[must_use]
5013
pub fn to_bytes(&self) -> [u8; Self::BYTES_SIZE] {
5114
let mut res = [0; Self::BYTES_SIZE];
5215
res[0..32].copy_from_slice(&self.0.to_bytes());
@@ -58,6 +21,8 @@ impl Ciphertext {
5821
///
5922
/// # Errors
6023
/// - Cannot decode group element field.
24+
///
25+
/// # Panics
6126
#[allow(clippy::unwrap_used)]
6227
pub fn from_bytes(bytes: &[u8; Self::BYTES_SIZE]) -> anyhow::Result<Self> {
6328
Ok(Self(
@@ -75,18 +40,6 @@ mod tests {
7540

7641
use super::*;
7742

78-
#[proptest]
79-
fn keys_to_bytes_from_bytes_test(s1: SecretKey) {
80-
let bytes = s1.to_bytes();
81-
let s2 = SecretKey::from_bytes(bytes).unwrap();
82-
assert_eq!(s1, s2);
83-
84-
let p1 = s1.public_key();
85-
let bytes = p1.to_bytes();
86-
let p2 = PublicKey::from_bytes(&bytes).unwrap();
87-
assert_eq!(p1, p2);
88-
}
89-
9043
#[proptest]
9144
fn ciphertext_to_bytes_from_bytes_test(c1: Ciphertext) {
9245
let bytes = c1.to_bytes();

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

Lines changed: 17 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,17 @@
1-
//! Implementation of the lifted ``ElGamal`` crypto system, and combine with `ChaCha`
1+
//! Implementation of the lifted `ElGamal` crypto system, and combine with `ChaCha`
22
//! stream cipher to produce a hybrid encryption scheme.
33
44
mod decoding;
55

6-
use std::ops::{Add, Deref, Mul};
7-
8-
use rand_core::CryptoRngCore;
6+
use std::ops::{Add, Mul};
97

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

12-
/// ``ElGamal`` secret key.
13-
#[derive(Debug, Clone, PartialEq, Eq)]
14-
pub struct SecretKey(Scalar);
15-
16-
/// ``ElGamal`` public key.
17-
#[derive(Clone, Debug, PartialEq, Eq)]
18-
pub struct PublicKey(GroupElement);
19-
20-
/// ``ElGamal`` ciphertext, encrypted message with the public key.
10+
/// `ElGamal` ciphertext, encrypted message with the public key.
2111
#[derive(Debug, Clone, PartialEq, Eq)]
12+
#[must_use]
2213
pub struct Ciphertext(GroupElement, GroupElement);
2314

24-
impl Deref for SecretKey {
25-
type Target = Scalar;
26-
27-
fn deref(&self) -> &Self::Target {
28-
&self.0
29-
}
30-
}
31-
32-
impl Deref for PublicKey {
33-
type Target = GroupElement;
34-
35-
fn deref(&self) -> &Self::Target {
36-
&self.0
37-
}
38-
}
39-
40-
impl SecretKey {
41-
/// Generate a random `SecretKey` value from the random number generator.
42-
pub fn random<R: CryptoRngCore>(rng: &mut R) -> Self {
43-
Self(Scalar::random(rng))
44-
}
45-
46-
/// Generate a corresponding `PublicKey`.
47-
#[must_use]
48-
pub fn public_key(&self) -> PublicKey {
49-
PublicKey(GroupElement::GENERATOR.mul(&self.0))
50-
}
51-
}
52-
5315
impl Ciphertext {
5416
/// Generate a zero `Ciphertext`.
5517
/// The same as encrypt a `Scalar::zero()` message and `Scalar::zero()` randomness.
@@ -68,19 +30,24 @@ impl Ciphertext {
6830
}
6931
}
7032

33+
/// Generate `ElGamal` public key from the secret key value.
34+
pub fn generate_public_key(secret_key: &Scalar) -> GroupElement {
35+
GroupElement::GENERATOR.mul(secret_key)
36+
}
37+
7138
/// Given a `message` represented as a `Scalar`, return a ciphertext using the
72-
/// lifted ``ElGamal`` mechanism.
39+
/// lifted `ElGamal` mechanism.
7340
/// Returns a ciphertext of type `Ciphertext`.
74-
pub fn encrypt(message: &Scalar, public_key: &PublicKey, randomness: &Scalar) -> Ciphertext {
41+
pub fn encrypt(message: &Scalar, public_key: &GroupElement, randomness: &Scalar) -> Ciphertext {
7542
let e1 = GroupElement::GENERATOR.mul(randomness);
76-
let e2 = &GroupElement::GENERATOR.mul(message) + &public_key.0.mul(randomness);
43+
let e2 = &GroupElement::GENERATOR.mul(message) + &public_key.mul(randomness);
7744
Ciphertext(e1, e2)
7845
}
7946

80-
/// Decrypt ``ElGamal`` `Ciphertext`, returns the original message represented as a
47+
/// Decrypt `ElGamal` `Ciphertext`, returns the original message represented as a
8148
/// `GroupElement`.
82-
pub fn decrypt(cipher: &Ciphertext, secret_key: &SecretKey) -> GroupElement {
83-
&(&cipher.0 * &secret_key.0.negate()) + &cipher.1
49+
pub fn decrypt(cipher: &Ciphertext, secret_key: &Scalar) -> GroupElement {
50+
&(&cipher.0 * &secret_key.negate()) + &cipher.1
8451
}
8552

8653
impl Mul<&Scalar> for &Ciphertext {
@@ -109,15 +76,6 @@ mod tests {
10976

11077
use super::*;
11178

112-
impl Arbitrary for SecretKey {
113-
type Parameters = ();
114-
type Strategy = BoxedStrategy<Self>;
115-
116-
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
117-
any::<Scalar>().prop_map(SecretKey).boxed()
118-
}
119-
}
120-
12179
impl Arbitrary for Ciphertext {
12280
type Parameters = ();
12381
type Strategy = BoxedStrategy<Self>;
@@ -152,10 +110,8 @@ mod tests {
152110
}
153111

154112
#[proptest]
155-
fn elgamal_encryption_decryption_test(
156-
secret_key: SecretKey, message: Scalar, randomness: Scalar,
157-
) {
158-
let public_key = secret_key.public_key();
113+
fn elgamal_encryption_decryption_test(secret_key: Scalar, message: Scalar, randomness: Scalar) {
114+
let public_key = generate_public_key(&secret_key);
159115

160116
let cipher = encrypt(&message, &public_key, &randomness);
161117
let decrypted = decrypt(&cipher, &secret_key);

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,23 @@ use std::{
1111

1212
use curve25519_dalek::{
1313
constants::{RISTRETTO_BASEPOINT_POINT, RISTRETTO_BASEPOINT_TABLE},
14-
digest::{consts::U64, Digest},
15-
ristretto::RistrettoPoint as Point,
1614
scalar::Scalar as IScalar,
1715
traits::Identity,
16+
RistrettoPoint,
1817
};
1918
use rand_core::CryptoRngCore;
2019

20+
use crate::crypto::hash::digest::{consts::U64, Digest};
21+
2122
/// Ristretto group scalar.
2223
#[derive(Debug, Clone, PartialEq, Eq)]
24+
#[must_use]
2325
pub struct Scalar(IScalar);
2426

2527
/// Ristretto group element.
2628
#[derive(Debug, Clone, PartialEq, Eq)]
27-
pub struct GroupElement(Point);
29+
#[must_use]
30+
pub struct GroupElement(RistrettoPoint);
2831

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

4950
/// additive identity
@@ -84,7 +85,13 @@ impl GroupElement {
8485

8586
/// Generate a zero group element.
8687
pub fn zero() -> Self {
87-
GroupElement(Point::identity())
88+
GroupElement(RistrettoPoint::identity())
89+
}
90+
91+
/// Generate a `GroupElement` from a hash digest.
92+
pub fn from_hash<D>(hash: D) -> GroupElement
93+
where D: Digest<OutputSize = U64> + Default {
94+
GroupElement(RistrettoPoint::from_hash(hash))
8895
}
8996
}
9097

0 commit comments

Comments
 (0)