La Notaría Digital del Futuro: Cómo Demostrar que un Documento Existió Sin Revelar su Contenido

"Solidity + EIP-712 + Keccak-256 + Next.js + Foundry: La combinación tecnológica que hizo posible un sistema de notaría digital descentralizada donde la privacidad y la inmutabilidad coexisten."

En el mundo legal y corporativo, demostrar que un documento existió en un momento determinado sin revelar su contenido es uno de los desafíos más comunes. Desde contratos confidenciales hasta títulos académicos, esta solución demuestra cómo la blockchain puede reemplazar a los notarios tradicionales — por una fracción del costo, disponible 24/7, y verificable por cualquiera en el mundo.


DocumentSignStorage decentralized registry

Subtítulo técnico: Hashing Keccak-256 y firmas EIP-712 on-chain para timestamping inmutable de documentos


🎯 Tus Documentos Digitales, Ahora con Prueba respaldada por Blockchain

Imaginate esto: Firmás un contrato importante hoy. Seis meses después, la otra parte dice que nunca existió. ¿Cómo la demostrás que está equivocada? Con DocumentSignStorage, podés demostrar que un documento existió en un timestamp específico — sin revelar nunca su contenido.


📊 El Problema: Confiar en Terceros para Demostrar Existencia

Actualmente, demostrar que un documento existió en un momento determinado requiere confiar en un tercero:

Método Actual Debilidad
Notario público Caro ($100+), lento, centralizado
Email con adjunto Cualquiera puede cambiar la fecha
Almacenamiento en la nube El proveedor puede alterar timestamps
Bases de datos internas El admin puede modificar registros

La pregunta fundamental: ¿Cómo creás un timestamp que nadie pueda manipular — ni vos mismo?

flowchart LR
    subgraph Sistema Actual
        A[Documento] --> B[Tercero de Confianza]
        B --> C[Timestamp]
        C --> D[Puede ser Manipulado]
    end
    
    subgraph Solución Blockchain
        E[Documento] --> F[Hash Keccak-256]
        F --> G[Firma EIP-712]
        G --> H[Registro On-Chain]
        H --> I[Timestamp Inmutable]
    end
    
    style D fill:#ffcccc,stroke:#ff0000
    style I fill:#ccffcc,stroke:#00aa00

💡 La Solución: Criptografía como Notario

DocumentSignStorage reemplaza al notario humano con matemáticas. El sistema funciona en 4 pasos:

  1. Subir cualquier archivo — permanece en tu navegador, nunca toca la blockchain
  2. Calcular su hash Keccak-256 — la "huella digital" única del documento
  3. Firmar ese hash con tu wallet usando EIP-712 — demostrando que lo controlabas
  4. Registrar la firma on-chain — timestamp inmutable en Ethereum

La Magia: Los Hashes Son Únicos

Cada documento produce un hash completamente único. Cambiar una sola coma, un solo carácter, y el hash cambia por completo:

Original: "0x7f83b...e42a1" Modificado: "0x2c91f...a83d7" ← Completamente diferente

Esto significa: cualquiera puede demostrar después que un documento específico coincide con el hash on-chain — sin que la blockchain almacene el documento mismo.


🛠️ Tecnologías Usadas

Capa Tecnología Propósito
Smart Contracts Solidity 0.8.19, OpenZeppelin Implementación EIP-712 on-chain
Criptografía Keccak-256 Hash único del documento (32 bytes)
Firmas EIP-712 Firmas estructuradas con dominio
Testing Foundry (Forge + Anvil) Tests unitarios e integración
Frontend Next.js 15+, React 19, TypeScript Interfaz segura en navegador
Web3 Ethers.js v6 Interacción con blockchain
Validación Zod Esquemas de validación
Estilos TailwindCSS UI responsive
Tests Frontend Jest + Testing Library Suite de pruebas del frontend

🔧 Implementación: Arquitectura del Smart Contract

La Estructura de Documento

// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; struct Document { bytes32 documentHash; // La huella digital única address signer; // Quién lo firmó uint256 timestamp; // Cuándo fue registrado bytes signature; // Prueba criptográfica EIP-712 }

Ejemplo: Dominio EIP-712 en Solidity

El contrato define el dominio de firma EIP-712 que asegura que las firmas no puedan ser reutilizadas en otro contexto:

// contracts/DocumentSignStorageEIP712.sol pragma solidity ^0.8.19; import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; contract DocumentSignStorageEIP712 is EIP712 { // Nombre del dominio que aparece en MetaMask constructor() EIP712("DocumentSignStorage", "1") {} // Estructura tipada que el usuario firma bytes32 public constant DOCUMENT_TYPEHASH = keccak256( "Document(bytes32 documentHash,string purpose)" ); function hashStruct(Document memory doc) public pure returns (bytes32) { return keccak256(abi.encode( DOCUMENT_TYPEHASH, doc.documentHash, doc.purpose )); } }

Ejemplo: Hash Keccak-256 en TypeScript

El frontend genera el hash del documento en el navegador del usuario:

// frontend/src/utils/documentHash.ts async function generateDocumentHash(file: File): Promise<string> { // Leer el archivo como ArrayBuffer const arrayBuffer = await file.arrayBuffer(); const uint8Array = new Uint8Array(arrayBuffer); // Generar hash Keccak-256 usando viem const hash = keccak256(uint8Array); return hash; // ej: "0x7f83b...e42a1" } // Ejemplo de uso const file = document.querySelector('input[type="file"]')?.files?.[0]; if (file) { const hash = await generateDocumentHash(file); console.log('Hash del documento:', hash); // "0x7f83b...e42a1" — único para este archivo }

Ejemplo: Función de Verificación

Cualquiera puede verificar que una firma es válida:

// contracts/DocumentSignStorage.sol function verifyDocument( Document memory doc, bool expected ) public view returns (bool) { // Verificar que la firma es del signer indicado bytes32 structHash = hashStruct(doc); bytes32 typedHash = _hashTypedDataV4(structHash); bool isValid = SignatureChecker.isValidSignatureNow( doc.signer, typedHash, doc.signature ); return isValid == expected; } function isDocumentRegistered(bytes32 documentHash) public view returns (bool) { return registeredDocuments[documentHash]; }

Ejemplo: Test con Foundry

// test/DocumentSignStorage.t.sol pragma solidity ^0.8.19; import "forge-std/Test.sol"; import "../contracts/DocumentSignStorage.sol"; contract DocumentSignStorageTest is Test { DocumentSignStorage public contract; address public signer; function setUp() public { contract = new DocumentSignStorage(); signer = address(0x1234); } function testDocumentRegistration() public { bytes32 docHash = keccak256(abi.encodePacked("test-document")); vm.prank(signer); contract.registerDocument(docHash, "Prove existence"); assertTrue(contract.isDocumentRegistered(docHash)); assertEq(contract.getDocumentCount(), 1); } function testVerifyValidSignature() public { bytes32 docHash = keccak256(abi.encodePacked("contract.pdf")); vm.prank(signer); contract.registerDocument(docHash, "Legal contract"); Document memory doc = Document({ documentHash: docHash, signer: signer, timestamp: block.timestamp, signature: vm.sign(1, keccak256("test")) }); assertTrue(contract.verifyDocument(doc, true)); } }

💻 Frontend: Procesamiento Seguro en el Navegador

Flujo de Usuario

flowchart TD
    A[📄 Usuario sube archivo] --&gt; B[🔐 Navegador genera&lt;br/&gt;hash Keccak-256]
    B --&gt; C[🦊 Usuario firma con&lt;br/&gt;MetaMask EIP-712]
    C --&gt; D[📤 Frontend envía&lt;br/&gt;hash + firma]
    D --&gt; E[⛓️ Contrato registra]
    E --&gt; F[📋 Evento DocumentRegistered]
    F --&gt; G[✅ Cualquiera puede verificar]
    
    style A fill:#fff3e0,stroke:#ffa000
    style C fill:#e0f7ff,stroke:#00f2ff
    style E fill:#1a1a2e,stroke:#00f2ff,color:#fff
    style G fill:#ccffcc,stroke:#00aa00

