package identity_test import ( "strings" "testing" "go-blockchain/identity" ) func mustGenerate(t *testing.T) *identity.Identity { t.Helper() id, err := identity.Generate() if err != nil { t.Fatalf("identity.Generate: %v", err) } return id } // TestGenerate checks that a freshly generated identity has non-zero keys of // the expected lengths. func TestGenerate(t *testing.T) { id := mustGenerate(t) // Ed25519 public key: 32 bytes → 64 hex chars. pubHex := id.PubKeyHex() if len(pubHex) != 64 { t.Errorf("PubKeyHex length: got %d, want 64", len(pubHex)) } // Ed25519 private key (seed + pub): 64 bytes → 128 hex chars. privHex := id.PrivKeyHex() if len(privHex) != 128 { t.Errorf("PrivKeyHex length: got %d, want 128", len(privHex)) } // X25519 keys: 32 bytes each → 64 hex chars each. if len(id.X25519PubHex()) != 64 { t.Errorf("X25519PubHex length: got %d, want 64", len(id.X25519PubHex())) } if len(id.X25519PrivHex()) != 64 { t.Errorf("X25519PrivHex length: got %d, want 64", len(id.X25519PrivHex())) } // Keys must not be all-zero strings. allZero := strings.Repeat("0", 64) if pubHex == allZero { t.Error("PubKeyHex is all zeros") } } // TestGenerateUnique verifies that two Generate calls produce distinct keypairs. func TestGenerateUnique(t *testing.T) { id1 := mustGenerate(t) id2 := mustGenerate(t) if id1.PubKeyHex() == id2.PubKeyHex() { t.Error("two Generate calls produced the same Ed25519 public key") } if id1.X25519PubHex() == id2.X25519PubHex() { t.Error("two Generate calls produced the same X25519 public key") } } // TestSignVerify signs a message and verifies that the signature is valid. func TestSignVerify(t *testing.T) { id := mustGenerate(t) msg := []byte("hello blockchain") sig := id.Sign(msg) ok, err := identity.Verify(id.PubKeyHex(), msg, sig) if err != nil { t.Fatalf("Verify: %v", err) } if !ok { t.Error("Verify should return true for a valid signature") } } // TestVerifyWrongKey verifies that a signature fails when checked against a // different public key (should return false, not an error). func TestVerifyWrongKey(t *testing.T) { id1 := mustGenerate(t) id2 := mustGenerate(t) msg := []byte("hello blockchain") sig := id1.Sign(msg) ok, err := identity.Verify(id2.PubKeyHex(), msg, sig) if err != nil { t.Fatalf("Verify returned unexpected error: %v", err) } if ok { t.Error("Verify should return false when checked against a different public key") } } // TestVerifyTamperedMessage verifies that a signature is invalid when the // message is modified after signing. func TestVerifyTamperedMessage(t *testing.T) { id := mustGenerate(t) msg := []byte("original message") sig := id.Sign(msg) tampered := []byte("tampered message") ok, err := identity.Verify(id.PubKeyHex(), tampered, sig) if err != nil { t.Fatalf("Verify returned unexpected error: %v", err) } if ok { t.Error("Verify should return false for a tampered message") } } // TestFromHexRoundTrip serialises an identity to hex, reconstructs it via // FromHex, and verifies that the reconstructed identity can sign and verify. func TestFromHexRoundTrip(t *testing.T) { orig := mustGenerate(t) restored, err := identity.FromHex(orig.PubKeyHex(), orig.PrivKeyHex()) if err != nil { t.Fatalf("FromHex: %v", err) } if restored.PubKeyHex() != orig.PubKeyHex() { t.Errorf("PubKeyHex mismatch after FromHex round-trip") } msg := []byte("round-trip test") sig := restored.Sign(msg) ok, err := identity.Verify(orig.PubKeyHex(), msg, sig) if err != nil { t.Fatalf("Verify: %v", err) } if !ok { t.Error("signature from restored identity should verify against original public key") } } // TestFromHexFullRoundTrip serialises all four keys and reconstructs via // FromHexFull, checking both Ed25519 and X25519 key equality. func TestFromHexFullRoundTrip(t *testing.T) { orig := mustGenerate(t) restored, err := identity.FromHexFull( orig.PubKeyHex(), orig.PrivKeyHex(), orig.X25519PubHex(), orig.X25519PrivHex(), ) if err != nil { t.Fatalf("FromHexFull: %v", err) } if restored.PubKeyHex() != orig.PubKeyHex() { t.Error("Ed25519 public key mismatch after FromHexFull round-trip") } if restored.X25519PubHex() != orig.X25519PubHex() { t.Error("X25519 public key mismatch after FromHexFull round-trip") } if restored.X25519PrivHex() != orig.X25519PrivHex() { t.Error("X25519 private key mismatch after FromHexFull round-trip") } // Ed25519 sign+verify still works after full round-trip. msg := []byte("full round-trip test") sig := restored.Sign(msg) ok, err := identity.Verify(orig.PubKeyHex(), msg, sig) if err != nil { t.Fatalf("Verify: %v", err) } if !ok { t.Error("signature from fully restored identity should verify") } } // TestFromHexMissingX25519 verifies that FromHexFull with empty X25519 strings // derives a valid (non-zero) X25519 keypair deterministically from the Ed25519 key. func TestFromHexMissingX25519(t *testing.T) { orig := mustGenerate(t) id, err := identity.FromHexFull(orig.PubKeyHex(), orig.PrivKeyHex(), "", "") if err != nil { t.Fatalf("FromHexFull: %v", err) } // Derived X25519 keys must be non-zero. allZero := strings.Repeat("0", 64) if id.X25519PubHex() == allZero { t.Error("X25519PubHex should be derived (non-zero) when empty string passed") } if id.X25519PrivHex() == allZero { t.Error("X25519PrivHex should be derived (non-zero) when empty string passed") } // Calling again with the same Ed25519 key must produce the same X25519 keys (deterministic). id2, err := identity.FromHexFull(orig.PubKeyHex(), orig.PrivKeyHex(), "", "") if err != nil { t.Fatalf("FromHexFull second call: %v", err) } if id.X25519PubHex() != id2.X25519PubHex() { t.Error("X25519 derivation is not deterministic") } } // TestMineRegistration runs proof-of-work at difficulty 16 and verifies that // the resulting target hash starts with "0000". // MineRegistration uses difficulty/4 as the number of leading hex zeros, so // difficulty=16 produces a 4-zero prefix. This completes in well under a second. func TestMineRegistration(t *testing.T) { id := mustGenerate(t) // difficulty/4 == 4 leading hex zeros → prefix "0000". nonce, target, err := identity.MineRegistration(id.PubKeyHex(), 16) if err != nil { t.Fatalf("MineRegistration: %v", err) } // The nonce is just a counter — any value is acceptable. _ = nonce if !strings.HasPrefix(target, "0000") { t.Errorf("target should start with '0000' for difficulty 16, got %s", target) } } // TestRegisterTxValid builds a REGISTER_KEY transaction at difficulty 4 and // verifies the signature via VerifyTx. func TestRegisterTxValid(t *testing.T) { id := mustGenerate(t) tx, err := identity.RegisterTx(id, "testnode", 4) if err != nil { t.Fatalf("RegisterTx: %v", err) } if tx == nil { t.Fatal("RegisterTx returned nil transaction") } if err := identity.VerifyTx(tx); err != nil { t.Errorf("VerifyTx should return nil for a freshly built transaction, got: %v", err) } } // TestX25519KeyLengths checks that X25519PubHex and X25519PrivHex are each // exactly 64 hex characters (32 bytes). func TestX25519KeyLengths(t *testing.T) { id := mustGenerate(t) pubHex := id.X25519PubHex() privHex := id.X25519PrivHex() if len(pubHex) != 64 { t.Errorf("X25519PubHex: expected 64 hex chars (32 bytes), got %d", len(pubHex)) } if len(privHex) != 64 { t.Errorf("X25519PrivHex: expected 64 hex chars (32 bytes), got %d", len(privHex)) } }