diff --git a/go.mod b/go.mod index fd114d34..bb8c4ffa 100644 --- a/go.mod +++ b/go.mod @@ -18,3 +18,6 @@ require ( golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 google.golang.org/grpc v1.24.0 ) + +// Used for debug reasons +// replace github.com/nspcc-dev/neofs-crypto => ../neofs-crypto diff --git a/service/verify.go b/service/verify.go index 85714592..2bd1661c 100644 --- a/service/verify.go +++ b/service/verify.go @@ -2,6 +2,7 @@ package service import ( "crypto/ecdsa" + "sync" crypto "github.com/nspcc-dev/neofs-crypto" "github.com/nspcc-dev/neofs-proto/internal" @@ -12,7 +13,8 @@ import ( type ( // VerifiableRequest adds possibility to sign and verify request header. VerifiableRequest interface { - Marshal() ([]byte, error) + Size() int + MarshalTo([]byte) (int, error) AddSignature(*RequestVerificationHeader_Signature) GetSignatures() []*RequestVerificationHeader_Signature SetSignatures([]*RequestVerificationHeader_Signature) @@ -133,6 +135,10 @@ func newSignature(key *ecdsa.PrivateKey, data []byte) (*RequestVerificationHeade }, nil } +var bytesPool = sync.Pool{New: func() interface{} { + return make([]byte, 4.5*1024*1024) // 4.5MB +}} + // SignRequestHeader receives private key and request with RequestVerificationHeader, // tries to marshal and sign request with passed PrivateKey, after that adds // new signature to headers. If something went wrong, returns error. @@ -146,12 +152,23 @@ func SignRequestHeader(key *ecdsa.PrivateKey, msg VerifiableRequest) error { }() } - data, err := msg.Marshal() + data := bytesPool.Get().([]byte) + defer func() { + bytesPool.Put(data) + }() + + if size := msg.Size(); size <= cap(data) { + data = data[:size] + } else { + data = make([]byte, size) + } + + size, err := msg.MarshalTo(data) if err != nil { return err } - signature, err := newSignature(key, data) + signature, err := newSignature(key, data[:size]) if err != nil { return err } @@ -174,8 +191,10 @@ func VerifyRequestHeader(msg VerifiableRequest) error { }() } + data := bytesPool.Get().([]byte) signatures := msg.GetSignatures() defer func() { + bytesPool.Put(data) msg.SetSignatures(signatures) }() @@ -189,9 +208,15 @@ func VerifyRequestHeader(msg VerifiableRequest) error { return errors.Wrapf(ErrCannotLoadPublicKey, "%d: %02x", i, peer) } - if data, err := msg.Marshal(); err != nil { + if size := msg.Size(); size <= cap(data) { + data = data[:size] + } else { + data = make([]byte, size) + } + + if size, err := msg.MarshalTo(data); err != nil { return errors.Wrapf(err, "%d: %02x", i, peer) - } else if err := crypto.Verify(key, data, sign); err != nil { + } else if err := crypto.Verify(key, data[:size], sign); err != nil { return errors.Wrapf(err, "%d: %02x", i, peer) } } diff --git a/service/verify_test.go b/service/verify_test.go index 237e3623..44542c4a 100644 --- a/service/verify_test.go +++ b/service/verify_test.go @@ -14,6 +14,57 @@ import ( "github.com/stretchr/testify/require" ) +func BenchmarkSignRequestHeader(b *testing.B) { + key := test.DecodeKey(0) + + custom := testCustomField{1, 2, 3, 4, 5, 6, 7, 8} + + some := &TestRequest{ + IntField: math.MaxInt32, + StringField: "TestRequestStringField", + BytesField: make([]byte, 1<<22), + CustomField: &custom, + RequestMetaHeader: RequestMetaHeader{ + TTL: math.MaxInt32 - 8, + Epoch: math.MaxInt64 - 12, + }, + } + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + require.NoError(b, SignRequestHeader(key, some)) + } +} + +func BenchmarkVerifyRequestHeader(b *testing.B) { + custom := testCustomField{1, 2, 3, 4, 5, 6, 7, 8} + + some := &TestRequest{ + IntField: math.MaxInt32, + StringField: "TestRequestStringField", + BytesField: make([]byte, 1<<22), + CustomField: &custom, + RequestMetaHeader: RequestMetaHeader{ + TTL: math.MaxInt32 - 8, + Epoch: math.MaxInt64 - 12, + }, + } + + for i := 0; i < 10; i++ { + key := test.DecodeKey(i) + require.NoError(b, SignRequestHeader(key, some)) + } + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + require.NoError(b, VerifyRequestHeader(some)) + } +} + func TestSignRequestHeader(t *testing.T) { req := &TestRequest{ IntField: math.MaxInt32,