-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Example - Centralized Telescope with BLS Signatures (#114)
## Content This PR introduces an example demonstrating the ALBA Threshold Signature scheme with the following workflow: ### Workflow 1. **Define Telescope Parameters**: - Set `nb_elements` to 1,000. - Configure `soundness_param` and `completeness_param` as 128.0. - Calculate the prover set size (`set_size`) as 80% of `nb_elements` and the lower bound as 20%. - Initialize the Telescope with these parameters. 2. **Initialize Signers**: - Create a list of candidate `Signer`s with randomly generated keys. 3. **Start Registration**: - Open a new `Registration`. - Select and register a subset of candidate signers, tracking the number of successfully registered signers. 4. **Close Registration**: - Finalize the registration by computing the checksum of registered keys. - Update registered signers with the computed checksum. 5. **Generate Individual Signatures**: - Select a subset of registered signers. - Generate `IndividualSignature`s for each based on the input message. 6. **Generate ALBA Threshold Signature**: - Aggregate valid individual signatures to create an `AlbaThresholdSignature`. - Print details of the generated threshold signature if successful. 7. **Verify ALBA Threshold Signature**: - Validate the generated threshold signature using the Telescope, registration, and input message. - Print whether the verification succeeded or failed. ### Purpose This example provides a detailed implementation of the ALBA Threshold Signature workflow, showcasing the integration of Telescope parameters, signer registration, individual signature generation, and threshold signature verification. ## Pre-submit checklist - Branch - [x] Commit sequence broadly makes sense - [x] Key commits have useful messages - PR - [x] No clippy warnings in the CI - [x] Self-reviewed the diff - [x] Useful pull request description - [x] Reviewer requested ## Issue(s) Closes #78 --------- Co-authored-by: Raphael <raphael.r.toledo@gmail.com>
- Loading branch information
1 parent
b5629b0
commit e2990b9
Showing
8 changed files
with
780 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
# Centralized Telescope with BLS Signatures | ||
|
||
## Workflow: ALBA Threshold Signature Example | ||
1. **Define Telescope Parameters**: | ||
- Set the number of elements (`nb_elements`) to 1,000. | ||
- Specify `soundness_param` and `completeness_param` as 128.0 each. | ||
- Calculate the prover set size (`set_size`) as 80% of `nb_elements` and the lower bound as 20%. | ||
- Initialize the Telescope with these parameters. | ||
2. **Initialize Signers**: | ||
- Create a list of candidate signers (`Signer`s) with randomly generated keys. | ||
3. **Start Registration**: | ||
- Open a new `Registration`. | ||
- Select and register a subset of candidate signers, updating the count of successfully registered signers. | ||
4. **Close Registration**: | ||
- Finalize the registration by computing the checksum of registered keys. | ||
- Update each registered signer with the computed checksum. | ||
5. **Generate Individual Signatures**: | ||
- Select a subset of registered signers. | ||
- Generate `IndividualSignature`s for each selected signer based on the input message. | ||
6. **Generate ALBA Threshold Signature**: | ||
- Use valid individual signatures to create an `AlbaThresholdSignature`. | ||
- If successful, print details of the generated threshold signature. | ||
7. **Verify ALBA Threshold Signature**: | ||
- Verify the generated threshold signature using the Telescope parameters, registration, and input message. | ||
- Print whether the verification succeeded or failed. | ||
|
||
## Code structure | ||
### Signer | ||
- Signer structure covering signing key, verification key, registration index and registration checksum. | ||
- Initialization of a signer is handled by `new` function. | ||
- It generates a signing key and its verification key. | ||
- The index is set to `0` since the signer is not registered yet. | ||
- The checksum is not initialized since the registration is not closed. | ||
- An initialized signer can be registered with `register` function. | ||
- If the registration is not already closed and the signer is not already registered, the registration can be done. | ||
- The verification key is added to the registration table with the next index. | ||
- The `index` field of the signer is updated with the registration index. | ||
- After the registration is closed, a registered signer is updated with the `get_closed_registration`. | ||
- If the registration is closed and the key of the signer exists in the registered keys, `checksum` field of the signer is updated. | ||
|
||
### Registration | ||
- Registration includes registered keys as a `BTreeMap` and the checksum of all registered keys. | ||
- New registration is initialized by `new` function with an empty `BTreeMap`. Checksum is not initialized in this phase. | ||
- Registration process is closed with `close` function if the registration is not already closed. It is done by hashing all verification keys registered into the `checksum`. | ||
|
||
### Signature | ||
- A single signature is represented with `IndividualSignature`. | ||
- It contains the BLS signature and the index of the signer of signature. | ||
- An `IndividualSignature` is verified by `verify` function with given verification key, against `commitment = Hash(checksum||msg)`. | ||
|
||
### Helpers | ||
- Get commitment: | ||
- Instead of signing only the message, the signature is generated by signing a commitment. | ||
- This is the helper function to compute a commitment by hashing `(checksum || msg)`. | ||
- Collect valid signatures: | ||
- The prover set is built on the individual signatures. | ||
- Each individual signature is validated by checking if the signer of a signature is registered and the signature is valid. | ||
- Each valid signature is converted to its byte representation. | ||
- A hashmap, including the byte representation of a valid signature and its registration index is returned. | ||
- Validate signatures: | ||
- The verifier needs to validate signatures that creates the alba proof. The process is as follows: | ||
- Convert each element in the element sequence of alba proof to a bls signature. | ||
- Aggregate collected signatures by calling `AggregateSignature::aggregate`. | ||
- Collect verification keys by using the indices. Get each verification key from registration if its index is included in `AlbaThresholdSignature.indices`. | ||
- Aggregate collected verification keys by calling `AggregatePublicKey::aggregate`. | ||
- Convert aggregated signature to a signature and aggregated public key to a public key. | ||
- Verify them using `verify` function of bls signatures against the commitment. | ||
|
||
### ALBA Threshold Signature | ||
- It includes the alba proof, indices of the elements exist in the alba proof, and the commitment. | ||
- The `prove` function: | ||
- Checks whether the registration is closed, | ||
- Creates the commitment, | ||
- Collects the valid signatures with `collect_valid_signatures`, | ||
- If the length of valid signatures is less than the set size provided, abort the process, | ||
- Creates the prover set by collecting only the signatures from the hash map of valid signatures, | ||
- Creates the alba proof, | ||
- Collects the registration indices of the elements that are in the alba proof by using valid signatures hashmap. | ||
- Returns the alba proof, indices, and the commitment. | ||
- The `verify` function: | ||
- Checks whether the registration is closed, | ||
- Creates the commitment and compares it with the provided commitment, | ||
- Verifies the signatures that are included in the alba proof by calling `validate_signatures` function. | ||
- Verifies the alba proof. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
use crate::aggregate_signature::registration::Registration; | ||
use crate::aggregate_signature::signature::IndividualSignature; | ||
use crate::{AlbaThresholdSignature, Element}; | ||
use blake2::digest::{Update, VariableOutput}; | ||
use blake2::Blake2bVar; | ||
use blst::min_sig::{AggregatePublicKey, AggregateSignature, PublicKey, Signature}; | ||
use blst::BLST_ERROR; | ||
use std::collections::HashMap; | ||
|
||
/// Helper function to compute a commitment by hashing `(checksum || msg)` | ||
pub(crate) fn get_commitment<const N: usize>(checksum: &[u8], msg: &[u8]) -> [u8; N] { | ||
let mut hasher = Blake2bVar::new(N).expect("Invalid hash size"); | ||
let mut commitment = [0u8; N]; | ||
|
||
hasher.update(checksum); | ||
hasher.update(msg); | ||
hasher | ||
.finalize_variable(&mut commitment) | ||
.expect("Hash finalization failed"); | ||
commitment | ||
} | ||
|
||
/// Collect valid signatures by verifying each individual signature. | ||
/// Returns a hashmap of valid signature bytes to their corresponding index. | ||
pub(crate) fn collect_valid_signatures<const N: usize>( | ||
signature_list: &[IndividualSignature], | ||
registration: &Registration, | ||
msg: &[u8], | ||
) -> HashMap<Element, usize> { | ||
let mut valid_signatures = HashMap::new(); | ||
|
||
match ®istration.checksum { | ||
Some(checksum) => { | ||
for sig in signature_list { | ||
if let Some(verification_key) = registration.registered_keys.get(&sig.index) { | ||
if sig.verify::<N>(checksum, msg, verification_key) { | ||
valid_signatures.insert(sig.signature.to_bytes(), sig.index); | ||
} | ||
} else { | ||
println!("Warning: No verification key found for index {}", sig.index); | ||
} | ||
} | ||
} | ||
None => { | ||
println!("Error: Registration is not closed. Cannot verify signatures."); | ||
} | ||
} | ||
valid_signatures | ||
} | ||
|
||
/// Validate the signatures in `alba_threshold_signature` against the provided message and registration. | ||
/// Returns `true` if all signatures are valid and correctly aggregated, `false` otherwise. | ||
pub(crate) fn validate_signatures( | ||
alba_threshold_signature: &AlbaThresholdSignature, | ||
registration: &Registration, | ||
commitment: &[u8], | ||
) -> bool { | ||
let mut signatures = Vec::with_capacity(alba_threshold_signature.proof.element_sequence.len()); | ||
for sig_bytes in &alba_threshold_signature.proof.element_sequence { | ||
if let Ok(signature) = Signature::from_bytes(sig_bytes.as_slice()) { | ||
signatures.push(signature); | ||
} else { | ||
println!("Error: Failed to parse signature from bytes."); | ||
return false; | ||
} | ||
} | ||
let signature_refs: Vec<&Signature> = signatures.iter().collect(); | ||
let aggregate_signature = | ||
if let Ok(agg_sig) = AggregateSignature::aggregate(signature_refs.as_slice(), false) { | ||
agg_sig.to_signature() | ||
} else { | ||
println!("Error: Failed to aggregate signatures."); | ||
return false; | ||
}; | ||
|
||
let public_key_refs: Vec<&PublicKey> = alba_threshold_signature | ||
.indices | ||
.iter() | ||
.filter_map(|index| registration.registered_keys.get(index)) | ||
.collect(); | ||
|
||
if public_key_refs.len() != signature_refs.len() { | ||
println!("Error: Mismatch between public keys and signatures count."); | ||
return false; | ||
} | ||
|
||
let aggregate_public_key = | ||
if let Ok(agg_pk) = AggregatePublicKey::aggregate(public_key_refs.as_slice(), false) { | ||
agg_pk.to_public_key() | ||
} else { | ||
println!("Error: Failed to aggregate public keys."); | ||
return false; | ||
}; | ||
|
||
let result = | ||
aggregate_signature.verify(false, commitment, &[], &[], &aggregate_public_key, false); | ||
if result == BLST_ERROR::BLST_SUCCESS { | ||
true | ||
} else { | ||
println!("Error: Aggregate signature verification failed."); | ||
false | ||
} | ||
} | ||
|
||
pub(crate) fn ats_size<const N: usize>(ats: &AlbaThresholdSignature) -> usize { | ||
let nb_elements = ats.indices.len(); | ||
let size_indices = nb_elements.saturating_mul(8); | ||
let size_elements = nb_elements.saturating_mul(N); | ||
let size_commitment = ats.commitment.len(); | ||
|
||
size_indices | ||
.saturating_add(size_elements) | ||
.saturating_add(size_commitment) | ||
.saturating_add(16) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
pub(crate) mod helpers; | ||
pub(crate) mod registration; | ||
pub(crate) mod signature; | ||
pub(crate) mod signer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
use blake2::digest::{Update, VariableOutput}; | ||
use blake2::Blake2bVar; | ||
use blst::min_sig::PublicKey; | ||
use std::collections::BTreeMap; | ||
|
||
type Keys = BTreeMap<usize, PublicKey>; | ||
|
||
/// Structure for registration functionality. | ||
#[derive(Debug, Clone)] | ||
pub(crate) struct Registration { | ||
/// Registered keys | ||
pub(crate) registered_keys: Keys, | ||
/// Checksum of registry data | ||
pub(crate) checksum: Option<Vec<u8>>, | ||
} | ||
|
||
impl Registration { | ||
/// Initialize key registration | ||
pub(crate) fn new() -> Self { | ||
Self { | ||
registered_keys: BTreeMap::new(), | ||
checksum: None, | ||
} | ||
} | ||
|
||
/// Close the registration by computing a checksum if it is not already closed. | ||
pub(crate) fn close<const N: usize>(&mut self) { | ||
if self.checksum.is_some() { | ||
println!("Registration is already closed."); | ||
return; | ||
} | ||
|
||
let mut hasher = Blake2bVar::new(N).expect("Invalid hash size"); | ||
let mut hash_output = vec![0u8; N]; | ||
|
||
for key in self.registered_keys.values() { | ||
hasher.update(key.to_bytes().as_slice()); | ||
} | ||
|
||
hasher | ||
.finalize_variable(&mut hash_output) | ||
.expect("Hash finalization failed"); | ||
self.checksum = Some(hash_output); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
use crate::aggregate_signature::helpers::get_commitment; | ||
use blst::min_sig::{PublicKey, Signature}; | ||
use blst::BLST_ERROR; | ||
|
||
/// Single signature | ||
#[derive(Debug, Clone)] | ||
pub(crate) struct IndividualSignature { | ||
/// Signature of type bls signature | ||
pub(crate) signature: Signature, | ||
/// Registration index of the signer | ||
pub(crate) index: usize, | ||
} | ||
|
||
impl IndividualSignature { | ||
/// Verify signature against `commitment = Hash(checksum || msg)` | ||
pub(crate) fn verify<const N: usize>( | ||
&self, | ||
checksum: &[u8], | ||
msg: &[u8], | ||
verification_key: &PublicKey, | ||
) -> bool { | ||
let commitment = get_commitment::<N>(checksum, msg); | ||
let result = self | ||
.signature | ||
.verify(false, &commitment, &[], &[], verification_key, false); | ||
result == BLST_ERROR::BLST_SUCCESS | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
use crate::aggregate_signature::helpers::get_commitment; | ||
use crate::aggregate_signature::registration::Registration; | ||
use crate::aggregate_signature::signature::IndividualSignature; | ||
use blst::min_sig::{PublicKey, SecretKey}; | ||
use rand_core::{CryptoRng, RngCore}; | ||
|
||
/// Threshold signature signer | ||
#[derive(Debug, Clone)] | ||
pub(crate) struct Signer { | ||
/// Signature generation key | ||
signing_key: SecretKey, | ||
/// Signature verification key | ||
verification_key: PublicKey, | ||
/// Registration index of the signer | ||
pub(crate) index: usize, | ||
/// Closed registration checksum | ||
pub(crate) checksum: Option<Vec<u8>>, | ||
} | ||
|
||
impl Signer { | ||
/// Create signing key and verification key for a signer | ||
pub(crate) fn init(rng: &mut (impl RngCore + CryptoRng)) -> Self { | ||
let mut ikm = [0u8; 32]; | ||
rng.fill_bytes(&mut ikm); | ||
|
||
let sk = SecretKey::key_gen(&ikm, &[]) | ||
.expect("Error: Key generation failed due to insufficient IKM length. This should never happen."); | ||
let vk = sk.sk_to_pk(); | ||
|
||
Self { | ||
signing_key: sk, | ||
verification_key: vk, | ||
index: 0, | ||
checksum: None, | ||
} | ||
} | ||
|
||
/// Register signer's verification key. If the registration is not closed, i.e., no checksum found and the | ||
/// signer's verification key is already registered return `None`. Otherwise, insert new verification key with a | ||
/// new index. Return the `RegisteredSigner`. | ||
pub(crate) fn register(&mut self, registration: &mut Registration) -> bool { | ||
if registration.checksum.is_none() { | ||
if registration | ||
.registered_keys | ||
.values() | ||
.any(|v| *v == self.verification_key) | ||
{ | ||
println!("Error: Key already registered!"); | ||
return false; | ||
} | ||
let index = registration.registered_keys.len().saturating_add(1); | ||
registration | ||
.registered_keys | ||
.insert(index, self.verification_key); | ||
self.index = index; | ||
true | ||
} else { | ||
println!("Error: Cannot register, registration is closed!"); | ||
false | ||
} | ||
} | ||
|
||
/// Get closed registration. Update the registered signer with the checksum of registration. | ||
pub(crate) fn get_closed_registration(&mut self, registration: &Registration) { | ||
match ®istration.checksum { | ||
Some(checksum) => { | ||
if registration.registered_keys.contains_key(&self.index) { | ||
self.checksum = Some(checksum.clone()); | ||
} else { | ||
println!("Error: Registration is closed, but the signer is not registered."); | ||
} | ||
} | ||
None => { | ||
println!("Error: Registration is not closed."); | ||
} | ||
} | ||
} | ||
|
||
/// Create an individual signature by signing `commitment = Hash(checksum || msg)` | ||
pub(crate) fn sign<const N: usize>(&self, msg: &[u8]) -> Option<IndividualSignature> { | ||
let checksum = self.checksum.as_ref()?; | ||
let commitment = get_commitment::<N>(checksum, msg); | ||
|
||
Some(IndividualSignature { | ||
signature: self.signing_key.sign(&commitment, &[], &[]), | ||
index: self.index, | ||
}) | ||
} | ||
} |
Oops, something went wrong.