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

Example - Centralized Telescope with BLS Signatures #114

Open
wants to merge 38 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
51d994c
simple bls signature
curiecrypt Dec 17, 2024
2d3d6d6
example with bls signatures v0
curiecrypt Dec 18, 2024
158395d
to bytes for sig
curiecrypt Dec 19, 2024
4f897da
centralized telescope with bls sig, basic flow
curiecrypt Dec 19, 2024
f364f7e
Merge branch 'main' into curiecrypt/example-centralized
curiecrypt Dec 19, 2024
036f467
registration feature
curiecrypt Dec 20, 2024
c492c0a
clippy
curiecrypt Dec 20, 2024
a9a563b
clippy warnings
curiecrypt Dec 20, 2024
d143670
prover
curiecrypt Dec 20, 2024
bc95a15
added struct AlbaThresholdProof
curiecrypt Dec 20, 2024
ea2eeb9
clippy warnings
curiecrypt Dec 20, 2024
bd151d4
verify alba proof
curiecrypt Dec 23, 2024
9faf078
clippy warnings
curiecrypt Dec 23, 2024
0f61dc5
re-structure the examples
curiecrypt Dec 23, 2024
0c09939
clippy warnings
curiecrypt Dec 23, 2024
57d60c8
Apply suggestions from code review
curiecrypt Jan 2, 2025
1d835ef
resolving centralized_telescope
curiecrypt Jan 2, 2025
718dfcc
resolving aggregate
curiecrypt Jan 2, 2025
c63483a
changed HashMap to BTreeSet for registered keys
curiecrypt Jan 2, 2025
b3aeba3
enhancements over new registration
curiecrypt Jan 3, 2025
a15fd73
clippy warnings
curiecrypt Jan 3, 2025
9237bcf
renaming the module and candidate
curiecrypt Jan 3, 2025
ef9affc
cargo fmt
curiecrypt Jan 3, 2025
cf33101
enhancements
curiecrypt Jan 3, 2025
8e0b3d5
clippy warnings
curiecrypt Jan 3, 2025
a1ac0fe
rename new to init for signer
curiecrypt Jan 3, 2025
13cb0b2
Signature struct as BlstSignature
curiecrypt Jan 3, 2025
a815d9b
try batch verification of bls v0
curiecrypt Jan 3, 2025
08b3873
unsafe helpers for signature aggregation
curiecrypt Jan 3, 2025
35dae91
batch verify aggregate
curiecrypt Jan 3, 2025
3df7bf6
clippy error and warnings
curiecrypt Jan 3, 2025
c24c21b
clippy error
curiecrypt Jan 3, 2025
3984938
remove allow dead code
curiecrypt Jan 3, 2025
72b7e0d
saturating add
curiecrypt Jan 3, 2025
a994074
cargo fmt
curiecrypt Jan 3, 2025
4b61ead
style and comments
curiecrypt Jan 7, 2025
bac3cdd
alba threshold signature including only proof signatures
curiecrypt Jan 7, 2025
7684e52
clippy warnings
curiecrypt Jan 7, 2025
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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ include = ["**/*.rs", "Cargo.toml", "README.md", ".gitignore"]

[dependencies]
blake2 = "0.10.6"
blst = "0.3.13"

[dev-dependencies]
rand_core = "0.6.4"
Expand Down
146 changes: 146 additions & 0 deletions examples/centralized_telescope.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
//! Example

// REMOVE!!!!!!!!!!!!!!!!
#![allow(dead_code)]

mod threshold_signature;
curiecrypt marked this conversation as resolved.
Show resolved Hide resolved

use alba::centralized_telescope::params::Params;
use alba::centralized_telescope::proof::Proof;
use alba::centralized_telescope::CentralizedTelescope;
use rand_chacha::ChaCha20Rng;
use rand_core::{RngCore, SeedableRng};

use crate::threshold_signature::registration::{ClosedRegistration, RegisteredKeys};
use crate::threshold_signature::signature::IndividualSignature;
use crate::threshold_signature::signer::{Candidate, Signer};
use threshold_signature::aggregate::AggregateSignature;

const DATA_LENGTH: usize = 32;
pub(crate) type Element = [u8; DATA_LENGTH];

/// Alba proof with aggregate signature
#[derive(Debug, Clone)]
pub struct AlbaThresholdProof {
curiecrypt marked this conversation as resolved.
Show resolved Hide resolved
/// Aggregate signature
pub(crate) aggregate: AggregateSignature,
/// Centralized telescope proof
pub proof: Proof,
}

