El protocolo v1, exacto.
Todo lo que sigue está escrito contra código congelado y publicado. Cada constante puede comprobarse en el repositorio, y los vectores de prueba entre implementaciones fijan el comportamiento a nivel de bytes. Si esta página y el código alguna vez discrepan, el código es la verdad y la discrepancia es un error que conviene reportar.
La versión corta
- El texto de la nota se cifra en el dispositivo del remitente. Ningún servidor lo ve jamás.
- La clave de descifrado viaja solo en el fragmento del enlace, la parte tras
#, que los navegadores nunca envían a ningún servidor. - El descifrado ocurre en el navegador del destinatario, en JavaScript que cualquiera puede leer con Ver código fuente.
- El token de gestión detrás de quemar, caducidad y estado puede controlar una nota pero nunca descifrarla. El servidor almacena solo su hash SHA-256.
- El enlace completo no puede reconstruirse tras cerrar la pantalla de nota creada. La app conserva solo el id de la nota y el token, nunca la clave.
El enlace
burnpony.app/n/{id}#{key}{flags}. El id son 22 caracteres base64url acuñados por el servidor a partir de 16 bytes aleatorios. La key son 43 caracteres de base64url sin relleno que codifican exactamente 32 bytes aleatorios, generados en el dispositivo del remitente. El sufijo opcional flags es un punto más letras: p significa que se requiere una frase de contraseña, r que el remitente activó un acuse de lectura, para que la página pueda decirte ambas cosas antes de la revelación que consume vista. Todo tras # es un fragmento de URL, y los navegadores no envían fragmentos en las peticiones. La clave nunca llega a ningún servidor, no aparece en ningún registro y no forma parte de la nota almacenada. Esa única propiedad sostiene todo el diseño.
El envelope
Lo que el servidor almacena es una cadena JSON opaca: {"v":1,"pw":false,"salt":"…","nonce":"…","ct":"…"} — una versión de formato, el indicador de frase de contraseña, una sal de 16 bytes, un nonce de 12 bytes y el texto cifrado AES-256-GCM con su etiqueta de autenticación de 16 bytes añadida, todo en base64 estándar. El orden de las claves en el envelope no es significativo; se parsea como JSON.
La carga, byte a byte
El texto plano bajo el cifrado es {"v":1,"t":"<text>","ah":<seconds>} — la nota (hasta 50.000 caracteres) y el temporizador de ocultar automáticamente. Su serialización es determinista en toda implementación: claves exactamente en ese orden, sin espacios, escape limitado a \" \\ \n \r \t \b \f más \u00XX para los caracteres de control restantes, todo lo demás emitido literalmente como UTF-8. Las implementaciones de referencia en Swift, WebCrypto y Python producen bytes idénticos, y los vectores compartidos lo imponen.
Derivación de clave
La clave de cifrado es HKDF-SHA256(ikm, salt, info = "BurnPony-v1-key", 32 bytes). Sin frase de contraseña, ikm es la clave del fragmento. Con una, la frase se refuerza con PBKDF2-HMAC-SHA256 a 600.000 iteraciones sobre la misma sal y se antepone a la clave del fragmento. Las frases se canonicalizan antes de reforzarse en toda implementación: normalización Unicode NFC, luego recorte de espacios iniciales y finales incluido U+FEFF. Un espacio de teclado perdido o una entrada Unicode descompuesta se perdona; los cambios de mayúsculas y de espaciado interior, deliberadamente no. Una frase equivocada y un texto cifrado alterado fallan ambos simplemente la autenticación GCM; nada los distingue.
La API
Seis operaciones bajo /api, todas JSON. POST /notes almacena un envelope con un tope de vistas (1–100), una caducidad (300 a 2.592.000 segundos) y el indicador de acuse, devolviendo el id de la nota y un token de gestión de 64 hex de un solo uso. GET /notes/{id} consume una vista dentro de una transacción y anula el texto cifrado cuando se alcanza el tope. Con el token de gestión: GET …/status informa contadores y horas de acuse, DELETE quema de inmediato, PATCH re-temporiza una nota viva contada desde ahora, y POST /notes/{id}/push registra hasta cinco tokens APNs o FCM para la entrega de acuses. POST /report/{id} presenta un informe de abuso sin consumir una vista. El token se almacena del lado del servidor solo como hash SHA-256, y cada fallo que pudiera distinguir un token equivocado de una nota quemada de una nota que nunca existió devuelve el byte-idéntico {"error":"not_found"} — la constancia es deliberada.
Qué almacena el servidor
Texto cifrado (anulado al quemar o agotarse), contadores de vistas, marcas de tiempo, el hash del token, marcas de acuse, tokens push para notas con acuse, contadores de límite de tasa hasheados con un secreto local, e informes de abuso. Sin cuentas, sin correos, sin direcciones IP sin procesar, sin analítica. Un barredor elimina las filas caducadas, pero la caducidad también se aplica en el momento de lectura, así que un cron muerto nunca extiende la vida de una nota.
Acuses de lectura, mecánicamente
Los acuses se divulgan al destinatario antes de que revele — para eso existe el indicador r. La carga del push es deliberadamente genérica: la alerta dice solo que se abrió una nota, y la única clave personalizada es el id de la nota. El propio dispositivo del remitente reescribe la notificación localmente usando una etiqueta que no existe en ningún sitio salvo ese dispositivo; el servidor nunca conoce ninguna etiqueta.
Verifícalo tú mismo
Tres comprobaciones, sin necesidad de confianza. Una: la página del destinatario es reproducible — scripts/assemble_viewer.py --verify en el repositorio demuestra que la viewer.html publicada es byte-idéntica a sus tres partes auditables, y Ver código fuente en cualquier nota viva permite comparar con el archivo publicado. Dos: la criptografía está fijada — shared/burnpony_vectors.json contiene la verdad de referencia de una implementación independiente, y tanto la suite de pruebas de Swift como el núcleo WebCrypto embebido reproducen cada texto cifrado con exactitud. Tres: la pobreza del servidor es legible — server/schema.sql y el router de la API son lo bastante cortos para leerse de una sentada, y no hay nada en ellos que filtrar. Los límites honestos se mantienen igual: una nota revelada siempre puede copiarse o fotografiarse, y el operador podría servir código distinto al publicado — que es precisamente por lo que la página es reproducible y esta especificación existe, para que la sustitución sea detectable.
El recorrido más amable por la misma maquinaria está en la documentación; el modelo de seguridad y la vía de reporte están en la página de seguridad.