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.
1876 lines
40 KiB
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.**
|