testdata at spec-v0.15: the release objects are releases/<round>.cbor

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main
dev 20 hours ago
parent d2fad410fa
commit 7446db6084

@ -8,7 +8,7 @@ La especificación 0.15, de la rama `v0.15` de `datekeys-go`: el objeto release,
### La especificación 0.15: el objeto release y el release en la mano (07-10-2026)
- `SPEC_VERSION` pasa a `0.15` y la versión a `0.4.0-dev`. `testdata` se sincroniza con `datekeys-go` en `3c3e737`, que añade `vectors/release.json`, los ficheros de `releases/` y el campo `source` de `mutations.json`.
- `SPEC_VERSION` pasa a `0.15` y la versión a `0.4.0-dev`. `testdata` se sincroniza con el tag `spec-v0.15` de `datekeys-go` (`fe50885`), que añade `vectors/release.json`, los ficheros de `releases/` y el campo `source` de `mutations.json`.
- **El objeto release** (§47.1). `releaseobject.ts`, sin noble, hace lo de `provider/release.go` y `provider/archive.go` de Go, con sus textos byte a byte: `encodeRelease` y `decodeRelease`, con las capas del paso 10 (el tamaño de 1 a 1024 bytes antes de decodificar, el tipo y la versión, el schema); `parseRelease`, que lee también el JSON de drand como lo lee `encoding/json` de Go, con `ERR_RELEASE_INVALID` para cualquier fallo; `ReleaseSupplier` y `encodedRelease`, el release en la mano; y `ReleaseArchive`, el archivo de releases local, formato informativo (§50), cuyos fallos son `ERR_RELEASE_UNAVAILABLE`, también una ronda a ceros.
- **La cadena en el paso 10.** `verifyRelease` compara primero la cadena que nombra el release con la del perfil fijado (`ERR_PROFILE_MISMATCH`), y después la ronda y la firma.
- **El paso 9.c, opción B.** `open` acepta `release`, un release en la mano, en lugar de `source`: no se compara con el reloj, `Opened.clockBehind` dice si el reloj iba por detrás de la fecha, y el paso 10 lo decodifica con los códigos de ese paso. A una fuente de red no se le pide nada antes de `round_time`, como antes. El release verificado lleva la cadena del perfil fijado.

@ -21,7 +21,7 @@ Hay tres números de versión, cada uno con su significado, como en la referenci
| Versión | Dónde | Cambia cuando |
|---|---|---|
| Formato | Dentro de los objetos: el formato de la cápsula, el `VERSION` del prelude de DKC1, 1, 2 o 3 al leer, que fija también la versión de schema de CONTROL_CBOR; y 1 en la trama DKK1 y en el schema de los demás objetos | Cambia el formato. Un lector rechaza una versión que no conoce (§22, §70) |
| Especificación | `SPEC_VERSION` de `src/lib/dkc/version.ts`, hoy `0.15`: la v0.15 de `datekeys-go`, en su rama `v0.15`. `testdata` está en `3c3e737`, y todos sus ficheros dicen `0.15` | Cambia el texto normativo |
| Especificación | `SPEC_VERSION` de `src/lib/dkc/version.ts`, hoy `0.15`: la del tag `spec-v0.15` de `datekeys-go`, que aprobó el autor el 7 de octubre de 2026. `testdata` está en ese tag (`fe50885`), y todos sus ficheros dicen `0.15` | Cambia el texto normativo |
| Librería | `VERSION` de `src/lib/dkc/version.ts`, igual al campo `version` de `package.json` | Cambia la API o el comportamiento. Versionado semántico, sin promesa de estabilidad antes de 1.0.0 |
`version.test.ts` comprueba que `VERSION` coincide con `package.json` y con su lockfile, y que `SPEC_VERSION` es la versión que nombran los vectores y fixtures compartidos; `vectors.test.ts` exige esa versión a cada fichero. El pie de la página muestra las dos.
@ -466,7 +466,7 @@ Comprueba que los ficheros coinciden con `SOURCE.json`, sin faltantes ni sobrant
`.gitattributes` marca `testdata/**` como binario para que git no altere ningún byte.
Copia actual: la de `testdata/SOURCE.json` (la v0.15 de `datekeys-go`, `3c3e737` de su rama `v0.15`), que se sincroniza con `node scripts/sync-testdata.mjs sync --repo ../datekeys-go --commit 3c3e737`. Añade `vectors/release.json`, los ficheros de `releases/` y el campo `source` de `mutations.json`.
Copia actual: la de `testdata/SOURCE.json` (el tag `spec-v0.15` de `datekeys-go`, `fe50885`), que se sincroniza con `node scripts/sync-testdata.mjs sync --commit spec-v0.15`. Añade `vectors/release.json`, los ficheros de `releases/` y el campo `source` de `mutations.json`.
## Licencia