Decisión Crítica de Diseño: El Archivo Nunca Llega a la Blockchain

Subir archivos on-chain cuesta cientos de dólares en gas por megabyte. La solución: solo el hash va on-chain.

Documento (1 MB) → Keccak-256 → Hash (32 bytes) → On-Chain Costo: $0 en navegador Costo: $0 en navegador ~$0.50 on-chain

Ejemplo: Firma EIP-712 en TypeScript

// frontend/src/hooks/useDocumentSign.ts import { useWalletClient, useChainId } from 'wagmi'; import { ethers } from 'ethers'; export function useDocumentSign() { const { data: walletClient } = useWalletClient(); const chainId = useChainId(); async function signDocument( documentHash: `0x${string}`, purpose: string ): Promise<`0x${string}`> { if (!walletClient) throw new Error('Wallet not connected'); const domain = { name: 'DocumentSignStorage', version: '1', chainId, verifyingContract: CONTRACT_ADDRESS, }; const types = { Document: [ { name: 'documentHash', type: 'bytes32' }, { name: 'purpose', type: 'string' }, ], }; const message = { documentHash, purpose }; // MetaMask mostrará datos legibles, no hex const signature = await walletClient.signTypedData({ domain, types, primaryType: 'Document', message, }); return signature; } return { signDocument }; }

📊 Comparación: Tradicional vs Blockchain

Aspecto Tradicional (Notario) Blockchain (DocumentSign)
Costo $100+ por documento ~$0.50 (gas)
Tiempo Cita previa, horas Instantáneo, 24/7
Verificación Manual, requiere presencia Cualquiera, online, global
Privacidad Notario ve el contenido Solo el hash es público
Inmutabilidad Documentos pueden alterarse Hash inmutable en blockchain
Alcance Local/jurisdicción Global, sin fronteras
Confianza En persona física En criptografía
Auditabilidad Limitada Completa, on-chain

📈 Aplicaciones en el Mundo Real

Esta tecnología habilita:

Caso de Uso Beneficio
Notaría digital Demostrar existencia sin almacenar
Credenciales académicas Certificar grados sin exponer datos
Propiedad intelectual Timestamp de trabajo creativo
Evidencia legal Demostrar documentos antes de disputa
Registros corporativos Trail inmutable para contratos

🤔 Por Qué Esto Importa

Privacidad + Inmutabilidad = Confianza

Los sistemas tradicionales te obligan a elegir: o almacenás el documento públicamente (transparente pero sin privacidad) o privadamente (privado pero sin prueba). DocumentSignStorage te da ambos — el hash prueba existencia sin revelar contenido.

Esta es la base para una notaría digital descentralizada que:

  • Cuesta fracciones de centavo vs. $100+ de un notario tradicional
  • Funciona 24/7 sin citas
  • Es verificable globalmente por cualquiera
  • Nunca puede ser manipulada retroactivamente

✅ Lecciones Aprendidas

  1. La UX de EIP-712 es superior: Los usuarios entienden qué están firmando cuando ven datos estructurados en lugar de strings "0x..."
  2. El almacenamiento solo con hash es esencial: Subir archivos on-chain es prohibitivamente caro; los hashes resuelven esto elegantemente
  3. Foundry es rápido: forge test corre en segundos comparado con minutos de Hardhat

🔗 Explorar el Código

Código fuente completo: github.com/87maxi/documentSignStorage

Probalo: Cloná el repo, ejecutá anvil para una blockchain local, y registrá tu primer documento. El frontend se conecta a MetaMask y firma usando EIP-712 — vas a ver el diálogo de firma estructurada en acción.


Proyecto del Master en Blockchain y Web3 — CodeCrypto Academy

💬

Comentarios

Powered by Giscus · GitHub Discussions

🧠 Web3 & Blockchain