impl AlbaThresholdProof {
/// Create an Alba proof
/// - Try to aggregate given list of aggregate signatures
/// - If aggregation succeeds, create the Alba proof
/// - If Alba proof is generated, return the aggregate signature and the Alba proof
pub(crate) fn prove<const N: usize>(
params: &Params,
signatures: &[IndividualSignature],
set_size: u64,
closed_registration: &ClosedRegistration,
msg: &[u8],
) -> Option<Self> {
let try_aggregate =
AggregateSignature::aggregate::<N>(signatures, closed_registration, msg, set_size);
if let Some(aggregate) = try_aggregate {
curiecrypt marked this conversation as resolved.
Show resolved Hide resolved
let prover_set: Vec<Element> = aggregate.create_prover_set::<N>();
curiecrypt marked this conversation as resolved.
Show resolved Hide resolved
let alba = CentralizedTelescope::create(params);
let try_proof = alba.prove(&prover_set);
if let Some(proof) = try_proof {
curiecrypt marked this conversation as resolved.
Show resolved Hide resolved
Some(Self { aggregate, proof })
} else {
println!("Proof generation failed.");
None
}
} else {
println!("Aggregation failed.");
None
}
}

/// Verify given Alba proof
/// Create the commitment by hashing the checksum of the closed registration and the message
/// If the computed commitment is different than the commitment of given aggregate signature, abort
/// Verify each individual signature of the aggregate signature. Abort if the aggregate includes an invalid signature
/// Return true if Alba proof is verified and all checks passed.
pub(crate) fn verify<const N: usize>(
&self,
params: &Params,
closed_registration: &ClosedRegistration,
msg: &[u8],
) -> bool {
let commitment: [u8; N] =
ClosedRegistration::get_commitment(&closed_registration.check_sum, msg);

if commitment != self.aggregate.commitment.as_slice() {
return false;
}

for sig in self.aggregate.valid_signatures.clone() {
Copy link
Collaborator

@djetchev djetchev Dec 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since you are not modifying the valid_signatures during iteration, it is more efficient and more idiomatic to use iter() instead of clone(), that is

if !self.aggregate.valid_signatures
    .iter()
    .all(|sig| sig.verify::<N>(&commitment, closed_registration))
{
    return false;
}

This way, you are avoiding unnecessary cloning (better memory efficiency).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since all these signatures are BLS signatures you could batch verify them!

You could implement this in aggregate.rs using blst's function

pub fn aggregate_verify(
                &self,
                sig_groupcheck: bool,
                msgs: &[&[u8]],
                dst: &[u8],
                pks: &[&PublicKey],
                pks_validate: bool,
            ) -> BLST_ERROR 

if !sig.verify::<N>(&commitment, closed_registration) {
return false;
}
}
let alba = CentralizedTelescope::create(params);
alba.verify(&self.proof)
}
}

fn main() {
let mut rng = ChaCha20Rng::from_seed(Default::default());
let mut msg = [0u8; 16];
rng.fill_bytes(&mut msg);
let set_size = 1_000;
let params = Params {
soundness_param: 10.0,
completeness_param: 10.0,
set_size: 80 * set_size / 100,
lower_bound: 20 * set_size / 100,
};

// Create a list of candidates (signers) of the size `set_size`
let candidates: Vec<Candidate> = (0..set_size).map(|_| Candidate::new(&mut rng)).collect();

// Create a new key registration
let mut registration = RegisteredKeys::new();

// Register the candidates
for (index, candidate) in candidates.iter().enumerate() {
registration.insert_key::<DATA_LENGTH>(&candidate.verification_key, index);
}

// Close the registration
let closed_registration = ClosedRegistration::close::<DATA_LENGTH>(&registration);

// Create the threshold signature signers from the candidates if they are registered
let signers: Vec<Signer> = candidates
.into_iter()
.filter_map(|candidate| candidate.new_signer::<DATA_LENGTH>(&closed_registration))
.collect();

// Collect the individual signatures
let signatures = signers
.iter()
.map(|signer| signer.sign::<DATA_LENGTH>(&msg))
.collect::<Vec<IndividualSignature>>();

// Create the Alba proof.
curiecrypt marked this conversation as resolved.
Show resolved Hide resolved
// - Aggregate the valid signatures
// - Create the `prover_set` with the list of valid signatures
// - Create the proof with the `prover_set`
curiecrypt marked this conversation as resolved.
Show resolved Hide resolved
let result = AlbaThresholdProof::prove::<DATA_LENGTH>(
&params,
&signatures,
set_size,
&closed_registration,
&msg,
);
if result.is_some() {
let alba = result.unwrap();
// Verify the proof
let verify_result = alba.verify::<DATA_LENGTH>(&params, &closed_registration, &msg);
print!("{verify_result}");
curiecrypt marked this conversation as resolved.
Show resolved Hide resolved
} else {
println!("Proof is not generated.");
curiecrypt marked this conversation as resolved.
Show resolved Hide resolved
}
}
62 changes: 62 additions & 0 deletions examples/threshold_signature/aggregate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use crate::threshold_signature::registration::ClosedRegistration;
use crate::threshold_signature::signature::IndividualSignature;
use crate::Element;

