# Plan: firma de autor en Go (spec v0.11, entrega 2 del formato 3)
*1 de octubre de 2026. El autor pidió poder firmar cápsulas («no tengo opción de meter firmas en la página») y dio el visto bueno a empezar por el spec y Go. La base es la parte 2 del [diseño del formato 3](spec_v0.10/formato3_diseno.md) (apartados 10 a 15), ya revisada. Se hace en la rama `v0.11` de `datekeys-go`, que ya tiene la llave de palabras (`5b3d2d4`).*
## Por qué primero el spec y Go
- **El spec** es el contrato: dice qué bytes se firman, cómo se verifica una firma y qué ve quien abre. Lo que no está en el spec no puede comprobarlo otra implementación.
- **Go** es la implementación de referencia: escribe los vectores y las cápsulas de prueba que después tiene que reproducir TypeScript byte a byte, como en el formato 3.
- **La página** va después, con su propio plan, y reutiliza esas pruebas.
| 0 | Borrador del spec v0.11: qué se firma (`AUTHOR_MESSAGE`), Ed25519 estricto, las claves `dkauthor1…`, los veredictos de la firma, la llave de palabras y la nota pública | Texto para que lo apruebe el autor |
| 1 | `ed25519strict`: la verificación estricta, con código propio para lo que `ed25519.Verify` de Go acepta de más | Los casos de «Taming the many EdDSAs» en `ed25519_strict.json` |
| 2 | `authorkey`: generar la clave, escribirla como `dkauthor1…` y `DKAUTHOR-SECRET-KEY-1…`, y guardarla en un fichero cifrado con contraseña (`age` scrypt, logN 16) | Pruebas de ida y vuelta y de errores |
| 3 | Firmar en el escritor: `payload_commit`, `control_commit`, `head_digest` y la firma en el área de seguridad, verificada antes de escribir | Una cápsula firmada que abre un lector de la v0.10, que muestra F1 |
| 4 | Verificar en el lector: los veredictos de la firma, y la comparación con la clave que se espera | Firmas válidas, alteradas, quitadas, rehechas y trasplantadas |
| 5 | CLI: `author keygen`, `author public`, `encrypt -sign` y `decrypt -expect-author` | Prueba de ida y vuelta con la CLI |
| 6 | Datos de prueba: el fixture `format3_signed`, `statements.json` y las mutaciones de la firma | Lo que comprobará TypeScript |
| 7 | Documentación, `check.sh`, revisión adversarial y, con permiso del autor, el tag `spec-v0.11` | Rama lista |
Queda para después la verificación por un tercero (`statement export|verify`, apartado 13), que no hace falta para firmar y comprobar firmas.
El autor quiere que se sepa quién firma: con un certificado reconocido (FNMT, DNIe), firmado fuera de la página con su aplicación de firma.
- **Dos tipos de firma.** `alg` 1, con una clave propia Ed25519 (`dkauthor1…`), como en el diseño. `alg` 2, una firma CAdES separada, la que produce AutoFirma, con el certificado X.509 de la persona: la página da el `AUTHOR_MESSAGE` como fichero, la persona lo firma fuera y carga la firma.
- **Escritura en dos tiempos.** El escritor prepara la cápsula (sorteos, head y compromisos), devuelve `AUTHOR_MESSAGE`, espera la firma y entonces escribe. Los secretos solo viven en memoria entre los dos tiempos.
- **Área mayor.** 512 bytes no caben una firma CAdES con su cadena. La v0.11 fija un área mayor, por ejemplo 16 KiB, la misma para toda cápsula, firmada o no (§29.2, hallazgo 5).
- **Verificación.** DateKeys comprueba la firma y enseña el titular del certificado. La validez legal (cadena de confianza, revocación) la dan VALIDe o AutoFirma, a los que la página exporta la firma y el mensaje.
- **Sello de tiempo** RFC 3161 de una autoridad cualificada (`seal_type` 2), dentro de la firma (CAdES-T) o pedido aparte, en lugar del servicio propio de la entrega 3.
DateKeys tendrá aplicación móvil, de escritorio (macOS, Windows y Linux), de línea de órdenes y la web. Todas escriben y leen la misma cápsula firmada. Lo que cambia es cómo consigue cada una la firma con certificado:
| Aplicación | Cómo firma con certificado |
|---|---|
| Web | Fuera, con la aplicación de firma de la persona (AutoFirma), y carga la firma |
| Escritorio | Con el certificado del sistema o de la tarjeta: almacén de Windows, llavero de macOS, PKCS#11 en Linux |
| Móvil | Con el certificado importado en la app, o con el DNIe por NFC |
| CLI | Con un fichero `.p12` o la tarjeta (PKCS#11) |
Por eso:
- la API del escritor, en Go y en TypeScript, trabaja en dos tiempos, preparar y escribir con la firma, con un firmante que aporta cada aplicación;
- la firma con certificado es CMS/CAdES estándar (`SignedData` separado sobre `AUTHOR_MESSAGE`), que todas esas plataformas producen de serie;
- la librería de Go sirve para la CLI, el escritorio y el móvil (compilada para Android e iOS), y la de TypeScript para la web.
1.**Dónde se guarda la clave secreta de autor.** El diseño propone un fichero cifrado con contraseña. Como con la llave de palabras, podría poder guardarse también como palabras. Pero a diferencia de esa llave, la clave de autor es una sola para todas tus cápsulas, así que unas palabras flojas la exponen todas a la vez. Recomendación: fichero cifrado y, como alternativa, 12 palabras generadas al azar, nunca elegidas por la persona.
2.**Una sola firma por cápsula.** El diseño deja un único hueco de firma, ampliable más adelante sin cambiar el formato. Recomendación: así.
- **Nota pública.** Un texto corto que se ve antes de la fecha, para saber qué es cada cápsula: una extensión no crítica de `PUBLIC_HEADER`, registrada en §72, de hasta 1 KiB y con las reglas de texto de §29.6. Un lector de la v0.10 la ignora y abre la cápsula igual.
- Antes de la fecha no está comprobada: cualquiera puede fabricar una cápsula con la nota que quiera. La página y la CLI la muestran como nota de quien la creó, sin comprobar.
- Al abrir, `header_binding` la ata a la cápsula, y la firma la cubre a través de `control_commit`. Cambiarla después rompe la cápsula.
- **La nota y la fecha, también en la `.dkk`**, como pidió el autor, en una extensión no crítica (§44), para reconocer una llave suelta.
- Es informativa, porque nada la ata a la cápsula. La `.dkk` se empareja con su cápsula por `capsule_id` y `capsule_digest`, que ya escriben Go y TypeScript, nunca por la nota.
- Si la cápsula y la llave traen notas distintas, manda la de la cápsula y se avisa de la diferencia.
- **Dónde está el `.dkc`, también en la `.dkk` y cifrado para la fecha**, como pidió el autor: una o varias direcciones (`https://…`, `ipfs://…`), dentro de un fichero `age` con un stanza tlock para la ronda de la cápsula, en la misma extensión que la nota.
- Nadie las lee antes de la fecha, tampoco el destinatario. En la fecha, la misma firma de drand descifra las direcciones y abre la cápsula.
-`capsule_digest` va dentro, con las direcciones, y el escritor omite entonces `verification_metadata`. En IPFS, con CIDv1 y bloques raw, un `.dkc` de menos de 256 KiB tiene como dirección su propio SHA-256, y el digest en claro la delataría. La `.dkk` se sigue emparejando con su cápsula por `capsule_id`.
- Cifrar hacia una ronda no exige ningún secreto, así que las direcciones se pueden añadir a la `.dkk` después de subir la cápsula.
- Lo que se acepta: nadie puede bajar ni guardar la cápsula antes de la fecha, ni comprobar que sigue ahí; si el enlace muere, se pierde; y quien robe la `.dkk` leerá la dirección en la fecha. Quien crea la cápsula tiene que guardar su propia copia.
- **Código:** en Go, las dos extensiones y `-note` en la CLI, con el paso 5; en la página, con su propio plan.
- **Ficheros fuera de la cápsula**, propuesta pendiente de que el autor la confirme. Cada fichero iría dentro, como hoy, o fuera (IPFS, Drive, una URL), cifrado con la misma clave, con su hash, su tamaño y sus direcciones en una extensión crítica del head. Sus riesgos: que el fichero dure, que se pueda borrar antes de la fecha, que el cifrado quede público y que la web no pueda descargarlo de cualquier sitio.