Implement SyncKeyGen.

This is a _synchronous_ key generation algorithm. We will use it in
`DynamicHoneyBadger`, on top of `HoneyBadger` to satisfy the synchrony
requirements.

It can also be used independently e.g. on top of a blockchain.
This commit is contained in:
Andreas Fackler 2018-06-20 10:11:33 +02:00
parent 58c1a7e15d
commit 7eb487f329
6 changed files with 464 additions and 22 deletions

View File

@ -2,7 +2,7 @@ pub mod error;
pub mod poly;
#[cfg(feature = "serialization-protobuf")]
pub mod protobuf_impl;
mod serde_impl;
pub mod serde_impl;
use std::fmt;
use std::hash::{Hash, Hasher};
@ -132,6 +132,10 @@ impl<E: Engine> SecretKey<E> {
SecretKey(rng.gen())
}
pub fn from_value(f: E::Fr) -> Self {
SecretKey(f)
}
/// Returns the matching public key.
pub fn public_key(&self) -> PublicKey<E> {
PublicKey(E::G1Affine::one().mul(self.0))
@ -167,7 +171,7 @@ impl<E: Engine> SecretKey<E> {
}
/// An encrypted message.
#[derive(Deserialize, Serialize, Debug)]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Ciphertext<E: Engine>(
#[serde(with = "serde_impl::projective")] E::G1,
Vec<u8>,
@ -216,13 +220,25 @@ impl<E: Engine> Hash for DecryptionShare<E> {
}
/// A public key and an associated set of public key shares.
#[derive(Serialize, Deserialize, Clone, Debug, Hash)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct PublicKeySet<E: Engine> {
/// The coefficients of a polynomial whose value at `0` is the "master key", and value at
/// `i + 1` is key share number `i`.
commit: Commitment<E>,
}
impl<E: Engine> PartialEq for PublicKeySet<E> {
fn eq(&self, other: &Self) -> bool {
self.commit == other.commit
}
}
impl<E: Engine> Hash for PublicKeySet<E> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.commit.hash(state);
}
}
impl<E: Engine> From<Commitment<E>> for PublicKeySet<E> {
fn from(commit: Commitment<E>) -> PublicKeySet<E> {
PublicKeySet { commit }
@ -449,7 +465,7 @@ mod tests {
// Each of the shares is a valid signature matching its public key share.
for (i, sig) in &sigs {
pk_set.public_key_share(*i).verify(sig, msg);
assert!(pk_set.public_key_share(*i).verify(sig, msg));
}
// Combined, they produce a signature matching the main public key.

View File

@ -1,23 +1,20 @@
//! Utilities for distributed key generation.
//! Utilities for distributed key generation: uni- and bivariate polynomials and commitments.
//!
//! A `BivarPoly` can be used for Verifiable Secret Sharing (VSS) and for key generation by a
//! trusted dealer. In a perfectly synchronous setting, e.g. on a blockchain or other agreed
//! transaction log, it works like this:
//! If `G` is a group of prime order `r` (written additively), and `g` is a generator, then
//! multiplication by integers factors through `r`, so the map `x -> x * g` (the sum of `x`
//! copies of `g`) is a homomorphism from the field `Fr` of integers modulo `r` to `G`. If the
//! _discrete logarithm_ is hard, i.e. it is infeasible to reverse this map, then `x * g` can be
//! considered a _commitment_ to `x`: By publishing it, you can guarantee to others that you won't
//! change your mind about the value `x`, without revealing it.
//!
//! The dealer generates a `BivarPoly` of degree `t` and publishes the `BivariateCommitment`,
//! with which the polynomial's values can be publicly verified. They then send _row_ `m > 0` to
//! node number `m`. Node `m`, in turn, sends _value_ `s` to node number `s`. Then if `2 * t + 1`
//! nodes confirm that they received a valid row, and there are at most `t` faulty nodes, then at
//! least `t + 1` honest nodes sent on an entry of every other node's column to that node. So we
//! know that every node can now reconstruct its column and the value at `0` of its column. These
//! values all lie on a univariate polynomial of degree `t`, so they can be used as secret keys.
//! This concept extends to polynomials: If you have a polynomial `f` over `Fr`, defined as
//! `a * X * X + b * X + c`, you can publish `a * g`, `b * g` and `c * g`. Then others will be able
//! to verify any single value `f(x)` of the polynomial without learning the original polynomial,
//! because `f(x) * g == x * x * (a * g) + x * (b * g) + (c * g)`. Only after learning three (in
//! general `degree + 1`) values, they can interpolate `f` itself.
//!
//! For Distributed Key Generation (DKG), every node proposes a polynomial via VSS. After a fixed
//! number (at least `N - 2 * t` if there are `N` nodes and up to `t` faulty ones) of them have
//! successfully been distributed, every node adds up the resulting secrets. Since the sum of
//! polynomials of degree `t` is itself a polynomial of degree `t`, these sums are still valid
//! secret keys, but now nobody knows the master key (number `0`).
// TODO: Expand this explanation and add examples, once the API is complete and stable.
//! This module defines univariate polynomials (in one variable) and _symmetric_ bivariate
//! polynomials (in two variables) over a field `Fr`, as well as their _commitments_ in `G`.
use std::borrow::Borrow;
use std::hash::{Hash, Hasher};
@ -27,9 +24,10 @@ use pairing::{CurveAffine, CurveProjective, Engine, Field, PrimeField};
use rand::Rng;
/// A univariate polynomial in the prime field.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Poly<E: Engine> {
/// The coefficients of a polynomial.
#[serde(with = "super::serde_impl::field_vec")]
coeff: Vec<E::Fr>,
}

View File

@ -80,3 +80,105 @@ pub mod projective_vec {
Ok(wrap_vec.into_iter().map(|CurveWrap(c, _)| c).collect())
}
}
/// Serialization and deserialization of vectors of field elements.
pub mod field_vec {
use std::borrow::Borrow;
use std::marker::PhantomData;
use pairing::{PrimeField, PrimeFieldRepr};
use serde::de::Error as DeserializeError;
use serde::ser::Error as SerializeError;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
/// A wrapper type to facilitate serialization and deserialization of field elements.
pub struct FieldWrap<F, B>(B, PhantomData<F>);
impl<F, B> FieldWrap<F, B> {
pub fn new(f: B) -> Self {
FieldWrap(f, PhantomData)
}
}
impl<F> FieldWrap<F, F> {
pub fn into_inner(self) -> F {
self.0
}
}
impl<F: PrimeField, B: Borrow<F>> Serialize for FieldWrap<F, B> {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
let mut bytes = Vec::new();
self.0
.borrow()
.into_repr()
.write_be(&mut bytes)
.map_err(|_| S::Error::custom("failed to write bytes"))?;
bytes.serialize(s)
}
}
impl<'de, F: PrimeField> Deserialize<'de> for FieldWrap<F, F> {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let bytes: Vec<u8> = Deserialize::deserialize(d)?;
let mut repr = F::zero().into_repr();
repr.read_be(&bytes[..])
.map_err(|_| D::Error::custom("failed to write bytes"))?;
Ok(FieldWrap::new(F::from_repr(repr).map_err(|_| {
D::Error::custom("invalid field element representation")
})?))
}
}
pub fn serialize<S, F>(vec: &[F], s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
F: PrimeField,
{
let wrap_vec: Vec<FieldWrap<F, &F>> = vec.iter().map(FieldWrap::new).collect();
wrap_vec.serialize(s)
}
pub fn deserialize<'de, D, F>(d: D) -> Result<Vec<F>, D::Error>
where
D: Deserializer<'de>,
F: PrimeField,
{
let wrap_vec = <Vec<FieldWrap<F, F>>>::deserialize(d)?;
Ok(wrap_vec.into_iter().map(|FieldWrap(f, _)| f).collect())
}
}
#[cfg(test)]
mod tests {
use bincode;
use pairing::bls12_381::Bls12;
use pairing::Engine;
use rand::{self, Rng};
#[derive(Debug, Serialize, Deserialize)]
pub struct Vecs<E: Engine> {
#[serde(with = "super::projective_vec")]
curve_points: Vec<E::G1>,
#[serde(with = "super::field_vec")]
field_elements: Vec<E::Fr>,
}
impl<E: Engine> PartialEq for Vecs<E> {
fn eq(&self, other: &Self) -> bool {
self.curve_points == other.curve_points && self.field_elements == other.field_elements
}
}
#[test]
fn vecs() {
let mut rng = rand::thread_rng();
let vecs: Vecs<Bls12> = Vecs {
curve_points: rng.gen_iter().take(10).collect(),
field_elements: rng.gen_iter().take(10).collect(),
};
let ser_vecs = bincode::serialize(&vecs).expect("serialize vecs");
let de_vecs = bincode::deserialize(&ser_vecs).expect("deserialize vecs");
assert_eq!(vecs, de_vecs);
}
}

