The SHOULDs of the official SDK that the CLI did not follow yet. encrypt
writes next to the .dkc the recovery annex, FILE.dkc.recuperacion.txt
(spec §62.1, rule 27): datekeys.RecoveryAnnex, annex/recovery.md, which is
§79 of the specification under a title with its version and SHA-256, the
same for every capsule; TestRecoveryAnnex checks it against the text of
SpecVersion. -no-recovery leaves it out. encrypt also says what opening the
capsule years later will take (rule 26): the .dkc, a credential of a
time_and_key capsule, and the release of its round, which an archive of
releases or a cache service must keep if drand no longer serves it; and
beyond one year it recommends time_and_key to a time_only capsule (§7.6).
profile.Status and StatusOf give the state of a pinned profile in the
registry of §71, which DateKeys does not publish yet: Quicknet is active.
encrypt writes no capsule with a profile that is not active, and decrypt
and inspect warn when the profile of a capsule is compromised.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`encrypt` escribe junto a la cápsula `FICHERO.dkc.recuperacion.txt`, el anexo de la especificación sobre cómo abrir una cápsula sin software de DateKeys (spec §79), salvo con `-no-recovery`; y dice qué hará falta para abrirla años después: el `.dkc`, una credencial si es `time_and_key` y el release de su ronda, que tendrá que conservar un archivo de releases o un servicio de caché si drand ya no lo sirve (spec §50). A más de un año, recomienda `time_and_key` a una cápsula `time_only` (spec §7.6). Con un perfil que no esté activo en el registro de §71 no escribe ninguna cápsula, y `decrypt` e `inspect` avisan si el perfil de una cápsula está comprometido.
`encrypt` nunca usa la red. Cada `-in` es un fichero o una carpeta; una
carpeta da su nombre como primer segmento de sus rutas, como hace un
navegador, y se recorre sin seguir enlaces, tomando solo ficheros regulares y
`encrypt` writes next to the capsule `FILE.dkc.recuperacion.txt`, the annex of the specification, in Spanish, on how to open a capsule without DateKeys software (spec §79), unless `-no-recovery`; and says what opening the capsule years later will take: the `.dkc`, a credential of a `time_and_key` capsule and the release of its round, which an archive of releases or a cache service must keep if drand no longer serves it (spec §50). Beyond one year, it recommends `time_and_key` to a `time_only` capsule (spec §7.6). A profile that is not active in the registry of §71 writes no capsule, and `decrypt` and `inspect` warn when the profile of a capsule is compromised.
`encrypt` never touches the network. Each `-in` is a file or a folder; a
folder gives its name as the first segment of its paths, as a browser does,
and is walked without following links, taking regular files only and leaving
# Cómo abrir una cápsula DateKeys sin software de DateKeys
Este texto acompaña a una cápsula del tiempo de DateKeys, un fichero `.dkc`: dice cómo abrirla, llegada su fecha, sin ningún software de DateKeys, por si ya no existe. Es el anexo informativo §79 de la especificación del protocolo DateKeys v0.15, cuyo texto tiene el SHA-256 45105e693be4187af4dd30f4d254402612587b6427c746f5d29f07a541c1e3f3. Es el mismo para toda cápsula: no lleva ningún dato de esta.
## 79. Anexo informativo: recuperación sin software DateKeys
Este anexo no es normativo. Dice cómo abrir una cápsula de Quicknet sin ningún software de DateKeys, por si dentro de décadas no existe. Repite lo que fijan las secciones que cita, que deciden en caso de duda.
La regla 27 de §62.1 recomienda al SDK oficial guardar este anexo junto al `.dkc`. No contiene ningún dato de una cápsula.
Hace falta:
- el `.dkc`;
- el release de su ronda, de cualquier fuente: un relay de drand, un archivo de releases, un servicio de caché (§50) o cualquier copia. No hace falta confiar en quien lo da: se verifica con la clave pública de 79.1 (79.3);
- en `time_and_key`, una credencial: la `.dkk`, la identity `age` de un recipient o las palabras de una llave de palabras (§38.1);
- una librería de BLS12-381 con pairing y con el hash a G1 de RFC 9380, SHA-256, HMAC-SHA256, HKDF-SHA256 (RFC 5869), ChaCha20-Poly1305 (RFC 8439), un decodificador de CBOR y la herramienta `age` (§77) o una librería compatible.
No sirven las herramientas de drand: `tle` pide el release a la red y no acepta uno dado, y `age` no acepta una file key, que es lo que da el stanza tlock (79.4). Por eso este anexo describe esos dos pasos enteros (79.4 y 79.5).
La implementación de referencia lo sigue en `scripts/recovery`, un programa que no importa ningún paquete de DateKeys, tlock ni drand: solo la librería estándar de Go, `golang.org/x/crypto`, `filippo.io/age` y la librería BLS12-381 `drand/kyber-bls12381`. `scripts/recovery_check.sh` abre con él una cápsula `time_only` y otra `time_and_key` de los fixtures oficiales.
### 79.1 Parámetros de Quicknet
Son los de §12, y pueden no estar ya en ningún otro sitio:
Los puntos se codifican comprimidos, en el formato de ZCash (§12.2): 48 bytes en G1, la firma, y 96 en G2, la clave pública y U, con la coordenada c1 antes que c0.
### 79.2 La cápsula
El `.dkc` empieza por 16 bytes (§22):
```text
0 4 "DKC1"
4 1 VERSION: el formato, 1, 2 o 3
5 1 0
6 2 0
8 4 PUBLIC_HEADER_LEN, entero big-endian
12 4 SEALED_CONTROL_LEN, entero big-endian
```
Le siguen `PUBLIC_HEADER`, de `PUBLIC_HEADER_LEN` bytes; `SEALED_CONTROL`, de `SEALED_CONTROL_LEN` bytes; y `PAYLOAD_AGE`, desde el byte 16 + `PUBLIC_HEADER_LEN` + `SEALED_CONTROL_LEN` hasta el final del fichero.
`PUBLIC_HEADER` es un mapa CBOR (§24). Su clave 2 es `capsule_id`, 16 bytes; su clave 4, la política, 0 para `time_only` y 1 para `time_and_key`; y su clave 3, la DateKey, un texto `dk1_` seguido del Base64URL sin relleno de un JSON (§18):
Un objeto release es un mapa CBOR (§47.1): la clave 0 es `"datekeys-release"`; la 2, el `chain_hash`, que ha de ser el de 79.1; la 3, la ronda, que ha de ser la de la DateKey; y la 4, la firma, de 48 bytes. Así lo sirven la Release API y un servicio de caché. En un archivo de releases (§50), la firma de la ronda r son los 48 bytes que empiezan en |cabecera| + (r − primera ronda)·48, y 48 ceros si el archivo no la tiene. Un relay de drand la entrega como JSON, `{"round": …, "signature": "…"}`, con la firma en hexadecimal. Hoy se pide así, aunque las direcciones pueden cambiar:
```text
GET https://api.drand.sh/v2/chains/<chain_hash>/rounds/<ronda>
```
La firma no necesita confianza: se verifica (§63, paso 10). Con M el SHA-256 de la ronda en 8 bytes big-endian, y H el hash a G1 de RFC 9380 con la suite `BLS12381G1_XMD:SHA-256_SSWU_RO_` y el DST de 79.1:
```text
e(H(M), clave_pública) == e(firma, G2)
```
con e el pairing de G1 × G2, el primer argumento en G1 y el segundo en G2, y G2 el generador de G2. La firma y la clave pública se decodifican como puntos comprimidos válidos del subgrupo, distintos del punto en el infinito. Para una ronda solo hay una firma válida: cualquier copia que verifique es el release.
### 79.4 El stanza tlock y FK_TIME
`SEALED_CONTROL` es un fichero `age` (79.5) cuya cabecera tiene un solo stanza:
El cuerpo mide 128 bytes, U ‖ V ‖ W: U, de 96 bytes, un punto de G2, y V y W, de 16 bytes. Con la firma del release (§63, paso 11):
```text
sigma = V XOR H2(e(firma, U))
FK_TIME = W XOR H4(sigma)
r = H3(sigma, FK_TIME)
comprobar r·G2 == U
```
- H2(x) son los 16 primeros bytes de SHA-256(`IBE-H2` ‖ x), con x los 576 bytes del elemento de GT en el orden de §63, «Serialización de GT en H2»: c1 antes que c0 en cada nivel de la torre, y c2, c1, c0 en Fp6, cada elemento de Fp en 48 bytes big-endian. Una librería que serializa con c0 primero da otro H2. El vector de `testdata/vectors/tlock_ibe.json` lo comprueba: H2(e(G1, G2)) = `cb87319f24560b5231579a09ad79f12e`, con G1 y G2 los generadores.
- H4(sigma) son los 16 primeros bytes de SHA-256(`IBE-H4` ‖ sigma).
- H3(sigma, FK_TIME): base = SHA-256(`IBE-H3` ‖ sigma ‖ FK_TIME); para i = 1, 2, … hasta 65534, d = SHA-256(uint16_le(i) ‖ base), con el contador en 2 bytes little-endian delante de base; se desplaza un bit a la derecha el primer byte de d, solo ese byte; y si d, como entero big-endian de 32 bytes, es menor que q, r = d.
Las etiquetas son los bytes ASCII, sin longitud ni terminador. FK_TIME, de 16 bytes, es la file key del fichero `age` de `SEALED_CONTROL`. `testdata/vectors/tlock_steps.json` da cada valor intermedio de cinco stanzas.
### 79.5 Abrir un fichero `age` con su file key
Es la especificación `age` v1 de C2SP (§77), resumida. Un fichero `age` es una cabecera de texto y un payload binario:
1. La cabecera: clave_mac = HKDF-SHA256(ikm = FK, salt = vacío, info = `header`), 32 bytes. El MAC es HMAC-SHA256(clave_mac, la cabecera desde `age-encryption.org/v1` hasta `---` inclusive, sin el espacio que lo sigue). Si no coincide con el de la línea `---`, la file key o la cabecera son otras.
2. El payload empieza tras el salto de línea del MAC por un nonce de 16 bytes. clave = HKDF-SHA256(ikm = FK, salt = nonce, info = `payload`), 32 bytes.
3. Lo demás son bloques de ChaCha20-Poly1305 de 65 536 bytes de texto, 65 552 cifrados, el último más corto. El nonce de 12 bytes del bloque n, desde 0, es n en 11 bytes big-endian seguido de 0x01 en el último bloque y de 0x00 en los demás. No hay datos asociados. El último bloque solo puede estar vacío si es el único, y nada sigue al último bloque.
### 79.6 Las capas siguientes
El plaintext de `SEALED_CONTROL` es:
- en `time_only`, `CONTROL_CBOR`;
- en `time_and_key`, otro fichero `age`, `INNER_ACCESS_AGE`, con stanzas X25519, uno por credencial y señuelos hasta 16 en los formatos 2 y 3. Se abre con `age -d -i clave.txt`, con la identity de la credencial en `clave.txt`. La de una `.dkk` es su `access_material`. La `.dkk` empieza por 12 bytes, `DKK1`, `01`, `00`, `00 00` y `BODY_LEN` en 4 bytes big-endian (§40), y le sigue un mapa CBOR cuya clave 5 es ese `access_material`, 32 bytes (§41), y cuya clave 3 es el `capsule_id` de su cápsula. Una llave de palabras da la identity con §38.1.
`CONTROL_CBOR` es un mapa CBOR (§31). Su clave 3 es `I_PAYLOAD`, otra identity X25519 de 32 bytes, y en los formatos 2 y 3 su clave 6 es L, una cadena de 8 bytes con un entero big-endian, no un entero CBOR. Con `I_PAYLOAD`, `age -d -i payload.txt` abre `PAYLOAD_AGE`.
Una identity X25519 de 32 bytes se escribe para `age` en Bech32 (BIP 173, §77), no Bech32m: el prefijo `age-secret-key-`, los 32 bytes reagrupados de 8 en 5 bits con ceros al final, que dan 52 caracteres, y la suma de comprobación de BIP 173, calculada con el prefijo en minúsculas. Después, todo en mayúsculas: `AGE-SECRET-KEY-1…`.
### 79.7 El contenido
El plaintext de `PAYLOAD_AGE` es:
- en formato 1, el contenido entero;
- en formato 2, el contenido en sus L primeros bytes, seguido de ceros;
- en formato 3, `BODY` en sus L primeros bytes, seguido de ceros (§29.2).
`BODY` empieza por tres enteros de 4 bytes big-endian: `AREA_LEN`, `SECURITY_LEN` y `HEAD_LEN`. Siguen el área de `security`, de `AREA_LEN` bytes, que se puede saltar: solo da los veredictos de la firma y del sello (§29.7); el head, un mapa CBOR de `HEAD_LEN` bytes que empieza en 12 + `AREA_LEN`; y los ficheros, desde 12 + `AREA_LEN` + `HEAD_LEN`, el origen de sus desplazamientos.
La clave 5 del head es la lista de ficheros (§29.4). Cada uno es un mapa:
```text
0 → ruta, con "/" entre carpetas
1 → tamaño
2 → start
3 → end
4 → SHA-256 de sus bytes
5 → mtime, en segundos Unix, opcional
```
Sus bytes van de start a end, sin incluir end, contados desde el origen. Las claves 3 y 4 del head son el comentario y el autor declarado: textos del creador que no prueban nada (§29.7). Una ruta que saldría de la carpeta de destino no se escribe.
### 79.8 Lo que el anexo no comprueba
Este anexo comprueba lo que decide que el resultado es el correcto: la firma del release, r·G2 == U, los MAC de cada fichero `age` y el SHA-256 de cada fichero. No comprueba, entre otras cosas, la codificación canónica de cada objeto, `header_binding` (§26), los 16 stanzas de `INNER_ACCESS_AGE` (§39), los ceros del relleno (§29.1) ni las reglas de las rutas (§29.5). Una cápsula que el lector de §63 rechazaría puede abrirse siguiendo este anexo; su contenido es el que sellaron las MAC de `age`, pero no tiene la garantía de un lector conforme.
t.Skip("wrote annex/recovery.md; build the package again to embed it")
}
ifRecoveryAnnex!=want{
t.Fatalf("annex/recovery.md is not §79 of the specification %s: write it again with DATEKEYS_WRITE_ANNEX=1 go test -run TestRecoveryAnnex .",SpecVersion)
}
for_,s:=range[]string{"## 79. Anexo informativo: recuperación sin software DateKeys","### 79.8 Lo que el anexo no comprueba","DateKeys v"+SpecVersion+","}{
note:=fs.String("note","","public note of the capsule: one line that anyone with the .dkc reads before the date, and that can identify someone with it")
sign:=fs.String("sign","","file with the author key that signs the capsule")
signPass:=fs.String("sign-pass-file","","file with the passphrase of the author key, or - for the standard input")
noRecovery:=fs.Bool("no-recovery",false,"do not write the recovery annex of the specification next to the .dkc (spec §79)")
largeArea:=fs.Bool("large-area",false,"let the security area grow to 64 KiB if a signature does not fit in 32 KiB (an author key always fits)")
returnfmt.Errorf("the capsule was written to %s but its recovery annex could not be: %w",*out,err)
}
}
fmt.Fprintf(stderr,"Encrypted locally for %s (round %d)\n datekey %s\n capsule_id %x\n format %d: %d files, a payload of %d bytes, padded to %d (%s)\n",
fmt.Fprintf(stderr," words %s: the %d words of the dice in the list %s, %d bits; keep these words, which open the capsule: the numbers give them only with this list\n",
iferr!=nil||!strings.Contains(stderr,"(spec §53)")||!strings.Contains(stderr,"note: for a horizon this long, or a valuable content, -policy time_and_key adds a credential"){
t.Errorf("a long horizon, time_only: %v\n%s",err,stderr)
| 4 | Security goals, among them goal 8, the privacy of metadata in format 2 | whole module | whole suite; goal 8: the tests of rows 29.1, 39 and 55.2 |
| 7 | Threat model | creator model in `internal/testkit.Build`, `RewriteAge`; third-party edits in the mutation corpus | `agewrap.TestTimeIdentityStrictness`, `TestPayloadIdentityStrictness`, `TestAccessIdentityStrictness`, `capsule.TestMutationCorpus` |
| 7 | Threat model; §7.6, SHOULD: recommend `time_and_key` for long horizons or valuable content | creator model in `internal/testkit.Build`, `RewriteAge`; third-party edits in the mutation corpus; `cmd/datekeys` recommends `-policy time_and_key` beyond one year with `time_only` (`TestRecoveryAndNotices`) | `agewrap.TestTimeIdentityStrictness`, `TestPayloadIdentityStrictness`, `TestAccessIdentityStrictness`, `capsule.TestMutationCorpus` |
| 11 | Canonical profile encoding, `profile_hash`; `period` in 1..2^53−1, `genesis_time` in 0..2^53−1 | `Profile.CanonicalCBOR`, `Profile.Hash`, `profile.Decode` (hand-written `wire` encode and decode: keys 0 to 10, all required, in order; unsigned `genesis_time`, `codec.MaxSafeUint`; `period` limited to 1..86400 s, an implementation limit marked in `spec/datekeys.cddl`, `ERR_NON_CANONICAL_CBOR`) | `profile.TestQuicknetMatchesGoldenVector`, `TestQuicknetCBORLayout`, `TestDecodeRoundTrip`, `TestDecodeStructure`, `TestIntegerRanges`, `FuzzDecode`; `testdata/vectors/profile_quicknet.json`; the `provider_profile` block of `testdata/vectors/cbor.json` (*period of one day, the implementation limit*, *… above the implementation limit*) |
@ -85,7 +85,7 @@ v0.14, and §47.1 and §79, new in v0.15. A case of §64 that is not in the repo
| 50 | Historical release dependency; v0.15: long-term recovery on archives of all rounds and on cache services that serve them, with no hosting promise, the release archive as an informative format | documented in `README.md`; `provider.Archive`, `provider.EncodeArchiveHeader`; `datekeys decrypt -release` with a local archive | `provider.TestArchive`; `cmd/datekeys.TestDecryptWithReleaseInHand` (*a local archive*); the `archive` block of `testdata/vectors/release.json`, `testdata/releases/archive_1000_1004.bin` |
| 51 | Quicknet release verification; order and codes of §63 step 10: the round (`ERR_ROUND_MISMATCH`), then the signature, the canonical encoding of a point of G1 other than the identity (§12.2) that verifies as the BLS signature of the round (`ERR_RELEASE_INVALID`); those codes for a release supplied directly, a network source discarding an invalid one at step 9 (`ERR_RELEASE_UNAVAILABLE`) | `provider.Verify`; v0.15: first the chain hash a release object names, `ERR_PROFILE_MISMATCH` (`provider.Verify`) | `provider.TestVerifyPublishedReleases`, `TestVerifyRejects` (x + p, the point at infinity alone, with a payload or with the sort flag, no compression flag, the negated signature), `TestVerifyUsesThePinnedKeyOnly`; `capsule.TestReleaseFromANetworkSource`; mutations *DateKey A + release of round B*, *release of another round*, *release signature …*; the `objects` of `testdata/vectors/release.json`; mutations *release object of another chain* |
| 52 | DNS / MITM | `provider/drand` (no redirects, bounded responses, BLS) | `drand.TestRedirectsAreNotFollowed`, `TestRejectMalformedRelayResponses`, `TestRandomnessMustMatchWhenPresent` |
| 53 | Harvest now, decrypt later | `cmd/datekeys` warning beyond one year; v0.15: the release among what a long horizon needs (text) | `cmd/datekeys.TestLongHorizonWarning` |
| 53 | Harvest now, decrypt later | `cmd/datekeys` warning beyond one year; v0.15: the release among what a long horizon needs (text) | `cmd/datekeys.TestLongHorizonWarning`, `TestRecoveryAndNotices` |
| 54 | Extensions: data absent or a non-empty opaque byte string, never decoded; 1 to 64 per array; `extension_id` of at least 1 byte; `extension_version` ≤ 2^32−1; elements in strictly ascending unsigned bytewise order of the UTF-8 bytes of `extension_id` (a proper prefix first, never UTF-16 code units or a collation), so one `extension_id` per array, and none in both arrays; a known extension in an object or array it is not registered for is unknown there, its data never interpreted | `extension.New`, `Canonical`, `EncodeArray` (refuses, through `codec.Encoder.Fail`, an array that `DecodeArray` rejects), `DecodeArray` (64 entries checked on the array head, explicit key 2 check), `CheckDisjoint` (linear merge), `CheckCritical`, `CheckNoncritical`, `Unusable`; `Object`, `Array`, the optional `Placement` of a `Registry`, `KnownIn`, and `CheckCriticalIn` and `CheckNoncriticalIn`, which `capsule` runs at steps 4, 9.a and 14 | `extension.TestNew`, `TestData`, `TestCanonicalSorts`, `TestOrderIsUnsignedBytewise`, `TestCanonicalRejects`, `TestEncodeArrayRejects`, `TestDecodeArrayRejects`, `TestCheckDisjoint`, `TestCheckDisjointIsLinear`, `TestCheckCritical`, `TestCheckNoncritical`, `TestPlacement`, `FuzzDecodeArray`; `capsule.TestKnownCriticalExtensions`, `TestUnusableNoncriticalExtensions`, `TestExtensionPlacement`; mutations *unknown critical … extension*, *known critical … extension with invalid data*, *extension_version above 2^32-1*, *null extension data* |
| 55 | Auxiliary integrity | `capsule_digest` treated as UX only | — |
| 55.1 | Trust model: who writes each section, from which step and by what it is bound, what it never proves | no code of its own: `header_binding` (step 15), the age header MACs (steps 11, 13 and 17), `capsule_id` and `capsule_digest` (step 9.a) | `capsule.TestTrustModel` (a capsule forged from the public bytes of `time_only.dkc` or `format2_time_only.dkc` opens; a control that declares L + 1 over a zero padding byte opens to another content; edited PUBLIC_HEADER data passes steps 1 to 8 and fails step 15; other `.dkk` extension data opens the capsule) |
@ -108,12 +108,12 @@ v0.14, and §47.1 and §79, new in v0.15. A case of §64 that is not in the repo
| 69 | Normative errors, including `ERR_EXTENSION_DATA_INVALID` and `ERR_HEAD_INVALID`; every error of the module wraps exactly one | `errors.go`; `provider/drand.Client` and step 9 of `capsule.Open` keep another code of a failure as text only | `datekeys.TestCatalogueMatchesSpec`, `TestCode`; `drand.TestFetchErrorHasOneCode`; `capsule.TestReleaseSourceErrorsAtStep9` |
| 69.1 | Error precedence: the first failing layer of each object (frame, a truncated prelude before the version; type tag and schema version; CBOR profile and CDDL, except the rules with codes of their own; fields with codes of their own in ascending key order, an extension unknown in its object or array before invalid data), the step order of §63 across objects and steps; only the optional inspection of steps 5, 6 and 8 and the `capsule_digest` check can change the code; the codes of step 10 are those of a release supplied directly, one from a network source being discarded at step 9; in format 2, the 16 stanzas belong to step 12, the version of CONTROL_CBOR against the format to layer 2 of step 14, the rules of keys 6 and 7 to its layer 3, and the length and padding of the plaintext to step 17 | `capsule.ParsePrelude`, `capsule.DecodeHeader`, `capsule.DecodeControl`, `accesskey.Decode`, `accesskey.DecodeBody`, `profile.Decode`, `codec.CheckSchema`, `codec.Unmarshal`, `extension.CheckCriticalIn`, `capsule.checkAccessKey`, `OpenOptions.AccessKeyFile`, `agewrap.AccessIdentity`, `provider/drand.Client` | `capsule.TestPrecedenceWithinPublicHeader`, `TestPrecedenceAcrossSteps` (with the examples of format 2), `TestDecodeHeaderReportsTheCDDLFirst`, `TestAccessKeyCheckOrder`, `TestAccessKeyFileAtStep9`, `TestControlCriticalBeforeHeaderBinding`, `TestReleaseFromANetworkSource`, `TestExtensionPlacement`; `accesskey.TestDecodePrecedence`; `agewrap.TestAccessIdentityStrictness`; `profile.TestDecodePrecedence`, `TestPinPathMatchesDecode`; `cmd/datekeys.TestDecryptAccessKeyOrder`; `extension.TestCheckCritical`, `TestPlacement`; `codec.TestCheckSchemaVersionForms`; `testdata/vectors/cbor.json`, `inspect_differential.json` |
| 70 | Compatibility: a reader accepts the three formats and opens formats 1 and 2 with the semantics of v0.8.2 and v0.9; an implementation that writes capsules writes format 3, and only a generator of test vectors writes formats 1 and 2; a reader should report the format; the format is neither the version of the specification (`datekeys.SpecVersion`) nor that of the module (`datekeys.Version`, `datekeys version`); a reader of v0.10 opens the capsules of v0.11, with an area of 32 or 64 KiB, F1 for `alg` 1 and 2, S1 for `seal_type` 2, and the public note and `datekeys.capsule` ignored, and a reader of v0.11 those of v0.10, with their area of 512 bytes; v0.12 changes no format, only texts of the verdicts and how a certificate and a token are read | magic and version checks; `capsule.Format`; `codec.Peek` and `codec.CheckSchema` read keys 0 and 1 only, before strict decoding, with a type tag of at most `codec.MaxTypeTagLen` bytes; `Inspection.Prelude.Format`, `Opened.Format`; `internal/testkit.Build`; `version.go`, where `SpecVersion` stays 0.11 until v0.12 is approved; `capsule.EvaluateSecurity`, which reads security without a context, as a reader of v0.10; `ParseBodyFrame`, which accepts any area of the frame; v0.15: a valid release in hand opens a capsule with a clock behind its round time | mutations; `capsule.TestFormatDispatch`, `TestFormatRelabel`, `TestFormat1Compatibility`; `codec.TestPeek`, `TestCheckSchema`, `TestCheckSchemaVersionForms`, `FuzzPeek`; `capsule.TestDecodeSchemaVersion`; `datekeys.TestSpecVersionNamesTheSpecification`, `TestVersion`; `cmd/datekeys.TestVersion`; `capsule.TestEvaluateSecurityIn` and `TestEvaluateCMS` (no context: F1), `TestEvaluateSeal` (no context: S1), `TestCMSVectors` (the cases without a context); the fixtures of v0.10, with their area of 512 bytes, among those of v0.11 (`TestConformanceFixtures`); mutation *round not reached yet* |
| 71 | Profile registry; the states `activo`, `solo lectura` and `comprometido`; SHOULD: warn of a compromised profile | `profile.Decode` + `profile.NewRegistry` with pinned hashes; `profile.Status`, `StatusOf` (the states that this release of the module knows, by profile_hash: Quicknet active); `cmd/datekeys`: `encrypt` writes no capsule with a profile that is not active, and `decrypt` and `inspect` warn when the profile of a capsule is compromised | `profile.TestRegistry`, `TestStatus`; `cmd/datekeys.TestProfileStatus` |
| 72 | Extension registry and registration rules, among them the objects and arrays where each extension may appear, and an encoder never writes one elsewhere; the encoder decodes its own output before sealing; security-relevant claims in CONTROL_CBOR or under a signature extension, `.dkk` extension data advisory; the registered extensions, `datekeys.note` in the noncritical array of PUBLIC_HEADER and `datekeys.capsule` in the noncritical array of a `.dkk`, both informative | `extension.Registry`, `extension.Set`, `extension.DataValidator`, `extension.Placement` (optional: a `Registry` without it knows its extensions in every object and array); `extension.Standard`, the registry of the extensions of the specification (`NoteID`, `CapsuleID`), and `locator.Standard`, which validates the data of `datekeys.capsule`; `extension.CheckWrite`, which the writers of capsules and `.dkk` files apply with `extension.Standard` (`capsule.newSealer`, `accesskey.AccessKey.MarshalBody`); self-checks in `capsule.Encrypt`, `capsule.EncryptFiles`, `accesskey.MarshalBody` and `locator.Info.Extension`; these writers take no `Registry`: the application writes each extension of its own only where it is registered | `capsule.TestKnownCriticalExtensions`, `TestUnusableNoncriticalExtensions`, `TestExtensionPlacement`, `TestNestedDataSealsAndOpens`, `TestRegisteredExtensionsWhereRegistered`, `TestPublicNoteRules`, `FuzzEncodeImpliesDecode`; `extension.TestPlacement`, `TestCheckWrite`; `locator.TestInfo` |
| 74 | Provisional aspects; the implementation limits of the reference (name lengths, `public_key`, `period`, maximum `extension_id` length, `dk1_` length, age parser limits, `ERR_POLICY_STRUCTURE_MISMATCH` for INNER_ACCESS_AGE) | `profile.ValidID`, `validName`, `maxPublicKeyLen`, `maxPeriod`; `extension.MaxIDLen`; `datekey.MaxEncodedLen`; `filippo.io/age` | `profile.TestValidateRejectsTamperedProfiles`, `TestIntegerRanges`; `extension.TestNew`; the vectors of `cbor.json` named after the implementation limit |
| 75 | Blocking requirements before v1.0 | items 1–9 and 11 above, with fixtures and mutations in the three formats; item 10 (external review) pending | — |
| 76 | Change policy; the normative changes of v0.8.2: the extension change and its reproducible cases; the refinements and theirs; the amendment on point canonicality and its case (a second implementation on `tlock-js` and `@noble/curves` 1.9.7 accepted U with c0 + p and a signature with x + p); the corrections of the formal review (an invalid release from a network source, the objects and arrays of each extension, the serialization of GT in H2) and their cases, and those of its second round (the encoder rule of §72, the codes of step 10 in §17 and §51 for a release supplied directly, one code for any failure of a source at step 9); the normative changes of v0.9, capsule format 2, and their cases; those of v0.10, capsule format 3, and theirs; those of v0.11, the area of 32 KiB, what is signed, the signatures of `alg` 1 and 2, the seal of `seal_type` 2, the key of words, the public note and `datekeys.capsule`, and theirs; and those of the draft v0.12, which change no format: the names of certificates and the seal of each signer of F6 in the verdicts, the holder without its identifier, the profile of the certificate, identifiers, repetitions and edge cases, the addresses and the padding of the locator, errata, and the vectors of v0.11 | `extension`, `codec`, fixture `time_only_extensions` regenerated; refinements: the order of `capsule.checkAccessKey`, `BODY_LEN` 0 in `accesskey.Decode`, CR and LF and invalid UTF-8 in `datekey.Parse`, the `.dkk` decoded at step 9.a (`OpenOptions.AccessKeyFile`, the CLI), nil identities in `capsule.Open`, every identity tried in `agewrap.AccessIdentity`, `profile.NewRegistry` through `Decode`, `Profile.Validate` rule 1 first; four new `dk1.json` vectors; corrections: `extension.Placement` and the object-aware checks, the `provider.ReleaseSource` contract, `testdata/vectors/tlock_ibe.json`; second round: the error of `provider/drand.Client` and of step 9 in `capsule.Open`; v0.9: rows 22, 29, 29.1, 31, 33, 36, 37, 39, 55.2, 56, 57, 61, 62, 62.1, 63 and 70; v0.10: rows 22, 23, 29 to 29.7, 31, 56, 57, 61 to 64 and 67 to 70; v0.11: rows 24.1, 29.2, 29.3, 29.7 to 29.12, 38.1, 44.1, 62.1, 64, 67, 70 and 72; v0.12: rows 29.3, 29.7, 29.10, 29.11, 44.1, 64, 67 and 70, and the sizes of the locator in `spec/datekeys.cddl` | case 2: `extension.TestNew`; case 3: `capsule.TestNaNKeyedDataHasOneVerdict`; case 4: `capsule.TestExtensionFixtureData`; case 5: `capsule.TestNestedDataSealsAndOpens`; case 6: `capsule.TestHugeExtensionArraysAreRejected`, `extension.TestCheckDisjointIsLinear`; refinements: the tests of rows 12.1, 15, 17, 19, 22, 28.1, 35, 36, 40, 51, 55.1, 63 and 69.1, and `extension.TestOrderIsUnsignedBytewise`; amendment: the tests of rows 12.2 and 64; corrections: `capsule.TestReleaseFromANetworkSource`, `TestExtensionPlacement`, `extension.TestPlacement`, `agewrap.TestTlockH2Vector`; second round: `capsule.TestReleaseSourceErrorsAtStep9`, `TestExtensionPlacement` (the noncritical array of a `.dkk`), `drand.TestFetchErrorHasOneCode`, `TestUnavailabilityAndCancellation`, `datekeys.TestCode`; v0.9: the tests that §76 names for each change, in rows 22, 29.1, 31, 37, 39, 55.2, 57, 62.1, 64 and 70; v0.10: those of the rows it changed; v0.11: those of the rows it added and changed; v0.12: changes 1 and 2, `capsule.TestEvaluateCMS`, `TestIssuerTextFiltered`, `cmd/datekeys.TestRows` and the record of `format3_signed_cms`; change 5, `der.TestSetOfSorted`, `TestCheck` and `cms.TestTSTInfoStrict`; changes 6 and 7, `locator.TestAddresses`, `TestUsableAddresses`, `TestLeastMultiple` and `TestPaddingBoundaries`; change 9, `testdata/vectors/security.json` and the fixture `format3_seal_unsupported`; the cases of changes 1 and 3 to 5 in `security_cms.json`, and those of changes 6 and 7 in `locator.json` (`TestLocatorVectors`); the changes of v0.15, each with its case: the release object (`release.json`, `testdata/releases`), its chain hash at step 10 and step 9.c (`mutations.json`, field `source`), the recovery (`scripts/recovery_check.sh`) |
| 79 | Informative annex: recovery without DateKeys software (v0.15) | `scripts/recovery` (no package of this module, tlock or drand: Go, `golang.org/x/crypto`, `filippo.io/age`, `drand/kyber-bls12381`), `scripts/recovery_check.sh`, run by `scripts/check.sh` | `scripts/recovery.TestImports`, `TestRecoverFixtures` and the other tests of the package, over eight fixtures of the three formats; `scripts/recovery_check.sh` on `format3_single` and `format3_time_and_key_portable` |
| 79 | Informative annex: recovery without DateKeys software (v0.15); §62.1 rule 27, the annex next to each `.dkc` | `datekeys.RecoveryAnnex` (`annex/recovery.md`, §79 under a title with the version and the SHA-256 of the specification) and `RecoveryAnnexSuffix`; `encrypt` writes it as `FILE.dkc.recuperacion.txt` unless `-no-recovery`; `scripts/recovery` (no package of this module, tlock or drand: Go, `golang.org/x/crypto`, `filippo.io/age`, `drand/kyber-bls12381`), `scripts/recovery_check.sh`, run by `scripts/check.sh` | `scripts/recovery.TestImports`, `TestRecoverFixtures` and the other tests of the package, over eight fixtures of the three formats; `datekeys.TestRecoveryAnnex` (the annex is §79 of the specification of `SpecVersion`), `cmd/datekeys.TestRecoveryAndNotices`; `scripts/recovery_check.sh` on `format3_single` and `format3_time_and_key_portable` |