Format 3, step 8: fuzzing of format 3 and the SHA-256 of spec v0.10

- Three fuzz targets, in scripts/fuzz.sh too: FuzzDecodeHead (a head
  that is accepted re-encodes to its input, and a rejection carries one
  normative code), FuzzEvaluateSecurity (verdicts of this version, X for
  both or for neither) and FuzzCheckPath (the rules of one entry and the
  decoder of the head agree on every path). About a million runs each,
  clean; scripts/check.sh 60s is clean.
- Spec v0.10, section 67: the fixtures of format 3 exist, so "Serán ...
  (por implementar)" reads "Son ...", as for those of format 2. No rule
  changes.
- spec/README.md: v0.10 approved by its author on 30 September 2026 and
  implemented on this branch, with the SHA-256 of its text; the tag
  spec-v0.10 waits for the author.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.10
dev 1 week ago
parent 735885cb29
commit 6fa2b54bd3

@ -118,6 +118,67 @@ func FuzzDecodeControl(f *testing.F) {
})
}
// FuzzDecodeHead: a head that DecodeHead accepts re-encodes to its input,
// and each rejection carries exactly one normative code (spec §29.4, §69.1).
func FuzzDecodeHead(f *testing.F) {
for _, name := range fixtureNames {
if fx := loadFixture(f, name); fx.Head != "" {
b, _ := hexDecode(fx.Head)
f.Add(b)
}
}
var heads testkit.HeadSchemaFile
if err := testkit.ReadJSON("../testdata/vectors/head_schema.json", &heads); err != nil {
f.Fatal(err)
}
for _, v := range heads.Heads {
b, _ := hexDecode(v.Hex)
f.Add(b)
}
f.Fuzz(func(t *testing.T, b []byte) {
h, err := capsule.DecodeHead(b, nil)
if err != nil {
if n := codes(err); n != 1 {
t.Fatalf("%d normative codes: %v", n, err)
}
return
}
if re, err := capsule.EncodeHead(h); err != nil || !bytes.Equal(re, b) {
t.Fatalf("accepted a head that does not re-encode to its input: %v", err)
}
})
}
// FuzzEvaluateSecurity: security never fails, and gives verdicts of this
// version, X for both the signature and the seal or for neither (spec §29.3,
// §29.7).
func FuzzEvaluateSecurity(f *testing.F) {
for _, name := range fixtureNames {
if fx := loadFixture(f, name); fx.Security != "" {
b, _ := hexDecode(fx.Security)
f.Add(b)
}
}
var security testkit.SecurityVectorFile
if err := testkit.ReadJSON("../testdata/vectors/security.json", &security); err != nil {
f.Fatal(err)
}
for _, v := range security.Vectors {
b, _ := hexDecode(v.Hex)
f.Add(b)
}
f.Fuzz(func(t *testing.T, b []byte) {
v := capsule.EvaluateSecurity(b)
switch {
case v.Signature != capsule.VerdictUnreadable && v.Signature != capsule.VerdictNoSignature && v.Signature != capsule.VerdictSignatureUnchecked,
v.Seal != capsule.VerdictUnreadable && v.Seal != capsule.VerdictNoSeal && v.Seal != capsule.VerdictSealUnsupported && v.Seal != capsule.VerdictSealUnreadable,
(v.Signature == capsule.VerdictUnreadable) != (v.Seal == capsule.VerdictUnreadable),
len(v.Lines()) == 0:
t.Fatalf("verdicts %+v", v)
}
})
}
// FuzzInspect feeds whole capsules to the pre-unlock validation, and to Open
// with a source that never has the release: a capsule that Inspect rejects
// must not cause a request, and nothing may pass the release step. The

@ -0,0 +1,39 @@
package pathrule_test
import (
"errors"
"testing"
"unicode/utf8"
"g.activething.com/go/DateKeys/internal/pathrule"
"g.activething.com/go/DateKeys/internal/testkit"
)
// FuzzCheckPath: the rules of one entry and the decoder of the head agree on
// every path: a head that holds it alone decodes exactly when the path is
// valid UTF-8 of 1 to 1024 bytes (R1, layer 3) that CheckPath accepts, since
// one path collides with none and makes at most 31 folders (spec §29.5).
func FuzzCheckPath(f *testing.F) {
var paths testkit.PathVectorFile
if err := testkit.ReadJSON("../../testdata/vectors/paths.json", &paths); err != nil {
f.Fatal(err)
}
for _, v := range paths.Paths {
f.Add(v.Path)
}
f.Fuzz(func(t *testing.T, p string) {
err := pathrule.CheckPath(p)
var e *pathrule.Error
if err != nil && !errors.As(err, &e) {
t.Fatalf("an error that is not a pathrule.Error: %v", err)
}
valid := err == nil && utf8.ValidString(p) && len(p) >= 1 && len(p) <= pathrule.MaxPathLen
head, merr := testkit.TreeHead([]string{p})
if merr != nil {
return
}
if got, _ := testkit.HeadResult(head); (got == testkit.ResultOK) != valid {
t.Fatalf("path %+q: CheckPath %v, the head decodes to %s", p, err, got)
}
})
}

