Pago privado no rastreable. A deposita 10 XLM en un pool y entrega una nota secreta; C retira esos 10 XLM presentando una prueba de conocimiento cero. Nadie puede vincular on-chain el depósito con el retiro, y la misma nota no se puede usar dos veces.
Depósitos
…
Retiros
…
XLM en el pool
…
Contrato
Cómo funciona
1
Depósito. Alice genera nullifier y secret, calcula commitment = Poseidon(nullifier, secret) y lo inserta en un Merkle tree on-chain pagando 10 XLM. Guarda la nota off-chain.
2
Retiro. Carol reconstruye el árbol desde los eventos, genera una prueba Groth16 de que conoce una hoja del árbol, y el contrato la verifica con las host functions BN254 de Soroban. Paga 10 XLM a Dave.
3
Sin doble gasto. La prueba expone nullifierHash = Poseidon(nullifier). El contrato lo marca como gastado y rechaza cualquier segundo intento.
Sin instalar nada (2 min). Abre el contrato en stellar.expert: cada withdraw lleva la prueba Groth16 como argumento y solo tuvo éxito porque el pairing_check BN254 pasó on-chain. Quien deposita (alice) y quien retira (carol) no tienen relación.
2
Tests del contrato (5 min, solo Rust).cd contracts && cargo test: Poseidon == circomlib, una prueba real de snarkjs verifica y una alterada no, y el flujo completo con doble gasto rechazado.
3
Demo contra el contrato (10 min). Node + Stellar CLI + 3 cuentas de testnet. Tu propio depósito, tu propia prueba, tu propio retiro. Pasos exactos en el README.
git clone https://github.com/NovaCorpAI/veil && cd veil && npm i
stellar keys generate alice --network testnet --fund # idem carol y dave
STELLAR=$(which stellar) bash scripts/demo.sh # deposit → nota → withdraw con ZK → doble retiro rechazado
Lo que había que acertar: Poseidon on-chain idéntico a circomlib (Poseidon(1,2) verificado), y el orden c1‖c0 de las coordenadas G2 entre snarkjs y las host functions. Las dos cosas se validaron con tests antes del deploy.