🌐 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.

RuleLimit
Daily gain capMax +1 point/day per DID
Weekly gain capMax +2 points/week per DID
New DID probationDIDs < 7 days need 2+ attestations before earning
Same-issuer cooldownMax 1 reward/day per service
Session durationMin 30 seconds
Tool entropyMin 4 distinct tools
Robotic patternCall 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

EndpointDescription
GET /consensus/state-infoHandshake for state sync. Returns nullifierCount, protocolHash, nodeVersion. Incompatible nodes rejected immediately.
GET /consensus/state?page=N&since=TSBulk state sync (paginated, 500 entries/page). Returns nullifiers[], attestations{}, reps{}. Incremental with since timestamp.
POST /consensus/messageReceive consensus messages (PROPOSE, VOTE, COMMIT, ATTEST). Payload is AES-256-GCM encrypted. Wrong PROTOCOL_HASH → 400.

Security guarantees

PropertyMechanism
Anti-sybilNullifier = Poseidon(biometrics) — one per real person
Non-repudiationEd25519 signature on every consensus message
Network isolationPROTOCOL_HASH verified on every message — modified node ignored
Anti-replaySet<msgHash> — each message applied exactly once
ZK groundingEvery voter verifies the ZK proof independently
Fault toleranceQuorum 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

ContractAddressExplorer
Groth16Verifier (real)0x5b5B...75a6BaseScan ↗
SoulprintRegistry v20xD09e...1f12BaseScan ↗
GovernanceModule v20x0d3b...f345BaseScan ↗

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

GuaranteeMechanism
1 identity = 1 voteDID must have verified ZK proof in SoulprintRegistry
No AI takeoverBiometric identity required per voter — can't create infinite voters
No fast coup48h timelock gives humans time to react
Immutable parameters70% / 25% / 48h hardcoded — not upgradeable
Full audit trailhashHistory[] 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

ContractAddressExplorer
ProtocolConstants0x20EEe...4529BaseScan ↗
SoulprintRegistry0xE6F8...54FdBaseScan ↗
AttestationLedger0xD915...fE57BaseScan ↗
ValidatorRegistry0xE941...eE9BaseScan ↗

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

VariableDefaultDescription
SOULPRINT_HTTP_PORT4888HTTP API port
SOULPRINT_P2P_PORT6888P2P libp2p port (HTTP port + 2000)
SOULPRINT_HOST0.0.0.0Bind address for HTTP API
SOULPRINT_BOOTSTRAPnoneComma-separated bootstrap multiaddrs (for joining mainnet)
SOULPRINT_DATA_DIR~/.soulprintDirectory for persistent data (keys, attestations, proof registry)
SOULPRINT_RATE_LIMIT100Max requests per minute per IP
SOULPRINT_OPERATOR_KEYauto-generatedOperator 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:

ComponentPurpose
TCP TransportReliable connection layer
Noise encryptionEncrypted channels between all peers
Yamux multiplexingMultiple streams per connection
Kademlia DHTPeer routing and discovery
GossipSubAttestation broadcast (topic: soulprint-attestations-v1)
mDNSAuto-discovery on local networks

Dual-channel attestation propagation

When a validator issues an attestation, it is sent via two channels simultaneously:

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:

Security Considerations

Rate limiting
The default rate limit is 100 requests/min per IP. Set SOULPRINT_RATE_LIMIT lower for public-facing nodes. DDoS protection via IP banning is implemented after 10x rate limit breach.
Anti-Sybil protection
Each identity hash can only be registered once. A validator that tries to register duplicate hashes will have its attestations rejected by the network.

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.

AttackDetection
ZK always returns true (bypass)Invalid proof test fails
ZK always returns falseValid proof test fails
Pre-computed / cached responseRandom nonce makes each challenge unique
Node impersonationEd25519 signature tied to node_did
Replay attack30-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.

ScenarioWindowAction
Token valid, < 1h remainingPre-emptiveAuto-renew
Token expired < 7 days agoGrace periodAuto-renew
Token expired > 7 days agoStaleFull re-verification required
POST /token/renew
Body: { "spt": "<current_token>" }

Response 200:
{
  "spt": "<new_token>",
  "expires_in": 86400,
  "renewed": true,
  "method": "preemptive" | "grace_window"
}