View File

@ -127,3 +127,4 @@ pub mod messaging;
pub mod proto;
#[cfg(feature = "serialization-protobuf")]
pub mod proto_io;
pub mod sync_key_gen;

239
src/sync_key_gen.rs Normal file
View File

@ -0,0 +1,239 @@
//! A _synchronous_ algorithm for dealerless distributed key generation.
//!
//! This protocol is meant to run in a _completely synchronous_ setting where each node handles all
//! messages in the same order. This can be achieved by making its messages transactions on top of
//! `HoneyBadger`, or by running it "on-chain", i.e. committing its messages to a blockchain.
//!
//! Its messages are encrypted where necessary, so they can be publicly broadcast.
//!
//! When the protocol completes, every node receives a secret key share suitable for threshold
//! signatures and encryption. The secret master key is not known by anyone. The protocol succeeds
//! if up to `threshold` nodes are faulty.
//!
//! # How it works
//!
//! The algorithm is based on ideas from
//! [Distributed Key Generation in the Wild](https://eprint.iacr.org/2012/377.pdf) and
//! [A robust threshold elliptic curve digital signature providing a new verifiable secret sharing scheme](https://www.researchgate.net/profile/Ihab_Ali/publication/4205262_A_robust_threshold_elliptic_curve_digital_signature_providing_a_new_verifiable_secret_sharing_scheme/links/02e7e538f15726323a000000/A-robust-threshold-elliptic-curve-digital-signature-providing-a-new-verifiable-secret-sharing-scheme.pdf?origin=publication_detail).
//!
//! If there were a trusted dealer, they would generate a `BivarPoly` of degree `t` and publish
//! the `BivarCommitment`, with which the polynomial's values can be publicly verified. They'd
//! then send _row_ `m > 0` to node number `m`. Node `m`, in turn, sends _value_ `s` to node number
//! `s`. Then if `2 * t + 1` nodes confirm that they received a valid row, and there are at most
//! `t` faulty nodes, then at least `t + 1` honest nodes sent on an entry of every other node's
//! column to that node. So we know that every node can now reconstruct its column and the value at
//! `0` of its column. These values all lie on a univariate polynomial of degree `t`, so they can
//! be used as secret keys.
//!
//! To avoid trusting a single dealer, we make sure that at least `t + 1` nodes use the above
//! method to generate a polynomial each. We then sum up the secret keys we received from each
//! "dealer", and use that as our secret key. Then no single node knows the sum of the master keys.
use std::collections::btree_map::Entry;
use std::collections::{BTreeMap, BTreeSet};
use crypto::poly::{BivarCommitment, BivarPoly, Poly};
use crypto::serde_impl::field_vec::FieldWrap;
use crypto::{Ciphertext, PublicKey, PublicKeySet, SecretKey};
use bincode;
use pairing::bls12_381::{Bls12, Fr, G1Affine};
use pairing::{CurveAffine, Field};
use rand::OsRng;
// TODO: No need to send our own row and value to ourselves.
/// A commitment to a bivariate polynomial, and for each node, an encrypted row of values.
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Propose(BivarCommitment<Bls12>, Vec<Ciphertext<Bls12>>);
/// A confirmation that we have received a node's proposal and verified our row against the
/// commitment. For each node, it contains one encrypted value of our row.
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Accept(u64, Vec<Ciphertext<Bls12>>);
/// The information needed to track a single proposer's secret sharing process.
struct ProposalState {
/// The proposer's commitment.
commit: BivarCommitment<Bls12>,
/// The verified values we received from `Accept` messages.
values: BTreeMap<u64, Fr>,
/// The nodes which have accepted this proposal, valid or not.
accepts: BTreeSet<u64>,
}
impl ProposalState {
/// Creates a new proposal state with a commitment.
fn new(commit: BivarCommitment<Bls12>) -> ProposalState {
ProposalState {
commit,
values: BTreeMap::new(),
accepts: BTreeSet::new(),
}
}
/// Returns `true` if at least `2 * threshold + 1` nodes have accepted.
fn is_complete(&self, threshold: usize) -> bool {
self.accepts.len() > 2 * threshold
}
}
/// A synchronous algorithm for dealerless distributed key generation.
///
/// It requires that all nodes handle all messages in the exact same order.
pub struct SyncKeyGen {
/// Our node index.
our_idx: u64,
/// Our secret key.
sec_key: SecretKey<Bls12>,
/// The public keys of all nodes, by node index.
pub_keys: Vec<PublicKey<Bls12>>,
/// Proposed bivariate polynomial.
proposals: BTreeMap<u64, ProposalState>,
/// The degree of the generated polynomial.
threshold: usize,
}
impl SyncKeyGen {
/// Creates a new `SyncKeyGen` instance, together with the `Propose` message that should be
/// broadcast.
pub fn new(
our_idx: u64,
sec_key: SecretKey<Bls12>,
pub_keys: Vec<PublicKey<Bls12>>,
threshold: usize,
) -> (SyncKeyGen, Propose) {
let mut rng = OsRng::new().expect("OS random number generator");
let our_proposal = BivarPoly::random(threshold, &mut rng);
let commit = our_proposal.commitment();
let rows: Vec<_> = pub_keys
.iter()
.enumerate()
.map(|(i, pk)| {
let row = our_proposal.row(i as u64 + 1);
let bytes = bincode::serialize(&row).expect("failed to serialize row");
pk.encrypt(&bytes)
})
.collect();
let key_gen = SyncKeyGen {
our_idx,
sec_key,
pub_keys,
proposals: BTreeMap::new(),
threshold,
};
(key_gen, Propose(commit, rows))
}
/// Handles a `Propose` message. If it is valid, returns an `Accept` message to be broadcast.
pub fn handle_propose(
&mut self,
sender_idx: u64,
Propose(commit, rows): Propose,
) -> Option<Accept> {
let commit_row = commit.row(self.our_idx + 1);
match self.proposals.entry(sender_idx) {
Entry::Occupied(_) => return None, // Ignore multiple proposals.
Entry::Vacant(entry) => {
entry.insert(ProposalState::new(commit));
}
}
let ser_row = self.sec_key.decrypt(rows.get(self.our_idx as usize)?)?;
let row: Poly<Bls12> = bincode::deserialize(&ser_row).ok()?; // Ignore invalid messages.
if row.commitment() != commit_row {
debug!("Invalid proposal from node {}.", sender_idx);
return None;
}
// The row is valid: now encrypt one value for each node.
let values = self
.pub_keys
.iter()
.enumerate()
.map(|(idx, pk)| {
let val = row.evaluate(idx as u64 + 1);
let ser_val =
bincode::serialize(&FieldWrap::new(val)).expect("failed to serialize value");
pk.encrypt(ser_val)
})
.collect();
Some(Accept(sender_idx, values))
}
/// Handles an `Accept` message.
pub fn handle_accept(&mut self, sender_idx: u64, accept: Accept) {
if let Err(err) = self.handle_accept_or_err(sender_idx, accept) {
debug!("Invalid accept from node {}: {}", sender_idx, err);
}
}
/// Returns the number of complete proposals. If this is at least `threshold + 1`, the keys can
/// be generated, but it is possible to wait for more to increase security.
pub fn count_complete(&self) -> usize {
self.proposals
.values()
.filter(|proposal| proposal.is_complete(self.threshold))
.count()
}
/// Returns `true` if the proposal of the given node is complete.
pub fn is_node_ready(&self, proposer_idx: u64) -> bool {
self.proposals
.get(&proposer_idx)
.map_or(false, |proposal| proposal.is_complete(self.threshold))
}
/// Returns `true` if enough proposals are complete to safely generate the new key.
pub fn is_ready(&self) -> bool {
self.count_complete() > self.threshold
}
/// Returns the new secret key and the public key set.
///
/// These are only secure if `is_ready` returned `true`. Otherwise it is not guaranteed that
/// none of the nodes knows the secret master key.
pub fn generate(&self) -> (PublicKeySet<Bls12>, SecretKey<Bls12>) {
let mut pk_commit = Poly::zero().commitment();
let mut sk_val = Fr::zero();
for proposal in self
.proposals
.values()
.filter(|proposal| proposal.is_complete(self.threshold))
{
pk_commit += proposal.commit.row(0);
let row: Poly<Bls12> =
Poly::interpolate(proposal.values.iter().take(self.threshold + 1));
sk_val.add_assign(&row.evaluate(0));
}
(pk_commit.into(), SecretKey::from_value(sk_val))
}
/// Handles an `Accept` message or returns an error string.
fn handle_accept_or_err(
&mut self,
sender_idx: u64,
Accept(proposer_idx, values): Accept,
) -> Result<(), String> {
let proposal = self
.proposals
.get_mut(&proposer_idx)
.ok_or_else(|| "sender does not exist".to_string())?;
if !proposal.accepts.insert(sender_idx) {
return Err("duplicate accept".to_string());
}
if values.len() != self.pub_keys.len() {
return Err("wrong node count".to_string());
}
let ser_val: Vec<u8> = self
.sec_key
.decrypt(&values[self.our_idx as usize])
.ok_or_else(|| "value decryption failed".to_string())?;
let val = bincode::deserialize::<FieldWrap<Fr, Fr>>(&ser_val)
.map_err(|err| format!("deserialization failed: {:?}", err))?
.into_inner();
if proposal.commit.evaluate(self.our_idx + 1, sender_idx + 1) != G1Affine::one().mul(val) {
return Err("wrong value".to_string());
}
proposal.values.insert(sender_idx + 1, val);
Ok(())
}
}

