Skip to content

Commit

Permalink
Merge branch 'gdemay/CRP-1888-documents-a-run-of-idkg-protocol-for-ke…
Browse files Browse the repository at this point in the history
…y-generation-with-4-nodes' into 'master'

doc(crypto): CRP-1888 document IDKG key generation protocol with 4 nodes

Pursue documentation effort started with [MR-8307](https://gitlab.com/dfinity-lab/public/ic/-/merge_requests/8307) by documenting how IDKG key generation protocol works. The mode of operations `IDkgTranscriptOperation::Random` and `IDkgTranscriptOperation::ReshareOfMasked` are detailed  for the concrete scenario of 4 nodes, which supports at most one corrupted node. 

See merge request dfinity-lab/public/ic!11221
  • Loading branch information
gregorydemay committed Apr 6, 2023
2 parents 1233077 + 653c458 commit 446bd3d
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 2 deletions.
7 changes: 6 additions & 1 deletion rs/crypto/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_doc_test", "rust_library", "rust_test")
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_doc", "rust_doc_test", "rust_library", "rust_test")
load("//bazel:defs.bzl", "rust_bench", "rust_test_suite_with_extra_srcs")

package(default_visibility = ["//visibility:public"])
Expand Down Expand Up @@ -120,6 +120,11 @@ rust_library(
deps = DEPENDENCIES,
)

rust_doc(
name = "crypto_doc",
crate = ":crypto",
)

rust_binary(
name = "ic-crypto-csp",
srcs = ["src/bin/ic-crypto-csp.rs"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ fn verify_pop(
///
/// We also compute a proof of possession by hashing various information,
/// including the ephemeral key, to another elliptic curve point
/// (`pop_base`). We compute a scalar multipliction of the `pop_base` and
/// (`pop_base`). We compute a scalar multiplication of the `pop_base` and
/// `beta`, producing `pop_public_key`. Finally we create a ZK proof that the
/// discrete logarithms of `pop_public_key` and `v` are the same value (`beta`)
/// in the respective bases.
Expand Down
170 changes: 170 additions & 0 deletions rs/crypto/src/sign/canister_threshold_sig/idkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,176 @@ pub use utils::{
MegaKeyFromRegistryError,
};

/// Implementation of the [`IDkgProtocol`] for the crypto component.
///
/// # Examples
///
/// We detail the protocol execution for the use-cases mentioned in the documentation of [`IDkgProtocol`].
/// * The protocol tolerates less than 1/3 malicious nodes, so the minimal non-trivial settings consist of 4 nodes, denoted by `N_1`, `N_2`, `N_3` and `N_4`,
/// where at most 1 node could be malicious. For simplicity, all the examples described here will use these settings.
/// * The protocol uses an elliptic curve `EC` of prime order p.
/// The initial implementation of the protocol uses curve secp256k1 (aka K-256) but it can be extended to support multiple elliptic curves.
/// We consider two *distinct* generators of the chosen elliptic curve, denoted by `G` and `H`.
/// * We will denote group elements from the elliptic curve by capital letters while elements of Zₚ (i.e. scalars) will be denoted by lower-case letters.
/// The elliptic curve group operation will be written additively.
/// * Each node `N_i` is assumed to have the following keys:
/// * An idkg dealing encryption key pair denoted by `(IDKG_PK_i, idkg_sk_i) ∈ EC x Zₚ`, where the secret key is the discrete logarithm of the public key,
/// i.e. `IDKG_PK_i = G * idkg_sk_i`.
/// Node `n` is assumed to know the dealing encryption public keys of all other nodes.
/// * A signing secret key denoted by `node_signing_sk_i`.
/// * The public keys of a node are stored in the registry and locally by the node.
/// A node may have a different IDKG dealing encryption public key (and corresponding secret key stored locally) depending on the registry version.
/// Each run of the [`IDkgProtocol`] is bound to a particular registry version.
///
/// ## Master Threshold Key Pair Generation
/// * Goal: generate a public/private signing key pair where the public key is known to the receivers and the secret key is secret-shared among the receivers,
/// such that at least 2 shares are required to reconstruct the secret key (`reconstruction_threshold ≥ 2`).
/// * Dealers: all 4 nodes (`N_1`, `N_2`, `N_3`, `N_4`)
/// * Receivers: same 4 nodes (`N_1`, `N_2`, `N_3`, `N_4`)
///
/// Steps:
/// 1. Run [`IDkgTranscriptOperation::Random`] to generate a new public/private key pair secret-shared among the receivers.
/// 2. Run [`IDkgTranscriptOperation::ReshareOfMasked`] to reshare the shares from step 1 to reveal the public key to all receivers.
///
/// ### [`IDkgTranscriptOperation::Random`]
///
/// #### Dealer
/// Dealer `d` creates a single dealing for all receivers (see [`IDkgProtocol::create_dealing`]):
/// * Pick a random polynomial of degree 1 (we need maximal number of malicious nodes +1 coefficients): `p(x) := p_0 + p_1 x`,
/// where the polynomial's coefficients are random over Zₚ.
/// * Commit to the polynomial coefficients using Pedersen commitments (see [`PedersenCommitment`]):
/// * Select another random polynomial of same degree: `q(x) := q_0 + q_1 x`,
/// where the polynomial's coefficients are random over Zₚ.
/// * Compute polynomial commitment `C(x) := C_0 + C_1 x`, where
/// `C_0 := G * p_0 + H * q_0 ∈ EC` and `C_1 := G * p_1 + H * q_1 ∈ EC`,
/// where `G` and `H` are distinct generators of `EC`.
/// Note that the part `H * q_i` is a random group element of `EC`, and thus the commitment `C_i` perfectly hides `p_i`.
/// * Compute shares for each receiver `N_r: (p(r), q(r))` by evaluating the polynomials `p(x)` and `q(x)` in a fixed point different from zero, e.g., their index `r in 1..=4`.
/// * Encrypt shares for all receivers (see [`MEGaCiphertextPair::encrypt`]):
/// * Generate ephemeral key: `EK := G * alpha`, for some random `alpha` over Zₚ
/// * Compute proof of possession of `alpha`: `pop_ek` (see [`ProofOfDLogEquivalence`])
/// * Encrypt all the shares: `(E_1, E_2, E_3, E_4) := (E_{IDKG_PK_1} [p(1), q(1)], E_{IDKG_PK_2} [p(2), q(2)], E_{IDKG_PK_3} [p(3), q(3)], E_{IDKG_PK_3} [p(4), q(4)])`,
/// where for each receiver `r` the encryption is computed as follows:
/// * Compute Diffie-Hellman tuple: `DH_r := (IDKG_PK_r, EK, IDKG_PK_r * alpha)`
/// * Set associated data: `AD_dr` identifies protocol instance, identity of dealer and receiver
/// * Hash `(h_0, h_1) := hash_to_scalars(DH_r, AD_dr)` (model in the paper as a random oracle, implemented using [`xmd`] and [`hash2curve`])
/// * `E_{IDKG_PK_r} [p(r), q(r)] := (h_0 + p(r), h_1 + q(r))`
/// * Construct a single dealing for all receivers: `Dealing_d := ((C_0, C_1), (EK, pop_ek, E_1, E_2, E_3, E_4))`
/// * Sign dealing: `signed_dealing_d := Dealing_d ||Sign_{node_signing_sk_d} [Dealing_d]`
/// * Broadcast `signed_dealing_d` to all receivers
///
/// #### Receiver
/// Receiver `r` receives `signed_dealing_d := Dealing_d ||Sign_{node_signing_sk_d} [Dealing_d]` from dealer d:
/// * Public verification (can be done by any receiver), see [`IDkgProtocol::verify_dealing_public`]
/// * Check signature of dealer `d`
/// * Check length of commitments == reconstruction_threshold (which is 2 here)
/// * Check length of ciphertexts: one pair of scalars for each receiver
/// * Check proof of possession of ephemeral key
/// * Check commitment type is Pedersen
/// * Private verification (can only be done by owner of `idkg_sk_r`), see [`IDkgProtocol::verify_dealing_private`]
/// * Pre-requisite: public verification was successful
/// * Decrypt ciphertext `(p(r), q(r))` using IDKG dealing encryption secret key `idkg_sk_r`:
/// * Compute `EK * idkg_sk_r`, since `EK * idkg_sk_r = (G * alpha) * idkg_sk_r = (G * idkg_sk_r) * alpha = IDKG_PK_r * alpha`. Then, the Diffie-Hellman tuple: `DH_r := (IDKG_PK_r, EK, IDKG_PK_r * alpha)` is known.
/// * Compute associated data `AD_dr`
/// * Compute Hash `(h_0, h_1) := hash_to_scalars(DH_r, AD_dr)` and its inverse `-h_0`, `-h_1` in Zₚ.
/// * `D_{idkg_sk_r} [p, q] := (p - h_0, q - h_1)`
/// * Check commitment of polynomial `C(r) = C_0 + C_1 r == G * p(r) + H * q(r)`
/// * If all ok, receiver supports received dealing by signing it `support_dealing_d := Sign_{node_signing_sk_r} [signed_dealing_d]`
/// * Broadcast supported dealing to all receivers
///
/// #### Reconstruction
/// * A receiver receives support for various dealings (issued by different dealers).
/// * For each dealing, a receiver needs to receive the support of 3 different receivers:
/// since we tolerate at most 1 malicious node, this guarantees that for each dealing included in a transcript there are at least 2 honest nodes supporting it, and thus they could reconstruct the secret shared by the dealer.
/// For `Random` a transcript needs to contain at least 2 dealings
/// (since at most one node is malicious the result is guaranteed to be random).
/// * A transcript consists in a collection of dealings, each having sufficient support and the combined commitment.
/// A transcript contains the following information
/// * Combined Commitment from `(C_0, C_1)` and `(C_0', C_1')`: `Combi_0 := C_0 + C_0'` and `Combi_1:=C_1 + C_1'`
/// * Dealing 1 `(EK, pop_ek, E_1, E_2, E_3, E_4)` and dealing 2 `(EK', pop_ek', E_1', E_2', E_3', E_4')`
/// * Nodes agree (via consensus) on valid transcript in case there are several candidates which will be saved (on the block-chain).
/// Transcript verification is as follows (see [`IDkgProtocol::create_transcript`]):
/// * The combined commitment was constructed correctly from the dealings
/// * The transcript contains enough dealings
/// * Each dealing has at least support of 3 distinct nodes
/// * Each support is a valid signature from the supporting node
/// * From a transcript a node r can reconstruct its shares as follows, see [`IDkgProtocol::load_transcript`]
/// * Decrypt shares from dealing 1: `(p(r), q(r))` and from dealing 2 `(p'(r), q'(r))`
/// * Combined shares: `p(r) + p'(r)` and `q(r) + q'(r)`
/// * If the receiver cannot decrypt, they issue a complaint against the dealing.
/// * Other nodes will open their shares so the complainer can reconstruct its shares, see [`IDkgProtocol::load_transcript_with_openings`]
/// * Combined shares will be stored in the node's node canister secret key store.
/// * The key ID used to insert the keys in the store is the hash of the combined commitment.
///
/// #### Complaint
/// The complaint mechanism allows a receiver to retrieve its shares in case of a corrupted dealer. The steps are as follows:
/// 1. Issue a complaint: If node `r` cannot load a transcript successfully because it cannot decrypt its shares or the decrypted shares do not match the combined commitment,
/// then it issues a complaint against the faulty dealing `d` (see [`IDkgProtocol::load_transcript`]):
/// * Reveal Diffie-Hellman tuple: `DH_r := (IDKG_PK_r, EK, DH)` and a proof that `IDKG_PK_r=G * idkg_sk_r` and `DH=EK * idkg_sk_r` have the same discrete logarithm with respect to `G` and `EK`.
/// * Sign complaint (done by consensus)
/// * Broadcast complaint to all other receivers
/// 1. Verify complaint: another receiver `r'` verifies the broadcasted complaint (see [`IDkgProtocol::verify_complaint`]) as follows:
/// * Check signature
/// * Check proof
/// * Check that the dealing is indeed incorrect: note that `EK * idkg_sk_r = IDKG_PK_r * alpha`
/// which allows receiver `r'` to try to decrypt the shares of party `r` in dealing `d` in the same way receiver `r` tried to do.
/// * If complaint is valid, broadcast its shares for that particular dealing `(p(r'), q(r'))` (see [`IDkgProtocol::open_transcript`])
/// * Sign openings (done by consensus)
/// 1. Verify openings: the complainer `r` verifies each received opening (see [`IDkgProtocol::verify_opening`])
/// * Check signature
/// * Verify that the openings are correct against the commitment polynomial
/// 1. Load transcript with openings: at some the complainer `r` will eventually collect at least two shares (because dealing had support of 3 nodes) (see [`IDkgProtocol::load_transcript_with_openings`]):
/// * With enough correct shares, do polynomial interpolation to reconstruct the polynomial from the faulty dealing.
/// * Compute own shares from reconstructed polynomial
/// * Combine reconstructed shares with shares from other dealing and store combined shares in canister secret key store.
///
/// ### [`IDkgTranscriptOperation::ReshareOfMasked`]
/// Reshare of masked assumes a previous masked transcript. Each receiver `r` in that transcript has in particular a share of a secret `p(r)`, where `p(0)` is the secret, and a share of a mask `q(r)`.
/// The goal at the end of `ReshareOfMasked` is for each receiver to be able to compute the public key `G * p(0)`.
///
/// #### Dealer
/// * Each receiver `r` in the initial masked transcript does the following:
/// * Based on the combined commitment contained in the masked transcript, retrieves his share of the secret `p(r)` and his share of the mask `q(r)` from the canister secret key store.
/// * Pick a random polynomial of degree 1 (we need maximal number of malicious nodes +1 coefficients): `r(x) := r_0 + r_1 x`, such that `r_0 = p(r)` which is the dealer's secret share and
/// where `r_1` is random over Zₚ.
/// * Feldman commitment to polynomial (see [`SimpleCommitment`]):
/// * Compute polynomial commitment `D(x) := D_0 + D_1 x` over the elliptic curve, where
/// `D_0 := G * r_0` and `D_1 := G * r_1`. Note that `D_0 = G * p(r)`.
/// * Compute shares for each receiver `s` in the resharing `r(s)`
/// * Encrypt shares for all receivers (similar to [`IDkgTranscriptOperation::Random`] for for a single element instead of a pair):
/// * Ephemeral key: `EK := G * alpha`, for some random `alpha` over Zₚ
/// * Proof of possession of `alpha`: `pop_ek`
/// * Encryption of shares: `(E_1, E_2, E_3, E_4) := (E_{IDKG_PK_1} [r(1)], E_{IDKG_PK_2} [r(2)], E_{IDKG_PK_3} [r(3)], E_{IDKG_PK_3} [r(4)])`,
/// * Construct zero-knowledge proof that the simple commitment `G * p(r)` and the Pedersen commitment `G * p(r) + H * q(r)` are committing to the same value (see [`ProofOfEqualOpenings`]).
/// * Construct a single dealing for all receivers: `Dealing_d := ((D_0, D_1), (EK, pop_ek, E_1, E_2, E_3, E_4), proof)`
///
/// #### Reconstruction
/// * A receiver receives support for various dealings (issued by different dealers).
/// * For each dealing, a receiver needs to receive the support of 3 different receivers
/// (we need 2 for reconstruction + 1 since at most one node could me malicious) to include it in a transcript.
/// * For `ReshareOfMasked` a transcript needs to contain at least 2 dealings
/// (number of coefficients of polynomial from previous transcript).
/// * Public verification verifies the [`ProofOfEqualOpenings`].
/// Otherwise, verification and complaint mechanism are as in [`IDkgTranscriptOperation::Random`] described above.
/// * From a transcript with 2 dealings a node reconstructs the public key as follows:
/// * Dealing 1 from dealer `d`: `D_0 = G * r_0 = G * p(d)`
/// * Dealing 2 from another dealer `d'`: `D_0' = G * r_0' = G * p(d')`
/// * Note that `P(x) := G * p(x) = G * p_0 + (G * p_1) * x` is a polynomial of degree 1 over the group and by definition
/// * `P(d) = D_0`
/// * `P(d') = D_0'`
/// * We can therefore do Lagrange interpolation to recover the polynomial `P(x)` and compute `P(0) = G * p_0` which is the public key:
/// * `P(x) = P(d) * (d'-x)/(d'-d) + P(d') * (d-x)/(d-d')` and so
/// * `P(0) = P(d) * d'/(d'-d) + P(d') * d/(d-d')`
///
/// [`hash2curve`]: ic_crypto_internal_threshold_sig_ecdsa::hash2curve
/// [`IDkgTranscriptOperation::Random`]: ic_types::crypto::canister_threshold_sig::idkg::IDkgTranscriptOperation::Random
/// [`IDkgTranscriptOperation::ReshareOfMasked`]: ic_types::crypto::canister_threshold_sig::idkg::IDkgTranscriptOperation::ReshareOfMasked
/// [`MEGaCiphertextPair::encrypt`]: ic_crypto_internal_threshold_sig_ecdsa::MEGaCiphertextPair::encrypt
/// [`PedersenCommitment`]: ic_crypto_internal_threshold_sig_ecdsa::PedersenCommitment
/// [`ProofOfDLogEquivalence`]: ic_crypto_internal_threshold_sig_ecdsa::zk::ProofOfDLogEquivalence
/// [`ProofOfEqualOpenings`]: ic_crypto_internal_threshold_sig_ecdsa::zk::ProofOfEqualOpenings
/// [`SimpleCommitment`]: ic_crypto_internal_threshold_sig_ecdsa::SimpleCommitment
/// [`xmd`]: ic_crypto_internal_seed::xmd
impl<C: CryptoServiceProvider> IDkgProtocol for CryptoComponentImpl<C> {
fn create_dealing(
&self,
Expand Down

0 comments on commit 446bd3d

Please sign in to comment.