package profile_test import ( "bytes" "errors" "testing" "github.com/drand/drand/v2/crypto" datekeys "g.activething.com/go/DateKeys" "g.activething.com/go/DateKeys/internal/testkit" "g.activething.com/go/DateKeys/profile" ) // Spec §12.2: the decoders of drand, through which the reference reads public // keys, release signatures and the U of a tlock stanza, accept exactly the // canonical encodings. The reference relies on them for the rule, so this // test fails if a dependency update makes them lenient. func TestDrandPointDecodersAreCanonical(t *testing.T) { s, err := crypto.SchemeFromName(crypto.SigsOnG1ID) if err != nil { t.Fatal(err) } g1 := func(b []byte) error { return s.SigGroup.Point().UnmarshalBinary(b) } g2 := func(b []byte) error { return s.KeyGroup.Point().UnmarshalBinary(b) } must := func(b []byte, err error) []byte { t.Helper() if err != nil { t.Fatal(err) } return b } cleared := func(b []byte, flags byte) []byte { c := bytes.Clone(b) c[0] &^= flags return c } key, sig := profile.Quicknet().PublicKey, testkit.Release(1000).Signature for _, tc := range []struct { name string decode func([]byte) error in []byte ok bool }{ {"G1: a release signature", g1, sig, true}, {"G1: the point at infinity", g1, testkit.Infinity(48), true}, {"G2: the Quicknet public key", g2, key, true}, {"G2: the point at infinity", g2, testkit.Infinity(96), true}, {"G1: x + p", g1, must(testkit.AddModulus(testkit.Release(testkit.XPlusPRound).Signature, 0)), false}, {"G2: c1 + p", g2, must(testkit.AddModulus(key, 0)), false}, {"G2: c0 + p", g2, must(testkit.AddModulus(key, testkit.CoordinateLen)), false}, {"G1: infinity flag and a payload", g1, testkit.InfinityWithPayload(sig), false}, {"G2: infinity flag and a payload", g2, testkit.InfinityWithPayload(key), false}, {"G1: the point at infinity with the sort flag", g1, testkit.Negated(testkit.Infinity(48)), false}, {"G2: the point at infinity with the sort flag", g2, testkit.Negated(testkit.Infinity(96)), false}, {"G1: the point at infinity without the compression flag", g1, cleared(testkit.Infinity(48), testkit.FlagCompressed), false}, {"G1: compression flag cleared", g1, cleared(sig, testkit.FlagCompressed), false}, {"G2: compression flag cleared", g2, cleared(key, testkit.FlagCompressed), false}, {"G1: 96 bytes, the length of the uncompressed form", g1, append(bytes.Clone(sig), sig...), false}, {"G2: 192 bytes, the length of the uncompressed form", g2, append(bytes.Clone(key), key...), false}, {"G1: 47 bytes", g1, sig[:47], false}, {"G2: 97 bytes", g2, append(bytes.Clone(key), 0), false}, {"G1: a point of the curve outside the prime-order subgroup", g1, offSubgroupG1(t), false}, } { if err := tc.decode(tc.in); (err == nil) != tc.ok { t.Errorf("%s: decoded %v, want %v (%v)", tc.name, err == nil, tc.ok, err) } } } // Spec §12.1, §12.2: the public key of a profile is the canonical encoding of // a point of the key group other than the point at infinity. Each profile // below is otherwise valid, its chain hash computed over the exact bytes of // its key, and is refused whether it is validated or decoded. func TestPublicKeyEncodingIsCanonical(t *testing.T) { key := profile.Quicknet().PublicKey c1PlusP, err := testkit.AddModulus(key, 0) if err != nil { t.Fatal(err) } c0PlusP, err := testkit.AddModulus(key, testkit.CoordinateLen) if err != nil { t.Fatal(err) } uncompressed := bytes.Clone(key) uncompressed[0] &^= testkit.FlagCompressed for _, tc := range []struct { name string key []byte }{ {"c1 + p", c1PlusP}, {"c0 + p", c0PlusP}, {"the point at infinity", testkit.Infinity(96)}, {"infinity flag and a payload", testkit.InfinityWithPayload(key)}, {"compression flag cleared", uncompressed}, {"192 bytes, the length of the uncompressed form", append(bytes.Clone(key), key...)}, } { p := profile.Quicknet() p.PublicKey = tc.key copy(p.ChainHash[:], chainHashFormula(p)) if err := p.Validate(); !errors.Is(err, datekeys.ErrUnknownProfile) { t.Errorf("%s: Validate: %v", tc.name, err) } b, err := p.CanonicalCBOR() if err != nil { t.Fatal(err) } if _, err := profile.Decode(b); !errors.Is(err, datekeys.ErrUnknownProfile) { t.Errorf("%s: Decode: %v", tc.name, err) } } }