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/seal_interop_go_verdicts.go

216 lines
6.7 KiB

//go:build ignore
// Opens with Go what datekeys-dart writes in stage 7b of docs/PLAN_dart.md,
// and writes test/vectors/seal_interop.json and seal_interop.g.dart: each
// recipe of tool/seal_interop_dart_samples.dart with the length and the
// SHA-256 of each of its files and the verdict of Go:
//
// - locator: locator.Open of the sealed locator with Quicknet, its round
// and the published release of the round, from the fixtures; Marshal
// of the locator that Open gives, which must be the plaintext that
// Dart sealed; and OpenEnvelope of the rest, which must give the .dkc;
// - key: authorkey.Read of the key file with its passphrase, which must
// give the secret key that Dart wrote, and ParsePublic of its public
// key;
// - signature: authorkey.ParseSecret of the key, ed25519.Sign of the
// message with it, which must be the signature that Dart wrote, and
// ed25519.Verify.
//
// A verdict is "ok" or the text of the first thing that failed.
//
// It imports only public packages, so it runs in the module of the
// reference implementation, without changing anything there, after the
// Dart tool wrote the samples:
//
// tmp=$(mktemp -d)
// dart run tool/seal_interop_dart_samples.dart "$tmp"
// (cd ../datekeys-go && go run ../datekeys-dart/tool/seal_interop_go_verdicts.go \
// -source $(git rev-parse v0.12) -testdata ../datekeys-dart/testdata \
// -samples "$tmp" -out ../datekeys-dart/test/vectors)
// rm -rf "$tmp"
package main
import (
"bytes"
"crypto/ed25519"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"flag"
"fmt"
"log"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"g.activething.com/go/DateKeys/authorkey"
"g.activething.com/go/DateKeys/locator"
"g.activething.com/go/DateKeys/profile"
"g.activething.com/go/DateKeys/provider"
)
type obj = map[string]any
func h(b []byte) string { return hex.EncodeToString(b) }
func check(err error) {
if err != nil {
_, file, line, _ := runtime.Caller(1)
log.Fatalf("%s:%d: %v", filepath.Base(file), line, err)
}
}
func pattern(n int) []byte {
b := make([]byte, n)
for i := range b {
b[i] = byte(31*i + 7)
}
return b
}
func releases(testdata string) map[uint64]provider.Release {
out := map[uint64]provider.Release{}
files, err := filepath.Glob(filepath.Join(testdata, "fixtures", "*.json"))
check(err)
sort.Strings(files)
for _, f := range files {
raw, err := os.ReadFile(f)
check(err)
var v struct {
Release *struct {
Round uint64 `json:"round"`
Signature string `json:"signature"`
} `json:"release"`
}
if err := json.Unmarshal(raw, &v); err != nil || v.Release == nil {
continue
}
sig, err := hex.DecodeString(v.Release.Signature)
check(err)
out[v.Release.Round] = provider.Release{Round: v.Release.Round, Signature: sig}
}
return out
}
func verdict(r obj, files map[string][]byte, rel map[uint64]provider.Release) error {
switch r["kind"] {
case "locator":
round := uint64(r["round"].(float64))
release, ok := rel[round]
if !ok {
return fmt.Errorf("no release of round %d", round)
}
loc, err := locator.Open(profile.Quicknet(), round, release, files["sealed"])
if err != nil {
return err
}
pt, err := loc.Marshal()
if err != nil {
return err
}
if !bytes.Equal(pt, files["plaintext"]) {
return errors.New("another plaintext")
}
dkc, err := loc.OpenEnvelope(files["rest"])
if err != nil {
return err
}
if !bytes.Equal(dkc, files["dkc"]) || !bytes.Equal(dkc, pattern(int(r["dkc_length"].(float64)))) {
return errors.New("another .dkc")
}
return nil
case "key":
k, err := authorkey.Read(bytes.NewReader(files["file"]), r["passphrase"].(string))
if err != nil {
return err
}
if k.Secret() != string(files["secret"]) {
return errors.New("another secret key")
}
pub, err := authorkey.ParsePublic(string(files["public"]))
if err != nil {
return err
}
if !bytes.Equal(pub, k.Public()) {
return errors.New("another public key")
}
return nil
default:
k, err := authorkey.ParseSecret(string(files["seed"]))
if err != nil {
return err
}
msg := pattern(int(r["message_length"].(float64)))
if !bytes.Equal(k.Public(), files["public_key"]) {
return errors.New("another public key")
}
if !bytes.Equal(k.Sign(msg), files["signature"]) {
return errors.New("another signature")
}
if !ed25519.Verify(files["public_key"], msg, files["signature"]) {
return errors.New("the signature does not verify")
}
return nil
}
}
func main() {
samples := flag.String("samples", "", "the directory of the samples")
out := flag.String("out", "", "where the vectors go")
src := flag.String("source", "", "the commit of datekeys-go")
testdata := flag.String("testdata", "", "the testdata of this repository")
flag.Parse()
if *samples == "" || *out == "" || *src == "" || *testdata == "" {
log.Fatal("usage: -source <commit> -testdata <dir> -samples <dir> -out <dir>")
}
rel := releases(*testdata)
raw, err := os.ReadFile(filepath.Join(*samples, "samples.json"))
check(err)
var doc struct {
Samples []obj `json:"samples"`
}
check(json.Unmarshal(raw, &doc))
result := []obj{}
for i, r := range doc.Samples {
names, err := filepath.Glob(filepath.Join(*samples, fmt.Sprintf("%d.*", i)))
check(err)
files := map[string][]byte{}
digests := obj{}
for _, n := range names {
b, err := os.ReadFile(n)
check(err)
suffix := strings.TrimPrefix(filepath.Ext(n), ".")
files[suffix] = b
s := sha256.Sum256(b)
digests[suffix] = h(s[:])
}
v := "ok"
if err := verdict(r, files, rel); err != nil {
v = err.Error()
log.Printf("%s: %s", r["name"], v)
}
r["files"] = digests
r["verdict"] = v
result = append(result, r)
}
var buf bytes.Buffer
e := json.NewEncoder(&buf)
e.SetEscapeHTML(false)
e.SetIndent("", " ")
check(e.Encode(obj{"source": *src, "go": runtime.Version(), "description": "What Go makes of the files that datekeys-dart writes in stage 7b with SeededRandomSource, from the recipes of test/seal_interop_support.dart, by tool/seal_interop_go_verdicts.go: the SHA-256 of each file and the verdict of Go, ok or the text of what failed.", "samples": result}))
path := filepath.Join(*out, "seal_interop.json")
check(os.WriteFile(path, buf.Bytes(), 0o644))
fmt.Printf("wrote %s, %d bytes\n", path, buf.Len())
dart := "// Generated by tool/seal_interop_go_verdicts.go from seal_interop.json, for\n" +
"// the tests that also run compiled to JavaScript, where no file can be\n" +
"// read. Do not edit.\n\n" +
"/// The text of test/vectors/seal_interop.json.\n" +
"const sealInteropJson = r'''\n" + buf.String() + "''';\n"
dpath := filepath.Join(*out, "seal_interop.g.dart")
check(os.WriteFile(dpath, []byte(dart), 0o644))
fmt.Printf("wrote %s, %d bytes\n", dpath, len(dart))
}

Powered by TurnKey Linux.