chore: initial commit for v0.0.1
DChain single-node blockchain + React Native messenger client. Core: - PBFT consensus with multi-sig validator admission + equivocation slashing - BadgerDB + schema migration scaffold (CurrentSchemaVersion=0) - libp2p gossipsub (tx/v1, blocks/v1, relay/v1, version/v1) - Native Go contracts (username_registry) alongside WASM (wazero) - WebSocket gateway with topic-based fanout + Ed25519-nonce auth - Relay mailbox with NaCl envelope encryption (X25519 + Ed25519) - Prometheus /metrics, per-IP rate limit, body-size cap Deployment: - Single-node compose (deploy/single/) with Caddy TLS + optional Prometheus - 3-node dev compose (docker-compose.yml) with mocked internet topology - 3-validator prod compose (deploy/prod/) for federation - Auto-update from Gitea via /api/update-check + systemd timer - Build-time version injection (ldflags → node --version) - UI / Swagger toggle flags (DCHAIN_DISABLE_UI, DCHAIN_DISABLE_SWAGGER) Client (client-app/): - Expo / React Native / NativeWind - E2E NaCl encryption, typing indicator, contact requests - Auto-discovery of canonical contracts, chain_id aware, WS reconnect on node switch Documentation: - README.md, CHANGELOG.md, CONTEXT.md - deploy/single/README.md with 6 operator scenarios - deploy/UPDATE_STRATEGY.md with 4-layer forward-compat design - docs/contracts/*.md per contract
This commit is contained in:
257
identity/identity_test.go
Normal file
257
identity/identity_test.go
Normal file
@@ -0,0 +1,257 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user