You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dateKeys-dart/tool/formats_go_vectors.go

2313 lines
79 KiB

Stage 4b: the differential of the formats against Go 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>
2 days ago
//go:build ignore
// Writes the vectors of the formats of datekeys-dart, stage 4b of
// docs/PLAN_dart.md: the results, the normative codes and the texts of the Go
// reference on
//
// - formats_framing.json: the PRELUDE of a .dkc (capsule.ParsePrelude), the
// framing steps 1 to 3 of capsule.Inspect on truncated and edited
// fixtures, and whole .dkk files (accesskey.Decode), edited and built;
// - formats_header.json: PUBLIC_HEADER (capsule.DecodeHeader);
// - formats_control.json: CONTROL_CBOR of the three formats
// (capsule.DecodeControl);
// - formats_profile.json: Provider Profiles (profile.Decode, Validate and
// MaxRound);
// - formats_extension.json: extension arrays (extension.DecodeArray and
// EncodeArray through codec.Unmarshal), Canonical, CheckDisjoint,
// CheckCriticalIn and CheckNoncriticalIn with a registry of this file,
// and CheckWrite with extension.Standard;
// - formats_datekey.json: dk1_ strings (datekey.Parse);
// - formats_time.json: RFC 3339 (time.Parse with time.RFC3339Nano, and
// Format with RFC3339 and RFC3339Nano), datekey.Resolve, RoundTime,
// Validate and Profile.MaxRound on several profiles;
// - formats_padding.json: capsule.PaddedLength and PayloadAgeLength at the
// boundaries of spec §29.1 up to L_MAX and past it, and the check of the
// padding by capsule.Open at step 17 on fixtures whose PAYLOAD_AGE is
// encrypted again to their I_PAYLOAD with an edited plaintext;
// - formats_encode.json: the encoders on values (capsule.EncodeHeader and
// EncodeControl, accesskey.Encode, profile.NewRegistry, and CanonicalJSON
// and Compact of DateKeys), and the decoders at the limits of spec §57;
// - formats_body.json: capsule.ParseBodyFrame and CheckArea.
//
// The inputs are the synced fixtures of testdata/, edited, and objects built
// with a fixed seed, valid and broken in every layer of spec §69.1; no
// expected value is written by hand. Each file has one case per line, and
// the output is the same on every run.
//
// It also writes test/vectors/formats_vectors.g.dart: every eighth case of
// each section of each file, as Dart constants for the tests that also run
// compiled to JavaScript, where no file can be read. A test on the VM checks
// that they are those of the files.
//
// Run it in the module of the reference implementation, which it imports,
// without changing anything there, from the datekeys-go next to this
// repository, on the branch v0.12 at c531e93: spec v0.11 and the fixes of
// the review of 2 October. At the tag spec-v0.11, extension.CheckWrite, the
// rule of encoders of spec §72, does not exist yet, and accesskey.Encode
// writes the extensions of the specification anywhere; every other output is
// the same.
//
// cd ../datekeys-go && go run ../datekeys-dart/tool/formats_go_vectors.go \
// -testdata ../datekeys-dart/testdata -out ../datekeys-dart/test/vectors
package main
import (
"bytes"
"context"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"io"
"math/rand/v2"
"os"
"path/filepath"
"slices"
"sort"
"strings"
"time"
"unicode/utf8"
"filippo.io/age"
datekeys "g.activething.com/go/DateKeys"
"g.activething.com/go/DateKeys/accesskey"
"g.activething.com/go/DateKeys/agewrap"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/codec"
"g.activething.com/go/DateKeys/datekey"
"g.activething.com/go/DateKeys/extension"
"g.activething.com/go/DateKeys/profile"
"g.activething.com/go/DateKeys/provider"
)
const specVersion = "0.11"
// The fixed seed of every random choice.
var rng = rand.New(rand.NewPCG(0x4b0a0b2026, 0xdc1dc1d))
func must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
func unhex(s string) []byte { return must(hex.DecodeString(s)) }
func hx(b []byte) string { return hex.EncodeToString(b) }
func randBytes(n int) []byte {
b := make([]byte, n)
for i := range b {
b[i] = byte(rng.IntN(256))
}
return b
}
func pick[T any](xs ...T) T { return xs[rng.IntN(len(xs))] }
// Case is one vector: a JSON object.
type Case map[string]any
// outcome records the result of err in c: "ok", or the normative code, or
// "error" for an error without one, with its text.
func outcome(c Case, err error) Case {
if err == nil {
c["result"] = "ok"
return c
}
code := datekeys.Code(err)
if code == "" {
code = "error"
}
c["result"] = code
text := err.Error()
if !utf8.ValidString(text) {
panic("an error text that is not valid UTF-8: " + text)
}
c["text"] = text
return c
}
// ---------------------------------------------------------------------------
// CBOR built by hand, in the profile or out of it
func head(major byte, n uint64) []byte {
m := major << 5
switch {
case n < 24:
return []byte{m | byte(n)}
case n <= 0xff:
return []byte{m | 24, byte(n)}
case n <= 0xffff:
return binary.BigEndian.AppendUint16([]byte{m | 25}, uint16(n))
case n <= 0xffffffff:
return binary.BigEndian.AppendUint32([]byte{m | 26}, uint32(n))
}
return binary.BigEndian.AppendUint64([]byte{m | 27}, n)
}
// headN is a head whose argument takes exactly size bytes, 1, 2, 4 or 8:
// not its shortest form when n is small.
func headN(major byte, n uint64, size int) []byte {
m := major << 5
switch size {
case 1:
return []byte{m | 24, byte(n)}
case 2:
return binary.BigEndian.AppendUint16([]byte{m | 25}, uint16(n))
case 4:
return binary.BigEndian.AppendUint32([]byte{m | 26}, uint32(n))
}
return binary.BigEndian.AppendUint64([]byte{m | 27}, n)
}
func cu(n uint64) []byte { return head(0, n) }
func ct(s string) []byte { return append(head(3, uint64(len(s))), s...) }
func cbs(b []byte) []byte { return append(head(2, uint64(len(b))), b...) }
func cneg(n uint64) []byte { return head(1, n) }
func cfloat() []byte { return []byte{0xf9, 0x3c, 0x00} }
func ctag(v []byte) []byte { return append([]byte{0xc2}, v...) }
func cnull() []byte { return []byte{0xf6} }
func ctrue() []byte { return []byte{0xf5} }
func cindef(v []byte) []byte { return append(append([]byte{0x5f}, v...), 0xff) }
func ca(items ...[]byte) []byte {
out := head(4, uint64(len(items)))
for _, it := range items {
out = append(out, it...)
}
return out
}
type entry struct{ key, val []byte }
func kv(k uint64, v []byte) entry { return entry{cu(k), v} }
func cm(entries ...entry) []byte {
out := head(5, uint64(len(entries)))
for _, e := range entries {
out = append(out, e.key...)
out = append(out, e.val...)
}
return out
}
// mapBytes encodes entries with the head of a map of n entries.
func mapBytes(n uint64, entries []entry) []byte {
out := head(5, n)
for _, e := range entries {
out = append(out, e.key...)
out = append(out, e.val...)
}
return out
}
// ---------------------------------------------------------------------------
// Extensions
type ext struct {
id string
version uint64
data []byte // nil without data
}
func extItem(x ext) []byte {
if x.data == nil {
return cm(kv(0, ct(x.id)), kv(1, cu(x.version)))
}
return cm(kv(0, ct(x.id)), kv(1, cu(x.version)), kv(2, cbs(x.data)))
}
var extIDs = []string{
"a", "b", "z", "Z", "a\x00", "ab", "é", "\uff61", "\U00010000",
"org.example.a", "org.example.b", "org.example.label", "datekeys.note",
"datekeys.capsule", "\ufefforg.example.a", "x.y-z_1",
}
// randExts returns 1 to n valid extensions in canonical order.
func randExts(n int) []ext {
ids := slices.Clone(extIDs)
rng.Shuffle(len(ids), func(i, j int) { ids[i], ids[j] = ids[j], ids[i] })
k := 1 + rng.IntN(n)
out := make([]ext, 0, k)
for _, id := range ids[:k] {
x := ext{id: id, version: pick[uint64](0, 1, 2, 7, 4294967295)}
if rng.IntN(3) > 0 {
x.data = randBytes(1 + rng.IntN(6))
}
out = append(out, x)
}
slices.SortFunc(out, func(a, b ext) int { return strings.Compare(a.id, b.id) })
return out
}
func extArray(xs []ext) []byte {
items := make([][]byte, len(xs))
for i, x := range xs {
items[i] = extItem(x)
}
return ca(items...)
}
// badExtArray is an extension array that breaks one rule of spec §54, or
// none when ok.
func badExtArray() []byte {
xs := randExts(4)
switch rng.IntN(22) {
case 0:
return ca()
case 1:
many := make([][]byte, 65)
for i := range many {
many[i] = extItem(ext{id: fmt.Sprintf("e%05d", i), version: 1})
}
return ca(many...)
case 2:
if len(xs) > 1 {
xs[0], xs[1] = xs[1], xs[0]
} else {
xs = append(xs, xs[0])
}
case 3:
xs = append(xs, ext{id: xs[len(xs)-1].id, version: 9})
case 4:
xs[0].id = ""
case 5:
xs[0].id = strings.Repeat("a", 257)
case 6:
return ca(cm(kv(0, append(head(3, 3), 'a', 0xff, 'b')), kv(1, cu(1))))
case 7:
xs[0].version = 1 << 32
case 8:
xs[0].version = 1<<53 - 1
case 9:
return ca(cm(kv(0, ct("a")), kv(1, cu(1)), kv(2, cbs(nil))))
case 10:
return ca(cm(kv(0, ct("a")), kv(1, cu(1)), kv(2, ct("x"))))
case 11:
return ca(cm(kv(0, ct("a")), kv(1, cu(1)), kv(3, cu(0))))
case 12:
return ca(cm(kv(0, ct("a"))))
case 13:
return ca(cm(kv(1, cu(1))))
case 14:
return ca(cm(kv(0, ct("a")), kv(1, cu(1)), kv(2, cbs([]byte{1})), kv(3, cu(0))))
case 15:
return cm(kv(0, ct("a")), kv(1, cu(1)))
case 16:
return ca(ct("a"))
case 17:
return ca(cm(kv(1, cu(1)), kv(0, ct("a"))))
case 18:
return ca(cm(kv(0, ct("a")), kv(1, headN(0, 1, 1))))
case 19:
return ca(cm(kv(0, ct("a")), kv(1, cu(1)), kv(2, pick(cnull(), cu(5), ca(), cm(), ctag(cbs([]byte{1})), cindef(cbs([]byte{1})), headN(2, 1, 1)))))
case 20:
return ca(cm(kv(0, cu(5)), kv(1, cu(1))))
}
return extArray(xs)
}
func extJSON(xs []extension.Extension) [][]any {
out := make([][]any, len(xs))
for i, x := range xs {
var d any
if x.Data != nil {
d = hx(x.Data)
}
out[i] = []any{x.ID, x.Version, d}
}
return out
}
func toExts(xs []ext) []extension.Extension {
out := make([]extension.Extension, len(xs))
for i, x := range xs {
out[i] = extension.Extension{ID: x.id, Version: x.version, Data: x.data}
}
return out
}
func extsJSON(xs []ext) [][]any { return extJSON(toExts(xs)) }
// ---------------------------------------------------------------------------
// Edits of bytes
// mutate applies 1 to 3 random edits: a byte changed, inserted or removed,
// or the end cut.
func mutate(b []byte) []byte {
out := bytes.Clone(b)
for range 1 + rng.IntN(3) {
if len(out) == 0 {
out = append(out, byte(rng.IntN(256)))
continue
}
i := rng.IntN(len(out))
switch rng.IntN(5) {
case 0, 1:
out[i] ^= byte(1 + rng.IntN(255))
case 2:
out = slices.Insert(out, i, byte(rng.IntN(256)))
case 3:
out = slices.Delete(out, i, i+1)
case 4:
out = out[:i]
}
}
return out
}
// ---------------------------------------------------------------------------
// Fixtures
type fixture struct {
Format int `json:"format"`
PublicHeader string `json:"public_header"`
ControlCBOR string `json:"control_cbor"`
HeaderBinding string `json:"header_binding"`
name string
dkc []byte
}
func loadFixtures(dir string) ([]fixture, map[string][]byte) {
var fxs []fixture
dkks := map[string][]byte{}
names := must(filepath.Glob(filepath.Join(dir, "fixtures", "*.dkc")))
sort.Strings(names)
for _, path := range names {
name := strings.TrimSuffix(filepath.Base(path), ".dkc")
var f fixture
if err := json.Unmarshal(must(os.ReadFile(filepath.Join(dir, "fixtures", name+".json"))), &f); err != nil {
panic(err)
}
f.name = name
f.dkc = must(os.ReadFile(path))
fxs = append(fxs, f)
}
keys := must(filepath.Glob(filepath.Join(dir, "fixtures", "*.dkk")))
sort.Strings(keys)
for _, path := range keys {
dkks[strings.TrimSuffix(filepath.Base(path), ".dkk")] = must(os.ReadFile(path))
}
return fxs, dkks
}
// ---------------------------------------------------------------------------
// The frame of a .dkc and of a .dkk
func preludeCases() []Case {
var out []Case
add := func(b []byte) {
c := Case{"hex": hx(b)}
p, err := capsule.ParsePrelude(b)
outcome(c, err)
if err == nil {
c["format"] = int(p.Format)
c["public_header_len"] = p.PublicHeaderLen
c["sealed_control_len"] = p.SealedControlLen
}
out = append(out, c)
}
base := unhex("444b43310100000000000079000001be")
for _, n := range []int{0, 1, 3, 4, 5, 11, 12, 15} {
add(base[:n])
}
add(unhex("444b4b3101000000000000790000"))
// Each bit of FLAGS and of RESERVED alone.
for i := 5; i < 8; i++ {
for bit := range 8 {
b := bytes.Clone(base)
b[i] = 1 << bit
add(b)
}
}
Stage 4b: the differential of the formats against Go 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>
2 days ago
lengths := []uint32{0, 1, 2, 121, 1 << 20, 1<<20 + 1, 64 << 20, 64<<20 + 1, 1<<31 + 7, 0xffffffff}
for range 220 {
b := bytes.Clone(base)
switch rng.IntN(6) {
case 0:
b[rng.IntN(4)] ^= byte(1 + rng.IntN(255))
case 1:
b[4] = pick[byte](0, 1, 2, 3, 4, 5, 0x80, 0xff)
case 2:
b[5+rng.IntN(3)] = byte(1 + rng.IntN(255))
}
if rng.IntN(2) == 0 {
b[4] = pick[byte](0, 1, 2, 3, 4, 0xff)
}
if rng.IntN(4) == 0 {
b[5+rng.IntN(3)] = byte(rng.IntN(256))
}
binary.BigEndian.PutUint32(b[8:], pick(lengths...))
binary.BigEndian.PutUint32(b[12:], pick(lengths...))
if rng.IntN(3) == 0 {
binary.BigEndian.PutUint32(b[8+4*rng.IntN(2):], rng.Uint32())
}
add(b)
}
return out
}
// splitCases runs capsule.Inspect on truncated and edited fixtures and keeps
// what steps 1 to 3 decide, and whether step 5 finds SEALED_CONTROL short.
func splitCases(fxs []fixture) []Case {
reg := must(profile.Default())
var out []Case
add := func(name string, cut int, edits [][]any, b []byte) {
c := Case{"fixture": name, "cut": cut}
if edits != nil {
c["edits"] = edits
}
in, err := capsule.Inspect(bytes.NewReader(b), capsule.InspectOptions{Registry: reg})
step := 0
if err != nil {
step = in.Checks[len(in.Checks)-1].Step
}
c["step"] = step
out = append(out, outcome(c, err))
}
for _, f := range fxs {
hl := int(binary.BigEndian.Uint32(f.dkc[8:12]))
sl := int(binary.BigEndian.Uint32(f.dkc[12:16]))
cuts := []int{0, 2, 4, 9, 15, 16, 16 + hl - 1, 16 + hl, 16 + hl + 1, 16 + hl + sl - 1, 16 + hl + sl, 16 + hl + sl + 1, len(f.dkc)}
if f.name == "time_only" || f.name == "format3_single" {
for range 6 {
cuts = append(cuts, 16+rng.IntN(hl+sl))
}
}
for _, cut := range cuts {
add(f.name, cut, nil, f.dkc[:cut])
}
// The lengths of the PRELUDE beyond the file, and FLAGS.
for _, e := range [][]any{
{8, 4, "00100001"},
{8, 4, "000fffff"},
{12, 4, "04000001"},
{12, 4, "03ffffff"},
{12, 4, "00000000"},
{4, 1, "07"},
{6, 1, "01"},
} {
b := bytes.Clone(f.dkc)
copy(b[e[0].(int):], unhex(e[2].(string)))
add(f.name, len(b), [][]any{e}, b)
}
}
return out
}
func dkkCases(dkks map[string][]byte) []Case {
var out []Case
add := func(b []byte) {
c := Case{"hex": hx(b)}
k, err := accesskey.Decode(bytes.NewReader(b))
outcome(c, err)
if err == nil {
c["credential_id"] = hx(k.CredentialID[:])
c["capsule_id"] = hx(k.CapsuleID[:])
c["type"] = k.Type
c["material"] = hx(k.Material)
if k.Verification != nil {
c["capsule_digest"] = hx(k.Verification.CapsuleDigest)
}
c["critical"] = extJSON(k.Critical)
c["noncritical"] = extJSON(k.Noncritical)
// The writer applies the rule of spec §72, which the reader does
// not: a .dkk with datekeys.note decodes, and is not written.
var w bytes.Buffer
if err := accesskey.Encode(&w, k); err != nil {
e := outcome(Case{}, err)
c["encode_result"], c["encode_text"] = e["result"], e["text"]
} else if !bytes.Equal(w.Bytes(), b) {
panic("a .dkk that does not encode again to itself")
}
}
out = append(out, c)
}
var names []string
for n := range dkks {
names = append(names, n)
}
sort.Strings(names)
for _, n := range names {
add(dkks[n])
}
frame := func(body []byte) []byte {
pre := []byte{'D', 'K', 'K', '1', 1, 0, 0, 0, 0, 0, 0, 0}
binary.BigEndian.PutUint32(pre[8:], uint32(len(body)))
return append(pre, body...)
}
base := dkks[names[0]]
// The frame: the prelude, its lengths and the end of the file.
for _, n := range []int{0, 1, 3, 4, 7, 11, 12, len(base) - 1} {
add(base[:n])
}
add(append(bytes.Clone(base), 0))
add(append(bytes.Clone(base), 1, 2, 3))
// Each bit of FLAGS and of RESERVED alone.
for i := 5; i < 8; i++ {
for bit := range 8 {
b := bytes.Clone(base)
b[i] = 1 << bit
add(b)
}
}
Stage 4b: the differential of the formats against Go 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>
2 days ago
for range 50 {
b := bytes.Clone(base)
switch rng.IntN(5) {
case 0:
b[rng.IntN(4)] ^= byte(1 + rng.IntN(255))
case 1:
b[4] = pick[byte](0, 2, 3, 0xff)
case 2:
b[5+rng.IntN(3)] = byte(1 + rng.IntN(255))
case 3:
binary.BigEndian.PutUint32(b[8:], pick[uint32](0, 1, uint32(len(b)-13), uint32(len(b)-11), 16<<20, 16<<20+1, 0xffffffff))
case 4:
b = b[:12+rng.IntN(len(b)-12)]
}
add(b)
}
// Edits of the bodies, kept in their frame.
for _, n := range names {
body := dkks[n][12:]
for range 12 {
add(frame(mutate(body)))
}
}
// Bodies built field by field.
for range 130 {
add(frame(genDkkBody()))
}
return out
}
func genDkkBody() []byte {
entries := []entry{
kv(0, ct("datekeys-access-key")),
kv(1, cu(1)),
kv(2, cbs(randBytes(16))),
kv(3, cbs(randBytes(16))),
kv(4, ct("x25519")),
kv(5, cbs(randBytes(32))),
}
if rng.IntN(2) == 0 {
entries = append(entries, kv(6, cm(kv(0, cbs(randBytes(32))))))
}
if rng.IntN(3) == 0 {
entries = append(entries, kv(7, extArray(randExts(2))))
}
if rng.IntN(3) == 0 {
entries = append(entries, kv(8, extArray(randExts(3))))
}
set := func(k uint64, v []byte) {
for i, e := range entries {
if bytes.Equal(e.key, cu(k)) {
entries[i].val = v
return
}
}
entries = append(entries, kv(k, v))
slices.SortFunc(entries, func(a, b entry) int { return bytes.Compare(a.key, b.key) })
}
for range rng.IntN(3) {
switch rng.IntN(16) {
case 0:
set(0, ct(pick("datekeycap", "datekeys-access-ke", "datekeys-access-keys", "")))
case 1:
set(1, pick(cu(0), cu(2), cu(1<<53), headN(0, 1, 1), ct("1")))
case 2:
set(2, cbs(randBytes(pick(0, 15, 17))))
case 3:
set(3, pick(cbs(randBytes(15)), ct("capsule")))
case 4:
set(4, pick(ct("X25519"), ct("mlkem768"), ct(""), cbs([]byte("x25519")), cu(1)))
case 5:
set(5, pick(cbs(randBytes(31)), cbs(randBytes(33)), cbs(nil), ct("material")))
case 6:
set(6, pick(cm(), cm(kv(0, cbs(nil))), cm(kv(0, cbs(randBytes(31)))), cm(kv(1, cbs(randBytes(32)))), cm(kv(0, cbs(randBytes(32))), kv(1, cu(0))), cnull(), ca()))
case 7:
set(7, badExtArray())
case 8:
set(8, badExtArray())
case 9:
id := extIDs[rng.IntN(len(extIDs))]
set(7, extArray([]ext{{id: id, version: 1}}))
set(8, extArray([]ext{{id: id, version: 2}}))
case 10:
set(9, cu(0))
case 11:
k := rng.IntN(len(entries))
entries = slices.Delete(entries, k, k+1)
case 12:
if len(entries) > 2 {
i := rng.IntN(len(entries) - 1)
entries[i], entries[i+1] = entries[i+1], entries[i]
}
case 13:
return append(cm(entries...), 0)
case 14:
b := cm(entries...)
return b[:rng.IntN(len(b))]
case 15:
return mapBytes(uint64(len(entries)+pick(-1, 1)), entries)
}
}
return cm(entries...)
}
// ---------------------------------------------------------------------------
// DateKeys
func dk1(json string) string {
return "dk1_" + base64.RawURLEncoding.EncodeToString([]byte(json))
}
func randRound() uint64 {
switch rng.IntN(5) {
case 0:
return pick[uint64](1, 2, 1000, 66884212, 83903165811, 83903165812, 1<<53-1)
case 1:
return 1 + rng.Uint64N(100)
}
return 1 + rng.Uint64N(1<<53-1)
}
var networks = []string{
"datekeys:quicknet:v1", "a", "0", "x.y_z-w:1", strings.Repeat("a", 128),
}
func headerDateKey() string {
return dk1(fmt.Sprintf(`{"version":1,"network":"%s","round":%d}`, pick(networks...), randRound()))
}
// genDateKey builds a dk1_ string from a JSON object whose members, values,
// spelling and Base64 vary.
func genDateKey() string {
round := randRound()
version := `1`
if rng.IntN(4) == 0 {
version = pick(`1.0`, `1e0`, `10e-1`, `0.1e1`, `100E-2`, `2`, `0`, `-1`, `-0`, `1.5`, `true`, `null`, `"1"`, `[1]`, `{}`, `1.0000000000000001`, `1e32768`, `01`, `1.`, `+1`, `1e`, `[1,"a",true,null,{"b":2,"a":[]}]`)
}
network := `"` + pick(networks...) + `"`
if rng.IntN(5) == 0 {
network = pick(`"datekeys\u003aquicknet:v1"`, `"A"`, `""`, `1`, `null`, `"`+strings.Repeat("a", 129)+`"`, `"a b"`, `{"x":[1,{"y":null}]}`, `"a\ud800"`, `"\ud83d\ude00"`, `"é"`, `"a\n"`)
}
roundText := fmt.Sprint(round)
if rng.IntN(4) == 0 {
roundText = pick(fmt.Sprintf("%d.0", round), fmt.Sprintf("%de0", round), fmt.Sprintf("%d0e-1", round), "9007199254740992", "18446744073709551615", "18446744073709551616", "0", "-5", "1.5", `"5"`, "1e21", "1e3", "1000.0000000000001", "9007199254740991.0", "1e-1", "0e99999", "-0", "1E+2", "123456789012345678901234", "tru", "false")
}
members := [][2]string{{`"version"`, version}, {`"network"`, network}, {`"round"`, roundText}}
switch rng.IntN(12) {
case 0:
members = append(members, [2]string{`"x"`, pick(`null`, `1`, `"y"`, `[]`, `{}`)})
case 1:
members = members[:2]
case 2:
rng.Shuffle(len(members), func(i, j int) { members[i], members[j] = members[j], members[i] })
case 3:
members = append([][2]string{{`"round"`, "1"}}, members...)
case 4:
members[0][0] = `"\u0076ersion"`
case 5:
members = append(members, members[rng.IntN(3)])
}
var sb strings.Builder
ws := func() {
if rng.IntN(8) == 0 {
sb.WriteString(pick(" ", "\t", "\n", "\r", " ", "\f"))
}
}
if rng.IntN(20) == 0 {
sb.WriteString("\ufeff")
}
ws()
sb.WriteString("{")
for i, m := range members {
if i > 0 {
sb.WriteString(",")
}
ws()
sb.WriteString(m[0])
ws()
sb.WriteString(":")
ws()
sb.WriteString(m[1])
ws()
}
sb.WriteString("}")
ws()
if rng.IntN(15) == 0 {
sb.WriteString(pick("x", "{}", "1", "\x00"))
}
raw := []byte(sb.String())
if rng.IntN(15) == 0 && len(raw) > 4 {
raw[1+rng.IntN(len(raw)-2)] = pick[byte](0xff, 0xc0, 0x80, 0xed)
}
var payload string
switch rng.IntN(8) {
case 0:
payload = base64.URLEncoding.EncodeToString(raw)
case 1:
payload = base64.RawStdEncoding.EncodeToString(raw)
case 2:
payload = base64.StdEncoding.EncodeToString(raw)
default:
payload = base64.RawURLEncoding.EncodeToString(raw)
}
switch rng.IntN(14) {
case 0:
if n := len(payload); n > 0 {
// Other trailing bits in the last character.
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
payload = payload[:n-1] + string(alphabet[rng.IntN(64)])
}
case 1:
i := rng.IntN(len(payload) + 1)
payload = payload[:i] + pick("\n", "\r", " ", "\t", "=", "!", "+", "/", "-", "_", "é") + payload[i:]
case 2:
payload = payload[:rng.IntN(len(payload)+1)]
}
prefix := "dk1_"
if rng.IntN(25) == 0 {
prefix = pick("DK1_", "dk1", "", "dk2_", " dk1_")
}
return prefix + payload
}
func datekeyCases() []Case {
var out []Case
add := func(s string) {
if !utf8.ValidString(s) {
panic("a dk1_ input that is not valid UTF-8")
}
c := Case{"input": s}
d, err := datekey.Parse(s)
outcome(c, err)
if err == nil {
c["network"] = d.ProfileID
c["round"] = d.Round
}
out = append(out, c)
}
for _, s := range []string{
"", "dk1", "dk1_", "x", "dk1_!!!", "dk1_" + strings.Repeat("A", 252), "dk1_" + strings.Repeat("A", 253),
"dk1_" + strings.Repeat("é", 126), "dk1_" + strings.Repeat("é", 127),
dk1(`{"version":1,"network":"a","round":1}`) + strings.Repeat("=", 2),
} {
add(s)
}
// The limit of 256 bytes: canonical strings padded with spaces in the
// JSON, around it.
for _, n := range []int{240, 250, 251, 252, 253, 254} {
json := `{"version":1,"network":"a","round":1}`
for len(dk1(json)) < n {
json += " "
}
add(dk1(json))
}
for range 30 {
add(dk1(fmt.Sprintf(`{"version":1,"network":"%s","round":%d}`, pick(networks...), randRound())))
}
for range 420 {
add(genDateKey())
}
return out
}
// ---------------------------------------------------------------------------
// PUBLIC_HEADER
func headerCases(fxs []fixture) []Case {
var out []Case
add := func(b []byte) {
c := Case{"hex": hx(b)}
h, err := capsule.DecodeHeader(b)
outcome(c, err)
if err == nil {
c["capsule_id"] = h.CapsuleIDHex()
c["datekey"] = h.DateKey.Compact()
c["policy"] = h.Policy.String()
c["critical"] = extJSON(h.Critical)
c["noncritical"] = extJSON(h.Noncritical)
if !bytes.Equal(must(capsule.EncodeHeader(h)), b) {
panic("a header that does not encode again to itself")
}
}
out = append(out, c)
}
seen := map[string]bool{}
for _, f := range fxs {
if seen[f.PublicHeader] {
continue
}
seen[f.PublicHeader] = true
hb := unhex(f.PublicHeader)
add(hb)
for range 14 {
add(mutate(hb))
}
}
for range 300 {
add(genHeader())
}
return out
}
func genHeader() []byte {
entries := []entry{
kv(0, ct("datekeycap")),
kv(1, cu(1)),
kv(2, cbs(randBytes(16))),
kv(3, ct(headerDateKey())),
kv(4, cu(uint64(rng.IntN(2)))),
}
if rng.IntN(3) == 0 {
entries = append(entries, kv(5, extArray(randExts(2))))
}
if rng.IntN(3) == 0 {
entries = append(entries, kv(6, extArray(randExts(4))))
}
return applyFaults(entries, func(k int, set func(uint64, []byte)) bool {
switch k {
case 0:
set(0, pick(ct("datekeys-control"), ct("datekeyca"), ct("datekeycapX"), ct(""), ct(strings.Repeat("x", 64)), ct(strings.Repeat("x", 65)), cu(0), cbs([]byte("datekeycap"))))
case 1:
set(1, pick(cu(0), cu(2), cu(1<<53-1), cu(1<<53), headN(0, 1, 1), ct("1"), cnull()))
case 2:
set(2, pick(cbs(randBytes(0)), cbs(randBytes(15)), cbs(randBytes(17)), ct("0123456789abcdef")))
case 3:
set(3, pick(
ct(genDateKey()),
ct(dk1(fmt.Sprintf(`{"version":1,"network":"datekeys:quicknet:v1","round":%de0}`, randRound()))),
ct("dk1_!!"), ct("dk1_x"), ct(""), cbs([]byte(headerDateKey())),
append(head(3, 4), 'd', 'k', 0xff, '_'),
))
case 4:
set(4, pick(cu(2), cu(255), cu(256), cu(1<<32), cu(1<<53-1), cu(1<<53), cu(1<<64-1), headN(0, 1, 8), headN(0, 0, 1), cneg(0), ct("time_only")))
case 5:
set(5, badExtArray())
case 6:
set(6, badExtArray())
case 7:
id := extIDs[rng.IntN(len(extIDs))]
set(5, extArray([]ext{{id: id, version: 1}}))
set(6, extArray([]ext{{id: id, version: 1, data: []byte{1}}}))
default:
return false
}
return true
}, 8, 7)
}
// applyFaults applies 0 to 2 faults to the entries of a map: one of the
// fields of the object (field, with nFields of them), an unknown key, a
// missing key, keys out of order or repeated, or the encoding itself. It
// returns the encoding.
func applyFaults(entries []entry, field func(k int, set func(uint64, []byte)) bool, nFields int, unknownKey uint64) []byte {
set := func(k uint64, v []byte) {
for i, e := range entries {
if bytes.Equal(e.key, cu(k)) {
entries[i].val = v
return
}
}
entries = append(entries, kv(k, v))
slices.SortFunc(entries, func(a, b entry) int { return bytes.Compare(a.key, b.key) })
}
faults := rng.IntN(3)
if rng.IntN(4) == 0 {
faults = 0
}
for range faults {
k := rng.IntN(nFields + 9)
if k < nFields && field(k, set) {
continue
}
switch k - nFields {
case 0:
set(unknownKey+uint64(rng.IntN(3)), cu(0))
case 1:
i := rng.IntN(len(entries))
entries = slices.Delete(entries, i, i+1)
case 2:
if len(entries) > 2 {
i := rng.IntN(len(entries) - 1)
entries[i], entries[i+1] = entries[i+1], entries[i]
}
case 3:
i := rng.IntN(len(entries))
entries = slices.Insert(entries, i, entries[i])
case 4:
return append(cm(entries...), byte(rng.IntN(256)))
case 5:
b := cm(entries...)
return b[:rng.IntN(len(b))]
case 6:
return mapBytes(uint64(len(entries)+pick(-1, 1, 30)), entries)
case 7:
entries = append(entries, entry{headN(0, 1<<53, 8), cu(0)})
case 8:
entries = append(entries, entry{ct("x"), cu(0)})
}
}
return cm(entries...)
}
// ---------------------------------------------------------------------------
// CONTROL_CBOR
func controlCases(fxs []fixture) []Case {
var out []Case
add := func(b []byte, f capsule.Format) {
c := Case{"format": int(f), "hex": hx(b)}
ctl, err := capsule.DecodeControl(b, f)
outcome(c, err)
if err == nil {
c["header_binding"] = hx(ctl.HeaderBinding[:])
c["payload_identity"] = hx(ctl.PayloadIdentity[:])
if f != capsule.Format1 {
c["payload_length"] = ctl.PayloadLength
c["padding"] = int(ctl.Padding)
}
c["critical"] = extJSON(ctl.Critical)
c["noncritical"] = extJSON(ctl.Noncritical)
if !bytes.Equal(must(capsule.EncodeControl(ctl, f)), b) {
panic("a control that does not encode again to itself")
}
}
out = append(out, c)
}
seen := map[string]bool{}
for _, fx := range fxs {
key := fmt.Sprint(fx.Format, fx.ControlCBOR)
if seen[key] {
continue
}
seen[key] = true
cb := unhex(fx.ControlCBOR)
f := capsule.Format(fx.Format)
add(cb, f)
add(cb, capsule.Format(1+fx.Format%3))
for range 10 {
add(mutate(cb), f)
}
}
for range 330 {
f := capsule.Format(1 + rng.IntN(3))
add(genControl(f), f)
}
return out
}
func genControl(f capsule.Format) []byte {
padded := f != capsule.Format1
entries := []entry{
kv(0, ct("datekeys-control")),
kv(1, cu(uint64(f))),
kv(2, cbs(randBytes(32))),
kv(3, cbs(randBytes(32))),
}
if rng.IntN(4) == 0 {
entries = append(entries, kv(4, extArray(randExts(2))))
}
if rng.IntN(3) == 0 {
entries = append(entries, kv(5, extArray(randExts(3))))
}
l8 := func(l uint64) []byte { return cbs(binary.BigEndian.AppendUint64(nil, l)) }
if padded {
entries = append(entries, kv(6, l8(randLength())), kv(7, cu(uint64(1+rng.IntN(2)))))
}
return applyFaults(entries, func(k int, set func(uint64, []byte)) bool {
switch k {
case 0:
set(0, pick(ct("datekeycap"), ct("datekeys-contro"), ct("datekeys-controls"), cu(0)))
case 1:
set(1, pick(cu(uint64(1+rng.IntN(3))), cu(0), cu(4), cu(1<<53), headN(0, uint64(f), 1), ct("1")))
case 2:
set(2, pick(cbs(randBytes(31)), cbs(randBytes(33)), ct(strings.Repeat("x", 32))))
case 3:
set(3, pick(cbs(randBytes(31)), cbs(randBytes(33)), cbs(nil), ct(strings.Repeat("x", 32))))
case 4:
set(4, badExtArray())
case 5:
set(5, badExtArray())
case 6:
set(6, pick(
l8(8936830510563328), l8(8936830510563329), l8(1<<53), l8(1<<64-1), l8(1<<32+1), l8(0),
cbs(randBytes(7)), cbs(randBytes(9)), cu(78000), ct("78000"),
))
case 7:
set(7, pick(cu(0), cu(1), cu(2), cu(3), cu(257), cu(1<<53), headN(0, 2, 1), cbs([]byte{2})))
case 8:
id := extIDs[rng.IntN(len(extIDs))]
set(4, extArray([]ext{{id: id, version: 3}}))
set(5, extArray([]ext{{id: id, version: 4}}))
default:
return false
}
return true
}, 9, 8)
}
func randLength() uint64 {
switch rng.IntN(4) {
case 0:
return pick[uint64](0, 1, 255, 256, 257, 78000, 1<<32+1, 8936830510563328)
case 1:
return rng.Uint64N(1 << 20)
}
return rng.Uint64N(8936830510563328 + 1)
}
// ---------------------------------------------------------------------------
// Provider Profiles
var (
g1Generator = unhex("97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb")
g2Generator = unhex("93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8")
drandDefaultKey = unhex("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31")
fieldP = unhex("1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab")
)
// chainHash is the hash of the drand chain information of spec §12.1 rule
// 3, to build profiles whose chain hash matches.
func chainHash(period uint64, genesis int64, key, seed []byte, network string) []byte {
h := sha256.New()
binary.Write(h, binary.BigEndian, uint32(period))
binary.Write(h, binary.BigEndian, genesis)
h.Write(key)
h.Write(seed)
if network != "default" && network != "" {
h.Write([]byte(network))
}
return h.Sum(nil)
}
type profileFields struct {
id, provider, network, scheme string
chainHash, publicKey, seed []byte
period, genesis uint64
}
func (p profileFields) encode() []byte {
return cm(
kv(0, ct("datekeys-provider-profile")), kv(1, cu(1)),
kv(2, ct(p.id)), kv(3, ct(p.provider)), kv(4, ct(p.network)),
kv(5, cbs(p.chainHash)), kv(6, cbs(p.publicKey)),
kv(7, cu(p.period)), kv(8, cu(p.genesis)),
kv(9, ct(p.scheme)), kv(10, cbs(p.seed)),
)
}
func quicknetFields() profileFields {
q := profile.Quicknet()
return profileFields{
id: q.ID, provider: q.Provider, network: q.Network, scheme: q.Scheme,
chainHash: q.ChainHash[:], publicKey: q.PublicKey, seed: q.GenesisSeed[:],
period: uint64(q.Period / time.Second), genesis: uint64(q.GenesisTime),
}
}
func profileJSON(p *profile.Profile) Case {
return Case{
"id": p.ID, "provider": p.Provider, "network": p.Network,
"chain_hash": hx(p.ChainHash[:]), "public_key": hx(p.PublicKey),
"period": int64(p.Period / time.Second), "genesis_time": p.GenesisTime,
"scheme": p.Scheme, "genesis_seed": hx(p.GenesisSeed[:]),
"max_round": p.MaxRound(),
}
}
func profileCases() (decode, validate []Case) {
add := func(b []byte) {
c := Case{"hex": hx(b)}
p, err := profile.Decode(b)
outcome(c, err)
if err == nil {
c["profile"] = profileJSON(p)
}
decode = append(decode, c)
}
q := quicknetFields()
add(q.encode())
for range 50 {
add(mutate(q.encode()))
}
keys := [][]byte{q.publicKey, g2Generator, g1Generator, drandDefaultKey, append([]byte{0xc0}, make([]byte, 95)...), append([]byte{0xc0}, make([]byte, 47)...), append([]byte{0x80}, make([]byte, 95)...), append([]byte{0x80}, make([]byte, 47)...), append([]byte{0x80 | fieldP[0]}, fieldP[1:]...), nil, {1}, randBytes(1024), randBytes(1025), randBytes(96)}
for range 110 {
p := q
rehash := rng.IntN(4) > 0
for range 1 + rng.IntN(2) {
switch rng.IntN(10) {
case 0:
p.id = pick("Datekeys", "", "-a", strings.Repeat("a", 128), strings.Repeat("a", 129), "a b", "datekeys:quicknet:v2", "é", "0a:b.c_d-e")
case 1:
p.provider = pick("drand2", "Drand", "", "drand", "d")
case 2:
p.network = pick("quicknet", "default", "defaults", "", strings.Repeat("a", 64), strings.Repeat("a", 65), "quick net", "mainnet")
case 3:
p.scheme = pick("bls-unchained-g1-rfc9380", "pedersen-bls-unchained", "bls-unchained-on-g1", "pedersen-bls-chained", "bls-bn254-unchained-on-g1", "nope", "", "Bls")
case 4:
p.publicKey = keys[rng.IntN(len(keys))]
case 5:
p.period = pick[uint64](0, 1, 2, 3, 30, 61, 86400, 86401, 1<<32, 1<<53-1)
case 6:
p.genesis = pick[uint64](0, 1, 1595431050, 1692803367, 253402300798, 253402300799, 1<<53-1)
case 7:
p.seed = randBytes(32)
case 8:
p.chainHash = randBytes(32)
rehash = false
case 9:
p.seed = randBytes(pick(31, 33))
}
}
if rehash && len(p.seed) == 32 {
p.chainHash = chainHash(p.period, int64(p.genesis), p.publicKey, p.seed, p.network)
}
add(p.encode())
}
// The drand default network, with the schemes of its G1 key.
d := profileFields{
id: "drand:default:v1", provider: "drand", network: "default", scheme: "pedersen-bls-unchained",
publicKey: drandDefaultKey, seed: unhex("176f93498eac9ca337150b46d21dd58673ea4e3581185f869672e59fa4cb390a"),
period: 30, genesis: 1595431050,
}
d.chainHash = chainHash(d.period, int64(d.genesis), d.publicKey, d.seed, d.network)
add(d.encode())
d.scheme = "pedersen-bls-chained"
add(d.encode())
// Values, as Validate sees a profile built in memory.
vadd := func(p *profile.Profile) {
c := Case{"profile": profileJSON(p)}
validate = append(validate, outcome(c, p.Validate()))
}
for range 60 {
p := profile.Quicknet()
switch rng.IntN(7) {
case 0:
p.Period = time.Duration(pick[int64](0, -1, -61, 61, 86400, 86401, 90000, 1)) * time.Second
case 1:
p.GenesisTime = pick[int64](-1, 0, 1, 253402300798, 253402300799, 1<<53-1, 1<<53)
case 2:
p.ID = pick("Q", "", "datekeys:quicknet:v1", "a"+strings.Repeat("b", 127))
case 3:
p.Network = pick("default", "quicknet", "Q")
case 4:
p.Scheme = pick("pedersen-bls-chained", "nope", "bls-unchained-on-g1")
case 5:
p.PublicKey = keys[rng.IntN(len(keys))]
case 6:
p.Provider = pick("drand", "x", "")
}
vadd(p)
}
return decode, validate
}
// ---------------------------------------------------------------------------
// Extensions
// placed is the registry of the cases: org.a v1 and org.b v1 known
// everywhere; org.ctl v1 registered only in the critical array of
// CONTROL_CBOR and org.note v1 only in the noncritical array of
// PUBLIC_HEADER. Data is valid when it is "ok"; org.b with data "ko!" makes
// the validator return an error of code ERR_EXTENSION_DATA_INVALID, as
// CheckNote does.
type placed struct{}
func (placed) Known(id string, version uint64) bool {
return version == 1 && (id == "org.a" || id == "org.b" || id == "org.ctl" || id == "org.note")
}
func (placed) ValidateData(e extension.Extension) error {
switch {
case string(e.Data) == "ok":
return nil
case e.ID == "org.b" && string(e.Data) == "ko!":
return fmt.Errorf("a data of %d bytes: %w", len(e.Data), datekeys.ErrExtensionDataInvalid)
}
return fmt.Errorf("want %q", "ok")
}
func (placed) RegisteredIn(id string, version uint64, obj extension.Object, arr extension.Array) bool {
switch id {
case "org.ctl":
return obj == extension.Control && arr == extension.Critical
case "org.note":
return obj == extension.PublicHeader && arr == extension.Noncritical
}
return true
}
var objects = []extension.Object{extension.PublicHeader, extension.Control, extension.AccessKey, extension.Head}
func extensionCases() (arrays, canonical, disjoint, registry, write []Case) {
for range 260 {
b := badExtArray()
if rng.IntN(5) == 0 {
b = mutate(b)
}
c := Case{"hex": hx(b)}
var xs []extension.Extension
err := codec.Unmarshal(b, func(d *codec.Decoder) error {
var err error
xs, err = extension.DecodeArray(d)
return err
}, func(e *codec.Encoder) { extension.EncodeArray(e, xs) })
outcome(c, err)
if err == nil {
c["extensions"] = extJSON(xs)
}
arrays = append(arrays, c)
}
for range 80 {
xs := randExts(5)
rng.Shuffle(len(xs), func(i, j int) { xs[i], xs[j] = xs[j], xs[i] })
switch rng.IntN(6) {
case 0:
xs = append(xs, ext{id: xs[0].id, version: 5})
case 1:
xs[rng.IntN(len(xs))].id = ""
case 2:
xs[rng.IntN(len(xs))].version = 1 << 32
case 3:
xs[rng.IntN(len(xs))].data = []byte{}
case 4:
xs = nil
for i := range 65 {
xs = append(xs, ext{id: fmt.Sprintf("o.%03d", i), version: 1})
}
}
c := Case{"extensions": extsJSON(xs)}
out, err := extension.Canonical(toExts(xs))
outcome(c, err)
if err == nil {
c["sorted"] = extJSON(out)
}
canonical = append(canonical, c)
}
for range 50 {
a, b := randExts(4), randExts(4)
if rng.IntN(2) == 0 {
rng.Shuffle(len(a), func(i, j int) { a[i], a[j] = a[j], a[i] })
}
c := Case{"critical": extsJSON(a), "noncritical": extsJSON(b)}
disjoint = append(disjoint, outcome(c, extension.CheckDisjoint(toExts(a), toExts(b))))
}
regIDs := []string{"org.a", "org.b", "org.ctl", "org.note", "org.z"}
regExts := func() []ext {
var xs []ext
ids := slices.Clone(regIDs)
rng.Shuffle(len(ids), func(i, j int) { ids[i], ids[j] = ids[j], ids[i] })
for _, id := range ids[:rng.IntN(4)] {
x := ext{id: id, version: pick[uint64](1, 1, 1, 2)}
switch rng.IntN(4) {
case 0:
x.data = []byte("ok")
case 1:
x.data = []byte("ko!")
case 2:
x.data = []byte("no")
}
xs = append(xs, x)
}
return xs
}
for range 120 {
obj := objects[rng.IntN(len(objects))]
crit, non := regExts(), regExts()
c := Case{"object": obj.String(), "critical": extsJSON(crit), "noncritical": extsJSON(non)}
var reg extension.Registry = placed{}
if rng.IntN(8) == 0 {
reg = nil
c["registry"] = "none"
}
if rng.IntN(6) == 0 {
// Without the object: CheckCritical and CheckNoncritical.
c["object"] = ""
outcome(c, extension.CheckCritical(toExts(crit), reg))
c["unusable"] = unusableJSON(extension.CheckNoncritical(toExts(non), reg))
} else {
outcome(c, extension.CheckCriticalIn(obj, toExts(crit), reg))
c["unusable"] = unusableJSON(extension.CheckNoncriticalIn(obj, toExts(non), reg))
}
registry = append(registry, c)
}
stdIDs := []string{extension.NoteID, extension.CapsuleID, "org.example"}
for range 60 {
obj := objects[rng.IntN(len(objects))]
arr := pick(extension.Critical, extension.Noncritical)
var xs []ext
for range 1 + rng.IntN(2) {
x := ext{id: stdIDs[rng.IntN(len(stdIDs))], version: pick[uint64](1, 1, 2)}
if rng.IntN(4) > 0 {
x.data = []byte("Cartas")
}
xs = append(xs, x)
}
c := Case{"object": obj.String(), "array": arr.String(), "extensions": extsJSON(xs)}
write = append(write, outcome(c, extension.CheckWrite(extension.Standard{}, obj, arr, toExts(xs))))
}
return
}
func unusableJSON(us []extension.Unusable) [][]any {
out := [][]any{}
for _, u := range us {
out = append(out, []any{u.ID, u.Version, u.Err.Error()})
}
return out
}
// ---------------------------------------------------------------------------
// Times and rounds
func timeString() string {
year := fmt.Sprintf("%04d", rng.IntN(10000))
switch rng.IntN(20) {
case 0:
year = pick("10000", "+2023", "-001", "20a3", "202", "0000", "9999")
}
two := func(max int) string {
switch rng.IntN(25) {
case 0:
return fmt.Sprint(rng.IntN(10))
case 1:
return fmt.Sprintf("%02d", max+rng.IntN(3))
case 2:
return pick("0a", "a0", "", "123", "-1")
}
return fmt.Sprintf("%02d", rng.IntN(max+1))
}
month := two(12)
if rng.IntN(3) > 0 {
month = fmt.Sprintf("%02d", 1+rng.IntN(12))
}
day := two(31)
if rng.IntN(2) > 0 {
day = fmt.Sprintf("%02d", 1+rng.IntN(28))
}
if rng.IntN(15) == 0 {
month, day = "02", pick("29", "30")
}
sep := func(want string) string {
if rng.IntN(40) == 0 {
return pick("-", ":", "T", "t", " ", "/", "")
}
return want
}
s := year + sep("-") + month + sep("-") + day + sep("T") + two(23) + sep(":") + two(59) + sep(":") + two(59)
if rng.IntN(2) == 0 {
s += pick(".", ".", ",") + strings.Repeat("0", rng.IntN(3)) + fmt.Sprintf("%09d", rng.IntN(1000000000))[:1+rng.IntN(9)]
if rng.IntN(10) == 0 {
s += strings.Repeat("7", 1+rng.IntN(5))
}
} else if rng.IntN(20) == 0 {
s += pick(".", ",", ".x")
}
switch rng.IntN(6) {
case 0, 1, 2:
s += pick("Z", "Z", "z", "")
default:
hh := fmt.Sprintf("%02d", rng.IntN(25))
mm := fmt.Sprintf("%02d", pick(0, 30, rng.IntN(61)))
if rng.IntN(10) == 0 {
hh, mm = pick("25", "1", "0a", "24"), pick("61", "60", "5", "00")
}
s += pick("+", "-", "+", "-", "*") + hh + pick(":", ":", ":", "") + mm
}
if rng.IntN(25) == 0 {
s += pick(" ", "Z", "x", "\u00a0")
}
if rng.IntN(30) == 0 {
s = " " + s
}
return s
}
type prof struct {
genesis int64
period int64
}
func (p prof) profile() *profile.Profile {
q := profile.Quicknet()
q.GenesisTime = p.genesis
q.Period = time.Duration(p.period) * time.Second
return q
}
var profs = []prof{
{1692803367, 3}, {1595431050, 30}, {1, 1}, {1, 86400}, {253402300789, 7},
{253402300799, 1}, {253402300800, 1}, {1692803367, 0},
}
func timeCases() (parse, format, resolve, roundTime, validate, maxRound []Case) {
for _, s := range []string{
"2023-08-23T15:09:27Z", "2023-08-23T15:09:27.000000001Z", "2026-10-22T19:00:00.001+02:00",
"2023-08-23T5:09:27Z", "2023-08-23T15:09:27,5Z", "2023-08-23T15:09:27+24:00", "2023-08-23T15:09:27-23:60",
"0000-01-01T00:00:00+24:00", "9999-12-31T23:59:59-24:00", "2023-08-23T15:09:27.1234567899Z",
"2024-02-29T00:00:00Z", "2100-02-29T00:00:00Z", "", "x",
} {
parse = append(parse, timeParse(s))
}
for range 420 {
parse = append(parse, timeParse(timeString()))
}
for range 80 {
sec := pick[int64](-62167219200, -62167305600, -377705116800, -1, 0, 951782400, 253402300799, 253402300800, 253402300799+86400*366) + rng.Int64N(200000) - 100000
if rng.IntN(2) == 0 {
sec = rng.Int64N(253402300799+62167219200*2) - 62167219200*2
}
ns := pick[int](0, 0, 1, 500000000, 999999999, rng.IntN(1000000000))
t := time.Unix(sec, int64(ns)).UTC()
format = append(format, Case{"seconds": sec, "nanos": ns, "rfc3339": t.Format(time.RFC3339), "rfc3339nano": t.Format(time.RFC3339Nano)})
}
for _, pf := range profs {
p := pf.profile()
last := int64(p.MaxRound())
maxRound = append(maxRound, Case{"genesis_time": pf.genesis, "period": pf.period, "max_round": last})
for range 40 {
var sec int64
switch rng.IntN(6) {
case 0:
sec = pf.genesis + rng.Int64N(21) - 10
case 1:
sec = 253402300799 + rng.Int64N(21) - 10
case 2:
if pf.period > 0 {
r := 1 + rng.Int64N(max(1, last))
sec = pf.genesis + (r-1)*pf.period + rng.Int64N(3) - 1
}
case 3:
sec = rng.Int64N(253402300799*2) - 253402300799/2
default:
sec = pf.genesis + rng.Int64N(max(1, 253402300800-pf.genesis))
}
ns := pick[int64](0, 0, 0, 1, 999999999, rng.Int64N(1000000000))
c := Case{"genesis_time": pf.genesis, "period": pf.period, "seconds": sec, "nanos": ns}
d, err := datekey.Resolve(p, time.Unix(sec, ns))
outcome(c, err)
if err == nil {
c["round"] = d.Round
c["round_time"] = must(datekey.RoundTime(p, d.Round)).Unix()
}
resolve = append(resolve, c)
}
for _, r := range []uint64{0, 1, 2, uint64(max(last, 1)) - 1, uint64(last), uint64(last) + 1, 1<<53 - 1, 1 + rng.Uint64N(uint64(max(last, 1)))} {
c := Case{"genesis_time": pf.genesis, "period": pf.period, "round": r}
t, err := datekey.RoundTime(p, r)
outcome(c, err)
if err == nil {
c["seconds"] = t.Unix()
}
roundTime = append(roundTime, c)
for _, id := range []string{p.ID, "datekeys:quicknet:v2"} {
vc := Case{"genesis_time": pf.genesis, "period": pf.period, "profile_id": id, "round": r}
validate = append(validate, outcome(vc, datekey.DateKey{ProfileID: id, Round: r}.Validate(p)))
}
}
}
return
}
func timeParse(s string) Case {
c := Case{"input": s}
t, err := time.Parse(time.RFC3339Nano, s)
if err != nil {
c["result"] = "error"
return c
}
c["result"] = "ok"
c["seconds"] = t.Unix()
c["nanos"] = t.Nanosecond()
return c
}
// ---------------------------------------------------------------------------
// Padding and the frame of BODY
func paddingCases() []Case {
var ls []uint64
for l := range uint64(300) {
if l%7 == 0 || l > 250 {
ls = append(ls, l)
}
}
for e := 8; e <= 53; e++ {
p := uint64(1) << e
ls = append(ls, p-1, p, p+1, p+255, p+257)
}
ls = append(ls, 8192, 8193, 2113929216, 2113929217, 4227858432, 4227858433, 1<<49-1,
8936830510563328-1, 8936830510563328, 8936830510563328+1, 8936830510563328-256, 8936830510563328+256)
for range 150 {
bits := 1 + rng.IntN(53)
ls = append(ls, rng.Uint64N(uint64(1)<<bits))
}
var out []Case
for _, l := range ls {
// Every number of the files is exact as a double: up to 2^53.
if l > 1<<53 {
continue
}
c := Case{"l": l}
for _, p := range []capsule.Padding{capsule.Bloque256, capsule.Reforzado} {
name := p.String()
v, err := capsule.PaddedLength(l, p)
if err != nil {
c[name] = nil
c["text"] = err.Error()
continue
}
c[name] = v
c["payload_age_"+name] = capsule.PayloadAgeLength(v)
}
out = append(out, c)
}
return out
}
// ---------------------------------------------------------------------------
// The precedence of spec §69.1: every fault of a list, alone and with every
// other, on a valid object
// mapBuilder is a map of an object of the protocol and the faults of its
// encoding: a trailing byte, or a head that announces other entries.
type mapBuilder struct {
entries []entry
trailing []byte
delta int
}
func (m *mapBuilder) set(k uint64, v []byte) {
for i, e := range m.entries {
if bytes.Equal(e.key, cu(k)) {
m.entries[i].val = v
return
}
}
m.entries = append(m.entries, kv(k, v))
slices.SortFunc(m.entries, func(a, b entry) int { return bytes.Compare(a.key, b.key) })
}
func (m *mapBuilder) del(k uint64) {
m.entries = slices.DeleteFunc(m.entries, func(e entry) bool { return bytes.Equal(e.key, cu(k)) })
}
func (m *mapBuilder) bytes() []byte {
return append(mapBytes(uint64(len(m.entries)+m.delta), m.entries), m.trailing...)
}
type fault struct {
name string
apply func(m *mapBuilder)
}
// pairs builds each fault alone and each pair of faults, in the order of
// the list, on the map that base returns.
func pairs(base func() *mapBuilder, faults []fault, add func(name string, b []byte)) {
for i, f := range faults {
m := base()
f.apply(m)
add(f.name, m.bytes())
for _, g := range faults[i+1:] {
m := base()
f.apply(m)
g.apply(m)
add(f.name+", and "+g.name, m.bytes())
}
}
}
var sharedFaults = []fault{
{"an unknown key", func(m *mapBuilder) { m.set(20, cu(0)) }},
{"a trailing byte", func(m *mapBuilder) { m.trailing = []byte{0} }},
{"a head of one entry more", func(m *mapBuilder) { m.delta = 1 }},
{"keys 1 and 2 swapped", func(m *mapBuilder) { m.entries[1], m.entries[2] = m.entries[2], m.entries[1] }},
}
func headerPrecedenceCases() []Case {
var out []Case
base := func() *mapBuilder {
return &mapBuilder{entries: []entry{
kv(0, ct("datekeycap")), kv(1, cu(1)), kv(2, cbs(bytes.Repeat([]byte{9}, 16))),
kv(3, ct(dk1(`{"version":1,"network":"datekeys:quicknet:v1","round":1000}`))), kv(4, cu(1)),
}}
}
faults := append([]fault{
{"the type tag of CONTROL_CBOR", func(m *mapBuilder) { m.set(0, ct("datekeys-control")) }},
{"schema version 2", func(m *mapBuilder) { m.set(1, cu(2)) }},
{"a capsule_id of 15 bytes", func(m *mapBuilder) { m.set(2, cbs(bytes.Repeat([]byte{9}, 15))) }},
{"the DateKey dk1_x", func(m *mapBuilder) { m.set(3, ct("dk1_x")) }},
{"a DateKey of round 1.0e3", func(m *mapBuilder) {
m.set(3, ct(dk1(`{"version":1,"network":"datekeys:quicknet:v1","round":1.0e3}`)))
}},
{"a DateKey as a byte string", func(m *mapBuilder) { m.set(3, cbs([]byte("dk1_x"))) }},
{"access_policy 2", func(m *mapBuilder) { m.set(4, cu(2)) }},
{"access_policy 2^53", func(m *mapBuilder) { m.set(4, cu(1<<53)) }},
{"an empty critical array", func(m *mapBuilder) { m.set(5, ca()) }},
{"an extension_id in both arrays", func(m *mapBuilder) {
m.set(5, extArray([]ext{{id: "a", version: 1}}))
m.set(6, extArray([]ext{{id: "a", version: 2}}))
}},
{"noncritical extensions out of order", func(m *mapBuilder) {
m.set(6, extArray([]ext{{id: "b", version: 1}, {id: "a", version: 1}}))
}},
{"an unknown critical extension", func(m *mapBuilder) { m.set(5, extArray([]ext{{id: "org.unknown", version: 1}})) }},
{"no key 4", func(m *mapBuilder) { m.del(4) }},
}, sharedFaults...)
pairs(base, faults, func(name string, b []byte) {
c := Case{"name": name, "hex": hx(b)}
_, err := capsule.DecodeHeader(b)
out = append(out, outcome(c, err))
})
return out
}
func controlPrecedenceCases() []Case {
var out []Case
for _, f := range []capsule.Format{capsule.Format1, capsule.Format2, capsule.Format3} {
padded := f != capsule.Format1
base := func() *mapBuilder {
m := &mapBuilder{entries: []entry{
kv(0, ct("datekeys-control")), kv(1, cu(uint64(f))),
kv(2, cbs(bytes.Repeat([]byte{1}, 32))), kv(3, cbs(bytes.Repeat([]byte{2}, 32))),
}}
if padded {
m.set(6, cbs(binary.BigEndian.AppendUint64(nil, 78000)))
m.set(7, cu(2))
}
return m
}
faults := append([]fault{
{"the type tag of PUBLIC_HEADER", func(m *mapBuilder) { m.set(0, ct("datekeycap")) }},
{"the schema version of another format", func(m *mapBuilder) { m.set(1, cu(uint64(1+int(f)%3))) }},
{"a header_binding of 31 bytes", func(m *mapBuilder) { m.set(2, cbs(bytes.Repeat([]byte{1}, 31))) }},
{"no key 3", func(m *mapBuilder) { m.del(3) }},
{"an extension_id in both arrays", func(m *mapBuilder) {
m.set(4, extArray([]ext{{id: "a", version: 1}}))
m.set(5, extArray([]ext{{id: "a", version: 1}}))
}},
{"an empty noncritical array", func(m *mapBuilder) { m.set(5, ca()) }},
{"an unknown critical extension", func(m *mapBuilder) { m.set(4, extArray([]ext{{id: "org.unknown", version: 1}})) }},
{"payload_length L_MAX + 1", func(m *mapBuilder) {
m.set(6, cbs(binary.BigEndian.AppendUint64(nil, capsule.MaxPayloadLength+1)))
}},
{"payload_length of 7 bytes", func(m *mapBuilder) { m.set(6, cbs(make([]byte, 7))) }},
{"padding code 3", func(m *mapBuilder) { m.set(7, cu(3)) }},
{"no key 7", func(m *mapBuilder) { m.del(7) }},
}, sharedFaults...)
pairs(base, faults, func(name string, b []byte) {
c := Case{"name": name, "format": int(f), "hex": hx(b)}
_, err := capsule.DecodeControl(b, f)
out = append(out, outcome(c, err))
})
}
return out
}
func dkkPrecedenceCases() []Case {
var out []Case
base := func() *mapBuilder {
return &mapBuilder{entries: []entry{
kv(0, ct("datekeys-access-key")), kv(1, cu(1)), kv(2, cbs(bytes.Repeat([]byte{1}, 16))),
kv(3, cbs(bytes.Repeat([]byte{2}, 16))), kv(4, ct("x25519")), kv(5, cbs(bytes.Repeat([]byte{3}, 32))),
}}
}
faults := append([]fault{
{"the type tag of PUBLIC_HEADER", func(m *mapBuilder) { m.set(0, ct("datekeycap")) }},
{"schema version 2", func(m *mapBuilder) { m.set(1, cu(2)) }},
{"a credential_id of 15 bytes", func(m *mapBuilder) { m.set(2, cbs(bytes.Repeat([]byte{1}, 15))) }},
{"access_type mlkem768", func(m *mapBuilder) { m.set(4, ct("mlkem768")) }},
{"access_type as an unsigned integer", func(m *mapBuilder) { m.set(4, cu(1)) }},
{"access_material of 31 bytes", func(m *mapBuilder) { m.set(5, cbs(bytes.Repeat([]byte{3}, 31))) }},
{"access_material as a text string", func(m *mapBuilder) { m.set(5, ct("material")) }},
{"an empty verification_metadata", func(m *mapBuilder) { m.set(6, cm()) }},
{"a capsule_digest of 31 bytes", func(m *mapBuilder) { m.set(6, cm(kv(0, cbs(make([]byte, 31))))) }},
{"an extension_id in both arrays", func(m *mapBuilder) {
m.set(7, extArray([]ext{{id: "a", version: 1}}))
m.set(8, extArray([]ext{{id: "a", version: 1}}))
}},
{"an unknown critical extension", func(m *mapBuilder) { m.set(7, extArray([]ext{{id: "org.unknown", version: 1}})) }},
{"datekeys.note in the noncritical array", func(m *mapBuilder) {
m.set(8, extArray([]ext{{id: extension.NoteID, version: 1, data: []byte("A")}}))
}},
}, sharedFaults...)
frame := func(body []byte) []byte {
pre := []byte{'D', 'K', 'K', '1', 1, 0, 0, 0, 0, 0, 0, 0}
binary.BigEndian.PutUint32(pre[8:], uint32(len(body)))
return append(pre, body...)
}
i := 0
pairs(base, faults, func(name string, b []byte) {
for _, flags := range []bool{false, true} {
dkk := frame(b)
n := name
if flags {
// The frame first (spec §40): FLAGS 1 decides.
dkk[5] = 1
n += ", and FLAGS 1"
}
c := Case{"name": n, "hex": hx(dkk)}
_, err := accesskey.Decode(bytes.NewReader(dkk))
out = append(out, outcome(c, err))
if i++; i%4 != 0 {
break
}
}
})
return out
}
func profilePrecedenceCases() []Case {
var out []Case
q := quicknetFields()
base := func() *mapBuilder {
var m mapBuilder
m.entries = []entry{
kv(0, ct("datekeys-provider-profile")), kv(1, cu(1)),
kv(2, ct(q.id)), kv(3, ct(q.provider)), kv(4, ct(q.network)),
kv(5, cbs(q.chainHash)), kv(6, cbs(q.publicKey)),
kv(7, cu(q.period)), kv(8, cu(q.genesis)),
kv(9, ct(q.scheme)), kv(10, cbs(q.seed)),
}
return &m
}
faults := append([]fault{
{"the type tag of PUBLIC_HEADER", func(m *mapBuilder) { m.set(0, ct("datekeycap")) }},
{"schema version 2", func(m *mapBuilder) { m.set(1, cu(2)) }},
{"the profile_id X", func(m *mapBuilder) { m.set(2, ct("X")) }},
{"the provider Drand", func(m *mapBuilder) { m.set(3, ct("Drand")) }},
{"the network quick net", func(m *mapBuilder) { m.set(4, ct("quick net")) }},
{"a chain_hash of 31 bytes", func(m *mapBuilder) { m.set(5, cbs(make([]byte, 31))) }},
{"another chain_hash", func(m *mapBuilder) { m.set(5, cbs(make([]byte, 32))) }},
{"the public key of G1", func(m *mapBuilder) { m.set(6, cbs(g1Generator)) }},
{"period 0", func(m *mapBuilder) { m.set(7, cu(0)) }},
{"period 86401", func(m *mapBuilder) { m.set(7, cu(86401)) }},
{"genesis_time 0", func(m *mapBuilder) { m.set(8, cu(0)) }},
{"the scheme pedersen-bls-chained", func(m *mapBuilder) { m.set(9, ct("pedersen-bls-chained")) }},
{"no key 10", func(m *mapBuilder) { m.del(10) }},
}, sharedFaults...)
pairs(base, faults, func(name string, b []byte) {
c := Case{"name": name, "hex": hx(b)}
_, err := profile.Decode(b)
out = append(out, outcome(c, err))
})
return out
}
Stage 4b: the differential of the formats against Go 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>
2 days ago
// ---------------------------------------------------------------------------
// The encoders on values, and the limits of spec §57
// written records in c the encoding b of an encoder that succeeded: its
// bytes, or its length and SHA-256 when it is large.
func written(c Case, b []byte) {
if len(b) <= 4096 {
c["hex"] = hx(b)
return
}
sum := sha256.Sum256(b)
c["length"] = len(b)
c["sha256"] = hx(sum[:])
}
// big is the noncritical extension "a" of version 1 whose data is n bytes
// of 0x01, which takes an object to the limit of its frame.
func big(n int) extension.Extension {
return extension.Extension{ID: "a", Version: 1, Data: bytes.Repeat([]byte{1}, n)}
}
func headerEncodeCases() []Case {
var out []Case
add := func(name, id string, round uint64, policy capsule.Policy, crit, non []ext, n int) int {
h := &capsule.Header{DateKey: datekey.DateKey{ProfileID: id, Round: round}, Policy: policy, Critical: toExts(crit), Noncritical: toExts(non)}
copy(h.CapsuleID[:], bytes.Repeat([]byte{7}, 16))
c := Case{"name": name, "profile_id": id, "round": round, "policy": int(policy), "critical": extsJSON(crit), "noncritical": extsJSON(non)}
if n > 0 {
h.Noncritical = append(h.Noncritical, big(n))
c["big"] = n
}
b, err := capsule.EncodeHeader(h)
if err == nil {
written(c, b)
}
out = append(out, outcome(c, err))
return len(b)
}
q := profile.QuicknetID
add("time_only, no extension", q, 1000, capsule.TimeOnly, nil, nil, 0)
add("extensions written in canonical order", q, 1000, capsule.TimeAndKey, []ext{{id: "z", version: 3}}, []ext{{id: "b", version: 1}, {id: "a", version: 2, data: []byte{1}}}, 0)
add("profile_id not valid", "X", 1, capsule.TimeOnly, nil, nil, 0)
add("round 0", q, 0, capsule.TimeOnly, nil, nil, 0)
add("round 2^53", q, 1<<53, capsule.TimeOnly, nil, nil, 0)
add("the last round of a dk1_", "a", 1<<53-1, capsule.TimeOnly, nil, nil, 0)
add("one extension_id in both arrays", q, 1000, capsule.TimeOnly, []ext{{id: "b", version: 1}}, []ext{{id: "b", version: 2}}, 0)
add("empty extension_id", q, 1000, capsule.TimeOnly, []ext{{id: "", version: 1}}, nil, 0)
add("extension_version 2^32", q, 1000, capsule.TimeOnly, nil, []ext{{id: "a", version: 1 << 32}}, 0)
add("data present and empty", q, 1000, capsule.TimeOnly, nil, []ext{{id: "a", version: 1, data: []byte{}}}, 0)
add("one extension_id twice", q, 1000, capsule.TimeOnly, nil, []ext{{id: "a", version: 1}, {id: "a", version: 2}}, 0)
var many []ext
for i := range 65 {
many = append(many, ext{id: fmt.Sprintf("e%02d", i), version: 1})
}
add("65 extensions", q, 1000, capsule.TimeOnly, many, nil, 0)
n := 100000
n += capsule.MaxPublicHeaderLen - add("the limit of spec §57, sized", q, 1000, capsule.TimeAndKey, nil, nil, n)
add("exactly 1 MiB", q, 1000, capsule.TimeAndKey, nil, nil, n)
add("1 MiB and 1 byte", q, 1000, capsule.TimeAndKey, nil, nil, n+1)
return out
}
func controlEncodeCases() []Case {
var out []Case
add := func(name string, f capsule.Format, l uint64, padding capsule.Padding, crit, non []ext) {
ctl := &capsule.Control{PayloadLength: l, Padding: padding, Critical: toExts(crit), Noncritical: toExts(non)}
copy(ctl.HeaderBinding[:], bytes.Repeat([]byte{1}, 32))
copy(ctl.PayloadIdentity[:], bytes.Repeat([]byte{5}, 32))
c := Case{"name": name, "format": int(f), "payload_length": l, "padding": int(padding), "critical": extsJSON(crit), "noncritical": extsJSON(non)}
b, err := capsule.EncodeControl(ctl, f)
if err == nil {
written(c, b)
}
out = append(out, outcome(c, err))
}
add("format 1", capsule.Format1, 0, 0, nil, nil)
add("format 1 with L", capsule.Format1, 1, 0, nil, nil)
add("format 1 with a padding code", capsule.Format1, 0, capsule.Reforzado, nil, nil)
for _, f := range []capsule.Format{capsule.Format2, capsule.Format3} {
name := fmt.Sprintf("format %d", f)
for _, l := range []uint64{0, 1, 78000, 1<<32 + 1, capsule.MaxPayloadLength} {
for _, p := range []capsule.Padding{capsule.Bloque256, capsule.Reforzado} {
add(fmt.Sprintf("%s, L %d, %s", name, l, p), f, l, p, nil, nil)
}
}
add(name+" without a padding code", f, 1, 0, nil, nil)
add(name+" with L_MAX + 1", f, capsule.MaxPayloadLength+1, capsule.Reforzado, nil, nil)
}
add("extensions written in canonical order", capsule.Format2, 34, capsule.Reforzado, []ext{{id: "z", version: 3, data: []byte{1}}}, []ext{{id: "org.b", version: 1}, {id: "org.a", version: 2}})
add("one extension_id in both arrays", capsule.Format1, 0, 0, []ext{{id: "a", version: 1}}, []ext{{id: "a", version: 1}})
add("extension_version 2^32", capsule.Format3, 1, capsule.Bloque256, nil, []ext{{id: "a", version: 1 << 32}})
return out
}
func dkkEncodeCases() []Case {
var out []Case
add := func(name, typ string, material, digest []byte, crit, non []ext, n int) int {
k := &accesskey.AccessKey{Type: typ, Material: material, Critical: toExts(crit), Noncritical: toExts(non)}
copy(k.CredentialID[:], bytes.Repeat([]byte{1}, 16))
copy(k.CapsuleID[:], bytes.Repeat([]byte{2}, 16))
c := Case{"name": name, "type": typ, "material": hx(material), "critical": extsJSON(crit), "noncritical": extsJSON(non)}
if digest != nil {
k.Verification = &accesskey.Verification{CapsuleDigest: digest}
c["capsule_digest"] = hx(digest)
}
if n > 0 {
k.Noncritical = append(k.Noncritical, big(n))
c["big"] = n
}
var w bytes.Buffer
err := accesskey.Encode(&w, k)
if err == nil {
written(c, w.Bytes())
}
out = append(out, outcome(c, err))
return w.Len()
}
m := bytes.Repeat([]byte{3}, 32)
d := bytes.Repeat([]byte{4}, 32)
add("minimal", "x25519", m, nil, nil, nil, 0)
add("with capsule_digest and extensions", "x25519", m, d, []ext{{id: "org.z", version: 1}}, []ext{{id: "org.b", version: 1, data: []byte{0}}, {id: "org.a", version: 7}}, 0)
add("access_type y", "y", m, nil, nil, nil, 0)
add("access_material of 31 bytes", "x25519", m[:31], nil, nil, nil, 0)
add("access_type y and access_material of 31 bytes", "y", m[:31], nil, nil, nil, 0)
add("empty capsule_digest", "x25519", m, []byte{}, nil, nil, 0)
add("capsule_digest of 31 bytes", "x25519", m, d[:31], nil, nil, 0)
add("one extension_id in both arrays", "x25519", m, nil, []ext{{id: "z", version: 1}}, []ext{{id: "z", version: 1}}, 0)
add("datekeys.note in the noncritical array", "x25519", m, nil, nil, []ext{{id: extension.NoteID, version: 1, data: []byte("A")}}, 0)
add("datekeys.capsule in the critical array", "x25519", m, nil, []ext{{id: extension.CapsuleID, version: 1, data: []byte{0xa0}}}, nil, 0)
add("datekeys.capsule without data", "x25519", m, nil, nil, []ext{{id: extension.CapsuleID, version: 1}}, 0)
add("datekeys.capsule with data", "x25519", m, nil, nil, []ext{{id: extension.CapsuleID, version: 1, data: []byte{0xa0}}}, 0)
add("datekeys.note of version 2", "x25519", m, nil, nil, []ext{{id: extension.NoteID, version: 2, data: []byte("A")}}, 0)
n := 100000
n += 12 + accesskey.MaxBodyLen - add("the limit of spec §57, sized", "x25519", m, nil, nil, nil, n)
add("BODY_CBOR of exactly 16 MiB", "x25519", m, nil, nil, nil, n)
add("BODY_CBOR of 16 MiB and 1 byte", "x25519", m, nil, nil, nil, n+1)
return out
}
func registryCases() []Case {
var out []Case
add := func(name string, pins []profile.Pin) {
c := Case{"name": name}
var ps []Case
for _, pin := range pins {
ps = append(ps, Case{"profile": profileJSON(pin.Profile), "hash": hx(pin.Hash[:])})
}
c["pins"] = ps
_, err := profile.NewRegistry(pins...)
out = append(out, outcome(c, err))
}
q := profile.Quicknet()
h := must(q.Hash())
add("Quicknet", []profile.Pin{{Profile: q, Hash: h}})
add("Quicknet with another hash", []profile.Pin{{Profile: q, Hash: [32]byte{1}}})
add("Quicknet twice", []profile.Pin{{Profile: q, Hash: h}, {Profile: profile.Quicknet(), Hash: h}})
add("none", nil)
for _, edit := range []func(p *profile.Profile){
func(p *profile.Profile) { p.ID = "X" },
func(p *profile.Profile) { p.Period = 0 },
func(p *profile.Profile) { p.Period = 90000 * time.Second },
func(p *profile.Profile) { p.Period = 61 * time.Second },
func(p *profile.Profile) { p.GenesisTime = -1 },
func(p *profile.Profile) { p.Network = "default" },
} {
p := profile.Quicknet()
edit(p)
add("an edited Quicknet", []profile.Pin{{Profile: p, Hash: h}})
}
return out
}
func compactCases() []Case {
var out []Case
for _, d := range []datekey.DateKey{
{ProfileID: profile.QuicknetID, Round: 66884212}, {ProfileID: "a", Round: 1<<53 - 1},
{ProfileID: "a", Round: 1 << 53}, {ProfileID: "a", Round: 0}, {ProfileID: "A", Round: 1},
{ProfileID: "", Round: 1}, {ProfileID: strings.Repeat("a", 128), Round: 1}, {ProfileID: strings.Repeat("a", 129), Round: 1},
{ProfileID: "0:._-", Round: 7},
} {
var j any
if b := d.CanonicalJSON(); b != nil {
j = string(b)
}
out = append(out, Case{"profile_id": d.ProfileID, "round": d.Round, "json": j, "compact": d.Compact()})
}
return out
}
// limitCases are the objects at the limits of spec §57 and one byte past
// them, n bytes of zeros or, for the data of an extension, a whole array.
func limitCases() []Case {
var out []Case
for _, n := range []int{capsule.MaxPublicHeaderLen, capsule.MaxPublicHeaderLen + 1} {
_, err := capsule.DecodeHeader(make([]byte, n))
out = append(out, outcome(Case{"what": "header", "zeros": n}, err))
}
for _, n := range []int{accesskey.MaxBodyLen, accesskey.MaxBodyLen + 1} {
_, err := accesskey.DecodeBody(make([]byte, n))
out = append(out, outcome(Case{"what": "body", "zeros": n}, err))
b := make([]byte, 12+n)
copy(b, "DKK1\x01")
binary.BigEndian.PutUint32(b[8:], uint32(n))
_, err = accesskey.Decode(bytes.NewReader(b))
out = append(out, outcome(Case{"what": "dkk", "zeros": n}, err))
}
for _, n := range []int{extension.MaxDataLen, extension.MaxDataLen + 1} {
b := append(unhex("81a30061610101025a"), binary.BigEndian.AppendUint32(nil, uint32(n))...)
b = append(b, bytes.Repeat([]byte{7}, n)...)
var xs []extension.Extension
err := codec.Unmarshal(b, func(d *codec.Decoder) error {
var err error
xs, err = extension.DecodeArray(d)
return err
}, func(e *codec.Encoder) { extension.EncodeArray(e, xs) })
out = append(out, outcome(Case{"what": "extension_data", "len": n}, err))
_, err = extension.Canonical([]extension.Extension{{ID: "a", Version: 1, Data: make([]byte, n)}})
out = append(out, outcome(Case{"what": "extension_canonical", "len": n}, err))
}
return out
}
// discard is a capsule.Sink that keeps nothing.
type discard struct{}
func (discard) Begin(*capsule.Head) error { return nil }
func (discard) Create(int) (io.WriteCloser, error) {
return nopCloser{io.Discard}, nil
}
func (discard) Commit() error { return nil }
func (discard) Abort() {}
type nopCloser struct{ io.Writer }
func (nopCloser) Close() error { return nil }
// paddingCheckCases opens fixtures of formats 2 and 3 whose PAYLOAD_AGE is
// encrypted again, to the same I_PAYLOAD, with a plaintext edited after L or
// cut: the outcome is the check of the padding of capsule.Open at step 17,
// checkPadding, whose texts the Dart PaddingCheck reproduces.
func paddingCheckCases(dir string, fxs []fixture) []Case {
reg := must(profile.Default())
type record struct {
Release struct {
Round uint64 `json:"round"`
Signature string `json:"signature"`
} `json:"release"`
PayloadIdentity string `json:"payload_identity"`
PayloadLength uint64 `json:"payload_length"`
PaddedLength uint64 `json:"padded_length"`
PlaintextFile string `json:"plaintext_file"`
}
var out []Case
for _, f := range fxs {
switch f.name {
case "format2_empty_payload", "format2_time_only_extensions", "format2_time_only", "format3_single", "format3_tree":
default:
continue
}
var rec record
if err := json.Unmarshal(must(os.ReadFile(filepath.Join(dir, "fixtures", f.name+".json"))), &rec); err != nil {
panic(err)
}
content := must(os.ReadFile(filepath.Join(dir, "fixtures", rec.PlaintextFile)))
l, p := rec.PayloadLength, rec.PaddedLength
if uint64(len(content)) != l {
panic("a plaintext file that is not L bytes")
}
id := must(agewrap.X25519IdentityFromRaw(unhex(rec.PayloadIdentity)))
release := provider.Release{Round: rec.Release.Round, Signature: unhex(rec.Release.Signature)}
offset := capsule.Prelude{
PublicHeaderLen: binary.BigEndian.Uint32(f.dkc[8:12]),
SealedControlLen: binary.BigEndian.Uint32(f.dkc[12:16]),
}.PayloadOffset()
type variant struct {
length uint64
at int64 // -1 for none
value byte
}
vs := []variant{{p, -1, 0}, {p + 1, -1, 0}, {p + 100, -1, 0}, {p - 1, -1, 0}, {p + 1, int64(p), 7}}
if p > l {
vs = append(vs, variant{p, int64(l), 1}, variant{p, int64(p - 1), 0x80}, variant{p, int64(l + (p-l)/2), 0xff}, variant{l, -1, 0})
if p-l > 1 {
vs = append(vs, variant{p - 1, int64(p - 2), 3})
}
}
if f.Format == 2 {
vs = append(vs, variant{0, -1, 0})
if l > 0 {
vs = append(vs, variant{l - 1, -1, 0}, variant{l / 2, -1, 0})
}
}
seen := map[variant]bool{}
for _, v := range vs {
if seen[v] {
continue
}
seen[v] = true
plain := make([]byte, v.length)
copy(plain, content)
if v.at >= 0 {
plain[v.at] = v.value
}
var payload bytes.Buffer
w := must(age.Encrypt(&payload, id.Recipient()))
must(w.Write(plain))
if err := w.Close(); err != nil {
panic(err)
}
dkc := append(bytes.Clone(f.dkc[:offset]), payload.Bytes()...)
opts := capsule.OpenOptions{
Registry: reg,
Source: provider.ReleaseSourceFunc(func(context.Context, *profile.Profile, provider.Condition) (provider.Release, error) {
return release, nil
}),
Now: func() time.Time { return time.Unix(2000000000, 0) },
}
var dst io.Writer = io.Discard
if f.Format == 3 {
opts.Sink, dst = discard{}, nil
}
c := Case{"fixture": f.name, "format": f.Format, "l": l, "p": p, "length": v.length}
if v.at >= 0 {
c["at"], c["value"] = v.at, v.value
}
opened, err := capsule.Open(context.Background(), dst, bytes.NewReader(dkc), opts)
step := 0
if err != nil {
step = opened.Inspection.Checks[len(opened.Inspection.Checks)-1].Step
if step != 17 {
panic(fmt.Sprintf("%s: a failure at step %d: %v", f.name, step, err))
}
}
c["step"] = step
out = append(out, outcome(c, err))
}
}
return out
}
func bodyCases() (frames, areas []Case) {
areaLens := []uint32{0, 1, 511, 512, 513, 1024, 32768, 32769, 65536, 66048, 1 << 31, 0xffffffff}
for range 260 {
a := pick(areaLens...)
if rng.IntN(5) == 0 {
a = 512 * uint32(1+rng.IntN(130))
}
s := pick(0, 1, 22, a-1, a, a+1, uint32(rng.IntN(int(min(a, 70000))+1)))
h := pick[uint32](0, 1, 53, 1<<24, 1<<24+1, uint32(rng.IntN(1<<25)), 0xffffffff)
b := make([]byte, 12)
binary.BigEndian.PutUint32(b, a)
binary.BigEndian.PutUint32(b[4:], s)
binary.BigEndian.PutUint32(b[8:], h)
if rng.IntN(25) == 0 {
b = b[:pick(0, 11)]
if rng.IntN(2) == 0 {
b = append(b, 0, 0)
}
}
need := uint64(12) + uint64(a) + uint64(h)
l := pick(need, need-1, need+1, uint64(11), uint64(12), uint64(0), rng.Uint64N(8936830510563328+1), need+rng.Uint64N(1<<40))
c := Case{"hex": hx(b), "l": l}
f, err := capsule.ParseBodyFrame(b, l)
outcome(c, err)
if err == nil {
c["area_len"] = f.AreaLen
c["security_len"] = f.SecurityLen
c["head_len"] = f.HeadLen
c["content_length"] = f.ContentLength(l)
}
frames = append(frames, c)
}
for range 60 {
n := pick(1, 2, 22, 64)
area := make([]byte, n)
s := uint32(rng.IntN(n + 1))
copy(area, randBytes(int(s)))
if rng.IntN(2) == 0 {
area[rng.IntN(n)] = byte(1 + rng.IntN(255))
}
c := Case{"hex": hx(area), "security_len": s}
areas = append(areas, outcome(c, capsule.CheckArea(area, s)))
}
return
}
// ---------------------------------------------------------------------------
// Output
// renderJSON is the text of one file: the object with its sections, one
// case per line, and the apostrophes escaped, so that the text is a raw
// string of Dart.
func renderJSON(description string, sections []string, cases map[string][]Case) string {
var sb strings.Builder
enc := func(v any) string {
var b bytes.Buffer
e := json.NewEncoder(&b)
e.SetEscapeHTML(false)
if err := e.Encode(v); err != nil {
panic(err)
}
return strings.ReplaceAll(strings.TrimSuffix(b.String(), "\n"), "'", `'`)
}
sb.WriteString("{\n")
sb.WriteString(` "spec": ` + enc(specVersion) + ",\n")
sb.WriteString(` "generator": "tool/formats_go_vectors.go",` + "\n")
sb.WriteString(` "description": ` + enc(description))
for _, s := range sections {
sb.WriteString(",\n " + enc(s) + ": [")
for i, c := range cases[s] {
if i > 0 {
sb.WriteString(",")
}
sb.WriteString("\n " + enc(c))
}
sb.WriteString("\n ]")
}
sb.WriteString("\n}\n")
return sb.String()
}
func main() {
testdata := flag.String("testdata", "../datekeys-dart/testdata", "the synced testdata/ of datekeys-dart")
outDir := flag.String("out", "../datekeys-dart/test/vectors", "where the vectors go")
flag.Parse()
fxs, dkks := loadFixtures(*testdata)
type file struct {
name, description string
sections []string
cases map[string][]Case
}
var files []file
files = append(files, file{"framing", "The PRELUDE of a .dkc (capsule.ParsePrelude), the steps 1 to 3 of capsule.Inspect on truncated and edited fixtures (with the step that fails, 0 when steps 1 to 8 pass), and whole .dkk files (accesskey.Decode), with the result, the code and the text of the Go reference.",
[]string{"prelude", "split", "dkk", "dkk_precedence"},
map[string][]Case{"prelude": preludeCases(), "split": splitCases(fxs), "dkk": dkkCases(dkks), "dkk_precedence": dkkPrecedenceCases()}})
Stage 4b: the differential of the formats against Go 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>
2 days ago
files = append(files, file{"header", "PUBLIC_HEADER (capsule.DecodeHeader) of the fixtures, edited, and built field by field.",
[]string{"header", "precedence"}, map[string][]Case{"header": headerCases(fxs), "precedence": headerPrecedenceCases()}})
Stage 4b: the differential of the formats against Go 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>
2 days ago
files = append(files, file{"control", "CONTROL_CBOR of the three formats (capsule.DecodeControl) of the fixtures, in their format and in another, edited, and built field by field.",
[]string{"control", "precedence"}, map[string][]Case{"control": controlCases(fxs), "precedence": controlPrecedenceCases()}})
Stage 4b: the differential of the formats against Go 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>
2 days ago
decode, validate := profileCases()
files = append(files, file{"profile", "Provider Profiles: profile.Decode of the Quicknet profile, edited and with fields replaced, and Profile.Validate of values.",
[]string{"decode", "validate", "precedence"}, map[string][]Case{"decode": decode, "validate": validate, "precedence": profilePrecedenceCases()}})
Stage 4b: the differential of the formats against Go 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>
2 days ago
arrays, canonical, disjoint, registry, write := extensionCases()
files = append(files, file{"extension", "Extension arrays through codec.Unmarshal with extension.DecodeArray and EncodeArray, Canonical, CheckDisjoint, CheckCriticalIn and CheckNoncriticalIn with the registry placed of the generator (or CheckCritical and CheckNoncritical when object is empty, and no registry when registry is none), and CheckWrite with extension.Standard.",
[]string{"array", "canonical", "disjoint", "registry", "write"},
map[string][]Case{"array": arrays, "canonical": canonical, "disjoint": disjoint, "registry": registry, "write": write}})
files = append(files, file{"datekey", "dk1_ strings (datekey.Parse).",
[]string{"parse"}, map[string][]Case{"parse": datekeyCases()}})
parse, format, resolve, roundTime, validateDK, maxRound := timeCases()
files = append(files, file{"time", "RFC 3339: time.Parse with time.RFC3339Nano, ok or error, and Format with RFC3339 and RFC3339Nano in UTC; datekey.Resolve, RoundTime and DateKey.Validate, and Profile.MaxRound, on the Quicknet profile with other genesis times and periods.",
[]string{"parse", "format", "resolve", "round_time", "validate", "max_round"},
map[string][]Case{"parse": parse, "format": format, "resolve": resolve, "round_time": roundTime, "validate": validateDK, "max_round": maxRound}})
files = append(files, file{"padding", "capsule.PaddedLength with both codes and capsule.PayloadAgeLength, at the boundaries of spec §29.1 up to L_MAX and past it; a code with null was rejected with text. check: the plaintext of PAYLOAD_AGE checked by capsule.Open against L and P (step 17): the content of the fixture (its .plaintext) followed by zeros, cut or extended to length bytes, with the byte at at set to value when given; step 0 when the capsule opens.",
[]string{"padded", "check"}, map[string][]Case{"padded": paddingCases(), "check": paddingCheckCases(*testdata, fxs)}})
files = append(files, file{"encode", "The encoders on values: capsule.EncodeHeader and EncodeControl, accesskey.Encode, profile.NewRegistry, and the canonical JSON and dk1_ string of DateKeys (CanonicalJSON and Compact, empty when not valid); an encoding is in hex, or as its length and SHA-256 when it is large. big is the size of the data of the noncritical extension a of version 1, bytes 0x01, that takes the object to its limit. limits: the decoders on objects at the limits of spec §57 and one byte past them: zeros bytes of zeros, or an extension array 81 a3 00 61 61 01 01 02 5a <len, 4 bytes> and len bytes 0x07 (extension_data), or Canonical of the extension a of version 1 whose data is len zeros (extension_canonical).",
[]string{"header", "control", "dkk", "registry", "compact", "limits"},
map[string][]Case{"header": headerEncodeCases(), "control": controlEncodeCases(), "dkk": dkkEncodeCases(), "registry": registryCases(), "compact": compactCases(), "limits": limitCases()}})
frames, areas := bodyCases()
files = append(files, file{"body", "The frame of BODY (capsule.ParseBodyFrame) against L, and the zeros of the security area (capsule.CheckArea).",
[]string{"frame", "area"}, map[string][]Case{"frame": frames, "area": areas}})
var dart strings.Builder
dart.WriteString("// Generated by tool/formats_go_vectors.go: every eighth case of each\n")
dart.WriteString("// section of test/vectors/formats_*.json, for the tests that also run\n")
dart.WriteString("// compiled to JavaScript, where no file can be read. Do not edit.\n\n")
for _, f := range files {
path := filepath.Join(*outDir, "formats_"+f.name+".json")
if err := os.WriteFile(path, []byte(renderJSON(f.description, f.sections, f.cases)), 0o644); err != nil {
panic(err)
}
sub := map[string][]Case{}
for _, s := range f.sections {
for i, c := range f.cases[s] {
if i%8 == 0 {
sub[s] = append(sub[s], c)
}
}
}
text := renderJSON(f.description, f.sections, sub)
if strings.Contains(text, "'''") {
panic("a raw string of Dart cannot hold '''")
}
name := strings.ToUpper(f.name[:1]) + f.name[1:]
fmt.Fprintf(&dart, "/// Part of test/vectors/formats_%s.json.\nconst formats%sJson = r'''\n%s''';\n\n", f.name, name, text)
fmt.Fprintf(os.Stderr, "formats_%s.json: %v\n", f.name, counts(f.sections, f.cases))
}
out := strings.TrimSuffix(dart.String(), "\n")
if err := os.WriteFile(filepath.Join(*outDir, "formats_vectors.g.dart"), []byte(out), 0o644); err != nil {
panic(err)
}
}
func counts(sections []string, cases map[string][]Case) string {
var parts []string
for _, s := range sections {
ok := 0
for _, c := range cases[s] {
if c["result"] == "ok" {
ok++
}
}
parts = append(parts, fmt.Sprintf("%s %d (%d ok)", s, len(cases[s]), ok))
}
return strings.Join(parts, ", ")
}

Powered by TurnKey Linux.