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-App/scripts/locator-go-vectors.go

2486 lines
86 KiB

//go:build ignore
// Prints the vectors of the locator of datekeys-ts, src/lib/dkc/locator.ts,
// ipaddr.ts, ageio.ts and envelope.ts, into src/lib/dkc/testing/: the
// results, the normative codes and the texts of package locator of
// datekeys-go at the tag spec-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 JavaScript string 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 is the generator of the stage 7a of datekeys-dart, with the seeds of
// this repository, so that the cases drawn are other than those of Dart.
// 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.
//
// It imports only public packages of the reference implementation, and runs
// in a module of it without changing anything there: an export of the tag
// spec-v0.12, so that no change in progress in datekeys-go is read.
//
// git -C ../datekeys-go archive spec-v0.12 | tar -x -C /tmp/dkgo
// (cd /tmp/dkgo && go run .../datekeys-ts/scripts/locator-go-vectors.go \
// -testdata .../datekeys-ts/testdata -out .../datekeys-ts/src/lib/dkc/testing)
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-ts locator: " + 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, escaped as the generator of datekeys-dart escapes it.
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 JavaScript string 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 main() {
testdata := flag.String("testdata", "", "the directory testdata of datekeys-ts")
outDir := flag.String("out", "", "the directory src/lib/dkc/testing of datekeys-ts")
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 src/lib/dkc/locator.ts; see scripts/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", "scripts/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)
// 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])})
}
vd := newDocument()
vd.value("description", "The locator and the envelope of datekeys.capsule (spec v0.12 §44.1) for src/lib/dkc/locator.ts and envelope.ts; see scripts/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.")
vd.value("generator", "scripts/locator-go-vectors.go, "+runtime.Version())
vd.list("texts", anyList(w.texts.list))
vd.value("releases", rel)
vd.value("envelope", envelope)
vd.list("padding", paddingRuns())
vd.value("testdata", testdataRest)
vd.list("plaintext_bases", anyList(mapHex(plainBases)))
vd.list("sealed_bases", sealedBases)
vd.list("parse_bases", anyList(mapHex(parseBases)))
vd.list("datas", anyList(mapHex(datas)))
vd.value("rest_in_resource", hx(restResource))
vd.list("plaintexts", plaintexts)
vd.list("marshal", marshal)
vd.list("open", opens)
vd.value("limit", limit)
vd.list("envelopes", envelopes)
vd.list("rest_in", restIn)
vd.list("hide", hideCases())
vd.list("split", split)
vd.list("info", info)
vd.list("open_info", openInfo)
vd.list("parse", parse)
vd.list("registry", registry)
vd.value("capsule", capsules)
vj := vd.bytes()
writeFile(*outDir, "locator-vectors.json", vj)
}
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
}

Powered by TurnKey Linux.