Stage 4c: the vectors of Go for the head, the note and the opening
tool/mutation_go_texts.go ports scripts/mutation-go-texts.go of
datekeys-ts over the synced testdata of this repository, the tag
spec-v0.11: it replays every case of the mutation corpus as the testkit of
the reference does and writes the text of capsule.Open and its checks,
with the detail of each step, in mutation_texts.json. Where Go and the
corpus disagree on a code or a step it would say so in the case; Go at
c531e93 and at the tag spec-v0.11 agree with all 210 cases, and give the
same file, byte for byte.
tool/open_go_vectors.go runs in an export of datekeys-go, since it uses
internal/testkit, internal/cbortest and internal/inspectview, and writes:
- open_cases.json: capsule.Open on every fixture with each of its
credentials, and 117 openings of edited fixtures or with other options
at each step that the corpus does not reach: the frame, the fields, the
extensions and the bindings of a .dkk at step 9.a, the clock and the
failures of the release source, the age headers of steps 11 and 17, a
malformed X25519 stanza in INNER_ACCESS_AGE, CONTROL_CBOR and the BODY
of format 3 sealed again, the sinks and the output that fail, the
refusal of Accept and the unusable extensions of each object, with the
text, the step, the checks, the release requests, the state of the
sink and the content or the files;
- open_heads.json: capsule.DecodeHead and EncodeHead of heads of a fixed
seed, valid and broken in each layer of spec §69.1;
- open_notes.json: extension.CheckNote, Note, Header.UnusableNote and
extension.Standard with a note;
- open_inspect.json: the text of capsule.Inspect for each of the 5110
mutations of inspect_differential.json, where Go and the file also
agree, and the exact output of datekeys inspect -json with public notes;
- open_vectors.g.dart: seven small fixtures, their records and a part of
each file, for the tests that run compiled to JavaScript.
The edited capsules are sealed again with the file keys and the nonces of
the fixtures, as the testkit does, so the output is the same on every run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
//go:build ignore
// Writes test/vectors/mutation_texts.json: the text of the error that
// capsule.Open of the Go reference gives for every case of
// testdata/vectors/mutations.json, or "ok" for a capsule that opens, and the
// checks that it records, step by step with their details, so that the
// tests of datekeys-dart require the same text of the opening, byte for
// byte, and not only the same code and step. A port of
// scripts/mutation-go-texts.go of datekeys-ts, over the synced testdata of
Test data of the branch v0.12 of datekeys-go at c531e93
testdata/ is synced with datekeys-go at c531e93, the head of the branch
v0.12, whose testdata is that of 601e6d2, the copy of datekeys-ts. Every
file keeps "spec": "0.11": the draft v0.12 is not approved yet. From the
tag spec-v0.11 it brings the fixtures format3_unsigned and format3_note,
format3_seal_unsupported with seal_type 4294967295, the records of
format3_sealed and format3_signed_cms, the mutation corpus of 218 cases,
note.json, security.json with a context and lines, security_cms.json of
135 cases and locator.json.
The vectors that the generators of tool/ make from the testdata are
written again by Go at c531e93: mutation_texts.json, open_cases.json,
formats_*.json with formats_vectors.g.dart, ibe_vectors.json and
age_fixtures.json; release_vectors.json, primitives.json and the views
of open_vectors.g.dart come out the same. age.json does not read the
testdata, and stays frozen: age draws its keys from crypto/rand. The
tests count 26 fixtures and 218 cases, and the inspection of
format3_note gives the note of its record. No difference with Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
// this repository, that of the branch v0.12 of datekeys-go at c531e93.
Stage 4c: the vectors of Go for the head, the note and the opening
tool/mutation_go_texts.go ports scripts/mutation-go-texts.go of
datekeys-ts over the synced testdata of this repository, the tag
spec-v0.11: it replays every case of the mutation corpus as the testkit of
the reference does and writes the text of capsule.Open and its checks,
with the detail of each step, in mutation_texts.json. Where Go and the
corpus disagree on a code or a step it would say so in the case; Go at
c531e93 and at the tag spec-v0.11 agree with all 210 cases, and give the
same file, byte for byte.
tool/open_go_vectors.go runs in an export of datekeys-go, since it uses
internal/testkit, internal/cbortest and internal/inspectview, and writes:
- open_cases.json: capsule.Open on every fixture with each of its
credentials, and 117 openings of edited fixtures or with other options
at each step that the corpus does not reach: the frame, the fields, the
extensions and the bindings of a .dkk at step 9.a, the clock and the
failures of the release source, the age headers of steps 11 and 17, a
malformed X25519 stanza in INNER_ACCESS_AGE, CONTROL_CBOR and the BODY
of format 3 sealed again, the sinks and the output that fail, the
refusal of Accept and the unusable extensions of each object, with the
text, the step, the checks, the release requests, the state of the
sink and the content or the files;
- open_heads.json: capsule.DecodeHead and EncodeHead of heads of a fixed
seed, valid and broken in each layer of spec §69.1;
- open_notes.json: extension.CheckNote, Note, Header.UnusableNote and
extension.Standard with a note;
- open_inspect.json: the text of capsule.Inspect for each of the 5110
mutations of inspect_differential.json, where Go and the file also
agree, and the exact output of datekeys inspect -json with public notes;
- open_vectors.g.dart: seven small fixtures, their records and a part of
each file, for the tests that run compiled to JavaScript.
The edited capsules are sealed again with the file keys and the nonces of
the fixtures, as the testkit does, so the output is the same on every run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
//
// Each case is replayed as internal/testkit replays the corpus
// (MutationInput.Open), which a program outside the reference cannot
// import: the Quicknet profile pinned, or no profile for the registry
// "empty"; a release source that answers every request with the release of
// the case, or with "testkit: no release" and ERR_RELEASE_UNAVAILABLE; the
// .dkk decoded before, as OpenOptions.AccessKey; the identities of the case;
// the extensions of the case, known at (id, version) and whose data is valid
// only when it is valid_data, with the texts of testkit.KnownExtensions; a
// sink that discards the files; and the clock of the case.
//
// The code and the step of every case are checked against the corpus: a
// case where Go and the corpus disagree is written with go_error and
// go_step, and reported on the standard error, never resolved silently.
//
// 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:
//
// cd ../datekeys-go && go run ../datekeys-dart/tool/mutation_go_texts.go \
// ../datekeys-dart/testdata > ../datekeys-dart/test/vectors/mutation_texts.json
package main
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
"filippo.io/age"
datekeys "g.activething.com/go/DateKeys"
"g.activething.com/go/DateKeys/accesskey"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/extension"
"g.activething.com/go/DateKeys/profile"
"g.activething.com/go/DateKeys/provider"
)
type corpus struct {
Spec string ` json:"spec" `
Cases [ ] struct {
Name string ` json:"name" `
DKC struct {
Base string ` json:"base" `
Edits [ ] [ ] json . RawMessage ` json:"edits" `
} ` json:"dkc" `
DKK string ` json:"dkk" `
Identities [ ] string ` json:"identities" `
Release * struct {
Round uint64 ` json:"round" `
Signature string ` json:"signature" `
} ` json:"release" `
Now string ` json:"now" `
Registry string ` json:"registry" `
Extensions [ ] struct {
ID string ` json:"id" `
Version uint64 ` json:"version" `
ValidData string ` json:"valid_data" `
} ` json:"extensions" `
Error string ` json:"error" `
Step int ` json:"step" `
} ` json:"cases" `
}
// caseOut is one case: its name, the text of the error or "ok", the checks
// as [step, name, ok, detail, code], and, when Go disagrees with the corpus,
// what Go gives.
type caseOut struct {
Name string ` json:"name" `
Text string ` json:"text" `
Checks [ ] [ ] any ` json:"checks" `
GoError string ` json:"go_error,omitempty" `
GoStep int ` json:"go_step,omitempty" `
}
type output struct {
Spec string ` json:"spec" `
Generator string ` json:"generator" `
Description string ` json:"description" `
Cases [ ] caseOut ` json:"cases" `
}
// known is testkit.KnownExtensions: an extension.Registry and
// extension.DataValidator with the same texts.
type known [ ] struct {
id string
version uint64
valid [ ] byte
}
func ( k known ) find ( id string , version uint64 ) int {
for i := range k {
if k [ i ] . id == id && k [ i ] . version == version {
return i
}
}
return - 1
}
func ( k known ) Known ( id string , version uint64 ) bool { return k . find ( id , version ) >= 0 }
func ( k known ) ValidateData ( e extension . Extension ) error {
i := k . find ( e . ID , e . Version )
if i < 0 {
return fmt . Errorf ( "extension %s version %d is not known" , e . ID , e . Version )
}
if ! bytes . Equal ( k [ i ] . valid , e . Data ) {
return fmt . Errorf ( "data %x is not %x" , e . Data , k [ i ] . valid )
}
return nil
}
// source is testkit's singleSource.
type source struct { release * provider . Release }
func ( s source ) Fetch ( context . Context , * profile . Profile , provider . Condition ) ( provider . Release , error ) {
if s . release == nil {
return provider . Release { } , fmt . Errorf ( "testkit: no release: %w" , datekeys . ErrReleaseUnavailable )
}
return * s . release , nil
}
// discard is testkit's DiscardSink.
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 }
func must [ T any ] ( v T , err error ) T {
if err != nil {
panic ( err )
}
return v
}
// applyEdits is the reading of testdata/README.md, "Edited files":
// [at, delete, insert] on the offsets of the unmodified base, sorted and not
// overlapping.
func applyEdits ( base [ ] byte , edits [ ] [ ] json . RawMessage ) [ ] byte {
var out [ ] byte
pos := 0
for _ , e := range edits {
var at , del int
var insert string
if len ( e ) != 3 || json . Unmarshal ( e [ 0 ] , & at ) != nil || json . Unmarshal ( e [ 1 ] , & del ) != nil || json . Unmarshal ( e [ 2 ] , & insert ) != nil {
panic ( "an edit is [at, delete, insert]" )
}
if at < pos || at + del > len ( base ) {
panic ( "edits out of order or beyond the base" )
}
out = append ( append ( out , base [ pos : at ] ... ) , must ( hex . DecodeString ( insert ) ) ... )
pos = at + del
}
return append ( out , base [ pos : ] ... )
}
func main ( ) {
dir := os . Args [ 1 ]
var c corpus
if err := json . Unmarshal ( must ( os . ReadFile ( filepath . Join ( dir , "vectors" , "mutations.json" ) ) ) , & c ) ; err != nil {
panic ( err )
}
out := output {
Spec : c . Spec ,
Generator : "tool/mutation_go_texts.go" ,
Description : "The text of the error of capsule.Open for every case of testdata/vectors/mutations.json, or ok for a capsule that opens, and its checks as [step, name, ok, detail, code]; go_error and go_step where Go and the corpus disagree. See the header of tool/mutation_go_texts.go." ,
}
disagree := 0
for _ , m := range c . Cases {
var base [ ] byte
if m . DKC . Base != "" {
base = must ( os . ReadFile ( filepath . Join ( dir , "fixtures" , m . DKC . Base ) ) )
}
dkc := applyEdits ( base , m . DKC . Edits )
reg := must ( profile . Default ( ) )
if m . Registry == "empty" {
reg = must ( profile . NewRegistry ( ) )
}
var src source
if m . Release != nil {
src . release = & provider . Release { Round : m . Release . Round , Signature : must ( hex . DecodeString ( m . Release . Signature ) ) }
}
now := must ( time . Parse ( time . RFC3339Nano , m . Now ) )
o := capsule . OpenOptions { Registry : reg , Source : src , Now : func ( ) time . Time { return now } , Sink : discard { } }
if m . Extensions != nil {
var k known
for _ , x := range m . Extensions {
k = append ( k , struct {
id string
version uint64
valid [ ] byte
} { x . ID , x . Version , must ( hex . DecodeString ( x . ValidData ) ) } )
}
o . Extensions = k
}
if m . DKK != "" {
o . AccessKey = must ( accesskey . Decode ( bytes . NewReader ( must ( hex . DecodeString ( m . DKK ) ) ) ) )
}
for _ , s := range m . Identities {
o . Identities = append ( o . Identities , must ( age . ParseX25519Identity ( s ) ) )
}
opened , err := capsule . Open ( context . Background ( ) , io . Discard , bytes . NewReader ( dkc ) , o )
text , code , step := "ok" , "ok" , 0
if err != nil {
text , code = err . Error ( ) , datekeys . Code ( err )
}
co := caseOut { Name : m . Name , Text : text , Checks : [ ] [ ] any { } }
for _ , ch := range opened . Inspection . Checks {
co . Checks = append ( co . Checks , [ ] any { ch . Step , ch . Name , ch . OK , ch . Detail , ch . Error } )
}
if err != nil {
step = opened . Inspection . Checks [ len ( opened . Inspection . Checks ) - 1 ] . Step
}
if code != m . Error || step != m . Step {
disagree ++
co . GoError , co . GoStep = code , step
fmt . Fprintf ( os . Stderr , "%s: Go gives %s at step %d, the corpus says %s at step %d\n" , m . Name , code , step , m . Error , m . Step )
}
out . Cases = append ( out . Cases , co )
}
fmt . Fprintf ( os . Stderr , "%d cases, %d where Go and the corpus disagree\n" , len ( out . Cases ) , disagree )
var b bytes . Buffer
e := json . NewEncoder ( & b )
e . SetEscapeHTML ( false )
if err := e . Encode ( out . Cases ) ; err != nil {
panic ( err )
}
// One case per line.
var sb strings . Builder
head := func ( v any ) string {
var h bytes . Buffer
enc := json . NewEncoder ( & h )
enc . SetEscapeHTML ( false )
if err := enc . Encode ( v ) ; err != nil {
panic ( err )
}
return strings . TrimSuffix ( h . String ( ) , "\n" )
}
sb . WriteString ( "{\n" )
sb . WriteString ( ` "spec": ` + head ( out . Spec ) + ",\n" )
sb . WriteString ( ` "generator": ` + head ( out . Generator ) + ",\n" )
sb . WriteString ( ` "description": ` + head ( out . Description ) + ",\n" )
sb . WriteString ( ` "cases": [ ` )
for i , co := range out . Cases {
if i > 0 {
sb . WriteString ( "," )
}
sb . WriteString ( "\n " + head ( co ) )
}
sb . WriteString ( "\n ]\n}\n" )
os . Stdout . WriteString ( sb . String ( ) )
}