40 KiB
DateKeys Protocol Specification
DateKey, DateKeyCap (.dkc) y DateKeys Access Key (.dkk)
Borrador normativo v0.8.1
Estado: Draft / pre-estándar
Fecha: 25 septiembre 2026
Proyecto: DateKeys
Implementación de referencia prevista: Go
Proveedor temporal V1: drand Quicknet
1. Alcance
Este documento define exclusivamente el protocolo base DateKeys.
Define:
- DateKey: descriptor público de una condición temporal criptográfica.
- DateKeyCap (
.dkc): contenedor cifrado asociado a una DateKey y a una política de acceso. - DateKeys Access Key (
.dkk): credencial portable de acceso. - perfiles de proveedor;
- Quicknet como proveedor temporal V1;
- resolución fecha → ronda;
- framing de
.dkcy.dkk; - cifrado y bindings;
- políticas
time_onlyytime_and_key; - recipient X25519 V1;
- verificación;
- Release API;
- Release Cache;
- recuperación directa contra el proveedor;
- extensiones genéricas.
Este documento no define almacenamiento, descubrimiento, distribución ni aplicaciones construidas sobre DateKeys.
Una aplicación que necesite información de transporte, descubrimiento o integración DEBE expresarla mediante extensiones no críticas, sin modificar el núcleo del protocolo.
2. Terminología normativa
Las palabras MUST, MUST NOT, SHOULD, SHOULD NOT, MAY y OPTIONAL se interpretan conforme a RFC 2119 / RFC 8174.
- MUST / DEBE: requisito obligatorio.
- MUST NOT / NO DEBE: prohibición obligatoria.
- SHOULD / DEBERÍA: recomendación fuerte.
- MAY / PUEDE: opcional.
3. Principio rector
Nunca confiar en el servidor cuando la misma propiedad puede verificarse criptográficamente en el cliente.
Consecuencias:
- fecha → condición se calcula localmente;
- Provider Profiles se pinnean localmente;
- releases se verifican localmente;
.dkc,.dkk, APIs y relays se consideran entradas no confiables;- DateKeys no debe necesitar plaintext ni secretos finales de acceso.
4. Objetivos de seguridad
DateKeys persigue:
-
Confidencialidad temporal
Bajo los supuestos del proveedor, una condición temporal no debe poder satisfacerse antes del momento elegido. -
Confidencialidad frente al operador
DateKeys no debe necesitar conocer el plaintext ni la credencial final de acceso. -
Verificabilidad local
El SDK debe detectar condiciones, perfiles y releases manipulados. -
Integridad
Alteraciones del framing, cabecera, control o payload deben provocar fallo. -
Interoperabilidad
Implementaciones independientes deben producir y consumir objetos compatibles. -
Recuperación independiente
Siempre que el proveedor conserve o pueda servir el release necesario, el ciphertext siga disponible y las credenciales correspondientes existan, un objeto maduro debería poder abrirse sin pasar por la API DateKeys. -
Extensibilidad
Nuevos proveedores y extensiones no deben redefinir objetos antiguos.
5. No objetivos
El protocolo base no garantiza:
- existencia perpetua de Quicknet;
- conservación perpetua del histórico de releases;
- resistencia post-cuántica del timelock V1;
- revocación de una copia ya distribuida;
- anonimato absoluto;
- autoría legal por metadatos;
- fecha probatoria solo por
created_at; - protección frente a un dispositivo ya comprometido;
- control del plaintext después de un descifrado legítimo.
6. Fuera de alcance
El protocolo base NO define:
- dónde se almacena un
.dkc; - cómo se descubre un
.dkc; - cómo se distribuye un
.dkc; - políticas de disponibilidad del fichero;
- mecanismos de naming externos;
- mecanismos de entrega de una
.dkk.
Estas cuestiones pertenecen a capas superiores.
7. Modelo de amenazas
El protocolo asume potencialmente maliciosos:
7.1 Servidor DateKeys
Puede intentar:
- devolver una ronda pasada;
- devolver un perfil falso;
- devolver releases inválidos;
- correlacionar consultas;
- servir datos obsoletos;
- guardar copias del ciphertext.
7.2 Relay / MITM / DNS
Puede:
- suplantar endpoints;
- modificar respuestas;
- retrasarlas;
- denegar servicio.
7.3 Poseedor de .dkc
Puede:
- modificar bytes;
- truncar;
- reordenar;
- intercambiar cabeceras;
- intercambiar controles;
- intercambiar payloads;
- cambiar una política declarada;
- intentar downgrade.
7.4 Poseedor de .dkk
Debe tratarse como poseedor de una capacidad sensible.
7.5 Cadena de suministro
Un SDK o dependencia comprometidos pueden:
- sustituir la raíz de confianza;
- aceptar condiciones falsas;
- exfiltrar secretos;
- debilitar criptografía.
7.6 Provider comprometido
Si deja de cumplirse el supuesto de seguridad del provider, puede fallar la confidencialidad temporal.
7.7 Adversario cuántico futuro
El timelock Quicknet V1 no se considera post-cuántico.
Existe riesgo harvest now, decrypt later para ciphertexts de larga duración.
7.8 Dispositivo del creador
Si está comprometido antes o durante el cifrado, el protocolo no puede impedir la copia del plaintext o de secretos.
8. Objetos del protocolo
DateKey
↓
condición temporal pública
DateKeyCap (.dkc)
↓
objeto protegido
DateKeys Access Key (.dkk)
↓
capacidad adicional de acceso
9. Provider abstraction
Toda condición temporal se expresa como:
provider
profile
condition
Ejemplo Quicknet:
{
"provider": "drand",
"profile": "datekeys:quicknet:v1",
"condition": {
"round": 66884212
}
}
El protocolo no presupone que todos los providers utilicen rondas.
10. Provider Profile
Un Provider Profile es inmutable.
Debe definir:
profile_id;- provider;
- identificador de red;
- parámetros criptográficos;
- parámetros de tiempo;
- reglas de validación;
profile_hash.
Cualquier cambio criptográficamente relevante exige otro perfil.
11. Codificación canónica del Provider Profile
V1 usa Deterministic CBOR conforme a RFC 8949.
Quicknet se serializa como mapa CBOR con claves enteras:
0 → "datekeys-provider-profile"
1 → 1
2 → "datekeys:quicknet:v1"
3 → "drand"
4 → "quicknet"
5 → <32-byte chain hash>
6 → <public key bytes>
7 → 3
8 → 1692803367
9 → "bls-unchained-g1-rfc9380"
10 → <genesis seed bytes>
El hash:
profile_hash =
SHA-256(exact_deterministic_cbor_bytes)
La seguridad NO procede de un profile_hash autodeclarado por una entrada remota.
La seguridad procede del perfil pinneado/confiado localmente.
12. Quicknet Provider Profile V1
profile_id:
datekeys:quicknet:v1
provider:
drand
network:
quicknet
chain_hash:
52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971
public_key:
83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a
period_seconds:
3
genesis_time:
1692803367
genesis_seed:
f477d5c89f21a17c863a7f937c6a6d15859414d2be09cd448d4279af331c5d3e
scheme:
bls-unchained-g1-rfc9380
El SDK oficial DEBE pinnear estos parámetros o una representación firmada equivalente.
13. Root of trust
El cliente NO DEBE aceptar como raíz de confianza una clave pública o un perfil suministrados por el mismo endpoint que entrega el release.
Debe conocer previamente:
- chain hash;
- public key;
- scheme;
- genesis time;
- genesis seed;
- period;
- profile hash.
14. DateKey
Una DateKey:
- es pública;
- no es una clave simétrica;
- no es una private key;
- no es una
.dkk; - no es un secreto.
Representa una condición temporal verificable.
15. Resolución Quicknet fecha → ronda
Para Quicknet:
round_time(r) =
genesis_time + (r - 1) * period
El SDK MUST elegir la primera ronda cuyo:
round_time >= requested_unlock_at
Algoritmo:
candidate =
floor((timestamp - genesis_time) / period) + 1
if round_time(candidate) < requested_unlock_at:
candidate++
requested_unlock_at MAY tener precisión inferior al segundo. La comparación
round_time(candidate) < requested_unlock_at MUST realizarse a la precisión
completa del instante solicitado; truncar o redondear el instante antes de
comparar está prohibido.
Nunca se redondea hacia atrás.
16. Vector normativo de ronda
Con:
genesis_time = 1692803367
period = 3
requested_unlock_at = 2030-01-01T00:00:00Z
resultado:
round = 66884212
round_time = 2030-01-01T00:00:00Z
La ronda:
66432123
corresponde a:
2029-12-16T07:15:33Z
Los vectores definitivos MUST generarse desde la implementación de referencia y congelarse antes de v1.0.
17. Ataque de ronda pasada
Verificar únicamente una firma BLS válida NO basta.
El SDK MUST exigir, cuando disponga de la fecha solicitada:
received_round == locally_resolved_round
y:
round_time(received_round) >= requested_unlock_at
18. Representación dk1_
Por compatibilidad con el prototipo V1 conserva:
dk1_
con JSON UTF-8.
Payload canónico:
{"version":1,"network":"datekeys:quicknet:v1","round":66884212}
Representación:
dk1_<base64url(JSON_CANONICO)>
sin padding.
19. Canonicalidad dk1_
Existe una única cadena válida para una DateKey V1.
El parser MUST:
- Base64URL-decodificar.
- Parsear JSON.
- Validar campos.
- Reemitir el JSON canónico exactamente.
- Recrear
dk1_.... - Comparar byte a byte con la entrada.
Si no coincide:
ERR_DATEKEY_NON_CANONICAL
20. Extensiones de archivo
.dkc → DateKeyCap
.dkk → DateKeys Access Key
La extensión no sustituye los magic bytes.
21. capsule_id
Cada .dkc MUST contener un capsule_id.
Debe ser:
- aleatorio;
- opaco;
- independiente de identidad, fecha o servicio;
- de al menos 128 bits de entropía.
V1 recomienda exactamente:
16 random bytes
generados por CSPRNG.
22. Framing .dkc V1
V1 fija el siguiente prelude:
offset size field
0 4 MAGIC = "DKC1"
4 1 VERSION = 1
5 1 FLAGS = 0
6 2 RESERVED = 0
8 4 PUBLIC_HEADER_LEN (uint32 BE)
12 4 SEALED_CONTROL_LEN (uint32 BE)
16 ... PUBLIC_HEADER
... ... SEALED_CONTROL
... EOF PAYLOAD_AGE
No existe PAYLOAD_LEN.
El payload es el resto del fichero hasta EOF.
V1 MUST exigir:
FLAGS == 0
RESERVED == 0
23. PRELUDE
PRELUDE son exactamente los primeros 16 bytes:
MAGIC
VERSION
FLAGS
RESERVED
PUBLIC_HEADER_LEN
SEALED_CONTROL_LEN
El binding criptográfico de la cabecera utiliza estos bytes exactos.
24. PUBLIC_HEADER
V1 almacena la cabecera directamente como Deterministic CBOR.
Schema:
0 → "datekeycap"
1 → 1
2 → capsule_id (16 bytes)
3 → compact_datekey (text, canonical dk1_)
4 → access_policy
5 → critical_extensions
6 → noncritical_extensions
No se almacena profile_id por separado.
El perfil se obtiene de la DateKey, evitando dos fuentes de verdad.
25. Política de acceso declarada
Valores V1:
0 → time_only
1 → time_and_key
El parser MUST comprobar que la estructura criptográfica real de SEALED_CONTROL coincide con la política declarada.
Una discrepancia:
ERR_POLICY_STRUCTURE_MISMATCH
26. Header binding
CONTROL_CBOR MUST contener:
header_binding =
SHA-256(
PRELUDE ||
PUBLIC_HEADER_BYTES
)
donde PUBLIC_HEADER_BYTES son exactamente los bytes CBOR almacenados.
La implementación NO DEBE reserializar la cabecera para calcular el binding.
27. Validación pre-unlock
Antes de abrir el control pueden verificarse:
- magic;
- version;
- flags;
- reserved;
- longitudes;
- CBOR canónico;
capsule_id;- DateKey canónica;
- provider profile pinneado;
- coherencia fecha/ronda cuando la fecha solicitada esté disponible;
- extensions críticas conocidas.
La implementación SHOULD inspeccionar, antes de utilizar secretos o realizar una petición de red, la estructura visible de los ficheros age accesibles en ese momento:
OUTER_TIME_AGE: número y tipo de stanzas;PAYLOAD_AGE: número y tipo de stanzas;- argumentos visibles del stanza
tlock, cuando la implementación pueda inspeccionarlos de forma segura.
Esta inspección previa permite rechazar una cápsula mal formada antes de solicitar un release. Además de reducir trabajo innecesario, evita que una cápsula inválida genere una consulta observable en la Release API o en los relays.
Estas comprobaciones de stanzas previas al descifrado son estructurales. Su autenticidad frente a modificaciones de terceros queda confirmada únicamente cuando la cabecera age correspondiente supera la verificación del MAC durante la apertura. Frente a un creador que incluya vías alternativas de descifrado mediante stanzas adicionales, el MAC es válido por construcción y la comprobación estructural es la defensa del protocolo.
La inspección pre-unlock es una optimización de validación y privacidad y, por tanto, es un SHOULD. La aplicación de las reglas de cardinalidad de las secciones 29, 32 y 33 es un MUST y DEBE realizarse, como mínimo, en el momento de abrir cada fichero age: la identity que desenvuelve la file key MUST rechazar el fichero si el conjunto completo de stanzas recibido viola la regla correspondiente.
La autenticidad criptográfica definitiva de PUBLIC_HEADER se comprueba mediante header_binding tras abrir el control, salvo que una extensión adicional aporte autenticidad previa.
28. Tres ficheros age
La construcción time_and_key V1 utiliza tres ficheros age estándar e independientes:
1. PAYLOAD_AGE
2. INNER_ACCESS_AGE
3. OUTER_TIME_AGE
Cada fichero age genera su propia file key aleatoria de 16 bytes.
Se nombran:
FK_PAYLOAD
FK_ACCESS
FK_TIME
No se reutiliza ninguna de ellas.
29. PAYLOAD_AGE
El payload del usuario se cifra como un fichero age v1 estándar completo.
Durante la creación se genera:
I_PAYLOAD = X25519 identity aleatoria de 32 bytes
R_PAYLOAD = X25519 public recipient correspondiente
Entonces:
PAYLOAD_AGE =
age.Encrypt(
recipient = R_PAYLOAD,
plaintext = user payload
)
PAYLOAD_AGE MUST contener exactamente un stanza, de tipo X25519, para R_PAYLOAD.
age genera internamente:
FK_PAYLOAD = 16 random bytes
y la protege para R_PAYLOAD.
I_PAYLOAD se almacena dentro de CONTROL_CBOR.
30. Por qué PAYLOAD_AGE es un fichero age completo
V1 NO expone ni reimplementa STREAM como un subformato DateKeys.
El STREAM de ChaCha20-Poly1305, chunks de 64 KiB, nonce y final-chunk marker forman parte interna del formato age.
DateKeys trata PAYLOAD_AGE como bytes de un fichero age estándar.
Esto permite reutilizar:
- header MAC;
- recipient stanza;
- file key de 16 bytes;
- STREAM;
- detección de truncado;
- vectores y librerías existentes.
30.1 Binding CONTROL_CBOR ↔ PAYLOAD_AGE
El binding entre control y payload se obtiene mediante I_PAYLOAD.
CONTROL_CBOR contiene exactamente la identity privada I_PAYLOAD generada para esa cápsula. Esa identity solo abre el PAYLOAD_AGE cifrado para su recipient correspondiente.
Por tanto:
CONTROL_A + PAYLOAD_AGE_B
MUST fallar porque I_PAYLOAD_A no puede desenvolver la file key de PAYLOAD_AGE_B.
Este binding criptográfico es independiente de cualquier digest auxiliar del fichero.
31. CONTROL_CBOR
Una vez abierto, el control es siempre Deterministic CBOR. Una codificación no canónica MUST rechazarse aunque CONTROL_CBOR no participe directamente en un hash.
Schema base:
0 → "datekeys-control"
1 → 1
2 → header_binding (32 bytes)
3 → payload_identity (32 raw bytes, I_PAYLOAD)
4 → critical_extensions
5 → noncritical_extensions
No contiene el payload grande.
DateKeys V1 utiliza un único mecanismo de extensión. No existe un campo core separado para aplicaciones o semánticas superiores. Cualquier semántica adicional se registra como una extensión identificada por namespace.
Una extensión usa conceptualmente:
0 → extension_id
1 → extension_version
2 → data
Reglas:
extension_idMUST ser UTF-8 válido;(extension_id, extension_version)identifica el schema;- un mismo
extension_idMUST NOT aparecer simultáneamente encritical_extensionsynoncritical_extensionsdentro del mismo objeto; - salvo que el schema registrado permita multiplicidad, un mismo
extension_idMUST NOT aparecer más de una vez dentro del mismo objeto, aunque cambieextension_version; - el orden del array NO tiene semántica;
- el encoder canónico MUST ordenar por bytes UTF-8 de
extension_idy después por versión; - una extensión crítica desconocida MUST provocar rechazo;
- una extensión no crítica desconocida MAY ignorarse.
El protocolo base no interpreta data.
32. Política time_only
Construcción:
CONTROL_CBOR
↓
OUTER_TIME_AGE
donde:
OUTER_TIME_AGE =
age file
recipient = tlock(DateKey)
plaintext = CONTROL_CBOR
OUTER_TIME_AGE MUST contener exactamente un stanza, de tipo tlock.
age genera:
FK_TIME = 16 random bytes
y tlock envuelve FK_TIME para la ronda futura.
No existe INNER_ACCESS_AGE.
33. Política time_and_key
Construcción:
CONTROL_CBOR
↓
INNER_ACCESS_AGE
↓
OUTER_TIME_AGE
Primero:
INNER_ACCESS_AGE =
age file
recipient(s) = X25519
plaintext = CONTROL_CBOR
INNER_ACCESS_AGE MUST contener uno o más stanzas, todos ellos de tipo X25519, exactamente uno por recipient.
age genera:
FK_ACCESS = 16 random bytes
Después:
OUTER_TIME_AGE =
age file
recipient = tlock(DateKey)
plaintext = exact bytes of INNER_ACCESS_AGE
age genera:
FK_TIME = 16 random bytes
La política implementa:
TIME AND KEY
No dos envolturas paralelas.
34. SEALED_CONTROL
En .dkc:
SEALED_CONTROL =
exact bytes of OUTER_TIME_AGE
Debe ser siempre un fichero age completo.
Para time_only, el plaintext interno es CONTROL_CBOR.
Para time_and_key, el plaintext interno es INNER_ACCESS_AGE.
35. tlock strict mode
El recipient/identity tlock V1 MUST usar:
- chain hash pinneado;
- public key pinneada;
- scheme pinneado;
- round esperado.
DateKeys MUST desactivar confianza automática en chain hash suministrado por el ciphertext.
La root of trust procede del Provider Profile local.
36. Comprobación política ↔ estructura
Tras abrir OUTER_TIME_AGE:
Si access_policy = time_only
el resultado MUST ser directamente un CONTROL_CBOR canónico válido.
Si access_policy = time_and_key
el resultado MUST ser un fichero age v1 válido cuyo header contenga uno o más stanzas, todos ellos de tipo X25519.
Si la estructura no coincide:
ERR_POLICY_STRUCTURE_MISMATCH
36.1 Semántica de autenticidad
time_only NO proporciona autenticidad del creador, ni antes ni después de madurar la condición temporal, salvo que una extensión de firma explícita añada esa propiedad.
La falsificación es posible desde el momento de creación: cifrar hacia una ronda futura solo requiere la clave pública del Provider Profile y la condición temporal pública. Además, header_binding se calcula únicamente a partir de bytes públicos (PRELUDE y PUBLIC_HEADER). Un tercero puede construir otro CONTROL_CBOR con un header_binding correcto, generar su propio I_PAYLOAD, crear su propio PAYLOAD_AGE y sellar ese control hacia la misma DateKey sin esperar a que la ronda madure.
Por tanto capsule_id, DateKey y header_binding proporcionan coherencia interna, no autoría.
time_and_key añade una barrera adicional de acceso: un tercero que no posea la identity/capacidad requerida no puede abrir el INNER_ACCESS_AGE. Esta propiedad NO sustituye una firma del creador y no debe presentarse como prueba de autoría.
37. X25519 recipient V1
V1 adopta el recipient X25519 estándar de age.
Una identity X25519:
I = 32 random bytes from CSPRNG
El recipient correspondiente se deriva según la especificación age.
No se define un KEM propio.
38. Portable Access Key
Cuando el creador quiere generar una credencial portable, genera:
I_ACCESS = 32 random bytes
R_ACCESS = corresponding X25519 recipient
R_ACCESS se usa como recipient de INNER_ACCESS_AGE.
I_ACCESS se almacena como 32 bytes crudos dentro del .dkk.
Una I_ACCESS portable MUST generarse específicamente para una única capsule_id y MUST NOT reutilizarse en otra DateKeyCap. La reutilización convertiría una misma .dkk en capacidad válida para varias cápsulas y rompería el aislamiento esperado entre objetos.
La representación Bech32 AGE-SECRET-KEY-... MAY mostrarse/exportarse para interoperabilidad humana, pero NO es la representación binaria canónica del campo.
39. Múltiples recipients
INNER_ACCESS_AGE MAY contener múltiples stanzas X25519.
Todas envuelven la misma:
FK_ACCESS
Por tanto el CONTROL_CBOR y PAYLOAD_AGE no se duplican.
40. Framing .dkk V1
offset size field
0 4 MAGIC = "DKK1"
4 1 VERSION = 1
5 1 FLAGS = 0
6 2 RESERVED = 0
8 4 BODY_LEN (uint32 BE)
12 ... BODY_CBOR
V1 MUST exigir:
FLAGS == 0
RESERVED == 0
41. .dkk BODY_CBOR
Schema:
0 → "datekeys-access-key"
1 → 1
2 → credential_id (16 random bytes)
3 → capsule_id (16 bytes)
4 → access_type
5 → access_material
6 → verification_metadata
7 → critical_extensions
8 → noncritical_extensions
Para V1 portable X25519:
access_type = "x25519"
access_material = 32 raw identity bytes
42. credential_id
Debe ser:
16 random bytes
generados por CSPRNG.
Es un identificador opaco.
No deriva de la key ni de identidad personal.
43. verification_metadata
V1 define opcionalmente:
0 → capsule_digest
donde:
capsule_digest =
SHA-256(exact .dkc bytes)
Su función es:
- fallo rápido;
- detección de fichero equivocado;
- UX;
- deduplicación.
NO constituye una propiedad de seguridad necesaria para el acceso.
Una identity X25519 solo podrá abrir el INNER_ACCESS_AGE para el que fue utilizada; el digest no sustituye esa propiedad criptográfica.
Si no existe metadata de verificación, la clave verification_metadata MUST omitirse. Un mapa vacío no es una representación canónica válida de ausencia en V1.
44. Extensiones de aplicación en .dkk
Información de integración que no pertenezca al protocolo base DEBE ir en:
key 8 → noncritical_extensions
salvo que una futura especificación DateKeys registre una extensión crítica concreta.
De este modo las implementaciones no inventan campos core incompatibles.
45. Release API
La API recomendada se indexa por condición:
GET /v1/releases/{profile}/{condition}
Ejemplo Quicknet:
GET /v1/releases/datekeys:quicknet:v1/66884212
No requiere capsule_id.
46. Release Queue
La unidad de trabajo es:
profile + condition
No cápsula.
Muchas cápsulas de una misma ronda comparten un único release.
47. Release Cache
Un release publicado puede almacenarse como:
profile
condition
release_material
verified
verified_at
El servidor MUST verificarlo antes de marcarlo como válido.
El SDK MUST verificarlo de nuevo.
48. Multi-relay
La implementación SHOULD soportar varios relays independientes por disponibilidad.
La autenticidad procede de la verificación BLS.
No del hostname.
49. Recuperación directa contra el proveedor
Una implementación conforme SHOULD poder obtener un release directamente del proveedor temporal, sin pasar por la API DateKeys.
Para Quicknet:
.dkc
+
Provider Profile pinneado
+
release obtenido de un relay drand
+
.dkk si la política la exige
debe ser suficiente para ejecutar el flujo de apertura.
La API DateKeys es una capa de conveniencia, disponibilidad y caché, no una autoridad criptográfica obligatoria.
50. Dependencia del histórico de releases
La recuperación años después depende de que el release histórico necesario siga disponible.
Para Quicknet, esto puede provenir de:
- un relay drand que conserve/entregue rondas históricas; o
- una Release Cache válida conservada por otra fuente.
El protocolo NO debe asumir silenciosamente que cualquier proveedor conservará histórico indefinidamente.
Una aplicación que prometa horizontes largos SHOULD documentar esta dependencia.
51. Verificación de release Quicknet
El SDK MUST comprobar:
expected Provider Profile
expected chain hash
expected round
valid BLS signature
Un campo remoto:
verified = true
no tiene valor de seguridad.
52. DNS / MITM
Controlar DNS, TLS termination o un relay no debe permitir fabricar un release válido mientras:
- la raíz de confianza esté pinneada;
- la condición esperada se calcule localmente;
- la firma se verifique.
53. Harvest now, decrypt later
El SDK oficial SHOULD advertir al usuario en horizontes temporales largos.
Debe explicar:
- Quicknet V1 no es post-cuántico;
- el ciphertext puede permanecer disponible durante años;
- la seguridad futura depende del provider y de la criptografía subyacente.
El umbral temporal de la advertencia es política de producto, no parte de la semántica criptográfica del protocolo.
54. Extensiones
PUBLIC_HEADER, CONTROL_CBOR y .dkk pueden incluir extensiones mediante el mismo mecanismo.
Cada extensión declara:
0 → extension_id
1 → extension_version
2 → data
El par:
(extension_id, extension_version)
identifica el schema de la extensión.
La posición del array determina si la extensión es:
critical
o:
noncritical
Reglas:
- una extensión crítica desconocida → MUST reject;
- una extensión no crítica desconocida → MAY ignore;
- un mismo
extension_idMUST NOT aparecer simultáneamente encritical_extensionsynoncritical_extensionsdentro del mismo objeto; - salvo que un schema registrado permita expresamente multiplicidad, un mismo
extension_idMUST NOT aparecer más de una vez en el mismo objeto, aunque cambieextension_version.
La información específica de una aplicación que no pertenezca al núcleo DateKeys —incluidos datos de transporte o descubrimiento si una aplicación los necesita— MUST ir en noncritical_extensions y no en campos core del protocolo.
55. Integridad auxiliar
Hashes públicos MAY utilizarse para:
- identificación;
- caché;
- deduplicación;
- auditoría;
- UX.
No sustituyen:
- header MAC de age;
- X25519 wrapping;
- tlock;
- STREAM authentication;
header_binding.
56. Atomic plaintext output
El descifrado SHOULD usar:
- fichero temporal;
- stream transaccional;
- mecanismo equivalente.
No debe presentar plaintext parcial como válido si falla cualquier autenticación posterior.
57. Límites del parser
V1 recomienda:
PUBLIC_HEADER <= 1 MiB
SEALED_CONTROL <= 64 MiB
DKK BODY <= 16 MiB
PAYLOAD_AGE se procesa en streaming.
Los tamaños se validan antes de reservar memoria.
58. Canonical CBOR
Todas las estructuras CBOR del protocolo MUST:
- usar Deterministic CBOR;
- rechazar siempre codificaciones no canónicas;
- usar enteros de clave según los schemas normativos.
58.1 Regla global para campos opcionales
V1 usa una única convención:
Un campo opcional semánticamente ausente MUST omitirse.
No se debe representar ausencia mediante:
{}
[]
null
""
salvo que el schema de ese campo defina expresamente uno de esos valores como dato real.
Por tanto:
- metadata opcional ausente → clave omitida;
- arrays opcionales sin elementos → clave omitida;
- mapas opcionales sin entradas → clave omitida;
- listas de extensiones vacías → clave omitida.
Esto reduce representaciones equivalentes y simplifica vectores canónicos.
59. Supply-chain security
Implementaciones oficiales SHOULD:
- pinnear dependencias criptográficas;
- publicar SBOM;
- firmar releases;
- publicar hashes;
- publicar perfiles firmados;
- usar builds reproducibles cuando sea viable;
- fuzzear parsers;
- publicar vectores;
- someter v1.0 a revisión criptográfica externa.
60. Interfaces Go conceptuales
type Condition any
type Release any
type TimeProvider interface {
ProfileID() string
Resolve(time.Time) (Condition, error)
EffectiveTime(Condition) (time.Time, error)
FetchRelease(
context.Context,
Condition,
) (Release, error)
VerifyRelease(
Condition,
Release,
) error
}
61. Flujo de cifrado time_only
1. Resolver DateKey localmente.
2. Generar capsule_id.
3. Generar I_PAYLOAD X25519 (32 bytes).
4. Crear PAYLOAD_AGE:
age genera FK_PAYLOAD (16 bytes)
recipient = R_PAYLOAD
plaintext = payload
5. Construir PUBLIC_HEADER.
6. Construir PRELUDE.
7. Calcular header_binding.
8. Crear CONTROL_CBOR:
header_binding
I_PAYLOAD
critical_extensions
noncritical_extensions
9. Crear OUTER_TIME_AGE:
age genera FK_TIME (16 bytes)
recipient = tlock(DateKey)
plaintext = CONTROL_CBOR
10. SEALED_CONTROL = OUTER_TIME_AGE.
11. Escribir PRELUDE || PUBLIC_HEADER || SEALED_CONTROL || PAYLOAD_AGE.
File keys utilizadas:
FK_PAYLOAD
FK_TIME
62. Flujo de cifrado time_and_key
1. Resolver DateKey localmente.
2. Generar capsule_id.
3. Generar I_PAYLOAD X25519.
4. Crear PAYLOAD_AGE:
age genera FK_PAYLOAD
recipient = R_PAYLOAD
plaintext = payload
5. Construir PUBLIC_HEADER.
6. Construir PRELUDE.
7. Calcular header_binding.
8. Crear CONTROL_CBOR.
9. Crear INNER_ACCESS_AGE:
age genera FK_ACCESS
recipients = R_ACCESS[...]
plaintext = CONTROL_CBOR
10. Crear OUTER_TIME_AGE:
age genera FK_TIME
recipient = tlock(DateKey)
plaintext = exact INNER_ACCESS_AGE bytes
11. SEALED_CONTROL = OUTER_TIME_AGE.
12. Escribir .dkc.
13. Si se generó una identity portable:
escribir I_ACCESS cruda en .dkk.
Las tres file keys son:
FK_PAYLOAD
FK_ACCESS
FK_TIME
y MUST ser independientes.
63. Flujo de descifrado
1. Parsear DKC1.
2. Validar PRELUDE, version, flags, reserved, longitudes y límites.
3. Leer PUBLIC_HEADER exacto.
4. Validar CBOR canónico, DateKey canónica y Provider Profile pinneado.
5. SHOULD: inspeccionar OUTER_TIME_AGE antes de usar red o secretos:
exactamente un stanza;
de tipo tlock.
6. SHOULD: localizar PAYLOAD_AGE mediante las longitudes del PRELUDE
e inspeccionar únicamente su cabecera age:
exactamente un stanza;
de tipo X25519.
7. Resolver/verificar localmente la condición temporal.
8. SHOULD, cuando se haya realizado la inspección previa:
comprobar que la ronda del stanza tlock coincide con DateKey.round;
comprobar que el chain hash del stanza tlock coincide con el
Provider Profile pinneado.
Una discrepancia debe producir:
ERR_ROUND_MISMATCH o ERR_PROFILE_MISMATCH.
9. Obtener el release.
10. Verificar el release localmente.
11. Abrir OUTER_TIME_AGE.
La identity tlock MUST recibir y validar el conjunto completo de stanzas
y MUST rechazar el fichero salvo que contenga exactamente un stanza tlock.
La ronda y el chain hash MUST coincidir con la DateKey y con el
Provider Profile pinneado.
La apertura autentica además la cabecera age mediante su MAC.
12. Verificar que la estructura resultante coincide con access_policy.
13. Si time_and_key:
la identity X25519 que abre INNER_ACCESS_AGE MUST recibir y validar
el conjunto completo de stanzas;
MUST existir uno o más stanzas;
todos MUST ser X25519;
debe existir exactamente un stanza por recipient;
después, abrir INNER_ACCESS_AGE con la identity adecuada.
14. Parsear CONTROL_CBOR canónico.
15. Verificar header_binding.
16. Recuperar I_PAYLOAD.
17. Abrir PAYLOAD_AGE usando I_PAYLOAD.
La identity de payload MUST recibir y validar el conjunto completo de
stanzas y MUST rechazar el fichero salvo que contenga exactamente un
stanza X25519 para R_PAYLOAD.
18. Commit del plaintext solo si age completa sin error.
La inspección de los pasos 5, 6 y 8 es un SHOULD de fail-fast. La aplicación de las reglas de cardinalidad durante los pasos 11, 13 y 17 es un MUST. Una implementación no puede considerar válido un .dkc únicamente porque age haya podido desenvolver una file key: debe verificar también que el conjunto completo de stanzas cumple la política DateKeys V1.
PAYLOAD_AGE comienza exactamente en:
16 + PUBLIC_HEADER_LEN + SEALED_CONTROL_LEN
Una implementación MAY saltar directamente a ese offset y leer solo la cabecera age necesaria para la inspección previa. No es necesario leer ni descifrar el payload completo y esta comprobación no rompe el procesamiento en streaming.
Siempre que sea viable, una implementación SHOULD validar todo lo verificable localmente antes de realizar una petición de red o utilizar un secreto. Además de fallar antes, esta regla evita que cápsulas inválidas generen consultas observables en relays o en la Release API.
64. Mutation tests obligatorios
Antes de v1.0 deben fallar, como mínimo:
PUBLIC_HEADER_A + SEALED_CONTROL_B
SEALED_CONTROL_A + PAYLOAD_AGE_B
DateKey A + release de round B
chain hash cambiado
version cambiada
flags != 0
reserved != 0
payload truncado
payload age modificado
control modificado
dk1_ JSON no canónico
perfil desconocido
release de otra ronda
access_policy=time_only con estructura time_and_key
access_policy=time_and_key con estructura time_only
stanza adicional en OUTER_TIME_AGE
stanza adicional en PAYLOAD_AGE
stanza de tipo distinto de X25519 en INNER_ACCESS_AGE
ronda del stanza tlock distinta de DateKey.round
chain hash del stanza tlock distinto del Provider Profile pinneado
65. Test vectors Quicknet
Deben incluir:
- timestamp exactamente en frontera;
- un segundo antes;
- un segundo después;
- timestamp con fracción de segundo inmediatamente posterior a una frontera de ronda;
- fecha 2030-01-01;
- fechas próximas al genesis.
Cada vector:
requested timestamp
expected round
effective timestamp
66. Test vectors dk1_
Cada vector:
logical object
exact canonical JSON bytes
exact Base64URL bytes
final dk1_ string
La cadena textual debe ser única.
67. Test vectors .dkc
Los vectores .dkc V1 son fixtures de descifrado y validación, no pruebas que exijan reproducir byte a byte una llamada pública a age.Encrypt.
La implementación de age genera internamente randomness que su API pública no permite inyectar de forma estable. El protocolo NO exige parchear age ni tlock para controlar:
- file keys internas;
- efímeros X25519;
- nonces internos.
Cada vector oficial incluirá:
- bytes
.dkcfijos previamente generados; PUBLIC_HEADEResperado;- PRELUDE esperado;
- DateKey esperada;
header_bindingesperado;- estructura esperada (
time_onlyotime_and_key); - identity
.dkkcuando corresponda; CONTROL_CBOResperado después de abrir;I_PAYLOADesperado;- plaintext final esperado;
- resultado esperado de cada etapa de verificación.
La conformidad se demuestra descifrando/verificando el fixture y comparando resultados intermedios y plaintext.
68. Test vectors .dkk
Los vectores .dkk son igualmente fixtures de parseo, validación y uso.
Cada vector incluirá:
- bytes
.dkkfijos; credential_idesperado;capsule_idesperado;access_typeesperado;- 32 bytes crudos esperados de X25519 identity;
verification_metadataesperado cuando exista;- extensiones esperadas;
- resultado esperado al utilizar la identity contra el
INNER_ACCESS_AGEasociado.
No se exige reproducir los bytes de una .dkk partiendo de una generación aleatoria nueva.
69. Errores normativos
ERR_INVALID_MAGIC
ERR_UNSUPPORTED_VERSION
ERR_INVALID_FLAGS
ERR_NON_CANONICAL_CBOR
ERR_UNKNOWN_PROFILE
ERR_PROFILE_MISMATCH
ERR_DATEKEY_INVALID
ERR_DATEKEY_NON_CANONICAL
ERR_ROUND_MISMATCH
ERR_RELEASE_UNAVAILABLE
ERR_RELEASE_INVALID
ERR_ACCESS_REQUIRED
ERR_ACCESS_INVALID
ERR_POLICY_STRUCTURE_MISMATCH
ERR_HEADER_BINDING
ERR_INTEGRITY
ERR_EXTENSION_CRITICAL_UNKNOWN
70. Compatibilidad
Una implementación V1:
- MUST aceptar
DKC1yDKK1; - MUST rechazar major versions desconocidas;
- MUST rechazar critical extensions desconocidas;
- MAY ignorar noncritical extensions desconocidas;
- MUST mantener inmutable la interpretación de perfiles publicados.
71. Registro de perfiles
DateKeys SHOULD publicar un registro de Provider Profiles.
Cada entrada debería incluir:
- exact canonical CBOR;
- SHA-256;
- firma offline;
- fecha de publicación;
- estado.
72. Registro de extensiones
DateKeys MAY publicar un registro de extension_id.
Registrar una extensión no cambia el núcleo del protocolo.
73. Decisiones canónicas v0.8.1
DateKeys
= plataforma / protocolo
DateKey
= condición temporal pública
DateKeyCap
= .dkc
DateKeys Access Key
= .dkk
Quicknet
= provider V1
dk1_
= JSON canónico heredado del prototipo
Provider Profile
= Deterministic CBOR + genesis_seed
capsule_id
= 16 bytes aleatorios y opacos
DKC framing
= sin PAYLOAD_LEN
PUBLIC_HEADER
= Deterministic CBOR exacto
profile_id duplicado en header
= eliminado; la DateKey es la fuente única
header binding
= SHA-256(PRELUDE || exact PUBLIC_HEADER bytes)
payload
= fichero age v1 estándar completo
payload access
= X25519 identity I_PAYLOAD dentro de CONTROL_CBOR
time_only
= age(tlock → CONTROL_CBOR)
time_and_key
= age(tlock → age(X25519 recipient(s) → CONTROL_CBOR))
portable .dkk
= X25519 identity de 32 bytes crudos
extensions
= único mecanismo genérico de extensibilidad en PUBLIC_HEADER, CONTROL_CBOR y .dkk
recovery
= puede obtener release directamente del provider
historical release availability
= dependencia explícita del horizonte de recuperación
74. Aspectos todavía provisionales
Antes de v1.0 quedan por cerrar:
- schema CBOR final byte-a-byte de
PUBLIC_HEADER; - schema CBOR final byte-a-byte de
CONTROL_CBOR; - schema CBOR final byte-a-byte de
.dkk; - formato exacto de extensiones;
- límites definitivos de campos;
- vectores definitivos de Provider Profile;
- suite exacta de tests interoperables.
El framing base, la ausencia de PAYLOAD_LEN, el uso de age files estándar y la identity X25519 cruda de .dkk dejan de considerarse provisionales en este borrador.
75. Requisitos bloqueantes antes de v1.0
- Schemas CBOR congelados.
profile_hashvector oficial.- Quicknet vectors oficiales.
dk1_vectors canónicos.- fixtures oficiales
.dkcde descifrado/validación. - fixtures oficiales
.dkkde parseo/uso. - mutation tests completos.
- strict tlock tests.
- parser fuzzing.
- revisión criptográfica externa.
76. Política de cambios del borrador v0.8.1
La v0.8.1 congela el diseño normativo del borrador para la fase de implementación e interoperabilidad.
A partir de esta versión, un cambio normativo posterior SHOULD responder a un caso reproducible descubierto mediante al menos una de estas fuentes:
- implementación de referencia;
- schema
datekeys.cddl; - fixture oficial;
- mutation test;
- prueba de interoperabilidad;
- fuzzing;
- segunda implementación independiente;
- revisión criptográfica o técnica externa.
Nuevas ideas, preferencias editoriales o posibilidades futuras que no estén respaldadas por uno de esos casos SHOULD documentarse fuera del núcleo normativo hasta que exista evidencia suficiente para modificarlo.
Esta política no impide correcciones editoriales que no alteren la semántica normativa.
77. Referencias
-
drand Protocol Specification
https://docs.drand.love/docs/specification/ -
drand/tlock
https://github.com/drand/tlock -
age specification — C2SP
https://github.com/C2SP/C2SP/blob/main/age.md -
RFC 8949 — CBOR
-
RFC 2119 / RFC 8174 — normative terminology
78. Principio final
DateKey define cuándo.
DateKeyCap protege qué.
.dkktransporta la capacidad adicional de acceso cuando la política la exige.
DateKeys facilita, registra y acelera; el cliente verifica.