Source file src/crypto/ed25519/ed25519vectors_test.go

     1  // Copyright 2021 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ed25519_test
     6  
     7  import (
     8  	"crypto/ed25519"
     9  	"encoding/hex"
    10  	"encoding/json"
    11  	"internal/testenv"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"testing"
    16  )
    17  
    18  // TestEd25519Vectors runs a very large set of test vectors that exercise all
    19  // combinations of low-order points, low-order components, and non-canonical
    20  // encodings. These vectors lock in unspecified and spec-divergent behaviors in
    21  // edge cases that are not security relevant in most contexts, but that can
    22  // cause issues in consensus applications if changed.
    23  //
    24  // Our behavior matches the "classic" unwritten verification rules of the
    25  // "ref10" reference implementation.
    26  //
    27  // Note that although we test for these edge cases, they are not covered by the
    28  // Go 1 Compatibility Promise. Applications that need stable verification rules
    29  // should use github.com/hdevalence/ed25519consensus.
    30  //
    31  // See https://hdevalence.ca/blog/2020-10-04-its-25519am for more details.
    32  func TestEd25519Vectors(t *testing.T) {
    33  	jsonVectors := downloadEd25519Vectors(t)
    34  	var vectors []struct {
    35  		A, R, S, M string
    36  		Flags      []string
    37  	}
    38  	if err := json.Unmarshal(jsonVectors, &vectors); err != nil {
    39  		t.Fatal(err)
    40  	}
    41  	for i, v := range vectors {
    42  		expectedToVerify := true
    43  		for _, f := range v.Flags {
    44  			switch f {
    45  			// We use the simplified verification formula that doesn't multiply
    46  			// by the cofactor, so any low order residue will cause the
    47  			// signature not to verify.
    48  			//
    49  			// This is allowed, but not required, by RFC 8032.
    50  			case "LowOrderResidue":
    51  				expectedToVerify = false
    52  			// Our point decoding allows non-canonical encodings (in violation
    53  			// of RFC 8032) but R is not decoded: instead, R is recomputed and
    54  			// compared bytewise against the canonical encoding.
    55  			case "NonCanonicalR":
    56  				expectedToVerify = false
    57  			}
    58  		}
    59  
    60  		publicKey := decodeHex(t, v.A)
    61  		signature := append(decodeHex(t, v.R), decodeHex(t, v.S)...)
    62  		message := []byte(v.M)
    63  
    64  		didVerify := ed25519.Verify(publicKey, message, signature)
    65  		if didVerify && !expectedToVerify {
    66  			t.Errorf("#%d: vector with flags %s unexpectedly verified", i, v.Flags)
    67  		}
    68  		if !didVerify && expectedToVerify {
    69  			t.Errorf("#%d: vector with flags %s unexpectedly rejected", i, v.Flags)
    70  		}
    71  	}
    72  }
    73  
    74  func downloadEd25519Vectors(t *testing.T) []byte {
    75  	testenv.MustHaveExternalNetwork(t)
    76  
    77  	// Create a temp dir and modcache subdir.
    78  	d := t.TempDir()
    79  	// Create a spot for the modcache.
    80  	modcache := filepath.Join(d, "modcache")
    81  	if err := os.Mkdir(modcache, 0777); err != nil {
    82  		t.Fatal(err)
    83  	}
    84  
    85  	t.Setenv("GO111MODULE", "on")
    86  	t.Setenv("GOMODCACHE", modcache)
    87  
    88  	// Download the JSON test file from the GOPROXY with `go mod download`,
    89  	// pinning the version so test and module caching works as expected.
    90  	goTool := testenv.GoToolPath(t)
    91  	path := "filippo.io/mostly-harmless/ed25519vectors@v0.0.0-20210322192420-30a2d7243a94"
    92  	cmd := exec.Command(goTool, "mod", "download", "-modcacherw", "-json", path)
    93  	// TODO: enable the sumdb once the TryBots proxy supports it.
    94  	cmd.Env = append(os.Environ(), "GONOSUMDB=*")
    95  	output, err := cmd.Output()
    96  	if err != nil {
    97  		t.Fatalf("failed to run `go mod download -json %s`, output: %s", path, output)
    98  	}
    99  	var dm struct {
   100  		Dir string // absolute path to cached source root directory
   101  	}
   102  	if err := json.Unmarshal(output, &dm); err != nil {
   103  		t.Fatal(err)
   104  	}
   105  
   106  	jsonVectors, err := os.ReadFile(filepath.Join(dm.Dir, "ed25519vectors.json"))
   107  	if err != nil {
   108  		t.Fatalf("failed to read ed25519vectors.json: %v", err)
   109  	}
   110  	return jsonVectors
   111  }
   112  
   113  func decodeHex(t *testing.T, s string) []byte {
   114  	t.Helper()
   115  	b, err := hex.DecodeString(s)
   116  	if err != nil {
   117  		t.Errorf("invalid hex: %v", err)
   118  	}
   119  	return b
   120  }
   121  

View as plain text