Cómo se llega al —, paso a paso — y por qué no se pudo elegir.
Los pasos 1 y 2 comprueban los dos insumos; el 3 produce el número con ellos; el 4 comprueba que el archivo que lo declara es nuestro. Ninguno necesita confiar en nosotros.
— | turno %%Fecha%% %%Turno%% UTC | versión —
La semilla se reveló recién en el -emission. Su SHA-256 tiene que dar exactamente lo que ya estaba publicado en el -commitment, anclado antes de que existiera la ronda de drand.
seed revelada en el -emission — editala y mirá qué pasa
seed_sha256 del -commitment… — si ese es el tuyo, ya sabés dónde está.
No hay que creerle a nuestro archivo: la ronda se comprueba contra la firma BLS del grupo de drand, o se pide directo a cualquiera de sus servidores.
«Seguido de» es pegar, no sumar. Los bytes de uno y después los del otro, sin separador y sin operación aritmética entre ellos:
Y round no va como texto: son 8 bytes, el número en binario, empezando por el más significativo — lo que se llama big-endian. La ronda %%Ronda%% es %%RondaBE%%, no "%%Ronda%%".
Este mensaje es lo que el grupo firmó. No entra en nuestro cálculo — sirve para comprobar que la ronda es auténtica.
previous_signature
round — 8 bytes big-endian
mensaje — el único campo que no se edita: se calculadrand_public_key — la clave del grupo, viaja en el archivo
verla en drand
mensajeround.signature — la firma del grupo, en el archivo
true o falseSe hace acá mismo, en tu navegador. Tarda un instante: son emparejamientos sobre BLS12-381.
randomnessLas dos comprobaciones hacen falta: sin la segunda, alguien en el medio podría entregar un par randomness/signature inventado pero coherente entre sí.
randomness — esto sí entra en el cálculo, como salt del HKDF
abrir esa ronda
previous_signature es insumo del mensaje BLS. No es un encadenamiento entre nuestras emisiones — eso no existe en este formato.
—Acá salen los números. Elegí la variante y el cálculo se rehace entero.
Nada de esto es propio: HKDF-SHA256 (RFC 5869) convierte la semilla en bytes, rejection sampling —muestreo por rechazo— los pasa a números sin sesgo, y Fisher-Yates hace las selecciones sin reposición. Tres algoritmos publicados, para que cualquiera los reimplemente.
version — con qué reglas salió
drand_round — de qué ronda
ikm = seed — la del paso 1, editala allásalt = randomness — el del paso 2, editalo alláinfo armado con los valores de este turno.byte MOD 10 a secas mete sesgo: con 256 valores posibles y 10 resultados, los dígitos 0..5 saldrían también con los bytes 250..255 y quedarían sobrerrepresentados — 26 bytes cada uno contra 25, un 4 % más seguido. Si el byte cae ahí se descarta y se toma el siguiente.(espacio DIV universo) × universo, donde DIV es la división entera — se descarta la parte decimal. Con universo 10 da (256 DIV 10) × 10 = 250; con universo 37, (256 DIV 37) × 37 = 222.
Ed25519 sobre cabecera_b64 . payload_b64 exactamente como vienen. La clave pública sale del registro, y su época tiene que cubrir la fecha que el archivo declara.
kid — qué clave dice el archivo que lo firmó
e1 y e2. Cambialo a e1 y la firma deja de validar, porque la clave es otra.
kid, en hexadecimal
kid de arriba la repone sola. Restaurar vuelve a la del kid que esté puesto.
.jws
%%Ruta%%-emission.jwsalg de la cabecera se ignora: aceptarlo es el ataque alg:none.
El paso 1 en cinco lenguajes. Lo demás está en la especificación. Los cinco verificadores completos, en: GitHub o GitLab
seed, _ := hex.DecodeString("%%Seed%%")
suma := sha256.Sum256(seed) // sobre los BYTES, no sobre el texto
ok := hex.EncodeToString(suma[:]) == "%%SeedSHA%%"
import hashlib
seed = bytes.fromhex("%%Seed%%")
ok = hashlib.sha256(seed).hexdigest() == "%%SeedSHA%%"
const hex = s => Uint8Array.from(s.match(/../g).map(h => parseInt(h, 16)));
const seed = hex("%%Seed%%");
const d = new Uint8Array(await crypto.subtle.digest("SHA-256", seed));
const ok = [...d].map(b => b.toString(16).padStart(2, "0")).join("")
=== "%%SeedSHA%%";
// OpenSSL
unsigned char seed[32]; // 5748e8b8afced252… decodificado
unsigned char out[SHA256_DIGEST_LENGTH];
SHA256(seed, sizeof seed, out); // comparar con
// %%SeedSHA%%
LSeed := TNetEncoding.Base16.Decode('5748E8B8AFCED2520B989A4A3CF013264B3792984B3C55564D2AEBD9D012FA1C');
LHash := THashSHA2.Create(SHA256);
LHash.Update(LSeed[0], LSeed.Length);
LOk := LHash.HashAsString = '%%SeedSHA%%';
cabecera.payload.firma
%%Cab%%.%%Pay%%.%%Fir%%
kid
payload — los datos y los 34 resultados
firma — los 64 bytes de Ed25519
Se firma cabecera.payload: las dos primeras partes con el punto del medio, tal cual están ahí arriba.
—
Lo que se firma es el texto cabecera.payload tal como está arriba. El JSON de abajo es solo para leerlo: re-serializarlo rompe la firma.
Descargar el archivo firmado | Verlo anclado en: GitHub o GitLab | Especificación del formato en: GitHub o GitLab