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): Tally proof generation and verification #45

Merged
merged 59 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
05af510
initialize a new crate
Mr-Leshiy Sep 24, 2024
6935a1b
add intentionally failed test
Mr-Leshiy Sep 24, 2024
78f601f
fix CI
Mr-Leshiy Sep 24, 2024
9423567
fix
Mr-Leshiy Sep 24, 2024
5d6b95b
fix
Mr-Leshiy Sep 24, 2024
b8d7ece
update vscode setting.recommended.json
Mr-Leshiy Sep 24, 2024
d644602
Merge branch 'main' into feat/voting-crate-setup
Mr-Leshiy Sep 24, 2024
e70f571
Merge branch 'main' into feat/el-gamal
Mr-Leshiy Sep 25, 2024
0803b50
add a basic interfaces for the vote part
Mr-Leshiy Sep 25, 2024
0ea84b3
add basic elgamal encryption based on the ristretto255 group
Mr-Leshiy Sep 25, 2024
91106ff
add arithmetic tests for ristretto255
Mr-Leshiy Sep 26, 2024
e1b8251
fix tests
Mr-Leshiy Sep 26, 2024
6c2d961
wip
Mr-Leshiy Sep 26, 2024
594d114
add decryption algorithm, add tests
Mr-Leshiy Sep 26, 2024
c1b749d
fix CI
Mr-Leshiy Sep 26, 2024
0a5bb99
remove unused std_ops_gen
Mr-Leshiy Sep 26, 2024
0348b6c
add new voter module
Mr-Leshiy Sep 27, 2024
9d9ddd6
add EncryptionRandomness random generation
Mr-Leshiy Sep 27, 2024
c342a1e
add a tally function
Mr-Leshiy Sep 27, 2024
bc658d6
Merge branch 'main' into feat/tally
Mr-Leshiy Sep 27, 2024
7eacd5b
fix
Mr-Leshiy Sep 27, 2024
15d97c6
wip
Mr-Leshiy Sep 27, 2024
dd1b1a3
add a babystep implementation
Mr-Leshiy Sep 28, 2024
a3c4d61
wip
Mr-Leshiy Sep 28, 2024
dcd1484
refactor, add decrypt_tally_result
Mr-Leshiy Sep 28, 2024
4936687
wip
Mr-Leshiy Sep 28, 2024
f572ad3
wip
Mr-Leshiy Sep 28, 2024
81a6323
add voting test
Mr-Leshiy Sep 28, 2024
f988417
remove rayon dependency for now
Mr-Leshiy Sep 29, 2024
37cf886
fix spelling, remove rayon
Mr-Leshiy Sep 30, 2024
24da2bc
fix
Mr-Leshiy Sep 30, 2024
804c723
remove unused anyhow dep
Mr-Leshiy Sep 30, 2024
921ad46
intentionally break the test
Mr-Leshiy Sep 30, 2024
fa63fe1
try
Mr-Leshiy Sep 30, 2024
c54f806
wip
Mr-Leshiy Sep 30, 2024
c444a02
update DecryptionTallySetup interface
Mr-Leshiy Sep 30, 2024
a63d11a
add doctest example
Mr-Leshiy Sep 30, 2024
688f5b7
refactor, make voting_test as integration test
Mr-Leshiy Sep 30, 2024
8466187
fix baby_step_giant_step_test
Mr-Leshiy Oct 1, 2024
eafd538
move tally module into the seprate dir
Mr-Leshiy Oct 1, 2024
ed25f6c
add new proof.rs
Mr-Leshiy Oct 1, 2024
5918f0d
refactor
Mr-Leshiy Oct 1, 2024
87af867
add to_bytes, from_bytes functions for Scalar and GroupElement
Mr-Leshiy Oct 1, 2024
5c399b9
add zk_dl_equality.rs
Mr-Leshiy Oct 1, 2024
dde81b8
add hash module
Mr-Leshiy Oct 2, 2024
e7d5079
update hash implementation
Mr-Leshiy Oct 2, 2024
c275f5a
add dleq verify function, add tests
Mr-Leshiy Oct 2, 2024
ae6c00c
implement tally proof generation and tally proof verification
Mr-Leshiy Oct 2, 2024
0a9ac9b
update voting_test with the tally proofs, fix verify_tally_proof
Mr-Leshiy Oct 2, 2024
ee09463
remove uneeded comment
Mr-Leshiy Oct 2, 2024
c75c0b0
Merge branch 'main' into feat/tally-proof
Mr-Leshiy Oct 2, 2024
1ca194c
fix
Mr-Leshiy Oct 2, 2024
5533456
fix
Mr-Leshiy Oct 2, 2024
359cf53
fix spelling
Mr-Leshiy Oct 2, 2024
a0e25ea
fix comment
Mr-Leshiy Oct 2, 2024
23b0bdc
update rust docs
Mr-Leshiy Oct 2, 2024
9fd9e0c
fix rustdoc tests
Mr-Leshiy Oct 2, 2024
8ddca46
Merge branch 'main' into feat/tally-proof
Mr-Leshiy Oct 3, 2024
041eb66
Merge branch 'main' into feat/tally-proof
Mr-Leshiy Oct 4, 2024
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
1 change: 1 addition & 0 deletions .config/dictionaries/project.dic
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ dbsync
dcbor
decompressor
delegators
dleq
dockerhub
Dominik
dotenv
Expand Down
3 changes: 2 additions & 1 deletion rust/catalyst-voting/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ workspace = true
[dependencies]
thiserror = "1.0.56"
rand_core = "0.6.4"
curve25519-dalek = { version = "4.0" }
curve25519-dalek = { version = "4.0", features = ["digest"] }
blake2b_simd = "1.0.2"

