From 553ec5d6eaa23411325ead4ebacac99cbf21d7c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Mon, 30 Oct 2023 11:04:29 +0100 Subject: [PATCH 1/8] Support certifier --- registration/config.go | 23 +- registration/registration.go | 40 +- registration/registration_test.go | 110 ++++- release/proto/go/rpc/api/v1/api.pb.go | 405 ++++++++++++------ .../openapiv2/rpc/api/v1/api.swagger.json | 32 +- rpc/api/v1/api.proto | 15 + rpc/rpcserver.go | 22 +- server/server_test.go | 159 +++++-- 8 files changed, 625 insertions(+), 181 deletions(-) diff --git a/registration/config.go b/registration/config.go index 2eaa1e77..92fd2df5 100644 --- a/registration/config.go +++ b/registration/config.go @@ -1,6 +1,10 @@ package registration -import "time" +import ( + "time" + + "go.uber.org/zap/zapcore" +) func DefaultConfig() Config { return Config{ @@ -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 } diff --git a/registration/registration.go b/registration/registration.go index f160806b..0c48dcf8 100644 --- a/registration/registration.go +++ b/registration/registration.go @@ -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: @@ -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 { @@ -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) } @@ -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 a 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 diff --git a/registration/registration_test.go b/registration/registration_test.go index d0c45bc7..baf15b37 100644 --- a/registration/registration_test.go +++ b/registration/registration_test.go @@ -3,6 +3,7 @@ package registration_test import ( "bytes" "context" + "crypto/ed25519" "testing" "time" @@ -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) @@ -270,3 +279,100 @@ func TestRecoveringRoundInProgress(t *testing.T) { ) req.NoError(r.Run(ctx)) } + +func Test_GetCertifierInfo(t *testing.T) { + certifier := ®istration.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: ®istration.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) + }) +} diff --git a/release/proto/go/rpc/api/v1/api.pb.go b/release/proto/go/rpc/api/v1/api.pb.go index be83931a..1a0573d7 100644 --- a/release/proto/go/rpc/api/v1/api.pb.go +++ b/release/proto/go/rpc/api/v1/api.pb.go @@ -171,8 +171,10 @@ type SubmitRequest struct { unknownFields protoimpl.UnknownFields // Proof of Work nonce + // deprecated - use certificate instead Nonce uint64 `protobuf:"varint,1,opt,name=nonce,proto3" json:"nonce,omitempty"` // Proof of Work parameters that were used to generate the nonce + // deprecated - use certificate instead PowParams *PowParams `protobuf:"bytes,2,opt,name=pow_params,json=powParams,proto3" json:"pow_params,omitempty"` // Prefix to be added to challenge for signature verification Prefix []byte `protobuf:"bytes,3,opt,name=prefix,proto3" json:"prefix,omitempty"` @@ -185,7 +187,8 @@ type SubmitRequest struct { // The time by which the proof is needed. // If the currently open round will end after this time // and the proof cannot be generated by this time, the request will be rejected. - Deadline *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=deadline,proto3" json:"deadline,omitempty"` + Deadline *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=deadline,proto3" json:"deadline,omitempty"` + Certificate *SubmitRequest_Certificate `protobuf:"bytes,8,opt,name=certificate,proto3" json:"certificate,omitempty"` } func (x *SubmitRequest) Reset() { @@ -269,6 +272,13 @@ func (x *SubmitRequest) GetDeadline() *timestamppb.Timestamp { return nil } +func (x *SubmitRequest) GetCertificate() *SubmitRequest_Certificate { + if x != nil { + return x.Certificate + } + return nil +} + type SubmitResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -367,9 +377,10 @@ type InfoResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - ServicePubkey []byte `protobuf:"bytes,3,opt,name=service_pubkey,json=servicePubkey,proto3" json:"service_pubkey,omitempty"` - PhaseShift *durationpb.Duration `protobuf:"bytes,4,opt,name=phase_shift,json=phaseShift,proto3" json:"phase_shift,omitempty"` - CycleGap *durationpb.Duration `protobuf:"bytes,5,opt,name=cycle_gap,json=cycleGap,proto3" json:"cycle_gap,omitempty"` + ServicePubkey []byte `protobuf:"bytes,3,opt,name=service_pubkey,json=servicePubkey,proto3" json:"service_pubkey,omitempty"` + PhaseShift *durationpb.Duration `protobuf:"bytes,4,opt,name=phase_shift,json=phaseShift,proto3" json:"phase_shift,omitempty"` + CycleGap *durationpb.Duration `protobuf:"bytes,5,opt,name=cycle_gap,json=cycleGap,proto3" json:"cycle_gap,omitempty"` + Certifier *InfoResponse_Cerifier `protobuf:"bytes,6,opt,name=certifier,proto3" json:"certifier,omitempty"` } func (x *InfoResponse) Reset() { @@ -425,6 +436,13 @@ func (x *InfoResponse) GetCycleGap() *durationpb.Duration { return nil } +func (x *InfoResponse) GetCertifier() *InfoResponse_Cerifier { + if x != nil { + return x.Certifier + } + return nil +} + type MembershipProof struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -716,6 +734,110 @@ func (x *ProofResponse) GetPubkey() []byte { return nil } +type SubmitRequest_Certificate struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The certifier's signature of the pubkey + // confirming that the pubkey is verified. + Signature []byte `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` +} + +func (x *SubmitRequest_Certificate) Reset() { + *x = SubmitRequest_Certificate{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_api_v1_api_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SubmitRequest_Certificate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubmitRequest_Certificate) ProtoMessage() {} + +func (x *SubmitRequest_Certificate) ProtoReflect() protoreflect.Message { + mi := &file_rpc_api_v1_api_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubmitRequest_Certificate.ProtoReflect.Descriptor instead. +func (*SubmitRequest_Certificate) Descriptor() ([]byte, []int) { + return file_rpc_api_v1_api_proto_rawDescGZIP(), []int{3, 0} +} + +func (x *SubmitRequest_Certificate) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +type InfoResponse_Cerifier struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + Pubkey []byte `protobuf:"bytes,2,opt,name=pubkey,proto3" json:"pubkey,omitempty"` +} + +func (x *InfoResponse_Cerifier) Reset() { + *x = InfoResponse_Cerifier{} + if protoimpl.UnsafeEnabled { + mi := &file_rpc_api_v1_api_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *InfoResponse_Cerifier) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InfoResponse_Cerifier) ProtoMessage() {} + +func (x *InfoResponse_Cerifier) ProtoReflect() protoreflect.Message { + mi := &file_rpc_api_v1_api_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InfoResponse_Cerifier.ProtoReflect.Descriptor instead. +func (*InfoResponse_Cerifier) Descriptor() ([]byte, []int) { + return file_rpc_api_v1_api_proto_rawDescGZIP(), []int{6, 0} +} + +func (x *InfoResponse_Cerifier) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *InfoResponse_Cerifier) GetPubkey() []byte { + if x != nil { + return x.Pubkey + } + return nil +} + var File_rpc_api_v1_api_proto protoreflect.FileDescriptor var file_rpc_api_v1_api_proto_rawDesc = []byte{ @@ -737,7 +859,7 @@ var file_rpc_api_v1_api_proto_rawDesc = []byte{ 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x0a, 0x70, 0x6f, 0x77, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x77, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, - 0x52, 0x09, 0x70, 0x6f, 0x77, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x22, 0xff, 0x01, 0x0a, 0x0d, + 0x52, 0x09, 0x70, 0x6f, 0x77, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x22, 0xf5, 0x02, 0x0a, 0x0d, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x0a, 0x70, 0x6f, 0x77, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, @@ -753,87 +875,102 @@ var file_rpc_api_v1_api_proto_rawDesc = []byte{ 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x36, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x22, 0x63, 0x0a, - 0x0e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x36, 0x0a, 0x09, 0x72, 0x6f, - 0x75, 0x6e, 0x64, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x45, - 0x6e, 0x64, 0x22, 0x0d, 0x0a, 0x0b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0xb5, 0x01, 0x0a, 0x0c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x70, 0x75, - 0x62, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x3a, 0x0a, 0x0b, 0x70, 0x68, 0x61, - 0x73, 0x65, 0x5f, 0x73, 0x68, 0x69, 0x66, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, + 0x61, 0x6d, 0x70, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x47, 0x0a, + 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x1a, 0x2b, 0x0a, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x22, 0x63, 0x0a, 0x0e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, + 0x12, 0x36, 0x0a, 0x09, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, + 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x45, 0x6e, 0x64, 0x22, 0x0d, 0x0a, 0x0b, 0x49, 0x6e, 0x66, 0x6f, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xac, 0x02, 0x0a, 0x0c, 0x49, 0x6e, 0x66, 0x6f, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, + 0x3a, 0x0a, 0x0b, 0x70, 0x68, 0x61, 0x73, 0x65, 0x5f, 0x73, 0x68, 0x69, 0x66, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x0a, 0x70, 0x68, 0x61, 0x73, 0x65, 0x53, 0x68, 0x69, 0x66, 0x74, 0x12, 0x36, 0x0a, 0x09, 0x63, + 0x79, 0x63, 0x6c, 0x65, 0x5f, 0x67, 0x61, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x70, 0x68, 0x61, 0x73, 0x65, - 0x53, 0x68, 0x69, 0x66, 0x74, 0x12, 0x36, 0x0a, 0x09, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x5f, 0x67, - 0x61, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x47, 0x61, 0x70, 0x4a, 0x04, 0x08, - 0x01, 0x10, 0x02, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0x51, 0x0a, 0x0f, 0x4d, 0x65, 0x6d, - 0x62, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x14, 0x0a, 0x05, - 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x69, 0x6e, 0x64, - 0x65, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, - 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0x67, 0x0a, 0x0b, - 0x4d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x12, 0x0a, 0x04, 0x72, - 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x12, - 0x23, 0x0a, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x6e, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x6e, 0x4c, 0x65, - 0x61, 0x76, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x5f, 0x6e, 0x6f, - 0x64, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x6f, 0x66, - 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x6c, 0x0a, 0x09, 0x50, 0x6f, 0x65, 0x74, 0x50, 0x72, 0x6f, - 0x6f, 0x66, 0x12, 0x2d, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x17, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4d, - 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x6f, - 0x66, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6c, - 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6c, 0x65, 0x61, - 0x76, 0x65, 0x73, 0x22, 0x29, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x22, 0x54, - 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x2b, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, - 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x65, 0x74, - 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x16, 0x0a, 0x06, - 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, - 0x62, 0x6b, 0x65, 0x79, 0x32, 0xf1, 0x02, 0x0a, 0x0b, 0x50, 0x6f, 0x65, 0x74, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x60, 0x0a, 0x09, 0x50, 0x6f, 0x77, 0x50, 0x61, 0x72, 0x61, 0x6d, - 0x73, 0x12, 0x1c, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, - 0x6f, 0x77, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1d, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x77, - 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x6f, 0x77, 0x5f, - 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x56, 0x0a, 0x06, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, - 0x12, 0x19, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, - 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x72, 0x70, - 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x3a, - 0x01, 0x2a, 0x22, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x12, 0x4b, - 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x17, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x18, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x66, - 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x0a, 0x12, 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x5b, 0x0a, 0x05, 0x50, - 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x18, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, - 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, - 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x6f, - 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x17, 0x12, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x2f, 0x7b, 0x72, - 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x7d, 0x42, 0xa3, 0x01, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, - 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x42, 0x08, 0x41, 0x70, 0x69, - 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x73, 0x68, 0x6f, 0x73, 0x2f, - 0x70, 0x6f, 0x65, 0x74, 0x2f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, - 0x3b, 0x61, 0x70, 0x69, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x52, 0x41, 0x58, 0xaa, 0x02, 0x0a, 0x52, - 0x70, 0x63, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0a, 0x52, 0x70, 0x63, 0x5c, - 0x41, 0x70, 0x69, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x16, 0x52, 0x70, 0x63, 0x5c, 0x41, 0x70, 0x69, - 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, - 0x02, 0x0c, 0x52, 0x70, 0x63, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x63, 0x79, 0x63, 0x6c, 0x65, + 0x47, 0x61, 0x70, 0x12, 0x3f, 0x0a, 0x09, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x43, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x09, 0x63, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x72, 0x1a, 0x34, 0x0a, 0x08, 0x43, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x72, + 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, + 0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, + 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0x51, 0x0a, 0x0f, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, + 0x73, 0x68, 0x69, 0x70, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, + 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, + 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x72, + 0x6f, 0x6f, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x0c, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0x67, 0x0a, 0x0b, 0x4d, 0x65, 0x72, + 0x6b, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x12, 0x23, 0x0a, 0x0d, + 0x70, 0x72, 0x6f, 0x76, 0x65, 0x6e, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x6e, 0x4c, 0x65, 0x61, 0x76, 0x65, + 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x4e, 0x6f, 0x64, + 0x65, 0x73, 0x22, 0x6c, 0x0a, 0x09, 0x50, 0x6f, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, + 0x2d, 0x0a, 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, + 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x72, 0x6b, + 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x18, + 0x0a, 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, + 0x07, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x61, 0x76, + 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, + 0x22, 0x29, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x22, 0x54, 0x0a, 0x0d, 0x50, + 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, + 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x70, + 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x65, 0x74, 0x50, 0x72, 0x6f, + 0x6f, 0x66, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, + 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, + 0x79, 0x32, 0xf1, 0x02, 0x0a, 0x0b, 0x50, 0x6f, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x60, 0x0a, 0x09, 0x50, 0x6f, 0x77, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x1c, + 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x77, 0x50, + 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, + 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x77, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x10, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x6f, 0x77, 0x5f, 0x70, 0x61, 0x72, + 0x61, 0x6d, 0x73, 0x12, 0x56, 0x0a, 0x06, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x12, 0x19, 0x2e, + 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x3a, 0x01, 0x2a, 0x22, + 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x12, 0x4b, 0x0a, 0x04, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x17, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, + 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, + 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, + 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x5b, 0x0a, 0x05, 0x50, 0x72, 0x6f, 0x6f, + 0x66, 0x12, 0x18, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, + 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x70, + 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, + 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x73, 0x2f, 0x7b, 0x72, 0x6f, 0x75, 0x6e, + 0x64, 0x5f, 0x69, 0x64, 0x7d, 0x42, 0xa3, 0x01, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x2e, 0x72, 0x70, + 0x63, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x42, 0x08, 0x41, 0x70, 0x69, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x73, 0x68, 0x6f, 0x73, 0x2f, 0x70, 0x6f, 0x65, + 0x74, 0x2f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x67, 0x6f, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x3b, 0x61, 0x70, + 0x69, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x52, 0x41, 0x58, 0xaa, 0x02, 0x0a, 0x52, 0x70, 0x63, 0x2e, + 0x41, 0x70, 0x69, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0a, 0x52, 0x70, 0x63, 0x5c, 0x41, 0x70, 0x69, + 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x16, 0x52, 0x70, 0x63, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x31, + 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0c, 0x52, + 0x70, 0x63, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -848,45 +985,49 @@ func file_rpc_api_v1_api_proto_rawDescGZIP() []byte { return file_rpc_api_v1_api_proto_rawDescData } -var file_rpc_api_v1_api_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_rpc_api_v1_api_proto_msgTypes = make([]protoimpl.MessageInfo, 14) var file_rpc_api_v1_api_proto_goTypes = []interface{}{ - (*PowParams)(nil), // 0: rpc.api.v1.PowParams - (*PowParamsRequest)(nil), // 1: rpc.api.v1.PowParamsRequest - (*PowParamsResponse)(nil), // 2: rpc.api.v1.PowParamsResponse - (*SubmitRequest)(nil), // 3: rpc.api.v1.SubmitRequest - (*SubmitResponse)(nil), // 4: rpc.api.v1.SubmitResponse - (*InfoRequest)(nil), // 5: rpc.api.v1.InfoRequest - (*InfoResponse)(nil), // 6: rpc.api.v1.InfoResponse - (*MembershipProof)(nil), // 7: rpc.api.v1.MembershipProof - (*MerkleProof)(nil), // 8: rpc.api.v1.MerkleProof - (*PoetProof)(nil), // 9: rpc.api.v1.PoetProof - (*ProofRequest)(nil), // 10: rpc.api.v1.ProofRequest - (*ProofResponse)(nil), // 11: rpc.api.v1.ProofResponse - (*timestamppb.Timestamp)(nil), // 12: google.protobuf.Timestamp - (*durationpb.Duration)(nil), // 13: google.protobuf.Duration + (*PowParams)(nil), // 0: rpc.api.v1.PowParams + (*PowParamsRequest)(nil), // 1: rpc.api.v1.PowParamsRequest + (*PowParamsResponse)(nil), // 2: rpc.api.v1.PowParamsResponse + (*SubmitRequest)(nil), // 3: rpc.api.v1.SubmitRequest + (*SubmitResponse)(nil), // 4: rpc.api.v1.SubmitResponse + (*InfoRequest)(nil), // 5: rpc.api.v1.InfoRequest + (*InfoResponse)(nil), // 6: rpc.api.v1.InfoResponse + (*MembershipProof)(nil), // 7: rpc.api.v1.MembershipProof + (*MerkleProof)(nil), // 8: rpc.api.v1.MerkleProof + (*PoetProof)(nil), // 9: rpc.api.v1.PoetProof + (*ProofRequest)(nil), // 10: rpc.api.v1.ProofRequest + (*ProofResponse)(nil), // 11: rpc.api.v1.ProofResponse + (*SubmitRequest_Certificate)(nil), // 12: rpc.api.v1.SubmitRequest.Certificate + (*InfoResponse_Cerifier)(nil), // 13: rpc.api.v1.InfoResponse.Cerifier + (*timestamppb.Timestamp)(nil), // 14: google.protobuf.Timestamp + (*durationpb.Duration)(nil), // 15: google.protobuf.Duration } var file_rpc_api_v1_api_proto_depIdxs = []int32{ 0, // 0: rpc.api.v1.PowParamsResponse.pow_params:type_name -> rpc.api.v1.PowParams 0, // 1: rpc.api.v1.SubmitRequest.pow_params:type_name -> rpc.api.v1.PowParams - 12, // 2: rpc.api.v1.SubmitRequest.deadline:type_name -> google.protobuf.Timestamp - 13, // 3: rpc.api.v1.SubmitResponse.round_end:type_name -> google.protobuf.Duration - 13, // 4: rpc.api.v1.InfoResponse.phase_shift:type_name -> google.protobuf.Duration - 13, // 5: rpc.api.v1.InfoResponse.cycle_gap:type_name -> google.protobuf.Duration - 8, // 6: rpc.api.v1.PoetProof.proof:type_name -> rpc.api.v1.MerkleProof - 9, // 7: rpc.api.v1.ProofResponse.proof:type_name -> rpc.api.v1.PoetProof - 1, // 8: rpc.api.v1.PoetService.PowParams:input_type -> rpc.api.v1.PowParamsRequest - 3, // 9: rpc.api.v1.PoetService.Submit:input_type -> rpc.api.v1.SubmitRequest - 5, // 10: rpc.api.v1.PoetService.Info:input_type -> rpc.api.v1.InfoRequest - 10, // 11: rpc.api.v1.PoetService.Proof:input_type -> rpc.api.v1.ProofRequest - 2, // 12: rpc.api.v1.PoetService.PowParams:output_type -> rpc.api.v1.PowParamsResponse - 4, // 13: rpc.api.v1.PoetService.Submit:output_type -> rpc.api.v1.SubmitResponse - 6, // 14: rpc.api.v1.PoetService.Info:output_type -> rpc.api.v1.InfoResponse - 11, // 15: rpc.api.v1.PoetService.Proof:output_type -> rpc.api.v1.ProofResponse - 12, // [12:16] is the sub-list for method output_type - 8, // [8:12] is the sub-list for method input_type - 8, // [8:8] is the sub-list for extension type_name - 8, // [8:8] is the sub-list for extension extendee - 0, // [0:8] is the sub-list for field type_name + 14, // 2: rpc.api.v1.SubmitRequest.deadline:type_name -> google.protobuf.Timestamp + 12, // 3: rpc.api.v1.SubmitRequest.certificate:type_name -> rpc.api.v1.SubmitRequest.Certificate + 15, // 4: rpc.api.v1.SubmitResponse.round_end:type_name -> google.protobuf.Duration + 15, // 5: rpc.api.v1.InfoResponse.phase_shift:type_name -> google.protobuf.Duration + 15, // 6: rpc.api.v1.InfoResponse.cycle_gap:type_name -> google.protobuf.Duration + 13, // 7: rpc.api.v1.InfoResponse.certifier:type_name -> rpc.api.v1.InfoResponse.Cerifier + 8, // 8: rpc.api.v1.PoetProof.proof:type_name -> rpc.api.v1.MerkleProof + 9, // 9: rpc.api.v1.ProofResponse.proof:type_name -> rpc.api.v1.PoetProof + 1, // 10: rpc.api.v1.PoetService.PowParams:input_type -> rpc.api.v1.PowParamsRequest + 3, // 11: rpc.api.v1.PoetService.Submit:input_type -> rpc.api.v1.SubmitRequest + 5, // 12: rpc.api.v1.PoetService.Info:input_type -> rpc.api.v1.InfoRequest + 10, // 13: rpc.api.v1.PoetService.Proof:input_type -> rpc.api.v1.ProofRequest + 2, // 14: rpc.api.v1.PoetService.PowParams:output_type -> rpc.api.v1.PowParamsResponse + 4, // 15: rpc.api.v1.PoetService.Submit:output_type -> rpc.api.v1.SubmitResponse + 6, // 16: rpc.api.v1.PoetService.Info:output_type -> rpc.api.v1.InfoResponse + 11, // 17: rpc.api.v1.PoetService.Proof:output_type -> rpc.api.v1.ProofResponse + 14, // [14:18] is the sub-list for method output_type + 10, // [10:14] is the sub-list for method input_type + 10, // [10:10] is the sub-list for extension type_name + 10, // [10:10] is the sub-list for extension extendee + 0, // [0:10] is the sub-list for field type_name } func init() { file_rpc_api_v1_api_proto_init() } @@ -1039,6 +1180,30 @@ func file_rpc_api_v1_api_proto_init() { return nil } } + file_rpc_api_v1_api_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubmitRequest_Certificate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rpc_api_v1_api_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InfoResponse_Cerifier); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -1046,7 +1211,7 @@ func file_rpc_api_v1_api_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_rpc_api_v1_api_proto_rawDesc, NumEnums: 0, - NumMessages: 12, + NumMessages: 14, NumExtensions: 0, NumServices: 1, }, diff --git a/release/proto/openapiv2/rpc/api/v1/api.swagger.json b/release/proto/openapiv2/rpc/api/v1/api.swagger.json index fb90a205..8a2b1991 100644 --- a/release/proto/openapiv2/rpc/api/v1/api.swagger.json +++ b/release/proto/openapiv2/rpc/api/v1/api.swagger.json @@ -127,6 +127,28 @@ } }, "definitions": { + "InfoResponseCerifier": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "pubkey": { + "type": "string", + "format": "byte" + } + } + }, + "SubmitRequestCertificate": { + "type": "object", + "properties": { + "signature": { + "type": "string", + "format": "byte", + "description": "The certifier's signature of the pubkey\nconfirming that the pubkey is verified." + } + } + }, "protobufAny": { "type": "object", "properties": { @@ -167,6 +189,9 @@ }, "cycleGap": { "type": "string" + }, + "certifier": { + "$ref": "#/definitions/InfoResponseCerifier" } } }, @@ -253,11 +278,11 @@ "nonce": { "type": "string", "format": "uint64", - "title": "Proof of Work nonce" + "title": "Proof of Work nonce\ndeprecated - use certificate instead" }, "powParams": { "$ref": "#/definitions/v1PowParams", - "title": "Proof of Work parameters that were used to generate the nonce" + "title": "Proof of Work parameters that were used to generate the nonce\ndeprecated - use certificate instead" }, "prefix": { "type": "string", @@ -283,6 +308,9 @@ "type": "string", "format": "date-time", "description": "The time by which the proof is needed.\nIf the currently open round will end after this time\nand the proof cannot be generated by this time, the request will be rejected." + }, + "certificate": { + "$ref": "#/definitions/SubmitRequestCertificate" } } }, diff --git a/rpc/api/v1/api.proto b/rpc/api/v1/api.proto index 6d27f515..d0cbb1ac 100644 --- a/rpc/api/v1/api.proto +++ b/rpc/api/v1/api.proto @@ -59,8 +59,10 @@ message PowParamsResponse { message SubmitRequest { // Proof of Work nonce + // deprecated - use certificate instead uint64 nonce = 1; // Proof of Work parameters that were used to generate the nonce + // deprecated - use certificate instead PowParams pow_params = 2; // Prefix to be added to challenge for signature verification bytes prefix = 3; @@ -74,6 +76,13 @@ message SubmitRequest { // If the currently open round will end after this time // and the proof cannot be generated by this time, the request will be rejected. google.protobuf.Timestamp deadline = 7; + + message Certificate { + // The certifier's signature of the pubkey + // confirming that the pubkey is verified. + bytes signature = 1; + } + Certificate certificate = 8; } message SubmitResponse { @@ -90,6 +99,12 @@ message InfoResponse { google.protobuf.Duration phase_shift = 4; google.protobuf.Duration cycle_gap = 5; + + message Cerifier { + string url = 1; + bytes pubkey = 2; + } + Cerifier certifier = 6; } message MembershipProof { diff --git a/rpc/rpcserver.go b/rpc/rpcserver.go index 5f19a534..afa7dacb 100644 --- a/rpc/rpcserver.go +++ b/rpc/rpcserver.go @@ -60,6 +60,7 @@ func (r *rpcServer) Submit(ctx context.Context, in *api.SubmitRequest) (*api.Sub return nil, status.Error(codes.InvalidArgument, "invalid signature") } + // FIXME: PoW is deprecated powParams := registration.PowParams{ Challenge: in.GetPowParams().GetChallenge(), Difficulty: uint(in.GetPowParams().GetDifficulty()), @@ -70,10 +71,21 @@ func (r *rpcServer) Submit(ctx context.Context, in *api.SubmitRequest) (*api.Sub deadline = in.Deadline.AsTime() } - epoch, end, err := r.registration.Submit(ctx, in.Challenge, in.Pubkey, in.Nonce, powParams, deadline) + epoch, end, err := r.registration.Submit( + ctx, + in.Challenge, + in.Pubkey, + in.Nonce, + powParams, + in.Certificate.GetSignature(), + deadline, + ) switch { + // FIXME: remove deprecated PoW case errors.Is(err, registration.ErrInvalidPow) || errors.Is(err, registration.ErrInvalidPowParams): return nil, status.Error(codes.InvalidArgument, err.Error()) + case errors.Is(err, registration.ErrInvalidCertificate): + return nil, status.Error(codes.Unauthenticated, err.Error()) case errors.Is(err, registration.ErrMaxMembersReached): return nil, status.Error(codes.ResourceExhausted, err.Error()) case errors.Is(err, registration.ErrConflictingRegistration): @@ -94,10 +106,18 @@ func (r *rpcServer) Submit(ctx context.Context, in *api.SubmitRequest) (*api.Sub } func (r *rpcServer) Info(ctx context.Context, in *api.InfoRequest) (*api.InfoResponse, error) { + var certifierResp *api.InfoResponse_Cerifier + if certifier := r.registration.CertifierInfo(); certifier != nil { + certifierResp = &api.InfoResponse_Cerifier{ + Url: certifier.URL, + Pubkey: certifier.PubKey, + } + } out := &api.InfoResponse{ ServicePubkey: r.registration.Pubkey(), PhaseShift: durationpb.New(r.phaseShift), CycleGap: durationpb.New(r.cycleGap), + Certifier: certifierResp, } return out, nil diff --git a/server/server_test.go b/server/server_test.go index 5004d8bf..579f2ce9 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -60,35 +60,70 @@ func spawnPoet(ctx context.Context, t *testing.T, cfg server.Config) (*server.Se func TestInfoEndpoint(t *testing.T) { t.Parallel() - req := require.New(t) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - ctx = logging.NewContext(ctx, zaptest.NewLogger(t)) cfg := server.DefaultConfig() cfg.DisableWorker = true - cfg.PoetDir = t.TempDir() + cfg.RawRPCListener = randomHost cfg.RawRESTListener = randomHost cfg.Round.PhaseShift = 5 * time.Minute cfg.Round.CycleGap = 7 * time.Minute - srv, client := spawnPoet(ctx, t, *cfg) - t.Cleanup(func() { assert.NoError(t, srv.Close()) }) + t.Run("no certifier", func(t *testing.T) { + req := require.New(t) + cfg := cfg + cfg.PoetDir = t.TempDir() - var eg errgroup.Group - eg.Go(func() error { - return srv.Start(ctx) - }) + ctx, cancel := context.WithCancel(logging.NewContext(context.Background(), zaptest.NewLogger(t))) + defer cancel() + srv, client := spawnPoet(ctx, t, *cfg) + t.Cleanup(func() { assert.NoError(t, srv.Close()) }) - info, err := client.Info(context.Background(), &api.InfoRequest{}) - req.NoError(err) - req.Equal(cfg.Round.PhaseShift, info.PhaseShift.AsDuration()) - req.Equal(cfg.Round.CycleGap, info.CycleGap.AsDuration()) - req.NotEmpty(info.ServicePubkey) + var eg errgroup.Group + eg.Go(func() error { + return srv.Start(ctx) + }) - cancel() - req.NoError(eg.Wait()) + info, err := client.Info(context.Background(), &api.InfoRequest{}) + req.NoError(err) + req.Equal(cfg.Round.PhaseShift, info.PhaseShift.AsDuration()) + req.Equal(cfg.Round.CycleGap, info.CycleGap.AsDuration()) + req.NotEmpty(info.ServicePubkey) + req.Nil(info.Certifier) + + cancel() + req.NoError(eg.Wait()) + }) + t.Run("with certifier", func(t *testing.T) { + req := require.New(t) + cfg := cfg + cfg.PoetDir = t.TempDir() + cfg.Registration.Certifier = ®istration.CertifierConfig{ + URL: "http://localhost:8080", + PubKey: []byte("certifier pubkey"), + } + + ctx, cancel := context.WithCancel(logging.NewContext(context.Background(), zaptest.NewLogger(t))) + defer cancel() + srv, client := spawnPoet(ctx, t, *cfg) + t.Cleanup(func() { assert.NoError(t, srv.Close()) }) + + var eg errgroup.Group + eg.Go(func() error { + return srv.Start(ctx) + }) + + info, err := client.Info(context.Background(), &api.InfoRequest{}) + req.NoError(err) + req.Equal(cfg.Round.PhaseShift, info.PhaseShift.AsDuration()) + req.Equal(cfg.Round.CycleGap, info.CycleGap.AsDuration()) + req.NotEmpty(info.ServicePubkey) + req.Equal(info.Certifier.Pubkey, cfg.Registration.Certifier.PubKey) + req.Equal(info.Certifier.Url, cfg.Registration.Certifier.URL) + + cancel() + req.NoError(eg.Wait()) + }) } func TestSubmitSignatureVerification(t *testing.T) { @@ -141,19 +176,25 @@ func TestSubmitSignatureVerification(t *testing.T) { req.NoError(eg.Wait()) } -func TestSubmitPowVerification(t *testing.T) { +func TestSubmitCertificateVerification(t *testing.T) { t.Parallel() - req := require.New(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() ctx = logging.NewContext(ctx, zaptest.NewLogger(t)) + certifierPubKey, certifierPrivKey, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + cfg := server.DefaultConfig() cfg.DisableWorker = true cfg.PoetDir = t.TempDir() cfg.RawRPCListener = randomHost cfg.RawRESTListener = randomHost cfg.Registration.PowDifficulty = 3 + cfg.Registration.Certifier = ®istration.CertifierConfig{ + URL: "http://localhost:8080", + PubKey: certifierPubKey, + } srv, client := spawnPoet(ctx, t, *cfg) t.Cleanup(func() { assert.NoError(t, srv.Close()) }) @@ -165,42 +206,66 @@ func TestSubmitPowVerification(t *testing.T) { // User credentials pubKey, privKey, err := ed25519.GenerateKey(rand.Reader) - req.NoError(err) + require.NoError(t, err) // Submit challenge with valid signature but invalid pow challenge := []byte("poet challenge") signature := ed25519.Sign(privKey, challenge) - _, err = client.Submit(context.Background(), &api.SubmitRequest{ - Challenge: challenge, - Pubkey: pubKey, - Signature: signature, - }) - req.ErrorIs(err, status.Error(codes.InvalidArgument, "invalid proof of work parameters")) - - // Submit data with valid signature and pow - resp, err := client.PowParams(context.Background(), &api.PowParamsRequest{}) - req.NoError(err) - nonce, err := shared.FindSubmitPowNonce( - context.Background(), - resp.PowParams.Challenge, - challenge, - pubKey, - uint(resp.PowParams.Difficulty), - ) - req.NoError(err) - _, err = client.Submit(context.Background(), &api.SubmitRequest{ - Nonce: nonce, - Challenge: challenge, - Pubkey: pubKey, - Signature: signature, - PowParams: resp.PowParams, + t.Run("invalid certificate", func(t *testing.T) { + _, err = client.Submit(context.Background(), &api.SubmitRequest{ + Challenge: challenge, + Pubkey: pubKey, + Signature: signature, + Certificate: &api.SubmitRequest_Certificate{ + Signature: []byte("invalid signature"), + }, + }) + require.ErrorIs(t, err, status.Error(codes.Unauthenticated, registration.ErrInvalidCertificate.Error())) }) - req.NoError(err) + t.Run("valid certificate", func(t *testing.T) { + _, err = client.Submit(context.Background(), &api.SubmitRequest{ + Challenge: challenge, + Pubkey: pubKey, + Signature: signature, + Certificate: &api.SubmitRequest_Certificate{ + Signature: ed25519.Sign(certifierPrivKey, pubKey), + }, + }) + require.NoError(t, err) + }) + t.Run("no certificate - fallback to PoW (invalid)", func(t *testing.T) { + _, err = client.Submit(context.Background(), &api.SubmitRequest{ + Challenge: challenge, + Pubkey: pubKey, + Signature: signature, + }) + require.ErrorIs(t, err, status.Error(codes.InvalidArgument, "invalid proof of work parameters")) + }) + t.Run("no certificate - fallback to PoW (valid)", func(t *testing.T) { + resp, err := client.PowParams(context.Background(), &api.PowParamsRequest{}) + require.NoError(t, err) + nonce, err := shared.FindSubmitPowNonce( + context.Background(), + resp.PowParams.Challenge, + challenge, + pubKey, + uint(resp.PowParams.Difficulty), + ) + require.NoError(t, err) + _, err = client.Submit(context.Background(), &api.SubmitRequest{ + Nonce: nonce, + Challenge: challenge, + Pubkey: pubKey, + Signature: signature, + PowParams: resp.PowParams, + }) + require.NoError(t, err) + }) cancel() - req.NoError(eg.Wait()) + require.NoError(t, eg.Wait()) } // Test submitting a challenge followed by proof generation and getting the proof via GRPC. From 83d178a57e4d1cc9748b74860b74ce475b784c37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Wed, 1 Nov 2023 12:39:54 +0100 Subject: [PATCH 2/8] Add metrics counting registrations with pow/cert --- registration/registration.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/registration/registration.go b/registration/registration.go index 0c48dcf8..e60028b4 100644 --- a/registration/registration.go +++ b/registration/registration.go @@ -15,6 +15,8 @@ import ( "go.uber.org/zap" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" "github.com/spacemeshos/poet/logging" "github.com/spacemeshos/poet/shared" ) @@ -35,6 +37,20 @@ type roundConfig interface { var ( ErrInvalidCertificate = errors.New("invalid certificate") ErrTooLateToRegister = errors.New("too late to register for the desired round") + + registerWithCertMetric = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: "poet", + Subsystem: "registration", + Name: "with_cert_total", + Help: "Number of registrations with a certificate", + }, []string{"result"}) + + registerWithPoWMetric = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: "poet", + Subsystem: "registration", + Name: "with_pow_total", + Help: "Number of registrations with a PoW", + }, []string{"result"}) ) // Registration orchestrates rounds functionality @@ -313,16 +329,20 @@ func (r *Registration) Submit( // the certificate path is being stabilized. if r.cfg.Certifier != nil && certificate != nil { if !ed25519.Verify(r.cfg.Certifier.PubKey, nodeID, certificate) { + registerWithCertMetric.WithLabelValues("invalid").Inc() return 0, time.Time{}, ErrInvalidCertificate } + registerWithCertMetric.WithLabelValues("valid").Inc() } else { // FIXME: PoW is deprecated // Remove once certificate path is stabilized and mandatory. err := r.powVerifiers.VerifyWithParams(challenge, nodeID, nonce, powParams) if err != nil { + registerWithPoWMetric.WithLabelValues("invalid").Inc() logger.Debug("PoW verification failed", zap.Error(err)) return 0, time.Time{}, err } + registerWithPoWMetric.WithLabelValues("valid").Inc() logger.Debug("verified PoW", zap.String("node_id", hex.EncodeToString(nodeID))) } From 165150d411bc5a113f7d783932339a6797f9be6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Wed, 1 Nov 2023 13:25:07 +0100 Subject: [PATCH 3/8] Fix parsing certifier pubkey from config --- registration/config.go | 22 +++++++++++++++++++--- registration/registration.go | 2 +- registration/registration_test.go | 4 ++-- server/server_test.go | 2 +- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/registration/config.go b/registration/config.go index 92fd2df5..b937ae4c 100644 --- a/registration/config.go +++ b/registration/config.go @@ -1,6 +1,7 @@ package registration import ( + "encoding/base64" "time" "go.uber.org/zap/zapcore" @@ -25,14 +26,29 @@ type Config struct { Certifier *CertifierConfig } +type Base64Enc []byte + +func (k *Base64Enc) UnmarshalFlag(value string) error { + b, err := base64.StdEncoding.DecodeString(value) + if err != nil { + return err + } + *k = b + return nil +} + +func (k *Base64Enc) Bytes() []byte { + return []byte(*k) +} + 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"` + URL string `long:"certifier-url" description:"The URL of the certifier service"` + PubKey Base64Enc `long:"certifier-pubkey" description:"The public key of the certifier service (base64 encoded)"` } // implement zap.ObjectMarshaler interface. func (c CertifierConfig) MarshalLogObject(enc zapcore.ObjectEncoder) error { enc.AddString("url", c.URL) - enc.AddBinary("pubkey", c.PubKey) + enc.AddString("pubkey", base64.StdEncoding.EncodeToString(c.PubKey)) return nil } diff --git a/registration/registration.go b/registration/registration.go index e60028b4..dbcbec8d 100644 --- a/registration/registration.go +++ b/registration/registration.go @@ -328,7 +328,7 @@ func (r *Registration) Submit( // Support both a certificate and a PoW while // the certificate path is being stabilized. if r.cfg.Certifier != nil && certificate != nil { - if !ed25519.Verify(r.cfg.Certifier.PubKey, nodeID, certificate) { + if !ed25519.Verify(r.cfg.Certifier.PubKey.Bytes(), nodeID, certificate) { registerWithCertMetric.WithLabelValues("invalid").Inc() return 0, time.Time{}, ErrInvalidCertificate } diff --git a/registration/registration_test.go b/registration/registration_test.go index baf15b37..5b87197c 100644 --- a/registration/registration_test.go +++ b/registration/registration_test.go @@ -282,7 +282,7 @@ func TestRecoveringRoundInProgress(t *testing.T) { func Test_GetCertifierInfo(t *testing.T) { certifier := ®istration.CertifierConfig{ - PubKey: []byte("pubkey"), + PubKey: registration.Base64Enc("pubkey"), URL: "http://the-certifier.org", } @@ -353,7 +353,7 @@ func Test_CheckCertificate(t *testing.T) { registration.WithConfig(registration.Config{ MaxRoundMembers: 10, Certifier: ®istration.CertifierConfig{ - PubKey: pub, + PubKey: registration.Base64Enc(pub), }, }), ) diff --git a/server/server_test.go b/server/server_test.go index 579f2ce9..399d3100 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -193,7 +193,7 @@ func TestSubmitCertificateVerification(t *testing.T) { cfg.Registration.PowDifficulty = 3 cfg.Registration.Certifier = ®istration.CertifierConfig{ URL: "http://localhost:8080", - PubKey: certifierPubKey, + PubKey: registration.Base64Enc(certifierPubKey), } srv, client := spawnPoet(ctx, t, *cfg) From e18296170842e205ced350f304e079669e4df75b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Thu, 2 Nov 2023 14:32:16 +0100 Subject: [PATCH 4/8] Satisfy linter --- registration/registration.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/registration/registration.go b/registration/registration.go index dbcbec8d..e6b574c9 100644 --- a/registration/registration.go +++ b/registration/registration.go @@ -13,10 +13,10 @@ import ( "sync" "time" - "go.uber.org/zap" - "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "go.uber.org/zap" + "github.com/spacemeshos/poet/logging" "github.com/spacemeshos/poet/shared" ) From ec524fb98125472669ea74035285115249a09db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Fri, 3 Nov 2023 17:26:16 +0100 Subject: [PATCH 5/8] Fix UT --- server/server_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server_test.go b/server/server_test.go index 399d3100..35cf5639 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -118,7 +118,7 @@ func TestInfoEndpoint(t *testing.T) { req.Equal(cfg.Round.PhaseShift, info.PhaseShift.AsDuration()) req.Equal(cfg.Round.CycleGap, info.CycleGap.AsDuration()) req.NotEmpty(info.ServicePubkey) - req.Equal(info.Certifier.Pubkey, cfg.Registration.Certifier.PubKey) + req.Equal(info.Certifier.Pubkey, cfg.Registration.Certifier.PubKey.Bytes()) req.Equal(info.Certifier.Url, cfg.Registration.Certifier.URL) cancel() From 594ef568d8df1d050e4752029ccd1ef5ad45e176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Fri, 10 Nov 2023 11:39:39 +0700 Subject: [PATCH 6/8] Update registration/config.go Co-authored-by: Matthias Fasching <5011972+fasmat@users.noreply.github.com> --- registration/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registration/config.go b/registration/config.go index b937ae4c..a9bddc97 100644 --- a/registration/config.go +++ b/registration/config.go @@ -16,7 +16,7 @@ func DefaultConfig() Config { } type Config struct { - // FIXME: remove depreacated PoW + // FIXME: remove deprecated 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"` From d5a7ee2347b6ce7f8402e2161e0472d1dd8763b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Fri, 10 Nov 2023 15:24:36 +0700 Subject: [PATCH 7/8] Bump go to v1.21.4 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 7d728318..6357bd95 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/spacemeshos/poet -go 1.21.3 +go 1.21.4 require ( github.com/c0mm4nd/go-ripemd v0.0.0-20200326052756-bd1759ad7d10 From 1284049ab120b3bfb01a865adcdb5c5057e2dbfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20R=C3=B3=C5=BCa=C5=84ski?= Date: Fri, 10 Nov 2023 15:34:10 +0700 Subject: [PATCH 8/8] Add UT for Base64Enc --- registration/config_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 registration/config_test.go diff --git a/registration/config_test.go b/registration/config_test.go new file mode 100644 index 00000000..be3b3783 --- /dev/null +++ b/registration/config_test.go @@ -0,0 +1,17 @@ +package registration_test + +import ( + "encoding/base64" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/spacemeshos/poet/registration" +) + +func TestBase64EncDecode(t *testing.T) { + enc := base64.StdEncoding.EncodeToString([]byte("hello")) + b64 := registration.Base64Enc{} + require.NoError(t, b64.UnmarshalFlag(enc)) + require.Equal(t, []byte("hello"), b64.Bytes()) +}