‹ back to start

Verify this result

How is reached, step by step — and why it could not be chosen.

Steps 1 and 2 check the two inputs; 3 produces the number from them; 4 checks that the file declaring it is ours. None of them requires trusting us.

| turn %%Fecha%% %%Turno%% UTC | version

The turn's time is the reference. The number comes into existence at the turn's time and is published a few seconds later: the time it takes to read it, sign it and anchor it. Before that time it does not exist, and nobody —ourselves included— can know it or work it out. That time is fixed in advance and is the same for everyone.
checking…
1 / 4

The commitment

The seed was only revealed in the -emission. Its SHA-256 must give exactly what was already published in the -commitment, anchored before the drand round existed.

in · seed revealed in the -emission — edit it and see what happens
SHA-256( bytes )
out · what your seed gives
must be · seed_sha256 from the -commitment
%%SeedSHA%%
If your code gives another value, it is almost always this: hashing the hexadecimal text instead of the 32 bytes it represents. With this seed, the text gives — if that is yours, you already know where it is.
2 / 4

The drand round

You don't have to believe our file: the round is checked against the BLS signature of the drand group, or asked directly of any of its servers.

round
%%Ronda%%
1 · message = SHA-256( previous_signature followed by round )

«Followed by» means joined, not added. The bytes of one and then those of the other, with no separator and no arithmetic between them:

96 bytes of previous_signature + 8 bytes of round = 104 bytes hashed together

And round does not go as text: it is 8 bytes, the number in binary, most significant first — what is called big-endian. Round %%Ronda%% is %%RondaBE%%, not "%%Ronda%%".

This message is what the group signed. It does not enter our calculation — it is there to check that the round is authentic.

in · previous_signature
in · round — 8 bytes big-endian
out · message — the only field you cannot edit: it is computed
2 · BLS.Verify( group key, message, signature )
in 1 · drand_public_key — the group key, travels in the file see it on drand
in 2 · message
the one that just came out of step 1
in 3 · round.signature — the group signature, in the file
out · true or false

It happens right here, in your browser. It takes a moment: these are pairings over BLS12-381.

3 · randomness == SHA-256( signature )
out · must give the randomness

Both checks are needed: without the second one, somebody in the middle could hand over a made-up randomness/signature pair that is consistent with itself.

randomnessthis one does enter the calculation, as the HKDF salt open that round
BLS signature valid against the group key
Careful: previous_signature is an input to the BLS message. It is not a chain between our emissions — that does not exist in this format.
3 / 4

The derivation of

Here is where the numbers come out. Pick the variant and the whole calculation is redone.

None of this is ours: HKDF-SHA256 (RFC 5869) turns the seed into bytes, rejection sampling turns those into numbers without bias, and Fisher-Yates makes the selections without replacement. Three published algorithms, so that anyone can reimplement them.

in · the variant
in · version — which rules it came out with
in · drand_round — from which round
in · ikm = seed — the one from step 1, edit it there
in · salt = randomness — the one from step 2, edit it there
HKDF-SHA256( ikm = seed , salt = randomness , info = "version|variant|round|block|delivered" ) → byte stream
The block is the index inside the variant — without it, the two blocks of a compound variant would be correlated. The delivered count is the bytes already consumed: the stream is stretched 64 at a time and that counter makes each stretch different from the previous one. Further down you can see the info built with this turn's values.
out · the result
Rejection sampling is not optional. Taking byte MOD 10 straight introduces bias: with 256 possible values and 10 results, digits 0..5 would also come from bytes 250..255 and would be over-represented — 26 bytes each against 25, about 4 % more often. If the byte falls there it is discarded and the next one is taken.

The cut is (space DIV universe) × universe, where DIV is integer division — the decimal part is dropped. With universe 10 it gives (256 DIV 10) × 10 = 250; with universe 37, (256 DIV 37) × 37 = 222.
4 / 4

The file signature

Ed25519 over header_b64 . payload_b64 exactly as they come. The public key comes from the registry, and its epoch must cover the date the file declares.

in · kid — which key the file says signed it
From the key registry: e1 and e2. Change it to e1 and the signature stops validating, because the key is another one.
in · the public key for that kid, in hexadecimal
Changing the kid above puts it back on its own. Restore returns to the one for the kid currently set.
in · the header — first part of the .jws
in · the payload — second part
this is what is signed: header.payload — from the file %%Ruta%%-emission.jws
in · the signature — what comes after the second dot
64 bytes in base64url.
Ed25519.Verify( public key , "header.payload" , signature )
out · the verdict
Do not re-serialize the JSON. One space of difference breaks the signature. And the alg field of the header is ignored: accepting it is the alg:none attack.

Step 1 in five languages. The rest is in the specification. The five complete verifiers, at: GitHub or GitLab

Go Python JavaScript C++ Delphi
seed, _ := hex.DecodeString("%%Seed%%")
suma := sha256.Sum256(seed)          // sobre los BYTES, no sobre el texto
ok := hex.EncodeToString(suma[:]) == "%%SeedSHA%%"
See the signed file
the JWS exactly as published — header.payload.signature
%%Cab%%.%%Pay%%.%%Fir%%
header — which algorithm and which kid payload — the data and the 34 results signature — the 64 bytes of Ed25519

What is signed is header.payload: the first two parts with the dot in between, exactly as they are up there.

the decoded payload — to read and copy values

What is signed is the text header.payload exactly as it is above. The JSON below is only for reading it: re-serializing it breaks the signature.

Download the signed file | See it anchored in: GitHub or GitLab | Format specification at: GitHub or GitLab