|
|
|
|
package ed25519strict_test
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"crypto/ed25519"
|
|
|
|
|
"encoding/hex"
|
|
|
|
|
"slices"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"g.activething.com/go/DateKeys/internal/ed25519strict"
|
|
|
|
|
"g.activething.com/go/DateKeys/internal/testkit"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// The table of the points of small order is what the arithmetic of testkit
|
|
|
|
|
// computes from the curve.
|
|
|
|
|
func TestSmallOrderTable(t *testing.T) {
|
|
|
|
|
var got, want [][32]byte
|
|
|
|
|
for _, p := range ed25519strict.SmallOrderPoints() {
|
|
|
|
|
got = append(got, p)
|
|
|
|
|
}
|
|
|
|
|
want = testkit.Ed25519Torsion()
|
|
|
|
|
cmp := func(a, b [32]byte) int { return slices.Compare(a[:], b[:]) }
|
|
|
|
|
slices.SortFunc(got, cmp)
|
|
|
|
|
slices.SortFunc(want, cmp)
|
|
|
|
|
if !slices.Equal(got, want) {
|
|
|
|
|
t.Fatalf("table %x, want %x", got, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestCanonical(t *testing.T) {
|
|
|
|
|
enc := func(s string) []byte {
|
|
|
|
|
b, err := hex.DecodeString(s)
|
|
|
|
|
if err != nil || len(b) != 32 {
|
|
|
|
|
t.Fatalf("bad test encoding %s", s)
|
|
|
|
|
}
|
|
|
|
|
return b
|
|
|
|
|
}
|
|
|
|
|
for _, c := range []struct {
|
|
|
|
|
name string
|
|
|
|
|
a string
|
|
|
|
|
want bool
|
|
|
|
|
}{
|
|
|
|
|
{"y = 0", "0000000000000000000000000000000000000000000000000000000000000000", true},
|
|
|
|
|
{"y = 0, sign set: x is not 0", "0000000000000000000000000000000000000000000000000000000000000080", true},
|
|
|
|
|
{"y = 1", "0100000000000000000000000000000000000000000000000000000000000000", true},
|
|
|
|
|
{"y = 1, sign set: x is 0", "0100000000000000000000000000000000000000000000000000000000000080", false},
|
|
|
|
|
{"y = p - 1", "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", true},
|
|
|
|
|
{"y = p - 1, sign set", "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", false},
|
|
|
|
|
{"y = p", "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", false},
|
|
|
|
|
{"y = 2^255 - 1", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", false},
|
|
|
|
|
{"y = p - 2, sign set", "ebffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", true},
|
|
|
|
|
} {
|
|
|
|
|
if got := ed25519strict.Canonical(enc(c.a)); got != c.want {
|
|
|
|
|
t.Errorf("%s: Canonical = %v, want %v", c.name, got, c.want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ed25519strict.Canonical(make([]byte, 31)) || ed25519strict.SmallOrder(make([]byte, 33)) {
|
|
|
|
|
t.Error("a length other than 32 is accepted")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
|
|
|
// OnCurve is true on the base point, on the points of small order and on
|
|
|
|
|
// keys of the CSPRNG, and false on y = 2, which has no x. Over the first 4096
|
|
|
|
|
// values of y it agrees with the square root of testkit.
|
|
|
|
|
func TestOnCurve(t *testing.T) {
|
|
|
|
|
base, _ := hex.DecodeString("5866666666666666666666666666666666666666666666666666666666666666")
|
|
|
|
|
if !ed25519strict.OnCurve(base) {
|
|
|
|
|
t.Error("the base point is not on the curve")
|
|
|
|
|
}
|
|
|
|
|
for _, p := range ed25519strict.SmallOrderPoints() {
|
|
|
|
|
if !ed25519strict.OnCurve(p[:]) {
|
|
|
|
|
t.Errorf("%x, of small order, is not on the curve", p)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for range 32 {
|
|
|
|
|
pub, _, _ := ed25519.GenerateKey(nil)
|
|
|
|
|
if !ed25519strict.OnCurve(pub) {
|
|
|
|
|
t.Fatalf("a key of the CSPRNG %x is not on the curve", pub)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
two := make([]byte, 32)
|
|
|
|
|
two[0] = 2
|
|
|
|
|
if ed25519strict.OnCurve(two) || ed25519strict.OnCurve(make([]byte, 31)) {
|
|
|
|
|
t.Error("y = 2, or 31 bytes, is on the curve")
|
|
|
|
|
}
|
|
|
|
|
for y := range 4096 {
|
|
|
|
|
a := make([]byte, 32)
|
|
|
|
|
a[0], a[1] = byte(y), byte(y>>8)
|
|
|
|
|
if got, want := ed25519strict.OnCurve(a), testkit.Ed25519Decodes(a); got != want {
|
|
|
|
|
t.Fatalf("y = %d: OnCurve %v, the square root %v", y, got, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// crypto/ed25519 accepts any message with A = 01 00…00, R the identity and S
|
|
|
|
|
// = 0; Verify does not, nor a key or a signature of another length.
|
|
|
|
|
func TestVerifyRejectsWhatStdlibAccepts(t *testing.T) {
|
|
|
|
|
a := make([]byte, 32)
|
|
|
|
|
a[0] = 1
|
|
|
|
|
sig := make([]byte, 64)
|
|
|
|
|
sig[0] = 1
|
|
|
|
|
msg := []byte("anything")
|
|
|
|
|
if !ed25519.Verify(a, msg, sig) {
|
|
|
|
|
t.Fatal("crypto/ed25519 no longer accepts the forgery: review the comment of the package")
|
|
|
|
|
}
|
|
|
|
|
if ed25519strict.Verify(a, msg, sig) {
|
|
|
|
|
t.Error("Verify accepts a key of small order")
|
|
|
|
|
}
|
|
|
|
|
pub, priv, _ := ed25519.GenerateKey(nil)
|
|
|
|
|
good := ed25519.Sign(priv, msg)
|
|
|
|
|
if !ed25519strict.Verify(pub, msg, good) {
|
|
|
|
|
t.Error("Verify rejects a valid signature")
|
|
|
|
|
}
|
|
|
|
|
if ed25519strict.Verify(pub[:31], msg, good) || ed25519strict.Verify(pub, msg, good[:63]) {
|
|
|
|
|
t.Error("Verify accepts another length")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The committed vectors give their result.
|
|
|
|
|
func TestVectors(t *testing.T) {
|
|
|
|
|
var f testkit.Ed25519StrictFile
|
|
|
|
|
if err := testkit.ReadJSON("../../testdata/vectors/ed25519_strict.json", &f); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if len(f.Vectors) == 0 {
|
|
|
|
|
t.Fatal("no vectors")
|
|
|
|
|
}
|
|
|
|
|
for _, v := range f.Vectors {
|
|
|
|
|
msg, _ := hex.DecodeString(v.Message)
|
|
|
|
|
pub, _ := hex.DecodeString(v.PublicKey)
|
|
|
|
|
sig, _ := hex.DecodeString(v.Signature)
|
|
|
|
|
if got := ed25519strict.Verify(pub, msg, sig); got != v.Valid {
|
|
|
|
|
t.Errorf("%s: Verify = %v, want %v", v.Name, got, v.Valid)
|
|
|
|
|
}
|
|
|
|
|
if got := ed25519.Verify(pub, msg, sig); got != v.Stdlib {
|
|
|
|
|
t.Errorf("%s: crypto/ed25519 = %v, recorded %v", v.Name, got, v.Stdlib)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|