Extension data becomes a non-empty opaque bstr bounded by the frame of its
container; the base protocol never decodes or validates it. Arrays hold at
most 64 extensions, extension_version is bounded to 2^32-1 and the profile's
period and genesis_time to 2^53-1. The multiplicity exception is removed,
ERR_EXTENSION_DATA_INVALID is added, the §57 limits become MUST with an
explicit error mapping, §58 names the protocol's CBOR profile and §72 sets
the registration rules. §76 records the six reproducible cases behind the
change. The implementation follows in the next commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
V1 usa **Deterministic CBOR** conforme a RFC 8949.
V1 usa **Deterministic CBOR** conforme a RFC 8949, con el perfil CBOR del protocolo (§58).
Quicknet se serializa como mapa CBOR con claves enteras:
Quicknet se serializa como mapa CBOR con claves enteras:
@ -269,6 +269,11 @@ Quicknet se serializa como mapa CBOR con claves enteras:
10 → <genesisseedbytes>
10 → <genesisseedbytes>
```
```
Rangos de los enteros del perfil:
- `period` (clave 7) MUST estar entre 1 y 2⁵³ − 1 (9007199254740991);
- `genesis_time` (clave 8) MUST estar entre 0 y 2⁵³ − 1.
El hash:
El hash:
```text
```text
@ -574,6 +579,10 @@ Schema:
6 → noncritical_extensions
6 → noncritical_extensions
```
```
Las claves 5 y 6 son opcionales y se omiten cuando no hay extensiones (§58.1). Cada una, cuando existe, contiene entre 1 y 64 extensiones (§54).
La `data` de las extensiones de `PUBLIC_HEADER` es pública y el protocolo base no la vincula al control hasta que se verifica `header_binding` (§63, paso 15). Esa verificación aporta coherencia interna, no autoría (§36.1, §72).
No se almacena `profile_id` por separado.
No se almacena `profile_id` por separado.
El perfil se obtiene de la DateKey, evitando dos fuentes de verdad.
El perfil se obtiene de la DateKey, evitando dos fuentes de verdad.
@ -760,6 +769,8 @@ Schema base:
5 → noncritical_extensions
5 → noncritical_extensions
```
```
Las claves 4 y 5 son opcionales y se omiten cuando no hay extensiones (§58.1).
No contiene el payload grande.
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.
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.
@ -769,21 +780,25 @@ Una extensión usa conceptualmente:
```text
```text
0 → extension_id
0 → extension_id
1 → extension_version
1 → extension_version
2 → data
2 → data (opcional)
```
```
Reglas:
Reglas:
- `extension_id` MUST ser UTF-8 válido;
- `extension_id` MUST ser UTF-8 válido;
- `extension_version` MUST ser un entero sin signo entre 0 y 2³² − 1;
- `(extension_id, extension_version)` identifica el schema;
- `(extension_id, extension_version)` identifica el schema;
- `data`, cuando existe, MUST ser una cadena de bytes (`bstr`) no vacía, opaca para el protocolo base (§54);
- una extensión sin datos MUST omitir la clave 2;
- cada array de extensiones MUST contener entre 1 y 64 extensiones;
- un mismo `extension_id` MUST NOT aparecer simultáneamente en `critical_extensions` y `noncritical_extensions` dentro del mismo objeto;
- 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`;
- un mismo `extension_id` MUST NOT aparecer más de una vez dentro del mismo objeto, aunque cambie `extension_version`; si un schema necesita varios valores, los lleva dentro de su `data`;
- el orden del array NO tiene semántica;
- 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;
- el encoder canónico MUST ordenar por bytes UTF-8 de `extension_id`;
- una extensión crítica desconocida MUST provocar rechazo;
- una extensión crítica desconocida MUST provocar rechazo;
- una extensión no crítica desconocida MAY ignorarse.
- una extensión no crítica desconocida MAY ignorarse.
El protocolo base no interpreta `data`.
El protocolo base no decodifica ni valida el contenido de `data` (§54).
---
---
@ -1024,6 +1039,8 @@ Schema:
8 → noncritical_extensions
8 → noncritical_extensions
```
```
Las claves 6, 7 y 8 son opcionales y se omiten cuando están ausentes (§43, §58.1). Las claves 7 y 8, cuando existen, contienen entre 1 y 64 extensiones (§54).
Para V1 portable X25519:
Para V1 portable X25519:
```text
```text
@ -1091,6 +1108,8 @@ salvo que una futura especificación DateKeys registre una extensión crítica c
De este modo las implementaciones no inventan campos core incompatibles.
De este modo las implementaciones no inventan campos core incompatibles.
La `data` de esas extensiones viaja en claro junto a la credencial y `header_binding` no la cubre: el protocolo base no la autentica (§72).
---
---
## 45. Release API
## 45. Release API
@ -1244,7 +1263,7 @@ Cada extensión declara:
```text
```text
0 → extension_id
0 → extension_id
1 → extension_version
1 → extension_version
2 → data
2 → data (opcional)
```
```
El par:
El par:
@ -1253,7 +1272,7 @@ El par:
(extension_id, extension_version)
(extension_id, extension_version)
```
```
identifica el schema de la extensión.
identifica el schema de la extensión.`extension_version` es un entero sin signo entre 0 y 2³² − 1 (4294967295).
La posición del array determina si la extensión es:
La posición del array determina si la extensión es:
@ -1269,10 +1288,26 @@ noncritical
Reglas:
Reglas:
- una extensión crítica desconocida → MUST reject;
- una extensión crítica desconocida → MUST reject (`ERR_EXTENSION_CRITICAL_UNKNOWN`);
- una extensión no crítica desconocida → MAY ignore;
- 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;
- 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`.
- un mismo `extension_id` MUST NOT aparecer más de una vez en el mismo objeto, aunque cambie `extension_version`; la multiplicidad, si un schema la necesita, va dentro de su `data`;
- cada array de extensiones contiene entre 1 y 64 extensiones: un encoder MUST NOT producir más de 64 y un decoder MUST rechazar un array de más de 64.
`data` (clave 2):
- MUST ser una cadena de bytes CBOR (`bstr`, tipo mayor 2) de al menos 1 byte;
- una extensión sin datos MUST omitir la clave 2; `h''` (`0x40`) es inválido;
- cualquier otro tipo CBOR en la clave 2 es inválido;
- su longitud está acotada por la trama del objeto que la contiene (§57);
- su contenido es opaco: el protocolo base nunca lo decodifica ni lo valida, y la validez base de `PUBLIC_HEADER`, `CONTROL_CBOR` y `.dkk` nunca depende de él.
Una `data` vacía o de tipo distinto de `bstr` y un array de más de 64 extensiones son violaciones del CDDL: MUST rechazarse con `ERR_NON_CANONICAL_CBOR` (§57).
Solo una implementación que conoce `(extension_id, extension_version)` interpreta su `data`, conforme a su schema registrado (§72):
- una extensión crítica conocida cuya `data` no cumple su schema registrado → MUST reject (`ERR_EXTENSION_DATA_INVALID`);
- una extensión no crítica conocida cuya `data` no cumple su schema registrado no invalida el objeto: la implementación MUST tratarla como inutilizable, sin usar su `data`, y MUST notificarlo al llamador.
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.
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.
@ -1312,7 +1347,7 @@ No debe presentar plaintext parcial como válido si falla cualquier autenticaci
## 57. Límites del parser
## 57. Límites del parser
V1 recomienda:
Encoders y decoders MUST aplicar estos límites de trama:
```text
```text
PUBLIC_HEADER <= 1 MiB
PUBLIC_HEADER <= 1 MiB
@ -1320,20 +1355,54 @@ SEALED_CONTROL <= 64 MiB
DKK BODY <= 16 MiB
DKK BODY <= 16 MiB
```
```
- un encoder MUST NOT producir un objeto que los supere;
- un decoder MUST rechazar un objeto que los supere;
- cada trama acota todo lo que contiene, incluida la `data` de las extensiones (§54); `CONTROL_CBOR` queda acotado por `SEALED_CONTROL`.
`PAYLOAD_AGE` se procesa en streaming.
`PAYLOAD_AGE` se procesa en streaming.
Los tamaños se validan antes de reservar memoria.
Los tamaños se validan antes de reservar memoria.
Correspondencia de errores:
- una longitud de trama (`PUBLIC_HEADER_LEN`, `SEALED_CONTROL_LEN`, `BODY_LEN`) fuera de estos límites, o un objeto que supera el límite de su trama → `ERR_INTEGRITY`;
- cualquier violación de una regla normativa de `datekeys.cddl`, incluidas las restricciones de tamaño y de rango y el máximo de 64 extensiones por array → `ERR_NON_CANONICAL_CBOR`, salvo los casos con código propio: versión de schema (clave 1) no soportada → `ERR_UNSUPPORTED_VERSION` (§70); `compact_datekey` → `ERR_DATEKEY_INVALID` o `ERR_DATEKEY_NON_CANONICAL` (§19); `access_type` no soportado o `access_material` de longitud incorrecta → `ERR_ACCESS_INVALID`; en el Provider Profile, un `profile_id`, `provider`, `network` o `scheme` que no cumple su regla, o una clave pública (clave 6) fuera de la suya → `ERR_UNKNOWN_PROFILE` (§13);
- las reglas que `datekeys.cddl` marca como límites de la implementación de referencia no son normativas mientras §74 deje abiertos los límites definitivos de campos; quien las aplica usa esta misma correspondencia.
---
---
## 58. Canonical CBOR
## 58. Canonical CBOR
Todas las estructuras CBOR del protocolo MUST:
Todas las estructuras CBOR del protocolo MUST:
- usar Deterministic CBOR;
- usar Deterministic CBOR con el perfil CBOR del protocolo definido en esta sección;
- rechazar siempre codificaciones no canónicas;
- rechazar siempre codificaciones no canónicas;
- usar enteros de clave según los schemas normativos.
- usar enteros de clave según los schemas normativos.
- tipos mayores 0 (entero sin signo), 2 (cadena de bytes), 3 (cadena de texto), 4 (array) y 5 (mapa);
- claves de mapa que son enteros sin signo;
- longitudes definidas;
- enteros y longitudes en su forma más corta;
- claves de mapa en orden ascendente estricto, sin duplicados;
- mapas cerrados: una clave no prevista por el schema MUST rechazarse;
- cadenas de texto en UTF-8 válido.
Con claves enteras sin signo en su forma más corta, el orden por bytes de RFC 8949 §4.2.1 coincide con el orden numérico.
Se rechazan expresamente, con `ERR_NON_CANONICAL_CBOR`:
- enteros negativos (tipo mayor 1);
- tags (tipo mayor 6);
- flotantes y valores simples, incluidos `false`, `true`, `null` y `undefined` (tipo mayor 7);
- longitudes indefinidas;
- claves de mapa que no son enteros sin signo.
Los schemas del protocolo acotan todo entero sin signo a 2⁵³ − 1 (9007199254740991) como máximo (§11, §54), de modo que cada uno se representa exactamente como double IEEE 754.
En la `data` de una extensión, el perfil se aplica a la cabecera de la cadena de bytes (tipo mayor 2 y longitud en su forma más corta), nunca a su contenido (§54). Las extensiones registradas cuya `data` sea CBOR siguen además §72.
---
---
## 58.1 Regla global para campos opcionales
## 58.1 Regla global para campos opcionales
@ -1349,16 +1418,18 @@ No se debe representar ausencia mediante:
[]
[]
null
null
""
""
h''
```
```
salvo que el schema de ese campo defina expresamente uno de esos valores como dato real.
salvo que el schema de ese campo defina expresamente uno de esos valores como dato real.`null` no puede serlo nunca: el perfil de §58 excluye los valores simples.
Por tanto:
Por tanto:
- metadata opcional ausente → clave omitida;
- metadata opcional ausente → clave omitida;
- arrays opcionales sin elementos → clave omitida;
- arrays opcionales sin elementos → clave omitida;
- mapas opcionales sin entradas → clave omitida;
- mapas opcionales sin entradas → clave omitida;
- listas de extensiones vacías → clave omitida.
- listas de extensiones vacías → clave omitida;
- extensión sin `data` → clave 2 omitida; `h''` es inválido (§54).
Esto reduce representaciones equivalentes y simplifica vectores canónicos.
Esto reduce representaciones equivalentes y simplifica vectores canónicos.
@ -1491,6 +1562,9 @@ y MUST ser independientes.
3. Leer PUBLIC_HEADER exacto.
3. Leer PUBLIC_HEADER exacto.
4. Validar CBOR canónico, DateKey canónica y Provider Profile pinneado.
4. Validar CBOR canónico, DateKey canónica y Provider Profile pinneado.
Validar las extensiones críticas de PUBLIC_HEADER (§54):
desconocida → ERR_EXTENSION_CRITICAL_UNKNOWN;
conocida con data inválida → ERR_EXTENSION_DATA_INVALID.
5. SHOULD: inspeccionar OUTER_TIME_AGE antes de usar red o secretos:
5. SHOULD: inspeccionar OUTER_TIME_AGE antes de usar red o secretos:
exactamente un stanza;
exactamente un stanza;
@ -1532,8 +1606,12 @@ y MUST ser independientes.
después, abrir INNER_ACCESS_AGE con la identity adecuada.
después, abrir INNER_ACCESS_AGE con la identity adecuada.
14. Parsear CONTROL_CBOR canónico.
14. Parsear CONTROL_CBOR canónico.
Validar sus extensiones críticas como en el paso 4.
15. Verificar header_binding.
15. Verificar header_binding.
Desde aquí la data de las extensiones de PUBLIC_HEADER queda
vinculada al control abierto: coherencia interna (§36.1), no
autoría, salvo que la cubra una extensión de firma (§72).
16. Recuperar I_PAYLOAD.
16. Recuperar I_PAYLOAD.
@ -1584,6 +1662,9 @@ stanza adicional en PAYLOAD_AGE
stanza de tipo distinto de X25519 en INNER_ACCESS_AGE
stanza de tipo distinto de X25519 en INNER_ACCESS_AGE
ronda del stanza tlock distinta de DateKey.round
ronda del stanza tlock distinta de DateKey.round
chain hash del stanza tlock distinto del Provider Profile pinneado
chain hash del stanza tlock distinto del Provider Profile pinneado
data de extensión de tipo distinto de bstr
data de extensión vacía (h'')
65 extensiones en un mismo array
```
```
---
---
@ -1664,9 +1745,11 @@ Cada vector incluirá:
- `access_type` esperado;
- `access_type` esperado;
- 32 bytes crudos esperados de X25519 identity;
- 32 bytes crudos esperados de X25519 identity;
- `verification_metadata` esperado cuando exista;
- `verification_metadata` esperado cuando exista;
- extensiones esperadas;
- extensiones esperadas, con los bytes exactos de su `data`;
- resultado esperado al utilizar la identity contra el `INNER_ACCESS_AGE` asociado.
- resultado esperado al utilizar la identity contra el `INNER_ACCESS_AGE` asociado.
Al menos un vector oficial `.dkk` MUST incluir una extensión con `data`.
No se exige reproducir los bytes de una `.dkk` partiendo de una generación aleatoria nueva.
No se exige reproducir los bytes de una `.dkk` partiendo de una generación aleatoria nueva.
@ -1727,9 +1811,26 @@ DateKeys MAY publicar un registro de `extension_id`.
Registrar una extensión no cambia el núcleo del protocolo.
Registrar una extensión no cambia el núcleo del protocolo.
Cada extensión registrada, identificada por `(extension_id, extension_version)`, MUST declarar:
- la codificación de su `data`, o que no lleva `data`;
- su forma canónica;
- su longitud máxima, que no puede superar la trama del objeto que la contiene (§57);
- sus vectores de prueba.
Si la codificación de `data` es CBOR:
- MUST usar el perfil de §58 de forma recursiva, con enteros sin signo de como mucho 2⁵³ − 1, arrays y mapas no vacíos y sin `null`;
- los lectores que implementan la extensión MUST rechazar una codificación interna no canónica o fuera de ese perfil como `data` inválida (§54);
- su encoder MUST decodificar su propia salida, con las reglas de esos lectores, antes de sellar o escribir el objeto.
Una extensión de firma que cubra la `data` de otras extensiones MUST firmar los bytes exactos del contenido de esos `bstr`, nunca una reserialización de su contenido decodificado.
Nota: la `data` de `PUBLIC_HEADER` es pública. El protocolo base no la vincula al control hasta que se verifica `header_binding` (§63, paso 15), y esa verificación aporta coherencia interna, no autoría (§36.1): solo una extensión de firma puede aportar autenticidad del creador (§27). La de `.dkk` viaja en claro y `header_binding` no la cubre, así que el protocolo base no la autentica en ningún paso.
---
---
## 73. Decisiones canónicas v0.8.1
## 73. Decisiones canónicas v0.8.2
```text
```text
DateKeys
DateKeys
@ -1786,6 +1887,18 @@ portable .dkk
extensions
extensions
= único mecanismo genérico de extensibilidad en PUBLIC_HEADER, CONTROL_CBOR y .dkk
= único mecanismo genérico de extensibilidad en PUBLIC_HEADER, CONTROL_CBOR y .dkk
extension arrays
= 1 a 64 extensiones; extension_id único en cada objeto
extension data
= bstr no vacío y opaco; el protocolo base nunca lo decodifica
CBOR profile
= RFC 8949 §4.2.1 con tipos mayores 0, 2, 3, 4 y 5 y claves enteras sin signo
parser limits
= MUST para encoders y decoders
recovery
recovery
= puede obtener release directamente del provider
= puede obtener release directamente del provider
@ -1802,12 +1915,11 @@ Antes de v1.0 quedan por cerrar:
- schema CBOR final byte-a-byte de `PUBLIC_HEADER`;
- 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 `CONTROL_CBOR`;
- schema CBOR final byte-a-byte de `.dkk`;
- schema CBOR final byte-a-byte de `.dkk`;
- formato exacto de extensiones;
- límites definitivos de campos;
- límites definitivos de campos;
- vectores definitivos de Provider Profile;
- vectores definitivos de Provider Profile;
- suite exacta de tests interoperables.
- 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.
El framing base, la ausencia de `PAYLOAD_LEN`, el uso de age files estándar, la identity X25519 cruda de `.dkk` y el formato de extensiones (§54) dejan de considerarse provisionales en este borrador.
---
---
@ -1845,6 +1957,31 @@ Nuevas ideas, preferencias editoriales o posibilidades futuras que no estén res
Esta política no impide correcciones editoriales que no alteren la semántica normativa.
Esta política no impide correcciones editoriales que no alteren la semántica normativa.
### Cambio normativo v0.8.2: extensiones
La v0.8.2 cierra el formato de las extensiones (§74) en un único cambio normativo:
- `data` (clave 2) pasa de `any` a `bstr` no vacío y opaco, sin más límite de longitud que la trama de su contenedor; el protocolo base nunca decodifica ni valida su contenido (§31, §54, §57, §58.1);
- cada array admite como máximo 64 extensiones (§31, §54);
- `extension_version` queda acotado a 2³² − 1, y `period` y `genesis_time` a 2⁵³ − 1 (§11, §54, §58);
- se elimina la excepción de multiplicidad: un `extension_id` aparece una sola vez por objeto (§31, §54);
- nuevo error `ERR_EXTENSION_DATA_INVALID` para una extensión crítica conocida con `data` inválida (§54, §63, §69);
- los límites de §57 pasan de recomendación a MUST para encoders y decoders, con su correspondencia de errores;
- §58 nombra el perfil CBOR del protocolo;
- §72 fija las reglas de registro de extensiones;
- nuevas mutaciones (§64) y un vector `.dkk` con extensión (§68).
Casos reproducibles que lo justifican, obtenidos con la implementación de referencia, sus fixtures y `datekeys.cddl` de la v0.8.1:
1. El CDDL se contradecía: sus líneas 12 a 14 establecen que `null` y los valores vacíos nunca representan ausencia, y su línea 82 declaraba `? 2 => any`.
2. `extension.New(id, v, nil)` emitía `f6` (`null`) como `data`.
3. La misma cápsula de 810 bytes, con `data` de cabecera `{NaN:0, NaN:1}` (`a2f97e0000f97e0001`), se aceptaba de forma no determinista: `Inspect` la aceptó 437 veces de 500 y `Open` 82 de 100.
4. El fixture oficial `time_only_extensions` usaba claves de texto y `true` en `data`.
5. `Encrypt` sellaba una `data` de `CONTROL_CBOR` con 14 arrays anidados que `Open` rechazaba en el paso 14, ya desbloqueada (`exceeded max nested level 16`): la cápsula quedaba irrecuperable.
6. Sin máximo de extensiones, una `PUBLIC_HEADER` de 880 KB con 40 000 + 40 000 extensiones hacía que `DecodeHeader` tardara 8,3 s, por una comprobación de disjunción cuadrática.
Las versiones de framing y de schema (clave 1) no cambian. Un objeto v0.8.1 deja de ser válido si la `data` de una extensión no es un `bstr` no vacío, si un array supera 64 extensiones, si repite un `extension_id` al amparo de la antigua excepción de multiplicidad o si un `extension_version` supera 2³² − 1; también un Provider Profile con `period` o `genesis_time` por encima de 2⁵³ − 1. El fixture `time_only_extensions` se regenera.