Fixes T14 of the review of the session of 1 and 2 October:
- README: the table of modules gains author.ts, ed25519strict.ts, der.ts,
cms.ts, securitycms.ts and note.ts, and names Go at spec-v0.11; the row
of the format 3 gives the verdicts of v0.11, X, F0 to F6 and S0 to S5,
where it said X to S2, and says that evaluateSecurity never throws; the
rows of the writers, lengths.ts, index.ts and testing/ say where the test
vectors come from now, the area of 32 KiB and the public note.
- README: the table of runtime dependencies says what noble does for the
signatures and the seals, and the guards list the importers of noble of
v0.11 and the new guards of testing/; the counts of the corpus of
mutations (210 cases, four that open) and of capsule-vectors.json are
those of today.
- security.test.ts no longer says that the library does not reach the
verdicts of v0.11, nor that its texts are those of spec-v0.10.
- CHANGELOG: an entry for the fixes of the review, and what waits for
v0.12: the reader of certificates (T2, T6, T7, T8), the text of an
issuer without a commonName and the test of cms.test.ts that compares it
with itself.
npm run verify passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
der.ts checks DER byte by byte. cms.ts reads the CMS signature and the RFC
3161 token of spec v0.11 29.10 and 29.11 with the closed table of
algorithms: RSA PKCS 1 and PSS with BigInt, ECDSA with the arithmetic of
@noble/curves, no new package. securitycms.ts gives F1, F2, F5 and F6 with
the signers named, and S1 to S5 with the authority of a valid seal.
evaluateSecurity returns them with their detail, and verdictLines writes the
lines of F6 and S4. The 22 cases of security_cms.json and the fixtures
format3_signed_cms and format3_sealed give the verdicts, the signers and the
seal of the Go reference. testing/cmsbuild.ts builds signatures and tokens
with WebCrypto for the hostile cases ported from the Go tests, and the
pending mechanism of the first sync is gone. npm run verify passes.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ed25519strict.ts checks the four conditions of spec v0.11 29.9 on top of the
arithmetic of @noble/curves and gives the answer of Go on the 18 vectors of
ed25519_strict.json. author.ts computes payload_commit, control_commit,
head_digest, signers_digest, AUTHOR_MESSAGE and its code, as the record of
format3_signed says. evaluateSecurity takes the context of the capsule and
gives F2, F3 or F4; open passes it, and OpenOptions.authorKeys are the keys
the person saved. No new package: both modules use @noble/curves and
@noble/hashes, which were already in the bundle.
Alg 2 and the time seal are still read as v0.10 reads them, and the tests say
so with testing/pending.ts. npm run verify passes.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
SPEC_VERSION is 0.11. testdata brings format3_signed, format3_signed_cms and
format3_sealed, and the vectors ed25519_strict.json, security_cms.json and
locator.json; the mutation corpus has 210 cases. ibe-vectors.json adds the
three fixtures and remakes the two that Go regenerated, and
mutation-texts.json is made again with that reference.
This library still reads the security area as a reader of v0.10, so a
signature or a seal that the reference checks gives F1 or S1 here. The
Verdict type and the texts know F2 to F6 and S3 to S5, and the tests state
the gap with testing/pending.ts instead of hiding it; porting the
verification makes that file the identity. npm run verify and
testdata:check pass.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
As the spec v0.11 draft decides after its review (38.1), and as the Go
reference does from 2213b8c:
- dkc/wordkey.ts replaces inspector/wordkey.ts. The salt carries the
capsule_id too, so the same words give another key in each capsule;
the words are lowered with the new LOWERCASE table of pathrule.ts,
loaded on demand; checkWords refuses controls, invisible and
unassigned code points and counts only different words of three
letters or more, with the errors of Go. New vector of 38.1.
- encryptFiles takes the words and derives their key once it has drawn
capsule_id; the creator passes them, and the opener salts them with
the capsule_id of the capsule.
- /create asks for the words twice and shows how they are kept; the form
checks them with the tables of the platform, and the writer again with
those of Unicode 18.0.0.
- The tables, regenerated with the lower case, and testdata synced from
2213b8c; the frozen texts of Go for the invalid options, updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The author wanted a key that people can keep without files. A capsule
"solo con una llave" can now take words the person chooses, at least
six, on /create, and /inspect opens it with them. wordkey.ts derives
the X25519 identity with PBKDF2-SHA256 of Web Crypto, 600 000 rounds,
salted with the chain and the round of the capsule, after making case,
accents and extra spaces not matter; its public key is one more
recipient, so the format does not change. The writer wipes the private
half at once; the opener adds it to the identities it tries.
Checked in Chromium: a capsule created with "Perro luna casa verde
trén mar" and no .dkk opened with "perro LUNA casa verde tren mar",
with the release fetched from drand by the page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page seals files and folders, chosen or dropped, with a comment
and a declared author, into a capsule of format 3.
- create-files.ts, with the first load: the files as the person chose
them, a dropped folder walked with webkitGetAsEntry and named first
in each path as webkitRelativePath does, the files of a system left
out of folders as collect.go leaves them out (strings.EqualFold for
.DS_Store, Thumbs.db and desktop.ini, ._* and __MACOSX), and the
list of editable paths.
- create-check.ts, on demand with the tables: every problem of every
path, and of the comment and the author, in Spanish, from the
violations of pathrule.ts. A property test holds that the page sees
no problem exactly when checkPath and checkTree accept the paths.
- lengths.ts: measureFiles, headLengthOf and bodyLengthOf, so that
the exact size is planned again without sorting the files again.
- creator.ts: several files, the comment and the author, the progress
of both readings of encryptFiles, the cancellation in the first
one, and the room checked before reading anything.
Checked in Chromium: Go's datekeys decrypt and /inspect open a capsule
that the page wrote, with its six files, their mtimes, the author and
the comment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page opens capsules of format 3. The files go to the temporary
file through a ZipSink, a lone file of one segment as it is and a ZIP
otherwise, or to memory; the page shows the verdicts first, then the
declared author and the comment as unchecked text of the creator, and
then each path as text in a bdi, with its size, its mtime and the
warnings of the CLI of the reference, compared by their key of R7.
- files.ts: pathWarnings, fileFacts, and the names and order of the
downloads: the file itself when it is the only one, with the ZIP of
its folder second (decision 8), or the ZIP and each file.
- opener.ts: OpenedFiles, and noRoom when the ZIP does not fit.
- zipsink.ts: NoRoom, thrown by begin before writing anything.
- check-build.mjs: the tables of pathrule-tables.ts never come with
the first load of a page, and do come with the code on demand of
/inspect and /create.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
zipsink.ts is the sink of the page for a format 3 capsule (design of
format 3, section 5). Its files go to the temporary file of OPFS: the
file itself when the capsule holds one file of one segment, and
otherwise a ZIP of stored entries laid out from the head before any
byte arrives. Each local header is written at its offset, the bytes of
the file follow as open delivers them, the CRC-32 of the entry is
patched in its header with a positioned write, and commit writes the
central directory and closes the file; abort discards it, also when
the opening failed before begin. Each file is a contiguous range of the
file written (ranges), and zipOf makes the same ZIP in memory as a Blob
of its parts.
tempfile.ts: the writable of a temporary file takes TempChunk, bytes at
the position of the file or at a given one, as
FileSystemWritableFileStream does; cancellable is generic.
Interoperability: scripts/zip-ts-samples.mjs writes the samples of
testing/zip.ts with ZipSink, and scripts/zip-go-read.go reads them with
archive/zip of the Go standard library: names out of ASCII with bit 11,
stored entries, their CRC-32 and sizes, the times of the extra fields
in 1970, at 2^31 - 1 and after it, in 9999 and the time of the round for
a file without one, and 65535 entries, ZIP64 by their number. The
reading is frozen in testing/zip-vectors.json, and zipsink.test.ts
writes each sample again, requires its SHA-256 and computes the entries
Go must have read. zipsink.ts is covered at 100 %.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
capsule-vectors.json is written again with six capsules of format 3
from encryptFiles, next to the thirteen of format 2:
- one file with its mtime; a tree of seven files, one of them over two
STREAM chunks, with paths out of ASCII and the pair U+FFFD and
U+10000, which UTF-8 and UTF-16 order the other way round, a comment
and a declared author; a comment and no file; bloque256; time_and_key
with three recipients and a portable key; and head extensions.
- capsule-go-verdicts.go opens them with capsule.Open into a Sink, with
each credential alone and with all of them, and records the files it
receives with their SHA-256, the head encoded again with
capsule.EncodeHead and the verdicts of the security area. It decodes
the control of each capsule in the format of its prelude, and runs
capsule.Encrypt on the invalid options as a generator of test vectors.
- interop.test.ts requires Go to find the files, the head and the
verdicts written, and open, into a MemorySink, to reach the same
verdicts on the same bytes.
The format 2 samples, the mixes, the encoder differential (500 equal),
the recipients and the 21 option errors are regenerated too, with the
same verdicts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec 62.1 rule 1: a writer writes format 3, and only a generator of test
vectors may write format 2. As capsule.Encrypt at spec-v0.10, encrypt now
fails without EncryptOptions.testVectors, and with a comment, a declared
author or head extensions, which format 2 has no place for, with the
texts of the reference, before anything else is checked. The tests of
the writer, encryptWith, the interoperability cases and the sample
script ask for it.
The create page writes format 3 with encryptFiles: the chosen file goes
under its name, which is its path in the capsule, with its modification
time, both sealed in the head. The plan carries the file as encryptFiles
takes it, and its size is exact again with bodyLength. A name that
breaks a rule of the paths makes the writing fail with the text of the
rule; several files, folders, editable paths, the comment and the
author come with step 7.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
encryptFiles(files, opts) writes a .dkc of format 3 as
capsule.EncryptFiles at spec-v0.10, on the sealer of the previous
commit:
- newHead: the comment, with CR LF and a lone CR turned into LF, the
declared author and every path checked with the rules of the reader,
in the words of a writer (spec 62.1 rule 15); the files in the byte
order of their paths, not the UTF-16 order of JavaScript strings; and
the mtime in seconds from 1970 to 9999, none outside.
- L measured with a head whose salt and SHA-256 are zero, at most 16 MiB
and L_MAX; the first reading hashes each file; the head with a fresh
salt, the empty security area and the frame are checked with the rules
of the reader before anything is written.
- BODY is the content of seal: the frame, the area, the head and the
files read a second time, which fail if a size or a SHA-256 changed
(rule 18), with the texts of readSource.
FileSource describes a file (path, size, mtime in milliseconds, open),
and fileSource makes the one of a File or a Blob. The draws gain the
salt of the head. lengths.ts gains bodyLength and headLength, which
measure the head from the sizes of its CBOR items without the Unicode
tables, and mtimeSeconds and headComment, which the writer shares.
Tests: the five fixtures that EncryptFiles wrote are reproduced byte for
byte, PRELUDE, PUBLIC_HEADER, CONTROL_CBOR, HEAD_CBOR and BODY, and
their .dkk; capsuleLength with bodyLength gives the size written, and
headLength agrees with encodeHead on 300 random heads around every
boundary of the CBOR heads; the invalid inputs give the texts that
capsule.EncryptFiles gives to the same inputs, taken from the reference
with a scratch program. writer.ts, encrypt.ts and lengths.ts stay at
100 %.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Syncs testdata with datekeys-go at the tag spec-v0.10 (cc35d2c) and
moves the reader to the DateKeys Protocol Specification v0.10. The
three capsule formats are read.
- framing: FORMAT_3, and isPadded for formats 2 and 3. control: schema
version 3, with the keys 6 and 7 of version 2. SPEC_VERSION is 0.10.
- open: OpenOptions.sink receives the files of a format 3 capsule
(sink.ts: Sink with begin, create, commit and abort, as capsule.Sink,
and MemorySink). Without one, open rejects with a TypeError right
after step 2, before any request, as ErrSinkRequired. Opened gains
head, verdicts, areaLen and unusableHeadExtensions.
- open3.ts: step 17 of format 3 in its substeps 17.2 to 17.8, as
openBody of the reference: a failure of age or a plaintext whose
length is not P prevails, the first failing substep decides, and the
codes other than ERR_INTEGRITY are reported only after reading
PAYLOAD_AGE to its end. Reads grow with the bytes received, never
with the lengths BODY declares. A failure of the sink is ERR_INTEGRITY
with its text, and the sink is aborted once after begin.
- The page: opener.ts opens the fixtures of format 3 into a
MemorySink; the open panel says that it does not deliver their files
yet, and the glosses of the steps name format 3. check-build.mjs
refuses to ship the heads, salts, comments and paths of the format 3
fixtures.
Tests: the 21 fixtures, format 3 laid out byte by byte from its record
and opened into a sink with its files and verdicts; the 209 cases of
the corpus from memory and from a Blob, with the code, the step and,
new, the exact text of capsule.Open, frozen by
scripts/mutation-go-texts.go in testing/mutation-texts.json, which
replays the corpus as internal/testkit does (its extension validator
texts included); the 5110 differential cases over 14 bases; paths,
path_fold, head_schema and security vectors; the control of schema
version 3 in cbor.json; and step 17 on crafted plaintexts sealed again
to I_PAYLOAD, whose texts capsule.Open gives on the same plaintexts.
ibe-vectors.json gains the nine format 3 fixtures from
scripts/ibe-go-vectors.go; the twelve before are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a capsule opens, /inspect now shows its content right under the
verdict, before steps 9 to 18. A capsule does not keep the name of the
file it seals (spec §6, §55.2), and the capsula-<date>.dkc of /create
has no extension of its own, so the download was nameless for the
system: contentExtension now gives it .txt for a text, or the
extension of a common type of file by its first bytes (.pdf, .png,
.jpg, .zip…).
vite preview served the pages without Cache-Control, and a tab
reloaded after a build could keep the old page, whose chunks are gone;
a small plugin, before SvelteKit's, has the pages revalidated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/inspect showed the plaintext only of the official fixtures; a capsule
of one's own was only offered for download. Now the start of the
plaintext is shown whenever the capsule opens and it is text:
opener.ts reads its first 128 KiB (PREVIEW_BYTES), from memory or from
the temporary file, and plaintextPreview in opening.ts shows up to
100 000 characters of printable UTF-8, cut on a whole character. A text
written on Windows shows too: CR LF as a line feed, no BOM. The
download keeps the exact bytes.
The pages now explain age keys: /create, in a folding block, what an
age1… recipient is and how to get one with age-keygen; /inspect, next
to the identities, which line of the age-keygen file to paste.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/create seals a person's file into a .dkc of format 2 and, when asked, a
portable .dkk, in the browser and without network, with the decisions the
author confirmed in step 6: time_only by default, the zone of the device
with a selector, the warnings of §53 and §50 beyond 365 days, a notice of
preliminary protocol, and files named capsula-<opening time, UTC>.
- lengths.ts: sealedControlLength moves out of writer.ts, and
capsuleLength gives the size of the .dkc before writing it; the
property loop checks it on every capsule (500 seeds pass).
- datekey.ts: LONG_HORIZON_SECONDS and isLongHorizon.
- src/lib/inspector: localtime.ts (local times of a zone as UTC instants,
a skipped time refused, a repeated one taken at its later instant),
create-input.ts (the form, checked in its order) and creator.ts (the
writing, loaded on demand), all at 100 %.
- The .dkc goes to an OPFS temporary file, committed only when complete
and checked, or to memory up to 64 MiB; the writing can be cancelled.
The .dkk stays in memory only; losing it when it is the only
credential asks for confirmation.
- /inspect also cleans the temporary files of /create, and check-build
checks the code loaded on demand of both pages.
Checked in the browser on the production build: a capsule made for four
minutes later opened afterwards in /inspect with the pasted release and
with datekeys decrypt of Go over the network, to the same content. An
adversarial review found one major and eight minor issues, all fixed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scripts/capsule-ts-samples.mjs writes thirteen capsules of format 2 with
encrypt (both policies and padding rules, 0 to 16 credentials, chunk
borders, extensions in the three objects, an instant with nanoseconds),
four mixes of two capsules, the inputs of an encoder differential drawn
from a seed, a corpus of recipient strings and the invalid options that
Go also rejects. scripts/capsule-go-verdicts.go gives the verdicts of the
reference on them:
- capsule.Inspect, and capsule.Open with each credential alone and all
together: every sample opens to its content, with format 2 and the L,
padding rule and P requested;
- SEALED_CONTROL opened layer by layer with the agewrap identities, 16
stanzas in INNER_ACCESS_AGE, and PUBLIC_HEADER, CONTROL_CBOR and the
.dkk encoded again to the same bytes;
- the code and step of each mix;
- EncodeHeader, EncodeControl (format 2) and MarshalBody equal on all 500
encoder inputs;
- the texts of age.ParseX25519Recipient, agewrap.CheckX25519Recipient and
capsule.Encrypt, equal to those of this library.
The capsules are random, so the output is frozen in
src/lib/dkc/testing/capsule-vectors.json, and interop.test.ts checks the
verdicts of Go and that open reaches the same ones on the frozen bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
encrypt(src, opts) writes a .dkc of format 2 and, when asked, a portable
.dkk (spec §61, §62, §62.1), in the order and with the texts and codes
of capsule.Encrypt:
- L known in advance: the size of a Uint8Array or a Blob, or the length
declared with a ReadableStream; a source of another length fails with
Go's texts;
- reforzado padding by default, or bloque256;
- 1 to 16 credentials, canonical and not of low order, a dummy in each
slot left, whose scalar is wiped once its public key is derived, and
a uniform order of the 16;
- SEALED_CONTROL_LEN from the formula of §62.1, checked against the
real seal;
- the self-checks of rule 11, plus OUTER_TIME_AGE under the reader's
rules and the header of PAYLOAD_AGE opened by I_PAYLOAD before
anything is written.
The content is streamed in pieces of 64 KiB, then the zeros of the
padding, into memory (up to MAX_MEMORY_DKC) or an output that is closed
only once the capsule is complete and checked and aborted on any
failure. The core in writer.ts takes its random values from the caller:
encrypt.ts passes crypto.getRandomValues, and only testing/encrypt.ts
fixes them.
Tests: the deterministic sections of the seven format 2 fixtures of Go
byte for byte; round trips with open for both policies, 1 to 16
credentials and every padding boundary; the invalid options; streaming
and failures of the source and the output; the internal errors with
age-encryption replaced by a spy; a property loop (50 seeds per run,
500 by hand).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- recipient.ts: age1… recipients as age 1.3.2 reads and writes them, the
rules of spec §37 with the texts of agewrap.CheckX25519Recipient (the
five low-order u checked by list, the twist accepted as in Go), and a
recipient list read line by line. No noble.
- random.ts: an index without bias and the Fisher-Yates permutation of
the 16 slots, with Go's uniformity test.
- agefile.ts: the whole-age-file helpers of the opening, shared with the
writer's self-checks.
- x25519.ts: newX25519Identity and x25519PublicKey (RFC 7748 vectors);
digest.ts: sha256Hasher; datekey.ts: compareInstants, used by open.ts,
and isInstant.
- tempfile.ts: an area for the opening and one for creating capsules.
- Guards: age-encryption and the writer core have import allowlists, and
index.ts re-exports neither the opening nor the writer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
datekeys-ts 0.1.0 inspects and opens DateKeys capsules of both formats
of spec v0.9 (tag spec-v0.9 of datekeys-go), in memory or streaming,
with the /inspect page. The writer of phase 3 comes with 0.2.0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Syncs testdata with datekeys-go at spec-v0.9 (7e2d83c) and moves the
reader to the DateKeys Protocol Specification v0.9. Both capsule formats
are read; a format 1 capsule keeps the verdict v0.8.2 gave it.
- framing: the VERSION of the prelude is the capsule format, 1 or 2
(Prelude.format, FORMAT_1, FORMAT_2, isFormat).
- control: decodeControl and encodeControl take the format; schema
version 2 adds payload_length (8 bytes, at most L_MAX) and padding
(1 or 2).
- padding.ts: the rules bloque256 and reforzado of spec §29.1, exact up
to L_MAX with BigInt bit lengths and ceil roundings, and the length of
PAYLOAD_AGE.
- open: exactly 16 stanzas in INNER_ACCESS_AGE of format 2 (step 12), P
at step 16, and at step 17 a plaintext of exactly P bytes whose
padding is zero; only the first L bytes are delivered, never the
padding. Step 17 is recorded when it passes, and step 18 gives the
bytes of content, as the reference does. Opened reports the format, L
and, in format 2, the rule and P.
- inspect: the JSON view carries format, as datekeys inspect -json.
- The page shows the format, warns about format 1, and gives the
padding rule and P once a format 2 capsule opens.
Tests: the twelve fixtures, the 125 mutation cases through open from
memory and from a Blob, the 4380 differential cases, padding.json, the
format 2 CBOR vectors, and padding.test.ts against a BigInt statement of
§29.1. The error texts of the 125 corpus cases were compared with
capsule.Open at spec-v0.9. ibe-vectors.json gains the seven format 2
fixtures from scripts/ibe-go-vectors.go; its frozen values are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After steps 1 to 8, a valid capsule whose date has passed on the device
clock can be opened in the page: steps 9 to 18 of spec section 63 with
open, loaded on demand with a dynamic import (opener.ts), so noble and
age-encryption stay out of the first load of every page.
- The release is supplied directly by the person (spec 63, step 10):
drand's JSON answer or the bare signature, pasted after opening the
drand URL the page links to, or the release in the record of an
official fixture. The page never fetches it and reads only its round
and signature (spec 11, 13). The CSP is unchanged.
- time_and_key credentials: a .dkk (readAccessKey reads at most
12 bytes + 16 MiB + 1) or age identities, one per line.
- The plaintext of the person's own file goes to a temporary OPFS file
(tempfile.ts), committed only after step 18 (spec 56), offered for
download and deleted on request, with another capsule, on pagehide
and, if left over, on the next visit. One directory and one Web Lock
per tab keep other tabs' clean-up away from files in use. Without
OPFS, or when the browser refuses it, capsules up to 64 MiB open in
memory. An opening in progress stops when another capsule is loaded.
- opening.ts builds the page model of steps 9 to 18 as the reference
records them; fixtures show their plaintext and compare its SHA-256
with their record.
- licenses.txt: the notices of tlock-js (ibe.ts) and age (bech32.ts),
the license of every package in the client bundle, Vite's and
rolldown's runtime code, and the site's own license. check-build now
fails if a notice is missing, or if a page loads noble, @scure/base
or age-encryption with its first load.
- The home page no longer says that the page never asks for keys.
Checked in the browser on the production build: the time_only,
time_and_key_portable (with its .dkk) and time_and_key_recipients (with
a pasted identity) fixtures open with the SHA-256 of their records; a
tampered signature fails at step 10 and a tampered STREAM chunk at step
17, with no download and no file left; an own file opens to OPFS,
downloads without a CSP violation and is deleted with its lock; a left
over directory goes on the next visit; no request leaves the origin.
An adversarial review (four dimensions, each finding checked by a
refuter) confirmed 15 findings, all fixed here.
2611 tests; coverage 100 % of the new modules, now a threshold.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ibe.ts gains encryptOnG2RFC9380, EncryptCCAonG2 of kyber with the
suite of tlock for Quicknet. Qid is H(id) on G1 with the RFC 9380 DST,
sigma comes from crypto.getRandomValues, U = r·G2, V = sigma XOR
H2(e(Qid, key)^r) and W = msg XOR H4(sigma). The key passes the
canonical gate, and sigma and the masks are wiped. encryptOnG2WithSigma
takes a given sigma, for the vectors only; index.ts exports neither.
- tlock.ts adds timeRecipient, the age-encryption Recipient of
OUTER_TIME_AGE, as Go's agewrap.TimeRecipient. It writes the stanza
"tlock <round> <chain hash>" with the checks and texts of
NewTimeRecipient: the scheme and the pinned key, then the round range.
age-encryption has no labels, so the writer of phase 3 adds it alone.
Vectors, in src/lib/dkc/testing/tlock-vectors.json from
scripts/tlock-go-vectors.go:
- Fixed-sigma encryptions of 1, 16 and 32 bytes for rounds 1000 and
1001. Go restates EncryptCCAonG2, since kyber draws sigma itself, and
checks the restatement with ibe.DecryptCCAonG2 and tlock.TimeUnlock.
encryptOnG2WithSigma reproduces them byte for byte.
- The samples of scripts/tlock-ts-samples.mjs, which Node runs on the
TypeScript sources: IBE bodies and age files that this library made
for rounds 1000 and 1001. Go opened every one: the bodies with
tlock.TimeUnlock and the age files with age.Decrypt and
agewrap.NewTimeIdentity, the identity of step 11. It got the same
file keys and plaintexts, and the samples are frozen with those
verdicts.
tlock.test.ts replays both blocks, the random round trip, the
rejections with their texts, and an age file sealed with timeRecipient
and opened with the step-11 identity of open.ts. Coverage of ibe.ts and
tlock.ts is 100 %, now a threshold for tlock.ts too. Step 6 of the plan
is recorded as done: the canonicality amendment is in spec-v0.8.2.
npm run verify is green: 2,567 tests. The site does not change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
open(dkc, opts) now takes a Uint8Array or a Blob, such as a File.
- Of a Blob it reads only the prefix that steps 1 to 8 need.
inspectedLength and readCapsule move from src/lib/inspector/load.ts to
src/lib/dkc/prefix.ts, and the page imports them from the library.
- The .dkk capsule_digest is computed over the Blob's stream with the
new src/lib/dkc/digest.ts, an incremental SHA-256 on @noble/hashes,
since Web Crypto hashes whole buffers only. digest.ts joins the noble
allowlist of the guards.
- PAYLOAD_AGE is decrypted in streaming.
The plaintext goes to memory, as before, or to opts.output, a
WritableStream. The output is written as age authenticates each chunk,
closed only after step 18, and aborted after any failure at any step,
even before step 17 (spec §56). A failure of the output is ERR_INTEGRITY
with its text, as Go keeps the error of the writer of the plaintext.
Tests:
- the fixtures from Blobs, to memory and to an output;
- a truncated two-chunk payload whose first chunk reached the output
before the abort;
- early failures that never write;
- write and close failures, and an abort that fails;
- a .dkk without capsule_digest;
- the whole mutation corpus again as Blobs into an output, aborted in
every case;
- digest.ts against Web Crypto.
Coverage of digest.ts is 100 % and a threshold.
Checked in the browser (dev server, real OPFS). time_only.dkc, two
STREAM chunks, opened from a Blob into FileSystemFileHandle.createWritable
gives 78,000 bytes with the SHA-256 of its sidecar. The same file
truncated fails at step 17, and the OPFS file keeps its previous
content. The quota check, the temporary file and the download belong to
the page, in step 8.
npm run verify is green: 2,560 tests. The site does not change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/lib/dkc/open.ts runs steps 9 to 18 of spec §63 on top of the steps
1 to 8 of inspectWith. It follows capsule.Open of the Go reference, with
its checks, codes and texts:
- step 9: the access credentials (the .dkk as an object, then its
capsule_id and capsule_digest), then the release, never before the
round time. Any failure of the source is ERR_RELEASE_UNAVAILABLE
alone, keeping its text and its cause (correction 6);
- step 10: verifyRelease;
- steps 11 to 13: OUTER_TIME_AGE, the structure against access_policy
and INNER_ACCESS_AGE;
- steps 14 to 18: CONTROL_CBOR, header_binding, I_PAYLOAD, PAYLOAD_AGE
and the commit.
The three age files open with the Decrypter of age-encryption and
identities that apply the rules of Go's agewrap: the tlock identity on
ibe.ts, and the access and payload identities on x25519.ts. A failure of
age that no identity reports is ERR_INTEGRITY with the fixed reason of
its phase, header or STREAM, never the text of age-encryption. The
plaintext is decrypted in memory and returned only after step 18.
Streaming to OPFS is step 5b.
src/lib/dkc/x25519.ts opens one age X25519 stanza at a time, in the
order of age's X25519Identity. Step 13 must try every identity on every
stanza (spec §36), and age-encryption's Decrypter stops at the first.
Its primitives are the ones age-encryption uses: X25519 and HKDF from
noble curves and hashes, and ChaCha20-Poly1305 from @noble/ciphers
2.4.0. The author approved declaring that package as a direct
dependency on 2026-09-28; it is the copy already installed and bundled.
The guards now allow ciphers, and x25519.ts in the noble allowlist.
src/lib/dkc/bech32.ts ports age's internal/bech32, with its MIT notice,
to read AGE-SECRET-KEY-1 identities.
Tests:
- vectors.test.ts runs all 65 cases of the mutation corpus through open.
Each gives the code and the step of Go, and no case that fails without
the network requests a release. This includes the 34 cases of steps 9
to 18 that were skipped, so the suite no longer skips any test.
- open.test.ts:
- the five official fixtures open to their plaintext, with each
credential, with the checks and details of the reference;
- the unusable noncritical extensions are reported;
- the source failures and the clock;
- age failures by phase;
- CONTROL_CBOR that does not decode, and a low-order share in
INNER_ACCESS_AGE, through an OUTER_TIME_AGE resealed with the FK_TIME
of the Go vectors;
- the texts of the identities.
- x25519.test.ts checks against age-encryption both ways and against the
Go-written stanzas of the fixtures, and covers every low-order share.
- bech32.test.ts has the vectors of the reference.
x25519.ts and bech32.ts are at 100 % coverage, now thresholds. open.ts
is at 100 % of lines; the one branch left is the one for an error that
is not a DateKeysError.
index.ts does not re-export the opening yet. The page imports index.ts,
and re-exporting would pull noble into /inspect (58.7 to 84.9 KB gzip)
even unused; step 8 will load it on demand. The site does not change.
npm run verify is green: 2,490 tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- src/lib/dkc/version.ts exports VERSION (0.1.0-dev, which becomes
0.1.0 once phase 2 adds the opening of capsules) and SPEC_VERSION
(0.8.2, the tag spec-v0.8.2 of datekeys-go), from index.ts too.
- package.json and its lockfile move to 0.1.0-dev. version.test.ts ties
VERSION to both and checks it is semantic versioning. It also ties
SPEC_VERSION to the spec field of the shared vectors and fixtures.
testing/vectors.ts now takes SPEC_VERSION from version.ts, so every
vector file is checked against the version the library declares.
- The footer of the page shows both, instead of a fixed 0.8.2.
- README.md gains a "Versiones" section: the three versions (format,
specification, library) and what 0.1.0-dev covers. CHANGELOG.md is
new.
The Go reference gained datekeys.SpecVersion, datekeys.Version() and
`datekeys version` in 5b342d3.
npm run verify is green: 2,406 tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>