Skip to main content
Glama

๐Ÿ” Soulprint

Decentralized KYC identity protocol for AI agents.

Soulprint lets any AI bot prove there's a verified human behind it โ€” without revealing who that human is. No companies, no servers, no paid APIs. Just cryptographic proof.

License: MIT [npm soulprint npm soulprint-mcp Phase() npm soulprint-networkBuilt with


The Problem

AI agents are acting on behalf of humans: booking flights, calling APIs, making decisions. But no service can know if a bot is legitimate or malicious. There's no accountability.

Soulprint solves this by linking every bot to a verified human identity โ€” cryptographically, privately, and without any central authority.


How It Works

1. User runs: npx soulprint verify-me --selfie me.jpg --document cedula.jpg
              โ†“
2. LOCAL (on-device, nothing leaves your machine):
   โ€ข Tesseract OCR reads the cedula (Colombian ID)
   โ€ข InsightFace matches your face to the document photo
   โ€ข Poseidon hash derives a unique nullifier from (cedula + birthdate + face_key)
   โ€ข ZK proof generated: "I verified my identity" without revealing any data
   โ€ข Photos deleted from memory
              โ†“
3. ZK proof + SPT broadcast to validator node (verifies in 25ms, offline)
              โ†“
4. Soulprint Token (SPT) stored in ~/.soulprint/token.spt โ€” valid 24h
              โ†“
5. Any MCP server or API verifies in <50ms, offline, for free

What the verifier knows: โœ… Real human, verified Colombian ID, trust score
What the verifier doesn't know: ๐Ÿ”’ Name, cedula number, face, birthdate


Quick Start

1. Install Python deps (face recognition)

npx soulprint install-deps

2. Verify your identity

npx soulprint verify-me \
  --selfie path/to/selfie.jpg \
  --document path/to/cedula.jpg

Output:

๐Ÿ” Soulprint โ€” Verificaciรณn de identidad
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
  โœ… Validaciรณn de imรกgenes
  โœ… OCR del documento
  โœ… Coincidencia facial
  โœ… Derivaciรณn de nullifier
  โœ… Generaciรณn de ZK proof
  โœ… Emisiรณn del token SPT

  DID:          did:key:z6Mk...
  Trust Score:  45/100
  ZK Proof:     โœ… incluido
  Tiempo:       3.2s

3. Show your token

npx soulprint show

4. Renew (no re-verify needed)

npx soulprint renew

5. Run a validator node

npx soulprint node --port 4888

Protect Any MCP Server (3 lines)

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { soulprint } from "soulprint-mcp";

const server = new McpServer({ name: "my-server", version: "1.0" });
server.use(soulprint({ minScore: 60 }));  // require KYC-verified humans

The client must include the SPT in capabilities:

{
  "capabilities": {
    "identity": { "soulprint": "<token>" }
  }
}

Or in the HTTP header: X-Soulprint: <token>

With DPoP โ€” prevent token theft (v0.3.8)

// โ”€โ”€ Server side โ€” strict mode โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
server.use(soulprint({ minScore: 60, requireDPoP: true }));
// โ†’ 401 { error: "dpop_required" } if no proof header

// โ”€โ”€ Client side โ€” sign every request โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import { signDPoP, serializeDPoP } from "soulprint-core";

// Load your keypair (never transmit the private key)
const { privateKey, did } = loadKeypair();
const myToken = "<your-SPT>";

// Before each tool call:
const proof = signDPoP(privateKey, did, "POST", toolUrl, myToken);
headers["X-Soulprint"]       = myToken;
headers["X-Soulprint-Proof"] = serializeDPoP(proof);

A stolen SPT is useless without the private key. The proof is:

  • Unique per request (random nonce)

  • URL + method bound (no MITM)

  • Expires in 5 minutes

  • Hash-bound to the specific token


Protect Any REST API

import express from "express";
import { soulprint } from "soulprint-express";

const app = express();

// Protect entire API
app.use(soulprint({ minScore: 40 }));

// Strict: require DPoP proof (prevent token theft)
app.use(soulprint({ minScore: 65, requireDPoP: true }));

// Or specific routes
app.post("/sensitive", soulprint({ require: ["DocumentVerified", "FaceMatch"] }), handler);

// Access the verified identity + check if token was auto-renewed
app.get("/me", soulprint({ minScore: 20 }), (req, res) => {
  const renewedToken = res.getHeader("X-Soulprint-Token-Renewed");
  res.json({
    nullifier: req.soulprint!.nullifier,  // unique per human, no PII
    score:     req.soulprint!.score,
    ...(renewedToken ? { token_renewed: renewedToken } : {}),
  });
});

Fastify

import { soulprintFastify } from "soulprint-express";

await fastify.register(soulprintFastify, { minScore: 60 });

fastify.get("/me", async (request) => ({
  nullifier: request.soulprint?.nullifier,
}));

Run a Validator Node

Anyone can run a validator node. Each node runs two stacks simultaneously: HTTP (port 4888) + libp2p P2P (port 6888).

# Arranque simple โ€” mDNS descubre nodos en la misma LAN automรกticamente
npx soulprint node

# Con bootstrap nodes para conectar a la red global
SOULPRINT_BOOTSTRAP=/ip4/x.x.x.x/tcp/6888/p2p/12D3KooW... \
npx soulprint node

Output esperado:

๐ŸŒ Soulprint Validator Node v0.2.2
   Node DID:     did:key:z6Mk...
   Listening:    http://0.0.0.0:4888

๐Ÿ”— P2P activo
   Peer ID:    12D3KooW...
   Multiaddrs: /ip4/x.x.x.x/tcp/6888/p2p/12D3KooW...
   Gossip:     HTTP fallback + GossipSub P2P
   Discovery:  mDNS (+ DHT si hay bootstraps)

Node API:

GET  /info              โ€” node info + p2p stats (peer_id, peers, multiaddrs)
POST /verify            โ€” verify ZK proof + co-sign SPT
POST /reputation/attest โ€” issue +1/-1 attestation (propagado via GossipSub)
GET  /reputation/:did   โ€” get bot reputation
GET  /nullifier/:hash   โ€” check anti-Sybil registry

Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Layer 4 โ€” SDKs (soulprint-mcp, express)      โœ… Done  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Layer 3 โ€” Validator Nodes (HTTP + anti-Sybil)  โœ… Done โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Layer 2 โ€” ZK Proofs (Circom + snarkjs)         โœ… Done โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Layer 1 โ€” Local Verification (Face + OCR)      โœ… Done โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

On-Demand ML Models

AI models are never running persistently:

Idle state:       ~8MB RAM   (only the CLI)
During verify:    ~200MB RAM (InsightFace subprocess spawned)
After verify:     ~8MB RAM   (subprocess exits โ†’ memory freed)

Packages

Package

Version

Description

Install

soulprint-core

0.1.6

DID, SPT tokens, Poseidon nullifier, PROTOCOL constants, anti-farming

npm i soulprint-core

soulprint-verify

0.1.4

OCR + face match (on-demand), biometric thresholds from PROTOCOL

npm i soulprint-verify

soulprint-zkp

0.1.5

Circom circuit + snarkjs prover, face_key via PROTOCOL.FACE_KEY_DIMS

npm i soulprint-zkp

soulprint-network

0.4.1

Validator node: HTTP + P2P + credential validators + anti-farming

npm i soulprint-network

soulprint-mcp

0.1.5

MCP middleware (3 lines)

npm i soulprint-mcp

soulprint-express

0.1.3

Express/Fastify middleware

npm i soulprint-express

soulprint

0.1.3

npx soulprint CLI

npm i -g soulprint


ZK Circuit

The heart of Soulprint is a Circom circuit that proves:

"I know a cedula number + birthdate + face key such that
Poseidon(cedula, birthdate, face_key) == nullifier
AND the cedula is within valid Registradurรญa ranges"

Without revealing any of the private inputs.

Circuit stats:

  • 844 non-linear constraints

  • 4 private inputs (cedula, birthdate, face_key, salt)

  • 2 public inputs (nullifier, context_tag)

  • Proof generation: ~600ms on a laptop

  • Proof verification: ~25ms offline


Soulprint Token (SPT)

A base64url-encoded signed JWT. Contains no PII.

{
  "sip":         "1",
  "did":         "did:key:z6MkhaXgBZ...",
  "score":       45,
  "level":       "KYCFull",
  "country":     "CO",
  "credentials": ["DocumentVerified", "FaceMatch"],
  "nullifier":   "0x7090787188...",
  "zkp":         "eyJwIjp7InBpX2EiOlsi...",
  "issued":      1740000000,
  "expires":     1740086400,
  "sig":         "ed25519_signature"
}

Trust Scoring

Credential          | Score
--------------------|-------
EmailVerified       | +10
PhoneVerified       | +15
GitHubLinked        | +20
DocumentVerified    | +25
FaceMatch           | +20
BiometricBound      | +10
                    |
KYCFull (doc+face)  |  45/100

Services choose their own threshold:

soulprint({ minScore: 20 })   // email verified is enough
soulprint({ minScore: 45 })   // require doc + face KYC
soulprint({ minScore: 80 })   // require full biometric + extra

Anti-Sybil Protection

The nullifier is derived from biometric + document data:

nullifier = Poseidon(cedula_number, birthdate, face_key)
face_key  = Poseidon(quantized_face_embedding[0..31])
  • Same person, different device โ†’ same nullifier

  • Different person, same cedula โ†’ different nullifier (face doesn't match)

  • Person registers twice โ†’ nullifier already exists โ†’ rejected by validator


Supported Countries

Country

Document

Status

๐Ÿ‡จ๐Ÿ‡ด Colombia

Cรฉdula de Ciudadanรญa (MRZ + OCR)

โœ… Supported

๐ŸŒŽ Others

Passport (ICAO TD3 MRZ)

๐Ÿšง Planned


Development Setup

git clone https://github.com/manuelariasfz/soulprint
cd soulprint
pnpm install
pnpm build

Run integration tests

# ZK proof tests (no circuit compilation needed)
cd packages/zkp && node dist/prover.test.js

# Full integration tests
node -e "require('./packages/core/dist/index.js')"

Compile ZK circuit (first time only)

pnpm --filter soulprint-zkp build:circuits

Python dependencies

pip3 install insightface opencv-python-headless onnxruntime

Trust Score โ€” 0 to 100

Total Score (0-100) = Identity (0-80) + Bot Reputation (0-20)

Identity credentials (max 80 pts):

Credential

Points

How

EmailVerified

+8

Email confirmation

PhoneVerified

+12

SMS OTP

GitHubLinked

+16

OAuth

DocumentVerified

+20

OCR + MRZ (ICAO 9303)

FaceMatch

+16

InsightFace biometric

BiometricBound

+8

Device binding

Access levels:

Score

Level

Access

0โ€“17

Anonymous

Basic tools

18โ€“59

Partial KYC

Standard features

60โ€“94

KYCFull

Advanced features

95โ€“100

KYCFull + reputation

Premium endpoints


Bot Reputation (v0.1.3)

The reputation layer (0โ€“20 pts) builds over time from behavioral attestations issued by verified services.

Reputation starts at: 10 (neutral)
Verified service issues +1  โ†’  goes up  (max 20)
Verified service issues -1  โ†’  goes down (min 0)

Attestation format (Ed25519 signed):

interface BotAttestation {
  issuer_did: string;  // service DID (requires score >= 60 to issue)
  target_did: string;  // bot being rated
  value:      1 | -1;
  context:    string;  // "spam-detected", "normal-usage", "payment-completed"
  timestamp:  number;
  sig:        string;  // Ed25519 โ€” bound to issuer_did
}

Only services with score โ‰ฅ 60 can issue attestations. This prevents low-quality services from gaming the network.

Attestations propagate P2P across all validator nodes via libp2p GossipSub (with HTTP fallback for legacy nodes).


Anti-Farming Protection (v0.3.5)

The reputation system is protected against point farming. Detected farming โ†’ automatic -1 penalty (not just rejection).

Rules enforced by all validator nodes (FARMING_RULES โ€” Object.freeze):

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+ existing attestations before earning

Same-issuer cooldown

Max 1 reward/day from the same service

Session duration

Min 30 seconds

Tool entropy

Min 4 distinct tools used

Robotic pattern

Call interval stddev < 10% of mean โ†’ detected as bot

// Example: attacker trying to farm +1 every 60s
// Result: +1 โ†’ converted to -1 (automatic penalty)
POST /reputation/attest
{ did, value: 1, context: "normal-usage", session: { duration: 8000, tools: ["search","search","search"] } }
// โ†’ { value: -1, farming_detected: true, reason: "robotic-pattern" }

Credential Validators (v0.3.5)

Every validator node ships with 3 open-source credential verifiers โ€” no API keys required:

๐Ÿ“ง Email OTP (nodemailer)

POST /credentials/email/start   { did, email }
# โ†’ OTP sent to email (dev: Ethereal preview, prod: any SMTP)
POST /credentials/email/verify  { sessionId, otp }
# โ†’ issues credential:EmailVerified attestation, gossiped P2P

๐Ÿ“ฑ Phone TOTP (RFC 6238 โ€” no SMS, no API key)

POST /credentials/phone/start   { did, phone }
# โ†’ returns totpUri โ€” scan with Google Authenticator / Authy / Aegis
POST /credentials/phone/verify  { sessionId, code }
# โ†’ issues credential:PhoneVerified attestation

๐Ÿ™ GitHub OAuth (native fetch)

GET /credentials/github/start?did=...
# โ†’ redirects to github.com OAuth
GET /credentials/github/callback
# โ†’ issues credential:GitHubLinked attestation with github.login

Config: GITHUB_CLIENT_ID + GITHUB_CLIENT_SECRET + SOULPRINT_BASE_URL


Protocol Constants (v0.3.5)

All critical values are immutable at runtime via Object.freeze() in soulprint-core. Changing them requires a new SIP (Soulprint Improvement Proposal) and a protocol version bump.

import { PROTOCOL } from 'soulprint-core';

PROTOCOL.FACE_SIM_DOC_SELFIE    // 0.35 โ€” min similarity document vs selfie
PROTOCOL.FACE_SIM_SELFIE_SELFIE // 0.65 โ€” min similarity selfie vs selfie (liveness)
PROTOCOL.FACE_KEY_DIMS          // 32   โ€” embedding dimensions for face_key
PROTOCOL.FACE_KEY_PRECISION     // 1    โ€” decimal precision (absorbs ยฑ0.01 noise)
PROTOCOL.SCORE_FLOOR            // 65   โ€” minimum score any service can require
PROTOCOL.VERIFIED_SCORE_FLOOR   // 52   โ€” floor for DocumentVerified identities
PROTOCOL.MIN_ATTESTER_SCORE     // 65   โ€” minimum score to issue attestations
PROTOCOL.VERIFY_RETRY_MAX       // 3    โ€” max retries for remote verification

These constants are write-protected โ€” PROTOCOL.FACE_SIM_DOC_SELFIE = 0.1 throws at runtime.


Live Ecosystem โ€” mcp-colombia-hub

mcp-colombia-hub is the first verified service in the Soulprint ecosystem:

  • Service score: 80 (DocumentVerified + FaceMatch + GitHubLinked + BiometricBound)

  • Auto-issues -1 when a bot spams (>5 req/60s)

  • Auto-issues +1 when a bot completes 3+ tools normally

  • Premium endpoint trabajo_aplicar requires score โ‰ฅ 40

npx -y mcp-colombia-hub

Security Model

Threat

Defense

Someone learns your DID

DID is public โ€” harmless without private key

Private key theft

Key lives in ~/.soulprint/ (mode 0600)

Fake cedula image

Face match required

Register twice

Nullifier uniqueness on validator network

Replay attack

Token expires in 24h + context_tag per service

Sybil attack

Biometric nullifier โ€” same face = same nullifier

DID substitution attack

Ed25519 signature bound to DID keypair


Roadmap

โœ… Phase 1 โ€” Local verification (cedula OCR + face match + nullifier)
โœ… Phase 2 โ€” ZK proofs (Circom circuit + snarkjs prover/verifier)
โœ… Phase 3 โ€” Validator nodes (HTTP + ZK verify + anti-Sybil registry)
โœ… Phase 4 โ€” SDKs (soulprint-mcp, soulprint-express)
โœ… Phase 5 โ€” P2P network (libp2p v2 ยท Kademlia DHT + GossipSub + mDNS ยท soulprint-network@0.2.2)
โœ… v0.3.7 โ€” Challenge-Response peer integrity ยท snarkjs critical fix ยท SPT auto-renewal
โœ… v0.3.5 โ€” Anti-farming engine ยท Credential validators (email/phone/GitHub) ยท Biometric PROTOCOL constants
๐Ÿšง Phase 6 โ€” Multi-country support (passport, DNI, CURP, RUT...)
๐Ÿ”ฎ Phase 7 โ€” On-chain nullifier registry (optional, EVM-compatible)

Phase 5f โ€” Auto-Renewal of SPT (v0.3.6) โœ…

SPTs (Soulprint Protocol Tokens) now renew automatically โ€” no more downtime when a 24-hour token expires.

How it works

[Bot SDK] โ”€โ”€detects near-expiryโ”€โ”€โ–บ POST /token/renew โ”€โ”€โ–บ [Validator Node]
                                        โ†‘ current SPT           โ†“ fresh SPT (24h)
[Middleware] โ—„โ”€โ”€โ”€ X-Soulprint-Token-Renewed: <new_spt> โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Renewal windows:

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

Validator endpoint

POST /token/renew
Body: { "spt": "<current_token>" }

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

Express middleware (automatic)

import { soulprint } from "soulprint-express";

app.use(soulprint({
  minScore: 40,
  nodeUrl: "https://validator.soulprint.digital",  // enables auto-renew
}));

// New token arrives in response header if renewed:
// X-Soulprint-Token-Renewed: <new_spt>
// X-Soulprint-Expires-In: 86400

MCP middleware (automatic)

import { requireSoulprint } from "soulprint-mcp";

server.use(requireSoulprint({
  minScore: 65,
  nodeUrl: "https://validator.soulprint.digital",
}));
// Renewed token propagated in context.meta["x-soulprint-token-renewed"]

Manual (any SDK)

import { autoRenew, needsRenewal } from "soulprint-core";

const check = needsRenewal(currentSpt);
if (check.needsRenew) {
  const { spt, renewed } = await autoRenew(currentSpt, { nodeUrl });
  if (renewed) saveSpt(spt);  // persist the new token
}

Phase 5g โ€” Challenge-Response Peer Integrity + snarkjs Fix (v0.3.7) โœ…

Critical bug fix โ€” soulprint-zkp@0.1.5

verifyProof() was silently broken since v0.1.0. The snarkjs CJS module has __esModule: true but no .default property โ€” TypeScript's __importDefault returned the module as-is, then code accessed .default.groth16 which was undefined. All ZK proof verifications crashed at runtime.

// โŒ Before (broken):
import snarkjs from "snarkjs";          // compiles to snarkjs_1.default.groth16 โ†’ undefined

// โœ… After (fixed):
import * as snarkjs from "snarkjs";     // compiles to snarkjs.groth16 โœ…

Challenge-Response Protocol (soulprint-network@0.3.7)

Peers now cryptographically verify that remote nodes are running unmodified ZK verification code before accepting them into the network.

Challenger                          Peer
    โ”‚                                 โ”‚
    โ”‚โ”€โ”€ POST /challenge โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚
    โ”‚   {challenge_id, nonce,         โ”‚
    โ”‚    valid_proof,                 โ”‚  verifyProof(valid_proof)   โ†’ true
    โ”‚    invalid_proof}               โ”‚  verifyProof(invalid_proof) โ†’ false
    โ”‚                                 โ”‚  sign(results, node_key)
    โ”‚โ—„โ”€โ”€ {result_valid: true, โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚
    โ”‚     result_invalid: false,      โ”‚
    โ”‚     signature: Ed25519(...)}    โ”‚
    โ”‚                                 โ”‚
    โ”‚  verify signature โœ…            โ”‚
    โ”‚  result_valid == true โœ…        โ”‚
    โ”‚  result_invalid == false โœ…     โ”‚
    โ”‚                                 โ”‚
    โ”‚  โ†’ PEER ACCEPTED                โ”‚

Attacks blocked:

Attack

Detection

ZK always returns true (bypass)

invalid_proof must return false

ZK always returns false (broken)

valid_proof must return true

Pre-computed / cached response

Fresh random nonce makes invalid_proof unique per challenge

Node impersonation

Ed25519 signature tied to node_did

Replay attack

30-second TTL on challenges

Invalid proof generation โ€” the challenger mutates the valid proof with a random nonce:

invalid_proof.pi_a[0] = (valid_proof.pi_a[0] + nonce) mod p

This produces a cryptographically invalid proof that snarkjs will always reject โ€” but it's unpredictable without the nonce.

Automatic peer verification โ€” POST /peers/register now runs verifyPeerBehavior() before accepting any peer. A peer with modified ZK code is rejected with HTTP 403.

Phase 5h โ€” DPoP: Demonstrating Proof of Possession (v0.3.8) โœ…

SPT tokens are bearer tokens โ€” stolen tokens could be used until expiry (24h). DPoP closes this window by requiring a fresh cryptographic proof with every request.

Without DPoP:  stolen SPT โ†’ attacker calls API โ†’ SUCCESS โœ—
With DPoP:     stolen SPT โ†’ attacker has no private key โ†’ 401 โœ“

How it works:

Every request carries X-Soulprint-Proof โ€” a payload signed with the user's Ed25519 private key:

{
  typ:      "soulprint-dpop",
  method:   "POST",           // HTTP method โ€” bound
  url:      "https://...",    // exact URL โ€” bound
  nonce:    "a3f1b2...",      // 16 random bytes โ€” unique per request
  iat:      1740000000,       // expires in 5 minutes
  spt_hash: sha256(spt),      // bound to THIS specific token
}
// Signed: Ed25519(sha256(JSON.stringify(payload)), privateKey)

Attacks blocked (8): token theft, replay, URL MITM, method MITM, DID mismatch, expired proof, malformed proof, foreign token reuse.

API:

import { signDPoP, verifyDPoP, serializeDPoP, NonceStore } from "soulprint-core";

const proof  = signDPoP(privateKey, did, "POST", url, spt);
const header = serializeDPoP(proof);  // base64url string โ†’ X-Soulprint-Proof

const result = verifyDPoP(header, spt, "POST", url, nonceStore, sptDid);
// result.valid โ†’ bool | result.reason โ†’ string

Phase 5i โ€” MCPRegistry: Verified MCP Ecosystem (v0.3.9) โœ…

A public on-chain registry of verified MCP servers. Agents can check whether a server is legitimate before trusting it.

Contract: MCPRegistry.sol on Base Sepolia
Address: 0x59EA3c8f60ecbAe22B4c323A8dDc2b0BCd9D3C2a
Admin: Soulprint Protocol (not any individual MCP)

Unverified MCP:  agent connects โ†’ no guarantee โ†’ risk โœ—
Verified MCP:    isVerified(0x...) โ†’ true on-chain โ†’ trusted โœ“

Registration flow:

# 1. Any dev registers their MCP (permissionless)
curl -X POST http://soulprint-node/admin/mcp/register \
  -d '{ "ownerKey": "0x...", "address": "0x...",
        "name": "My Finance MCP", "url": "https://...", "category": "finance" }'

# 2. Soulprint admin reviews and verifies
curl -X POST http://soulprint-node/admin/mcp/verify \
  -H "Authorization: Bearer ADMIN_TOKEN" \
  -d '{ "address": "0x..." }'
# โ†’ on-chain tx โ†’ MCPVerified event โ†’ permanent record

# 3. Anyone checks
curl http://soulprint-node/mcps/verified
# โ†’ [{ name: "My Finance MCP", badge: "โœ… VERIFIED", verified_at: "..." }]

Check from code:

import { isVerifiedOnChain, getMCPEntry } from "soulprint-network";

const trusted = await isVerifiedOnChain("0x...");  // โ†’ true/false, on-chain

const entry = await getMCPEntry("0x...");
// โ†’ { name, url, category, verified, verified_at, badge: "โœ… VERIFIED by Soulprint" }

Architectural separation:

Soulprint validator = protocol authority โ†’ admin endpoints (verify/revoke)
Individual MCPs     = participants โ†’ read-only (check status, list verified)
MCPRegistry.sol     = source of truth โ†’ on-chain, immutable, auditable

Phase 5j โ€” ProtocolThresholds: Mutable On-Chain Governance (v0.4.1) โœ…

Protocol thresholds (SCORE_FLOOR, VERIFIED_SCORE_FLOOR, FACE_SIM_*, etc.) now live on-chain in ProtocolThresholds.sol instead of being hardcoded.

Contract (Base Sepolia): 0xD8f78d65b35806101672A49801b57F743f2D2ab1

// Anyone can read
getThreshold("SCORE_FLOOR")         // โ†’ 65
getThreshold("FACE_SIM_DOC_SELFIE") // โ†’ 350 (= 0.35)
getAll()                            // โ†’ all 9 thresholds

// Only superAdmin can write
setThreshold("SCORE_FLOOR", 70)     // emits ThresholdUpdated event

// Admin transfer (2-step safety)
proposeSuperAdmin(addr) โ†’ acceptSuperAdmin()

Validator integration:

  • Node loads thresholds from blockchain at startup (non-blocking, fallback to local if RPC unreachable)

  • Auto-refresh every 10 minutes

  • New endpoint: GET /protocol/thresholds

{
  "source": "blockchain",
  "contract": "0xD8f78d65b35806101672A49801b57F743f2D2ab1",
  "thresholds": {
    "SCORE_FLOOR": 65,
    "VERIFIED_SCORE_FLOOR": 52,
    "MIN_ATTESTER_SCORE": 65,
    "FACE_SIM_DOC_SELFIE": 0.35,
    "FACE_SIM_SELFIE_SELFIE": 0.65,
    "DEFAULT_REPUTATION": 10,
    "IDENTITY_MAX": 80,
    "REPUTATION_MAX": 20
  }
}

Tests: 17/17 real flow tests on Base Sepolia (tests/protocol-thresholds-tests.mjs)


Protocol Spec

See specs/SIP-v0.1.md for the Soulprint Identity Protocol specification.


Contributing

See CONTRIBUTING.md. All countries welcome โ€” add your ID document format in packages/verify-local/src/document/.


License

MIT โ€” free for personal and commercial use.


Built for the age of AI agents. Every bot has a soul behind it.

-
security - not tested
F
license - not found
-
quality - not tested

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/manuelariasfz/soulprint'

If you have feedback or need assistance with the MCP directory API, please join our Discord server