# datekeys-go Implementación de referencia en Go de la **DateKeys Protocol Specification v0.8.2** ([`spec/`](spec/DateKeys_Protocol_Specification_v0.8.2.md)). [English version](README.md). DateKeys cifra datos de forma que solo puedan abrirse a partir de un instante elegido. La condición temporal procede del beacon de aleatoriedad **Quicknet** de drand: los datos se sellan con cifrado timelock hacia una ronda futura, y la firma BLS de esa ronda, que drand publica cuando llega, es la llave. Todo lo que se puede verificar localmente se verifica localmente; relays, cachés y APIs son transportes no confiables. > **Estado: v0.x, pre-estándar.** La especificación es un borrador y la API > puede cambiar antes de v1.0.0. El código aún no ha pasado una revisión > criptográfica externa (spec §75). No lo uses para secretos de alto valor. ## Qué implementa | Objeto | Spec | Paquete | |---|---|---| | 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` | §20–§39, §61–§63 | [`capsule`](capsule) | | DateKeys Access Key `.dkk` | §40–§44 | [`accesskey`](accesskey) | | Extensiones | §54 | [`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. No implementa, a propósito: el servidor y la cola de la Release API, el almacenamiento y la entrega, extensiones concretas ni el cliente TypeScript (plan §2). ## Versiones Hay tres números de versión, cada uno con su significado: | Versión | Dónde | Cambia cuando | |---|---|---| | Formato | Dentro de los objetos: la versión de framing de DKC1 y DKK1 y la versión de schema de la clave 1, hoy todas 1 | Cambia el formato. Un lector rechaza una versión que no conoce (spec §70) | | Especificación | `datekeys.SpecVersion`, hoy `0.8.2`, y el tag `spec-v0.8.2` | 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 `main`, aún sin publicar, cubre: - la especificación 0.8.2, con versiones de formato 1; - el perfil Quicknet pinneado, y cualquier perfil de los tres schemes de drand que soporta tlock; - cifrado, inspección y apertura, 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. ## Una cápsula, en un dibujo ```text .dkc = PRELUDE (16 B) || PUBLIC_HEADER (CBOR) || SEALED_CONTROL (age) || PAYLOAD_AGE (age, hasta EOF) time_only: SEALED_CONTROL = age(tlock ronda R → CONTROL_CBOR) time_and_key: SEALED_CONTROL = age(tlock ronda R → age(recipients X25519 → CONTROL_CBOR)) CONTROL_CBOR = { header_binding = SHA-256(PRELUDE || PUBLIC_HEADER), I_PAYLOAD, extensiones } PAYLOAD_AGE = age(X25519 R_PAYLOAD → tus datos), en streaming ``` ## CLI ```bash go install g.activething.com/go/DateKeys/cmd/datekeys@latest ``` El módulo se sirve desde el Gitea del proyecto, cuyo certificado Go no reconoce por defecto. Define `GOPRIVATE=g.activething.com` para que el proxy y la base de datos de sumas de Go no intervengan, e instala el certificado del servidor o, en una red de confianza, define `GOINSECURE=g.activething.com`. ```bash 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 -policy time_and_key -dkk carta.dkk -in carta.txt -out carta.dkc datekeys inspect -in carta.dkc datekeys decrypt -in carta.dkc -out carta.txt -dkk carta.dkk datekeys profile hash datekeys version ``` `encrypt` nunca usa la red. `inspect` ejecuta solo las comprobaciones previas al desbloqueo (spec §63, pasos 1 a 8): nunca pide un release ni usa secretos. `decrypt` obtiene el release de relays públicos de drand, lo verifica localmente y publica el plaintext solo cuando age lo ha autenticado entero. Nunca se sobrescriben ficheros de salida. ## Librería ```go reg, err := profile.Default() // perfil Quicknet pinneado, comprobado contra su profile_hash // Cifrar: sin red, la ronda se resuelve localmente. res, err := capsule.Encrypt(dst, src, capsule.EncryptOptions{ Profile: profile.Quicknet(), UnlockAt: time.Date(2030, 1, 1, 0, 0, 0, 0, time.UTC), Policy: capsule.TimeAndKey, NewPortableKey: true, // res.PortableKey es la .dkk; se codifica con accesskey.Encode Now: time.Now, }) // Inspeccionar: pasos 1 a 8, sin red ni secretos. in, err := capsule.Inspect(f, capsule.InspectOptions{Registry: reg}) // Abrir: pasos 1 a 18; el release se verifica localmente. opened, err := capsule.Open(ctx, tmp, f, capsule.OpenOptions{ Registry: reg, Source: drand.New(), AccessKey: key, // o Identities: []age.Identity{...} Now: time.Now, }) if errors.Is(err, datekeys.ErrReleaseUnavailable) { /* todavía no */ } ``` `Open` escribe el plaintext en streaming; si falla, descarta lo escrito (spec §56). Todo fallo del protocolo envuelve uno de los 17 errores normativos del §69, así que `errors.Is` y `datekeys.Code(err)` lo identifican. ## Propiedades de seguridad y límites - **Confidencialidad temporal** bajo el supuesto de umbral de drand. El timelock de Quicknet **no es post-cuántico**: los ciphertexts guardados durante años quedan expuestos a *harvest now, decrypt later* (spec §7.7, §53). - **Sin confianza en servidores**: el perfil va pinneado en el binario, la ronda se calcula localmente, los releases se verifican con BLS localmente y una firma válida de otra ronda se rechaza (spec §13, §17, §51). - **Integridad**: framing, cabecera, control y payload están autenticados frente a quien no conoce las file keys: un cambio de un tercero hace fallar la apertura (fixtures y corpus de mutaciones). Publicada la ronda, cualquiera puede calcular la file key temporal, y cualquiera puede sellar un control nuevo para una cabecera pública; quién puede escribir cada parte y desde qué paso queda vinculada es el modelo de confianza de spec §27 y §55.1. - **Sin autoría**: `time_only` da coherencia interna, no prueba de quién creó la cápsula, ni antes ni después de madurar; `time_and_key` añade una barrera de acceso, no una firma (spec §36.1). - **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). Ver [SECURITY.md](SECURITY.md). ## Conformidad y tests ```bash go test ./... # unitarios, vectores golden, fixtures, mutaciones go test -race -cover ./... go test -fuzz=FuzzInspect ./capsule # un objetivo de fuzzing cada vez go test -tags interop ./capsule # las CLI oficiales age y tle abren nuestros ficheros 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, 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. Cada `.dkc` tiene congelada su salida de `datekeys inspect -json`. - `internal/testkit.Mutations`: las 33 mutaciones del §64 y 32 más, cada una con su error y su paso exactos, 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. ## Licencia Código: Apache-2.0 ([LICENSE](LICENSE)). Especificación: CC-BY-4.0 ([spec/README.md](spec/README.md)). `codec/bech32` se copia de age bajo su propia licencia. "DateKeys" es un nombre reservado: ver [TRADEMARKS.md](TRADEMARKS.md).