]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/ed25519: add comprehensive edge-case test vectors
authorFilippo Valsorda <filippo@golang.org>
Wed, 24 Mar 2021 13:41:25 +0000 (14:41 +0100)
committerFilippo Valsorda <filippo@golang.org>
Mon, 5 Apr 2021 20:53:42 +0000 (20:53 +0000)
This will allow us to make changes to the internals confidently, without
risking causing issues in consensus applications. It will also prevent
architecture-specific divergence, like #40475.

Fixes #40478

Change-Id: I8c2b31406ca88add6941f14d8df8cecb96379cde
Reviewed-on: https://go-review.googlesource.com/c/go/+/304349
Run-TryBot: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Katie Hockman <katie@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Filippo Valsorda <filippo@golang.org>
Trust: Katie Hockman <katie@golang.org>

src/crypto/ed25519/ed25519vectors_test.go [new file with mode: 0644]

diff --git a/src/crypto/ed25519/ed25519vectors_test.go b/src/crypto/ed25519/ed25519vectors_test.go
new file mode 100644 (file)
index 0000000..74fcdcd
--- /dev/null
@@ -0,0 +1,109 @@
+// 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)
+
+       // 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", "-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
+}