You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dateKeys-dart/tool/tlock_go_vectors.go

282 lines
9.2 KiB

//go:build ignore
// Prints test/vectors/tlock_vectors.json: the Go reference values for the
// tlock encryption of lib/src/ibe.dart and lib/src/tlock.dart, a port of
// scripts/tlock-go-vectors.go of datekeys-ts.
//
// - encrypt: EncryptCCAonG2 of drand/kyber with the suite of tlock for
// Quicknet, restated with a fixed sigma, for messages of 0, 1, 16 and 32
// bytes to rounds 1000 and 1001. kyber draws sigma from crypto/rand, so
// the restatement is checked: ibe.DecryptCCAonG2 opens every ciphertext
// with the published signature of its round, and tlock.BytesToCiphertext
// with tlock.TimeUnlock opens the 16-byte ones, as a tlock stanza body.
// - interop: the ciphertexts that scripts/tlock-ts-samples.mjs of
// datekeys-ts made with the TypeScript library, frozen in its
// src/lib/dkc/testing/tlock-vectors.json at 289fe71, each opened again by
// the reference here. An IBE body goes through tlock.BytesToCiphertext and
// tlock.TimeUnlock; an age file through age.Decrypt with
// agewrap.NewTimeIdentity, the identity of capsule.Open at step 11. Each
// sample gets the verdict "ok" with what Go recovered, or "reject".
//
// H2, H3 and H4 are unexported in kyber; they are restated with its tags, as
// in tool/ibe_go_vectors.go, and the decryptions above check them.
//
// 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.
//
// cd ../datekeys-go && go run ../datekeys-dart/tool/tlock_go_vectors.go \
// ../datekeys-ts/src/lib/dkc/testing/tlock-vectors.json \
// > ../datekeys-dart/test/vectors/tlock_vectors.json
package main
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"math/big"
"os"
"runtime/debug"
"sort"
"strings"
"filippo.io/age"
"g.activething.com/go/DateKeys/agewrap"
"g.activething.com/go/DateKeys/profile"
"g.activething.com/go/DateKeys/provider"
"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 signatures of rounds 1000 and 1001, as in the
// fixtures and the mutation corpus; provider.Verify checks them below.
var published = map[uint64]string{
1000: "b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39",
1001: "b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b25883abf2853d10337fb8fa41",
}
var (
suite = bls.NewBLS12381Suite()
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 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
}
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]
}
func h3(sigma, msg []byte) kyber.Scalar {
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 {
r := suite.G1().Scalar()
if err := r.UnmarshalBinary(d[:]); err != nil {
panic(err)
}
return r
}
}
panic("h3: rejection sampling failed")
}
// encrypt is EncryptCCAonG2 of kyber with the given sigma.
func encrypt(key kyber.Point, id, msg, sigma []byte) *ibe.Ciphertext {
qid := suite.G1().Point().(kyber.HashablePoint).Hash(id)
gid := suite.Pair(qid, key)
r := h3(sigma, msg)
gt := must(suite.GT().Point().Mul(r, gid).MarshalBinary())
return &ibe.Ciphertext{U: suite.G2().Point().Mul(r, nil), V: xor(sigma, h2(gt, len(msg))), W: xor(msg, h4(sigma, len(msg)))}
}
type encryptVector struct {
Name string `json:"name"`
Round uint64 `json:"round"`
ID string `json:"id"`
Msg string `json:"msg"`
Sigma string `json:"sigma"`
U string `json:"u"`
V string `json:"v"`
W string `json:"w"`
Signature string `json:"signature"`
}
type sample struct {
Name string `json:"name"`
Kind string `json:"kind"`
Round uint64 `json:"round"`
FileKey string `json:"file_key,omitempty"`
Body string `json:"body,omitempty"`
Plaintext string `json:"plaintext,omitempty"`
File string `json:"file,omitempty"`
Go string `json:"go"`
GoResult string `json:"go_result,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)
}
release := func(round uint64) provider.Release {
r := provider.Release{Round: round, Signature: unhex(published[round])}
if err := provider.Verify(quicknet, provider.Condition{Round: round}, r); err != nil {
panic(err)
}
return r
}
var vectors []encryptVector
for i, c := range []struct {
round uint64
n int
}{{1000, 16}, {1001, 16}, {1000, 1}, {1000, 32}, {1000, 0}} {
seed := sha256.Sum256(binary.BigEndian.AppendUint32([]byte("DateKeys tlock vector "), uint32(i)))
msgSeed := sha256.Sum256(seed[:])
msg, sigma := msgSeed[:c.n], seed[:c.n]
id := scheme.DigestBeacon(&common.Beacon{Round: c.round})
ct := encrypt(key, id, msg, sigma)
rel := release(c.round)
sig := scheme.SigGroup.Point()
if err := sig.UnmarshalBinary(rel.Signature); err != nil {
panic(err)
}
if got, err := ibe.DecryptCCAonG2(suite, sig, ct); err != nil || !bytes.Equal(got, msg) {
panic(fmt.Sprintf("kyber does not decrypt the restated encryption %d: %v", i, err))
}
u := must(ct.U.MarshalBinary())
if c.n == 16 {
body := concat(u, ct.V, ct.W)
got := must(tlock.TimeUnlock(*scheme, key, common.Beacon{Round: rel.Round, Signature: rel.Signature}, must(tlock.BytesToCiphertext(*scheme, body))))
if !bytes.Equal(got, msg) {
panic("tlock does not decrypt the restated encryption")
}
}
vectors = append(vectors, encryptVector{
Name: fmt.Sprintf("a %d-byte message for round %d", c.n, c.round), Round: c.round, ID: hex.EncodeToString(id),
Msg: hex.EncodeToString(msg), Sigma: hex.EncodeToString(sigma), U: hex.EncodeToString(u),
V: hex.EncodeToString(ct.V), W: hex.EncodeToString(ct.W), Signature: published[c.round],
})
}
var frozen struct {
Interop struct {
Generator string `json:"generator"`
Samples []sample `json:"samples"`
} `json:"interop"`
}
if err := json.Unmarshal(must(os.ReadFile(os.Args[1])), &frozen); err != nil {
panic(err)
}
in := frozen.Interop
if len(in.Samples) != 4 {
panic("want the 4 frozen samples of datekeys-ts")
}
for i := range in.Samples {
s := &in.Samples[i]
rel := release(s.Round)
s.Go = "reject"
switch s.Kind {
case "ibe":
ct, err := tlock.BytesToCiphertext(*scheme, unhex(s.Body))
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", s.Name, err)
continue
}
fk, err := tlock.TimeUnlock(*scheme, key, common.Beacon{Round: rel.Round, Signature: rel.Signature}, ct)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", s.Name, err)
continue
}
s.Go, s.GoResult = "ok", hex.EncodeToString(fk)
case "age":
id := must(agewrap.NewTimeIdentity(quicknet, s.Round, rel))
r, err := age.Decrypt(bytes.NewReader(unhex(s.File)), id)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", s.Name, err)
continue
}
pt, err := io.ReadAll(r)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", s.Name, err)
continue
}
s.Go, s.GoResult = "ok", hex.EncodeToString(pt)
default:
panic("unknown sample kind " + s.Kind)
}
}
out := map[string]any{
"description": "Go reference values for the tlock encryption of lib/src/ibe.dart and lib/src/tlock.dart; " +
"see tool/tlock_go_vectors.go for how each block is obtained.",
"generator": "tool/tlock_go_vectors.go",
"interop_from": "the interop samples of src/lib/dkc/testing/tlock-vectors.json of datekeys-ts at 289fe71, " +
"opened again by this generator",
"libraries": libraries(),
"scheme": scheme.Name,
"public_key": hex.EncodeToString(quicknet.PublicKey),
"encrypt": vectors,
"interop": map[string]any{"generator": in.Generator, "samples": in.Samples},
}
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, ", ")
}

Powered by TurnKey Linux.