Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
978 changes: 561 additions & 417 deletions crypto-benchmarks.rs/Cargo.lock

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions crypto-benchmarks.rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ bitvec = "1.0.1"
blst = "0.3.13"
clap = { version = "4.5.28", features = ["derive"] }
criterion = "0.5.1"
embed-doc-image = "0.1.4"
hex = "0.4.3"
num-bigint = { version = "0.4.6", features = ["serde"] }
num-rational = "0.4.2"
Expand All @@ -29,6 +30,12 @@ serde = { version = "1.0.217", features = ["derive"] }
serde_cbor = "0.11.2"
statrs = "0.18.0"

[features]
doc-images = []

[package.metadata.docs.rs]
features = ["doc-images"]

[[bench]]
name = "serialization_bench"
harness = false
Expand Down
17 changes: 17 additions & 0 deletions crypto-benchmarks.rs/ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@ See [Specification.md](Specification.md) for a description of the algorithms imp
- Benchmarks: [benches/](benches/)


## Background material

![BLS scheme from original Leios preprint](figure-7-bls.png)

![Voting scheme from original Leios preprint](figure-8-voting.png)


## Efficiency notes

The benchmark code uses an efficient BLS implementation, but it has not been optimized.

- Some of the hash data structures in the certificate could be replaced by bit sets.
- The optional group checks are turned on for calls to the `blst` library even though in some cases these may not strictly be necessary.
- The rational arthmetic for computing logarithms and performing sortition uses the maximum precision obtainable in `Ratio<BigInt>` even though that precision is not necessarily needed.
- The code has not been profiled.


## Command-line interface

A command-line interface is provided for testing and exploration. Note that all input and output files are CBOR.
Expand Down
2 changes: 2 additions & 0 deletions crypto-benchmarks.rs/benches/fait_accompli_bench.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Criterion benchmarks for Fait Accompli.

use criterion::{criterion_group, criterion_main, Criterion};
use quickcheck::{Arbitrary, Gen};
use std::collections::BTreeMap;
Expand Down
2 changes: 2 additions & 0 deletions crypto-benchmarks.rs/benches/serialization_bench.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Criterion benchmarks for serialization and deserialization.

use criterion::{criterion_group, criterion_main, Criterion};
use quickcheck::{Arbitrary, Gen};

Expand Down
2 changes: 2 additions & 0 deletions crypto-benchmarks.rs/benches/sortition_bench.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Criterion benchmarks for sortition.

use criterion::{criterion_group, criterion_main, Criterion};
use num_bigint::BigInt;
use num_rational::Ratio;
Expand Down
2 changes: 2 additions & 0 deletions crypto-benchmarks.rs/benches/vote_bench.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Criterion benchmarks for voting.

use criterion::{criterion_group, criterion_main, Criterion};
use leios_crypto_benchmarks::cert::*;
use leios_crypto_benchmarks::registry::{arbitrary_pools, PersistentId, Registry};
Expand Down
2 changes: 2 additions & 0 deletions crypto-benchmarks.rs/benches/vrf_bench.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Criterion benchmarks for VRF.

use criterion::{criterion_group, criterion_main, Criterion};

use leios_crypto_benchmarks::vrf::{sk_random, sk_to_pk_point, vrf_prove, vrf_verify};
Expand Down
Binary file added crypto-benchmarks.rs/figure-7-bls.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added crypto-benchmarks.rs/figure-8-voting.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions crypto-benchmarks.rs/src/bls_util.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
//! Low-level utility functions for BLS operations not supported by the `blst` library.

use blst::min_sig::*;
use blst::*;
use num_bigint::{BigInt, Sign};
use num_rational::Ratio;
use num_traits::FromPrimitive;

/// Apply a function to a public key (i.e., to a point in G2).
pub fn pk_transform(f: &dyn Fn(blst_p2) -> blst_p2, pk: &PublicKey) -> PublicKey {
let mut point: blst_p2 = blst_p2::default();
unsafe {
Expand All @@ -24,6 +27,7 @@ pub fn pk_transform(f: &dyn Fn(blst_p2) -> blst_p2, pk: &PublicKey) -> PublicKey
}
}

/// Apply a function to a signature (i.e., to a point in G1).
pub fn sig_transform(f: &dyn Fn(blst_p1) -> blst_p1, sig: &Signature) -> Signature {
let mut point: blst_p1 = blst_p1::default();
unsafe {
Expand All @@ -44,6 +48,7 @@ pub fn sig_transform(f: &dyn Fn(blst_p1) -> blst_p1, sig: &Signature) -> Signatu
}
}

