Traducido del artículo original en inglés. Leer en inglés

Los sistemas de identidad de hoy depositan muchísima confianza en las empresas para que guarden tus datos de forma segura. Varios proveedores de KYC conservan tus datos indefinidamente. Cada app recibe todos tus datos aunque solo necesite saber que tienes más de 18 años, lo cual es demasiada información para compartir y dejar fragmentada por todas partes.
La identidad autosoberana SSI resuelve esto dándote el control de tus credenciales, puedes compartir solo lo necesario y probar afirmaciones sin revelar datos. básicamente, las credenciales verificables VC te permiten probar que tienes más de 18 años sin mostrar tu fecha de nacimiento exacta. Pero SSI tiene un problema - ¿dónde guardas las claves privadas que controlan tu identidad?
Hay muchas soluciones de fondo para esto y una de ellas son las biometric crypto identities, que no es de las más populares, la pregunta es: ¿y si tu biometric, por ejemplo tu face, fuera tu private-key? Sin seed phrases que perder, sin passwords que olvidar, sin keys que guardar. Tu biometric genera tu wallet, que guarda tus credenciales verificables, que pruebas usando ZKPs.
Esto es posible gracias a fuzzy extractors + zero-knowledge proofs. Así es como funciona.
Los sistemas criptográficos tradicionales se diseñaron para bits reproducibles. Pero la biometría rompe dos reglas clave de las que dependen estos sistemas:
Las claves deben ser perfectamente reproducibles - Exigen que la clave se reproduzca bit a bit. Tu clave privada para firmar transacciones debe ser idéntica todas y cada una de las veces. Pero la biometría tiene ruido, hay muchas variables que considerar. El escaneo de tu huella de hoy no va a coincidir bit a bit con el de ayer por variaciones del sensor, la posición del dedo, la humedad, la temperatura - y la lista sigue y sigue. Lo mismo pasa con los escaneos faciales, los patrones del iris o cualquier medición biológica.
Las claves deben ser uniformemente aleatorias - La seguridad criptográfica asume que tu clave se toma de forma uniformemente aleatoria de todo el espacio de claves. Pero los datos biométricos tienen estructura y patrones. Tu iris no es uniformemente aleatorio, tiene restricciones biológicas, similitudes a nivel de población y entropía concentrada en rasgos específicos.
Esto crea el típico problema del huevo y la gallina: no puedes usar la biometría directamente como clave tiene muchísimo ruido, no puedes guardarla de forma segura es PII e inmutable, y no puedes simplemente hashearla el ruido rompe las funciones hash, como ya sabes. Entonces, ¿qué haces?
¡Y aquí viene lo bueno!
Piensa en los fuzzy extractors como criptografía con corrección de errores para datos con ruido. Son un par de algoritmos, Gen generar y Rep reproducir, que hacen algo muy emocionante:
- Toma tu entrada biométrica con ruido w y devuelve un string uniformemente aleatorio R la clave criptográfica en sí y un string auxiliar público P. Aquí va otro dato curioso: P se puede publicar abiertamente sin comprometer la seguridad. No revela absolutamente ninguna información significativa sobre tu biometría.
- Toma una lectura biométrica distinta w' y el mismo auxiliar público P, y reproduce exactamente el mismo R siempre que w' esté lo bastante cerca del w original. Ya sé que eso suena aterrador, pero en este caso lo bastante cerca se mide como una distancia en algún espacio métrico distancia de Hamming para strings binarios, distancia de edición para secuencias, diferencia de conjuntos para vectores de características.
La garantía de seguridad es de teoría de la información, incluso conociendo P, la clave extraída R está -cerca de la uniforme siempre que tu biometría tenga una min-entropía . No hace falta ninguna suposición de dureza computacional. Un adversario con poder de cómputo infinito sigue sin poder distinguir R de una clave realmente aleatoria con una ventaja mayor que .
Las matemáticas son preciosas. Para un fuzzy extractor con parámetros :
Rw' de wLa construcción usa dos primitivas que trabajan juntas:
Un secure sketch es como un fuzzy commitment. devuelve un sketch s que revela la información justa para corregir errores, pero ni de lejos la suficiente para reconstruir el propio w. Luego recupera el w original a partir de un w' con ruido usando el sketch.
Para strings binarios con distancia de Hamming, la Syndrome Construction es óptima:
class SyndromeSketch:
def __init__(self, n, k, t):
self.n, self.k, self.t = n, k, t
self.syn_matrix = generate_bch_syndrome_matrix(n, k)
def sketch(self, w):
return np.dot(self.syn_matrix, w) % 2
def recover(self, w_prime, s):
s_prime = np.dot(self.syn_matrix, w_prime) % 2
error_syn = (s_prime - s) % 2
e = bch_decode(error_syn, self.t)
return (w_prime - e) % 2Lo que está pasando aquí:
sketch() calcula el síndrome de la biometría w y devuelve s de $n-k$ bitsrecover() corrige los errores del w_prime con ruido usando el sketch s$n-k$ bitsBCH extendido códigos Bose–Chaudhuri–Hocquenghem: pierdes 64 bits y corriges hasta erroresDespués de la corrección de errores, hasheas la biometría recuperada para extraer una clave uniforme, por ejemplo:
pub fn fuzzy_gen(biometric: &[u8]) -> (Vec<u8>, Vec<u8>) {
let sketch = secure_sketch(biometric);
let seed: Vec<u8> = (0..32).map(|_| rng.gen()).collect();
let key = extract_key(biometric, &seed);
let helper = [sketch, seed].concat();
(key, helper)
}
pub fn fuzzy_rep(noisy_biometric: &[u8], helper: &[u8]) -> Vec<u8> {
let (sketch, seed) = split_helper(helper);
let recovered = sketch_recover(noisy_biometric, sketch);
extract_key(&recovered, &seed)
}La función fuzzy_gen():
hash(biometric || seed)key, helper, donde helper = sketch, seedLa función fuzzy_rep():
Para las biometrías típicas:
Nota: Las representaciones biométricas en bruto contienen redundancia y correlaciones. Los códigos de iris muestran una proporción de ~8:1 entre los bits en bruto (2048) y la entropía en términos de teoría de la información (~249 bits). La extracción de claves debe respetar la entropía real, no el tamaño de la representación.

