Fixes T9, T11 and T12 of the review of the session of 1 and 2 October:
- T9: EncryptOptions no longer has testVectors nor areaLen, with which any
caller could write format 2, or an area of 512 bytes, which rule 13
forbids and which tells that the capsule has no signature (§55.2). What
only a generator of test vectors asks, as Go's TestVectors, is the
TestVectors argument of the core of writer.ts, which only the helpers of
testing/encrypt.ts pass: encryptVectors and encryptWith write format 2,
and encryptFilesWith another area, with which the tests still reproduce
byte for byte the fixtures of 512 bytes. encrypt keeps the shape of
capsule.Encrypt: without a generator it fails with the text of Go,
whatever the caller adds. dependencies.test.ts refuses an import of
testing/ from anything but the tests and testing/ itself, check-build.mjs
refuses a test or a module of testing/ in the bundle of the pages, and
note.ts joins the modules that index.ts must not re-export.
- T12: encrypt as a generator refuses a public note and an area with the
text of Go, "capsule: format 2 has no security area or public note: ...",
after the head and the length, as capsule.Encrypt. The errors of the note
carry "capsule: ", and newSealer checks the note, then the profile and the
clock, in the order of Go. The tests compare the texts byte for byte, also
for two faults at once.
- T11: capsuleLength takes the public note and predicts exactly the size of
the .dkc with it: eight notes of 1 to 1024 bytes, across the boundaries of
the heads of CBOR, with both policies and with other extensions.
The 40 texts that capsule.EncryptFiles and extension.CheckNote give at
spec-v0.11 on the same notes and options, taken with an oracle, are those
of this library; HEAD gave another one in 23 of them. npm run verify
passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
expect(err.message,name).toBe('capsule: the security area is 32768 bytes: another size is for a generator of test vectors, a multiple of 512 up to 65536 (spec §62.1 rule 13)');
}
}
// No option of encryptFiles asks for another area: what a caller adds is nothing, and the area is 32 KiB.
['a bad note and no profile',[source('a','x')],{...options(bad),profile: undefined}asunknownasEncryptOptions,tab],
['a bad note and no clock',[source('a','x')],{...options(bad),now: undefined}asunknownasEncryptOptions,tab],
['a bad note and an instant in the past',[source('a','x')],options({...bad,unlockAt: GENESIS}),tab],
['a bad note and policy 7',[source('a','x')],options({...bad,policy: 7}),tab],
['a bad note and padding code 3',[source('a','x')],options({...bad,padding: 3asPadding}),tab],
['a bad note and a bad declared author',[source('a','x')],options({...bad,author:' x'}),tab],
['a bad note and no files',[],options(bad),tab],
['a bad note and Length',[source('a','x')],options({...bad,length: 5}),'capsule: EncryptOptions.Length is for Encrypt: EncryptFiles computes L from the files'],
['no profile and no clock',[source('a','x')],{...options(),profile: undefined,now: undefined}asunknownasEncryptOptions,'capsule: EncryptOptions.Profile is required'],
['no clock and an instant in the past',[source('a','x')],{...options({unlockAt: GENESIS}),now: undefined}asunknownasEncryptOptions,'capsule: EncryptOptions.Now is required'],
@ -366,6 +411,27 @@ describe('the lengths of format 3', () => {
}
}
});
});
// Spec v0.11 §24.1: the public note is an extension of PUBLIC_HEADER, which capsuleLength measures with the others,
// around the boundaries of the heads of CBOR and up to its 1024 bytes.
it('gives the exact size of the .dkc with a public note',async()=>{
constnotes=['x','a'.repeat(23),'a'.repeat(24),'b'.repeat(255),'c'.repeat(256),'Cartas del viaje a Lisboa · 2026','ñ'.repeat(512),'\u{1f600}'.repeat(256)];
`encrypt: a capsule of ${16+121+458+payloadAgeLength(paddedLength(MAX_MEMORY_DKC,REFORZADO))} bytes needs EncryptOptions.output; in memory the limit is ${MAX_MEMORY_DKC}`,
`encrypt: a capsule of ${16+121+458+payloadAgeLength(paddedLength(MAX_MEMORY_DKC,REFORZADO))} bytes needs EncryptOptions.output; in memory the limit is ${MAX_MEMORY_DKC}`,
]);
]);
});
});
// Spec §62.1 rule 1, §70: only a generator of test vectors writes format
// Spec §62.1 rule 1, §70: only a generator of test vectors writes format 2, which has no head, no security area and
// 2, and format 2 has no head; the texts of capsule.Encrypt.
// no public note; the texts of capsule.Encrypt, in its order: the generator, the head, the length, the area and the
it('writes format 2 only for a generator of test vectors, and with no head',async()=>{
// note, and then the options of every writer.
it('writes format 2 only for a generator of test vectors, and with no head, no area and no note',async()=>{
constgenerator='capsule: Encrypt writes format 2, which only a generator of test vectors may write (spec §62.1 rule 1): EncryptFiles writes format 3';
constgenerator='capsule: Encrypt writes format 2, which only a generator of test vectors may write (spec §62.1 rule 1): EncryptFiles writes format 3';
consthead='capsule: format 2 has no head: Comment, Author and the head extensions are for EncryptFiles';
consthead='capsule: format 2 has no head: Comment, Author and the head extensions are for EncryptFiles';
const{testVectors: _,...plain}=options();
constarea='capsule: format 2 has no security area or public note: AuthorKey, CMSSigner, Sealer, LargeArea and PublicNote are for EncryptFiles';
for(const[label,opts,text]of[
// The encrypt of encrypt.ts is no generator: no option asks for format 2, and a testVectors of the caller is nothing.
it('rejects options that are not an object, and an output that is not a WritableStream',async()=>{
it('rejects options that are not an object, and an output that is not a WritableStream',async()=>{
expect((awaitfailure(encrypt(te.encode('x'),undefinedasunknownasEncryptOptions))).message).toBe('encrypt: options are required');
expect((awaitfailure(encryptVectors(te.encode('x'),undefinedasunknownasEncryptOptions))).message).toBe('encrypt: options are required');
expect((awaitfailure(encrypt(te.encode('x'),{...options(),output:{}asWritableStream<Uint8Array>}))).message).toMatch(/output is not a WritableStream/);
expect((awaitfailure(encryptVectors(te.encode('x'),{...options(),output:{}asWritableStream<Uint8Array>}))).message).toMatch(/output is not a WritableStream/);
expect((awaitfailure(encrypt(te.encode('x'),{...options(),progress: 3asunknownas()=>void}))).message).toMatch(/progress is not a function/);
expect((awaitfailure(encryptVectors(te.encode('x'),{...options(),progress: 3asunknownas()=>void}))).message).toMatch(/progress is not a function/);
});
});
it('rejects a random draw of the wrong length',async()=>{
it('rejects a random draw of the wrong length',async()=>{
thrownewError(`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)`);
thrownewError(`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)`);