package checkpoint_test import ( "bytes" "crypto/ed25519" "crypto/rand" "testing" "git.n1ko.dev/Niko/niko_trust/internal/checkpoint" ) func testCkpt() *checkpoint.Checkpoint { var root, prev [32]byte rand.Read(root[:]) rand.Read(prev[:]) return &checkpoint.Checkpoint{ Epoch: 3, Size: 42, Root: root, Prev: prev, CreatedAt: 1_700_000_000, } } func TestEncodeDecodeRoundTrip(t *testing.T) { c := testCkpt() b, err := c.Encode() if err != nil { t.Fatal(err) } got, err := checkpoint.Decode(b) if err != nil { t.Fatal(err) } if got.Epoch != c.Epoch || got.Size != c.Size || got.CreatedAt != c.CreatedAt { t.Fatal("field mismatch") } if got.Root != c.Root || got.Prev != c.Prev { t.Fatal("hash mismatch") } again, _ := c.Encode() if !bytes.Equal(b, again) { t.Fatal("encoding nondeterministic") } } func TestGenesisPrevIsZeros(t *testing.T) { c := &checkpoint.Checkpoint{Epoch: 1, Size: 0, CreatedAt: 1_700_000_000} if c.Prev != ([32]byte{}) { t.Fatal("genesis prev must be zeros") } } func TestTamperedBytesRejected(t *testing.T) { c := testCkpt() pub, priv, _ := ed25519.GenerateKey(rand.Reader) b, err := c.Encode() if err != nil { t.Fatal(err) } sig, err := checkpoint.Sign(priv, b) if err != nil { t.Fatal(err) } if err := checkpoint.Verify(pub, b, sig); err != nil { t.Fatal("honest signature rejected") } for i := len(checkpoint.Magic); i < len(b); i++ { bad := append([]byte(nil), b...) bad[i] ^= 0x01 if checkpoint.Verify(pub, bad, sig) == nil { t.Fatalf("tampered byte %d verified", i) } // A mutated body is also a different checkpoint entirely. if _, err := checkpoint.Decode(bad); err == nil && i >= len(b)-1 { // Mutating created_at's last byte may still decode if it stays // in range; signature binding covers that case above. continue } } } func TestDecodeRejectsGarbage(t *testing.T) { cases := [][]byte{ nil, {}, bytes.Repeat([]byte{0x00}, 21), append(append([]byte(nil), checkpoint.Magic...), 0x02), // unknown version checkpoint.Magic[:10], } for i, b := range cases { if _, err := checkpoint.Decode(b); err == nil { t.Errorf("case %d decoded garbage", i) } } // Trailing byte after the last field. c := testCkpt() b, _ := c.Encode() if _, err := checkpoint.Decode(append(b, 0x00)); err == nil { t.Error("trailing byte accepted") } } func TestChainLinkage(t *testing.T) { c1 := &checkpoint.Checkpoint{Epoch: 1, Size: 5, CreatedAt: 1_700_000_000} b1, _ := c1.Encode() id1 := checkpoint.ID(b1) c2 := &checkpoint.Checkpoint{Epoch: 2, Size: 6, Root: id1, Prev: id1, CreatedAt: 1_700_000_060} if c2.Prev != id1 { t.Fatal("prev must equal previous checkpoint's ID") } } func TestSignKeySizeEnforced(t *testing.T) { b, _ := testCkpt().Encode() if _, err := checkpoint.Sign(ed25519.PrivateKey(make([]byte, 10)), b); err == nil { t.Fatal("short private key accepted") } if err := checkpoint.Verify(ed25519.PublicKey(make([]byte, 10)), b, make([]byte, 64)); err == nil { t.Fatal("short public key accepted") } if err := checkpoint.Verify(ed25519.PublicKey(make([]byte, 32)), b, make([]byte, 63)); err == nil { t.Fatal("short signature accepted") } }