Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: inverse dlog statement is built by the verifier, and not by the prover #30

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 9 additions & 14 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,28 @@ name = "fs-dkr"
version = "0.1.0"
authors = [
"Omer Shlomovits <[email protected]>",
"Tudor Cebere <[email protected]>"
"Tudor Cebere <[email protected]>",
"Drew Stone <[email protected]>",
]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html



[dependencies.paillier]
git = "https://github.com/KZen-networks/rust-paillier"
tag = "v0.3.10"
default-features = false

[dependencies.zk-paillier]
git = "https://github.com/KZen-networks/zk-paillier"
tag = "v0.3.12"
version = "0.4.2"
package = "kzen-paillier"
default-features = false

[dependencies.multi-party-ecdsa]
git = "https://github.com/ZenGo-X/multi-party-ecdsa"
tag = "v0.7.1"
git = "https://github.com/webb-tools/multi-party-ecdsa"
default-features = false

[dependencies]
curv = { package = "curv-kzen", version = "0.7", default-features = true }
zk-paillier = { version = "0.4.3", default-features = false }
curv = { package = "curv-kzen", version = "0.9", default-features = true }
serde = { version = "1.0", features = ["derive"] }
serde_derive = "1.0"
zeroize = "1"
round-based = { version = "0.1.4", features = ["dev"] }
thiserror = "1.0.26"
thiserror = "1.0.26"
sha2 = "0.9"
50 changes: 48 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,57 @@ Rushing adversary is a common assumption in Multiparty Computation (MPC). In FS-
in this write-up we show how by adjusting FS-DKG to key rotation for threshold ecdsa the above shortcomings are avoided.

