You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DateKeys/spec/DateKeys_Protocol_Specifica...

1876 lines
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 `.dkc` y `.dkk`;
- cifrado y bindings;
- políticas `time_only` y `time_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:
1. **Confidencialidad temporal**
Bajo los supuestos del proveedor, una condición temporal no debe poder satisfacerse antes del momento elegido.
2. **Confidencialidad frente al operador**
DateKeys no debe necesitar conocer el plaintext ni la credencial final de acceso.
3. **Verificabilidad local**
El SDK debe detectar condiciones, perfiles y releases manipulados.
4. **Integridad**
Alteraciones del framing, cabecera, control o payload deben provocar fallo.
5. **Interoperabilidad**
Implementaciones independientes deben producir y consumir objetos compatibles.
6. **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.
7. **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
```text
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:
```text
provider
profile
condition
```
Ejemplo Quicknet:
```json
{
"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:
```text
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:
```text
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
```text
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:
```text
round_time(r) =
genesis_time + (r - 1) * period
```
El SDK MUST elegir la primera ronda cuyo:
```text
round_time >= requested_unlock_at
```
Algoritmo:
```text
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:
```text
genesis_time = 1692803367
period = 3
requested_unlock_at = 2030-01-01T00:00:00Z
```
resultado:
```text
round = 66884212
round_time = 2030-01-01T00:00:00Z
```
La ronda:
```text
66432123
```
corresponde a:
```text
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:
```text
received_round == locally_resolved_round
```
y:
```text
round_time(received_round) >= requested_unlock_at
```
---
## 18. Representación `dk1_`
Por compatibilidad con el prototipo V1 conserva:
```text
dk1_
```
con JSON UTF-8.
Payload canónico:
```json
{"version":1,"network":"datekeys:quicknet:v1","round":66884212}
```
Representación:
```text
dk1_<base64url(JSON_CANONICO)>
```
sin padding.
---
## 19. Canonicalidad `dk1_`
Existe una única cadena válida para una DateKey V1.
El parser MUST:
1. Base64URL-decodificar.
2. Parsear JSON.
3. Validar campos.
4. Reemitir el JSON canónico exactamente.
5. Recrear `dk1_...`.
6. Comparar byte a byte con la entrada.
Si no coincide:
```text
ERR_DATEKEY_NON_CANONICAL
```
---
## 20. Extensiones de archivo
```text
.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:
```text
16 random bytes
```
generados por CSPRNG.
---
## 22. Framing `.dkc` V1
V1 fija el siguiente prelude:
```text
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:
```text
FLAGS == 0
RESERVED == 0
```
---
## 23. PRELUDE
`PRELUDE` son exactamente los primeros 16 bytes:
```text
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:
```text
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:
```text
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:
```text
ERR_POLICY_STRUCTURE_MISMATCH
```
---
## 26. Header binding
`CONTROL_CBOR` MUST contener:
```text
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:
```text
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:
```text
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:
```text
I_PAYLOAD = X25519 identity aleatoria de 32 bytes
R_PAYLOAD = X25519 public recipient correspondiente
```
Entonces:
```text
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:
```text
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:
```text
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:
```text
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:
```text
0 → extension_id
1 → extension_version
2 → data
```
Reglas:
- `extension_id` MUST ser UTF-8 válido;
- `(extension_id, extension_version)` identifica el schema;
- un mismo `extension_id` MUST NOT aparecer simultáneamente en `critical_extensions` y `noncritical_extensions` dentro del mismo objeto;
- salvo que el schema registrado permita multiplicidad, un mismo `extension_id` MUST NOT aparecer más de una vez dentro del mismo objeto, aunque cambie `extension_version`;
- el orden del array NO tiene semántica;
- el encoder canónico MUST ordenar por bytes UTF-8 de `extension_id` y 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:
```text
CONTROL_CBOR
↓
OUTER_TIME_AGE
```
donde:
```text
OUTER_TIME_AGE =
age file
recipient = tlock(DateKey)
plaintext = CONTROL_CBOR
```
`OUTER_TIME_AGE` MUST contener exactamente un stanza, de tipo tlock.
`age` genera:
```text
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:
```text
CONTROL_CBOR
↓
INNER_ACCESS_AGE
↓
OUTER_TIME_AGE
```
Primero:
```text
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:
```text
FK_ACCESS = 16 random bytes
```
Después:
```text
OUTER_TIME_AGE =
age file
recipient = tlock(DateKey)
plaintext = exact bytes of INNER_ACCESS_AGE
```
`age` genera:
```text
FK_TIME = 16 random bytes
```
La política implementa:
```text
TIME AND KEY
```
No dos envolturas paralelas.
---
## 34. `SEALED_CONTROL`
En `.dkc`:
```text
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:
```text
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:
```text
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:
```text
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:
```text
FK_ACCESS
```
Por tanto el `CONTROL_CBOR` y `PAYLOAD_AGE` no se duplican.
---
## 40. Framing `.dkk` V1
```text
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:
```text
FLAGS == 0
RESERVED == 0
```
---
## 41. `.dkk` BODY_CBOR
Schema:
```text
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:
```text
access_type = "x25519"
access_material = 32 raw identity bytes
```
---
## 42. `credential_id`
Debe ser:
```text
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:
```text
0 → capsule_digest
```
donde:
```text
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:
```text
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:
```text
GET /v1/releases/{profile}/{condition}
```
Ejemplo Quicknet:
```text
GET /v1/releases/datekeys:quicknet:v1/66884212
```
No requiere `capsule_id`.
---
## 46. Release Queue
La unidad de trabajo es:
```text
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:
```text
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:
```text
.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:
```text
expected Provider Profile
expected chain hash
expected round
valid BLS signature
```
Un campo remoto:
```text
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:
```text
0 → extension_id
1 → extension_version
2 → data
```
El par:
```text
(extension_id, extension_version)
```
identifica el schema de la extensión.
La posición del array determina si la extensión es:
```text
critical
```
o:
```text
noncritical
```
Reglas:
- una extensión crítica desconocida → MUST reject;
- una extensión no crítica desconocida → MAY ignore;
- un mismo `extension_id` MUST NOT aparecer simultáneamente en `critical_extensions` y `noncritical_extensions` dentro del mismo objeto;
- salvo que un schema registrado permita expresamente multiplicidad, un mismo `extension_id` MUST NOT aparecer más de una vez en el mismo objeto, aunque cambie `extension_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:
```text
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:
```text
{}
[]
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
```go
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`
```text
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:
```text
FK_PAYLOAD
FK_TIME
```
---
## 62. Flujo de cifrado `time_and_key`
```text
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:
```text
FK_PAYLOAD
FK_ACCESS
FK_TIME
```
y MUST ser independientes.
---
## 63. Flujo de descifrado
```text
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:
```text
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:
```text
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:
```text
requested timestamp
expected round
effective timestamp
```
---
## 66. Test vectors `dk1_`
Cada vector:
```text
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 `.dkc` fijos previamente generados;
- `PUBLIC_HEADER` esperado;
- PRELUDE esperado;
- DateKey esperada;
- `header_binding` esperado;
- estructura esperada (`time_only` o `time_and_key`);
- identity `.dkk` cuando corresponda;
- `CONTROL_CBOR` esperado después de abrir;
- `I_PAYLOAD` esperado;
- 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 `.dkk` fijos;
- `credential_id` esperado;
- `capsule_id` esperado;
- `access_type` esperado;
- 32 bytes crudos esperados de X25519 identity;
- `verification_metadata` esperado cuando exista;
- extensiones esperadas;
- resultado esperado al utilizar la identity contra el `INNER_ACCESS_AGE` asociado.
No se exige reproducir los bytes de una `.dkk` partiendo de una generación aleatoria nueva.
---
## 69. Errores normativos
```text
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 `DKC1` y `DKK1`;
- 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
```text
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
1. Schemas CBOR congelados.
2. `profile_hash` vector oficial.
3. Quicknet vectors oficiales.
4. `dk1_` vectors canónicos.
5. fixtures oficiales `.dkc` de descifrado/validación.
6. fixtures oficiales `.dkk` de parseo/uso.
7. mutation tests completos.
8. strict tlock tests.
9. parser fuzzing.
10. 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é.**
> **`.dkk` transporta la capacidad adicional de acceso cuando la política la exige.**
> **DateKeys facilita, registra y acelera; el cliente verifica.**

Powered by TurnKey Linux.