Skip to content

Commit

Permalink
DKG messages between participants are now signed and verified
Browse files Browse the repository at this point in the history
  • Loading branch information
CluEleSsUK committed May 2, 2023
1 parent 447a60c commit b88bbb7
Show file tree
Hide file tree
Showing 6 changed files with 527 additions and 734 deletions.
6 changes: 3 additions & 3 deletions core/drand_daemon_control.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ func (dd *DrandDaemon) WaitExit() chan bool {
return dd.exitCh
}

func (dd *DrandDaemon) Migrate(context context.Context, _ *drand.Empty) (*drand.Empty, error) {
func (dd *DrandDaemon) Migrate(ctx context.Context, _ *drand.Empty) (*drand.Empty, error) {
for beaconID, bp := range dd.beaconProcesses {
dd.log.Debugw("Migrating DKG from group file...", "beaconID", beaconID)

Expand All @@ -296,8 +296,8 @@ func (dd *DrandDaemon) Migrate(context context.Context, _ *drand.Empty) (*drand.

// then stop and start the beacon process to load the new DKG
// state and start listening for messages correctly
bp.StopBeacon(context)
_, err = dd.LoadBeaconFromStore(context, beaconID, bp.store)
bp.StopBeacon(ctx)
_, err = dd.LoadBeaconFromStore(ctx, beaconID, bp.store)
if err != nil {
return nil, err
}
Expand Down
63 changes: 44 additions & 19 deletions dkg/actions_active.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ func (d *DKGProcess) StartNetwork(ctx context.Context, options *drand.FirstPropo
Joining: options.Joining,
}

metadata, err := d.signMessage(beaconID, "StartNetwork", &terms)
if err != nil {
return nil, err
}
terms.Metadata = metadata

// apply our enriched DKG payload onto the current DKG state to create a new state
nextState, err := currentState.Proposing(me, &terms)
if err != nil {
Expand All @@ -87,7 +93,7 @@ func (d *DKGProcess) StartNetwork(ctx context.Context, options *drand.FirstPropo
// if there's an error sending to a party or saving the state, attempt a rollback by issuing an abort
rollback := func(err error) {
d.log.Errorw("there was an error starting the network. Attempting rollback", "beaconID", beaconID, "error", err)
_ = d.attemptAbort(ctx, me, nextState.Joining, beaconID)
_ = d.attemptAbort(ctx, me, nextState.Joining, beaconID, &terms)
}

return responseOrError(rollbackOnError(sendProposalAndStoreNextState, rollback))
Expand All @@ -107,17 +113,21 @@ func (d *DKGProcess) attemptAbort(
me *drand.Participant,
participants []*drand.Participant,
beaconID string,
proposal *drand.ProposalTerms,
) error {
ctx, span := metrics.NewSpan(ctx, "dkg.attemptAbort")
defer span.End()

return d.network.Send(ctx, me, participants, func(ctx context.Context, client net.DKGClient, peer net.Peer) (*drand.EmptyResponse, error) {
metadata, err := d.signMessage(beaconID, "StartAbort", proposal)
if err != nil {
return nil, err
}
return client.Abort(
ctx,
peer,
&drand.AbortDKG{Metadata: &drand.DKGMetadata{
BeaconID: beaconID,
}})
&drand.AbortDKG{Metadata: metadata},
)
})
}

Expand Down Expand Up @@ -168,7 +178,14 @@ func (d *DKGProcess) StartProposal(ctx context.Context, options *drand.ProposalO
me,
util.Concat(nextState.Joining, nextState.Remaining),
func(ctx context.Context, client net.DKGClient, peer net.Peer) (*drand.EmptyResponse, error) {
return client.Propose(ctx, peer, &terms)
metadata, err := d.signMessage(beaconID, "StartProposal", &terms)
//nolint:govet // the copied lock isn't used, it's just magic protobuf and the race checker complains if we don't copy the terms
t := terms
if err != nil {
return nil, err
}
t.Metadata = metadata
return client.Propose(ctx, peer, &t)
},
)
if err != nil {
Expand Down Expand Up @@ -200,7 +217,7 @@ func (d *DKGProcess) StartProposal(ctx context.Context, options *drand.ProposalO
rollback := func(err error) {
allParticipants := util.Concat(nextState.Joining, nextState.Remaining, nextState.Leaving)
d.log.Errorw("There was an error proposing a DKG", "err", err, "beaconID", beaconID)
_ = d.attemptAbort(ctx, me, allParticipants, beaconID)
_ = d.attemptAbort(ctx, me, allParticipants, beaconID, &terms)
}

return responseOrError(rollbackOnError(sendProposalToAllAndStoreState, rollback))
Expand Down Expand Up @@ -233,7 +250,7 @@ func (d *DKGProcess) StartAbort(ctx context.Context, options *drand.AbortOptions
}

allParticipants := util.Concat(nextState.Joining, nextState.Remaining, nextState.Leaving)
if err := d.attemptAbort(ctx, me, allParticipants, beaconID); err != nil {
if err := d.attemptAbort(ctx, me, allParticipants, beaconID, termsFromState(current)); err != nil {
return nil, err
}

Expand Down Expand Up @@ -265,11 +282,14 @@ func (d *DKGProcess) StartExecute(ctx context.Context, options *drand.ExecutionO
me,
allParticipants,
func(ctx context.Context, client net.DKGClient, peer net.Peer) (*drand.EmptyResponse, error) {
metadata, err := d.signMessage(beaconID, "StartExecute", termsFromState(nextState))
if err != nil {
return nil, err
}

return client.Execute(ctx, peer, &drand.StartExecution{
Metadata: &drand.DKGMetadata{
BeaconID: beaconID,
}},
)
Metadata: metadata,
})
},
)
}
Expand Down Expand Up @@ -339,14 +359,17 @@ func (d *DKGProcess) StartAccept(ctx context.Context, options *drand.AcceptOptio
}

callback := func(me *drand.Participant, nextState *DBState) error {
_, err := d.internalClient.Accept(
metadata, err := d.signMessage(beaconID, "StartAccept", termsFromState(nextState))
if err != nil {
return err
}
_, err = d.internalClient.Accept(
ctx,
util.ToPeer(nextState.Leader),

&drand.AcceptProposal{
Acceptor: me,
Metadata: &drand.DKGMetadata{
BeaconID: beaconID,
},
Metadata: metadata,
})
return err
}
Expand All @@ -370,13 +393,15 @@ func (d *DKGProcess) StartReject(ctx context.Context, options *drand.RejectOptio
}

callback := func(me *drand.Participant, nextState *DBState) error {
_, err := d.internalClient.Reject(ctx,
metadata, err := d.signMessage(beaconID, "StartReject", termsFromState(nextState))
if err != nil {
return err
}
_, err = d.internalClient.Reject(ctx,
util.ToPeer(nextState.Leader),
&drand.RejectProposal{
Rejector: me,
Metadata: &drand.DKGMetadata{
BeaconID: beaconID,
},
Metadata: metadata,
})
return err
}
Expand Down
30 changes: 30 additions & 0 deletions dkg/actions_passive.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ func (d *DKGProcess) Propose(ctx context.Context, proposal *drand.ProposalTerms)
_, span := metrics.NewSpan(ctx, "dkg.Propose")
defer span.End()

if proposal.Epoch == 1 {
err := d.verifyMessage("StartNetwork", proposal.Metadata, proposal)
if err != nil {
return nil, err
}
} else {
err := d.verifyMessage("StartProposal", proposal.Metadata, proposal)
if err != nil {
return nil, err
}
}

err := d.executeAction("DKG proposal", proposal.BeaconID, func(me *drand.Participant, current *DBState) (*DBState, error) {
// strictly speaking, we don't actually _know_ this proposal came from the leader here
// it will have to be verified by signing later
Expand All @@ -25,22 +37,32 @@ func (d *DKGProcess) Propose(ctx context.Context, proposal *drand.ProposalTerms)
return responseOrError(err)
}

//nolint:dupl // it's similar to Reject, but not the same
func (d *DKGProcess) Accept(ctx context.Context, acceptance *drand.AcceptProposal) (*drand.EmptyResponse, error) {
_, span := metrics.NewSpan(ctx, "dkg.Accept")
defer span.End()

err := d.executeAction("DKG acceptance", acceptance.Metadata.BeaconID, func(me *drand.Participant, current *DBState) (*DBState, error) {
err := d.verifyMessage("StartAccept", acceptance.Metadata, termsFromState(current))
if err != nil {
return nil, err
}
return current.ReceivedAcceptance(me, acceptance.Acceptor)
})

return responseOrError(err)
}

//nolint:dupl // it's similar to Accept, but not the same
func (d *DKGProcess) Reject(ctx context.Context, rejection *drand.RejectProposal) (*drand.EmptyResponse, error) {
_, span := metrics.NewSpan(ctx, "dkg.Reject")
defer span.End()

err := d.executeAction("DKG rejection", rejection.Metadata.BeaconID, func(me *drand.Participant, current *DBState) (*DBState, error) {
err := d.verifyMessage("StartReject", rejection.Metadata, termsFromState(current))
if err != nil {
return nil, err
}
return current.ReceivedRejection(me, rejection.Rejector)
})

Expand All @@ -52,6 +74,10 @@ func (d *DKGProcess) Abort(ctx context.Context, abort *drand.AbortDKG) (*drand.E
defer span.End()

err := d.executeAction("abort DKG", abort.Metadata.BeaconID, func(_ *drand.Participant, current *DBState) (*DBState, error) {
err := d.verifyMessage("StartAbort", abort.Metadata, termsFromState(current))
if err != nil {
return nil, err
}
return current.Aborted()
})

Expand All @@ -65,6 +91,10 @@ func (d *DKGProcess) Execute(ctx context.Context, kickoff *drand.StartExecution)
beaconID := kickoff.Metadata.BeaconID

err := d.executeAction("DKG execution", beaconID, func(me *drand.Participant, current *DBState) (*DBState, error) {
err := d.verifyMessage("StartExecute", kickoff.Metadata, termsFromState(current))
if err != nil {
return nil, err
}
return current.Executing(me)
})

Expand Down
84 changes: 84 additions & 0 deletions dkg/actions_signing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package dkg

import (
"errors"

"github.com/drand/drand/util"

"github.com/drand/drand/key"
"github.com/drand/drand/protobuf/drand"
"google.golang.org/protobuf/types/known/timestamppb"
)

func (d *DKGProcess) signMessage(beaconID, messageType string, proposal *drand.ProposalTerms) (*drand.DKGMetadata, error) {
kp, err := d.beaconIdentifier.KeypairFor(beaconID)
if err != nil {
return nil, err
}

sig, err := kp.Scheme().AuthScheme.Sign(kp.Key, messageForProto(proposal, messageType, beaconID))
if err != nil {
return nil, err
}
return &drand.DKGMetadata{
BeaconID: beaconID,
Address: kp.Public.Address(),
Signature: sig,
}, nil
}

func (d *DKGProcess) verifyMessage(messageType string, metadata *drand.DKGMetadata, proposal *drand.ProposalTerms) error {
participants := util.Concat(proposal.Remaining, proposal.Joining)
// signing is done before the metadata is attached, so we must remove it before we perform verification
proposal.Metadata = nil

// find the participant the signature is allegedly from
var p *drand.Participant
for _, participant := range participants {
if participant.Address == metadata.Address {
p = participant
break
}
}
if p == nil {
return errors.New("no such participant")
}

// get the scheme for the network so we can correctly unmarshal the public key
kp, err := d.beaconIdentifier.KeypairFor(metadata.BeaconID)
if err != nil {
return err
}

// use that scheme to verify the message came from the alleged author
pubPoint := kp.Scheme().KeyGroup.Point()
err = pubPoint.UnmarshalBinary(p.PubKey)
if err != nil {
return key.ErrInvalidKeyScheme
}
return kp.Scheme().AuthScheme.Verify(pubPoint, messageForProto(proposal, messageType, metadata.BeaconID), metadata.Signature)
}

func messageForProto(proposal *drand.ProposalTerms, messageType, beaconID string) []byte {
return []byte(proposal.String() + messageType + beaconID)
}

// used for determining the message that was signed for verifying packet authenticity
func termsFromState(state *DBState) *drand.ProposalTerms {
return &drand.ProposalTerms{
BeaconID: state.BeaconID,
Threshold: state.Threshold,
Epoch: state.Epoch,
SchemeID: state.SchemeID,
BeaconPeriodSeconds: uint32(state.BeaconPeriod.Seconds()),
CatchupPeriodSeconds: uint32(state.CatchupPeriod.Seconds()),
GenesisTime: timestamppb.New(state.GenesisTime),
GenesisSeed: state.GenesisSeed,
TransitionTime: timestamppb.New(state.TransitionTime),
Timeout: timestamppb.New(state.Timeout),
Leader: state.Leader,
Joining: state.Joining,
Remaining: state.Remaining,
Leaving: state.Leaving,
}
}
Loading

0 comments on commit b88bbb7

Please sign in to comment.