DEV Community

Manuel Felipe Arias Pineda
Manuel Felipe Arias Pineda

Posted on • Originally published at manuelariasfz.github.io

I built an open ZK identity protocol for AI agents — prove you're human without revealing who you are

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.

GitHub logo 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.

License: MIT [npm soulprint npm soulprint-mcp Phase]() Built 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"

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
Enter fullscreen mode Exit fullscreen mode

Everything happens locally — your data never leaves your machine:

  1. EXIF rotation — fixes phone photos automatically
  2. CLAHE normalization — handles backlit/shadow conditions
  3. Tesseract OCR — reads your ID + MRZ
  4. ICAO 9303 check digits — validates MRZ is real (weights 7/3/1)
  5. InsightFace — matches face to document photo
  6. 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
Enter fullscreen mode Exit fullscreen mode

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..."
}
Enter fullscreen mode Exit fullscreen mode

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 }));
Enter fullscreen mode Exit fullscreen mode

Anti-Sybil protection

nullifier = Poseidon(cedula_number, birthdate, face_key)
face_key  = Poseidon(quantized_face_embedding[0..31])
Enter fullscreen mode Exit fullscreen mode

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
});
Enter fullscreen mode Exit fullscreen mode

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 });
});
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Links


MIT license. Built in one week. Country contributions very welcome!

Top comments (0)