Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Random beacon comments #6590

Open
wants to merge 4 commits into
base: feature/efm-recovery
Choose a base branch
from
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
4 changes: 2 additions & 2 deletions cmd/bootstrap/run/qc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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("nonexistent node id (%x) in participant lookup", node.NodeID)
}
dkgIndex := dkgParticipant.Index

Expand Down
6 changes: 3 additions & 3 deletions model/convert/service_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 beacon key share: %w", err)
}
convertedKeys = append(convertedKeys, pubKey)
}
Expand All @@ -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
}
Expand Down
63 changes: 38 additions & 25 deletions model/flow/dkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,51 +31,64 @@ 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
// 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
// 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 π’Ÿ:
// 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
// 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 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 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
Expand Down
21 changes: 12 additions & 9 deletions module/dkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,28 @@ 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 results are the node's local computation of the group public key and the public
// 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 order of 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.
// Therefore, we need to explicitly specify the DKG committee π’Ÿ on the fallback path. For uniformity of implementation, we do the
// 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
// 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.
// 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
}
Expand Down
8 changes: 4 additions & 4 deletions module/dkg/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 results are the node's local computation of the group public key and the public
// 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 order of 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.
Expand Down
2 changes: 1 addition & 1 deletion module/signature/signing_tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
20 changes: 14 additions & 6 deletions state/protocol/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 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
Expand Down
2 changes: 1 addition & 1 deletion state/protocol/prg/prg.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down
11 changes: 11 additions & 0 deletions state/protocol/validity.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading