From b9b81130df2dee87fc4d1da18910558e20e43b73 Mon Sep 17 00:00:00 2001 From: cce <51567+cce@users.noreply.github.com> Date: Wed, 1 May 2024 11:28:49 -0400 Subject: [PATCH] convert BatchVerifier to interface and update default config --- crypto/batchverifier.go | 62 +++++++++++++++++++++------- crypto/batchverifier_test.go | 2 +- crypto/multisig.go | 2 +- crypto/onetimesig.go | 19 ++++++++- data/transactions/verify/txn.go | 8 ++-- data/transactions/verify/txnBatch.go | 2 +- 6 files changed, 71 insertions(+), 24 deletions(-) diff --git a/crypto/batchverifier.go b/crypto/batchverifier.go index 0281e86e87..661e382898 100644 --- a/crypto/batchverifier.go +++ b/crypto/batchverifier.go @@ -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") @@ -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() @@ -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) @@ -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 } @@ -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) @@ -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) diff --git a/crypto/batchverifier_test.go b/crypto/batchverifier_test.go index 2b8fcec0e9..3922105aa5 100644 --- a/crypto/batchverifier_test.go +++ b/crypto/batchverifier_test.go @@ -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) diff --git a/crypto/multisig.go b/crypto/multisig.go index d5529c149f..f17833f1a0 100644 --- a/crypto/multisig.go +++ b/crypto/multisig.go @@ -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{}) { diff --git a/crypto/onetimesig.go b/crypto/onetimesig.go index bc11070125..d05ccaa961 100644 --- a/crypto/onetimesig.go +++ b/crypto/onetimesig.go @@ -324,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 @@ -336,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)}, diff --git a/data/transactions/verify/txn.go b/data/transactions/verify/txn.go index 640189cc57..a345c679af 100644 --- a/data/transactions/verify/txn.go +++ b/data/transactions/verify/txn.go @@ -163,7 +163,7 @@ func (g *GroupContext) Equal(other *GroupContext) bool { // txnBatchPrep verifies a SignedTxn having no obviously inconsistent data. // Block-assembly time checks of LogicSig and accounting rules may still block the txn. // It is the caller responsibility to call batchVerifier.Verify(). -func txnBatchPrep(gi int, groupCtx *GroupContext, verifier *crypto.BatchVerifier) *TxGroupError { +func txnBatchPrep(gi int, groupCtx *GroupContext, verifier crypto.BatchVerifier) *TxGroupError { s := &groupCtx.signedGroupTxns[gi] if !groupCtx.consensusParams.SupportRekeying && (s.AuthAddr != basics.Address{}) { return &TxGroupError{err: errRekeyingNotSupported, GroupIndex: gi, Reason: TxGroupErrorReasonGeneric} @@ -206,7 +206,7 @@ func txnGroup(stxs []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader // txnGroupBatchPrep verifies a []SignedTxn having no obviously inconsistent data. // it is the caller responsibility to call batchVerifier.Verify() -func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader, ledger logic.LedgerForSignature, verifier *crypto.BatchVerifier, evalTracer logic.EvalTracer) (*GroupContext, error) { +func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr *bookkeeping.BlockHeader, ledger logic.LedgerForSignature, verifier crypto.BatchVerifier, evalTracer logic.EvalTracer) (*GroupContext, error) { groupCtx, err := PrepareGroupContext(stxs, contextHdr, ledger, evalTracer) if err != nil { return nil, err @@ -287,7 +287,7 @@ func checkTxnSigTypeCounts(s *transactions.SignedTxn, groupIndex int) (sigType s } // stxnCoreChecks runs signatures validity checks and enqueues signature into batchVerifier for verification. -func stxnCoreChecks(gi int, groupCtx *GroupContext, batchVerifier *crypto.BatchVerifier) *TxGroupError { +func stxnCoreChecks(gi int, groupCtx *GroupContext, batchVerifier crypto.BatchVerifier) *TxGroupError { s := &groupCtx.signedGroupTxns[gi] sigType, err := checkTxnSigTypeCounts(s, gi) if err != nil { @@ -340,7 +340,7 @@ func LogicSigSanityCheck(gi int, groupCtx *GroupContext) error { // logicSigSanityCheckBatchPrep checks that the signature is valid and that the program is basically well formed. // It does not evaluate the logic. // it is the caller responsibility to call batchVerifier.Verify() -func logicSigSanityCheckBatchPrep(gi int, groupCtx *GroupContext, batchVerifier *crypto.BatchVerifier) error { +func logicSigSanityCheckBatchPrep(gi int, groupCtx *GroupContext, batchVerifier crypto.BatchVerifier) error { if groupCtx.consensusParams.LogicSigVersion == 0 { return errors.New("LogicSig not enabled") } diff --git a/data/transactions/verify/txnBatch.go b/data/transactions/verify/txnBatch.go index 4b309f6946..8619208da8 100644 --- a/data/transactions/verify/txnBatch.go +++ b/data/transactions/verify/txnBatch.go @@ -170,7 +170,7 @@ func (tbp *txnSigBatchProcessor) ProcessBatch(txns []execpool.InputJob) { tbp.postProcessVerifiedJobs(ctx, failed, err) } -func (tbp *txnSigBatchProcessor) preProcessUnverifiedTxns(uTxns []execpool.InputJob) (batchVerifier *crypto.BatchVerifier, ctx interface{}) { +func (tbp *txnSigBatchProcessor) preProcessUnverifiedTxns(uTxns []execpool.InputJob) (batchVerifier crypto.BatchVerifier, ctx interface{}) { batchVerifier = crypto.MakeBatchVerifier() bl := makeBatchLoad(len(uTxns)) // TODO: separate operations here, and get the sig verification inside the LogicSig to the batch here