The modules of the primitives and of age, the integer bounds of each, the
note that BigInt is not constant time and where it is used, the vectors of
test/vectors/ and how Go writes them, and the timings on the VM and on
Node.js.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The unmasked rotations of the previous commit were exact, but on the VM
they left values of up to 62 bits in the sums, against the rule of the
package: every value in 32 bits on the VM as on the web. Masked again, the
unrolled rounds are as fast: PBKDF2 at 600 000 iterations takes about 1 s
on the VM.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The working variables rotate by name instead of by assignment, and the
left halves of the rotations are no longer masked: on the VM their bits
above 32 only reach sums that are masked before any other use, and on the
web `<<` keeps 32 bits itself. PBKDF2 at 600 000 iterations goes from 1.27
to 1.11 s on the VM; HMAC of package:crypto takes about 3.5 s for the same
work.
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>
The product of Poly1305 and that of the field of curve25519 are unrolled
over locals, as TweetNaCl-js does, and ChaCha20 XORs whole blocks: on the
VM, X25519 goes from 2.4 to 1.3 ms, Ed25519 verification from 8.6 to
4.7 ms and ChaCha20-Poly1305 from 40 to 19 ms per MiB. The bounds of the
arithmetic do not change.
tool/bench.dart times the primitives on the VM or compiled to JavaScript.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
age: the header and its limits (internal/format), with the texts of Go's
errors; the header MAC; the X25519 and scrypt stanzas, the scrypt work
factor bounded at 16 by default, as authorkey bounds it; the payload key
and the STREAM of internal/stream, decrypted as the ciphertext arrives,
with the same end-of-file cases as Go's DecryptReader. Each failure is an
AgeException with Go's text and its phase, the header or the payload.
agewrap: the stanza rules of OUTER_TIME_AGE, PAYLOAD_AGE and
INNER_ACCESS_AGE, the probe of the stanzas, and the payload and access
identities, with the fixed texts and the codes of datekeys-go.
tool/gen_age_vectors.go writes, with filippo.io/age and agewrap:
- test/vectors/age.json: X25519 and scrypt files, their truncations and
manipulations, a corpus of headers against the grammar of spec §28.1
and the 2 MiB limit, the rules and identities of agewrap, and Go's text
for each. A file of more than one chunk is its header, nonce and file
key; the tests encrypt the plaintext again and check the SHA-256 of the
whole file;
- test/vectors/age_fixtures.json: the PAYLOAD_AGE of every fixture with its
payload_identity, and the INNER_ACCESS_AGE of the time_and_key ones,
taken from OUTER_TIME_AGE with the release of the fixture.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The generator checks its copy of the strict profile against the
testdata/ of datekeys-dart, the copy synced at spec-v0.11, rather than
the working tree of datekeys-go. The vectors do not change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SHA-256 with its own compression, and on it HMAC-SHA256 with the inner and
outer states of the key computed once, HKDF-SHA256 and PBKDF2-HMAC-SHA256,
whose iterations are two compressions over words; scrypt with Salsa20/8;
ChaCha20, Poly1305 in 13-bit limbs and ChaCha20-Poly1305 with the tag
compared in constant time; X25519 on the field of TweetNaCl in doubles,
with the all-zero secret refused; the strict Ed25519 verification of
internal/ed25519strict; Go's Base64 with the offsets of its errors, and
age's Bech32.
tool/gen_primitive_vectors.go computes every expected value with Go and
x/crypto, from the inputs of RFC 5869, 7748, 7914, 8032 and 8439, edge
cases and seeded random ones. The tests also run compiled to JavaScript,
from a Dart copy of the JSON, without the cases that would take too long.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tool/check.sh runs dart test -p node after dart test: the tests that read
no file check on every commit that the integers of the library are exact
on the web, where an int is a double and the bit operators work on 32 bits.
The gate needs Node.js, as datekeys-ts does. The author decided it on 5
October.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- README: the state, with the modules of stage 1 and how its integers
are exact on the VM and on the web, and how to run the tests compiled
to JavaScript, which the gate does not run.
- CHANGELOG: stage 1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib/src/der.dart is the port of internal/der/der.go at 601e6d2, the
draft of v0.12, whose DER already differs from spec-v0.11: check, split,
content, setOfSorted and parseTime, with the texts of the reference. It
is internal, as in Go, and keeps the names of the Go package for an
import with a prefix.
parseTime reads UTCTime and GeneralizedTime in their forms of X.690
and refuses a date or a time that does not exist. It returns a DerTime,
exact to the nanosecond as Go's time.Time and unlike Dart's DateTime:
its fields and its seconds since the epoch, below 2^53.
The tests port der_test.go, with the texts that Go prints. The fuzz
target is a property over seeded mutations of its seeds and of the
signatures and tokens of security_cms.json.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lib/src/cbor.dart is the port of codec/codec.go: CborEncoder,
CborDecoder, unmarshalCbor, peekSchema, checkSchema and walkCbor, with
the same reads, the same checks in the same order and the same error
texts, such as "codec: offset 0: 23 is not in its shortest form
(initial byte 0x18): ERR_NON_CANONICAL_CBOR".
Integers are exact on the VM and on the web, where an int is a double
and the bit operators work on 32 bits. An argument of eight bytes is
read as two halves of 32 bits, and is an int up to 2^53-1 and a BigInt
above, map keys and the numbers of the error texts included. uint
returns an int, since every schema bounds its integers at 2^53-1, and
uint64 a BigInt. The map that peekSchema reads is bounded at 2^63-1,
Go's math.MaxInt, on the web too.
Two kinds of text are of Dart only. CborEncoder.uint refuses an int
outside 0..2^53-1 and uint64 a BigInt outside 0..2^64-1, with the text
of datekeys-ts, where Go's uint64 cannot hold such a value. And the only
invalid text that a Dart String holds is a lone surrogate: the error
quotes it as Go quotes its bytes in generalized UTF-8.
The tests port codec_test.go, internal_test.go and vectors_test.go,
with the texts that Go prints, and cbor.test.ts. The fuzz targets are
properties over seeded inputs, checked against a reference encoder and
decoder written apart, as internal/cbortest. cbor.json runs its 36
accept and 67 reject vectors with the walk limits and the values; its
172 schema vectors are read and wait for the schema decoders of stage 4.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- lib/src/errors.dart, the port of errors.go: ErrorCode, the 19 codes in
the order of section 69, and DateKeysException, which carries one of
them with the message of the reference, the context and then the code.
wrap and withContext are fmt.Errorf("prefix: %w"), errorCode is
datekeys.Code. test/errors_spec_test.dart reads section 69 from
../datekeys-go at the tag spec-v0.11 and compares its ERR_ lines with
the catalogue; it is skipped when that repository is missing.
- lib/src/bytes.dart, as bytes.ts of datekeys-ts: hexadecimal,
comparison and concatenation; strict UTF-8 that keeps a leading
U+FEFF, which the Utf8Decoder of dart:convert drops on the VM and on
the web; Go's utf8.DecodeRune; and Go's %q, with the table of
strconv.IsPrint of Go 1.26 copied from datekeys-ts and the SHA-256 of
the whole rune set pinned. A lone surrogate, which a Dart String may
hold, is written in generalized UTF-8, which is not UTF-8.
- lib/datekeys.dart exports the errors and the byte functions that a
user needs.
- The tests that read files are marked @TestOn('vm'), those of stage 0
included, so that the others also run compiled to JavaScript with
dart test -p node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A pure Dart package, without Flutter, for the app that the author will
write in Flutter (docs/PLAN_dart.md, stage 0):
- pubspec.yaml: the package datekeys, unpublished, for Dart 3.13; at run
time only package:crypto, and in development only package:test, which
the author approved on 5 October.
- testdata/ vendored from datekeys-go at the tag spec-v0.11 (ae33434),
124 files, with the same testdata/SOURCE.json that datekeys-ts writes for
that commit.
- tool/sync_testdata.dart, the port of scripts/sync-testdata.mjs, and
test/testdata_test.dart, which checks the copy and that every file names
specVersion; test/version_test.dart keeps pubspec.yaml and
lib/src/version.dart in step.
- tool/check.sh, the local gate: format, analysis with every info fatal,
tests, and the copy against ../datekeys-go.
- The licence, Apache-2.0, as the other two implementations.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>