86
tests/sync_key_gen.rs Normal file
View File

@ -0,0 +1,86 @@
//! Tests for synchronous distributed key generation.
extern crate env_logger;
extern crate hbbft;
extern crate pairing;
extern crate rand;
use std::collections::BTreeMap;
use hbbft::crypto::{PublicKey, SecretKey};
use hbbft::sync_key_gen::SyncKeyGen;
use pairing::bls12_381::Bls12;
fn test_sync_key_gen_with(threshold: usize, node_num: usize) {
let mut rng = rand::thread_rng();
// Generate individual key pairs for encryption. These are not suitable for threshold schemes.
let sec_keys: Vec<SecretKey<Bls12>> = (0..node_num).map(|_| SecretKey::new(&mut rng)).collect();
let pub_keys: Vec<PublicKey<Bls12>> = sec_keys.iter().map(|sk| sk.public_key()).collect();
// Create the `SyncKeyGen` instances and initial proposals.
let mut nodes = Vec::new();
let proposals: Vec<_> = sec_keys
.into_iter()
.enumerate()
.map(|(idx, sk)| {
let (sync_key_gen, proposal) =
SyncKeyGen::new(idx as u64, sk, pub_keys.clone(), threshold);
nodes.push(sync_key_gen);
proposal
})
.collect();
// Handle the first `threshold + 1` proposals. Those should suffice for key generation.
let mut accepts = Vec::new();
for (sender_idx, proposal) in proposals[..=threshold].iter().enumerate() {
for (node_idx, node) in nodes.iter_mut().enumerate() {
let accept = node
.handle_propose(sender_idx as u64, proposal.clone())
.expect("valid proposal");
// Only the first `threshold + 1` manage to commit their `Accept`s.
if node_idx <= 2 * threshold {
accepts.push((node_idx, accept));
}
}
}
// Handle the `Accept`s from `2 * threshold + 1` nodes.
for (sender_idx, accept) in accepts {
for node in &mut nodes {
assert!(!node.is_ready()); // Not enough `Accept`s yet.
node.handle_accept(sender_idx as u64, accept.clone());
}
}
// Compute the keys and test a threshold signature.
let msg = "Help I'm trapped in a unit test factory";
let pub_key_set = nodes[0].generate().0;
let sig_shares: BTreeMap<_, _> = nodes
.iter()
.enumerate()
.map(|(idx, node)| {
assert!(node.is_ready());
let (pks, sk) = node.generate();
assert_eq!(pks, pub_key_set);
let sig = sk.sign(msg);
assert!(pks.public_key_share(idx as u64).verify(&sig, msg));
(idx as u64, sig)
})
.collect();
let sig = pub_key_set
.combine_signatures(sig_shares.iter().take(threshold + 1))
.expect("signature shares match");
assert!(pub_key_set.public_key().verify(&sig, msg));
}
#[test]
fn test_sync_key_gen() {
// This returns an error in all but the first test.
let _ = env_logger::try_init();
for &node_num in &[1, 2, 3, 4, 8, 15] {
let threshold = (node_num - 1) / 3;
test_sync_key_gen_with(threshold, node_num);
}
}