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:
vsecoder
2026-04-17 14:16:44 +03:00
commit 7e7393e4f8
196 changed files with 55947 additions and 0 deletions

257
identity/identity_test.go Normal file
View 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))
}
}