|
|
|
|
package testkit_test
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"encoding/hex"
|
|
|
|
|
"encoding/json"
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"reflect"
|
Test data: security.json in the context of a capsule, with v0.12 verdicts
The review found that security.json said spec 0.11 and still gave the
verdicts of a reader of v0.10, evaluated without context, contrary to what
section 76 announced (D1).
- security.json carries the context of a capsule, its commitments and the
time of its round, and each case its verdicts and the lines of the
official SDK in it: a signature of alg 1 that does not verify is F2, a
token of seal_type 2 that is not DER is S2, and new cases give a valid
signature of alg 1 (F4) and alg and seal_type 4294967295 (F1, S1).
- mutations.json: the signature of alg 1 that does not verify (F2) is a
case of 64, and the seal that opens with S1 uses seal_type 4294967295,
not seal_type 1, which a later version may define.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
"slices"
|
|
|
|
|
"testing"
|
|
|
|
|
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
"filippo.io/age"
|
|
|
|
|
|
Format 3, step 6c: the vectors of paths, keys, heads and security
The vector files of spec 67 for format 3, generated with the result
each case is written for, so that the generator fails when the
implementation or the tables change:
- paths.json: 83 paths with the result of the rules of one entry, the
violation worded as every implementation must word it, and 16 trees,
the paths of a head and the result of decoding it: U+00A0, accepted,
and U+3000, R6c, at both ends of a segment; best-fit, full-width
forms, U+00B4 of cp1253; 8.3 aliases with ~1; Cn; U+206A to U+206F,
tags and other ignorables; a dot and ZWJ, and ZWJ alone; 127 and 85
times U+0390; U+F03A; .datekeys-x at two levels; U+FF5E and U+1F600
in both orders; ab with and without ZWNJ; U+00BF, U+00A7 and U+2665,
accepted; VS16 after U+2764 and after a; ZWJ at the start, at the end
and twice; the rainbow flag and the flag of Scotland; b/.. and a.
- path_fold.json: 22 segments with their NFD and their key of R7,
among them the entries F of CaseFolding, the dotless i, the Kelvin
and Angstrom signs, Cherokee, Hangul and the whitelist dropped
before NFD.
- head_schema.json: 63 heads through layers 2, 3 and 4, in key order,
with the violation of each ERR_HEAD_INVALID; they add comments with
tags and with loose variation selectors.
- security.json: 21 areas with their verdicts, X, F0, F1, S0, S1 and
S2, among them key 2 that is not a byte string, a key 4, a byte more,
alg 0, an empty key with the seal intact, and a seal that breaks its
schema with an unknown seal_type.
- cbor.json gains the control of schema version 3.
A test replays every committed vector through the implementation, and
another checks that the files are current.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
"g.activething.com/go/DateKeys/internal/pathrule"
|
|
|
|
|
"g.activething.com/go/DateKeys/internal/testkit"
|
Spec v0.8.2 amendment: canonical point encoding; no library error text
Amendment of the unreleased v0.8.2, recorded in §76 with its case: the
second implementation's phase-2 research found that tlock-js over
@noble/curves 1.9.7 accepts U re-encoded as c0 + p and a signature
x + p and returns the same file key, while the reference rejects both
(noble 1.9.7 differed from kilic on 5,615 of 41,686 encodings), and the
spec did not say which encodings are valid.
- §12.2 defines the canonical encoding of a BLS12-381 point (drand's
compressed ZCash form) and requires decoders to reject every other
byte string; §12.1 applies it to public_key.
- §63 step 10 applies it to the release signature (ERR_RELEASE_INVALID)
and step 11 defines the tlock stanza body U || V || W (96 + 16 + 16
bytes for Quicknet) with a canonical, non-infinity U (ERR_INTEGRITY).
- §64 gains ten mutations, exported to mutations.json (65 cases). The
signature x + p case uses published Quicknet round 1004, the first
after 1000 whose x allows x + p < 2^381. The reference already gave
every stated code and step.
Errors no longer copy text from tlock, kyber, age, drand or
kyber-bls12381. kyber's IBE error carried the candidate plaintext and r,
and with one bit of W flipped the message disclosed the real tlock file
key with that bit flipped. Every such place now uses a fixed reason with
its normative sentinel; TestTlockFailureDiagnosticsCarryNoSecrets fails
with the old wrapping.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
"g.activething.com/go/DateKeys/profile"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestEdits(t *testing.T) {
|
|
|
|
|
base := []byte("0123456789")
|
Format 3, step 6d: the mutations of format 3
The mutation corpus of spec 64 gains format 3: 209 cases, 169 of the
spec.
- The 33 mutations of the first two lists on format3_single and
format3_time_and_key_portable, named "format 3: ...", with a sibling
written by EncryptFiles and built capsules that hold a BODY.
- The list of format 3, 47 cases: one for each value of a line with
several (AREA_LEN 0, 511, 513 and 66048; SECURITY_LEN 0 and 513;
HEAD_LEN 0 and 2^24 + 1), and the three that open without a code,
with the verdicts X, F1 and S1. VERSION 4 is "format 3: version
changed", as in format 2. Each seals BODY again with FK_PAYLOAD and
the nonce of its fixture, and the control with the new L when L
changes; the two that need a head followed by another STREAM chunk
derive from format3_tree, whose comment takes the bytes the path
loses so that only the head and its chunk change.
- Further cases: format 3 relabeled 1, and time_and_key relabeled 2
with the identity and with the .dkk.
- A mutation may expect the capsule to open with its verdicts; the
exported case records them, with the result ok at step 0.
- Splice gives an edit for each run of changed bytes, runs closer than
16 bytes merged, and one more for what one side has beyond the
other: a head sealed again changes its bytes and the tag of its
chunk, 64 KiB apart. Earlier cases are written with more edits and
give the same capsules. The corpus is 706 KB, 476 KB of them the
capsule of 65536 implicit folders, whose head is 235 KB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
long := "0123456789abcdefghijklmnopqrstuvwxyz"
|
|
|
|
|
for _, tc := range []struct {
|
Format 3, step 6d: the mutations of format 3
The mutation corpus of spec 64 gains format 3: 209 cases, 169 of the
spec.
- The 33 mutations of the first two lists on format3_single and
format3_time_and_key_portable, named "format 3: ...", with a sibling
written by EncryptFiles and built capsules that hold a BODY.
- The list of format 3, 47 cases: one for each value of a line with
several (AREA_LEN 0, 511, 513 and 66048; SECURITY_LEN 0 and 513;
HEAD_LEN 0 and 2^24 + 1), and the three that open without a code,
with the verdicts X, F1 and S1. VERSION 4 is "format 3: version
changed", as in format 2. Each seals BODY again with FK_PAYLOAD and
the nonce of its fixture, and the control with the new L when L
changes; the two that need a head followed by another STREAM chunk
derive from format3_tree, whose comment takes the bytes the path
loses so that only the head and its chunk change.
- Further cases: format 3 relabeled 1, and time_and_key relabeled 2
with the identity and with the .dkk.
- A mutation may expect the capsule to open with its verdicts; the
exported case records them, with the result ok at step 0.
- Splice gives an edit for each run of changed bytes, runs closer than
16 bytes merged, and one more for what one side has beyond the
other: a head sealed again changes its bytes and the tag of its
chunk, 64 KiB apart. Earlier cases are written with more edits and
give the same capsules. The corpus is 706 KB, 476 KB of them the
capsule of 65536 implicit folders, whose head is 235 KB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
base string
|
|
|
|
|
out string
|
|
|
|
|
want string // JSON of Splice(base, out)
|
|
|
|
|
}{
|
Format 3, step 6d: the mutations of format 3
The mutation corpus of spec 64 gains format 3: 209 cases, 169 of the
spec.
- The 33 mutations of the first two lists on format3_single and
format3_time_and_key_portable, named "format 3: ...", with a sibling
written by EncryptFiles and built capsules that hold a BODY.
- The list of format 3, 47 cases: one for each value of a line with
several (AREA_LEN 0, 511, 513 and 66048; SECURITY_LEN 0 and 513;
HEAD_LEN 0 and 2^24 + 1), and the three that open without a code,
with the verdicts X, F1 and S1. VERSION 4 is "format 3: version
changed", as in format 2. Each seals BODY again with FK_PAYLOAD and
the nonce of its fixture, and the control with the new L when L
changes; the two that need a head followed by another STREAM chunk
derive from format3_tree, whose comment takes the bytes the path
loses so that only the head and its chunk change.
- Further cases: format 3 relabeled 1, and time_and_key relabeled 2
with the identity and with the .dkk.
- A mutation may expect the capsule to open with its verdicts; the
exported case records them, with the result ok at step 0.
- Splice gives an edit for each run of changed bytes, runs closer than
16 bytes merged, and one more for what one side has beyond the
other: a head sealed again changes its bytes and the tag of its
chunk, 64 KiB apart. Earlier cases are written with more edits and
give the same capsules. The corpus is 706 KB, 476 KB of them the
capsule of 65536 implicit folders, whose head is 235 KB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
{"", "0123456789", `[]`},
|
|
|
|
|
{"", "01X3456789", `[[2,1,"58"]]`},
|
|
|
|
|
{"", "012", `[[3,7,""]]`},
|
|
|
|
|
{"", "", `[[0,10,""]]`},
|
|
|
|
|
{"", "01234567890", `[[10,0,"30"]]`},
|
|
|
|
|
{"", "0123XY456789", `[[4,0,"5859"]]`},
|
|
|
|
|
// Runs 16 or more equal bytes apart are edits of their own; closer
|
|
|
|
|
// ones are merged.
|
|
|
|
|
{long, "0X23456789abcdefghijklmnopqrstuvwxYz", `[[1,1,"58"],[34,1,"59"]]`},
|
|
|
|
|
{long, "0X23456789aXcdefghijklmnopqrstuvwxyz", `[[1,11,"5832333435363738396158"]]`},
|
|
|
|
|
// A change and a cut: an edit and a deletion.
|
|
|
|
|
{long, "0X23456789abcdefghijklmnopqrst", `[[1,1,"58"],[30,6,""]]`},
|
|
|
|
|
// A change and more bytes: an edit and an insertion.
|
|
|
|
|
{long, "0X23456789abcdefghijklmnopqrstuvwxyz!!", `[[1,1,"58"],[36,0,"2121"]]`},
|
|
|
|
|
} {
|
Format 3, step 6d: the mutations of format 3
The mutation corpus of spec 64 gains format 3: 209 cases, 169 of the
spec.
- The 33 mutations of the first two lists on format3_single and
format3_time_and_key_portable, named "format 3: ...", with a sibling
written by EncryptFiles and built capsules that hold a BODY.
- The list of format 3, 47 cases: one for each value of a line with
several (AREA_LEN 0, 511, 513 and 66048; SECURITY_LEN 0 and 513;
HEAD_LEN 0 and 2^24 + 1), and the three that open without a code,
with the verdicts X, F1 and S1. VERSION 4 is "format 3: version
changed", as in format 2. Each seals BODY again with FK_PAYLOAD and
the nonce of its fixture, and the control with the new L when L
changes; the two that need a head followed by another STREAM chunk
derive from format3_tree, whose comment takes the bytes the path
loses so that only the head and its chunk change.
- Further cases: format 3 relabeled 1, and time_and_key relabeled 2
with the identity and with the .dkk.
- A mutation may expect the capsule to open with its verdicts; the
exported case records them, with the result ok at step 0.
- Splice gives an edit for each run of changed bytes, runs closer than
16 bytes merged, and one more for what one side has beyond the
other: a head sealed again changes its bytes and the tag of its
chunk, 64 KiB apart. Earlier cases are written with more edits and
give the same capsules. The corpus is 706 KB, 476 KB of them the
capsule of 65536 implicit folders, whose head is 235 KB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
base := base
|
|
|
|
|
if tc.base != "" {
|
|
|
|
|
base = []byte(tc.base)
|
|
|
|
|
}
|
|
|
|
|
edits := testkit.Splice(base, []byte(tc.out))
|
|
|
|
|
if edits == nil {
|
|
|
|
|
edits = []testkit.Edit{}
|
|
|
|
|
}
|
|
|
|
|
b, err := json.Marshal(edits)
|
|
|
|
|
if err != nil || string(b) != tc.want {
|
|
|
|
|
t.Errorf("Splice(%q) = %s, %v; want %s", tc.out, b, err, tc.want)
|
|
|
|
|
}
|
|
|
|
|
var back []testkit.Edit
|
|
|
|
|
if err := json.Unmarshal(b, &back); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
out, err := testkit.ApplyEdits(base, back)
|
|
|
|
|
if err != nil || string(out) != tc.out {
|
|
|
|
|
t.Errorf("ApplyEdits(%s) = %q, %v; want %q", b, out, err, tc.out)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Several edits refer to offsets of the base.
|
|
|
|
|
out, err := testkit.ApplyEdits(base, []testkit.Edit{{At: 1, Delete: 1, Insert: []byte("ab")}, {At: 5, Delete: 2}, {At: 10, Insert: []byte("!")}})
|
|
|
|
|
if err != nil || string(out) != "0ab234789!" {
|
|
|
|
|
t.Fatalf("got %q, %v", out, err)
|
|
|
|
|
}
|
|
|
|
|
for _, bad := range [][]testkit.Edit{
|
|
|
|
|
{{At: 5, Delete: 1}, {At: 2, Delete: 1}}, // out of order
|
|
|
|
|
{{At: 2, Delete: 3}, {At: 4, Delete: 1}}, // overlapping
|
|
|
|
|
{{At: 9, Delete: 2}}, // beyond the base
|
|
|
|
|
{{At: -1}},
|
|
|
|
|
} {
|
|
|
|
|
if _, err := testkit.ApplyEdits(base, bad); err == nil {
|
|
|
|
|
t.Errorf("ApplyEdits accepted %v", bad)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, bad := range []string{`[1,2]`, `[1,2,"zz"]`, `["1",2,""]`, `{}`} {
|
|
|
|
|
var e testkit.Edit
|
|
|
|
|
if err := json.Unmarshal([]byte(bad), &e); err == nil {
|
|
|
|
|
t.Errorf("Edit accepted %s", bad)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Spec v0.8.2 amendment: canonical point encoding; no library error text
Amendment of the unreleased v0.8.2, recorded in §76 with its case: the
second implementation's phase-2 research found that tlock-js over
@noble/curves 1.9.7 accepts U re-encoded as c0 + p and a signature
x + p and returns the same file key, while the reference rejects both
(noble 1.9.7 differed from kilic on 5,615 of 41,686 encodings), and the
spec did not say which encodings are valid.
- §12.2 defines the canonical encoding of a BLS12-381 point (drand's
compressed ZCash form) and requires decoders to reject every other
byte string; §12.1 applies it to public_key.
- §63 step 10 applies it to the release signature (ERR_RELEASE_INVALID)
and step 11 defines the tlock stanza body U || V || W (96 + 16 + 16
bytes for Quicknet) with a canonical, non-infinity U (ERR_INTEGRITY).
- §64 gains ten mutations, exported to mutations.json (65 cases). The
signature x + p case uses published Quicknet round 1004, the first
after 1000 whose x allows x + p < 2^381. The reference already gave
every stated code and step.
Errors no longer copy text from tlock, kyber, age, drand or
kyber-bls12381. kyber's IBE error carried the candidate plaintext and r,
and with one bit of W flipped the message disclosed the real tlock file
key with that bit flipped. Every such place now uses a fixed reason with
its normative sentinel; TestTlockFailureDiagnosticsCarryNoSecrets fails
with the old wrapping.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
// The re-encodings of the point mutations (spec §12.2) change only what their
|
|
|
|
|
// names say, and x + p fits for XPlusPRound only among the known rounds.
|
|
|
|
|
func TestPointReencodings(t *testing.T) {
|
|
|
|
|
sig := testkit.Release(testkit.XPlusPRound).Signature
|
|
|
|
|
xp, err := testkit.AddModulus(sig, 0)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if xp[0]&0xe0 != sig[0]&0xe0 || bytes.Equal(xp, sig) || !bytes.Equal(testkit.ReduceCoordinate(xp, 0), sig) {
|
|
|
|
|
t.Fatalf("x + p of %x is %x", sig, xp)
|
|
|
|
|
}
|
|
|
|
|
for _, r := range testkit.Rounds {
|
|
|
|
|
if _, err := testkit.AddModulus(testkit.Release(r).Signature, 0); (err == nil) != (r == testkit.XPlusPRound) {
|
|
|
|
|
t.Errorf("round %d: x + p fits: %v", r, err == nil)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
key := profile.Quicknet().PublicKey
|
|
|
|
|
for _, at := range []int{0, testkit.CoordinateLen} {
|
|
|
|
|
k, err := testkit.AddModulus(key, at)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("coordinate at %d of the Quicknet key: %v", at, err)
|
|
|
|
|
}
|
|
|
|
|
other := testkit.CoordinateLen - at
|
|
|
|
|
if !bytes.Equal(k[other:other+testkit.CoordinateLen], key[other:other+testkit.CoordinateLen]) || !bytes.Equal(testkit.ReduceCoordinate(k, at), key) {
|
|
|
|
|
t.Fatalf("coordinate at %d: %x", at, k)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, at := range []int{-48, 1, testkit.CoordinateLen} {
|
|
|
|
|
if _, err := testkit.AddModulus(sig, at); err == nil {
|
|
|
|
|
t.Errorf("offset %d of a G1 point accepted", at)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if n := testkit.Negated(sig); n[0]^sig[0] != testkit.FlagSort || !bytes.Equal(n[1:], sig[1:]) || !bytes.Equal(testkit.Negated(n), sig) {
|
|
|
|
|
t.Fatalf("negated %x", n)
|
|
|
|
|
}
|
|
|
|
|
if i := testkit.InfinityWithPayload(sig); i[0] != 0xc0|sig[0]&0x1f || !bytes.Equal(i[1:], sig[1:]) {
|
|
|
|
|
t.Fatalf("infinity with payload %x", i)
|
|
|
|
|
}
|
|
|
|
|
if i := testkit.Infinity(96); len(i) != 96 || i[0] != 0xc0 || !bytes.Equal(i[1:], make([]byte, 95)) {
|
|
|
|
|
t.Fatalf("infinity %x", i)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The schema vectors of testdata/vectors/cbor.json replay: the decoder of each
|
|
|
|
|
// schema gives exactly the recorded result.
|
|
|
|
|
func TestSchemaVectors(t *testing.T) {
|
|
|
|
|
var f testkit.CBORVectorFile
|
|
|
|
|
if err := testkit.ReadJSON("../../testdata/vectors/cbor.json", &f); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
blocks := map[string]int{}
|
|
|
|
|
for _, v := range f.Schemas {
|
|
|
|
|
b, err := hex.DecodeString(v.Hex)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
if got := testkit.Result(testkit.DecodeSchema(v.Schema, b, v.Format)); got != v.Result {
|
|
|
|
|
t.Errorf("%s %q: got %s, want %s", v.Block, v.Name, got, v.Result)
|
|
|
|
|
}
|
|
|
|
|
blocks[v.Block]++
|
|
|
|
|
}
|
|
|
|
|
for _, b := range []string{testkit.SchemaProfile, testkit.SchemaHeader, testkit.SchemaControl, testkit.SchemaDKKBody, "verification_metadata", "extension"} {
|
|
|
|
|
if blocks[b] < 5 {
|
|
|
|
|
t.Errorf("block %s has %d vectors", b, blocks[b])
|
|
|
|
|
}
|
|
|
|
|
}
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
if err := testkit.DecodeSchema("nope", nil, 0); err == nil {
|
|
|
|
|
t.Error("unknown schema accepted")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Format 3, step 6c: the vectors of paths, keys, heads and security
The vector files of spec 67 for format 3, generated with the result
each case is written for, so that the generator fails when the
implementation or the tables change:
- paths.json: 83 paths with the result of the rules of one entry, the
violation worded as every implementation must word it, and 16 trees,
the paths of a head and the result of decoding it: U+00A0, accepted,
and U+3000, R6c, at both ends of a segment; best-fit, full-width
forms, U+00B4 of cp1253; 8.3 aliases with ~1; Cn; U+206A to U+206F,
tags and other ignorables; a dot and ZWJ, and ZWJ alone; 127 and 85
times U+0390; U+F03A; .datekeys-x at two levels; U+FF5E and U+1F600
in both orders; ab with and without ZWNJ; U+00BF, U+00A7 and U+2665,
accepted; VS16 after U+2764 and after a; ZWJ at the start, at the end
and twice; the rainbow flag and the flag of Scotland; b/.. and a.
- path_fold.json: 22 segments with their NFD and their key of R7,
among them the entries F of CaseFolding, the dotless i, the Kelvin
and Angstrom signs, Cherokee, Hangul and the whitelist dropped
before NFD.
- head_schema.json: 63 heads through layers 2, 3 and 4, in key order,
with the violation of each ERR_HEAD_INVALID; they add comments with
tags and with loose variation selectors.
- security.json: 21 areas with their verdicts, X, F0, F1, S0, S1 and
S2, among them key 2 that is not a byte string, a key 4, a byte more,
alg 0, an empty key with the seal intact, and a seal that breaks its
schema with an unknown seal_type.
- cbor.json gains the control of schema version 3.
A test replays every committed vector through the implementation, and
another checks that the files are current.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
// The vectors of format 3 replay: each path, tree, key, head and security
|
|
|
|
|
// area of the committed files gets its recorded result from the
|
|
|
|
|
// implementation, with the tables the files name (spec §29.3 to §29.7).
|
|
|
|
|
func TestFormat3VectorFiles(t *testing.T) {
|
|
|
|
|
const dir = "../../testdata/vectors/"
|
|
|
|
|
var paths testkit.PathVectorFile
|
|
|
|
|
if err := testkit.ReadJSON(dir+"paths.json", &paths); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
var fold testkit.PathFoldFile
|
|
|
|
|
if err := testkit.ReadJSON(dir+"path_fold.json", &fold); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
for _, f := range []struct{ version, digest string }{{paths.UnicodeVersion, paths.TablesDigest}, {fold.UnicodeVersion, fold.TablesDigest}} {
|
|
|
|
|
if f.version != pathrule.UnicodeVersion || f.digest != pathrule.TablesDigest {
|
|
|
|
|
t.Fatalf("vectors of Unicode %s and tables %s", f.version, f.digest)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, v := range paths.Paths {
|
|
|
|
|
if got := testkit.PathResult(v.Path); got != v.Result {
|
|
|
|
|
t.Errorf("path %q: %q, want %q", v.Name, got, v.Result)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, v := range paths.Trees {
|
|
|
|
|
b, err := testkit.TreeHead(v.Paths)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if got, detail := testkit.HeadResult(b); got != v.Result || detail != v.Detail {
|
|
|
|
|
t.Errorf("tree %q: %s %q, want %s %q", v.Name, got, detail, v.Result, v.Detail)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, v := range fold.Keys {
|
|
|
|
|
if pathrule.NFD(v.Segment) != v.NFD || pathrule.Key(v.Segment) != v.Key {
|
|
|
|
|
t.Errorf("key %q: NFD %+q, key %+q", v.Name, pathrule.NFD(v.Segment), pathrule.Key(v.Segment))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var heads testkit.HeadSchemaFile
|
|
|
|
|
if err := testkit.ReadJSON(dir+"head_schema.json", &heads); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
for _, v := range heads.Heads {
|
|
|
|
|
b, err := hex.DecodeString(v.Hex)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if got, detail := testkit.HeadResult(b); got != v.Result || detail != v.Detail {
|
|
|
|
|
t.Errorf("head %q: %s %q, want %s %q", v.Name, got, detail, v.Result, v.Detail)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var security testkit.SecurityVectorFile
|
|
|
|
|
if err := testkit.ReadJSON(dir+"security.json", &security); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
Test data: security.json in the context of a capsule, with v0.12 verdicts
The review found that security.json said spec 0.11 and still gave the
verdicts of a reader of v0.10, evaluated without context, contrary to what
section 76 announced (D1).
- security.json carries the context of a capsule, its commitments and the
time of its round, and each case its verdicts and the lines of the
official SDK in it: a signature of alg 1 that does not verify is F2, a
token of seal_type 2 that is not DER is S2, and new cases give a valid
signature of alg 1 (F4) and alg and seal_type 4294967295 (F1, S1).
- mutations.json: the signature of alg 1 that does not verify (F2) is a
case of 64, and the seal that opens with S1 uses seal_type 4294967295,
not seal_type 1, which a later version may define.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
c, err := testkit.SecurityContextOf(security.Context)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
Format 3, step 6c: the vectors of paths, keys, heads and security
The vector files of spec 67 for format 3, generated with the result
each case is written for, so that the generator fails when the
implementation or the tables change:
- paths.json: 83 paths with the result of the rules of one entry, the
violation worded as every implementation must word it, and 16 trees,
the paths of a head and the result of decoding it: U+00A0, accepted,
and U+3000, R6c, at both ends of a segment; best-fit, full-width
forms, U+00B4 of cp1253; 8.3 aliases with ~1; Cn; U+206A to U+206F,
tags and other ignorables; a dot and ZWJ, and ZWJ alone; 127 and 85
times U+0390; U+F03A; .datekeys-x at two levels; U+FF5E and U+1F600
in both orders; ab with and without ZWNJ; U+00BF, U+00A7 and U+2665,
accepted; VS16 after U+2764 and after a; ZWJ at the start, at the end
and twice; the rainbow flag and the flag of Scotland; b/.. and a.
- path_fold.json: 22 segments with their NFD and their key of R7,
among them the entries F of CaseFolding, the dotless i, the Kelvin
and Angstrom signs, Cherokee, Hangul and the whitelist dropped
before NFD.
- head_schema.json: 63 heads through layers 2, 3 and 4, in key order,
with the violation of each ERR_HEAD_INVALID; they add comments with
tags and with loose variation selectors.
- security.json: 21 areas with their verdicts, X, F0, F1, S0, S1 and
S2, among them key 2 that is not a byte string, a key 4, a byte more,
alg 0, an empty key with the seal intact, and a seal that breaks its
schema with an unknown seal_type.
- cbor.json gains the control of schema version 3.
A test replays every committed vector through the implementation, and
another checks that the files are current.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
verdicts := map[string]bool{}
|
|
|
|
|
for _, v := range security.Vectors {
|
|
|
|
|
b, err := hex.DecodeString(v.Hex)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
Test data: security.json in the context of a capsule, with v0.12 verdicts
The review found that security.json said spec 0.11 and still gave the
verdicts of a reader of v0.10, evaluated without context, contrary to what
section 76 announced (D1).
- security.json carries the context of a capsule, its commitments and the
time of its round, and each case its verdicts and the lines of the
official SDK in it: a signature of alg 1 that does not verify is F2, a
token of seal_type 2 that is not DER is S2, and new cases give a valid
signature of alg 1 (F4) and alg and seal_type 4294967295 (F1, S1).
- mutations.json: the signature of alg 1 that does not verify (F2) is a
case of 64, and the seal that opens with S1 uses seal_type 4294967295,
not seal_type 1, which a later version may define.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
if sig, seal, lines := testkit.SecurityResultIn(b, c); sig != v.Signature || seal != v.Seal || !slices.Equal(lines, v.Lines) {
|
|
|
|
|
t.Errorf("security %q: %s %s %q, want %s %s %q", v.Name, sig, seal, lines, v.Signature, v.Seal, v.Lines)
|
Format 3, step 6c: the vectors of paths, keys, heads and security
The vector files of spec 67 for format 3, generated with the result
each case is written for, so that the generator fails when the
implementation or the tables change:
- paths.json: 83 paths with the result of the rules of one entry, the
violation worded as every implementation must word it, and 16 trees,
the paths of a head and the result of decoding it: U+00A0, accepted,
and U+3000, R6c, at both ends of a segment; best-fit, full-width
forms, U+00B4 of cp1253; 8.3 aliases with ~1; Cn; U+206A to U+206F,
tags and other ignorables; a dot and ZWJ, and ZWJ alone; 127 and 85
times U+0390; U+F03A; .datekeys-x at two levels; U+FF5E and U+1F600
in both orders; ab with and without ZWNJ; U+00BF, U+00A7 and U+2665,
accepted; VS16 after U+2764 and after a; ZWJ at the start, at the end
and twice; the rainbow flag and the flag of Scotland; b/.. and a.
- path_fold.json: 22 segments with their NFD and their key of R7,
among them the entries F of CaseFolding, the dotless i, the Kelvin
and Angstrom signs, Cherokee, Hangul and the whitelist dropped
before NFD.
- head_schema.json: 63 heads through layers 2, 3 and 4, in key order,
with the violation of each ERR_HEAD_INVALID; they add comments with
tags and with loose variation selectors.
- security.json: 21 areas with their verdicts, X, F0, F1, S0, S1 and
S2, among them key 2 that is not a byte string, a key 4, a byte more,
alg 0, an empty key with the seal intact, and a seal that breaks its
schema with an unknown seal_type.
- cbor.json gains the control of schema version 3.
A test replays every committed vector through the implementation, and
another checks that the files are current.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
}
|
|
|
|
|
verdicts[v.Signature], verdicts[v.Seal] = true, true
|
|
|
|
|
}
|
Test data: security.json in the context of a capsule, with v0.12 verdicts
The review found that security.json said spec 0.11 and still gave the
verdicts of a reader of v0.10, evaluated without context, contrary to what
section 76 announced (D1).
- security.json carries the context of a capsule, its commitments and the
time of its round, and each case its verdicts and the lines of the
official SDK in it: a signature of alg 1 that does not verify is F2, a
token of seal_type 2 that is not DER is S2, and new cases give a valid
signature of alg 1 (F4) and alg and seal_type 4294967295 (F1, S1).
- mutations.json: the signature of alg 1 that does not verify (F2) is a
case of 64, and the seal that opens with S1 uses seal_type 4294967295,
not seal_type 1, which a later version may define.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
// Every verdict that needs no certificate has a vector here; those of
|
|
|
|
|
// alg 2 and seal_type 2 are in security_cms.json.
|
|
|
|
|
for _, v := range []string{"X", "F0", "F1", "F2", "F4", "S0", "S1", "S2"} {
|
Format 3, step 6c: the vectors of paths, keys, heads and security
The vector files of spec 67 for format 3, generated with the result
each case is written for, so that the generator fails when the
implementation or the tables change:
- paths.json: 83 paths with the result of the rules of one entry, the
violation worded as every implementation must word it, and 16 trees,
the paths of a head and the result of decoding it: U+00A0, accepted,
and U+3000, R6c, at both ends of a segment; best-fit, full-width
forms, U+00B4 of cp1253; 8.3 aliases with ~1; Cn; U+206A to U+206F,
tags and other ignorables; a dot and ZWJ, and ZWJ alone; 127 and 85
times U+0390; U+F03A; .datekeys-x at two levels; U+FF5E and U+1F600
in both orders; ab with and without ZWNJ; U+00BF, U+00A7 and U+2665,
accepted; VS16 after U+2764 and after a; ZWJ at the start, at the end
and twice; the rainbow flag and the flag of Scotland; b/.. and a.
- path_fold.json: 22 segments with their NFD and their key of R7,
among them the entries F of CaseFolding, the dotless i, the Kelvin
and Angstrom signs, Cherokee, Hangul and the whitelist dropped
before NFD.
- head_schema.json: 63 heads through layers 2, 3 and 4, in key order,
with the violation of each ERR_HEAD_INVALID; they add comments with
tags and with loose variation selectors.
- security.json: 21 areas with their verdicts, X, F0, F1, S0, S1 and
S2, among them key 2 that is not a byte string, a key 4, a byte more,
alg 0, an empty key with the seal intact, and a seal that breaks its
schema with an unknown seal_type.
- cbor.json gains the control of schema version 3.
A test replays every committed vector through the implementation, and
another checks that the files are current.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
if !verdicts[v] {
|
|
|
|
|
t.Errorf("no vector gives %s", v)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The committed vector files are what the generators compute now.
|
|
|
|
|
func TestVectorFilesAreCurrent(t *testing.T) {
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
cbor, err := testkit.CBORVectors()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
padding, err := testkit.PaddingVectors()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
Format 3, step 6c: the vectors of paths, keys, heads and security
The vector files of spec 67 for format 3, generated with the result
each case is written for, so that the generator fails when the
implementation or the tables change:
- paths.json: 83 paths with the result of the rules of one entry, the
violation worded as every implementation must word it, and 16 trees,
the paths of a head and the result of decoding it: U+00A0, accepted,
and U+3000, R6c, at both ends of a segment; best-fit, full-width
forms, U+00B4 of cp1253; 8.3 aliases with ~1; Cn; U+206A to U+206F,
tags and other ignorables; a dot and ZWJ, and ZWJ alone; 127 and 85
times U+0390; U+F03A; .datekeys-x at two levels; U+FF5E and U+1F600
in both orders; ab with and without ZWNJ; U+00BF, U+00A7 and U+2665,
accepted; VS16 after U+2764 and after a; ZWJ at the start, at the end
and twice; the rainbow flag and the flag of Scotland; b/.. and a.
- path_fold.json: 22 segments with their NFD and their key of R7,
among them the entries F of CaseFolding, the dotless i, the Kelvin
and Angstrom signs, Cherokee, Hangul and the whitelist dropped
before NFD.
- head_schema.json: 63 heads through layers 2, 3 and 4, in key order,
with the violation of each ERR_HEAD_INVALID; they add comments with
tags and with loose variation selectors.
- security.json: 21 areas with their verdicts, X, F0, F1, S0, S1 and
S2, among them key 2 that is not a byte string, a key 4, a byte more,
alg 0, an empty key with the seal intact, and a seal that breaks its
schema with an unknown seal_type.
- cbor.json gains the control of schema version 3.
A test replays every committed vector through the implementation, and
another checks that the files are current.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
paths, err := testkit.PathVectors()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
fold, err := testkit.PathFoldVectors()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
heads, err := testkit.HeadSchemaVectors()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
security, err := testkit.SecurityVectors()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
strict, err := testkit.Ed25519StrictVectors()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
Test data: note.json, the rules of the public note
The review found no vector of the public note in testdata (D3). note.json
gives the data of datekeys.note and what the rules of 24.1 make of it, the
result and the exact text of the rule it breaks: notes that pass, from one
byte to 1024, with letters that are not ASCII and an emoji with VS16; and
notes that a writer refuses and a reader does not show, empty, of 1025
bytes, with a tab, a line feed, a space at an end, a bidi control, an
ignorable, a byte order mark, bytes that are not UTF-8, the UTF-8 of a lone
surrogate and a noncharacter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
note, err := testkit.NoteVectors()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
words, err := testkit.WordKeyVectors()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
resolved, err := testkit.ResolvedIPVectors()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
Spec v0.14 draft: what is not guaranteed, the provider, the root of trust
The draft writes down what the completeness review of 6 October 2026 found
missing, and changes no format and no verdict: what the protocol does not
guarantee, the provider and the states of a profile, signatures and seals
against a quantum adversary, the web client, the entropy of a key of words,
and errata of section 76. Steps 10 and 11 of section 63 now give the root
of trust byte for byte, as the three implementations apply it: the message
a Quicknet round signs, its hash to G1 with its DST, and H2, H3 and H4 of
the tlock IBE. testdata/vectors/tlock_steps.json gives every intermediate
value for four published rounds, checked against drand, kyber and tlock.
SpecVersion stays 0.13 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 day ago
|
|
|
steps, err := testkit.TlockStepVectors()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
Release object, release in hand and step 9.c option B (spec v0.15 draft)
The release of a round becomes a file, .dkr: a release object in
deterministic CBOR, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round,
4: signature}, which provider.EncodeRelease writes and DecodeRelease reads
with its layers (size, type and version, schema). provider.ParseRelease
also reads drand's JSON as the input of the caller. Verify checks the chain
hash a release names before its round and its signature, with
ERR_PROFILE_MISMATCH. provider.Archive reads a local release archive, the
informative format of the draft.
capsule.OpenOptions.Release takes a release in hand, a provider.Supplier,
exclusive with Source: Open does not compare it with the clock (step 9.c,
option B) and reports a clock behind it in Opened.ClockBehind; a network
source is still never asked before the round time. The CLI gains
decrypt -release FILE (.dkr, drand's JSON or a local archive),
decrypt -save-release FILE.dkr and the command release, which fetches,
verifies and saves the .dkr without opening the capsule.
Test data: vectors/release.json, releases/<round>.dkr for rounds 1000,
1001, 1004 and 2000, and a local archive of rounds 1000 to 1004. In
mutations.json every case says its source, "supplied" or "network"; the
case "round not reached yet", a release in hand, now opens, and four cases
are added: the same with a network source, a release of another round from
a network source, and two release objects of another chain. SpecVersion
stays 0.14 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
23 hours ago
|
|
|
releases, err := testkit.ReleaseVectors()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
files, err := testkit.ReleaseFiles()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
for name, want := range files {
|
|
|
|
|
got, err := os.ReadFile("../../testdata/releases/" + name)
|
|
|
|
|
if err != nil || !bytes.Equal(got, want) {
|
|
|
|
|
t.Errorf("testdata/releases/%s is stale: run go run ./internal/testkit/genfixtures -out testdata", name)
|
|
|
|
|
}
|
|
|
|
|
}
|
Spec v0.15 draft: remove the .dkr file
The author's decision of 7 October 2026. A release saved next to a capsule
cannot exist when the capsule is made, and once the date comes the capsule
can be opened: such a file only opens it again and does not cover the real
case, someone opening it decades later when drand is gone and nobody saved
anything. Long-term recovery rests instead on archives and cache services
that keep the releases of all rounds; a reader asks for its round and
verifies the signature against the pinned key.
Spec: the .dkr extension (section 20, back to v0.14), sections 1, 4, 8, 45,
47.1, 49, 50 (rewritten), 53, 62.1 (rule 28 removed, rule 26 reworded),
63, 70, 73, 74 (the datekeys.release .dkk extension dropped too), 76
(the v0.15 block, with the discarded design) and the annex 79. The
release object, the chain hash at step 10, step 9.c option B and the
archive format stay.
Code: decrypt -save-release and the command datekeys release are gone,
with writeRelease and their tests; decrypt -release FILE stays. The
release objects of testdata/releases are now <round>.cbor, and
TestVectorFilesAreCurrent fails on a file the generator no longer writes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
21 hours ago
|
|
|
// The generator never deletes: a file it no longer writes is stale.
|
|
|
|
|
entries, err := os.ReadDir("../../testdata/releases")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
for _, e := range entries {
|
|
|
|
|
if _, ok := files[e.Name()]; !ok {
|
|
|
|
|
t.Errorf("testdata/releases/%s is not generated: remove it", e.Name())
|
|
|
|
|
}
|
|
|
|
|
}
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
for _, v := range []struct {
|
|
|
|
|
file string
|
|
|
|
|
want any
|
|
|
|
|
got any
|
|
|
|
|
}{
|
|
|
|
|
{"cbor.json", cbor, &testkit.CBORVectorFile{}},
|
|
|
|
|
{"padding.json", padding, &testkit.PaddingVectorFile{}},
|
Format 3, step 6c: the vectors of paths, keys, heads and security
The vector files of spec 67 for format 3, generated with the result
each case is written for, so that the generator fails when the
implementation or the tables change:
- paths.json: 83 paths with the result of the rules of one entry, the
violation worded as every implementation must word it, and 16 trees,
the paths of a head and the result of decoding it: U+00A0, accepted,
and U+3000, R6c, at both ends of a segment; best-fit, full-width
forms, U+00B4 of cp1253; 8.3 aliases with ~1; Cn; U+206A to U+206F,
tags and other ignorables; a dot and ZWJ, and ZWJ alone; 127 and 85
times U+0390; U+F03A; .datekeys-x at two levels; U+FF5E and U+1F600
in both orders; ab with and without ZWNJ; U+00BF, U+00A7 and U+2665,
accepted; VS16 after U+2764 and after a; ZWJ at the start, at the end
and twice; the rainbow flag and the flag of Scotland; b/.. and a.
- path_fold.json: 22 segments with their NFD and their key of R7,
among them the entries F of CaseFolding, the dotless i, the Kelvin
and Angstrom signs, Cherokee, Hangul and the whitelist dropped
before NFD.
- head_schema.json: 63 heads through layers 2, 3 and 4, in key order,
with the violation of each ERR_HEAD_INVALID; they add comments with
tags and with loose variation selectors.
- security.json: 21 areas with their verdicts, X, F0, F1, S0, S1 and
S2, among them key 2 that is not a byte string, a key 4, a byte more,
alg 0, an empty key with the seal intact, and a seal that breaks its
schema with an unknown seal_type.
- cbor.json gains the control of schema version 3.
A test replays every committed vector through the implementation, and
another checks that the files are current.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
{"paths.json", paths, &testkit.PathVectorFile{}},
|
|
|
|
|
{"path_fold.json", fold, &testkit.PathFoldFile{}},
|
|
|
|
|
{"head_schema.json", heads, &testkit.HeadSchemaFile{}},
|
|
|
|
|
{"security.json", security, &testkit.SecurityVectorFile{}},
|
|
|
|
|
{"ed25519_strict.json", strict, &testkit.Ed25519StrictFile{}},
|
Test data: note.json, the rules of the public note
The review found no vector of the public note in testdata (D3). note.json
gives the data of datekeys.note and what the rules of 24.1 make of it, the
result and the exact text of the rule it breaks: notes that pass, from one
byte to 1024, with letters that are not ASCII and an emoji with VS16; and
notes that a writer refuses and a reader does not show, empty, of 1025
bytes, with a tab, a line feed, a space at an end, a bidi control, an
ignorable, a byte order mark, bytes that are not UTF-8, the UTF-8 of a lone
surrogate and a noncharacter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
{"note.json", note, &testkit.NoteVectorFile{}},
|
|
|
|
|
{"wordkey.json", words, &testkit.WordKeyVectorFile{}},
|
|
|
|
|
{"resolved_ip.json", resolved, &testkit.ResolvedIPVectorFile{}},
|
Spec v0.14 draft: what is not guaranteed, the provider, the root of trust
The draft writes down what the completeness review of 6 October 2026 found
missing, and changes no format and no verdict: what the protocol does not
guarantee, the provider and the states of a profile, signatures and seals
against a quantum adversary, the web client, the entropy of a key of words,
and errata of section 76. Steps 10 and 11 of section 63 now give the root
of trust byte for byte, as the three implementations apply it: the message
a Quicknet round signs, its hash to G1 with its DST, and H2, H3 and H4 of
the tlock IBE. testdata/vectors/tlock_steps.json gives every intermediate
value for four published rounds, checked against drand, kyber and tlock.
SpecVersion stays 0.13 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 day ago
|
|
|
{"tlock_steps.json", steps, &testkit.TlockStepsFile{}},
|
Release object, release in hand and step 9.c option B (spec v0.15 draft)
The release of a round becomes a file, .dkr: a release object in
deterministic CBOR, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round,
4: signature}, which provider.EncodeRelease writes and DecodeRelease reads
with its layers (size, type and version, schema). provider.ParseRelease
also reads drand's JSON as the input of the caller. Verify checks the chain
hash a release names before its round and its signature, with
ERR_PROFILE_MISMATCH. provider.Archive reads a local release archive, the
informative format of the draft.
capsule.OpenOptions.Release takes a release in hand, a provider.Supplier,
exclusive with Source: Open does not compare it with the clock (step 9.c,
option B) and reports a clock behind it in Opened.ClockBehind; a network
source is still never asked before the round time. The CLI gains
decrypt -release FILE (.dkr, drand's JSON or a local archive),
decrypt -save-release FILE.dkr and the command release, which fetches,
verifies and saves the .dkr without opening the capsule.
Test data: vectors/release.json, releases/<round>.dkr for rounds 1000,
1001, 1004 and 2000, and a local archive of rounds 1000 to 1004. In
mutations.json every case says its source, "supplied" or "network"; the
case "round not reached yet", a release in hand, now opens, and four cases
are added: the same with a network source, a release of another round from
a network source, and two release objects of another chain. SpecVersion
stays 0.14 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
23 hours ago
|
|
|
{"release.json", releases, &testkit.ReleaseVectorFile{}},
|
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
} {
|
|
|
|
|
if err := testkit.ReadJSON("../../testdata/vectors/"+v.file, v.got); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
a, _ := json.Marshal(v.want)
|
|
|
|
|
b, _ := json.Marshal(v.got)
|
|
|
|
|
if !bytes.Equal(a, b) {
|
|
|
|
|
t.Errorf("testdata/vectors/%s is stale: run go run ./internal/testkit/genfixtures -out testdata", v.file)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The tools that seal age files again with known keys reproduce the official
|
|
|
|
|
// fixtures byte for byte when nothing is edited: their STREAM, header MAC and
|
|
|
|
|
// PRELUDE are those of age and of the reference, so that the format 2
|
|
|
|
|
// mutations derived with them differ only where they are edited.
|
|
|
|
|
func TestResealReproducesFixtures(t *testing.T) {
|
|
|
|
|
const dir = "../../testdata/fixtures"
|
|
|
|
|
to, err := testkit.LoadFixture(dir, "format2_time_only")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
same, err := to.WithControl(func(map[uint64]any) {})
|
|
|
|
|
if err != nil || !bytes.Equal(same.DKC, to.DKC) {
|
|
|
|
|
t.Fatalf("WithControl without an edit changes the capsule: %v", err)
|
|
|
|
|
}
|
|
|
|
|
content, err := os.ReadFile(filepath.Join(dir, to.PlaintextFile))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
padded := append(content, make([]byte, to.PaddedLength-to.PayloadLength)...)
|
|
|
|
|
same, err = to.WithPayloadPlaintext(padded)
|
|
|
|
|
if err != nil || !bytes.Equal(same.DKC, to.DKC) {
|
|
|
|
|
t.Fatalf("WithPayloadPlaintext of the same plaintext changes the capsule: %v", err)
|
|
|
|
|
}
|
|
|
|
|
tk, err := testkit.LoadFixture(dir, "format2_time_and_key_portable")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
same, err = tk.WithInnerStanzas(func(_ []byte, s []*age.Stanza) ([]*age.Stanza, error) { return s, nil })
|
|
|
|
|
if err != nil || !bytes.Equal(same.DKC, tk.DKC) || same.DKK != nil || len(same.Identities) != 1 {
|
|
|
|
|
t.Fatalf("WithInnerStanzas without an edit changes the capsule: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if testkit.StreamLen(0) != 16 || testkit.StreamLen(65536) != 65552 || testkit.StreamLen(65537) != 65569 {
|
|
|
|
|
t.Fatal("StreamLen")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FixedX25519Stanza builds the stanza age builds, with a chosen ephemeral:
|
|
|
|
|
// the identity of the recipient unwraps the file key, and the same seed gives
|
|
|
|
|
// the same stanza.
|
|
|
|
|
func TestFixedX25519Stanza(t *testing.T) {
|
|
|
|
|
id, _ := age.GenerateX25519Identity()
|
|
|
|
|
fk := bytes.Repeat([]byte{7}, 16)
|
|
|
|
|
s, err := testkit.FixedX25519Stanza(fk, id.Recipient(), "seed")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
got, err := id.Unwrap([]*age.Stanza{s})
|
|
|
|
|
if err != nil || !bytes.Equal(got, fk) {
|
|
|
|
|
t.Fatalf("unwrap: %v", err)
|
|
|
|
|
}
|
|
|
|
|
again, _ := testkit.FixedX25519Stanza(fk, id.Recipient(), "seed")
|
|
|
|
|
other, _ := testkit.FixedX25519Stanza(fk, id.Recipient(), "other seed")
|
|
|
|
|
if !reflect.DeepEqual(s, again) || reflect.DeepEqual(s, other) {
|
|
|
|
|
t.Fatal("the stanza does not depend on the seed alone")
|
|
|
|
|
}
|
|
|
|
|
}
|