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.
216 lines
6.7 KiB
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))
|
|
}
|