Address the independent review of the alignment

- testdata.test.ts runs the sync-testdata check inside npm test, so a
  truncated or edited vector file fails instead of running fewer cases;
  the quicknet_rounds and inspect_differential blocks also assert that
  they are not empty.
- A boundary test pins the 2 MiB age header limit: exactly 2 MiB is
  accepted and 2 MiB + 1 byte rejected, as filippo.io/age does.
- The page no longer says time_and_key needs a .dkk: the credential is
  a .dkk or the X25519 identity of a recipient (spec §38, §63 step 9.b).
- The landing text says the guarantee rests on drand not revealing the
  signature early, instead of claiming nobody can open a capsule (§4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main
dev 2 weeks ago
parent 4574a65c57
commit 4b0aa60277

@ -111,6 +111,24 @@ describe('parseAgeHeader', () => {
expect(parseAgeHeader(age(INTRO, ...many, MAC)).stanzas).toHaveLength(1024);
});
it('accepts a header of exactly 2 MiB and rejects one of 2 MiB + 1 byte, as filippo.io/age', () => {
// A header of exactly n bytes, MAC line included: the intro, "-> t <arg>",
// k full body lines of 64 columns, the final empty body line and the MAC
// line. The argument length absorbs what the full lines leave.
const header = (n: number): Uint8Array => {
const fixed = INTRO.length + 1 + '-> t '.length + 1 + 1 + MAC.length + 1;
const k = Math.floor((n - fixed - 1) / 65);
return age(INTRO, `-> t ${'a'.repeat(n - fixed - 65 * k)}`, ...Array<string>(k).fill('A'.repeat(64)), '', MAC);
};
const limit = 2 << 20;
const at = header(limit);
expect(at.length).toBe(limit);
expect(parseAgeHeader(at).length).toBe(limit);
const over = header(limit + 1);
expect(over.length).toBe(limit + 1);
expect(() => parseAgeHeader(over)).toThrow(/header exceeds 2 MiB/);
});
it('rejects everything age rejects', () => {
const cases: Uint8Array[] = [
new Uint8Array(0),

@ -0,0 +1,17 @@
import { execFileSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
// vectors.test.ts runs whatever testdata/ holds, so the shared Go vectors are
// only as good as their integrity: a truncated or edited file must fail here
// rather than pass with fewer cases. The check is the one of
// scripts/sync-testdata.mjs: every file matches the SHA-256 recorded in
// testdata/SOURCE.json for the pinned datekeys-go commit, with none missing
// and none extra.
describe('testdata', () => {
it('matches testdata/SOURCE.json byte for byte, with no missing or extra file', () => {
const root = fileURLToPath(new URL('../../../', import.meta.url));
const out = execFileSync(process.execPath, ['scripts/sync-testdata.mjs', 'check'], { cwd: root, encoding: 'utf8' });
expect(out).toMatch(/^testdata: \d+ files match g\.activething\.com\/go\/DateKeys at [0-9a-f]{40}/);
});
});

@ -182,6 +182,10 @@ describe('vectors/quicknet_rounds.json', () => {
expect(f.profile).toBe(QUICKNET_ID);
});
it('is not empty', () => {
expect(f.vectors.length).toBeGreaterThan(0);
});
it.each(f.vectors.map((v) => [v.name, v] as const))('%s', async (_n, v) => {
const p = quicknet();
const at = parseRFC3339(v.requested);
@ -583,6 +587,10 @@ describe('vectors/inspect_differential.json', () => {
for (const b of f.bases) expect(hx(await sha256(readBytes(`fixtures/${b.file}`))), b.file).toBe(b.sha256);
});
it('is not empty', () => {
expect(f.mutations.length).toBeGreaterThan(0);
});
it.each(f.mutations.map((c) => [c.index, c.kind, c.base, c.result, c] as const))('#%i %s of %s: %s', async (_i, _k, _b, _r, c) => {
const view = inspectView(await inspect(capsuleOf({ base: c.base, edits: c.edits })));
expect({ valid: view.valid, error: view.error ?? 'ok', step: view.valid ? undefined : view.checks.at(-1)!.step }).toEqual({

@ -81,7 +81,7 @@ export function errorGloss(code: ErrorCode): string {
case 'ERR_RELEASE_INVALID':
return 'La firma de la ronda no verifica con el perfil fijado.';
case 'ERR_ACCESS_REQUIRED':
return 'La política exige una clave de acceso .dkk.';
return 'La política exige una credencial de acceso: una clave .dkk o la identidad X25519 de un destinatario.';
case 'ERR_ACCESS_INVALID':
return 'La clave de acceso no abre esta cápsula.';
case 'ERR_POLICY_STRUCTURE_MISMATCH':
@ -103,7 +103,7 @@ export function policyGloss(policy: string): string {
case 'time_only':
return 'Solo tiempo: se abre con la firma pública de la ronda.';
case 'time_and_key':
return 'Tiempo y clave: además de la ronda hace falta una clave de acceso .dkk.';
return 'Tiempo y clave: además de la firma de la ronda hace falta una credencial de acceso, una clave .dkk o la identidad X25519 de un destinatario.';
default:
return 'Política no definida en V1.';
}

@ -15,9 +15,9 @@
<div class="pitch">
<h1>Mira una cápsula DateKeys sin abrirla</h1>
<p class="lead">
Una cápsula <code>.dkc</code> es un fichero cifrado que nadie puede abrir antes de una fecha: la clave depende de
Una cápsula <code>.dkc</code> es un fichero cifrado que no se puede abrir antes de una fecha: la clave depende de
una firma que todavía no existe. La publicará en esa fecha drand, una red pública que emite una firma nueva cada
pocos segundos. El inspector lee la parte pública de la cápsula y comprueba que está bien formada, con los mismos
pocos segundos; la garantía se apoya en que esa red no revele la firma antes de tiempo. El inspector lee la parte pública de la cápsula y comprueba que está bien formada, con los mismos
pasos que la herramienta <code>datekeys inspect</code>.
</p>
<p class="actions">

Loading…
Cancel
Save

Powered by TurnKey Linux.