@ -1,6 +1,6 @@
// The release object of spec v0.15, §47.1, as provider/release.go and
// provider/archive.go of the Go reference: the release of a round as data
// that is kept, the content of a .dkr file and the answer of a release cache
// that is kept: an entry of a release cache or archive and the answer of a release cache
// or of the Release API; drand's JSON, which a reader accepts too as the
// input of the caller; the sources of a release in the caller's hand
// (provider.Supplier); and a local release archive, the informative format
@ -10,13 +10,20 @@
// only decodes, so that the page can read what the person gives before
// loading the code that opens a capsule.
import { equalBytes, goQuote, sha256, toHex, utf8Length } from './bytes.ts';
import { checkSchema, Decoder, Encoder, MAX_SAFE_UINT, peek, unmarshal } from './cbor.ts';
import { DateKeysError, withContext } from './errors.ts';
import { QUICKNET_SCHEME, type Profile } from './profile.ts';
import { equalBytes, goQuote, sha256, toHex, utf8Length } from "./bytes.ts";
import {
checkSchema,
Decoder,
Encoder,
MAX_SAFE_UINT,
peek,
unmarshal,
} from "./cbor.ts";
import { DateKeysError, withContext } from "./errors.ts";
import { QUICKNET_SCHEME, type Profile } from "./profile.ts";
/** The type tag of the release object (key 0). */
export const RELEASE_TYPE_TAG = 'datekeys-release';
export const RELEASE_TYPE_TAG = "datekeys-release";
/** The schema version of the release object (key 1). */
export const RELEASE_SCHEMA_VERSION = 1;
/**
@ -32,7 +39,7 @@ export const MAX_SIGNATURE_LEN = 96;
export const MAX_RELEASE_JSON_SIZE = 8 << 10;
/** The type tag of the header of a release archive (spec v0.15, §50). */
export const ARCHIVE_TYPE_TAG = 'datekeys-release-archive';
export const ARCHIVE_TYPE_TAG = "datekeys-release-archive";
/** The schema version of the header of a release archive. */
export const ARCHIVE_SCHEMA_VERSION = 1;
@ -94,12 +101,24 @@ function encodeWire(e: Encoder, w: ReleaseWire): void {
// ERR_NON_CANONICAL_CBOR: what each field means against the pinned profile
// and the DateKey is checked by verifyRelease, at step 10.
function decodeWire(d: Decoder): ReleaseWire {
const w: ReleaseWire = { chainHash: new Uint8Array(0), round: 0, signature: new Uint8Array(0) };
const w: ReleaseWire = {
chainHash: new Uint8Array(0),
round: 0,
signature: new Uint8Array(0),
};
const pairs = d.map(RELEASE_KEYS);
if (pairs !== RELEASE_KEYS) throw new DateKeysError('ERR_NON_CANONICAL_CBOR', `${pairs} keys, want all ${RELEASE_KEYS}`);
if (pairs !== RELEASE_KEYS)
throw new DateKeysError(
"ERR_NON_CANONICAL_CBOR",
`${pairs} keys, want all ${RELEASE_KEYS}`,
);
for (let want = 0; want < RELEASE_KEYS; want++) {
const k = d.key();
if (k !== want) throw new DateKeysError('ERR_NON_CANONICAL_CBOR', `key ${k} where key ${want} was expected`);
if (k !== want)
throw new DateKeysError(
"ERR_NON_CANONICAL_CBOR",
`key ${k} where key ${want} was expected`,
);
withContext(`key ${k}`, () => {
switch (want) {
case 0:
@ -113,7 +132,8 @@ function decodeWire(d: Decoder): ReleaseWire {
break;
case 3:
w.round = d.uint(MAX_SAFE_UINT);
if (w.round === 0) throw new DateKeysError('ERR_NON_CANONICAL_CBOR', 'round 0');
if (w.round === 0)
throw new DateKeysError("ERR_NON_CANONICAL_CBOR", "round 0");
break;
default:
w.signature = d.bstr(1, MAX_SIGNATURE_LEN);
@ -125,7 +145,7 @@ function decodeWire(d: Decoder): ReleaseWire {
}
/**
* The release object of `r`, the content of a .dkr file (spec v0.15,
* The release object of `r`, as a release cache or archive keeps it (spec v0.15,
* §47.1): its chain hash, its round and its signature, as
* provider.EncodeRelease. It does not verify the release: verifyRelease
* does, against the pinned profile.
@ -133,14 +153,20 @@ function decodeWire(d: Decoder): ReleaseWire {
export function encodeRelease(r: Release): Uint8Array {
const chainHash = r.chainHash ?? new Uint8Array(0);
if (chainHash.length !== 32) {
throw new DateKeysError('ERR_NON_CANONICAL_CBOR', `provider: release object: chain hash of ${chainHash.length} bytes, want 32`);
throw new DateKeysError(
"ERR_NON_CANONICAL_CBOR",
`provider: release object: chain hash of ${chainHash.length} bytes, want 32`,
);
}
if (!Number.isSafeInteger(r.round) || r.round < 1) {
throw new DateKeysError('ERR_NON_CANONICAL_CBOR', `provider: release object: round ${r.round} outside 1..${MAX_SAFE_UINT}`);
throw new DateKeysError(
"ERR_NON_CANONICAL_CBOR",
`provider: release object: round ${r.round} outside 1..${MAX_SAFE_UINT}`,
);
}
if (r.signature.length === 0 || r.signature.length > MAX_SIGNATURE_LEN) {
throw new DateKeysError(
'ERR_NON_CANONICAL_CBOR',
"ERR_NON_CANONICAL_CBOR",
`provider: release object: signature of ${r.signature.length} bytes outside 1..${MAX_SIGNATURE_LEN}`,
);
}
@ -159,9 +185,12 @@ export function encodeRelease(r: Release): Uint8Array {
*/
export function decodeRelease(b: Uint8Array): Release {
if (b.length === 0 || b.length > MAX_RELEASE_OBJECT_SIZE) {
throw new DateKeysError('ERR_NON_CANONICAL_CBOR', `provider: release object of ${b.length} bytes, outside 1..${MAX_RELEASE_OBJECT_SIZE}`);
throw new DateKeysError(
"ERR_NON_CANONICAL_CBOR",
`provider: release object of ${b.length} bytes, outside 1..${MAX_RELEASE_OBJECT_SIZE}`,
);
}
const w = withContext('provider: release object', () => {
const w = withContext("provider: release object", () => {
checkSchema(b, RELEASE_TYPE_TAG, RELEASE_SCHEMA_VERSION);
return unmarshal(b, decodeWire, encodeWire);
});
@ -201,52 +230,76 @@ export async function parseRelease(b: Uint8Array): Promise<ParsedRelease> {
// integer of 0 to 2^64-1, fails the whole input.
async function parseDrandJSON(b: Uint8Array): Promise<ParsedRelease> {
if (b.length > MAX_RELEASE_JSON_SIZE) {
throw new DateKeysError('ERR_RELEASE_INVALID', `provider: drand JSON of ${b.length} bytes, larger than ${MAX_RELEASE_JSON_SIZE}`);
throw new DateKeysError(
"ERR_RELEASE_INVALID",
`provider: drand JSON of ${b.length} bytes, larger than ${MAX_RELEASE_JSON_SIZE}`,
);
}
const malformed = (): DateKeysError =>
new DateKeysError('ERR_RELEASE_INVALID', 'provider: drand JSON: malformed, or without round or signature');
new DateKeysError(
"ERR_RELEASE_INVALID",
"provider: drand JSON: malformed, or without round or signature",
);
const text = new TextDecoder().decode(b);
const members = jsonMembers(text);
if (members === undefined) throw malformed();
let round: bigint | undefined;
let signature: string | undefined;
let randomness = '';
let randomness = "";
let typeError = false;
for (const [key, raw] of members) {
const name = foldName(key);
if (name === 'round') {
if (raw === 'null') round = undefined;
else if (/^(0|[1-9][0-9]*)$/.test(raw) && BigInt(raw) < 2n ** 64n) round = BigInt(raw);
if (name === "round") {
if (raw === "null") round = undefined;
else if (/^(0|[1-9][0-9]*)$/.test(raw) && BigInt(raw) < 2n ** 64n)
round = BigInt(raw);
else typeError = true;
} else if (name === 'signature' || name === 'randomness') {
if (raw === 'null') {
if (name === 'signature') signature = undefined;
} else if (name === "signature" || name === "randomness") {
if (raw === "null") {
if (name === "signature") signature = undefined;
} else if (raw.startsWith('"')) {
const v = JSON.parse(raw) as string;
if (name === 'signature') signature = v;
if (name === "signature") signature = v;
else randomness = v;
} else {
typeError = true;
}
}
}
if (typeError || round === undefined || signature === undefined) throw malformed();
if (typeError || round === undefined || signature === undefined)
throw malformed();
if (!/^([0-9a-fA-F]{2})*$/.test(signature)) {
throw new DateKeysError('ERR_RELEASE_INVALID', 'provider: drand JSON: signature is not hex');
throw new DateKeysError(
"ERR_RELEASE_INVALID",
"provider: drand JSON: signature is not hex",
);
}
const sig = Uint8Array.from(signature.match(/../g) ?? [], (h) => parseInt(h, 16));
if (randomness !== '' && randomness.toLowerCase() !== toHex(await sha256(sig))) {
throw new DateKeysError('ERR_RELEASE_INVALID', 'provider: drand JSON: randomness does not match the signature');
const sig = Uint8Array.from(signature.match(/../g) ?? [], (h) =>
parseInt(h, 16),
);
if (
randomness !== "" &&
randomness.toLowerCase() !== toHex(await sha256(sig))
) {
throw new DateKeysError(
"ERR_RELEASE_INVALID",
"provider: drand JSON: randomness does not match the signature",
);
}
return { round: round <= BigInt(MAX_SAFE_UINT) ? Number(round) : round, signature: sig };
return {
round: round <= BigInt(MAX_SAFE_UINT) ? Number(round) : round,
signature: sig,
};
}
// Go's encoding/json matches a key to a field name without case: ASCII
// letters in lower case, and any other character as unicode.ToLower of
// unicode.ToUpper, so that U+017F (long s) is an s and U+212A (Kelvin) a k.
function foldName(s: string): string {
let out = '';
for (const c of s) out += c.charCodeAt(0) < 0x80 ? c.toLowerCase() : c.toUpperCase().toLowerCase();
let out = "";
for (const c of s)
out +=
c.charCodeAt(0) < 0x80 ? c.toLowerCase() : c.toUpperCase().toLowerCase();
return out;
}
@ -263,16 +316,16 @@ function jsonMembers(text: string): [string, string][] | undefined {
const out: [string, string][] = [];
let i = 0;
const ws = (): void => {
while (i < text.length && ' \t\n\r'.includes(text[i]!)) i++;
while (i < text.length && " \t\n\r".includes(text[i]!)) i++;
};
const skipString = (): void => {
i++;
while (text[i] !== '"') i += text[i] === '\\' ? 2 : 1;
while (text[i] !== '"') i += text[i] === "\\" ? 2 : 1;
i++;
};
const skipValue = (): void => {
if (text[i] === '"') return skipString();
if (text[i] === '{' || text[i] === '[') {
if (text[i] === "{" || text[i] === "[") {
let depth = 0;
do {
const c = text[i]!;
@ -280,18 +333,18 @@ function jsonMembers(text: string): [string, string][] | undefined {
skipString();
continue;
}
if (c === '{' || c === '[') depth++;
else if (c === '}' || c === ']') depth--;
if (c === "{" || c === "[") depth++;
else if (c === "}" || c === "]") depth--;
i++;
} while (depth > 0);
return;
}
while (i < text.length && !',}] \t\n\r'.includes(text[i]!)) i++;
while (i < text.length && !",}] \t\n\r".includes(text[i]!)) i++;
};
ws();
i++; // {
ws();
while (text[i] !== '}') {
while (text[i] !== "}") {
const k0 = i;
skipString();
const key = JSON.parse(text.slice(k0, i)) as string;
@ -302,7 +355,7 @@ function jsonMembers(text: string): [string, string][] | undefined {
skipValue();
out.push([key, text.slice(v0, i)]);
ws();
if (text[i] === ',') {
if (text[i] === ",") {
i++;
ws();
}
@ -316,7 +369,11 @@ function jsonMembers(text: string): [string, string][] | undefined {
* verifying the release (spec v0.15, §47.1).
*/
export function newReleaseObject(p: Profile, r: Release): Uint8Array {
return encodeRelease({ round: r.round, signature: r.signature, chainHash: p.chainHash });
return encodeRelease({
round: r.round,
signature: r.signature,
chainHash: p.chainHash,
});
}
// ---------------------------------------------------------------------------
@ -351,7 +408,8 @@ export function encodedRelease(b: Uint8Array): ReleaseSupplier {
// A local release archive (informative, spec v0.15, §50)
// The text of what a read threw, for an error of the archive.
const errText = (err: unknown): string => (err instanceof Error ? err.message : String(err));
const errText = (err: unknown): string =>
err instanceof Error ? err.message : String(err);
interface ArchiveHeader {
chainHash: Uint8Array;
@ -378,7 +436,8 @@ function encodeArchiveWire(e: Encoder, h: ArchiveHeader): void {
function decodeArchiveWire(d: Decoder): ArchiveHeader {
const h: ArchiveHeader = { chainHash: new Uint8Array(0), first: 0, count: 0 };
const pairs = d.map(ARCHIVE_KEYS);
if (pairs !== ARCHIVE_KEYS) throw new Error(`${pairs} keys, want all ${ARCHIVE_KEYS}`);
if (pairs !== ARCHIVE_KEYS)
throw new Error(`${pairs} keys, want all ${ARCHIVE_KEYS}`);
for (let want = 0; want < ARCHIVE_KEYS; want++) {
const k = d.key();
if (k !== want) throw new Error(`key ${k} where key ${want} was expected`);
@ -414,7 +473,11 @@ function decodeArchiveWire(d: Decoder): ArchiveHeader {
* signatures follow it, one after another, each with the length of a
* signature of the chain, and a round the archive lacks is written as zeros.
*/
export function encodeArchiveHeader(chainHash: Uint8Array, first: number, count: number): Uint8Array {
export function encodeArchiveHeader(
chainHash: Uint8Array,
first: number,
count: number,
): Uint8Array {
if (
chainHash.length !== 32 ||
!Number.isSafeInteger(first) ||
@ -423,7 +486,9 @@ export function encodeArchiveHeader(chainHash: Uint8Array, first: number, count:
count < 1 ||
first > MAX_SAFE_UINT - count + 1
) {
throw new Error(`provider: archive header: chain hash of ${chainHash.length} bytes, rounds ${first} to ${first} + ${count} - 1`);
throw new Error(
`provider: archive header: chain hash of ${chainHash.length} bytes, rounds ${first} to ${first} + ${count} - 1`,
);
}
const e = new Encoder();
encodeArchiveWire(e, { chainHash, first, count });
@ -469,8 +534,12 @@ export class ReleaseArchive implements ReleaseSupplier {
async supply(p: Profile, round: number): Promise<Uint8Array> {
const unavailable = (detail: string): DateKeysError =>
new DateKeysError('ERR_RELEASE_UNAVAILABLE', `provider: release archive: ${detail}`);
const size = this.#data instanceof Uint8Array ? this.#data.length : this.#data.size;
new DateKeysError(
"ERR_RELEASE_UNAVAILABLE",
`provider: release archive: ${detail}`,
);
const size =
this.#data instanceof Uint8Array ? this.#data.length : this.#data.size;
let head: Uint8Array;
try {
head = await this.#read(0, Math.min(size, MAX_ARCHIVE_HEADER));
@ -497,20 +566,39 @@ export class ReleaseArchive implements ReleaseSupplier {
const enc = e.out();
/* v8 ignore next 3 -- @preserve: what the strict decoder read encodes back to the same bytes */
if (!equalBytes(enc, head.subarray(0, Math.min(enc.length, head.length)))) {
throw unavailable('its header is not the deterministic encoding of its value');
throw unavailable(
"its header is not the deterministic encoding of its value",
);
}
if (!equalBytes(h.chainHash, p.chainHash)) {
throw unavailable(`archive of chain ${toHex(h.chainHash)}, the pinned profile ${p.id} is chain ${toHex(p.chainHash)}`);
throw unavailable(
`archive of chain ${toHex(h.chainHash)}, the pinned profile ${p.id} is chain ${toHex(p.chainHash)}`,
);
}
if (h.first === 0 || h.count === 0 || round < h.first || round - h.first >= h.count) {
throw unavailable(`round ${round} is not in the archive, which holds ${h.count} rounds from ${h.first}`);
if (
h.first === 0 ||
h.count === 0 ||
round < h.first ||
round - h.first >= h.count
) {
throw unavailable(
`round ${round} is not in the archive, which holds ${h.count} rounds from ${h.first}`,
);
}
if (p.provider !== 'drand') throw unavailable(`profile ${p.id}: provider ${goQuote(p.provider)} is not drand: ERR_UNKNOWN_PROFILE`);
if (p.scheme !== QUICKNET_SCHEME) throw unavailable(`profile ${p.id}: ${goQuote(p.scheme)} is not a drand scheme: ERR_UNKNOWN_PROFILE`);
if (p.provider !== "drand")
throw unavailable(
`profile ${p.id}: provider ${goQuote(p.provider)} is not drand: ERR_UNKNOWN_PROFILE`,
);
if (p.scheme !== QUICKNET_SCHEME)
throw unavailable(
`profile ${p.id}: ${goQuote(p.scheme)} is not a drand scheme: ERR_UNKNOWN_PROFILE`,
);
const n = QUICKNET_SIGNATURE_LEN;
const want = BigInt(enc.length) + BigInt(h.count) * BigInt(n);
if (BigInt(h.count) > (1n << 62n) / BigInt(n) || BigInt(size) !== want) {
throw unavailable(`${size} bytes, its header announces ${h.count} rounds of ${n} bytes`);
throw unavailable(
`${size} bytes, its header announces ${h.count} rounds of ${n} bytes`,
);
}
let sig: Uint8Array;
try {
@ -518,7 +606,8 @@ export class ReleaseArchive implements ReleaseSupplier {
} catch (err) {
throw unavailable(errText(err));
}
if (sig.every((x) => x === 0)) throw unavailable(`round ${round} is missing: its entry is zeros`);
if (sig.every((x) => x === 0))
throw unavailable(`round ${round} is missing: its entry is zeros`);
return encodeRelease({ chainHash: h.chainHash, round, signature: sig });
}
}

25
testdata/README.md vendored

@ -51,8 +51,8 @@ Conventions for every file:
| `vectors/dk1.json` | canonical `dk1_` strings, and rejected encodings with their code | §18, §19, §66 |
| `vectors/cbor.json` | the CBOR profile, and one block of vectors per schema, CONTROL_CBOR in the three formats | §58, CDDL |
| `vectors/tlock_ibe.json` | H2 of the tlock IBE: the serialization of an element of GT | §63 step 11 |
| `vectors/release.json` | the release object, the content of a `.dkr` file, and drand's JSON, each with the result of step 10; the lookups of a local release archive | §47.1, §50, §63 step 10 (v0.15) |
| `releases/<round>.dkr` | the release object of each published round of the tests: 1000, 1001, 1004 and 2000 | §47.1 (v0.15) |
| `vectors/release.json` | the release object and drand's JSON, each with the result of step 10; the lookups of a local release archive | §47.1, §50, §63 step 10 (v0.15) |
| `releases/<round>.cbor` | the release object of each published round of the tests: 1000, 1001, 1004 and 2000 | §47.1 (v0.15) |
| `releases/archive_1000_1004.bin` | a local release archive, the informative format of §50, of rounds 1000 to 1004, two of them missing | §50 (v0.15) |
| `vectors/tlock_steps.json` | steps 10 and 11 for Quicknet value by value: the message of a round, its hash to G1, and the decryption of a tlock stanza with H2, H4, H3 and the file key | §63 steps 10 and 11 (v0.14) |
| `vectors/padding.json` | the padding of formats 2 and 3: P for each content length L, and the length of PAYLOAD_AGE | §29.1 |
@ -320,8 +320,9 @@ writes them, give `0118eea9d5971745f71e3c94926f1717` and another FK_TIME.
## `vectors/release.json` and `releases/`
The release object of spec v0.15, §47.1: the release of a round as a file,
`.dkr`, that a person keeps next to the capsule. It is deterministic CBOR with
The release object of spec v0.15, §47.1: the release of a round as data,
the answer of the Release API, an entry of a Release Cache and a release the
caller gives from a file or from an archive. It is deterministic CBOR with
the profile of §58, a map of five keys, all required:
```text
@ -332,10 +333,11 @@ the profile of §58, a map of five keys, all required:
4 → signature (1 to 96 bytes; 48 in Quicknet)
```
The object of a round above 255 measures 111 bytes. `releases/<round>.dkr` is
the object of each published round the fixtures use, 1000, 1001, 1004 and
The object of a round above 255 measures 111 bytes. `releases/<round>.cbor`
is the object of each published round the fixtures use, 1000, 1001, 1004 and
2000, with the Quicknet chain hash: the release that opens each fixture, as a
file.
file. The protocol gives the object no file extension; `.cbor` is the generic
one of CBOR (RFC 8949).
`release.json` has three lists:
@ -364,7 +366,7 @@ file.
each round, one after another; a round the archive lacks is 48 zero bytes.
This one holds rounds 1000 to 1004, and 1002 and 1003 are zeros. Each lookup
gives a `round` and its `result`: `ok` with the `encoding` of the release
object the archive supplies, the `.dkr` of that round, or
object the archive supplies, `releases/<round>.cbor` for that round, or
`ERR_RELEASE_UNAVAILABLE` for a round the archive lacks or does not cover.
The texts are those of the reference, for an implementation that wants to
@ -848,12 +850,13 @@ reading flow (`capsule.Open`, §63) must fail.
cases only, is the chain the release object names when it is not the
Quicknet chain.
- `source` (v0.15): what kind of source answers, spec v0.15 §63 step 9:
- `supplied`: the release is in the caller's hand, as a `.dkr` would be.
- `supplied`: the release is in the caller's hand, as a release object
read from a file would be.
The reader is given the release object of `release`, with the Quicknet
chain hash unless `chain_hash` says another, and decodes and verifies it
at step 10: one that breaks a rule of step 10 gets the code of step 10.
The clock is not compared with the round time (step 9.c): with a `now`
before it, the capsule opens all the same. Every case but three is
before it, the capsule opens all the same. Every case but two is
`supplied`.
- `network`: a network source, such as a drand relay. It is never asked
before the round time (step 9.c), and it verifies its answer with the
@ -888,7 +891,7 @@ reading flow (`capsule.Open`, §63) must fail.
Every case reproduces offline: the recorded release stands in for the network.
What v0.15 changed in this file: every case gained `source`, `supplied` but
for three; the further case "round not reached yet", a valid release of round
for two; the further case "round not reached yet", a valid release of round
1000 and a clock one nanosecond before its round time, was
`ERR_RELEASE_UNAVAILABLE` at step 9 and now opens (`ok`, step 0, with
`network` true), because the release is in the caller's hand; and four cases

@ -1,8 +1,8 @@
{
"module": "g.activething.com/go/DateKeys",
"commit": "3c3e7370c2b6319e1fc261a1b26c88f1d43e3fa6",
"commit": "fe5088549186465e08d55f89680be986076bf165",
"files": {
"README.md": "4cf931dfa0a1d3a33dd24cef95b3f4fc30d190ed912ee8d30808ac9bc3aca930",
"README.md": "d26b3f507edf5afe89600f063cb509b9ef62a908b93ed476bff964b5a443bf4b",
"fixtures/empty_payload.dkc": "871e9bf05b52bbae17f3adfbbf97b46e7f0e53aa8f57bcaa506e43f36f53a9d4",
"fixtures/empty_payload.inspect.json": "373e5d012b023ad58bbb54cbdffe0bed9e50c637438a4083ddb74d5414c59f59",
"fixtures/empty_payload.json": "d1fc459ab76d4ee0231a8c6b7dfc212fcf8da90e7a392b30133f646b3a9df4db",
@ -119,10 +119,10 @@
"fixtures/time_only_extensions.inspect.json": "6f957b028da8a4a495b5e951ced0b91e0678128dac4e962b02d024b9439a0ba1",
"fixtures/time_only_extensions.json": "4bb636805cc681ba1c74aa426f5afda72580f4350929a720da49eeccee3eb2be",
"fixtures/time_only_extensions.plaintext": "1129768e195e2f1e50b7a6f926b6eebef120212c29b5642c8a662c503b2a9131",
"releases/1000.dkr": "5d2e86210d2e8d64ce36e55edf3ff6c8997fda4bc06fec7fd5feb0dd6cab8293",
"releases/1001.dkr": "2b55dcc09dfe8fa97192aa6d9a85f9fc50206142d52f66329261cfe9e03de755",
"releases/1004.dkr": "aabdffe0fb944d8796528a6682fdbafb448b1e5da164ee039b6c3ee7f354e766",
"releases/2000.dkr": "9e37be0004850faaa8541658a90ee8aba0832fccf2b695df42c89cb3f7de29ff",
"releases/1000.cbor": "5d2e86210d2e8d64ce36e55edf3ff6c8997fda4bc06fec7fd5feb0dd6cab8293",
"releases/1001.cbor": "2b55dcc09dfe8fa97192aa6d9a85f9fc50206142d52f66329261cfe9e03de755",
"releases/1004.cbor": "aabdffe0fb944d8796528a6682fdbafb448b1e5da164ee039b6c3ee7f354e766",
"releases/2000.cbor": "9e37be0004850faaa8541658a90ee8aba0832fccf2b695df42c89cb3f7de29ff",
"releases/archive_1000_1004.bin": "bb53d542abd704f3af9f6436c9178f65bf812a06630607b3aad3a09eaed0cce1",
"vectors/cbor.json": "715c8e7ca88d17c4e68a8764350217f108e96484e59282d384a032169d36bfb8",
"vectors/dk1.json": "e2b849b1f606e7961a8571c305dcd0c4374f03c8943a3a715b20a9a43002ea44",
@ -137,7 +137,7 @@
"vectors/paths.json": "a33ecdbd6a191d693600e18879e15a5813d77b133d46f30c5535aae72eeaeb04",
"vectors/profile_quicknet.json": "e291d5167cdce9b9e24993571a7f349e35fbbcfea793665883ec9ec8d479b498",
"vectors/quicknet_rounds.json": "bf990896dddc51a91e309143fede773adeae918ef47433e0ab6132a4456ce9a8",
"vectors/release.json": "4e9d4902c041d70361bcf6e0f069b963fa91c22e494f3239ffeda2c2688da5b5",
"vectors/release.json": "97bd46e055024a00f6a765f840b47ebfea5dad1e08a88c70d2cf42a77df6d2ba",
"vectors/resolved_ip.json": "7c554e7c3f272a62a89c3f3e97203dc7d5dc50290bbe356f4a8efd44a19eea70",
"vectors/security.json": "6045492767cbeb02fce5b6faf6cd0e179ffe96c898c8c023793e4a138b0277f0",
"vectors/security_cms.json": "4915fa3cfe93b1ad92e91a68bba7517c3b2dc3e33fd9def7340b3a045eff99cf",

@ -1,6 +1,6 @@
{
"spec": "0.15",
"description": "The release object, the content of a .dkr file (spec v0.15, §47.1), and drand's JSON as the input of the caller, each checked against the pinned Quicknet profile and the round of a DateKey as step 10 of spec §63 checks a release that the caller supplies; and the lookups of a local release archive (spec v0.15, §50). See testdata/README.md.",
"description": "The release object (spec v0.15, §47.1), and drand's JSON as the input of the caller, each checked against the pinned Quicknet profile and the round of a DateKey as step 10 of spec §63 checks a release that the caller supplies; and the lookups of a local release archive (spec v0.15, §50). See testdata/README.md.",
"profile": "datekeys:quicknet:v1",
"objects": [
{

Loading…
Cancel
Save

Powered by TurnKey Linux.