Added an example for threshold signing.

This commit is contained in:
DrPeterVanNostrand 2018-08-14 12:47:32 +00:00 committed by Andreas Fackler
parent fdfeeae821
commit 29498c4d89
2 changed files with 231 additions and 0 deletions

View File

@ -7,3 +7,9 @@ number before the ciphertext can be successfully decrypted. This example also
demonstrates the idea of a "trusted dealer", i.e. some trusted entity that is
responsible for generating the keys.
- [`Threshold Signing`](threshold_sig.rs) - Demonstrates how threshold signing
can be used to generate an append-only ledger of chat messages. Each node
running our chat protocol receives and signs messages (using its share of the
network's master secret-key). The network adds a new message to the ledger once
enough nodes (`threshold + 1`) have signed a given message.

225
examples/threshold_sig.rs Normal file
View File

@ -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);
}