Skip to content

Commit

Permalink
Merge pull request #69 from oasisprotocol/feature/batch-verifier-reuse
Browse files Browse the repository at this point in the history
primitives/[ed,sr]25519: BatchVerifier improvements for large batches
  • Loading branch information
Yawning committed Jun 29, 2021
2 parents 0a56a4b + 3263885 commit fc681bf
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 0 deletions.
26 changes: 26 additions & 0 deletions primitives/ed25519/batch_verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
47 changes: 47 additions & 0 deletions primitives/ed25519/batch_verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
25 changes: 25 additions & 0 deletions primitives/sr25519/batch_verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
49 changes: 49 additions & 0 deletions primitives/sr25519/batch_verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit fc681bf

Please sign in to comment.