fork of https://github.com/poanetwork/threshold_crypto for the needs of nextgraph.org
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
222 lines
8.0 KiB
222 lines
8.0 KiB
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 validator 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, 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<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` - 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 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);
|
|
}
|
|
|