parent
fdfeeae821
commit
29498c4d89
@ -0,0 +1,225 @@ |
|||||||
|
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 that 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<UserId, BTreeMap<Msg, Vec<NodeSignature>>>; |
||||||
|
|
||||||
|
// 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, 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 that has received `threshold + 1` signatures from validator nodes,
|
||||||
|
// gets added to the `chat_log`.
|
||||||
|
struct ChatNetwork { |
||||||
|
pk_set: PublicKeySet, |
||||||
|
nodes: Vec<Node>, |
||||||
|
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` - our protocol requires a message to 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).unwrap(); |
||||||
|
let pk_set = sk_set.public_keys(); |
||||||
|
|
||||||
|
let nodes = (0..n_nodes) |
||||||
|
.map(|id| { |
||||||
|
let sk_share = sk_set.secret_key_share(id).unwrap(); |
||||||
|
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. Produces a new block to be appended to the chat
|
||||||
|
// log. Our consensus uses threshold-signing to verify that 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 that
|
||||||
|
// has received `threshold + 1` node signatures, will produce a valid "combined" signature
|
||||||
|
// and will be 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 node the network that is 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 with 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, i.e. 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 here 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 here 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); |
||||||
|
} |
Loading…
Reference in new issue