|
|
|
|
|
package testkit
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"crypto/ed25519"
|
|
|
|
|
|
"crypto/sha256"
|
|
|
|
|
|
"crypto/sha512"
|
|
|
|
|
|
"encoding/binary"
|
|
|
|
|
|
"encoding/hex"
|
|
|
|
|
|
"errors"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"math/big"
|
|
|
|
|
|
"slices"
|
|
|
|
|
|
|
|
|
|
|
|
"g.activething.com/go/DateKeys/internal/ed25519strict"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Ed25519StrictFile is testdata/vectors/ed25519_strict.json: Ed25519
|
|
|
|
|
|
// signatures with the result of the strict profile of the author signature
|
|
|
|
|
|
// (spec v0.11, §29.9), built after the cases of «Taming the many EdDSAs»
|
|
|
|
|
|
// (Chalkias, Garillot, Nikolaenko, 2020): small-order and non-canonical
|
|
|
|
|
|
// keys, non-canonical R and S, keys of mixed order, and signatures that only
|
|
|
|
|
|
// the equation with the cofactor accepts. Stdlib is what crypto/ed25519 of
|
|
|
|
|
|
// Go says, for the record: where it says true and Valid is false, an
|
|
|
|
|
|
// implementation needs the checks of package ed25519strict.
|
|
|
|
|
|
type Ed25519StrictFile struct {
|
|
|
|
|
|
Spec string `json:"spec"`
|
|
|
|
|
|
Description string `json:"description"`
|
|
|
|
|
|
Vectors []Ed25519StrictVector `json:"vectors"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Ed25519StrictVector is one signature, its message and its public key, in
|
|
|
|
|
|
// hexadecimal.
|
|
|
|
|
|
type Ed25519StrictVector struct {
|
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
|
Message string `json:"message"`
|
|
|
|
|
|
PublicKey string `json:"public_key"`
|
|
|
|
|
|
Signature string `json:"signature"`
|
|
|
|
|
|
Valid bool `json:"valid"`
|
|
|
|
|
|
Stdlib bool `json:"stdlib"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Ed25519StrictVectors builds ed25519_strict.json, and fails if
|
|
|
|
|
|
// ed25519strict.Verify does not give the result each case is built for.
|
|
|
|
|
|
func Ed25519StrictVectors() (Ed25519StrictFile, error) {
|
|
|
|
|
|
f := Ed25519StrictFile{
|
|
|
|
|
|
Spec: SpecVersion,
|
|
|
|
|
|
Description: "Ed25519 signatures and the result of the strict profile of the author signature (spec v0.11, §29.9), after the cases of " +
|
|
|
|
|
|
"«Taming the many EdDSAs»; stdlib is the result of crypto/ed25519 of Go, for the record. Generated by the reference implementation. See testdata/README.md.",
|
|
|
|
|
|
}
|
|
|
|
|
|
var errs []error
|
|
|
|
|
|
for _, c := range ed25519Cases() {
|
|
|
|
|
|
if got := ed25519strict.Verify(c.pub, c.msg, c.sig); got != c.valid {
|
|
|
|
|
|
errs = append(errs, fmt.Errorf("ed25519 %q: Verify = %v, want %v", c.name, got, c.valid))
|
|
|
|
|
|
}
|
|
|
|
|
|
f.Vectors = append(f.Vectors, Ed25519StrictVector{
|
|
|
|
|
|
Name: c.name,
|
|
|
|
|
|
Message: hex.EncodeToString(c.msg),
|
|
|
|
|
|
PublicKey: hex.EncodeToString(c.pub),
|
|
|
|
|
|
Signature: hex.EncodeToString(c.sig),
|
|
|
|
|
|
Valid: c.valid,
|
|
|
|
|
|
Stdlib: ed25519.Verify(ed25519.PublicKey(c.pub), c.msg, c.sig),
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
return f, errors.Join(errs...)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type ed25519Case struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
msg, pub, sig []byte
|
|
|
|
|
|
valid bool
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func ed25519Cases() []ed25519Case {
|
|
|
|
|
|
seed := sha256.Sum256([]byte("DateKeys ed25519_strict vectors"))
|
|
|
|
|
|
priv := ed25519.NewKeyFromSeed(seed[:])
|
|
|
|
|
|
pub := []byte(priv.Public().(ed25519.PublicKey))
|
|
|
|
|
|
msg := []byte("DateKeys")
|
|
|
|
|
|
sig := ed25519.Sign(priv, msg)
|
|
|
|
|
|
|
|
|
|
|
|
cases := []ed25519Case{{"a valid signature", msg, pub, sig, true}}
|
|
|
|
|
|
|
|
|
|
|
|
// S + ℓ is below 2^253, so its top bits are clear, but S is not canonical.
|
|
|
|
|
|
s := leInt(sig[32:])
|
|
|
|
|
|
s.Add(s, edL)
|
|
|
|
|
|
cases = append(cases, ed25519Case{"S + ℓ", msg, pub, slices.Concat(sig[:32], leBytes(s)), false})
|
|
|
|
|
|
high := slices.Clone(sig)
|
|
|
|
|
|
high[63] |= 0x20
|
|
|
|
|
|
cases = append(cases, ed25519Case{"S with bit 253 set", msg, pub, high, false})
|
|
|
|
|
|
r := slices.Clone(sig)
|
|
|
|
|
|
copy(r[:32], nonCanonicalZero(0))
|
|
|
|
|
|
cases = append(cases, ed25519Case{"R not canonical", msg, pub, r, false})
|
|
|
|
|
|
|
|
|
|
|
|
// A of small order, R the identity and S = 0: [S]B − [k]A = −[k]A is
|
|
|
|
|
|
// the identity when the order of A divides k, so a message is searched.
|
|
|
|
|
|
identity := edEncode(edPoint{big.NewInt(0), big.NewInt(1)})
|
|
|
|
|
|
forged := slices.Concat(identity, make([]byte, 32))
|
|
|
|
|
|
for i, t := range edTorsion() {
|
|
|
|
|
|
a := edEncode(t)
|
|
|
|
|
|
m := messageFor(identity, a, func(k *big.Int) bool { return new(big.Int).Mod(k, big.NewInt(8)).Sign() == 0 })
|
|
|
|
|
|
cases = append(cases, ed25519Case{fmt.Sprintf("A of small order, the point %d of the torsion, R the identity and S = 0", i), m, a, forged, false})
|
|
|
|
|
|
}
|
|
|
|
|
|
// The same with A not canonical: y = p + 0, a point of order 4, with
|
|
|
|
|
|
// either sign; and the identity with its sign bit set.
|
|
|
|
|
|
for _, sign := range []byte{0, 0x80} {
|
|
|
|
|
|
a := nonCanonicalZero(sign)
|
|
|
|
|
|
m := messageFor(identity, a, func(k *big.Int) bool { return new(big.Int).Mod(k, big.NewInt(8)).Sign() == 0 })
|
|
|
|
|
|
cases = append(cases, ed25519Case{fmt.Sprintf("A not canonical, y = p, sign %d, R the identity and S = 0", sign>>7), m, a, forged, false})
|
|
|
|
|
|
}
|
|
|
|
|
|
negZero := slices.Clone(identity)
|
|
|
|
|
|
negZero[31] |= 0x80
|
|
|
|
|
|
m := messageFor(identity, negZero, func(*big.Int) bool { return true })
|
|
|
|
|
|
cases = append(cases, ed25519Case{"A the identity with the sign bit, R the identity and S = 0", m, negZero, forged, false})
|
|
|
|
|
|
|
|
|
|
|
|
// A of mixed order, [a]B plus a point of order 8: the equation without
|
|
|
|
|
|
// the cofactor holds only when [k]T is the identity, that is, when 8
|
|
|
|
|
|
// divides k; the equation with the cofactor holds always.
|
|
|
|
|
|
a := new(big.Int).Mod(leInt(seed[:]), edL)
|
|
|
|
|
|
t8 := edTorsion()[edOrder8]
|
|
|
|
|
|
mixed := edAdd(edMul(a, edBase()), t8)
|
|
|
|
|
|
am := edEncode(mixed)
|
|
|
|
|
|
rr := new(big.Int).Mod(leInt(slices.Concat(seed[:], seed[:])), edL)
|
|
|
|
|
|
rp := edEncode(edMul(rr, edBase()))
|
|
|
|
|
|
for _, holds := range []bool{true, false} {
|
|
|
|
|
|
m := messageFor(rp, am, func(k *big.Int) bool { return (new(big.Int).Mod(k, big.NewInt(8)).Sign() == 0) == holds })
|
|
|
|
|
|
k := hramScalar(rp, am, m)
|
|
|
|
|
|
sv := new(big.Int).Mod(new(big.Int).Add(rr, new(big.Int).Mul(k, a)), edL)
|
|
|
|
|
|
name := "A of mixed order, 8 divides k: the equation without the cofactor holds"
|
|
|
|
|
|
if !holds {
|
|
|
|
|
|
name = "A of mixed order, 8 does not divide k: only the equation with the cofactor holds"
|
|
|
|
|
|
}
|
|
|
|
|
|
cases = append(cases, ed25519Case{name, m, am, slices.Concat(rp, leBytes(sv)), holds})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// R the identity with A of prime order: S = k·a makes [S]B − [k]A the
|
|
|
|
|
|
// identity, which is R; libsodium rejects an R of small order, and this
|
|
|
|
|
|
// profile does not.
|
|
|
|
|
|
ap := edEncode(edMul(a, edBase()))
|
|
|
|
|
|
m = []byte("DateKeys: R the identity")
|
|
|
|
|
|
k := hramScalar(identity, ap, m)
|
|
|
|
|
|
sv := new(big.Int).Mod(new(big.Int).Mul(k, a), edL)
|
|
|
|
|
|
cases = append(cases, ed25519Case{"R the identity, A of prime order", m, ap, slices.Concat(identity, leBytes(sv)), true})
|
|
|
|
|
|
return cases
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// messageFor returns the first message "DateKeys n", n = 0, 1, ..., whose k =
|
|
|
|
|
|
// SHA-512(R ‖ A ‖ M) mod ℓ satisfies ok.
|
|
|
|
|
|
func messageFor(r, a []byte, ok func(k *big.Int) bool) []byte {
|
|
|
|
|
|
for n := uint64(0); ; n++ {
|
|
|
|
|
|
m := binary.BigEndian.AppendUint64([]byte("DateKeys "), n)
|
|
|
|
|
|
if ok(hramScalar(r, a, m)) {
|
|
|
|
|
|
return m
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func hramScalar(r, a, m []byte) *big.Int {
|
|
|
|
|
|
h := sha512.Sum512(slices.Concat(r, a, m))
|
|
|
|
|
|
return new(big.Int).Mod(leInt(h[:]), edL)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// nonCanonicalZero is y = p, the non-canonical encoding of y = 0, with the
|
|
|
|
|
|
// sign bit given.
|
|
|
|
|
|
func nonCanonicalZero(sign byte) []byte {
|
|
|
|
|
|
b := make([]byte, 32)
|
|
|
|
|
|
b[0] = 0xed
|
|
|
|
|
|
for i := 1; i < 31; i++ {
|
|
|
|
|
|
b[i] = 0xff
|
|
|
|
|
|
}
|
|
|
|
|
|
b[31] = 0x7f | sign
|
|
|
|
|
|
return b
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Arithmetic on edwards25519 with math/big, slow and simple, only to build
|
|
|
|
|
|
// these vectors: the reference never computes on points itself.
|
|
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
|
edP = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 255), big.NewInt(19))
|
|
|
|
|
|
edL = func() *big.Int {
|
|
|
|
|
|
l, _ := new(big.Int).SetString("7237005577332262213973186563042994240857116359379907606001950938285454250989", 10)
|
|
|
|
|
|
return l
|
|
|
|
|
|
}()
|
|
|
|
|
|
edD = func() *big.Int {
|
|
|
|
|
|
d := new(big.Int).Mul(big.NewInt(-121665), edInv(big.NewInt(121666)))
|
|
|
|
|
|
return d.Mod(d, edP)
|
|
|
|
|
|
}()
|
|
|
|
|
|
edSqrtM1 = new(big.Int).Exp(big.NewInt(2), new(big.Int).Rsh(new(big.Int).Sub(edP, big.NewInt(1)), 2), edP)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// edOrder8 is the index in edTorsion of a point of order 8.
|
|
|
|
|
|
const edOrder8 = 1
|
|
|
|
|
|
|
|
|
|
|
|
type edPoint struct{ x, y *big.Int }
|
|
|
|
|
|
|
|
|
|
|
|
func edInv(x *big.Int) *big.Int {
|
|
|
|
|
|
return new(big.Int).Exp(x, new(big.Int).Sub(edP, big.NewInt(2)), edP)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func edAdd(a, b edPoint) edPoint {
|
|
|
|
|
|
t := new(big.Int).Mul(edD, a.x)
|
|
|
|
|
|
t.Mul(t, b.x).Mul(t, a.y).Mul(t, b.y).Mod(t, edP)
|
|
|
|
|
|
x := new(big.Int).Add(new(big.Int).Mul(a.x, b.y), new(big.Int).Mul(b.x, a.y))
|
|
|
|
|
|
x.Mul(x, edInv(new(big.Int).Add(big.NewInt(1), t))).Mod(x, edP)
|
|
|
|
|
|
y := new(big.Int).Add(new(big.Int).Mul(a.y, b.y), new(big.Int).Mul(a.x, b.x))
|
|
|
|
|
|
y.Mul(y, edInv(new(big.Int).Mod(new(big.Int).Sub(big.NewInt(1), t), edP))).Mod(y, edP)
|
|
|
|
|
|
return edPoint{x, y}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func edMul(k *big.Int, a edPoint) edPoint {
|
|
|
|
|
|
r := edPoint{big.NewInt(0), big.NewInt(1)}
|
|
|
|
|
|
for i := k.BitLen() - 1; i >= 0; i-- {
|
|
|
|
|
|
r = edAdd(r, r)
|
|
|
|
|
|
if k.Bit(i) == 1 {
|
|
|
|
|
|
r = edAdd(r, a)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return r
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// edX recovers x from y and its sign bit, or nil when y is not on the curve.
|
|
|
|
|
|
func edX(y *big.Int, sign uint) *big.Int {
|
|
|
|
|
|
yy := new(big.Int).Mul(y, y)
|
|
|
|
|
|
num := new(big.Int).Sub(yy, big.NewInt(1))
|
|
|
|
|
|
den := new(big.Int).Add(new(big.Int).Mul(edD, yy), big.NewInt(1))
|
|
|
|
|
|
xx := new(big.Int).Mul(num, edInv(den.Mod(den, edP)))
|
|
|
|
|
|
xx.Mod(xx, edP)
|
|
|
|
|
|
if xx.Sign() == 0 {
|
|
|
|
|
|
return big.NewInt(0)
|
|
|
|
|
|
}
|
|
|
|
|
|
x := new(big.Int).Exp(xx, new(big.Int).Rsh(new(big.Int).Add(edP, big.NewInt(3)), 3), edP)
|
|
|
|
|
|
if new(big.Int).Mod(new(big.Int).Sub(new(big.Int).Mul(x, x), xx), edP).Sign() != 0 {
|
|
|
|
|
|
x.Mul(x, edSqrtM1).Mod(x, edP)
|
|
|
|
|
|
}
|
|
|
|
|
|
if new(big.Int).Mod(new(big.Int).Sub(new(big.Int).Mul(x, x), xx), edP).Sign() != 0 {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
if x.Bit(0) != sign {
|
|
|
|
|
|
x.Sub(edP, x)
|
|
|
|
|
|
}
|
|
|
|
|
|
return x
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func edBase() edPoint {
|
|
|
|
|
|
y := new(big.Int).Mul(big.NewInt(4), edInv(big.NewInt(5)))
|
|
|
|
|
|
y.Mod(y, edP)
|
|
|
|
|
|
return edPoint{edX(y, 0), y}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func edEncode(a edPoint) []byte {
|
|
|
|
|
|
b := leBytes(a.y)
|
|
|
|
|
|
b[31] |= byte(a.x.Bit(0)) << 7
|
|
|
|
|
|
return b
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// edTorsion returns the eight points of small order, [i]T for a point T of
|
|
|
|
|
|
// order 8 and i from 0 to 7: T is [ℓ]P for the first point P, by y, whose
|
|
|
|
|
|
// [ℓ]P is not of order 4 or less.
|
|
|
|
|
|
func edTorsion() []edPoint {
|
|
|
|
|
|
for y := int64(2); ; y++ {
|
|
|
|
|
|
x := edX(big.NewInt(y), 0)
|
|
|
|
|
|
if x == nil {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
t := edMul(edL, edPoint{x, big.NewInt(y)})
|
|
|
|
|
|
if q := edMul(big.NewInt(4), t); q.x.Sign() == 0 && q.y.Cmp(big.NewInt(1)) == 0 {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
out := make([]edPoint, 8)
|
|
|
|
|
|
out[0] = edPoint{big.NewInt(0), big.NewInt(1)}
|
|
|
|
|
|
for i := 1; i < 8; i++ {
|
|
|
|
|
|
out[i] = edAdd(out[i-1], t)
|
|
|
|
|
|
}
|
|
|
|
|
|
return out
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func leInt(b []byte) *big.Int {
|
|
|
|
|
|
be := slices.Clone(b)
|
|
|
|
|
|
slices.Reverse(be)
|
|
|
|
|
|
return new(big.Int).SetBytes(be)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func leBytes(x *big.Int) []byte {
|
|
|
|
|
|
b := x.FillBytes(make([]byte, 32))
|
|
|
|
|
|
slices.Reverse(b)
|
|
|
|
|
|
return b
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Ed25519Torsion returns the canonical encodings of the eight points of small
|
|
|
|
|
|
// order, computed from the curve, to check the table of ed25519strict.
|
|
|
|
|
|
func Ed25519Torsion() [][32]byte {
|
|
|
|
|
|
var out [][32]byte
|
|
|
|
|
|
for _, p := range edTorsion() {
|
|
|
|
|
|
out = append(out, [32]byte(edEncode(p)))
|
|
|
|
|
|
}
|
|
|
|
|
|
return out
|
|
|
|
|
|
}
|