diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:23:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:23:18 +0000 |
commit | 43a123c1ae6613b3efeed291fa552ecd909d3acf (patch) | |
tree | fd92518b7024bc74031f78a1cf9e454b65e73665 /src/crypto/ed25519 | |
parent | Initial commit. (diff) | |
download | golang-1.20-43a123c1ae6613b3efeed291fa552ecd909d3acf.tar.xz golang-1.20-43a123c1ae6613b3efeed291fa552ecd909d3acf.zip |
Adding upstream version 1.20.14.upstream/1.20.14upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/crypto/ed25519')
-rw-r--r-- | src/crypto/ed25519/ed25519.go | 337 | ||||
-rw-r--r-- | src/crypto/ed25519/ed25519_test.go | 384 | ||||
-rw-r--r-- | src/crypto/ed25519/ed25519vectors_test.go | 120 | ||||
-rw-r--r-- | src/crypto/ed25519/testdata/sign.input.gz | bin | 0 -> 50330 bytes |
4 files changed, 841 insertions, 0 deletions
diff --git a/src/crypto/ed25519/ed25519.go b/src/crypto/ed25519/ed25519.go new file mode 100644 index 0000000..a45d056 --- /dev/null +++ b/src/crypto/ed25519/ed25519.go @@ -0,0 +1,337 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package ed25519 implements the Ed25519 signature algorithm. See +// https://ed25519.cr.yp.to/. +// +// These functions are also compatible with the “Ed25519” function defined in +// RFC 8032. However, unlike RFC 8032's formulation, this package's private key +// representation includes a public key suffix to make multiple signing +// operations with the same key more efficient. This package refers to the RFC +// 8032 private key as the “seed”. +package ed25519 + +import ( + "bytes" + "crypto" + "crypto/internal/edwards25519" + cryptorand "crypto/rand" + "crypto/sha512" + "errors" + "io" + "strconv" +) + +const ( + // PublicKeySize is the size, in bytes, of public keys as used in this package. + PublicKeySize = 32 + // PrivateKeySize is the size, in bytes, of private keys as used in this package. + PrivateKeySize = 64 + // SignatureSize is the size, in bytes, of signatures generated and verified by this package. + SignatureSize = 64 + // SeedSize is the size, in bytes, of private key seeds. These are the private key representations used by RFC 8032. + SeedSize = 32 +) + +// PublicKey is the type of Ed25519 public keys. +type PublicKey []byte + +// Any methods implemented on PublicKey might need to also be implemented on +// PrivateKey, as the latter embeds the former and will expose its methods. + +// Equal reports whether pub and x have the same value. +func (pub PublicKey) Equal(x crypto.PublicKey) bool { + xx, ok := x.(PublicKey) + if !ok { + return false + } + return bytes.Equal(pub, xx) +} + +// PrivateKey is the type of Ed25519 private keys. It implements [crypto.Signer]. +type PrivateKey []byte + +// Public returns the [PublicKey] corresponding to priv. +func (priv PrivateKey) Public() crypto.PublicKey { + publicKey := make([]byte, PublicKeySize) + copy(publicKey, priv[32:]) + return PublicKey(publicKey) +} + +// Equal reports whether priv and x have the same value. +func (priv PrivateKey) Equal(x crypto.PrivateKey) bool { + xx, ok := x.(PrivateKey) + if !ok { + return false + } + return bytes.Equal(priv, xx) +} + +// Seed returns the private key seed corresponding to priv. It is provided for +// interoperability with RFC 8032. RFC 8032's private keys correspond to seeds +// in this package. +func (priv PrivateKey) Seed() []byte { + return bytes.Clone(priv[:SeedSize]) +} + +// Sign signs the given message with priv. rand is ignored. +// +// If opts.HashFunc() is [crypto.SHA512], the pre-hashed variant Ed25519ph is used +// and message is expected to be a SHA-512 hash, otherwise opts.HashFunc() must +// be [crypto.Hash](0) and the message must not be hashed, as Ed25519 performs two +// passes over messages to be signed. +// +// A value of type [Options] can be used as opts, or crypto.Hash(0) or +// crypto.SHA512 directly to select plain Ed25519 or Ed25519ph, respectively. +func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) { + hash := opts.HashFunc() + context := "" + if opts, ok := opts.(*Options); ok { + context = opts.Context + } + if l := len(context); l > 255 { + return nil, errors.New("ed25519: bad Ed25519ph context length: " + strconv.Itoa(l)) + } + switch { + case hash == crypto.SHA512: // Ed25519ph + if l := len(message); l != sha512.Size { + return nil, errors.New("ed25519: bad Ed25519ph message hash length: " + strconv.Itoa(l)) + } + signature := make([]byte, SignatureSize) + sign(signature, priv, message, domPrefixPh, context) + return signature, nil + case hash == crypto.Hash(0) && context != "": // Ed25519ctx + signature := make([]byte, SignatureSize) + sign(signature, priv, message, domPrefixCtx, context) + return signature, nil + case hash == crypto.Hash(0): // Ed25519 + return Sign(priv, message), nil + default: + return nil, errors.New("ed25519: expected opts.HashFunc() zero (unhashed message, for standard Ed25519) or SHA-512 (for Ed25519ph)") + } +} + +// Options can be used with [PrivateKey.Sign] or [VerifyWithOptions] +// to select Ed25519 variants. +type Options struct { + // Hash can be zero for regular Ed25519, or crypto.SHA512 for Ed25519ph. + Hash crypto.Hash + + // Context, if not empty, selects Ed25519ctx or provides the context string + // for Ed25519ph. It can be at most 255 bytes in length. + Context string +} + +// HashFunc returns o.Hash. +func (o *Options) HashFunc() crypto.Hash { return o.Hash } + +// GenerateKey generates a public/private key pair using entropy from rand. +// If rand is nil, [crypto/rand.Reader] will be used. +func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) { + if rand == nil { + rand = cryptorand.Reader + } + + seed := make([]byte, SeedSize) + if _, err := io.ReadFull(rand, seed); err != nil { + return nil, nil, err + } + + privateKey := NewKeyFromSeed(seed) + publicKey := make([]byte, PublicKeySize) + copy(publicKey, privateKey[32:]) + + return publicKey, privateKey, nil +} + +// NewKeyFromSeed calculates a private key from a seed. It will panic if +// len(seed) is not [SeedSize]. This function is provided for interoperability +// with RFC 8032. RFC 8032's private keys correspond to seeds in this +// package. +func NewKeyFromSeed(seed []byte) PrivateKey { + // Outline the function body so that the returned key can be stack-allocated. + privateKey := make([]byte, PrivateKeySize) + newKeyFromSeed(privateKey, seed) + return privateKey +} + +func newKeyFromSeed(privateKey, seed []byte) { + if l := len(seed); l != SeedSize { + panic("ed25519: bad seed length: " + strconv.Itoa(l)) + } + + h := sha512.Sum512(seed) + s, err := edwards25519.NewScalar().SetBytesWithClamping(h[:32]) + if err != nil { + panic("ed25519: internal error: setting scalar failed") + } + A := (&edwards25519.Point{}).ScalarBaseMult(s) + + publicKey := A.Bytes() + + copy(privateKey, seed) + copy(privateKey[32:], publicKey) +} + +// Sign signs the message with privateKey and returns a signature. It will +// panic if len(privateKey) is not [PrivateKeySize]. +func Sign(privateKey PrivateKey, message []byte) []byte { + // Outline the function body so that the returned signature can be + // stack-allocated. + signature := make([]byte, SignatureSize) + sign(signature, privateKey, message, domPrefixPure, "") + return signature +} + +// Domain separation prefixes used to disambiguate Ed25519/Ed25519ph/Ed25519ctx. +// See RFC 8032, Section 2 and Section 5.1. +const ( + // domPrefixPure is empty for pure Ed25519. + domPrefixPure = "" + // domPrefixPh is dom2(phflag=1) for Ed25519ph. It must be followed by the + // uint8-length prefixed context. + domPrefixPh = "SigEd25519 no Ed25519 collisions\x01" + // domPrefixCtx is dom2(phflag=0) for Ed25519ctx. It must be followed by the + // uint8-length prefixed context. + domPrefixCtx = "SigEd25519 no Ed25519 collisions\x00" +) + +func sign(signature, privateKey, message []byte, domPrefix, context string) { + if l := len(privateKey); l != PrivateKeySize { + panic("ed25519: bad private key length: " + strconv.Itoa(l)) + } + seed, publicKey := privateKey[:SeedSize], privateKey[SeedSize:] + + h := sha512.Sum512(seed) + s, err := edwards25519.NewScalar().SetBytesWithClamping(h[:32]) + if err != nil { + panic("ed25519: internal error: setting scalar failed") + } + prefix := h[32:] + + mh := sha512.New() + if domPrefix != domPrefixPure { + mh.Write([]byte(domPrefix)) + mh.Write([]byte{byte(len(context))}) + mh.Write([]byte(context)) + } + mh.Write(prefix) + mh.Write(message) + messageDigest := make([]byte, 0, sha512.Size) + messageDigest = mh.Sum(messageDigest) + r, err := edwards25519.NewScalar().SetUniformBytes(messageDigest) + if err != nil { + panic("ed25519: internal error: setting scalar failed") + } + + R := (&edwards25519.Point{}).ScalarBaseMult(r) + + kh := sha512.New() + if domPrefix != domPrefixPure { + kh.Write([]byte(domPrefix)) + kh.Write([]byte{byte(len(context))}) + kh.Write([]byte(context)) + } + kh.Write(R.Bytes()) + kh.Write(publicKey) + kh.Write(message) + hramDigest := make([]byte, 0, sha512.Size) + hramDigest = kh.Sum(hramDigest) + k, err := edwards25519.NewScalar().SetUniformBytes(hramDigest) + if err != nil { + panic("ed25519: internal error: setting scalar failed") + } + + S := edwards25519.NewScalar().MultiplyAdd(k, s, r) + + copy(signature[:32], R.Bytes()) + copy(signature[32:], S.Bytes()) +} + +// Verify reports whether sig is a valid signature of message by publicKey. It +// will panic if len(publicKey) is not [PublicKeySize]. +func Verify(publicKey PublicKey, message, sig []byte) bool { + return verify(publicKey, message, sig, domPrefixPure, "") +} + +// VerifyWithOptions reports whether sig is a valid signature of message by +// publicKey. A valid signature is indicated by returning a nil error. It will +// panic if len(publicKey) is not [PublicKeySize]. +// +// If opts.Hash is [crypto.SHA512], the pre-hashed variant Ed25519ph is used and +// message is expected to be a SHA-512 hash, otherwise opts.Hash must be +// [crypto.Hash](0) and the message must not be hashed, as Ed25519 performs two +// passes over messages to be signed. +func VerifyWithOptions(publicKey PublicKey, message, sig []byte, opts *Options) error { + switch { + case opts.Hash == crypto.SHA512: // Ed25519ph + if l := len(message); l != sha512.Size { + return errors.New("ed25519: bad Ed25519ph message hash length: " + strconv.Itoa(l)) + } + if l := len(opts.Context); l > 255 { + return errors.New("ed25519: bad Ed25519ph context length: " + strconv.Itoa(l)) + } + if !verify(publicKey, message, sig, domPrefixPh, opts.Context) { + return errors.New("ed25519: invalid signature") + } + return nil + case opts.Hash == crypto.Hash(0) && opts.Context != "": // Ed25519ctx + if l := len(opts.Context); l > 255 { + return errors.New("ed25519: bad Ed25519ctx context length: " + strconv.Itoa(l)) + } + if !verify(publicKey, message, sig, domPrefixCtx, opts.Context) { + return errors.New("ed25519: invalid signature") + } + return nil + case opts.Hash == crypto.Hash(0): // Ed25519 + if !verify(publicKey, message, sig, domPrefixPure, "") { + return errors.New("ed25519: invalid signature") + } + return nil + default: + return errors.New("ed25519: expected opts.Hash zero (unhashed message, for standard Ed25519) or SHA-512 (for Ed25519ph)") + } +} + +func verify(publicKey PublicKey, message, sig []byte, domPrefix, context string) bool { + if l := len(publicKey); l != PublicKeySize { + panic("ed25519: bad public key length: " + strconv.Itoa(l)) + } + + if len(sig) != SignatureSize || sig[63]&224 != 0 { + return false + } + + A, err := (&edwards25519.Point{}).SetBytes(publicKey) + if err != nil { + return false + } + + kh := sha512.New() + if domPrefix != domPrefixPure { + kh.Write([]byte(domPrefix)) + kh.Write([]byte{byte(len(context))}) + kh.Write([]byte(context)) + } + kh.Write(sig[:32]) + kh.Write(publicKey) + kh.Write(message) + hramDigest := make([]byte, 0, sha512.Size) + hramDigest = kh.Sum(hramDigest) + k, err := edwards25519.NewScalar().SetUniformBytes(hramDigest) + if err != nil { + panic("ed25519: internal error: setting scalar failed") + } + + S, err := edwards25519.NewScalar().SetCanonicalBytes(sig[32:]) + if err != nil { + return false + } + + // [S]B = R + [k]A --> [k](-A) + [S]B = R + minusA := (&edwards25519.Point{}).Negate(A) + R := (&edwards25519.Point{}).VarTimeDoubleScalarBaseMult(k, minusA, S) + + return bytes.Equal(sig[:32], R.Bytes()) +} diff --git a/src/crypto/ed25519/ed25519_test.go b/src/crypto/ed25519/ed25519_test.go new file mode 100644 index 0000000..47c8698 --- /dev/null +++ b/src/crypto/ed25519/ed25519_test.go @@ -0,0 +1,384 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ed25519 + +import ( + "bufio" + "bytes" + "compress/gzip" + "crypto" + "crypto/internal/boring" + "crypto/rand" + "crypto/sha512" + "encoding/hex" + "internal/testenv" + "log" + "os" + "strings" + "testing" +) + +func Example_ed25519ctx() { + pub, priv, err := GenerateKey(nil) + if err != nil { + log.Fatal(err) + } + + msg := []byte("The quick brown fox jumps over the lazy dog") + + sig, err := priv.Sign(nil, msg, &Options{ + Context: "Example_ed25519ctx", + }) + if err != nil { + log.Fatal(err) + } + + if err := VerifyWithOptions(pub, msg, sig, &Options{ + Context: "Example_ed25519ctx", + }); err != nil { + log.Fatal("invalid signature") + } +} + +type zeroReader struct{} + +func (zeroReader) Read(buf []byte) (int, error) { + for i := range buf { + buf[i] = 0 + } + return len(buf), nil +} + +func TestSignVerify(t *testing.T) { + var zero zeroReader + public, private, _ := GenerateKey(zero) + + message := []byte("test message") + sig := Sign(private, message) + if !Verify(public, message, sig) { + t.Errorf("valid signature rejected") + } + + wrongMessage := []byte("wrong message") + if Verify(public, wrongMessage, sig) { + t.Errorf("signature of different message accepted") + } +} + +func TestSignVerifyHashed(t *testing.T) { + // From RFC 8032, Section 7.3 + key, _ := hex.DecodeString("833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf") + expectedSig, _ := hex.DecodeString("98a70222f0b8121aa9d30f813d683f809e462b469c7ff87639499bb94e6dae4131f85042463c2a355a2003d062adf5aaa10b8c61e636062aaad11c2a26083406") + message, _ := hex.DecodeString("616263") + + private := PrivateKey(key) + public := private.Public().(PublicKey) + hash := sha512.Sum512(message) + sig, err := private.Sign(nil, hash[:], crypto.SHA512) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(sig, expectedSig) { + t.Error("signature doesn't match test vector") + } + sig, err = private.Sign(nil, hash[:], &Options{Hash: crypto.SHA512}) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(sig, expectedSig) { + t.Error("signature doesn't match test vector") + } + if err := VerifyWithOptions(public, hash[:], sig, &Options{Hash: crypto.SHA512}); err != nil { + t.Errorf("valid signature rejected: %v", err) + } + + if err := VerifyWithOptions(public, hash[:], sig, &Options{Hash: crypto.SHA256}); err == nil { + t.Errorf("expected error for wrong hash") + } + + wrongHash := sha512.Sum512([]byte("wrong message")) + if VerifyWithOptions(public, wrongHash[:], sig, &Options{Hash: crypto.SHA512}) == nil { + t.Errorf("signature of different message accepted") + } + + sig[0] ^= 0xff + if VerifyWithOptions(public, hash[:], sig, &Options{Hash: crypto.SHA512}) == nil { + t.Errorf("invalid signature accepted") + } + sig[0] ^= 0xff + sig[SignatureSize-1] ^= 0xff + if VerifyWithOptions(public, hash[:], sig, &Options{Hash: crypto.SHA512}) == nil { + t.Errorf("invalid signature accepted") + } + + // The RFC provides no test vectors for Ed25519ph with context, so just sign + // and verify something. + sig, err = private.Sign(nil, hash[:], &Options{Hash: crypto.SHA512, Context: "123"}) + if err != nil { + t.Fatal(err) + } + if err := VerifyWithOptions(public, hash[:], sig, &Options{Hash: crypto.SHA512, Context: "123"}); err != nil { + t.Errorf("valid signature rejected: %v", err) + } + if err := VerifyWithOptions(public, hash[:], sig, &Options{Hash: crypto.SHA512, Context: "321"}); err == nil { + t.Errorf("expected error for wrong context") + } + if err := VerifyWithOptions(public, hash[:], sig, &Options{Hash: crypto.SHA256, Context: "123"}); err == nil { + t.Errorf("expected error for wrong hash") + } +} + +func TestSignVerifyContext(t *testing.T) { + // From RFC 8032, Section 7.2 + key, _ := hex.DecodeString("0305334e381af78f141cb666f6199f57bc3495335a256a95bd2a55bf546663f6dfc9425e4f968f7f0c29f0259cf5f9aed6851c2bb4ad8bfb860cfee0ab248292") + expectedSig, _ := hex.DecodeString("55a4cc2f70a54e04288c5f4cd1e45a7bb520b36292911876cada7323198dd87a8b36950b95130022907a7fb7c4e9b2d5f6cca685a587b4b21f4b888e4e7edb0d") + message, _ := hex.DecodeString("f726936d19c800494e3fdaff20b276a8") + context := "foo" + + private := PrivateKey(key) + public := private.Public().(PublicKey) + sig, err := private.Sign(nil, message, &Options{Context: context}) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(sig, expectedSig) { + t.Error("signature doesn't match test vector") + } + if err := VerifyWithOptions(public, message, sig, &Options{Context: context}); err != nil { + t.Errorf("valid signature rejected: %v", err) + } + + if VerifyWithOptions(public, []byte("bar"), sig, &Options{Context: context}) == nil { + t.Errorf("signature of different message accepted") + } + if VerifyWithOptions(public, message, sig, &Options{Context: "bar"}) == nil { + t.Errorf("signature with different context accepted") + } + + sig[0] ^= 0xff + if VerifyWithOptions(public, message, sig, &Options{Context: context}) == nil { + t.Errorf("invalid signature accepted") + } + sig[0] ^= 0xff + sig[SignatureSize-1] ^= 0xff + if VerifyWithOptions(public, message, sig, &Options{Context: context}) == nil { + t.Errorf("invalid signature accepted") + } +} + +func TestCryptoSigner(t *testing.T) { + var zero zeroReader + public, private, _ := GenerateKey(zero) + + signer := crypto.Signer(private) + + publicInterface := signer.Public() + public2, ok := publicInterface.(PublicKey) + if !ok { + t.Fatalf("expected PublicKey from Public() but got %T", publicInterface) + } + + if !bytes.Equal(public, public2) { + t.Errorf("public keys do not match: original:%x vs Public():%x", public, public2) + } + + message := []byte("message") + var noHash crypto.Hash + signature, err := signer.Sign(zero, message, noHash) + if err != nil { + t.Fatalf("error from Sign(): %s", err) + } + + signature2, err := signer.Sign(zero, message, &Options{Hash: noHash}) + if err != nil { + t.Fatalf("error from Sign(): %s", err) + } + if !bytes.Equal(signature, signature2) { + t.Errorf("signatures keys do not match") + } + + if !Verify(public, message, signature) { + t.Errorf("Verify failed on signature from Sign()") + } +} + +func TestEqual(t *testing.T) { + public, private, _ := GenerateKey(rand.Reader) + + if !public.Equal(public) { + t.Errorf("public key is not equal to itself: %q", public) + } + if !public.Equal(crypto.Signer(private).Public()) { + t.Errorf("private.Public() is not Equal to public: %q", public) + } + if !private.Equal(private) { + t.Errorf("private key is not equal to itself: %q", private) + } + + otherPub, otherPriv, _ := GenerateKey(rand.Reader) + if public.Equal(otherPub) { + t.Errorf("different public keys are Equal") + } + if private.Equal(otherPriv) { + t.Errorf("different private keys are Equal") + } +} + +func TestGolden(t *testing.T) { + // sign.input.gz is a selection of test cases from + // https://ed25519.cr.yp.to/python/sign.input + testDataZ, err := os.Open("testdata/sign.input.gz") + if err != nil { + t.Fatal(err) + } + defer testDataZ.Close() + testData, err := gzip.NewReader(testDataZ) + if err != nil { + t.Fatal(err) + } + defer testData.Close() + + scanner := bufio.NewScanner(testData) + lineNo := 0 + + for scanner.Scan() { + lineNo++ + + line := scanner.Text() + parts := strings.Split(line, ":") + if len(parts) != 5 { + t.Fatalf("bad number of parts on line %d", lineNo) + } + + privBytes, _ := hex.DecodeString(parts[0]) + pubKey, _ := hex.DecodeString(parts[1]) + msg, _ := hex.DecodeString(parts[2]) + sig, _ := hex.DecodeString(parts[3]) + // The signatures in the test vectors also include the message + // at the end, but we just want R and S. + sig = sig[:SignatureSize] + + if l := len(pubKey); l != PublicKeySize { + t.Fatalf("bad public key length on line %d: got %d bytes", lineNo, l) + } + + var priv [PrivateKeySize]byte + copy(priv[:], privBytes) + copy(priv[32:], pubKey) + + sig2 := Sign(priv[:], msg) + if !bytes.Equal(sig, sig2[:]) { + t.Errorf("different signature result on line %d: %x vs %x", lineNo, sig, sig2) + } + + if !Verify(pubKey, msg, sig2) { + t.Errorf("signature failed to verify on line %d", lineNo) + } + + priv2 := NewKeyFromSeed(priv[:32]) + if !bytes.Equal(priv[:], priv2) { + t.Errorf("recreating key pair gave different private key on line %d: %x vs %x", lineNo, priv[:], priv2) + } + + if pubKey2 := priv2.Public().(PublicKey); !bytes.Equal(pubKey, pubKey2) { + t.Errorf("recreating key pair gave different public key on line %d: %x vs %x", lineNo, pubKey, pubKey2) + } + + if seed := priv2.Seed(); !bytes.Equal(priv[:32], seed) { + t.Errorf("recreating key pair gave different seed on line %d: %x vs %x", lineNo, priv[:32], seed) + } + } + + if err := scanner.Err(); err != nil { + t.Fatalf("error reading test data: %s", err) + } +} + +func TestMalleability(t *testing.T) { + // https://tools.ietf.org/html/rfc8032#section-5.1.7 adds an additional test + // that s be in [0, order). This prevents someone from adding a multiple of + // order to s and obtaining a second valid signature for the same message. + msg := []byte{0x54, 0x65, 0x73, 0x74} + sig := []byte{ + 0x7c, 0x38, 0xe0, 0x26, 0xf2, 0x9e, 0x14, 0xaa, 0xbd, 0x05, 0x9a, + 0x0f, 0x2d, 0xb8, 0xb0, 0xcd, 0x78, 0x30, 0x40, 0x60, 0x9a, 0x8b, + 0xe6, 0x84, 0xdb, 0x12, 0xf8, 0x2a, 0x27, 0x77, 0x4a, 0xb0, 0x67, + 0x65, 0x4b, 0xce, 0x38, 0x32, 0xc2, 0xd7, 0x6f, 0x8f, 0x6f, 0x5d, + 0xaf, 0xc0, 0x8d, 0x93, 0x39, 0xd4, 0xee, 0xf6, 0x76, 0x57, 0x33, + 0x36, 0xa5, 0xc5, 0x1e, 0xb6, 0xf9, 0x46, 0xb3, 0x1d, + } + publicKey := []byte{ + 0x7d, 0x4d, 0x0e, 0x7f, 0x61, 0x53, 0xa6, 0x9b, 0x62, 0x42, 0xb5, + 0x22, 0xab, 0xbe, 0xe6, 0x85, 0xfd, 0xa4, 0x42, 0x0f, 0x88, 0x34, + 0xb1, 0x08, 0xc3, 0xbd, 0xae, 0x36, 0x9e, 0xf5, 0x49, 0xfa, + } + + if Verify(publicKey, msg, sig) { + t.Fatal("non-canonical signature accepted") + } +} + +func TestAllocations(t *testing.T) { + if boring.Enabled { + t.Skip("skipping allocations test with BoringCrypto") + } + testenv.SkipIfOptimizationOff(t) + + if allocs := testing.AllocsPerRun(100, func() { + seed := make([]byte, SeedSize) + message := []byte("Hello, world!") + priv := NewKeyFromSeed(seed) + pub := priv.Public().(PublicKey) + signature := Sign(priv, message) + if !Verify(pub, message, signature) { + t.Fatal("signature didn't verify") + } + }); allocs > 0 { + t.Errorf("expected zero allocations, got %0.1f", allocs) + } +} + +func BenchmarkKeyGeneration(b *testing.B) { + var zero zeroReader + for i := 0; i < b.N; i++ { + if _, _, err := GenerateKey(zero); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkNewKeyFromSeed(b *testing.B) { + seed := make([]byte, SeedSize) + for i := 0; i < b.N; i++ { + _ = NewKeyFromSeed(seed) + } +} + +func BenchmarkSigning(b *testing.B) { + var zero zeroReader + _, priv, err := GenerateKey(zero) + if err != nil { + b.Fatal(err) + } + message := []byte("Hello, world!") + b.ResetTimer() + for i := 0; i < b.N; i++ { + Sign(priv, message) + } +} + +func BenchmarkVerification(b *testing.B) { + var zero zeroReader + pub, priv, err := GenerateKey(zero) + if err != nil { + b.Fatal(err) + } + message := []byte("Hello, world!") + signature := Sign(priv, message) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Verify(pub, message, signature) + } +} diff --git a/src/crypto/ed25519/ed25519vectors_test.go b/src/crypto/ed25519/ed25519vectors_test.go new file mode 100644 index 0000000..f933f28 --- /dev/null +++ b/src/crypto/ed25519/ed25519vectors_test.go @@ -0,0 +1,120 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ed25519_test + +import ( + "crypto/ed25519" + "encoding/hex" + "encoding/json" + "internal/testenv" + "os" + "os/exec" + "path/filepath" + "testing" +) + +// TestEd25519Vectors runs a very large set of test vectors that exercise all +// combinations of low-order points, low-order components, and non-canonical +// encodings. These vectors lock in unspecified and spec-divergent behaviors in +// edge cases that are not security relevant in most contexts, but that can +// cause issues in consensus applications if changed. +// +// Our behavior matches the "classic" unwritten verification rules of the +// "ref10" reference implementation. +// +// Note that although we test for these edge cases, they are not covered by the +// Go 1 Compatibility Promise. Applications that need stable verification rules +// should use github.com/hdevalence/ed25519consensus. +// +// See https://hdevalence.ca/blog/2020-10-04-its-25519am for more details. +func TestEd25519Vectors(t *testing.T) { + jsonVectors := downloadEd25519Vectors(t) + var vectors []struct { + A, R, S, M string + Flags []string + } + if err := json.Unmarshal(jsonVectors, &vectors); err != nil { + t.Fatal(err) + } + for i, v := range vectors { + expectedToVerify := true + for _, f := range v.Flags { + switch f { + // We use the simplified verification formula that doesn't multiply + // by the cofactor, so any low order residue will cause the + // signature not to verify. + // + // This is allowed, but not required, by RFC 8032. + case "LowOrderResidue": + expectedToVerify = false + // Our point decoding allows non-canonical encodings (in violation + // of RFC 8032) but R is not decoded: instead, R is recomputed and + // compared bytewise against the canonical encoding. + case "NonCanonicalR": + expectedToVerify = false + } + } + + publicKey := decodeHex(t, v.A) + signature := append(decodeHex(t, v.R), decodeHex(t, v.S)...) + message := []byte(v.M) + + didVerify := ed25519.Verify(publicKey, message, signature) + if didVerify && !expectedToVerify { + t.Errorf("#%d: vector with flags %s unexpectedly verified", i, v.Flags) + } + if !didVerify && expectedToVerify { + t.Errorf("#%d: vector with flags %s unexpectedly rejected", i, v.Flags) + } + } +} + +func downloadEd25519Vectors(t *testing.T) []byte { + testenv.MustHaveExternalNetwork(t) + + // Create a temp dir and modcache subdir. + d := t.TempDir() + // Create a spot for the modcache. + modcache := filepath.Join(d, "modcache") + if err := os.Mkdir(modcache, 0777); err != nil { + t.Fatal(err) + } + + t.Setenv("GO111MODULE", "on") + t.Setenv("GOMODCACHE", modcache) + + // Download the JSON test file from the GOPROXY with `go mod download`, + // pinning the version so test and module caching works as expected. + goTool := testenv.GoToolPath(t) + path := "filippo.io/mostly-harmless/ed25519vectors@v0.0.0-20210322192420-30a2d7243a94" + cmd := exec.Command(goTool, "mod", "download", "-modcacherw", "-json", path) + // TODO: enable the sumdb once the TryBots proxy supports it. + cmd.Env = append(os.Environ(), "GONOSUMDB=*") + output, err := cmd.Output() + if err != nil { + t.Fatalf("failed to run `go mod download -json %s`, output: %s", path, output) + } + var dm struct { + Dir string // absolute path to cached source root directory + } + if err := json.Unmarshal(output, &dm); err != nil { + t.Fatal(err) + } + + jsonVectors, err := os.ReadFile(filepath.Join(dm.Dir, "ed25519vectors.json")) + if err != nil { + t.Fatalf("failed to read ed25519vectors.json: %v", err) + } + return jsonVectors +} + +func decodeHex(t *testing.T, s string) []byte { + t.Helper() + b, err := hex.DecodeString(s) + if err != nil { + t.Errorf("invalid hex: %v", err) + } + return b +} diff --git a/src/crypto/ed25519/testdata/sign.input.gz b/src/crypto/ed25519/testdata/sign.input.gz Binary files differnew file mode 100644 index 0000000..e6dc728 --- /dev/null +++ b/src/crypto/ed25519/testdata/sign.input.gz |