extern crate rand; extern crate threshold_crypto; use std::collections::BTreeMap; use threshold_crypto::{ PublicKeySet, PublicKeyShare, SecretKeySet, SecretKeyShare, Signature, SignatureShare, }; type UserId = usize; type NodeId = usize; type Msg = String; // The database schema that validator nodes use to store messages they receive from users. // Messages are first indexed numerically by user ID then alphabetically by message. Each message // is mapped to its list of valdidator signatures. type MsgDatabase = BTreeMap>>; // An append-only list of chat message "blocks". Each block contains the user ID for the user who // broadcast the message to the network, the message text, and the combined signature of the // message. A block can be appended to this list each time our chat protocol runs its consensus // algorithm. type ChatLog = Vec<(UserId, Msg, Signature)>; // Represents a network of nodes running a distributed chat protocol. Clients, or "users", of our // network, create a string that they want to append to the network's `chat_log`, they broadcast // this message to the network, and each node that receives the message signs it with their // signing-key. When the network runs a round of consensus, each node contributes its set of signed // messages. The first message to receive `threshold + 1` signatures from validator nodes // gets added to the `chat_log`. struct ChatNetwork { pk_set: PublicKeySet, nodes: Vec, chat_log: ChatLog, n_users: usize, } impl ChatNetwork { // Creates a new network of nodes running our distributed chat protocol. // // # Arguments // // `n_nodes` - the number of validator/signing nodes in the network. // `threshold` - a message must have `threshold + 1` validator signatures // before it can be added to the `chat_log`. fn new(n_nodes: usize, threshold: usize) -> Self { let mut rng = rand::thread_rng(); let sk_set = SecretKeySet::random(threshold, &mut rng); let pk_set = sk_set.public_keys(); let nodes = (0..n_nodes) .map(|id| { let sk_share = sk_set.secret_key_share(id); let pk_share = pk_set.public_key_share(id); Node::new(id, sk_share, pk_share) }).collect(); ChatNetwork { pk_set, nodes, chat_log: vec![], n_users: 0, } } fn create_user(&mut self) -> User { let user_id = self.n_users; let user = User::new(user_id); self.n_users += 1; user } fn get_node(&self, id: NodeId) -> &Node { self.nodes.get(id).expect("No `Node` exists with that ID") } fn get_mut_node(&mut self, id: NodeId) -> &mut Node { self.nodes .get_mut(id) .expect("No `Node` exists with that ID") } // Run a single round of the consensus algorithm. If consensus produced a new block, append // that block the chat log. fn step(&mut self) { if let Some(block) = self.run_consensus() { self.chat_log.push(block); } } // Our chat protocol's consensus algorithm. This algorithm produces a new block to append to the chat // log. Our consensus uses threshold-signing to verify a message has received enough // signature shares (i.e. has been signed by `threshold + 1` nodes). fn run_consensus(&self) -> Option<(UserId, Msg, Signature)> { // Create a new `MsgDatabase` of every message that has been signed by a validator node. let all_pending: MsgDatabase = self.nodes .iter() .fold(BTreeMap::new(), |mut all_pending, node| { for (user_id, signed_msgs) in &node.pending { let mut user_msgs = all_pending.entry(*user_id).or_insert_with(BTreeMap::new); for (msg, sigs) in signed_msgs.iter() { let sigs = sigs.iter().cloned(); user_msgs .entry(msg.to_string()) .or_insert_with(Vec::new) .extend(sigs); } } all_pending }); // Iterate over the `MsgDatabase` numerically by user ID, then iterate over each user's // messages alphabetically. Try to combine the validator signatures. The first message to // receive `threshold + 1` node signatures produces a valid "combined" signature // and is added to the chat log. for (user_id, signed_msgs) in &all_pending { for (msg, sigs) in signed_msgs.iter() { let sigs = sigs.iter().filter_map(|node_sig| { let node_sig_is_valid = self .get_node(node_sig.node_id) .pk_share .verify(&node_sig.sig, msg.as_bytes()); if node_sig_is_valid { Some((node_sig.node_id, &node_sig.sig)) } else { None } }); if let Ok(sig) = self.pk_set.combine_signatures(sigs) { return Some((*user_id, msg.clone(), sig)); } } } None } } // A network node running our chat protocol. struct Node { id: NodeId, sk_share: SecretKeyShare, pk_share: PublicKeyShare, pending: MsgDatabase, } impl Node { fn new(id: NodeId, sk_share: SecretKeyShare, pk_share: PublicKeyShare) -> Self { Node { id, sk_share, pk_share, pending: BTreeMap::new(), } } // Receives a message from a user, signs the message with the node's signing-key share, // then adds the signed message to its database of `pending` messages. fn recv(&mut self, user_id: UserId, msg: Msg) { let sig = NodeSignature { node_id: self.id, sig: self.sk_share.sign(msg.as_bytes()), }; self.pending .entry(user_id) .or_insert_with(BTreeMap::new) .entry(msg) .or_insert_with(Vec::new) .push(sig); } } #[derive(Clone, Debug)] struct NodeSignature { node_id: NodeId, sig: SignatureShare, } // A client of our chat protocol. struct User { id: UserId, } impl User { fn new(id: UserId) -> Self { User { id } } // Sends a message to one of the network's validator nodes. fn send(&self, node: &mut Node, msg: Msg) { node.recv(self.id, msg); } } fn main() { // Creates a new network of 3 nodes running our chat protocol. The protocol has a // signing-threshold of 1. This means each message requires 2 validator signatures before it can be // added to the chat log. let mut network = ChatNetwork::new(3, 1); let node1 = network.get_node(0).id; let node2 = network.get_node(1).id; // Register a new user, Alice, with the network. Alice wants to add a message to the chat log. let alice = network.create_user(); let alice_greeting = "hey, this is alice".to_string(); // Alice sends her message to a validator. The validator signs the message. Before Alice can // send her message to a second validator, the network runs a round of consensus. Because // Alice's message has only one validator signature, it is not added to the chat log. alice.send(network.get_mut_node(node1), alice_greeting.clone()); network.step(); assert!(network.chat_log.is_empty()); // Alice sends her message to a second validator. The validator signs the message. Alice's // message now has two signatures (which is `threshold + 1` signatures). The network runs a // round of consensus, which successfully creates a combined-signature for Alice's message. // Alice's message is appended to the chat log. alice.send(network.get_mut_node(node2), alice_greeting.clone()); network.step(); assert_eq!(network.chat_log.len(), 1); }