Skip to content

Commit

Permalink
Merge pull request algorand#6000 from Algo-devops-service/relstable3.…
Browse files Browse the repository at this point in the history
…24.0
  • Loading branch information
algojohnlee authored May 15, 2024
2 parents 78c8bd9 + 3b61030 commit 9e22848
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 21 deletions.
62 changes: 46 additions & 16 deletions batchverifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,20 @@ import (
)

// BatchVerifier enqueues signatures to be validated in batch.
type BatchVerifier struct {
type BatchVerifier interface {
EnqueueSignature(sigVerifier SignatureVerifier, message Hashable, sig Signature)
GetNumberOfEnqueuedSignatures() int
Verify() error
VerifyWithFeedback() (failed []bool, err error)
}

type cgoBatchVerifier 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.
useSingle bool
}

const minBatchVerifierAlloc = 16

// Batch verifications errors
var (
ErrBatchHasFailedSigs = errors.New("At least one signature didn't pass verification")
Expand All @@ -69,27 +75,31 @@ func ed25519_randombytes_unsafe(p unsafe.Pointer, len C.size_t) {
RandBytes(randBuf)
}

// MakeBatchVerifier creates a BatchVerifier instance.
func MakeBatchVerifier() *BatchVerifier {
const minBatchVerifierAlloc = 16
const useSingleVerifierDefault = true

// MakeBatchVerifier creates a BatchVerifier instance with the provided options.
func MakeBatchVerifier() BatchVerifier {
return MakeBatchVerifierWithHint(minBatchVerifierAlloc)
}

// MakeBatchVerifierWithHint creates a BatchVerifier instance. This function pre-allocates
// MakeBatchVerifierWithHint creates a cgoBatchVerifier instance. This function pre-allocates
// amount of free space to enqueue signatures without expanding
func MakeBatchVerifierWithHint(hint int) *BatchVerifier {
func MakeBatchVerifierWithHint(hint int) BatchVerifier {
// preallocate enough storage for the expected usage. We will reallocate as needed.
if hint < minBatchVerifierAlloc {
hint = minBatchVerifierAlloc
}
return &BatchVerifier{
return &cgoBatchVerifier{
messages: make([]Hashable, 0, hint),
publicKeys: make([]SignatureVerifier, 0, hint),
signatures: make([]Signature, 0, hint),
useSingle: useSingleVerifierDefault,
}
}

// EnqueueSignature enqueues a signature to be enqueued
func (b *BatchVerifier) EnqueueSignature(sigVerifier SignatureVerifier, message Hashable, sig Signature) {
func (b *cgoBatchVerifier) EnqueueSignature(sigVerifier SignatureVerifier, message Hashable, sig Signature) {
// do we need to reallocate ?
if len(b.messages) == cap(b.messages) {
b.expand()
Expand All @@ -99,7 +109,7 @@ func (b *BatchVerifier) EnqueueSignature(sigVerifier SignatureVerifier, message
b.signatures = append(b.signatures, sig)
}

func (b *BatchVerifier) expand() {
func (b *cgoBatchVerifier) 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)
Expand All @@ -112,12 +122,12 @@ func (b *BatchVerifier) expand() {
}

// GetNumberOfEnqueuedSignatures returns the number of signatures currently enqueued into the BatchVerifier
func (b *BatchVerifier) GetNumberOfEnqueuedSignatures() int {
func (b *cgoBatchVerifier) GetNumberOfEnqueuedSignatures() int {
return len(b.messages)
}

// Verify verifies that all the signatures are valid. in that case nil is returned
func (b *BatchVerifier) Verify() error {
func (b *cgoBatchVerifier) Verify() error {
_, err := b.VerifyWithFeedback()
return err
}
Expand All @@ -126,11 +136,15 @@ func (b *BatchVerifier) Verify() error {
// if all sigs are valid, nil will be returned for err (failed will have all false)
// if some signatures are invalid, true will be set in failed at the corresponding indexes, and
// ErrBatchVerificationFailed for err
func (b *BatchVerifier) VerifyWithFeedback() (failed []bool, err error) {
func (b *cgoBatchVerifier) VerifyWithFeedback() (failed []bool, err error) {
if len(b.messages) == 0 {
return nil, nil
}

if b.useSingle {
return b.singleVerify()
}

const estimatedMessageSize = 64
msgLengths := make([]uint64, 0, len(b.messages))
var messages = make([]byte, 0, len(b.messages)*estimatedMessageSize)
Expand All @@ -141,17 +155,33 @@ func (b *BatchVerifier) VerifyWithFeedback() (failed []bool, err error) {
msgLengths = append(msgLengths, uint64(len(messages)-lenWas))
lenWas = len(messages)
}
allValid, failed := batchVerificationImpl(messages, msgLengths, b.publicKeys, b.signatures)
allValid, failed := cgoBatchVerificationImpl(messages, msgLengths, b.publicKeys, b.signatures)
if allValid {
return failed, nil
}
return failed, ErrBatchHasFailedSigs
}

// batchVerificationImpl invokes the ed25519 batch verification algorithm.
func (b *cgoBatchVerifier) singleVerify() (failed []bool, err error) {
failed = make([]bool, len(b.messages))
var containsFailed bool

for i := range b.messages {
failed[i] = !ed25519Verify(ed25519PublicKey(b.publicKeys[i]), HashRep(b.messages[i]), ed25519Signature(b.signatures[i]))
if failed[i] {
containsFailed = true
}
}
if containsFailed {
return failed, ErrBatchHasFailedSigs
}
return failed, nil
}

// cgoBatchVerificationImpl invokes the ed25519 batch verification algorithm.
// it returns true if all the signatures were authentically signed by the owners
// otherwise, returns false, and sets the indexes of the failed sigs in failed
func batchVerificationImpl(messages []byte, msgLengths []uint64, publicKeys []SignatureVerifier, signatures []Signature) (allSigsValid bool, failed []bool) {
func cgoBatchVerificationImpl(messages []byte, msgLengths []uint64, publicKeys []SignatureVerifier, signatures []Signature) (allSigsValid bool, failed []bool) {

numberOfSignatures := len(msgLengths)
valid := make([]C.int, numberOfSignatures)
Expand Down
2 changes: 1 addition & 1 deletion batchverifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ func BenchmarkBatchVerifierBigWithInvalid(b *testing.B) {
failed, err := bv.VerifyWithFeedback()
if err != nil {
for i, f := range failed {
if bv.signatures[i] == badSig {
if bv.(*cgoBatchVerifier).signatures[i] == badSig {
require.True(b, f)
} else {
require.False(b, f)
Expand Down
2 changes: 1 addition & 1 deletion multisig.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ func MultisigVerify(msg Hashable, addr Digest, sig MultisigSig) (err error) {

// MultisigBatchPrep performs checks on the assembled MultisigSig and adds to the batch.
// The caller must call batchVerifier.verify() to verify it.
func MultisigBatchPrep(msg Hashable, addr Digest, sig MultisigSig, batchVerifier *BatchVerifier) error {
func MultisigBatchPrep(msg Hashable, addr Digest, sig MultisigSig, batchVerifier BatchVerifier) error {
// short circuit: if msig doesn't have subsigs or if Subsigs are empty
// then terminate (the upper layer should now verify the unisig)
if (len(sig.Subsigs) == 0 || sig.Subsigs[0] == MultisigSubsig{}) {
Expand Down
24 changes: 23 additions & 1 deletion onetimesig.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,11 @@ func (s *OneTimeSignatureSecrets) Sign(id OneTimeSignatureIdentifier, message Ha
return OneTimeSignature{}
}

// IsEmpty returns true if the verifier is empty/zero'd.
func (v OneTimeSignatureVerifier) IsEmpty() bool {
return v == OneTimeSignatureVerifier{}
}

// Verify verifies that some Hashable signature was signed under some
// OneTimeSignatureVerifier and some OneTimeSignatureIdentifier.
//
Expand All @@ -319,6 +324,23 @@ func (v OneTimeSignatureVerifier) Verify(id OneTimeSignatureIdentifier, message
Batch: id.Batch,
}

if !useSingleVerifierDefault {
return v.batchVerify(batchID, offsetID, message, sig)
}

if !ed25519Verify(ed25519PublicKey(v), HashRep(batchID), sig.PK2Sig) {
return false
}
if !ed25519Verify(batchID.SubKeyPK, HashRep(offsetID), sig.PK1Sig) {
return false
}
if !ed25519Verify(offsetID.SubKeyPK, HashRep(message), sig.Sig) {
return false
}
return true
}

func (v OneTimeSignatureVerifier) batchVerify(batchID OneTimeSignatureSubkeyBatchID, offsetID OneTimeSignatureSubkeyOffsetID, message Hashable, sig OneTimeSignature) bool {
// serialize encoded batchID, offsetID, message into a continuous memory buffer with the layout
// hashRep(batchID)... hashRep(offsetID)... hashRep(message)...
const estimatedSize = 256
Expand All @@ -331,7 +353,7 @@ func (v OneTimeSignatureVerifier) Verify(id OneTimeSignatureIdentifier, message
messageBuffer = HashRepToBuff(message, messageBuffer)
messageLen := uint64(len(messageBuffer)) - offsetIDLen - batchIDLen
msgLengths := []uint64{batchIDLen, offsetIDLen, messageLen}
allValid, _ := batchVerificationImpl(
allValid, _ := cgoBatchVerificationImpl(
messageBuffer,
msgLengths,
[]PublicKey{PublicKey(v), PublicKey(batchID.SubKeyPK), PublicKey(offsetID.SubKeyPK)},
Expand Down
3 changes: 1 addition & 2 deletions secp256k1/secp256_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package secp256k1
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"encoding/hex"
"io"
Expand All @@ -23,7 +22,7 @@ func generateKeyPair() (pubkey, privkey []byte) {
if err != nil {
panic(err)
}
pubkey = elliptic.Marshal(S256(), key.X, key.Y)
pubkey = S256().Marshal(key.X, key.Y)

privkey = make([]byte, 32)
blob := key.D.Bytes()
Expand Down
5 changes: 5 additions & 0 deletions vrf.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ func (pk VrfPubkey) verifyBytes(proof VrfProof, msg []byte) (bool, VrfOutput) {
return ret == 0, out
}

// IsEmpty returns true if the key is empty/zero'd.
func (pk VrfPubkey) IsEmpty() bool {
return pk == VrfPubkey{}
}

// Verify checks a VRF proof of a given Hashable. If the proof is valid the pseudorandom VrfOutput will be returned.
// For a given public key and message, there are potentially multiple valid proofs.
// However, given a public key and message, all valid proofs will yield the same output.
Expand Down

0 comments on commit 9e22848

Please sign in to comment.