[dev-dependencies]
proptest = {version = "1.5.0" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use std::collections::HashMap;

use super::{GroupElement, Scalar};
use super::group::{GroupElement, Scalar};

/// Default balance value.
/// Make steps asymmetric, in order to better use caching of baby steps.
Expand Down
34 changes: 30 additions & 4 deletions rust/catalyst-voting/src/crypto/elgamal.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Implementation of the lifted ``ElGamal`` crypto system, and combine with `ChaCha`
//! stream cipher to produce a hybrid encryption scheme.

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

use rand_core::CryptoRngCore;

Expand All @@ -19,6 +19,22 @@ pub struct PublicKey(GroupElement);
#[derive(Debug, Clone, PartialEq, Eq)]
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 generate<R: CryptoRngCore>(rng: &mut R) -> Self {
Expand All @@ -35,23 +51,33 @@ impl SecretKey {
impl Ciphertext {
/// Generate a zero `Ciphertext`.
/// The same as encrypt a `Scalar::zero()` message and `Scalar::zero()` randomness.
pub(crate) fn zero() -> Self {
pub fn zero() -> Self {
Ciphertext(GroupElement::zero(), GroupElement::zero())
}

/// Get the first element of the `Ciphertext`.
pub fn first(&self) -> &GroupElement {
&self.0
}

/// Get the second element of the `Ciphertext`.
pub fn second(&self) -> &GroupElement {
&self.1
}
}

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

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

Expand Down
2 changes: 0 additions & 2 deletions rust/catalyst-voting/src/crypto/group/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
//! Group definitions used in voting protocol.
//! For more information, see: <https://input-output-hk.github.io/catalyst-voices/architecture/08_concepts/voting_transaction/crypto/#a-group-definition>

mod babystep_giantstep;
mod ristretto255;

pub(crate) use babystep_giantstep::BabyStepGiantStep;
pub(crate) use ristretto255::{GroupElement, Scalar};
56 changes: 55 additions & 1 deletion rust/catalyst-voting/src/crypto/group/ristretto255.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use std::{

use curve25519_dalek::{
constants::{RISTRETTO_BASEPOINT_POINT, RISTRETTO_BASEPOINT_TABLE},
ristretto::RistrettoPoint as Point,
digest::{consts::U64, Digest},
ristretto::{CompressedRistretto, RistrettoPoint as Point},
scalar::Scalar as IScalar,
traits::Identity,
};
Expand Down Expand Up @@ -67,6 +68,22 @@ impl Scalar {
pub fn inverse(&self) -> 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> {
Scalar(IScalar::from_hash(hash))
}
}

impl GroupElement {
Expand All @@ -77,6 +94,19 @@ 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 @@ -133,6 +163,14 @@ impl Sub<&Scalar> for &Scalar {
}
}

impl Sub<&GroupElement> for &GroupElement {
type Output = GroupElement;

fn sub(self, other: &GroupElement) -> GroupElement {
GroupElement(self.0 + (-other.0))
}
}

#[cfg(test)]
mod tests {
use proptest::{
Expand All @@ -152,6 +190,21 @@ 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 All @@ -174,6 +227,7 @@ mod tests {
let ge3 = GroupElement::GENERATOR.mul(&(&e1 + &e2));

assert_eq!(&ge1 + &ge2, ge3);
assert_eq!(&(&ge1 + &ge2) - &ge2, ge1);

let ge = GroupElement::GENERATOR.mul(&e1).mul(&e1.inverse());
assert_eq!(ge, GroupElement::GENERATOR);
Expand Down
50 changes: 50 additions & 0 deletions rust/catalyst-voting/src/crypto/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//! Blake2b-256 hash implementation.

use curve25519_dalek::digest::{
consts::U64, typenum::Unsigned, FixedOutput, HashMarker, Output, OutputSizeUser, Update,
};

/// Blake2b-512 hasher instance.
pub struct Blake2b512Hasher(blake2b_simd::State);
cong-or marked this conversation as resolved.
Show resolved Hide resolved

impl Blake2b512Hasher {
/// Create a new `Blake2b256Hasher`.
pub fn new() -> Self {
Self(
blake2b_simd::Params::new()
.hash_length(Self::output_size())
.to_state(),
)
}
}

// Implementation of the `digest::Digest` trait for `Blake2b256Hasher`.

impl Default for Blake2b512Hasher {
fn default() -> Self {
Self::new()
}
}

impl Update for Blake2b512Hasher {
fn update(&mut self, data: &[u8]) {
self.0.update(data);
}
}

impl OutputSizeUser for Blake2b512Hasher {
type OutputSize = U64;

fn output_size() -> usize {
Self::OutputSize::USIZE
}
}

impl FixedOutput for Blake2b512Hasher {
fn finalize_into(self, out: &mut Output<Self>) {
let hash = self.0.finalize();
out.copy_from_slice(hash.as_bytes());
}
}

impl HashMarker for Blake2b512Hasher {}
3 changes: 3 additions & 0 deletions rust/catalyst-voting/src/crypto/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
//! 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;
111 changes: 111 additions & 0 deletions rust/catalyst-voting/src/crypto/zk_dl_equality.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//! Non-interactive Zero Knowledge proof of Discrete Logarithm
//! Equality (DLEQ).
//!
//! The proof is the following:
//!
//! `NIZK{(base_1, base_2, point_1, point_2), (dlog): point_1 = base_1^dlog AND point_2 =
//! base_2^dlog}`
//!
//! which makes the statement, the two bases `base_1` and `base_2`, and the two
//! points `point_1` and `point_2`. The witness, on the other hand
//! is the discrete logarithm, `dlog`.

// cspell: words NIZK dlog

use curve25519_dalek::digest::Update;

use super::{
group::{GroupElement, Scalar},
hash::Blake2b512Hasher,
};

/// DLEQ proof struct
pub struct DleqProof(Scalar, Scalar);

/// Generates a DLEQ proof.
pub fn generate_dleq_proof(
base_1: &GroupElement, base_2: &GroupElement, point_1: &GroupElement, point_2: &GroupElement,
dlog: &Scalar, randomness: &Scalar,
) -> DleqProof {
let a_1 = base_1 * randomness;
let a_2 = base_2 * randomness;

let challenge = calculate_challenge(base_1, base_2, point_1, point_2, &a_1, &a_2);
let response = &(dlog * &challenge) + randomness;

DleqProof(challenge, response)
}

/// Verify a DLEQ proof.
pub fn verify_dleq_proof(
proof: &DleqProof, base_1: &GroupElement, base_2: &GroupElement, point_1: &GroupElement,
point_2: &GroupElement,
) -> bool {
let a_1 = &(base_1 * &proof.1) - &(point_1 * &proof.0);
let a_2 = &(base_2 * &proof.1) - &(point_2 * &proof.0);

let challenge = calculate_challenge(base_1, base_2, point_1, point_2, &a_1, &a_2);
challenge == proof.0
}

/// Calculates the challenge value.
/// Its a hash value represented as `Scalar` of all provided elements.
fn calculate_challenge(
base_1: &GroupElement, base_2: &GroupElement, point_1: &GroupElement, point_2: &GroupElement,
a_1: &GroupElement, a_2: &GroupElement,
) -> Scalar {
let blake2b_hasher = Blake2b512Hasher::new()
.chain(base_1.to_bytes())
.chain(base_2.to_bytes())
.chain(point_1.to_bytes())
.chain(point_2.to_bytes())
.chain(a_1.to_bytes())
.chain(a_2.to_bytes());

Scalar::from_hash(blake2b_hasher)
}

#[cfg(test)]
mod tests {
use std::ops::Mul;

use test_strategy::proptest;

use super::*;

#[proptest]
fn zk_dleq_test(e1: Scalar, e2: Scalar, dlog1: Scalar, dlog2: Scalar, randomness: Scalar) {
let base_1 = GroupElement::GENERATOR.mul(&e1);
let base_2 = GroupElement::GENERATOR.mul(&e2);

let point_1 = base_1.mul(&dlog1);
let point_2 = base_2.mul(&dlog1);

let proof = generate_dleq_proof(&base_1, &base_2, &point_1, &point_2, &dlog1, &randomness);
assert!(verify_dleq_proof(
&proof, &base_1, &base_2, &point_1, &point_2
));

// use different discrete logarithm for both points
let point_1 = base_1.mul(&dlog2);
let point_2 = base_2.mul(&dlog2);

let proof = generate_dleq_proof(&base_1, &base_2, &point_1, &point_2, &dlog1, &randomness);
assert!(!verify_dleq_proof(
&proof, &base_1, &base_2, &point_1, &point_2
));

// use different discrete logarithm across points
let point_1 = base_1.mul(&dlog1);
let point_2 = base_2.mul(&dlog2);

let proof = generate_dleq_proof(&base_1, &base_2, &point_1, &point_2, &dlog1, &randomness);
assert!(!verify_dleq_proof(
&proof, &base_1, &base_2, &point_1, &point_2
));
let proof = generate_dleq_proof(&base_1, &base_2, &point_1, &point_2, &dlog2, &randomness);
assert!(!verify_dleq_proof(
&proof, &base_1, &base_2, &point_1, &point_2
));
}
}
Loading