Skip to content

Commit

Permalink
Support certifier
Browse files Browse the repository at this point in the history
  • Loading branch information
poszu committed Nov 1, 2023
1 parent 14da311 commit 9b97180
Show file tree
Hide file tree
Showing 8 changed files with 566 additions and 152 deletions.
23 changes: 21 additions & 2 deletions registration/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package registration

import "time"
import (
"time"

"go.uber.org/zap/zapcore"
)

func DefaultConfig() Config {
return Config{
Expand All @@ -11,9 +15,24 @@ func DefaultConfig() Config {
}

type Config struct {
PowDifficulty uint `long:"pow-difficulty" description:"PoW difficulty (in the number of leading zero bits)"`
// FIXME: remove depreacated PoW
PowDifficulty uint `long:"pow-difficulty" description:"(DEPRECATED) PoW difficulty (in the number of leading zero bits)"`

MaxRoundMembers int `long:"max-round-members" description:"the maximum number of members in a round"`
MaxSubmitBatchSize int `long:"max-submit-batch-size" description:"The maximum number of challenges to submit in a single batch"`
SubmitFlushInterval time.Duration `long:"submit-flush-interval" description:"The interval between flushes of the submit queue"`

Certifier *CertifierConfig
}

type CertifierConfig struct {
URL string `long:"certifier-url" description:"The URL of the certifier service"`
PubKey []byte `long:"certifier-pubkey" description:"The public key of the certifier service"`
}

// implement zap.ObjectMarshaler interface.
func (c CertifierConfig) MarshalLogObject(enc zapcore.ObjectEncoder) error {
enc.AddString("url", c.URL)
enc.AddBinary("pubkey", c.PubKey)
return nil
}
40 changes: 33 additions & 7 deletions registration/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ type roundConfig interface {
RoundEnd(genesis time.Time, epoch uint) time.Time
}

var ErrTooLateToRegister = errors.New("too late to register for the desired round")
var (
ErrInvalidCertificate = errors.New("invalid certificate")
ErrTooLateToRegister = errors.New("too late to register for the desired round")
)

// Registration orchestrates rounds functionality
// It is responsible for:
Expand Down Expand Up @@ -127,6 +130,13 @@ func New(
r.powVerifiers = powVerifiers{current: options.powVerifier}
}

if r.cfg.Certifier != nil && r.cfg.Certifier.PubKey != nil {
logging.FromContext(ctx).Info("configured certifier", zap.Inline(r.cfg.Certifier))
} else {
logging.FromContext(ctx).Info("disabled certificate checking")
r.cfg.Certifier = nil
}

epoch := r.roundCfg.OpenRoundId(r.genesis, time.Now())
round, err := newRound(epoch, r.dbdir, r.newRoundOpts()...)
if err != nil {
Expand All @@ -138,6 +148,10 @@ func New(
return r, nil
}

func (r *Registration) CertifierInfo() *CertifierConfig {
return r.cfg.Certifier
}

func (r *Registration) Pubkey() ed25519.PublicKey {
return r.privKey.Public().(ed25519.PublicKey)
}
Expand Down Expand Up @@ -287,18 +301,30 @@ func (r *Registration) newRoundOpts() []newRoundOptionFunc {
func (r *Registration) Submit(
ctx context.Context,
challenge, nodeID []byte,
// TODO: remove deprecated PoW
nonce uint64,
powParams PowParams,
certificate []byte,
deadline time.Time,
) (epoch uint, roundEnd time.Time, err error) {
logger := logging.FromContext(ctx)

err = r.powVerifiers.VerifyWithParams(challenge, nodeID, nonce, powParams)
if err != nil {
logger.Debug("challenge verification failed", zap.Error(err))
return 0, time.Time{}, err
// Verify if the node is allowed to register.
// Support both a certificate and PoW while
// the certificate path is being stabilized.
if r.cfg.Certifier != nil && certificate != nil {
if !ed25519.Verify(r.cfg.Certifier.PubKey, nodeID, certificate) {
return 0, time.Time{}, ErrInvalidCertificate
}
} else {
// FIXME: PoW is deprecated
// Remove once certificate path is stabilized and mandatory.
err := r.powVerifiers.VerifyWithParams(challenge, nodeID, nonce, powParams)
if err != nil {
logger.Debug("PoW verification failed", zap.Error(err))
return 0, time.Time{}, err
}
logger.Debug("verified PoW", zap.String("node_id", hex.EncodeToString(nodeID)))
}
logger.Debug("verified challenge", zap.String("node_id", hex.EncodeToString(nodeID)))

r.openRoundMutex.RLock()
epoch = r.openRound.epoch
Expand Down
110 changes: 108 additions & 2 deletions registration/registration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package registration_test
import (
"bytes"
"context"
"crypto/ed25519"
"testing"
"time"

Expand Down Expand Up @@ -57,12 +58,20 @@ func TestSubmitIdempotence(t *testing.T) {
eg.Go(func() error { return r.Run(ctx) })

// Submit challenge
epoch, _, err := r.Submit(context.Background(), challenge, nodeID, nonce, registration.PowParams{}, time.Time{})
epoch, _, err := r.Submit(
context.Background(),
challenge,
nodeID,
nonce,
registration.PowParams{},
nil,
time.Time{},
)
req.NoError(err)
req.Equal(uint(0), epoch)

// Try again - it should return the same result
epoch, _, err = r.Submit(context.Background(), challenge, nodeID, nonce, registration.PowParams{}, time.Time{})
epoch, _, err = r.Submit(context.Background(), challenge, nodeID, nonce, registration.PowParams{}, nil, time.Time{})
req.NoError(err)
req.Equal(uint(0), epoch)

Expand Down Expand Up @@ -270,3 +279,100 @@ func TestRecoveringRoundInProgress(t *testing.T) {
)
req.NoError(r.Run(ctx))
}

func Test_GetCertifierInfo(t *testing.T) {
certifier := &registration.CertifierConfig{
PubKey: []byte("pubkey"),
URL: "http://the-certifier.org",
}

r, err := registration.New(
context.Background(),
time.Now(),
t.TempDir(),
nil,
server.DefaultRoundConfig(),
registration.WithConfig(registration.Config{
MaxRoundMembers: 10,
Certifier: certifier,
}),
)
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, r.Close()) })
require.Equal(t, r.CertifierInfo(), certifier)
}

func Test_CheckCertificate(t *testing.T) {
challenge := []byte("challenge")
nodeID := []byte("nodeID00nodeID00nodeID00nodeID00")

t.Run("certification check disabled (default config)", func(t *testing.T) {
powVerifier := mocks.NewMockPowVerifier(gomock.NewController(t))
powVerifier.EXPECT().Params().Return(registration.PowParams{}).AnyTimes()
r, err := registration.New(
context.Background(),
time.Now(),
t.TempDir(),
nil,
server.DefaultRoundConfig(),
registration.WithPowVerifier(powVerifier),
)
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, r.Close()) })

// missing certificate - fallback to PoW
powVerifier.EXPECT().Verify(challenge, nodeID, uint64(5)).Return(nil)
_, _, err = r.Submit(context.Background(), challenge, nodeID, 5, registration.PowParams{}, nil, time.Time{})
require.NoError(t, err)

// passed certificate - still fallback to PoW
powVerifier.EXPECT().Verify(challenge, nodeID, uint64(7)).Return(nil)
_, _, err = r.Submit(
context.Background(),
challenge,
nodeID,
7,
registration.PowParams{},
[]byte{1, 2, 3, 4},
time.Time{},
)
require.NoError(t, err)
})
t.Run("certification check enabled", func(t *testing.T) {
pub, private, err := ed25519.GenerateKey(nil)
require.NoError(t, err)
powVerifier := mocks.NewMockPowVerifier(gomock.NewController(t))

r, err := registration.New(
context.Background(),
time.Now(),
t.TempDir(),
nil,
server.DefaultRoundConfig(),
registration.WithPowVerifier(powVerifier),
registration.WithConfig(registration.Config{
MaxRoundMembers: 10,
Certifier: &registration.CertifierConfig{
PubKey: pub,
},
}),
)
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, r.Close()) })

// missing certificate - fallback to PoW
powVerifier.EXPECT().Params().Return(registration.PowParams{}).AnyTimes()
powVerifier.EXPECT().Verify(challenge, nodeID, uint64(7)).Return(nil)
_, _, err = r.Submit(context.Background(), challenge, nodeID, 7, r.PowParams(), nil, time.Time{})
require.NoError(t, err)

// valid certificate
signature := ed25519.Sign(private, nodeID)
_, _, err = r.Submit(context.Background(), challenge, nodeID, 0, r.PowParams(), signature, time.Time{})
require.NoError(t, err)

// invalid certificate
_, _, err = r.Submit(context.Background(), challenge, nodeID, 0, r.PowParams(), []byte{1, 2, 3, 4}, time.Time{})
require.ErrorIs(t, err, registration.ErrInvalidCertificate)
})
}
Loading

0 comments on commit 9b97180

Please sign in to comment.