diff --git a/Cargo.toml b/Cargo.toml index edbfa649..64724a99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,11 +10,14 @@ include = ["**/*.rs", "Cargo.toml", "README.md", ".gitignore"] [dependencies] blake2 = "0.10.6" +rand = "0.8" [dev-dependencies] rand_core = "0.6.4" rand_chacha = "0.3.1" test-case = "3.3.1" +blst = "0.3.13" +time-elapsed = "0.1.0" [lints.rust] missing-copy-implementations = "warn" diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..97f844e6 --- /dev/null +++ b/examples/README.md @@ -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. diff --git a/examples/aggregate_signature/helpers.rs b/examples/aggregate_signature/helpers.rs new file mode 100644 index 00000000..701c4471 --- /dev/null +++ b/examples/aggregate_signature/helpers.rs @@ -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(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( + signature_list: &[IndividualSignature], + registration: &Registration, + msg: &[u8], +) -> HashMap { + 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::(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(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) +} diff --git a/examples/aggregate_signature/mod.rs b/examples/aggregate_signature/mod.rs new file mode 100644 index 00000000..defaa1ff --- /dev/null +++ b/examples/aggregate_signature/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod helpers; +pub(crate) mod registration; +pub(crate) mod signature; +pub(crate) mod signer; diff --git a/examples/aggregate_signature/registration.rs b/examples/aggregate_signature/registration.rs new file mode 100644 index 00000000..ef0c7091 --- /dev/null +++ b/examples/aggregate_signature/registration.rs @@ -0,0 +1,45 @@ +use blake2::digest::{Update, VariableOutput}; +use blake2::Blake2bVar; +use blst::min_sig::PublicKey; +use std::collections::BTreeMap; + +type Keys = BTreeMap; + +/// 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>, +} + +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(&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); + } +} diff --git a/examples/aggregate_signature/signature.rs b/examples/aggregate_signature/signature.rs new file mode 100644 index 00000000..320f865a --- /dev/null +++ b/examples/aggregate_signature/signature.rs @@ -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( + &self, + checksum: &[u8], + msg: &[u8], + verification_key: &PublicKey, + ) -> bool { + let commitment = get_commitment::(checksum, msg); + let result = self + .signature + .verify(false, &commitment, &[], &[], verification_key, false); + result == BLST_ERROR::BLST_SUCCESS + } +} diff --git a/examples/aggregate_signature/signer.rs b/examples/aggregate_signature/signer.rs new file mode 100644 index 00000000..318c6494 --- /dev/null +++ b/examples/aggregate_signature/signer.rs @@ -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>, +} + +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(&self, msg: &[u8]) -> Option { + let checksum = self.checksum.as_ref()?; + let commitment = get_commitment::(checksum, msg); + + Some(IndividualSignature { + signature: self.signing_key.sign(&commitment, &[], &[]), + index: self.index, + }) + } +} diff --git a/examples/centralized_threshold.rs b/examples/centralized_threshold.rs new file mode 100644 index 00000000..97a4c054 --- /dev/null +++ b/examples/centralized_threshold.rs @@ -0,0 +1,412 @@ +//! Centralized Telescope example with BLS multi-signature +use crate::aggregate_signature::helpers::{ + ats_size, collect_valid_signatures, get_commitment, validate_signatures, +}; +use crate::aggregate_signature::registration::Registration; +use crate::aggregate_signature::signature::IndividualSignature; +use crate::aggregate_signature::signer::Signer; +use alba::centralized_telescope::proof::Proof; +use alba::centralized_telescope::Telescope; +use rand::Rng; +use rand_chacha::ChaCha20Rng; +use rand_core::SeedableRng; +use std::time::Instant; +mod aggregate_signature; +const DATA_LENGTH: usize = 48; +pub(crate) type Element = [u8; DATA_LENGTH]; + +#[derive(Debug, Clone)] +pub(crate) struct AlbaThresholdSignature { + /// Centralized telescope proof + pub(crate) proof: Proof, + /// Registration indices of the element sequence signers + pub(crate) indices: Vec, + /// Commitment `Hash(checksum || msg)` + pub(crate) commitment: Vec, +} + +impl AlbaThresholdSignature { + /// Create AlbaThresholdSignature. Validate and collect signatures in byte representation. + /// Create Alba proof and extract indices of proof elements. + /// Return proof, commitment, and indices. + fn prove( + alba: &Telescope, + signature_list: &[IndividualSignature], + registration: &Registration, + msg: &[u8], + ) -> Option { + if let Some(checksum) = ®istration.checksum { + // Collect valid individual signatures' byte representation into a hashmap + let valid_signatures = collect_valid_signatures::(signature_list, registration, msg); + println!("-- Collected {} valid signatures. ", valid_signatures.len()); + + // Check if there are enough valid signatures + if valid_signatures.len() < alba.get_set_size() as usize { + println!( + "Error: Not enough valid signatures to create an ATS! Expected at least {} signatures, but got {}.", + alba.get_set_size(), + valid_signatures.len() + ); + return None; + } + + // Collect the byte representation of valid signatures into a Vec + let prover_set: Vec = valid_signatures.keys().copied().collect(); + + println!("-- Creating alba proof. "); + let proof = alba.prove(&prover_set)?; + println!("-- Alba proof created: "); + println!( + " - Numbers of retries done to find the proof: {}", + proof.retry_counter + ); + println!( + " - Index of the searched subtree to find the proof: {}", + proof.search_counter + ); + println!( + " - Number of elements in the proof sequence: {}", + proof.element_sequence.len() + ); + + // Extract the registration indices of the elements that form the proof element sequence + let indices: Vec = proof + .element_sequence + .iter() + .filter_map(|element| valid_signatures.get(element.as_slice()).copied()) + .collect(); + + let commitment = get_commitment::(checksum, msg).to_vec(); + + // Return the constructed AlbaThresholdSignature + Some(Self { + proof, + indices, + commitment, + }) + } else { + println!("Error: Registration is not closed."); + None + } + } + + /// Verify AlbaThresholdSignature. Validate individual signatures and verify Alba proof. + fn verify( + &self, + alba: &Telescope, + registration: &Registration, + msg: &[u8], + ) -> bool { + if let Some(checksum) = ®istration.checksum { + let commitment = get_commitment::(checksum, msg).to_vec(); + + if commitment != self.commitment { + println!("Error: Commitment mismatch."); + return false; + } + + println!("-- Validating proof elements. "); + // Verify the signers were registered, aggregate the signatures and verify them at once + if !validate_signatures(self, registration, &commitment) { + println!("Error: Signature validation failed."); + return false; + } + println!( + "-- {} proof elements are validated. ", + self.proof.element_sequence.len() + ); + + println!("-- Verifying alba proof. "); + let result = alba.verify(&self.proof); + + if result { + println!("-- Success: Alba proof verification passed."); + } else { + println!("Error: Alba proof verification failed."); + } + result + } else { + println!("Error: Registration is not closed."); + false + } + } +} + +fn main() { + // Initialize RNG + let mut rng = ChaCha20Rng::from_seed(Default::default()); + let sentence = "ALBA Rocks!"; + let msg = sentence.as_bytes(); + + println!("\n--------------- ALBA Threshold Signature ---------------"); + println!("--------------------------------------------------------"); + + // Telescope parameters + println!("-- Telescope parameters:"); + let nb_signers: u64 = 3_000; + let nb_elements: u64 = 1_000; + let soundness_param = 128.0; + let completeness_param = 128.0; + let np_percentage: u64 = 80; + let nf_percentage: u64 = 60; + let set_size = nb_elements.saturating_mul(np_percentage).div_ceil(100); + let lower_bound = nb_elements.saturating_mul(nf_percentage).div_ceil(100); + let alba = Telescope::create(soundness_param, completeness_param, set_size, lower_bound); + + println!(" - Soundness parameter: {soundness_param}"); + println!(" - Completeness parameter: {completeness_param}"); + println!(" - Prover set size: {np_percentage}%"); + println!(" - Lower bound: {nf_percentage}%"); + + // Initialize signers + println!("--------------------------------------------------------"); + let mut signers: Vec = (0..nb_signers as usize) + .map(|_| Signer::init(&mut rng)) + .collect(); + println!("-- {} signers initialized.", signers.len()); + + // Start new registration process + let mut registration = Registration::new(); + println!("-- Registration is opened."); + + // Register signer candidates + let mut registered_count: u64 = 0; + let register_range = nb_elements; + for signer in signers.iter_mut().take(register_range as usize) { + if signer.register(&mut registration) { + registered_count = registered_count.saturating_add(1); + } + } + println!("-- {registered_count} signers are registered."); + + // Close the registration process + registration.close::(); + println!("-- Registration is closed."); + + for signer in signers.iter_mut().take(registered_count as usize) { + signer.get_closed_registration(®istration); + } + + // Create individual signatures + let signature_range = rng.gen_range(set_size..registered_count); + let signature_list: Vec = signers + .iter() + .take(signature_range as usize) + .filter_map(|signer| signer.sign::(msg)) + .collect(); + println!("-- {} signatures generated.", signature_list.len()); + + println!("--------------------------------------------------------"); + println!("--------- Generating Alba threshold signature. ---------"); + + // Generate AlbaThresholdSignature proof + let start_prove = Instant::now(); + if let Some(alba_threshold_signature) = + AlbaThresholdSignature::prove::(&alba, &signature_list, ®istration, msg) + { + let duration_prove = start_prove.elapsed(); + println!("-- Alba threshold signature is generated."); + println!( + "** Time elapsed in signature generation: {:.3}s", + duration_prove.as_secs_f64() + ); + println!( + "** Alba Threshold Signature Size: {} B", + ats_size::(&alba_threshold_signature) + ); + println!("--------------------------------------------------------"); + println!("--------- Verifying Alba threshold signature. ----------"); + + // Verify the proof + let start_verify = Instant::now(); + if alba_threshold_signature.verify::(&alba, ®istration, msg) { + let duration_verify = start_verify.elapsed(); + println!("-- Verification successful."); + println!( + "** Time elapsed in signature verification: {:?}µs", + duration_verify.as_micros() + ); + } else { + println!("-- Verification failed."); + } + println!("--------------------------------------------------------"); + } else { + println!("-- Failed to generate Alba threshold signature."); + } +} + +// Some basic tests +#[cfg(test)] +mod tests { + use super::*; + use rand_core::RngCore; + + // Error: Registration is not closed. Cannot verify signatures. + #[test] + fn test_collect_valid_signatures_no_closed_reg() { + let mut rng = ChaCha20Rng::from_seed(Default::default()); + let mut msg = [0u8; 16]; + rng.fill_bytes(&mut msg); + + let set_size = 100; + let mut signers: Vec = (0..set_size).map(|_| Signer::init(&mut rng)).collect(); + + let mut registration = Registration::new(); + for signer in &mut signers { + signer.register(&mut registration); + } + + registration.close::(); + for signer in &mut signers { + signer.get_closed_registration(®istration); + } + + // Manually set an invalid index for the last signer + signers[99].index = 999; + + // Collect signatures from all signers + let signature_list: Vec = signers + .iter() + .filter_map(|signer| signer.sign::(&msg)) + .collect(); + + // Invalidate the registration by removing the checksum + registration.checksum = None; + + // Validate the collected signatures + let valid_signatures = + collect_valid_signatures::(&signature_list, ®istration, &msg); + + // Assert that the registration is not closed and no signatures are collected + if valid_signatures.len() != 0 { + println!( + "Test failed: Expected 0 valid signatures, but got {}.", + valid_signatures.len() + ); + } + assert_eq!( + valid_signatures.len(), + 0, + "Error: Registration is not closed. Cannot verify signatures." + ); + } + + // Warning: No verification key found for index 999 + #[test] + fn test_collect_valid_signatures_unregistered_signer() { + let mut rng = ChaCha20Rng::from_seed(Default::default()); + let mut msg = [0u8; 16]; + rng.fill_bytes(&mut msg); + + let set_size = 100; + let mut signers: Vec = (0..set_size).map(|_| Signer::init(&mut rng)).collect(); + + let mut registration = Registration::new(); + for signer in &mut signers { + signer.register(&mut registration); + } + + registration.close::(); + for signer in &mut signers { + signer.get_closed_registration(®istration); + } + + // Manually set an invalid index for the last signer + signers[99].index = 999; + + // Collect signatures from all signers + let signature_list: Vec = signers + .iter() + .filter_map(|signer| signer.sign::(&msg)) + .collect(); + + // Validate the collected signatures + let valid_signatures = + collect_valid_signatures::(&signature_list, ®istration, &msg); + + // Assert the number of valid signatures is one less than the total + assert_eq!( + valid_signatures.len(), + set_size - 1, + "Expected {} signatures, got {}.", + set_size - 1, + valid_signatures.len() + ); + + // Ensure the unregistered signer's signature is not included in valid signatures + let invalid_index_bytes = 999u64.to_le_bytes(); // Convert the invalid index to bytes + assert!( + !valid_signatures.contains_key(&invalid_index_bytes[..]), + "Unregistered signer's signature should not be included in valid signatures." + ); + } + + #[test] + fn test_sign_without_checksum() { + let mut rng = ChaCha20Rng::from_seed(Default::default()); + let mut msg = [0u8; 16]; + rng.fill_bytes(&mut msg); + let mut signer = Signer::init(&mut rng); + let mut registration = Registration::new(); + signer.register(&mut registration); + + // Attempt to sign the message without a checksum and assert it returns None + assert!(signer.sign::(&msg).is_none()); + } + + //Error: Registration is not closed. + #[test] + fn test_get_checksum_before_closed() { + let mut rng = ChaCha20Rng::from_seed(Default::default()); + let set_size = 10; + let mut signers: Vec = (0..set_size).map(|_| Signer::init(&mut rng)).collect(); + + let mut registration = Registration::new(); + for signer in &mut signers { + signer.register(&mut registration); + } + + // Attempt to get the closed registration checksum (before it's closed) + signers[0].get_closed_registration(®istration); + + // Assert that the checksum is still None + assert!( + signers[0].checksum.is_none(), + "Checksum should remain None before registration is closed." + ); + } + + // Error: Key already registered! + #[test] + fn test_duplicate_registering() { + let mut rng = ChaCha20Rng::from_seed(Default::default()); + let set_size = 10; + let mut signers: Vec = (0..set_size).map(|_| Signer::init(&mut rng)).collect(); + + let mut registration = Registration::new(); + for signer in &mut signers { + signer.register(&mut registration); + } + + // Attempt to re-register the first signer and assert it fails + assert_eq!(signers[0].register(&mut registration), false); + } + + // Error: Cannot register, registration is closed! + #[test] + fn test_register_closed_registration() { + let mut rng = ChaCha20Rng::from_seed(Default::default()); + let set_size = 10; + let mut signers: Vec = (0..set_size).map(|_| Signer::init(&mut rng)).collect(); + + let mut registration = Registration::new(); + for signer in &mut signers { + signer.register(&mut registration); + } + registration.close::(); + + // Attempt to register a new signer after closure + let mut new_signer = Signer::init(&mut rng); + assert_eq!(new_signer.register(&mut registration), false); + } +}