Stage 3: the IBE of tlock, its stanza and the verification of releases
ibe.dart is DecryptCCAonG2 and EncryptCCAonG2 of drand/kyber for Quicknet,
as ibe.ts of datekeys-ts: H2 over GT in the order of kilic, H3 with its
rejection of candidates, H4, the identity of a round, the gates of the
signature and of U, and fixed texts that carry no value of the
computation. Encryption takes an injectable sigma, so that the vectors of
Go reproduce byte for byte; sigma and r are secret and BigInt is not
constant time.
release.dart is provider.Verify, in its order and with its texts, for the
scheme of Quicknet only, as release.ts; the supplied release; and the rule
of step 9, under which whatever a source throws is
ERR_RELEASE_UNAVAILABLE. tlock.dart is NewTimeIdentity with its Unwrap and
NewTimeRecipient of agewrap on the arguments and the body of the stanza.
tool/ibe_go_vectors.go, tool/tlock_go_vectors.go and
tool/release_go_vectors.go, ports of the generators of datekeys-ts where
they exist, write the vectors from kyber, tlock, age, provider and agewrap
on the fixtures of testdata/. The ciphertexts with a random sigma are the
frozen ones of datekeys-ts at 289fe71, decrypted again by Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 days ago
//go:build ignore
// Prints test/vectors/release_vectors.json: the verdicts, codes and texts of
// the Go reference for lib/src/release.dart and lib/src/tlock.dart.
//
// - verify: provider.Verify (spec §17, §51, §63 step 10) on the published
// releases of the fixtures and on edited ones: other rounds, other
// lengths, re-encodings of the signature (x + p, the point at infinity,
// stray flags, the negation), rounds out of range, and profiles with
// another scheme or another key. The x + p signature of round 1004 is the
// one of the mutation corpus (testdata/vectors/mutations.json).
// - unwrap: agewrap.NewTimeIdentity and its Unwrap (spec §63 step 11) on
// the stanza of OUTER_TIME_AGE of the time_only fixture and on edited
// copies of it: the count and type of the stanzas, their arguments, the
// release, and the body (its length, U re-encoded or the point at
// infinity, V and W edited).
// - recipient: agewrap.NewTimeRecipient on the profile and the round.
//
// Each verdict is "ok", with the file key for an unwrap, or the normative
// code and the text of the error of Go.
//
// Run it in the module of the reference implementation, which it imports,
// without changing anything there, from the datekeys-go next to this
// repository: at the tag spec-v0.11 or at the draft v0.12, whose packages
// that it uses are the same, and so is the output.
Stage 3: the IBE of tlock, its stanza and the verification of releases
ibe.dart is DecryptCCAonG2 and EncryptCCAonG2 of drand/kyber for Quicknet,
as ibe.ts of datekeys-ts: H2 over GT in the order of kilic, H3 with its
rejection of candidates, H4, the identity of a round, the gates of the
signature and of U, and fixed texts that carry no value of the
computation. Encryption takes an injectable sigma, so that the vectors of
Go reproduce byte for byte; sigma and r are secret and BigInt is not
constant time.
release.dart is provider.Verify, in its order and with its texts, for the
scheme of Quicknet only, as release.ts; the supplied release; and the rule
of step 9, under which whatever a source throws is
ERR_RELEASE_UNAVAILABLE. tlock.dart is NewTimeIdentity with its Unwrap and
NewTimeRecipient of agewrap on the arguments and the body of the stanza.
tool/ibe_go_vectors.go, tool/tlock_go_vectors.go and
tool/release_go_vectors.go, ports of the generators of datekeys-ts where
they exist, write the vectors from kyber, tlock, age, provider and agewrap
on the fixtures of testdata/. The ciphertexts with a random sigma are the
frozen ones of datekeys-ts at 289fe71, decrypted again by Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 days ago
//
// cd ../datekeys-go && go run ../datekeys-dart/tool/release_go_vectors.go \
// ../datekeys-dart/testdata > ../datekeys-dart/test/vectors/release_vectors.json
Stage 3: the IBE of tlock, its stanza and the verification of releases
ibe.dart is DecryptCCAonG2 and EncryptCCAonG2 of drand/kyber for Quicknet,
as ibe.ts of datekeys-ts: H2 over GT in the order of kilic, H3 with its
rejection of candidates, H4, the identity of a round, the gates of the
signature and of U, and fixed texts that carry no value of the
computation. Encryption takes an injectable sigma, so that the vectors of
Go reproduce byte for byte; sigma and r are secret and BigInt is not
constant time.
release.dart is provider.Verify, in its order and with its texts, for the
scheme of Quicknet only, as release.ts; the supplied release; and the rule
of step 9, under which whatever a source throws is
ERR_RELEASE_UNAVAILABLE. tlock.dart is NewTimeIdentity with its Unwrap and
NewTimeRecipient of agewrap on the arguments and the body of the stanza.
tool/ibe_go_vectors.go, tool/tlock_go_vectors.go and
tool/release_go_vectors.go, ports of the generators of datekeys-ts where
they exist, write the vectors from kyber, tlock, age, provider and agewrap
on the fixtures of testdata/. The ciphertexts with a random sigma are the
frozen ones of datekeys-ts at 289fe71, decrypted again by Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 days ago
package main
import (
"bytes"
"encoding/hex"
"encoding/json"
"math/big"
"os"
"path/filepath"
"runtime/debug"
"sort"
"strings"
"filippo.io/age"
datekeys "g.activething.com/go/DateKeys"
"g.activething.com/go/DateKeys/agewrap"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/profile"
"g.activething.com/go/DateKeys/provider"
bls "github.com/drand/kyber-bls12381"
)
var p , _ = new ( big . Int ) . SetString ( "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" , 16 )
func must [ T any ] ( v T , err error ) T {
if err != nil {
panic ( err )
}
return v
}
func unhex ( s string ) [ ] byte { return must ( hex . DecodeString ( s ) ) }
func flip ( b [ ] byte , i int , mask byte ) [ ] byte {
c := bytes . Clone ( b )
c [ i ] ^ = mask
return c
}
func infinity ( n int , first byte ) [ ] byte {
b := make ( [ ] byte , n )
b [ 0 ] = first
return b
}
// minusP is the x + p encoding with p subtracted again, flags kept: the
// canonical one.
func minusP ( b [ ] byte ) [ ] byte {
flags := b [ 0 ] & 0xe0
c := bytes . Clone ( b )
c [ 0 ] &= 0x1f
x := new ( big . Int ) . Sub ( new ( big . Int ) . SetBytes ( c ) , p )
out := x . FillBytes ( make ( [ ] byte , 48 ) )
out [ 0 ] |= flags
return out
}
type result struct {
Go string ` json:"go" `
Code string ` json:"code,omitempty" `
Text string ` json:"text,omitempty" `
FileKey string ` json:"file_key,omitempty" `
}
func verdict ( err error ) result {
if err == nil {
return result { Go : "ok" }
}
return result { Go : "reject" , Code : datekeys . Code ( err ) , Text : err . Error ( ) }
}
// A profile edit: the Quicknet profile with another scheme or key.
type profileCase struct {
Name string ` json:"name" `
Scheme string ` json:"scheme" `
PublicKey string ` json:"public_key" `
}
type verifyCase struct {
Name string ` json:"name" `
Profile string ` json:"profile" `
Round uint64 ` json:"round" `
Release uint64 ` json:"release_round" `
Signature string ` json:"signature" `
result
}
type stanza struct {
Type string ` json:"type" `
Args [ ] string ` json:"args" `
Body string ` json:"body" `
}
type unwrapCase struct {
Name string ` json:"name" `
Profile string ` json:"profile" `
Round uint64 ` json:"round" `
Release uint64 ` json:"release_round" `
Signature string ` json:"signature" `
Stanzas [ ] stanza ` json:"stanzas" `
result
}
type recipientCase struct {
Name string ` json:"name" `
Profile string ` json:"profile" `
Round uint64 ` json:"round" `
result
}
func main ( ) {
dir := os . Args [ 1 ]
release := func ( name string ) ( uint64 , [ ] byte ) {
var side struct {
Release struct {
Round uint64 ` json:"round" `
Signature string ` json:"signature" `
} ` json:"release" `
}
if err := json . Unmarshal ( must ( os . ReadFile ( filepath . Join ( dir , "fixtures" , name + ".json" ) ) ) , & side ) ; err != nil {
panic ( err )
}
return side . Release . Round , unhex ( side . Release . Signature )
}
_ , sig1000 := release ( "time_only" )
_ , sig1001 := release ( "empty_payload" )
_ , sig2000 := release ( "time_only_extensions" )
var corpus struct {
Cases [ ] struct {
Name string ` json:"name" `
Release * struct {
Round uint64 ` json:"round" `
Signature string ` json:"signature" `
} ` json:"release" `
} ` json:"cases" `
}
if err := json . Unmarshal ( must ( os . ReadFile ( filepath . Join ( dir , "vectors" , "mutations.json" ) ) ) , & corpus ) ; err != nil {
panic ( err )
}
var xPlusP [ ] byte
for _ , c := range corpus . Cases {
if c . Name == "release signature re-encoded with x + p" {
if c . Release . Round != 1004 {
panic ( "the x + p case is not of round 1004" )
}
xPlusP = unhex ( c . Release . Signature )
}
}
if xPlusP == nil {
panic ( "no x + p case in the mutation corpus" )
}
sig1004 := minusP ( xPlusP )
quicknet := profile . Quicknet ( )
key := quicknet . PublicKey
g2 := must ( bls . NewBLS12381Suite ( ) . G2 ( ) . Point ( ) . Base ( ) . MarshalBinary ( ) )
profiles := [ ] profileCase {
{ "quicknet" , quicknet . Scheme , hex . EncodeToString ( key ) } ,
{ "scheme pedersen-bls-unchained" , "pedersen-bls-unchained" , hex . EncodeToString ( key ) } ,
{ "scheme bls-unchained-on-g1" , "bls-unchained-on-g1" , hex . EncodeToString ( key ) } ,
{ "scheme not of drand" , "datekeys-test" , hex . EncodeToString ( key ) } ,
{ "key the generator of G2" , quicknet . Scheme , hex . EncodeToString ( g2 ) } ,
{ "key the point at infinity" , quicknet . Scheme , hex . EncodeToString ( infinity ( 96 , 0xc0 ) ) } ,
{ "key with the compression flag cleared" , quicknet . Scheme , hex . EncodeToString ( flip ( key , 0 , 0x80 ) ) } ,
}
profileOf := func ( name string ) * profile . Profile {
for _ , c := range profiles {
if c . Name == name {
q := quicknet . Clone ( )
q . Scheme , q . PublicKey = c . Scheme , unhex ( c . PublicKey )
return q
}
}
panic ( "no profile " + name )
}
max := quicknet . MaxRound ( )
out := struct {
Description string ` json:"description" `
Generator string ` json:"generator" `
Libraries string ` json:"libraries" `
MaxRound uint64 ` json:"max_round" `
Profiles [ ] profileCase ` json:"profiles" `
Verify [ ] verifyCase ` json:"verify" `
Unwrap [ ] unwrapCase ` json:"unwrap" `
Recipient [ ] recipientCase ` json:"recipient" `
} {
Description : "Verdicts, codes and texts of provider.Verify, agewrap.NewTimeIdentity with its Unwrap and " +
"agewrap.NewTimeRecipient of the Go reference; see tool/release_go_vectors.go." ,
Generator : "tool/release_go_vectors.go" ,
Libraries : libraries ( ) ,
MaxRound : max ,
Profiles : profiles ,
}
// provider.Verify.
for _ , c := range [ ] struct {
name , profile string
round , release uint64
sig [ ] byte
} {
{ "the published release of round 1000" , "quicknet" , 1000 , 1000 , sig1000 } ,
{ "the published release of round 1001" , "quicknet" , 1001 , 1001 , sig1001 } ,
{ "the published release of round 1004" , "quicknet" , 1004 , 1004 , sig1004 } ,
{ "the published release of round 2000" , "quicknet" , 2000 , 2000 , sig2000 } ,
{ "a valid release of another round" , "quicknet" , 1000 , 1001 , sig1001 } ,
{ "another round and a short signature" , "quicknet" , 1000 , 1001 , sig1001 [ : 47 ] } ,
{ "the signature of another round relabelled" , "quicknet" , 1000 , 1000 , sig1001 } ,
{ "the signature of round 1000 for round 999" , "quicknet" , 999 , 999 , sig1000 } ,
{ "an all-zero signature" , "quicknet" , 1000 , 1000 , make ( [ ] byte , 48 ) } ,
{ "a flipped bit" , "quicknet" , 1000 , 1000 , flip ( sig1000 , 47 , 1 ) } ,
{ "a short signature" , "quicknet" , 1000 , 1000 , sig1000 [ : 47 ] } ,
{ "a long signature" , "quicknet" , 1000 , 1000 , append ( bytes . Clone ( sig1000 ) , 0 ) } ,
{ "a G2-sized signature" , "quicknet" , 1000 , 1000 , append ( bytes . Clone ( sig1000 ) , sig1000 ... ) } ,
{ "an empty signature" , "quicknet" , 1000 , 1000 , nil } ,
{ "the signature re-encoded with x + p" , "quicknet" , 1004 , 1004 , xPlusP } ,
{ "the signature is the point at infinity" , "quicknet" , 1000 , 1000 , infinity ( 48 , 0xc0 ) } ,
{ "the infinity flag and a payload" , "quicknet" , 1000 , 1000 , flip ( sig1000 , 0 , 0x40 ) } ,
{ "the point at infinity with the sign flag" , "quicknet" , 1000 , 1000 , infinity ( 48 , 0xe0 ) } ,
{ "the compression flag cleared" , "quicknet" , 1000 , 1000 , flip ( sig1000 , 0 , 0x80 ) } ,
{ "the signature negated" , "quicknet" , 1000 , 1000 , flip ( sig1000 , 0 , 0x20 ) } ,
{ "round zero" , "quicknet" , 0 , 0 , sig1000 } ,
{ "the last round of the profile" , "quicknet" , max , max , sig1000 } ,
{ "a round beyond the profile" , "quicknet" , max + 1 , max + 1 , sig1000 } ,
{ "another scheme of drand" , "scheme pedersen-bls-unchained" , 1000 , 1000 , sig1000 } ,
{ "another scheme of drand on G1" , "scheme bls-unchained-on-g1" , 1000 , 1000 , sig1000 } ,
{ "a scheme that is not of drand" , "scheme not of drand" , 1000 , 1000 , sig1000 } ,
{ "another scheme and another round" , "scheme pedersen-bls-unchained" , 1000 , 1001 , sig1001 } ,
{ "another valid key" , "key the generator of G2" , 1000 , 1000 , sig1000 } ,
{ "the key is the point at infinity" , "key the point at infinity" , 1000 , 1000 , sig1000 } ,
{ "the key and the signature are the point at infinity" , "key the point at infinity" , 1000 , 1000 , infinity ( 48 , 0xc0 ) } ,
{ "a key that is not a canonical encoding" , "key with the compression flag cleared" , 1000 , 1000 , sig1000 } ,
{ "a key that is not a canonical encoding and a short signature" , "key with the compression flag cleared" , 1000 , 1000 , sig1000 [ : 47 ] } ,
} {
err := provider . Verify ( profileOf ( c . profile ) , provider . Condition { Round : c . round } , provider . Release { Round : c . release , Signature : c . sig } )
out . Verify = append ( out . Verify , verifyCase { c . name , c . profile , c . round , c . release , hex . EncodeToString ( c . sig ) , verdict ( err ) } )
}
// agewrap.TimeIdentity on the stanza of the time_only fixture.
file := must ( os . ReadFile ( filepath . Join ( dir , "fixtures" , "time_only.dkc" ) ) )
pre := must ( capsule . ParsePrelude ( file ) )
start := capsule . PreludeSize + int ( pre . PublicHeaderLen )
sealed := file [ start : start + int ( pre . SealedControlLen ) ]
stanzas := must ( agewrap . Stanzas ( bytes . NewReader ( sealed ) ) )
if len ( stanzas ) != 1 {
panic ( "time_only has not one stanza" )
}
base := stanzas [ 0 ]
body := base . Body
u , v , w := body [ : 96 ] , body [ 96 : 112 ] , body [ 112 : ]
c0 := new ( big . Int ) . SetBytes ( u [ 48 : ] )
uc0p := append ( bytes . Clone ( u [ : 48 ] ) , new ( big . Int ) . Add ( c0 , p ) . FillBytes ( make ( [ ] byte , 48 ) ) ... )
cat := func ( parts ... [ ] byte ) [ ] byte { return bytes . Join ( parts , nil ) }
with := func ( edit func ( s * age . Stanza ) ) [ ] * age . Stanza {
s := & age . Stanza { Type : base . Type , Args : append ( [ ] string ( nil ) , base . Args ... ) , Body : bytes . Clone ( base . Body ) }
edit ( s )
return [ ] * age . Stanza { s }
}
same := func ( * age . Stanza ) { }
other := & age . Stanza { Type : "X25519" , Args : [ ] string { "LvR2+5baviJPeIiyw96VfTW1GnzTw3DbWIPGuq9amEw" } , Body : make ( [ ] byte , 32 ) }
for _ , c := range [ ] struct {
name , profile string
round , release uint64
sig [ ] byte
stanzas [ ] * age . Stanza
} {
{ "the stanza of time_only" , "quicknet" , 1000 , 1000 , sig1000 , with ( same ) } ,
{ "no stanza" , "quicknet" , 1000 , 1000 , sig1000 , nil } ,
{ "two stanzas" , "quicknet" , 1000 , 1000 , sig1000 , append ( with ( same ) , with ( same ) ... ) } ,
{ "a stanza of type X25519" , "quicknet" , 1000 , 1000 , sig1000 , [ ] * age . Stanza { other } } ,
{ "one argument" , "quicknet" , 1000 , 1000 , sig1000 , with ( func ( s * age . Stanza ) { s . Args = s . Args [ : 1 ] } ) } ,
{ "three arguments" , "quicknet" , 1000 , 1000 , sig1000 , with ( func ( s * age . Stanza ) { s . Args = append ( s . Args , "x" ) } ) } ,
{ "the round of another DateKey" , "quicknet" , 1000 , 1000 , sig1000 , with ( func ( s * age . Stanza ) { s . Args [ 0 ] = "1001" } ) } ,
{ "the round with a leading zero" , "quicknet" , 1000 , 1000 , sig1000 , with ( func ( s * age . Stanza ) { s . Args [ 0 ] = "01000" } ) } ,
{ "the round with a quote, a newline and a letter that is not ASCII" , "quicknet" , 1000 , 1000 , sig1000 , with ( func ( s * age . Stanza ) { s . Args [ 0 ] = "1000" + string ( rune ( 34 ) ) + string ( rune ( 10 ) ) + string ( rune ( 233 ) ) } ) } ,
{ "the chain hash in upper case" , "quicknet" , 1000 , 1000 , sig1000 , with ( func ( s * age . Stanza ) { s . Args [ 1 ] = strings . ToUpper ( s . Args [ 1 ] ) } ) } ,
{ "another chain hash" , "quicknet" , 1000 , 1000 , sig1000 , with ( func ( s * age . Stanza ) { s . Args [ 1 ] = strings . Repeat ( "ab" , 32 ) } ) } ,
{ "a release of another round" , "quicknet" , 1000 , 1001 , sig1001 , with ( same ) } ,
{ "the release signature negated" , "quicknet" , 1000 , 1000 , flip ( sig1000 , 0 , 0x20 ) , with ( same ) } ,
{ "the release signature negated and a body of 127 bytes" , "quicknet" , 1000 , 1000 , flip ( sig1000 , 0 , 0x20 ) , with ( func ( s * age . Stanza ) { s . Body = s . Body [ : 127 ] } ) } ,
{ "a body of 127 bytes" , "quicknet" , 1000 , 1000 , sig1000 , with ( func ( s * age . Stanza ) { s . Body = s . Body [ : 127 ] } ) } ,
{ "a body of 129 bytes" , "quicknet" , 1000 , 1000 , sig1000 , with ( func ( s * age . Stanza ) { s . Body = append ( s . Body , 0 ) } ) } ,
{ "an empty body" , "quicknet" , 1000 , 1000 , sig1000 , with ( func ( s * age . Stanza ) { s . Body = nil } ) } ,
{ "U re-encoded with c0 + p" , "quicknet" , 1000 , 1000 , sig1000 , with ( func ( s * age . Stanza ) { s . Body = cat ( uc0p , v , w ) } ) } ,
{ "U is the point at infinity" , "quicknet" , 1000 , 1000 , sig1000 , with ( func ( s * age . Stanza ) { s . Body = cat ( infinity ( 96 , 0xc0 ) , v , w ) } ) } ,
{ "U with the infinity flag and a payload" , "quicknet" , 1000 , 1000 , sig1000 , with ( func ( s * age . Stanza ) { s . Body = cat ( flip ( u , 0 , 0x40 ) , v , w ) } ) } ,
{ "U with the compression flag cleared" , "quicknet" , 1000 , 1000 , sig1000 , with ( func ( s * age . Stanza ) { s . Body = cat ( flip ( u , 0 , 0x80 ) , v , w ) } ) } ,
{ "U negated" , "quicknet" , 1000 , 1000 , sig1000 , with ( func ( s * age . Stanza ) { s . Body = cat ( flip ( u , 0 , 0x20 ) , v , w ) } ) } ,
{ "U is the generator of G2" , "quicknet" , 1000 , 1000 , sig1000 , with ( func ( s * age . Stanza ) { s . Body = cat ( unhex ( hex . EncodeToString ( g2 ) ) , v , w ) } ) } ,
{ "V with its first bit flipped" , "quicknet" , 1000 , 1000 , sig1000 , with ( func ( s * age . Stanza ) { s . Body = cat ( u , flip ( v , 0 , 0x80 ) , w ) } ) } ,
{ "W with its last bit flipped" , "quicknet" , 1000 , 1000 , sig1000 , with ( func ( s * age . Stanza ) { s . Body = cat ( u , v , flip ( w , 15 , 0x01 ) ) } ) } ,
{ "another scheme of drand" , "scheme pedersen-bls-unchained" , 1000 , 1000 , sig1000 , with ( same ) } ,
{ "a key that is not a canonical encoding" , "key with the compression flag cleared" , 1000 , 1000 , sig1000 , with ( same ) } ,
{ "the key is the point at infinity" , "key the point at infinity" , 1000 , 1000 , sig1000 , with ( same ) } ,
{ "another valid key" , "key the generator of G2" , 1000 , 1000 , sig1000 , with ( same ) } ,
} {
uc := unwrapCase { Name : c . name , Profile : c . profile , Round : c . round , Release : c . release , Signature : hex . EncodeToString ( c . sig ) , Stanzas : [ ] stanza { } }
for _ , s := range c . stanzas {
uc . Stanzas = append ( uc . Stanzas , stanza { s . Type , append ( [ ] string { } , s . Args ... ) , hex . EncodeToString ( s . Body ) } )
}
id , err := agewrap . NewTimeIdentity ( profileOf ( c . profile ) , c . round , provider . Release { Round : c . release , Signature : c . sig } )
if err == nil {
var fk [ ] byte
if fk , err = id . Unwrap ( c . stanzas ) ; err == nil {
uc . result = result { Go : "ok" , FileKey : hex . EncodeToString ( fk ) }
out . Unwrap = append ( out . Unwrap , uc )
continue
}
}
uc . result = verdict ( err )
out . Unwrap = append ( out . Unwrap , uc )
}
// agewrap.NewTimeRecipient.
for _ , c := range [ ] struct {
name , profile string
round uint64
} {
{ "round 1000" , "quicknet" , 1000 } ,
{ "the last round" , "quicknet" , max } ,
{ "round zero" , "quicknet" , 0 } ,
{ "a round beyond the profile" , "quicknet" , max + 1 } ,
{ "another scheme of drand" , "scheme pedersen-bls-unchained" , 1000 } ,
{ "a scheme that is not of drand" , "scheme not of drand" , 1000 } ,
{ "a key that is not a canonical encoding" , "key with the compression flag cleared" , 1000 } ,
{ "the key is the point at infinity" , "key the point at infinity" , 1000 } ,
{ "the key is the point at infinity and round zero" , "key the point at infinity" , 0 } ,
} {
_ , err := agewrap . NewTimeRecipient ( profileOf ( c . profile ) , c . round )
out . Recipient = append ( out . Recipient , recipientCase { c . name , c . profile , c . round , verdict ( err ) } )
}
enc := json . NewEncoder ( os . Stdout )
enc . SetIndent ( "" , " " )
enc . SetEscapeHTML ( false )
if err := enc . Encode ( out ) ; err != nil {
panic ( err )
}
}
// libraries names the versions of the libraries this program ran with.
func libraries ( ) string {
info , ok := debug . ReadBuildInfo ( )
if ! ok {
panic ( "no build info" )
}
var out [ ] string
for _ , d := range info . Deps {
switch d . Path {
case "filippo.io/age" , "github.com/drand/tlock" , "github.com/drand/kyber" , "github.com/drand/kyber-bls12381" , "github.com/drand/drand/v2" :
out = append ( out , d . Path + " " + d . Version )
}
}
sort . Strings ( out )
return strings . Join ( out , ", " )
}