From 435d1164330bfa03cd3926567f9a3b4cd4f63aca Mon Sep 17 00:00:00 2001 From: Richard Pringle Date: Wed, 18 Dec 2024 19:10:22 -0500 Subject: [PATCH] Add test for networked signer --- utils/crypto/bls/signers/json-rpc/client.go | 92 +++++++ utils/crypto/bls/signers/json-rpc/messages.go | 23 ++ utils/crypto/bls/signers/json-rpc/server.go | 108 ++++++++ utils/crypto/bls/test/bls_test.go | 253 ++++++++++-------- 4 files changed, 371 insertions(+), 105 deletions(-) create mode 100644 utils/crypto/bls/signers/json-rpc/client.go create mode 100644 utils/crypto/bls/signers/json-rpc/messages.go create mode 100644 utils/crypto/bls/signers/json-rpc/server.go diff --git a/utils/crypto/bls/signers/json-rpc/client.go b/utils/crypto/bls/signers/json-rpc/client.go new file mode 100644 index 000000000000..481bf714aff6 --- /dev/null +++ b/utils/crypto/bls/signers/json-rpc/client.go @@ -0,0 +1,92 @@ +package jsonrpc + +import ( + "bytes" + "net/http" + "net/url" + + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/gorilla/rpc/v2/json2" +) + +type Client struct { + // http client + http *http.Client + url url.URL +} + +func NewClient(url url.URL) *Client { + return &Client{ + http: &http.Client{}, + url: url, + } +} + +func (client *Client) call(method string, params []interface{}, result interface{}) error { + requestBody, err := json2.EncodeClientRequest(method, params) + + if err != nil { + return err + } + + resp, err := client.http.Post(client.url.String(), "application/json", bytes.NewBuffer(requestBody)) + + if err != nil { + return err + } + + return json2.DecodeClientResponse(resp.Body, result) +} + +func (c *Client) PublicKey() *bls.PublicKey { + reply := new(PublicKeyReply) + + err := c.call("Signer.PublicKey", []interface{}{PublicKeyArgs{}}, reply) + + if err != nil { + panic(err) + } + + pk := new(bls.PublicKey) + pk = pk.Deserialize(reply.PublicKey) + + return pk +} + +// Sign [msg] to authorize this message +func (c *Client) Sign(msg []byte) *bls.Signature { + // request the public key from the json-rpc server + reply := new(SignReply) + err := c.call("Signer.Sign", []interface{}{SignArgs{msg}}, reply) + + // TODO: handle this + if err != nil { + panic(err) + } + + // deserialize the public key + sig := new(bls.Signature) + sig = sig.Deserialize(reply.Signature) + + // can be nil if the public key is invalid + return sig +} + +// Sign [msg] to prove the ownership +func (c *Client) SignProofOfPossession(msg []byte) *bls.Signature { + // request the public key from the json-rpc server + reply := new(SignReply) + err := c.call("Signer.SignProofOfPossession", []interface{}{SignArgs{msg}}, reply) + + // TODO: handle this + if err != nil { + panic(err) + } + + // deserialize the public key + sig := new(bls.Signature) + sig = sig.Deserialize(reply.Signature) + + // can be nil if the public key is invalid + return sig +} diff --git a/utils/crypto/bls/signers/json-rpc/messages.go b/utils/crypto/bls/signers/json-rpc/messages.go new file mode 100644 index 000000000000..ded2ec8e3b8d --- /dev/null +++ b/utils/crypto/bls/signers/json-rpc/messages.go @@ -0,0 +1,23 @@ +package jsonrpc + +type PublicKeyArgs struct{} + +type SignArgs struct { + Msg []byte +} + +type SignProofOfPossessionArgs struct { + Msg []byte +} + +type PublicKeyReply struct { + PublicKey []byte +} + +type SignReply struct { + Signature []byte +} + +type SignProofOfPossessionReply struct { + Signature []byte +} diff --git a/utils/crypto/bls/signers/json-rpc/server.go b/utils/crypto/bls/signers/json-rpc/server.go new file mode 100644 index 000000000000..392d787d8aa0 --- /dev/null +++ b/utils/crypto/bls/signers/json-rpc/server.go @@ -0,0 +1,108 @@ +package jsonrpc + +import ( + "context" + "log" + "net" + "net/http" + "time" + + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/crypto/bls/signers/local" + "github.com/gorilla/rpc/v2" + "github.com/gorilla/rpc/v2/json" +) + +type signerService struct { + signer *local.LocalSigner +} + +type Server struct { + httpServer *http.Server + listener net.Listener +} + +func NewSignerService() *signerService { + signer, err := local.NewSigner() + + if err != nil { + panic(err) + } + + return &signerService{signer: signer} +} + +func Serve(service *signerService) (*Server, error) { + server := rpc.NewServer() + server.RegisterCodec(json.NewCodec(), "application/json") + + err := server.RegisterService(service, "Signer") + if err != nil { + return nil, err + } + + httpServer := &http.Server{ + Handler: server, + } + + listener, err := net.Listen("tcp", "") + if err != nil { + return nil, err + } + + go func() { + if err := httpServer.Serve(listener); err != nil && err != http.ErrServerClosed { + log.Fatal(err) + } + }() + + return &Server{ + httpServer: httpServer, + listener: listener, + }, nil +} + +func (s *Server) Addr() net.Addr { + return s.listener.Addr() +} + +func (s *Server) Close() error { + // Create a context with a timeout to allow for graceful shutdown + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + // Shutdown the HTTP server + if err := s.httpServer.Shutdown(ctx); err != nil { + return err + } + + // Close the listener + return s.listener.Close() +} + +func (s *signerService) PublicKey(r *http.Request, args *PublicKeyArgs, reply *PublicKeyReply) error { + *reply = toPkReply(s.signer.PublicKey()) + return nil +} + +func (s *signerService) Sign(r *http.Request, args *struct{ Msg []byte }, reply *SignReply) error { + *reply = toSignReply(s.signer.Sign(args.Msg)) + return nil +} + +func (s *signerService) SignProofOfPossession(r *http.Request, args *struct{ Msg []byte }, reply *SignProofOfPossessionReply) error { + *reply = toSignProofOfPossessionReply(s.signer.SignProofOfPossession(args.Msg)) + return nil +} + +func toPkReply(pk *bls.PublicKey) PublicKeyReply { + return PublicKeyReply{PublicKey: pk.Serialize()} +} + +func toSignReply(sig *bls.Signature) SignReply { + return SignReply{Signature: sig.Serialize()} +} + +func toSignProofOfPossessionReply(sig *bls.Signature) SignProofOfPossessionReply { + return SignProofOfPossessionReply{Signature: sig.Serialize()} +} diff --git a/utils/crypto/bls/test/bls_test.go b/utils/crypto/bls/test/bls_test.go index 2773c63eb690..98a8a7481c20 100644 --- a/utils/crypto/bls/test/bls_test.go +++ b/utils/crypto/bls/test/bls_test.go @@ -4,19 +4,72 @@ package bls_test import ( + "io" + "net/url" "testing" "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/crypto/bls" + jsonrpc "github.com/ava-labs/avalanchego/utils/crypto/bls/signers/json-rpc" "github.com/ava-labs/avalanchego/utils/crypto/bls/signers/local" ) +type Signer interface { + bls.Signer + io.Closer +} + +type jsonRPCSigner struct { + *jsonrpc.Client + server *jsonrpc.Server +} + +func (s jsonRPCSigner) Close() error { + return s.server.Close() +} + +type localSigner struct { + *local.LocalSigner +} + +func (s localSigner) Close() error { + return nil +} + +var localSignerFn = func() (Signer, error) { + signer, err := local.NewSigner() + return &localSigner{LocalSigner: signer}, err +} +var serverSignerFn = func() (Signer, error) { + // do I need to make sure the server gets closed properly? + service := jsonrpc.NewSignerService() + server, err := jsonrpc.Serve(service) + if err != nil { + return nil, err + } + + url := url.URL{ + Scheme: "http", + Host: server.Addr().String(), + } + + client := jsonrpc.NewClient(url) + + return jsonRPCSigner{server: server, Client: client}, nil +} + +var signerFns = []func() (Signer, error){ + localSignerFn, + serverSignerFn, +} + func TestAggregation(t *testing.T) { type test struct { name string - setup func(require *require.Assertions) ([]*bls.PublicKey, []*bls.Signature, []byte) + signers []func() (Signer, error) + setup func(*require.Assertions, bls.Signer) (pks []*bls.PublicKey, sigs []*bls.Signature, msg []byte) expectedSigAggError error expectedPubKeyAggError error expectedValid bool @@ -24,17 +77,16 @@ func TestAggregation(t *testing.T) { tests := []test{ { - name: "valid", - setup: func(require *require.Assertions) ([]*bls.PublicKey, []*bls.Signature, []byte) { - sk0, err := local.NewSigner() - require.NoError(err) + name: "valid", + signers: signerFns, + setup: func(require *require.Assertions, signer bls.Signer) ([]*bls.PublicKey, []*bls.Signature, []byte) { sk1, err := local.NewSigner() require.NoError(err) sk2, err := local.NewSigner() require.NoError(err) pks := []*bls.PublicKey{ - sk0.PublicKey(), + signer.PublicKey(), sk1.PublicKey(), sk2.PublicKey(), } @@ -42,7 +94,7 @@ func TestAggregation(t *testing.T) { msg := utils.RandomBytes(1234) sigs := []*bls.Signature{ - sk0.Sign(msg), + signer.Sign(msg), sk1.Sign(msg), sk2.Sign(msg), } @@ -52,19 +104,18 @@ func TestAggregation(t *testing.T) { expectedValid: true, }, { - name: "valid single key", - setup: func(require *require.Assertions) ([]*bls.PublicKey, []*bls.Signature, []byte) { - sk, err := local.NewSigner() - require.NoError(err) + name: "valid single key", + signers: signerFns, + setup: func(require *require.Assertions, signer bls.Signer) ([]*bls.PublicKey, []*bls.Signature, []byte) { pks := []*bls.PublicKey{ - sk.PublicKey(), + signer.PublicKey(), } msg := utils.RandomBytes(1234) sigs := []*bls.Signature{ - sk.Sign(msg), + signer.Sign(msg), } return pks, sigs, msg @@ -73,16 +124,14 @@ func TestAggregation(t *testing.T) { }, { name: "wrong message", - setup: func(require *require.Assertions) ([]*bls.PublicKey, []*bls.Signature, []byte) { - sk0, err := local.NewSigner() - require.NoError(err) + setup: func(require *require.Assertions, signer bls.Signer) ([]*bls.PublicKey, []*bls.Signature, []byte) { sk1, err := local.NewSigner() require.NoError(err) sk2, err := local.NewSigner() require.NoError(err) pks := []*bls.PublicKey{ - sk0.PublicKey(), + signer.PublicKey(), sk1.PublicKey(), sk2.PublicKey(), } @@ -90,7 +139,7 @@ func TestAggregation(t *testing.T) { msg := utils.RandomBytes(1234) sigs := []*bls.Signature{ - sk0.Sign(msg), + signer.Sign(msg), sk1.Sign(msg), sk2.Sign(msg), } @@ -102,17 +151,16 @@ func TestAggregation(t *testing.T) { expectedValid: false, }, { - name: "one sig over different message", - setup: func(require *require.Assertions) ([]*bls.PublicKey, []*bls.Signature, []byte) { - sk0, err := local.NewSigner() - require.NoError(err) + name: "one sig over different message", + signers: signerFns, + setup: func(require *require.Assertions, signer bls.Signer) ([]*bls.PublicKey, []*bls.Signature, []byte) { sk1, err := local.NewSigner() require.NoError(err) sk2, err := local.NewSigner() require.NoError(err) pks := []*bls.PublicKey{ - sk0.PublicKey(), + signer.PublicKey(), sk1.PublicKey(), sk2.PublicKey(), } @@ -121,7 +169,7 @@ func TestAggregation(t *testing.T) { msg2 := utils.RandomBytes(1234) sigs := []*bls.Signature{ - sk0.Sign(msg), + signer.Sign(msg), sk1.Sign(msg), sk2.Sign(msg2), } @@ -131,10 +179,9 @@ func TestAggregation(t *testing.T) { expectedValid: false, }, { - name: "one incorrect pubkey", - setup: func(require *require.Assertions) ([]*bls.PublicKey, []*bls.Signature, []byte) { - sk0, err := local.NewSigner() - require.NoError(err) + name: "one incorrect pubkey", + signers: signerFns, + setup: func(require *require.Assertions, signer bls.Signer) ([]*bls.PublicKey, []*bls.Signature, []byte) { sk1, err := local.NewSigner() require.NoError(err) sk2, err := local.NewSigner() @@ -143,7 +190,7 @@ func TestAggregation(t *testing.T) { require.NoError(err) pks := []*bls.PublicKey{ - sk0.PublicKey(), + signer.PublicKey(), sk1.PublicKey(), sk3.PublicKey(), } @@ -151,7 +198,7 @@ func TestAggregation(t *testing.T) { msg := utils.RandomBytes(1234) sigs := []*bls.Signature{ - sk0.Sign(msg), + signer.Sign(msg), sk1.Sign(msg), sk2.Sign(msg), } @@ -161,17 +208,16 @@ func TestAggregation(t *testing.T) { expectedValid: false, }, { - name: "num pubkeys > num sigs", - setup: func(require *require.Assertions) ([]*bls.PublicKey, []*bls.Signature, []byte) { - sk0, err := local.NewSigner() - require.NoError(err) + name: "num pubkeys > num sigs", + signers: signerFns, + setup: func(require *require.Assertions, signer bls.Signer) ([]*bls.PublicKey, []*bls.Signature, []byte) { sk1, err := local.NewSigner() require.NoError(err) sk2, err := local.NewSigner() require.NoError(err) pks := []*bls.PublicKey{ - sk0.PublicKey(), + signer.PublicKey(), sk1.PublicKey(), sk2.PublicKey(), } @@ -179,7 +225,7 @@ func TestAggregation(t *testing.T) { msg := utils.RandomBytes(1234) sigs := []*bls.Signature{ - sk0.Sign(msg), + signer.Sign(msg), sk1.Sign(msg), } @@ -188,24 +234,23 @@ func TestAggregation(t *testing.T) { expectedValid: false, }, { - name: "num pubkeys < num sigs", - setup: func(require *require.Assertions) ([]*bls.PublicKey, []*bls.Signature, []byte) { - sk0, err := local.NewSigner() - require.NoError(err) + name: "num pubkeys < num sigs", + signers: signerFns, + setup: func(require *require.Assertions, signer bls.Signer) ([]*bls.PublicKey, []*bls.Signature, []byte) { sk1, err := local.NewSigner() require.NoError(err) sk2, err := local.NewSigner() require.NoError(err) pks := []*bls.PublicKey{ - sk0.PublicKey(), + signer.PublicKey(), sk1.PublicKey(), } msg := utils.RandomBytes(1234) sigs := []*bls.Signature{ - sk0.Sign(msg), + signer.Sign(msg), sk1.Sign(msg), sk2.Sign(msg), } @@ -215,10 +260,9 @@ func TestAggregation(t *testing.T) { expectedValid: false, }, { - name: "no pub keys", - setup: func(require *require.Assertions) ([]*bls.PublicKey, []*bls.Signature, []byte) { - sk0, err := local.NewSigner() - require.NoError(err) + name: "no pub keys", + signers: signerFns, + setup: func(require *require.Assertions, signer bls.Signer) ([]*bls.PublicKey, []*bls.Signature, []byte) { sk1, err := local.NewSigner() require.NoError(err) sk2, err := local.NewSigner() @@ -227,7 +271,7 @@ func TestAggregation(t *testing.T) { msg := utils.RandomBytes(1234) sigs := []*bls.Signature{ - sk0.Sign(msg), + signer.Sign(msg), sk1.Sign(msg), sk2.Sign(msg), } @@ -238,17 +282,16 @@ func TestAggregation(t *testing.T) { expectedValid: false, }, { - name: "no sigs", - setup: func(require *require.Assertions) ([]*bls.PublicKey, []*bls.Signature, []byte) { - sk0, err := local.NewSigner() - require.NoError(err) + name: "no sigs", + signers: signerFns, + setup: func(require *require.Assertions, signer bls.Signer) ([]*bls.PublicKey, []*bls.Signature, []byte) { sk1, err := local.NewSigner() require.NoError(err) sk2, err := local.NewSigner() require.NoError(err) pks := []*bls.PublicKey{ - sk0.PublicKey(), + signer.PublicKey(), sk1.PublicKey(), sk2.PublicKey(), } @@ -263,18 +306,24 @@ func TestAggregation(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - require := require.New(t) + for _, newSigner := range tt.signers { + require := require.New(t) - pks, sigs, msg := tt.setup(require) + signer, err := newSigner() + require.NoError(err) - aggSig, err := bls.AggregateSignatures(sigs) - require.ErrorIs(err, tt.expectedSigAggError) + pks, sigs, msg := tt.setup(require, signer) - aggPK, err := bls.AggregatePublicKeys(pks) - require.ErrorIs(err, tt.expectedPubKeyAggError) + aggSig, err := bls.AggregateSignatures(sigs) + require.ErrorIs(err, tt.expectedSigAggError) - valid := bls.Verify(aggPK, aggSig, msg) - require.Equal(tt.expectedValid, valid) + aggPK, err := bls.AggregatePublicKeys(pks) + require.ErrorIs(err, tt.expectedPubKeyAggError) + + valid := bls.Verify(aggPK, aggSig, msg) + require.Equal(tt.expectedValid, valid) + signer.Close() + } }) } } @@ -283,9 +332,12 @@ func TestAggregationThreshold(t *testing.T) { require := require.New(t) // People in the network would privately generate their secret keys - sk0, err := local.NewSigner() + sk0, err := localSignerFn() require.NoError(err) - sk1, err := local.NewSigner() + sk1, err := serverSignerFn() + require.NoError(err) + defer sk1.Close() + require.NoError(err) sk2, err := local.NewSigner() require.NoError(err) @@ -342,31 +394,28 @@ func TestAggregationThreshold(t *testing.T) { func TestVerify(t *testing.T) { type test struct { name string - setup func(*require.Assertions) (pk *bls.PublicKey, sig *bls.Signature, msg []byte) + signers []func() (Signer, error) + setup func(*require.Assertions, bls.Signer) (pk *bls.PublicKey, sig *bls.Signature, msg []byte) expectedValid bool } tests := []test{ { name: "valid", - setup: func(require *require.Assertions) (*bls.PublicKey, *bls.Signature, []byte) { - sk, err := local.NewSigner() - require.NoError(err) - pk := sk.PublicKey() + setup: func(require *require.Assertions, signer bls.Signer) (*bls.PublicKey, *bls.Signature, []byte) { + pk := signer.PublicKey() msg := utils.RandomBytes(1234) - sig := sk.Sign(msg) + sig := signer.Sign(msg) return pk, sig, msg }, expectedValid: true, }, { name: "wrong message", - setup: func(require *require.Assertions) (*bls.PublicKey, *bls.Signature, []byte) { - sk, err := local.NewSigner() - require.NoError(err) - pk := sk.PublicKey() + setup: func(require *require.Assertions, signer bls.Signer) (*bls.PublicKey, *bls.Signature, []byte) { + pk := signer.PublicKey() msg := utils.RandomBytes(1234) - sig := sk.Sign(msg) + sig := signer.Sign(msg) msg[0]++ return pk, sig, msg }, @@ -374,11 +423,9 @@ func TestVerify(t *testing.T) { }, { name: "wrong pub key", - setup: func(require *require.Assertions) (*bls.PublicKey, *bls.Signature, []byte) { - sk, err := local.NewSigner() - require.NoError(err) + setup: func(require *require.Assertions, signer bls.Signer) (*bls.PublicKey, *bls.Signature, []byte) { msg := utils.RandomBytes(1234) - sig := sk.Sign(msg) + sig := signer.Sign(msg) sk2, err := local.NewSigner() require.NoError(err) @@ -389,14 +436,12 @@ func TestVerify(t *testing.T) { }, { name: "wrong sig", - setup: func(require *require.Assertions) (*bls.PublicKey, *bls.Signature, []byte) { - sk, err := local.NewSigner() - require.NoError(err) - pk := sk.PublicKey() + setup: func(require *require.Assertions, signer bls.Signer) (*bls.PublicKey, *bls.Signature, []byte) { + pk := signer.PublicKey() msg := utils.RandomBytes(1234) msg2 := utils.RandomBytes(1234) - sig2 := sk.Sign(msg2) + sig2 := signer.Sign(msg2) return pk, sig2, msg }, expectedValid: false, @@ -405,12 +450,17 @@ func TestVerify(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - pk, sig, msg := tt.setup(require) - valid := bls.Verify(pk, sig, msg) - require.Equal(tt.expectedValid, valid) - valid = bls.VerifyProofOfPossession(pk, sig, msg) - require.False(valid) + for _, newSigner := range tt.signers { + require := require.New(t) + signer, err := newSigner() + require.NoError(err) + pk, sig, msg := tt.setup(require, signer) + valid := bls.Verify(pk, sig, msg) + require.Equal(tt.expectedValid, valid) + valid = bls.VerifyProofOfPossession(pk, sig, msg) + require.False(valid) + signer.Close() + } }) } } @@ -418,17 +468,15 @@ func TestVerify(t *testing.T) { func TestVerifyProofOfPossession(t *testing.T) { type test struct { name string - signers []func() (bls.Signer, error) + signers []func() (Signer, error) setup func(*require.Assertions, bls.Signer) (pk *bls.PublicKey, sig *bls.Signature, msg []byte) expectedValid bool } tests := []test{ { - name: "valid", - signers: []func() (bls.Signer, error){ - func() (bls.Signer, error) { return local.NewSigner() }, - }, + name: "valid", + signers: signerFns, setup: func(require *require.Assertions, signer bls.Signer) (*bls.PublicKey, *bls.Signature, []byte) { pk := signer.PublicKey() msg := utils.RandomBytes(1234) @@ -438,10 +486,8 @@ func TestVerifyProofOfPossession(t *testing.T) { expectedValid: true, }, { - name: "wrong message", - signers: []func() (bls.Signer, error){ - func() (bls.Signer, error) { return local.NewSigner() }, - }, + name: "wrong message", + signers: signerFns, setup: func(require *require.Assertions, signer bls.Signer) (*bls.PublicKey, *bls.Signature, []byte) { pk := signer.PublicKey() msg := utils.RandomBytes(1234) @@ -452,10 +498,8 @@ func TestVerifyProofOfPossession(t *testing.T) { expectedValid: false, }, { - name: "wrong pub key", - signers: []func() (bls.Signer, error){ - func() (bls.Signer, error) { return local.NewSigner() }, - }, + name: "wrong pub key", + signers: signerFns, setup: func(require *require.Assertions, signer bls.Signer) (*bls.PublicKey, *bls.Signature, []byte) { msg := utils.RandomBytes(1234) sig := signer.SignProofOfPossession(msg) @@ -468,10 +512,8 @@ func TestVerifyProofOfPossession(t *testing.T) { expectedValid: false, }, { - name: "wrong sig", - signers: []func() (bls.Signer, error){ - func() (bls.Signer, error) { return local.NewSigner() }, - }, + name: "wrong sig", + signers: signerFns, setup: func(_ *require.Assertions, signer bls.Signer) (*bls.PublicKey, *bls.Signature, []byte) { pk := signer.PublicKey() msg := utils.RandomBytes(1234) @@ -496,6 +538,7 @@ func TestVerifyProofOfPossession(t *testing.T) { require.Equal(tt.expectedValid, valid) valid = bls.Verify(pk, sig, msg) require.False(valid) + signer.Close() } }) }