The writer of spec v0.11: the area of 32 KiB and the public note

encryptFiles writes the security area of 32 KiB, as rule 13 of 62.1 asks of
a writer of v0.11, and accepts publicNote, the extension datekeys.note of
PUBLIC_HEADER, with the rules of text of 24.1 (note.ts). Another area is for
a generator of test vectors, with testVectors and areaLen, so that the tests
still reproduce byte for byte the fixtures that a writer of v0.10 wrote with
512 bytes. lengths.ts and the page plan L with the new area. A note changed
after writing fails at step 15. npm run verify passes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
main
dev 6 days ago
parent 92b4d196eb
commit 3f80e589fa

@ -9,7 +9,8 @@ Cambios notables de la librería TypeScript y de la página. El proyecto usa ver
- `testdata` se sincroniza con el tag `spec-v0.11` de `datekeys-go` (`ae33434`), y `SPEC_VERSION` pasa a `0.11`. Trae tres fixtures (`format3_signed`, con firma de clave propia; `format3_signed_cms`, con dos certificados sellados; `format3_sealed`, con firma y sello RFC 3161) y tres ficheros de vectores (`ed25519_strict.json`, `security_cms.json` y `locator.json`). El corpus de mutaciones pasa a 210 casos, con uno fuera del §64: una firma de `alg` 1 que no verifica, F2. `ibe-vectors.json` añade los tres fixtures y rehace los de `format3_signature_unsupported` y `format3_seal_unsupported`; `mutation-texts.json` se rehace con el `capsule.Open` de esa referencia.
- **La firma de clave propia, `alg` 1** (§29.8, §29.9), portada: `ed25519strict.ts` comprueba las cuatro condiciones del perfil estricto con la aritmética de `@noble/curves` (que solo ofrece la ecuación con cofactor) y da la respuesta de Go en los 18 vectores de `ed25519_strict.json`; `author.ts` calcula `payload_commit`, `control_commit`, `head_digest`, `signers_digest`, `AUTHOR_MESSAGE` y su código, y los registros de `format3_signed` los confirman. `evaluateSecurity(área, contexto)` da F2, F3 y F4, `open` pasa el contexto del control y del head, y `OpenOptions.authorKeys` son las claves que la persona guardó. Los dos módulos usan solo `@noble/curves` y `@noble/hashes`, que ya iban en el bundle: ningún paquete nuevo, y entran en la lista de quien puede importar noble.
- **La firma con certificados, `alg` 2, y el sello RFC 3161, `seal_type` 2** (§29.10, §29.11), portados sin dependencias nuevas: `der.ts` comprueba el DER byte a byte, `cms.ts` lee la firma CMS y el token con la tabla cerrada de algoritmos (RSA PKCS #1 y PSS en `BigInt`, síncrono, y ECDSA con la aritmética de `@noble/curves`) y `securitycms.ts` da F1, F2, F5 y F6 con los firmantes nombrados, y S1 a S5 con la autoridad del sello. `evaluateSecurity` los devuelve con su `detail`, `verdictLines` escribe las líneas de F6 y S4, y `open` pasa la hora de la ronda. Reproducen los 22 casos de `security_cms.json`, con los resultados de cada firmante, y los fixtures `format3_signed_cms` y `format3_sealed`. `testing/cmsbuild.ts` construye firmas y tokens de prueba con WebCrypto, y `cms.test.ts` porta los casos hostiles de Go.
- **Lo que esta biblioteca no hace todavía:** el localizador del §44.1 (la extensión `datekeys.capsule` de la `.dkk`, su sobre y su relleno) y la página de firma. `locator.json` solo se comprueba en su estructura.
- **El escritor de la v0.11** (§29.2, §24.1, §62.1 regla 13): `encryptFiles` escribe el área de seguridad de 32 KiB (`AREA_LEN` pasa de 512 a 32768) y acepta `publicNote`, la extensión `datekeys.note` de la cabecera pública (`note.ts`: `checkNote`, `newNote`, `publicNote`, con las reglas de texto del autor declarado). Otra área solo la escribe un generador de vectores, con `testVectors` y `areaLen`, y así los tests reproducen byte a byte los fixtures que escribió un escritor de la v0.10, de 512 bytes. `lengths.ts` y la página calculan L con el área nueva. Una nota cambiada después de escribir la cápsula falla en el paso 15.
- **Lo que esta biblioteca no hace todavía:** el escritor no firma ni pide sellos (`alg` 1, `alg` 2 y RFC 3161 solo se leen y verifican), la página no pide ni muestra la nota, y el localizador del §44.1 (la extensión `datekeys.capsule` de la `.dkk`, su sobre y su relleno) y la página de firma. `locator.json` solo se comprueba en su estructura.
El formato 3 de la especificación 0.10, según `PLAN_formato3_ts.md` (en `../docs`). La versión que lo publique la decide el autor.

@ -1,6 +1,6 @@
# datekeys-ts
Implementación en TypeScript del protocolo DateKeys (formato 3 de la v0.10; de la v0.11, la firma de clave propia, la firma con certificados y el sello de tiempo; el localizador está pendiente) y página de prueba en el navegador. Sustituye al prototipo, archivado en `../archive/prototype` (API Quicknet en Go, CLI tlock y cliente Svelte, commit `4d2b0a1`).
Implementación en TypeScript del protocolo DateKeys (formato 3 de la v0.10; de la v0.11, la firma de clave propia, la firma con certificados y el sello de tiempo, que lee y verifica, y el escritor con el área de 32 KiB y la nota pública; el localizador y firmar al escribir están pendientes) y página de prueba en el navegador. Sustituye al prototipo, archivado en `../archive/prototype` (API Quicknet en Go, CLI tlock y cliente Svelte, commit `4d2b0a1`).
La implementación de referencia es la librería Go `g.activething.com/go/DateKeys`, en `../datekeys-go`. Los planes y el estado del trabajo están en `../docs`, el repositorio privado de documentación del proyecto.

@ -20,9 +20,12 @@ export const AREA_UNIT = 512;
export const MAX_AREA_LEN = 128 * AREA_UNIT;
/**
* The size of the security area that writers of this version write, always,
* whatever the capsule holds (spec §29.2, §62.1 rule 13).
* whatever the capsule holds (spec v0.11, §29.2, §62.1 rule 13): 32 KiB. A
* writer of v0.10 wrote AREA_UNIT, 512 bytes, which a reader still accepts.
*/
export const AREA_LEN = 512;
export const AREA_LEN = 64 * AREA_UNIT;
/** The area of a capsule whose creator expressly asked for a larger one because the signatures do not fit in AREA_LEN: 64 KiB. */
export const LARGE_AREA_LEN = MAX_AREA_LEN;
/** The maximum of HEAD_LEN, 16 MiB. */
export const MAX_HEAD_LEN = 16 << 20;

@ -17,6 +17,8 @@ import { type Extension, type ExtensionRegistry, ExtensionSet } from './extensio
import { FORMAT_3 } from './framing.ts';
import { decodeHead, encodeHead, type Head, type HeadFile } from './head.ts';
import { TIME_AND_KEY, TIME_ONLY } from './header.ts';
import { AREA_LEN, AREA_UNIT } from './body.ts';
import { publicNote } from './note.ts';
import { bodyLength, capsuleLength, headComment, headLength, type HeadShape, mtimeSeconds } from './lengths.ts';
import { accessIdentity, open, type OpenOptions, payloadIdentity, timeIdentity } from './open.ts';
import { BLOQUE256, MAX_PAYLOAD_LENGTH, type Padding, REFORZADO } from './padding.ts';
@ -151,6 +153,9 @@ describe('encryptFiles, the fixtures of format 3 of the Go reference', () => {
policy: keyed ? TIME_AND_KEY : TIME_ONLY,
newPortableKey: dkk !== undefined,
padding: fx.padding,
// The fixtures of format 3 were written by a writer of v0.10, with the area of 512 bytes, which only a generator of test vectors writes now.
testVectors: true,
areaLen: AREA_UNIT,
...(fx.comment === undefined ? {} : { comment: fx.comment }),
...(fx.declared_author === undefined ? {} : { author: fx.declared_author }),
}),
@ -185,6 +190,62 @@ describe('encryptFiles, the fixtures of format 3 of the Go reference', () => {
});
});
// Spec v0.11 §29.2, §62.1 rule 13: a writer writes the security area of 32 KiB, whatever the capsule holds, and only a generator of test
// vectors writes another; §24.1: the public note is an extension of PUBLIC_HEADER that header_binding ties to the control.
describe('encryptFiles, the writer of spec v0.11', () => {
it('writes the area of 32 KiB, and the capsule opens with the empty security area', async () => {
const res = await encryptFiles([source('nota.txt', 'Hola.\n', 1_790_000_000_000)], options());
const { head } = await openFiles(res.dkc!, R1000);
expect(head.files).toHaveLength(1);
const sink = new MemorySink();
const r = await open(res.dkc!, opening(R1000, { sink }));
expect([r.error, r.areaLen, r.verdicts?.signature, r.verdicts?.seal]).toEqual([undefined, AREA_LEN, 'F0', 'S0']);
expect(AREA_LEN).toBe(32768);
expect(res.length).toBeGreaterThan(AREA_LEN);
// The area of the writers of v0.10 is for a generator of test vectors, and only in multiples of its unit.
const old = await encryptFiles([source('a', 'x')], options({ testVectors: true, areaLen: AREA_UNIT }));
const oldOpened = await open(old.dkc!, opening(R1000, { sink: new MemorySink() }));
expect([oldOpened.error, oldOpened.areaLen]).toEqual([undefined, AREA_UNIT]);
for (const [name, extra] of [
['512 without testVectors', { areaLen: AREA_UNIT }],
['an area that is not a multiple of 512', { testVectors: true, areaLen: 1000 }],
['an area above 64 KiB', { testVectors: true, areaLen: 66_048 }],
] as const) {
expect((await failure(encryptFiles([source('a', 'x')], options({ ...extra })))).message, name).toMatch(/the security area is 32768 bytes/);
}
});
it('writes the public note in PUBLIC_HEADER, which anyone reads and header_binding ties to the control', async () => {
const res = await encryptFiles([source('a', 'x')], options({ publicNote: 'Cartas del viaje a Lisboa' }));
const insp = await open(res.dkc!, opening(R1000, { sink: new MemorySink() }));
expect(insp.error).toBeUndefined();
expect(publicNote(insp.inspection.header!.noncritical)).toBe('Cartas del viaje a Lisboa');
// The same bytes with another letter in the note still inspect, and fail at step 15.
const changed = res.dkc!.slice();
const at = indexOfText(changed, 'Lisboa');
changed.set(new TextEncoder().encode('Lisbon'), at);
const bad = await open(changed, opening(R1000, { sink: new MemorySink() }));
expect(bad.error?.code).toBe('ERR_HEADER_BINDING');
// A note that breaks the rules of text is refused before anything is written.
for (const text of ['a\tb', 'a\nb', ' a', 'a ', 'x'.repeat(1025)]) {
expect((await failure(encryptFiles([source('a', 'x')], options({ publicNote: text })))).message, JSON.stringify(text.slice(0, 8))).toMatch(/public note/);
}
// With no note, or an empty one, there is none.
const none = await encryptFiles([source('a', 'x')], options({ publicNote: '' }));
expect(none.dkc).toBeDefined();
});
});
function indexOfText(hay: Uint8Array, s: string): number {
const needle = new TextEncoder().encode(s);
for (let i = 0; i + needle.length <= hay.length; i++) {
let ok = true;
for (let j = 0; j < needle.length && ok; j++) ok = hay[i + j] === needle[j];
if (ok) return i;
}
throw new Error('not found');
}
describe('encryptFiles', () => {
it('writes the files in the byte order of their paths, whatever the order given, and open gives them back', async () => {
// UTF-16 puts U+10000 before U+FFFD; UTF-8 after.
@ -379,7 +440,7 @@ describe('encryptFiles, invalid inputs', () => {
['a negative size', [{ path: 'a', size: -1, open: a.open }], {}, 'capsule: file "a": negative size -1'],
['no open', [{ path: 'a', size: 1 } as FileSource], {}, 'capsule: file "a": Source.Open is nil'],
['files above L_MAX', [{ path: 'a', size: 2 ** 52, open: a.open }, { path: 'b', size: 2 ** 52, open: a.open }], {}, 'capsule: the files add up to more than 8936830510563328 bytes, the maximum of L'],
['one file of L_MAX bytes', [{ path: 'a', size: MAX_PAYLOAD_LENGTH, open: a.open }], {}, 'capsule: content of 8936830510563968 bytes exceeds L_MAX = 8936830510563328'],
['one file of L_MAX bytes', [{ path: 'a', size: MAX_PAYLOAD_LENGTH, open: a.open }], {}, 'capsule: content of 8936830510596224 bytes exceeds L_MAX = 8936830510563328'],
['path ..', [source('..', 'x')], {}, 'capsule: path "..": R3: segment 1: the segment is two dots'],
['path a/', [source('a/', 'x')], {}, 'capsule: path "a/": R2: segment 2 is empty'],
['path CON.txt', [source('CON.txt', 'x')], {}, 'capsule: path "CON.txt": R6: segment 1: CON is a reserved device name'],

@ -6,6 +6,7 @@
// with its first load.
import { ACCESS_SLOTS } from './age.ts';
import { AREA_LEN } from './body.ts';
import { compareBytes, utf8Bytes, utf8Length } from './bytes.ts';
import { encodeControl } from './control.ts';
import type { Extension } from './extension.ts';
@ -87,8 +88,8 @@ export function capsuleLength(input: CapsuleLengthInput): number {
// head.ts, which this module does not import: its rules of the paths bring
// the Unicode tables).
const LAST_MTIME = 253402300799;
// The frame of BODY and the security area that writers write (body.ts).
const FRAME_AND_AREA = 12 + 512;
// The frame of BODY and the security area that writers write, of 32 KiB (body.ts, spec v0.11 §29.2).
const FRAME_AND_AREA = 12 + AREA_LEN;
/**
* The comment as the writer of format 3 stores it: CR LF, and any lone CR,
@ -201,7 +202,7 @@ export function headLengthOf(files: MeasuredFiles, h: Omit<HeadShape, 'files'>):
/**
* L of a format 3 capsule: the length of its BODY, the frame, the security
* area of 512 bytes, the head and the files (spec §29.2). With it,
* area of 32 KiB, the head and the files (spec §29.2). With it,
* capsuleLength gives the size of the .dkc, since the control of format 3
* has the length of the control of format 2.
*/

@ -0,0 +1,43 @@
// Tests of note.ts, the public note of spec v0.11 §24.1: the same cases as the
// tests of extension.CheckNote, NewNote and Note of the Go reference.
import { describe, expect, it } from 'vitest';
import { checkNote, MAX_NOTE_LEN, newNote, NOTE_ID, publicNote } from './note.ts';
describe('checkNote', () => {
it('accepts a line of 1 to 1024 bytes that meets the rules of the declared author', () => {
expect(() => checkNote('Cartas del viaje a Lisboa')).not.toThrow();
expect(() => checkNote('x'.repeat(MAX_NOTE_LEN))).not.toThrow();
expect(() => checkNote('Ñandú, 日本')).not.toThrow();
});
it.each([
['empty', ''],
['a tab', 'a\tb'],
['a line feed', 'a\nb'],
['a space at the start', ' a'],
['a space at the end', 'a '],
['too long', 'x'.repeat(MAX_NOTE_LEN + 1)],
['a right-to-left override', 'a\u202eb'],
['a zero-width space', 'a\u200bb'],
])('refuses %s', (_name, text) => {
expect(() => checkNote(text)).toThrow(/public note/);
});
});
describe('newNote and publicNote', () => {
it('makes the extension, and reads it back among the noncritical ones', () => {
const e = newNote('Hola');
expect([e.id, e.version, new TextDecoder().decode(e.data)]).toEqual([NOTE_ID, 1, 'Hola']);
expect(publicNote([{ id: 'x.other', version: 1, data: undefined }, e])).toBe('Hola');
expect(publicNote([])).toBeUndefined();
expect(() => newNote('a\nb')).toThrow(/public note/);
});
it('shows nothing for a note that is unusable: another version, no data, bad text or bad UTF-8', () => {
expect(publicNote([{ id: NOTE_ID, version: 2, data: new TextEncoder().encode('v2') }])).toBeUndefined();
expect(publicNote([{ id: NOTE_ID, version: 1, data: undefined }])).toBeUndefined();
expect(publicNote([{ id: NOTE_ID, version: 1, data: new TextEncoder().encode('a\nb') }])).toBeUndefined();
expect(publicNote([{ id: NOTE_ID, version: 1, data: Uint8Array.of(0xff, 0xfe) }])).toBeUndefined();
});
});

@ -0,0 +1,51 @@
// The public note of a capsule (spec v0.11, §24.1): the extension
// datekeys.note, version 1, in the noncritical array of PUBLIC_HEADER. Its
// data is the text in UTF-8, from 1 to 1024 bytes, with no CBOR around it, and
// it meets the rules of text of the declared author of §29.6: one line, no
// tabs and no spaces at the ends. It is public: whoever holds the .dkc reads it
// before the date, and nobody can check who wrote it. As Go's extension.CheckNote,
// NewNote and Note.
import { utf8Length } from './bytes.ts';
import { DateKeysError } from './errors.ts';
import type { Extension } from './extension.ts';
import { checkAuthor } from './pathrule.ts';
/** The extension_id of the public note, and the longest note in bytes. */
export const NOTE_ID = 'datekeys.note';
export const MAX_NOTE_LEN = 1024;
/** The text of a public note is from 1 to 1024 bytes of valid UTF-8 that meets the rules of the declared author (spec §24.1). Throws a DateKeysError when it is not. */
export function checkNote(text: string): void {
const n = utf8Length(text);
if (n < 1 || n > MAX_NOTE_LEN) throw new DateKeysError('ERR_EXTENSION_DATA_INVALID', `a public note of ${n} bytes, not 1 to ${MAX_NOTE_LEN}`);
try {
checkAuthor(text);
} catch (err) {
throw new DateKeysError('ERR_EXTENSION_DATA_INVALID', `a public note that breaks the rules of text: ${(err as Error).message}`);
}
}
/** The extension of a public note for `text`, which must pass checkNote. */
export function newNote(text: string): Extension {
checkNote(text);
return { id: NOTE_ID, version: 1, data: new TextEncoder().encode(text) };
}
/**
* The text of the public note among the noncritical extensions of a
* PUBLIC_HEADER, and undefined when there is none that is usable: a note whose
* data breaks the rules of §24.1 is unusable, and shows nothing (spec §54).
*/
export function publicNote(noncritical: readonly Extension[]): string | undefined {
const e = noncritical.find((x) => x.id === NOTE_ID && x.version === 1);
if (e?.data === undefined) return undefined;
let text: string;
try {
text = new TextDecoder('utf-8', { fatal: true }).decode(e.data);
checkNote(text);
} catch {
return undefined;
}
return text;
}

@ -18,7 +18,8 @@ import { Decrypter, Encrypter, type ReadableStreamWithSize } from 'age-encryptio
import { ACCESS_TYPE_X25519, type AccessKey } from './accesskey.ts';
import { ACCESS_SLOTS, ageStanzas, checkAccessStanzas, checkTimeStanzas, parseAgeHeader } from './age.ts';
import { decryptAll } from './agefile.ts';
import { AREA_LEN, BODY_FRAME_SIZE, bodyFrameBytes, contentLength, MAX_HEAD_LEN, parseBodyFrame } from './body.ts';
import { AREA_LEN, AREA_UNIT, BODY_FRAME_SIZE, bodyFrameBytes, contentLength, MAX_AREA_LEN, MAX_HEAD_LEN, parseBodyFrame } from './body.ts';
import { newNote } from './note.ts';
import { compareBytes, copyBytes, equalBytes, goQuote, toHex, utf8Bytes, utf8Length } from './bytes.ts';
import { decodeControl, encodeControl } from './control.ts';
import { compareInstants, type DateKey, formatRFC3339Nano, type Instant, isInstant, resolveDateKey, roundTime } from './datekey.ts';
@ -110,11 +111,21 @@ export interface EncryptOptions {
readonly headCritical?: readonly Extension[];
readonly headNoncritical?: readonly Extension[];
/**
* Lets encrypt write format 2, which only a generator of test vectors may
* write (spec §62.1 rule 1, §70). encryptFiles, which writes format 3,
* ignores it.
* The public note of the capsule (spec v0.11, §24.1): the extension
* datekeys.note in PUBLIC_HEADER, a line of text that anyone who holds the
* .dkc reads before the date and that nobody can check. It must pass
* checkNote. Absent or '' for none. The writer SHOULD warn the person that
* it is public and that, with the date, it can identify someone.
*/
readonly publicNote?: string;
/**
* Lets encrypt write format 2, and encryptFiles another area than the 32 KiB
* of AREA_LEN, which only a generator of test vectors may write (spec §62.1
* rules 1 and 13, §70).
*/
readonly testVectors?: boolean;
/** The size of the security area, with testVectors only: 512 reproduces the fixtures of the writers of v0.10. */
readonly areaLen?: number;
/** The clock. Required, and called once. */
readonly now: () => Instant;
/**
@ -204,6 +215,11 @@ export async function writeFiles(files: readonly FileSource[], opts: EncryptOpti
if (typeof opts !== 'object' || opts === null) throw new TypeError('encrypt: options are required');
if (opts.length !== undefined) throw new Error('capsule: EncryptOptions.Length is for Encrypt: EncryptFiles computes L from the files');
checkOptions(opts);
// Spec §62.1 rule 13: the area is 32 KiB, and only a generator of test vectors writes another.
const areaLen = opts.areaLen ?? AREA_LEN;
if (areaLen !== AREA_LEN && (opts.testVectors !== true || !Number.isInteger(areaLen) || areaLen < AREA_UNIT || areaLen > MAX_AREA_LEN || areaLen % AREA_UNIT !== 0)) {
throw new Error(`capsule: the security area is ${AREA_LEN} bytes: another size is for a generator of test vectors, a multiple of ${AREA_UNIT} up to ${MAX_AREA_LEN} (spec §62.1 rule 13)`);
}
const sources = copySources(files);
const comment = checkText(opts.comment, 'comment');
const author = checkText(opts.author, 'author');
@ -217,7 +233,7 @@ export async function writeFiles(files: readonly FileSource[], opts: EncryptOpti
if (measured.length > MAX_HEAD_LEN) throw new Error(`capsule: the head is ${measured.length} bytes, more than ${MAX_HEAD_LEN}: fewer files or shorter paths`);
selfCheckHead(measured);
const content = head.files.at(-1)?.end ?? 0;
const length = BODY_FRAME_SIZE + AREA_LEN + measured.length + content;
const length = BODY_FRAME_SIZE + areaLen + measured.length + content;
checkLength(length, s.code);
// In memory, the limit is known before reading anything.
if (opts.output === undefined) {
@ -253,11 +269,11 @@ export async function writeFiles(files: readonly FileSource[], opts: EncryptOpti
if (v.signature !== 'F0' || v.seal !== 'S0') {
throw new Error(`capsule: self-check: the reader finds the verdicts ${v.signature} and ${v.seal} in this security area`);
}
const frame = bodyFrameBytes({ areaLen: AREA_LEN, securityLen: security.length, headLen: headBytes.length });
const frame = bodyFrameBytes({ areaLen, securityLen: security.length, headLen: headBytes.length });
selfCheck('capsule: self-check', () => checkHeadEnd(final, contentLength(parseBodyFrame(frame, length), length)));
// Step 16: BODY, and the second reading of each file.
const area = new Uint8Array(AREA_LEN);
const area = new Uint8Array(areaLen);
area.set(security);
const res = await seal(s, FORMAT_3, length, draws, state, () => bodyContent([frame, area, headBytes], final.files, sources, order));
return { ...res, head: final };
@ -455,6 +471,7 @@ async function newSealer(opts: EncryptOptions, length: number): Promise<Sealer>
const unlockAt: Instant = { seconds: opts.unlockAt.seconds, nanos: opts.unlockAt.nanos };
const critical = copyExtensions(opts.critical);
const noncritical = copyExtensions(opts.noncritical);
if (opts.publicNote !== undefined && opts.publicNote !== '') noncritical.push(newNote(opts.publicNote));
const controlCritical = copyExtensions(opts.controlCritical);
const controlNoncritical = copyExtensions(opts.controlNoncritical);
const { policy, output, progress } = opts;

@ -5,7 +5,7 @@
import { describe, expect, it } from 'vitest';
import { parseDateKey } from '../dkc/datekey.ts';
import { TIME_AND_KEY, TIME_ONLY } from '../dkc/header.ts';
import { MAX_HEAD_LEN } from '../dkc/body.ts';
import { AREA_LEN, MAX_HEAD_LEN } from '../dkc/body.ts';
import { bodyLength, capsuleLength, headLength } from '../dkc/lengths.ts';
import { MAX_PAYLOAD_LENGTH, paddedLength, REFORZADO } from '../dkc/padding.ts';
import { formatX25519Recipient } from '../dkc/recipient.ts';
@ -51,9 +51,9 @@ describe('planCapsule', () => {
]);
expect(parseDateKey(p.dk1)).toEqual(p.dateKey);
expect([p.policy, p.recipients, p.portable, p.ambiguous]).toEqual([TIME_ONLY, [], false, false]);
// Format 3: BODY holds the frame, the area of 512 bytes, the head of 111
// Format 3: BODY holds the frame, the area of 32 KiB, the head of 111
// bytes with the path and no mtime, and the file.
const L = 12 + 512 + 111 + 1000;
const L = 12 + AREA_LEN + 111 + 1000;
expect([p.files, p.comment, p.author, p.length, p.paddedLength]).toEqual([[{ path: 'nota.txt', size: 1000 }], '', '', L, paddedLength(L, REFORZADO)]);
expect(p.length).toBe(bodyLength({ files: p.files }));
expect(p.size).toBe(capsuleLength({ profileId: 'datekeys:quicknet:v1', round: 1000, policy: TIME_ONLY, length: L }));
@ -74,7 +74,7 @@ describe('planCapsule', () => {
expect([p.files, p.comment, p.author]).toEqual([files, 'Feliz\ncumpleaños\n', 'Ana']);
const shape = { files, comment: 'Feliz\r\ncumpleaños\r', author: 'Ana' };
expect(p.length).toBe(bodyLength(shape));
expect(p.length).toBe(12 + 512 + headLength(shape) + 70_037);
expect(p.length).toBe(12 + AREA_LEN + headLength(shape) + 70_037);
// A comment alone, with no files, is a capsule too (decision 9).
const alone = planCapsule(input({ files: chooseFiles([]), comment: 'Solo esto.' }), GENESIS_MS);
expect(alone.ok && [alone.plan.files, alone.plan.length]).toEqual([[], bodyLength({ files: [], comment: 'Solo esto.' })]);
@ -192,7 +192,7 @@ describe('planCapsule', () => {
[x25519PublicKey(sampleIdentity(1)), x25519PublicKey(sampleIdentity(2))],
true,
]);
expect(ok.ok && ok.plan.size).toBe(capsuleLength({ profileId: 'datekeys:quicknet:v1', round: 1000, policy: TIME_AND_KEY, length: 12 + 512 + 111 + 1000 }));
expect(ok.ok && ok.plan.size).toBe(capsuleLength({ profileId: 'datekeys:quicknet:v1', round: 1000, policy: TIME_AND_KEY, length: 12 + AREA_LEN + 111 + 1000 }));
expect(keyed('', true).ok).toBe(true);
expect(keyed(lines(15), true).ok).toBe(true);
expect(keyed(lines(16), false).ok).toBe(true);

Loading…
Cancel
Save

Powered by TurnKey Linux.