Docs at spec v0.11 and the draft v0.12: READMEs, SECURITY, traceability, CHANGELOG

The session that closed v0.11 left the documentation at v0.10 (review of
2 October, G14).

- README.md and README.es.md say the same again: the reference implements
  v0.11, tagged, and the branch v0.12 the draft; SpecVersion 0.11; the area
  of 32 KiB in the picture of BODY; the table of modules with authorkey,
  internal/cms, internal/der, locator, wordkey and the public note; and what
  the CLI does now: encrypt -sign shows the key and the code of
  AUTHOR_MESSAGE before it signs, decrypt -expect-author writes nothing
  unless the key of an F4 matches, the lines of the verdicts break behind a
  mark, and decrypt and inspect say when a public note is not shown. The
  security properties no longer say that no signature is checked.
- SECURITY.md: the scope is v0.11 and the draft v0.12; the limits of a
  signature, a seal and a key of words; the standard library among the
  cryptographic dependencies.
- docs/traceability.md at the draft v0.12: rows for 24.1, 29.8 to 29.12,
  38.1 and 44.1, and rows 29.2, 29.3, 29.7, 62.1, 64, 67, 70, 72 and 76 up
  to date, with the code and the tests of each. The cases of spec 64 that
  security_cms.json and locator.json still lack are marked pending.
- CHANGELOG.md: an entry for the draft v0.12: the review and its fixes, the
  draft, the CMS reader with its own profile, the addresses of the locator,
  the CLI, the new test data, and what is pending.
- capsule/format3.go: the comments of EncodeSecurityWith,
  EncodeAuthorSignature and EncodeSeal no longer say that this version
  defines no alg and no seal_type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.12
dev 6 days ago
parent ec08ec9578
commit 858c5f1f26

@ -3,6 +3,111 @@
All notable changes to this module are documented here. The project follows
semantic versioning; `v0.x` versions make no API stability promise.
## Unreleased — specification v0.12 draft
Implements the draft v0.12 of the DateKeys Protocol Specification, on the
branch `v0.12` and not approved yet. It fixes what the review of the
implementation of v0.11 found on 2 October 2026, and changes no format: a
reader of v0.11 opens these capsules, and this one opens those of v0.11.
`SpecVersion` stays 0.11 until the author approves the draft.
- **Review of 2 October.** Six adversarial reviews of the implementation of
v0.11 found nothing blocking: what is signed, the strict Ed25519, the CMS
and RFC 3161 checks and the envelope hold, and the failures were at the
edges. What the text of v0.11 already asked for is fixed in the code, as
this entry says; what it left open or got wrong goes to the draft.
- A typed nil in `EncryptOptions.AuthorKey`, `CMSSigner` or `Sealer` is an
error, never a capsule without the signature or the seal that was asked
for.
- A panic while evaluating the signature or the seal fails only that part,
F1 or S2, not both.
- `OpenOptions.Accept` sees the verdicts after step 17 and before step 18,
and can refuse to publish the files.
- `extension.CheckWrite`, the rule of encoders of spec §72: the writers of
capsules and `.dkk` files refuse `datekeys.note` and `datekeys.capsule`
outside the arrays where they are registered, or with invalid data.
- `authorkey.Key.String` and `GoString` hide the secret key, which only
`Key.Secret` returns. `ParsePublic` refuses a key that is not a point of
the curve (`ed25519strict.OnCurve`).
- `Header.UnusableNote` tells a public note that breaks the rules of text
from no note.
- **Specification.** `spec/DateKeys_Protocol_Specification_v0.12.md`, a draft
whose §76 lists each change with its case: the names of certificates in the
verdicts, between « and », shown only with at most 64 code points and no two
spaces in a row; the authority of each seal in the lines of F6, with the
warning that nobody checks who issued it; the holder by `givenName` and
`surname` before the `commonName`, which in the certificates of the FNMT
carries the NIF; the profile of the certificate field by field; object
identifiers by their bytes, repeated elements of a SET OF and the edge cases
of the token; the addresses and the padding of the locator; and the errata
of §44.1, §55.2, §64, §67 and §76. The CDDL fixes the sizes of the locator.
`Verdicts.Lines` follows it, and writes the result of a foreign signer in
Spanish.
- **CMS reader with a profile of its own.** `internal/cms` reads certificates
field by field, as the profile of §29.10 says, instead of with
`encoding/asn1` and `crypto/x509`, so that a second implementation reads
them the same; a certificate that breaks it decides nothing unless a
`SignerInfo` names it. The text of a name comes only from UTF8String,
PrintableString, IA5String, TeletexString in ASCII and BMPString without
surrogates, with nothing removed. The key is RSA with NULL parameters and
an odd modulus, or EC uncompressed on P-256, P-384 and P-521. Object
identifiers are compared by the bytes of their DER, so an arc of 2^31 or
more no longer makes an attribute that decides nothing fail the signature;
a SET OF may repeat an element, and two copies of a certificate are one; a
key of another scheme than its algorithm is F2; a `messageImprint` of
another length is S3; the `crls` of a token decide nothing. `internal/der`
checks UTCTime and GeneralizedTime in their forms of X.690, a date that
exists, and millis and micros as minimal INTEGERs.
- **Addresses of the locator.** A reader rejects an address that breaks §44.1
and uses the others (`Locator.Usable`); a writer never writes one. Addresses
refuse the special-purpose blocks of IANA, IPv6 outside 2000::/3,
`localhost` and local names, characters outside RFC 3986, dot segments, and
a CID that does not decode to version 1 and a multihash. `ParseInfo` checks
that the locator is an age file with one tlock stanza for the round of its
DateKey, `Info.Extension` reads what it writes, and `Info.OpenLocator`
refuses a nil registry. The errors of `Open` and `Unmarshal` carry no
normative code.
- **CLI.** `encrypt -sign` shows the author key and the code of
`AUTHOR_MESSAGE` before it signs (spec §62.1 rule 20).
`decrypt -expect-author` compares the key of an F4, and writes nothing
unless it matches. `decrypt` and `inspect` say when a public note is not
shown because it breaks the rules of text; `inspect -json` gives
`public_note_unusable`. The lines of the verdicts break at the last space
that fits, each row after the first behind ` ↳ `, so that the terminal
never breaks them. `encrypt` reports L as the length of the payload, not of
the content.
- **Test data.**
- `genfixtures -force` writes the fixtures of v0.10 again with their area
of 512 bytes, through `EncryptOptions.TestAreaLen`, which needs
`TestVectors`; it used to rewrite them with 32 KiB and fail halfway.
- `format3_seal_unsupported` uses `seal_type` 4294967295, reserved for
tests, instead of `seal_type` 1, which a later version may define;
`capsule.AlgTest` and `SealTypeTest` name the two values.
- `security.json` carries the context of a capsule, its commitments and the
time of its round, and each case the verdicts and the lines of a reader of
this version: a signature of `alg` 1 that does not verify is F2, a token
of `seal_type` 2 that is not DER is S2, and new cases give a valid
signature of `alg` 1 (F4), and `alg` and `seal_type` 4294967295 (F1, S1).
24 cases.
- `note.json`, new: 16 public notes, those that pass and those that a
writer refuses and a reader does not show, each with the text of the rule
it breaks.
- New fixtures `format3_unsigned`, the capsule of `format3_signed` without
its signature, with the same P, and `format3_note`, with a public note:
26 capsules, 14 of format 3. The records of `format3_signed_cms` and
`format3_sealed` give the lines of v0.12.
- `mutations.json` gains 8 mutations of the list of v0.11 of §64: the
signature of `alg` 1 altered (F2), removed (F0), made again with another
key (F4 of that key) and transplanted to another capsule (F2); a key of 31
bytes and a signature of 65 (F1); the area widened to 64 KiB after signing
(F4, the same `AUTHOR_MESSAGE`); and the public note changed
(`ERR_HEADER_BINDING`, step 15). The signature of `alg` 1 that does not
verify (F2) is now a case of §64. 218 cases, 178 of the spec.
- **Pending in this branch.** `security_cms.json` and `locator.json` extended
with the cases of §64 of v0.12: the names, the holder and the profile of the
certificate, identifiers, repetitions and the edge cases of the token, and
the addresses and the padding of the locator.
## Unreleased — specification v0.11
Moves the module to the DateKeys Protocol Specification v0.11, approved by its

