ADR 0007: Improved Random Beacon
Component
Oasis Core
Changelog
- 2020-10-22: Initial version
Status
Proposed
Context
Any one who considers arithmetical methods of producing random digits is, of course, in a state of sin.
--Dr. John von Neumann
The existing random beacon used by Oasis Core, is largely a placeholder implementation that naively uses the previous block's commit hash as the entropy input. As such it is clearly insecure as it is subject to manipulation.
A better random beacon which is harder for an adversary to manipulate is required to provide entropy for secure committee elections.
Decision
At a high level, this ADR proposes implementing an on-chain random beacon based on "SCRAPE: Scalabe Randomness Attested by Public Entities" by Cascudo and David. The new random beacon will use a commit-reveal scheme backed by a PVSS scheme so that as long as the threshold of participants is met, and one participant is honest, secure entropy will be generated.
Note: This document assumes the reader understands SCRAPE. Details regarding the underlying SCRAPE implementation are omitted for brevity.
Node Descriptor
The node descriptor of each node will be extended to include the following datastructure.
type Node struct {
// ... existing fields omitted ...
// Beacon contains information for this node's participation
// in the random beacon protocol.
//
// TODO: This is optional for now, make mandatory once enough
// nodes provide this field.
Beacon *BeaconInfo `json:"beacon,omitempty"`
}
// BeaconInfo contains information for this node's participation in
// the random beacon protocol.
type BeaconInfo struct {
// Point is the elliptic curve point used for the PVSS algorithm.
Point scrape.Point `json:"point"`
}
Each node will generate and maintain a long term elliptic curve point and scalar pair (public/private key pair), the point (public key) of which will be included in the node descriptor.
For the purposes of the initial implementation, the curve will be P-256.
Consensus Parameters
The beacon module will have the following consensus parameters that control behavior.
type SCRAPEParameters struct {
Participants uint64 `json:"participants"`
Threshold uint64 `json:"threshold"`
PVSSThreshold uint64 `json:"pvss_threshold"`
CommitInterval int64 `json:"commit_interval"`
RevealInterval int64 `json:"reveal_interval"`
TransitionDelay int64 `json:"transition_delay"`
}
Fields:
-
Participants
- The number of participants to be selected for each beacon generation protocol round. -
Threshold
- The minimum number of participants which must successfully contribute entropy for the final output to be considered valid. -
PVSSThreshold
- The minimum number of participants that are required to reconstruct a PVSS secret from the corresponding decrypted shares (Note: This usually should just be set toThreshold
). -
CommitInterval
- The duration of the Commit phase, in blocks. -
RevealInterval
- The duration of the Reveal phase, in blocks. -
TransitionDelay
- The duration of the post Reveal phase delay, in blocks.
Consensus State and Events
The on-chain beacon will maintain and make available the following consensus state.
// RoundState is a SCRAPE round state.
type RoundState uint8
const (
StateInvalid RoundState = 0
StateCommit RoundState = 1
StateReveal RoundState = 2
StateComplete RoundState = 3
)
// SCRAPEState is the SCRAPE backend state.
type SCRAPEState struct {
Height int64 `json:"height,omitempty"`
Epoch EpochTime `json:"epoch,omitempty"`
Round uint64 `json:"round,omitempty"`
State RoundState `json:"state,omitempty"`
Instance *scrape.Instance `json:"instance,omitempty"`
Participants []signature.PublicKey `json:"participants,omitempty"`
Entropy []byte `json:"entropy,omitempty"`
BadParticipants map[signature.PublicKey]bool `json:"bad_participants,omitempty"`
CommitDeadline int64 `json:"commit_deadline,omitempty"`
RevealDeadline int64 `json:"reveal_deadline,omitempty"`
TransitionHeight int64 `json:"transition_height,omitempty"`
RuntimeDisableHeight int64 `json:"runtime_disable_height,omitempty"`
}
Fields:
-
Height
- The block height at which the last event was emitted. -
Epoch
- The epoch in which this beacon is being generated. -
Round
- The epoch beacon generation round. -
State
- The beacon generation step (commit/reveal/complete). -
Instance
- The SCRAPE protocol state (encrypted/decrypted shares of all participants). -
Participants
- The node IDs of the nodes selected to participate in this beacon generation round. -
Entropy
- The final raw entropy, if any. -
BadParticipants
- A map of nodes that were selected, but have failed to execute the protocol correctly. -
CommitDeadline
- The height in blocks by which participants must submit their encrypted shares. -
RevealDeadline
- The height in blocks by which participants must submit their decrypted shares. -
TransitionHeight
- The height at which the epoch will transition assuming this round completes successfully. -
RuntimeDisableHeight
- The height at which, upon protocol failure, runtime transactions will be disabled. This height will be set to the transition height of the 0th round.
Upon transition to a next step of the protocol, the on-chain beacon will emit the following event.
// SCRAPEEvent is a SCRAPE backend event.
type SCRAPEEvent struct {
Height int64 `json:"height,omitempty"`
Epoch EpochTime `json:"epoch,omitempty"`
Round uint64 `json:"round,omitempty"`
State RoundState `json:"state,omitempty"`
Participants []signature.PublicKey `json:"participants,omitempty"`
}
Field definitions are identical to that of those in the SCRAPEState
datastructure.
Transactions
Participating nodes will submit the following transactions when required, signed by the node identity key.
var (
// MethodSCRAPECommit is the method name for a SCRAPE commitment.
MethodSCRAPECommit = transaction.NewMethodName(ModuleName, "SCRAPECommit", SCRAPECommit{})
// MethodSCRAPEReveal is the method name for a SCRAPE reveal.
MethodSCRAPEReveal = transaction.NewMethodName(ModuleName, "SCRAPEReveal", SCRAPEReveal{})
)
// SCRAPECommit is a SCRAPE commitment transaction payload.
type SCRAPECommit struct {
Epoch EpochTime `json:"epoch"`
Round uint64 `json:"round"`
Commit *scrape.Commit `json:"commit,omitempty"`
}
// SCRAPEReveal is a SCRAPE reveal transaction payload.
type SCRAPEReveal struct {
Epoch EpochTime `json:"epoch"`
Round uint64 `json:"round"`
Reveal *scrape.Reveal `json:"reveal,omitempty"`
}
Fields:
-
Epoch
- The epoch in which the transaction is applicable. -
Round
- The epoch beacon generation round for the transaction. -
Commit
- The SCRAPE commit consisting of PVSS shares encrypted to every participant. -
Reveal
- The SCRAPE reveal consisting of the decrypted result of PVSS shares received from every participant.
Beacon Generation
The beacon generation process is split into three sequential stages, roughly corresponding to the steps in the SCRAPE protocol. Any failures in the Commit and Reveal phases result in a failed protocol round, and the generation process will restart after disqualifying participants who have induced the failure.
Commit Phase
Upon epoch transition or a prior failed round the commit phase is initiated
the consensus application will select Particpants
nodes from the current
validator set (in order of decending stake) to serve as entropy contributors.
The SCRAPEState
structure is (re)-initialized, and a SCRAPEEvent
is
broadcast to signal to the participants that they should generate and
submit their encrypted shares via a SCRAPECommit
transaction.
Each commit phase lasts exactly CommitInterval
blocks, at the end of which,
the round will be closed to further commits.
At the end of the commit phase, the SCRAPE protocol state is evaluated
to ensure that Threshold
/PVSSThreshold
nodes have published encrypted
shares, and if an insufficient number of nodes have published in either
case, the round is considered to have failed.
The following behaviors are currently candidates for a node being marked
as malicious/non-particpatory (BadParticipant
) and subject to exclusion
from future rounds and slashing.
-
Not submitting a commitment.
-
Malformed commitments (corrupted/fails to validate/etc).
-
Attempting to alter an existing commitment for a given Epoch/Round.
Reveal Phase
When the CommitInterval
has passed, assuming that a sufficient number of
commits have been received, the consensus application transitions into the
reveal phase by updating the SCRAPEState
structure and broadcasting a
SCRAPEEvent
to signal to the participants that they should reveal the
decrypted values of the encrypted shares received from other participants
via a SCRAPEReveal
transaction.
Each reveal phase lasts exactly RevealInterval
blocks, at the end of which,
the round will be closed to further reveals.
At the end of the reveal phase, the SCRAPE protocol state is evaluated to
ensure that Threshold
/PVSSThreshold
nodes have published decrypted
shares, and if an insufficient number of nodes have published in either
case, the round is considered to have failed.
The following behaviors are currently candidates for a node being marked
as malicious/non-participatory (BadParticipant
) and subject to exclusion
from future rounds and slashing.
-
Not submitting a reveal.
-
Malformed commitments (corrupted/fails to validate/etc).
-
Attempting to alter an existing reveal for a given Epoch/Round.
Note: It is possible for anybody who can observe consensus state to derive
the entropy the moment a threshold number of SCRAPEReveal
transactions
have been processed. Therefore the reveal phase should be a small fraction
of the desired epoch as it is possible to derive the results of the
committee elections for the next epoch mid-reveal phase.
Complete (Transition Wait) Phase
When the RevealInterval
has passed, assuming that a sufficient number
of reveals have been received, the consensus application recovers the
final entropy output (the hash of the secret shared by each participant)
and transitions into the complete (transition wait) phase by updating the
SCRAPEState
structure and broadcasting a SCRAPEEvent
to signal to
participants the completion of the round.
No meaningful protocol activity happens one a round has successfully completed, beyond the scheduling of the next epoch transition.
Misc. Changes/Notes
Nodes MUST not be slashed for non-participation if they have not had the opportunity to propose any blocks during the relevant interval.
Processing commitments and reveals is currently rather CPU intensive and thus each block SHOULD only contain one of each to prevent the consesus from stalling.
To thwart attempts to manipulate committee placement by virute of the fact that it is possible to observe the entropy used for elections early nodes that register between the completion of the final commit phase and the epoch transition in any given epoch MUST be excluded from committee eligibility.
Consequences
Positive
-
The random beacon output is unbaised, provided that at least one participant is honest.
-
The amount of consensus state required is relatively small.
-
All protocol messages and steps can be verified on-chain, and misbehavior can be attributed.
-
The final output can be generated on-chain.
Negative
-
Epoch intervals are theoretically variable under this proposal, as the beacon generation needs to be re-ran with new participants upon failure.
-
A new failure mode is introduced at the consensus layer, where the beacon generation protocol exhausts eligible participants.
-
Without using pairing based cryptography, the number of participants in the beacon generation is limited to a small subset of the anticipated active validator set.
-
There is a time window where the next beacon can be derived by anyone with access to the consensus state before the epoch transition actually happens. This should be mitigated by having a relatively short reveal period.
-
The commit and reveal steps of the protocol are rather slow, especially as the number of participants increases.
Neutral
- Due to performance reasons, the curve used by the PVSS scheme will be P-256 instead of Ed25519. The point and scalar pairs that each node generates on this curve are exclusively for use in the random beacon protocol and are not used anywhere else.