18 KiB
Recuperación a largo plazo: diseño para decidir
Propuesta para cerrar los dos puntos que §74 de la v0.14 deja como trabajo futuro: «el formato de un objeto de release y de su fuente de archivo» y «el uso del reloj local en el paso 9.c de §63». Recoge el punto 6 de REVISION_completitud_protocolo.md y el 2.6 de REVISION_completitud_v0.13.md. No cambia nada todavía: la v0.14 está cerrada, así que todo lo de aquí iría a una v0.15.
Nota del 7-10. La v0.15 aprobada no sigue este diseño en un punto: el fichero
.dkrse descartó. El autor no lo había aprobado conscientemente, y guardarlo junto a la cápsula no tiene sentido: al crearla no hay release que guardar, y cuando llega la ronda la cápsula ya se abre. El objeto release se queda, sin extensión propia, y sale de archivos y servicios de caché que guardan todas las rondas (§50). Lo que aquí se dice del.dkres historia; lo vigente está en la spec v0.15 y en spec_v0.15/decisiones.md.
1. El problema
Una cápsula de Quicknet se abre con el release de su ronda: la firma BLS de 48 bytes que drand publica en round_time (§15, §63 paso 10). Sin ese release no hay apertura, ni en time_only ni en time_and_key (§7.6). El .dkc no lo contiene y el protocolo no lo guarda (§6, §50).
Lo que no falla
- Un release es público y se comprueba sin conexión contra el perfil pinneado:
e(H(M), clave_pública) == e(firma, G2)(§51, §63 paso 10). No caduca. - La firma BLS es única: para una ronda y una clave solo hay una firma válida. Cualquier copia que verifique es el release, venga de donde venga. Un archivo de releases no necesita firma ni confianza.
- La raíz de confianza está en el texto (§12) y en cada implementación, byte a byte. No depende de que drand siga publicándola.
agev1, CBOR determinista (RFC 8949) y BLS12-381 (RFC 9380) son formatos públicos y documentados fuera del proyecto.
Lo que falla, por horizonte
A 5 años
- Los endpoints cambian. Go, la CLI y la página
/inspectusanapi.drand.sh,api2yapi3(provider/drand/client.go,App/src/lib/inspector/drand.ts), y la CSP de la página los fija. Si cambian de nombre, ningún cliente lo encuentra solo. - El reloj local está mal: una pila del CMOS gastada, una máquina virtual o un cambio a propósito. Hoy el paso 9.c da
ERR_RELEASE_UNAVAILABLEaunque la persona tenga el release correcto en la mano. Lo fija el casoround not reached yetdemutations.json: un release válido y un reloj 1 ns antes deround_time.
A 20 años
- La red puede haberse parado. Fastnet es el precedente: drand la retiró, y
tleaún avisa de ello. Si se para antes de la ronda, la cápsula se pierde sin remedio (§7.6). Si se para después, abrirla depende de que alguien conserve el release. - Los relays pueden dejar de servir rondas antiguas. Hoy las sirven: los vectores usan rondas 1000 a 2000, de agosto de 2023. Nada lo garantiza (§5, §50), y no he encontrado ninguna promesa de drand sobre el histórico.
- El software puede no existir: los módulos están en
g.activething.com, que no es del autor, y no hay release publicada del módulo de Go.
A 50 años
- Lo anterior, y además los soportes y los lenguajes de hoy.
- BLS12-381 no es post-cuántico (§7.7). Eso rompe la confidencialidad, no la recuperación: quien rompa la clave puede calcular el release de cualquier ronda, que es justo el que abre la cápsula.
2. Opciones por pieza
a. Objeto release canónico
Hoy no existe. §47 deja release_material sin definir. Las implementaciones aceptan la respuesta JSON de drand, {"round": …, "signature": "…"}, o la firma sola en hexadecimal (App/src/lib/inspector/release-input.ts, y cmd/datekeys/main_test.go en Go). La CLI de Go no tiene ninguna opción para darle un release: solo relays.
Codificación
| Opción | A favor | En contra |
|---|---|---|
| JSON de drand | Ya lo aceptan TS y los tests de Go | No es canónico y no dice de qué cadena es |
| CBOR determinista (§58), como el Provider Profile (§11) | Una sola codificación, con el codec que ya tienen las tres implementaciones | El anexo debe describirlo |
CBOR con trama DKR1, como la .dkk (§40) |
Magic bytes (§20) | Otra trama que validar; el tipo ya va en la clave 0 |
Recomendación: CBOR determinista sin trama, como el Provider Profile:
0 → "datekeys-release"
1 → 1
2 → chain_hash (32 bytes)
3 → round (entero, 1 a 2⁵³ − 1)
4 → signature (cadena de bytes; 48 en Quicknet)
Mide unos 113 bytes. Los lectores SHOULD seguir aceptando el JSON de drand como entrada del llamador, sin que sea el formato canónico.
Qué identifica la cadena
chain_hash: es el identificador de drand, el mismo del stanza tlock (§63 paso 8) y de los archivos ajenos a DateKeys. Recomendado.profile_hash: ata al perfil de DateKeys, pero nadie fuera del proyecto lo conoce.profile_id: un nombre, no un hash. §24 ya evita guardarlo dos veces.
Verificación. No hace falta confiar en el objeto. Se comprueba contra el perfil pinneado, en el paso 10 y en este orden:
- capas del objeto (§69.1):
ERR_NON_CANONICAL_CBORoERR_UNSUPPORTED_VERSION; chain_hashdistinto del pinneado:ERR_PROFILE_MISMATCH;- ronda:
ERR_ROUND_MISMATCH; - firma:
ERR_RELEASE_INVALID.
No hace falta ningún código nuevo. El 2 es nuevo en el paso 10, y eso exige una línea en §69.1. Como con la .dkk (§63), un lector MAY decodificar el objeto al recibirlo, pero informa sus errores en el paso 10.
Extensión y nombre: .dkr. El nombre del fichero no es del protocolo (§6). El SDK SHOULD usar el de la cápsula, como carta.dkc y carta.dkr. En un archivo, el nombre es la ronda.
Dónde puede vivir
| Lugar | Valoración |
|---|---|
Fichero .dkr junto al .dkc |
Recomendado. No cambia ningún formato y sirve para las cápsulas que ya existen |
Dentro del .dkc |
No: el payload llega hasta EOF (§22) y cambiaría capsule_digest (§43). Sería un formato nuevo |
En la .dkk, extensión no crítica datekeys.release |
Posible tras la fecha, reescribiendo la .dkk; solo en time_and_key. Útil a quien solo tiene la llave con localizador (§44.1). Opcional, más tarde |
| En el plaintext del localizador | Imposible: se sella antes de que exista el release |
| En la nota pública (§24.1) | No: es anterior a la fecha y es texto del creador |
b. El paso 9.c y el reloj local
Hoy: si el instante actual es anterior a round_time, da ERR_RELEASE_UNAVAILABLE sin pedir el release (§63 paso 9.c). Las tres implementaciones lo aplican a toda fuente, también a un release suministrado: capsule/open.go, open.ts (la página /inspect pasa systemClock con el release pegado) y open.dart.
Por qué existe: no hace peticiones de red inútiles ni observables antes de la fecha, y falla pronto. Las dos razones son de red. Para un release que ya está en la mano no vale ninguna: su firma prueba que la ronda existe, y un dato local que no se puede verificar acaba pesando más que uno que sí, lo contrario de §3.
Opciones
- A. Dejarlo como está. El reloj veta releases válidos.
- B. 9.c solo antes de una petición de red. Un release suministrado, leído de un fichero o de un archivo local, no se compara con el reloj. Si su
round_timees posterior al reloj, el lector MAY avisar de que el reloj va atrasado. Para una fuente de red, 9.c sigue igual, pero la persona MAY pedir la petición expresamente («mi reloj puede estar mal»). - C. Quitar 9.c. Siempre se pide, y el relay contesta que aún no existe. Hace peticiones observables antes de la fecha.
- D. 9.c solo como aviso. Como C para la red.
Recomendación: B. Una firma válida de una ronda futura solo es posible si drand está comprometido (§7.6), y entonces la confidencialidad ya está perdida: el reloj no la protege. La única pérdida es una garantía falsa.
c. Archivo de releases de Quicknet
Tamaño: una ronda cada 3 s son 10 512 000 rondas al año. A 48 bytes, unos 505 MB al año y 1,4 MB al día. Desde el génesis (agosto de 2023) hasta finales de 2026, unos 1,7 GB; a 50 años, unos 25 GB. Las firmas no se comprimen.
Quién lo guarda
| Quién | A favor | En contra |
|---|---|---|
La persona, solo su ronda (.dkr) |
113 bytes, sin terceros ni fuga de privacidad | Depende de que lo guarde |
| El proyecto, la cadena entera | Una fuente para todos; no revela qué fechas interesan | Coste continuo. Ni el Gitea ni GitHub: hace falta otro sitio |
| Réplicas públicas (IPFS, otras) | Cualquiera replica y verifica, firma a firma | Nadie garantiza que sigan; IPFS solo conserva lo que alguien fija |
| El proyecto, solo las rondas de cápsulas conocidas | Pequeño | Revela fechas de cápsulas. No |
Formato del paquete (informativo): una cabecera CBOR {0: "datekeys-release-archive", 1: 1, 2: chain_hash, 3: primera_ronda, 4: número} seguida de las firmas de 48 bytes, una tras otra. La firma de la ronda r está en cabecera + (r − primera_ronda)·48. Una ronda que falta se escribe a cero y no verifica. Recomiendo un paquete por mes, de unos 42 MB, publicado con su SHA-256. Un fichero por ronda serían millones de ficheros, así que solo tiene sentido como .dkr individual.
Como fuente (§49): una fuente de archivo lee 48 bytes. En local, sin red: cuenta como release en la mano, y 9.c no se aplica (opción B). En remoto, con un rango de HTTP: es una fuente de red, verifica y descarta como un relay, y el servidor ve la ronda.
Recomendación: el .dkr de la persona es la vía principal. El archivo del proyecto es una red de seguridad, sin promesa de servicio en el texto normativo. §50 lo nombra como fuente posible. Dónde se aloja es decisión del autor (decisión 5).
d. Anexo de recuperación
Hace falta una página que permita abrir una cápsula sin software de DateKeys:
- Los parámetros de Quicknet de §12: clave pública,
chain_hash, génesis y periodo. En 50 años pueden no estar en ningún otro sitio. - La trama: PRELUDE de 16 bytes,
PUBLIC_HEADERen CBOR (clave 3: la DateKey, y con ella la ronda),SEALED_CONTROLyPAYLOAD_AGEhasta EOF (§22). - El release: verificarlo con cualquier librería BLS12-381 que tenga pairing y hash a G1 de RFC 9380, con el DST de §63.
- El stanza tlock:
U ‖ V ‖ W, y las fórmulas de sigma,FK_TIMEy r del paso 11, con H2, H3 y H4.tlev1.2.0 no admite un release dado, sino que lo pide a la red, según su ayuda: el anexo no puede depender de él. - Con
FK_TIME, descifrarSEALED_CONTROLcomo un ficheroagecon su file key. La herramientaageno acepta una file key, así que el anexo resume el formatoage: HKDF, el MAC de la cabecera y STREAM con ChaCha20-Poly1305. - En
time_and_key, el resultado es otro ficheroage. Se abre conage -dy la identity de la.dkk(suaccess_material, clave 5, §41) en formaAGE-SECRET-KEY-1. - En
CONTROL_CBOR, la clave 3 esI_PAYLOAD. En formaAGE-SECRET-KEY-1,age -dabrePAYLOAD_AGE. - En formato 3, la trama de
BODY(§29.2): saltar el área, leer el head y cortar los ficheros. En formato 2, los L primeros bytes.
Dónde vive
- En el spec, como anexo informativo. Es la fuente que se versiona y se traduce. Recomendado.
- Como texto fijo que el SDK guarda junto al
.dkc, igual para toda cápsula, sin datos de la cápsula. No revela nada que no revele ya el magicDKC1. Recomendado como SHOULD del SDK. - No en la nota pública: 1024 bytes, texto del creador sin autenticar (§24.1), y no cabe.
- No dentro del
.dkc: sería un formato nuevo.
La prueba de que el anexo basta: un script de scripts/check.sh que abre un fixture time_only y uno time_and_key con age y una librería BLS ajena al proyecto, sin código de DateKeys. Hoy tle solo se prueba con time_only (punto 6 de la revisión).
e. Al sellar y después
En el momento de sellar no existe el release, así que el escritor solo puede preparar:
- Avisar (amplía §50 y §53): en 20 años harán falta el
.dkc, el release de la ronda y, entime_and_key, la.dkk. El release solo existe después de la fecha. - Guardar el anexo junto al
.dkc(d). - Recordatorio: la app MAY programar un aviso local para
round_time. No recomiendo un fichero de calendario: sería otro formato que mantener. - Después de la fecha: el SDK SHOULD, la primera vez que tenga red tras
round_time, obtener el release, verificarlo y guardar el.dkrjunto al.dkc, aunque no abra la cápsula. Pedirlo revela la ronda al relay, como al abrir. Por eso, en un cliente sin estado como/inspect, solo cuando la persona lo pida. La página SHOULD ofrecer «Guardar el release» tras verificarlo, y aceptar un.dkrcomo entrada. time_only: guardar el.dkrjunto al.dkcno cambia la confidencialidad: tras la fecha, el release es público.time_and_key: el.dkrtambién SHOULD ir con la.dkkcuando lleva localizador, porque lo abre el mismo release (§44.1). §7.6 ya recomiendatime_and_keypara horizontes largos. No cambia nada más.
3. Qué cambiaría
Formatos
- Objeto nuevo: el release
.dkr. Es un formato nuevo, pero no toca el.dkcni la.dkk: toda cápsula existente, de los formatos 1, 2 y 3, se beneficia sin cambios. - Paquete de archivo: formato informativo, fuera del núcleo.
- Opcional: la extensión
datekeys.releasede la.dkk(§72), solo si se decide.
Texto normativo (v0.15)
- §20: la extensión
.dkr. - Una sección nueva junto a §47, «Objeto release»: schema, reglas y códigos. §47:
release_materiales ese objeto. §45: la respuesta de la Release API es ese objeto. - §49: el release desde un fichero o un archivo como fuente.
- §50: el archivo, el
.dkrjunto a la cápsula y el enlace con el sobre de §44.1 (nota 2 de la revisión v0.13). - §63 paso 9.c (opción B) y paso 10 (capas del objeto y
chain_hash). §69.1: la línea del paso 10. - §62.1: un SHOULD del SDK para guardar el release tras la fecha, y el anexo.
- §53: el aviso incluye el release.
- §74: quitar los dos puntos de trabajo futuro.
- §76: el cambio, con su caso reproducible,
round not reached yet. Nuevo SHA-256 enspec/README.md. - Anexo informativo «Recuperación sin software DateKeys».
datekeys.cddl: la reglarelease.
Implementaciones
- Go: en
provider, codificar y decodificar el objeto, y una fuente de fichero y otra de archivo. Encapsule.Open, distinguir el release en la mano de una fuente de red, por ejemplo con un campoOpenOptions.Releaseexclusivo conSource, y no aplicar 9.c al primero. En la CLI,decrypt -release FILE(.dkro JSON de drand) y una orden que obtiene, verifica y guarda el.dkr. Sin dependencias nuevas: el codec CBOR es propio y la BLS es la de drand/kyber. - TypeScript (
App/): enrelease.ts, el objeto;suppliedReleasemarcado como release en la mano; enopen.ts, 9.c como en Go. En/inspect: soltar un.dkr, «Guardar el release» y el aviso de reloj atrasado. Sin dependencias nuevas. - Dart: lo mismo en
release.dartyopen.dart, sin dependencias nuevas.
Testdata
vectors/release.json: codificaciones válidas y no canónicas, tipo y versión,chain_hashajeno, ronda ajena, firma inválida y punto en el infinito, cada caso con su código.mutations.json: el casoround not reached yetpasa a abrir, y otro igual con una fuente de red sigue dandoERR_RELEASE_UNAVAILABLE. Hace falta un campo nuevo que diga el tipo de fuente.- Un
.dkrpor ronda usada en los fixtures (1000, 1001, 1004 y 2000). - Un paquete de archivo pequeño, con una ronda a cero.
- Sincronizar con
scripts/sync-testdata.mjsytool/sync_testdata.dart. La guarda de TS exige que algún test use cada fichero nuevo.
4. Decisiones para el autor
- Cómo identifica el objeto la cadena.
chain_hash,profile_hashoprofile_id. Recomiendochain_hash. - Codificación. CBOR determinista sin trama, CBOR con trama
DKR1o solo el JSON de drand. Recomiendo CBOR sin trama, aceptando además el JSON de drand como entrada. - Paso 9.c. A, B, C o D. Recomiendo B.
- Dónde vive el release. Solo el
.dkrjunto al.dkc, o también la extensióndatekeys.releaseen la.dkk. Recomiendo el.dkrahora y la extensión más adelante, si hace falta. - Archivo de Quicknet. Ninguno, solo el
.dkr; o paquetes mensuales del proyecto en IPFS y otras réplicas. Dónde se alojan: no en el Gitea ni en GitHub. Recomiendo el.dkrcomo vía principal, el paquete como formato informativo y el alojamiento cuando haya sitio propio. - Anexo. Informativo o normativo; y si el SDK guarda o no su texto junto al
.dkc. Recomiendo un anexo informativo, que el SDK guarde como SHOULD. - Release API (§45 a §47). Mantenerla con el objeto como respuesta, o pasarla a un anexo informativo, como sugirió la revisión, porque no hay servicio. Recomiendo mantener §45 con el objeto y pasar la semántica HTTP a informativa.
- Versión. Todo esto va a la v0.15, junto con el texto de §7.6 y §71 si se quiere un solo cambio. Recomiendo una v0.15 solo con esto, porque cambia vectores.
Lo que se puede hacer sin decidir nada
- En la CLI de Go, aceptar un release dado como JSON de drand (
-release FILE). La v0.14 ya contempla el release suministrado (§63 paso 10). Igual en Dart, si la app lo necesita. - En
/inspect, ofrecer copiar o guardar el JSON de drand tras verificarlo. - Escribir el borrador del anexo y probarlo a mano con
agey una librería BLS ajena, sobre los fixtures detime_onlyytime_and_key. - Comprobar qué rondas antiguas sirven hoy los tres relays, desde la ronda 1, y anotarlo.
- Redactar el párrafo que une §50 y §44.1.