//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, ", ") }