Rebased on stage 6b. The hook of the signature of alg 1, which 6b named
AuthorKey, is now AuthorSigner, and AuthorKey of authorkey.dart implements
it. The tests of the writer sign the capsules of alg 1 with the AuthorKey
of Go's seed instead of replaying the recorded signature, and write the
bytes of Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two injected faults went unseen: utf8.DecodeLastRune accepting a rune
that does not end at the end of the line, and the position of the
separator checked one byte short for a string that is not ASCII. The
generator now writes lines whose ends are a space next to a stray
continuation byte or a space cut short, which TrimSpace keeps, and
strings with a byte that is not ASCII and a separator 6, 7 or 8 bytes
before the end. Go and Dart agree on all of them, and the tests now see
both faults.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
locator_seal.dart ports the writing side of package locator of
datekeys-go: sealLocator, Seal of Go, marshals the locator, makes the
tlock recipient of the round and encrypts, in that order and with the
texts of Go, and wipes the plaintext; newEnvelope, NewEnvelope of Go,
draws I_SOBRE, encrypts the .dkc for it with ageEncrypt of stage 6a and
splits the file with splitEnvelope of stage 7a.
tool/locator_seal_go_vectors.go writes test/vectors/locator_seal.json:
Seal of locators of one to three blocks for rounds from 1 to the last of
Quicknet, its refusals, NewEnvelope of .dkc of 0 bytes to 1 MiB and a
whole flow, while crypto/rand reads the keystream of SeededRandomSource.
With the same seed, Dart draws the same values and writes the same bytes
in every case, and the sealed locators open with the release of their
round.
In the other direction, tool/seal_interop_dart_samples.dart writes sealed
locators with their envelopes, author key files and signatures from the
recipes of test/seal_interop_support.dart, and
tool/seal_interop_go_verdicts.go opens them with locator.Open,
OpenEnvelope, authorkey.Read and crypto/ed25519: Go reads all fifteen.
test/vectors/seal_interop.json keeps the verdicts and the digest of each
file, which the tests write again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ed25519_sign.dart signs as crypto_sign of TweetNaCl in its JavaScript
port, with the SHA-512 of package:crypto: the field of curve25519.dart,
whose arithmetic is private there, copied with the product as a loop, and
modL over 64 limbs of 8 bits in a Float64List, with floor divisions in
place of the shifts of TweetNaCl, exact on the VM and on the web. The
secret scalar and the nonce never meet a BigInt or a branch; neither
platform promises constant time, and the values are wiped as a best
effort.
authorkey.dart ports package authorkey of datekeys-go: AuthorKey with
generate, fromSeed, publicKey, sign, clear and secret, and a toString
that hides it; authorPublicString, parseAuthorPublic and
parseAuthorSecret, also on the bytes of a Go string; marshalAuthorKey;
encryptAuthorKey, scrypt with logN 16 through ScryptRecipient and
ageEncrypt of stage 6a; and readAuthorKey, through the age reader with a
maximum work factor of 16, whose lines are those of bufio.Scanner and
strings.TrimSpace. Every error has the text of Go, with the sets of
go_unicode.dart for the case of a string and the spaces of a line. A
cleared key refuses every use, where Go would give the values of a key of
zeros.
tool/authorkey_go_vectors.go writes test/vectors/authorkey.json: the
signatures of crypto/ed25519 over lines of sign.input (RFC 8032 tests
1, 2, 3 and 1024), TEST SHA(abc), seeded seeds and messages up to 1 MiB
and other public keys; the scalars of math/big; and Generate, Encrypt,
ParsePublic, ParseSecret and Read of authorkey with each text, while
crypto/rand reads the keystream of SeededRandomSource. Dart writes the
same bytes and gives the same texts in every case; authorkey.g.dart, a
part of it, runs also in Node.js.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib/datekeys.dart exports the writer of capsules, and what its options
take: X25519Recipient and checkX25519Recipient, RandomSource and
secureRandom. The tests that imported them from their modules no longer
need to. tool/encrypt3_bench.dart times the writer on the VM and in
Node.js.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing reaches the sink before the signature; the sink receives the
prelude first, each buffer its own; a sink that fails stops the writing
with its error and is aborted, one that fails to close is not; a hook or a
source that throws a DateKeysException keeps its code, anything else is a
CapsuleWriteException with its cause; a string that is not well-formed
UTF-16 is not valid UTF-8 for Go; a source is read in streaming; the
result and its .dkk; and, on the VM, two capsules of the CSPRNG differ and
open.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tool/capsule_interop_dart_samples.dart writes the capsules of twelve
recipes, six with SeededRandomSource and six with the CSPRNG of the
platform, among them three MiB in three files, two hundred files, and a
capsule of fourteen recipients, a key of words and a portable key.
tool/capsule_interop_go_verdicts.go inspects and opens each with
capsule.Open of Go, with each credential alone, all together and none,
encodes PUBLIC_HEADER, CONTROL_CBOR and the .dkk again, and writes each
seeded one with capsule.EncryptFiles.
Go opens every capsule to the files of its recipe, refuses each without a
credential, finds the layers and the .dkk encoded as it encodes them, and
writes the seeded ones byte for byte. The tests check those verdicts and
write the seeded samples again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tool/capsule_writer_go_vectors_test.go runs capsule.EncryptFiles of Go on
87 recipes while crypto/rand reads the keystream of SeededRandomSource, as
a test in an export of datekeys-go so that the CMS signatures and tokens of
its hooks come out the same on every run. It records the size of each
draw, what each hook was given and returned, the capsule, the .dkk and the
openings of Go with each credential, and the text, the code and the bytes
written of each error.
The tests write each recipe again: the same draws, the same requests to
the hooks, and the same bytes or the same error, in 21 capsules and 66
errors; and this library opens each capsule as Go did. The cases marked
node also run compiled to JavaScript.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
StandardExtensions checks by default the data of datekeys.capsule with
checkCapsuleData, as locator.Standard of Go: when a .dkk is opened with
it, an extension whose data does not read is unusable, with the text of
Go. With validateCapsule: null it checks only that there is data, as
Go's extension.Standard without ValidateCapsule: the writer of a .dkk
keeps that one, as Go's writer does, and so do the tests of the formats
that compare with extension.Standard. Inspection and opening give Go's
results on every fixture and vector, and the tests of the locator use the
default registry.
lib/datekeys.dart exports locator.dart. The README has the section of
part 7a, its vectors and how the generator makes them; the changelog has
its entry, with its tests and its injected faults.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib/src/locator.dart gains the data of the extension, as Info, ParseInfo
and Info.Extension of Go, with the same checks in the same order and the
same texts:
- parseCapsuleInfo: the map of spec 44.1 with the note under the rules of
spec 24.1, the canonical DateKey, and a sealed locator that is an age
file with one tlock stanza for its round; every failure is
ERR_EXTENSION_DATA_INVALID and nothing else, so that it makes the
extension unusable and never the .dkk (spec 54).
- CapsuleInfo.toExtension, which reads back what it writes (spec 72), and
CapsuleInfo.openLocator, OpenLocator of Go, with the default registry of
pinned profiles when none is given, as the opening of a capsule takes it.
- checkCapsuleData, the ValidateCapsule of Go's locator.Standard, for
StandardExtensions.
The tests run the extension cases of locator.json with their code, the
writing and the reading of the data, OpenLocator, the registry of
locator.Standard through checkCritical, checkNoncritical and checkWrite,
and the opening of a fixture whose .dkk carries datekeys.capsule, whose
unusable extensions and checks are those of Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib/src/locator.dart gains the rest of package locator of datekeys-go but
Seal and the data of the extension, with the same checks in the same
order and the same texts:
- Locator: unmarshalLocator, the map of spec 44.1 with the length that
Marshal gives and nothing else, and marshal, its form, its addresses and
key 6 up to the least multiple of 4096 that it fills, as padFor and
PlaintextLength; usable, the addresses that a reader uses.
- openLocator, Open of Go: the profile, then age with the tlock identity
of agewrap, and at most 1 MiB of plaintext, as io.LimitReader: the
chunks after it are neither decrypted nor checked.
- The envelope: restIn, openEnvelope with the size and the SHA-256 of the
rest and of the .dkc, hideRest, and splitEnvelope, the part of
NewEnvelope after its age encryption, which needs the writer of age.
A Locator keeps the types of Go: keys and digests of 32 bytes, and no
negative size or offset. Its errors carry no code, as in Go.
The tests run the cases of locator.json, the padding of every base, 482
plaintexts, Marshal at its limits, 118 openings, the files past 1 MiB, the
envelope, the rests and the split; a part also on Node.js.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib/src/locator.dart starts with the addresses of spec 44.1, a port of
CheckURI and Address.Host of package locator of datekeys-go: the same
checks in the same order and the same texts, on the UTF-8 of the address,
with Go's %q of the first byte that RFC 3986 does not allow. And
checkResolvedIp, the check of the IP that a name resolves to, which a
reader runs on every connection: the classification of publicIP on the 4
or 16 bytes of an address, an IPv4-mapped one not public. Go has no such
function, since its reader does not download.
lib/src/ipaddr.dart, internal, parses IPv4 and IPv6 with the exact
acceptance of netip.ParseAddr, writes them as its String, and classifies
them as publicIP, with the blocks of spec 44.1. It works byte by byte, so
that the 128 bits of IPv6 stay exact on the web, and never uses dart:io.
The tests run the 247 addresses of locator.json and the 2 800 of the
vectors, the 1 700 strings of netip and the 868 byte strings of publicIP,
on the VM and on Node.js.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tool/locator_go_vectors.go runs package locator of datekeys-go at c531e93,
the draft v0.12, in its module, without changing it, and writes what Go
gives, with the texts of its errors:
- locator_uris.json: CheckURI and Address.Host on the 247 addresses of
testdata/vectors/locator.json and on 2 800 more, built at every edge of
spec 44.1 and drawn from a seed: IPv4 and IPv6 in every notation that
netip.ParseAddr accepts or rejects, zones, mapped, NAT64 and 6to4
addresses, the first and last address of every IANA block and their
neighbours, long and punycode labels, local names, ports, percent
signs, dot segments and CIDs; netip.ParseAddr and String on 1 700
strings; and publicIP, reached with go:linkname, on 868 byte strings.
- locator_vectors.json: the texts of the other cases of locator.json;
PlaintextLength from -4100 to 16484; Unmarshal on 482 plaintexts; Marshal
at every limit and boundary of the padding; Open on 118 sealed locators
of four rounds, edited or with other releases and profiles; Open past
1 MiB of plaintext, read through io.LimitReader; the envelope, its rest,
Hide and the split of NewEnvelope; Info.Extension, Info.OpenLocator and
ParseInfo; locator.Standard as a registry; and capsule.Open of a fixture
whose .dkk carries datekeys.capsule.
crypto/rand.Reader is a ChaCha8 of a fixed seed, so every run writes the
same bytes. The Dart constants hold the whole of the first file and a
part of the second, for the tests compiled to JavaScript.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tool/age_interop_dart_samples.dart writes the files of the recipes of
test/age_interop_support.dart with SeededRandomSource, and
tool/age_interop_go_verdicts.go, in an export of datekeys-go, opens them
with age and the identities of agewrap and writes age_interop.json:
X25519 from 0 bytes to 3 MiB, one written in pieces, three and sixteen
recipients, tlock opened with the release of round 1000 of the fixtures,
tlock over sixteen X25519 as a SEALED_CONTROL, and scrypt with work
factors 10 and 16. Each sample keeps its recipe, the length and the
SHA-256 of its file, the file when small, its stanzas, the stanza rules
of agewrap on them and the verdict of Go with each opener.
The tests write each file again and must get the one that Go read; Go
opened it to the plaintext of the recipe, or refused it with an
identity that must not open it; and this library makes the same of it
as Go, with the same texts and the same stanza rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
age_writer.dart ports age.Encrypt of filippo.io/age v1.3.2, with its
checks and texts. AgeEncryptor and ageEncrypt draw the file key, wrap it
for each recipient in its order, with its labels, compute the header MAC
and draw the nonce: no recipients, labels that cannot be mixed, a
recipient that fails and stanzas that cannot be marshalled give Go's
errors. AgePayloadEncryptor encrypts the STREAM as its plaintext
arrives, as Go's EncryptWriter: a full chunk waits for the next byte,
so the last one is full-length for a non-zero multiple of 64 KiB and
empty only for an empty plaintext. The lengths of a file follow from its
plaintext and the form of its stanzas, before anything is encrypted.
recipient.dart has X25519Recipient, with its age1 strings and the texts
of ParseX25519Recipient; ScryptRecipient, with its random label;
TimeRecipient, with the label datekeys-tlock- of agewrap;
checkX25519Recipient, with the texts of agewrap.CheckX25519Recipient;
generateX25519Identity and the raw keys of agewrap.
The tests write every file of age_writer.json again with the same seed,
whole and in pieces, and get the same draws and bytes; open each with
the readers of this library; and check the errors, the recipients, the
STREAM and the lengths, on the VM and, without the expensive cases, on
Node.js.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
random.dart: RandomSource, the injectable source of every random value
of a writer; secureRandom, the default, Random.secure of the platform;
SeededRandomSource, deterministic, for tests and vectors only; and, for
the 16 slots of INNER_ACCESS_AGE, randomIndex, an integer drawn as
crypto/rand.Int draws it, and permute, as the permute of
capsule.Encrypt.
encryptOnG2 and wrapTlockStanza take the source of sigma, secureRandom
by default, so that a tlock stanza can be written again byte for byte;
ibe.dart no longer holds a Random.secure of its own.
The tests check the keystream, randomIndex and permute against the
vectors of Go, randomIndex at its bounds, the uniformity of permute, and
the CSPRNG on the VM: in dart test -p node there is no Random.secure.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tool/age_writer_go_vectors.go runs in an export of datekeys-go at
c531e93 and makes crypto/rand read the keystream of the seeded source of
the writer: ChaCha20 under SHA-256(seed), with a zero nonce. age.Encrypt,
with the real X25519, scrypt and tlock recipients, then draws known
values, and age_writer.json records every draw, its size and its order,
with the files: one X25519 recipient over plaintexts of 0 bytes to 3 MiB
across the chunk boundaries; two, three and sixteen, and one with bit 255
set, which age accepts; scrypt with work factors 1 to 16; and the tlock
stanza of rounds of 1 to 11 digits.
The generator checks each file against testkit.SealAge, with the first
draw as the file key and the last as the nonce, checks each X25519
stanza against its ephemeral secret, and opens the file with Go. It also
records the errors of age.Encrypt with the draws before them, those of
the constructors, ParseX25519Recipient, CheckX25519Recipient,
GenerateX25519Identity, crypto/rand.Int and the permute of capsule over
the keystream, and the lengths of testkit and capsule. The output is the
same on every run. age_writer.g.dart holds the same JSON for the tests
compiled to JavaScript.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Injected faults showed two rules that only the tests on the VM checked:
SIGNERS of more than 16 entries, whose only case was in security_cms.json,
and the order of the foreign signers. securitycms_vectors.json gains
SIGNERS of 16 and of 17 entries with Ana among them, beside her signature
for those SIGNERS or for SIGNERS with her alone, and a required signer
beside two foreign ones, without seals so that the area stays small; the
part for Node.js holds them. 760 cases.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
securitycms.dart ports evaluateCMS, signerLine and evaluateSeal of
signature2.go of Go at the draft v0.12, on the reader of CMS of stage 5a,
with the same order of checks: SIGNERS with its profile and at most 16
entries, then the SignedData; each required signer in the order of
SIGNERS and each foreign one in the order of the encoding, valid,
invalid, absent, not verifiable, without seal, with an invalid seal or
out of validity at the time of its seal; F2, F5 and F6 with their
detail; and the seal over SEAL_SUBJECT, S1 to S5 with the authority and
t. A round time at Go's zero time is no round time, as IsZero, and
Verdicts.sealedAt skips a seal at that time, as SealedAt.
cmsReader is the default CmsEvaluator of evaluateSecurity, and so of
evaluateSecurityInput and the opening: nothing that Go evaluates is left
not evaluated; a caller that passes cms: null still gets the parts
without CMS alone. encodeSigners and maxSigners are exported, as
EncodeSigners and MaxSigners of Go.
The tests compare every part with Go: the 135 cases of security_cms.json
with the result of each signer, the 24 of security.json, the 56
signatures of alg 2 and 105 seals of seal_type 2 of security_vectors.json,
the 755 cases of securitycms_vectors.json with their detail and earliest
seal, the fixtures format3_signed_cms and format3_sealed, and their
openings in open_cases.json. On Node.js, a part of the vectors and the
two fixtures opened in full. 1572 tests on the VM and 332 on Node.js.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tool/security_go_vectors.go also writes securitycms_vectors.json:
EvaluateSecurityIn of package capsule at the draft v0.12 on 755 areas
whose CMS signature or RFC 3161 seal it makes, as internal/cms/cmstest
makes them, with the verdicts, the lines, the detail of every signer and
of a valid seal, and SealedAt. Required and foreign signers of every
result; three required signers drawn from a seed; the validity of a
certificate at the time of its seal, at the nanosecond; t plus the
accuracy against the round time on both sides of it, Go's zero time and
the last second of 9999; a seal of each verdict beside a signature of
each verdict; and mutations of a SignedData, of SIGNERS and of tokens.
cmstest cannot be imported from outside the tree of datekeys-go, so the
part of it these cases need is restated. The keys come from labels, ECDSA
signs with the nonce of RFC 6979 and RSA with PKCS #1 v1.5: every run
writes the same bytes, and security_vectors.json and its part are the
same as before. Certificates, tokens and SignerInfo are written once, as
chunks. securitycms_vectors.g.dart holds a part of the cases, each verdict
pair of each group among them, and the fixtures format3_signed_cms and
format3_sealed, for the tests compiled to JavaScript.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cms.dart ports internal/cms of datekeys-go at c531e93, with its order of
checks and its texts: parseCert reads a certificate field by field with
the profile of spec v0.12 §29.10, with the holder from givenName and
surname before the commonName and the issuer from organizationName when
there is no commonName with text; parseSignature reads a detached
SignedData, its certificates, OCSP responses, signers, signed and
unsigned attributes, and SignerInfo.check gives valid, invalid or not
verifiable with the closed table of algorithms, a key of another scheme
invalid; parseToken reads an RFC 3161 token and its TSTInfo field by
field, form errors before algorithm errors, and Token.check verifies it
over a subject. Object identifiers are compared by their bytes, and a
SET OF may repeat an element.
The tests run every case of the vectors on the VM, the fixtures
format3_signed_cms and format3_sealed included, and the part that
cms_vectors.g.dart holds compiled to JavaScript.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nist_curves.dart reads an uncompressed point as Go's
ecdsa.ParseUncompressedPublicKey does, and computes u1 G + u2 Q in
Jacobian coordinates with Shamir's trick, the infinity included.
ecdsa.dart is VerifyASN1: the encoding as cryptobyte reads it, r and s
in [1, n - 1] and never reduced, s above n/2 accepted, the hash cut to
the bits of the order. rsa.dart is VerifyPKCS1v15, which rebuilds the
encoding and compares it whole, and VerifyPSS with a salt of the length
of the hash, with the bytes before emLen that must be zero; and the
hashes of the table, SHA-256, SHA-384 and SHA-512 of package:crypto.
All on BigInt, exact on the VM and compiled to JavaScript, and not
constant-time: a verification sees public values only. The curve
constants are those that the vectors record from Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tool/cms_go_vectors_test.go records what internal/cms of datekeys-go at
c531e93 (the draft v0.12) and the ECDSA and RSA of Go give: the curves,
points and signatures of ecdsa.VerifyASN1; RSA keys of 2048 to 4096 bits
and signatures of rsa.VerifyPKCS1v15 and VerifyPSS, with encodings built
by hand, each with one defect of its padding; ParseCert, ParseSignature,
SignerInfo.Check, ParseToken and Token.Check on the cases of the tests of
internal/cms and others (identifiers whose arcs wrap around in 32 or 64
bits, SET OF with an element repeated, the limits of the accuracy and of
the imprint, the ends of the validity); signatures, tokens and
certificates edited node by node and bit by bit; and every signature and
token of security_cms.json and of the two CMS fixtures, signer by signer.
It runs as a test in an export of datekeys-go, so that it can import
internal/cms and make keys and signatures deterministic with
testing/cryptotest: every run writes the same bytes. Each file stays
under 560 KB; repeated certificates are written once per file.
cms_vectors.g.dart holds a part of each file for the tests compiled to
JavaScript, and a test on the VM checks that it is that part.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib/datekeys.dart exports author.dart and security.dart, as package
capsule of Go exports the commitments and the evaluation, and the tests
that imported them from lib/src import them from the library.
The README and the changelog give the testdata of the branch v0.12 of
datekeys-go, the modules of part 5b with their notes, the boundary with
the reader of CMS of part 5c, the vectors of the security area, and the
tests and the faults injected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OpenOptions.evaluator is now evaluateSecurityInput, so that opening a
capsule of format 3 gives the verdicts of Go at step 17: the signature of
alg 1 and every verdict of the form, with the author keys of the options,
and the signature of alg 2 and the seal of seal_type 2 not evaluated
until a reader of CMS is given. notEvaluated stays for a caller that
shows no verdict.
tool/open_go_vectors.go records the verdicts of each capsule of format 3
that opens, with their lines, the key and the label of alg 1 and the
earliest valid seal, and opens the fixtures signed with alg 1 also with
their author key saved, F3, and with another, F4. Every case of
open_cases.json and every case of the mutation corpus that opens gives
those verdicts and lines: format3_signed, format3_unsigned,
format3_signature_unsupported, format3_seal_unsupported,
format3_security_v2 and format3_note all of them, and format3_signed_cms
and format3_sealed the part that needs no reader of CMS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
author.dart ports what an author signs and a seal seals from
signature.go of datekeys-go: payload_commit, control_commit over
CONTROL_SIG in each format, head_digest, signers_digest, AUTHOR_MESSAGE
with its prefix and its 99 bytes, its code taken byte by byte as Go
takes it, SIG_PART and SEAL_SUBJECT.
security.dart ports SECURITY_CBOR and EvaluateSecurityIn: the outer
map, author-signature and seal with the schema and the limits of Go,
their encoders, and an evaluation that never throws. X for an outer map
that fails its layer 2 or 3, version 2 among them; F0 to F4 for the
signature, with verifyStrict for alg 1 and the key matched against the
saved ones by its dkauthor1 string; S0 to S2 for the seal; and a failure
inside one part fails that part only, as Go recovers a panic. Without a
context it reads as a reader of v0.10. securityContext is
newSecurityContext with the head digest, control_commit at zero when
CONTROL_SIG cannot be encoded, and holderText the rule of a name of a
certificate.
The signature of alg 2 and the seal of seal_type 2 belong to the reader
of CMS of stage 5c, behind the interface CmsEvaluator: without one their
verdict is null, not evaluated, never guessed.
verdicts.dart gains the texts of Go at the draft v0.12, Verdicts.lines
and sealedAt, Detail, SignerLine and SignerResult, and verdicts that may
be evaluated in part.
The tests check every case of security_vectors.json, security.json in its
context, the commitments, the signature and the seal of each fixture of
format 3, and the boundary with a reader of CMS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tool/security_go_vectors.go writes test/vectors/security_vectors.json
with package capsule of datekeys-go at c531e93, the draft v0.12, run in
its module without changing it, and a part of it for Node.js in
security_vectors.g.dart:
- the commitments: PayloadCommit, ControlCommit of the control of every
fixture and of controls built with extensions, in each format, with
the text of the error where CONTROL_SIG cannot be encoded, HeadDigest,
SignersDigest, AuthorMessage, AuthorCode, also of messages that
AuthorMessage never writes, SigPart and SealSubject;
- the encoders of SECURITY_CBOR, author-signature and seal;
- 1730 evaluations of SECURITY_CBOR with EvaluateSecurityIn in 23
contexts, and EvaluateSecurity without one: the outer map,
author-signature and seal broken in every way of their schemas and
limits; signatures of alg 1 valid and invalid, saved or not, with the
cases of Taming the many EdDSAs made over AUTHOR_MESSAGE by searching
the context; and mutations of nine bases from a fixed seed. Each case
gives the verdicts, the key and the label of alg 1, the lines, alg and
seal_type as read, and the parts that only the reader of CMS evaluates;
- Lines and SealedAt of verdicts built with every pair of verdicts and
the details of signers and seals;
- holderText, reached with go:linkname, on names at the limit of 64 code
points and drawn from the seed.
The files are ASCII, and every run writes the same bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each of the 16 notes of testdata/vectors/note.json, the cases of the
list of v0.11 of spec section 64, gives the result and the detail of the
reference through checkNoteData, and a reader shows it as publicNote only
when it is ok; otherwise unusableNote says so and StandardExtensions
reports it unusable with ERR_EXTENSION_DATA_INVALID, never the capsule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
testdata/ is synced with datekeys-go at c531e93, the head of the branch
v0.12, whose testdata is that of 601e6d2, the copy of datekeys-ts. Every
file keeps "spec": "0.11": the draft v0.12 is not approved yet. From the
tag spec-v0.11 it brings the fixtures format3_unsigned and format3_note,
format3_seal_unsupported with seal_type 4294967295, the records of
format3_sealed and format3_signed_cms, the mutation corpus of 218 cases,
note.json, security.json with a context and lines, security_cms.json of
135 cases and locator.json.
The vectors that the generators of tool/ make from the testdata are
written again by Go at c531e93: mutation_texts.json, open_cases.json,
formats_*.json with formats_vectors.g.dart, ibe_vectors.json and
age_fixtures.json; release_vectors.json, primitives.json and the views
of open_vectors.g.dart come out the same. age.json does not read the
testdata, and stays frozen: age draws its keys from crypto/rand. The
tests count 26 fixtures and 218 cases, and the inspection of
format3_note gives the note of its record. No difference with Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
open_cases_test.dart compares the opening of each fixture with each of its
credentials with the record of the fixture itself, not only with what Go
wrote: the content of formats 1 and 2 is its .plaintext, whose SHA-256 the
record gives, and in format 3 each file is the range of BODY that the
record names, with its comment, its declared author and the size of its
area. The changelog counts 1320 tests on the VM.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib/src/open.dart and open3.dart port Open and openBody of package capsule
of datekeys-go at c531e93, with the checks, codes, steps and texts of the
reference and the detail of each check: the .dkk of step 9.a, decoded or
still encoded, with its material, its critical extensions, its capsule_id
and its capsule_digest; at least one credential; the clock and the release,
with the rule of step 9 for the failures of its source (sourceFailure);
its verification at step 10; OUTER_TIME_AGE with the tlock stanza;
INNER_ACCESS_AGE with the X25519 identities, those of the .dkk and the key
of words, and the rules of the slots; CONTROL_CBOR, header_binding,
I_PAYLOAD and P; PAYLOAD_AGE streamed, with the padding of format 2
checked and never delivered, and in format 3 the frame of BODY, the area,
the head, each file to the sink with its SHA-256 and the padding, with the
precedence of spec §63; and the commit. A failure of age keeps the code of
the identity that reports it, or is ERR_INTEGRITY with the reason of its
phase, as classify of Go.
openCapsule opens a capsule in memory and openCapsuleSource one that a
ByteSource reads: the prefix of the inspection and the nonce of
PAYLOAD_AGE before the release, then pieces of 1 MiB. lib/src/sink.dart
has ByteSink, for the content of formats 1 and 2 and for each file, and
FileSink, for the files of format 3, as the dst and the Sink of Go, with
MemoryByteSink and MemoryFileSink. Nothing is presented as valid before
step 17 ends: the output is closed only then and aborted after any
failure, and the sink aborted after any failure that follows its begin
(spec §56).
The signature and the seal are stage 5: lib/src/verdicts.dart has the
verdicts and the SecurityEvaluator, given what newSecurityContext and
EvaluateSecurityIn of Go take, which never fails the opening; the default
evaluates nothing. OpenOptions.accept is Accept of Go. And
AgePayloadDecryptor.wipe clears the key of a STREAM left unread.
The tests run open_cases.json and the mutation corpus with the texts and
the checks of Go, in memory and from a source read in pieces; capsules of
several MiB made from the fixtures, for the streaming; a capsule with a
stanza for a key of words; and the caller, the sinks and the evaluator.
open_test.dart runs on Node.js too, with open_vectors.g.dart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib/src/inspect.dart ports Inspect of package capsule of datekeys-go at
c531e93: the steps 1 to 8 of spec §63, without network and without
secrets, with the check of each step as Go records it, its name and its
detail, and Inspection with the fields of the steps that passed. The
opening runs them with a hook right after step 2, as the afterPrelude of
Go. inspectedLength, from prefix.ts of datekeys-ts, names the first bytes
that give the inspection of a whole file, so that a large capsule is read
up to one byte after the largest age header of PAYLOAD_AGE, and
maxAccessKeyRead the most bytes of a .dkk that its decoder needs. And
inspectView and inspectJson, the exact output of datekeys inspect -json
of internal/inspectview, with the public note.
lib/src/source.dart has ByteSource, the bytes of a capsule read by ranges,
which the application adapts from a file or a Blob, and BytesSource, over
bytes in memory; inspectCapsuleSource reads only the prefix.
The tests compare, byte for byte, the 24 fixtures/*.inspect.json and the
views of open_inspect.json, and each of the 5110 mutations of
inspect_differential.json with its code, its step and the text of Go,
also from a source read in pieces; and the prefix at the limits of age.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib/src/note.dart ports CheckNote, NewNote and Note of package extension
of datekeys-go at c531e93: checkNote and checkNoteData check the length,
the UTF-8 and the rules of text of the declared author, in that order and
with the texts of Go, on the bytes of the note as Go reads its string;
newNote, publicNote and unusableNote, and Header.publicNote and
Header.unusableNote. StandardExtensions checks the data of a note as the
Standard of Go does, and its parameter validateNote, which could replace
those rules, is gone.
lib/src/head.dart ports DecodeHead, EncodeHead and CheckHeadEnd of
format3.go: the limit of HEAD_LEN, the type tag and the version, the CDDL
with R1 and R8 on the UTF-8 bytes of the paths, never on the UTF-16 code
units of a String, and then the comment, the declared author and the files
with the rules of pathrule and their layout, by subtraction, and R7 and R9,
as ERR_HEAD_INVALID with the text of Go, and the critical extensions of the
head. decodeWrittenHead is the decoder without the critical extensions, for
the self-check of the writer of stage 6.
The tests read open_heads.json, open_notes.json, head_schema.json with the
detail of each ERR_HEAD_INVALID, the trees of paths.json as heads of files
of 0 bytes, and the head of each fixture of format 3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tool/mutation_go_texts.go ports scripts/mutation-go-texts.go of
datekeys-ts over the synced testdata of this repository, the tag
spec-v0.11: it replays every case of the mutation corpus as the testkit of
the reference does and writes the text of capsule.Open and its checks,
with the detail of each step, in mutation_texts.json. Where Go and the
corpus disagree on a code or a step it would say so in the case; Go at
c531e93 and at the tag spec-v0.11 agree with all 210 cases, and give the
same file, byte for byte.
tool/open_go_vectors.go runs in an export of datekeys-go, since it uses
internal/testkit, internal/cbortest and internal/inspectview, and writes:
- open_cases.json: capsule.Open on every fixture with each of its
credentials, and 117 openings of edited fixtures or with other options
at each step that the corpus does not reach: the frame, the fields, the
extensions and the bindings of a .dkk at step 9.a, the clock and the
failures of the release source, the age headers of steps 11 and 17, a
malformed X25519 stanza in INNER_ACCESS_AGE, CONTROL_CBOR and the BODY
of format 3 sealed again, the sinks and the output that fail, the
refusal of Accept and the unusable extensions of each object, with the
text, the step, the checks, the release requests, the state of the
sink and the content or the files;
- open_heads.json: capsule.DecodeHead and EncodeHead of heads of a fixed
seed, valid and broken in each layer of spec §69.1;
- open_notes.json: extension.CheckNote, Note, Header.UnusableNote and
extension.Standard with a note;
- open_inspect.json: the text of capsule.Inspect for each of the 5110
mutations of inspect_differential.json, where Go and the file also
agree, and the exact output of datekeys inspect -json with public notes;
- open_vectors.g.dart: seven small fixtures, their records and a part of
each file, for the tests that run compiled to JavaScript.
The edited capsules are sealed again with the file keys and the nonces of
the fixtures, as the testkit does, so the output is the same on every run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Faults injected one at a time found four that the vectors let through:
R2 at 32 segments, R3 counting code points instead of UTF-16 code units
in an astral NFD, U+036F kept by normalizeWords, and DEL let through by
checkWords. The generators now write both sides of each limit: 32 and 33
segments, 255 and 256 bytes in letters of two and four bytes, 255 and 256
UTF-16 code units of NFD and 252 and 258 from astral decompositions,
bases of 8 and 9 runes, extensions of 3 and 4 also astral, the first and
the last mark of U+0300 to U+036F and their neighbours, and U+001F,
U+007E, U+007F, U+0080 and U+00A0 in a word. All four faults are caught
now.
wordKeyPassword is the password P of spec §38.1, as wordKeySalt is S, and
wordKey uses both: the tests compiled to JavaScript check P against the
one of Go, so that a wrong separator of the words is caught there too,
not only by the keys of 600 000 iterations on the VM.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib/src/wordkey.dart ports package wordkey of datekeys-go at c531e93:
normalizeWords is wordkey.Normalize (the NFD of pathrule.dart, without
U+0300 to U+036F, the simple lowercase of Unicode 18.0.0, split at the
white space of §38.1), checkWords is wordkey.Check with its texts,
wordKey is wordkey.Key (PBKDF2-HMAC-SHA256 of sha256.dart, 600 000
iterations, with the salt of §38.1) and wordIdentity is wordkey.Identity,
an X25519Identity of age.dart. As in pathrule.dart, the functions whose
name ends in Utf8 take the bytes of a Go string, and a String is taken as
utf8Bytes writes it.
tool/wordkey_go_vectors.go runs in the module context of datekeys-go and
writes test/vectors/wordkey_vectors.json and its Dart copy: 400 texts and
their words, 513 lists of words and the result of Check, and four keys
with their salt, the PBKDF2 of 1000 iterations for Node.js and the
recipient, the vector of §38.1 first. The keys of 600 000 iterations run
on the VM only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib/src/pathrule.dart ports internal/pathrule of datekeys-go at c531e93
(spec §29.5, §29.5.1, §29.6): NFD with the canonical ordering and Hangul,
the case folding, the simple lowercase, Default_Ignorable with the
whitelist of R4, the best-fit projections of R6c, every rule of a path
(R2 to R6c and R10), the tree (R7 with its key and the two paths it names,
and R9), and the texts of the comment and the declared author, with the
texts of Go. canonicalTables is pathrule.Canonical, and a test recomputes
tablesDigest from the lists.
Go reads a string as bytes, and so does this port: the functions whose
name ends in Utf8 take the bytes of a Go string, where a byte that is not
valid UTF-8 is the rune U+FFFD, and the limits count bytes; the others
take a String as utf8Bytes writes it. Each rule returns its violation, as
in Go, and only the public functions throw.
tool/pathrule_go_vectors.go runs in an export of datekeys-go, since
internal/pathrule cannot be imported from outside its tree, and writes
test/vectors/pathrule_vectors.json and its Dart copy: the cases of the
tests of Go and of datekeys-ts, 1300 strings and 350 trees drawn from a
fixed seed (marks, Hangul, ignorables, emoji, best-fit look-alikes,
device names, 8.3 aliases, limits, texts and invalid UTF-8), the cases of
R9, code points, and for each plane the SHA-256 of one line per code
point of each function. Every code point of every plane gives the results
of Go: planes 0, 1 and 14 also on Node.js.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib/datekeys.dart exports the formats, as index.ts of datekeys-ts: the
frames, PUBLIC_HEADER, CONTROL_CBOR, the .dkk, the extensions and their
registries, the Provider Profile, the DateKey with its rounds and times,
and the padding; the frame of BODY, the digest and the helpers of the
schemas stay internal for stage 4c. The README describes the modules, the
enums and the errors without a code, the Standard registry and the checks
it is given, the one difference of the formats between Go at c531e93 and
the tag spec-v0.11, and the generator of the vectors; the changelog the
stage, its tests and the faults injected. cbor_vectors_test.dart drops the
test it kept skipped until the decoders of the schemas, which
formats_vectors_test.dart now runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A fault that ignored the top bit of FLAGS of a .dkc passed the tests: the
cases had FLAGS of 1, or random bytes with other bits set. The generator
now writes each bit of FLAGS and of RESERVED alone, in the PRELUDE of a
.dkc and in the frame of a .dkk, with the text of Go, and the tests on the
VM and on Node.js try every bit of both frames.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A fault injected in the order of the layers of PUBLIC_HEADER, parsing the
DateKey before the rule across the extension arrays, passed the tests: no
random case had a DateKey of layer 4 and a fault of layer 3 together. The
generator now builds, for PUBLIC_HEADER, CONTROL_CBOR of the three
formats, the Provider Profile and the .dkk, each fault of a list alone and
each pair of them on a valid object, with Go's code and text: 153, 360,
153 and 181 cases, the .dkk sometimes with FLAGS 1 too. The cases use no
random value, so the other sections are the same as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tool/formats_go_vectors.go runs in the module of datekeys-go at c531e93,
without changing anything there, and writes test/vectors/formats_*.json:
the result, the normative code and the text of Go on inputs of a fixed
seed, valid and broken in every layer of spec §69.1, and on the fixtures
of testdata/, edited:
- the PRELUDE (229 cases), the steps 1 to 3 of capsule.Inspect on cut and
edited fixtures (492) and whole .dkk files (268);
- PUBLIC_HEADER (660) and CONTROL_CBOR of the three formats (618);
- Provider Profiles decoded (163) and validated as values (60);
- extension arrays (260), Canonical (80), CheckDisjoint (50), the
registries with places (120) and CheckWrite with Standard (60);
- dk1_ strings (466);
- RFC 3339 parsed (434) and formatted (80), Resolve (320), RoundTime (64),
Validate (128) and MaxRound (8), on profiles of other genesis times and
periods;
- PaddedLength and PayloadAgeLength at the boundaries up to L_MAX (474),
and the check of the padding of capsule.Open at step 17 on fixtures whose
PAYLOAD_AGE is encrypted again with an edited plaintext (56);
- the encoders on values and the decoders at the limits of spec §57 (90);
- the frame of BODY (260) and the zeros of the area (60).
The output is the same on every run. formats_vectors.g.dart holds every
eighth case as Dart constants, so that the differential runs compiled to
JavaScript too, on Node.js; a test on the VM checks that they are those of
the files. Every file is under 310 KB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib/src/header.dart, control.dart and accesskey.dart port DecodeHeader,
EncodeHeader, DecodeControl and EncodeControl of package capsule and
package accesskey of datekeys-go at c531e93, in the layers of spec §69.1
and with the texts of Go: the limit of the frame, the type tag and the
schema version, the CBOR profile with the re-encoding and the CDDL, keys 6
and 7 of CONTROL_CBOR versions 2 and 3, and only then the DateKey of the
header and access_type and access_material of the .dkk. The .dkk is
written with the rule of spec §72 for the extensions of the specification
and read back before it is returned, as MarshalBody. I_PAYLOAD and
access_material are copied once and wiped on every path; their objects
print without them. The access policy is the enum AccessPolicy.
The tests read every fixture of testdata/: the PRELUDE, the sections and
header_binding of the 24 capsules, their header and control decoded and
written back, the round time of their DateKey, P, and in format 3 the frame
of BODY, the area, the head and the files; the six .dkk with their
capsule_digest. And the shared vectors dk1.json, quicknet_rounds.json,
profile_quicknet.json, padding.json and the 172 schemas of cbor.json,
which stage 1 left aside, each decoded by its schema and written back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib/src/profile.dart ports package profile of datekeys-go at c531e93: the
Deterministic CBOR of a Provider Profile and its profile_hash, Decode and
Validate with rules 1 to 3 of spec §12.1 in their order and the texts of
Go (a period printed as Go prints a time.Duration), the drand schemes that
tlock supports and the group of their key, checked with
checkCompressedPoint, the chain hash of drand's chain.Info, MaxRound, the
pinned Quicknet profile and the registry with Default. Profile implements
PinnedProfile, which the verification of releases of stage 3 reads.
lib/src/datekey.dart ports package datekey: dk1_ strings, parsed with the
four Base64 decoders of Go and a JSON reader with the acceptance of
encoding/json with UseNumber, and numbers read by their exact decimal
value, with the texts of Go and its %v of the values; Resolve, RoundTime,
Validate and UnlockAt; and Instant, seconds and nanoseconds, with the RFC
3339 of Go's time.Parse(time.RFC3339Nano) and of Format, as datekey.ts of
datekeys-ts. A round is an int up to 2^53-1, exact on the web.
The tests, on the VM and compiled to JavaScript, check properties on
values of a fixed seed: dk1_ strings that read back, instants that format
and parse back to the nanosecond, and rounds whose time is the first at or
after the instant, at the end of the range of several profiles.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib/src/extension.dart ports package extension of datekeys-go at c531e93:
the structural rules of an array (1 to 64 entries in the order of the
UTF-8 bytes of extension_id, never of the UTF-16 code units of a String;
an extension_id of 1 to 256 bytes; data absent or non-empty) checked while
it is decoded and before it is written; Canonical, CheckDisjoint; the
registries with the optional data check and places of Go (an abstract
ExtensionRegistry whose defaults are those of a Go registry that is not a
DataValidator nor a Placement), ExtensionSet and KnownIn; CheckCritical
and CheckNoncritical, with and without the object; and CheckWrite with the
Standard registry of the extensions of spec §72, whose checks of the data
of a note and of a locator are given to it, since the rules of a note need
the tables of the rules of paths of stage 4a.
lib/src/schema.dart holds the helpers of the decoders of the objects, as
schema.ts of datekeys-ts: the key of a failing read, the required keys and
the extension arrays at their keys.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib/src/framing.dart ports ParsePrelude, Prelude.Bytes, PayloadOffset and
HeaderBinding of package capsule and the frame of Decode of package
accesskey of datekeys-go at c531e93, with their checks in the order of
spec §23 and §40 and their texts, and splitCapsule and FramingException,
the steps 1 to 3 of capsule.Inspect, as framing.ts of datekeys-ts. The
format of a capsule is the enum CapsuleFormat.
lib/src/padding.dart is padding.go: PaddedLength and PayloadAgeLength for
the codes of PaddingRule, exact up to L_MAX on the web too, where an int is
a double: bitlen doubles a power of two and the roundings divide and
multiply by powers of two, with no shift or mask of more than 31 bits. And
PaddingCheck, the checkPadding of capsule.Open at step 17, fed the
plaintext piece by piece.
lib/src/body.dart is the frame of BODY of format3.go (ParseBodyFrame,
CheckArea, ContentLength), and lib/src/digest.dart the incremental SHA-256
of a capsule_digest, with the comparison of checkCapsuleDigest. The errors
that Go returns without a normative code are ArgumentErrors with its text.
The tests run on the VM and compiled to JavaScript: the order of the
checks on capsules built in memory, P against a statement of spec §29.1 in
BigInt, next to 2^53 and at the boundaries of 32 bits, and the digest
against package:crypto however the file is cut.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The faults injected at the end of the stage were all caught on the VM, but
three of them only there: the point at infinity taken for a signature, the
code of a failing source kept at step 9, and the length of the stanza body
left unchecked. Three tests that read no file now catch them compiled to
JavaScript as well, the stanza being the one of the encryption of Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ibe.dart is DecryptCCAonG2 and EncryptCCAonG2 of drand/kyber for Quicknet,
as ibe.ts of datekeys-ts: H2 over GT in the order of kilic, H3 with its
rejection of candidates, H4, the identity of a round, the gates of the
signature and of U, and fixed texts that carry no value of the
computation. Encryption takes an injectable sigma, so that the vectors of
Go reproduce byte for byte; sigma and r are secret and BigInt is not
constant time.
release.dart is provider.Verify, in its order and with its texts, for the
scheme of Quicknet only, as release.ts; the supplied release; and the rule
of step 9, under which whatever a source throws is
ERR_RELEASE_UNAVAILABLE. tlock.dart is NewTimeIdentity with its Unwrap and
NewTimeRecipient of agewrap on the arguments and the body of the stanza.
tool/ibe_go_vectors.go, tool/tlock_go_vectors.go and
tool/release_go_vectors.go, ports of the generators of datekeys-ts where
they exist, write the vectors from kyber, tlock, age, provider and agewrap
on the fixtures of testdata/. The ciphertexts with a random sigma are the
frozen ones of datekeys-ts at 289fe71, decrypted again by Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The field layer, an extension type over BigInt that a later implementation
with fixed limbs can replace alone; the tower Fp2, Fp6 and Fp12 with the
formulas of kilic; G1 and G2 with their compressed encodings and the
verdicts of FromCompressed (flags, the point at infinity, coordinates below
p, the curve and the subgroup, checked in G2 by psi(P) = [x]P); the optimal
ate pairing with the final exponentiation of kilic, GT serialized c1 before
c0 at every level; and the hash to G1 of RFC 9380 with the DST of Quicknet.
tool/bls12381_go_vectors.go writes test/vectors/bls12381_vectors.json with
kilic and kyber-bls12381: the frozen edge cases of datekeys-ts with their Go
verdicts recomputed, and decodings, sums, multiples, pairings, hashes, maps
and BLS signatures drawn from a fixed seed. BigInt is not constant time:
the README says where that matters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the web a shift truncates to 32 bits, so the carries of Poly1305 are
taken with a division. A fault injected in the carry of limb 0 passed every
test, on the VM, where the shift is exact, and on Node, because no vector
took that sum past 2^32. The generator now simulates the 13-bit limbs with
the largest r that clamping allows and finds two messages that do; Go's
poly1305 gives their tags, and the fault fails on Node. The sums of the
other limbs stay below 2^32 (at most 4.14e9 with that r).
The strict Ed25519 verification is also checked against
testdata/vectors/ed25519_strict.json directly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>