Stage 6a: the vectors of Go for the age writer

tool/age_writer_go_vectors.go runs in an export of datekeys-go at
c531e93 and makes crypto/rand read the keystream of the seeded source of
the writer: ChaCha20 under SHA-256(seed), with a zero nonce. age.Encrypt,
with the real X25519, scrypt and tlock recipients, then draws known
values, and age_writer.json records every draw, its size and its order,
with the files: one X25519 recipient over plaintexts of 0 bytes to 3 MiB
across the chunk boundaries; two, three and sixteen, and one with bit 255
set, which age accepts; scrypt with work factors 1 to 16; and the tlock
stanza of rounds of 1 to 11 digits.

The generator checks each file against testkit.SealAge, with the first
draw as the file key and the last as the nonce, checks each X25519
stanza against its ephemeral secret, and opens the file with Go. It also
records the errors of age.Encrypt with the draws before them, those of
the constructors, ParseX25519Recipient, CheckX25519Recipient,
GenerateX25519Identity, crypto/rand.Int and the permute of capsule over
the keystream, and the lengths of testkit and capsule. The output is the
same on every run. age_writer.g.dart holds the same JSON for the tests
compiled to JavaScript.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.11
dev 2 days ago
parent a14a3b8d01
commit 6837b9c463

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,746 @@
//go:build ignore
// Writes test/vectors/age_writer.json, the vectors of the age writer of
// datekeys-dart, stage 6a of docs/PLAN_dart.md: age files that
// filippo.io/age v1.3.2 and the agewrap package of datekeys-go write,
// deterministically, and the texts of the errors of the writer.
//
// age.Encrypt draws every random value from crypto/rand: the file key, the
// ephemeral secret of each X25519 stanza, the salt and then the label of a
// scrypt stanza, sigma and then the label of the tlock stanza, and the nonce
// of the payload. Here crypto/rand.Reader reads the keystream of
// SeededRandomSource of lib/src/random.dart instead: ChaCha20 under
// SHA-256(seed), with a zero nonce. With the same seed, the writer of
// datekeys-dart draws the same values, in the same order, and must write the
// same bytes. Every case records the size and the bytes of each draw, and
// the generator checks the files against internal/testkit: testkit.SealAge
// seals the same file with the first draw as the file key and the last as
// the nonce, each X25519 stanza is the one of its ephemeral secret, the
// length is the one of testkit.HeaderLen and testkit.StreamLen, and Go opens
// the file with its identities.
//
// - seeded: the keystream of a few seeds, read in fills of several sizes;
// - rand_int and permute: crypto/rand.Int and the permute of
// capsule.Encrypt, restated because it is not exported, over the
// keystream of a seed;
// - encrypt: age.Encrypt with X25519 recipients (one, two, three and
// sixteen, and one with bit 255 set, which age accepts), scrypt
// recipients of work factor 1, 2, 10 and 16, and the tlock recipient of
// agewrap for rounds of 1 to 11 digits, over plaintexts of 0, 1, 64 KiB
// - 1, 64 KiB, 64 KiB + 1, 128 KiB and more bytes, up to a few MiB. Byte
// i of a plaintext is (31·i + 7) mod 256. A small file is stored whole;
// a large one, as its header, its length and its SHA-256;
// - errors: what age.Encrypt refuses, with its text: no recipient,
// recipients whose labels cannot be mixed and X25519 recipients of low
// order; and NewScryptRecipient, SetWorkFactor and NewTimeRecipient;
// - stream: the writer of age.Encrypt after Close;
// - parse, check and generate: age.ParseX25519Recipient,
// agewrap.CheckX25519Recipient and age.GenerateX25519Identity;
// - lengths: testkit.StreamLen and capsule.PayloadAgeLength.
//
// It also writes test/vectors/age_writer.g.dart, the same JSON as a Dart
// constant, for the tests that also run compiled to JavaScript.
//
// It imports internal/testkit, so it runs in an export of datekeys-go made
// with git archive, without changing the repository, on the branch v0.12 at
// c531e93:
//
// commit=$(git -C ../datekeys-go rev-parse v0.12)
// out=$PWD/test/vectors
// tmp=$(mktemp -d)
// git -C ../datekeys-go archive "$commit" | tar -x -C "$tmp"
// cp tool/age_writer_go_vectors.go "$tmp"
// (cd "$tmp" && go run ./age_writer_go_vectors.go -source "$commit" -out "$out")
// rm -rf "$tmp"
//
// The output is the same on every run.
package main
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"io"
"log"
"math/big"
"os"
"path/filepath"
"runtime"
"strings"
"filippo.io/age"
"golang.org/x/crypto/chacha20"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/hkdf"
"g.activething.com/go/DateKeys/agewrap"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/codec/bech32"
"g.activething.com/go/DateKeys/internal/testkit"
"g.activething.com/go/DateKeys/profile"
"g.activething.com/go/DateKeys/provider"
)
type obj = map[string]any
func h(b []byte) string { return hex.EncodeToString(b) }
func sum(b []byte) string {
s := sha256.Sum256(b)
return h(s[:])
}
func check(err error) {
if err != nil {
_, file, line, _ := runtime.Caller(1)
log.Fatalf("%s:%d: %v", filepath.Base(file), line, err)
}
}
func mustHex(s string) []byte {
b, err := hex.DecodeString(s)
check(err)
return b
}
// pattern is a plaintext of n bytes: byte i is (31·i + 7) mod 256.
func pattern(n int) []byte {
b := make([]byte, n)
for i := range b {
b[i] = byte(31*i + 7)
}
return b
}
// The published Quicknet signatures of rounds 1000 and 1001, as in the
// fixtures; provider.Verify checks them when a file is opened.
var releases = map[uint64]string{
1000: "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
1001: "b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b25883abf2853d10337fb8fa41",
}
// ---------------------------------------------------------------------------
// crypto/rand from a seed
// seeded is the crypto/rand.Reader of a case: the ChaCha20 keystream under
// SHA-256(seed) and a zero nonce, the stream of SeededRandomSource. It keeps
// every read: age reads each value whole, with io.ReadFull.
type seeded struct {
c *chacha20.Cipher
draws [][]byte
}
func newSeeded(seed string) *seeded {
key := sha256.Sum256([]byte(seed))
c, err := chacha20.NewUnauthenticatedCipher(key[:], make([]byte, chacha20.NonceSize))
check(err)
return &seeded{c: c}
}
func (s *seeded) Read(p []byte) (int, error) {
clear(p)
s.c.XORKeyStream(p, p)
s.draws = append(s.draws, bytes.Clone(p))
return len(p), nil
}
// with runs f while crypto/rand reads the keystream of seed, and returns
// the draws.
func with(seed string, f func()) [][]byte {
s := newSeeded(seed)
old := rand.Reader
rand.Reader = s
defer func() { rand.Reader = old }()
f()
return s.draws
}
func drawsOf(d [][]byte) []obj {
out := []obj{}
for _, b := range d {
out = append(out, obj{"n": len(b), "hex": h(b)})
}
return out
}
// ---------------------------------------------------------------------------
// Recipients
// identity is the X25519 identity of a label: its raw secret is
// SHA-256("identity " + label).
func identity(label string) *age.X25519Identity {
s := sha256.Sum256([]byte("identity " + label))
id, err := agewrap.X25519IdentityFromRaw(s[:])
check(err)
return id
}
func recipientOfRaw(raw []byte) *age.X25519Recipient {
s, err := bech32.Encode("age", raw)
check(err)
r, err := age.ParseX25519Recipient(s)
check(err)
return r
}
func rawOf(r *age.X25519Recipient) []byte {
raw, err := agewrap.RawX25519Recipient(r)
check(err)
return raw
}
// spec is a recipient of a case: X25519, scrypt or tlock.
type spec struct {
x25519 *age.X25519Recipient
id *age.X25519Identity // the identity of x25519, when known
pass string
wf int
round uint64
}
func x(label string) spec {
id := identity(label)
return spec{x25519: id.Recipient(), id: id}
}
func xraw(raw []byte) spec { return spec{x25519: recipientOfRaw(raw)} }
func sc(pass string, wf int) spec { return spec{pass: pass, wf: wf} }
func tl(round uint64) spec { return spec{round: round} }
func (s spec) recipient() age.Recipient {
switch {
case s.x25519 != nil:
return s.x25519
case s.pass != "":
r, err := age.NewScryptRecipient(s.pass)
check(err)
r.SetWorkFactor(s.wf)
return r
default:
r, err := agewrap.NewTimeRecipient(profile.Quicknet(), s.round)
check(err)
return r
}
}
func (s spec) json() obj {
switch {
case s.x25519 != nil:
o := obj{"x25519": s.x25519.String()}
if s.id != nil {
o["identity"] = s.id.String()
}
return o
case s.pass != "":
return obj{"scrypt": s.pass, "work_factor": s.wf}
default:
return obj{"tlock": s.round}
}
}
func recipients(specs []spec) []age.Recipient {
var out []age.Recipient
for _, s := range specs {
out = append(out, s.recipient())
}
return out
}
func specsJSON(specs []spec) []obj {
out := []obj{}
for _, s := range specs {
out = append(out, s.json())
}
return out
}
func encrypt(plain []byte, rs []age.Recipient) ([]byte, error) {
var buf bytes.Buffer
w, err := age.Encrypt(&buf, rs...)
if err != nil {
return nil, err
}
if _, err := w.Write(plain); err != nil {
return nil, err
}
if err := w.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// x25519Stanza is the X25519 stanza that wraps fileKey for pub with the
// ephemeral secret eph, restated from age's X25519Recipient.Wrap.
func x25519Stanza(fileKey, pub, eph []byte) *age.Stanza {
share, err := curve25519.X25519(eph, curve25519.Basepoint)
check(err)
secret, err := curve25519.X25519(eph, pub)
check(err)
key := make([]byte, 32)
_, err = io.ReadFull(hkdf.New(sha256.New, secret, append(bytes.Clone(share), pub...), []byte("age-encryption.org/v1/X25519")), key)
check(err)
a, err := chacha20poly1305.New(key)
check(err)
return &age.Stanza{Type: "X25519", Args: []string{base64.RawStdEncoding.EncodeToString(share)}, Body: a.Seal(nil, make([]byte, 12), fileKey, nil)}
}
// crossCheck checks a file that age.Encrypt wrote with draws against
// internal/testkit and Go's identities.
func crossCheck(name string, file []byte, draws [][]byte, plain []byte, specs []spec) {
stanzas, err := agewrap.Stanzas(bytes.NewReader(file))
check(err)
if len(stanzas) != len(specs) {
log.Fatalf("%s: %d stanzas for %d recipients", name, len(stanzas), len(specs))
}
fileKey, nonce := draws[0], draws[len(draws)-1]
if len(fileKey) != 16 || len(nonce) != 16 {
log.Fatalf("%s: a file key of %d bytes and a nonce of %d", name, len(fileKey), len(nonce))
}
again, err := testkit.SealAge(stanzas, fileKey, nonce, plain)
check(err)
if !bytes.Equal(again, file) {
log.Fatalf("%s: testkit.SealAge writes another file", name)
}
n, err := testkit.HeaderLen(file)
check(err)
if len(file) != n+16+testkit.StreamLen(len(plain)) {
log.Fatalf("%s: %d bytes, not the header, the nonce and the STREAM", name, len(file))
}
i := 1
for k, s := range specs {
switch {
case s.x25519 != nil:
want := x25519Stanza(fileKey, rawOf(s.x25519), draws[i])
got := stanzas[k]
if got.Type != want.Type || strings.Join(got.Args, " ") != strings.Join(want.Args, " ") || !bytes.Equal(got.Body, want.Body) {
log.Fatalf("%s: stanza %d is not the one of its ephemeral secret", name, k)
}
i++
case s.pass != "":
if stanzas[k].Args[0] != base64.RawStdEncoding.EncodeToString(draws[i]) {
log.Fatalf("%s: stanza %d has another salt", name, k)
}
i += 2 // the salt and the label
default:
i += 2 // sigma and the label
}
}
if i != len(draws)-1 {
log.Fatalf("%s: %d draws, %d expected", name, len(draws), i+1)
}
// Go opens it with every identity it knows.
for k, s := range specs {
var id age.Identity
switch {
case s.id != nil:
id = s.id
case s.pass != "":
sid, err := age.NewScryptIdentity(s.pass)
check(err)
sid.SetMaxWorkFactor(s.wf)
id = sid
case s.round != 0 && releases[s.round] != "":
tid, err := agewrap.NewTimeIdentity(profile.Quicknet(), s.round, provider.Release{Round: s.round, Signature: mustHex(releases[s.round])})
check(err)
id = tid
default:
continue
}
got, err := testkitOpen(file, id)
if err != nil {
log.Fatalf("%s: recipient %d does not open: %v", name, k, err)
}
if !bytes.Equal(got, plain) {
log.Fatalf("%s: recipient %d opens another plaintext", name, k)
}
}
}
func testkitOpen(file []byte, id age.Identity) ([]byte, error) {
r, err := age.Decrypt(bytes.NewReader(file), id)
if err != nil {
return nil, err
}
return io.ReadAll(r)
}
// ---------------------------------------------------------------------------
// The sections
func seededSection() []obj {
fills := []int{0, 1, 15, 16, 32, 63, 64, 65, 200}
var out []obj
for _, seed := range []string{"", "a", "age writer", "seed with spaces and ñ"} {
s := newSeeded(seed)
var all []byte
for _, n := range fills {
b := make([]byte, n)
_, _ = s.Read(b)
all = append(all, b...)
}
out = append(out, obj{"seed": seed, "fills": fills, "hex": h(all)})
}
return out
}
func randIntSection() []obj {
var out []obj
for _, n := range []int64{1, 2, 3, 5, 7, 8, 10, 16, 17, 100, 255, 256, 257, 1000, 65535, 65536, 65537, 1<<31 - 1, 1 << 31, 1<<32 - 1, 1 << 32} {
seed := fmt.Sprintf("rand.Int %d", n)
var results []int64
next := make([]byte, 4)
draws := with(seed, func() {
for range 24 {
v, err := rand.Int(rand.Reader, big.NewInt(n))
check(err)
results = append(results, v.Int64())
}
_, _ = rand.Read(next)
})
out = append(out, obj{"n": n, "seed": seed, "results": results, "reads": len(draws) - 1, "next": h(next)})
}
return out
}
// permute is the permute of capsule.Encrypt in Go, which capsule does not
// export, restated: Fisher-Yates with crypto/rand.Int.
func permute[T any](s []T) error {
for i := len(s) - 1; i > 0; i-- {
j, err := rand.Int(rand.Reader, big.NewInt(int64(i+1)))
if err != nil {
return err
}
k := int(j.Int64())
s[i], s[k] = s[k], s[i]
}
return nil
}
func permuteSection() []obj {
var out []obj
for _, n := range []int{0, 1, 2, 3, 5, 16, 16, 16, 16, 40} {
seed := fmt.Sprintf("permute %d %d", n, len(out))
items := make([]int, n)
for i := range items {
items[i] = i
}
draws := with(seed, func() { check(permute(items)) })
out = append(out, obj{"n": n, "seed": seed, "order": items, "reads": len(draws)})
}
return out
}
func encryptSection() []obj {
var out []obj
add := func(name string, specs []spec, n int, node bool) {
seed := "age writer " + name
plain := pattern(n)
var file []byte
draws := with(seed, func() {
var err error
file, err = encrypt(plain, recipients(specs))
check(err)
})
crossCheck(name, file, draws, plain, specs)
hdr, err := testkit.HeaderLen(file)
check(err)
o := obj{
"name": name,
"seed": seed,
"recipients": specsJSON(specs),
"length": n,
"draws": drawsOf(draws),
"header": h(file[:hdr]),
"file_length": len(file),
"file_sha256": sum(file),
"node": node,
}
if len(file) <= 2048 {
o["file"] = h(file)
}
out = append(out, o)
}
for _, n := range []int{0, 1, 2, 15, 16, 17, 100, 103, 1000, 65535, 65536, 65537, 131071, 131072, 131073, 196608, 200000, 2 << 20, 3<<20 + 3} {
add(fmt.Sprintf("x25519, %d bytes", n), []spec{x("one")}, n, n <= 262144)
}
add("x25519, two recipients", []spec{x("two a"), x("two b")}, 50, true)
add("x25519, three recipients", []spec{x("three a"), x("three b"), x("three c")}, 1000, true)
var sixteen []spec
for i := range 16 {
sixteen = append(sixteen, x(fmt.Sprintf("slot %d", i)))
}
add("x25519, sixteen recipients", sixteen, 103, true)
add("x25519, sixteen recipients, 70000 bytes", sixteen, 70000, true)
high := rawOf(identity("high").Recipient())
high[31] |= 0x80
add("x25519, a recipient with bit 255 set", []spec{xraw(high)}, 10, true)
for _, c := range []struct {
wf int
n int
node bool
}{{1, 0, true}, {2, 150, true}, {2, 70000, true}, {10, 150, true}, {16, 150, false}} {
add(fmt.Sprintf("scrypt, work factor %d, %d bytes", c.wf, c.n), []spec{sc("correct horse battery staple", c.wf)}, c.n, c.node)
}
add("scrypt, a passphrase of one byte", []spec{sc("p", 1)}, 3, true)
add("scrypt, a passphrase in UTF-8", []spec{sc("contraseña de prueba ñ €", 2)}, 3, true)
for _, c := range []struct {
round uint64
n int
node bool
}{{1000, 16, false}, {1000, 103, true}, {1000, 1773, false}, {1000, 70000, false}, {1001, 0, false}, {1, 103, true}, {profile.Quicknet().MaxRound(), 5, false}} {
add(fmt.Sprintf("tlock, round %d, %d bytes", c.round, c.n), []spec{tl(c.round)}, c.n, c.node)
}
return out
}
func errorsSection() []obj {
var out []obj
add := func(name string, specs []spec, node bool) {
seed := "age writer error " + name
var err error
draws := with(seed, func() { _, err = encrypt(pattern(10), recipients(specs)) })
if err == nil {
log.Fatalf("%s: no error", name)
}
out = append(out, obj{"name": name, "seed": seed, "recipients": specsJSON(specs), "draws": drawsOf(draws), "error": err.Error(), "node": node})
}
add("no recipients", nil, true)
add("x25519, then scrypt", []spec{x("mix a"), sc("pass", 1)}, true)
add("scrypt, then x25519", []spec{sc("pass", 1), x("mix a")}, true)
add("scrypt twice", []spec{sc("pass", 1), sc("pass", 1)}, true)
add("two x25519, then scrypt", []spec{x("mix a"), x("mix b"), sc("pass", 1)}, true)
add("tlock, then x25519", []spec{tl(1000), x("mix a")}, true)
add("x25519, then tlock", []spec{x("mix a"), tl(1000)}, false)
add("tlock twice", []spec{tl(1000), tl(1000)}, false)
add("scrypt, then tlock", []spec{sc("pass", 1), tl(1000)}, false)
p := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 255), big.NewInt(19))
le := func(v *big.Int) []byte {
b := make([]byte, 32)
v.FillBytes(b)
for i, j := 0, 31; i < j; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i]
}
return b
}
order8a, _ := new(big.Int).SetString("325606250916557431795983626356110631294008115727848805560023387167927233504", 10)
order8b, _ := new(big.Int).SetString("39382357235489614581723060781553021112529911719440698176882885853963445705823", 10)
low := map[string]*big.Int{
"0": big.NewInt(0),
"1": big.NewInt(1),
"order 8, a": order8a,
"order 8, b": order8b,
"p - 1": new(big.Int).Sub(p, big.NewInt(1)),
"p": p,
"p + 1": new(big.Int).Add(p, big.NewInt(1)),
}
for _, k := range []string{"0", "1", "order 8, a", "order 8, b", "p - 1", "p", "p + 1"} {
add("x25519 of low order, u = "+k, []spec{xraw(le(low[k]))}, true)
}
zeroHigh := make([]byte, 32)
zeroHigh[31] = 0x80
add("x25519 of low order, u = 0 with bit 255 set", []spec{xraw(zeroHigh)}, true)
add("x25519 of low order after another one", []spec{x("mix a"), xraw(le(big.NewInt(1)))}, true)
return out
}
func constructorSection() []obj {
var out []obj
_, err := age.NewScryptRecipient("")
out = append(out, obj{"name": "NewScryptRecipient of an empty passphrase", "error": err.Error()})
for _, wf := range []int{0, 31, -1} {
var text string
func() {
defer func() { text = fmt.Sprint(recover()) }()
r, err := age.NewScryptRecipient("p")
check(err)
r.SetWorkFactor(wf)
}()
out = append(out, obj{"name": fmt.Sprintf("SetWorkFactor(%d)", wf), "work_factor": wf, "panic": text})
}
q := profile.Quicknet()
for _, round := range []uint64{0, q.MaxRound() + 1} {
_, err := agewrap.NewTimeRecipient(q, round)
out = append(out, obj{"name": fmt.Sprintf("NewTimeRecipient(%d)", round), "round": round, "error": err.Error()})
}
for _, round := range []uint64{1, q.MaxRound()} {
_, err := agewrap.NewTimeRecipient(q, round)
check(err)
}
out = append(out, obj{"name": "the last round of Quicknet", "max_round": q.MaxRound()})
return out
}
func streamSection() obj {
var write, empty, closeAgain error
with("age writer stream", func() {
var buf bytes.Buffer
w, err := age.Encrypt(&buf, identity("stream").Recipient())
check(err)
check(w.Close())
_, write = w.Write([]byte{1})
_, empty = w.Write(nil)
closeAgain = w.Close()
})
return obj{"write_after_close": write.Error(), "empty_write_after_close": empty.Error(), "close_after_close": closeAgain.Error()}
}
func parseSection() []obj {
good := identity("parse").Recipient().String()
raw := rawOf(identity("parse").Recipient())
enc := func(hrp string, b []byte) string {
s, err := bech32.Encode(hrp, b)
check(err)
return s
}
flipped := good[:len(good)-1] + map[bool]string{true: "p", false: "q"}[strings.HasSuffix(good, "q")]
inputs := []string{
good,
strings.ToUpper(good),
good[:10] + strings.ToUpper(good[10:]),
enc("age1pq", raw),
enc("age1tag", raw),
enc("AGE", raw),
enc("age", raw[:31]),
enc("age", append(bytes.Clone(raw), 0)),
enc("age", nil),
flipped,
"",
"age1",
"age1qqqqqq",
identity("parse").String(),
" " + good,
good + "\n",
"age1é" + good[5:],
enc("age", make([]byte, 32)),
}
var out []obj
for _, s := range inputs {
r, err := age.ParseX25519Recipient(s)
if err != nil {
out = append(out, obj{"input": s, "error": err.Error()})
continue
}
out = append(out, obj{"input": s, "raw": h(rawOf(r)), "string": r.String()})
}
return out
}
func checkSection() []obj {
p := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 255), big.NewInt(19))
le := func(v *big.Int) []byte {
b := make([]byte, 32)
v.FillBytes(b)
for i, j := 0, 31; i < j; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i]
}
return b
}
order8a, _ := new(big.Int).SetString("325606250916557431795983626356110631294008115727848805560023387167927233504", 10)
order8b, _ := new(big.Int).SetString("39382357235489614581723060781553021112529911719440698176882885853963445705823", 10)
var raws [][]byte
for i := range 5 {
raws = append(raws, rawOf(identity(fmt.Sprintf("check %d", i)).Recipient()))
}
for _, v := range []*big.Int{
big.NewInt(9), big.NewInt(2), new(big.Int).Sub(p, big.NewInt(2)),
p, new(big.Int).Add(p, big.NewInt(1)), new(big.Int).Add(p, big.NewInt(18)),
big.NewInt(0), big.NewInt(1), order8a, order8b, new(big.Int).Sub(p, big.NewInt(1)),
} {
raws = append(raws, le(v))
}
for _, b := range [][]byte{le(big.NewInt(9)), le(big.NewInt(0)), raws[0]} {
b = bytes.Clone(b)
b[31] |= 0x80
raws = append(raws, b)
}
var out []obj
for _, raw := range raws {
r := recipientOfRaw(raw)
o := obj{"raw": h(raw), "recipient": r.String()}
if err := agewrap.CheckX25519Recipient(r); err != nil {
o["error"] = err.Error()
}
out = append(out, o)
}
return out
}
func generateSection() []obj {
var out []obj
for i := range 4 {
seed := fmt.Sprintf("generate %d", i)
var id *age.X25519Identity
draws := with(seed, func() {
var err error
id, err = age.GenerateX25519Identity()
check(err)
})
raw, err := agewrap.RawX25519Identity(id)
check(err)
out = append(out, obj{"seed": seed, "draws": drawsOf(draws), "raw": h(raw), "identity": id.String(), "recipient": id.Recipient().String()})
}
return out
}
func lengthsSection() obj {
var stream, payload []obj
for _, n := range []int{0, 1, 65535, 65536, 65537, 131072, 131073, 1 << 20, 1<<30 + 1, 5_000_000_000, 1<<40 + 7} {
stream = append(stream, obj{"n": n, "length": testkit.StreamLen(n)})
}
for _, n := range []uint64{0, 1, 65536, 65537, 1 << 32, 1<<40 + 3} {
payload = append(payload, obj{"p": n, "length": capsule.PayloadAgeLength(n)})
}
return obj{"stream": stream, "payload_age": payload}
}
func main() {
out := flag.String("out", "", "where the vectors go")
src := flag.String("source", "", "the commit of datekeys-go")
flag.Parse()
if *out == "" || *src == "" {
log.Fatal("usage: -source <commit> -out <dir>")
}
doc := obj{
"source": *src,
"go": runtime.Version(),
"description": "age files that filippo.io/age v1.3.2 and agewrap write while crypto/rand reads the keystream of SeededRandomSource (ChaCha20 under SHA-256(seed), zero nonce), with each draw; the texts of the errors of the writer; and the lengths of tool/age_writer_go_vectors.go. The plaintext of n bytes has (31·i + 7) mod 256 as byte i. A case marked node false is left out compiled to JavaScript.",
"seeded": seededSection(),
"rand_int": randIntSection(),
"permute": permuteSection(),
"encrypt": encryptSection(),
"errors": errorsSection(),
"constructors": constructorSection(),
"stream": streamSection(),
"parse": parseSection(),
"check": checkSection(),
"generate": generateSection(),
"lengths": lengthsSection(),
}
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
check(enc.Encode(doc))
path := filepath.Join(*out, "age_writer.json")
check(os.WriteFile(path, buf.Bytes(), 0o644))
fmt.Printf("wrote %s, %d bytes\n", path, buf.Len())
if bytes.Contains(buf.Bytes(), []byte("'''")) {
log.Fatal("the JSON holds three quotes")
}
dart := "// Generated by tool/age_writer_go_vectors.go from age_writer.json, for the\n" +
"// tests that also run compiled to JavaScript, where no file can be read. Do\n" +
"// not edit.\n\n" +
"/// The text of test/vectors/age_writer.json.\n" +
"const ageWriterJson = r'''\n" + buf.String() + "''';\n"
dpath := filepath.Join(*out, "age_writer.g.dart")
check(os.WriteFile(dpath, []byte(dart), 0o644))
fmt.Printf("wrote %s\n", dpath)
}
Loading…
Cancel
Save

Powered by TurnKey Linux.