From 0906889dad99af7d68e36a6fae2869bc7ecfe22c Mon Sep 17 00:00:00 2001 From: Tarak Ben youssef Date: Tue, 22 Oct 2024 18:38:44 -0600 Subject: [PATCH 1/7] comment updates about the DKG and the random beacon --- cmd/bootstrap/run/qc.go | 4 +-- model/convert/service_event.go | 6 ++-- model/flow/dkg.go | 60 +++++++++++++++++++++------------- module/dkg.go | 20 +++++++----- module/dkg/client.go | 8 ++--- state/protocol/defaults.go | 20 ++++++++---- state/protocol/validity.go | 11 +++++++ 7 files changed, 82 insertions(+), 47 deletions(-) diff --git a/cmd/bootstrap/run/qc.go b/cmd/bootstrap/run/qc.go index 6f6e304b159..a3864c7fb29 100644 --- a/cmd/bootstrap/run/qc.go +++ b/cmd/bootstrap/run/qc.go @@ -24,7 +24,7 @@ type Participant struct { RandomBeaconPrivKey crypto.PrivateKey } -// ParticipantData represents a subset of all consensus participants that contributing to some signing process (at the moment, we only use +// ParticipantData represents a subset of all consensus participants that contribute to some signing process (at the moment, we only use // it for the contributors for the root QC). For mainnet, this a *strict subset* of all consensus participants: // - In an early step during the bootstrapping process, every node operator locally generates votes for the root block from the nodes they // operate. During the vote-generation step, (see function `constructRootVotes`), `Participants` represents only the operator's own @@ -236,7 +236,7 @@ func GenerateQCParticipantData(allNodes, internalNodes []bootstrap.NodeInfo, dkg dkgParticipant, ok := participantLookup[node.NodeID] if !ok { - return nil, fmt.Errorf("nonexistannt node id (%x) in participant lookup", node.NodeID) + return nil, fmt.Errorf("non existent node id (%x) in participant lookup", node.NodeID) } dkgIndex := dkgParticipant.Index diff --git a/model/convert/service_event.go b/model/convert/service_event.go index f9e9e51e860..4876428d994 100644 --- a/model/convert/service_event.go +++ b/model/convert/service_event.go @@ -972,7 +972,7 @@ func convertDKGKeys(cdcDKGKeys []cadence.Value) ([]crypto.PublicKey, error) { for _, value := range cdcDKGKeys { pubKey, err := convertDKGKey(value) if err != nil { - return nil, fmt.Errorf("could not decode dkg public key: %w", err) + return nil, fmt.Errorf("could not decode public key share: %w", err) } convertedKeys = append(convertedKeys, pubKey) } @@ -992,11 +992,11 @@ func convertDKGKey(cdcDKGKeys cadence.Value) (crypto.PublicKey, error) { // decode individual public keys pubKeyBytes, err := hex.DecodeString(string(keyHex)) if err != nil { - return nil, fmt.Errorf("could not decode individual public key into bytes: %w", err) + return nil, fmt.Errorf("could not decode hex into bytes: %w", err) } pubKey, err := crypto.DecodePublicKey(crypto.BLSBLS12381, pubKeyBytes) if err != nil { - return nil, fmt.Errorf("could not decode dkg public key: %w", err) + return nil, fmt.Errorf("could not decode bytes into a public key: %w", err) } return pubKey, nil } diff --git a/model/flow/dkg.go b/model/flow/dkg.go index 00a610eb610..b8109caf26d 100644 --- a/model/flow/dkg.go +++ b/model/flow/dkg.go @@ -31,20 +31,27 @@ func (state DKGEndState) String() string { } } -// DKGIndexMap describes the membership of the DKG committee π’Ÿ. Flow's random beacon utilizes -// a threshold signature scheme, which requires a Distributed Key Generation [DKG] to generate the -// key shares for each committee member. In the formal cryptographic protocol for DKG with n parties, -// the individual participants are solely identified by indices {0, 1, ..., n-1} and the fact that these -// are non-negative integer values is actively used by the DKG protocol. Accordingly, our implementation -// of the lower-level cryptographic primitives work with these DKG index values. +// DKGIndexMap completely describes the DKG committee π’Ÿ of size |π’Ÿ| = n. +// Flow's random beacon utilizes a threshold signature scheme run by the committee π’Ÿ. +// In the formal cryptographic protocol for a threshold signature with n parties, +// the individual participants are identified by n public distinct non-negative integers, or simply indices. +// These public indices are agreed upon by all participants and are used by the low-level +// Shamir Secret Sharing [SSS]. +// In Flow, the threshold signature keys are generated by a Distributed Key Generation [DKG]. The DKG +// therefore requires the same SSS indices as an input to generate the private key shares of each participant. +// Accordingly, the lower-level cryptographic implementation of the threshold signature and DKG +// work with these indices. The lower-level cryptographic interface requires that the indices are exactly +// the set {0, 1, ..., n-1}. +// // On the protocol level, only consensus nodes (identified by their nodeIDs) are allowed to contribute -// random beacon signature shares. Hence, the protocol level needs to map nodeIDs to DKG indices when +// random beacon signature shares. Hence, the protocol level needs to map nodeIDs to the indices when // calling into the lower-level cryptographic primitives. // // Formal specification: -// - DKGIndexMap completely describes the DKG committee. If there were n parties authorized to participate +// - DKGIndexMap completely describes the DKG committee. If n parties are authorized to participate // in the DKG, DKGIndexMap must contain exactly n elements, i.e. n = len(DKGIndexMap) -// - The values in DKGIndexMap must form the set {0, 1, …, n-1}. +// - The values in DKGIndexMap must form the set {0, 1, …, n-1}, as required by the low level cryptography +// module. // // CAUTION: It is important to cleanly differentiate between the consensus committee π’ž, the random beacon // committee β„› and the DKG committee π’Ÿ: @@ -52,30 +59,37 @@ func (state DKGEndState) String() string { // to vote (i.e. membership in the consensus committee) is irrevocably granted for an epoch (though, honest nodes // will reject votes and proposals from ejected nodes; nevertheless, ejected nodes formally remain members of // the consensus committee). -// - Only consensus nodes are allowed to contribute to the random beacon. We define the random beacon committee β„› -// as the subset of the consensus nodes, which _successfully_ completed the DKG. Hence, β„› βŠ† π’ž. -// - Lastly, there is the DKG committee π’Ÿ, which is the set of parties that were authorized to -// participate in the DKG. Mathematically, the DKGIndexMap is an injective function +// - The DKG committee π’Ÿ is the set of parties that were authorized to +// participate in the DKG. Mathematically, the DKGIndexMap is an bijective function // DKGIndexMap: π’Ÿ ↦ {0,1,…,n-1}. +// - Only consensus nodes are allowed to contribute to the random beacon. We define the random beacon committee β„› +// as the subset of the consensus committee (β„› βŠ† π’ž), which _successfully_ completed the DKG, +// i.e which completed participation in DKG (β„› βŠ† π’Ÿ) with a private key that matches the public key assigned to them by +// the DKG committee (or a trusted dealer). Nodes in β„› are therefore able to submit valid random beacon votes. +// Based on this definition we note that β„› βŠ† (π’Ÿ ∩ π’ž). // // The protocol explicitly ALLOWS additional parties outside the current epoch's consensus committee to participate. // In particular, there can be a key-value pair (d,i) ∈ DKGIndexMap, such that the nodeID d is *not* a consensus -// committee member, i.e. d βˆ‰ π’ž. In terms of sets, this implies we must consistently work with the relatively -// general assumption that π’Ÿ \ π’ž β‰  βˆ… and π’ž \ π’Ÿ β‰  βˆ…. +// committee member, i.e. d βˆ‰ π’ž. This may be the case when a DKG is run off-protocol to bootstrap the network. +// In terms of sets, this implies we must consistently work with the relatively general +// assumption that π’Ÿ \ π’ž β‰  βˆ… and π’ž \ π’Ÿ β‰  βˆ…. // Nevertheless, in the vast majority of cases (happy path, roughly 98% of epochs) it will be the case that π’Ÿ = π’ž. // Therefore, we can optimize for the case π’Ÿ = π’ž, as long as we still support the more general case π’Ÿ β‰  π’ž. // Broadly, this makes the protocol more robust against temporary disruptions and sudden, large fluctuations in node // participation. -// Nevertheless, there is an important liveness constraint: the intersection, π’Ÿ ∩ π’ž = β„› should be a larger number of -// nodes. Specifically, an honest supermajority of consensus nodes must contain enough successful DKG participants -// (about n/2) to produce a valid group signature for the random beacon [1, 3]. Therefore, we have the approximate -// lower bound |β„›| = |π’Ÿ ∩ π’ž| = n/2 = |π’Ÿ|/2 = len(DKGIndexMap)/2. Operating close to this lower bound would -// require that every random beacon key-holder r ∈ β„› remaining in the consensus committee is honest +// +// Nevertheless, there is an important liveness constraint: the intersection, the random beacon committee β„› should +// be a large number of nodes. +// Specifically, an honest supermajority of consensus nodes must contain enough successful DKG participants +// (about n/2 + 1) to produce a valid group signature for the random beacon at each block [1, 3]. +// Therefore, we have the approximate lower bound |β„›| >= n/2 + 1 = |π’Ÿ|/2 + 1 = len(DKGIndexMap)/2 + 1. +// Operating close to this lower bound would require that every random beacon key-holder Ο± ∈ β„› remaining in the consensus committee is honest // (incl. quickly responsive) *all the time*. This is a lower bound, unsuited for decentralized production networks. // To reject configurations that are vulnerable to liveness failures, the protocol uses the threshold `t_safety` -// (heuristic, see [2]), which is implemented on the smart contract level. In a nutshell, the cardinality of intersection π’Ÿ ∩ π’ž -// (wrt both sets π’Ÿ ∩ π’ž) should be well above 70%, values in the range 70-62% should be considered for short-term -// recovery cases. Values of 62% or lower (i.e. |β„›| ≀ 0.62Β·|π’Ÿ| or |β„›| ≀ 0.62Β·|π’ž|) are not recommended for any +// (heuristic, see [2]), which is implemented on the smart contract level. +// In a nutshell, |β„›| and therefore |π’Ÿ ∩ π’ž| (given that |β„›| <= |π’Ÿ ∩ π’ž|) should be well above 70% * |π’Ÿ| = 0.7 * n, +// values in the range 70%-62% of |π’Ÿ| should be considered for short-term recovery cases. +// Values of 62% * |π’Ÿ| or lower (i.e. |β„›| ≀ 0.62Β·|π’Ÿ|) are not recommended for any // production network, as single-node crashes are already enough to halt consensus. // // For further details, see diff --git a/module/dkg.go b/module/dkg.go index 412a8b71235..00aa6d67ed7 100644 --- a/module/dkg.go +++ b/module/dkg.go @@ -33,11 +33,11 @@ type DKGContractClient interface { ReadBroadcast(fromIndex uint, referenceBlock flow.Identifier) ([]messages.BroadcastDKGMessage, error) // SubmitParametersAndResult posts the DKG setup parameters (`flow.DKGIndexMap`) and the node's locally-computed DKG result to - // the DKG white-board smart contract. The DKG result are the group public key and the node's local computation of the public - // keys for each DKG participant. Serialized public keys are encoded as hex. - // Conceptually the flow.DKGIndexMap is not and output of the DKG protocol. Rather, it is part of the configuration/initialization + // the DKG white-board smart contract. The DKG result are node's local computation of the group public key and the public + // keys shares. Serialized public keys are encoded as hex. + // Conceptually the flow.DKGIndexMap is not an output of the DKG protocol. Rather, it is part of the configuration/initialization // information of the DKG. Before an epoch transition on the happy path (using the data in the EpochSetup event), each consensus - // participant locally fixes the DKG committee π’Ÿ including the order of the respective nodes order to be identical to the consensus + // participant locally fixes the DKG committee π’Ÿ including the the respective nodes order to be identical to the consensus // committee π’ž. However, in case of a failed epoch transition, we desire the ability to manually provide the result of a successful // DKG for the immediately next epoch (so-called recovery epoch). The DKG committee π’Ÿ must have a sufficiently large overlap with // the recovery epoch's consensus committee π’ž -- though for flexibility, we do *not* want to require that both committees are identical. @@ -45,13 +45,15 @@ type DKGContractClient interface { // same also on the happy path. SubmitParametersAndResult(indexMap flow.DKGIndexMap, groupPublicKey crypto.PublicKey, publicKeys []crypto.PublicKey) error - // SubmitEmptyResult submits an empty result of the DKG protocol. The empty result is obtained by a node when - // it realizes locally that its DKG participation was unsuccessful (either because the DKG failed as a whole, - // or because the node received too many byzantine inputs). However, a node obtaining an empty result can - // happen in both cases of the DKG succeeding or failing. For further details, please see: + // SubmitEmptyResult submits an empty result of the DKG protocol. + // The empty result is obtained by a node when it realizes locally that its DKG participation + // was unsuccessful (for various reasons: because the node received too many byzantine inputs, + // because the node has networking issues, because a locally computed key is invalid..). + // However, a node obtaining an empty result can happen in both cases of the DKG succeeding or failing. + // For further details, please see: // https://flowfoundation.notion.site/Random-Beacon-2d61f3b3ad6e40ee9f29a1a38a93c99c // Honest nodes would call `SubmitEmptyResult` strictly after the final phase has ended if DKG has ended. - // Though, `SubmitEmptyResult` also supports implementing byzantine participants for testing that submit an + // However, `SubmitEmptyResult` also supports implementing byzantine participants for testing that submit an // empty result too early (intentional protocol violation), *before* the final DKG phase concluded. SubmitEmptyResult() error } diff --git a/module/dkg/client.go b/module/dkg/client.go index f20b391a836..31da63b6bc0 100644 --- a/module/dkg/client.go +++ b/module/dkg/client.go @@ -181,11 +181,11 @@ func (c *Client) Broadcast(msg model.BroadcastDKGMessage) error { } // SubmitParametersAndResult posts the DKG setup parameters (`flow.DKGIndexMap`) and the node's locally-computed DKG result to -// the DKG white-board smart contract. The DKG result are the group public key and the node's local computation of the public -// keys for each DKG participant. Serialized public keys are encoded as hex. -// Conceptually the flow.DKGIndexMap is not and output of the DKG protocol. Rather, it is part of the configuration/initialization +// the DKG white-board smart contract. The DKG result are node's local computation of the group public key and the public +// keys shares. Serialized public keys are encoded as hex. +// Conceptually the flow.DKGIndexMap is not an output of the DKG protocol. Rather, it is part of the configuration/initialization // information of the DKG. Before an epoch transition on the happy path (using the data in the EpochSetup event), each consensus -// participant locally fixes the DKG committee π’Ÿ including the order of the respective nodes order to be identical to the consensus +// participant locally fixes the DKG committee π’Ÿ including the the respective nodes order to be identical to the consensus // committee π’ž. However, in case of a failed epoch transition, we desire the ability to manually provide the result of a successful // DKG for the immediately next epoch (so-called recovery epoch). The DKG committee π’Ÿ must have a sufficiently large overlap with // the recovery epoch's consensus committee π’ž -- though for flexibility, we do *not* want to require that both committees are identical. diff --git a/state/protocol/defaults.go b/state/protocol/defaults.go index 72d08f1cdce..0d57b565b53 100644 --- a/state/protocol/defaults.go +++ b/state/protocol/defaults.go @@ -32,15 +32,23 @@ func DefaultEpochSafetyParams(chain flow.ChainID) (SafetyParams, error) { } // RandomBeaconSafetyThreshold defines a production network safety threshold for random beacon protocol based on the size -// of the DKG committee π’Ÿ which is a subset of consensus committee π’ž. +// of the random beacon committee β„› and the DKG committee π’Ÿ. +// +// We recall that the random beacon committee β„› is defined as the subset of the consensus committee (β„› βŠ† π’ž), +// and the DKG committee (β„› βŠ† π’Ÿ) which _successfully_ completed the DKG and is able to contribute with a random beacon share. +// // An honest supermajority of consensus nodes must contain enough successful DKG participants -// (about |π’Ÿ|/2) to produce a valid group signature for the random beacon [1, 3]. Therefore, we have the approximate -// lower bound |π’Ÿ|/2. This is a lower bound, unsuited for decentralized production networks. +// (about n/2 + 1) to produce a valid group signature for the random beacon at each block [1, 3]. +// Therefore, we have the approximate lower bound |β„›| >= n/2 + 1 = |π’Ÿ|/2 + 1 = len(DKGIndexMap)/2 + 1. +// Operating close to this lower bound would require that every random beacon key-holder Ο± ∈ β„› remaining in the consensus committee is honest +// (incl. quickly responsive) *all the time*. This is a lower bound, unsuited for decentralized production networks. // To reject configurations that are vulnerable to liveness failures, the protocol uses the threshold `t_safety` -// (heuristic, see [2]), which is implemented on the smart contract level. In a nutshell, the cardinality of intersection π’Ÿ ∩ π’ž -// (wrt both sets π’Ÿ ∩ π’ž) should be well above 70%, values in the range 70-62% should be considered for short-term -// recovery cases. Values of 62% or lower are not recommended for any +// (heuristic, see [2]), which is implemented on the smart contract level. +// In a nutshell, |β„›| and therefore |π’Ÿ ∩ π’ž| (given that |β„›| <= |π’Ÿ ∩ π’ž|) should be well above 70% * |π’Ÿ| = 0.7 * n, +// values in the range 70%-62% of |π’Ÿ| should be considered for short-term recovery cases. +// Values of 62% * |π’Ÿ| or lower (i.e. |β„›| ≀ 0.62Β·|π’Ÿ|) are not recommended for any // production network, as single-node crashes are already enough to halt consensus. +// // For further details, see // - godoc for [flow.DKGIndexMap] // - [1] https://www.notion.so/flowfoundation/Threshold-Signatures-7e26c6dd46ae40f7a83689ba75a785e3?pvs=4 diff --git a/state/protocol/validity.go b/state/protocol/validity.go index 47f059073a8..984a1c1c285 100644 --- a/state/protocol/validity.go +++ b/state/protocol/validity.go @@ -195,6 +195,17 @@ func IsValidEpochCommit(commit *flow.EpochCommit, setup *flow.EpochSetup) error // enforce invariant: RandomBeaconSafetyThreshold ≀ |π’ž ∩ π’Ÿ| where: // - π’ž is the set of all consensus committee members // - π’Ÿ is the set of all DKG participants + // + // Note that this is only a sanity check that makes sure the cardinality of β„› βŠ† π’Ÿ ∩ π’ž does not go below the + // critical liveness threshold of 0.62 * |π’Ÿ| (details in [2]). + // If RandomBeaconSafetyThreshold > |π’ž ∩ π’Ÿ|, we are certain that RandomBeaconSafetyThreshold > |β„›|. However, + // making sure that RandomBeaconSafetyThreshold <= |π’ž ∩ π’Ÿ| does not prove that |β„›| is above the critical threshold. + // + // This is different than the check implemented by the DKG contract where the value of |β„›| is known and compared + // to the threshold. Unlike the DKG contract, the protocol state does not have access to the value of |β„›| from a past + // key generation (decentralized or not). + // + // [2] https://www.notion.so/flowfoundation/DKG-contract-success-threshold-86c6bf2b92034855b3c185d7616eb6f1?pvs=4 if RandomBeaconSafetyThreshold(uint(n)) > numberOfRandomBeaconParticipants { return NewInvalidServiceEventErrorf("not enough random beacon participants required %d, got %d", signature.RandomBeaconThreshold(n), numberOfRandomBeaconParticipants) From c129baeab25bede2961b64829f11500da44b844d Mon Sep 17 00:00:00 2001 From: Tarak Ben youssef Date: Wed, 23 Oct 2024 14:03:29 -0600 Subject: [PATCH 2/7] remove references of R as the random beacon committee --- cmd/bootstrap/run/qc.go | 2 +- model/flow/dkg.go | 9 ++++----- state/protocol/defaults.go | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cmd/bootstrap/run/qc.go b/cmd/bootstrap/run/qc.go index a3864c7fb29..86010ad95d4 100644 --- a/cmd/bootstrap/run/qc.go +++ b/cmd/bootstrap/run/qc.go @@ -236,7 +236,7 @@ func GenerateQCParticipantData(allNodes, internalNodes []bootstrap.NodeInfo, dkg dkgParticipant, ok := participantLookup[node.NodeID] if !ok { - return nil, fmt.Errorf("non existent node id (%x) in participant lookup", node.NodeID) + return nil, fmt.Errorf("nonexistent node id (%x) in participant lookup", node.NodeID) } dkgIndex := dkgParticipant.Index diff --git a/model/flow/dkg.go b/model/flow/dkg.go index b8109caf26d..26932db9c35 100644 --- a/model/flow/dkg.go +++ b/model/flow/dkg.go @@ -53,8 +53,8 @@ func (state DKGEndState) String() string { // - The values in DKGIndexMap must form the set {0, 1, …, n-1}, as required by the low level cryptography // module. // -// CAUTION: It is important to cleanly differentiate between the consensus committee π’ž, the random beacon -// committee β„› and the DKG committee π’Ÿ: +// CAUTION: It is important to cleanly differentiate between the consensus committee π’ž, the DKG committee π’Ÿ +// and the the committee β„›: // - For an epoch, the consensus committee π’ž contains all nodes that are authorized to vote for blocks. Authority // to vote (i.e. membership in the consensus committee) is irrevocably granted for an epoch (though, honest nodes // will reject votes and proposals from ejected nodes; nevertheless, ejected nodes formally remain members of @@ -62,7 +62,7 @@ func (state DKGEndState) String() string { // - The DKG committee π’Ÿ is the set of parties that were authorized to // participate in the DKG. Mathematically, the DKGIndexMap is an bijective function // DKGIndexMap: π’Ÿ ↦ {0,1,…,n-1}. -// - Only consensus nodes are allowed to contribute to the random beacon. We define the random beacon committee β„› +// - Only consensus nodes are allowed to contribute to the random beacon. We define the committee β„› // as the subset of the consensus committee (β„› βŠ† π’ž), which _successfully_ completed the DKG, // i.e which completed participation in DKG (β„› βŠ† π’Ÿ) with a private key that matches the public key assigned to them by // the DKG committee (or a trusted dealer). Nodes in β„› are therefore able to submit valid random beacon votes. @@ -78,8 +78,7 @@ func (state DKGEndState) String() string { // Broadly, this makes the protocol more robust against temporary disruptions and sudden, large fluctuations in node // participation. // -// Nevertheless, there is an important liveness constraint: the intersection, the random beacon committee β„› should -// be a large number of nodes. +// Nevertheless, there is an important liveness constraint: the committee β„› should be a large number of nodes. // Specifically, an honest supermajority of consensus nodes must contain enough successful DKG participants // (about n/2 + 1) to produce a valid group signature for the random beacon at each block [1, 3]. // Therefore, we have the approximate lower bound |β„›| >= n/2 + 1 = |π’Ÿ|/2 + 1 = len(DKGIndexMap)/2 + 1. diff --git a/state/protocol/defaults.go b/state/protocol/defaults.go index 0d57b565b53..06c52b996d5 100644 --- a/state/protocol/defaults.go +++ b/state/protocol/defaults.go @@ -34,7 +34,7 @@ func DefaultEpochSafetyParams(chain flow.ChainID) (SafetyParams, error) { // RandomBeaconSafetyThreshold defines a production network safety threshold for random beacon protocol based on the size // of the random beacon committee β„› and the DKG committee π’Ÿ. // -// We recall that the random beacon committee β„› is defined as the subset of the consensus committee (β„› βŠ† π’ž), +// We recall that the committee β„› is defined as the subset of the consensus committee (β„› βŠ† π’ž), // and the DKG committee (β„› βŠ† π’Ÿ) which _successfully_ completed the DKG and is able to contribute with a random beacon share. // // An honest supermajority of consensus nodes must contain enough successful DKG participants From 036e482e77dc1fa62e3984759bc97548eae3b4e8 Mon Sep 17 00:00:00 2001 From: Tarak Ben youssef Date: Wed, 23 Oct 2024 14:43:05 -0600 Subject: [PATCH 3/7] typos --- model/flow/dkg.go | 2 +- module/dkg.go | 4 ++-- module/dkg/client.go | 4 ++-- module/signature/signing_tags.go | 2 +- state/protocol/prg/prg.go | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/model/flow/dkg.go b/model/flow/dkg.go index 26932db9c35..c6be85e99f6 100644 --- a/model/flow/dkg.go +++ b/model/flow/dkg.go @@ -54,7 +54,7 @@ func (state DKGEndState) String() string { // module. // // CAUTION: It is important to cleanly differentiate between the consensus committee π’ž, the DKG committee π’Ÿ -// and the the committee β„›: +// and the committee β„›: // - For an epoch, the consensus committee π’ž contains all nodes that are authorized to vote for blocks. Authority // to vote (i.e. membership in the consensus committee) is irrevocably granted for an epoch (though, honest nodes // will reject votes and proposals from ejected nodes; nevertheless, ejected nodes formally remain members of diff --git a/module/dkg.go b/module/dkg.go index 00aa6d67ed7..0941a3a4947 100644 --- a/module/dkg.go +++ b/module/dkg.go @@ -33,11 +33,11 @@ type DKGContractClient interface { ReadBroadcast(fromIndex uint, referenceBlock flow.Identifier) ([]messages.BroadcastDKGMessage, error) // SubmitParametersAndResult posts the DKG setup parameters (`flow.DKGIndexMap`) and the node's locally-computed DKG result to - // the DKG white-board smart contract. The DKG result are node's local computation of the group public key and the public + // the DKG white-board smart contract. The DKG results are the node's local computation of the group public key and the public // keys shares. Serialized public keys are encoded as hex. // Conceptually the flow.DKGIndexMap is not an output of the DKG protocol. Rather, it is part of the configuration/initialization // information of the DKG. Before an epoch transition on the happy path (using the data in the EpochSetup event), each consensus - // participant locally fixes the DKG committee π’Ÿ including the the respective nodes order to be identical to the consensus + // participant locally fixes the DKG committee π’Ÿ including the respective nodes order to be identical to the consensus // committee π’ž. However, in case of a failed epoch transition, we desire the ability to manually provide the result of a successful // DKG for the immediately next epoch (so-called recovery epoch). The DKG committee π’Ÿ must have a sufficiently large overlap with // the recovery epoch's consensus committee π’ž -- though for flexibility, we do *not* want to require that both committees are identical. diff --git a/module/dkg/client.go b/module/dkg/client.go index 31da63b6bc0..21574583265 100644 --- a/module/dkg/client.go +++ b/module/dkg/client.go @@ -181,11 +181,11 @@ func (c *Client) Broadcast(msg model.BroadcastDKGMessage) error { } // SubmitParametersAndResult posts the DKG setup parameters (`flow.DKGIndexMap`) and the node's locally-computed DKG result to -// the DKG white-board smart contract. The DKG result are node's local computation of the group public key and the public +// the DKG white-board smart contract. The DKG results are the node's local computation of the group public key and the public // keys shares. Serialized public keys are encoded as hex. // Conceptually the flow.DKGIndexMap is not an output of the DKG protocol. Rather, it is part of the configuration/initialization // information of the DKG. Before an epoch transition on the happy path (using the data in the EpochSetup event), each consensus -// participant locally fixes the DKG committee π’Ÿ including the the respective nodes order to be identical to the consensus +// participant locally fixes the DKG committee π’Ÿ including the respective nodes order to be identical to the consensus // committee π’ž. However, in case of a failed epoch transition, we desire the ability to manually provide the result of a successful // DKG for the immediately next epoch (so-called recovery epoch). The DKG committee π’Ÿ must have a sufficiently large overlap with // the recovery epoch's consensus committee π’ž -- though for flexibility, we do *not* want to require that both committees are identical. diff --git a/module/signature/signing_tags.go b/module/signature/signing_tags.go index f2d142b4253..00d7e06903c 100644 --- a/module/signature/signing_tags.go +++ b/module/signature/signing_tags.go @@ -61,7 +61,7 @@ var ( // NewBLSHasher returns a hasher to be used for BLS signing and verifying // in the protocol and abstracts the hasher details from the protocol logic. // -// The hasher returned is the the expand-message step in the BLS hash-to-curve. +// The hasher returned is the expand-message step in the BLS hash-to-curve. // It uses a xof (extendable output function) based on KMAC128. It therefore has // 128-bytes outputs. func NewBLSHasher(tag string) hash.Hasher { diff --git a/state/protocol/prg/prg.go b/state/protocol/prg/prg.go index 36b3b77751d..d17fd1a9ac0 100644 --- a/state/protocol/prg/prg.go +++ b/state/protocol/prg/prg.go @@ -17,7 +17,7 @@ const RandomSourceLength = crypto.SignatureLenBLSBLS12381 // The diversifier is used to further diversify the PRGs beyond the customizer. A diversifier // can be a slice of any length. If no diversification is needed, `diversifier` can be `nil`. // -// The function uses an extendable-output function (xof) to extract and expand the the input source, +// The function uses an extendable-output function (xof) to extract and expand the input source, // so that any source with enough entropy (at least 128 bits) can be used (no need to pre-hash). // Current implementation generates a ChaCha20-based CSPRG. // From c74dd806b333ad42c7ba5ae2b9e57926c415242b Mon Sep 17 00:00:00 2001 From: Tarak Ben youssef Date: Sat, 26 Oct 2024 12:25:27 -0600 Subject: [PATCH 4/7] corrections and clarifications --- model/convert/service_event.go | 2 +- model/flow/dkg.go | 2 +- module/dkg.go | 5 +++-- module/dkg/client.go | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/model/convert/service_event.go b/model/convert/service_event.go index 4876428d994..1420c5cbd93 100644 --- a/model/convert/service_event.go +++ b/model/convert/service_event.go @@ -972,7 +972,7 @@ func convertDKGKeys(cdcDKGKeys []cadence.Value) ([]crypto.PublicKey, error) { for _, value := range cdcDKGKeys { pubKey, err := convertDKGKey(value) if err != nil { - return nil, fmt.Errorf("could not decode public key share: %w", err) + return nil, fmt.Errorf("could not decode public beacon key share: %w", err) } convertedKeys = append(convertedKeys, pubKey) } diff --git a/model/flow/dkg.go b/model/flow/dkg.go index c6be85e99f6..da31566c98b 100644 --- a/model/flow/dkg.go +++ b/model/flow/dkg.go @@ -40,7 +40,7 @@ func (state DKGEndState) String() string { // In Flow, the threshold signature keys are generated by a Distributed Key Generation [DKG]. The DKG // therefore requires the same SSS indices as an input to generate the private key shares of each participant. // Accordingly, the lower-level cryptographic implementation of the threshold signature and DKG -// work with these indices. The lower-level cryptographic interface requires that the indices are exactly +// works with these indices. The lower-level cryptographic interface requires that the indices are exactly // the set {0, 1, ..., n-1}. // // On the protocol level, only consensus nodes (identified by their nodeIDs) are allowed to contribute diff --git a/module/dkg.go b/module/dkg.go index 0941a3a4947..456d19f424f 100644 --- a/module/dkg.go +++ b/module/dkg.go @@ -34,7 +34,7 @@ type DKGContractClient interface { // SubmitParametersAndResult posts the DKG setup parameters (`flow.DKGIndexMap`) and the node's locally-computed DKG result to // the DKG white-board smart contract. The DKG results are the node's local computation of the group public key and the public - // keys shares. Serialized public keys are encoded as hex. + // key shares. Serialized public keys are encoded as lower-case hex strings. // Conceptually the flow.DKGIndexMap is not an output of the DKG protocol. Rather, it is part of the configuration/initialization // information of the DKG. Before an epoch transition on the happy path (using the data in the EpochSetup event), each consensus // participant locally fixes the DKG committee π’Ÿ including the respective nodes order to be identical to the consensus @@ -49,7 +49,8 @@ type DKGContractClient interface { // The empty result is obtained by a node when it realizes locally that its DKG participation // was unsuccessful (for various reasons: because the node received too many byzantine inputs, // because the node has networking issues, because a locally computed key is invalid..). - // However, a node obtaining an empty result can happen in both cases of the DKG succeeding or failing. + // However, a node obtaining an empty result can happen in both cases of the DKG succeeding or failing + // globally. // For further details, please see: // https://flowfoundation.notion.site/Random-Beacon-2d61f3b3ad6e40ee9f29a1a38a93c99c // Honest nodes would call `SubmitEmptyResult` strictly after the final phase has ended if DKG has ended. diff --git a/module/dkg/client.go b/module/dkg/client.go index 21574583265..ea67cede449 100644 --- a/module/dkg/client.go +++ b/module/dkg/client.go @@ -182,7 +182,7 @@ func (c *Client) Broadcast(msg model.BroadcastDKGMessage) error { // SubmitParametersAndResult posts the DKG setup parameters (`flow.DKGIndexMap`) and the node's locally-computed DKG result to // the DKG white-board smart contract. The DKG results are the node's local computation of the group public key and the public -// keys shares. Serialized public keys are encoded as hex. +// key shares. Serialized public keys are encoded as lower-case hex strings. // Conceptually the flow.DKGIndexMap is not an output of the DKG protocol. Rather, it is part of the configuration/initialization // information of the DKG. Before an epoch transition on the happy path (using the data in the EpochSetup event), each consensus // participant locally fixes the DKG committee π’Ÿ including the respective nodes order to be identical to the consensus From b4eb301156f694e84346136e771ff26c72987613 Mon Sep 17 00:00:00 2001 From: Alexander Hentschel Date: Mon, 18 Nov 2024 13:05:06 -0800 Subject: [PATCH 5/7] =?UTF-8?q?changed=20=E2=80=A2=20DKG=20key=20->=20Rand?= =?UTF-8?q?om=20Beacon=20key=20=E2=80=A2=20DKG=20private=20key=20->=20Rand?= =?UTF-8?q?om=20Beacon=20private=20key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/util/cmd/epochs/cmd/recover.go | 2 +- .../hotstuff/signature/randombeacon_signer_store.go | 6 +++--- .../hotstuff/verification/combined_verifier_v3.go | 2 +- .../votecollector/combined_vote_processor_v2_test.go | 4 ++-- .../votecollector/combined_vote_processor_v3_test.go | 4 ++-- consensus/integration/epoch_test.go | 2 +- consensus/integration/nodes_test.go | 2 +- integration/testnet/network.go | 4 ++-- model/convert/service_event.go | 10 +++++----- state/protocol/badger/mutator_test.go | 2 +- state/protocol/validity.go | 4 ++-- 11 files changed, 21 insertions(+), 21 deletions(-) diff --git a/cmd/util/cmd/epochs/cmd/recover.go b/cmd/util/cmd/epochs/cmd/recover.go index cb56779d3f4..436fc59c229 100644 --- a/cmd/util/cmd/epochs/cmd/recover.go +++ b/cmd/util/cmd/epochs/cmd/recover.go @@ -20,7 +20,7 @@ import ( // EFM can be exited only by a special service event, EpochRecover, which initially originates from a manual service account transaction. // The full epoch data must be generated manually and submitted with this transaction in order for an // EpochRecover event to be emitted. This command retrieves the current protocol state identities, computes the cluster assignment using those -// identities, generates the cluster QCs and retrieves the DKG key vector of the last successful epoch. +// identities, generates the cluster QCs and retrieves the Random Beacon key vector of the last successful epoch. // This recovery process has some constraints: // - The RecoveryEpoch must have exactly the same consensus committee as participated in the most recent successful DKG. // - The RecoveryEpoch must contain enough "internal" collection nodes so that all clusters contain a supermajority of "internal" collection nodes (same constraint as sporks) diff --git a/consensus/hotstuff/signature/randombeacon_signer_store.go b/consensus/hotstuff/signature/randombeacon_signer_store.go index c5ff9d13f4a..f0f993f7cbc 100644 --- a/consensus/hotstuff/signature/randombeacon_signer_store.go +++ b/consensus/hotstuff/signature/randombeacon_signer_store.go @@ -42,9 +42,9 @@ func (s *EpochAwareRandomBeaconKeyStore) ByView(view uint64) (crypto.PrivateKey, } // When DKG has completed, - // - if a node successfully generated the DKG key, the valid private key will be stored in database. - // - if a node failed to generate the DKG key, we will save a record in database to indicate this - // node has no private key for this epoch. + // - if a node successfully generated the Random Beacon key, the valid private key will be stored in database. + // - if a node failed to generate the Random Beacon key, we will save a record in database to indicate this + // node has no private key for this epoch. // Within the epoch, we can look up my random beacon private key for the epoch. There are 3 cases: // 1. DKG has completed, and the private key is stored in database, and we can retrieve it (happy path) // 2. DKG has completed, but we failed to generate a private key (unhappy path) diff --git a/consensus/hotstuff/verification/combined_verifier_v3.go b/consensus/hotstuff/verification/combined_verifier_v3.go index 6b2c0507381..5aff5e352a3 100644 --- a/consensus/hotstuff/verification/combined_verifier_v3.go +++ b/consensus/hotstuff/verification/combined_verifier_v3.go @@ -174,7 +174,7 @@ func (c *CombinedVerifierV3) VerifyQC(signers flow.IdentitySkeletonList, sigData if protocol.IsIdentityNotFound(err) { return model.NewInvalidSignerErrorf("%v is not a random beacon participant: %w", signerID, err) } - return fmt.Errorf("unexpected error retrieving dkg key share for signer %v: %w", signerID, err) + return fmt.Errorf("unexpected error retrieving Random Beacon key share for signer %v: %w", signerID, err) } beaconPubKeys = append(beaconPubKeys, keyShare) } diff --git a/consensus/hotstuff/votecollector/combined_vote_processor_v2_test.go b/consensus/hotstuff/votecollector/combined_vote_processor_v2_test.go index 74387d32433..da25cc6ddb9 100644 --- a/consensus/hotstuff/votecollector/combined_vote_processor_v2_test.go +++ b/consensus/hotstuff/votecollector/combined_vote_processor_v2_test.go @@ -812,7 +812,7 @@ func TestCombinedVoteProcessorV2_BuildVerifyQC(t *testing.T) { identity.StakingPubKey = stakingPriv.PublicKey() keys := &storagemock.SafeBeaconKeys{} - // there is no DKG key for this epoch + // there is no Random Beacon key for this epoch keys.On("RetrieveMyBeaconPrivateKey", epochCounter).Return(nil, false, nil) beaconSignerStore := hsig.NewEpochAwareRandomBeaconKeyStore(epochLookup, keys) @@ -833,7 +833,7 @@ func TestCombinedVoteProcessorV2_BuildVerifyQC(t *testing.T) { } keys := &storagemock.SafeBeaconKeys{} - // there is DKG key for this epoch + // there is Random Beacon key for this epoch keys.On("RetrieveMyBeaconPrivateKey", epochCounter).Return(dkgKey, true, nil) beaconSignerStore := hsig.NewEpochAwareRandomBeaconKeyStore(epochLookup, keys) diff --git a/consensus/hotstuff/votecollector/combined_vote_processor_v3_test.go b/consensus/hotstuff/votecollector/combined_vote_processor_v3_test.go index ad684ee3d83..56bba752881 100644 --- a/consensus/hotstuff/votecollector/combined_vote_processor_v3_test.go +++ b/consensus/hotstuff/votecollector/combined_vote_processor_v3_test.go @@ -948,7 +948,7 @@ func TestCombinedVoteProcessorV3_BuildVerifyQC(t *testing.T) { identity.StakingPubKey = stakingPriv.PublicKey() keys := &storagemock.SafeBeaconKeys{} - // there is no DKG key for this epoch + // there is no Random Beacon key for this epoch keys.On("RetrieveMyBeaconPrivateKey", epochCounter).Return(nil, false, nil) beaconSignerStore := hsig.NewEpochAwareRandomBeaconKeyStore(epochLookup, keys) @@ -970,7 +970,7 @@ func TestCombinedVoteProcessorV3_BuildVerifyQC(t *testing.T) { } keys := &storagemock.SafeBeaconKeys{} - // there is DKG key for this epoch + // there is Random Beacon key for this epoch keys.On("RetrieveMyBeaconPrivateKey", epochCounter).Return(dkgKey, true, nil) beaconSignerStore := hsig.NewEpochAwareRandomBeaconKeyStore(epochLookup, keys) diff --git a/consensus/integration/epoch_test.go b/consensus/integration/epoch_test.go index 87e802a74cc..0b9b364cb1f 100644 --- a/consensus/integration/epoch_test.go +++ b/consensus/integration/epoch_test.go @@ -117,7 +117,7 @@ func TestEpochTransition_IdentitiesOverlap(t *testing.T) { newIdentity, ) - // generate new identities for next epoch, it will generate new DKG keys for random beacon participants + // generate new identities for next epoch, it will generate new Random Beacon keys for random beacon participants nextEpochParticipantData := completeConsensusIdentities(t, privateNodeInfos[1:]) rootSnapshot = withNextEpoch(t, rootSnapshot, nextEpochIdentities, nextEpochParticipantData, consensusParticipants, 4, func(block *flow.Block) *flow.QuorumCertificate { return createRootQC(t, block, firstEpochConsensusParticipants) diff --git a/consensus/integration/nodes_test.go b/consensus/integration/nodes_test.go index 0f76986a90c..e02761f9b40 100644 --- a/consensus/integration/nodes_test.go +++ b/consensus/integration/nodes_test.go @@ -530,7 +530,7 @@ func createNode( require.NoError(t, err) keys := &storagemock.SafeBeaconKeys{} - // there is DKG key for this epoch + // there is Random Beacon key for this epoch keys.On("RetrieveMyBeaconPrivateKey", mock.Anything).Return( func(epochCounter uint64) crypto.PrivateKey { dkgInfo, ok := participant.beaconInfoByEpoch[epochCounter] diff --git a/integration/testnet/network.go b/integration/testnet/network.go index 217078aaded..7180b909862 100644 --- a/integration/testnet/network.go +++ b/integration/testnet/network.go @@ -1072,8 +1072,8 @@ func BootstrapNetwork(networkConf NetworkConfig, bootstrapDir string, chainID fl allNodeInfos := append(toNodeInfos(stakedConfs), followerInfos...) - // IMPORTANT: we must use this ordering when writing the DKG keys as - // this ordering defines the DKG participant's indices + // IMPORTANT: we must use this ordering when writing the Random Beacon keys as + // this ordering defines the DKG participants' indices stakedNodeInfos := bootstrap.Sort(toNodeInfos(stakedConfs), flow.Canonical[flow.Identity]) dkg, dkgIndexMap, err := runBeaconKG(stakedConfs) diff --git a/model/convert/service_event.go b/model/convert/service_event.go index 6a9781b2319..c63dde275e5 100644 --- a/model/convert/service_event.go +++ b/model/convert/service_event.go @@ -275,13 +275,13 @@ func convertServiceEventEpochCommit(event flow.Event) (*flow.ServiceEvent, error // parse DKG participants commit.DKGParticipantKeys, err = convertDKGKeys(cdcDKGKeys.Values) if err != nil { - return nil, fmt.Errorf("could not convert DKG keys: %w", err) + return nil, fmt.Errorf("could not convert Random Beacon keys: %w", err) } // parse DKG group key commit.DKGGroupKey, err = convertDKGKey(cdcDKGGroupKey) if err != nil { - return nil, fmt.Errorf("could not convert DKG group key: %w", err) + return nil, fmt.Errorf("could not convert Random Beacon group key: %w", err) } // parse DKG Index Map @@ -469,7 +469,7 @@ func convertServiceEventEpochRecover(event flow.Event) (*flow.ServiceEvent, erro // parse DKG participants commit.DKGParticipantKeys, err = convertDKGKeys(cdcDKGKeys.Values) if err != nil { - return nil, fmt.Errorf("failed to decode DKG key shares from EpochRecover event: %w", err) + return nil, fmt.Errorf("failed to decode Random Beacon key shares from EpochRecover event: %w", err) } // parse DKG group key @@ -986,7 +986,7 @@ func convertClusterQCVotes(cdcClusterQCs []cadence.Value) ( return qcVoteDatas, nil } -// convertDKGKeys converts hex-encoded DKG public keys as received by the DKG +// convertDKGKeys converts hex-encoded public beacon keys as received by the DKG // smart contract into crypto.PublicKey representations suitable for inclusion // in the protocol state. func convertDKGKeys(cdcDKGKeys []cadence.Value) ([]crypto.PublicKey, error) { @@ -1001,7 +1001,7 @@ func convertDKGKeys(cdcDKGKeys []cadence.Value) ([]crypto.PublicKey, error) { return convertedKeys, nil } -// convertDKGKey converts a single hex-encoded DKG public key as received by the DKG +// convertDKGKey converts a single hex-encoded public beacon keys as received by the DKG // smart contract into crypto.PublicKey representations suitable for inclusion // in the protocol state. func convertDKGKey(cdcDKGKeys cadence.Value) (crypto.PublicKey, error) { diff --git a/state/protocol/badger/mutator_test.go b/state/protocol/badger/mutator_test.go index 69eb60daf38..220ad8901bf 100644 --- a/state/protocol/badger/mutator_test.go +++ b/state/protocol/badger/mutator_test.go @@ -1653,7 +1653,7 @@ func TestExtendEpochCommitInvalid(t *testing.T) { unittest.InsertAndFinalize(t, state, block3) _, receipt, seal := createCommit(block3, func(commit *flow.EpochCommit) { - // add an extra dkg key + // add an extra Random Beacon key commit.DKGParticipantKeys = append(commit.DKGParticipantKeys, unittest.KeyFixture(crypto.BLSBLS12381).PublicKey()) }) diff --git a/state/protocol/validity.go b/state/protocol/validity.go index 984a1c1c285..85099d6267d 100644 --- a/state/protocol/validity.go +++ b/state/protocol/validity.go @@ -163,7 +163,7 @@ func IsValidEpochCommit(commit *flow.EpochCommit, setup *flow.EpochSetup) error return NewInvalidServiceEventErrorf("inconsistent epoch counter between commit (%d) and setup (%d) events in same epoch", commit.Counter, setup.Counter) } - // make sure we have a valid DKG public key + // make sure we have a Random Beacon group key: if commit.DKGGroupKey == nil { return NewInvalidServiceEventErrorf("missing DKG public group key") } @@ -171,7 +171,7 @@ func IsValidEpochCommit(commit *flow.EpochCommit, setup *flow.EpochSetup) error // enforce invariant: len(DKGParticipantKeys) == len(DKGIndexMap) n := len(commit.DKGIndexMap) // size of the DKG committee if len(commit.DKGParticipantKeys) != n { - return NewInvalidServiceEventErrorf("dkg key list (len=%d) does not match index map (len=%d)", len(commit.DKGParticipantKeys), len(commit.DKGIndexMap)) + return NewInvalidServiceEventErrorf("number of %d Random Beacon key shares is inconsistent with number of DKG participatns (len=%d)", len(commit.DKGParticipantKeys), len(commit.DKGIndexMap)) } // enforce invariant: DKGIndexMap values form the set {0, 1, ..., n-1} where n=len(DKGParticipantKeys) From 6f4fd53722fc7a229def9cf61a87d29ee52f7ed5 Mon Sep 17 00:00:00 2001 From: Tarak Ben youssef Date: Thu, 28 Nov 2024 03:14:30 +0700 Subject: [PATCH 6/7] address review: multiple clarifications --- model/convert/service_event.go | 4 ++-- model/flow/dkg.go | 30 +++++++++++++++--------------- module/dkg.go | 9 ++++----- module/dkg/client.go | 2 +- state/protocol/defaults.go | 16 ++++++++-------- state/protocol/validity.go | 17 +++++++++++------ 6 files changed, 41 insertions(+), 37 deletions(-) diff --git a/model/convert/service_event.go b/model/convert/service_event.go index c63dde275e5..43f63cb85c1 100644 --- a/model/convert/service_event.go +++ b/model/convert/service_event.go @@ -475,7 +475,7 @@ func convertServiceEventEpochRecover(event flow.Event) (*flow.ServiceEvent, erro // parse DKG group key commit.DKGGroupKey, err = convertDKGKey(cdcDKGGroupKey) if err != nil { - return nil, fmt.Errorf("failed to decode DKG group key from EpochRecover event: %w", err) + return nil, fmt.Errorf("failed to decode Random Beacon group key from EpochRecover event: %w", err) } // parse DKG Index Map @@ -1014,7 +1014,7 @@ func convertDKGKey(cdcDKGKeys cadence.Value) (crypto.PublicKey, error) { // decode individual public keys pubKeyBytes, err := hex.DecodeString(string(keyHex)) if err != nil { - return nil, fmt.Errorf("could not decode hex into bytes: %w", err) + return nil, fmt.Errorf("converting hex to bytes failed: %w", err) } pubKey, err := crypto.DecodePublicKey(crypto.BLSBLS12381, pubKeyBytes) if err != nil { diff --git a/model/flow/dkg.go b/model/flow/dkg.go index da31566c98b..d34e862d842 100644 --- a/model/flow/dkg.go +++ b/model/flow/dkg.go @@ -51,7 +51,7 @@ func (state DKGEndState) String() string { // - DKGIndexMap completely describes the DKG committee. If n parties are authorized to participate // in the DKG, DKGIndexMap must contain exactly n elements, i.e. n = len(DKGIndexMap) // - The values in DKGIndexMap must form the set {0, 1, …, n-1}, as required by the low level cryptography -// module. +// module (convention simplifying the implementation). // // CAUTION: It is important to cleanly differentiate between the consensus committee π’ž, the DKG committee π’Ÿ // and the committee β„›: @@ -59,14 +59,14 @@ func (state DKGEndState) String() string { // to vote (i.e. membership in the consensus committee) is irrevocably granted for an epoch (though, honest nodes // will reject votes and proposals from ejected nodes; nevertheless, ejected nodes formally remain members of // the consensus committee). -// - The DKG committee π’Ÿ is the set of parties that were authorized to -// participate in the DKG. Mathematically, the DKGIndexMap is an bijective function -// DKGIndexMap: π’Ÿ ↦ {0,1,…,n-1}. -// - Only consensus nodes are allowed to contribute to the random beacon. We define the committee β„› -// as the subset of the consensus committee (β„› βŠ† π’ž), which _successfully_ completed the DKG, -// i.e which completed participation in DKG (β„› βŠ† π’Ÿ) with a private key that matches the public key assigned to them by -// the DKG committee (or a trusted dealer). Nodes in β„› are therefore able to submit valid random beacon votes. -// Based on this definition we note that β„› βŠ† (π’Ÿ ∩ π’ž). +// - The DKG committee π’Ÿ is the set of parties that were authorized to participate in the DKG (happy path; or +// eligible to receive a private key share from an alternative source on the fallback path). Mathematically, +// the DKGIndexMap is a bijective function DKGIndexMap: π’Ÿ ↦ {0,1,…,n-1}. +// - Only consensus nodes are allowed to contribute to the random beacon. Informally, we define β„› as the +// as the subset of the consensus committee (β„› βŠ† π’ž), which _successfully_ completed the DKG (hence β„› βŠ† π’Ÿ). +// Specifically, r ∈ β„› iff and only if r has a private Random Beacon key share matching the respective public +// key share in the `EpochCommit` event. In other words, consensus nodes are in β„› iff and only if they are able +// to submit valid random beacon votes. Based on this definition we note that β„› βŠ† (π’Ÿ ∩ π’ž). // // The protocol explicitly ALLOWS additional parties outside the current epoch's consensus committee to participate. // In particular, there can be a key-value pair (d,i) ∈ DKGIndexMap, such that the nodeID d is *not* a consensus @@ -80,16 +80,16 @@ func (state DKGEndState) String() string { // // Nevertheless, there is an important liveness constraint: the committee β„› should be a large number of nodes. // Specifically, an honest supermajority of consensus nodes must contain enough successful DKG participants -// (about n/2 + 1) to produce a valid group signature for the random beacon at each block [1, 3]. -// Therefore, we have the approximate lower bound |β„›| >= n/2 + 1 = |π’Ÿ|/2 + 1 = len(DKGIndexMap)/2 + 1. +// (about |π’Ÿ|/2 + 1) to produce a valid group signature for the random beacon at each block [1, 3]. +// Therefore, we have the approximate lower bound |β„›| ≳ n/2 + 1 = |π’Ÿ|/2 + 1 = len(DKGIndexMap)/2 + 1. // Operating close to this lower bound would require that every random beacon key-holder Ο± ∈ β„› remaining in the consensus committee is honest -// (incl. quickly responsive) *all the time*. This is a lower bound, unsuited for decentralized production networks. +// (incl. quickly responsive) *all the time*. Such a reliability assumption is unsuited for decentralized production networks. // To reject configurations that are vulnerable to liveness failures, the protocol uses the threshold `t_safety` // (heuristic, see [2]), which is implemented on the smart contract level. -// In a nutshell, |β„›| and therefore |π’Ÿ ∩ π’ž| (given that |β„›| <= |π’Ÿ ∩ π’ž|) should be well above 70% * |π’Ÿ| = 0.7 * n, -// values in the range 70%-62% of |π’Ÿ| should be considered for short-term recovery cases. +// Ideally, |β„›| and therefore |π’Ÿ ∩ π’ž| (given that |β„›| <= |π’Ÿ ∩ π’ž|) should be well above 70% . |π’Ÿ|. +// Values in the range 70%-62% of |π’Ÿ| should be considered for short-term recovery cases. // Values of 62% * |π’Ÿ| or lower (i.e. |β„›| ≀ 0.62Β·|π’Ÿ|) are not recommended for any -// production network, as single-node crashes are already enough to halt consensus. +// production network, as single-node crashes may already be enough to halt consensus. // // For further details, see // - [1] https://www.notion.so/flowfoundation/Threshold-Signatures-7e26c6dd46ae40f7a83689ba75a785e3?pvs=4 diff --git a/module/dkg.go b/module/dkg.go index 456d19f424f..db23a27ec50 100644 --- a/module/dkg.go +++ b/module/dkg.go @@ -37,7 +37,7 @@ type DKGContractClient interface { // key shares. Serialized public keys are encoded as lower-case hex strings. // Conceptually the flow.DKGIndexMap is not an output of the DKG protocol. Rather, it is part of the configuration/initialization // information of the DKG. Before an epoch transition on the happy path (using the data in the EpochSetup event), each consensus - // participant locally fixes the DKG committee π’Ÿ including the respective nodes order to be identical to the consensus + // participant locally fixes the DKG committee π’Ÿ including the respective nodes' order to be identical to the consensus // committee π’ž. However, in case of a failed epoch transition, we desire the ability to manually provide the result of a successful // DKG for the immediately next epoch (so-called recovery epoch). The DKG committee π’Ÿ must have a sufficiently large overlap with // the recovery epoch's consensus committee π’ž -- though for flexibility, we do *not* want to require that both committees are identical. @@ -47,10 +47,9 @@ type DKGContractClient interface { // SubmitEmptyResult submits an empty result of the DKG protocol. // The empty result is obtained by a node when it realizes locally that its DKG participation - // was unsuccessful (for various reasons: because the node received too many byzantine inputs, - // because the node has networking issues, because a locally computed key is invalid..). - // However, a node obtaining an empty result can happen in both cases of the DKG succeeding or failing - // globally. + // was unsuccessful (possible reasons include: node received too many byzantine inputs; + // node has networking issues; locally computed key is invalid…). However, a node obtaining an + // empty result can happen in both cases of the DKG succeeding or failing globally. // For further details, please see: // https://flowfoundation.notion.site/Random-Beacon-2d61f3b3ad6e40ee9f29a1a38a93c99c // Honest nodes would call `SubmitEmptyResult` strictly after the final phase has ended if DKG has ended. diff --git a/module/dkg/client.go b/module/dkg/client.go index ea67cede449..42865ebeef2 100644 --- a/module/dkg/client.go +++ b/module/dkg/client.go @@ -185,7 +185,7 @@ func (c *Client) Broadcast(msg model.BroadcastDKGMessage) error { // key shares. Serialized public keys are encoded as lower-case hex strings. // Conceptually the flow.DKGIndexMap is not an output of the DKG protocol. Rather, it is part of the configuration/initialization // information of the DKG. Before an epoch transition on the happy path (using the data in the EpochSetup event), each consensus -// participant locally fixes the DKG committee π’Ÿ including the respective nodes order to be identical to the consensus +// participant locally fixes the DKG committee π’Ÿ including the respective nodes' order to be identical to the consensus // committee π’ž. However, in case of a failed epoch transition, we desire the ability to manually provide the result of a successful // DKG for the immediately next epoch (so-called recovery epoch). The DKG committee π’Ÿ must have a sufficiently large overlap with // the recovery epoch's consensus committee π’ž -- though for flexibility, we do *not* want to require that both committees are identical. diff --git a/state/protocol/defaults.go b/state/protocol/defaults.go index 06c52b996d5..6faaaa61276 100644 --- a/state/protocol/defaults.go +++ b/state/protocol/defaults.go @@ -34,20 +34,20 @@ func DefaultEpochSafetyParams(chain flow.ChainID) (SafetyParams, error) { // RandomBeaconSafetyThreshold defines a production network safety threshold for random beacon protocol based on the size // of the random beacon committee β„› and the DKG committee π’Ÿ. // -// We recall that the committee β„› is defined as the subset of the consensus committee (β„› βŠ† π’ž), -// and the DKG committee (β„› βŠ† π’Ÿ) which _successfully_ completed the DKG and is able to contribute with a random beacon share. +// We recall that the committee β„› is defined as the subset of the consensus committee (β„› βŠ† π’ž) and the DKG +// committee (β„› βŠ† π’Ÿ) that _successfully_ completed the DKG and is able to contribute with a random beacon share. // // An honest supermajority of consensus nodes must contain enough successful DKG participants -// (about n/2 + 1) to produce a valid group signature for the random beacon at each block [1, 3]. -// Therefore, we have the approximate lower bound |β„›| >= n/2 + 1 = |π’Ÿ|/2 + 1 = len(DKGIndexMap)/2 + 1. +// (about |π’Ÿ|/2 + 1) to produce a valid group signature for the random beacon at each block [1, 3]. +// Therefore, we have the approximate lower bound |β„›| ≳ n/2 + 1 = |π’Ÿ|/2 + 1 = len(DKGIndexMap)/2 + 1. // Operating close to this lower bound would require that every random beacon key-holder Ο± ∈ β„› remaining in the consensus committee is honest -// (incl. quickly responsive) *all the time*. This is a lower bound, unsuited for decentralized production networks. +// (incl. quickly responsive) *all the time*. Such a reliability assumption is unsuited for decentralized production networks. // To reject configurations that are vulnerable to liveness failures, the protocol uses the threshold `t_safety` // (heuristic, see [2]), which is implemented on the smart contract level. -// In a nutshell, |β„›| and therefore |π’Ÿ ∩ π’ž| (given that |β„›| <= |π’Ÿ ∩ π’ž|) should be well above 70% * |π’Ÿ| = 0.7 * n, -// values in the range 70%-62% of |π’Ÿ| should be considered for short-term recovery cases. +// Ideally, |β„›| and therefore |π’Ÿ ∩ π’ž| (given that |β„›| <= |π’Ÿ ∩ π’ž|) should be well above 70% . |π’Ÿ|. +// Values in the range 70%-62% of |π’Ÿ| should be considered for short-term recovery cases. // Values of 62% * |π’Ÿ| or lower (i.e. |β„›| ≀ 0.62Β·|π’Ÿ|) are not recommended for any -// production network, as single-node crashes are already enough to halt consensus. +// production network, as single-node crashes may already be enough to halt consensus. // // For further details, see // - godoc for [flow.DKGIndexMap] diff --git a/state/protocol/validity.go b/state/protocol/validity.go index 85099d6267d..8704a8290b7 100644 --- a/state/protocol/validity.go +++ b/state/protocol/validity.go @@ -192,16 +192,21 @@ func IsValidEpochCommit(commit *flow.EpochCommit, setup *flow.EpochSetup) error numberOfRandomBeaconParticipants++ } } - // enforce invariant: RandomBeaconSafetyThreshold ≀ |π’ž ∩ π’Ÿ| where: + // Important SANITY CHECK: reject configurations where too few consensus nodes have valid random beacon key shares to + // reliably reach the required threshold of signers. Specifically, we enforce RandomBeaconSafetyThreshold ≀ |π’ž ∩ π’Ÿ|. // - π’ž is the set of all consensus committee members // - π’Ÿ is the set of all DKG participants + // - β„› is the subset of the consensus committee (β„› βŠ† π’ž): it contains consensus nodes (and only those) with a + // private Random Beacon key share matching the respective public key share in the `EpochCommit` event. // - // Note that this is only a sanity check that makes sure the cardinality of β„› βŠ† π’Ÿ ∩ π’ž does not go below the - // critical liveness threshold of 0.62 * |π’Ÿ| (details in [2]). - // If RandomBeaconSafetyThreshold > |π’ž ∩ π’Ÿ|, we are certain that RandomBeaconSafetyThreshold > |β„›|. However, - // making sure that RandomBeaconSafetyThreshold <= |π’ž ∩ π’Ÿ| does not prove that |β„›| is above the critical threshold. + // This is only a sanity check: on the protocol level, we only know which nodes (set π’Ÿ) could participate in the DKG, + // but not which consensus nodes obtained a *valid* random beacon key share. In other words, we only have access to the + // superset π’Ÿ ∩ π’ž βŠ‡ β„› here. If π’Ÿ ∩ π’ž is already too small, we are certain that too few consensus nodes have valid random + // beacon keys (RandomBeaconSafetyThreshold > |π’ž ∩ π’Ÿ| entails RandomBeaconSafetyThreshold > |β„›|) and we reject the + // Epoch configuration. However, enough nodes in the superset |π’ž ∩ π’Ÿ| does not guarantee that |β„›| is above the critical + // threshold (e.g. too many nodes |π’ž ∩ π’Ÿ| could have failed the DKG and therefore not be in β„›). // - // This is different than the check implemented by the DKG contract where the value of |β„›| is known and compared + // This is different than the check in the DKG smart contract, where the value of |β„›| is known and compared // to the threshold. Unlike the DKG contract, the protocol state does not have access to the value of |β„›| from a past // key generation (decentralized or not). // From b0f36be37db031fd8b43a5e929b360c6c7c13fab Mon Sep 17 00:00:00 2001 From: Tarak Ben youssef Date: Sat, 30 Nov 2024 00:57:57 +0800 Subject: [PATCH 7/7] move DKGIndexMap formal specs up --- model/flow/dkg.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/model/flow/dkg.go b/model/flow/dkg.go index 5065665e213..e9825d86fc8 100644 --- a/model/flow/dkg.go +++ b/model/flow/dkg.go @@ -37,9 +37,16 @@ func (state DKGEndState) String() string { } // DKGIndexMap completely describes the DKG committee π’Ÿ of size |π’Ÿ| = n. +// +// Formal specification: +// - If n parties are authorized to participate in the DKG, DKGIndexMap must contain exactly n +// elements, i.e. n = len(DKGIndexMap) +// - The values in DKGIndexMap must form the set {0, 1, …, n-1}, as required by the low level cryptography +// module (convention simplifying the implementation). +// // Flow's random beacon utilizes a threshold signature scheme run by the committee π’Ÿ. -// In the formal cryptographic protocol for a threshold signature with n parties, -// the individual participants are identified by n public distinct non-negative integers, or simply indices. +// In the formal cryptographic protocol for a threshold signature with n parties, the +// individual participants are identified by n public distinct non-negative integers, or simply indices. // These public indices are agreed upon by all participants and are used by the low-level // Shamir Secret Sharing [SSS]. // In Flow, the threshold signature keys are generated by a Distributed Key Generation [DKG]. The DKG @@ -52,12 +59,6 @@ func (state DKGEndState) String() string { // random beacon signature shares. Hence, the protocol level needs to map nodeIDs to the indices when // calling into the lower-level cryptographic primitives. // -// Formal specification: -// - DKGIndexMap completely describes the DKG committee. If n parties are authorized to participate -// in the DKG, DKGIndexMap must contain exactly n elements, i.e. n = len(DKGIndexMap) -// - The values in DKGIndexMap must form the set {0, 1, …, n-1}, as required by the low level cryptography -// module (convention simplifying the implementation). -// // CAUTION: It is important to cleanly differentiate between the consensus committee π’ž, the DKG committee π’Ÿ // and the committee β„›: // - For an epoch, the consensus committee π’ž contains all nodes that are authorized to vote for blocks. Authority