Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto: convert BatchVerifier to interface #5988

Merged
merged 1 commit into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
)

// 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 @@
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 @@
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 @@
}

// 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 @@
// 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 @@
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)

Check warning on line 158 in crypto/batchverifier.go

View check run for this annotation

Codecov / codecov/patch

crypto/batchverifier.go#L158

Added line #L158 was not covered by tests
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) {

Check warning on line 184 in crypto/batchverifier.go

View check run for this annotation

Codecov / codecov/patch

crypto/batchverifier.go#L184

Added line #L184 was not covered by tests

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 @@
Batch: id.Batch,
}

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

Check warning on line 328 in crypto/onetimesig.go

View check run for this annotation

Codecov / codecov/patch

crypto/onetimesig.go#L328

Added line #L328 was not covered by tests
}

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
algorandskiy marked this conversation as resolved.
Show resolved Hide resolved
}

func (v OneTimeSignatureVerifier) batchVerify(batchID OneTimeSignatureSubkeyBatchID, offsetID OneTimeSignatureSubkeyOffsetID, message Hashable, sig OneTimeSignature) bool {

Check warning on line 343 in crypto/onetimesig.go

View check run for this annotation

Codecov / codecov/patch

crypto/onetimesig.go#L343

Added line #L343 was not covered by tests
// 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 @@
messageBuffer = HashRepToBuff(message, messageBuffer)
messageLen := uint64(len(messageBuffer)) - offsetIDLen - batchIDLen
msgLengths := []uint64{batchIDLen, offsetIDLen, messageLen}
allValid, _ := batchVerificationImpl(
allValid, _ := cgoBatchVerificationImpl(

Check warning on line 356 in crypto/onetimesig.go

View check run for this annotation

Codecov / codecov/patch

crypto/onetimesig.go#L356

Added line #L356 was not covered by tests
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
Loading