š Validator Node
Run a node in the Soulprint P2P network ā verify proofs, issue reputation attestations, earn validator status.
Quick Start
# Start the node (HTTP 4888 + P2P 6888)
npx soulprint node
# Or install globally first:
npm install -g soulprint
soulprint node
Example startup output:
š Soulprint Validator Node v0.2.2
ā
HTTP API listening on http://0.0.0.0:4888
ā
P2P node started
Peer ID: 12D3KooW[...generated peer ID...]
Multiaddr: /ip4/0.0.0.0/tcp/6888/p2p/12D3KooW[...]
ā
mDNS discovery active (local network)
ā³ Waiting for bootstrap peers...
(No bootstrap peers configured ā running standalone)
Credential Validators v0.3.0
Every validator node ships with three open-source credential verifiers ā no API keys required.
š§ Email OTP
POST /credentials/email/start { "did": "...", "email": "user@example.com" }
// ā OTP sent via nodemailer (dev: Ethereal preview, prod: SMTP config)
POST /credentials/email/verify { "sessionId": "...", "otp": "123456" }
// ā { credential: "EmailVerified", attestation }
Config: SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS (all optional for dev)
š± Phone TOTP (RFC 6238 ā no SMS)
POST /credentials/phone/start { "did": "...", "phone": "+573001234567" }
// ā { sessionId, totpUri } ā scan QR with Google Authenticator / Authy / Aegis
POST /credentials/phone/verify { "sessionId": "...", "code": "179941" }
// ā { credential: "PhoneVerified", attestation }
š GitHub OAuth
GET /credentials/github/start?did=did:soulprint:abc...
// ā redirects to github.com/login/oauth/authorize
GET /credentials/github/callback
// ā { credential: "GitHubLinked", github: { login }, attestation }
Config: GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, SOULPRINT_BASE_URL
Create OAuth App: github.com/settings/applications/new
Anti-Farming Engine v0.3.0
Farming attempts are automatically detected and converted to -1 penalties.
| Rule | Limit |
|---|---|
| Daily gain cap | Max +1 point/day per DID |
| Weekly gain cap | Max +2 points/week per DID |
| New DID probation | DIDs < 7 days need 2+ attestations before earning |
| Same-issuer cooldown | Max 1 reward/day per service |
| Session duration | Min 30 seconds |
| Tool entropy | Min 4 distinct tools |
| Robotic pattern | Call interval stddev < 10% of mean ā penalty |
BFT P2P Consensus v0.3.1
Soulprint v0.3.1 adds BFT (Byzantine Fault Tolerant) consensus for nullifier registration ā no blockchain, no gas fees, no external dependencies. Everything runs over the existing P2P network.
š PROPOSE ā VOTE ā COMMIT
# When a new identity is verified:
# 1. Proposer node broadcasts PROPOSE with ZK proof hash
# 2. Each node verifies the ZK proof locally
# 3. Nodes broadcast VOTE (accept/reject)
# 4. When N/2+1 accepts received ā COMMIT broadcast to all nodes
# 5. All nodes store the nullifier as committed
# Single-node mode (0 peers): immediate local commit ā no timeout
Consensus Endpoints
| Endpoint | Description |
|---|---|
GET /consensus/state-info | Handshake for state sync. Returns nullifierCount, protocolHash, nodeVersion. Incompatible nodes rejected immediately. |
GET /consensus/state?page=N&since=TS | Bulk state sync (paginated, 500 entries/page). Returns nullifiers[], attestations{}, reps{}. Incremental with since timestamp. |
POST /consensus/message | Receive consensus messages (PROPOSE, VOTE, COMMIT, ATTEST). Payload is AES-256-GCM encrypted. Wrong PROTOCOL_HASH ā 400. |
Security guarantees
| Property | Mechanism |
|---|---|
| Anti-sybil | Nullifier = Poseidon(biometrics) ā one per real person |
| Non-repudiation | Ed25519 signature on every consensus message |
| Network isolation | PROTOCOL_HASH verified on every message ā modified node ignored |
| Anti-replay | Set<msgHash> ā each message applied exactly once |
| ZK grounding | Every voter verifies the ZK proof independently |
| Fault tolerance | Quorum N/2+1 ā tolerates up to N/2 malicious nodes |
Security Hardening v0.3.5
Fix 1 ā Real Groth16Verifier
Production now uses the real Groth16 verifier generated by snarkjs from the circuit's proving key. The mock accepted any proof where input[0] != 0. The real verifier performs full elliptic curve pairing checks.
| Mock (old) | Real (v0.3.5) | |
|---|---|---|
| Zero proof | ā accepted | š« REVERTS |
| Random values | ā accepted | š« REVERTS |
| Overflow values | ā accepted | š« REVERTS |
| Valid ZK proof | ā accepted | ā accepted |
Admin locked: SoulprintRegistry.admin = address(0) ā only GovernanceModule (70% supermajority) can update the verifier.
Fix 2 ā Code Integrity Hash
# Every build computes SHA-256 of all 17 source files
tsc && node scripts/compute-code-hash.mjs
# ā dist/code-hash.json: { codeHash, codeHashHex, fileCount }
# Validator logs at startup:
# [integrity] ā
Code hash: 559c4b60... (17 files)
# Check via API:
curl http://localhost:4888/health
{
"codeHash": "559c4b6036fc59ae...",
"codeHashHex": "0x559c4b60...",
"runtimeHash": "a1b2c3d4...",
"governanceApprovedHash": "0xdfe1ccca...",
"nodeCompatible": true
}
Contracts ā Base Sepolia v0.3.5
| Contract | Address | Explorer |
|---|---|---|
Groth16Verifier (real) | 0x5b5B...75a6 | BaseScan ā |
SoulprintRegistry v2 | 0xD09e...1f12 | BaseScan ā |
GovernanceModule v2 | 0x0d3b...f345 | BaseScan ā |
Test coverage
suite.js 104/104 ā
consensus-tests.mjs 32/32 ā
blockchain-e2e-tests.mjs 33/33 ā
governance-tests.mjs 33/33 ā
fix-verification-tests.mjs 43/43 ā
ā Fix 1+2 exhaustivo
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Total: 245/245 ā
On-chain Governance v0.3.5
The GovernanceModule ensures that no single entity ā not even the deployer ā can change the PROTOCOL_HASH without a supermajority of verified validators.
How it works
# Any upgrade to PROTOCOL_HASH requires:
1. proposeUpgrade(did, newHash, rationale) ā verified identity required
2. voteOnProposal(id, did, approve) ā 70% supermajority needed
3. 48h timelock ā humans can veto (25% threshold)
4. executeProposal(id) ā anyone can execute after timelock
# Emergency veto during timelock:
If 25%+ of validators vote AGAINST ā proposal VETOED immediately
Security guarantees
| Guarantee | Mechanism |
|---|---|
| 1 identity = 1 vote | DID must have verified ZK proof in SoulprintRegistry |
| No AI takeover | Biometric identity required per voter ā can't create infinite voters |
| No fast coup | 48h timelock gives humans time to react |
| Immutable parameters | 70% / 25% / 48h hardcoded ā not upgradeable |
| Full audit trail | hashHistory[] on-chain ā every approved hash recorded |
Governance endpoints
# Get governance status
curl http://localhost:4888/governance
{
"currentApprovedHash": "0xdfe1ccca...",
"blockchainConnected": true,
"activeProposals": 0,
"hashHistory": ["0xdfe1ccca..."],
"nodeCompatible": true
}
# Propose an upgrade (requires verified identity on-chain)
curl -X POST http://localhost:4888/governance/propose \
-H "Content-Type: application/json" \
-d '{"did":"did:key:z6Mk...","newHash":"0xnew...","rationale":"Security patch v1.1"}'
# Vote on a proposal
curl -X POST http://localhost:4888/governance/vote \
-d '{"proposalId":0,"did":"did:key:z6Mk...","approve":true}'
# Execute after timelock (48h)
curl -X POST http://localhost:4888/governance/execute \
-d '{"proposalId":0}'
Contract ā Base Sepolia
0xE74Cd1Aa66541dF76e5a82a05F11f80B31FCe217 ā
Blockchain Backup v0.3.5
Soulprint v0.3.5 adds async blockchain backup ā P2P is primary, blockchain is permanent record. No downtime if blockchain is unavailable.
Architecture
# P2P commits instantly (no gas, no wait)
BFT P2P: PROPOSE ā VOTE ā COMMIT (~2s, $0)
āāāā¶ Response to user ā immediate ā
āāāā¶ async (non-blocking)
BlockchainAnchor ā Base Sepolia
āāā OK: tx anchored (~5s, free testnet)
āāā fail: retry x3 (0sā2sā8s backoff)
āāā 3 fails: blockchain-queue.json
Deployed Contracts ā Base Sepolia
| Contract | Address | Explorer |
|---|---|---|
ProtocolConstants | 0x20EEe...4529 | BaseScan ā |
SoulprintRegistry | 0xE6F8...54Fd | BaseScan ā |
AttestationLedger | 0xD915...fE57 | BaseScan ā |
ValidatorRegistry | 0xE941...eE9 | BaseScan ā |
Enable blockchain backup
SOULPRINT_RPC_URL=https://sepolia.base.org \
SOULPRINT_PRIVATE_KEY=0x... \
SOULPRINT_NETWORK=base-sepolia \
npx soulprint node
# Node log will show:
# [anchor] ā
Blockchain backup enabled ā Base Sepolia
# [anchor] ā
nullifier 0xaa... ā blockchain tx 0x1234...
GET /anchor/stats
curl http://localhost:4888/anchor/stats
{
"nullifiersAnchored": 12,
"attestsAnchored": 47,
"pendingNullifiers": 0,
"pendingAttests": 0,
"blockchainConnected": true,
"lastAnchorTs": 1740000000000
}
Configuration
Environment Variables
| Variable | Default | Description |
|---|---|---|
SOULPRINT_HTTP_PORT | 4888 | HTTP API port |
SOULPRINT_P2P_PORT | 6888 | P2P libp2p port (HTTP port + 2000) |
SOULPRINT_HOST | 0.0.0.0 | Bind address for HTTP API |
SOULPRINT_BOOTSTRAP | none | Comma-separated bootstrap multiaddrs (for joining mainnet) |
SOULPRINT_DATA_DIR | ~/.soulprint | Directory for persistent data (keys, attestations, proof registry) |
SOULPRINT_RATE_LIMIT | 100 | Max requests per minute per IP |
SOULPRINT_OPERATOR_KEY | auto-generated | Operator private key (persisted to $DATA_DIR/operator.key) |
Example ā Custom ports
SOULPRINT_HTTP_PORT=8000 SOULPRINT_P2P_PORT=10000 npx soulprint node
Example ā Connect to mainnet bootstrap node
SOULPRINT_BOOTSTRAP="/ip4/<bootstrap-ip>/tcp/6888/p2p/<bootstrap-peer-id>" \
npx soulprint node
P2P Network Architecture
The Soulprint P2P network uses libp2p v2.10 with the following stack:
| Component | Purpose |
|---|---|
| TCP Transport | Reliable connection layer |
| Noise encryption | Encrypted channels between all peers |
| Yamux multiplexing | Multiple streams per connection |
| Kademlia DHT | Peer routing and discovery |
| GossipSub | Attestation broadcast (topic: soulprint-attestations-v1) |
| mDNS | Auto-discovery on local networks |
Dual-channel attestation propagation
When a validator issues an attestation, it is sent via two channels simultaneously:
- Primary: GossipSub broadcast to all peers on
soulprint-attestations-v1 - Fallback: HTTP
POST /reputation/attestto known peer HTTP endpoints (for legacy nodes)
Anti-loop and anti-replay protections prevent attestations from cycling: each message has a unique ID; nodes that have already processed an ID discard duplicates.
Validator Data Persistence
The node persists the following to $SOULPRINT_DATA_DIR:
operator.keyā the node's signing key (auto-generated on first run)proof-hashes.dbā registry of seen identity proof hashes (anti-replay)reputation.dbā reputation scores per DIDattestations.logā audit log of all issued attestations
Security Considerations
SOULPRINT_RATE_LIMIT lower for public-facing nodes. DDoS protection via IP banning is implemented after 10x rate limit breach.
Docker Deployment
docker run -d \
--name soulprint-node \
-p 4888:4888 \
-p 6888:6888 \
-v ~/.soulprint:/data \
-e SOULPRINT_DATA_DIR=/data \
-e SOULPRINT_BOOTSTRAP="<bootstrap-multiaddr>" \
node:20-alpine \
sh -c "npm i -g soulprint && soulprint node"
Monitoring
# Node health check
curl http://localhost:4888/info
# Response:
{
"peerId": "12D3KooW...",
"version": "0.2.2",
"httpPort": 4888,
"p2pPort": 6888,
"connectedPeers": 3,
"attestationsIssued": 142,
"proofHashesStored": 891,
"uptime": 86400
}
Challenge-Response Peer Integrity v0.3.7
Soulprint v0.3.7 adds cryptographic peer verification ā before accepting a new peer, the node challenges it to prove it's running unmodified ZK verification code.
How it works
The challenger sends two ZK proofs: one known-valid (the protocol's official test vector) and one freshly-mutated invalid proof (unique per challenge via random nonce). The peer must correctly return true for the valid proof and false for the invalid one ā and sign the response with its node key.
| Attack | Detection |
|---|---|
ZK always returns true (bypass) | Invalid proof test fails |
ZK always returns false | Valid proof test fails |
| Pre-computed / cached response | Random nonce makes each challenge unique |
| Node impersonation | Ed25519 signature tied to node_did |
| Replay attack | 30-second challenge TTL |
Endpoint
POST /challenge
Body: {
challenge_id: string, // UUID nonce
nonce: string, // 32-byte hex ā makes invalid_proof unique
issued_at: number, // unix timestamp (TTL: 30s)
valid_proof: ZKProof, // protocol's official test vector
invalid_proof: ZKProof // mutated proof ā must fail verification
}
Response 200:
{
challenge_id: string,
result_valid: true, // valid proof verified correctly
result_invalid: false, // invalid proof rejected correctly
verified_at: number,
node_did: string,
signature: string // Ed25519(challenge_id + results + verified_at, node_key)
}
Automatic peer verification
POST /peers/register now automatically runs verifyPeerBehavior() before accepting any peer. A peer with modified ZK code is rejected with HTTP 403.
SPT Auto-Renewal v0.3.6
Tokens now renew automatically ā no more downtime when a 24-hour SPT expires.
| Scenario | Window | Action |
|---|---|---|
| Token valid, < 1h remaining | Pre-emptive | Auto-renew |
| Token expired < 7 days ago | Grace period | Auto-renew |
| Token expired > 7 days ago | Stale | Full re-verification required |
POST /token/renew
Body: { "spt": "<current_token>" }
Response 200:
{
"spt": "<new_token>",
"expires_in": 86400,
"renewed": true,
"method": "preemptive" | "grace_window"
}