/// Convert a signature to a rational number between zero and one.
pub fn sig_to_rational(sig: &Signature) -> Ratio<BigInt> {
let bytes: [u8; 48] = sig.to_bytes();
let mut hashed: [u8; 32] = [0; 32];
Expand Down
24 changes: 24 additions & 0 deletions crypto-benchmarks.rs/src/bls_vote.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
//! Low-level operations on BLS votes.
//!
//! ![Figure 7: BLS](../../../../figure-7-bls.png)
//!
//! ![Figure 8: voting](../../../../figure-8-voting.png)

use blst::min_sig::*;
use blst::*;
use rand::RngCore;

use crate::bls_util::*;

/// An empty bytestring.
const EMPTY: [u8; 0] = [];

/// The domain separator for Leios.
const DST: &[u8; 5] = b"Leios";

/// Generate a secret BLS scalar.
pub fn gen_key() -> SecretKey {
let mut rng: rand::prelude::ThreadRng = rand::thread_rng();
let mut ikm: [u8; 32] = [0u8; 32];
Expand All @@ -16,12 +25,14 @@ pub fn gen_key() -> SecretKey {
SecretKey::key_gen(&ikm, info).unwrap()
}

/// Create a proof of possession from a secret key `sk`, namely $\mu_1$ and $\mu_2$ of [Figure 8 (voting)](index.html).
pub fn make_pop(sk: &SecretKey) -> (Signature, Signature) {
let m1: [u8; 192] = sk.sk_to_pk().serialize();
let m2 = EMPTY;
(sk.sign(&m1, DST, b"PoP"), sk.sign(&m2, DST, &EMPTY))
}

/// Verify the the proof of possession, namely $\mu_1$ and $\mu_2$ of [Figure 8 (voting)](index.html), for a public key `pk`.
pub fn check_pop(pk: &PublicKey, mu1: &Signature, mu2: &Signature) -> bool {
let m1: [u8; 192] = pk.serialize();
let m2 = EMPTY;
Expand All @@ -30,29 +41,35 @@ pub fn check_pop(pk: &PublicKey, mu1: &Signature, mu2: &Signature) -> bool {
result1 == BLST_ERROR::BLST_SUCCESS && result2 == BLST_ERROR::BLST_SUCCESS
}

/// Sign the message `m` in the election `eid` using the secret key `sk`.
pub fn gen_sig(sk: &SecretKey, eid: &[u8], m: &[u8]) -> Signature {
sk.sign(m, DST, eid)
}

/// Verify a signature `vs` on the message `m` in the election `eid` for the public key `pk`.
pub fn verify_sig(pk: &PublicKey, eid: &[u8], m: &[u8], vs: &Signature) -> bool {
let result_m = vs.verify(true, m, DST, eid, pk, false);
result_m == BLST_ERROR::BLST_SUCCESS
}

/// Sign the election `eid` with the secret key `sk`.
pub fn gen_sigma_eid(sk: &SecretKey, eid: &[u8]) -> Signature {
sk.sign(&EMPTY, DST, eid)
}

/// Create a vote for the message `m` in the election `eid` using the secret key `sk`.
pub fn gen_vote(sk: &SecretKey, eid: &[u8], m: &[u8]) -> (Signature, Signature) {
(sk.sign(&EMPTY, DST, eid), sk.sign(m, DST, eid))
}

/// Verify the vote `vs` for the message `m` in the election `eid` for the public key `pk`.
pub fn verify_vote(pk: &PublicKey, eid: &[u8], m: &[u8], vs: &(Signature, Signature)) -> bool {
let result_eid = vs.0.verify(true, &EMPTY, DST, eid, pk, true);
let result_m = vs.1.verify(true, m, DST, eid, pk, false);
result_eid == BLST_ERROR::BLST_SUCCESS && result_m == BLST_ERROR::BLST_SUCCESS
}

/// Hash an array of signatures `sigma_eids` and `sigma_ms`.
fn hash_sigs(sigma_eids: &[&Signature], sigma_ms: &[&Signature]) -> [u8; 32] {
let mut sigmas: Vec<&Signature> = Vec::new();
sigmas.extend(sigma_eids);
Expand All @@ -65,6 +82,7 @@ fn hash_sigs(sigma_eids: &[&Signature], sigma_ms: &[&Signature]) -> [u8; 32] {
}
}

/// Hash an integer `i` with a previous hash `h`.
fn hash_index(i: i32, h: &[u8; 32]) -> [u8; 32] {
let mut msg: [u8; 36] = [0; 36];
let ii: [u8; 4] = i.to_ne_bytes();
Expand All @@ -80,12 +98,14 @@ fn hash_index(i: i32, h: &[u8; 32]) -> [u8; 32] {
}
}

/// Create the signatures for a certificate from the individual vote signatures `vss`.
pub fn gen_cert(vss: &[&(Signature, Signature)]) -> Result<(Signature, Signature), BLST_ERROR> {
let sigma_eids: Vec<&Signature> = vss.iter().map(|vs| &vs.1).collect();
let sigma_ms: Vec<&Signature> = vss.iter().map(|vs| &vs.1).collect();
gen_cert_fa(&sigma_eids, &sigma_ms)
}

/// Verify the contents of certificate signatures `cs` for the message `m` in election `eid`, given the vote signatures `vss`.
pub fn verify_cert(
pks: &[&PublicKey],
eid: &[u8],
Expand Down Expand Up @@ -129,6 +149,7 @@ pub fn verify_cert(
}
}

/// Create certificate signatures including both sortition and message signing signatures.
pub fn gen_cert_fa(
sigma_eids: &[&Signature],
sigma_ms: &[&Signature],
Expand Down Expand Up @@ -161,6 +182,7 @@ pub fn gen_cert_fa(
}
}

/// Create cerificate signatures including only message signing signatures.
pub fn gen_cert_fa_pure(sigma_ms: &[&Signature]) -> Result<Signature, BLST_ERROR> {
let result_m = AggregateSignature::aggregate(sigma_ms, true);
match result_m {
Expand All @@ -169,6 +191,7 @@ pub fn gen_cert_fa_pure(sigma_ms: &[&Signature]) -> Result<Signature, BLST_ERROR
}
}

/// Verify cerificate sigantures according to [Figure 8 (voting)](index.html).
pub fn verify_cert_fa(
pks: &[&PublicKey],
pks_nonpersistent: &[&PublicKey],
Expand Down Expand Up @@ -212,6 +235,7 @@ pub fn verify_cert_fa(
}
}

/// Verify signatures for a message, without verifying sortition.
pub fn verify_cert_fa_pure(
pks: &[&PublicKey],
eid: &[u8],
Expand Down
18 changes: 18 additions & 0 deletions crypto-benchmarks.rs/src/cert.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! High-level operations on certificates.

use blst::min_sig::*;
use quickcheck::{Arbitrary, Gen};
use serde::{Deserialize, Serialize};
Expand All @@ -10,16 +12,24 @@ use crate::registry::{PersistentId, Registry};
use crate::sortition::voter_check;
use crate::vote::{do_voting, Vote};

/// A certificate records the election and EB information along with the persistent voters who voted and the proofs for the non-persistent voters. It contains two aggregate signatures, one for sortition and other for the votes themselves.
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
pub struct Cert {
/// Election identifier.
pub eid: Eid,
/// Hash of the EB being voted upon.
pub eb: EbHash,
/// Set of persistent voter identifiers.
pub persistent_voters: HashSet<PersistentId>,
/// Map of non-persistent voter pool hashes to their eligibility signature.
pub nonpersistent_voters: BTreeMap<PoolKeyhash, Sig>,
/// Aggregate eligibility signature.
pub sigma_tilde_eid: Option<Sig>,
/// Aggregate votes signature.
pub sigma_tilde_m: Sig,
}

/// Generate an arbitrary certificate.
pub fn arbitrary_cert(g: &mut Gen, reg: &Registry) -> Cert {
let eid = Eid::arbitrary(g);
let eb = EbHash::arbitrary(g);
Expand All @@ -33,6 +43,7 @@ impl Arbitrary for Cert {
}
}

/// It's convenient to have a data structure for holding certificate information as it is being built.
#[derive(Default)]
struct TraverseVote<'a> {
pub eids: HashSet<&'a Eid>,
Expand All @@ -43,6 +54,7 @@ struct TraverseVote<'a> {
pub sigma_ms: Vec<&'a Signature>,
}

/// Add one vote to a certification that is being constructed.
fn traverse_vote<'a>(reg: &'a Registry, t: &mut TraverseVote<'a>, vote: &'a Vote) -> Option<()> {
match vote {
Vote::Persistent {
Expand Down Expand Up @@ -74,6 +86,7 @@ fn traverse_vote<'a>(reg: &'a Registry, t: &mut TraverseVote<'a>, vote: &'a Vote
}
}

/// Test that a hash set is a singleton.
fn unique<X>(xs: &HashSet<X>) -> Option<&X> {
if xs.len() == 1 {
xs.iter().next()
Expand All @@ -82,6 +95,7 @@ fn unique<X>(xs: &HashSet<X>) -> Option<&X> {
}
}

/// Generate a certificate for the specified votes.
pub fn gen_cert(reg: &Registry, votes: &[Vote]) -> Option<Cert> {
let mut t: TraverseVote = TraverseVote::default();
let _ = votes
Expand Down Expand Up @@ -113,6 +127,7 @@ pub fn gen_cert(reg: &Registry, votes: &[Vote]) -> Option<Cert> {
}
}

/// Verify a certificate using the information in the voter registry.
pub fn verify_cert(reg: &Registry, cert: &Cert) -> bool {
let pks_persistent: Vec<&PublicKey> = cert
.persistent_voters
Expand Down Expand Up @@ -154,6 +169,7 @@ pub fn verify_cert(reg: &Registry, cert: &Cert) -> bool {
}
}

/// Compute the total weight (i.e., stake fraction) of the persistent votes in a certificate.
fn weigh_persistent(reg: &Registry, cert: &Cert) -> Option<CoinFraction> {
let weight: Option<Coin> = cert.persistent_voters.iter().try_fold(0, |acc, pid| {
reg.persistent_pool
Expand All @@ -163,6 +179,7 @@ fn weigh_persistent(reg: &Registry, cert: &Cert) -> Option<CoinFraction> {
weight.map(|total| CoinFraction::from_coins(total, 1))
}

/// Compute the total weight (i.e., stake fraction) of the non-persistent votes in a certificate.
fn weigh_nonpersistent(reg: &Registry, cert: &Cert) -> Option<CoinFraction> {
let nonpersistent_voters = reg.voters - cert.persistent_voters.len();
let weight: Option<usize> =
Expand All @@ -184,6 +201,7 @@ fn weigh_nonpersistent(reg: &Registry, cert: &Cert) -> Option<CoinFraction> {
})
}

/// Compute the total weight (i.e., stake fraction) of the votes in a certificate.
pub fn weigh_cert(reg: &Registry, cert: &Cert) -> Option<CoinFraction> {
let persistent_weight = weigh_persistent(reg, cert)?.to_ratio();
let nonpersistent_weight = weigh_nonpersistent(reg, cert)?.to_ratio();
Expand Down
12 changes: 12 additions & 0 deletions crypto-benchmarks.rs/src/fait_accompli.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Fait Accompli operations.

use num_bigint::BigInt;
use num_rational::Ratio;
use num_traits::{One, Zero};
Expand All @@ -7,16 +9,23 @@ use std::collections::BTreeMap;

use crate::primitive::{Coin, CoinFraction, PoolKeyhash};

/// Fait Accompli sortition results in a committee of persistent and non-persistent voters.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FaSortition {
/// Number of persistent voters.
pub n_persistent: usize,
/// Number of non-persistent voters.
pub n_nonpersistent: usize,
/// Stake held by persistent voters.
pub persistent: Vec<(PoolKeyhash, CoinFraction)>,
/// State held by non-persistent voters.
pub nonpersistent: BTreeMap<PoolKeyhash, CoinFraction>,
/// Stake cutoff for persistent vs non-persistent voters.
pub rho: CoinFraction,
}

impl FaSortition {
/// Perform Fait Accompli sortiion on the stake distribution `pools` and target committee size `n`.
pub fn fait_accompli(pools: &BTreeMap<PoolKeyhash, Coin>, n: usize) -> Self {
let zero: Ratio<BigInt> = Ratio::from_integer(BigInt::zero());
let (s, p): (Vec<Ratio<BigInt>>, Vec<PoolKeyhash>) = sort_stake(pools);
Expand Down Expand Up @@ -57,6 +66,7 @@ impl FaSortition {
}
}

/// Sort stake pools in order of decreasing stake.
fn sort_stake(pools: &BTreeMap<PoolKeyhash, Coin>) -> (Vec<Ratio<BigInt>>, Vec<PoolKeyhash>) {
let mut sp: Vec<(Ratio<BigInt>, &PoolKeyhash)> = pools
.iter()
Expand All @@ -67,6 +77,7 @@ fn sort_stake(pools: &BTreeMap<PoolKeyhash, Coin>) -> (Vec<Ratio<BigInt>>, Vec<P
sp.into_iter().unzip()
}

/// Sum stake fractions.
fn sum_stake(s: &[Ratio<BigInt>]) -> Vec<Ratio<BigInt>> {
let zero: Ratio<BigInt> = Ratio::from_integer(BigInt::zero());
let (mut rho, _): (Vec<Ratio<BigInt>>, Ratio<BigInt>) =
Expand All @@ -81,6 +92,7 @@ fn sum_stake(s: &[Ratio<BigInt>]) -> Vec<Ratio<BigInt>> {
rho
}

/// Perform the Fait Accompli test that check whether there are any more persistent pools to be selected.
fn fa_test(s: &[Ratio<BigInt>], rho: &[Ratio<BigInt>], n: usize, i: usize) -> bool {
let zero: Ratio<BigInt> = Ratio::from_integer(BigInt::zero());
let one: Ratio<BigInt> = Ratio::from_integer(BigInt::one());
Expand Down
Loading