schnorr_batch_test.go raw

   1  //go:build !js && !wasm && !tinygo && !wasm32
   2  
   3  package p256k1
   4  
   5  import (
   6  	"crypto/rand"
   7  	"fmt"
   8  	"testing"
   9  )
  10  
  11  func TestSchnorrBatchVerify(t *testing.T) {
  12  	// Generate test keys and signatures
  13  	testCases := []struct {
  14  		name  string
  15  		count int
  16  	}{
  17  		{"empty batch", 0},
  18  		{"single signature", 1},
  19  		{"two signatures", 2},
  20  		{"five signatures", 5},
  21  		{"ten signatures", 10},
  22  	}
  23  
  24  	for _, tc := range testCases {
  25  		t.Run(tc.name, func(t *testing.T) {
  26  			items := make([]BatchSchnorrItem, tc.count)
  27  
  28  			for i := 0; i < tc.count; i++ {
  29  				// Generate random key pair
  30  				kp, err := KeyPairGenerate()
  31  				if err != nil {
  32  					t.Fatalf("failed to create keypair: %v", err)
  33  				}
  34  				defer kp.Clear()
  35  
  36  				xonly, err := kp.XOnlyPubkey()
  37  				if err != nil {
  38  					t.Fatalf("failed to get x-only pubkey: %v", err)
  39  				}
  40  
  41  				// Generate random message
  42  				msg := make([]byte, 32)
  43  				rand.Read(msg)
  44  
  45  				// Generate random aux data
  46  				auxRand := make([]byte, 32)
  47  				rand.Read(auxRand)
  48  
  49  				// Sign the message
  50  				var sig [64]byte
  51  				if err := SchnorrSign(sig[:], msg, kp, auxRand); err != nil {
  52  					t.Fatalf("failed to sign: %v", err)
  53  				}
  54  
  55  				// Verify individual signature works
  56  				if !SchnorrVerify(sig[:], msg, xonly) {
  57  					t.Fatalf("individual signature verification failed for signature %d", i)
  58  				}
  59  
  60  				items[i] = BatchSchnorrItem{
  61  					Pubkey:    xonly,
  62  					Message:   msg,
  63  					Signature: sig[:],
  64  				}
  65  			}
  66  
  67  			// Batch verify
  68  			if !SchnorrBatchVerify(items) {
  69  				t.Errorf("batch verification failed for %d valid signatures", tc.count)
  70  			}
  71  		})
  72  	}
  73  }
  74  
  75  func TestSchnorrBatchVerifyInvalid(t *testing.T) {
  76  	// Create a batch with one invalid signature
  77  	items := make([]BatchSchnorrItem, 5)
  78  
  79  	for i := 0; i < 5; i++ {
  80  		kp, err := KeyPairGenerate()
  81  		if err != nil {
  82  			t.Fatalf("failed to create keypair: %v", err)
  83  		}
  84  		defer kp.Clear()
  85  
  86  		xonly, err := kp.XOnlyPubkey()
  87  		if err != nil {
  88  			t.Fatalf("failed to get x-only pubkey: %v", err)
  89  		}
  90  
  91  		msg := make([]byte, 32)
  92  		rand.Read(msg)
  93  
  94  		auxRand := make([]byte, 32)
  95  		rand.Read(auxRand)
  96  
  97  		var sig [64]byte
  98  		if err := SchnorrSign(sig[:], msg, kp, auxRand); err != nil {
  99  			t.Fatalf("failed to sign: %v", err)
 100  		}
 101  
 102  		items[i] = BatchSchnorrItem{
 103  			Pubkey:    xonly,
 104  			Message:   msg,
 105  			Signature: sig[:],
 106  		}
 107  	}
 108  
 109  	// Corrupt one signature
 110  	items[2].Signature[0] ^= 0xFF
 111  
 112  	// Batch should fail
 113  	if SchnorrBatchVerify(items) {
 114  		t.Error("batch verification should fail with corrupted signature")
 115  	}
 116  
 117  	// Test fallback
 118  	valid, invalidIndices := SchnorrBatchVerifyWithFallback(items)
 119  	if valid {
 120  		t.Error("fallback should report failure")
 121  	}
 122  	if len(invalidIndices) != 1 || invalidIndices[0] != 2 {
 123  		t.Errorf("expected invalid index [2], got %v", invalidIndices)
 124  	}
 125  }
 126  
 127  func TestSchnorrBatchVerifyWrongMessage(t *testing.T) {
 128  	// Create signatures where one has wrong message
 129  	items := make([]BatchSchnorrItem, 3)
 130  
 131  	for i := 0; i < 3; i++ {
 132  		kp, err := KeyPairGenerate()
 133  		if err != nil {
 134  			t.Fatalf("failed to create keypair: %v", err)
 135  		}
 136  		defer kp.Clear()
 137  
 138  		xonly, err := kp.XOnlyPubkey()
 139  		if err != nil {
 140  			t.Fatalf("failed to get x-only pubkey: %v", err)
 141  		}
 142  
 143  		msg := make([]byte, 32)
 144  		rand.Read(msg)
 145  
 146  		auxRand := make([]byte, 32)
 147  		rand.Read(auxRand)
 148  
 149  		var sig [64]byte
 150  		if err := SchnorrSign(sig[:], msg, kp, auxRand); err != nil {
 151  			t.Fatalf("failed to sign: %v", err)
 152  		}
 153  
 154  		items[i] = BatchSchnorrItem{
 155  			Pubkey:    xonly,
 156  			Message:   msg,
 157  			Signature: sig[:],
 158  		}
 159  	}
 160  
 161  	// Modify message for one item
 162  	items[1].Message[0] ^= 0xFF
 163  
 164  	// Batch should fail
 165  	if SchnorrBatchVerify(items) {
 166  		t.Error("batch verification should fail with wrong message")
 167  	}
 168  }
 169  
 170  func BenchmarkSchnorrBatchVerify(b *testing.B) {
 171  	// Prepare batch of signatures
 172  	benchCounts := []int{1, 10, 100}
 173  
 174  	for _, count := range benchCounts {
 175  		items := make([]BatchSchnorrItem, count)
 176  
 177  		for i := 0; i < count; i++ {
 178  			kp, _ := KeyPairGenerate()
 179  			defer kp.Clear()
 180  			xonly, _ := kp.XOnlyPubkey()
 181  
 182  			msg := make([]byte, 32)
 183  			rand.Read(msg)
 184  
 185  			auxRand := make([]byte, 32)
 186  			rand.Read(auxRand)
 187  
 188  			var sig [64]byte
 189  			SchnorrSign(sig[:], msg, kp, auxRand)
 190  
 191  			items[i] = BatchSchnorrItem{
 192  				Pubkey:    xonly,
 193  				Message:   msg,
 194  				Signature: sig[:],
 195  			}
 196  		}
 197  
 198  		b.Run(fmt.Sprintf("batch_%03d", count), func(b *testing.B) {
 199  			b.ReportAllocs()
 200  			for i := 0; i < b.N; i++ {
 201  				SchnorrBatchVerify(items)
 202  			}
 203  		})
 204  
 205  		// Compare with individual verification
 206  		b.Run(fmt.Sprintf("individual_%03d", count), func(b *testing.B) {
 207  			b.ReportAllocs()
 208  			for i := 0; i < b.N; i++ {
 209  				for j := range items {
 210  					SchnorrVerify(items[j].Signature, items[j].Message, items[j].Pubkey)
 211  				}
 212  			}
 213  		})
 214  	}
 215  }
 216