Stage 7b: the author keys and Ed25519 signing
ed25519_sign.dart signs as crypto_sign of TweetNaCl in its JavaScript
port, with the SHA-512 of package:crypto: the field of curve25519.dart,
whose arithmetic is private there, copied with the product as a loop, and
modL over 64 limbs of 8 bits in a Float64List, with floor divisions in
place of the shifts of TweetNaCl, exact on the VM and on the web. The
secret scalar and the nonce never meet a BigInt or a branch; neither
platform promises constant time, and the values are wiped as a best
effort.
authorkey.dart ports package authorkey of datekeys-go: AuthorKey with
generate, fromSeed, publicKey, sign, clear and secret, and a toString
that hides it; authorPublicString, parseAuthorPublic and
parseAuthorSecret, also on the bytes of a Go string; marshalAuthorKey;
encryptAuthorKey, scrypt with logN 16 through ScryptRecipient and
ageEncrypt of stage 6a; and readAuthorKey, through the age reader with a
maximum work factor of 16, whose lines are those of bufio.Scanner and
strings.TrimSpace. Every error has the text of Go, with the sets of
go_unicode.dart for the case of a string and the spaces of a line. A
cleared key refuses every use, where Go would give the values of a key of
zeros.
tool/authorkey_go_vectors.go writes test/vectors/authorkey.json: the
signatures of crypto/ed25519 over lines of sign.input (RFC 8032 tests
1, 2, 3 and 1024), TEST SHA(abc), seeded seeds and messages up to 1 MiB
and other public keys; the scalars of math/big; and Generate, Encrypt,
ParsePublic, ParseSecret and Read of authorkey with each text, while
crypto/rand reads the keystream of SeededRandomSource. Dart writes the
same bytes and gives the same texts in every case; authorkey.g.dart, a
part of it, runs also in Node.js.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 day ago
//go:build ignore
//go:debug cryptocustomrand=1
// Writes test/vectors/authorkey.json and authorkey.g.dart, the vectors of
// the author keys of datekeys-dart, stage 7b of docs/PLAN_dart.md: the
// Ed25519 signatures of Go's crypto/ed25519 and the package authorkey of
// datekeys-go, with the texts of its errors.
//
// - sign: crypto/ed25519.Sign over seeds and messages. The first 64 lines
// of sign.input of Go's crypto/ed25519 (SUPERCOP), whose lines 0, 1 and
// 2 are tests 1 to 3 of RFC 8032, 7.1, every 64th line after them, and
// line 1023, whose message of 1023 bytes is the one of its TEST 1024;
// TEST SHA(abc), the message SHA-512("abc") under the key of
// TestSignVerifyHashed of Go; seeds of a fixed seed with messages of 0
// bytes to 1 MiB; and private keys whose second half is another public
// key, which Go hashes as it is given;
// - scalars: x mod ℓ of 64-byte numbers and (a·b + c) mod ℓ of 32-byte
// ones, little-endian, with math/big, in the corners and at random;
// - keys: NewFromSeed, Public, PublicString, Secret, Marshal and String,
// and the errors of NewFromSeed and PublicString;
// - generate and encrypt: Generate and Encrypt while crypto/rand reads
// the keystream of SeededRandomSource of lib/src/random.dart (ChaCha20
// under SHA-256(seed), zero nonce), with each draw; Generate reads it
// through the GODEBUG cryptocustomrand=1 of this file;
// - public and secret: ParsePublic and ParseSecret over strings, as bytes:
// valid, in the other case or mixed, of other lengths, with each Bech32
// error, other prefixes, data of other lengths and paddings, keys that
// are not canonical, not on the curve or of small order, and bytes that
// are not UTF-8;
// - runes: the same over a valid string where one character is replaced
// by a rune of as many bytes, in the prefix and in the data, for the
// code points at each edge of the sets of unicode.ToLower,
// unicode.ToUpper and unicode.IsSpace of Go: [kind, position, rune,
// text], kind 0 for ParsePublic and 1 for ParseSecret;
// - read: Read of plain and encrypted files, with the result or the text
// of the error.
//
// Every expected value is what Go gives; none is written by hand. A text is
// an index into texts, whose first entry, "", stands for no error. Binary
// values are lower-case hexadecimal. A file is a list of parts, each
// {"hex": …}, {"byte": b, "n": count} or {"sealed": …, "length", "sha256"},
// the age file of a recipe, which the tests write again with
// SeededRandomSource. authorkey.g.dart is the same document with only the
// signatures marked node and one in eight of the runes, as a Dart
// constant, for the tests that also run compiled to JavaScript. Arrays of
// numbers are written on one line.
//
// It imports only public packages, so it runs in the module of the
// reference implementation, without changing anything there, from the
// datekeys-go next to this repository, on the branch v0.12 at c531e93:
//
// cd ../datekeys-go && go run ../datekeys-dart/tool/authorkey_go_vectors.go \
// -source $(git rev-parse v0.12) -out ../datekeys-dart/test/vectors
//
// The output is the same on every run.
package main
import (
"bufio"
"bytes"
"compress/gzip"
"crypto/ed25519"
cryptorand "crypto/rand"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"log"
"math/big"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"unicode"
"unicode/utf8"
_ "unsafe"
"filippo.io/age"
"golang.org/x/crypto/chacha20"
"g.activething.com/go/DateKeys/authorkey"
_ "g.activething.com/go/DateKeys/codec/bech32"
)
//go:linkname createChecksum g.activething.com/go/DateKeys/codec/bech32.createChecksum
func createChecksum ( hrp string , data [ ] byte ) [ ] byte
type obj = map [ string ] any
func h ( b [ ] byte ) string { return hex . EncodeToString ( b ) }
func check ( err error ) {
if err != nil {
_ , file , line , _ := runtime . Caller ( 1 )
log . Fatalf ( "%s:%d: %v" , filepath . Base ( file ) , line , err )
}
}
func mustHex ( s string ) [ ] byte {
b , err := hex . DecodeString ( s )
check ( err )
return b
}
func label ( s string ) [ ] byte {
b := sha256 . Sum256 ( [ ] byte ( "datekeys-dart stage 7b: " + s ) )
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
}
// texts are the error texts, indexed; 0 is no error.
var texts = [ ] string { "" }
var textIndex = map [ string ] int { "" : 0 }
func t ( err error ) int {
if err == nil {
return 0
}
s := err . Error ( )
if i , ok := textIndex [ s ] ; ok {
return i
}
texts = append ( texts , s )
textIndex [ s ] = len ( texts ) - 1
return len ( texts ) - 1
}
// ---------------------------------------------------------------------------
// crypto/rand from a seed, as in tool/age_writer_go_vectors.go
type seeded struct {
c * chacha20 . Cipher
draws [ ] [ ] byte
}
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
}
func with ( seed string , f func ( ) ) [ ] [ ] byte {
key := sha256 . Sum256 ( [ ] byte ( seed ) )
c , err := chacha20 . NewUnauthenticatedCipher ( key [ : ] , make ( [ ] byte , chacha20 . NonceSize ) )
check ( err )
s := & seeded { c : c }
old := cryptorand . Reader
cryptorand . Reader = s
defer func ( ) { cryptorand . 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
}
// ---------------------------------------------------------------------------
// Signatures
func signCase ( name string , seed , pub , msg [ ] byte , node bool ) obj {
priv := append ( bytes . Clone ( seed ) , pub ... )
sig := ed25519 . Sign ( priv , msg )
c := obj { "name" : name , "seed" : h ( seed ) , "public_key" : h ( pub ) , "signature" : h ( sig ) , "node" : node }
if len ( msg ) > 4096 {
if ! bytes . Equal ( msg , pattern ( len ( msg ) ) ) {
log . Fatal ( "a long message must be a pattern" )
}
c [ "message_pattern" ] = len ( msg )
} else {
c [ "message" ] = h ( msg )
}
ownPub := ed25519 . NewKeyFromSeed ( seed ) . Public ( ) . ( ed25519 . PublicKey )
c [ "valid" ] = ed25519 . Verify ( ownPub , msg , sig )
return c
}
func signSection ( ) [ ] obj {
out := [ ] obj { }
f , err := os . Open ( filepath . Join ( runtime . GOROOT ( ) , "src" , "crypto" , "ed25519" , "testdata" , "sign.input.gz" ) )
check ( err )
defer f . Close ( )
gz , err := gzip . NewReader ( f )
check ( err )
sc := bufio . NewScanner ( gz )
sc . Buffer ( nil , 1 << 20 )
for line := 0 ; sc . Scan ( ) ; line ++ {
if line >= 64 && line % 64 != 0 && line != 1023 {
continue
}
parts := strings . Split ( sc . Text ( ) , ":" )
seed := mustHex ( parts [ 0 ] ) [ : 32 ]
pub := mustHex ( parts [ 1 ] )
msg := mustHex ( parts [ 2 ] )
sig := mustHex ( parts [ 3 ] ) [ : 64 ]
if ! bytes . Equal ( ed25519 . Sign ( append ( bytes . Clone ( seed ) , pub ... ) , msg ) , sig ) {
log . Fatalf ( "sign.input line %d" , line )
}
out = append ( out , signCase ( fmt . Sprintf ( "sign.input line %d" , line ) , seed , pub , msg , line < 4 || line % 16 == 0 ) )
}
check ( sc . Err ( ) )
// TEST SHA(abc): the key of TestSignVerifyHashed of Go, the private key
// of RFC 8032, 7.3, which 7.1 signs SHA-512("abc") with.
src , err := os . ReadFile ( filepath . Join ( runtime . GOROOT ( ) , "src" , "crypto" , "ed25519" , "ed25519_test.go" ) )
check ( err )
m := regexp . MustCompile ( ` func TestSignVerifyHashed[^ { ]*\ { [^"]*key, _ := hex\.DecodeString\("([0-9a-f] { 128})"\) ` ) . FindSubmatch ( src )
if m == nil {
log . Fatal ( "no key in TestSignVerifyHashed" )
}
key := mustHex ( string ( m [ 1 ] ) )
abc := sha512 . Sum512 ( [ ] byte ( "abc" ) )
out = append ( out , signCase ( "RFC 8032 TEST SHA(abc)" , key [ : 32 ] , key [ 32 : ] , abc [ : ] , true ) )
lengths := [ ] int { 0 , 1 , 2 , 31 , 32 , 33 , 63 , 64 , 65 , 99 , 111 , 112 , 113 , 127 , 128 , 129 , 200 , 255 , 256 , 1000 , 4096 }
for i := 0 ; i < 160 ; i ++ {
seed := label ( fmt . Sprintf ( "sign seed %d" , i ) )
pub := ed25519 . NewKeyFromSeed ( seed ) . Public ( ) . ( ed25519 . PublicKey )
n := lengths [ i % len ( lengths ) ]
msg := label ( fmt . Sprintf ( "sign message %d" , i ) )
for len ( msg ) < n {
msg = append ( msg , label ( fmt . Sprintf ( "sign message %d %d" , i , len ( msg ) ) ) ... )
}
out = append ( out , signCase ( fmt . Sprintf ( "seeded %d, %d bytes" , i , n ) , seed , pub , msg [ : n ] , i % 8 == 0 ) )
}
for _ , n := range [ ] int { 64 << 10 , 1 << 20 } {
seed := label ( fmt . Sprintf ( "sign long %d" , n ) )
pub := ed25519 . NewKeyFromSeed ( seed ) . Public ( ) . ( ed25519 . PublicKey )
out = append ( out , signCase ( fmt . Sprintf ( "a message of %d bytes" , n ) , seed , pub , pattern ( n ) , n < 1 << 20 ) )
}
for _ , b := range [ ] byte { 0 , 0xff } {
seed := bytes . Repeat ( [ ] byte { b } , 32 )
pub := ed25519 . NewKeyFromSeed ( seed ) . Public ( ) . ( ed25519 . PublicKey )
out = append ( out , signCase ( fmt . Sprintf ( "seed of 0x%02x" , b ) , seed , pub , [ ] byte ( "DateKeys" ) , true ) )
}
// Go hashes the second half of the private key as the public key,
// whatever it is.
for i := 0 ; i < 4 ; i ++ {
seed := label ( fmt . Sprintf ( "other key seed %d" , i ) )
other := ed25519 . NewKeyFromSeed ( label ( fmt . Sprintf ( "other key %d" , i ) ) ) . Public ( ) . ( ed25519 . PublicKey )
if i == 3 {
other = make ( [ ] byte , 32 )
}
out = append ( out , signCase ( fmt . Sprintf ( "the public key of another seed, %d" , i ) , seed , other , [ ] byte ( "message" ) , true ) )
}
return out
}
// ---------------------------------------------------------------------------
// Scalars
var order , _ = new ( big . Int ) . SetString ( "7237005577332262213973186563042994240857116359379907606001950938285454250989" , 10 )
func le ( x * big . Int , n int ) [ ] byte {
b := x . FillBytes ( make ( [ ] byte , n ) )
for i , j := 0 , n - 1 ; i < j ; i , j = i + 1 , j - 1 {
b [ i ] , b [ j ] = b [ j ] , b [ i ]
}
return b
}
func fromLE ( b [ ] byte ) * big . Int {
r := bytes . Clone ( b )
for i , j := 0 , len ( r ) - 1 ; i < j ; i , j = i + 1 , j - 1 {
r [ i ] , r [ j ] = r [ j ] , r [ i ]
}
return new ( big . Int ) . SetBytes ( r )
}
func scalarSection ( ) obj {
// ℓ is checked against the order of crypto/ed25519: [ℓ ]B is the
// identity, through a signature whose S is ℓ - 1 + 1.
two := big . NewInt ( 2 )
if new ( big . Int ) . Sub ( order , new ( big . Int ) . Exp ( two , big . NewInt ( 252 ) , nil ) ) . String ( ) != "27742317777372353535851937790883648493" {
log . Fatal ( "ℓ " )
}
max512 := new ( big . Int ) . Sub ( new ( big . Int ) . Lsh ( big . NewInt ( 1 ) , 512 ) , big . NewInt ( 1 ) )
top := new ( big . Int ) . Mul ( new ( big . Int ) . Div ( max512 , order ) , order )
reduceIn := [ ] * big . Int {
big . NewInt ( 0 ) , big . NewInt ( 1 ) , new ( big . Int ) . Sub ( order , big . NewInt ( 1 ) ) , order ,
new ( big . Int ) . Add ( order , big . NewInt ( 1 ) ) , new ( big . Int ) . Mul ( order , two ) ,
new ( big . Int ) . Lsh ( big . NewInt ( 1 ) , 252 ) , new ( big . Int ) . Lsh ( big . NewInt ( 1 ) , 253 ) ,
new ( big . Int ) . Sub ( new ( big . Int ) . Lsh ( big . NewInt ( 1 ) , 256 ) , big . NewInt ( 1 ) ) ,
new ( big . Int ) . Lsh ( big . NewInt ( 1 ) , 511 ) , max512 , top , new ( big . Int ) . Sub ( top , big . NewInt ( 1 ) ) ,
new ( big . Int ) . Add ( top , big . NewInt ( 1 ) ) ,
}
for i := 0 ; i < 200 ; i ++ {
x := new ( big . Int ) . SetBytes ( append ( label ( fmt . Sprintf ( "reduce %d a" , i ) ) , label ( fmt . Sprintf ( "reduce %d b" , i ) ) ... ) )
if i % 4 == 1 {
x . Rsh ( x , uint ( i % 512 ) )
}
if i % 4 == 2 {
x . Add ( x . Mul ( new ( big . Int ) . Rsh ( x , 260 ) , order ) , big . NewInt ( int64 ( i % 3 ) - 1 ) )
x . And ( x , max512 )
}
reduceIn = append ( reduceIn , x )
}
reduce := [ ] obj { }
for _ , x := range reduceIn {
reduce = append ( reduce , obj { "in" : h ( le ( x , 64 ) ) , "out" : h ( le ( new ( big . Int ) . Mod ( x , order ) , 32 ) ) } )
}
max256 := new ( big . Int ) . Sub ( new ( big . Int ) . Lsh ( big . NewInt ( 1 ) , 256 ) , big . NewInt ( 1 ) )
corner := [ ] * big . Int { big . NewInt ( 0 ) , big . NewInt ( 1 ) , new ( big . Int ) . Sub ( order , big . NewInt ( 1 ) ) , order , max256 , new ( big . Int ) . Lsh ( big . NewInt ( 1 ) , 255 ) }
muladd := [ ] obj { }
add := func ( a , b , c * big . Int ) {
r := new ( big . Int ) . Mul ( a , b )
r . Add ( r , c ) . Mod ( r , order )
muladd = append ( muladd , obj { "a" : h ( le ( a , 32 ) ) , "b" : h ( le ( b , 32 ) ) , "c" : h ( le ( c , 32 ) ) , "out" : h ( le ( r , 32 ) ) } )
}
for _ , a := range corner {
for _ , b := range corner {
add ( a , b , corner [ ( len ( muladd ) ) % len ( corner ) ] )
}
}
for i := 0 ; i < 100 ; i ++ {
a := new ( big . Int ) . SetBytes ( label ( fmt . Sprintf ( "muladd %d a" , i ) ) )
b := new ( big . Int ) . SetBytes ( label ( fmt . Sprintf ( "muladd %d b" , i ) ) )
c := new ( big . Int ) . SetBytes ( label ( fmt . Sprintf ( "muladd %d c" , i ) ) )
add ( a , b , c )
}
return obj { "reduce" : reduce , "muladd" : muladd , "order" : h ( le ( order , 32 ) ) }
}
// ---------------------------------------------------------------------------
// Keys
func keySection ( ) obj {
keys := [ ] obj { }
for i := 0 ; i < 24 ; i ++ {
seed := label ( fmt . Sprintf ( "key %d" , i ) )
if i == 0 {
seed = make ( [ ] byte , 32 )
}
k , err := authorkey . NewFromSeed ( seed )
check ( err )
ps , err := authorkey . PublicString ( k . Public ( ) )
check ( err )
keys = append ( keys , obj { "seed" : h ( seed ) , "public_key" : h ( k . Public ( ) ) , "public" : ps , "secret" : k . Secret ( ) , "marshal" : string ( authorkey . Marshal ( k ) ) , "string" : k . String ( ) , "gostring" : fmt . Sprintf ( "%#v" , k ) } )
}
seedErrors := [ ] obj { }
for _ , n := range [ ] int { 0 , 31 , 33 , 64 } {
_ , err := authorkey . NewFromSeed ( make ( [ ] byte , n ) )
seedErrors = append ( seedErrors , obj { "length" : n , "error" : err . Error ( ) } )
}
publicErrors := [ ] obj { }
for _ , n := range [ ] int { 0 , 31 , 33 , 64 } {
_ , err := authorkey . PublicString ( make ( [ ] byte , n ) )
publicErrors = append ( publicErrors , obj { "length" : n , "error" : err . Error ( ) } )
}
return obj { "keys" : keys , "seed_errors" : seedErrors , "public_errors" : publicErrors }
}
func generateSection ( ) [ ] obj {
out := [ ] obj { }
for i := 0 ; i < 3 ; i ++ {
seed := fmt . Sprintf ( "authorkey generate %d" , i )
var k * authorkey . Key
d := with ( seed , func ( ) {
var err error
k , err = authorkey . Generate ( )
check ( err )
} )
out = append ( out , obj { "seed" : seed , "draws" : drawsOf ( d ) , "secret" : k . Secret ( ) , "public_key" : h ( k . Public ( ) ) } )
}
return out
}
func encryptSection ( ) [ ] obj {
out := [ ] obj { }
for i , pass := range [ ] string { "correct horse battery staple" , "contraseña ñ €" , "x" } {
k , err := authorkey . NewFromSeed ( label ( fmt . Sprintf ( "encrypt key %d" , i ) ) )
check ( err )
seed := fmt . Sprintf ( "authorkey encrypt %d" , i )
var buf bytes . Buffer
d := with ( seed , func ( ) { check ( authorkey . Encrypt ( & buf , k , pass ) ) } )
back , err := authorkey . Read ( bytes . NewReader ( buf . Bytes ( ) ) , pass )
check ( err )
if back . Secret ( ) != k . Secret ( ) {
log . Fatal ( "Read does not give the key back" )
}
out = append ( out , obj { "seed" : seed , "key_seed" : h ( label ( fmt . Sprintf ( "encrypt key %d" , i ) ) ) , "passphrase" : pass , "draws" : drawsOf ( d ) , "file" : h ( buf . Bytes ( ) ) , "node" : i == 0 } )
}
k , err := authorkey . NewFromSeed ( label ( "encrypt key 0" ) )
check ( err )
err = authorkey . Encrypt ( & bytes . Buffer { } , k , "" )
out = append ( out , obj { "passphrase" : "" , "error" : err . Error ( ) } )
return out
}
// ---------------------------------------------------------------------------
// Strings
const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
// encode5 writes hrp and the 5-bit values with a valid checksum, in lower
// case: a Bech32 string whose data part need not be 8-bit data.
func encode5 ( hrp string , values [ ] byte ) string {
var b strings . Builder
b . WriteString ( hrp )
b . WriteString ( "1" )
for _ , v := range values {
b . WriteByte ( charset [ v ] )
}
for _ , v := range createChecksum ( hrp , values ) {
b . WriteByte ( charset [ v ] )
}
return b . String ( )
}
func to5 ( data [ ] byte ) [ ] byte {
var out [ ] byte
acc , bits := 0 , 0
for _ , v := range data {
acc = acc << 8 | int ( v )
bits += 8
for bits >= 5 {
bits -= 5
out = append ( out , byte ( acc >> bits ) & 31 )
}
}
if bits > 0 {
out = append ( out , byte ( acc << ( 5 - bits ) ) & 31 )
}
return out
}
func enc ( hrp string , data [ ] byte ) string {
v := to5 ( data )
s := encode5 ( strings . ToLower ( hrp ) , v )
if strings . ToUpper ( hrp ) == hrp {
return strings . ToUpper ( s )
}
return s
}
// variants are the strings of a valid key string s, of the prefix hrp and
// the data data: other cases, lengths, characters, prefixes, paddings.
func variants ( s , hrp string , data [ ] byte , full bool ) [ ] string {
out := [ ] string { s , strings . ToUpper ( s ) , strings . ToLower ( s ) , s [ : 1 ] + strings . ToLower ( s [ 1 : ] ) , s [ : 1 ] + strings . ToUpper ( s [ 1 : ] ) ,
s [ : len ( s ) - 1 ] + strings . ToUpper ( s [ len ( s ) - 1 : ] ) , s [ : len ( s ) - 1 ] + strings . ToLower ( s [ len ( s ) - 1 : ] ) ,
"" , s [ : 1 ] , s [ : len ( s ) - 1 ] , s + "q" , s + s , " " + s [ 1 : ] , s [ : len ( s ) - 1 ] + " " , s [ : len ( s ) - 1 ] + "\n" }
// Each position changed to another character of the charset, to one
// out of it and to the separator.
for i := 0 ; full && i < len ( s ) ; i ++ {
for _ , c := range [ ] byte { 'q' , 'p' , 'b' , 'i' , 'o' , '1' , '0' , 'Z' , ' ' , 0 , 0x7f , '"' , '\\' } {
if s [ i ] == c {
continue
}
if c != 'q' && c != 'p' && i % 5 != 0 && c != 'b' {
continue
}
out = append ( out , s [ : i ] + string ( [ ] byte { c } ) + s [ i + 1 : ] )
}
}
lower := strings . ToLower ( hrp ) == hrp
casing := func ( x string ) string {
if lower {
return strings . ToLower ( x )
}
return strings . ToUpper ( x )
}
n := len ( hrp )
// Other prefixes of the same length and of a length one less or more,
// with data of the length that keeps the string length.
out = append ( out , enc ( casing ( hrp [ : n - 1 ] + "q" ) , data ) )
out = append ( out , enc ( casing ( hrp [ : n - 1 ] + "Q" ) , data ) )
out = append ( out , enc ( casing ( "x" + hrp [ 1 : ] ) , data ) )
v := to5 ( data )
out = append ( out , casing ( encode5 ( strings . ToLower ( hrp [ : n - 1 ] ) , append ( bytes . Clone ( v ) , 0 ) ) ) )
out = append ( out , casing ( encode5 ( strings . ToLower ( hrp [ : n - 1 ] ) , append ( bytes . Clone ( v ) , 1 ) ) ) )
out = append ( out , casing ( encode5 ( strings . ToLower ( hrp + "x" ) , v [ : len ( v ) - 1 ] ) ) )
out = append ( out , casing ( encode5 ( strings . ToLower ( hrp ) , append ( bytes . Clone ( v [ : len ( v ) - 1 ] ) , v [ len ( v ) - 1 ] | 1 ) ) ) )
out = append ( out , casing ( encode5 ( strings . ToLower ( hrp ) , append ( bytes . Clone ( v [ : len ( v ) - 1 ] ) , 31 ) ) ) )
out = append ( out , casing ( encode5 ( strings . ToLower ( hrp + "x" ) , v [ : len ( v ) - 2 ] ) ) )
out = append ( out , casing ( encode5 ( strings . ToLower ( hrp [ : n - 2 ] ) , append ( bytes . Clone ( v ) , 0 , 0 ) ) ) )
out = append ( out , casing ( encode5 ( strings . ToLower ( hrp [ : n - 1 ] + "1" ) , v [ : len ( v ) - 1 ] ) ) )
out = append ( out , casing ( encode5 ( "" , append ( bytes . Clone ( v ) , bytes . Repeat ( [ ] byte { 0 } , n + 1 ) ... ) ) ) )
// A byte that is not ASCII and a separator 6, 7 or 8 bytes before the
// end: the position of the separator is checked first.
for _ , bad := range [ ] string { "\xff" , "é" } {
for _ , back := range [ ] int { 6 , 7 , 8 } {
b := [ ] byte ( s [ : 3 ] + bad + s [ 3 + len ( bad ) : ] )
b [ len ( b ) - back ] = '1'
out = append ( out , string ( b ) )
}
}
Stage 7b: the author keys and Ed25519 signing
ed25519_sign.dart signs as crypto_sign of TweetNaCl in its JavaScript
port, with the SHA-512 of package:crypto: the field of curve25519.dart,
whose arithmetic is private there, copied with the product as a loop, and
modL over 64 limbs of 8 bits in a Float64List, with floor divisions in
place of the shifts of TweetNaCl, exact on the VM and on the web. The
secret scalar and the nonce never meet a BigInt or a branch; neither
platform promises constant time, and the values are wiped as a best
effort.
authorkey.dart ports package authorkey of datekeys-go: AuthorKey with
generate, fromSeed, publicKey, sign, clear and secret, and a toString
that hides it; authorPublicString, parseAuthorPublic and
parseAuthorSecret, also on the bytes of a Go string; marshalAuthorKey;
encryptAuthorKey, scrypt with logN 16 through ScryptRecipient and
ageEncrypt of stage 6a; and readAuthorKey, through the age reader with a
maximum work factor of 16, whose lines are those of bufio.Scanner and
strings.TrimSpace. Every error has the text of Go, with the sets of
go_unicode.dart for the case of a string and the spaces of a line. A
cleared key refuses every use, where Go would give the values of a key of
zeros.
tool/authorkey_go_vectors.go writes test/vectors/authorkey.json: the
signatures of crypto/ed25519 over lines of sign.input (RFC 8032 tests
1, 2, 3 and 1024), TEST SHA(abc), seeded seeds and messages up to 1 MiB
and other public keys; the scalars of math/big; and Generate, Encrypt,
ParsePublic, ParseSecret and Read of authorkey with each text, while
crypto/rand reads the keystream of SeededRandomSource. Dart writes the
same bytes and gives the same texts in every case; authorkey.g.dart, a
part of it, runs also in Node.js.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 day ago
// Bytes that are not ASCII or not UTF-8, in place of as many bytes.
for _ , bad := range [ ] string { "\xff" , "\x80" , "\xc0\x80" , "\xe0\x80\x80" , "\xed\xa0\x80" , "\xf4\x90\x80\x80" , "\xc3" , "é" , "€" , "İ" , "ß" , "Dž" , " " , "<22> " , "\U0001f600" } {
for _ , at := range [ ] int { 0 , 3 , n , n + 1 , len ( s ) - len ( bad ) } {
if at + len ( bad ) <= len ( s ) {
out = append ( out , s [ : at ] + bad + s [ at + len ( bad ) : ] )
}
}
}
return out
}
func keyStrings ( ) ( [ ] string , [ ] string ) {
var pub , sec [ ] string
for i := 0 ; i < 6 ; i ++ {
k , err := authorkey . NewFromSeed ( label ( fmt . Sprintf ( "strings %d" , i ) ) )
check ( err )
ps , err := authorkey . PublicString ( k . Public ( ) )
check ( err )
if i < 2 {
pub = append ( pub , variants ( ps , authorkey . PublicPrefix , k . Public ( ) , i == 0 ) ... )
seed := label ( fmt . Sprintf ( "strings %d" , i ) )
sec = append ( sec , variants ( k . Secret ( ) , authorkey . SecretPrefix , seed , i == 0 ) ... )
} else {
pub = append ( pub , ps )
sec = append ( sec , k . Secret ( ) )
}
}
// Keys that the strict profile rejects: the public keys of
// ed25519_strict.json, encodings that are not canonical, and random
// encodings, about half of them off the curve.
var raws [ ] [ ] byte
var strict struct {
Vectors [ ] struct {
PublicKey string ` json:"public_key" `
} ` json:"vectors" `
}
check ( json . Unmarshal ( mustRead ( filepath . Join ( * testdata , "vectors" , "ed25519_strict.json" ) ) , & strict ) )
for _ , v := range strict . Vectors {
raws = append ( raws , mustHex ( v . PublicKey ) )
}
p := new ( big . Int ) . Sub ( new ( big . Int ) . Lsh ( big . NewInt ( 1 ) , 255 ) , big . NewInt ( 19 ) )
for d := int64 ( - 1 ) ; d <= 19 ; d ++ {
y := le ( new ( big . Int ) . Add ( p , big . NewInt ( d ) ) , 32 )
raws = append ( raws , bytes . Clone ( y ) )
y [ 31 ] |= 0x80
raws = append ( raws , y )
}
for _ , y := range [ ] * big . Int { big . NewInt ( 0 ) , big . NewInt ( 1 ) , new ( big . Int ) . Sub ( p , big . NewInt ( 1 ) ) } {
b := le ( y , 32 )
raws = append ( raws , bytes . Clone ( b ) )
b [ 31 ] |= 0x80
raws = append ( raws , b )
}
for i := 0 ; i < 48 ; i ++ {
raws = append ( raws , label ( fmt . Sprintf ( "random key %d" , i ) ) )
}
for _ , r := range raws {
s , err := authorkey . PublicString ( r )
check ( err )
pub = append ( pub , s )
}
return pub , sec
}
func mustRead ( path string ) [ ] byte {
b , err := os . ReadFile ( path )
check ( err )
return b
}
func publicSection ( in [ ] string ) [ ] obj {
out := [ ] obj { }
for _ , s := range in {
k , err := authorkey . ParsePublic ( s )
c := obj { "in" : h ( [ ] byte ( s ) ) , "text" : t ( err ) }
if err == nil {
c [ "public_key" ] = h ( k )
}
out = append ( out , c )
}
return out
}
func secretSection ( in [ ] string ) [ ] obj {
out := [ ] obj { }
for _ , s := range in {
k , err := authorkey . ParseSecret ( s )
c := obj { "in" : h ( [ ] byte ( s ) ) , "text" : t ( err ) }
if err == nil {
c [ "public_key" ] = h ( k . Public ( ) )
}
out = append ( out , c )
}
return out
}
// edges returns the code points at each edge of a set: the last one out
// and the first one in, the last one in and the first one out.
func edges ( in func ( rune ) bool , add func ( rune ) ) {
prev := in ( 0 )
for r := rune ( 1 ) ; r <= unicode . MaxRune ; r ++ {
if r >= 0xd800 && r <= 0xdfff {
continue
}
cur := in ( r )
if cur != prev {
add ( r - 1 )
add ( r )
}
prev = cur
}
}
func runeSection ( n int ) obj {
seen := map [ rune ] bool { }
var runes [ ] rune
add := func ( r rune ) {
if r < 0x80 || ( r >= 0xd800 && r <= 0xdfff ) || seen [ r ] {
return
}
seen [ r ] = true
runes = append ( runes , r )
}
edges ( func ( r rune ) bool { return unicode . ToLower ( r ) != r } , add )
edges ( func ( r rune ) bool { return unicode . ToUpper ( r ) != r } , add )
edges ( unicode . IsSpace , add )
add ( utf8 . MaxRune )
add ( 0xfffd )
k , err := authorkey . NewFromSeed ( label ( "runes" ) )
check ( err )
ps , err := authorkey . PublicString ( k . Public ( ) )
check ( err )
sec := k . Secret ( )
cases := [ ] [ ] any { }
for i , r := range runes {
e := string ( r )
for kind , s := range [ ] string { ps , sec } {
for _ , at := range [ ] int { [ ] int { 3 , len ( s ) - 9 } [ i % 2 ] } {
in := s [ : at ] + e + s [ at + len ( e ) : ]
var err error
if kind == 0 {
_ , err = authorkey . ParsePublic ( in )
} else {
_ , err = authorkey . ParseSecret ( in )
}
if err == nil {
log . Fatalf ( "U+%04X passes" , r )
}
cases = append ( cases , [ ] any { kind , at , r , t ( err ) } )
}
}
}
if n > 1 {
var some [ ] [ ] any
for i := 0 ; i < len ( cases ) ; i += n * 2 {
some = append ( some , cases [ i : i + 2 ] ... )
}
cases = some
}
return obj { "public" : ps , "secret" : sec , "cases" : cases }
}
// ---------------------------------------------------------------------------
// Files
type part = obj
func sum ( b [ ] byte ) string {
s := sha256 . Sum256 ( b )
return h ( s [ : ] )
}
// sealedPart is the file of sealed(seed, pass, wf, plain), written as its
// recipe, its length and its SHA-256: the tests write it again with
// SeededRandomSource, as age writes it here.
func sealedPart ( seed , pass string , wf int , plain [ ] part ) part {
f := sealed ( seed , pass , wf , join ( plain ) )
return part { "sealed" : obj { "seed" : seed , "passphrase" : pass , "work_factor" : wf , "plain" : plain } , "length" : len ( f ) , "sha256" : sum ( f ) }
}
func hx ( b [ ] byte ) part { return part { "hex" : h ( b ) } }
func rep ( b byte , n int ) part { return part { "byte" : int ( b ) , "n" : n } }
func join ( parts [ ] part ) [ ] byte {
var out [ ] byte
for _ , p := range parts {
if x , ok := p [ "hex" ] ; ok {
out = append ( out , mustHex ( x . ( string ) ) ... )
} else if s , ok := p [ "sealed" ] ; ok {
r := s . ( obj )
f := sealed ( r [ "seed" ] . ( string ) , r [ "passphrase" ] . ( string ) , r [ "work_factor" ] . ( int ) , join ( r [ "plain" ] . ( [ ] part ) ) )
if len ( f ) != p [ "length" ] . ( int ) || sum ( f ) != p [ "sha256" ] . ( string ) {
log . Fatal ( "a sealed part" )
}
out = append ( out , f ... )
} else {
out = append ( out , bytes . Repeat ( [ ] byte { byte ( p [ "byte" ] . ( int ) ) } , p [ "n" ] . ( int ) ) ... )
}
}
return out
}
func readCase ( name string , parts [ ] part , pass string , node bool ) obj {
k , err := authorkey . Read ( bytes . NewReader ( join ( parts ) ) , pass )
c := obj { "name" : name , "file" : parts , "passphrase" : pass , "text" : t ( err ) , "node" : node }
if err == nil {
c [ "public_key" ] = h ( k . Public ( ) )
c [ "secret" ] = k . Secret ( )
}
return c
}
// sealed encrypts plain with a scrypt recipient of work factor wf while
// crypto/rand reads the keystream of seed.
func sealed ( seed , pass string , wf int , plain [ ] byte ) [ ] byte {
var buf bytes . Buffer
with ( seed , func ( ) {
r , err := age . NewScryptRecipient ( pass )
check ( err )
r . SetWorkFactor ( wf )
w , err := age . Encrypt ( & buf , r )
check ( err )
_ , err = w . Write ( plain )
check ( err )
check ( w . Close ( ) )
} )
return buf . Bytes ( )
}
func readSection ( ) [ ] obj {
k1 , err := authorkey . NewFromSeed ( label ( "read 1" ) )
check ( err )
k2 , err := authorkey . NewFromSeed ( label ( "read 2" ) )
check ( err )
s1 , s2 := k1 . Secret ( ) , k2 . Secret ( )
p1 , err := authorkey . PublicString ( k1 . Public ( ) )
check ( err )
out := [ ] obj { }
plain := func ( name , s string ) {
out = append ( out , readCase ( name , [ ] part { hx ( [ ] byte ( s ) ) } , "" , true ) )
}
plain ( "Marshal" , string ( authorkey . Marshal ( k1 ) ) )
plain ( "the line alone" , s1 )
plain ( "the line and LF" , s1 + "\n" )
plain ( "CR LF" , "# c\r\n" + s1 + "\r\n\r\n" )
plain ( "CR alone" , s1 + "\r" )
plain ( "CR inside" , s1 [ : 10 ] + "\r" + s1 [ 10 : ] )
plain ( "spaces and tabs" , " \t " + s1 + " \t\v\f\r\n" )
plain ( "comments and empty lines" , "\n\n# one\n # two\n\n" + s1 + "\n# three\n\n" )
plain ( "a comment without the space" , "#" + s1 + "\n" + s1 + "\n" )
plain ( "a comment that is not UTF-8" , "# \xff\xfe\n" + s1 )
plain ( "two keys" , s1 + "\n" + s2 + "\n" )
plain ( "the same key twice" , s1 + "\n" + s1 + "\n" )
plain ( "a key and something else" , s1 + "\nsomething\n" )
plain ( "something else and a key" , "something\n" + s1 + "\n" )
plain ( "a key in lower case" , strings . ToLower ( s1 ) )
plain ( "a public key" , p1 )
plain ( "an age identity" , "AGE-SECRET-KEY-1QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ" )
plain ( "empty" , "" )
plain ( "LF" , "\n" )
plain ( "only comments" , "# a\n# b\n" )
plain ( "only spaces" , " \n\t\n\v\f\n" )
plain ( "NUL before the key" , "\x00" + s1 )
plain ( "a BOM before the key" , bom + s1 )
plain ( "a byte that is not UTF-8 before the key" , "\xff" + s1 )
plain ( "NEL alone, not UTF-8" , "\x85" + s1 )
plain ( "NEL in UTF-8" , "\u0085" + s1 + "\u0085" )
// The ends of a line that are not quite a space: utf8.DecodeLastRune
// and DecodeRune give U+FFFD for them, which TrimSpace keeps.
plain ( "a space and a stray continuation byte at the end" , s1 + ideographicSpace + "\x80" )
plain ( "a stray continuation byte and a space at the start" , "\x80" + ideographicSpace + s1 )
plain ( "a space cut at the end" , s1 + ideographicSpace [ : 2 ] )
plain ( "a space cut at the start" , ideographicSpace [ 1 : ] + s1 )
plain ( "four continuation bytes after a space" , s1 + ideographicSpace + "\x80\x80\x80\x80" )
plain ( "a space after the key and a stray byte" , s1 + " \x80" )
Stage 7b: the author keys and Ed25519 signing
ed25519_sign.dart signs as crypto_sign of TweetNaCl in its JavaScript
port, with the SHA-512 of package:crypto: the field of curve25519.dart,
whose arithmetic is private there, copied with the product as a loop, and
modL over 64 limbs of 8 bits in a Float64List, with floor divisions in
place of the shifts of TweetNaCl, exact on the VM and on the web. The
secret scalar and the nonce never meet a BigInt or a branch; neither
platform promises constant time, and the values are wiped as a best
effort.
authorkey.dart ports package authorkey of datekeys-go: AuthorKey with
generate, fromSeed, publicKey, sign, clear and secret, and a toString
that hides it; authorPublicString, parseAuthorPublic and
parseAuthorSecret, also on the bytes of a Go string; marshalAuthorKey;
encryptAuthorKey, scrypt with logN 16 through ScryptRecipient and
ageEncrypt of stage 6a; and readAuthorKey, through the age reader with a
maximum work factor of 16, whose lines are those of bufio.Scanner and
strings.TrimSpace. Every error has the text of Go, with the sets of
go_unicode.dart for the case of a string and the spaces of a line. A
cleared key refuses every use, where Go would give the values of a key of
zeros.
tool/authorkey_go_vectors.go writes test/vectors/authorkey.json: the
signatures of crypto/ed25519 over lines of sign.input (RFC 8032 tests
1, 2, 3 and 1024), TEST SHA(abc), seeded seeds and messages up to 1 MiB
and other public keys; the scalars of math/big; and Generate, Encrypt,
ParsePublic, ParseSecret and Read of authorkey with each text, while
crypto/rand reads the keystream of SeededRandomSource. Dart writes the
same bytes and gives the same texts in every case; authorkey.g.dart, a
part of it, runs also in Node.js.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 day ago
plain ( "a key cut" , s1 [ : 78 ] )
plain ( "a key and a byte" , s1 + "x" )
plain ( "age-encryption.org/v1 without LF" , "age-encryption.org/v1" )
plain ( "age-encryption.org/v1 and a key" , "age-encryption.org/v1 \n" + s1 )
// Every space of Go, and its neighbours, around the line.
seen := map [ rune ] bool { }
for r := rune ( 0 ) ; r <= 0x3001 ; r ++ {
if ! unicode . IsSpace ( r ) {
continue
}
for _ , x := range [ ] rune { r - 1 , r , r + 1 } {
if seen [ x ] || x == '\n' || ( x >= 0x21 && x < 0x7f && x != r ) {
continue
}
seen [ x ] = true
e := string ( x )
out = append ( out , readCase ( fmt . Sprintf ( "U+%04X around the line" , x ) , [ ] part { hx ( [ ] byte ( e + e + s1 + e + "\n" ) ) } , "" , true ) )
}
}
// The limits: 64 KiB, the bufio.Scanner and its token of 64 KiB.
out = append ( out , readCase ( "64 KiB of comment without LF" , [ ] part { hx ( [ ] byte ( "#" ) ) , rep ( 'x' , 65535 ) } , "" , true ) )
out = append ( out , readCase ( "64 KiB of comment with LF" , [ ] part { hx ( [ ] byte ( "#" ) ) , rep ( 'x' , 65534 ) , hx ( [ ] byte ( "\n" ) ) } , "" , true ) )
out = append ( out , readCase ( "a key, then a comment, 64 KiB in all" , [ ] part { hx ( [ ] byte ( s1 + "\n#" ) ) , rep ( 'x' , 65536 - 82 ) , hx ( [ ] byte ( "\n" ) ) } , "" , true ) )
out = append ( out , readCase ( "64 KiB of spaces without LF" , [ ] part { rep ( ' ' , 65536 ) } , "" , true ) )
out = append ( out , readCase ( "64 KiB and a byte" , [ ] part { hx ( [ ] byte ( s1 + "\n#" ) ) , rep ( 'x' , 65536 - 80 ) } , "" , true ) )
out = append ( out , readCase ( "128 KiB" , [ ] part { rep ( '#' , 128 << 10 ) } , "" , true ) )
out = append ( out , readCase ( "64 KiB and a byte, encrypted" , [ ] part { hx ( [ ] byte ( "age-encryption.org/v1\n" ) ) , rep ( 'x' , 65536 - 21 ) } , "p" , true ) )
// Encrypted files, with work factors of 1 and 2, cheap for the tests.
enc := func ( name , seed , pass string , wf int , plain [ ] byte , read string , node bool ) {
out = append ( out , readCase ( name , [ ] part { hx ( sealed ( seed , pass , wf , plain ) ) } , read , node ) )
}
m1 := authorkey . Marshal ( k1 )
enc ( "encrypted, work factor 1" , "read enc 1" , "p" , 1 , m1 , "p" , true )
enc ( "encrypted, work factor 2, UTF-8 passphrase" , "read enc 2" , "pässwörd €" , 2 , m1 , "pässwörd €" , true )
enc ( "encrypted, wrong passphrase" , "read enc 3" , "p" , 1 , m1 , "q" , true )
enc ( "encrypted, no passphrase" , "read enc 4" , "p" , 1 , m1 , "" , true )
enc ( "encrypted, two keys" , "read enc 5" , "p" , 1 , [ ] byte ( s1 + "\n" + s2 + "\n" ) , "p" , true )
enc ( "encrypted, no key" , "read enc 6" , "p" , 1 , [ ] byte ( "# nothing\n" ) , "p" , true )
enc ( "encrypted, empty" , "read enc 7" , "p" , 1 , nil , "p" , true )
enc ( "encrypted, a key in lower case" , "read enc 8" , "p" , 1 , [ ] byte ( strings . ToLower ( s1 ) ) , "p" , true )
enc ( "encrypted, spaces around" , "read enc 9" , "p" , 1 , [ ] byte ( ideographicSpace + s1 + paragraphSeparator + "\r" + lf ) , "p" , true )
enc ( "encrypted, work factor 17" , "read enc 10" , "p" , 17 , m1 , "p" , false )
// Other work factors, edited into a file of work factor 1: age reads
// the work factor before it runs scrypt, and its MAC after.
w1 := sealed ( "read enc 11" , "p" , 1 , m1 )
for _ , wf := range [ ] string { "22" , "0" , "01" , "31" , "-1" , "1 " , "16" } {
e := bytes . Replace ( w1 , [ ] byte ( " 1" + lf ) , [ ] byte ( " " + wf + lf ) , 1 )
out = append ( out , readCase ( "encrypted, work factor edited to " + wf , [ ] part { hx ( e ) } , "p" , wf != "16" ) )
}
large := sealedPart ( "read enc 12" , "p" , 1 , [ ] part { hx ( [ ] byte ( s1 + "\n#" ) ) , rep ( 'x' , 65000 ) , hx ( [ ] byte ( "\n" ) ) } )
out = append ( out , readCase ( "encrypted, 64 KiB of plaintext" , [ ] part { large } , "p" , true ) )
tooBig := sealedPart ( "read enc 13" , "p" , 1 , [ ] part { hx ( [ ] byte ( s1 + "\n#" ) ) , rep ( 'x' , 65536 ) } )
out = append ( out , readCase ( "encrypted, more than 64 KiB" , [ ] part { tooBig } , "p" , true ) )
f := sealed ( "read enc 14" , "p" , 1 , m1 )
out = append ( out , readCase ( "encrypted, cut" , [ ] part { hx ( f [ : len ( f ) - 1 ] ) } , "p" , true ) )
out = append ( out , readCase ( "encrypted, header only" , [ ] part { hx ( f [ : bytes . Index ( f , [ ] byte ( "\n--- " ) ) + 1 ] ) } , "p" , true ) )
g := bytes . Clone ( f )
g [ len ( g ) - 1 ] ^ = 1
out = append ( out , readCase ( "encrypted, last byte changed" , [ ] part { hx ( g ) } , "p" , true ) )
g = bytes . Clone ( f )
i := bytes . Index ( g , [ ] byte ( "\n--- " ) ) + 6
g [ i ] ^ = 1
out = append ( out , readCase ( "encrypted, MAC changed" , [ ] part { hx ( g ) } , "p" , true ) )
out = append ( out , readCase ( "encrypted, garbage" , [ ] part { hx ( [ ] byte ( "age-encryption.org/v1\n-> what\n" ) ) } , "p" , true ) )
// An X25519 recipient instead of scrypt.
var xbuf bytes . Buffer
with ( "read enc x25519" , func ( ) {
id , err := age . GenerateX25519Identity ( )
check ( err )
w , err := age . Encrypt ( & xbuf , id . Recipient ( ) )
check ( err )
_ , err = w . Write ( m1 )
check ( err )
check ( w . Close ( ) )
} )
out = append ( out , readCase ( "encrypted for X25519" , [ ] part { hx ( xbuf . Bytes ( ) ) } , "p" , true ) )
return out
}
var testdata = flag . String ( "testdata" , "" , "the testdata of this repository" )
func main ( ) {
out := flag . String ( "out" , "" , "where the vectors go" )
src := flag . String ( "source" , "" , "the commit of datekeys-go" )
flag . Parse ( )
if * out == "" || * src == "" || * testdata == "" {
log . Fatal ( "usage: -source <commit> -testdata <dir> -out <dir>" )
}
pub , sec := keyStrings ( )
doc := obj {
"source" : * src ,
"go" : runtime . Version ( ) ,
"unicode" : unicode . Version ,
"description" : "The author keys of package authorkey of datekeys-go and the signatures of crypto/ed25519, by tool/authorkey_go_vectors.go. A text is an index into texts. A file is a list of parts: {hex}, {byte, n}, or {sealed: {seed, passphrase, work_factor, plain}, length, sha256}, the age file of plain, a list of parts, for a scrypt recipient while crypto/rand reads the keystream of the seed. A message_pattern of n is n bytes with (31·i + 7) mod 256 as byte i. Draws are those of crypto/rand reading the keystream of SeededRandomSource. A case marked node false is left out compiled to JavaScript." ,
"sign" : signSection ( ) ,
"scalars" : scalarSection ( ) ,
"keys" : keySection ( ) ,
"generate" : generateSection ( ) ,
"encrypt" : encryptSection ( ) ,
"public" : publicSection ( pub ) ,
"secret" : secretSection ( sec ) ,
"read" : readSection ( ) ,
}
write := func ( runes int ) [ ] byte {
doc [ "runes" ] = runeSection ( runes )
doc [ "texts" ] = texts
var buf bytes . Buffer
e := json . NewEncoder ( & buf )
e . SetEscapeHTML ( false )
e . SetIndent ( "" , " " )
check ( e . Encode ( doc ) )
// Arrays of numbers on one line each.
b := numbers . ReplaceAllFunc ( buf . Bytes ( ) , func ( m [ ] byte ) [ ] byte { return spaces . ReplaceAll ( m , nil ) } )
buf . Reset ( )
buf . Write ( b )
if bytes . Contains ( buf . Bytes ( ) , [ ] byte ( "'''" ) ) {
log . Fatal ( "the JSON holds three quotes" )
}
return buf . Bytes ( )
}
full := write ( 1 )
path := filepath . Join ( * out , "authorkey.json" )
check ( os . WriteFile ( path , full , 0 o644 ) )
fmt . Printf ( "wrote %s, %d bytes\n" , path , len ( full ) )
// The Dart constant: one in eight of the seeded signatures and of the
// rune cases, the long messages left out.
signs := doc [ "sign" ] . ( [ ] obj )
var some [ ] obj
for _ , c := range signs {
if c [ "node" ] . ( bool ) {
some = append ( some , c )
}
}
doc [ "sign" ] = some
part := write ( 8 )
dart := "// Generated by tool/authorkey_go_vectors.go: authorkey.json with a part of\n" +
"// its signatures and rune cases, for the tests that also run compiled to\n" +
"// JavaScript, where no file can be read. Do not edit.\n\n" +
"/// A part of test/vectors/authorkey.json.\n" +
"const authorKeyJson = r'''\n" + string ( part ) + "''';\n"
dpath := filepath . Join ( * out , "authorkey.g.dart" )
check ( os . WriteFile ( dpath , [ ] byte ( dart ) , 0 o644 ) )
fmt . Printf ( "wrote %s, %d bytes\n" , dpath , len ( dart ) )
}
// Characters written by their code points, so that the source stays ASCII
// where they matter.
var (
lf = string ( rune ( 0x0a ) )
bom = string ( rune ( 0xfeff ) )
ideographicSpace = string ( rune ( 0x3000 ) )
paragraphSeparator = string ( rune ( 0x2029 ) )
)
var (
numbers = regexp . MustCompile ( ` \[\s*-?[0-9]+(,\s*-?[0-9]+)*\s*\] ` )
spaces = regexp . MustCompile ( ` \s+ ` )
)