/// Aggregate signature storing the list of valid signatures and the hash of commitment with the message to be signed.
#[derive(Debug, Clone)]
pub(crate) struct AggregateSignature {
pub(crate) valid_signatures: Vec<IndividualSignature>,
pub(crate) commitment: Vec<u8>,
}

impl AggregateSignature {
/// Create the aggregate signature.
/// First, create the commitment by hashing the check sum of the closed registration and the message.
/// Verify all individual signatures
/// If the number of valid signatures are less than the given set_size, return `None`
/// Return the aggregate signature if all checks pass.
pub fn aggregate<const N: usize>(
signatures: &[IndividualSignature],
closed_registration: &ClosedRegistration,
msg: &[u8],
set_size: u64,
) -> Option<Self> {
let commitment: [u8; N] =
ClosedRegistration::get_commitment(&closed_registration.check_sum, msg);
let valid_signatures = AggregateSignature::collect_valid_signatures::<N>(
signatures,
closed_registration,
&commitment,
);

if valid_signatures.len() < set_size as usize {
None
} else {
Some(Self {
valid_signatures,
commitment: commitment.to_vec(),
})
}
}

/// Collect the verified individual signatures
pub fn collect_valid_signatures<const N: usize>(
signatures: &[IndividualSignature],
closed_registration: &ClosedRegistration,
msg: &[u8],
curiecrypt marked this conversation as resolved.
Show resolved Hide resolved
) -> Vec<IndividualSignature> {
signatures
.iter()
.filter(|sig| sig.verify::<N>(msg, closed_registration))
.cloned()
.collect()
}

/// Create the prover set by running `to_element` function for each valid signature
pub fn create_prover_set<const N: usize>(&self) -> Vec<Element> {
rrtoledo marked this conversation as resolved.
Show resolved Hide resolved
self.valid_signatures
.iter()
.map(IndividualSignature::to_element)
.collect()
}
}
4 changes: 4 additions & 0 deletions examples/threshold_signature/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub(crate) mod aggregate;
pub(crate) mod registration;
pub(crate) mod signature;
pub(crate) mod signer;
106 changes: 106 additions & 0 deletions examples/threshold_signature/registration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use blake2::digest::{Update, VariableOutput};
use blake2::Blake2bVar;
use blst::min_sig::PublicKey;
use std::collections::HashMap;

/// A hash table for storing BLS public keys with their indices
#[derive(Debug, Clone)]
pub(crate) struct RegisteredKeys {
curiecrypt marked this conversation as resolved.
Show resolved Hide resolved
curiecrypt marked this conversation as resolved.
Show resolved Hide resolved
keys: HashMap<usize, Vec<u8>>,
}

/// Closed registration including registered keys and the checksum of the registered keys
#[derive(Debug, Clone)]
pub(crate) struct ClosedRegistration {
pub(crate) registered_keys: RegisteredKeys,
pub(crate) check_sum: Vec<u8>,
}

