The AI agent revolution is here — bots book flights, call APIs, make decisions autonomously. But there's a growing problem nobody is solving:
How do you know the bot is legitimate?
Any script can pretend to be a helpful assistant. There's no accountability, no identity layer, no way to distinguish a trusted agent from a spam farm.
I spent a week building Soulprint — an open protocol that lets any AI agent prove there's a verified human behind it, using Zero-Knowledge Proofs. No central servers. No paid APIs. No PII stored anywhere.
manuelariasfz
/
soulprint
🔐 Decentralized KYC for AI agents — ZK proof of human identity behind any bot. No servers, no companies, no PII stored.
🔐 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.
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"…The idea in one sentence
"Every bot has a soul behind it. Prove yours — without revealing who you are."
How it works
Step 1 — Local verification (on your device)
npx soulprint verify-me --selfie me.jpg --document cedula.jpg
Everything happens locally — your data never leaves your machine:
- EXIF rotation — fixes phone photos automatically
- CLAHE normalization — handles backlit/shadow conditions
- Tesseract OCR — reads your ID + MRZ
- ICAO 9303 check digits — validates MRZ is real (weights 7/3/1)
- InsightFace — matches face to document photo
- Liveness detection — Laplacian + FFT banding (blocks photo-of-photo)
Models load on-demand and are killed after — zero persistent ML processes.
Step 2 — ZK Proof generation
A Circom circuit generates a Groth16 proof:
"I know a cedula_number + birthdate + face_key such that Poseidon(cedula, birthdate, face_key) = nullifier"
Without revealing any of those values. 564ms locally.
Private inputs (nobody sees): cedula_num, fecha_nac, face_key, salt
Public inputs (verifier sees): nullifier, context_tag
Proof: Poseidon(cedula, fecha_nac, face_key) == nullifier
Step 3 — Soulprint Token (SPT) issued
An Ed25519-signed token is created:
{
"did": "did:key:z6Mk...",
"score": 45,
"level": "KYCFull",
"country": "CO",
"credentials": ["DocumentVerified", "FaceMatch"],
"nullifier": "0x3f9a...",
"zkp": { "proof": "...", "publicSignals": [] },
"expires": 1740446400,
"sig": "base64url..."
}
No name. No ID number. No face data. Just proof.
Step 4 — Services verify in less than 50ms, offline
import { soulprint } from "soulprint-mcp";
// Protect any MCP server with 3 lines
server.use(soulprint({ minScore: 60 }));
Anti-Sybil protection
nullifier = Poseidon(cedula_number, birthdate, face_key)
face_key = Poseidon(quantized_face_embedding[0..31])
Same person on different devices → same nullifier
Different person with same ID → different nullifier (face doesn't match)
Can't register twice. Period.
What the verifier knows vs. doesn't
| Verifier KNOWS | Verifier does NOT know |
|---|---|
| Real human | Name |
| Valid ID document | ID number |
| Trust score (0-100) | Face data |
| Country | Birthdate |
Integration
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 }));
server.tool("my-tool", async (args, ctx) => {
const { nullifier, score } = ctx._soulprint;
// nullifier = unique per human, no PII
});
Express / REST API
import express from "express";
import { soulprint } from "soulprint-express";
const app = express();
app.use(soulprint({ minScore: 40 }));
app.get("/me", (req, res) => {
res.json({ nullifier: req.soulprint.nullifier });
});
Token delivery
The SPT token is passed via:
-
X-Soulprint: <token>HTTP header Authorization: Bearer <token>- MCP
capabilities.identity.soulprint
The ZK circuit (Circom 2.1.8)
pragma circom 2.0.0;
include "poseidon.circom";
template SoulprintIdentity() {
signal input cedula_num;
signal input fecha_nac;
signal input face_key;
signal input salt;
signal output nullifier;
signal output context_tag;
component hash = Poseidon(3);
hash.inputs[0] <== cedula_num;
hash.inputs[1] <== fecha_nac;
hash.inputs[2] <== face_key;
nullifier <== hash.out;
}
844 constraints. Groth16. 564ms prove, 25ms verify offline.
Multi-country protocol
Adding a country is one PR:
// countries/ES.ts
const ES: CountryVerifier = {
countryCode: "ES",
countryName: "Spain",
documentTypes: ["dni", "nie"],
parse(ocrText: string): DocumentResult { ... },
validate(docNumber: string): NumberValidation { ... },
};
export default ES;
Currently supported: Colombia (full), Mexico, Argentina, Venezuela, Peru, Brazil, Chile
Needed: Spain, Germany, India, Nigeria, South Africa... — CONTRIBUTING.md has the guide
Why not Worldcoin / Stripe Identity?
| Soulprint | Worldcoin | Stripe Identity | |
|---|---|---|---|
| Open source | MIT | Partial | No |
| No central server | Yes | No | No |
| No paid API | Yes | No | No |
| Works offline | Yes | No | No |
| ZK proof | Yes | Yes | No |
| MCP integration | Yes | No | No |
Numbers
- 90 tests passing (unit + pen testing + ZK)
- 564ms ZK proof generation
- 25ms offline verification
- 7 npm packages published
- 7 countries supported
- 0 bytes of PII stored anywhere
Install
npx soulprint install-deps
npx soulprint verify-me --selfie me.jpg --document cedula.jpg
npm install soulprint-mcp
npm install soulprint-express
npx soulprint node --port 4888
Links
- GitHub: https://github.com/manuelariasfz/soulprint
- Website: https://manuelariasfz.github.io/soulprint/
- npm: https://www.npmjs.com/package/soulprint
- Protocol Spec: https://github.com/manuelariasfz/soulprint/blob/main/specs/SIP-v0.1.md
- Add your country: https://github.com/manuelariasfz/soulprint/blob/main/CONTRIBUTING.md
MIT license. Built in one week. Country contributions very welcome!
Top comments (0)