From b856d0fb0dcee1d2143f84ea365026c9190bf949 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Mon, 28 Jun 2021 11:35:17 +0000 Subject: [PATCH 1/2] primitives/ed25519: Add routines for BatchVerifier reuse * `BatchVerifier.Reset()` which re-slices `v.entries` to allow the backing store to be reused. * `NewBatchVerifierWithCapacity(n int)` which preallocates `v.entries` to the desired capacity. --- primitives/ed25519/batch_verify.go | 26 ++++++++++++++ primitives/ed25519/batch_verify_test.go | 47 +++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/primitives/ed25519/batch_verify.go b/primitives/ed25519/batch_verify.go index 39b664e..0c1ee54 100644 --- a/primitives/ed25519/batch_verify.go +++ b/primitives/ed25519/batch_verify.go @@ -332,7 +332,33 @@ func (v *BatchVerifier) Verify(rand io.Reader) (bool, []bool) { return allValid, valid } +// Reset resets a batch for reuse. +// +// Note: This method will reuse the existing entires slice to reduce memory +// reallocations. If the next batch is known to be significantly smaller +// it may be more memory efficient to simply create a new batch. +func (v *BatchVerifier) Reset() *BatchVerifier { + // Allow re-using the existing entries slice. + v.entries = v.entries[:0] + + v.anyInvalid = false + v.anyCofactorless = false + + return v +} + // NewBatchVerfier creates an empty BatchVerifier. func NewBatchVerifier() *BatchVerifier { return &BatchVerifier{} } + +// NewBatchVerifierWithCapacity creates an empty BatchVerifier, with +// preallocations done for a pre-determined batch size. +func NewBatchVerifierWithCapacity(n int) *BatchVerifier { + v := NewBatchVerifier() + if n > 0 { + v.entries = make([]entry, 0, n) + } + + return v +} diff --git a/primitives/ed25519/batch_verify_test.go b/primitives/ed25519/batch_verify_test.go index 3ae4e92..ac88e1b 100644 --- a/primitives/ed25519/batch_verify_test.go +++ b/primitives/ed25519/batch_verify_test.go @@ -339,6 +339,53 @@ func TestBatchVerifier(t *testing.T) { t.Error("batch verification should fail on an empty batch") } }) + t.Run("Reset", func(t *testing.T) { + v := NewBatchVerifier() + + // Reseting an empty batch verifier should work. + v.Reset() + + pub, priv, err := GenerateKey(nil) + if err != nil { + t.Fatalf("failed to GenerateKey: %v", err) + } + msg := []byte("ResetTest") + sig := Sign(priv, msg) + + for i := 0; i < 10; i++ { + v.Add(pub, msg, sig) + } + v.Add(pub, msg, nil) + v.AddWithOptions(pub, msg, nil, &Options{ + Verify: VerifyOptionsStdLib, + }) + + v.Reset() + if len(v.entries) != 0 { + t.Fatalf("Reset did not shrink entries") + } + if cap(v.entries) == 0 { + // Can't check for an exact capacity since this is at the + // mercy of how stdlib reallocs. + t.Fatalf("Reset did not preserve entries backing store") + } + if v.anyInvalid != false { + t.Fatalf("Reset did not clear anyInvalid") + } + if v.anyCofactorless != false { + t.Fatalf("Reset did not clear anyCofactorless") + } + }) + t.Run("NewWithCapacity", func(t *testing.T) { + v := NewBatchVerifierWithCapacity(10) + + if l := len(v.entries); l != 0 { + t.Fatalf("unexpected v.entries length: %d", l) + } + if c := cap(v.entries); c != 10 { + t.Fatalf("unexpected v.entries capacity: %d", c) + } + }) } func BenchmarkVerifyBatchOnly(b *testing.B) { From 326388534dca60d491c96e1815761e0780e1d22b Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Mon, 28 Jun 2021 11:48:31 +0000 Subject: [PATCH 2/2] primitives/sr25519: Add routines for BatchVerifier reuse * `BatchVerifier.Reset()` which re-slices `v.entries` to allow the backing store to be reused. * `NewBatchVerifierWithCapacity(n int)` which preallocates `v.entries` to the desired capacity. --- primitives/sr25519/batch_verify.go | 25 +++++++++++++ primitives/sr25519/batch_verify_test.go | 49 +++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/primitives/sr25519/batch_verify.go b/primitives/sr25519/batch_verify.go index f664383..15b1952 100644 --- a/primitives/sr25519/batch_verify.go +++ b/primitives/sr25519/batch_verify.go @@ -274,7 +274,32 @@ func (v *BatchVerifier) Verify(rand io.Reader) (bool, []bool) { return allValid, valid } +// Reset resets a batch for reuse. +// +// Note: This method will reuse the existing entires slice to reduce memory +// reallocations. If the next batch is known to be significantly smaller +// it may be more memory efficient to simply create a new batch. +func (v *BatchVerifier) Reset() *BatchVerifier { + // Allow re-using the existing entries slice. + v.entries = v.entries[:0] + + v.anyInvalid = false + + return v +} + // NewBatchVerifier creates an empty BatchVerifier. func NewBatchVerifier() *BatchVerifier { return &BatchVerifier{} } + +// NewBatchVerifierWithCapacity creates an empty BatchVerifier, with +// preallocations done for a pre-determined batch size. +func NewBatchVerifierWithCapacity(n int) *BatchVerifier { + v := NewBatchVerifier() + if n > 0 { + v.entries = make([]entry, 0, n) + } + + return v +} diff --git a/primitives/sr25519/batch_verify_test.go b/primitives/sr25519/batch_verify_test.go index 6693791..55471cb 100644 --- a/primitives/sr25519/batch_verify_test.go +++ b/primitives/sr25519/batch_verify_test.go @@ -200,6 +200,55 @@ func TestBatchVerifier(t *testing.T) { t.Error("batch verification should fail on an empty batch") } }) + t.Run("Reset", func(t *testing.T) { + v := NewBatchVerifier() + + // Reseting an empty batch verifier should work. + v.Reset() + + ctx := NewSigningContext([]byte("test-batch-verify:reset")) + kp, err := GenerateKeyPair(nil) + if err != nil { + t.Fatalf("failed to GenerateKeyPair: %v", err) + } + pub := kp.PublicKey() + st := ctx.NewTranscriptBytes([]byte("ResetTest")) + sig, err := kp.Sign(nil, st) + if err != nil { + t.Fatalf("failed to Sign: %v", err) + } + + for i := 0; i < 10; i++ { + v.Add(pub, st, sig) + } + v.Add(pub, st, &Signature{}) + if v.anyInvalid == false { + t.Fatalf("Uninitialized signature did not invalidate batch") + } + + v.Reset() + if len(v.entries) != 0 { + t.Fatalf("Reset did not shrink entries") + } + if cap(v.entries) == 0 { + // Can't check for an exact capacity since this is at the + // mercy of how stdlib reallocs. + t.Fatalf("Reset did not preserve entries backing store") + } + if v.anyInvalid != false { + t.Fatalf("Reset did not clear anyInvalid") + } + }) + t.Run("NewWithCapacity", func(t *testing.T) { + v := NewBatchVerifierWithCapacity(10) + + if l := len(v.entries); l != 0 { + t.Fatalf("unexpected v.entries length: %d", l) + } + if c := cap(v.entries); c != 10 { + t.Fatalf("unexpected v.entries capacity: %d", c) + } + }) } func BenchmarkVerifyBatchOnly(b *testing.B) {