Skip to content

Commit

Permalink
crypto: convert BatchVerifier to interface (#5988)
Browse files Browse the repository at this point in the history
  • Loading branch information
cce authored May 7, 2024
1 parent e07ad9c commit 6c91f52
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 24 deletions.
62 changes: 46 additions & 16 deletions crypto/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 crypto/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 crypto/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
19 changes: 18 additions & 1 deletion crypto/onetimesig.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)},
Expand Down
8 changes: 4 additions & 4 deletions data/transactions/verify/txn.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
}
Expand Down
2 changes: 1 addition & 1 deletion data/transactions/verify/txnBatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 6c91f52

Please sign in to comment.