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

Clean up DKG simulation in bootstrap #4259

Merged
merged 3 commits into from
May 3, 2023
Merged
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
8 changes: 2 additions & 6 deletions cmd/bootstrap/cmd/dkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,15 @@ import (
"github.com/onflow/flow-go/state/protocol/inmem"
)

func runDKG(nodes []model.NodeInfo) dkg.DKGData {
func runBeaconKG(nodes []model.NodeInfo) dkg.DKGData {
n := len(nodes)

log.Info().Msgf("read %v node infos for DKG", n)

log.Debug().Msgf("will run DKG")
var dkgData dkg.DKGData
var err error
if flagFastKG {
dkgData, err = bootstrapDKG.RunFastKG(n, flagBootstrapRandomSeed)
} else {
dkgData, err = bootstrapDKG.RunDKG(n, GenerateRandomSeeds(n, crypto.SeedMinLenDKG))
}
dkgData, err = bootstrapDKG.RandomBeaconKG(n, GenerateRandomSeed(crypto.SeedMinLenDKG))
if err != nil {
log.Fatal().Err(err).Msg("error running DKG")
}
Expand Down
7 changes: 0 additions & 7 deletions cmd/bootstrap/cmd/finalize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ func TestFinalize_HappyPath(t *testing.T) {
flagPartnerWeights = partnerWeights
flagInternalNodePrivInfoDir = internalPrivDir

flagFastKG = true
flagRootChain = chainName
flagRootParent = hex.EncodeToString(rootParent[:])
flagRootHeight = rootHeight
Expand Down Expand Up @@ -119,8 +118,6 @@ func TestFinalize_Deterministic(t *testing.T) {
flagPartnerWeights = partnerWeights
flagInternalNodePrivInfoDir = internalPrivDir

flagFastKG = true

flagRootCommit = hex.EncodeToString(rootCommit[:])
flagRootParent = hex.EncodeToString(rootParent[:])
flagRootChain = chainName
Expand Down Expand Up @@ -198,8 +195,6 @@ func TestFinalize_SameSeedDifferentStateCommits(t *testing.T) {
flagPartnerWeights = partnerWeights
flagInternalNodePrivInfoDir = internalPrivDir

flagFastKG = true

flagRootCommit = hex.EncodeToString(rootCommit[:])
flagRootParent = hex.EncodeToString(rootParent[:])
flagRootChain = chainName
Expand Down Expand Up @@ -308,8 +303,6 @@ func TestFinalize_InvalidRandomSeedLength(t *testing.T) {
flagPartnerWeights = partnerWeights
flagInternalNodePrivInfoDir = internalPrivDir

flagFastKG = true

flagRootCommit = hex.EncodeToString(rootCommit[:])
flagRootParent = hex.EncodeToString(rootParent[:])
flagRootChain = chainName
Expand Down
8 changes: 2 additions & 6 deletions cmd/bootstrap/cmd/rootblock.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
)

var (
flagFastKG bool
flagRootChain string
flagRootParent string
flagRootHeight uint64
Expand All @@ -23,7 +22,7 @@ var (
var rootBlockCmd = &cobra.Command{
Use: "rootblock",
Short: "Generate root block data",
Long: `Run DKG, generate root block and votes for root block needed for constructing QC. Serialize all info into file`,
Long: `Run Beacon KeyGen, generate root block and votes for root block needed for constructing QC. Serialize all info into file`,
Run: rootBlock,
}

Expand Down Expand Up @@ -61,9 +60,6 @@ func addRootBlockCmdFlags() {
cmd.MarkFlagRequired(rootBlockCmd, "root-height")

rootBlockCmd.Flags().BytesHexVar(&flagBootstrapRandomSeed, "random-seed", GenerateRandomSeed(flow.EpochSetupRandomSourceLength), "The seed used to for DKG, Clustering and Cluster QC generation")

// optional parameters to influence various aspects of identity generation
rootBlockCmd.Flags().BoolVar(&flagFastKG, "fast-kg", false, "use fast (centralized) random beacon key generation instead of DKG")
}

func rootBlock(cmd *cobra.Command, args []string) {
Expand Down Expand Up @@ -104,7 +100,7 @@ func rootBlock(cmd *cobra.Command, args []string) {
log.Info().Msg("")

log.Info().Msg("running DKG for consensus nodes")
dkgData := runDKG(model.FilterByRole(stakingNodes, flow.RoleConsensus))
dkgData := runBeaconKG(model.FilterByRole(stakingNodes, flow.RoleConsensus))
log.Info().Msg("")

log.Info().Msg("constructing root block")
Expand Down
4 changes: 0 additions & 4 deletions cmd/bootstrap/cmd/rootblock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ func TestRootBlock_HappyPath(t *testing.T) {
flagPartnerWeights = partnerWeights
flagInternalNodePrivInfoDir = internalPrivDir

flagFastKG = true

flagRootParent = hex.EncodeToString(rootParent[:])
flagRootChain = chainName
flagRootHeight = rootHeight
Expand Down Expand Up @@ -93,8 +91,6 @@ func TestRootBlock_Deterministic(t *testing.T) {
flagPartnerWeights = partnerWeights
flagInternalNodePrivInfoDir = internalPrivDir

flagFastKG = true

flagRootParent = hex.EncodeToString(rootParent[:])
flagRootChain = chainName
flagRootHeight = rootHeight
Expand Down
201 changes: 5 additions & 196 deletions cmd/bootstrap/dkg/dkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,210 +2,19 @@ package dkg

import (
"fmt"
"sync"
"time"

"github.com/rs/zerolog/log"

"github.com/onflow/flow-go/crypto"
model "github.com/onflow/flow-go/model/dkg"
"github.com/onflow/flow-go/module/signature"
)

// RunDKG simulates a distributed DKG protocol by running the protocol locally
// and generating the DKG output info
func RunDKG(n int, seeds [][]byte) (model.DKGData, error) {

if n != len(seeds) {
return model.DKGData{}, fmt.Errorf("n needs to match the number of seeds (%v != %v)", n, len(seeds))
}

// separate the case whith one node
if n == 1 {
sk, pk, pkGroup, err := thresholdSignKeyGenOneNode(seeds[0])
if err != nil {
return model.DKGData{}, fmt.Errorf("run dkg failed: %w", err)
}

dkgData := model.DKGData{
PrivKeyShares: sk,
PubGroupKey: pkGroup,
PubKeyShares: pk,
}

return dkgData, nil
}

processors := make([]localDKGProcessor, 0, n)

// create the message channels for node communication
chans := make([]chan *message, n)
for i := 0; i < n; i++ {
chans[i] = make(chan *message, 5*n)
}

// create processors for all nodes
for i := 0; i < n; i++ {
processors = append(processors, localDKGProcessor{
current: i,
chans: chans,
})
}

// create DKG instances for all nodes
for i := 0; i < n; i++ {
var err error
processors[i].dkg, err = crypto.NewJointFeldman(n,
signature.RandomBeaconThreshold(n), i, &processors[i])
if err != nil {
return model.DKGData{}, err
}
}

var wg sync.WaitGroup
phase := 0

// start DKG in all nodes
// start listening on the channels
wg.Add(n)
for i := 0; i < n; i++ {
// start dkg could also run in parallel
// but they are run sequentially to avoid having non-deterministic
// output (the PRG used is common)
err := processors[i].dkg.Start(seeds[i])
if err != nil {
return model.DKGData{}, err
}
go dkgRunChan(&processors[i], &wg, phase)
}
phase++

// sync the two timeouts and start the next phase
for ; phase <= 2; phase++ {
wg.Wait()
wg.Add(n)
for i := 0; i < n; i++ {
go dkgRunChan(&processors[i], &wg, phase)
}
}

// synchronize the main thread to end all DKGs
wg.Wait()

skShares := make([]crypto.PrivateKey, 0, n)

for _, processor := range processors {
skShares = append(skShares, processor.privkey)
}

dkgData := model.DKGData{
PrivKeyShares: skShares,
PubGroupKey: processors[0].pubgroupkey,
PubKeyShares: processors[0].pubkeys,
}

return dkgData, nil
}

// localDKGProcessor implements DKGProcessor interface
type localDKGProcessor struct {
current int
dkg crypto.DKGState
chans []chan *message
privkey crypto.PrivateKey
pubgroupkey crypto.PublicKey
pubkeys []crypto.PublicKey
}

const (
broadcast int = iota
private
)

type message struct {
orig int
channel int
data []byte
}

// PrivateSend sends a message from one node to another
func (proc *localDKGProcessor) PrivateSend(dest int, data []byte) {
newMsg := &message{proc.current, private, data}
proc.chans[dest] <- newMsg
}

// Broadcast a message from one node to all nodes
func (proc *localDKGProcessor) Broadcast(data []byte) {
newMsg := &message{proc.current, broadcast, data}
for i := 0; i < len(proc.chans); i++ {
if i != proc.current {
proc.chans[i] <- newMsg
}
}
}

// Disqualify a node
func (proc *localDKGProcessor) Disqualify(node int, log string) {
}

// FlagMisbehavior flags a node for misbehaviour
func (proc *localDKGProcessor) FlagMisbehavior(node int, log string) {
}

// dkgRunChan simulates processing incoming messages by a node
// it assumes proc.dkg is already running
func dkgRunChan(proc *localDKGProcessor, sync *sync.WaitGroup, phase int) {
for {
select {
case newMsg := <-proc.chans[proc.current]:
var err error
if newMsg.channel == private {
err = proc.dkg.HandlePrivateMsg(newMsg.orig, newMsg.data)
} else {
err = proc.dkg.HandleBroadcastMsg(newMsg.orig, newMsg.data)
}
if err != nil {
log.Fatal().Err(err).Msg("failed to receive DKG mst")
}
// if timeout, stop and finalize
case <-time.After(1 * time.Second):
switch phase {
case 0:
err := proc.dkg.NextTimeout()
if err != nil {
log.Fatal().Err(err).Msg("failed to wait for next timeout")
}
case 1:
err := proc.dkg.NextTimeout()
if err != nil {
log.Fatal().Err(err).Msg("failed to wait for next timeout")
}
case 2:
privkey, pubgroupkey, pubkeys, err := proc.dkg.End()
if err != nil {
log.Fatal().Err(err).Msg("end dkg error should be nit")
}
if privkey == nil {
log.Fatal().Msg("privkey was nil")
}

proc.privkey = privkey
proc.pubgroupkey = pubgroupkey
proc.pubkeys = pubkeys
}
sync.Done()
return
}
}
}

// RunFastKG is an alternative to RunDKG that runs much faster by using a centralized threshold signature key generation.
func RunFastKG(n int, seed []byte) (model.DKGData, error) {
// RandomBeaconKG is centralized BLS threshold signature key generation.
func RandomBeaconKG(n int, seed []byte) (model.DKGData, error) {

if n == 1 {
sk, pk, pkGroup, err := thresholdSignKeyGenOneNode(seed)
if err != nil {
return model.DKGData{}, fmt.Errorf("fast KeyGen failed: %w", err)
return model.DKGData{}, fmt.Errorf("Beacon KeyGen failed: %w", err)
}

dkgData := model.DKGData{
Expand All @@ -219,7 +28,7 @@ func RunFastKG(n int, seed []byte) (model.DKGData, error) {
skShares, pkShares, pkGroup, err := crypto.BLSThresholdKeyGen(int(n),
signature.RandomBeaconThreshold(int(n)), seed)
if err != nil {
return model.DKGData{}, fmt.Errorf("fast KeyGen failed: %w", err)
return model.DKGData{}, fmt.Errorf("Beacon KeyGen failed: %w", err)
}

dkgData := model.DKGData{
Expand All @@ -231,7 +40,7 @@ func RunFastKG(n int, seed []byte) (model.DKGData, error) {
return dkgData, nil
}

// simulates DKG with one single node
// Beacon KG with one node
func thresholdSignKeyGenOneNode(seed []byte) ([]crypto.PrivateKey, []crypto.PublicKey, crypto.PublicKey, error) {
sk, err := crypto.GeneratePrivateKey(crypto.BLSBLS12381, seed)
if err != nil {
Expand Down
17 changes: 10 additions & 7 deletions cmd/bootstrap/dkg/dkg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@ import (
"github.com/onflow/flow-go/utils/unittest"
)

func TestRunDKG(t *testing.T) {
seedLen := crypto.SeedMinLenDKG
_, err := RunDKG(0, unittest.SeedFixtures(2, seedLen))
require.EqualError(t, err, "n needs to match the number of seeds (0 != 2)")
func TestBeaconKG(t *testing.T) {
seed := unittest.SeedFixture(2 * crypto.SeedMinLenDKG)

_, err = RunDKG(3, unittest.SeedFixtures(2, seedLen))
require.EqualError(t, err, "n needs to match the number of seeds (3 != 2)")
// n = 0
_, err := RandomBeaconKG(0, seed)
require.EqualError(t, err, "Beacon KeyGen failed: size should be between 2 and 254, got 0")

data, err := RunDKG(4, unittest.SeedFixtures(4, seedLen))
// should work for case n = 1
_, err = RandomBeaconKG(1, seed)
require.NoError(t, err)

// n = 4
data, err := RandomBeaconKG(4, seed)
require.NoError(t, err)
require.Len(t, data.PrivKeyShares, 4)
require.Len(t, data.PubKeyShares, 4)
}
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,7 @@ func TestCombinedVoteProcessorV2_BuildVerifyQC(t *testing.T) {
epochLookup.On("EpochForViewWithFallback", view).Return(epochCounter, nil)

// all committee members run DKG
dkgData, err := bootstrapDKG.RunFastKG(11, unittest.RandomBytes(32))
dkgData, err := bootstrapDKG.RandomBeaconKG(11, unittest.RandomBytes(32))
require.NoError(t, err)

// signers hold objects that are created with private key and can sign votes and proposals
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,7 @@ func TestCombinedVoteProcessorV3_BuildVerifyQC(t *testing.T) {
view := uint64(20)
epochLookup.On("EpochForViewWithFallback", view).Return(epochCounter, nil)

dkgData, err := bootstrapDKG.RunFastKG(11, unittest.RandomBytes(32))
dkgData, err := bootstrapDKG.RandomBeaconKG(11, unittest.RandomBytes(32))
require.NoError(t, err)

// signers hold objects that are created with private key and can sign votes and proposals
Expand Down
2 changes: 1 addition & 1 deletion consensus/integration/nodes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ func createConsensusIdentities(t *testing.T, n int) *run.ParticipantData {

// completeConsensusIdentities runs KG process and fills nodeInfos with missing random beacon keys
func completeConsensusIdentities(t *testing.T, nodeInfos []bootstrap.NodeInfo) *run.ParticipantData {
dkgData, err := bootstrapDKG.RunFastKG(len(nodeInfos), unittest.RandomBytes(48))
dkgData, err := bootstrapDKG.RandomBeaconKG(len(nodeInfos), unittest.RandomBytes(48))
require.NoError(t, err)

participantData := &run.ParticipantData{
Expand Down
Loading