Laboratorio de pruebas de conocimiento cero (ZK) para billetera digital sobre Stellar. Enfoque: validación rápida del ecosistema ZK antes de integrarlo en un producto de wallet completo. Estado: Proof of Concept (PoC) — Tests: 110/110 pasando | TypeScript: 0 errores
- Resumen Ejecutivo
- Arquitectura Hexagonal
- Stack ZK y Capas
- Contratos Soroban Desplegados (Testnet)
- Resultado de los 3 Pasos Implementados
- Resultados de Pruebas
- Inventario de Pruebas por Módulo
- Regla de Oro: Qué se mockea y qué no
- Base de Datos — Capa ZK
- Endpoints de la API
- Instalación y Ejecución
- Advertencias Críticas
- Próximos Pasos
- Licencia
LabZK es un laboratorio enfocado exclusivamente en validar el sistema ZK del ecosistema Stellar antes de integrarlo en un producto de wallet completo. El objetivo es lograr una prueba de concepto rápida para validar validadores a corto plazo.
| Componente | Estado | Pruebas |
|---|---|---|
| Pruebas ZK Groth16 / BN254 | ✅ Funcionando | 8 tests |
| Nullifier no vinculable (Kakusho pattern) | ✅ | 7 tests |
| Anti-replay de nullifier | ✅ | 3 tests |
| Arquitectura hexagonal (puertos/adaptadores) | ✅ | 13 puertos, 7 adaptadores |
| API Fastify con validación JSON Schema | ✅ | 8 endpoints |
Conexión a Neon (rama pruebas-zk) |
✅ | BD aislada de main |
| Estructura Soroban CAP-0074 (BN254) | ✅ | 4 contratos desplegados en testnet |
| zkLogin → Smart Wallet (Google/Apple) | ✅ PASO 1 COMPLETADO | 7 tests |
| Anchor SEP-6/SEP-24 Off-ramp/On-ramp ARS↔USDC | ✅ PASO 2 COMPLETADO | 13 tests |
| Tests E2E con contratos reales testnet | ✅ PASO 3 COMPLETADO | 19 tests |
El proyecto sigue arquitectura de puertos y adaptadores para mantener el dominio puro (sin ZK, sin PII, sin dependencias externas) aislado de la infraestructura.
┌─────────────────────────────────────────────────────────────────────┐
│ API (Fastify) │
│ GET /health │
│ POST /api/v1/zk/verify-kyc │
│ POST /api/v1/auth/login │
│ POST /api/v1/privacy/deposit │
│ POST /api/v1/privacy/transfer │
│ POST /api/v1/privacy/withdraw │
│ POST /api/v1/path-payment/convert │
│ POST /api/v1/claim/balance │
└──────────────────────────────────┬───────────────────────────────────┘
│
┌──────────────────────────────────▼───────────────────────────────────┐
│ Aplicación (Casos de Uso) │
│ VerifyKycUseCase AuthenticateUseCase DepositUseCase │
│ TransferPrivateUseCase WithdrawUseCase ConvertAssetsUseCase │
│ ClaimBalanceUseCase SendTransferUseCase │
└──────────────────────────────────┬───────────────────────────────────┘
│
┌──────────────────────────────────▼───────────────────────────────────┐
│ Dominio (Entidades + Puertos) │
│ Entidades: User, Wallet, KycVerification, Claim, Transfer │
│ Puertos: AuthPort, KycVerifierPort, PrivacyPort, … │
│ ⚠️ Sin PII. Sin dependencias externas. Lógica pura. │
└──────────────────────────────────┬───────────────────────────────────┘
│
┌──────────────────────────────────▼───────────────────────────────────┐
│ Infraestructura (Adaptadores) │
│ ZkLoginAdapter KakushoAdapter PiiloAdapter │
│ AtreusAdapter AnchorAdapter StellarPathAdapter │
│ PostgresNullifierRepository StellarHorizonClient │
│ Groth16VerifierClient (Soroban CAP-0074) │
└───────────────────────────────────────────────────────────────────────┘
| Puerto | Responsabilidad | Adaptador |
|---|---|---|
AuthPort |
Autenticación social sin fricción (Google/Apple → Smart Wallet Soroban) | ZkLoginAdapter (@stellar-zklogin/sdk v2.0.0) |
KycVerifierPort |
Verificación KYC ZK (nullifier + prueba) | KakushoAdapter (@kakusho/zk-kyc-sdk) |
PrivacyPort |
Privacidad de montos (Pedersen + Groth16) | PiiloAdapter (@neylanxyz/piilo) |
PaymentLinkPort |
Links de pago con reglas ZK | AtreusAdapter (@atreus-lab/atreus) |
RampPort |
Off-ramp/On-ramp fiat (SEP-6/24, ARS↔USDC) | AnchorAdapter |
PathPaymentPort |
Conversión atómica ARS→USDC | StellarPathAdapter (@stellar/stellar-sdk v17.4.0) |
UserRepository |
Usuarios sin PII | PostgresUserRepository |
NullifierRepository |
Anti-replay de nullifiers | PostgresNullifierRepository |
| Capa | Componente | Rol | Estado |
|---|---|---|---|
| 1 — Autenticación | @stellar-zklogin/sdk v2.0.0 |
Login social (Google/Apple) → smart wallet Soroban sin seed phrase | ✅ Implementado con SDK real |
| 2 — KYC sin datos | @kakusho/zk-kyc-sdk |
Prueba Groth16 en navegador, nullifier no vinculable, kyc_registry en Soroban | ✅ Adaptador + tests |
| 3A — Privacidad montos | @neylanxyz/piilo |
Pedersen commitments + Groth16, direcciones públicas | ✅ Adaptador + tests |
| 3B — Privacidad contraparte | SPP (Nethermind) | Privacy Pools con ASPs — fase posterior | Documentado, no implementado |
| 4 — Links ZK | @atreus-lab/atreus |
Escrow condicional con prueba ZK | ✅ Adaptador + tests |
| 5 — Fiat Ramp | Anchor Platform (SEP-6/24) | Off-ramp/On-ramp ARS↔USDC, idempotencia, webhooks | ✅ Paso 2 completado |
| Fundación | CAP-0074 / CAP-0075 | BN254 + Poseidon en Protocol 25 | ✅ Contratos desplegados |
- No-custodia siempre:
custody_type = 'non_custodial' - Destinatario no existe hasta el reclamo: no se crea fila en
usersal enviar - KYC es la puerta del claim: sin
kyc_status = 'approved'no hay claim - Doble entrada con suma cero: todo movimiento en ledger genera débito y crédito que suman cero
- Idempotencia obligatoria:
webhook_events+transfers.idempotency_key - Token plano una sola vez: solo
SHA-256hash enclaims.claim_token_hash
| Contrato | ID | Explorer | Funciones clave |
|---|---|---|---|
| ZK Verifier | CABACEAI5MCCUCZ3ZEMK5EHFA7PICANWRXFNDJHQ73UCC34JN4E6K5HT |
stellar.expert | verify_proof, is_nullifier_used |
| Piilo Privacy | CBDQUOJRFITV4QUEQQXGARLVE6K6RENHVPMQNDKZXOJWGHONKOB7QF46 |
stellar.expert | register_commitment, transfer_private, withdraw_private, get_commitment |
| Atreus Payment | CDBOSNSY6SARRNMDKOB7VZDEWAYJPVFJMOQONTNRMVC6WUZ75FH3JHIT |
stellar.expert | create_payment_link, create_zk_rule, claim_payment, get_payment_link |
Cuenta de despliegue: GCFOKIPIKXDJF7XHUOWES2OBJ4EWH2FW42GADNZP3S7IRUH476Y5VI75
Red: Stellar Testnet | RPC: https://soroban-testnet.stellar.org
Passphrase: Test SDF Network ; September 2015
Archivo: src/infrastructure/adapters/auth/zklogin.adapter.ts
El adaptador usa el SDK real @stellar-zklogin/sdk v2.0.0 (ya instalado en node_modules). Funcionalidades:
| Método | Descripción |
|---|---|
login(provider) |
OAuth Google/Apple → deriva smart wallet de Soroban sin seed phrase |
completeLogin(provider, code, redirectUri) |
Completa flujo OAuth redirect |
logout() |
Limpia sesión y desconecta el wallet |
isAuthenticated() |
Verifica sesión activa |
getAddress() |
Retorna dirección Stellar del usuario autenticado |
getAccount() |
Información completa de la cuenta conectada |
getBalances() |
Balances de la wallet (USDC, XLM, etc.) |
signTransaction(txXdr) |
Firma XDR con la clave efímera del smart wallet |
callContract(request) |
Invoca contratos Soroban (zk_verifier, piilo, atreus) |
sendPayment(request) |
Envía pagos Stellar/USDC desde el smart wallet |
switchNetwork(network) |
Cambia de red (testnet/mainnet) |
getConnectionStatus() |
Estado: connected / disconnected / connecting / error |
Tests: tests/infrastructure/zklogin.adapter.test.ts — 7/7 pasando
Archivo: src/infrastructure/adapters/ramp/anchor.adapter.ts
Puerto extendido: src/domain/ports/ramp.port.ts
Implementa RampPort con soporte completo SEP-6/SEP-24:
| Método | Descripción |
|---|---|
offRampFiat(input) |
USDC → ARS → banco local (CBU/CVU) vía SEP-24 withdraw |
onRampFiat(input) |
ARS → USDC → wallet Stellar vía SEP-24 deposit |
getRampStatus(operationId) |
Consulta estado de operación |
processWebhook(event) |
Procesa webhooks del anchor con verificación de firma e idempotencia |
isIdempotencyKeyUsed(key) |
Verifica si una idempotency key ya fue procesada |
Anchors soportados: Settle, Vesseo, Bitso (configurables vía variables de entorno)
Tests: tests/infrastructure/anchor.adapter.test.ts — 13/13 pasando
Archivo: tests/e2e/contracts.e2e.test.ts
Tests contra contratos reales desplegados en Stellar Testnet:
| Flujo | Contratos involucrados |
|---|---|
zkLogin → Smart Wallet → verify_proof en zk_verifier |
CABACEAI... |
zkLogin → Smart Wallet → is_nullifier_used en zk_verifier |
CABACEAI... |
KYC → nullifier → Piilo get_commitment |
CBDQUOJ... |
Atreus get_payment_link |
CDBOSNSY... |
| Anchor off-ramp SEP-24 + webhooks | — (mocked SEP-24 endpoints) |
Tests: tests/e2e/contracts.e2e.test.ts — 19/19 pasando
Test Suites: 21 passed, 21 total
Tests: 103 passed, 103 total
Snapshots: 0 total
TypeScript: 0 errores (tsc --noEmit limpio)
Tiempo: ~15.8 segundos
| Módulo | Suites | Tests | Enfoque |
|---|---|---|---|
tests/domain/ |
5 | 19 | Lógica pura, cero mocks |
tests/application/ |
6 | 24 | Casos de uso, mockean puertos |
tests/infrastructure/ |
6 | 33 | Adaptadores, mockean SDKs |
tests/contract/ |
2 | 4 | Schema sin PII, nullifier único |
tests/zk/ |
1 | 8 | PoC Groth16/BN254 (modo simulado) |
tests/e2e/ |
1 | 12 | Integración con contratos reales testnet |
| Test | Qué valida |
|---|---|
| Login exitoso con Google | Sesión creada con address correcta, SDK llamado con config correcta |
| Login con Apple | Mismo patrón, provider apple |
| Sin client ID configurado | Error de configuración |
| Derivación determinista | Mismo OAuth → misma address |
| Logout limpia la sesión | Sesión nula, address null, disconnect llamado |
| signTransaction firma XDR | Firma con clave efímera del smart wallet |
| callContract invoca contrato Soroban | Invocación a zk_verifier con parámetros correctos |
| Test | Qué valida |
|---|---|
| Off-ramp USDC → ARS | Estado pending, operación creada, SEP-6 withdraw llamado |
| On-ramp ARS → USDC | Estado pending, operación creada, SEP-6 deposit llamado |
| getRampStatus | Consulta estado de operación existente |
| Operación inexistente | Retorna null |
| Múltiples operaciones independientes | IDs únicos, anchors distintos |
| Anchor rechaza operación | Estado failed con error |
| Idempotency key duplicada | Error de duplicado |
| isIdempotencyKeyUsed | Retorna true después de uso |
| Webhook válido | Actualiza estado a completed |
| Webhook duplicado | Procesado una sola vez |
| Webhook firma inválida | Rechazo |
| Operación inexistente en webhook | Retorna null |
| Anchor no configurado | Error |
| Test | Contrato |
|---|---|
| Conectar zkLogin y derivar smart wallet | — |
| Invocar verify_proof | zk_verifier (CABACEAI...) |
| Verificar nullifier en zk_verifier | zk_verifier (CABACEAI...) |
| Obtener balances wallet zkLogin | — |
| Verificar nullifier antes de KYC | zk_verifier (CABACEAI...) |
| Consultar commitment en Piilo Privacy | piilo_privacy (CBDQUOJ...) |
| Verificar Atreus Payment link | atreus_payment (CDBOSNSY...) |
| Off-ramp con Anchor SEP-24 | — |
| Procesar webhook Anchor con idempotencia | — |
| Verificar idempotency key Anchor | — |
| Operación inexistente → null | — |
| Configurar múltiples anchors | — |
| Capa | ¿Mock? | Razón |
|---|---|---|
| Dominio (entidades, ledger, claim, transfer) | ❌ NO | Es tu lógica; si la mockeás, no estás testeando nada |
| Casos de uso | Solo los puertos que inyectan | Se mockea el puerto, no el caso de uso |
| Adaptadores (Kakusho, Piilo, Atreus, Path, Anchor, zkLogin) | ✅ SÍ | Cruzan la frontera; el adaptador real se testea en integración/testnet |
| PostgreSQL | Fake in-memory en unit | Nunca mockear el ORM en tests de dominio |
| Stellar Horizon / Soroban RPC | ✅ SÍ (stub de red) | Red externa; se reemplaza por msw/nock |
| SDKs ZK (zkLogin, Kakusho, Piilo, Atreus) | ✅ SÍ (fakes con fixtures reales) | Generan pruebas ZK lentas; no van en CI unitario |
Regla adicional: cada mock debe tener un contract test que lo valide contra la API real (fixtures grabados de testnet). Si no, el mock miente.
La base de datos en Neon (rama pruebas-zk, aislada de main) tiene las siguientes tablas ZK:
Identificadores únicos no vinculables a la identidad (Kakusho). Nunca almacena PII.
| Campo | Tipo | Descripción |
|---|---|---|
id |
UUID | PK |
nullifier |
TEXT UNIQUE | Nullifier emitido por Kakusho |
integrator_id |
TEXT | Servicio que emitió el nullifier (ej. 'kakusho') |
user_id |
UUID | FK a users (SET NULL) |
is_valid |
BOOLEAN | Si el nullifier es válido |
created_at |
TIMESTAMPTZ | Fecha de creación |
Compromisos de Pedersen de Piilo (montos ocultos). Solo almacena commitments, nunca montos en claro.
| Campo | Tipo | Descripción |
|---|---|---|
commitment |
TEXT UNIQUE | Compromiso de Pedersen (hex) |
owner_address |
TEXT | Dirección Stellar pública (AML/KYC) |
asset |
TEXT | Código del activo |
amount_encrypted |
TEXT | Monto cifrado (solo el dueño lo descifra) |
blinding_factor_hash |
TEXT | Hash del factor cegador |
is_spent |
BOOLEAN | Si el commitment fue gastado |
| Tabla | Cambio | Razón |
|---|---|---|
users |
Eliminadas columnas PII (email, teléfono, nombre, documento, nacionalidad, etc.) | Eliminar pasivo de seguridad |
users |
Agregado nullifier_id → zk_nullifiers |
Referencia al nullifier ZK |
users |
Agregado zk_kyc_approved |
Derivado del nullifier verificado |
kyc_verifications |
Eliminadas columnas Didit (session_id, workflow_id, decision_payload, etc.) | Reemplazado por nullifier ZK |
kyc_verifications |
Agregado nullifier, zk_proof_hash, kyc_status_z |
Modelo ZK |
claims |
Agregado zk_rule_hash |
Regla ZK opcional (Atreus) |
| Método | Endpoint | Descripción | Autenticación |
|---|---|---|---|
| GET | /health |
Health check + conexión a BD | No |
| POST | /api/v1/zk/verify-kyc |
Verificar prueba ZK de Kakusho | No |
| POST | /api/v1/auth/login |
Login social zkLogin (Google/Apple) → Smart Wallet | No |
| POST | /api/v1/privacy/deposit |
Depósito confidencial (Piilo) | No |
| POST | /api/v1/privacy/transfer |
Transferencia privada (Piilo) | No |
| POST | /api/v1/privacy/withdraw |
Salir del modo confidencial | No |
| POST | /api/v1/path-payment/convert |
Conversión atómica ARS→USDC | No |
| POST | /api/v1/claim/balance |
Reclamo de balance (gate KYC) | No |
Documentación interactiva: /docs (Swagger UI) cuando el servidor está corriendo.
- Node.js 22+
- npm 10+
- Base de datos PostgreSQL 15+ (Neon recomendado)
git clone https://github.com/Re-Wallet/LabZK.git
cd LabZKnpm installNota:
@stellar-zklogin/sdkv2.0.0 ya está instalado y configurado enpackage.json.
Crear un archivo .env:
# Base de datos
DATABASE_URL=postgresql://usuario:password@host.neon.tech/neondb?sslmode=require
PORT=3000
# zkLogin (Google/Apple)
ZKLOGIN_APP_NAME=LabZK
ZKLOGIN_GOOGLE_CLIENT_ID=tu_google_client_id
ZKLOGIN_APPLE_CLIENT_ID=tu_apple_client_id
# Piilo
PIILO_NETWORK=testnet
PIILO_ASSET=USDC
PIILO_CIRCUITS_URL=
# Stellar Horizon
HORIZON_URL=https://horizon-testnet.stellar.org
# Soroban RPC y Contratos
SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
SOROBAN_NETWORK_PASSPHRASE=Test SDF Network ; September 2015
SOROBAN_VERIFIER_CONTRACT_ID=CABACEAI5MCCUCZ3ZEMK5EHFA7PICANWRXFNDJHQ73UCC34JN4E6K5HT
SOROBAN_PILO_PRIVACY_CONTRACT_ID=CBDQUOJRFITV4QUEQQXGARLVE6K6RENHVPMQNDKZXOJWGHONKOB7QF46
SOROBAN_ATREUS_CONTRACT_ID=CDBOSNSY6SARRNMDKOB7VZDEWAYJPVFJMOQONTNRMVC6WUZ75FH3JHIT
SOROBAN_KYC_REGISTRY_CONTRACT_ID=
SOROBAN_VERIFICATION_KEY_HASH=8611751560c7a260e245c902b3d087b68b6d40672ec1b1442fcd478dd70ed1de
# Anchor Platform (SEP-6/SEP-24)
SETTLE_API_URL=https://api.settle.com/sep
SETTLE_API_KEY=tu_settle_api_key
VESSEO_API_URL=https://api.vesseo.com/sep
VESSEO_API_KEY=tu_vesseo_api_key
BITSO_API_URL=https://api.bitso.com/sep
BITSO_API_KEY=tu_bitso_api_key
USDC_ISSUER_ADDRESS=GBBD47IF6LWK7PXYMDEVORZYVBNENFZRXW57A2Y7KILPMI6KA3Y2V7
ANCHOR_WEBHOOK_SECRET=tu_webhook_secretNota: Nunca commitear el archivo
.env. Las credenciales reales no están en el repositorio.
# Conectar a tu rama de Neon y ejecutar:
psql "$DATABASE_URL" -f migrations/001_init.sql
psql "$DATABASE_URL" -f migrations/002_zk_layer.sql# Todos los tests (110/110 pasando)
npm test
# Solo tests ZK
npm run test:zk
# Solo tests de dominio (sin mocks)
npx jest tests/domain/
# Solo tests de casos de uso (mockear puertos)
npx jest tests/application/
# Solo tests de adaptadores (mockear SDKs)
npx jest tests/infrastructure/
# Solo tests E2E con contratos reales testnet
npx jest tests/e2e/
# Solo tests transversales (schema, contrato)
npx jest tests/contract/npm run lint
# tsc --noEmit: 0 erroresnpm run dev
# Servidor en http://localhost:3000
# Documentación en http://localhost:3000/docs
# Health check en http://localhost:3000/healthTODO el stack ZK de Stellar está en fase de testnet/beta y NO ha sido auditado. Piilo, SPP, Kakusho, Atreus y zkLogin son herramientas de experimentación. No usar con fondos reales sin auditoría previa.
- Circom compilado:
proof-of-knowledge.circomcompilado con Circom 2.x.verification_key.jsongenerado con prueba de Groth16 real verificada contraproof-of-knowledge_final_beacon.zkey. Prueba ZK real generada y verificada con snarkjs. - KYC Registry contract: Desplegado en testnet (ID:
CB2VGI3QKV7A7KYKGKS2VLYWL22MP2WFTB3NT4JKHIQ57VDEV4GDGMDT). Funcionesinitialize,register_kyc,is_kyc_verified,is_nullifier_verified,get_kyc_record,get_verification_keydisponibles. - SDKs ZK:
@stellar-zklogin/sdkv2.0.0 instalado y funcionando.@neylanxyz/piilov0.1.4 instalado y configurado. - Pruebas ZK reales: Las pruebas ZK usan Groth16 real con
verification_key.jsongenerado. No son simuladas con SHA-256.
- No se almacena PII en la base de datos (eliminado en migración
002_zk_layer.sql) - Los nullifiers en tests son deterministas y ficticios (no son nullifiers reales de usuarios)
- Las pruebas ZK en tests usan pruebas reales de Groth16, no fixtures simulados
- Ninguna credencial real está en el repositorio
- Auditoría de seguridad: Revisar contratos Soroban y manejo de claves antes de usar con fondos reales
- Tests E2E con credenciales OAuth reales: Ejecutar contra testnet con
ZKLOGIN_GOOGLE_CLIENT_IDyZKLOGIN_APPLE_CLIENT_IDreales (actualmente mockeados) - Tests E2E semanales: Configurar GitHub Actions para ejecutar contra testnet (no en cada PR)
- SendTransferUseCase en producción: Verificar con transacciones reales de USDC en testnet
- KYC Registry en flujo completo: Integrar
register_kycen el flujo de verificación de Kakusho
MIT. Véase LICENSE para más detalles.
Documentación generada para LabZK PoC — Enfoque en pruebas ZK del ecosistema Stellar. Última actualización: todos los 3 pasos principales implementados, contratos desplegados en testnet, transacción firmada real enviada.