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 15, 2023
1 parent 66dea3c commit 3e05fc0
Show file tree
Hide file tree
Showing 6 changed files with 528 additions and 736 deletions.
63 changes: 44 additions & 19 deletions internal/dkg/actions_active.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ func (d *Process) StartNetwork(ctx context.Context, options *drand.FirstProposal
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 @@ -85,7 +91,7 @@ func (d *Process) StartNetwork(ctx context.Context, options *drand.FirstProposal
// 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 @@ -105,17 +111,21 @@ func (d *Process) 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 @@ -166,7 +176,14 @@ func (d *Process) StartProposal(ctx context.Context, options *drand.ProposalOpti
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 @@ -198,7 +215,7 @@ func (d *Process) StartProposal(ctx context.Context, options *drand.ProposalOpti
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 @@ -231,7 +248,7 @@ func (d *Process) 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 @@ -263,11 +280,14 @@ func (d *Process) StartExecute(ctx context.Context, options *drand.ExecutionOpti
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 @@ -337,14 +357,17 @@ func (d *Process) StartAccept(ctx context.Context, options *drand.AcceptOptions)
}

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 @@ -368,13 +391,15 @@ func (d *Process) StartReject(ctx context.Context, options *drand.RejectOptions)
}

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 internal/dkg/actions_passive.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ func (d *Process) 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,21 +37,31 @@ func (d *Process) Propose(ctx context.Context, proposal *drand.ProposalTerms) (*
return responseOrError(err)
}

//nolint:dupl // it's similar to Reject, but not the same
func (d *Process) 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 *Process) 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 @@ -51,6 +73,10 @@ func (d *Process) Abort(ctx context.Context, abort *drand.AbortDKG) (*drand.Empt
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 @@ -63,6 +89,10 @@ func (d *Process) 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
83 changes: 83 additions & 0 deletions internal/dkg/actions_signing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package dkg

import (
"errors"
"github.com/drand/drand/common/key"
"github.com/drand/drand/internal/util"

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

func (d *Process) 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 *Process) 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,
}
}
10 changes: 5 additions & 5 deletions internal/test/docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ services:
command: start --verbose --control 8888 --private-listen 0.0.0.0:8080 --public-listen 0.0.0.0:8081 --metrics 0.0.0.0:8083 --tls-disable true
environment:
DRAND_PUBLIC_ADDRESS: "drand_0:8080"
DRAND_SHARE_SECRET: "thisisthesecretweshouldsettorundkgprocessonthenodes"
DRAND_SHARE_SECRET: "thisisthesecretweshouldsettorundkg.Processonthenodes"

drand_1:
container_name: drand_1
Expand All @@ -33,7 +33,7 @@ services:
command: start --verbose --control 8888 --private-listen 0.0.0.0:8180 --public-listen 0.0.0.0:8181 --metrics 0.0.0.0:8183 --tls-disable true
environment:
DRAND_PUBLIC_ADDRESS: "drand_1:8180"
DRAND_SHARE_SECRET: "thisisthesecretweshouldsettorundkgprocessonthenodes"
DRAND_SHARE_SECRET: "thisisthesecretweshouldsettorundkg.Processonthenodes"

drand_2:
container_name: drand_2
Expand All @@ -50,7 +50,7 @@ services:
command: start --verbose --control 8888 --private-listen 0.0.0.0:8280 --public-listen 0.0.0.0:8281 --metrics 0.0.0.0:8283 --tls-disable true
environment:
DRAND_PUBLIC_ADDRESS: "drand_2:8280"
DRAND_SHARE_SECRET: "thisisthesecretweshouldsettorundkgprocessonthenodes"
DRAND_SHARE_SECRET: "thisisthesecretweshouldsettorundkg.Processonthenodes"

drand_3:
container_name: drand_3
Expand All @@ -67,7 +67,7 @@ services:
command: start --verbose --control 8888 --private-listen 0.0.0.0:8380 --public-listen 0.0.0.0:8381 --metrics 0.0.0.0:8383 --tls-disable true
environment:
DRAND_PUBLIC_ADDRESS: "drand_3:8380"
DRAND_SHARE_SECRET: "thisisthesecretweshouldsettorundkgprocessonthenodes"
DRAND_SHARE_SECRET: "thisisthesecretweshouldsettorundkg.Processonthenodes"

drand_4:
container_name: drand_4
Expand All @@ -84,7 +84,7 @@ services:
command: start --verbose --control 8888 --private-listen 0.0.0.0:8480 --public-listen 0.0.0.0:8481 --metrics 0.0.0.0:8483 --tls-disable true
environment:
DRAND_PUBLIC_ADDRESS: "drand_4:8480"
DRAND_SHARE_SECRET: "thisisthesecretweshouldsettorundkgprocessonthenodes"
DRAND_SHARE_SECRET: "thisisthesecretweshouldsettorundkg.Processonthenodes"

drand_client:
container_name: drand_client
Expand Down
Loading

0 comments on commit 3e05fc0

Please sign in to comment.