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.
113 lines
4.2 KiB
113 lines
4.2 KiB
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)
|
|
}
|
|
}
|
|
}
|