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

2529 lines
87 KiB

Stage 7a: the vectors of Go for the locator tool/locator_go_vectors.go runs package locator of datekeys-go at c531e93, the draft v0.12, in its module, without changing it, and writes what Go gives, with the texts of its errors: - locator_uris.json: CheckURI and Address.Host on the 247 addresses of testdata/vectors/locator.json and on 2 800 more, built at every edge of spec 44.1 and drawn from a seed: IPv4 and IPv6 in every notation that netip.ParseAddr accepts or rejects, zones, mapped, NAT64 and 6to4 addresses, the first and last address of every IANA block and their neighbours, long and punycode labels, local names, ports, percent signs, dot segments and CIDs; netip.ParseAddr and String on 1 700 strings; and publicIP, reached with go:linkname, on 868 byte strings. - locator_vectors.json: the texts of the other cases of locator.json; PlaintextLength from -4100 to 16484; Unmarshal on 482 plaintexts; Marshal at every limit and boundary of the padding; Open on 118 sealed locators of four rounds, edited or with other releases and profiles; Open past 1 MiB of plaintext, read through io.LimitReader; the envelope, its rest, Hide and the split of NewEnvelope; Info.Extension, Info.OpenLocator and ParseInfo; locator.Standard as a registry; and capsule.Open of a fixture whose .dkk carries datekeys.capsule. crypto/rand.Reader is a ChaCha8 of a fixed seed, so every run writes the same bytes. The Dart constants hold the whole of the first file and a part of the second, for the tests compiled to JavaScript. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
//go:build ignore
// Writes the vectors of the locator of datekeys-dart, stage 7a of
// docs/PLAN_dart.md: the results, the normative codes and the texts of
// package locator of datekeys-go at the draft v0.12 (spec §43 to §44.1), and
// of the parser of IP addresses of Go that it calls, on
//
// - locator_uris.json: CheckURI and Address.Host on the 247 addresses of
// testdata/vectors/locator.json, and on addresses built at every edge of
// §44.1 and drawn from a fixed seed: IPv4 and IPv6 in every notation
// that netip.ParseAddr accepts or rejects, with zones, mapped,
// compatible, of NAT64, 6to4 and Teredo, the first and the last address
// of every block of IANA and their neighbours, names with long and
// punycode labels, local names in any case, ports, percent-encodings,
// dot segments and CIDs, canonical or not; CheckURI on bytes with the
// three bytes of a surrogate, those of a String of Dart with a lone
// surrogate; netip.ParseAddr itself, with the address, its zone and its
// String; and locator.publicIP on the bytes of an address, as a reader
// checks the IP that a name resolves to on every connection. publicIP
// is not exported: this program reaches it with go:linkname, which Go
// allows for a package outside the standard library.
// - locator_vectors.json: the texts of the other cases of
// testdata/vectors/locator.json; PlaintextLength on every base from
// -4100 to 16484, in runs; Unmarshal on plaintexts of three bases, valid
// and broken byte by byte and field by field; Marshal on locators at
// every limit and at every boundary of the padding; Open on sealed
// locators of four rounds, with the releases of the others, broken
// profiles and edited files; Open on files whose plaintext passes 1 MiB,
// which Go reads through io.LimitReader, written here with a file key
// that this program knows; OpenEnvelope, RestIn and Hide on envelopes
// and rests, valid and edited; the split of an age file into the header
// and the rest of an envelope, as NewEnvelope does it (headerEnd,
// reached with go:linkname too); Info.Extension, Info.OpenLocator with
// the default registry, and ParseInfo; the registry of locator.Standard
// through CheckNoncriticalIn, CheckCriticalIn and CheckWrite; and
// capsule.Open of a fixture with its .dkk carrying datekeys.capsule,
// with locator.Standard.
//
// It also writes the Dart constants of locator_uris.g.dart, the whole of
// locator_uris.json, and of locator_vectors.g.dart, the part of
// locator_vectors.json that the tests compiled to JavaScript need, where no
// file can be read. A test on the VM checks that they are those of the
// files.
//
// Every expected value is what Go gives; none is written by hand. The
// randomness of age, tlock and NewEnvelope comes from crypto/rand.Reader,
// which this program replaces with a ChaCha8 of a fixed seed, and every
// other choice from another: every run writes the same bytes with Go 1.26.8.
// Binary values are lower-case hexadecimal, and a text is an index into
// texts, whose first entry, "", stands for no error. An edited value is a
// list of edits of a base, as in "Edited files" of testdata/README.md, with
// one more form: [at, 0, byte, count] inserts count copies of the byte. An
// address with a run of 32 or more equal bytes is written [before, byte,
// count, after]. The JSON is ASCII.
//
// 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/locator_go_vectors.go \
// -testdata ../datekeys-dart/testdata -out ../datekeys-dart/test/vectors
package main
import (
"bytes"
"context"
"crypto/hkdf"
cryptorand "crypto/rand"
"crypto/sha256"
"encoding/base32"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"log"
"math/rand/v2"
"net/netip"
"os"
"path/filepath"
"runtime"
"slices"
"strconv"
"strings"
"time"
"unicode/utf16"
"unicode/utf8"
_ "unsafe"
"filippo.io/age"
"golang.org/x/crypto/chacha20poly1305"
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/datekey"
"g.activething.com/go/DateKeys/extension"
"g.activething.com/go/DateKeys/locator"
"g.activething.com/go/DateKeys/profile"
"g.activething.com/go/DateKeys/provider"
)
//go:linkname publicIP g.activething.com/go/DateKeys/locator.publicIP
func publicIP(a netip.Addr) bool
//go:linkname headerEnd g.activething.com/go/DateKeys/locator.headerEnd
func headerEnd(file []byte) (int, error)
// locatorCID is the CID of locator.json: dag-pb, SHA-256.
const locatorCID = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"
// seed derives the seed of one part of the output, so that a change in one
// part leaves the others as they were.
func seed(label string) [32]byte {
return sha256.Sum256([]byte("datekeys-dart stage 7a: " + label))
}
// newRand returns the generator of the choices of one part.
func newRand(label string) *rand.Rand { return rand.New(rand.NewChaCha8(seed(label))) }
// setCryptoRand makes crypto/rand.Reader, which age, tlock and NewEnvelope
// read, a ChaCha8 of a fixed seed: crypto/rand.Read reads the Reader that
// replaces the default one.
func setCryptoRand(label string) { cryptorand.Reader = rand.NewChaCha8(seed("crypto/rand " + label)) }
// recorder keeps every byte read through it.
type recorder struct {
r io.Reader
got []byte
}
func (r *recorder) Read(p []byte) (int, error) {
n, err := r.r.Read(p)
r.got = append(r.got, p[:n]...)
return n, err
}
func hx(b []byte) string { return hex.EncodeToString(b) }
func sum(b []byte) string {
s := sha256.Sum256(b)
return hx(s[:])
}
func must[T any](v T, err error) T {
if err != nil {
log.Fatal(err)
}
return v
}
// patterned returns n bytes that depend only on label.
func patterned(label string, n int) []byte {
out := make([]byte, 0, n+32)
for i := uint32(0); len(out) < n; i++ {
var c [4]byte
binary.BigEndian.PutUint32(c[:], i)
h := sha256.Sum256(append([]byte(label), c[:]...))
out = append(out, h[:]...)
}
return out[:n]
}
// edits writes b as edits of base: the bytes between their common prefix
// and their common suffix replace those of base, in pieces, each run of 32
// or more equal bytes as an edit [at, 0, byte, count] that inserts count
// copies of the byte.
func edits(base, b []byte) [][]any {
if bytes.Equal(base, b) {
return [][]any{}
}
p := 0
for p < len(base) && p < len(b) && base[p] == b[p] {
p++
}
s := 0
for s < len(base)-p && s < len(b)-p && base[len(base)-1-s] == b[len(b)-1-s] {
s++
}
insert := b[p : len(b)-s]
out := [][]any{}
del := len(base) - p - s
at := p
flush := func(piece []byte, repeat int) {
e := []any{at, del, hx(piece)}
if repeat > 0 {
e = append(e, repeat)
}
out = append(out, e)
at += del
del = 0
}
start := 0
for i := 0; i < len(insert); {
j := i
for j < len(insert) && insert[j] == insert[i] {
j++
}
if j-i >= 32 {
if start < i {
flush(insert[start:i], 0)
}
flush(insert[i:i+1], j-i)
start = j
}
i = j
}
if start < len(insert) || len(out) == 0 {
flush(insert[start:], 0)
}
return out
}
// compactURI writes an address with a run of 32 or more equal bytes as
// [before, byte, count, after].
func compactURI(u string) any {
for i := 0; i < len(u); {
j := i
for j < len(u) && u[j] == u[i] {
j++
}
if j-i >= 32 {
return []any{u[:i], u[i : i+1], j - i, u[j:]}
}
i = j
}
return u
}
// ---------------------------------------------------------------------------
// The texts
// textTable numbers the texts of the errors: 0 is no error.
type textTable struct {
list []string
index map[string]int
}
func newTexts() *textTable { return &textTable{list: []string{""}, index: map[string]int{"": 0}} }
func (t *textTable) of(err error) int {
s := ""
if err != nil {
s = err.Error()
if s == "" {
log.Fatal("an error without text")
}
}
if i, ok := t.index[s]; ok {
return i
}
t.index[s] = len(t.list)
t.list = append(t.list, s)
return len(t.list) - 1
}
// ---------------------------------------------------------------------------
// The JSON
// asciiJSON is the JSON of v in one line, ASCII: every other character and
// the apostrophe, which three times in a row would end the raw string of the
// Dart constant, escaped.
func asciiJSON(v any) string {
var b bytes.Buffer
enc := json.NewEncoder(&b)
enc.SetEscapeHTML(false)
if err := enc.Encode(v); err != nil {
log.Fatal(err)
}
s := strings.TrimSuffix(b.String(), "\n")
var out strings.Builder
for _, r := range s {
switch {
case r == '\'':
fmt.Fprintf(&out, "\\u%04x", r)
case r < 0x80:
out.WriteRune(r)
case r < 0x10000:
fmt.Fprintf(&out, "\\u%04x", r)
default:
r1, r2 := utf16.EncodeRune(r)
fmt.Fprintf(&out, "\\u%04x\\u%04x", r1, r2)
}
}
return out.String()
}
// document writes a JSON object whose members are in the order given, each
// list one item per line.
type document struct {
b bytes.Buffer
first bool
}
func newDocument() *document {
d := &document{first: true}
d.b.WriteString("{\n")
return d
}
func (d *document) sep() {
if !d.first {
d.b.WriteString(",\n")
}
d.first = false
}
func (d *document) value(key string, v any) {
d.sep()
fmt.Fprintf(&d.b, " %s: %s", asciiJSON(key), asciiJSON(v))
}
func (d *document) list(key string, items []any) {
d.sep()
fmt.Fprintf(&d.b, " %s: [", asciiJSON(key))
for i, it := range items {
if i > 0 {
d.b.WriteByte(',')
}
d.b.WriteString("\n ")
d.b.WriteString(asciiJSON(it))
}
d.b.WriteString("\n ]")
}
func (d *document) bytes() []byte {
out := slices.Clone(d.b.Bytes())
out = append(out, "\n}\n"...)
var parsed any
if err := json.Unmarshal(out, &parsed); err != nil {
log.Fatalf("the JSON does not parse: %v", err)
}
for _, c := range out {
if c >= 0x80 {
log.Fatal("the JSON is not ASCII")
}
}
if bytes.Contains(out, []byte("'''")) {
log.Fatal("the JSON holds three apostrophes")
}
return out
}
func anyList[T any](xs []T) []any {
out := make([]any, len(xs))
for i, x := range xs {
out[i] = x
}
return out
}
// ---------------------------------------------------------------------------
// Addresses
// uriCase is CheckURI and Host of an address: [uri, text, host].
func uriCase(t *textTable, uri string) []any {
if !utf8.ValidString(uri) {
log.Fatalf("an address that is not UTF-8: %q", uri)
}
err := locator.CheckURI(uri)
host := locator.Address{URI: uri}.Host()
if (err == nil) != (host != "") {
log.Fatalf("%q: %v and host %q", uri, err, host)
}
return []any{uri, t.of(err), host}
}
// prefixInfo is a block of addresses, its first and last address and their
// neighbours.
type prefixInfo struct {
first, last netip.Addr
}
func blockOf(s string) prefixInfo {
p := netip.MustParsePrefix(s).Masked()
first := p.Addr()
b := first.AsSlice()
bits := p.Bits()
for i := bits; i < len(b)*8; i++ {
b[i/8] |= 0x80 >> (i % 8)
}
last, _ := netip.AddrFromSlice(b)
return prefixInfo{first, last}
}
// The blocks of §44.1, those that hold an IPv4 address, and others outside
// 2000::/3, whose edges the cases visit.
var blocks4 = []string{"0.0.0.0/8", "10.0.0.0/8", "100.64.0.0/10", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12", "192.0.0.0/24", "192.0.2.0/24",
"192.88.99.0/24", "192.168.0.0/16", "198.18.0.0/15", "198.51.100.0/24", "203.0.113.0/24", "224.0.0.0/4", "240.0.0.0/4",
// Neighbours of interest that are public.
"1.0.0.0/24", "8.8.8.0/24", "192.0.1.0/24", "192.31.196.0/24", "192.52.193.0/24", "192.175.48.0/24"}
var blocks6 = []string{"2000::/3", "2001::/23", "2001:db8::/32", "2002::/16", "3fff::/20",
"::/96", "::ffff:0:0/96", "64:ff9b::/96", "64:ff9b:1::/48", "2001::/32", "fc00::/7", "fe80::/10", "ff00::/8", "100::/64", "::1/128",
"2001:20::/28", "2001:2::/48", "2620:4f:8000::/48", "2001:4860::/32"}
// edges gives the first and the last address of each block, and the
// addresses before and after them; inner reports the first and the last.
func edges(list []string) (out []netip.Addr, inner []bool) {
for _, s := range list {
b := blockOf(s)
for i, a := range []netip.Addr{b.first.Prev(), b.first, b.first.Next(), b.last.Prev(), b.last, b.last.Next()} {
if a.IsValid() {
out = append(out, a)
inner = append(inner, i == 1 || i == 4)
}
}
}
return out, inner
}
func randomAddr4(r *rand.Rand) netip.Addr {
var b [4]byte
switch r.IntN(4) {
case 0: // inside a block of §44.1
bl := blockOf(blocks4[r.IntN(15)])
f, l := bl.first.As4(), bl.last.As4()
for i := range b {
b[i] = f[i] + byte(r.IntN(int(l[i]-f[i])+1))
}
default:
for i := range b {
b[i] = byte(r.IntN(256))
}
}
return netip.AddrFrom4(b)
}
func randomAddr6(r *rand.Rand) netip.Addr {
var b [16]byte
for i := range b {
b[i] = byte(r.IntN(256))
}
switch r.IntN(6) {
case 0: // inside a block
bl := blockOf(blocks6[r.IntN(len(blocks6))])
f, l := bl.first.As16(), bl.last.As16()
for i := range b {
if f[i] == l[i] {
b[i] = f[i]
} else {
b[i] = f[i] | b[i]&(l[i]^f[i])
}
}
case 1: // zeros, to compress
for i := range b {
if r.IntN(3) > 0 {
b[i] = 0
}
}
case 2: // 2000::/3
b[0] = 0x20 | b[0]&0x1f
}
return netip.AddrFrom16(b)
}
func randomCase(r *rand.Rand, s string) string {
var out strings.Builder
for _, c := range s {
switch {
case c >= 'a' && c <= 'z' && r.IntN(3) == 0:
out.WriteRune(c - 32)
case c >= 'A' && c <= 'Z' && r.IntN(3) == 0:
out.WriteRune(c + 32)
default:
out.WriteRune(c)
}
}
return out.String()
}
// notations4 writes an IPv4 address in the notations that netip and some
// clients read.
func notations4(r *rand.Rand, a netip.Addr) []string {
b := a.As4()
v := binary.BigEndian.Uint32(b[:])
octet := func(x byte, form int) string {
switch form {
case 1:
return "0" + strconv.Itoa(int(x))
case 2:
return "0x" + strconv.FormatUint(uint64(x), 16)
case 3:
return "0" + strconv.FormatUint(uint64(x), 8)
case 4:
return "00" + strconv.Itoa(int(x))
}
return strconv.Itoa(int(x))
}
dotted := func(forms [4]int) string {
parts := make([]string, 4)
for i := range parts {
parts[i] = octet(b[i], forms[i])
}
return strings.Join(parts, ".")
}
out := []string{a.String(), dotted([4]int{r.IntN(5) * r.IntN(2), 0, 0, 0}), dotted([4]int{0, 0, 0, r.IntN(5)}),
fmt.Sprintf("%d.%d.%d", b[0], b[1], uint32(b[2])<<8|uint32(b[3])), fmt.Sprintf("%d.%d", b[0], v&0xffffff), strconv.FormatUint(uint64(v), 10),
"0x" + strconv.FormatUint(uint64(v), 16), "0" + strconv.FormatUint(uint64(v), 8), a.String() + ".", a.String() + ".0",
randomCase(r, "0X"+strconv.FormatUint(uint64(v), 16))}
return out
}
// notations6 writes an IPv6 address in the notations that netip accepts and
// in some that it rejects.
func notations6(r *rand.Rand, a netip.Addr) []string {
b := a.As16()
groups := make([]string, 8)
for i := range groups {
groups[i] = strconv.FormatUint(uint64(binary.BigEndian.Uint16(b[2*i:])), 16)
}
full := strings.Join(groups, ":")
padded := make([]string, 8)
for i, g := range groups {
padded[i] = strings.Repeat("0", 4-len(g)) + g
}
out := []string{a.String(), full, strings.Join(padded, ":"), randomCase(r, a.String())}
// :: in place of each run of zeros, the longest or not.
for i := 0; i < 8; i++ {
if groups[i] != "0" {
continue
}
j := i
for j < 8 && groups[j] == "0" {
j++
}
out = append(out, strings.Join(groups[:i], ":")+"::"+strings.Join(groups[j:], ":"))
i = j
}
// The last 32 bits as an IPv4 address.
v4 := fmt.Sprintf("%d.%d.%d.%d", b[12], b[13], b[14], b[15])
out = append(out, strings.Join(groups[:6], ":")+":"+v4)
if a.Is4In6() || b[0] == 0 && b[1] == 0 {
out = append(out, "::"+strings.Join(groups[5:6], ":")+":"+v4)
}
// Rejected: a group of five digits, nine groups, two ::, a short form.
k := r.IntN(8)
five := slices.Clone(padded)
five[k] = "0" + five[k]
nine := append(slices.Clone(groups), "1")
out = append(out, strings.Join(five, ":"), strings.Join(nine, ":"), strings.Join(groups[:7], ":"),
strings.Join(groups[:3], ":")+"::"+strings.Join(groups[4:5], ":")+"::1", full+":", ":"+full, full+"::")
return out
}
func randomLabel(r *rand.Rand) string {
const alnum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
n := 1 + r.IntN(12)
switch r.IntN(10) {
case 0:
n = 61 + r.IntN(5)
case 1:
n = 1
}
var s strings.Builder
for i := 0; i < n; i++ {
if i > 0 && i < n-1 && r.IntN(8) == 0 {
s.WriteByte('-')
} else {
s.WriteByte(alnum[r.IntN(len(alnum))])
}
}
l := s.String()
switch r.IntN(30) {
case 0:
l = "-" + l
case 1:
l += "-"
case 2:
l = "xn--" + strings.ToLower(l)
case 3:
l = strconv.Itoa(r.IntN(1000))
case 4:
l = randomCase(r, "0x") + strconv.FormatUint(r.Uint64N(1<<32), 16)
case 5:
l = strings.Replace(l, string(l[0]), "_", 1)
case 6:
l = ""
case 7:
l = randomCase(r, []string{"localhost", "local", "internal", "invalid", "test", "example", "onion", "arpa", "home"}[r.IntN(9)])
}
return l
}
var nonASCII = []string{"\xc3\xa9", "\xc3\xb1", "\xe2\x80\xae", "\xef\xbc\x8e", "\xe3\x80\x82", "\xf0\x9f\x8c\x8d", "\xc2\xa0", "\xd0\xb0", "\xe2\x80\x8b"}
func randomName(r *rand.Rand) string {
n := 1 + r.IntN(4)
labels := make([]string, n)
for i := range labels {
labels[i] = randomLabel(r)
}
if n > 1 && r.IntN(3) == 0 {
labels[n-1] = []string{"org", "com", "es", "io", "net", "arpa", "local", "test", "0", "123", "0x1f", "0Xff", "1e3"}[r.IntN(13)]
}
if r.IntN(4) == 0 {
suffix := []string{"localhost", "local", "home.arpa", "internal", "invalid", "test", "example", "onion"}[r.IntN(8)]
labels = append(labels, strings.Split(randomCase(r, suffix), ".")...)
}
name := strings.Join(labels, ".")
switch r.IntN(25) {
case 0:
name += "."
case 1:
name = "." + name
case 2:
i := r.IntN(len(name) + 1)
name = name[:i] + nonASCII[r.IntN(len(nonASCII))] + name[i:]
case 3:
i := r.IntN(len(name) + 1)
name = name[:i] + fmt.Sprintf("%%%02X", r.IntN(256)) + name[i:]
}
return name
}
func randomPort(r *rand.Rand) string {
switch r.IntN(12) {
case 0:
return ":"
case 1:
return ":0"
case 2:
return ":0" + strconv.Itoa(1+r.IntN(65535))
case 3:
return ":" + strconv.Itoa(65536+r.IntN(40000))
case 4:
return ":" + strings.Repeat("0", 1+r.IntN(3)) + strconv.Itoa(r.IntN(1000))
case 5:
return []string{":65535", ":65536", ":1", ":99999", ":100000", ":443", ":8443", ":-1", ":+1", ":4a", ": 1", "::1"}[r.IntN(12)]
}
return ":" + strconv.Itoa(1+r.IntN(65535))
}
// The characters of a path: allowed, percent-encoded and not allowed.
func randomSegment(r *rand.Rand) string {
const allowed = "abcxyzABZ019-._~!$&'()*+,;=:@"
n := r.IntN(8)
var s strings.Builder
for i := 0; i < n; i++ {
switch r.IntN(20) {
case 0:
s.WriteString([]string{"%2e", "%2E", "%2f", "%41", "%00", "%ff", "%25", "%7e"}[r.IntN(8)])
case 1:
s.WriteString([]string{"%", "%4", "%zz", "%g0", "%0g"}[r.IntN(5)])
case 2:
s.WriteString([]string{" ", "\"", "<", ">", "\\", "^", "`", "{", "|", "}", "\x00", "\t", "\n", "\x7f", "\x1f"}[r.IntN(15)])
case 3:
s.WriteString(nonASCII[r.IntN(len(nonASCII))])
default:
s.WriteByte(allowed[r.IntN(len(allowed))])
}
}
seg := s.String()
switch r.IntN(10) {
case 0:
seg = []string{".", "..", "%2e", "%2E%2e", ".%2E", "%2e.", "...", "..a"}[r.IntN(8)]
}
return seg
}
func randomPath(r *rand.Rand) string {
var s strings.Builder
for n := r.IntN(4); n > 0; n-- {
s.WriteByte('/')
s.WriteString(randomSegment(r))
}
if r.IntN(5) == 0 {
s.WriteString("?" + randomSegment(r) + "/" + randomSegment(r))
}
if r.IntN(6) == 0 {
s.WriteString("#" + randomSegment(r) + "/" + randomSegment(r))
}
return s.String()
}
func cidV1(b []byte) string {
return "b" + strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(b))
}
func uvarint(v uint64) []byte { return binary.AppendUvarint(nil, v) }
// randomCID writes a CID v1 in base32, valid or with one defect of its bytes
// or of its text.
func randomCID(r *rand.Rand) string {
version := uint64(1)
if r.IntN(12) == 0 {
version = []uint64{0, 2, 3, 0x81}[r.IntN(4)]
}
codec := []uint64{0x55, 0x70, 0x71, 0x0129, 0x0200, r.Uint64N(1 << 14)}[r.IntN(6)]
hash := []uint64{0x12, 0x13, 0x00, 0xb220, r.Uint64N(1 << 21)}[r.IntN(5)]
n := []int{32, 64, 20, 1 + r.IntN(80)}[r.IntN(4)]
digest := make([]byte, n)
for i := range digest {
digest[i] = byte(r.IntN(256))
}
parts := [][]byte{uvarint(version), uvarint(codec), uvarint(hash), uvarint(uint64(n)), digest}
switch r.IntN(10) {
case 0: // a varint that is not minimal
i := r.IntN(4)
v := parts[i]
parts[i] = append(append(slices.Clone(v[:len(v)-1]), v[len(v)-1]|0x80), 0)
case 1: // a varint of ten bytes, or of nine with the last continued
i := r.IntN(4)
parts[i] = append(bytes.Repeat([]byte{0xff}, 8+r.IntN(2)), 0x01)
case 2: // a length that is not that of the digest
parts[3] = uvarint(uint64(n + 1 - 2*r.IntN(2)))
case 3: // a byte after the digest
parts = append(parts, []byte{byte(r.IntN(256))})
case 4: // an empty digest
parts[3], parts[4] = []byte{0}, nil
case 5: // cut
all := slices.Concat(parts...)
parts = [][]byte{all[:r.IntN(len(all))]}
}
s := cidV1(slices.Concat(parts...))
switch r.IntN(14) {
case 0: // the last character with bits beyond a byte
s = s[:len(s)-1] + string("abcdefghijklmnopqrstuvwxyz234567"[r.IntN(32)])
case 1:
s = "b" + strings.ToUpper(s[1:])
case 2:
s += "="
case 3:
s += "a"
case 4:
s = s[:len(s)-1]
case 5:
i := 1 + r.IntN(len(s)-1)
s = s[:i] + string("0189"[r.IntN(4)]) + s[i:]
case 6:
s = "B" + s[1:]
case 7:
s = "Qm" + s[1:]
}
return s
}
func randomURI(r *rand.Rand) string {
scheme := "https"
switch x := r.IntN(20); {
case x < 4:
scheme = "ipfs"
case x < 6:
scheme = []string{"http", "HTTPS", "Https", "IPFS", "ipfS", "", "ftp", "file", "h", "https+x", "1https", "ht-tp", "https.", "data"}[r.IntN(14)]
}
sep := "://"
if r.IntN(25) == 0 {
sep = []string{":/", ":", "//", ":///", ":\\\\", "::/"}[r.IntN(6)]
}
var host string
if strings.EqualFold(scheme, "ipfs") && r.IntN(5) > 0 {
host = randomCID(r)
} else {
switch r.IntN(10) {
case 0, 1:
ns := notations4(r, randomAddr4(r))
host = ns[r.IntN(len(ns))]
case 2, 3:
ns := notations6(r, randomAddr6(r))
host = ns[r.IntN(len(ns))]
switch r.IntN(12) {
case 0:
host = "[[" + host + "]"
case 1:
host = "[" + host
case 2:
host += "]"
case 3:
host = "[" + host + "%25" + []string{"eth0", "1", "lo"}[r.IntN(3)] + "]"
case 4:
host = "[" + host + "]]"
case 5:
default:
host = "[" + host + "]"
}
default:
host = randomName(r)
}
}
authority := host
if r.IntN(25) == 0 {
authority = []string{"user@", "@", "a:b@", "u%40v@"}[r.IntN(4)] + authority
}
if r.IntN(5) == 0 {
authority += randomPort(r)
}
uri := scheme + sep + authority + randomPath(r)
if r.IntN(40) == 0 {
// At the limit of the length.
want := 1023 + r.IntN(3)
if len(uri) < want {
if !strings.Contains(uri[len(scheme)+len(sep):], "/") {
uri += "/"
}
uri += strings.Repeat("a", want-len(uri))
}
}
return uri
}
// fixedURIs are the addresses built at the edges of §44.1.
func fixedURIs(r *rand.Rand) []string {
var out []string
add := func(s ...string) { out = append(out, s...) }
// Every block of IPv4 at its edges; its first and last address in every
// notation, and inside IPv6.
as, inner := edges(blocks4)
for i, a := range as {
add("https://" + a.String() + "/")
if !inner[i] {
continue
}
for _, n := range notations4(r, a)[1:] {
add("https://" + n + "/")
}
add("https://"+a.String()+":443/x", "https://["+a.String()+"]/", "https://[::ffff:"+a.String()+"]/", "https://[::"+a.String()+"]/",
"https://[64:ff9b::"+a.String()+"]/", "https://[64:ff9b:1::"+a.String()+"]/")
}
// Every block of IPv6 at its edges; its first and last address in every
// notation, with and without brackets, with a zone and a port.
as, inner = edges(blocks6)
for i, a := range as {
s := a.String()
add("https://[" + s + "]/")
if !inner[i] {
continue
}
for _, n := range notations6(r, a)[1:] {
add("https://[" + n + "]/")
}
add("https://"+s+"/", "https://["+s+"]:8443/a", "https://["+s+"]:0443/", "https://["+s+"%25eth0]/", "https://["+s+"%eth0]/",
"https://[["+s+"]/", "https://[["+s+"]]/", "https://["+s+"]]/", "https://["+s+"/", "https://["+s+"]x/", "https://["+s+"]:/")
}
// Names.
for _, n := range []string{"a", "a.b", "ejemplo.org", "EJEMPLO.ORG", "xn--pple-43d.com", "xn--a.b", "a-b.c-d.e", "a--b.org", "1.org", "org.1",
"123.456", "0x.org", "org.0x", "org.0X", "org.0x1", "a.0b1", "a.1b", "a.b1", "a.b-1", "a.0", "a.00", "a.08", "a.1e1",
strings.Repeat("a", 63) + ".org", strings.Repeat("a", 64) + ".org", "org." + strings.Repeat("b", 63), "org." + strings.Repeat("b", 64),
strings.Repeat("a.", 120) + "org", "localhost", "LOCALHOST", "LocalHost", "a.localhost", "localhost.org", "a.local", "A.LOCAL", "local",
"home.arpa", "a.home.arpa", "a.HOME.ARPA", "home.arpa.org", "arpa", "a.arpa", "x.internal", "x.invalid", "x.test", "x.example",
"x.onion", "x.Onion", "onion.x", "example", "example.com", "test.org", "a.localhost.", "a.local-", "a._b.org", "a.b_.org"} {
add("https://"+n+"/", "https://"+n+":8443/x")
}
// Ports.
for _, p := range []string{"", ":", ":0", ":00", ":1", ":01", ":9", ":09", ":443", ":0443", ":00443", ":000443", ":65535", ":065535",
":65536", ":99999", ":100000", ":4294967739", ":-443", ":+443", ":44 3", ":4a", ":443:80", "::443"} {
add("https://ejemplo.org"+p+"/x", "https://[2606:4700::1111]"+p+"/x", "ipfs://"+locatorCID+p+"/x")
}
// Schemes, separators and userinfo.
for _, u := range []string{"https://ejemplo.org/", "HTTPS://ejemplo.org/", "hTTps://ejemplo.org/", "https:/ejemplo.org/", "https:ejemplo.org/",
"https:///ejemplo.org/", "https:////ejemplo.org/", "://ejemplo.org/", "https//ejemplo.org/", "ipfs:/" + locatorCID, "ipfs:" + locatorCID,
"ipfs:///" + locatorCID, "https://user@ejemplo.org/", "https://@ejemplo.org/", "https://ejemplo.org@x/", "https://ejemplo.org/a@b",
"https://ejemplo.org?a@b", "https://ejemplo.org#a@b", "https://ejemplo.org/?", "https://ejemplo.org#", "https://ejemplo.org?",
"https://ejemplo.org/%", "https://ejemplo.org/%2", "https://ejemplo.org/%2G", "https://ejemplo.org/%2f%2F", "https://ejemplo.org%2f/",
"https://ejemplo.org/a%", "https://ejemplo.org/a%%41", "https://", "ipfs://", "https://:443/", "https://.:443/", "https://./"} {
add(u)
}
// Dot segments.
for _, p := range []string{"/.", "/..", "/./", "/../", "/a/.", "/a/..", "/a/./b", "/a/../b", "/%2e", "/%2E", "/%2e%2e", "/%2E%2E", "/.%2e",
"/%2e.", "/%2e%2E/", "/...", "/.a", "/a.", "/.%2", "/%2e%2e%2f", "/a?/../", "/a#/../", "/a?x=/..", "?/..", "#/..", "?a/../b", "//..", "/..//"} {
add("https://ejemplo.org"+p, "ipfs://"+locatorCID+p)
}
// Each byte that is not a character of RFC 3986, and the first byte of
// a character that is not ASCII.
for c := 0; c < 0x80; c++ {
add(fmt.Sprintf("https://ejemplo.org/a%cb", c))
}
for _, n := range nonASCII {
add("https://ejemplo.org/"+n, "https://ejempl"+n+".org/")
}
// Lengths.
for _, n := range []int{1, 2, 1023, 1024, 1025, 2000} {
u := "https://ejemplo.org/"
if n > len(u) {
add(u + strings.Repeat("a", n-len(u)))
} else {
add(strings.Repeat("h", n))
}
}
// CIDs at their limits.
digest := sha256.Sum256([]byte("locator cid"))
for _, b := range [][]byte{
slices.Concat([]byte{0x01, 0x55, 0x12, 0x20}, digest[:]), slices.Concat([]byte{0x01, 0x70, 0x12, 0x20}, digest[:]),
slices.Concat([]byte{0x01, 0x55, 0x00, 0x01}, []byte{0}), slices.Concat([]byte{0x01, 0x55, 0x00, 0x00}),
slices.Concat([]byte{0x01, 0x55, 0x00, 0x4b}, patterned("cid 75", 75)), slices.Concat([]byte{0x01, 0x55, 0x00, 0x4c}, patterned("cid 76", 76)),
slices.Concat([]byte{0x01, 0x55, 0x00, 0x4d}, patterned("cid 77", 77)), slices.Concat([]byte{0x01, 0x80, 0x01, 0x12, 0x20}, digest[:]),
slices.Concat([]byte{0x01, 0x80, 0x00, 0x12, 0x20}, digest[:]), slices.Concat([]byte{0x81, 0x00, 0x55, 0x12, 0x20}, digest[:]),
slices.Concat([]byte{0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x12, 0x20}, digest[:]),
slices.Concat([]byte{0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x12, 0x20}, digest[:]),
slices.Concat([]byte{0x01, 0x55, 0x12, 0x80, 0x00}), slices.Concat([]byte{0x01, 0x55, 0x12, 0xa0, 0x00}, digest[:]),
slices.Concat([]byte{0x01, 0x55, 0x12, 0x21}, digest[:]), slices.Concat([]byte{0x01, 0x55, 0x12, 0x1f}, digest[:]),
slices.Concat([]byte{0x01, 0x55, 0x12, 0x20}, digest[:], []byte{0}), {0x01}, {0x01, 0x55}, {0x01, 0x55, 0x12}, {},
} {
s := cidV1(b)
add("ipfs://"+s, "ipfs://"+s+"/x", "ipfs://"+s+"a", "ipfs://"+s+"aa", "ipfs://"+s+"q", "ipfs://"+s[:max(1, len(s)-1)])
}
add("ipfs://"+locatorCID+"a", "ipfs://"+locatorCID+"b", "ipfs://"+locatorCID+"aa", "ipfs://"+locatorCID+"7", "ipfs://"+strings.ToUpper(locatorCID),
"ipfs://b", "ipfs://", "ipfs://bb", "ipfs://"+locatorCID+":443", "ipfs://"+locatorCID+"@x", "ipfs://x@"+locatorCID, "ipfs://["+locatorCID+"]")
return out
}
func uriCases(t *textTable, r *rand.Rand) []any {
seen := map[string]bool{}
var out []any
add := func(u string) {
if seen[u] || !utf8.ValidString(u) {
return
}
seen[u] = true
out = append(out, uriCase(t, u))
}
for _, u := range fixedURIs(r) {
add(u)
}
for len(out) < 2800 {
add(randomURI(r))
}
return out
}
// wtf8Cases gives CheckURI on addresses with the three bytes of a
// surrogate, which are not UTF-8: the bytes of a String of Dart with a lone
// surrogate, [bytes, text].
func wtf8Cases(t *textTable) []any {
var out []any
for _, s := range []string{"https://a.org/\xed\xa0\x80", "https://a.org/\xed\xbf\xbf", "\xed\xa0\x80", "https://ejempl\xed\xb0\x80.org/"} {
out = append(out, []any{hx([]byte(s)), t.of(locator.CheckURI(s))})
}
return out
}
// ipCase is netip.ParseAddr of s: [s, false], or [s, true, the bytes of the
// address, its zone, its String, publicIP].
func ipCase(s string) []any {
a, err := netip.ParseAddr(s)
if err != nil {
return []any{s, false}
}
return []any{s, true, hx(a.AsSlice()), a.Zone(), a.String(), publicIP(a)}
}
func ipCases(r *rand.Rand) []any {
seen := map[string]bool{}
var out []any
add := func(s string) {
if seen[s] || !utf8.ValidString(s) {
return
}
seen[s] = true
out = append(out, ipCase(s))
}
as, inner := edges(blocks4)
for i, a := range as {
add(a.String())
if inner[i] {
for _, n := range notations4(r, a)[1:] {
add(n)
}
}
}
as, inner = edges(blocks6)
for i, a := range as {
add(a.String())
if inner[i] {
for _, n := range notations6(r, a)[1:] {
add(n)
add(n + "%" + []string{"eth0", "1", "%", "\xc3\xa9", "a b", "]", ""}[r.IntN(7)])
}
}
}
for _, s := range []string{"", ".", ":", "::", ":::", "%", "%eth0", "::%", "::%eth0", "::1%", "::1%%", "1.2.3.4%eth0", "::1.2.3.4%x",
"::ffff:1.2.3.4", "::ffff:01.2.3.4", "::ffff:1.2.3", "::ffff:1.2.3.4.5", "::1.2.3.4:5", "1.2.3.4::", "1::2.3.4.5", "1:2:3:4:5:6:1.2.3.4",
"1:2:3:4:5:6:7:1.2.3.4", "1:2:3:4:5:1.2.3.4", "1:2:3:4:5::1.2.3.4", "::1:2:3:4:5:6:1.2.3.4", "0:0:0:0:0:0:0:0", "0::0", "0:0::0:0:0:0:0:0",
"0:0:0:0:0:0:0::0", "1:2:3:4:5:6:7::", "::2:3:4:5:6:7:8", "1::3:4:5:6:7:8", "1:2:3:4:5:6:7:8::", "1:2:3:4:5:6:7:8:9", "fffff::",
"FFFF::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "g::", "::g", "1.2.3.4", "01.2.3.4", "1.2.3.04", "1.2.3.00", "0.0.0.0", "00.0.0.0",
"255.255.255.255", "256.255.255.255", "1.2.3", "1.2.3.4.5", "1..2.3", ".1.2.3", "1.2.3.", "1.2.3.4 ", " 1.2.3.4", "1.2.3.4\x00",
"\xef\xbc\x91.2.3.4", "1\xe3\x80\x822.3.4", "0x1.2.3.4", "1e1.2.3.4", "2130706433", "0x7f000001", "[::1]", "::1]", "[::1"} {
add(s)
}
const alphabet = "0123456789abcdefABCDEFgx.:%"
for len(out) < 1700 {
n := 1 + r.IntN(24)
var s strings.Builder
for i := 0; i < n; i++ {
s.WriteByte(alphabet[r.IntN(len(alphabet))])
}
add(s.String())
a := randomAddr6(r)
ns := notations6(r, a)
add(ns[r.IntN(len(ns))])
a4 := randomAddr4(r)
ns4 := notations4(r, a4)
add(ns4[r.IntN(len(ns4))])
}
return out
}
// publicCases is publicIP of the address of each byte string, as a reader
// checks the IP that a name resolves to: [hex, public, String], or [hex,
// null] for a length that is no address.
func publicCases(r *rand.Rand) []any {
var out []any
add := func(b []byte) {
a, ok := netip.AddrFromSlice(b)
if !ok {
out = append(out, []any{hx(b), nil})
return
}
out = append(out, []any{hx(b), publicIP(a), a.String()})
}
as, _ := edges(blocks4)
for _, a := range as {
add(a.AsSlice())
m := netip.AddrFrom16(a.As16())
add(m.AsSlice())
}
as, _ = edges(blocks6)
for _, a := range as {
add(a.AsSlice())
}
for i := 0; i < 250; i++ {
add(randomAddr4(r).AsSlice())
add(randomAddr6(r).AsSlice())
}
for _, n := range []int{0, 1, 3, 5, 8, 15, 17, 32} {
add(patterned("length", n))
}
return out
}
// ---------------------------------------------------------------------------
// Locators
// enc writes CBOR by hand, so that a plaintext may break any rule.
type enc struct{ b []byte }
// head writes a head of major type m and argument n in size bytes: 0 for the
// shortest.
func (e *enc) head(m byte, n uint64, size int) {
if size == 0 {
switch {
case n < 24:
size = 1
case n < 1<<8:
size = 2
case n < 1<<16:
size = 3
case n < 1<<32:
size = 5
default:
size = 9
}
}
switch size {
case 1:
e.b = append(e.b, m<<5|byte(n))
case 2:
e.b = append(e.b, m<<5|24, byte(n))
case 3:
e.b = binary.BigEndian.AppendUint16(append(e.b, m<<5|25), uint16(n))
case 5:
e.b = binary.BigEndian.AppendUint32(append(e.b, m<<5|26), uint32(n))
default:
e.b = binary.BigEndian.AppendUint64(append(e.b, m<<5|27), n)
}
}
func (e *enc) uint(n uint64) { e.head(0, n, 0) }
func (e *enc) bstr(b []byte) { e.head(2, uint64(len(b)), 0); e.b = append(e.b, b...) }
func (e *enc) text(s string) { e.head(3, uint64(len(s)), 0); e.b = append(e.b, s...) }
func (e *enc) raw(b []byte) { e.b = append(e.b, b...) }
func (e *enc) array(n uint64) { e.head(4, n, 0) }
func (e *enc) mapOf(n uint64) { e.head(5, n, 0) }
// rawAddr is an address of a plaintext: its entries in order.
type rawAddr struct {
pairs []rawPair
}
type rawPair struct {
key uint64
val func(*enc)
}
// rawLoc is a plaintext of a locator: the entries of its map in order, the
// count its head declares (-1 for the number of entries), and the padding.
type rawLoc struct {
pairs []rawPair
count int
}
func addrPairs(uri string, offset uint64) rawAddr {
a := rawAddr{pairs: []rawPair{{0, func(e *enc) { e.text(uri) }}}}
if offset != 0 {
a.pairs = append(a.pairs, rawPair{1, func(e *enc) { e.uint(offset) }})
}
return a
}
func addrsValue(addrs []rawAddr) func(*enc) {
return func(e *enc) {
e.array(uint64(len(addrs)))
for _, a := range addrs {
e.mapOf(uint64(len(a.pairs)))
for _, p := range a.pairs {
e.uint(p.key)
p.val(e)
}
}
}
}
func rawOf(loc *locator.Locator, addrs []rawAddr) rawLoc {
return rawLoc{count: -1, pairs: []rawPair{
{0, addrsValue(addrs)},
{1, func(e *enc) { e.bstr(loc.EnvelopeKey[:]) }},
{2, func(e *enc) { e.bstr(loc.EnvelopeHeader) }},
{3, func(e *enc) { e.bstr(loc.RestDigest[:]) }},
{4, func(e *enc) { e.uint(loc.RestSize) }},
{5, func(e *enc) { e.bstr(loc.CapsuleDigest[:]) }},
}}
}
func (l rawLoc) encode(extra ...rawPair) []byte {
var e enc
pairs := append(slices.Clone(l.pairs), extra...)
n := uint64(len(pairs))
if l.count >= 0 {
n = uint64(l.count)
}
e.mapOf(n)
for _, p := range pairs {
e.uint(p.key)
p.val(&e)
}
return e.b
}
func bstrHeadLen(n int) int {
switch {
case n < 24:
return 1
case n < 256:
return 2
case n < 65536:
return 3
}
return 5
}
// padded is the plaintext with the key 6 that makes it the least multiple of
// 4096 that holds it, as Marshal pads it, or none when it already is one.
func (l rawLoc) padded() []byte {
base := l.encode()
total := locator.PlaintextLength(len(base))
if total == len(base) {
return base
}
for pad := 1; ; pad++ {
if len(base)+1+bstrHeadLen(pad)+pad == total {
return l.encode(rawPair{6, func(e *enc) { e.bstr(make([]byte, pad)) }})
}
}
}
// summary is what a reader reads of a locator: [[uri, offset, host,
// usable]...], the key, the SHA-256 of the header, the digest of the rest,
// its size and capsule_digest.
func summary(l *locator.Locator) []any {
addrs := []any{}
for _, a := range l.Addresses {
addrs = append(addrs, []any{compactURI(a.URI), a.Offset, compactURI(a.Host()), locator.CheckURI(a.URI) == nil})
}
usable := l.Usable()
n := 0
for _, a := range l.Addresses {
if locator.CheckURI(a.URI) == nil {
if usable[n] != a {
log.Fatal("Usable is not the addresses that CheckURI accepts")
}
n++
}
}
return []any{addrs, hx(l.EnvelopeKey[:]), sum(l.EnvelopeHeader), hx(l.RestDigest[:]), l.RestSize, hx(l.CapsuleDigest[:])}
}
// readCase is Unmarshal of b: [base, edits, text, summary or null].
func readCase(t *textTable, bases [][]byte, base int, b []byte) []any {
l, err := locator.Unmarshal(b)
var s any
if err == nil {
s = summary(l)
if again, err := locator.Unmarshal(b); err != nil || !slices.Equal(again.Addresses, l.Addresses) {
log.Fatal("Unmarshal is not stable")
}
}
return []any{base, edits(bases[base], b), t.of(err), s}
}
type world struct {
p *profile.Profile
texts *textTable
releases map[uint64]provider.Release
dkc []byte
datas [][]byte // the data of datekeys.capsule of registry and capsule
loc *locator.Locator
rest []byte
file []byte // the envelope, header and rest
}
func (w *world) release(round uint64) provider.Release {
r, ok := w.releases[round]
if !ok {
log.Fatalf("no release of round %d in the fixtures", round)
}
return r
}
// plaintextBases are the plaintexts that the edits of plaintexts refer to:
// the locator of the envelope with two addresses, with eight, and with
// addresses that a reader rejects next to one it uses.
func (w *world) plaintextBases() ([][]byte, []rawLoc) {
two := rawOf(w.loc, []rawAddr{addrPairs("https://ejemplo.org/foto.jpg", 3000), addrPairs("ipfs://"+locatorCID, 0)})
var eight []rawAddr
for i := 0; i < 8; i++ {
eight = append(eight, addrPairs(fmt.Sprintf("https://ejemplo%d.org/%s", i, strings.Repeat("x", 400+i)), uint64(i)*1000))
}
eightLoc := rawOf(w.loc, eight)
mixed := rawOf(w.loc, []rawAddr{addrPairs("http://ejemplo.org/a", 7), addrPairs("https://[64:ff9b::7f00:1]/b", 0),
addrPairs("https://ejemplo.org/c", 9007199254740991), addrPairs("ipfs://"+locatorCID+"a", 1)})
raws := []rawLoc{two, eightLoc, mixed}
var bases [][]byte
for _, l := range raws {
bases = append(bases, l.padded())
}
return bases, raws
}
// plaintextCases breaks each base field by field and byte by byte.
func (w *world) plaintextCases(bases [][]byte, raws []rawLoc) []any {
t := w.texts
r := newRand("plaintexts")
var out []any
add := func(base int, b []byte) { out = append(out, readCase(t, bases, base, b)) }
for i, b := range bases {
add(i, b)
}
loc := w.loc
two := raws[0]
addr := func(uri string, offset uint64) rawAddr { return addrPairs(uri, offset) }
foto := addr("https://ejemplo.org/foto.jpg", 3000)
with := func(key uint64, val func(*enc)) rawLoc {
l := two
l.pairs = slices.Clone(l.pairs)
for i, p := range l.pairs {
if p.key == key {
l.pairs[i] = rawPair{key, val}
return l
}
}
l.pairs = append(l.pairs, rawPair{key, val})
slices.SortStableFunc(l.pairs, func(a, b rawPair) int { return int(a.key) - int(b.key) })
return l
}
without := func(key uint64) rawLoc {
l := two
l.pairs = slices.DeleteFunc(slices.Clone(l.pairs), func(p rawPair) bool { return p.key == key })
return l
}
addrs := func(as ...rawAddr) func(*enc) { return addrsValue(as) }
// The fields, each padded as Marshal would pad it.
for _, l := range []rawLoc{
without(0), without(1), without(2), without(3), without(4), without(5),
with(0, addrs()), with(0, addrs(foto, foto, foto, foto, foto, foto, foto, foto)), with(0, addrs(foto, foto, foto, foto, foto, foto, foto, foto, foto)),
with(0, addrs(addr("", 0))), with(0, addrs(addr("h", 0))), with(0, addrs(addr("https://ejemplo.org/"+strings.Repeat("a", 1004), 0))),
with(0, addrs(addr("https://ejemplo.org/"+strings.Repeat("a", 1005), 0))),
with(0, addrs(addr("https://ejemplo.org/a", 1))), with(0, addrs(addr("https://ejemplo.org/a", 9007199254740991))),
with(0, addrs(addr("https://ejemplo.org/a", 9007199254740992))), with(0, addrs(addr("https://ejemplo.org/a", 1<<63))),
with(0, addrs(rawAddr{pairs: []rawPair{{0, func(e *enc) { e.text("https://ejemplo.org/a") }}, {1, func(e *enc) { e.uint(0) }}}})),
with(0, addrs(rawAddr{pairs: []rawPair{{1, func(e *enc) { e.uint(5) }}}})),
with(0, addrs(rawAddr{pairs: []rawPair{{0, func(e *enc) { e.text("https://ejemplo.org/a") }}, {2, func(e *enc) { e.uint(5) }}}})),
with(0, addrs(rawAddr{pairs: []rawPair{{1, func(e *enc) { e.uint(5) }}, {0, func(e *enc) { e.text("https://ejemplo.org/a") }}}})),
with(0, addrs(rawAddr{pairs: []rawPair{{0, func(e *enc) { e.bstr([]byte("https://ejemplo.org/a")) }}}})),
with(0, addrs(rawAddr{pairs: []rawPair{{0, func(e *enc) { e.head(3, 2, 0); e.raw([]byte{0xc3, 0x28}) }}}})),
with(0, addrs(rawAddr{pairs: []rawPair{{0, func(e *enc) { e.text("https://ejemplo.org/a") }}, {1, func(e *enc) { e.head(0, 5, 2) }}}})),
with(0, addrs(rawAddr{pairs: []rawPair{{0, func(e *enc) { e.text("https://ejemplo.org/a") }}, {0, func(e *enc) { e.text("https://ejemplo.org/b") }}}})),
with(0, func(e *enc) { e.mapOf(0) }), with(0, func(e *enc) { e.text("https://ejemplo.org/a") }),
with(0, func(e *enc) { e.head(4, 1, 2); addrPairsEnc(e, "https://ejemplo.org/a") }),
with(0, addrs(addr("http://ejemplo.org/a", 0))), with(0, addrs(addr("https://127.0.0.1/a", 0), addr("ipfs://"+locatorCID, 5))),
with(1, func(e *enc) { e.bstr(loc.EnvelopeKey[:31]) }), with(1, func(e *enc) { e.bstr(append(loc.EnvelopeKey[:], 0)) }),
with(1, func(e *enc) { e.text(strings.Repeat("k", 32)) }),
with(2, func(e *enc) { e.bstr(nil) }), with(2, func(e *enc) { e.bstr([]byte{'h'}) }), with(2, func(e *enc) { e.bstr(patterned("h", 1024)) }),
with(2, func(e *enc) { e.bstr(patterned("h", 1025)) }),
with(3, func(e *enc) { e.bstr(loc.RestDigest[:16]) }), with(5, func(e *enc) { e.bstr(nil) }),
with(4, func(e *enc) { e.uint(0) }), with(4, func(e *enc) { e.uint(9007199254740991) }), with(4, func(e *enc) { e.uint(9007199254740992) }),
with(4, func(e *enc) { e.uint(1<<64 - 1) }), with(4, func(e *enc) { e.head(0, loc.RestSize, 9) }), with(4, func(e *enc) { e.text("5") }),
with(7, func(e *enc) { e.uint(0) }), with(1<<53, func(e *enc) { e.uint(0) }),
} {
add(0, l.padded())
}
// The map: its count, its keys out of order or repeated.
{
l := two
l.count = 5
add(0, l.padded())
l.count = 7
add(0, l.padded())
l = two
l.pairs = slices.Clone(l.pairs)
l.pairs[1], l.pairs[2] = l.pairs[2], l.pairs[1]
add(0, l.padded())
l = two
l.pairs = append(slices.Clone(l.pairs), l.pairs[5])
add(0, l.padded())
}
// Key 6: absent, empty, of every length near the boundaries of its head,
// not zeros, in another type, not the least multiple.
base := two.encode()
for _, pad := range []int{0, 1, 2, 22, 23, 24, 25, 254, 255, 256, 257, 4096 - len(base) - 3, 4096 - len(base) - 4, 8192 - len(base) - 4} {
add(0, two.encode(rawPair{6, func(e *enc) { e.bstr(make([]byte, pad)) }}))
}
full := 4096 - len(base) - 1
for _, size := range []int{2, 3, 5, 9} {
n := full - size
add(0, two.encode(rawPair{6, func(e *enc) { e.head(2, uint64(n), size); e.raw(make([]byte, n)) }}))
}
add(0, two.encode(rawPair{6, func(e *enc) { e.text(strings.Repeat("\x00", full-3)) }}))
nonzero := slices.Clone(bases[0])
nonzero[len(nonzero)-1] = 1
add(0, nonzero)
nonzero = slices.Clone(bases[0])
nonzero[len(base)+4] = 0x80
add(0, nonzero)
add(0, base)
add(0, append(slices.Clone(bases[0]), make([]byte, 4096)...))
add(0, append(slices.Clone(bases[0]), 0))
add(0, bases[0][:len(bases[0])-1])
add(0, nil)
add(0, []byte{0xa0})
add(0, []byte{0xa7})
// Bases at the boundaries of the padding: the least multiple and the
// next one.
for _, target := range []int{3836, 3837, 3838, 4069, 4070, 4071, 4093, 4094, 4095, 4096, 4097, 8188, 8189, 8190, 8191, 8192} {
l := lengthFor(w.loc, target)
add(0, l.padded())
if b := l.encode(); len(b)%4096 != 0 {
for _, total := range []int{(len(b)/4096 + 1) * 4096, (len(b)/4096 + 2) * 4096} {
if pad := fillTo(len(b), total); pad >= 0 {
add(0, l.encode(rawPair{6, func(e *enc) { e.bstr(make([]byte, pad)) }}))
}
}
}
}
// Bytes edited at random, mostly in the map before the padding.
for i := 0; i < 360; i++ {
bi := r.IntN(len(bases))
b := slices.Clone(bases[bi])
meaningful := len(raws[bi].encode())
at := r.IntN(meaningful + 8)
if r.IntN(6) == 0 {
at = r.IntN(len(b))
}
switch r.IntN(6) {
case 0, 1:
b[at] ^= 1 << r.IntN(8)
case 2:
b[at] = byte(r.IntN(256))
case 3:
n := 1 + r.IntN(4)
b = slices.Insert(b, at, patterned(fmt.Sprint("insert", i), n)...)
case 4:
n := min(1+r.IntN(4), len(b)-at)
b = slices.Delete(b, at, at+n)
case 5:
b = b[:at]
}
add(bi, b)
}
return out
}
func addrPairsEnc(e *enc, uri string) {
e.mapOf(1)
e.uint(0)
e.text(uri)
}
// fillTo is the length of key 6, 0 included, that makes a CBOR of base bytes
// measure total, or -1.
func fillTo(base, total int) int {
for pad := 0; base+2+pad <= total; pad++ {
if base+1+bstrHeadLen(pad)+pad == total {
return pad
}
}
return -1
}
// lengthFor returns a plaintext of the envelope of loc whose CBOR without key
// 6 measures target bytes, with addresses that §44.1 accepts.
func lengthFor(loc *locator.Locator, target int) rawLoc {
for _, n := range []int{980, 880, 700} {
long := addrPairs("https://ejemplo.org/"+strings.Repeat("a", n), 0)
for count := 0; count < locator.MaxAddresses; count++ {
for k := 1; k <= locator.MaxURILen-20; k++ {
addrs := append(slices.Repeat([]rawAddr{long}, count), addrPairs("https://ejemplo.org/"+strings.Repeat("b", k), 0))
l := rawOf(loc, addrs)
n := len(l.encode())
if n == target {
return l
}
if n > target {
break
}
}
}
}
log.Fatalf("no addresses make a base of %d bytes", target)
return rawLoc{}
}
// paddingRuns gives PlaintextLength on every base from -4100 to 16484, as
// runs [from, to, total] of bases with the same result.
func paddingRuns() []any {
var out []any
from, last := -4100, locator.PlaintextLength(-4100)
for b := -4099; b <= 16485; b++ {
var got int
if b <= 16484 {
got = locator.PlaintextLength(b)
}
if b == 16485 || got != last {
out = append(out, []any{from, b - 1, last})
from, last = b, got
}
}
return out
}
// marshalCases gives Marshal of locators at every limit: [addresses,
// header, rest_size, text, length, SHA-256], the header null for that of
// the envelope.
func (w *world) marshalCases() []any {
t := w.texts
var out []any
type addr = locator.Address
add := func(addrs []addr, header []byte, restSize uint64) {
l := *w.loc
l.Addresses = addrs
var h any
if header != nil {
l.EnvelopeHeader = header
h = hx(header)
}
l.RestSize = restSize
b, err := l.Marshal()
as := []any{}
for _, a := range addrs {
as = append(as, []any{compactURI(a.URI), a.Offset})
}
c := []any{as, h, restSize, t.of(err), len(b), ""}
if err == nil {
c[5] = sum(b)
}
out = append(out, c)
}
rs := w.loc.RestSize
one := []addr{{URI: "https://ejemplo.org/foto.jpg"}}
foto := addr{URI: "https://ejemplo.org/foto.jpg", Offset: 3000}
add(one, nil, rs)
add([]addr{foto, {URI: "ipfs://" + locatorCID}}, nil, rs)
add(nil, nil, rs)
add(slices.Repeat([]addr{foto}, 8), nil, rs)
add(slices.Repeat([]addr{foto}, 9), nil, rs)
add([]addr{{URI: ""}}, nil, rs)
add([]addr{{URI: "https://ejemplo.org/" + strings.Repeat("a", 1004)}}, nil, rs)
add([]addr{{URI: "https://ejemplo.org/" + strings.Repeat("a", 1005)}}, nil, rs)
add([]addr{{URI: "http://ejemplo.org/a"}}, nil, rs)
add([]addr{foto, {URI: "https://[64:ff9b::7f00:1]/"}}, nil, rs)
add([]addr{{URI: "https://ejemplo.org/a", Offset: 9007199254740991}}, nil, rs)
add([]addr{{URI: "https://ejemplo.org/a", Offset: 9007199254740992}}, nil, rs)
add([]addr{{URI: "http://ejemplo.org/a", Offset: 9007199254740992}}, nil, rs)
add(one, nil, 0)
add(one, nil, 9007199254740991)
add(one, nil, 9007199254740992)
add([]addr{{URI: "http://x"}}, nil, 9007199254740992)
add(one, []byte{}, rs)
add([]addr{{URI: ""}}, []byte{}, rs)
add(one, []byte{'h'}, rs)
add(one, patterned("header", 1024), rs)
add(one, patterned("header", 1025), rs)
// The bases of the boundaries of the padding.
for _, target := range []int{3000, 3836, 3837, 3838, 4069, 4070, 4071, 4072, 4092, 4093, 4094, 4095, 4096, 4097, 4098, 7932, 7933, 7934,
8165, 8166, 8167, 8188, 8189, 8190, 8191, 8192, 8193} {
l := lengthFor(w.loc, target)
// The addresses of l, read back from its encoding.
back, err := locator.Unmarshal(l.padded())
if err != nil {
log.Fatalf("a base of %d: %v", target, err)
}
add(back.Addresses, nil, rs)
}
return out
}
// ---------------------------------------------------------------------------
// Sealed locators
// sealPlain seals the plaintext of a locator for round as locator.Seal does,
// for a plaintext that Marshal does not write.
func (w *world) sealPlain(round uint64, plain []byte) []byte {
r := must(agewrap.NewTimeRecipient(w.p, round))
var buf bytes.Buffer
wr := must(age.Encrypt(&buf, r))
must(wr.Write(plain))
if err := wr.Close(); err != nil {
log.Fatal(err)
}
return buf.Bytes()
}
// openCase is Open of base edited: [base, round, release, profile, edits,
// text, summary or null]; release is the round of the release given, or a
// name for another; profile names an edit of the profile, "" for none.
func (w *world) openCase(sealedBases [][]byte, base int, round uint64, release any, prof string, b []byte) []any {
var rel provider.Release
switch x := release.(type) {
case uint64:
rel = w.release(x)
case string:
switch x {
case "flipped":
rel = w.release(round)
rel.Signature = slices.Clone(rel.Signature)
rel.Signature[len(rel.Signature)-1] ^= 1
case "short":
rel = w.release(round)
rel.Signature = rel.Signature[:47]
case "other round":
rel = w.release(1001)
rel.Round = round
default:
log.Fatalf("release %q", x)
}
}
p := w.p.Clone()
switch prof {
case "":
case "key not on the curve":
p.PublicKey = slices.Clone(p.PublicKey)
p.PublicKey[len(p.PublicKey)-1] ^= 1
case "key at infinity":
p.PublicKey = append([]byte{0xc0}, make([]byte, 95)...)
case "key of another network":
// The generator of G2, compressed: a key that no release verifies under.
p.PublicKey = must(hex.DecodeString("93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8"))
case "chain hash of another network":
p.ChainHash[0] ^= 1
default:
log.Fatalf("profile %q", prof)
}
l, err := locator.Open(p, round, rel, b)
var s any
if err == nil {
s = summary(l)
}
return []any{base, round, release, prof, edits(sealedBases[base], b), w.texts.of(err), s}
}
func (w *world) openCases(plainBases [][]byte) ([][]byte, []uint64, []any) {
setCryptoRand("sealed")
rounds := []uint64{1000, 1001, 2000, 1004}
plains := [][]byte{plainBases[0], plainBases[0], plainBases[2], plainBases[1]}
var sealed [][]byte
for i, round := range rounds {
sealed = append(sealed, w.sealPlain(round, plains[i]))
}
var out []any
add := func(base int, round uint64, release any, prof string, b []byte) {
out = append(out, w.openCase(sealed, base, round, release, prof, b))
}
// Each with its release, and with the others.
for i, round := range rounds {
for _, other := range rounds {
add(i, round, other, "", sealed[i])
if other != round {
add(i, other, other, "", sealed[i])
}
}
add(i, round, "flipped", "", sealed[i])
add(i, round, "short", "", sealed[i])
add(i, round, "other round", "", sealed[i])
}
add(0, 0, uint64(1000), "", sealed[0])
for _, prof := range []string{"key not on the curve", "key at infinity", "key of another network", "chain hash of another network"} {
add(0, 1000, uint64(1000), prof, sealed[0])
}
// The header of the first, edited line by line.
s := sealed[0]
end := must(headerEnd(s))
header := string(s[:end])
lines := strings.SplitAfter(header, "\n")
lines = lines[:len(lines)-1]
edit := func(f func(lines []string) []string) []byte {
ls := f(slices.Clone(lines))
return append([]byte(strings.Join(ls, "")), s[end:]...)
}
stanza := strings.Fields(strings.TrimSpace(lines[1]))
setStanza := func(fields ...string) []byte {
return edit(func(ls []string) []string { ls[1] = strings.Join(fields, " ") + "\n"; return ls })
}
add(0, 1000, uint64(1000), "", setStanza("->", "tlock", "1001", stanza[3]))
add(0, 1000, uint64(1000), "", setStanza("->", "tlock", "01000", stanza[3]))
add(0, 1000, uint64(1000), "", setStanza("->", "tlock", "1000", stanza[3][:63]+"0"))
add(0, 1000, uint64(1000), "", setStanza("->", "tlock", "1000"))
add(0, 1000, uint64(1000), "", setStanza("->", "tlock", "1000", stanza[3], "x"))
add(0, 1000, uint64(1000), "", setStanza("->", "tlocK", "1000", stanza[3]))
add(0, 1000, uint64(1000), "", setStanza("->", "X25519", "1000", stanza[3]))
add(0, 1000, uint64(1000), "", edit(func(ls []string) []string {
return slices.Insert(ls, 1, "-> X25519 "+base64.RawStdEncoding.EncodeToString(patterned("share", 32))+"\n", base64.RawStdEncoding.EncodeToString(patterned("body", 32))+"\n")
}))
add(0, 1000, uint64(1000), "", edit(func(ls []string) []string { return slices.Insert(ls, 1, slices.Clone(ls[1:len(ls)-1])...) }))
add(0, 1000, uint64(1000), "", edit(func(ls []string) []string { ls[0] = "age-encryption.org/v2\n"; return ls }))
add(0, 1000, uint64(1000), "", edit(func(ls []string) []string { ls[len(ls)-1] = "--- " + strings.Repeat("A", 43) + "\n"; return ls }))
add(0, 1000, uint64(1000), "", edit(func(ls []string) []string { ls[len(ls)-1] = "---\n"; return ls }))
add(0, 1000, uint64(1000), "", edit(func(ls []string) []string { return slices.Delete(ls, 2, 3) }))
add(0, 1000, uint64(1000), "", s[:end])
add(0, 1000, uint64(1000), "", s[:end+15])
add(0, 1000, uint64(1000), "", s[:end+16])
add(0, 1000, uint64(1000), "", s[:end+16+15])
add(0, 1000, uint64(1000), "", s[:len(s)-1])
add(0, 1000, uint64(1000), "", append(slices.Clone(s), 0))
add(0, 1000, uint64(1000), "", nil)
// The body of the stanza: U not on the curve, U at infinity, V and W
// changed, so that only the IBE check finds it.
bodyText := ""
for _, l := range lines[2 : len(lines)-1] {
bodyText += strings.TrimSuffix(l, "\n")
}
body := must(base64.RawStdEncoding.DecodeString(bodyText))
if len(body) != 128 {
log.Fatalf("a tlock body of %d bytes", len(body))
}
for _, f := range []func(b []byte){
func(b []byte) { b[95] ^= 1 },
func(b []byte) { copy(b, append([]byte{0xc0}, make([]byte, 95)...)) },
func(b []byte) { b[100] ^= 1 },
func(b []byte) { b[127] ^= 1 },
func(b []byte) { b[0] ^= 0x20 },
} {
nb := slices.Clone(body)
f(nb)
enc := base64.RawStdEncoding.EncodeToString(nb)
add(0, 1000, uint64(1000), "", edit(func(ls []string) []string {
body := []string{enc[:64] + "\n", enc[64:128] + "\n", enc[128:] + "\n"}
return slices.Concat(ls[:2], body, ls[len(ls)-1:])
}))
}
// The payload, byte by byte at random.
r := newRand("open")
for i := 0; i < 48; i++ {
b := slices.Clone(s)
at := r.IntN(len(b))
switch r.IntN(4) {
case 0, 1:
b[at] ^= 1 << r.IntN(8)
case 2:
b = slices.Delete(b, at, at+1)
case 3:
b = slices.Insert(b, at, byte(r.IntN(256)))
}
add(0, 1000, uint64(1000), "", b)
}
return sealed, rounds, out
}
// limitCases are files of round 1000 whose plaintext is the first base and
// zeros after it, in chunks of the STREAM that this program writes, with a
// file key it knows: Go reads at most 1 MiB of plaintext through
// io.LimitReader, so a defect after it is never seen.
func (w *world) limitCases(plain []byte) map[string]any {
setCryptoRand("limit")
rec := &recorder{r: cryptorand.Reader}
cryptorand.Reader = rec
r := must(agewrap.NewTimeRecipient(w.p, 1000))
var buf bytes.Buffer
wr := must(age.Encrypt(&buf, r))
if err := wr.Close(); err != nil {
log.Fatal(err)
}
setCryptoRand("limit after")
file := buf.Bytes()
end := must(headerEnd(file))
fileKey, nonce := rec.got[:16], rec.got[len(rec.got)-16:]
if !bytes.Equal(file[end:end+16], nonce) {
log.Fatal("the nonce is not the last read")
}
streamKey := must(hkdf.Key(sha256.New, fileKey, nonce, "payload", 32))
aead := must(chacha20poly1305.New(streamKey))
const chunk = 64 << 10
content := func(n int) []byte {
b := make([]byte, n)
copy(b, plain)
return b
}
type chunkSpec struct {
n int
last bool
corrupt bool
}
build := func(chunks []chunkSpec, trailing int) []byte {
out := slices.Clone(file[:end+16])
total := 0
for _, c := range chunks {
total += c.n
}
text := content(total)
at := 0
for i, c := range chunks {
var n [12]byte
binary.BigEndian.PutUint64(n[3:11], uint64(i))
if c.last {
n[11] = 1
}
ct := aead.Seal(nil, n[:], text[at:at+c.n], nil)
if c.corrupt {
ct[0] ^= 1
}
out = append(out, ct...)
at += c.n
}
return append(out, make([]byte, trailing)...)
}
full := func(k int) []chunkSpec { return slices.Repeat([]chunkSpec{{n: chunk}}, k) }
var cases []any
add := func(name string, chunks []chunkSpec, trailing int) {
b := build(chunks, trailing)
_, err := locator.Open(w.p, 1000, w.release(1000), b)
cs := []any{}
for _, c := range chunks {
cs = append(cs, []any{c.n, c.last, c.corrupt})
}
cases = append(cases, map[string]any{"name": name, "chunks": cs, "trailing": trailing, "length": len(b), "sha256": sum(b), "text": w.texts.of(err)})
}
last := func(cs []chunkSpec) []chunkSpec { cs[len(cs)-1].last = true; return cs }
add("the first base in one chunk", []chunkSpec{{n: len(plain), last: true}}, 0)
add("16 chunks, the last of full length: 1 MiB", last(full(16)), 0)
add("16 chunks and a byte in a 17th", append(full(16), chunkSpec{n: 1, last: true}), 0)
add("16 chunks and a 17th that does not authenticate, which Go never reads", append(full(16), chunkSpec{n: 1, last: true, corrupt: true}), 0)
add("16 chunks and an empty 17th, which Go never reads", append(full(16), chunkSpec{n: 0, last: true}), 0)
add("16 chunks and nothing after them, which Go never reads", full(16), 0)
add("the 16th chunk does not authenticate", append(append(full(15), chunkSpec{n: chunk, corrupt: true}), chunkSpec{n: 1, last: true}), 0)
add("16 chunks, the last of full length, and a byte after it, which Go never reads", last(full(16)), 1)
add("15 chunks, the last of full length, and a byte after it", last(full(15)), 1)
add("15 chunks and a short 16th", append(full(15), chunkSpec{n: 10, last: true}), 0)
add("15 chunks and an empty 16th", append(full(15), chunkSpec{n: 0, last: true}), 0)
add("15 chunks and nothing after them", full(15), 0)
add("17 chunks, the last of full length", last(full(17)), 0)
add("2 chunks, the first marked as the last", []chunkSpec{{n: chunk, last: true}, {n: 5, last: true}}, 0)
return map[string]any{"round": 1000, "header": hx(file[:end]), "nonce": hx(nonce), "file_key": hx(fileKey), "stream_key": hx(streamKey), "cases": cases}
}
// ---------------------------------------------------------------------------
// The envelope
// envelopeCases gives OpenEnvelope of the locator of the envelope, each field
// edited, on its rest, edited: [name, key, header edits, rest digest, rest
// size, capsule digest, rest edits, text, SHA-256 of the .dkc]; a field
// empty is that of the envelope.
func (w *world) envelopeCases() []any {
t := w.texts
var out []any
other := must(age.GenerateX25519Identity())
otherRaw := must(agewrap.RawX25519Identity(other))
add := func(name string, mutate func(l *locator.Locator), rest []byte) {
l := *w.loc
l.EnvelopeHeader = slices.Clone(l.EnvelopeHeader)
mutate(&l)
dkc, err := l.OpenEnvelope(rest)
c := []any{name, "", edits(w.loc.EnvelopeHeader, l.EnvelopeHeader), "", l.RestSize, "", edits(w.rest, rest), t.of(err), ""}
if l.EnvelopeKey != w.loc.EnvelopeKey {
c[1] = hx(l.EnvelopeKey[:])
}
if l.RestDigest != w.loc.RestDigest {
c[3] = hx(l.RestDigest[:])
}
if l.CapsuleDigest != w.loc.CapsuleDigest {
c[5] = hx(l.CapsuleDigest[:])
}
if err == nil {
c[8] = sum(dkc)
}
out = append(out, c)
}
none := func(*locator.Locator) {}
rest := w.rest
flip := func(b []byte, at int) []byte { b = slices.Clone(b); b[at] ^= 1; return b }
withDigest := func(r []byte) func(*locator.Locator) {
return func(l *locator.Locator) { l.RestDigest = sha256.Sum256(r); l.RestSize = uint64(len(r)) }
}
add("the rest", none, rest)
add("a byte of the rest changed", none, flip(rest, len(rest)/2))
add("the rest cut by a byte", none, rest[:len(rest)-1])
add("a byte after the rest", none, append(slices.Clone(rest), 0))
add("no rest", none, nil)
add("rest_size one byte more", func(l *locator.Locator) { l.RestSize++ }, rest)
add("rest_size of 2^53 - 1", func(l *locator.Locator) { l.RestSize = 1<<53 - 1 }, rest)
add("resto_digest of another rest", func(l *locator.Locator) { l.RestDigest[0] ^= 1 }, rest)
add("capsule_digest of another .dkc", func(l *locator.Locator) { l.CapsuleDigest[31] ^= 1 }, rest)
add("the key of another envelope", func(l *locator.Locator) { copy(l.EnvelopeKey[:], otherRaw) }, rest)
add("the key and the digest wrong", func(l *locator.Locator) { copy(l.EnvelopeKey[:], otherRaw); l.CapsuleDigest[0] ^= 1 }, rest)
// A rest changed whose digest and size the locator gives: age finds it.
for _, at := range []int{0, 15, 16, 17, len(rest) / 2, len(rest) - 17, len(rest) - 1} {
r := flip(rest, at)
add(fmt.Sprintf("the byte %d of the rest changed, with its digest", at), withDigest(r), r)
}
for _, n := range []int{0, 1, 15, 16, 17, 32, len(rest) - 16, len(rest) - 1} {
r := rest[:n]
add(fmt.Sprintf("the rest cut to %d bytes, with its digest", n), withDigest(r), r)
}
r := append(slices.Clone(rest), 7)
add("a byte after the rest, with its digest", withDigest(r), r)
// The header, edited.
header := w.loc.EnvelopeHeader
lines := strings.SplitAfter(string(header), "\n")
lines = lines[:len(lines)-1]
setHeader := func(h string) func(*locator.Locator) {
return func(l *locator.Locator) { l.EnvelopeHeader = []byte(h) }
}
add("the MAC of the header changed", setHeader(strings.Join(lines[:len(lines)-1], "")+"--- "+strings.Repeat("A", 43)+"\n"), rest)
add("the header without its MAC line", setHeader(strings.Join(lines[:len(lines)-1], "")), rest)
add("the header without its last line feed", setHeader(string(header[:len(header)-1])), rest)
add("an X25519 stanza of another share", setHeader(strings.Replace(string(header), lines[1][10:20], strings.Repeat("A", 10), 1)), rest)
add("the intro line of another version", setHeader("age-encryption.org/v2\n"+strings.Join(lines[1:], "")), rest)
add("one byte of header", setHeader("a"), rest)
add("a scrypt stanza", setHeader("age-encryption.org/v1\n-> scrypt "+base64.RawStdEncoding.EncodeToString(patterned("salt", 16))+" 10\n"+
base64.RawStdEncoding.EncodeToString(patterned("body", 32))+"\n--- "+strings.Repeat("A", 43)+"\n"), rest)
return out
}
// restInCases gives RestIn on two resources, the host with the rest of the
// envelope after its 300 bytes and nothing: [resource, offset, rest_size,
// text, SHA-256 of the rest read].
func (w *world) restInCases() ([]byte, []any) {
t := w.texts
var out []any
host := patterned("host file", 300)
file := append(slices.Clone(host), w.rest...)
add := func(resource []byte, offset, size uint64) {
l := *w.loc
l.RestSize = size
r, err := l.RestIn(resource, offset)
which := 0
if resource == nil {
which = 1
}
c := []any{which, offset, size, t.of(err), ""}
if err == nil {
c[4] = sum(r)
}
out = append(out, c)
}
n := uint64(len(file))
rs := w.loc.RestSize
for _, o := range []uint64{0, 1, 299, 300, 301, n - rs - 1, n - rs, n - rs + 1, n - 1, n, n + 1, 1 << 53} {
add(file, o, rs)
}
add(file, 300, 0)
add(file, n, 0)
add(file, n+1, 0)
add(file, 0, n)
add(file, 0, n+1)
add(nil, 0, 0)
add(nil, 0, 1)
add(file, 1, 1<<53)
return file, out
}
// hideCases gives Hide of hosts and rests: [host, rest, file, offset].
func hideCases() []any {
var out []any
for _, c := range [][2][]byte{{nil, nil}, {nil, []byte("rest")}, {[]byte("host"), nil}, {patterned("h", 100), patterned("r", 50)}} {
f, off := locator.Hide(c[0], c[1])
out = append(out, []any{hx(c[0]), hx(c[1]), hx(f), off})
}
return out
}
// splitCases gives the split of an age file, as NewEnvelope splits it with
// headerEnd: [file, text, end]. NewEnvelope itself writes the envelope with
// age; the file of the envelope is the first case.
func (w *world) splitCases() []any {
t := w.texts
var out []any
add := func(b []byte) {
n, err := headerEnd(b)
out = append(out, []any{hx(b), t.of(err), n})
}
f := w.file
end := len(w.loc.EnvelopeHeader)
add(f)
add(f[:end])
add(f[:end-1])
add(f[:end-2])
add(nil)
add([]byte("--- x\n"))
add([]byte("\n--- x\n"))
add([]byte("\n--- x"))
add([]byte("\n--- \n"))
add([]byte("a\n---x\n--- y\nz"))
add([]byte("a\n--- y\n--- z\n"))
return out
}
// ---------------------------------------------------------------------------
// The extension
// infoCases gives Info.Extension: [note, profile_id, round, sealed, text,
// length of the data, its SHA-256]; sealed is a hexadecimal, an index into
// the sealed bases, or the number of zeros; the data of the first cases
// that write one are in datas.
func (w *world) infoCases(sealed [][]byte, rounds []uint64) []any {
t := w.texts
var out []any
dk := func(round uint64) datekey.DateKey {
return must(datekey.Resolve(w.p, must(datekey.RoundTime(w.p, round))))
}
add := func(note string, d datekey.DateKey, s any) {
var b []byte
switch x := s.(type) {
case nil:
case int:
b = sealed[x]
case []byte:
b = x
case string:
n := must(strconv.Atoi(x))
b = make([]byte, n)
s = map[string]any{"zeros": n}
}
if x, ok := s.([]byte); ok {
s = hx(x)
}
x, err := (&locator.Info{Note: note, DateKey: d, Sealed: b}).Extension()
c := []any{note, d.ProfileID, d.Round, s, t.of(err), 0, ""}
if err == nil {
if x.ID != extension.CapsuleID || x.Version != 1 {
log.Fatal("not datekeys.capsule")
}
c[5], c[6] = len(x.Data), sum(x.Data)
if len(w.datas) < 4 {
w.datas = append(w.datas, x.Data)
}
}
out = append(out, c)
}
d1000 := dk(1000)
add("", d1000, nil)
add("Cartas del viaje a Lisboa", d1000, nil)
add("Cartas del viaje a Lisboa", d1000, 0)
add("", d1000, 0)
for i, round := range rounds {
add("n", dk(round), i)
if round != 1000 {
add("n", d1000, i)
}
}
add("", d1000, []byte("an age file"))
add("", d1000, []byte{})
add("", d1000, "1048576")
add("", d1000, "1048577")
add("a\tb", d1000, nil)
add("a\nb", d1000, nil)
add(" a", d1000, nil)
add(strings.Repeat("n", 1024), d1000, nil)
add(strings.Repeat("n", 1025), d1000, nil)
add("\xc3\xb1and\xc3\xba", d1000, nil)
add("\xe2\x80\xae", d1000, nil)
add("", datekey.DateKey{ProfileID: "datekeys:quicknet:v1", Round: 0}, nil)
add("", datekey.DateKey{ProfileID: "datekeys:quicknet:v1", Round: 1 << 53}, nil)
add("", datekey.DateKey{ProfileID: "datekeys:quicknet:v1", Round: 1<<53 - 1}, nil)
add("", datekey.DateKey{ProfileID: "Datekeys:quicknet:v1", Round: 1000}, nil)
add("", datekey.DateKey{ProfileID: "datekeys:other:v1", Round: 1000}, nil)
add("", datekey.DateKey{}, nil)
add("a\tb", datekey.DateKey{}, []byte{})
return out
}
// openInfoCases gives Info.OpenLocator with the default registry of Go:
// [profile_id, round, sealed, release, text, summary]; sealed is an index
// into the sealed bases or null.
func (w *world) openInfoCases(sealed [][]byte) []any {
reg := must(profile.Default())
dk := func(round uint64) datekey.DateKey {
return must(datekey.Resolve(w.p, must(datekey.RoundTime(w.p, round))))
}
var out []any
add := func(d datekey.DateKey, base any, release uint64) {
var b []byte
if i, ok := base.(int); ok {
b = sealed[i]
}
l, err := (&locator.Info{DateKey: d, Sealed: b}).OpenLocator(reg, w.release(release))
var s any
if err == nil {
s = summary(l)
}
out = append(out, []any{d.ProfileID, d.Round, base, release, w.texts.of(err), s})
}
add(dk(1000), 0, 1000)
add(dk(1000), nil, 1000)
add(datekey.DateKey{ProfileID: "datekeys:other:v1", Round: 1000}, 0, 1000)
add(dk(1001), 1, 1001)
add(dk(1001), 1, 1000)
add(dk(1000), 1, 1000)
add(dk(2000), 2, 2000)
return out
}
// parseCase is ParseInfo: [id, version, base, edits, text, note, compact
// datekey, round, SHA-256 of sealed or null]: the data is the edits of the
// base of parse_bases, or absent when base is -1.
func (w *world) parseCase(bases [][]byte, id string, version uint64, data []byte) []any {
x := extension.Extension{ID: id, Version: version, Data: data}
info, err := locator.ParseInfo(x)
base, d := -1, [][]any{}
if data != nil {
// The base whose edits are the shortest.
best := -1
for i, b := range bases {
e := edits(b, data)
if n := len(asciiJSON(e)); best < 0 || n < best {
base, best, d = i, n, e
}
}
}
c := []any{id, version, base, d, w.texts.of(err), "", "", 0, nil}
if err != nil && !errors.Is(err, datekeys.ErrExtensionDataInvalid) {
log.Fatalf("ParseInfo: %v", err)
}
if err == nil {
c[5], c[6], c[7] = info.Note, info.DateKey.Compact(), info.DateKey.Round
if info.Sealed != nil {
c[8] = sum(info.Sealed)
}
}
return c
}
func (w *world) parseCases(sealed [][]byte) ([][]byte, []any) {
var out []any
var bases [][]byte
add := func(data []byte) { out = append(out, w.parseCase(bases, extension.CapsuleID, 1, data)) }
dk := must(datekey.Resolve(w.p, must(datekey.RoundTime(w.p, 1000)))).Compact()
build := func(f func(e *enc)) []byte { var e enc; f(&e); return e.b }
pairs := func(ps ...rawPair) []byte {
return build(func(e *enc) {
e.mapOf(uint64(len(ps)))
for _, p := range ps {
e.uint(p.key)
p.val(e)
}
})
}
text := func(k uint64, s string) rawPair { return rawPair{k, func(e *enc) { e.text(s) }} }
bstr := func(k uint64, b []byte) rawPair { return rawPair{k, func(e *enc) { e.bstr(b) }} }
good := pairs(text(0, "Cartas"), text(1, dk), bstr(2, sealed[0]))
bases = [][]byte{{}, good}
for _, x := range sealed[1:] {
bases = append(bases, pairs(text(1, dk), bstr(2, x)))
}
out = append(out, w.parseCase(bases, extension.NoteID, 1, good), w.parseCase(bases, extension.CapsuleID, 2, good),
w.parseCase(bases, extension.CapsuleID, 0, good), w.parseCase(bases, extension.CapsuleID, 1, nil), w.parseCase(bases, "datekeys.capsulE", 1, good))
add(good)
add(pairs(text(1, dk)))
add(pairs(text(0, "n"), text(1, dk)))
add(pairs(text(1, dk), bstr(2, sealed[0])))
add(pairs())
add(pairs(text(0, "n")))
add(pairs(text(0, ""), text(1, dk)))
add(pairs(text(0, strings.Repeat("n", 1024)), text(1, dk)))
add(pairs(text(0, strings.Repeat("n", 1025)), text(1, dk)))
add(pairs(text(0, "a\tb"), text(1, dk)))
add(pairs(text(0, "a "), text(1, dk)))
add(pairs(rawPair{0, func(e *enc) { e.head(3, 2, 0); e.raw([]byte{0xff, 0xfe}) }}, text(1, dk)))
add(pairs(rawPair{0, func(e *enc) { e.uint(5) }}, text(1, dk)))
add(pairs(text(1, dk), text(0, "n")))
add(pairs(text(1, dk), text(1, dk)))
add(pairs(text(1, dk), bstr(3, []byte{0})))
add(pairs(text(1, dk), text(1<<53, "x")))
add(pairs(text(1, "")))
add(pairs(text(1, "dk1_")))
add(pairs(text(1, dk+"A")))
add(pairs(text(1, strings.Repeat("d", 1024))))
add(pairs(text(1, strings.Repeat("d", 1025))))
add(pairs(bstr(1, []byte(dk))))
add(pairs(text(1, strings.ToUpper(dk[:4])+dk[4:])))
// A DateKey whose JSON has its members in another order (spec §19).
raw := must(base64.RawURLEncoding.DecodeString(strings.TrimPrefix(dk, datekey.Prefix)))
var members map[string]any
if err := json.Unmarshal(raw, &members); err != nil {
log.Fatal(err)
}
add(pairs(text(1, datekey.Prefix+base64.RawURLEncoding.EncodeToString(must(json.Marshal(members))))))
add(pairs(text(1, datekey.Prefix+base64.URLEncoding.EncodeToString(raw))))
// The locator: empty, another round, stanzas, garbage.
add(pairs(text(1, dk), bstr(2, nil)))
add(pairs(text(1, dk), bstr(2, []byte{0})))
for i := range sealed {
add(pairs(text(1, dk), bstr(2, sealed[i])))
}
s := sealed[0]
end := must(headerEnd(s))
add(pairs(text(1, dk), bstr(2, s[:end])))
add(pairs(text(1, dk), bstr(2, s[:end-1])))
lines := strings.SplitAfter(string(s[:end]), "\n")
lines = lines[:len(lines)-1]
stanza := strings.Fields(strings.TrimSpace(lines[1]))
withLine := func(i int, line string) []byte {
ls := slices.Clone(lines)
ls[i] = line
return append([]byte(strings.Join(ls, "")), s[end:]...)
}
for _, line := range []string{"-> tlock 01000 " + stanza[3] + "\n", "-> tlock 1000\n", "-> tlock 1000 " + stanza[3] + " x\n",
"-> tlock 1000 " + strings.Repeat("0", 64) + "\n", "-> X25519 1000 " + stanza[3] + "\n", "-> tlock 1000 " + stanza[3] + "\n"} {
add(pairs(text(1, dk), bstr(2, withLine(1, line))))
}
add(pairs(text(1, dk), bstr(2, withLine(len(lines)-1, "--- "+strings.Repeat("A", 43)+"\n"))))
two := slices.Clone(lines)
two = slices.Insert(two, 1, slices.Clone(two[1:len(two)-1])...)
add(pairs(text(1, dk), bstr(2, append([]byte(strings.Join(two, "")), s[end:]...))))
// Data edited at random.
r := newRand("parse")
for i := 0; i < 80; i++ {
b := slices.Clone(good)
at := r.IntN(min(len(b), 600))
switch r.IntN(4) {
case 0, 1:
b[at] ^= 1 << r.IntN(8)
case 2:
b = slices.Delete(b, at, at+1)
case 3:
b = b[:at]
}
add(b)
}
return bases, out
}
// registryCases give the registry of locator.Standard on datekeys.capsule
// in a .dkk: [id, version, index into datas or -1 for none,
// CheckNoncriticalIn, CheckCriticalIn,
// CheckWrite noncritical, CheckWrite critical, CheckWrite noncritical with
// extension.Standard]; CheckNoncriticalIn as the texts of the unusable.
func (w *world) registryCases(datas [][]byte) []any {
t := w.texts
std := locator.Standard()
var out []any
add := func(x extension.Extension) {
exts := []extension.Extension{x}
unusable := []any{}
for _, u := range extension.CheckNoncriticalIn(extension.AccessKey, exts, std) {
if u.ID != x.ID || u.Version != x.Version || !errors.Is(u.Err, datekeys.ErrExtensionDataInvalid) {
log.Fatal("an unusable extension of another kind")
}
unusable = append(unusable, t.of(u.Err))
}
d := slices.IndexFunc(datas, func(d []byte) bool { return x.Data != nil && bytes.Equal(d, x.Data) })
out = append(out, []any{x.ID, x.Version, d, unusable,
t.of(extension.CheckCriticalIn(extension.AccessKey, exts, std)),
t.of(extension.CheckWrite(std, extension.AccessKey, extension.Noncritical, exts)),
t.of(extension.CheckWrite(std, extension.AccessKey, extension.Critical, exts)),
t.of(extension.CheckWrite(extension.Standard{}, extension.AccessKey, extension.Noncritical, exts))})
}
for _, d := range datas {
add(extension.Extension{ID: extension.CapsuleID, Version: 1, Data: d})
}
add(extension.Extension{ID: extension.CapsuleID, Version: 1})
add(extension.Extension{ID: extension.CapsuleID, Version: 2, Data: []byte{0xa0}})
return out
}
// capsuleCases open a fixture of format 2 with its .dkk carrying
// datekeys.capsule, with locator.Standard: the result, the unusable
// extensions of the .dkk and the checks.
func (w *world) capsuleCases(testdata string, datas [][]byte) map[string]any {
t := w.texts
const name = "format2_time_and_key_portable"
dkc := must(os.ReadFile(filepath.Join(testdata, "fixtures", name+".dkc")))
dkk := must(os.ReadFile(filepath.Join(testdata, "fixtures", name+".dkk")))
now := time.Date(2026, 10, 6, 0, 0, 0, 0, time.UTC)
reg := must(profile.Default())
var cases []any
add := func(exts []extension.Extension, std bool) {
k := must(accesskey.Decode(bytes.NewReader(dkk)))
k.Noncritical = exts
var buf bytes.Buffer
if err := accesskey.Encode(&buf, k); err != nil {
log.Fatal(err)
}
opts := capsule.OpenOptions{Registry: reg, Source: fixed{w.release(1000)}, Now: func() time.Time { return now }, AccessKeyFile: bytes.NewReader(buf.Bytes())}
if std {
opts.Extensions = locator.Standard()
}
var out bytes.Buffer
opened, err := capsule.Open(context.Background(), &out, bytes.NewReader(dkc), opts)
unusable := []any{}
checks := []any{}
if opened != nil {
for _, u := range opened.UnusableAccessKeyExtensions {
unusable = append(unusable, []any{u.ID, u.Version, t.of(u.Err)})
}
for _, c := range opened.Inspection.Checks {
checks = append(checks, []any{c.Step, c.Name, c.OK, c.Detail, c.Error})
}
}
var spec []any
for _, x := range exts {
i := slices.IndexFunc(datas, func(d []byte) bool { return bytes.Equal(d, x.Data) })
spec = append(spec, []any{x.ID, x.Version, i, hx(x.Data)})
}
if spec == nil {
spec = []any{}
}
cases = append(cases, map[string]any{"noncritical": spec, "dkk_sha256": sum(buf.Bytes()), "standard": std, "text": t.of(err), "unusable": unusable,
"checks": checks, "content": sum(out.Bytes())})
}
for _, d := range datas {
x := extension.Extension{ID: extension.CapsuleID, Version: 1, Data: d}
add([]extension.Extension{x}, true)
}
add([]extension.Extension{{ID: extension.CapsuleID, Version: 1, Data: datas[0]}, {ID: "org.example", Version: 1, Data: []byte{1}}}, true)
add([]extension.Extension{{ID: extension.CapsuleID, Version: 1, Data: datas[len(datas)-1]}}, false)
add(nil, true)
return map[string]any{"fixture": name, "now": now.Format(time.RFC3339Nano), "cases": cases}
}
type fixed struct{ r provider.Release }
func (f fixed) Fetch(context.Context, *profile.Profile, provider.Condition) (provider.Release, error) {
return f.r, nil
}
// ---------------------------------------------------------------------------
// testdata/vectors/locator.json
type locatorFile struct {
Spec string `json:"spec"`
Round uint64 `json:"round"`
Sealed string `json:"locator_sealed"`
Plaintext string `json:"locator_plaintext"`
Extension string `json:"extension_data"`
Host string `json:"host"`
EnvelopeHeader string `json:"envelope_header"`
Rest string `json:"rest"`
URICases []struct {
URI string `json:"uri"`
OK bool `json:"ok"`
} `json:"uri_cases"`
Mixed struct {
Plaintext string `json:"locator_plaintext"`
Sealed string `json:"locator_sealed"`
} `json:"mixed"`
RestCases []struct {
Name string `json:"name"`
Resource string `json:"resource"`
Offset uint64 `json:"offset"`
Opens bool `json:"opens"`
} `json:"rest_cases"`
ExtensionCases []struct {
Name string `json:"name"`
Data string `json:"extension_data"`
OK bool `json:"ok"`
} `json:"extension_cases"`
PlaintextCases []struct {
Name string `json:"name"`
Plaintext string `json:"locator_plaintext"`
OK bool `json:"ok"`
} `json:"plaintext_cases"`
}
func unhex(s string) []byte { return must(hex.DecodeString(s)) }
// testdataCases give the texts of the cases of locator.json: the addresses
// in locator_uris.json, and here the extensions, the plaintexts and the
// rests.
func (w *world) testdataCases(v *locatorFile, uriTexts *textTable) ([]any, map[string]any) {
var uris []any
for _, c := range v.URICases {
u := uriCase(uriTexts, c.URI)
if (u[1] == 0) != c.OK {
log.Fatalf("%q: not the verdict of locator.json", c.URI)
}
uris = append(uris, u)
}
t := w.texts
main, err := locator.Open(w.p, v.Round, w.release(v.Round), unhex(v.Sealed))
if err != nil {
log.Fatal(err)
}
mixed, err := locator.Open(w.p, v.Round, w.release(v.Round), unhex(v.Mixed.Sealed))
if err != nil {
log.Fatal(err)
}
ext := []any{}
for _, c := range v.ExtensionCases {
_, err := locator.ParseInfo(extension.Extension{ID: extension.CapsuleID, Version: 1, Data: unhex(c.Data)})
if (err == nil) != c.OK {
log.Fatalf("%s: not the verdict of locator.json", c.Name)
}
ext = append(ext, t.of(err))
}
plain := []any{}
for _, c := range v.PlaintextCases {
_, err := locator.Unmarshal(unhex(c.Plaintext))
if (err == nil) != c.OK {
log.Fatalf("%s: not the verdict of locator.json", c.Name)
}
plain = append(plain, t.of(err))
}
rest := []any{}
for _, c := range v.RestCases {
r, err := main.RestIn(unhex(c.Resource), c.Offset)
openErr := errors.New("not read")
if err == nil {
_, openErr = main.OpenEnvelope(r)
}
rest = append(rest, []any{t.of(err), t.of(openErr)})
}
return uris, map[string]any{"main": summary(main), "mixed": summary(mixed), "extension_cases": ext, "plaintext_cases": plain, "rest_cases": rest}
}
// releases reads the releases of the fixtures, public data of drand.
func releases(testdata string) map[uint64]provider.Release {
out := map[uint64]provider.Release{}
files := must(filepath.Glob(filepath.Join(testdata, "fixtures", "*.json")))
slices.Sort(files)
for _, f := range files {
var v struct {
Release *struct {
Round uint64 `json:"round"`
Signature string `json:"signature"`
} `json:"release"`
}
if err := json.Unmarshal(must(os.ReadFile(f)), &v); err != nil || v.Release == nil {
continue
}
r := provider.Release{Round: v.Release.Round, Signature: unhex(v.Release.Signature)}
if old, ok := out[r.Round]; ok && !bytes.Equal(old.Signature, r.Signature) {
log.Fatalf("two releases of round %d", r.Round)
}
out[r.Round] = r
}
return out
}
// ---------------------------------------------------------------------------
func writeFile(dir, name string, b []byte) {
if len(b) > 900<<10 {
log.Printf("TOO BIG %s: %d bytes", name, len(b))
}
path := filepath.Join(dir, name)
if err := os.WriteFile(path, b, 0o644); err != nil {
log.Fatal(err)
}
fmt.Printf("wrote %s, %d bytes\n", path, len(b))
}
func dartConstant(name, json, comment string) []byte {
return []byte("// Generated by tool/locator_go_vectors.go, for the tests that also run\n" +
"// compiled to JavaScript, where no file can be read. Do not edit.\n\n" +
"/// " + comment + "\n" +
"const " + name + " = r'''\n" + json + "''';\n")
}
func main() {
testdata := flag.String("testdata", "", "the directory testdata of datekeys-dart")
outDir := flag.String("out", "", "the directory test/vectors of datekeys-dart")
flag.Parse()
if *testdata == "" || *outDir == "" {
log.Fatal("usage: go run locator_go_vectors.go -testdata DIR -out DIR")
}
var v locatorFile
if err := json.Unmarshal(must(os.ReadFile(filepath.Join(*testdata, "vectors", "locator.json"))), &v); err != nil {
log.Fatal(err)
}
w := &world{p: profile.Quicknet(), texts: newTexts(), releases: releases(*testdata)}
setCryptoRand("envelope")
w.dkc = patterned("locator dkc", 777)
w.loc, w.rest = must2(locator.NewEnvelope(w.dkc))
w.file = append(slices.Clone(w.loc.EnvelopeHeader), w.rest...)
w.loc.Addresses = []locator.Address{{URI: "https://ejemplo.org/foto.jpg", Offset: 3000}, {URI: "ipfs://" + locatorCID}}
// The addresses.
ut := newTexts()
testdataURIs, testdataRest := w.testdataCases(&v, ut)
uris := uriCases(ut, newRand("uris"))
ips := ipCases(newRand("ips"))
public := publicCases(newRand("public"))
wtf8 := wtf8Cases(ut)
ud := newDocument()
ud.value("description", "The addresses of a locator (spec v0.12 §44.1) for lib/src/locator.dart; see tool/locator_go_vectors.go. testdata: [uri, text, host] of locator.CheckURI and "+
"Address.Host on the uri_cases of testdata/vectors/locator.json, in their order; uris: the same on addresses built at the edges of §44.1 and drawn from a seed; "+
"wtf8: [bytes, text] of CheckURI on bytes with a surrogate. "+
"A text is an index into texts, 0 for none. ips: [s, false] or [s, true, bytes, zone, String, publicIP] of netip.ParseAddr. public: [bytes, publicIP, String] "+
"of netip.AddrFromSlice, or [bytes, null] for a length that is no address.")
ud.value("generator", "tool/locator_go_vectors.go, "+runtime.Version())
ud.list("texts", anyList(ut.list))
ud.list("testdata", testdataURIs)
ud.list("uris", uris)
ud.list("wtf8", wtf8)
ud.list("ips", ips)
ud.list("public", public)
uj := ud.bytes()
writeFile(*outDir, "locator_uris.json", uj)
writeFile(*outDir, "locator_uris.g.dart", dartConstant("locatorUrisJson", string(uj), "The text of test/vectors/locator_uris.json."))
// The locator.
plainBases, raws := w.plaintextBases()
plaintexts := w.plaintextCases(plainBases, raws)
marshal := w.marshalCases()
sealed, rounds, opens := w.openCases(plainBases)
limit := w.limitCases(plainBases[0])
setCryptoRand("envelopes")
envelopes := w.envelopeCases()
restResource, restIn := w.restInCases()
split := w.splitCases()
setCryptoRand("info")
info := w.infoCases(sealed, rounds)
openInfo := w.openInfoCases(sealed)
parseBases, parse := w.parseCases(sealed)
datas := append(slices.Clone(w.datas), []byte{0xa0}, []byte("Cartas"))
registry := w.registryCases(datas)
capsules := w.capsuleCases(*testdata, datas)
rel := map[string]string{}
for _, round := range []uint64{1000, 1001, 1004, 2000} {
rel[strconv.FormatUint(round, 10)] = hx(w.release(round).Signature)
}
envelope := map[string]any{"dkc": hx(w.dkc), "key": hx(w.loc.EnvelopeKey[:]), "header": hx(w.loc.EnvelopeHeader), "rest": hx(w.rest),
"rest_digest": hx(w.loc.RestDigest[:]), "rest_size": w.loc.RestSize, "capsule_digest": hx(w.loc.CapsuleDigest[:])}
var sealedBases []any
for i := range sealed {
sealedBases = append(sealedBases, map[string]any{"round": rounds[i], "sealed": hx(sealed[i])})
}
section := func(d *document, full bool) {
d.value("generator", "tool/locator_go_vectors.go, "+runtime.Version())
d.list("texts", anyList(w.texts.list))
d.value("releases", rel)
d.value("envelope", envelope)
d.list("padding", paddingRuns())
d.value("testdata", testdataRest)
d.list("plaintext_bases", anyList(mapHex(plainBases)))
d.list("sealed_bases", sealedBases)
d.list("parse_bases", anyList(mapHex(parseBases)))
d.list("datas", anyList(mapHex(datas)))
d.value("rest_in_resource", hx(restResource))
if full {
d.list("plaintexts", plaintexts)
d.list("marshal", marshal)
d.list("open", opens)
d.value("limit", limit)
d.list("envelopes", envelopes)
d.list("rest_in", restIn)
d.list("hide", hideCases())
d.list("split", split)
d.list("info", info)
d.list("open_info", openInfo)
d.list("parse", parse)
d.list("registry", registry)
d.value("capsule", capsules)
return
}
d.list("plaintexts", every(plaintexts, 3))
d.list("marshal", marshal)
d.list("open", opens[:24])
d.list("envelopes", envelopes)
d.list("rest_in", restIn)
d.list("hide", hideCases())
d.list("split", split)
d.list("info", info)
d.list("open_info", openInfo)
d.list("parse", every(parse, 2))
d.list("registry", registry)
}
vd := newDocument()
vd.value("description", "The locator and the envelope of datekeys.capsule (spec v0.12 §44.1) for lib/src/locator.dart; see tool/locator_go_vectors.go for each list. "+
"A text is an index into texts, 0 for none; an edited value is a list of edits [at, delete, insert] of its base.")
section(vd, true)
vj := vd.bytes()
writeFile(*outDir, "locator_vectors.json", vj)
pd := newDocument()
pd.value("description", "Part of test/vectors/locator_vectors.json, for the tests compiled to JavaScript: every third plaintext, the first opens, every second parse, "+
"and the rest but the limit and the capsule.")
section(pd, false)
writeFile(*outDir, "locator_vectors.g.dart", dartConstant("locatorVectorsJson", string(pd.bytes()), "Part of test/vectors/locator_vectors.json."))
}
func must2[A, B any](a A, b B, err error) (A, B) {
if err != nil {
log.Fatal(err)
}
return a, b
}
func mapHex(bs [][]byte) []string {
out := make([]string, len(bs))
for i, b := range bs {
out[i] = hx(b)
}
return out
}
func every(xs []any, n int) []any {
var out []any
for i, x := range xs {
if i%n == 0 {
out = append(out, x)
}
}
return out
}

Powered by TurnKey Linux.