Skip to content
Open
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
135 changes: 135 additions & 0 deletions docs/dkg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Distributed Key Generation

We define a variant of the two-round DKG protocol PedPop \[[KG](https://eprint.iacr.org/2020/852.pdf)\].
Our variant, PedPop+ is less efficient, but achieves a notion of simulatability with aborts,
a stronger notion of security than the one promised by plain PedPop.

PedPop+ is a five rounds protocol and makes use in three of its rounds of a reliable broadcast channel. A reliable broadcast is a 3 round protocol,
implying that the total number of PedPop+ rounds 11. The broadcast channel is implemented in `src/protocol/echo_broadcast.rs`.

The implemented DKG serves as a generic one that can be used with multiple different underlying elliptic curves. We thus use it with `Secp256k1` for ECDSA schemes, `Curve25519` for EdDSA scheme, and `BLS12_381` for the confidential key derivation functionality.

## Keygen, Reshare and Refresh

The core of the dkg protocol is implemented in a function called `do_keyshare` and serves for three applications:

* Key generation: denoted in the implementation with `keygen`. It allows a set of parties to jointly generate from scratch a private key share each and a master public key. The master public key should be common for all the participants and should reflect each of the private shares.

* Key resharing: denoted in the implementation with `reshare`. It allows for a set of participants who already own valid private shares to kick away other participants from the pool, create fresh shares for new participants i.e. new joiners to the pool, and/or change the **cryptographic threshold** described in section [Types of Thresholds](#types-of-thresholds).

* Key refresh: denoted in the implementation with `refresh`. It is a special case of the key resharing in which the set of participants stays the same before and after the protocol run and with no changes to the crypto. The goal being that each participant would refresh their signing share without modifying the master public key.

## Types of Thresholds

There are two types of thresholds one has to be aware of: the **asynchronous distributed systems threshold** a.k.a. the **BFT threshold**, and the **cryptography threshold** a.k.a. the **reconstruction threshold**.

The BFT threshold states that the maximum number of faulty nodes a distributed system ($\mathsf{MaxFaulty}$) can tolerate while still reaching consensus is at most one-third of the total number of participants $N$. More specifically:
Comment on lines +22 to +26
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I like that we're getting into the threshold types here but I wonder if it's relevant for the DKG algorithm. MaxFaulty is unused so I think we could move this part of the discussion to an appendix or similar since it's still interesting. For the purpose of understanding the DKG we only need to know the MaxMalicious parameter which in this case is the reconstruction lower bound - 1 as per our conversation yesterday.

Also, I think we take the reconstruction bound as input in the code but we talk abut max malicious here. Is that correct? I wonder if we should consider migrating to use max malicious as input to the dkg protocol in the future (which is a bit of a tricky change to do safely).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the purpose of understanding the DKG we only need to know the MaxMalicious parameter which in this case is the reconstruction lower bound - 1 as per our conversation yesterday.

I see that the MaxFaulty is also essential to the understanding of the algorithm. I suggest I keep this section but I definitely expand things in #98 and #18

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I think we take the reconstruction bound as input in the code but we talk abut max malicious here. Is that correct? I wonder if we should consider migrating to use max malicious as input to the dkg protocol in the future (which is a bit of a tricky change to do safely).

Could you pinpoint the spot in the implementation where we talk about reconstruction bound?


$$\mathsf{MaxFaulty} \leq \frac{N - 1}{3}$$

The cryptography threshold refers to the maximum number of necessay malicious parties ($\mathsf{MaxMalicious}$) a certain scheme can handle without compromising on the security and assuming the existance of an underlying reliable broadcast channel. $\mathsf{MaxMalicious}$ is scheme dependent and can have a different value than $\mathsf{MaxFaulty}$. For instance, in the OT based ECDSA, $\mathsf{MaxMalicious}$ can be up to $N-1$, but in Robust ECDSA scheme $\mathsf{MaxMalicious}$ must not exceed $\frac{N - 1}{2}$.

### DKG and thresholds

Due to the fact that PedPop+ utilizes reliable broadcast channel to securely generate private shares, it thus lies on the edge between the asynchronous distributed systems and cryptography. For this reason, we set
$\mathsf{MaxFaulty} = \frac{N - 1}{3}$ as an invariable parameter and allow our key generation and key resharing protocols to fix/modify only the $\mathsf{MaxMalicious}$ threshold depending on the scheme requirements and on the library user's choice.

## Technical Details: Key Generation & Key Resharing

Let $P_1, \cdots P_N$ be $N$ different participants, and $\mathsf{MaxMalicious}$ be the desired cryptography threshold. Let $H_1, H_2, H_3$ be domain separated hash functions.

We define PedPop+ key generation as follows; All the instruction preceeded with `+++` are added to the key generation, transforming it to the key resharing protocol.

No special inputs are given to the **key generation** protocol beyond the public parameters defined above. However, the inputs to the **key resharing** are as follows:

1. `+++` The old private share $\mathit{OldSK}$ that $P_i$ held prior to the key resharing. This value is set to None only if $P_i$ is a freshly new participant.

2. `+++` The old participants set $\mathit{OldSigners}$ that held valid private shares prior to the key resharing.

3. `+++` The old master public key $\mathit{OldPK}$ that the $\mathit{OldSigners}$ held prior to the key resharing.

4. `+++` The old cryptography threshold $\mathsf{OldMaxMalicious}$ prior to the key resharing.
``

### Round 1

1.1 Each $P_i$ asserts that $1 < \mathsf{MaxMalicious} < N$.

$\quad$ `+++` Each $P_i$ sets $I \gets \set{P_1 \ldots P_N} \cap \mathit{OldSigners}$

$\quad$ `+++` Each $P_i$ asserts that $\mathsf{OldMaxMalicious} \leq |I|$.

1.2 Each $P_i$ generates a random 32-byte sesssion identifier $\mathit{sid}_i$

1.3 Each $P_i$ reliably broadcasts $\mathit{sid}_i$

### Round 2

2.1 Each $P_i$ waits to receive $\mathit{sid}_j$ from every participant $P_j$

2.2 Each $P_i$ computes the hash $\mathit{sid} \gets H_1(\mathit{sid}_1, \cdots \mathit{sid}_N)$

2.3 Each $P_i$ generates a random polynomial $f_i$ of degree $\mathsf{MaxMalicious}$.


$\quad$ `+++` Each $P_i$ computes the following:

$\quad$ `+++` If $P_i\notin \mathit{OldSigners}$ then set $f_i(0) \gets 0$

$\quad$ `+++` Else set $f_i(0) \gets \lambda_i(I) \cdot \mathit{OldSK}$


2.4 Each $P_i$ generates a commitment of the polynomial $C_i \gets f_i \cdot G$ (commits every coefficient of the polynomial).

2.5 Each $P_i$ generates a hash $h_i \gets H_2(i, C_i, \mathit{sid})$

2.6 Each $P_i$ picks a random nonce $k_i$ and computes $R_i \gets k_i \cdot G$

2.7 Each $P_i$ computes the Schnorr challenge $c_i \gets H_3(\mathit{sid}, i, C_i(0), R_i)$

2.8 Each $P_i$ computes the proof $s_i \gets k_i + f_i(0) \cdot c_i$

2.9 Each $P_i$ sends $h_i$ to every participant

### Round 3

3.1 Each $P_i$ waits to receive $h_i$ from every participant $P_j$.

3.2 Each $P_i$ reliably broadcasts $(C_i, R_i, s_i)$.

### Round 4

4.1 Each $P_i$ waits to receive $(C_j, \pi_j)$ from every participant $P_j$.

4.2 Each $P_i$ computes: $\forall j\in\set{1, \cdots N}, \quad c_j \gets H_3(\mathit{sid}, j, C_j(0), R_j)$.

4.3 Each $P_i$ asserts that: $\forall j\in\set{1, \cdots N}, \quad R_j = s_i \cdot G - c_j \cdot C_j(0)$.

4.4 Each $P_i$ asserts that: $\forall j\in\set{1, \cdots N}, \quad h_j = H_2(j, C_j, \mathit{sid})$.

4.5 $\textcolor{red}{\star}$ Each $P_i$ **privately** sends the evaluation $f_i(j)$ to every participant $P_j$.

### Round 5

5.1 Each $P_i$ waits to receive $f_j(i)$ from every participant $P_j$.

5.2 Each $P_i$ asserts that: $\forall j\in\set{1, \cdots N}, \quad f_j(i) \cdot G = \sum_m j^m \cdot C_j[m]$ where $C_j[m]$ denotes the m-th coefficient of $C_j$.

5.3 Each $P_i$ computes its private share $\mathit{sk}_i \gets \sum_j f_j(i)$.

5.4 Each $P_i$ computes the master public key $\mathit{pk} \gets \sum_j C_j(0)$.

$\quad$ `+++` Each $P_i$ asserts that $\mathit{pk} = \mathit{OldPK}$

5.5 Each $P_i$ reliably broadcasts $\mathsf{success_i}$.

#### Round 5.5

5.6 Each $P_i$ waits to receive $\mathsf{success_j}$ from every participant $P_j$.

**Output:** the keypair $(\mathit{sk}_i, \mathit{pk})$.


### Key Refresh

A key refresh protocol is a special case of the key resharing where $\mathit{OldSigners} = \set{P_1, \ldots P_N}$ and where $\mathsf{OldMaxMalicious} = \mathsf{MaxMalicious}$
2 changes: 1 addition & 1 deletion docs/ecdsa/robust_ecdsa/signing.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ The inputs to this phase are:
**Round 1:**

1. Each $P_i$ computes its signature share $s_i \gets \alpha_i * h + \beta_i \cdot R_\mathsf{x} + e_i$ where $R_\mathsf{x}$ is the x coordinate of $R$.
2. Each $P_i$ linearizes its signature share $s_i \gets \lambda(\mathcal{P}_2)_i s_i$.
2. Each $P_i$ linearizes its signature share $s_i \gets \lambda_i(\mathcal{P}_2) s_i$.
3. $\star$ Each $P_i$ sends $s_i$ **only to the coordinator**.

**Round 1 (Coordinator):**
Expand Down
14 changes: 7 additions & 7 deletions src/dkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ fn generate_coefficient_commitment<C: Ciphersuite>(
}

/// Generates the challenge for the proof of knowledge
/// H(id, `context_string`, g^{secret} , R)
/// H(`domain_separator`, id, g^{secret} , R)
fn challenge<C: Ciphersuite>(
session_id: &HashOutput,
domain_separator: u32,
session_id: &HashOutput,
id: Scalar<C>,
Comment on lines -72 to 76
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it strange that the domain separator is of type u32. Shouldn't it be [u8; 4]?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reasoning behind this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we use it only as bytes later for example. And one of the important characteristics of domain separators is exactly that they have fixed width, so why have them as u32?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the logic you would have then when you want to update the domain separator? Currently we add +1

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting, so this is a domain separator that is a counter, I couldn't guess that from reviewing this local change. I couldn´t not find an explicit mention of that in the doc. Is it used to separate $H_1, H_2, H_3$?

Copy link
Contributor Author

@SimonRastikian SimonRastikian Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc should be agnostic of how the domain separation happens.
This detail is cryptographically irrelevant.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This detail is cryptographically irrelevant.

As long as it is used correctly right?

I looked at the code, and the main logic happens in do_keyshare, so I think I understand it is used exactly for what I mentioned above, separating $H_1, H_2, H_3$. In that case, in the frost crate implementations when doing similar domain separation they did something different. They prepended a different const string (but not a counter) in each hash function that needs to be separated, see for example https://github.com/ZcashFoundation/frost/blob/ca1df5f0c464d36e2047cd91e03ae27df0d167df/frost-ed25519/src/lib.rs#L166-L219

I think we should do the same, mainly because it looks more standard and robust, but probably not in this PR, could be a relatively easy follow-up. If you agree, could you open an issue for it?

vk_share: &CoefficientCommitment<C>,
big_r: &Element<C>,
Expand Down Expand Up @@ -128,8 +128,8 @@ fn proof_of_knowledge<C: Ciphersuite>(
// pick a random k_i and compute R_id = g^{k_id},
let (k, big_r) = <C>::generate_nonce(rng);

// compute H(id, context_string, g^{a_0} , R_id) as a scalar
let hash = challenge::<C>(session_id, domain_separator, id, &vk_share, &big_r)?;
// compute H(domain_separator, id, me, g^{a_0}, R_id) as a scalar
let hash = challenge::<C>(domain_separator, session_id, id, &vk_share, &big_r)?;
let a_0 = coefficients.eval_at_zero()?.0;
let mu = k + a_0 * hash.to_scalar();
Ok(Signature::new(big_r, mu))
Expand All @@ -153,7 +153,7 @@ fn internal_verify_proof_of_knowledge<C: Ciphersuite>(

let big_r = proof_of_knowledge.R();
let z = proof_of_knowledge.z();
let c = challenge::<C>(session_id, domain_separator, id, vk_share, big_r)?;
let c = challenge::<C>(domain_separator, session_id, id, vk_share, big_r)?;
if *big_r != <C::Group>::generator() * *z - vk_share.value() * c.to_scalar() {
return Err(ProtocolError::InvalidProofOfKnowledge(participant));
}
Expand Down Expand Up @@ -348,12 +348,12 @@ async fn do_keyshare<C: Ciphersuite>(
let (old_verification_key, old_participants) =
assert_keyshare_inputs(me, &secret, old_reshare_package)?;

// Start Round 0
// Start Round 1
let mut my_session_id = [0u8; 32]; // 256 bits
rng.fill_bytes(&mut my_session_id);
let session_ids = do_broadcast(&mut chan, &participants, me, my_session_id).await?;

// Start Round 1
// Start Round 2
// generate your secret polynomial p with the constant term set to the secret
// and the rest of the coefficients are picked at random
// because the library does not allow serializing the zero and identity term,
Expand Down