## Our Model
We use standard proactive security assumptions. The protocol will be run by $n$ parties. We assume honest majority, that is, number of corruptions is $t<n/2$. The adversary is malicious, and rushing.
We use standard proactive security assumptions. The protocol will be run by $n$ parties. We assume honest majority, that is, number of corruptions is $t<=n/2$. The adversary is malicious, and rushing.
For communication, the parties have access to a broadcast channel (can be implemented via a bulletin board).
For threshold ECDSA, we focus on [GG20](https://eprint.iacr.org/2020/540.pdf) protocol, currently considered state of the art and most widely deployed threshold ecdsa scheme (e.g. [multi-party-ecdsa](https://github.com/ZenGo-X/multi-party-ecdsa), [tss-lib](https://github.com/binance-chain/tss-lib)).

## How To Use
### Refresh a Key
Each party calls `RefreshMessage::distribute(key)` on their `LocalKey` and broadcasts the `RefreshMessage` while saving their new `DecryptionKey`. <br>
After recieving all the refresh messages each party calls `RefreshMessage::collect(..)` with a vector of all the refresh messages, a mutable reference to their own key, and their new `DecryptionKey`, This will validate all the refresh messages, and if all the proofs are correct it will update the local key to contain the new decryption keys of all the parties.

Example:
```rust
// All parties should run this
let mut party_i_key: LocalKey<_>;
let (party_i_refresh_message, party_i_new_decryption_key) = RefreshMessage::distribute(party_i_key);
broadcast(party_i_refresh_message);
let vec_refresh_messages = recv_from_broadcast();
RefreshMessage::collect(&vec_refresh_messages, &mut party_i_key, party_i_new_decryption_key, &[])?;
```

### Replacing a party
Each party that wants to join first generates a `JoinMessage` via `JoinMessage::distribute()` and broadcasts it to the current parties. <br>
The existing parties choose the index(who are they replacing) for the joining party.
Note that this part is delicate and needs to happen outside of the library because it requires some kind of mutual agreement, and you cannot trust the new party to communicate which party are they replacing. <br>
After agreeing on the index each party modifies the join message to contain the index `join_message.party_index = Some(index)`. <br>
Each existing party calls `RefreshMessage::replace(join_message, local_key)` with the join message and its own local key, this returns a refresh message and a new decryption key, just like in a Key Refresh, and they all broadcast the `RefreshMessage`. <br>
Each existing party recieves all the broadcasted refresh messages and calls `RefreshMessage::collect(..)` with a vector of all the refresh messages, a mutable reference to their own key, the new `DecryptionKey`, and a slice of all the join messages(`JoinMessage`) <br>
This will validate both the refresh messages and the join messages and if all the proofs are correct it will update the local key both as a refresh(new decryption keys) and replace the existing parties with the new ones. <br>
The new party calls `join_message.collect(..)` with the broadcasted `RefreshMessage` of the existing parties and all the join messages which returns a new `LocalKey` for the new party.

Example:
```rust
// PoV of the new party
let (join_message, new_party_decryption_key) = JoinMessage::distribute();
broadcast(join_message);
let new_party_index = recv_broadcast();
let vec_refresh_messages = recv_from_broadcast();
let new_party_local_key = join_message.collect(&vec_refresh_messages, new_party_decryption_key, &[])?;

// PoV of the other parties
let mut party_i_key: LocalKey<_>;
let mut join_message = recv_broadcast();
let new_index = decide_who_to_replace(&join_message);
broadcast(new_index);
assert!(recv_from_broadcast().iter().all(|index| index == new_index));
join_message.party_index = Some(new_index);
let (party_i_refresh_message, party_i_new_decryption_key) = RefreshMessage::replace(join_message, party_i_key)?;
broadcast(party_i_refresh_message);
let vec_refresh_messages = recv_from_broadcast();
RefreshMessage::collect(&vec_refresh_messages, &mut party_i_key, party_i_new_decryption_key, &[join_message])?;
```

## High-level Description of FS-DKG
Here we give a short description of the FS-DKG protocol.
FS-DKG works in one round. This round includes a single broadcast message from each party $P_j$. For Setup, we assume every party in the system has a public/private key pair for Paillier encryption scheme.
Expand Down Expand Up @@ -54,4 +101,3 @@ The third relevant work is Jens Groth' [Non interactive DKG and DKR](https://epr
## Acknowledgments
We thank Claudio Orlandi, Kobi Gurkan and Nikolaos Makriyannis for reviewing the note


93 changes: 42 additions & 51 deletions src/add_party_message.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Message definitions for new parties that can join the protocol
//! Key points about a new party joining the refresh protocol:
//! * A new party wants to join, broadcasting a pailier ek, correctness of the ek generation,
//! * A new party wants to join, broadcasting a paillier ek, correctness of the ek generation,
//! dlog statements and dlog proofs.
//! * All the existing parties receives the join message. We assume for now that everyone accepts
//! the new party. All parties pick an index and add the new ek to their LocalKey at the given index.
Expand All @@ -13,10 +13,11 @@
use crate::error::{FsDkrError, FsDkrResult};
use crate::refresh_message::RefreshMessage;
use curv::arithmetic::{BasicOps, Modulo, One, Samplable, Zero};
use curv::cryptographic_primitives::hashing::{Digest, DigestExt};
use curv::cryptographic_primitives::secret_sharing::feldman_vss::{
ShamirSecretSharing, VerifiableSS,
};
use curv::elliptic::curves::traits::{ECPoint, ECScalar};
use curv::elliptic::curves::{Curve, Point, Scalar};
use curv::BigInt;
use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::party_i::Keys;
use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::party_i::SharedKeys;
Expand All @@ -25,17 +26,15 @@ use paillier::{Decrypt, EncryptionKey, KeyGeneration, Paillier};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::Debug;
use zeroize::Zeroize;
use zk_paillier::zkproofs::{CompositeDLogProof, DLogStatement, NICorrectKeyProof};
use zk_paillier::zkproofs::{CompositeDLogProof, DLogStatement, NiCorrectKeyProof};

/// Message used by new parties to join the protocol.
#[derive(Clone, Deserialize, Serialize, Debug)]
pub struct JoinMessage {
pub(crate) ek: EncryptionKey,
pub(crate) dk_correctness_proof: NICorrectKeyProof,
pub(crate) party_index: Option<usize>,
pub(crate) dlog_statement_base_h1: DLogStatement,
pub(crate) dlog_statement_base_h2: DLogStatement,
pub(crate) dk_correctness_proof: NiCorrectKeyProof,
pub(crate) party_index: Option<u16>,
pub(crate) dlog_statement: DLogStatement,
pub(crate) composite_dlog_proof_base_h1: CompositeDLogProof,
pub(crate) composite_dlog_proof_base_h2: CompositeDLogProof,
}
Expand All @@ -44,7 +43,7 @@ pub struct JoinMessage {
/// environment variables for each party that they agree on. In this case, each new party generates
/// it's own DlogStatements and submits it's proofs
fn generate_h1_h2_n_tilde() -> (BigInt, BigInt, BigInt, BigInt, BigInt) {
let (ek_tilde, dk_tilde) = Paillier::keypair().keys();
let (ek_tilde, dk_tilde) = Paillier::keypair_with_modulus_size(crate::PAILLIER_KEY_SIZE).keys();
let one = BigInt::one();
let phi = (&dk_tilde.p - &one) * (&dk_tilde.q - &one);
let h1 = BigInt::sample_below(&ek_tilde.n);
Expand All @@ -61,13 +60,8 @@ fn generate_h1_h2_n_tilde() -> (BigInt, BigInt, BigInt, BigInt, BigInt) {
(ek_tilde.n, h1, h2, xhi, xhi_inv)
}

/// Generates the DlogStatements and CompositeProofs using the parameters generated by [generate_h1_h2_n_tilde]
fn generate_dlog_statement_proofs() -> (
DLogStatement,
DLogStatement,
CompositeDLogProof,
CompositeDLogProof,
) {
/// Generates the DlogStatement and CompositeProofs using the parameters generated by [generate_h1_h2_n_tilde]
fn generate_dlog_statement_proofs() -> (DLogStatement, CompositeDLogProof, CompositeDLogProof) {
let (n_tilde, h1, h2, xhi, xhi_inv) = generate_h1_h2_n_tilde();

let dlog_statement_base_h1 = DLogStatement {
Expand All @@ -87,41 +81,38 @@ fn generate_dlog_statement_proofs() -> (

(
dlog_statement_base_h1,
dlog_statement_base_h2,
composite_dlog_proof_base_h1,
composite_dlog_proof_base_h2,
)
}

impl JoinMessage {
pub fn set_party_index(&mut self, new_party_index: u16) {
self.party_index = Some(new_party_index);
}
/// The distribute phase for a new party. This distribute phase has to happen before the existing
/// parties distribute. Calling this function will generate a JoinMessage and a pair of Pailier
/// parties distribute. Calling this function will generate a JoinMessage and a pair of Paillier
/// [Keys] that are going to be used when generating the [LocalKey].
pub fn distribute() -> (Self, Keys) {
let pailier_key_pair = Keys::create(0);
let (
dlog_statement_base_h1,
dlog_statement_base_h2,
composite_dlog_proof_base_h1,
composite_dlog_proof_base_h2,
) = generate_dlog_statement_proofs();
let paillier_key_pair = Keys::create(0);
let (dlog_statement, composite_dlog_proof_base_h1, composite_dlog_proof_base_h2) =
generate_dlog_statement_proofs();

let join_message = JoinMessage {
// in a join message, we only care about the ek and the correctness proof
ek: pailier_key_pair.ek.clone(),
dk_correctness_proof: NICorrectKeyProof::proof(&pailier_key_pair.dk, None),
dlog_statement_base_h1,
dlog_statement_base_h2,
ek: paillier_key_pair.ek.clone(),
dk_correctness_proof: NiCorrectKeyProof::proof(&paillier_key_pair.dk, None),
dlog_statement,
composite_dlog_proof_base_h1,
composite_dlog_proof_base_h2,
party_index: None,
};

(join_message, pailier_key_pair)
(join_message, paillier_key_pair)
}
/// Returns the party index if it has been assigned one, throws
/// [FsDkrError::NewPartyUnassignedIndexError] otherwise
pub fn get_party_index(&self) -> FsDkrResult<usize> {
pub fn get_party_index(&self) -> FsDkrResult<u16> {
self.party_index
.ok_or(FsDkrError::NewPartyUnassignedIndexError)
}
Expand All @@ -130,17 +121,17 @@ impl JoinMessage {
/// tailored for a sent JoinMessage on which we assigned party_index. In this collect, a [LocalKey]
/// is filled with the information provided by the [RefreshMessage]s from the other parties and
/// the other join messages (multiple parties can be added/replaced at once).
pub fn collect<P>(
pub fn collect<E, H>(
&self,
refresh_messages: &[RefreshMessage<P>],
refresh_messages: &[RefreshMessage<E, H>],
paillier_key: Keys,
join_messages: &[JoinMessage],
t: usize,
n: usize,
) -> FsDkrResult<LocalKey<P>>
t: u16,
n: u16,
) -> FsDkrResult<LocalKey<E>>
where
P: ECPoint + Clone + Zeroize + Debug,
P::Scalar: PartialEq + Clone + Debug + Zeroize,
E: Curve,
H: Digest + Clone,
{
RefreshMessage::validate_collect(refresh_messages, t, n)?;

Expand Down Expand Up @@ -169,28 +160,29 @@ impl JoinMessage {
.0
.into_owned();

let new_share_fe: P::Scalar = ECScalar::from(&new_share);
let new_share_fe: Scalar<E> = Scalar::<E>::from(&new_share);
let paillier_dk = paillier_key.dk.clone();
let key_linear_x_i = new_share_fe.clone();
let key_linear_y = P::generator() * new_share_fe.clone();
let key_linear_y = Point::<E>::generator() * new_share_fe.clone();
let keys_linear = SharedKeys {
x_i: key_linear_x_i,
y: key_linear_y,
};
let mut pk_vec: Vec<_> = (0..n)
let mut pk_vec: Vec<_> = (0..n as usize)
.map(|i| refresh_messages[0].points_committed_vec[i].clone() * li_vec[0].clone())
.collect();

for i in 0..n as usize {
for j in 1..(t as usize + 1) {
for j in 1..(t + 1) as usize {
pk_vec[i] = pk_vec[i].clone()
+ refresh_messages[j].points_committed_vec[i].clone() * li_vec[j].clone();
}
}

// check what parties are assigned in the current rotation and associate their paillier
// ek to each available party index.
let available_parties: HashMap<usize, &EncryptionKey> = refresh_messages

let available_parties: HashMap<u16, &EncryptionKey> = refresh_messages
.iter()
.map(|msg| (msg.party_index, &msg.ek))
.chain(std::iter::once((party_index, &paillier_key.ek)))
Expand All @@ -204,14 +196,14 @@ impl JoinMessage {
// TODO: submit the statement the dlog proof as well!
// check what parties are assigned in the current rotation and associate their DLogStatements
// and check their CompositeDlogProofs.
let available_h1_h2_ntilde_vec: HashMap<usize, &DLogStatement> = refresh_messages
let available_h1_h2_ntilde_vec: HashMap<u16, &DLogStatement> = refresh_messages
.iter()
.map(|msg| (msg.party_index, &msg.dlog_statement))
.chain(std::iter::once((party_index, &self.dlog_statement_base_h1)))
.chain(std::iter::once((party_index, &self.dlog_statement)))
.chain(join_messages.iter().map(|join_message| {
(
join_message.party_index.unwrap(),
&join_message.dlog_statement_base_h1,
&join_message.dlog_statement,
)
}))
.collect();
Expand All @@ -220,7 +212,6 @@ impl JoinMessage {
let paillier_key_vec: Vec<EncryptionKey> = (1..n + 1)
.map(|party| {
let ek = available_parties.get(&party);

match ek {
None => EncryptionKey {
n: BigInt::zero(),
Expand Down Expand Up @@ -251,7 +242,7 @@ impl JoinMessage {
}

// generate the vss_scheme for the LocalKey
let (vss_scheme, _) = VerifiableSS::<P>::share(t, n, &new_share_fe);
let (vss_scheme, _) = VerifiableSS::<E>::share(t, n, &new_share_fe);
// TODO: secret cleanup might be needed.

let local_key = LocalKey {
Expand All @@ -262,9 +253,9 @@ impl JoinMessage {
y_sum_s: refresh_messages[0].public_key.clone(),
h1_h2_n_tilde_vec: h1_h2_ntilde_vec,
vss_scheme,
i: party_index as u16,
t: t as u16,
n: n as u16,
i: party_index,
t: t,
n: n,
};

Ok(local_key)
Expand Down
28 changes: 19 additions & 9 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use thiserror::Error;

pub type FsDkrResult<T> = Result<T, FsDkrError>;

#[derive(Error, Debug)]
#[derive(Error, Debug, Clone)]
pub enum FsDkrError {
#[error("Too many malicious parties detected! Threshold {threshold:?}, Number of Refreshed Messages: {refreshed_keys:?}, Malicious parties detected when trying to refresh: malicious_parties:?")]
PartiesThresholdViolation {
Expand All @@ -15,22 +15,32 @@ pub enum FsDkrError {
#[error("Shares did not pass verification.")]
PublicShareValidationError,

#[error("SizeMismatch error for the refresh message {refresh_message_index:?} - Fairness proof length: {fairness_proof_len:?}, Points Commited Length: {points_commited_len:?}, Points Encrypted Length: {points_encrypted_len:?}")]
#[error("SizeMismatch error for the refresh message {refresh_message_index:?} - pdl proof length: {pdl_proof_len:?}, Points Commited Length: {points_commited_len:?}, Points Encrypted Length: {points_encrypted_len:?}")]
SizeMismatchError {
refresh_message_index: usize,
fairness_proof_len: usize,
pdl_proof_len: usize,
points_commited_len: usize,
points_encrypted_len: usize,
},

#[error("Fairness proof verification failed, results - T_add_e_Y == z_G: {t_add_eq_z_g:?} - e_u_add_c_e == enc_z_w: {e_u_add_eq_z_w:?}")]
FairnessProof {
t_add_eq_z_g: bool,
e_u_add_eq_z_w: bool,
#[error("PDLwSlack proof verification failed, results: u1 == u1_test: {is_u1_eq:?}, u2 == u2_test: {is_u2_eq:?}, u3 == u3_test: {is_u3_eq:?}")]
PDLwSlackProof {
is_u1_eq: bool,
is_u2_eq: bool,
is_u3_eq: bool,
},

#[error("Range Proof failed for party: {party_index:?}")]
RangeProof { party_index: usize },

#[error("The Paillier moduli size of party: {party_index:?} is {moduli_size:?} bits, when it should be 2047-2048 bits")]
MouliTooSmall {
party_index: u16,
moduli_size: usize,
},

#[error("Paillier verification proof failed for party {party_index:?}")]
PaillierVerificationError { party_index: usize },
PaillierVerificationError { party_index: u16 },

#[error("A new party did not receive a valid index.")]
NewPartyUnassignedIndexError,
Expand All @@ -39,5 +49,5 @@ pub enum FsDkrError {
BroadcastedPublicKeyError,

#[error("DLog proof failed for party {party_index:?}")]
DLogProofValidation { party_index: usize },
DLogProofValidation { party_index: u16 },
}
Loading