impl RegisteredKeys {
curiecrypt marked this conversation as resolved.
Show resolved Hide resolved
/// Creates a new hash table with a specified hash size
pub fn new() -> Self {
Self {
keys: HashMap::new(),
}
}

/// Inserts the given list of public keys into the hash table
pub fn insert_keys<const N: usize>(&mut self, keys: &[PublicKey]) {
curiecrypt marked this conversation as resolved.
Show resolved Hide resolved
for (index, key) in keys.iter().enumerate() {
let key_hash = RegisteredKeys::hash_key::<N>(key);
self.keys.insert(index, key_hash);
}
}

/// Insert the given public key into the hash table
pub fn insert_key<const N: usize>(&mut self, key: &PublicKey, index: usize) {
let key_hash = RegisteredKeys::hash_key::<N>(key);
self.keys.insert(index, key_hash);
}

/// Finds the index of the given public key, if it exists in the registered keys
pub fn get_index<const N: usize>(&self, key: &PublicKey) -> Option<usize> {
let key_hash = RegisteredKeys::hash_key::<N>(key);
self.keys.iter().find_map(|(&index, stored_hash)| {
if *stored_hash == key_hash {
Some(index)
} else {
None
}
})
}

/// Checks if the hash of the given `PublicKey` matches the hash stored at the specified index
pub fn is_key_at_index<const N: usize>(&self, key: &PublicKey, index: usize) -> bool {
match self.keys.get(&index) {
Some(stored_hash) => &RegisteredKeys::hash_key::<N>(key) == stored_hash,
None => false,
}
}

/// Hashes a public key using Blake2b
fn hash_key<const N: usize>(key: &PublicKey) -> Vec<u8> {
let mut hasher = Blake2bVar::new(N).expect("Invalid hash size");
let mut hash_output = vec![0u8; N];

hasher.update(&key.to_bytes());
hasher.finalize_variable(&mut hash_output).unwrap();

hash_output
}
}

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

impl ClosedRegistration {
/// Close the registration and create the hash of all registered keys.
pub fn close<const N: usize>(registration: &RegisteredKeys) -> Self {
let mut hasher = Blake2bVar::new(N).expect("Invalid hash size");
let mut hash_output = vec![0u8; N];

for reg in registration.clone().keys {
curiecrypt marked this conversation as resolved.
Show resolved Hide resolved
hasher.update(&reg.1.clone());
}
hasher.finalize_variable(&mut hash_output).unwrap();

Self {
registered_keys: registration.clone(),
check_sum: hash_output.clone(),
}
}

/// Compute the commitment by hashing check sum of closed registration and the message
pub fn get_commitment<const N: usize>(check_sum: &[u8], msg: &[u8]) -> [u8; N] {
let mut hasher = Blake2bVar::new(N).expect("Invalid hash size");
let mut commitment: [u8; N] = [0u8; N];

hasher.update(check_sum);
hasher.update(msg);
hasher.finalize_variable(&mut commitment).unwrap();
commitment
}
}
49 changes: 49 additions & 0 deletions examples/threshold_signature/signature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use crate::threshold_signature::registration::ClosedRegistration;
use blake2::digest::{Update, VariableOutput};
use blake2::Blake2bVar;
use blst::min_sig::{PublicKey, Signature};
use blst::BLST_ERROR;

/// Individual Signature.
/// It includes a blst signature, its verification key, and the registration index of the signer.
curiecrypt marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Debug, Clone)]
pub(crate) struct IndividualSignature {
pub(crate) signature: Signature,
pub(crate) verification_key: PublicKey,
pub(crate) signer_index: usize,
}

impl IndividualSignature {
curiecrypt marked this conversation as resolved.
Show resolved Hide resolved
/// Verify a signature
/// First, validate that the signer's verification key is actually registered.
/// Then, verify the blst signature.
pub fn verify<const N: usize>(
&self,
commitment: &[u8],
closed_registration: &ClosedRegistration,
) -> bool {
if closed_registration
.registered_keys
.is_key_at_index::<N>(&self.verification_key, self.signer_index)
curiecrypt marked this conversation as resolved.
Show resolved Hide resolved
{
let result =
self.signature
rrtoledo marked this conversation as resolved.
Show resolved Hide resolved
.verify(false, commitment, &[], &[], &self.verification_key, false);
return result == BLST_ERROR::BLST_SUCCESS;
};
false
}

/// Return the hash of the signature and its public key
/// This function is used to create the `prover_set` of Alba protocol.
pub fn to_element<const N: usize>(&self) -> [u8; N] {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BLS signatures have good randomness properties so you could use as elements of your prover sets the signatures directly, this function would just return the signature.to_bytes() (but it is 48B indeed).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My intention was to use the byte representation of the signature as an element. Even, I changed the DATA_LENGTH to 48B. But, as we discussed previously, we can keep the DATA_LENGTH as it is by hashing the signatures.

let mut hasher = Blake2bVar::new(N).expect("Invalid hash size");
let mut element = [0u8; N];

hasher.update(&self.signature.to_bytes());
hasher.update(&self.verification_key.to_bytes());
hasher.finalize_variable(&mut element).unwrap();

element
}
}
Loading
Loading