@ -25,6 +25,9 @@ targets=(
"./capsule FuzzParsePrelude"
"./capsule FuzzDecodeHeader"
"./capsule FuzzDecodeControl"
"./capsule FuzzDecodeHead"
"./capsule FuzzEvaluateSecurity"
"./internal/pathrule FuzzCheckPath"
"./capsule FuzzInspect"
"./capsule FuzzEncodeImpliesDecode"
)

@ -2883,7 +2883,7 @@ Los de formato 3 cubren, como mínimo:
- una firma de `alg` 1 con una clave de 32 bytes y una firma de 64, aleatorias, con el veredicto F1;
- esa firma y un sello de `seal_type` 1 con un token aleatorio, con F1 y S1.
Serán `format3_single.dkc`, `format3_tree.dkc`, `format3_comment_only.dkc`, `format3_bloque256.dkc`, `format3_time_and_key_portable.dkc` con su `.dkk`, `format3_area_1024.dkc`, `format3_security_v2.dkc`, `format3_signature_unsupported.dkc` y `format3_seal_unsupported.dkc` (por implementar). Los vectores de rutas y del head van en `testdata/vectors/paths.json`, `head_schema.json` y `path_fold.json`, y los de `security`, en `security.json`.
Son `format3_single.dkc`, `format3_tree.dkc`, `format3_comment_only.dkc`, `format3_bloque256.dkc`, `format3_time_and_key_portable.dkc` con su `.dkk`, `format3_area_1024.dkc`, `format3_security_v2.dkc`, `format3_signature_unsupported.dkc` y `format3_seal_unsupported.dkc`. Los vectores de rutas y del head van en `testdata/vectors/paths.json`, `head_schema.json` y `path_fold.json`, y los de `security`, en `security.json`.
Los vectores de rutas y del head cubren, como mínimo: U+00A0 y U+3000 al principio y al final de un segmento, con el veredicto que dan las tablas, que para U+3000 es el rechazo por R6c; best-fit; alias 8.3, «~1» incluido; Cn; `["b/..", "a"]`; U+206A a U+206F, las etiquetas y otros ignorables fuera de la lista blanca; «.» seguido de ZWJ y un segmento hecho solo de ZWJ; 127 veces «ΐ»; U+F03A; «.datekeys-x» en el primer nivel y en otro; el orden de U+FF5E y U+1F600; «ab» con y sin ZWNJ; «¿», «§» y «♥», que se aceptan; VS16 tras un emoji que lo admite, que se acepta, y tras «a», que no; ZWJ al principio, al final y dos seguidos; y la bandera arcoíris, que se acepta, y la de Escocia, que no. Los de `head_schema.json` cubren además un comentario con etiquetas y con selectores de variante sueltos. Los de `security.json` cubren: el mapa exterior con la clave 2 que no es una cadena de bytes, con una clave 4 o con un byte de más dentro de `SECURITY_LEN` (X); `alg` 0 o una clave vacía dentro de la clave 2 (F1, con el sello intacto); y un `seal` que incumple su schema con un `seal_type` desconocido (S2).

@ -14,16 +14,19 @@
reproducible cases in the specification's §76.
- `DateKeys_Protocol_Specification_v0.9.md`: frozen copy of the normative
draft v0.9 (29 September 2026), approved by its author on that date and
tagged `spec-v0.9`; this module implements it. SHA-256:
tagged `spec-v0.9`. SHA-256:
`36189e1e62f0f835b7665219aa63df7200cd89ac4e4e924f2705f970fa7c40a9`.
It adds capsule format 2, which hides until the unlock date the exact
length of the content (the payload is padded) and the number of
credentials (always 16 X25519 stanzas), and it makes the writer rules
normative. A v0.9 reader still opens format 1, the format of v0.8.2. Its
§76 records each change with its reproducible case.
- `DateKeys_Protocol_Specification_v0.10.md`: working draft v0.10 (30
September 2026), not yet approved by its author and not implemented. It
adds capsule format 3, which stores several files with their paths, sizes,
- `DateKeys_Protocol_Specification_v0.10.md`: normative draft v0.10 (30
September 2026), approved by its author on that date; this module
implements it on the branch `v0.10`, and it is tagged `spec-v0.10` once its
author authorizes the tag. SHA-256:
`7f26419a444aa3e89a3aa8afbbba9d952af69e048aee1e93cd70732c2d1d99d1`.
It adds capsule format 3, which stores several files with their paths, sizes,
hashes and dates, encrypted, and reserves the `security` area for an author
signature and a timestamp seal that later versions will define without
changing the format. Its §76 records each change with its reproducible case.

Loading…
Cancel
Save

Powered by TurnKey Linux.