Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion activation/certifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ func (c *CertifierClient) Certify(
if cert.Expiration != nil {
c.logger.Info("certificate has expiration date", zap.Time("expiration", *cert.Expiration))
if time.Until(*cert.Expiration) < 0 {
return nil, errors.New("certificate is expired")
return nil, shared.ErrCertExpired
}
}

Expand Down
3 changes: 2 additions & 1 deletion activation/e2e/activation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func Test_BuilderWithMultipleClients(t *testing.T) {
t,
cfg,
func(id []byte) *poetShared.Cert {
exp := time.Now().Add(epoch)
exp := time.Now().Add(5 * time.Minute)
return &poetShared.Cert{Pubkey: id, Expiration: &exp}
},
verifying.WithLabelScryptParams(scrypt),
Expand All @@ -114,6 +114,7 @@ func Test_BuilderWithMultipleClients(t *testing.T) {
URL: (&url.URL{Scheme: "http", Host: address.String()}).String(),
PubKey: registration.Base64Enc(pubkey),
}),
WithTrustedKeysDirPath(t.TempDir()),
)
certClient := activation.NewCertifierClient(db, localDB, logger.Named("certifier"))
certifier := activation.NewCertifier(localDB, logger, certClient)
Expand Down
6 changes: 6 additions & 0 deletions activation/e2e/poet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ func WithCertifier(certifier *registration.CertifierConfig) HTTPPoetOpt {
}
}

func WithTrustedKeysDirPath(path string) HTTPPoetOpt {
return func(cfg *server.Config) {
cfg.Registration.Certifier.TrustedKeysDirPath = path
}
}

