Centralized registry server for the HushNet decentralized network, enabling node registration, discovery, and monitoring.
HushNet Registry is a Rust-based backend service that maintains a registry of active nodes in the HushNet network. It provides a REST API for secure node registration using Ed25519 cryptographic signatures, along with health monitoring and geolocation features.
- Rust (Edition 2021)
- Axum - Async web framework
- PostgreSQL - Relational database
- Docker - Containerization
- SQLx - Async PostgreSQL client
- Ed25519-Dalek - Elliptic curve cryptography
- MaxMindDB - IP geolocation
- Tokio - Async runtime
- REST API - HTTP interface for nodes and clients
- Authentication System - Challenge-response with Ed25519 signatures
- Health Worker - Periodic node monitoring
- PostgreSQL Database - Persistent storage
- Docker and Docker Compose
- Rust 1.70+ (for local development)
- PostgreSQL 15+ (for local development)
- Clone the repository:
git clone https://github.com/HushNet/HushNet-Registry.git
cd HushNet-Registry- Configure environment variables (optional):
# Create a .env file
POSTGRES_USER=postgres
POSTGRES_PASSWORD=dev
POSTGRES_DB=hushreg
POSTGRES_PORT=5432
REGISTRY_PORT=8081
HEALTH_TIMEOUT_MS=3000- Start the services:
docker compose up --buildThe registry will be accessible at http://localhost:8081
- Install dependencies:
cargo build- Set up the database:
# Start PostgreSQL
docker run -d \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=dev \
-e POSTGRES_DB=hushreg \
-p 5432:5432 \
postgres:latest
# Apply schema
psql -h localhost -U postgres -d hushreg -f sql_models/seed.sql- Download GeoLite2 database:
bash setup_geolite.sh- Configure DATABASE_URL:
export DATABASE_URL="postgres://postgres:dev@localhost:5432/hushreg"- Run the application:
cargo runRequest a cryptographic challenge for authentication.
Request:
{
"pubkey_b64": "base64_encoded_public_key"
}Response:
{
"nonce": "random_nonce",
"expires_at": "2025-11-07T12:34:56Z"
}Status Codes:
- 200: Challenge generated successfully
- 400: Invalid or missing public key
Register a new node in the registry.
Request:
{
"payload": {
"name": "My Node",
"host": "node.example.com",
"api_base_url": "https://node.example.com/api",
"protocol_version": "1.0",
"features": {},
"contact_email": "admin@example.com"
},
"nonce": "challenge_nonce",
"signature_b64": "base64_encoded_signature",
"pubkey_b64": "base64_encoded_public_key"
}Response:
{
"ok": true
}Status Codes:
- 200: Registration successful
- 400: Invalid data or expired nonce
- 401: Invalid signature
- 403: Host already registered with another key
Notes:
- The payload is canonicalized before signing
- Signature must be computed on:
canonical_json(payload) + nonce - Nonce expires after 5 minutes
Update an existing node's status.
Request:
{
"host": "node.example.com",
"nonce": "random_nonce",
"signature_b64": "base64_encoded_signature",
"pubkey_b64": "base64_encoded_public_key"
}Response:
{
"ok": true
}Status Codes:
- 200: Heartbeat recorded
- 400: Invalid data
- 401: Invalid signature
Notes:
- Signature must be computed on:
host + nonce
Retrieve the list of all registered nodes.
Response:
{
"nodes": [
{
"name": "My Node",
"host": "node.example.com",
"ip": "192.168.1.1",
"api_base_url": "https://node.example.com/api",
"protocol_version": "1.0",
"features": {},
"country_code": "FR",
"country_name": "France",
"last_seen_at": "2025-11-07T12:34:56Z",
"last_latency_ms": 150,
"status": "online"
}
]
}Status Codes:
- 200: List retrieved successfully
Notes:
- Nodes are sorted by status (online first) then by name
- Status can be:
online,offline, orunknown
Stores information about registered nodes.
| Column | Type | Description |
|---|---|---|
| id | UUID | Unique identifier (PK) |
| name | TEXT | Node name |
| host | TEXT | Hostname (unique) |
| ip | INET | Resolved IP address |
| api_base_url | TEXT | Base API URL |
| pubkey | BYTEA | Ed25519 public key (unique) |
| protocol_version | TEXT | Protocol version |
| features | JSONB | Supported features |
| contact_email | TEXT | Contact email |
| registered_at | TIMESTAMPTZ | Registration timestamp |
| country_code | TEXT | ISO country code (geolocation) |
| country_name | TEXT | Country name (geolocation) |
| last_seen_at | TIMESTAMPTZ | Last activity detected |
| last_latency_ms | INTEGER | Last measured latency (ms) |
| status | TEXT | Status: online/offline/unknown |
| uptime_ratio | REAL | Availability ratio |
Stores temporary authentication challenges.
| Column | Type | Description |
|---|---|---|
| nonce | TEXT | Unique nonce (PK) |
| pubkey_b64 | TEXT | Base64-encoded public key |
| expires_at | TIMESTAMPTZ | Expiration timestamp |
- Key Generation: Node generates an Ed25519 key pair
- Challenge Request: POST /api/registry/challenge with public key
- Payload Signing: Node signs
canonical_json(payload) + nonce - Registration: POST /api/registry/register with signed payload
- Verification: Registry verifies signature and registers the node
The payload is canonicalized before signing to ensure consistency:
- Object keys are sorted alphabetically
- The process is recursive for nested objects
- Arrays preserve their order
Example:
// Original
{"z": 1, "a": 2}
// Canonicalized
{"a": 2, "z": 1}The service runs a background worker that:
- Executes every 60 seconds
- Checks each node's
/healthendpoint - Measures response latency
- Updates status and geolocation
- Configurable timeout (default: 3000ms)
- online: Node responded successfully to last check
- offline: Node failed to respond or returned an error
- unknown: Initial status, never checked
The service uses the GeoLite2 City database to:
- Determine node country via IP address
- Store ISO country code (e.g., "FR", "US")
- Store country name (e.g., "France", "United States")
| Variable | Description | Default |
|---|---|---|
| DATABASE_URL | PostgreSQL connection URL | Required |
| POSTGRES_USER | PostgreSQL user | postgres |
| POSTGRES_PASSWORD | PostgreSQL password | dev |
| POSTGRES_DB | Database name | hushreg |
| POSTGRES_PORT | PostgreSQL port | 5432 |
| REGISTRY_PORT | Exposed registry port | 8081 |
| HEALTH_TIMEOUT_MS | Health check timeout (ms) | 3000 |
- CORS: Permissive for all domains
- Timeout: 10 seconds per request
- Tracing: HTTP request logging
- Compression: Not enabled
- Algorithm: Ed25519 (elliptic curve)
- Encoding: Standard Base64 for keys and signatures
- Key Size: 32 bytes
- Signature Size: 64 bytes
- Single-use nonces: Each challenge is deleted after use
- Time expiration: Challenges expire after 5 minutes
- Host verification: A host can only be registered with one public key
- Cryptographic signatures: All sensitive operations require valid signatures
- Never share private keys
- Regenerate nonces for each operation
- Use HTTPS in production
- Restrict database access
- Monitor logs for intrusion attempts
HushNet-Registry/
├── src/
│ ├── main.rs # Entry point, API routes, handlers
│ ├── types.rs # Data structures (Request/Response)
│ ├── canon.rs # JSON canonicalization
│ └── mod.rs # Module declarations
├── sql_models/
│ └── seed.sql # Database schema
├── data/
│ └── GeoLite2-City.mmdb # Geolocation database
├── Cargo.toml # Rust dependencies
├── Dockerfile # Multi-stage Docker image
├── docker-compose.yml # Service orchestration
├── setup_geolite.sh # GeoLite2 download script
└── README.md # This documentation
- Check that PostgreSQL is accessible:
docker compose logs db- Verify environment variables:
docker compose config- Check registry logs:
docker compose logs registry- Verify that the node's
/healthendpoint responds:
curl http://node.example.com/api/health- Increase timeout if nodes are slow:
HEALTH_TIMEOUT_MS=5000 docker compose up- Verify that payload is canonicalized before signing
- Verify that nonce is concatenated correctly
- Verify that Base64 encoding is correct (standard, not URL-safe)
bash setup_geolite.sh- Response time: < 50ms for simple requests
- Throughput: > 1000 req/s on modern hardware
- Monitoring latency: 60 seconds between checks
- Memory: ~50MB in normal operation
- Index on
statusfor list queries - UNIQUE constraints to prevent duplicates
- Prepared statements via SQLx
- Async Tokio runtime for concurrency
cargo testcargo fmt --checkcargo clippycargo build --release- Prometheus metrics support
- WebSocket for real-time updates
- Web-based admin interface
- Advanced search API
- Full IPv6 support
- Multi-region replication
See the LICENSE file in the repository.
For questions or contributions, open an issue on GitHub.
Developed and maintained by the HushNet team.