From 4d4074613d13e477f8b7bc5d4cd595f45a3e5987 Mon Sep 17 00:00:00 2001 From: giwaov Date: Sun, 28 Jun 2026 12:49:50 +0100 Subject: [PATCH] Guard stake RPC operator pubkey --- cmd/rpc/admin.go | 25 ++++++++++++++--- cmd/rpc/admin_test.go | 63 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 cmd/rpc/admin_test.go diff --git a/cmd/rpc/admin.go b/cmd/rpc/admin.go index af9d49e8b6..ededdadd72 100644 --- a/cmd/rpc/admin.go +++ b/cmd/rpc/admin.go @@ -177,8 +177,8 @@ func (s *Server) TransactionStake(w http.ResponseWriter, r *http.Request, _ http if err != nil { return nil, err } - // Convert the public key from string to a crypto.PublicKey - pk, err := crypto.NewPublicKeyFromString(ptr.PubKey) + // Convert and validate the public key from string to a crypto.PublicKey + pk, err := stakePublicKeyFromRequest(ptr) if err != nil { return nil, err } @@ -584,8 +584,8 @@ func (s *Server) txHandler(w http.ResponseWriter, r *http.Request, callback func write(w, err, http.StatusBadRequest) return } - // Set the public key in transaction request for reference. - ptr.PubKey = privateKey.PublicKey().String() + // Set the public key in transaction request for reference when one was not supplied. + setRequestPublicKey(ptr, privateKey) // Call the provided callback function with the private key and transaction request. p, err := callback(privateKey, ptr) @@ -611,6 +611,23 @@ func (s *Server) txHandler(w http.ResponseWriter, r *http.Request, callback func } } +func stakePublicKeyFromRequest(ptr *txRequest) (crypto.PublicKeyI, error) { + pk, err := crypto.NewPublicKeyFromString(ptr.PubKey) + if err != nil { + return nil, err + } + if !bytes.Equal(pk.Address().Bytes(), ptr.Address) { + return nil, fmt.Errorf("stake public key address must match validator address") + } + return pk, nil +} + +func setRequestPublicKey(ptr *txRequest, privateKey crypto.PrivateKeyI) { + if ptr.PubKey == "" { + ptr.PubKey = privateKey.PublicKey().String() + } +} + // AddVote adds a vote to a proposal func (s *Server) AddVote(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { // Initialize a map to hold government proposals. diff --git a/cmd/rpc/admin_test.go b/cmd/rpc/admin_test.go new file mode 100644 index 0000000000..fb59a59a2f --- /dev/null +++ b/cmd/rpc/admin_test.go @@ -0,0 +1,63 @@ +package rpc + +import ( + "testing" + + "github.com/canopy-network/canopy/lib/crypto" + "github.com/stretchr/testify/require" +) + +func TestSetRequestPublicKeyPreservesExplicitPubKey(t *testing.T) { + operatorKey, err := crypto.NewBLS12381PrivateKey() + require.NoError(t, err) + signerKey, err := crypto.NewBLS12381PrivateKey() + require.NoError(t, err) + + ptr := &txRequest{PubKey: operatorKey.PublicKey().String()} + + setRequestPublicKey(ptr, signerKey) + + require.Equal(t, operatorKey.PublicKey().String(), ptr.PubKey) +} + +func TestSetRequestPublicKeyFallsBackToSignerPubKey(t *testing.T) { + signerKey, err := crypto.NewBLS12381PrivateKey() + require.NoError(t, err) + + ptr := new(txRequest) + + setRequestPublicKey(ptr, signerKey) + + require.Equal(t, signerKey.PublicKey().String(), ptr.PubKey) +} + +func TestStakePublicKeyFromRequestRequiresValidatorAddress(t *testing.T) { + operatorKey, err := crypto.NewBLS12381PrivateKey() + require.NoError(t, err) + signerKey, err := crypto.NewBLS12381PrivateKey() + require.NoError(t, err) + + ptr := &txRequest{ + PubKey: signerKey.PublicKey().String(), + addressRequest: addressRequest{Address: operatorKey.PublicKey().Address().Bytes()}, + } + + _, err = stakePublicKeyFromRequest(ptr) + + require.ErrorContains(t, err, "stake public key address must match validator address") +} + +func TestStakePublicKeyFromRequestAcceptsValidatorPubKey(t *testing.T) { + operatorKey, err := crypto.NewBLS12381PrivateKey() + require.NoError(t, err) + + ptr := &txRequest{ + PubKey: operatorKey.PublicKey().String(), + addressRequest: addressRequest{Address: operatorKey.PublicKey().Address().Bytes()}, + } + + got, err := stakePublicKeyFromRequest(ptr) + + require.NoError(t, err) + require.True(t, operatorKey.PublicKey().Equals(got)) +}