sign_test.go raw

   1  package crypto
   2  
   3  import (
   4  	"testing"
   5  
   6  	"git.mleku.dev/mleku/dendrite/pkg/ratio"
   7  	"git.mleku.dev/mleku/dendrite/pkg/state"
   8  )
   9  
  10  func TestSignProducesSignature(t *testing.T) {
  11  	l := buildMatureLattice()
  12  	params := DefaultParams(Security128)
  13  	kp := GenerateKeyPair(l, params, testFactory)
  14  
  15  	sig, err := Sign(&kp.Private, []byte("test message"), params)
  16  	if err != nil {
  17  		t.Fatalf("Sign: %v", err)
  18  	}
  19  	if sig == nil {
  20  		t.Fatal("signature should not be nil")
  21  	}
  22  	if sig.Fingerprint.Hash == "" {
  23  		t.Error("fingerprint hash should not be empty")
  24  	}
  25  
  26  	expected := Hash([]byte("test message"))
  27  	if sig.Challenge != expected {
  28  		t.Error("challenge should be Hamadryad hash of message")
  29  	}
  30  }
  31  
  32  func TestSignHasProof(t *testing.T) {
  33  	l := buildMatureLattice()
  34  	params := DefaultParams(Security128)
  35  	kp := GenerateKeyPair(l, params, testFactory)
  36  
  37  	sig, err := Sign(&kp.Private, []byte("proof test"), params)
  38  	if err != nil {
  39  		t.Fatalf("Sign: %v", err)
  40  	}
  41  
  42  	// The lattice already has bonded elements, so proof should have data.
  43  	if len(sig.Proof.LockIns) == 0 {
  44  		t.Error("proof should have lock-in depths")
  45  	}
  46  	if len(sig.Proof.NeighborCounts) == 0 {
  47  		t.Error("proof should have neighbor counts")
  48  	}
  49  	if len(sig.Proof.HexTrace) == 0 {
  50  		t.Error("proof should have hexagram trace")
  51  	}
  52  }
  53  
  54  func TestVerifyValidSignature(t *testing.T) {
  55  	l := buildMatureLattice()
  56  	params := DefaultParams(Security128)
  57  	kp := GenerateKeyPair(l, params, testFactory)
  58  
  59  	msg := []byte("verify me")
  60  	sig, err := Sign(&kp.Private, msg, params)
  61  	if err != nil {
  62  		t.Fatalf("Sign: %v", err)
  63  	}
  64  
  65  	fp := FingerprintFromSpore(kp.Public.Spore)
  66  	if !Verify(fp, msg, sig) {
  67  		t.Error("valid signature should verify")
  68  	}
  69  }
  70  
  71  func TestVerifyTamperedMessage(t *testing.T) {
  72  	l := buildMatureLattice()
  73  	params := DefaultParams(Security128)
  74  	kp := GenerateKeyPair(l, params, testFactory)
  75  
  76  	msg := []byte("original")
  77  	sig, err := Sign(&kp.Private, msg, params)
  78  	if err != nil {
  79  		t.Fatalf("Sign: %v", err)
  80  	}
  81  
  82  	fp := FingerprintFromSpore(kp.Public.Spore)
  83  	if Verify(fp, []byte("tampered"), sig) {
  84  		t.Error("tampered message should not verify")
  85  	}
  86  }
  87  
  88  func TestVerifyWrongFingerprint(t *testing.T) {
  89  	l := buildMatureLattice()
  90  	params := DefaultParams(Security128)
  91  	kp := GenerateKeyPair(l, params, testFactory)
  92  
  93  	msg := []byte("test")
  94  	sig, err := Sign(&kp.Private, msg, params)
  95  	if err != nil {
  96  		t.Fatalf("Sign: %v", err)
  97  	}
  98  
  99  	// Different fingerprint.
 100  	wrongFP := SporeFingerprint{Hash: "wrong_hash"}
 101  	if Verify(wrongFP, msg, sig) {
 102  		t.Error("wrong fingerprint should not verify")
 103  	}
 104  }
 105  
 106  func TestVerifyNilSignature(t *testing.T) {
 107  	fp := SporeFingerprint{Hash: "test"}
 108  	if Verify(fp, []byte("msg"), nil) {
 109  		t.Error("nil signature should not verify")
 110  	}
 111  }
 112  
 113  func TestFingerprintFromSpore(t *testing.T) {
 114  	l := buildMatureLattice()
 115  	params := DefaultParams(Security128)
 116  	kp := GenerateKeyPair(l, params, testFactory)
 117  
 118  	fp := FingerprintFromSpore(kp.Public.Spore)
 119  	if fp.Hash == "" {
 120  		t.Error("fingerprint hash should not be empty")
 121  	}
 122  	if len(fp.TypeSignature) == 0 {
 123  		t.Error("fingerprint should have type signature")
 124  	}
 125  }
 126  
 127  func TestSortedTags(t *testing.T) {
 128  	tags := sortedTags(kp().Public.Spore.TypeSignature)
 129  	for i := 1; i < len(tags); i++ {
 130  		if tags[i] < tags[i-1] {
 131  			t.Errorf("tags not sorted: %q after %q", tags[i], tags[i-1])
 132  		}
 133  	}
 134  }
 135  
 136  func kp() *KeyPair {
 137  	l := buildMatureLattice()
 138  	return GenerateKeyPair(l, DefaultParams(Security128), testFactory)
 139  }
 140  
 141  func TestSignNilLattice(t *testing.T) {
 142  	privkey := &PrivateKey{Lattice: nil, ConstraintFactory: testFactory}
 143  	_, err := Sign(privkey, []byte("test"), DefaultParams(Security128))
 144  	if err == nil {
 145  		t.Error("expected error with nil lattice")
 146  	}
 147  }
 148  
 149  func TestSignDoesNotMutateLattice(t *testing.T) {
 150  	l := buildMatureLattice()
 151  	params := DefaultParams(Security128)
 152  	kp := GenerateKeyPair(l, params, testFactory)
 153  
 154  	// Record original occupancy.
 155  	origOcc := occupiedCount(l)
 156  
 157  	// Sign multiple messages.
 158  	for _, msg := range []string{"msg1", "msg2", "msg3"} {
 159  		_, err := Sign(&kp.Private, []byte(msg), params)
 160  		if err != nil {
 161  			t.Fatalf("Sign(%q): %v", msg, err)
 162  		}
 163  	}
 164  
 165  	// Lattice must be unchanged.
 166  	if occupiedCount(l) != origOcc {
 167  		t.Errorf("lattice occupancy changed: was %d, now %d", origOcc, occupiedCount(l))
 168  	}
 169  }
 170  
 171  func TestSignMultipleAllVerify(t *testing.T) {
 172  	l := buildMatureLattice()
 173  	params := DefaultParams(Security128)
 174  	kp := GenerateKeyPair(l, params, testFactory)
 175  	fp := FingerprintFromSpore(kp.Public.Spore)
 176  
 177  	messages := []string{
 178  		"first", "second", "third", "fourth", "fifth",
 179  		"sixth", "seventh", "eighth", "ninth", "tenth",
 180  	}
 181  	for _, msg := range messages {
 182  		sig, err := Sign(&kp.Private, []byte(msg), params)
 183  		if err != nil {
 184  			t.Fatalf("Sign(%q): %v", msg, err)
 185  		}
 186  		if !Verify(fp, []byte(msg), sig) {
 187  			t.Errorf("signature for %q did not verify", msg)
 188  		}
 189  	}
 190  }
 191  
 192  func TestVerifyStructuralChecks(t *testing.T) {
 193  	l := buildMatureLattice()
 194  	params := DefaultParams(Security128)
 195  	kp := GenerateKeyPair(l, params, testFactory)
 196  
 197  	msg := []byte("structural test")
 198  	sig, err := Sign(&kp.Private, msg, params)
 199  	if err != nil {
 200  		t.Fatalf("Sign: %v", err)
 201  	}
 202  
 203  	fp := FingerprintFromSpore(kp.Public.Spore)
 204  
 205  	// Sanity: valid signature verifies.
 206  	if !Verify(fp, msg, sig) {
 207  		t.Fatal("valid signature should verify")
 208  	}
 209  
 210  	// Proof length mismatch: truncate LockIns.
 211  	t.Run("truncated_lockins", func(t *testing.T) {
 212  		bad := copySig(sig)
 213  		if len(bad.Proof.LockIns) > 1 {
 214  			bad.Proof.LockIns = bad.Proof.LockIns[:1]
 215  		}
 216  		if Verify(fp, msg, bad) {
 217  			t.Error("truncated lock-ins should not verify")
 218  		}
 219  	})
 220  
 221  	// Proof length mismatch: truncate NeighborCounts.
 222  	t.Run("truncated_neighbors", func(t *testing.T) {
 223  		bad := copySig(sig)
 224  		if len(bad.Proof.NeighborCounts) > 1 {
 225  			bad.Proof.NeighborCounts = bad.Proof.NeighborCounts[:1]
 226  		}
 227  		if Verify(fp, msg, bad) {
 228  			t.Error("truncated neighbor counts should not verify")
 229  		}
 230  	})
 231  
 232  	// Zero neighbor count at a bonded site.
 233  	t.Run("zero_neighbor", func(t *testing.T) {
 234  		bad := copySig(sig)
 235  		if len(bad.Proof.NeighborCounts) > 0 {
 236  			bad.Proof.NeighborCounts[0] = 0
 237  		}
 238  		if Verify(fp, msg, bad) {
 239  			t.Error("zero neighbor count should not verify")
 240  		}
 241  	})
 242  }
 243  
 244  // copySig returns a shallow copy of a Signature with independent proof slices.
 245  func copySig(s *Signature) *Signature {
 246  	cp := *s
 247  	cp.Proof.LockIns = append([]ratio.Ratio(nil), s.Proof.LockIns...)
 248  	cp.Proof.NeighborCounts = append([]int(nil), s.Proof.NeighborCounts...)
 249  	cp.Proof.HexTrace = append([]state.Hexagram(nil), s.Proof.HexTrace...)
 250  	cp.Response = append([]SiteMark(nil), s.Response...)
 251  	return &cp
 252  }
 253