// NewHTTPPoetTestHarness returns a new instance of HTTPPoetHarness.
func NewHTTPPoetTestHarness(ctx context.Context, poetdir string, opts ...HTTPPoetOpt) (*HTTPPoetTestHarness, error) {
cfg := server.DefaultConfig()
Expand Down
17 changes: 15 additions & 2 deletions activation/poet.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ type PoetPoW struct {
type PoetAuth struct {
*PoetPoW
*certifier.PoetCert

CertPubKey []byte
}

type PoetClient interface {
Expand Down Expand Up @@ -234,13 +236,19 @@ func (c *HTTPPoetClient) Submit(
Data: auth.PoetCert.Data,
Signature: auth.PoetCert.Signature,
}

if len(auth.CertPubKey) > shared.CertKeyHintSize {
request.CertificatePubkeyHint = auth.CertPubKey[:shared.CertKeyHintSize]
} else {
request.CertificatePubkeyHint = auth.CertPubKey
}
}

resBody := rpcapi.SubmitResponse{}
if err := c.req(ctx, http.MethodPost, "/v1/submit", &request, &resBody, c.submitChallengeClient); err != nil {
return nil, fmt.Errorf("submitting challenge: %w", err)
}
roundEnd := time.Time{}
var roundEnd time.Time
if resBody.RoundEnd != nil {
roundEnd = time.Now().Add(resBody.RoundEnd.AsDuration())
}
Expand Down Expand Up @@ -492,7 +500,12 @@ func (c *poetService) authorize(
cert, err := c.Certify(ctx, nodeID)
switch {
case err == nil:
return &PoetAuth{PoetCert: cert}, nil
info, err := c.getInfo(ctx)
if err != nil {
return nil, err
}

return &PoetAuth{PoetCert: cert, CertPubKey: info.Certifier.Pubkey}, nil
Comment on lines +518 to +523
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should continue to work the way it was (without passing the hint).

Copy link
Member

@fasmat fasmat Oct 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand? Sure the node just chose to not provide the certifier pubkey hint and then everything works like before (after the fix in this PR: spacemeshos/poet#523), but if the nodes certificate has been signed by a different Certifier than the one returned from /info of the PoET it is using then it should be able to at least try to still submit in case the PoET accepts certificates from the certifier the node is using or not?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean: if the cert is signed by the certifier exposed on info.Certfier.URL, then you don't need to pass the hint (legacy support). If it is not signed by this certifier, then passing info.Certifier.Pubkey makes no sense (as it will be rejected)

case errors.Is(err, ErrCertificatesNotSupported):
logger.Debug("poet doesn't support certificates")
default:
Expand Down
100 changes: 98 additions & 2 deletions activation/poet_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

rpcapi "github.com/spacemeshos/poet/release/proto/go/rpc/api/v1"
"github.com/spacemeshos/poet/server"
"github.com/spacemeshos/poet/shared"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"go.uber.org/zap"
Expand Down Expand Up @@ -182,8 +183,21 @@ func Test_HTTPPoetClient_Proof(t *testing.T) {
}

func TestPoetClient_CachesProof(t *testing.T) {
var proofsCalled atomic.Uint64
certifierAddress := &url.URL{Scheme: "http", Host: "certifier"}
certifierPubKey := []byte("certifier-pubkey")
infoResp, err := protojson.Marshal(&rpcapi.InfoResponse{
ServicePubkey: []byte("pubkey"),
Certifier: &rpcapi.InfoResponse_Cerifier{
Url: certifierAddress.String(),
Pubkey: certifierPubKey,
},
})
require.NoError(t, err)

mux := http.NewServeMux()
mux.HandleFunc("GET /v1/info", func(w http.ResponseWriter, r *http.Request) { w.Write(infoResp) })

var proofsCalled atomic.Uint64
mux.HandleFunc("GET /v1/proofs/", func(w http.ResponseWriter, r *http.Request) {
proofsCalled.Add(1)
resp, err := protojson.Marshal(&rpcapi.ProofResponse{})
Expand All @@ -209,7 +223,7 @@ func TestPoetClient_CachesProof(t *testing.T) {
require.NoError(t, err)
poet := NewPoetServiceWithClient(db, client, DefaultPoetConfig(), zaptest.NewLogger(t))

eg := errgroup.Group{}
var eg errgroup.Group
for range 20 {
eg.Go(func() error {
_, _, err := poet.Proof(ctx, "1")
Expand Down Expand Up @@ -377,6 +391,88 @@ func TestPoetClient_ObtainsCertOnSubmit(t *testing.T) {
require.NoError(t, err)
}

func TestCheckCertifierPublickeyHint(t *testing.T) {
sig, err := signing.NewEdSigner()
require.NoError(t, err)

certifierAddress := &url.URL{Scheme: "http", Host: "certifier"}
certifierPubKey := []byte("certifier-pubkey")
infoResp, err := protojson.Marshal(&rpcapi.InfoResponse{
ServicePubkey: []byte("pubkey"),
Certifier: &rpcapi.InfoResponse_Cerifier{
Url: certifierAddress.String(),
Pubkey: certifierPubKey,
},
})
require.NoError(t, err)

submitResp, err := protojson.Marshal(&rpcapi.SubmitResponse{})
require.NoError(t, err)

mux := http.NewServeMux()
mux.HandleFunc("GET /v1/info", func(w http.ResponseWriter, r *http.Request) { w.Write(infoResp) })

publicKeyHint := certifierPubKey[:shared.CertKeyHintSize]
mux.HandleFunc("POST /v1/submit", func(w http.ResponseWriter, r *http.Request) {
rawReq, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body", http.StatusInternalServerError)
return
}

req := &rpcapi.SubmitRequest{}
if err := protojson.Unmarshal(rawReq, req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}

if len(req.CertificatePubkeyHint) > 0 && !bytes.Equal(publicKeyHint, req.CertificatePubkeyHint) {
http.Error(w, "Unknown public key hint", http.StatusUnauthorized)
return
}

w.Write(submitResp)
})

ts := httptest.NewServer(mux)
defer ts.Close()

server := types.PoetServer{
Address: ts.URL,
Pubkey: types.NewBase64Enc([]byte("pubkey")),
}
cfg := PoetConfig{RequestTimeout: time.Millisecond * 100}
client, err := NewHTTPPoetClient(server, cfg, withCustomHttpClient(ts.Client()))
require.NoError(t, err)

cert := certifier.PoetCert{Data: []byte("abc")}
t.Run("public key hint is valid", func(t *testing.T) {
_, err = client.Submit(context.Background(), time.Time{}, nil, nil, types.RandomEdSignature(), sig.NodeID(),
PoetAuth{
PoetCert: &cert,
CertPubKey: publicKeyHint,
})
require.NoError(t, err)
})

t.Run("no public key hint", func(t *testing.T) {
_, err = client.Submit(context.Background(), time.Time{}, nil, nil, types.RandomEdSignature(), sig.NodeID(),
PoetAuth{
PoetCert: &cert,
})
require.NoError(t, err)
})

t.Run("public key hint is invalid", func(t *testing.T) {
_, err = client.Submit(context.Background(), time.Time{}, nil, nil, types.RandomEdSignature(), sig.NodeID(),
PoetAuth{
PoetCert: &cert,
CertPubKey: []byte{1, 2, 3, 4, 5},
})
require.ErrorIs(t, err, ErrUnauthorized)
})
}

func TestPoetClient_RecertifiesOnAuthFailure(t *testing.T) {
sig, err := signing.NewEdSigner()
require.NoError(t, err)
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ require (
github.com/spacemeshos/fixed v0.1.1
github.com/spacemeshos/go-scale v1.2.0
github.com/spacemeshos/merkle-tree v0.2.3
github.com/spacemeshos/poet v0.10.4
github.com/spacemeshos/poet v0.10.6
github.com/spacemeshos/post v0.12.8
github.com/spf13/afero v1.11.0
github.com/spf13/cobra v1.8.1
Expand Down Expand Up @@ -233,7 +233,7 @@ require (
gonum.org/v1/gonum v0.15.0 // indirect
google.golang.org/api v0.187.0 // indirect
google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -613,8 +613,8 @@ github.com/spacemeshos/go-scale v1.2.0 h1:ZlA2L1ILym2gmyJUwUdLTiyP1ZIG0U4xE9nFVF
github.com/spacemeshos/go-scale v1.2.0/go.mod h1:HV6e3/X5h9u2aFpYKJxt7PY/fBuLBegEKWgeZJ+/5jE=
github.com/spacemeshos/merkle-tree v0.2.3 h1:zGEgOR9nxAzJr0EWjD39QFngwFEOxfxMloEJZtAysas=
github.com/spacemeshos/merkle-tree v0.2.3/go.mod h1:VomOcQ5pCBXz7goiWMP5hReyqOfDXGSKbrH2GB9Htww=
github.com/spacemeshos/poet v0.10.4 h1:MHGG1dhMVwy5DdlsmwdRLDQTTqlPA21lSQB2PVd8MSk=
github.com/spacemeshos/poet v0.10.4/go.mod h1:hz21GMyHb9h29CqNhVeKxCD5dxZdQK27nAqLpT46gjE=
github.com/spacemeshos/poet v0.10.6 h1:WUf3hUpsAYOUKvhOIVsTee9sXtKG4r4SxiYFQhI1uBE=
github.com/spacemeshos/poet v0.10.6/go.mod h1:73rdy97oIc3wo8HtlTLIYOiayCz6Ylz8BVn1ZMi8ruw=
github.com/spacemeshos/post v0.12.8 h1:xcxVPWTvt2zJYJUvdpmX7CwR1CQnMnmd4VndThGbSdY=
github.com/spacemeshos/post v0.12.8/go.mod h1:WzfVgaa1wxgrsytC4EVKkG8rwoUxjyoyQL0ZSxs56Y0=
github.com/spacemeshos/sha256-simd v0.1.0 h1:G7Mfu5RYdQiuE+wu4ZyJ7I0TI74uqLhFnKblEnSpjYI=
Expand Down Expand Up @@ -907,8 +907,8 @@ google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d h1:PksQg4dV6Sem3/HkBX+Ltq8T0ke0PKIRBNBatoDTVls=
google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:s7iA721uChleev562UJO2OYB0PPT9CMFjV+Ce7VJH5M=
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8=
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo=
google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f h1:jTm13A2itBi3La6yTGqn8bVSrc3ZZ1r8ENHlIXBfnRA=
google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f/go.mod h1:CLGoBuH1VHxAUXVPP8FfPwPEVJB6lz3URE5mY2SuayE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f h1:cUMEy+8oS78BWIH9OWazBkzbr090Od9tWBNtZHkOhf0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
Expand Down
6 changes: 3 additions & 3 deletions sql/localsql/certifier/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ type PoetCert struct {
Signature []byte
}

func AddCertificate(db sql.Executor, nodeID types.NodeID, cert PoetCert, cerifierID []byte) error {
func AddCertificate(db sql.Executor, nodeID types.NodeID, cert PoetCert, certifierID []byte) error {
enc := func(stmt *sql.Statement) {
stmt.BindBytes(1, nodeID.Bytes())
stmt.BindBytes(2, cerifierID)
stmt.BindBytes(2, certifierID)
stmt.BindBytes(3, cert.Data)
stmt.BindBytes(4, cert.Signature)
}
if _, err := db.Exec(`
REPLACE INTO poet_certificates (node_id, certifier_id, certificate, signature)
VALUES (?1, ?2, ?3, ?4);`, enc, nil,
); err != nil {
return fmt.Errorf("storing poet certificate for (%s; %x): %w", nodeID.ShortString(), cerifierID, err)
return fmt.Errorf("storing poet certificate for (%s; %x): %w", nodeID.ShortString(), certifierID, err)
}
return nil
}
Expand Down
1 change: 1 addition & 0 deletions systest/tests/poets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ func TestRegisteringInPoetWithPowAndCert(t *testing.T) {
powRegs, err := fetchCounterMetric(tctx, metricsEndpoint, "poet_registration_with_pow_total", valid)
require.NoError(t, err)
require.GreaterOrEqual(t, powRegs, float64(cl.Smeshers()*epoch))

powRegsInvalid, err := fetchCounterMetric(tctx, metricsEndpoint, "poet_registration_with_pow_total", invalid)
require.ErrorIs(t, err, errMetricNotFound, "metric for invalid PoW registrations value: %v", powRegsInvalid)

Expand Down
Loading