Skip to content

Commit

Permalink
Add batch verification (algorand#2578)
Browse files Browse the repository at this point in the history
Add the framework for supporting batch verification.
  • Loading branch information
id-ms authored Aug 7, 2021
1 parent 2ead144 commit b442369
Show file tree
Hide file tree
Showing 5 changed files with 550 additions and 80 deletions.
98 changes: 98 additions & 0 deletions batchverifier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright (C) 2019-2021 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.

package crypto

import "errors"

// BatchVerifier enqueues signatures to be validated in batch.
type BatchVerifier struct {
messages []Hashable // contains a slice of messages to be hashed. Each message is varible length
publicKeys []SignatureVerifier // contains a slice of public keys. Each individual public key is 32 bytes.
signatures []Signature // contains a slice of signatures keys. Each individual signature is 64 bytes.
}

const minBatchVerifierAlloc = 16

// Batch verifications errors
var (
ErrBatchVerificationFailed = errors.New("At least on signature didn't pass verification")
ErrZeroTranscationsInBatch = errors.New("Could not validate empty signature set")
)

// MakeBatchVerifierDefaultSize create a BatchVerifier instance. This function pre-allocates
// amount of free space to enqueue signatures without exapneding
func MakeBatchVerifierDefaultSize() *BatchVerifier {
return MakeBatchVerifier(minBatchVerifierAlloc)
}

// MakeBatchVerifier create a BatchVerifier instance. This function pre-allocates
// a given space so it will not expaned the storage
func MakeBatchVerifier(hint int) *BatchVerifier {
// preallocate enough storage for the expected usage. We will reallocate as needed.
if hint < minBatchVerifierAlloc {
hint = minBatchVerifierAlloc
}
return &BatchVerifier{
messages: make([]Hashable, 0, hint),
publicKeys: make([]SignatureVerifier, 0, hint),
signatures: make([]Signature, 0, hint),
}
}

// EnqueueSignature enqueues a signature to be enqueued
func (b *BatchVerifier) EnqueueSignature(sigVerifier SignatureVerifier, message Hashable, sig Signature) {
// do we need to reallocate ?
if len(b.messages) == cap(b.messages) {
b.expand()
}
b.messages = append(b.messages, message)
b.publicKeys = append(b.publicKeys, sigVerifier)
b.signatures = append(b.signatures, sig)
}

func (b *BatchVerifier) expand() {
messages := make([]Hashable, len(b.messages), len(b.messages)*2)
publicKeys := make([]SignatureVerifier, len(b.publicKeys), len(b.publicKeys)*2)
signatures := make([]Signature, len(b.signatures), len(b.signatures)*2)
copy(messages, b.messages)
copy(publicKeys, b.publicKeys)
copy(signatures, b.signatures)
b.messages = messages
b.publicKeys = publicKeys
b.signatures = signatures
}

// GetNumberOfEnqueuedSignatures returns the number of signatures current enqueue onto the bacth verifier object
func (b *BatchVerifier) GetNumberOfEnqueuedSignatures() int {
return len(b.messages)
}

// Verify verifies that all the signatures are valid. in that case nil is returned
// if the batch is zero an appropriate error is return.
func (b *BatchVerifier) Verify() error {
if b.GetNumberOfEnqueuedSignatures() == 0 {
return ErrZeroTranscationsInBatch
}

for i := range b.messages {
verifier := SignatureVerifier(b.publicKeys[i])
if !verifier.Verify(b.messages[i], b.signatures[i]) {
return ErrBatchVerificationFailed
}
}
return nil
}
119 changes: 119 additions & 0 deletions batchverifier_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright (C) 2019-2021 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.

package crypto

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestBatchVerifierSingle(t *testing.T) {
// test expected success
bv := MakeBatchVerifier(1)
msg := randString()
var s Seed
RandBytes(s[:])
sigSecrets := GenerateSignatureSecrets(s)
sig := sigSecrets.Sign(msg)
bv.EnqueueSignature(sigSecrets.SignatureVerifier, msg, sig)
require.NoError(t, bv.Verify())

// test expected failuire
bv = MakeBatchVerifier(1)
msg = randString()
RandBytes(s[:])
sigSecrets = GenerateSignatureSecrets(s)
sig = sigSecrets.Sign(msg)
// break the signature:
sig[0] = sig[0] + 1
bv.EnqueueSignature(sigSecrets.SignatureVerifier, msg, sig)
require.Error(t, bv.Verify())
}

func TestBatchVerifierBulk(t *testing.T) {
for i := 1; i < 64*2+3; i++ {
n := i
bv := MakeBatchVerifier(n)
var s Seed

for i := 0; i < n; i++ {
msg := randString()
RandBytes(s[:])
sigSecrets := GenerateSignatureSecrets(s)
sig := sigSecrets.Sign(msg)
bv.EnqueueSignature(sigSecrets.SignatureVerifier, msg, sig)
}
require.Equal(t, n, bv.GetNumberOfEnqueuedSignatures())
require.NoError(t, bv.Verify())
}

}

func TestBatchVerifierBulkWithExpand(t *testing.T) {
n := 64
bv := MakeBatchVerifier(1)
var s Seed
RandBytes(s[:])

for i := 0; i < n; i++ {
msg := randString()
sigSecrets := GenerateSignatureSecrets(s)
sig := sigSecrets.Sign(msg)
bv.EnqueueSignature(sigSecrets.SignatureVerifier, msg, sig)
}
require.NoError(t, bv.Verify())
}

func TestBatchVerifierWithInvalidSiganture(t *testing.T) {
n := 64
bv := MakeBatchVerifier(1)
var s Seed
RandBytes(s[:])

for i := 0; i < n-1; i++ {
msg := randString()
sigSecrets := GenerateSignatureSecrets(s)
sig := sigSecrets.Sign(msg)
bv.EnqueueSignature(sigSecrets.SignatureVerifier, msg, sig)
}

msg := randString()
sigSecrets := GenerateSignatureSecrets(s)
sig := sigSecrets.Sign(msg)
sig[0] = sig[0] + 1
bv.EnqueueSignature(sigSecrets.SignatureVerifier, msg, sig)

require.Error(t, bv.Verify())
}

func BenchmarkBatchVerifier(b *testing.B) {
c := makeCurve25519Secret()
bv := MakeBatchVerifier(1)
for i := 0; i < b.N; i++ {
str := randString()
bv.EnqueueSignature(c.SignatureVerifier, str, c.Sign(str))
}

b.ResetTimer()
require.NoError(b, bv.Verify())
}

func TestEmpty(t *testing.T) {
bv := MakeBatchVerifierDefaultSize()
require.Error(t, bv.Verify())
}
19 changes: 11 additions & 8 deletions cryptoerror.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ package crypto

import "errors"

const errorinvalidversion = "Invalid version"
const errorinvalidaddress = "Invalid address"
const errorinvalidthreshold = "Invalid threshold"
const errorinvalidnumberofsignature = "Invalid number of signatures"
const errorkeynotexist = "Key does not exist"
const errorsubsigverification = "Verification failure: subsignature"
const errorkeysnotmatch = "Public key lists do not match"
const errorinvalidduplicates = "Invalid duplicates"
var (
errInvalidVersion = errors.New("Invalid version")
errInvalidAddress = errors.New("Invalid address")
errInvalidThreshold = errors.New("Invalid threshold")
errInvalidNumberOfSignature = errors.New("Invalid number of signatures")
errKeyNotExist = errors.New("Key does not exist")
errSubsigVerification = errors.New("Verification failure: subsignature")
errKeysNotMatch = errors.New("Public key lists do not match")
errInvalidDuplicates = errors.New("Invalid duplicates")
errInvalidNumberOfSig = errors.New("invalid number of signatures to add")
)

var errUnknownVersion = errors.New("unknown version")
Loading

0 comments on commit b442369

Please sign in to comment.