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/bls12381_go_vectors.go

548 lines
18 KiB

//go:build ignore
// Prints test/vectors/bls12381_vectors.json: the Go reference values for the
// BLS12-381 code of lib/src/bls12381_*.dart, from the libraries that drand
// and tlock use for Quicknet: github.com/kilic/bls12-381 and
// github.com/drand/kyber-bls12381 over it.
//
// - points: the frozen edge-case encodings of datekeys-ts
// (src/lib/dkc/testing/bls12381-vectors.json; valid points, sign-bit
// flips, identity encodings with stray flags or payload, missing
// compression flag, wrong lengths, uncompressed forms, x + p, points on
// the curve outside the subgroup, cofactor torsion), each with the
// verdict of the reference recomputed here: the KeyGroup of the drand
// crypto schemes (G1 for pedersen-bls-unchained, G2 for
// bls-unchained-g1-rfc9380), whose UnmarshalBinary is FromCompressed of
// kilic, and Equal(Null()) for the identity, as profile.Validate uses it.
// - decode: encodings drawn from a fixed seed: multiples of the generators,
// their negations, one flipped bit, random x with the compression flag,
// and random x of points on the curve outside the subgroup; each with the
// verdict, and for an invalid one the class of the error of kilic
// ("format", "curve" or "subgroup").
// - add, multiply: sums and multiples of points drawn from the seed, the
// special cases included (P + P, P + (-P), the point at infinity, the
// scalars 0, 1, r - 1, r, r + 1 and 2^256 - 1), computed by kilic.
// - pairing: e(P, Q) for points drawn from the seed, serialized by
// kyber-bls12381 (the order of kilic: c1 before c0 at every level).
// - hash_to_g1: HashToCurve of kilic for the DST of Quicknet and of tlock
// (BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_) on messages drawn from
// the seed and on the identities of some rounds, and for the DST of the
// test vectors of RFC 9380, appendix J.9.1, on their messages, with the
// affine coordinates.
// - map_to_g1: MapToCurve of kilic (the simplified SWU map, the isogeny and
// the clearing of the cofactor) on elements u drawn from the seed and on
// the exceptional ones, u = 0 and Z·u² = -1, which no hash reaches.
// - signatures: BLS signatures on G1 of kyber's sign/bls (NewSchemeOnG1,
// the scheme of Quicknet) under keys drawn from the seed, and the verdict
// of its Verify on each and on edited copies.
//
// The seed is fixed: the output is the same on every run. 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/bls12381_go_vectors.go \
// ../datekeys-ts/src/lib/dkc/testing/bls12381-vectors.json \
// > ../datekeys-dart/test/vectors/bls12381_vectors.json
package main
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
"os"
"runtime/debug"
"sort"
"strings"
"github.com/drand/drand/v2/common"
"github.com/drand/drand/v2/crypto"
"github.com/drand/kyber"
bls "github.com/drand/kyber-bls12381"
signbls "github.com/drand/kyber/sign/bls"
bls12381 "github.com/kilic/bls12-381"
)
const quicknetDST = "BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_"
var (
p, _ = new(big.Int).SetString("1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", 16)
order, _ = new(big.Int).SetString("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", 16)
g1 = bls12381.NewG1()
g2 = bls12381.NewG2()
)
func must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
// stream is a deterministic byte source: SHA-256 of the label, a counter
// and a block index.
type stream struct {
label string
n uint32
}
func (s *stream) bytes(n int) []byte {
s.n++
var out []byte
for i := uint32(0); len(out) < n; i++ {
h := sha256.Sum256(binary.BigEndian.AppendUint32(binary.BigEndian.AppendUint32([]byte("DateKeys BLS12-381 vectors "+s.label), s.n), i))
out = append(out, h[:]...)
}
return out[:n]
}
// scalar is a nonzero scalar below r.
func (s *stream) scalar() *big.Int {
k := new(big.Int).SetBytes(s.bytes(64))
k.Mod(k, new(big.Int).Sub(order, big.NewInt(1)))
return k.Add(k, big.NewInt(1))
}
func (s *stream) fp() *big.Int {
x := new(big.Int).SetBytes(s.bytes(64))
return x.Mod(x, p)
}
func fp48(x *big.Int) []byte { return x.FillBytes(make([]byte, 48)) }
func hx(b []byte) string { return hex.EncodeToString(b) }
func g1Mul(k *big.Int) *bls12381.PointG1 {
return g1.MulScalarBig(g1.New(), g1.One(), k)
}
func g2Mul(k *big.Int) *bls12381.PointG2 {
return g2.MulScalarBig(g2.New(), g2.One(), k)
}
func g1Enc(q *bls12381.PointG1) []byte { return g1.ToCompressed(g1.New().Set(q)) }
func g2Enc(q *bls12381.PointG2) []byte { return g2.ToCompressed(g2.New().Set(q)) }
// errorClass names the check of FromCompressed of kilic that failed.
func errorClass(err error) string {
switch {
case strings.Contains(err.Error(), "not on curve"):
return "curve"
case strings.Contains(err.Error(), "correct subgroup"):
return "subgroup"
default:
return "format"
}
}
type decodeVector struct {
Label string `json:"label"`
Group string `json:"group"`
Hex string `json:"hex"`
Go string `json:"go"`
Class string `json:"class,omitempty"`
}
func verdict(group string, b []byte) decodeVector {
v := decodeVector{Group: group, Hex: hx(b)}
var err error
var zero bool
if group == "G1" {
var q *bls12381.PointG1
if q, err = g1.FromCompressed(b); err == nil {
zero = g1.IsZero(q)
}
} else {
var q *bls12381.PointG2
if q, err = g2.FromCompressed(b); err == nil {
zero = g2.IsZero(q)
}
}
switch {
case err != nil:
v.Go, v.Class = "invalid", errorClass(err)
case zero:
v.Go = "identity"
default:
v.Go = "point"
}
return v
}
// schemeVerdict is the verdict of the KeyGroup of the drand scheme, as
// scripts/bls12381-go-verdicts.go of datekeys-ts computes it.
func schemeVerdict(group string, b []byte) string {
id := crypto.UnchainedSchemeID
if group == "G2" {
id = crypto.SigsOnG1ID
}
s := must(crypto.SchemeFromName(id))
k := s.KeyGroup.Point()
switch {
case k.UnmarshalBinary(b) != nil:
return "invalid"
case k.Equal(k.Null()):
return "identity"
default:
return "point"
}
}
type addVector struct {
Label string `json:"label"`
Group string `json:"group"`
A string `json:"a"`
B string `json:"b"`
Sum string `json:"sum"`
}
type mulVector struct {
Label string `json:"label"`
Group string `json:"group"`
Point string `json:"point"`
Scalar string `json:"scalar"`
Product string `json:"product"`
}
type pairingVector struct {
Label string `json:"label"`
G1 string `json:"g1"`
G2 string `json:"g2"`
GT string `json:"gt"`
}
type hashVector struct {
Label string `json:"label"`
DST string `json:"dst"`
Msg string `json:"msg"`
Point string `json:"point"`
X string `json:"x"`
Y string `json:"y"`
}
type mapVector struct {
Label string `json:"label"`
U string `json:"u"`
Point string `json:"point"`
}
type signatureVector struct {
Label string `json:"label"`
PublicKey string `json:"public_key"`
Msg string `json:"msg"`
Signature string `json:"signature"`
Go bool `json:"go"`
}
func main() {
out := struct {
Description string `json:"description"`
Generator string `json:"generator"`
Libraries string `json:"libraries"`
PointsFrom string `json:"points_from"`
Points []decodeVector `json:"points"`
Decode []decodeVector `json:"decode"`
Add []addVector `json:"add"`
Multiply []mulVector `json:"multiply"`
Pairing []pairingVector `json:"pairing"`
HashToG1 []hashVector `json:"hash_to_g1"`
MapToG1 []mapVector `json:"map_to_g1"`
Signatures []signatureVector `json:"signatures"`
}{
Description: "Go reference values for the BLS12-381 code of lib/src/bls12381_*.dart: decoding verdicts, " +
"sums, multiples, pairings, hashes to G1 and BLS signatures on G1; see tool/bls12381_go_vectors.go.",
Generator: "tool/bls12381_go_vectors.go",
Libraries: libraries(),
PointsFrom: "the encodings of src/lib/dkc/testing/bls12381-vectors.json of datekeys-ts at 289fe71, " +
"with the verdicts recomputed by this generator",
}
// The frozen edge cases of datekeys-ts.
var frozen struct {
Vectors []struct{ Label, Group, Hex, Go string }
}
if err := json.Unmarshal(must(os.ReadFile(os.Args[1])), &frozen); err != nil {
panic(err)
}
for _, f := range frozen.Vectors {
b := must(hex.DecodeString(f.Hex))
v := verdict(f.Group, b)
if (len(b) == 48 && f.Group == "G1") || (len(b) == 96 && f.Group == "G2") {
if s := schemeVerdict(f.Group, b); s != v.Go {
panic(f.Label + ": kilic and the drand scheme disagree")
}
} else {
v.Go, v.Class = schemeVerdict(f.Group, b), "format"
}
if v.Go != f.Go {
panic(f.Label + ": the verdict of datekeys-ts is not the one of Go")
}
v.Label = f.Label
out.Points = append(out.Points, v)
}
// Decoding.
s := &stream{label: "decode"}
for i := 0; i < 24; i++ {
b := g1Enc(g1Mul(s.scalar()))
neg := bytes.Clone(b)
neg[0] ^= 0x20
flip := bytes.Clone(b)
bit := 3 + int(binary.BigEndian.Uint16(s.bytes(2)))%(48*8-3)
flip[bit/8] ^= 0x80 >> (bit % 8)
for j, c := range [][]byte{b, neg, flip} {
v := verdict("G1", c)
v.Label = fmt.Sprintf("G1 multiple %d, %s", i, []string{"as encoded", "negated", fmt.Sprintf("bit %d flipped", bit)}[j])
out.Decode = append(out.Decode, v)
}
}
for i := 0; i < 8; i++ {
b := g2Enc(g2Mul(s.scalar()))
neg := bytes.Clone(b)
neg[0] ^= 0x20
flip := bytes.Clone(b)
bit := 3 + int(binary.BigEndian.Uint16(s.bytes(2)))%(96*8-3)
flip[bit/8] ^= 0x80 >> (bit % 8)
for j, c := range [][]byte{b, neg, flip} {
v := verdict("G2", c)
v.Label = fmt.Sprintf("G2 multiple %d, %s", i, []string{"as encoded", "negated", fmt.Sprintf("bit %d flipped", bit)}[j])
out.Decode = append(out.Decode, v)
}
}
// Random x, with the compression flag and either sign: on the curve or
// not, and then outside the subgroup.
classes := map[string]int{}
for i := 0; classes["G1 subgroup"] < 16 || classes["G1 curve"] < 8; i++ {
b := fp48(s.fp())
b[0] |= 0x80 | s.bytes(1)[0]&0x20
v := verdict("G1", b)
key := "G1 " + v.Class
if classes[key] >= 16 {
continue
}
classes[key]++
v.Label = fmt.Sprintf("G1 random x %d", i)
out.Decode = append(out.Decode, v)
}
for i := 0; classes["G2 subgroup"] < 8 || classes["G2 curve"] < 4; i++ {
b := append(fp48(s.fp()), fp48(s.fp())...)
b[0] |= 0x80 | s.bytes(1)[0]&0x20
v := verdict("G2", b)
key := "G2 " + v.Class
if classes[key] >= 8 {
continue
}
classes[key]++
v.Label = fmt.Sprintf("G2 random x %d", i)
out.Decode = append(out.Decode, v)
}
// Sums.
s = &stream{label: "add"}
inf1, inf2 := g1.Zero(), g2.Zero()
for i := 0; i < 16; i++ {
a, b := g1Mul(s.scalar()), g1Mul(s.scalar())
out.Add = append(out.Add, addVector{fmt.Sprintf("G1 sum %d", i), "G1", hx(g1Enc(a)), hx(g1Enc(b)), hx(g1Enc(g1.Add(g1.New(), a, b)))})
}
a1 := g1Mul(s.scalar())
for _, c := range []struct {
label string
a, b *bls12381.PointG1
}{
{"G1 P + P", a1, a1},
{"G1 P + (-P)", a1, g1.Neg(g1.New(), a1)},
{"G1 P + infinity", a1, inf1},
{"G1 infinity + P", inf1, a1},
{"G1 infinity + infinity", inf1, inf1},
} {
out.Add = append(out.Add, addVector{c.label, "G1", hx(g1Enc(c.a)), hx(g1Enc(c.b)), hx(g1Enc(g1.Add(g1.New(), c.a, c.b)))})
}
for i := 0; i < 8; i++ {
a, b := g2Mul(s.scalar()), g2Mul(s.scalar())
out.Add = append(out.Add, addVector{fmt.Sprintf("G2 sum %d", i), "G2", hx(g2Enc(a)), hx(g2Enc(b)), hx(g2Enc(g2.Add(g2.New(), a, b)))})
}
a2 := g2Mul(s.scalar())
for _, c := range []struct {
label string
a, b *bls12381.PointG2
}{
{"G2 P + P", a2, a2},
{"G2 P + (-P)", a2, g2.Neg(g2.New(), a2)},
{"G2 P + infinity", a2, inf2},
{"G2 infinity + P", inf2, a2},
} {
out.Add = append(out.Add, addVector{c.label, "G2", hx(g2Enc(c.a)), hx(g2Enc(c.b)), hx(g2Enc(g2.Add(g2.New(), c.a, c.b)))})
}
// Multiples.
s = &stream{label: "multiply"}
max256 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1))
special := []struct {
label string
k *big.Int
}{
{"0", big.NewInt(0)},
{"1", big.NewInt(1)},
{"2", big.NewInt(2)},
{"r - 1", new(big.Int).Sub(order, big.NewInt(1))},
{"r", new(big.Int).Set(order)},
{"r + 1", new(big.Int).Add(order, big.NewInt(1))},
{"2^256 - 1", max256},
}
for i := 0; i < 12; i++ {
q, k := g1Mul(s.scalar()), new(big.Int).SetBytes(s.bytes(32))
out.Multiply = append(out.Multiply, mulVector{fmt.Sprintf("G1 multiple %d", i), "G1", hx(g1Enc(q)), k.Text(16), hx(g1Enc(g1.MulScalarBig(g1.New(), q, k)))})
}
q1 := g1Mul(s.scalar())
for _, c := range special {
out.Multiply = append(out.Multiply, mulVector{"G1 by " + c.label, "G1", hx(g1Enc(q1)), c.k.Text(16), hx(g1Enc(g1.MulScalarBig(g1.New(), q1, c.k)))})
}
for i := 0; i < 6; i++ {
q, k := g2Mul(s.scalar()), new(big.Int).SetBytes(s.bytes(32))
out.Multiply = append(out.Multiply, mulVector{fmt.Sprintf("G2 multiple %d", i), "G2", hx(g2Enc(q)), k.Text(16), hx(g2Enc(g2.MulScalarBig(g2.New(), q, k)))})
}
q2 := g2Mul(s.scalar())
for _, c := range special {
out.Multiply = append(out.Multiply, mulVector{"G2 by " + c.label, "G2", hx(g2Enc(q2)), c.k.Text(16), hx(g2Enc(g2.MulScalarBig(g2.New(), q2, c.k)))})
}
// Pairings, through kyber-bls12381.
s = &stream{label: "pairing"}
suite := bls.NewBLS12381Suite()
for i := 0; i < 6; i++ {
a := suite.G1().Point().Mul(scalarOf(s.scalar()), nil)
b := suite.G2().Point().Mul(scalarOf(s.scalar()), nil)
out.Pairing = append(out.Pairing, pairingVector{fmt.Sprintf("e(P, Q) %d", i), marshal(a), marshal(b), marshal(suite.Pair(a, b))})
}
{
a := suite.G1().Point().Mul(scalarOf(s.scalar()), nil)
b := suite.G2().Point().Mul(scalarOf(s.scalar()), nil)
out.Pairing = append(out.Pairing,
pairingVector{"e(infinity, Q)", marshal(suite.G1().Point().Null()), marshal(b), marshal(suite.Pair(suite.G1().Point().Null(), b))},
pairingVector{"e(P, infinity)", marshal(a), marshal(suite.G2().Point().Null()), marshal(suite.Pair(a, suite.G2().Point().Null()))})
}
// Hashes to G1.
s = &stream{label: "hash"}
hashOne := func(label, dst string, msg []byte) hashVector {
q := must(g1.HashToCurve(msg, []byte(dst)))
u := g1.ToUncompressed(g1.New().Set(q))
return hashVector{label, dst, hx(msg), hx(g1Enc(q)), hx(u[:48]), hx(u[48:])}
}
for i := 0; i < 24; i++ {
n := int(s.bytes(1)[0]) % 80
out.HashToG1 = append(out.HashToG1, hashOne(fmt.Sprintf("a message of %d bytes", n), quicknetDST, s.bytes(n)))
}
sch := must(crypto.SchemeFromName(crypto.SigsOnG1ID))
for _, round := range []uint64{1, 1000, 1001, 1004, 2000, 83903165811, 1<<53 - 1} {
id := sch.DigestBeacon(&common.Beacon{Round: round})
out.HashToG1 = append(out.HashToG1, hashOne(fmt.Sprintf("the identity of round %d", round), quicknetDST, id))
}
const rfcDST = "QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_"
for _, m := range []string{"", "abc", "abcdef0123456789", "q128_" + strings.Repeat("q", 128), "a512_" + strings.Repeat("a", 512)} {
out.HashToG1 = append(out.HashToG1, hashOne(fmt.Sprintf("RFC 9380 J.9.1, msg %q", shorten(m)), rfcDST, []byte(m)))
}
// The map to G1 of one element.
s = &stream{label: "map"}
z := big.NewInt(11)
// Z·u² = -1: u² = -1/Z, a square since -Z is one.
minusInvZ := new(big.Int).Sub(p, new(big.Int).ModInverse(z, p))
root := new(big.Int).ModSqrt(minusInvZ, p)
if root == nil {
panic("-1/Z is not a square")
}
us := []struct {
label string
u *big.Int
}{
{"u = 0, exceptional", big.NewInt(0)},
{"Z·u² = -1, exceptional", root},
{"Z·u² = -1, the other root, exceptional", new(big.Int).Sub(p, root)},
{"u = 1", big.NewInt(1)},
{"u = p - 1", new(big.Int).Sub(p, big.NewInt(1))},
}
for i := 0; i < 8; i++ {
us = append(us, struct {
label string
u *big.Int
}{fmt.Sprintf("u drawn from the seed %d", i), s.fp()})
}
for _, c := range us {
q := must(g1.MapToCurve(fp48(c.u)))
out.MapToG1 = append(out.MapToG1, mapVector{c.label, hx(fp48(c.u)), hx(g1Enc(q))})
}
// BLS signatures on G1, the scheme of Quicknet.
s = &stream{label: "signatures"}
scheme := signbls.NewSchemeOnG1(suite)
for i := 0; i < 6; i++ {
sk := scalarOf(s.scalar())
pk := suite.G2().Point().Mul(sk, nil)
msg := s.bytes(32)
sig := must(scheme.Sign(sk, msg))
other := s.bytes(32)
for _, c := range []struct {
label string
msg, sig []byte
}{
{fmt.Sprintf("signature %d", i), msg, sig},
{fmt.Sprintf("signature %d of another message", i), other, sig},
{fmt.Sprintf("signature %d negated", i), msg, negate(sig)},
} {
out.Signatures = append(out.Signatures, signatureVector{c.label, marshal(pk), hx(c.msg), hx(c.sig), scheme.Verify(pk, c.msg, c.sig) == nil})
}
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
enc.SetEscapeHTML(false)
if err := enc.Encode(out); err != nil {
panic(err)
}
}
func scalarOf(k *big.Int) kyber.Scalar {
return bls.NewKyberScalar().SetBytes(k.FillBytes(make([]byte, 32)))
}
func marshal(m interface{ MarshalBinary() ([]byte, error) }) string {
return hex.EncodeToString(must(m.MarshalBinary()))
}
func negate(sig []byte) []byte {
c := bytes.Clone(sig)
c[0] ^= 0x20
return c
}
func shorten(m string) string {
if len(m) > 20 {
return m[:20] + "…"
}
return m
}
// 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 "github.com/kilic/bls12-381", "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.