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.
419 lines
15 KiB
419 lines
15 KiB
|
1 week ago
|
//go:build ignore
|
||
|
|
|
||
|
|
// Prints the Go reference values of src/lib/dkc/ibe.ts (plan of phase 2,
|
||
|
|
// section 4) as the JSON of src/lib/dkc/testing/ibe-vectors.json. Everything
|
||
|
|
// comes from the libraries that tlock decrypts with for Quicknet
|
||
|
|
// (bls-unchained-g1-rfc9380): drand/kyber encrypt/ibe on
|
||
|
|
// kyber-bls12381.NewBLS12381Suite(), over kilic/bls12-381.
|
||
|
|
//
|
||
|
|
// - gt: e(G1, G2) and e(2·G1, G2), serialized by kyber-bls12381 (the order
|
||
|
|
// of kilic: c1 before c0 at every level of the tower), with H2 truncated
|
||
|
|
// to 16 and 32 bytes.
|
||
|
|
// - h3 and h4: H3 and H4 on fixed inputs, among them inputs whose first
|
||
|
|
// candidates for r are rejected.
|
||
|
|
// - round_identities: the identity of a round, scheme.DigestBeacon.
|
||
|
|
// - fixtures: for the tlock stanza of every official .dkc, the pairing of
|
||
|
|
// the release signature with U, sigma, r and the file key. The file key
|
||
|
|
// is the one tlock.TimeUnlock unwraps, and age.Decrypt opens
|
||
|
|
// OUTER_TIME_AGE with it: the header MAC and the STREAM verify.
|
||
|
|
// - kyber: messages of 0, 1, 16 and 32 bytes encrypted for round 1000 by
|
||
|
|
// ibe.EncryptCCAonG2 itself, with its random sigma, and decrypted back by
|
||
|
|
// ibe.DecryptCCAonG2.
|
||
|
|
// - decrypt: the verdict of ibe.DecryptCCAonG2, after decoding the points
|
||
|
|
// as the scheme does, on edited copies of the time_only stanza.
|
||
|
|
//
|
||
|
|
// H2, H3 and H4 are unexported in kyber: this file restates them with
|
||
|
|
// kyber's exported tags, and checks them on every fixture against what
|
||
|
|
// tlock.TimeUnlock unwraps and against U = r·G2. A mismatch panics.
|
||
|
|
//
|
||
|
|
// The kyber vectors use a random sigma, so the output is not
|
||
|
|
// byte-reproducible: the file is generated once and frozen, like the .dkc
|
||
|
|
// fixtures of the reference.
|
||
|
|
//
|
||
|
|
// Run it from a scratch module that requires the reference implementation
|
||
|
|
// (replace g.activething.com/go/DateKeys => ../datekeys-go and
|
||
|
|
// GOFLAGS=-mod=mod), passing the directory of the official fixtures:
|
||
|
|
//
|
||
|
|
// go run ibe-go-vectors.go path/to/testdata/fixtures > ibe-vectors.json
|
||
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"crypto/sha256"
|
||
|
|
"encoding/binary"
|
||
|
|
"encoding/hex"
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"math/big"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"runtime/debug"
|
||
|
|
"sort"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"filippo.io/age"
|
||
|
|
"g.activething.com/go/DateKeys/capsule"
|
||
|
|
"g.activething.com/go/DateKeys/profile"
|
||
|
|
"github.com/drand/drand/v2/common"
|
||
|
|
"github.com/drand/drand/v2/crypto"
|
||
|
|
"github.com/drand/kyber"
|
||
|
|
bls "github.com/drand/kyber-bls12381"
|
||
|
|
"github.com/drand/kyber/encrypt/ibe"
|
||
|
|
"github.com/drand/tlock"
|
||
|
|
)
|
||
|
|
|
||
|
|
// The published Quicknet signature of round 1001, as in the mutation corpus.
|
||
|
|
const sig1001 = "b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b25883abf2853d10337fb8fa41"
|
||
|
|
|
||
|
|
var (
|
||
|
|
suite = bls.NewBLS12381Suite()
|
||
|
|
// The field and the scalar orders of BLS12-381.
|
||
|
|
p, _ = new(big.Int).SetString("1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", 16)
|
||
|
|
order, _ = new(big.Int).SetString("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", 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 marshal(m interface{ MarshalBinary() ([]byte, error) }) string {
|
||
|
|
return hex.EncodeToString(must(m.MarshalBinary()))
|
||
|
|
}
|
||
|
|
|
||
|
|
func concat(parts ...[]byte) []byte { return bytes.Join(parts, nil) }
|
||
|
|
|
||
|
|
func xor(a, b []byte) []byte {
|
||
|
|
out := make([]byte, len(a))
|
||
|
|
for i := range a {
|
||
|
|
out[i] = a[i] ^ b[i]
|
||
|
|
}
|
||
|
|
return out
|
||
|
|
}
|
||
|
|
|
||
|
|
// H2, H3 and H4 of kyber encrypt/ibe (gtToHash, h3, h4), restated.
|
||
|
|
func h2(gt []byte, n int) []byte {
|
||
|
|
sum := sha256.Sum256(concat(ibe.H2Tag(), gt))
|
||
|
|
return sum[:n]
|
||
|
|
}
|
||
|
|
|
||
|
|
func h4(sigma []byte, n int) []byte {
|
||
|
|
sum := sha256.Sum256(concat(ibe.H4Tag(), sigma))
|
||
|
|
return sum[:n]
|
||
|
|
}
|
||
|
|
|
||
|
|
// h3 returns r as 32 big-endian bytes and the iteration that accepted it.
|
||
|
|
func h3(sigma, msg []byte) ([]byte, int) {
|
||
|
|
base := sha256.Sum256(concat(ibe.H3Tag(), sigma, msg))
|
||
|
|
for i := uint16(1); i < 65535; i++ {
|
||
|
|
d := sha256.Sum256(concat(binary.LittleEndian.AppendUint16(nil, i), base[:]))
|
||
|
|
d[0] >>= 1
|
||
|
|
if new(big.Int).SetBytes(d[:]).Cmp(order) < 0 {
|
||
|
|
return d[:], int(i)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
panic("h3: rejection sampling failed")
|
||
|
|
}
|
||
|
|
|
||
|
|
// rG2 is r·G2 through kyber, with r decoded as kyber's h3 decodes it.
|
||
|
|
func rG2(r []byte) kyber.Point {
|
||
|
|
s := suite.G2().Scalar()
|
||
|
|
if err := s.UnmarshalBinary(r); err != nil {
|
||
|
|
panic(err)
|
||
|
|
}
|
||
|
|
return suite.G2().Point().Mul(s, nil)
|
||
|
|
}
|
||
|
|
|
||
|
|
type gtVector struct {
|
||
|
|
Name string `json:"name"`
|
||
|
|
G1 string `json:"g1"`
|
||
|
|
G2 string `json:"g2"`
|
||
|
|
GT string `json:"gt"`
|
||
|
|
H2 string `json:"h2_16"`
|
||
|
|
H232 string `json:"h2_32"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type h3Vector struct {
|
||
|
|
Name string `json:"name"`
|
||
|
|
Sigma string `json:"sigma"`
|
||
|
|
Msg string `json:"msg"`
|
||
|
|
R string `json:"r"`
|
||
|
|
Iterations int `json:"iterations"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type h4Vector struct {
|
||
|
|
Sigma string `json:"sigma"`
|
||
|
|
H416 string `json:"h4_16"`
|
||
|
|
H432 string `json:"h4_32"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type roundIdentity struct {
|
||
|
|
Round uint64 `json:"round"`
|
||
|
|
ID string `json:"id"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type fixtureVector struct {
|
||
|
|
Name string `json:"name"`
|
||
|
|
Round uint64 `json:"round"`
|
||
|
|
Signature string `json:"signature"`
|
||
|
|
Body string `json:"body"`
|
||
|
|
GT string `json:"gt"`
|
||
|
|
Sigma string `json:"sigma"`
|
||
|
|
R string `json:"r"`
|
||
|
|
FileKey string `json:"file_key"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type ciphertextVector struct {
|
||
|
|
Name string `json:"name"`
|
||
|
|
Round uint64 `json:"round"`
|
||
|
|
Signature string `json:"signature"`
|
||
|
|
U string `json:"u"`
|
||
|
|
V string `json:"v"`
|
||
|
|
W string `json:"w"`
|
||
|
|
Go string `json:"go"`
|
||
|
|
Msg string `json:"msg,omitempty"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func main() {
|
||
|
|
scheme := must(crypto.SchemeFromName(crypto.SigsOnG1ID))
|
||
|
|
quicknet := profile.Quicknet()
|
||
|
|
key := scheme.KeyGroup.Point()
|
||
|
|
if err := key.UnmarshalBinary(quicknet.PublicKey); err != nil {
|
||
|
|
panic(err)
|
||
|
|
}
|
||
|
|
out := struct {
|
||
|
|
Description string `json:"description"`
|
||
|
|
Generator string `json:"generator"`
|
||
|
|
Libraries string `json:"libraries"`
|
||
|
|
Scheme string `json:"scheme"`
|
||
|
|
PublicKey string `json:"public_key"`
|
||
|
|
GT []gtVector `json:"gt"`
|
||
|
|
H3 []h3Vector `json:"h3"`
|
||
|
|
H4 []h4Vector `json:"h4"`
|
||
|
|
RoundIdentities []roundIdentity `json:"round_identities"`
|
||
|
|
Fixtures []fixtureVector `json:"fixtures"`
|
||
|
|
Kyber []ciphertextVector `json:"kyber"`
|
||
|
|
Decrypt []ciphertextVector `json:"decrypt"`
|
||
|
|
}{
|
||
|
|
Description: "Go reference values of the tlock IBE-CCA on G2 (spec §63 step 11) for src/lib/dkc/ibe.ts; " +
|
||
|
|
"see scripts/ibe-go-vectors.go for how each block is obtained.",
|
||
|
|
Generator: "scripts/ibe-go-vectors.go",
|
||
|
|
Libraries: libraries(),
|
||
|
|
Scheme: scheme.Name,
|
||
|
|
PublicKey: hex.EncodeToString(quicknet.PublicKey),
|
||
|
|
}
|
||
|
|
|
||
|
|
// GT and H2.
|
||
|
|
g1, g2 := suite.G1().Point().Base(), suite.G2().Point().Base()
|
||
|
|
two := suite.G1().Point().Mul(suite.G1().Scalar().SetInt64(2), g1)
|
||
|
|
square := suite.GT().Point().Add(suite.Pair(g1, g2), suite.Pair(g1, g2))
|
||
|
|
if !square.Equal(suite.Pair(two, g2)) {
|
||
|
|
panic("e(2·G1, G2) is not e(G1, G2) squared")
|
||
|
|
}
|
||
|
|
for _, c := range []struct {
|
||
|
|
name string
|
||
|
|
a, b kyber.Point
|
||
|
|
}{{"e(G1, G2)", g1, g2}, {"e(2·G1, G2), the square of e(G1, G2)", two, g2}} {
|
||
|
|
gt := must(suite.Pair(c.a, c.b).MarshalBinary())
|
||
|
|
out.GT = append(out.GT, gtVector{c.name, marshal(c.a), marshal(c.b), hex.EncodeToString(gt),
|
||
|
|
hex.EncodeToString(h2(gt, 16)), hex.EncodeToString(h2(gt, 32))})
|
||
|
|
}
|
||
|
|
|
||
|
|
// H3: fixed inputs, then the first inputs of a deterministic sequence
|
||
|
|
// whose r is accepted at the second and at the third iteration.
|
||
|
|
for _, c := range []struct{ name, sigma, msg string }{
|
||
|
|
{"16 zero bytes each", strings.Repeat("00", 16), strings.Repeat("00", 16)},
|
||
|
|
{"32 bytes each", strings.Repeat("ab", 32), strings.Repeat("cd", 32)},
|
||
|
|
{"empty", "", ""},
|
||
|
|
} {
|
||
|
|
r, it := h3(unhex(c.sigma), unhex(c.msg))
|
||
|
|
out.H3 = append(out.H3, h3Vector{c.name, c.sigma, c.msg, hex.EncodeToString(r), it})
|
||
|
|
}
|
||
|
|
for want := 2; want <= 3; want++ {
|
||
|
|
for i := uint32(0); ; i++ {
|
||
|
|
seed := sha256.Sum256(binary.BigEndian.AppendUint32([]byte("DateKeys H3 vector "), i))
|
||
|
|
sigma, msg := seed[:16], seed[16:]
|
||
|
|
if r, it := h3(sigma, msg); it == want {
|
||
|
|
out.H3 = append(out.H3, h3Vector{fmt.Sprintf("accepted at iteration %d (sequence item %d)", want, i),
|
||
|
|
hex.EncodeToString(sigma), hex.EncodeToString(msg), hex.EncodeToString(r), it})
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// H4.
|
||
|
|
for _, sigma := range []string{strings.Repeat("00", 16), strings.Repeat("5a", 32)} {
|
||
|
|
out.H4 = append(out.H4, h4Vector{sigma, hex.EncodeToString(h4(unhex(sigma), 16)), hex.EncodeToString(h4(unhex(sigma), 32))})
|
||
|
|
}
|
||
|
|
|
||
|
|
// Round identities.
|
||
|
|
for _, round := range []uint64{1, 1000, 1001, 83903165811, 1<<53 - 1} {
|
||
|
|
out.RoundIdentities = append(out.RoundIdentities, roundIdentity{round, hex.EncodeToString(scheme.DigestBeacon(&common.Beacon{Round: round}))})
|
||
|
|
}
|
||
|
|
|
||
|
|
// The fixtures.
|
||
|
|
dir := os.Args[1]
|
||
|
|
names := must(filepath.Glob(filepath.Join(dir, "*.dkc")))
|
||
|
|
sort.Strings(names)
|
||
|
|
var timeOnly fixtureVector
|
||
|
|
for _, name := range names {
|
||
|
|
v := fixture(scheme, key, name)
|
||
|
|
out.Fixtures = append(out.Fixtures, v)
|
||
|
|
if v.Name == "time_only" {
|
||
|
|
timeOnly = v
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if timeOnly.Name == "" {
|
||
|
|
panic("no time_only fixture")
|
||
|
|
}
|
||
|
|
|
||
|
|
// Encryptions by kyber, for round 1000.
|
||
|
|
id1000 := scheme.DigestBeacon(&common.Beacon{Round: 1000})
|
||
|
|
sig1000 := unhex(timeOnly.Signature)
|
||
|
|
for _, n := range []int{0, 1, 16, 32} {
|
||
|
|
msg := sha256.Sum256([]byte("DateKeys IBE vector message"))
|
||
|
|
ct := must(ibe.EncryptCCAonG2(suite, key, id1000, msg[:n]))
|
||
|
|
v := verdict(scheme, fmt.Sprintf("a %d-byte message", n), 1000, sig1000, unhex(marshal(ct.U)), ct.V, ct.W)
|
||
|
|
if v.Go != "ok" || v.Msg != hex.EncodeToString(msg[:n]) {
|
||
|
|
panic("kyber does not decrypt its own ciphertext")
|
||
|
|
}
|
||
|
|
out.Kyber = append(out.Kyber, v)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Edited copies of the time_only stanza.
|
||
|
|
body := unhex(timeOnly.Body)
|
||
|
|
u, vv, w := body[:96], body[96:112], body[112:]
|
||
|
|
flip := func(b []byte, i int, mask byte) []byte {
|
||
|
|
c := bytes.Clone(b)
|
||
|
|
c[i] ^= mask
|
||
|
|
return c
|
||
|
|
}
|
||
|
|
// c0 is the second coordinate of the compressed encoding of G2.
|
||
|
|
c0 := new(big.Int).SetBytes(u[48:])
|
||
|
|
uc0p := concat(u[:48], new(big.Int).Add(c0, p).FillBytes(make([]byte, 48)))
|
||
|
|
infinityG2 := concat([]byte{0xc0}, make([]byte, 95))
|
||
|
|
infinityG1 := concat([]byte{0xc0}, make([]byte, 47))
|
||
|
|
for _, c := range []struct {
|
||
|
|
name string
|
||
|
|
sig, u, v, w []byte
|
||
|
|
}{
|
||
|
|
{"the time_only stanza", sig1000, u, vv, w},
|
||
|
|
{"U with p added to c0", sig1000, uc0p, vv, w},
|
||
|
|
{"U is the point at infinity", sig1000, infinityG2, vv, w},
|
||
|
|
{"U negated", sig1000, flip(u, 0, 0x20), vv, w},
|
||
|
|
{"V with its first bit flipped", sig1000, u, flip(vv, 0, 0x80), w},
|
||
|
|
{"W with its last bit flipped", sig1000, u, vv, flip(w, 15, 0x01)},
|
||
|
|
{"the signature of round 1001", unhex(sig1001), u, vv, w},
|
||
|
|
{"the signature negated", flip(sig1000, 0, 0x20), u, vv, w},
|
||
|
|
{"the signature is the point at infinity", infinityG1, u, vv, w},
|
||
|
|
{"W one byte shorter than V", sig1000, u, vv, w[:15]},
|
||
|
|
{"V and W of 33 bytes", sig1000, u, concat(vv, vv, []byte{0}), concat(w, w, []byte{0})},
|
||
|
|
} {
|
||
|
|
out.Decrypt = append(out.Decrypt, verdict(scheme, c.name, 1000, c.sig, c.u, c.v, c.w))
|
||
|
|
}
|
||
|
|
|
||
|
|
enc := json.NewEncoder(os.Stdout)
|
||
|
|
enc.SetIndent("", " ")
|
||
|
|
enc.SetEscapeHTML(false)
|
||
|
|
if err := enc.Encode(out); err != nil {
|
||
|
|
panic(err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// fixture opens the OUTER_TIME_AGE of a .dkc with the release of its sidecar,
|
||
|
|
// through tlock.TimeUnlock inside an age identity, and restates the IBE.
|
||
|
|
func fixture(scheme *crypto.Scheme, key kyber.Point, path string) fixtureVector {
|
||
|
|
file := must(os.ReadFile(path))
|
||
|
|
var side struct {
|
||
|
|
Release struct {
|
||
|
|
Round uint64 `json:"round"`
|
||
|
|
Signature string `json:"signature"`
|
||
|
|
} `json:"release"`
|
||
|
|
}
|
||
|
|
if err := json.Unmarshal(must(os.ReadFile(strings.TrimSuffix(path, ".dkc")+".json")), &side); err != nil {
|
||
|
|
panic(err)
|
||
|
|
}
|
||
|
|
sig := unhex(side.Release.Signature)
|
||
|
|
pre := must(capsule.ParsePrelude(file))
|
||
|
|
start := capsule.PreludeSize + int(pre.PublicHeaderLen)
|
||
|
|
sealed := file[start : start+int(pre.SealedControlLen)]
|
||
|
|
|
||
|
|
var body, fileKey []byte
|
||
|
|
id := unwrap(func(stanzas []*age.Stanza) ([]byte, error) {
|
||
|
|
if len(stanzas) != 1 || stanzas[0].Type != "tlock" || len(stanzas[0].Args) != 2 || stanzas[0].Args[0] != strconv.FormatUint(side.Release.Round, 10) {
|
||
|
|
panic("not one tlock stanza for the round of the release")
|
||
|
|
}
|
||
|
|
body = stanzas[0].Body
|
||
|
|
ct := must(tlock.BytesToCiphertext(*scheme, body))
|
||
|
|
fileKey = must(tlock.TimeUnlock(*scheme, key, common.Beacon{Round: side.Release.Round, Signature: sig}, ct))
|
||
|
|
return bytes.Clone(fileKey), nil
|
||
|
|
})
|
||
|
|
r := must(age.Decrypt(bytes.NewReader(sealed), id))
|
||
|
|
if _, err := io.Copy(io.Discard, r); err != nil {
|
||
|
|
panic(fmt.Sprintf("%s: the age payload does not open: %v", path, err))
|
||
|
|
}
|
||
|
|
|
||
|
|
// The IBE restated, checked against tlock.
|
||
|
|
u, v, w := body[:96], body[96:112], body[112:]
|
||
|
|
sp, up := scheme.SigGroup.Point(), scheme.KeyGroup.Point()
|
||
|
|
if sp.UnmarshalBinary(sig) != nil || up.UnmarshalBinary(u) != nil {
|
||
|
|
panic("points do not decode")
|
||
|
|
}
|
||
|
|
gt := must(suite.Pair(sp, up).MarshalBinary())
|
||
|
|
sigma := xor(v, h2(gt, len(w)))
|
||
|
|
msg := xor(w, h4(sigma, len(w)))
|
||
|
|
if !bytes.Equal(msg, fileKey) {
|
||
|
|
panic(path + ": the restated H2 and H4 disagree with tlock")
|
||
|
|
}
|
||
|
|
rb, _ := h3(sigma, msg)
|
||
|
|
if !rG2(rb).Equal(up) {
|
||
|
|
panic(path + ": the restated H3 disagrees with U")
|
||
|
|
}
|
||
|
|
return fixtureVector{strings.TrimSuffix(filepath.Base(path), ".dkc"), side.Release.Round, side.Release.Signature,
|
||
|
|
hex.EncodeToString(body), hex.EncodeToString(gt), hex.EncodeToString(sigma), hex.EncodeToString(rb), hex.EncodeToString(fileKey)}
|
||
|
|
}
|
||
|
|
|
||
|
|
// verdict decodes the points as the scheme does and runs ibe.DecryptCCAonG2,
|
||
|
|
// the decryption of tlock.TimeUnlock for Quicknet after its beacon check.
|
||
|
|
func verdict(scheme *crypto.Scheme, name string, round uint64, sig, u, v, w []byte) ciphertextVector {
|
||
|
|
out := ciphertextVector{Name: name, Round: round, Signature: hex.EncodeToString(sig), U: hex.EncodeToString(u),
|
||
|
|
V: hex.EncodeToString(v), W: hex.EncodeToString(w), Go: "reject"}
|
||
|
|
sp, up := scheme.SigGroup.Point(), scheme.KeyGroup.Point()
|
||
|
|
if sp.UnmarshalBinary(sig) != nil || up.UnmarshalBinary(u) != nil {
|
||
|
|
return out
|
||
|
|
}
|
||
|
|
msg, err := ibe.DecryptCCAonG2(suite, sp, &ibe.Ciphertext{U: up, V: v, W: w})
|
||
|
|
if err != nil {
|
||
|
|
return out
|
||
|
|
}
|
||
|
|
out.Go, out.Msg = "ok", hex.EncodeToString(msg)
|
||
|
|
return out
|
||
|
|
}
|
||
|
|
|
||
|
|
type unwrap func([]*age.Stanza) ([]byte, error)
|
||
|
|
|
||
|
|
func (f unwrap) Unwrap(stanzas []*age.Stanza) ([]byte, error) { return f(stanzas) }
|
||
|
|
|
||
|
|
// 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, ", ")
|
||
|
|
}
|