El modelo de confianza de SSI tiene tres actores:
El flujo completo: Biometría → Wallet → Credenciales → Pruebas ZK → Identidad verificada

Veámoslo paso a paso:
async function biometricToWallet(rawBiometric) {
const binary = await preprocessBiometric(rawBiometric);
const { key, helper } = await fuzzyExtractor.gen(binary);
const expandedKey = await hkdf(
key,
128,
'bip39-seed',
await crypto.getRandomValues(new Uint8Array(32))
);
const mnemonic = bip39.entropyToMnemonic(expandedKey);
const seed = await bip39.mnemonicToSeed(mnemonic);
const master = bip32.fromSeed(seed);
return {
btc: deriveBitcoin(master),
eth: deriveEthereum(master),
sol: deriveSolana(seed),
helper,
salt
};
}El proceso:
fuzzy_gen() → obtener una clave de 40-128 bits + el auxiliar públicoBIP39 → derivar la semilla de la wallet HDP2WPKH, ETH EVM, SOL Ed25519Para Solana en concreto es una curva distinta:
import { derivePath } from 'ed25519-hd-key';
const solSeed = derivePath("m/44'/501'/0'/0'", seed.toString('hex')).key;
const solKeypair = Keypair.fromSeed(solSeed);
const solAddress = solKeypair.publicKey.toBase58();Nota: Solana usa
Ed25519, nosecp256k1. Así que en tu implementación usaed25519-hd-keypara cumplir conSLIP-0010coincide conPhantom,Solflare.
Tu wallet ya tiene un DID, pero para probar cualquier cosa necesitas unas Verifiable Credentials VC. Aquí es donde empieza el triángulo de confianza.
El emisor verifica tus documentos off-chain y emite una prueba firmada criptográficamente:
async function requestCredential(wallet, issuerEndpoint, claimData) {
const connectionRequest = await issuerEndpoint.createConnection(wallet.did);
const credentialOffer = await issuerEndpoint.offerCredential({
type: 'KYCCredential',
schema: 'https://schema.org/kyc-v1',
claims: {
country: claimData.country,
birthYear: claimData.birthYear,
verified: true
}
});
const signedVC = await wallet.acceptCredential(credentialOffer);
await wallet.storeCredential(signedVC);
return signedVC;
}Lo que pasó:
VC firmada cryptographically con tus afirmacionesVC incluye la firma del DID del emisor, que prueba su autenticidadVCs de distintos emisores: educación, empleo, KYCEl schema de la credencial define qué hay en la VC:
{
"@context": "https://www.w3.org/2018/credentials/v1",
"type": ["VerifiableCredential", "KYCCredential"],
"issuer": "did:polygonid:polygon:amoy:2qH7...",
"issuanceDate": "2025-01-15T00:00:00Z",
"credentialSubject": {
"id": "did:polygonid:polygon:amoy:2qPz...",
"birthYear": 1995,
"country": "US",
"kycVerified": true
},
"proof": {
"type": "BJJSignature2021",
"issuerData": {...},
"signature": "0x..."
}
}Ahora, cuando un verificador te pide prueba que tienes más de 18, generas una ZKP a partir de esta credencial.
Aquí es donde puedes probar tus datos personales, como edad, ciudadanía, un título, dirección, fecha de nacimiento, etc., sin revelar esos datos sensibles.
async function proveAge(wallet, minAge) {
const credential = await wallet.getCredential('kyc');
const inputs = {
birthYear: credential.birthYear,
currentYear: 2025,
minAge: minAge,
signature: credential.signature
};
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
inputs,
"ageCheck.wasm",
"ageCheck.zkey"
);
return { proof, publicSignals };
}Lo que se prueba:
minAge añosEl circuito en Circom:
template AgeCheck() {
signal input birthYear;
signal input currentYear;
signal input minAge;
signal age <== currentYear - birthYear;
component check = GreaterEqThan(16);
check.in[0] <== age;
check.in[1] <== minAge;
signal output isValid;
isValid <== check.out;
}Verificación on-chain - Solidity:
contract IdentityGate {
mapping(address => mapping(uint256 => bool)) public verified;
function submitProof(
uint256 requestId,
uint[2] calldata pA,
uint[2][2] calldata pB,
uint[2] calldata pC,
uint[3] calldata pubSignals
) external {
require(zkpVerifier.verifyProof(pA, pB, pC, pubSignals), "Invalid proof");
require(pubSignals[2] >= minAge, "Age requirement not met");
verified[msg.sender][requestId] = true;
_grantAccess(msg.sender);
}
}El contrato:
Así que en este flujo puedes ver cómo tu biometría se convirtió en:
DID anclado a tu biometríaVC de emisores de confianzaZKP no revelan nada sobre tiY esto no son varios sistemas separados - es UN solo flujo unificado. La generación de la wallet ocurre una sola vez durante el registro. El flujo de ZKP se puede usar cada vez que haga falta verificar la identidad. Piénsalo así: puedes usar tu wallet biométrica igual que cualquier otra wallet cripto para transacciones normales. Pero cuando una dApp necesita KYC o cualquier tipo de verificación basada en PII como los casos de uso de Polygon ID, entra en acción la capa ZKP para probar credenciales sin exponer tus datos.
Cada agente de IA puede tener un DID derivado de su biometría. Imagina un agente de trading con IA que tiene su propia wallet y credenciales verificadas:
class TradingAgent {
constructor(agentFingerprint) {
this.wallet = await biometricToWallet(agentFingerprint);
this.did = await createDID(this.wallet);
}
async trade(dex, params) {
const proof = await this.proveReputation();
return await dex.trade(params, proof);
}
}El agente:
biometric hash de la red neuronalZKPSingularityNET está construyendo exactamente esto, un AI Agent Trust Registry donde los agentes prueban su autenticidad, seguridad y estado de auditoría usando credenciales verificables. El flujo biometric-wallet-ZKP permite que los agentes operen por su cuenta sin dejar de rendir cuentas.
Un protocolo DeFi necesita KYC pero no quiere guardar datos de usuarios responsabilidad regulatoria + un honeypot para hackers:
contract DeFiProtocol {
modifier onlyKYCVerified() {
require(identityGate.verified(msg.sender, KYC_ID), "KYC required");
_;
}
function borrowUndercollateralized(uint256 amount) external onlyKYCVerified {
_issueLoan(msg.sender, amount);
}
}El protocolo nunca ve tu pasaporte, tu nombre ni tu dirección. Cumplimiento logrado, privacidad preservada.
El mismo patrón sirve para:
async function login(website) {
const { key } = await fuzzyExtractor.rep(biometric, helper);
const siteKey = deriveKey(key, website.domain);
const signature = sign(challenge, siteKey);
await website.verify(signature);
}Cada sitio recibe una clave derivada única. No se envía ninguna contraseña, no hay credenciales que robar con phishing.
Tu biometría autoriza la transacción:
async function pay(merchant, amount) {
const wallet = await biometricToWallet(scan);
if (amount > 1000) {
await merchant.verifyProof(generateKYCProof(wallet));
}
await wallet.sendTransaction({ to: merchant.address, value: amount });
}BMONI + BKey ya implementa esto: todas tus wallets se derivan por completo de tu biometría "Tu cara" y se aprovisionan wallets en cadenas EVM y tokens específicos, ¡todo con tu cara!
contract AML {
bytes32 public scamMerkleRoot;
function checkClean(address wallet, bytes32[] calldata proof) public view returns (bool) {
bytes32 hash = keccak256(abi.encodePacked(wallet));
return !MerkleProof.verify(proof, scamMerkleRoot, hash);
}
}Las dApps consultan antes de permitir el acceso. Los estafadores conocidos quedan bloqueados en toda la red y los que tienen un historial de actividad rara también quedan marcados, mientras que los integradores reciben una notificación de la actividad sospechosa pasada.
Construir con identidades biométricas necesita infraestructura. El ecosistema ofrece tres componentes centrales:
Las organizaciones corren issuer nodes self-hosted para emitir credenciales:
docker-compose up -d
make import-private-key-to-kms private_key=<eth-key>
# access the ui at localhost:8088
# access the api at localhost:3001Funcionalidades:
DID del emisorschemas de credencialesVCsEjemplo de la API:
const issuerAPI = new IssuerNode({
url: 'https://issuer.example.com',
auth: { user: 'admin', password: 'secure' }
});
await issuerAPI.createIdentity({
blockchain: 'polygon',
network: 'amoy',
method: 'polygonid'
});
await issuerAPI.issueCredential({
did: 'did:polygonid:...',
schema: 'KYCCredential',
claims: { ageOver: 18 }
});Ecosistema de herramientas
tools.privado.id/buildertools.privado.id/query-builderschemas de la comunidadissuers de credenciales1: Variabilidad biométrica
Tu huella dactilar varía un 10-15% entre escaneos. Solución: los códigos BCH son óptimos para la corrección de errores a nivel de bit. Para un código de iris de 2048 bits que contiene ~249 bits de entropía real con un 10% de errores (~205 bits erróneos):
Distinción crítica: Los 2048 bits son la representación en BRUTO del código de iris la salida del algoritmo de Daugman. La entropía real en términos de teoría de la información es de solo ~249 bits por las correlaciones biológicas y la redundancia proporción de ~8:1.
2: Envejecimiento de la plantilla
Problema: Tu cara cambia con el tiempo envejecimiento, peso, vello facial, etc.
Solución: Registro periódico con migración de claves:
async function migrate(oldBio, newBio) {
const oldWallet = await recover(oldBio);
const { newWallet, newHelper } = await enroll(newBio);
await oldWallet.transferAll(newWallet.address);
await storage.update(newHelper);
}Vuelve a registrarte cada 2-3 años para la cara, cada 5 años para el iris, cada año para la voz, etc.
3: Detección de vida
Problema: Un atacante con tu foto no debería poder acceder a tu wallet.
Solución: Comprobaciones de vida durante la captura biométrica:
const checks = await Promise.all([
depthSensor.measure(),
motionDetector.verify(['blink', 'nod']),
thermalCamera.capture(),
videoAnalyzer.checkTemporal()
]);El Orb de WorldCoin hace esto con escaneos de iris: más de 25 comprobaciones de vida, imágenes 3D, cámaras infrarrojas + RGB. Funciona increíblemente bien - tasa de falsa aceptación .
4: Privacidad del string auxiliar
¿El P que expliqué antes filtra información biométrica? No. Según el teorema Dodis et al., el atacante aprende como máximo bits longitud del sketch, y la clave R sigue siendo estadísticamente aleatoria. Para nuestro iris: aprende 408 bits, quedan 392 bits de entropía.
Las piezas ya existen:
CompFE (MIT), corre en móviles¿Qué falta? Integraciones...
Así se ve usar este sistema de verdad en un entorno de producción
async function enrollUser() {
const bio = await captureLive();
const { key, helper } = await fuzzyExtractor.gen(bio);
const salt = await crypto.getRandomValues(new Uint8Array(32));
const expandedKey = await hkdf('sha256', key, salt, 'bip39-seed', 128/8);
const wallet = await deriveWallet(expandedKey);
const did = await createDID(wallet.eth);
const kycCred = await requestKYC(did, { passport, selfie });
await storage.save({ helper, salt, did, credentials: [kycCred] });
return { wallet, did };
}
async function accessDApp(dappURL) {
const bio = await captureLive();
const { helper, salt } = await storage.getHelperData();
const key = await fuzzyExtractor.rep(bio, helper);
const expandedKey = await hkdf('sha256', key, salt, 'bip39-seed', 128/8);
const wallet = await deriveWallet(expandedKey);
const proof = await generateProof(wallet, dappURL.request);
await submitProof(wallet, proof, dappURL.verifier);
await dappURL.grantAccess(wallet.address);
}
async function sendPayment(recipient, amount) {
const bio = await captureLive();
const { helper, salt } = await storage.getHelperData();
const key = await fuzzyExtractor.rep(bio, helper);
const expandedKey = await hkdf('sha256', key, salt, 'bip39-seed', 128/8);
const wallet = await deriveWallet(expandedKey);
await wallet.send({ to: recipient, value: amount });
}El flujo normal de la wallet y el flujo de identidad conviven. La ZKP entra en juego solo cuando hace falta una verificación, ya sea para KYC o para cualquier verificación basada en PII, como edad, ciudadanía, un título, dirección, fecha de nacimiento, etc.
Tu biometría es tu clave, tu wallet es tu identidad en todas las cadenas soportadas y nunca tienes que preocuparte por perder tu seed phrase o tus claves privadas, todo está ligado a tus atributos físicos, ¡y en la implementación de BMoni es tu cara!
Recibe los últimos artículos en tu bandeja de entrada.