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>
2 days ago
|
|
|
//go:build ignore
|
|
|
|
|
|
|
|
|
|
// Prints test/vectors/ibe_vectors.json: the Go reference values of the tlock
|
|
|
|
|
// IBE-CCA on G2 of lib/src/ibe.dart (spec §63 step 11), a port of
|
|
|
|
|
// scripts/ibe-go-vectors.go of datekeys-ts. 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 of testdata/
|
Test data of the branch v0.12 of datekeys-go at c531e93
testdata/ is synced with datekeys-go at c531e93, the head of the branch
v0.12, whose testdata is that of 601e6d2, the copy of datekeys-ts. Every
file keeps "spec": "0.11": the draft v0.12 is not approved yet. From the
tag spec-v0.11 it brings the fixtures format3_unsigned and format3_note,
format3_seal_unsupported with seal_type 4294967295, the records of
format3_sealed and format3_signed_cms, the mutation corpus of 218 cases,
note.json, security.json with a context and lines, security_cms.json of
135 cases and locator.json.
The vectors that the generators of tool/ make from the testdata are
written again by Go at c531e93: mutation_texts.json, open_cases.json,
formats_*.json with formats_vectors.g.dart, ibe_vectors.json and
age_fixtures.json; release_vectors.json, primitives.json and the views
of open_vectors.g.dart come out the same. age.json does not read the
testdata, and stays frozen: age draws its keys from crypto/rand. The
tests count 26 fixtures and 218 cases, and the inspection of
format3_note gives the note of its record. No difference with Go.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
// (the branch v0.12 at c531e93), 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.
|
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>
2 days ago
|
|
|
// - kyber: messages of 0, 1, 16 and 32 bytes encrypted for round 1000 by
|
|
|
|
|
// ibe.EncryptCCAonG2 itself, with its random sigma. They are not
|
|
|
|
|
// reproducible: they are the frozen ones of
|
|
|
|
|
// src/lib/dkc/testing/ibe-vectors.json of datekeys-ts at 289fe71, which
|
|
|
|
|
// this program reads and decrypts again with ibe.DecryptCCAonG2; a
|
|
|
|
|
// mismatch panics.
|
|
|
|
|
// - 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.
|
|
|
|
|
//
|
|
|
|
|
// 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>
2 days ago
|
|
|
//
|
|
|
|
|
// cd ../datekeys-go && go run ../datekeys-dart/tool/ibe_go_vectors.go \
|
|
|
|
|
// ../datekeys-dart/testdata/fixtures \
|
|
|
|
|
// ../datekeys-ts/src/lib/dkc/testing/ibe-vectors.json \
|
|
|
|
|
// > ../datekeys-dart/test/vectors/ibe_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>
2 days ago
|
|
|
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"`
|
|
|
|
|
KyberFrom string `json:"kyber_from"`
|
|
|
|
|
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 lib/src/ibe.dart; " +
|
|
|
|
|
"see tool/ibe_go_vectors.go for how each block is obtained.",
|
|
|
|
|
Generator: "tool/ibe_go_vectors.go",
|
|
|
|
|
KyberFrom: "the kyber section of src/lib/dkc/testing/ibe-vectors.json of datekeys-ts at 289fe71, decrypted again by this generator",
|
|
|
|
|
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: the frozen ones of datekeys-ts,
|
|
|
|
|
// decrypted again.
|
|
|
|
|
sig1000 := unhex(timeOnly.Signature)
|
|
|
|
|
var frozen struct {
|
|
|
|
|
Kyber []ciphertextVector `json:"kyber"`
|
|
|
|
|
}
|
|
|
|
|
if err := json.Unmarshal(must(os.ReadFile(os.Args[2])), &frozen); err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
for i, f := range frozen.Kyber {
|
|
|
|
|
msg := sha256.Sum256([]byte("DateKeys IBE vector message"))
|
|
|
|
|
n := []int{0, 1, 16, 32}[i]
|
|
|
|
|
v := verdict(scheme, f.Name, f.Round, unhex(f.Signature), unhex(f.U), unhex(f.V), unhex(f.W))
|
|
|
|
|
if f.Round != 1000 || f.Signature != timeOnly.Signature || f.Go != "ok" || v.Go != "ok" || v.Msg != f.Msg || v.Msg != hex.EncodeToString(msg[:n]) {
|
|
|
|
|
panic("kyber does not decrypt the frozen ciphertext " + f.Name)
|
|
|
|
|
}
|
|
|
|
|
out.Kyber = append(out.Kyber, v)
|
|
|
|
|
}
|
|
|
|
|
if len(out.Kyber) != 4 {
|
|
|
|
|
panic("want the 4 frozen kyber ciphertexts")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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})},
|
|
|
|
|
{"V and W empty", sig1000, u, nil, nil},
|
|
|
|
|
{"U is the generator of G2", sig1000, unhex(marshal(suite.G2().Point().Base())), vv, w},
|
|
|
|
|
} {
|
|
|
|
|
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, ", ")
|
|
|
|
|
}
|