11 KiB
Cómo abrir una cápsula DateKeys sin software de DateKeys
Este texto acompaña a una cápsula del tiempo de DateKeys, un fichero .dkc: dice cómo abrirla, llegada su fecha, sin ningún software de DateKeys, por si ya no existe. Es el anexo informativo §79 de la especificación del protocolo DateKeys v0.15, cuyo texto tiene el SHA-256 45105e693be4187af4dd30f4d254402612587b6427c746f5d29f07a541c1e3f3. Es el mismo para toda cápsula: no lleva ningún dato de esta.
79. Anexo informativo: recuperación sin software DateKeys
Este anexo no es normativo. Dice cómo abrir una cápsula de Quicknet sin ningún software de DateKeys, por si dentro de décadas no existe. Repite lo que fijan las secciones que cita, que deciden en caso de duda.
La regla 27 de §62.1 recomienda al SDK oficial guardar este anexo junto al .dkc. No contiene ningún dato de una cápsula.
Hace falta:
- el
.dkc; - el release de su ronda, de cualquier fuente: un relay de drand, un archivo de releases, un servicio de caché (§50) o cualquier copia. No hace falta confiar en quien lo da: se verifica con la clave pública de 79.1 (79.3);
- en
time_and_key, una credencial: la.dkk, la identityagede un recipient o las palabras de una llave de palabras (§38.1); - una librería de BLS12-381 con pairing y con el hash a G1 de RFC 9380, SHA-256, HMAC-SHA256, HKDF-SHA256 (RFC 5869), ChaCha20-Poly1305 (RFC 8439), un decodificador de CBOR y la herramienta
age(§77) o una librería compatible.
No sirven las herramientas de drand: tle pide el release a la red y no acepta uno dado, y age no acepta una file key, que es lo que da el stanza tlock (79.4). Por eso este anexo describe esos dos pasos enteros (79.4 y 79.5).
La implementación de referencia lo sigue en scripts/recovery, un programa que no importa ningún paquete de DateKeys, tlock ni drand: solo la librería estándar de Go, golang.org/x/crypto, filippo.io/age y la librería BLS12-381 drand/kyber-bls12381. scripts/recovery_check.sh abre con él una cápsula time_only y otra time_and_key de los fixtures oficiales.
79.1 Parámetros de Quicknet
Son los de §12, y pueden no estar ya en ningún otro sitio:
chain_hash 52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971
clave pública (G2, 96 bytes)
83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c
8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb
5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a
genesis_time 1692803367 (segundos Unix de la ronda 1)
period 3 segundos
round_time(r) = genesis_time + (r − 1)·3
q 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001
DST BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_
Los puntos se codifican comprimidos, en el formato de ZCash (§12.2): 48 bytes en G1, la firma, y 96 en G2, la clave pública y U, con la coordenada c1 antes que c0.
79.2 La cápsula
El .dkc empieza por 16 bytes (§22):
0 4 "DKC1"
4 1 VERSION: el formato, 1, 2 o 3
5 1 0
6 2 0
8 4 PUBLIC_HEADER_LEN, entero big-endian
12 4 SEALED_CONTROL_LEN, entero big-endian
Le siguen PUBLIC_HEADER, de PUBLIC_HEADER_LEN bytes; SEALED_CONTROL, de SEALED_CONTROL_LEN bytes; y PAYLOAD_AGE, desde el byte 16 + PUBLIC_HEADER_LEN + SEALED_CONTROL_LEN hasta el final del fichero.
PUBLIC_HEADER es un mapa CBOR (§24). Su clave 2 es capsule_id, 16 bytes; su clave 4, la política, 0 para time_only y 1 para time_and_key; y su clave 3, la DateKey, un texto dk1_ seguido del Base64URL sin relleno de un JSON (§18):
{"version":1,"network":"datekeys:quicknet:v1","round":1000}
round es la ronda de la cápsula.
79.3 El release
Un objeto release es un mapa CBOR (§47.1): la clave 0 es "datekeys-release"; la 2, el chain_hash, que ha de ser el de 79.1; la 3, la ronda, que ha de ser la de la DateKey; y la 4, la firma, de 48 bytes. Así lo sirven la Release API y un servicio de caché. En un archivo de releases (§50), la firma de la ronda r son los 48 bytes que empiezan en |cabecera| + (r − primera ronda)·48, y 48 ceros si el archivo no la tiene. Un relay de drand la entrega como JSON, {"round": …, "signature": "…"}, con la firma en hexadecimal. Hoy se pide así, aunque las direcciones pueden cambiar:
GET https://api.drand.sh/v2/chains/<chain_hash>/rounds/<ronda>
La firma no necesita confianza: se verifica (§63, paso 10). Con M el SHA-256 de la ronda en 8 bytes big-endian, y H el hash a G1 de RFC 9380 con la suite BLS12381G1_XMD:SHA-256_SSWU_RO_ y el DST de 79.1:
e(H(M), clave_pública) == e(firma, G2)
con e el pairing de G1 × G2, el primer argumento en G1 y el segundo en G2, y G2 el generador de G2. La firma y la clave pública se decodifican como puntos comprimidos válidos del subgrupo, distintos del punto en el infinito. Para una ronda solo hay una firma válida: cualquier copia que verifique es el release.
79.4 El stanza tlock y FK_TIME
SEALED_CONTROL es un fichero age (79.5) cuya cabecera tiene un solo stanza:
-> tlock <ronda en decimal> <chain_hash en hexadecimal en minúsculas>
<cuerpo en Base64 estándar sin relleno, en líneas de 64 caracteres>
El cuerpo mide 128 bytes, U ‖ V ‖ W: U, de 96 bytes, un punto de G2, y V y W, de 16 bytes. Con la firma del release (§63, paso 11):
sigma = V XOR H2(e(firma, U))
FK_TIME = W XOR H4(sigma)
r = H3(sigma, FK_TIME)
comprobar r·G2 == U
- H2(x) son los 16 primeros bytes de SHA-256(
IBE-H2‖ x), con x los 576 bytes del elemento de GT en el orden de §63, «Serialización de GT en H2»: c1 antes que c0 en cada nivel de la torre, y c2, c1, c0 en Fp6, cada elemento de Fp en 48 bytes big-endian. Una librería que serializa con c0 primero da otro H2. El vector detestdata/vectors/tlock_ibe.jsonlo comprueba: H2(e(G1, G2)) =cb87319f24560b5231579a09ad79f12e, con G1 y G2 los generadores. - H4(sigma) son los 16 primeros bytes de SHA-256(
IBE-H4‖ sigma). - H3(sigma, FK_TIME): base = SHA-256(
IBE-H3‖ sigma ‖ FK_TIME); para i = 1, 2, … hasta 65534, d = SHA-256(uint16_le(i) ‖ base), con el contador en 2 bytes little-endian delante de base; se desplaza un bit a la derecha el primer byte de d, solo ese byte; y si d, como entero big-endian de 32 bytes, es menor que q, r = d.
Las etiquetas son los bytes ASCII, sin longitud ni terminador. FK_TIME, de 16 bytes, es la file key del fichero age de SEALED_CONTROL. testdata/vectors/tlock_steps.json da cada valor intermedio de cinco stanzas.
79.5 Abrir un fichero age con su file key
Es la especificación age v1 de C2SP (§77), resumida. Un fichero age es una cabecera de texto y un payload binario:
age-encryption.org/v1
-> <tipo> <argumentos…>
<cuerpo del stanza en Base64 sin relleno, líneas de 64 caracteres, la última más corta>
--- <MAC en Base64 sin relleno, 43 caracteres>
<payload>
Con la file key FK, de 16 bytes:
- La cabecera: clave_mac = HKDF-SHA256(ikm = FK, salt = vacío, info =
header), 32 bytes. El MAC es HMAC-SHA256(clave_mac, la cabecera desdeage-encryption.org/v1hasta---inclusive, sin el espacio que lo sigue). Si no coincide con el de la línea---, la file key o la cabecera son otras. - El payload empieza tras el salto de línea del MAC por un nonce de 16 bytes. clave = HKDF-SHA256(ikm = FK, salt = nonce, info =
payload), 32 bytes. - Lo demás son bloques de ChaCha20-Poly1305 de 65 536 bytes de texto, 65 552 cifrados, el último más corto. El nonce de 12 bytes del bloque n, desde 0, es n en 11 bytes big-endian seguido de 0x01 en el último bloque y de 0x00 en los demás. No hay datos asociados. El último bloque solo puede estar vacío si es el único, y nada sigue al último bloque.
79.6 Las capas siguientes
El plaintext de SEALED_CONTROL es:
- en
time_only,CONTROL_CBOR; - en
time_and_key, otro ficheroage,INNER_ACCESS_AGE, con stanzas X25519, uno por credencial y señuelos hasta 16 en los formatos 2 y 3. Se abre conage -d -i clave.txt, con la identity de la credencial enclave.txt. La de una.dkkes suaccess_material. La.dkkempieza por 12 bytes,DKK1,01,00,00 00yBODY_LENen 4 bytes big-endian (§40), y le sigue un mapa CBOR cuya clave 5 es eseaccess_material, 32 bytes (§41), y cuya clave 3 es elcapsule_idde su cápsula. Una llave de palabras da la identity con §38.1.
CONTROL_CBOR es un mapa CBOR (§31). Su clave 3 es I_PAYLOAD, otra identity X25519 de 32 bytes, y en los formatos 2 y 3 su clave 6 es L, una cadena de 8 bytes con un entero big-endian, no un entero CBOR. Con I_PAYLOAD, age -d -i payload.txt abre PAYLOAD_AGE.
Una identity X25519 de 32 bytes se escribe para age en Bech32 (BIP 173, §77), no Bech32m: el prefijo age-secret-key-, los 32 bytes reagrupados de 8 en 5 bits con ceros al final, que dan 52 caracteres, y la suma de comprobación de BIP 173, calculada con el prefijo en minúsculas. Después, todo en mayúsculas: AGE-SECRET-KEY-1….
79.7 El contenido
El plaintext de PAYLOAD_AGE es:
- en formato 1, el contenido entero;
- en formato 2, el contenido en sus L primeros bytes, seguido de ceros;
- en formato 3,
BODYen sus L primeros bytes, seguido de ceros (§29.2).
BODY empieza por tres enteros de 4 bytes big-endian: AREA_LEN, SECURITY_LEN y HEAD_LEN. Siguen el área de security, de AREA_LEN bytes, que se puede saltar: solo da los veredictos de la firma y del sello (§29.7); el head, un mapa CBOR de HEAD_LEN bytes que empieza en 12 + AREA_LEN; y los ficheros, desde 12 + AREA_LEN + HEAD_LEN, el origen de sus desplazamientos.
La clave 5 del head es la lista de ficheros (§29.4). Cada uno es un mapa:
0 → ruta, con "/" entre carpetas
1 → tamaño
2 → start
3 → end
4 → SHA-256 de sus bytes
5 → mtime, en segundos Unix, opcional
Sus bytes van de start a end, sin incluir end, contados desde el origen. Las claves 3 y 4 del head son el comentario y el autor declarado: textos del creador que no prueban nada (§29.7). Una ruta que saldría de la carpeta de destino no se escribe.
79.8 Lo que el anexo no comprueba
Este anexo comprueba lo que decide que el resultado es el correcto: la firma del release, r·G2 == U, los MAC de cada fichero age y el SHA-256 de cada fichero. No comprueba, entre otras cosas, la codificación canónica de cada objeto, header_binding (§26), los 16 stanzas de INNER_ACCESS_AGE (§39), los ceros del relleno (§29.1) ni las reglas de las rutas (§29.5). Una cápsula que el lector de §63 rechazaría puede abrirse siguiendo este anexo; su contenido es el que sellaron las MAC de age, pero no tiene la garantía de un lector conforme.