use std::{
collections::{HashMap, hash_map::Entry},
sync::Arc,
};
use anyhow::anyhow;
use crate::{
MembershipDigest, RegisterError, Stake, StmResult,
membership_commitment::{MerkleTree, MerkleTreeConcatenationLeaf},
signature_scheme::{BlsVerificationKey, BlsVerificationKeyProofOfPossession},
};
pub type RegisteredParty = MerkleTreeConcatenationLeaf;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct KeyRegistration {
keys: HashMap<BlsVerificationKey, Stake>,
}
impl KeyRegistration {
pub fn init() -> Self {
Self::default()
}
pub fn register(
&mut self,
stake: Stake,
pk: BlsVerificationKeyProofOfPossession,
) -> StmResult<()> {
if let Entry::Vacant(e) = self.keys.entry(pk.vk) {
pk.verify_proof_of_possession()
.map_err(|_| RegisterError::KeyInvalid(Box::new(pk)))?;
e.insert(stake);
return Ok(());
}
Err(anyhow!(RegisterError::KeyRegistered(Box::new(pk.vk))))
}
pub fn close<D>(self) -> ClosedKeyRegistration<D>
where
D: MembershipDigest,
{
let mut total_stake: Stake = 0;
let mut reg_parties = self
.keys
.iter()
.map(|(&vk, &stake)| {
let (res, overflow) = total_stake.overflowing_add(stake);
if overflow {
panic!("Total stake overflow");
}
total_stake = res;
MerkleTreeConcatenationLeaf(vk, stake)
})
.collect::<Vec<RegisteredParty>>();
reg_parties.sort();
ClosedKeyRegistration {
merkle_tree: Arc::new(MerkleTree::new(®_parties)),
reg_parties,
total_stake,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ClosedKeyRegistration<D: MembershipDigest> {
pub reg_parties: Vec<RegisteredParty>,
pub total_stake: Stake,
pub merkle_tree: Arc<MerkleTree<D::ConcatenationHash, MerkleTreeConcatenationLeaf>>,
}
#[cfg(test)]
mod tests {
use proptest::{collection::vec, prelude::*};
use rand_chacha::ChaCha20Rng;
use rand_core::SeedableRng;
use crate::{MithrilMembershipDigest, signature_scheme::BlsSigningKey};
use super::*;
proptest! {
#[test]
fn test_keyreg(stake in vec(1..1u64 << 60, 2..=10),
nkeys in 2..10_usize,
fake_it in 0..4usize,
seed in any::<[u8;32]>()) {
let mut rng = ChaCha20Rng::from_seed(seed);
let mut kr = KeyRegistration::init();
let gen_keys = (1..nkeys).map(|_| {
let sk = BlsSigningKey::generate(&mut rng);
BlsVerificationKeyProofOfPossession::from(&sk)
}).collect::<Vec<_>>();
let fake_key = {
let sk = BlsSigningKey::generate(&mut rng);
BlsVerificationKeyProofOfPossession::from(&sk)
};
let mut keys = HashMap::new();
for (i, &stake) in stake.iter().enumerate() {
let mut pk = gen_keys[i % gen_keys.len()];
if fake_it == 0 {
pk.pop = fake_key.pop;
}
let reg = kr.register(stake, pk);
match reg {
Ok(_) => {
assert!(keys.insert(pk.vk, stake).is_none());
},
Err(error) => match error.downcast_ref::<RegisterError>(){
Some(RegisterError::KeyRegistered(pk1)) => {
assert!(pk1.as_ref() == &pk.vk);
assert!(keys.contains_key(&pk.vk));
},
Some(RegisterError::KeyInvalid(a)) => {
assert_eq!(fake_it, 0);
assert!(a.verify_proof_of_possession().is_err());
},
_ => {panic!("Unexpected error: {error}")}
}
}
}
if !kr.keys.is_empty() {
let closed = kr.close::<MithrilMembershipDigest>();
let retrieved_keys = closed.reg_parties.iter().map(|r| (r.0, r.1)).collect::<HashMap<_,_>>();
assert!(retrieved_keys == keys);
}
}
}
mod golden {
use blake2::{Blake2b, digest::consts::U32};
use crate::{
Initializer, MithrilMembershipDigest, Parameters,
membership_commitment::MerkleTreeBatchCommitment,
};
use super::*;
const GOLDEN_JSON: &str = r#"
{
"root":[4,3,108,183,145,65,166,69,250,202,51,64,90,232,45,103,56,138,102,63,209,245,81,22,120,16,6,96,140,204,210,55],
"nr_leaves":4,
"hasher":null
}"#;
fn golden_value() -> MerkleTreeBatchCommitment<Blake2b<U32>, MerkleTreeConcatenationLeaf> {
let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
let params = Parameters {
m: 10,
k: 5,
phi_f: 0.8,
};
let number_of_parties = 4;
let mut key_reg = KeyRegistration::init();
for stake in 0..number_of_parties {
let initializer = Initializer::new(params, stake, &mut rng);
key_reg.register(initializer.stake, initializer.pk).unwrap();
}
let closed_key_reg: ClosedKeyRegistration<MithrilMembershipDigest> = key_reg.close();
closed_key_reg.merkle_tree.to_merkle_tree_batch_commitment()
}
#[test]
fn golden_conversions() {
let value = serde_json::from_str(GOLDEN_JSON)
.expect("This JSON deserialization should not fail");
assert_eq!(golden_value(), value);
let serialized =
serde_json::to_string(&value).expect("This JSON serialization should not fail");
let golden_serialized = serde_json::to_string(&golden_value())
.expect("This JSON serialization should not fail");
assert_eq!(golden_serialized, serialized);
}
}
}