From 7446db60848571ef1edf297ae518bc4c4d5fb03e Mon Sep 17 00:00:00 2001 From: dev Date: Wed, 7 Oct 2026 01:55:36 +0200 Subject: [PATCH] testdata at spec-v0.15: the release objects are releases/.cbor Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 +- README.md | 4 +- src/lib/dkc/releaseobject.ts | 205 ++++++++++++++++------ testdata/README.md | 25 +-- testdata/SOURCE.json | 14 +- testdata/releases/{1000.dkr => 1000.cbor} | Bin testdata/releases/{1001.dkr => 1001.cbor} | Bin testdata/releases/{1004.dkr => 1004.cbor} | Bin testdata/releases/{2000.dkr => 2000.cbor} | Bin testdata/vectors/release.json | 2 +- 10 files changed, 172 insertions(+), 80 deletions(-) rename testdata/releases/{1000.dkr => 1000.cbor} (100%) rename testdata/releases/{1001.dkr => 1001.cbor} (100%) rename testdata/releases/{1004.dkr => 1004.cbor} (100%) rename testdata/releases/{2000.dkr => 2000.cbor} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc5d025..2984aba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/README.md b/README.md index 890ea67..b64cfaa 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/lib/dkc/releaseobject.ts b/src/lib/dkc/releaseobject.ts index 4fe7cd1..e01d4d7 100644 --- a/src/lib/dkc/releaseobject.ts +++ b/src/lib/dkc/releaseobject.ts @@ -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 { // integer of 0 to 2^64-1, fails the whole input. async function parseDrandJSON(b: Uint8Array): Promise { 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 { 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 }); } } diff --git a/testdata/README.md b/testdata/README.md index a94e2a3..9e3c532 100644 --- a/testdata/README.md +++ b/testdata/README.md @@ -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/.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/.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/.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/.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/.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 diff --git a/testdata/SOURCE.json b/testdata/SOURCE.json index ed44c4a..58d279b 100644 --- a/testdata/SOURCE.json +++ b/testdata/SOURCE.json @@ -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", diff --git a/testdata/releases/1000.dkr b/testdata/releases/1000.cbor similarity index 100% rename from testdata/releases/1000.dkr rename to testdata/releases/1000.cbor diff --git a/testdata/releases/1001.dkr b/testdata/releases/1001.cbor similarity index 100% rename from testdata/releases/1001.dkr rename to testdata/releases/1001.cbor diff --git a/testdata/releases/1004.dkr b/testdata/releases/1004.cbor similarity index 100% rename from testdata/releases/1004.dkr rename to testdata/releases/1004.cbor diff --git a/testdata/releases/2000.dkr b/testdata/releases/2000.cbor similarity index 100% rename from testdata/releases/2000.dkr rename to testdata/releases/2000.cbor diff --git a/testdata/vectors/release.json b/testdata/vectors/release.json index 4fd148c..f41bb1b 100644 --- a/testdata/vectors/release.json +++ b/testdata/vectors/release.json @@ -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": [ {