@ -1,7 +1,10 @@
# datekeys-go
Implementación de referencia en Go de la **DateKeys Protocol Specification
v0.10** ([`spec/`](spec/DateKeys_Protocol_Specification_v0.10.md)).
v0.11** ([`spec/`](spec/DateKeys_Protocol_Specification_v0.11.md)), etiquetada
`spec-v0.11`. Esta rama, `v0.12`, implementa además el borrador v0.12
([`spec/`](spec/DateKeys_Protocol_Specification_v0.12.md)), aún sin aprobar,
que no cambia ningún formato.
[English version](README.md).
DateKeys cifra datos de forma que solo puedan abrirse a partir de un instante
@ -22,25 +25,37 @@ transportes no confiables.
| DateKey: fecha → ronda, cadena canónica `dk1_…` | §14–§19 | [`datekey`](datekey) |
| Provider Profile, perfil Quicknet pinneado, `profile_hash` | §10–§13 | [`profile`](profile) |
| Fuentes de releases, verificación BLS local, relays drand | §45–§52 | [`provider`](provider), [`provider/drand`](provider/drand) |
| DateKeyCap `.dkc`: `time_only` y `time_and_key`, los ficheros del formato 3 | §20–§39, §61–§63 | [`capsule`](capsule) |
| DateKeyCap `.dkc`: `time_only` y `time_and_key`, los ficheros del formato 3, el área de seguridad y sus veredictos | §20–§39, §61–§63 | [`capsule`](capsule) |
| Rutas y textos del head, con las tablas de Unicode 18.0.0 y best-fit | §29.5, §29.6 | [`internal/pathrule`](internal/pathrule) |
| Firma de autor: qué se firma, `alg` 1 y 2, el sello de `seal_type` 2 | §29.8–§29.11 | [`capsule`](capsule) |
| Claves de autor `dkauthor1…` y el Ed25519 estricto de `alg` 1 | §29.9, §29.12 | [`authorkey`](authorkey), [`internal/ed25519strict`](internal/ed25519strict) |
| Firma con certificados (`alg` 2, CMS) y sello de tiempo (RFC 3161): DER, el perfil del certificado, la tabla cerrada de algoritmos | §29.10, §29.11 | [`internal/cms`](internal/cms), [`internal/der`](internal/der) |
| La nota pública `datekeys.note` | §24.1 | [`extension`](extension), [`capsule`](capsule) |
| Llave de palabras | §38.1 | [`wordkey`](wordkey) |
| DateKeys Access Key `.dkk` | §40–§44 | [`accesskey`](accesskey) |
| Extensiones | §54 | [`extension`](extension) |
| La extensión `datekeys.capsule`: localizador, sobre y direcciones | §44.1 | [`locator`](locator) |
| Extensiones y su registro | §54, §72 | [`extension`](extension) |
| CBOR determinista | §58 | [`codec`](codec) |
| Errores normativos | §69 | [`errors.go`](errors.go) |
| CLI | — | [`cmd/datekeys`](cmd/datekeys) |
Aquí no se implementa criptografía. El cifrado es [age](https://age-encryption.org)
(`filippo.io/age`); el timelock es [tlock](https://github.com/drand/tlock)
(solo su núcleo exportado); la verificación BLS es la de drand. Este módulo
aporta framing, CBOR, bindings, reglas de verificación y flujo, y aplica las
reglas de stanzas del protocolo dentro de las identities de age, para que un
fichero nunca se acepte solo porque age haya podido desenvolver una clave.
(solo su núcleo exportado); la verificación BLS es la de drand; las firmas de
autor y los sellos se verifican con la biblioteca estándar de Go, con el perfil
estricto de Ed25519 comprobado alrededor de `crypto/ed25519`. Este módulo
aporta framing, CBOR, DER, bindings, reglas de verificación y flujo, y aplica
las reglas de stanzas del protocolo dentro de las identities de age, para que
un fichero nunca se acepte solo porque age haya podido desenvolver una clave.
No implementa, a propósito: el servidor y la cola de la Release API, el
almacenamiento y la entrega, extensiones concretas, la firma de autor y el
sello de tiempo, que la especificación reserva para versiones posteriores, ni
el cliente TypeScript (plan §2).
almacenamiento y la entrega, la descarga del resto de un sobre ni el cliente
TypeScript (plan §2). El paquete `locator` comprueba las direcciones de un
localizador y lo que un lector trae de ellas, y no descarga nada (spec §44.1).
La firma con certificados y el sello comprueban la criptografía, nunca quién
emitió un certificado o un sello ni si se revocó: eso lo hace un validador
oficial (spec §29.10). Las curvas brainpool y los algoritmos nacionales como
GOST o SM2 quedan fuera de la tabla.
## Versiones
@ -49,15 +64,15 @@ Hay tres números de versión, cada uno con su significado:
| Versión | Dónde | Cambia cuando |
|---|---|---|
| Formato | Dentro de los objetos: el formato de la cápsula, el `VERSION` del prelude de DKC1, 3 al escribir y de 1 a 3 al leer, que es también la versión de schema de CONTROL_CBOR; y 1 en la trama de DKK1 y en el schema de los demás objetos, incluidos el head y `security` del formato 3 | Cambia el formato. Un lector rechaza una versión que no conoce (spec §22, §70) |
| Especificación | `datekeys.SpecVersion`, hoy `0.10`, y el tag `spec-v0.10` | Cambia el texto normativo. §76 del spec recoge cada cambio con su caso |
| Especificación | `datekeys.SpecVersion`, hoy `0.11`, y el tag `spec-v0.11`. Un borrador, como la v0.12, no tiene tag ni la cambia | Cambia el texto normativo. §76 del spec recoge cada cambio con su caso |
| Módulo | Los tags de este módulo Go, `vX.Y.Z`, y `datekeys.Version()` | Cambia la API o el comportamiento. Versionado semántico, sin promesa de estabilidad antes de v1.0.0 |
`datekeys version` imprime la versión del módulo, la del spec y la del toolchain de Go. Un binario compilado en un checkout muestra la pseudo-versión de su commit, por ejemplo `v0.0.0-20260928105528-9ac9cd952f04`.
Cada release dice qué cubre, aquí y en el [CHANGELOG](CHANGELOG.md). El código de esta rama, aún sin publicar, cubre:
- la especificación 0.10: escribe el formato 3 de cápsula y lee los formatos 1, 2 y 3;
- la especificación 0.11 y el borrador v0.12: escribe el formato 3 de cápsula y lee los formatos 1, 2 y 3;
- el perfil Quicknet pinneado, y cualquier perfil de los tres schemes de drand que soporta tlock;
- cifrado, inspección y apertura, y la CLI;
- cifrado, inspección y apertura, firmas de autor y sellos, la nota pública, la llave de palabras y el localizador, y la CLI;
- todos los vectores y fixtures compartidos de [`testdata/`](testdata).
El primer tag, v0.1.0, llegará cuando `go get` funcione desde una máquina limpia.
@ -73,8 +88,8 @@ CONTROL_CBOR = { header_binding = SHA-256(PRELUDE || PUBLIC_HEADER), I_PAYLOAD,
PAYLOAD_AGE = age(X25519 R_PAYLOAD → BODY || ceros hasta P = regla(L)), en streaming
BODY = AREA_LEN || SECURITY_LEN || HEAD_LEN (3 × uint32)
|| SECURITY_CBOR, ceros hasta AREA_LEN (512) firma y sello: vacío en la v0.10
|| HEAD_CBOR sal, comentario, autor declarado, los ficheros
|| SECURITY_CBOR, ceros hasta AREA_LEN (32 KiB) firma de autor y sello, o vacío
|| HEAD_CBOR sal, comentario, autor declarado, los ficheros
|| los ficheros, uno tras otro
```
@ -82,12 +97,15 @@ Es el formato 3, el que escribe `encrypt` (spec §22, §29.2). Sus 16 stanzas
llevan de 1 a 16 credenciales y un señuelo en cada hueco libre, en orden
aleatorio, y su payload se rellena hasta P: hasta la fecha nadie sabe cuántas
credenciales hay, ni la longitud exacta L, ni los nombres de los ficheros ni
cuántos son, más allá de lo que acota P (spec §29.1, §39, §55.2). Ya abierta,
da ficheros con sus rutas, tamaños, SHA-256 y fechas de modificación, un
comentario y un autor declarado, y los veredictos de su área de seguridad. El
formato 2, el de la v0.9, lleva un solo contenido; el formato 1, el de la
v0.8.2, lleva un stanza por credencial y no rellena. Los lectores siguen
abriendo los dos.
cuántos son, más allá de lo que acota P (spec §29.1, §39, §55.2). El área de
seguridad mide 32 KiB, lleve o no firma, para que P no diga si la cápsula va
firmada, y 64 KiB solo cuando quien la crea lo pide porque una firma no cabe
(spec §29.2). Los escritores de la v0.10 escribían 512 bytes, que un lector
sigue aceptando. Ya abierta, da ficheros con sus rutas, tamaños, SHA-256 y
fechas de modificación, un comentario y un autor declarado, y los veredictos
de su área de seguridad. El formato 2, el de la v0.9, lleva un solo contenido;
el formato 1, el de la v0.8.2, lleva un stanza por credencial y no rellena.
Los lectores siguen abriendo los dos.
## CLI
@ -105,6 +123,7 @@ datekeys datekey resolve -at 2030-01-01T00:00:00Z
datekeys encrypt -at 2030-01-01T00:00:00Z -in carta.txt -out carta.dkc
datekeys encrypt -at 2030-01-01T00:00:00Z -in fotos -in carta.txt -comment "Para Ana" -author "Juan" -out regalo.dkc
datekeys encrypt -at 2030-01-01T00:00:00Z -policy time_and_key -dkk regalo.dkk -in fotos -out regalo.dkc
datekeys encrypt -at 2030-01-01T00:00:00Z -policy time_and_key -words-file palabras.txt -in carta.txt -out carta.dkc
datekeys encrypt -at 2030-01-01T00:00:00Z -padding bloque256 -no-mtime -in carta.txt -out carta.dkc
datekeys author keygen -out autor.key -pass-file clave.txt
datekeys encrypt -at 2030-01-01T00:00:00Z -in carta.txt -note "Cartas de Lisboa" -sign autor.key -sign-pass-file clave.txt -out carta.dkc
@ -132,8 +151,30 @@ comprobaciones han pasado (spec §56); una cápsula con solo un comentario no
crea la carpeta. Muestra los veredictos al principio y al final, y el autor
declarado, el comentario y las rutas como texto del creador que nadie ha
comprobado, cada línea tras el prefijo `│ ` y nunca más ancha que el terminal
(spec §29.7). Los formatos 1 y 2 siguen dando un fichero. Nunca se
sobrescriben ficheros de salida.
(spec §29.7). Una línea de los veredictos se parte por el último espacio que
cabe, y cada fila tras la primera empieza por ` ↳ ` (dos espacios, U+21B3 y
un espacio): el terminal nunca parte una, y ningún nombre de un certificado
puede empezar una fila y pasar por un veredicto. Los formatos 1 y 2 siguen
dando un fichero. Nunca se sobrescriben ficheros de salida.
`author keygen` crea una clave de autor Ed25519 (spec §29.12) en un fichero
cifrado con una contraseña, o en texto con `-plain`, e imprime su clave
pública, `dkauthor1…`; `author public` la vuelve a imprimir. La contraseña
llega de un fichero, o de la entrada estándar con `-`, nunca de la línea de
órdenes ni del entorno. `encrypt -sign` firma la cápsula con ella (`alg` 1):
antes de firmar muestra la clave y el código de `AUTHOR_MESSAGE` (spec §62.1,
regla 20), y comprueba la firma antes de escribir nada. `decrypt` muestra la
firma; con `-expect-author` falla, sin escribir nada, salvo que la cápsula
lleve una firma válida de esa clave pública, el veredicto F4: compara la clave
antes del paso 18, cuando aún no se ha publicado ningún fichero.
`encrypt -note` pone una nota pública en claro en la cápsula (spec §24.1), y
avisa de que es pública. `inspect` y `decrypt` la muestran como texto del
creador que nadie ha comprobado; una nota que incumple las reglas de texto no
se muestra, y los dos lo dicen (`public_note_unusable` en `inspect -json`).
`-words` y `-words-file` dan a una cápsula `time_and_key` una llave de
palabras: al menos seis palabras distintas de tres letras o más, que la abren
con `decrypt -words-file` en lugar de una `.dkk` (spec §38.1).
## Librería
@ -177,14 +218,17 @@ solo cuando el paso 17 ha pasado; tras cualquier fallo recibe `Abort`, y nada
de lo que recibió puede presentarse como válido (spec §56). `opened.Head`
lleva las rutas, los tamaños, los SHA-256 y las fechas de modificación, el
comentario y el autor declarado, y `opened.Verdicts.Lines()` los veredictos
que se muestran antes (spec §29.7). `Open` sin `Sink` se detiene justo tras
el paso 2 con `capsule.ErrSinkRequired`, un error del llamador sin código. Las
cápsulas de los formatos 1 y 2 siguen escribiendo su contenido en streaming en
`dst`, nunca el relleno. `opened.Format` es el formato de la cápsula: el
formato 1 no oculta el número de credenciales ni la longitud exacta del
contenido, así que muéstralo (spec §70). Todo fallo del protocolo envuelve uno
de los 18 errores normativos del §69, así que `errors.Is` y
`datekeys.Code(err)` lo identifican.
que se muestran antes (spec §29.7). `EncryptOptions.AuthorKey` o `CMSSigner`
firman la cápsula, y `Sealer` la sella (spec §29.9 a §29.11);
`OpenOptions.AuthorKeys` son las claves que la persona guardó, que dan F3, y
`OpenOptions.Accept` ve los veredictos antes del paso 18 y puede negarse a
publicar los ficheros. `Open` sin `Sink` se detiene justo tras el paso 2 con
`capsule.ErrSinkRequired`, un error del llamador sin código. Las cápsulas de
los formatos 1 y 2 siguen escribiendo su contenido en streaming en `dst`,
nunca el relleno. `opened.Format` es el formato de la cápsula: el formato 1 no
oculta el número de credenciales ni la longitud exacta del contenido, así que
muéstralo (spec §70). Todo fallo del protocolo envuelve uno de los 18 errores
normativos del §69, así que `errors.Is` y `datekeys.Code(err)` lo identifican.
## Propiedades de seguridad y límites
@ -203,17 +247,22 @@ de los 18 errores normativos del §69, así que `errors.Is` y
§55.1.
- **Privacidad de metadatos**: hasta la fecha quedan ocultos la longitud
exacta del contenido, el número de credenciales, y los nombres, tamaños y
número de los ficheros, más allá de lo que acota el tamaño con relleno P; no
la fecha, la política de acceso, `capsule_id` ni las extensiones de la
cabecera (spec §55.2).
número de los ficheros, más allá de lo que acota el tamaño con relleno P, y
también si la cápsula va firmada, salvo que se haya ampliado su área; no la
fecha, la política de acceso, `capsule_id` ni las extensiones de la
cabecera, entre ellas la nota pública (spec §55.2).
- **Extracción segura**: las rutas del head no pueden salir de la carpeta,
colisionar en Windows, macOS o Linux, esconder caracteres ni cambiar la
dirección del texto; las reglas usan tablas fijas de Unicode 18.0.0, nunca
las de la plataforma (spec §29.5, §29.6).
- **Sin autoría**: el autor declarado y el comentario son texto del creador
que no prueba nada, y esta versión no comprueba firmas ni sellos: sus
veredictos solo lo dicen, y nunca deciden la apertura (spec §29.3, §29.7,
§36.1). `time_and_key` añade una barrera de acceso, no una firma.
- **Autoría solo por una firma**: el autor declarado, el comentario, la nota
pública y las fechas de modificación son texto del creador que no prueba
nada (spec §24.1, §29.7, §36.1). Una firma de `alg` 1 prueba que firmó quien
tenía la clave secreta, no quién la tiene; de una firma con certificados y de
un sello, DateKeys comprueba la criptografía y las fechas, nunca quién los
emitió ni si se revocaron (spec §29.9 a §29.11). Los veredictos nunca
deciden la apertura (spec §29.3). `time_and_key` añade una barrera de
acceso, no una firma.
- **Recuperar años después** exige el release histórico: de un relay drand que
aún lo sirva o de cualquier caché, verificado de nuevo localmente (spec §50).
@ -232,21 +281,25 @@ go test -tags integration ./capsule ./provider/drand # Quicknet en vivo
- `testdata/vectors`: vectores de `profile_hash`, fecha→ronda y `dk1_` (spec
§65, §66), vectores del perfil CBOR y de cada schema, vectores del relleno
(spec §29.1), las rutas, las claves de R7, los heads y las áreas de
seguridad del formato 3 (spec §67), el corpus de mutaciones exportado y un
corpus diferencial de las comprobaciones previas al desbloqueo; formatos en
seguridad del formato 3 (spec §67), el Ed25519 estricto de `alg` 1, las
firmas con certificados y los sellos, la nota pública y la extensión
`datekeys.capsule`, el corpus de mutaciones exportado y un corpus
diferencial de las comprobaciones previas al desbloqueo; formatos en
[`testdata/README.md`](testdata/README.md).
- `testdata/fixtures`: fixtures oficiales `.dkc`/`.dkk` sobre rondas ya
publicadas, con la firma BLS embebida y todos los valores intermedios (spec
§67, §68); se descifran sin red. Nueve son de formato 3; las siete de
formato 2, de la v0.9, y las cinco de formato 1, de la v0.8.2, se conservan
por compatibilidad. Cada `.dkc` tiene congelada su salida de
§67, §68); se descifran sin red. Catorce son de formato 3, cinco de ellas
con el área de 32 KiB de la v0.11: sin firma, firmada con `alg` 1, sellada,
firmada con certificados y con una nota pública. Las siete de formato 2, de
la v0.9, y las cinco de formato 1, de la v0.8.2, se conservan por
compatibilidad. Cada `.dkc` tiene congelada su salida de
`datekeys inspect -json`.
- `internal/testkit.Mutations`: las mutaciones del §64, las 33 de sus dos
primeras listas en cada formato, las 23 de la lista del formato 2 y las 47
de la del formato 3, y 40 más, cada una con su error y su paso exactos, o
sus veredictos cuando se abre, comprobando además que los fallos previos al
desbloqueo nunca provocan una petición de release; exportadas a
`testdata/vectors/mutations.json`.
primeras listas en cada formato, las 23 de la lista del formato 2, las 48
de la del formato 3 y 8 de la de la v0.11, y 40 más, cada una con su error
y su paso exactos, o sus veredictos cuando se abre, comprobando además que
los fallos previos al desbloqueo nunca provocan una petición de release;
exportadas a `testdata/vectors/mutations.json`.
- [`docs/traceability.md`](docs/traceability.md): sección del spec → código → test.
- [`spec/datekeys.cddl`](spec/datekeys.cddl): schemas CBOR.

@ -1,7 +1,10 @@
# datekeys-go
Reference implementation in Go of the **DateKeys Protocol Specification
v0.10** ([`spec/`](spec/DateKeys_Protocol_Specification_v0.10.md)).
v0.11** ([`spec/`](spec/DateKeys_Protocol_Specification_v0.11.md)), tagged
`spec-v0.11`. This branch, `v0.12`, also implements the draft v0.12
([`spec/`](spec/DateKeys_Protocol_Specification_v0.12.md)), not approved yet,
which changes no format.
[Versión en español](README.es.md).
DateKeys encrypts data so that it can only be opened after a chosen instant.
@ -22,26 +25,33 @@ untrusted transports.
| DateKey: date → round, canonical `dk1_…` string | §14–§19 | [`datekey`](datekey) |
| Provider Profile, pinned Quicknet profile, `profile_hash` | §10–§13 | [`profile`](profile) |
| Release sources, local BLS verification, drand relays | §45–§52 | [`provider`](provider), [`provider/drand`](provider/drand) |
| DateKeyCap `.dkc`: `time_only` and `time_and_key`, the files of format 3 | §20–§39, §61–§63 | [`capsule`](capsule) |
| DateKeyCap `.dkc`: `time_only` and `time_and_key`, the files of format 3, the security area and its verdicts | §20–§39, §61–§63 | [`capsule`](capsule) |
| Paths and texts of a head, with the Unicode 18.0.0 and best-fit tables | §29.5, §29.6 | [`internal/pathrule`](internal/pathrule) |
| Author signature: what is signed, `alg` 1 and 2, the seal of `seal_type` 2 | §29.8–§29.11 | [`capsule`](capsule) |
| Author keys `dkauthor1…` and the strict Ed25519 of `alg` 1 | §29.9, §29.12 | [`authorkey`](authorkey), [`internal/ed25519strict`](internal/ed25519strict) |
| Signature with certificates (`alg` 2, CMS) and time seal (RFC 3161): DER, the profile of the certificate, the closed table of algorithms | §29.10, §29.11 | [`internal/cms`](internal/cms), [`internal/der`](internal/der) |
| The public note `datekeys.note` | §24.1 | [`extension`](extension), [`capsule`](capsule) |
| Key of words | §38.1 | [`wordkey`](wordkey) |
| DateKeys Access Key `.dkk` | §40–§44 | [`accesskey`](accesskey) |
| Extensions, the public note | §24.1, §54 | [`extension`](extension) |
| Author keys `dkauthor1…`, `alg` 1 | §29.9, §29.12 | [`authorkey`](authorkey), [`internal/ed25519strict`](internal/ed25519strict) |
| Signature with certificates (`alg` 2, CMS), time seal (RFC 3161) | §29.10, §29.11 | [`internal/cms`](internal/cms), [`internal/der`](internal/der), [`capsule`](capsule) |
| The extension `datekeys.capsule`: locator and envelope | §44.1 | [`locator`](locator) |
| The extension `datekeys.capsule`: locator, envelope and addresses | §44.1 | [`locator`](locator) |
| Extensions and their registry | §54, §72 | [`extension`](extension) |
| Deterministic CBOR | §58 | [`codec`](codec) |
| Normative errors | §69 | [`errors.go`](errors.go) |
| CLI | — | [`cmd/datekeys`](cmd/datekeys) |
No cryptography is implemented here. Encryption is [age](https://age-encryption.org)
(`filippo.io/age`); the timelock is [tlock](https://github.com/drand/tlock)
(its exported core only); BLS verification is drand's. This module adds
framing, CBOR, bindings, verification rules and the flow, and it enforces the
stanza rules of the protocol inside the age identities, so that a file is never
accepted just because age could unwrap a key.
(its exported core only); BLS verification is drand's; author signatures and
seals are verified with the standard library of Go, with the strict profile of
Ed25519 checked around `crypto/ed25519`. This module adds framing, CBOR, DER,
bindings, verification rules and the flow, and it enforces the stanza rules of
the protocol inside the age identities, so that a file is never accepted just
because age could unwrap a key.
Not implemented on purpose: the Release API server and queue, storage and
delivery, and the TypeScript client (plan §2). The signature with
delivery, the download of the rest of an envelope, and the TypeScript client
(plan §2). Package `locator` checks the addresses of a locator and what a
reader brings from them, and downloads nothing (spec §44.1). The signature with
certificates and the seal check the cryptography, never who issued a
certificate or a seal, or whether it was revoked: that is for an official
validator (spec §29.10). Brainpool curves and national algorithms such as GOST
@ -54,15 +64,15 @@ Three version numbers, each with its own meaning:
| Version | Where | Changes when |
|---|---|---|
| Format | Inside the objects: the capsule format, the `VERSION` of the DKC1 prelude, 3 when written and 1 to 3 when read, which is also the schema version of CONTROL_CBOR; and 1 for the framing of DKK1 and the schema of the other objects, the head and security of format 3 included | The format changes. A reader rejects a version it does not know (spec §22, §70) |
| Specification | `datekeys.SpecVersion`, today `0.10`, and the tag `spec-v0.10` | The normative text changes. Spec §76 records each change with its case |
| Specification | `datekeys.SpecVersion`, today `0.11`, and the tag `spec-v0.11`. A draft, such as v0.12, has no tag and does not change it | The normative text changes. Spec §76 records each change with its case |
| Module | The tags of this Go module, `vX.Y.Z`, and `datekeys.Version()` | The API or the behaviour changes. Semantic versioning, with no stability promise before v1.0.0 |
`datekeys version` prints the module version, the specification and the Go toolchain. A binary built in a checkout shows the pseudo-version of its commit, for example `v0.0.0-20260928105528-9ac9cd952f04`.
Each release states what it covers, here and in the [CHANGELOG](CHANGELOG.md). The code of this branch, not yet released, covers:
- specification 0.10: it writes capsule format 3 and reads formats 1, 2 and 3;
- specification 0.11 and the draft v0.12: it writes capsule format 3 and reads formats 1, 2 and 3;
- the pinned Quicknet profile, and any profile on the three drand schemes that tlock supports;
- encryption, inspection and opening, and the CLI;
- encryption, inspection and opening, author signatures and seals, the public note, the key of words and the locator, and the CLI;
- every shared vector and fixture of [`testdata/`](testdata).
The first tag, v0.1.0, comes once `go get` works from a clean machine.
@ -78,8 +88,8 @@ CONTROL_CBOR = { header_binding = SHA-256(PRELUDE || PUBLIC_HEADER), I_PAYLOAD,
PAYLOAD_AGE = age(X25519 R_PAYLOAD → BODY || zeros up to P = rule(L)), streamed
BODY = AREA_LEN || SECURITY_LEN || HEAD_LEN (3 × uint32)
|| SECURITY_CBOR, zeros up to AREA_LEN (512) signature and seal: empty in v0.10
|| HEAD_CBOR salt, comment, declared author, the files
|| SECURITY_CBOR, zeros up to AREA_LEN (32 KiB) author signature and seal, or empty
|| HEAD_CBOR salt, comment, declared author, the files
|| the files, one after another
```
@ -87,9 +97,13 @@ This is format 3, the one `encrypt` writes (spec §22, §29.2). Its 16 stanzas
hold from 1 to 16 credentials and a dummy in each slot left, in a random
order, and its payload is padded to P, so that until the unlock date nobody
learns the number of credentials, the exact length L, the names of the files
or how many there are, beyond what P bounds (spec §29.1, §39, §55.2). Once
open, it gives files with their paths, sizes, SHA-256 and modification times,
a comment and a declared author, and the verdicts of its security area.
or how many there are, beyond what P bounds (spec §29.1, §39, §55.2). The
security area measures 32 KiB, signed or not, so that P does not tell whether
a capsule is signed, and 64 KiB only when the creator asks for it because a
signature does not fit (spec §29.2). The writers of v0.10 wrote 512 bytes,
which a reader still accepts. Once open, it gives files with their paths,
sizes, SHA-256 and modification times, a comment and a declared author, and
the verdicts of its security area.
Format 2, that of v0.9, holds one content; format 1, that of v0.8.2, has one
stanza per credential and no padding. Readers still open both.
@ -109,6 +123,7 @@ datekeys datekey resolve -at 2030-01-01T00:00:00Z
datekeys encrypt -at 2030-01-01T00:00:00Z -in letter.txt -out letter.dkc
datekeys encrypt -at 2030-01-01T00:00:00Z -in photos -in letter.txt -comment "For Ana" -author "Juan" -out gift.dkc
datekeys encrypt -at 2030-01-01T00:00:00Z -policy time_and_key -dkk gift.dkk -in photos -out gift.dkc
datekeys encrypt -at 2030-01-01T00:00:00Z -policy time_and_key -words-file words.txt -in letter.txt -out letter.dkc
datekeys encrypt -at 2030-01-01T00:00:00Z -padding bloque256 -no-mtime -in letter.txt -out letter.dkc
datekeys author keygen -out author.key -pass-file pass.txt
datekeys encrypt -at 2030-01-01T00:00:00Z -in letter.txt -note "Letters from Lisbon" -sign author.key -sign-pass-file pass.txt -out letter.dkc
@ -135,19 +150,30 @@ inside it and moved into place only after every check passed (spec §56); a
capsule with only a comment creates no folder. It shows the verdicts first
and last, and the declared author, the comment and the paths as text of the
creator that nobody has checked, each line behind a `│ ` prefix and never
wider than the terminal (spec §29.7). Formats 1 and 2 still give one file.
Outputs are never overwritten.
`author keygen` makes an Ed25519 author key (spec v0.11, §29.12) in a file
encrypted with a passphrase, or as text with `-plain`, and prints its public
key, `dkauthor1…`; `author public` prints it again. `encrypt -sign` signs the
capsule with it (`alg` 1) and checks the signature before writing anything;
`decrypt` shows the signature, and with `-expect-author` it fails, after
writing the files, unless the capsule is signed with that public key. The
passphrase comes from a file, or from the standard input with `-`, never from
the command line or the environment. `encrypt -note` puts a public note in
clear in the capsule (§24.1): `inspect` and `decrypt` show it as text of the
creator that nobody has checked.
wider than the terminal (spec §29.7). A line of the verdicts breaks at the
last space that fits, and each row after its first starts with ` ↳ ` (two
spaces, U+21B3 and a space): the terminal never breaks one, and no name of a
certificate can start a row and pass for a verdict. Formats 1 and 2 still give
one file. Outputs are never overwritten.
`author keygen` makes an Ed25519 author key (spec §29.12) in a file encrypted
with a passphrase, or as text with `-plain`, and prints its public key,
`dkauthor1…`; `author public` prints it again. The passphrase comes from a
file, or from the standard input with `-`, never from the command line or the
environment. `encrypt -sign` signs the capsule with it (`alg` 1): before it
signs it shows the key and the code of `AUTHOR_MESSAGE` (spec §62.1 rule 20),
and it checks the signature before writing anything. `decrypt` shows the
signature; with `-expect-author` it fails, and writes nothing, unless the
capsule has a valid signature of that public key, the verdict F4: it compares
the key before step 18, when no file has been published.
`encrypt -note` puts a public note in clear in the capsule (spec §24.1), and
warns that it is public. `inspect` and `decrypt` show it as text of the creator
that nobody has checked; a note that breaks the rules of text is not shown,
and both say so (`public_note_unusable` in `inspect -json`).
`-words` and `-words-file` give a `time_and_key` capsule a key of words: at
least six different words of three or more letters, which open it with
`decrypt -words-file` instead of a `.dkk` (spec §38.1).
## Library
@ -190,6 +216,10 @@ when step 17 has passed; after any failure it gets `Abort`, and nothing it
received may be presented as valid (spec §56). `opened.Head` holds the paths,
sizes, SHA-256 and modification times, the comment and the declared author,
and `opened.Verdicts.Lines()` the verdicts to show before them (spec §29.7).
`EncryptOptions.AuthorKey` or `CMSSigner` sign the capsule, and `Sealer` seals
it (spec §29.9 to §29.11); `OpenOptions.AuthorKeys` are the keys that the
person saved, which give F3, and `OpenOptions.Accept` sees the verdicts before
step 18 and can refuse to publish the files.
`Open` without a `Sink` stops right after step 2 with `capsule.ErrSinkRequired`,
a caller error with no code. Capsules of formats 1 and 2 still stream their
content to `dst`, never the padding. `opened.Format` is the format of the
@ -214,16 +244,22 @@ one of the 18 normative errors of spec §69, so `errors.Is` and
it is bound, is the trust model of spec §27 and §55.1.
- **Metadata privacy**: until the date, the exact length of the content, the
number of credentials, and the names, sizes and number of the files stay
hidden, beyond what the padded size P bounds; the date, the access policy,
`capsule_id` and the header extensions do not (spec §55.2).
hidden, beyond what the padded size P bounds, and so does whether the
capsule is signed, unless its area was widened; the date, the access policy,
`capsule_id` and the header extensions, the public note among them, do not
(spec §55.2).
- **Safe extraction**: the paths of a head cannot leave the folder, collide
on Windows, macOS or Linux, hide characters or change the direction of the
text; the rules use fixed Unicode 18.0.0 tables, never those of the
platform (spec §29.5, §29.6).
- **No authorship**: the declared author and the comment are text of the
creator that proves nothing, and this version checks no signature and no
seal: its verdicts only say so, and never decide the opening (spec §29.3,
§29.7, §36.1). `time_and_key` adds an access barrier, not a signature.
- **Authorship only by a signature**: the declared author, the comment, the
public note and the modification times are text of the creator that proves
nothing (spec §24.1, §29.7, §36.1). A signature of `alg` 1 proves that
whoever had the secret key signed, not who has it; for a signature with
certificates and a seal, DateKeys checks the cryptography and the dates,
never who issued them or whether they were revoked (spec §29.9 to §29.11).
The verdicts never decide the opening (spec §29.3). `time_and_key` adds an
access barrier, not a signature.
- **Recovery years later** needs the historical release: from a drand relay
that still serves it or from any cache, re-verified locally (spec §50).
@ -242,18 +278,23 @@ go test -tags integration ./capsule ./provider/drand # live Quicknet
- `testdata/vectors`: profile hash, date→round and `dk1_` vectors (spec §65, §66),
CBOR profile and schema vectors, padding vectors (spec §29.1), the paths, the
keys of R7, the heads and the security areas of format 3 (spec §67), the
exported mutation corpus and a differential corpus of the pre-unlock checks;
formats in [`testdata/README.md`](testdata/README.md).
strict Ed25519 of `alg` 1, the signatures with certificates and the seals,
the public note and the extension `datekeys.capsule`, the exported mutation
corpus and a differential corpus of the pre-unlock checks; formats in
[`testdata/README.md`](testdata/README.md).
- `testdata/fixtures`: official `.dkc`/`.dkk` fixtures over published rounds,
with the BLS signature embedded and every intermediate value (spec §67, §68);
they decrypt offline. Nine are of format 3; the seven of format 2, from
v0.9, and the five of format 1, from v0.8.2, are kept for compatibility.
Each `.dkc` has its frozen `datekeys inspect -json` output.
they decrypt offline. Fourteen are of format 3, five of them with the area
of 32 KiB of v0.11: unsigned, signed with `alg` 1, sealed, signed with
certificates, and with a public note. The seven of format 2, from v0.9, and
the five of format 1, from v0.8.2, are kept for compatibility. Each `.dkc`
has its frozen `datekeys inspect -json` output.
- `internal/testkit.Mutations`: the mutations of spec §64, the 33 of its first
two lists in each format, the 23 of the list of format 2 and the 47 of the
list of format 3, and 40 more, each with its exact error and step, or its
verdicts when it opens, and a check that pre-unlock failures never cause a
release request; exported to `testdata/vectors/mutations.json`.
two lists in each format, the 23 of the list of format 2, the 48 of the list
of format 3 and 8 of the list of v0.11, and 40 more, each with its exact
error and step, or its verdicts when it opens, and a check that pre-unlock
failures never cause a release request; exported to
`testdata/vectors/mutations.json`.
- [`docs/traceability.md`](docs/traceability.md): spec section → code → test.
- [`spec/datekeys.cddl`](spec/datekeys.cddl): CBOR schemas.

@ -19,10 +19,14 @@ The module is pre-1.0 (`v0.x`). Only the latest `v0.x` release receives fixes.
## Scope and assumptions
In scope: every rule of the DateKeys Protocol Specification v0.10 this module
In scope: every rule of the DateKeys Protocol Specification v0.11, tagged
`spec-v0.11`, and of the draft v0.12 of the branch `v0.12`, that this module
implements (see `docs/traceability.md`), the CLI, and the handling of
untrusted input (`.dkc`, `.dkk`, relay responses), among it the paths and
texts of a format 3 head, which the CLI writes to disk and to a terminal.
untrusted input (`.dkc`, `.dkk`, relay responses, author key files). Among it:
the paths and texts of a format 3 head, which the CLI writes to disk and to a
terminal; the signatures, certificates and seals of the security area, whose
names reach the lines of the verdicts; the public note; and the locator of a
`.dkk`, with its addresses.
The protocol's own limits, which are not vulnerabilities of this module:
@ -30,9 +34,19 @@ The protocol's own limits, which are not vulnerabilities of this module:
to harvest-now, decrypt-later (spec §7.7, §53).
- Time confidentiality depends on drand's threshold assumption (spec §7.6).
- `time_only` and `time_and_key` do not authenticate the creator (spec §36.1).
In format 3 the declared author, the comment and the modification times are
text of the creator that proves nothing, and this version checks no author
signature and no time seal: its verdicts say so (spec §29.3, §29.7, §55.1).
In format 3 the declared author, the comment, the public note and the
modification times are text of the creator that proves nothing (spec §24.1,
§29.7, §55.1).
- An author signature of `alg` 1 proves that someone with the secret key
signed, not who holds it (spec §29.9, §29.12). For a signature with
certificates (`alg` 2) and for a seal, the module checks the cryptography and
the dates, never who issued a certificate or a seal, or whether it was
revoked: an official validator does (spec §29.10, §29.11). Whoever can open a
capsule can remake its security area, so only a valid seal says that it was
signed before the date (spec §7.9). The verdicts never decide the opening
(spec §29.3).
- A key of words is as strong as its words: after the date, whoever holds the
`.dkc` can try words offline (spec §38.1).
- Opening a mature capsule years later needs the historical release (spec §50).
- A compromised device can copy plaintext or secrets (spec §7.8).
- Go cannot guarantee that secrets are erased from memory. The module wipes
@ -45,13 +59,17 @@ No cryptography is implemented in this module. It depends on:
| Dependency | Role |
|---|---|
| `filippo.io/age` v1.3.2 | age files, X25519, STREAM, header MAC |
| `filippo.io/age` v1.3.2 | age files, X25519, STREAM, header MAC; scrypt for the files of author keys |
| `github.com/drand/tlock` v1.2.0 | `TimeLock`, `TimeUnlock`, ciphertext encoding |
| `github.com/drand/drand/v2` v2.1.7 | BLS verification (`crypto.Scheme`), chain-info hash |
| `github.com/drand/kyber`, `github.com/drand/kyber-bls12381` | BLS12-381 pairing |
| The Go standard library | `crypto/ed25519` (with the strict checks of `internal/ed25519strict`), `crypto/rsa`, `crypto/ecdsa` and SHA-2: author signatures and seals; `crypto/pbkdf2`: the key of words |
Deterministic CBOR (spec §58) is implemented by the module itself, in package
`codec`, without dependencies.
`codec`, without dependencies. So are DER and the reading of CMS signatures,
X.509 certificates and RFC 3161 tokens (spec §29.10, §29.11), in
`internal/der` and `internal/cms`: they read the structures with the profile
of the specification, and the standard library verifies the signatures.
All versions are pinned in `go.mod` and verified through `go.sum`. Changes to
`age`, `tlock`, `drand` or `kyber` are reviewed manually.

@ -395,8 +395,9 @@ func EncodeSecurity() []byte {
}
// EncodeSecurityWith returns SECURITY_CBOR with the given contents of keys 2
// and 3, nil when absent. This version defines no alg and no seal_type: only
// a generator of test vectors writes them (spec §62.1 rule 13).
// and 3, nil when absent (spec §29.3). EncryptFiles writes it with what this
// version defines, a signature of alg 1 or 2 and a seal of seal_type 2; any
// other content only a generator of test vectors writes (spec §62.1 rule 13).
func EncodeSecurityWith(signature, seal []byte) ([]byte, error) {
var e codec.Encoder
(&securityWire{signature: signature, seal: seal}).encode(&e)
@ -466,8 +467,10 @@ func decodeAuthorSignature(b []byte) (*authorSignature, error) {
return &a, nil
}
// EncodeAuthorSignature returns the content of key 2 of security for a
// generator of test vectors: this version defines no alg.
// EncodeAuthorSignature returns the content of key 2 of security, {0: alg,
// 1: key, 2: signature} (spec §29.3). This version defines alg 1, AlgEd25519,
// and alg 2, AlgCMS, whose key 1 holds SIGNERS: EncryptFiles writes them, and
// only a generator of test vectors writes another alg, such as AlgTest.
func EncodeAuthorSignature(alg uint64, key, signature []byte) ([]byte, error) {
var e codec.Encoder
(&authorSignature{alg, key, signature}).encode(&e)
@ -510,8 +513,10 @@ func decodeSeal(b []byte) (*seal, error) {
return &s, nil
}
// EncodeSeal returns the content of key 3 of security for a generator of
// test vectors: this version defines no seal_type.
// EncodeSeal returns the content of key 3 of security, {0: seal_type, 1:
// token} (spec §29.3). This version defines seal_type 2, SealTypeRFC3161,
// which EncryptFiles writes; only a generator of test vectors writes another
// seal_type, such as SealTypeTest.
func EncodeSeal(sealType uint64, token []byte) ([]byte, error) {
var e codec.Encoder
(&seal{sealType, token}).encode(&e)

@ -1,4 +1,4 @@
# Traceability: DateKeys Protocol Specification v0.10 ↔ datekeys-go
# Traceability: DateKeys Protocol Specification v0.12 ↔ datekeys-go
This table maps every normative section of the specification to the code that
implements it and to the tests that exercise it. It is updated in the same
@ -7,7 +7,10 @@ reviewer together with the specification, the fixtures and the mutation corpus
(plan §10).
Paths are relative to the repository root. `§` numbers refer to
`spec/DateKeys_Protocol_Specification_v0.10.md`.
`spec/DateKeys_Protocol_Specification_v0.12.md`, the draft of the branch
`v0.12`, not approved yet; v0.11, tagged `spec-v0.11`, numbers its sections
the same. A case of §64 that is not in the repository yet is marked
*pending*.
## Section map
@ -34,6 +37,7 @@ Paths are relative to the repository root. `§` numbers refer to
| 22 | `.dkc` framing; `VERSION` is the capsule format, 1, 2 or 3, which fixes the schema version of CONTROL_CBOR, the stanzas of INNER_ACCESS_AGE and the padding of the payload; `PUBLIC_HEADER_LEN` in 1..1 MiB, `SEALED_CONTROL_LEN` in 1..64 MiB; PAYLOAD_AGE to EOF, at least an age header | `capsule.Format` (`Format1`, `Format2`, `Format3`), `capsule.Prelude` (`Format`), `capsule.ParsePrelude` | `capsule.TestFormatDispatch`, `TestFormatRelabel`, `TestFrameLengthLowerBounds`, `FuzzParsePrelude`; mutations *version changed* (`VERSION` 4, in the three formats), *flags != 0*, *reserved != 0*, *magic*, length limits, and the relabelings of the lists of formats 2 and 3 of §64 |
| 23 | PRELUDE; order of the checks of steps 1 and 2, the version being the format, 1, 2 or 3; section bytes present at steps 3 and 5 | `Prelude.Bytes`, `capsule.ParsePrelude`, `capsule.Inspect` | `capsule.TestConformanceFixtures`, `TestFrameLengthLowerBounds`, `TestPrecedenceAcrossSteps`, `TestFormatDispatch`; `testdata/vectors/inspect_differential.json` |
| 24 | PUBLIC_HEADER; keys 5 and 6 optional, 1 to 64 extensions each | `capsule.Header`, `EncodeHeader`, `DecodeHeader` (hand-written `headerWire` encode and decode; CDDL checked before the DateKey) | `capsule.TestConformanceFixtures` (exact extension data), `TestDecodeHeaderRejects`, `TestDecodeMapStructure`, `TestDecodeHeaderReportsTheCDDLFirst`, `FuzzDecodeHeader`, `FuzzEncodeImpliesDecode`; mutations *header schema version changed*, *unknown key in PUBLIC_HEADER* |
| 24.1 | Public note: `datekeys.note` version 1, only in the noncritical array of PUBLIC_HEADER; its data the text in UTF-8, 1 to 1024 bytes, under the rules of the declared author of §29.6; a reader that knows it and finds data that breaks them does not show it and says so; shown as text of the creator that nobody has checked, before the date with the warning that nobody can check who made the capsule; bound to the control by `header_binding` (step 15); the writer writes it only when asked, and warns that it is public (§62.1 rule 23) | `extension.NoteID`, `MaxNoteLen`, `CheckNote`, `NewNote`, `Note`, `Standard`; `capsule.Header.PublicNote`, `Header.UnusableNote`; `EncryptOptions.PublicNote` (`newSealer`); `cmd/datekeys`: `encrypt -note` and its warning, `showNote` (inspect), `present` (decrypt), `noteTitle`, `unusableNote`; `internal/inspectview` (`public_note`, `public_note_unusable`) | `capsule.TestPublicNote` (a note changed after writing: `ERR_HEADER_BINDING` at step 15), `TestPublicNoteRules`, `TestRegisteredExtensionsWhereRegistered`; `cmd/datekeys.TestPublicNoteCLI`; `testdata/vectors/note.json` (`internal/testkit.NoteVectors`, `NoteResult`, `TestVectorFilesAreCurrent`); fixture `format3_note`; mutation *the public note changed in PUBLIC_HEADER*; no test yet of the notice of an unusable note in the CLI |
| 25 | Declared access policy | `capsule.Policy`; `capsule.DecodeHeader` (the value read, up to 2^53−1, must be 0 or 1 before any narrowing); `capsule.Open` step 12 | `capsule.TestDecodeMapStructure` (2, 255, 256, 257, 2^32, 2^53−256 and others), `FuzzDecodeHeader` (seeds 256, 257, 2^32); mutations *access_policy=… with … structure* (four cases), *undefined access_policy*, *access_policy 256 / 257 with a consistent header_binding* |
| 26 | Header binding; a mismatch at step 15 is `ERR_HEADER_BINDING` | `capsule.HeaderBinding`; `capsule.Open` step 15 | `capsule.TestConformanceFixtures`; mutation *PUBLIC_HEADER_A + SEALED_CONTROL_B* |
| 27 | Pre-unlock validation; the age header MAC authenticates only against whoever does not know the file key (anyone recomputes that of OUTER_TIME_AGE once the round is published), and `header_binding` gives internal coherence, not authorship or a date (§55.1) | `capsule.Inspect` (steps 1–8), `agewrap.Stanzas` probe | `capsule.TestMutationCorpus` (no release request for any pre-unlock failure), `FuzzInspect`, `TestTrustModel`; the U and stanza body mutations of §64, whose header MAC is recomputed |
@ -41,13 +45,18 @@ Paths are relative to the repository root. `§` numbers refer to
| 28.1 | Age file format: the C2SP header grammar (at least one stanza); malformed OUTER_TIME_AGE and PAYLOAD_AGE headers `ERR_INTEGRITY` (steps 5 and 6, or 11 and 17), a malformed INNER_ACCESS_AGE `ERR_POLICY_STRUCTURE_MISMATCH` (step 12); wrong stanza count or type `ERR_POLICY_STRUCTURE_MISMATCH`; in format 2, a plaintext of PAYLOAD_AGE of a length other than P, or whose padding is not zero, `ERR_INTEGRITY` at step 17 | `agewrap.Stanzas` (the header parser of `filippo.io/age`), `capsule.classify`, `capsule.Open` step 12, `capsule.checkPadding` | `capsule.TestMalformedAgeHeaders`, `TestPaddingChecksAtStep17`, `agewrap.TestStanzasProbe`, `FuzzStanzas`; the 10 header-without-stanzas cases of `testdata/vectors/inspect_differential.json` (5 at step 5, 5 at step 6) |
| 29 | PAYLOAD_AGE; `I_PAYLOAD` from a CSPRNG, new for each capsule and never derived; in formats 2 and 3 its plaintext is the content, BODY in format 3, followed by zeros up to P | `capsule.EncryptFiles` and `capsule.Encrypt`, through the sealer of `capsule/encrypt.go` (`age.GenerateX25519Identity`, `copyExactly`, `writeZeros`); `agewrap.PayloadIdentity`, `agewrap.CheckPayloadStanzas` | `agewrap.TestPayloadIdentityStrictness`; `capsule.TestPayloadIdentityReuse`, `TestEncryptWritesFormat2`; mutation *extra stanza in PAYLOAD_AGE* |
| 29.1 | Padding of the payload in formats 2 and 3: codes 1 (`bloque256`) and 2 (`reforzado`, Padmé), no rule without padding, exact integer arithmetic of more than 32 bits, L_MAX = 2^53 − 2^46; the reader checks that the plaintext is P bytes with a zero padding, and delivers the first L | `capsule.Padding` (`Bloque256`, `Reforzado`), `capsule.PaddedLength`, `capsule.MaxPayloadLength`, `capsule.PayloadAgeLength`; `capsule.Open` steps 16 to 18 (`checkPadding`); `EncryptOptions.Padding`, reforzado by default; `datekeys encrypt -padding` | `capsule.TestPaddingRules`, `TestPaddingChecksAtStep17`, `TestPaddingAcrossChunks`, `TestCheckPadding`, `TestEncryptWritesFormat2`; `testdata/vectors/padding.json`, generated by `internal/testkit.PaddingVectors` and checked against a Padmé in `math/big` (`internal/testkit.TestVectorFilesAreCurrent`); fixtures `format2_time_only`, `format2_time_only_bloque256`, `format2_empty_payload`, `format3_bloque256`; the padding mutations of the lists of formats 2 and 3 of §64 |
| 29.2 | Content of format 3: BODY, a frame of `AREA_LEN`, `SECURITY_LEN` and `HEAD_LEN`, the security area, 512 bytes when written and a multiple of 512 from 512 to 65536 when read, zero after SECURITY_CBOR, then the head and CONTENT; any violation of the frame, L < 12 included, is `ERR_INTEGRITY` at step 17 | `capsule.BodyFrame`, `ParseBodyFrame`, `CheckArea`, `AreaLen`, `MaxAreaLen`, `MaxHeadLen`; `capsule.Open` step 17 (`openBody`); `capsule.EncryptFiles` | `capsule.TestBodyFrame`, `TestOpen3`, `TestOpen3Substeps`; fixtures `format3_*`, `format3_area_1024`; mutations *AREA_LEN …*, *SECURITY_LEN …*, *HEAD_LEN …*, *12 + AREA_LEN + HEAD_LEN = L + 1*, *L < 12: 11*, *a byte of the area not zero* |
| 29.3 | `security`: the outer map of version 1, keys 2 and 3 byte strings that hold the author signature and the seal encoded apart; layers 2 and 3 without a code; it never decides the opening; writers of this version write it empty, 22 bytes | `capsule.EncodeSecurity`, `EncodeSecurityWith`, `EvaluateSecurity`, `EncodeAuthorSignature`, `EncodeSeal` | `capsule.TestSecurityVerdicts`; `testdata/vectors/security.json` (`internal/testkit.SecurityVectors`, `TestFormat3VectorFiles`); fixtures `format3_security_v2`, `format3_signature_unsupported`, `format3_seal_unsupported`; the three mutations of §64 that open with their verdicts |
| 29.2 | Content of format 3: BODY, a frame of `AREA_LEN`, `SECURITY_LEN` and `HEAD_LEN`, the security area, zero after SECURITY_CBOR, then the head and CONTENT; the area of 32768 bytes when written, signed or not, 65536 only when the creator widens it, and any multiple of 512 from 512 to 65536 when read, the 512 bytes of the writers of v0.10 included; any violation of the frame, L < 12 included, is `ERR_INTEGRITY` at step 17 | `capsule.BodyFrame`, `ParseBodyFrame`, `CheckArea`, `AreaUnit`, `AreaLen`, `LargeAreaLen`, `MaxAreaLen`, `MaxHeadLen`; `capsule.Open` step 17 (`openBody`); `capsule.EncryptFiles` (`EncryptOptions.LargeArea`; `EncryptOptions.TestAreaLen`, with `TestVectors`, for the area of a generator of test vectors) | `capsule.TestBodyFrame`, `TestOpen3`, `TestOpen3Substeps`, `TestEncryptFilesSigned` (a signature that fits keeps 32 KiB with `LargeArea`), `TestAreaChosenAfterSigning`; fixtures `format3_*`: 512 bytes in eight of the nine of v0.10, 1024 in `format3_area_1024`, 32768 in `format3_unsigned`, `format3_signed`, `format3_sealed`, `format3_signed_cms` and `format3_note`; mutations *AREA_LEN …*, *SECURITY_LEN …*, *HEAD_LEN …*, *12 + AREA_LEN + HEAD_LEN = L + 1*, *L < 12: 11*, *a byte of the area not zero*, *the area widened to 64 KiB after signing …* |
| 29.3 | `security`: the outer map of version 1, keys 2 and 3 byte strings that hold the author signature and the seal encoded apart; layers 2 and 3 without a code; it never decides the opening, and a failure of the signature or of the seal, a panic of a parser included, changes only its own verdict; `alg` 1 and 2 and `seal_type` 2 defined, `seal_type` 1 and 3 reserved, `alg` and `seal_type` 4294967295 reserved for tests; empty, 22 bytes, without a signature or a seal; no key 3 beside `alg` 2 | `capsule.EncodeSecurity`, `EncodeSecurityWith`, `EncodeAuthorSignature`, `EncodeSeal`, `DecodeAuthorSignature`, `SecurityKey2`, `SecurityKey3`; `EvaluateSecurityIn` (`recovered`, `setSeal`) in a `SecurityContext`, and `EvaluateSecurity`, without one, as a reader of v0.10; `AlgEd25519`, `AlgCMS`, `SealTypeRFC3161`, `AlgTest`, `SealTypeTest` | `capsule.TestSecurityVerdicts`, `TestEvaluateSecurityIn`, `FuzzEvaluateSecurity` (without a context); `testdata/vectors/security.json`, in the context of a capsule, with the lines of each case (`internal/testkit.SecurityVectors`, `SecurityResultIn`, `TestFormat3VectorFiles`, `TestVectorFilesAreCurrent`); fixtures `format3_security_v2`, `format3_signature_unsupported` and `format3_seal_unsupported` (`alg` and `seal_type` 4294967295); the four mutations of the list of format 3 of §64 that open with their verdicts; no test yet of a panic of a parser |
| 29.4 | Head: version 1, a salt of 32 bytes, the comment and the declared author, 1 to 65535 files in the order of R8 with their layout, SHA-256 and mtime up to 9999, at most 16 MiB; layer 2, layer 3 with R1 and R8, then layer 4 in key order, `ERR_HEAD_INVALID`, and the critical extensions | `capsule.Head`, `File`, `EncodeHead`, `DecodeHead` (`decodeHead`, `checkHeadFields`), `CheckHeadEnd`; `extension.Head` | `capsule.TestHeadRoundTrip`, `TestDecodeHeadLayers`, `TestOpen3Substeps`; `testdata/vectors/head_schema.json` (`internal/testkit.HeadSchemaVectors`); the head mutations of the list of format 3 of §64 |
| 29.5 | Paths: R1 and R8 in layer 3; R2 to R6c, R4b and R10 for each entry, then R7 and R9 over the tree, in layer 4; the key of R7; errors that name the rule and the character, never the text | `internal/pathrule` (`CheckPath`, `CheckTree`, `Key`, `NFD`, `Fold`, `Error`) | `pathrule.TestCheckPath`, `TestCheckTree`, `TestKey`, `TestNFD`; `testdata/vectors/paths.json` and `path_fold.json` (`internal/testkit.PathVectors`, `PathFoldVectors`, `TestFormat3VectorFiles`); the path mutations of §64 |
| 29.5.1 | Tables: Unicode 18.0.0 and the 15 WindowsBestFit tables, pinned by their SHA-256, never the Unicode functions of the platform | `internal/pathrule/gen`, which checks the 19 pinned files in `.cache` and writes `tables.go`; `pathrule.UnicodeVersion`, `TablesDigest` | `pathrule.TestTablesDigest`, `TestProperties`; NFD and folding compared with `golang.org/x/text` outside this module |
| 29.6 | Text of the comment and of the declared author: no control but TAB and LF in the comment, no bidirectional control, separator, byte order mark or noncharacter, the invisibles rule with R4b for each line, no space at the ends of the author; the writer turns CR LF and a lone CR into LF | `pathrule.CheckComment`, `CheckAuthor`; `capsule.EncryptFiles` (`newHead`) | `pathrule.TestTexts`; `testdata/vectors/head_schema.json`; `capsule.TestEncryptFilesRoundTrip`, `TestEncryptFilesRejects` |
| 29.7 | Verdicts X, F0, F1, S0, S1 and S2 with the texts of the table; the presentation: the verdicts first and last, the labels of the text of the creator, TABs expanded, pieces of at most W − 3 columns behind `│ ` with the width counted by excess, and warnings of risky names | `capsule.Verdict`, `Verdicts.Lines`; `cmd/datekeys.present` (`writeCreator`, `pieces`, `expandTabs`, `outputWidth`, `termWidth`, `risks`) | `capsule.TestSecurityVerdicts`; `cmd/datekeys.TestPresent`, `TestEncryptDecryptRoundTrip`, `TestDecryptFormat3Fixtures` |
| 29.7 | Verdicts X, F0 to F6 and S0 to S5, for each part the first row of the table that holds, with the texts of the table; the name of a certificate between « and », shown when it meets the rules of the declared author, has at most 64 code points and no two spaces in a row, and its SHA-256 otherwise; the holder by `givenName` and `surname` before `commonName`, the issuer by `commonName` or `organizationName`; after F6, a line for each required signer with the authority of its seal, and «DateKeys no comprueba quién emitió los sellos.» when one says before the date; a foreign signer apart, with its result in Spanish; before the date only by a valid seal with t + accuracy < round_time, with its warning; an mtime later than a valid seal shown as an inconsistency (SHOULD); the presentation: the verdicts first and last, the labels of the text of the creator, TABs expanded, pieces of at most W − 3 columns behind `│ ` with the width counted by excess, the lines of the verdicts in rows of at most W − 3 columns, broken at the last space that fits, each row after the first behind ` ↳ `, and warnings of risky names | `capsule.Verdict`, `Verdict.Text`, `Verdicts.Lines`, `Verdicts.SealedAt`, `Detail`, `SignerLine` (`quoted`, `instant`, `resultText`), `holderText`, `MaxNameLen`; `internal/cms` `Cert.Holder`, `Cert.IssuerName`; `capsule.Open` step 17.6 (`newSecurityContext`, `openBody`), `OpenOptions.AuthorKeys` (F3), `OpenOptions.Accept` (the verdicts before step 18); `cmd/datekeys.present` (`writeVerdicts`, `rows`, `contMark`, `writeCreator`, `pieces`, `expandTabs`, `outputWidth`, `termWidth`, `risks`) | `capsule.TestSecurityVerdicts`, `TestEvaluateSecurityIn` (the lines of F3 and F4), `TestEvaluateCMS` (the lines of F6 with the authority of each seal and the warning, a late seal without it, a foreign signer), `TestEvaluateSeal` (the line of S4), `TestIssuerTextFiltered`, `TestCMSVectors`; the lines of the records of the fixtures (`capsule.TestConformanceFixtures`) and of `testdata/vectors/security.json` (`internal/testkit.TestFormat3VectorFiles`); `cmd/datekeys.TestRows`, `TestPresent`, `TestMTimeAfterSeal`, `TestAuthorSignRoundTrip`, `TestEncryptDecryptRoundTrip`, `TestDecryptFormat3Fixtures`; the names of §64 of v0.12 (two spaces, more than 64 code points, ESC, U+202E, a byte order mark, `givenName` and `surname` with a NIF in `commonName`, an issuer without text): `security_cms.json`, *pending* |
| 29.8 | Author signature, what is signed: `payload_commit`; `CONTROL_SIG`, the control with `payload_commit` in place of `I_PAYLOAD` and L at zero; `control_commit`, `head_digest`, `signers_digest`, and `AUTHOR_MESSAGE`, ASCII of 99 bytes, with its code of 8 characters; never the area or the padding, so the area can grow after signing; every value recomputed from the opened capsule | `capsule.PayloadCommit`, `ControlCommit`, `HeadDigest`, `SignersDigest`, `AuthorMessage`, `AuthorMessagePrefix`, `AuthorMessageSize`, `AuthorCode`; `capsule.Open` step 17.6 (`newSecurityContext`); the writer signs once the control is final, in the `prepare` that `EncryptFiles` gives `sealer.write` (`sealer.security`) | `capsule.TestAuthorMessage` (99 bytes, the code, `control_commit` without L and with `I_PAYLOAD`), `TestSignedFixtureVerdicts` (another control or head: F2), `TestAreaChosenAfterSigning` (signed once); `TestConformanceFixtures` (`checkSignature3`: the commitments, `AUTHOR_MESSAGE` and its code in the records of `format3_signed`, `format3_signed_cms` and `format3_sealed`); mutations *the signature of alg 1 transplanted to another capsule …*, *the area widened to 64 KiB after signing …* |
| 29.9 | Signature with a key of one's own, `alg` 1: Ed25519 of `AUTHOR_MESSAGE`; a key or a signature of another length is F1; valid only with A canonical and not of small order, `sig[63] & 0xE0` = 0, S < ℓ and the equation without the cofactor, F2 otherwise; F4, or F3 with a key the person saved | `internal/ed25519strict` (`Verify`, `Canonical`, `SmallOrder`, `SmallOrderPoints`, `OnCurve`), around `crypto/ed25519`; `capsule.evaluateSignature`; `EncryptOptions.AuthorKey`, the interface `capsule.AuthorKey` | `ed25519strict.TestVectors`, `TestVerifyRejectsWhatStdlibAccepts`, `TestCanonical`, `TestSmallOrderTable`, `TestOnCurve`; `testdata/vectors/ed25519_strict.json`, the cases of «Taming the many EdDSAs» (`internal/testkit.Ed25519StrictVectors`); `capsule.TestEvaluateSecurityIn`, `TestEncryptFilesSigned`, `TestEncryptFilesSignatureChecked`, `TestSignedFixtureVerdicts`; fixture `format3_signed`, whose signature `TestConformanceFixtures` makes again from its seed; mutations *a signature of alg 1 that does not verify …* and those of `alg` 1 of the list of v0.11: altered, removed, made again with another key, transplanted, a key of 31 bytes and a signature of 65 |
| 29.10 | Signature with certificates, `alg` 2: a detached CMS signature with the CAdES profile; `SIGNERS`, 1 to 16 SHA-256 of certificates in strictly ascending order; the form in its order (F1), with the version of a `SignerInfo` by its `sid`, attributes counted by attribute, a `signing-certificate` beside the v2 that decides nothing, an `ESSCertIDv2` with SHA-256 written, the parameters of PSS, object identifiers compared by the bytes of their DER and a SET OF that repeats an element; the closed table of algorithms; the profile of the certificate field by field, its key RSA with NULL parameters and an odd modulus, or EC uncompressed on P-256, P-384 or P-521; the result of each required signer in its order: absent, not verifiable, invalid (a key of another scheme than the algorithm included), without seal, invalid seal, out of validity, valid; F2, F5 or F6; no key 3; never who issued a certificate or whether it was revoked | `internal/der` (`Check`, `Split`, `Content`, `SetOfSorted`, `ParseTime`); `internal/cms` (`ParseSignature`, `SignedData`, `SignerInfo`, `SignerInfo.Check`, `ParseCert`, `Cert`, `Cert.ValidAt`, `ErrForm`, `ErrAlgorithm`), with the standard library only; `capsule.EncodeSigners`, `decodeSigners`, `MaxSigners`, `evaluateCMS`, `signerLine`; `EncryptOptions.CMSSigner`, the interface `capsule.CMSSigner`; `internal/cms/cmstest`, which makes certificates, signatures and tokens for the tests | `cms.TestSignatureAlgorithms` (RSA PKCS #1 v1.5 and PSS, SHA-256 to SHA-512, ECDSA on P-256, P-384 and P-521, a `sid` by `subjectKeyIdentifier`), `TestCoSignature`, `TestSignatureNotVerifiable`, `TestSignatureForm`, `TestSignatureStrictness`; `der.TestCheck`, `TestSetOfSorted`, `TestDepth`, `TestParseTime`, `TestSplit`; `capsule.TestEvaluateCMS` (a required signer absent, without seal, a key 3 beside it, another head, `SIGNERS` out of order or empty), `TestEncodeSigners`, `TestEncryptFilesCMSAndSeal`, `TestCMSVectors` (`testdata/vectors/security_cms.json`); fixture `format3_signed_cms`; the cases of `alg` 2 of §64 of v0.12 (a certificate out of validity with a valid TSA, the profile of the certificate, identifiers, repetitions, keys of another scheme or compressed): `security_cms.json`, *pending* |
| 29.11 | Time seal, RFC 3161: the CAdES-T of each signer of `alg` 2, over its signature value, or `seal_type` 2 in key 3, over `SEAL_SUBJECT`, without a signature or with `alg` 1; `SIG_PART`; the profile of the token in its order: the form (S2), with the `TSTInfo` in DER field by field, `genTime` in UTC with Z, `accuracy` of 0 to 2³¹ − 1 seconds and minimal millis and micros, `ordering` only TRUE, nothing after the last field, and `crls` deciding nothing; the algorithms (S1); the verification (S3), a `messageImprint` of another length included; S4 when t + accuracy < round_time, S5 otherwise | `internal/cms` (`ParseToken`, `Token`, `Token.Check`, `Token.ImprintIsSHA256`, `parseTSTInfo`, `parseAccuracy`); `capsule.SigPart`, `SealSubject`, `SealTypeRFC3161`, `evaluateSeal`, `signerLine`; `EncryptOptions.Sealer`, the interface `capsule.Sealer` | `cms.TestTokenOverSignature`, `TestTokenFailures`, `TestTSTInfoStrict` (a negative accuracy, millis of 0, a `genTime` with an offset or a trailing zero, `ordering` FALSE written, a field after the last); `capsule.TestEvaluateSeal`, `TestEncryptFilesCMSAndSeal`, `TestCMSVectors`; fixture `format3_sealed`; the cases of `seal_type` 2 of §64 of v0.12: `security_cms.json`, *pending* |
| 29.12 | Author keys of `alg` 1: a seed of Ed25519; the public key `dkauthor1…`, 67 characters in lower case, and the secret key `DKAUTHOR-SECRET-KEY-1…`, 79 in upper case, refused in another case, length or prefix, or with padding bits that are not zero; a public key canonical, a point of the curve and not of small order; a file of a secret key encrypted with age and a passphrase, scrypt of logN 16; a key that the person saved gives F3 with her label | `authorkey` (`Generate`, `NewFromSeed`, `Key.Public`, `Key.Sign`, `Key.Secret`, `Key.String` and `Key.GoString`, which hide the secret key, `Key.Clear`, `PublicString`, `ParsePublic`, `ParseSecret`, `Marshal`, `Encrypt`, `Read`, `WorkFactor`); `capsule.OpenOptions.AuthorKeys`; `cmd/datekeys`: `author keygen` and `author public` (`authorKeygen`, `authorPublic`, `loadAuthorKey`, `readPass`: the passphrase from a file or the standard input), `encrypt -sign` (`announced`), `decrypt -expect-author` | `authorkey.TestStrings` (a printed key never shows its secret), `TestParseRejects` (y = 2, off the curve), `TestFiles`; `ed25519strict.TestOnCurve`; `capsule.TestEncryptFilesSigned` (F3 with the key saved); `cmd/datekeys.TestAuthorSignRoundTrip` |
| 30 | PAYLOAD_AGE is a complete age file | `filippo.io/age` public API only | `capsule.TestInteropAgeOpensPayload` (`-tags interop`, official `age` CLI) |
| 30.1 | CONTROL_CBOR ↔ PAYLOAD_AGE binding; in format 2, L and the code fix the length and the padding of the plaintext, which adds determinism, not authenticity | `agewrap.PayloadIdentity` | mutations *SEALED_CONTROL_A + PAYLOAD_AGE_B*, *padding code 2 changed to 1, with L = 78000*, *payload_length L - 1, the last byte of the content not zero*; `capsule.TestTrustModel` (another L of the same P); `agewrap.TestPayloadIdentityStrictness` |
| 31 | CONTROL_CBOR; its schema version is the format of its capsule, another being `ERR_UNSUPPORTED_VERSION` at step 14; keys 4 and 5 optional; keys 6, `payload_length` (8 bytes, at most L_MAX), and 7, `padding` (1 or 2), required in versions 2 and 3 and not defined in version 1, `ERR_NON_CANONICAL_CBOR`; a control of version 2 or 3 without extensions is 103 bytes; extension entry rules (`extension_id` valid UTF-8 of at least 1 byte), elements in strictly ascending bytewise order of `extension_id` (§54) | `capsule.Control` (`PayloadLength`, `Padding`), `EncodeControl` and `DecodeControl`, which take the format (hand-written `controlWire` encode and decode); `extension` | `capsule.TestConformanceFixtures` (exact extension data), `TestDecodeControlRejects`, `TestControlLengthIsConstant`, `TestDecodeMapStructure`, `FuzzDecodeControl` (the three formats), `FuzzEncodeImpliesDecode`; the `control_cbor` block of `testdata/vectors/cbor.json`, with `format`; mutations *unknown critical CONTROL_CBOR extension*, and those of keys 6 and 7 in the third list of §64 |
@ -59,12 +68,14 @@ Paths are relative to the repository root. `§` numbers refer to
| 36.1 | Authenticity semantics | documented in `README.md`, `SECURITY.md` | — (a property the protocol does not provide) |
| 37 | X25519 recipient V1; the writer rejects a recipient that is not canonical (bit 255 set, or u ≥ p) or of low order, and MAY reject a point of the twist | `age.X25519Recipient`; `agewrap.X25519IdentityFromRaw`, `agewrap.CheckX25519Recipient` (run by `capsule.Encrypt`); the twist check is not implemented | `agewrap.TestRawKeys`, `TestNonCanonicalRecipients`; `capsule.TestEncryptRejectsInvalidOptions` |
| 38 | Portable Access Key | `EncryptOptions.NewPortableKey` (fresh `I_ACCESS` per capsule; no API accepts an existing one); `accesskey.AccessKey` | `capsule.TestPortableKeysAreNeverReused` |
| 38.1 | Key of words: one more X25519 credential of `time_and_key`, among the 16; the normalization: NFD with the tables of Unicode 18.0.0, without U+0300 to U+036F, the simple lower case of each code point, split by the spaces of the list; PBKDF2-HMAC-SHA256 of 600 000 rounds, salted with the chain hash, the round and `capsule_id`, into a raw X25519 identity; the writer requires at least 6 words, counting only different words of 3 or more letters, and refuses controls, ignorables and unassigned code points; a reader may ask for the words instead of a `.dkk` | `wordkey` (`Normalize`, `Check`, `Key`, `Identity`, `Rounds`, `MinWords`, `MinLetters`), with `pathrule.NFD`, `Lower`, `DefaultIgnorable` and `Assigned`; `capsule.EncryptOptions.Words` (`accessRecipients`; `sealer.write` derives the identity once `capsule_id` is drawn); `cmd/datekeys`: `-words` and `-words-file` of `encrypt` and `decrypt` (`wordsText`), the words of `decrypt` salted with what `capsule.Inspect` gives | `wordkey.TestNormalize`, `TestCheck`, `TestKeyVector` (the vector of §38.1); `capsule.TestEncryptFilesWords` (the words of another `capsule_id` do not open); `cmd/datekeys.TestKeyOfWords` |
| 39 | Recipients of INNER_ACCESS_AGE: in formats 2 and 3 from 1 to 16 credentials, a dummy in each slot left (a fresh public key whose private key is dropped at once), the 16 in a uniformly random order; which slots are dummies is recorded only in the official vectors | `capsule/encrypt.go` (`accessRecipients`, `fillSlots`, `permute`); `agewrap.AccessIdentity` | `capsule.TestInnerHasSixteenStanzas`, `TestDummyRecipients`, `TestStanzaOrderIsUniform`, `TestCredentialBounds`, `TestFixtureRecipients`, `TestEncryptRoundTripBothPolicies`; `TestConformanceFixtures` (the stanza each credential opens, `access_key_stanza` and `identity_stanzas` in the records) |
| 40 | `.dkk` framing; `BODY_LEN` in 1..16 MiB (0 is `ERR_INTEGRITY`); order of the frame checks | `accesskey.Encode`, `accesskey.Decode` (the body buffer grows with the data read; every buffer holding the body is wiped) | `accesskey.TestDecodeRejects`, `TestDecodePrecedence`, `TestDecodeShortBodyAllocatesLittle`, `TestEncodeAndDecodeLeaveNoStaleMaterial`, `FuzzDecode` |
| 41 | `.dkk` BODY_CBOR | `AccessKey.MarshalBody`, `accesskey.DecodeBody` (hand-written `bodyWire` encode and decode) | `accesskey.TestFixtures`, `TestDecodeBodyStructure` |
| 42 | `credential_id` | `capsule.Encrypt` (16 bytes from `crypto/rand`) | `capsule.TestPortableKeysAreNeverReused` |
| 43 | `verification_metadata` | `accesskey.Verification`, `decodeVerification` (the closed map `{0: capsule_digest}`); `capsule.Open` (`checkCapsuleDigest`, seekable readers) | `accesskey.TestDecodeRejects` *empty verification map*, `TestDecodeBodyStructure`; mutation *capsule_digest of the .dkk does not match* |
| 44 | Application extensions in `.dkk` | `AccessKey.Critical/Noncritical`; `capsule.Open` (`checkAccessKey`, `Opened.UnusableAccessKeyExtensions`) | `accesskey.TestEncodeRejectsAbsenceAsEmptyMap`, `TestDecodeBodyExtensionRules`, `TestFixtureWithExtension`; `capsule.TestAccessKeyFixtureWithExtension`; mutation *known critical .dkk extension with invalid data* |
| 44.1 | The extension `datekeys.capsule` of a `.dkk`, noncritical: the note, the DateKey and an optional locator, an age file with one tlock stanza for the round of that DateKey, unusable for another round or chain; its plaintext, 1 to 8 addresses, `I_SOBRE`, the header of the envelope, the digest and the size of the rest, `capsule_digest` and a zero padding of at least one byte, of exactly 4096 bytes or the least multiple that holds it; the envelope, the `.dkc` in age split into the header and a rest without a mark, alone or inside a host at an offset; the addresses, ASCII of RFC 3986, read without decoding: `https` with a host of letters, digits and hyphens or a public IP outside the special-purpose blocks of IANA, no local name, no dot segment, or `ipfs` with a CID v1 in base32; a reader rejects each address that breaks them and uses the others; the rest and the `.dkc` checked by their digests; a writer never writes a rejected address and decodes what it writes | `locator` (`Info`, `Info.Extension`, `ParseInfo`, `Info.OpenLocator`, `Standard`; `Locator`, `Locator.Marshal`, `Unmarshal`, `Locator.Usable`, `PlaintextLength`, `Block`, `MaxAddresses`, `MaxURILen`, `MaxHeaderLen`; `Seal`, `Open`; `NewEnvelope`, `Locator.OpenEnvelope`, `Hide`, `Locator.RestIn`; `Address`, `Address.Host`, `CheckURI`), which downloads nothing; `extension.CapsuleID`, `extension.Standard` (`ValidateCapsule`); `accesskey.AccessKey.MarshalBody` (`extension.CheckWrite`); `spec/datekeys.cddl` (`capsule-locator`, `capsule-address`) | `locator.TestEnvelope`, `TestLocatorPlaintext`, `TestAddresses` (the blocks of IANA, NAT64, mapped and 6to4 addresses, local names, characters outside RFC 3986, dot segments, CIDs that do not decode), `TestSealedLocator` (another round or release: unusable), `TestInfo`, `TestUsableAddresses`, `TestLeastMultiple`, `TestPaddingBoundaries`, `TestLocatorVectors` (`testdata/vectors/locator.json`); `capsule.TestRegisteredExtensionsWhereRegistered` (never in a capsule); the cases of §64 of v0.11 and v0.12 for `datekeys.capsule` that `locator.json` lacks: *pending* |
| 45 | Release API | `provider.ReleaseSource` interface only (server out of scope, plan §2) | — |
| 46 | Release Queue | out of scope (server) | — |
| 47 | Release Cache | every release is verified again: `capsule.Open` step 10 and `agewrap.TimeIdentity` | mutations *release of another round* |
@ -86,21 +97,21 @@ Paths are relative to the repository root. `§` numbers refer to
| 60 | Conceptual Go interfaces | `provider.ReleaseSource`, `provider.Verify`, `datekey.Resolve`, `datekey.RoundTime` | — |
| 61 | `time_only` encryption flow, format 3: the files measured, hashed, sealed and read again | `capsule.EncryptFiles` | `capsule.TestEncryptFilesRoundTrip`, `TestEncryptFilesLengths`, `TestEncryptFilesChangedFile`, `TestSealedControlLength`; the live test (`-tags integration`) |
| 62 | `time_and_key` encryption flow, format 3: 16 recipients, and the SEALED_CONTROL_LEN of an INNER_ACCESS_AGE of 16 stanzas | `capsule.EncryptFiles` | `capsule.TestEncryptFilesTimeAndKey`, `TestEncryptRoundTripBothPolicies`, `TestPortableKeysAreNeverReused`, `TestSealedControlLength` |
| 62.1 | Writer rules: format 3 only, formats 1 and 2 being written only by a generator of test vectors; an instant after the clock of the writer; from 1 to 16 credentials, none twice, canonical and not of low order; dummies and a random order; `capsule_id`, `I_PAYLOAD`, `I_ACCESS`, `credential_id`, dummies and order from a CSPRNG, `I_PAYLOAD` and dummies never reused or derived; L known before sealing, at most L_MAX, code 1 or 2; SEALED_CONTROL_LEN exact, measured by a provisional seal and checked; limits; on error, the output is discarded; in format 3, the area of 512 bytes with the empty security, a head with a fresh salt, a file or a comment, the order of R8, the layout and the SHA-256 of what is written, at most 16 MiB, paths and texts refused with the rule and the character, the mtime taken at load and omitted out of range, the three CBOR objects decoded with the rules of the reader (MUST), and files that must not change between the two readings. SHOULD: code 2 by default, self-checks, wiping | `capsule.EncryptFiles` (`newHead`, `readSource`, `selfCheckHead`) and `capsule.Encrypt`, through their sealer (`EncryptOptions.Padding`, `accessRecipients`, `fillSlots`, `copyExactly`, `selfCheckHeader`, `selfCheckControl`, `selfCheckInner`, `selfCheckPayload`); `agewrap.CheckX25519Recipient`; `EncryptOptions.TestVectors` for `Encrypt` of format 2, and `internal/testkit.Build`, generators of test vectors (§70) | `capsule.TestEncryptFilesRejects`, `TestEncryptFilesChangedFile`, `TestEncryptIsForTestVectors`, `TestEncryptFilesHeadCritical`, `TestEncryptRejectsInvalidOptions`, `TestCredentialBounds`, `TestEncryptSourceLength`, `TestEncryptSelfCheck`, `TestSealedControlLength`, `TestPayloadIdentityReuse`, `TestStanzaOrderIsUniform`, `TestDummyRecipients`, `TestPortableKeysAreNeverReused`; `cmd/datekeys.TestEncryptRefusesPaths` |
| 62.1 | Writer rules: format 3 only, formats 1 and 2 being written only by a generator of test vectors; an instant after the clock of the writer; from 1 to 16 credentials, none twice, canonical and not of low order; dummies and a random order; `capsule_id`, `I_PAYLOAD`, `I_ACCESS`, `credential_id`, dummies and order from a CSPRNG, `I_PAYLOAD` and dummies never reused or derived; L known before sealing, at most L_MAX, code 1 or 2; SEALED_CONTROL_LEN exact, measured by a provisional seal and checked; limits; on error, the output is discarded; in format 3, the area of 32768 bytes, signed or not, and 65536 only when the creator widens it once the signatures are made, without signing again for it, a capsule refused when they do not fit, `SECURITY_CBOR` always and empty without a signature or a seal, another area or `SECURITY_CBOR` only from a generator of test vectors (rule 13), a head with a fresh salt, a file or a comment, the order of R8, the layout and the SHA-256 of what is written, at most 16 MiB, paths and texts refused with the rule and the character, the mtime taken at load and omitted out of range, the three CBOR objects decoded with the rules of the reader (MUST), and files that must not change between the two readings; with a signature or a seal, each verified with the rules of the reader before writing, never one that gives F1, F2, F5, S1, S2 or S3 (rule 19), `AUTHOR_MESSAGE` given as text and its code shown before each signature, `SIGNERS` closed before the first (rule 20), a CAdES-T for each signer of `alg` 2 (rule 21), the seal of `seal_type` 2 over `SEAL_SUBJECT` after the signature (rule 22), and no secret on disk while waiting for them (rule 25); the public note only when asked for, with the rules of the declared author and a warning (rule 23); the rules of §38.1 for a key of words, and for a `.dkk` with a locator the rest stored before the `.dkk` is written (rule 24). SHOULD: code 2 by default, self-checks, wiping | `capsule.EncryptFiles` (`newHead`, `readSource`, `selfCheckHead`) and `capsule.Encrypt`, through their sealer (`EncryptOptions.Padding`, `accessRecipients`, `fillSlots`, `copyExactly`, `selfCheckHeader`, `selfCheckControl`, `selfCheckInner`, `selfCheckPayload`); `agewrap.CheckX25519Recipient`; `EncryptOptions.TestVectors` for `Encrypt` of format 2, and `internal/testkit.Build`, generators of test vectors (§70); in format 3, `capsule.AreaLen`, `LargeAreaLen` and `EncryptOptions.LargeArea`, the area decided by `EncryptFiles`, in the `prepare` that it gives `sealer.write`, once `sealer.security` has signed and sealed and checked both with `EvaluateSecurityIn` (rules 13, 19, 21 and 22), `EncryptOptions.AuthorKey`, `CMSSigner` and `Sealer`, a typed nil in one of them an error (`newSealer`, `isNil`), nothing written to `dst` before they return and the control and `I_PAYLOAD` kept in memory (rule 25); `EncryptOptions.TestAreaLen`, with `TestVectors`, the area of 512 bytes of the fixtures of v0.10 (rule 13); `EncryptOptions.PublicNote` and `extension.CheckWrite` (rule 23, §72); `EncryptOptions.Words` and `wordkey.Check`, and `locator.NewEnvelope`, `Locator.Marshal` and `Info.Extension` (rule 24); `cmd/datekeys`: `announced` (rule 20) and the warning of `-note` (rule 23) | `capsule.TestEncryptFilesRejects`, `TestEncryptFilesChangedFile`, `TestEncryptIsForTestVectors`, `TestEncryptFilesHeadCritical`, `TestEncryptRejectsInvalidOptions`, `TestCredentialBounds`, `TestEncryptSourceLength`, `TestEncryptSelfCheck`, `TestSealedControlLength`, `TestPayloadIdentityReuse`, `TestStanzaOrderIsUniform`, `TestDummyRecipients`, `TestPortableKeysAreNeverReused`; `cmd/datekeys.TestEncryptRefusesPaths`; rules 13 and 19 to 25: `capsule.TestEncryptFilesSigned`, `TestEncryptFilesSignatureChecked` (nothing written), `TestEncryptFilesCMSAndSeal` (a signature without a required signer, or without seals, is not written), `TestAreaChosenAfterSigning` (the area widened once signed, signing once; without a signature, 32 KiB with `LargeArea`), `TestWriterOptionsChecked`, `TestPublicNoteRules`, `TestRegisteredExtensionsWhereRegistered`, `TestEncryptFilesWords`; `wordkey.TestCheck`; `locator.TestUsableAddresses`, `TestInfo`; `cmd/datekeys.TestAuthorSignRoundTrip` (the key and the code before the signature), `TestPublicNoteCLI`, `TestKeyOfWords`; rule 25 has no test of its own |
| 63 | Decryption flow; step 2 accepts the formats 1, 2 and 3, and the steps after it apply the rules of the format: in formats 2 and 3, 16 stanzas at step 12, a control of schema version 2 at step 14, L, the code and P at step 16, a plaintext of P bytes with a zero padding at step 17 (`ERR_INTEGRITY` whenever it is found), the first L bytes at step 18; in format 3, step 17 in its substeps, a failure of age or a plaintext whose length is not P prevailing and a code other than `ERR_INTEGRITY` reported only after reading to EOF, and a caller without a `Sink` stopped right after step 2; steps 4 and 14 validate critical extensions (unknown, then invalid data); step 5 reads SEALED_CONTROL, a MUST (`ERR_INTEGRITY`), and SHOULD inspect its age header; step 8 argument rules; step 9 order: the `.dkk` as an object (decoded there when still encoded), its `capsule_id` and `capsule_digest`, credentials (nil identities are none) before the clock, round time, request, and nothing of the credentials under `time_only`; a network source verifies each response with the rules of step 10 and discards the invalid ones (none valid: `ERR_RELEASE_UNAVAILABLE`, step 9), and any failure of a source is `ERR_RELEASE_UNAVAILABLE` alone, whatever code its error carries; step 10: round, then signature, a canonical point other than the identity (§12.2), the codes of a release supplied directly; step 11: the tlock stanza body `U \|\| V \|\| W` of \|U\| + 32 bytes (128 in Quicknet), U canonical and not the identity, the IBE check r·G == U, every failure `ERR_INTEGRITY`, H2, H3 and H4 those of drand/kyber `encrypt/ibe`, H2 over the element of GT serialized in the order of kilic/bls12-381 (c1 before c0 at every level of the tower), with the frozen vector H2(e(G1, G2)) = `cb87319f24560b5231579a09ad79f12e`; the codes of the identities at steps 11, 13 (malformed X25519 stanza `ERR_INTEGRITY`, an identity that unwraps two stanzas `ERR_POLICY_STRUCTURE_MISMATCH` whatever the order, none `ERR_ACCESS_INVALID`) and 17; step 15 `ERR_HEADER_BINDING` | `capsule.Inspect` (steps 1–8), `capsule.Open` (steps 9–18; `openBody`, `drain`, `ErrSinkRequired`; `OpenOptions.AccessKeyFile`, `checkAccessKey`, `checkCapsuleDigest`), the `provider.ReleaseSource` contract, `provider/drand.Client` and `capsule.sourceFailure` (step 9), `tlock.TimeUnlock` with the kyber-bls12381 pairing (step 11), MUST rules inside `agewrap` identities (`AccessIdentity` tries every identity on every stanza; `TimeIdentity` checks the length of the tlock stanza body and U before `tlock.TimeUnlock`); no error copies the text of an error of age, tlock, kyber or drand (`agewrap`, `capsule.classify`), since kyber's IBE error carries the candidate plaintext and r; `cmd/datekeys` hands the `.dkk` over encoded; `datekeys inspect -json` rendered by `internal/inspectview` | `capsule.TestConformanceFixtures` (stage by stage), `TestOpen3`, `TestOpen3Substeps`, `TestFormatDispatch`, `TestFormatRelabel`, `TestPaddingChecksAtStep17`, `TestTlockFailureDiagnosticsCarryNoSecrets`, `TestPlaintextWriterFailureKeepsItsText`, `TestAccessKeyCheckOrder`, `TestAccessKeyFileAtStep9`, `TestPrecedenceAcrossSteps`, `TestControlCriticalBeforeHeaderBinding`, `TestReleaseFromANetworkSource`, `TestReleaseSourceErrorsAtStep9`, `agewrap.TestAccessIdentityStrictness`, `TestMalformedX25519Stanzas`, `TestTlockH2Vector` (`testdata/vectors/tlock_ibe.json`, generated by `internal/testkit.IBEVectors`, and step 11 recomputed with H2 and H4 against the file key tlock unwraps), `cmd/datekeys.TestDecryptAccessKeyOrder`, `TestMutationCorpus`, `TestInspectDifferentialCorpus` (`testdata/vectors/inspect_differential.json`: 5110 deterministic mutations of 14 fixtures, two of them of format 3, the 1825 of the format 1 ones first with the verdict of steps 1–8, generated by `internal/testkit.InspectDifferential`); `cmd/datekeys.TestInspectJSONGoldens` (`testdata/fixtures/*.inspect.json`) |
| 64 | Mandatory mutation tests: the first two lists in the three formats, the list of format 2 in format 2, and that of format 3, three of whose cases open with their verdicts | `internal/testkit.Mutations` (the corpus: `specMutations` for each format, `furtherMutations`, `format2Mutations`, `format3Mutations` with `LoadedFixture.WithBody`), `internal/testkit.MutationCorpus` (its export); the cases of formats 2 and 3 derived without randomness, by sealing the fixtures again with their known file keys and nonces (`internal/testkit/reseal.go`, `mutations3.go`), exported as edits of their fixture (`internal/testkit.Splice`) | `capsule.TestMutationCorpus`: the 169 listed mutations, 33 in each format, the 23 of the list of format 2 and the 47 of that of format 3, plus 40 more, built afresh; `capsule.TestExportedMutationCorpus`: `testdata/vectors/mutations.json`, the same 209 cases as frozen data (capsule, `.dkk`, identities, recorded release, clock, registry, known extensions), replayed with the recorded error and step, or the recorded verdicts; `capsule.TestPointMutationsChangeOnlyTheEncoding`: the ten point mutations keep a valid header MAC, and a decoder that reduces coordinates modulo p opens the c0 + p and x + p cases; `internal/testkit.TestResealReproducesFixtures`, `TestFixedX25519Stanza` |
| 64 | Mandatory mutation tests: the first two lists in the three formats, the list of format 2 in format 2, and that of format 3, four of whose cases open with their verdicts, F2 for a signature of `alg` 1 that does not verify among them; the lists of v0.11 and v0.12: the signature of `alg` 1 and of `alg` 2, the area widened after signing, the seal, `alg` and `seal_type` 4294967295, the same P with a signature and without, the public note, the key of words and `datekeys.capsule` | `internal/testkit.Mutations` (the corpus: `specMutations` for each format, `furtherMutations`, `format2Mutations`, `format3Mutations` with `LoadedFixture.WithBody`, among them those of the list of v0.11 from `format3_signed`, `format3_unsigned` and `format3_note`, `signed1`), `internal/testkit.MutationCorpus` (its export); the cases of formats 2 and 3 derived without randomness, by sealing the fixtures again with their known file keys and nonces (`internal/testkit/reseal.go`, `mutations3.go`), exported as edits of their fixture (`internal/testkit.Splice`); the cases of `alg` 2, `seal_type` 2 and `datekeys.capsule`, vectors of `security_cms.json` and `locator.json`, frozen once written (`internal/testkit/genfixtures`, `frozenVectors`) | `capsule.TestMutationCorpus`: the 178 listed mutations, 33 in each format, the 23 of the list of format 2, the 48 of that of format 3 and 8 of that of v0.11 (`internal/testkit.SpecMutationsPerFormat`, `Format2SpecMutations`, `Format3SpecMutations`, `V011SpecMutations`), plus 40 more, built afresh; `capsule.TestExportedMutationCorpus`: `testdata/vectors/mutations.json`, the same 218 cases as frozen data (capsule, `.dkk`, identities, recorded release, clock, registry, known extensions), replayed with the recorded error and step, or the recorded verdicts; `capsule.TestPointMutationsChangeOnlyTheEncoding`: the ten point mutations keep a valid header MAC, and a decoder that reduces coordinates modulo p opens the c0 + p and x + p cases; `internal/testkit.TestResealReproducesFixtures`, `TestFixedX25519Stanza`; the cases of the lists of v0.11 and v0.12 outside the corpus: `ed25519strict.TestVectors` (`ed25519_strict.json`), `capsule.TestCMSVectors` (`security_cms.json`), `locator.TestLocatorVectors` (`locator.json`), `wordkey.TestKeyVector`, `TestNormalize` and `TestCheck`, `testdata/vectors/note.json`, and the same P of the fixtures `format3_unsigned` and `format3_signed` (`capsule.TestConformanceFixtures`); those of `alg` 2, `seal_type` 2 and `datekeys.capsule` of the list of v0.12, and the ones of v0.11 that `security_cms.json` and `locator.json` lack: *pending* |
| 65 | Quicknet vectors | `internal/testkit.RoundVectors` | `datekey.TestGoldenRoundVectors` |
| 66 | `dk1_` vectors | `internal/testkit.DK1Vectors` | `datekey.TestGoldenDK1Vectors` |
| 67 | `.dkc` vectors: the format 1 fixtures of v0.8.2, kept for compatibility, and format 2 fixtures for both policies, both codes, L = 0, one, several and 16 credentials, and extensions; the format 3 fixtures: one file, a tree, a comment alone, both codes, `time_and_key` with a portable key, an area of 1024 bytes, security of version 2, and an unsupported signature and seal; the records give the format, L, the code, P and the stanza each credential opens, and in format 3 the head, security, each file and the verdicts; the padding vectors, and those of paths, keys of R7, heads and security | `testdata/fixtures/*.dkc` + `*.json`, `internal/testkit/genfixtures`, which never regenerates a format 1 fixture and recomputes the derived fields of every record; the frozen `datekeys inspect -json` output of each, `*.inspect.json`; `testdata/vectors/padding.json`; formats in `testdata/README.md` | `capsule.TestConformanceFixtures` (`checkBody3`), `TestPaddingAcrossChunks` (a capsule generated at run time); `internal/testkit.TestFormat3VectorFiles`; `cmd/datekeys.TestInspectJSONGoldens`, `TestDecryptFixtures`, `TestDecryptFormat3Fixtures` |
| 67 | `.dkc` vectors: the format 1 fixtures of v0.8.2, kept for compatibility, and format 2 fixtures for both policies, both codes, L = 0, one, several and 16 credentials, and extensions; the format 3 fixtures: one file, a tree, a comment alone, both codes, `time_and_key` with a portable key, an area of 1024 bytes, security of version 2, a signature of `alg` 4294967295 and that with a seal of `seal_type` 4294967295, the area of 32 KiB without a signature, a signature of `alg` 1, that with a seal of `seal_type` 2 and a file whose mtime is later than the seal, and a signature of `alg` 2 of two signers, ECDSA P-256 and RSA 2048, each with its CAdES-T; the records give the format, L, the code, P and the stanza each credential opens, and in format 3 the head, security, each file and the verdicts with their lines, and for a signature or a seal the commitments, `AUTHOR_MESSAGE` and its code, the key or `SIGNERS` and the certificates, the result of each signer, `SEAL_SUBJECT` and the token; the padding vectors, and those of paths, keys of R7, heads, security, `security_cms.json`, `ed25519_strict.json`, `note.json` and `locator.json` | `testdata/fixtures/*.dkc` + `*.json`, `internal/testkit/genfixtures`, which never regenerates a format 1 fixture, gives the five fixtures that `EncryptFiles` wrote in v0.10 their area of 512 bytes (`EncryptOptions.TestAreaLen`) and recomputes the derived fields of every record; `format3_note`, with a public note, for the mutations of §64; the frozen `datekeys inspect -json` output of each, `*.inspect.json`; `testdata/vectors/padding.json`; `security_cms.json` and `locator.json`, frozen once written (`frozenVectors`); formats in `testdata/README.md` | `capsule.TestConformanceFixtures` (`checkBody3`, `checkSignature3`), `TestPaddingAcrossChunks` (a capsule generated at run time), `TestCMSVectors`; `locator.TestLocatorVectors`; `ed25519strict.TestVectors`; `internal/testkit.TestFormat3VectorFiles`, `TestVectorFilesAreCurrent`; `cmd/datekeys.TestInspectJSONGoldens`, `TestDecryptFixtures`, `TestDecryptFormat3Fixtures`, `TestMTimeAfterSeal` |
| 68 | `.dkk` vectors, with the exact extension data; one carries an extension with data, two accompany a format 2 capsule and one a format 3 capsule | `testdata/fixtures/*.dkk` + `*.dkk.json`; `time_and_key_portable_extension.dkk` derived by `genfixtures`; `format2_time_and_key_portable.dkk`, `format2_time_and_key_recipients.dkk`, `format3_time_and_key_portable.dkk` | `accesskey.TestFixtures`, `TestFixtureWithExtension`; `capsule.TestAccessKeyFixtureWithExtension` |
| 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`) | 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` | mutations; `capsule.TestFormatDispatch`, `TestFormatRelabel`, `TestFormat1Compatibility`; `codec.TestPeek`, `TestCheckSchema`, `TestCheckSchemaVersionForms`, `FuzzPeek`; `capsule.TestDecodeSchemaVersion`; `datekeys.TestSpecVersionNamesTheSpecification`, `TestVersion`; `cmd/datekeys.TestVersion` |
| 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 | 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`) |
| 71 | Profile registry | `profile.Decode` + `profile.NewRegistry` with pinned hashes | `profile.TestRegistry` |
| 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 | `extension.Registry`, `extension.Set`, `extension.DataValidator`, `extension.Placement` (optional: a `Registry` without it knows its extensions in every object and array); self-checks in `capsule.Encrypt` and `accesskey.MarshalBody`, which take no `Registry`: the application writes each extension only where it is registered | `capsule.TestKnownCriticalExtensions`, `TestUnusableNoncriticalExtensions`, `TestExtensionPlacement`, `TestNestedDataSealsAndOpens`, `FuzzEncodeImpliesDecode`; `extension.TestPlacement` |
| 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 | `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 | 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 |
| 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 7 in `security_cms.json` and `locator.json`: *pending* |
## Error mapping

Loading…
Cancel
Save

Powered by TurnKey Linux.