internal/wycheproof: add crypto/ecdh tests

Alongside the existing ECDH tests, add tests that use the new
crypto/ecdh package. The test vectors include a number of private
that use non-standard sizes, which we reject, but aren't flagged,
so we need to skip them.

Change-Id: Iaaef225b0149a86833095f51748d230385d43bfe
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/424274
Reviewed-by: Russ Cox <rsc@golang.org>
Run-TryBot: Roland Shoemaker <roland@golang.org>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Roland Shoemaker <roland@golang.org>
diff --git a/internal/wycheproof/ecdh_stdlib_test.go b/internal/wycheproof/ecdh_stdlib_test.go
new file mode 100644
index 0000000..44315fc
--- /dev/null
+++ b/internal/wycheproof/ecdh_stdlib_test.go
@@ -0,0 +1,142 @@
+// Copyright 2022 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.
+
+//go:build go1.20
+
+package wycheproof
+
+import (
+	"bytes"
+	"crypto/ecdh"
+	"fmt"
+	"testing"
+)
+
+func TestECDHStdLib(t *testing.T) {
+	type ECDHTestVector struct {
+		// A brief description of the test case
+		Comment string `json:"comment,omitempty"`
+		// A list of flags
+		Flags []string `json:"flags,omitempty"`
+		// the private key
+		Private string `json:"private,omitempty"`
+		// Encoded public key
+		Public string `json:"public,omitempty"`
+		// Test result
+		Result string `json:"result,omitempty"`
+		// The shared secret key
+		Shared string `json:"shared,omitempty"`
+		// Identifier of the test case
+		TcID int `json:"tcId,omitempty"`
+	}
+
+	type ECDHTestGroup struct {
+		Curve string            `json:"curve,omitempty"`
+		Tests []*ECDHTestVector `json:"tests,omitempty"`
+	}
+
+	type Root struct {
+		TestGroups []*ECDHTestGroup `json:"testGroups,omitempty"`
+	}
+
+	flagsShouldPass := map[string]bool{
+		// We don't support compressed points.
+		"CompressedPoint": false,
+		// We don't support decoding custom curves.
+		"UnnamedCurve": false,
+		// WrongOrder and UnusedParam are only found with UnnamedCurve.
+		"WrongOrder":  false,
+		"UnusedParam": false,
+
+		// X25519 specific flags
+		"Twist":              true,
+		"SmallPublicKey":     false,
+		"LowOrderPublic":     false,
+		"ZeroSharedSecret":   false,
+		"NonCanonicalPublic": true,
+	}
+
+	// curveToCurve is a map of all elliptic curves supported
+	// by crypto/elliptic, which can subsequently be parsed and tested.
+	curveToCurve := map[string]ecdh.Curve{
+		"secp256r1":  ecdh.P256(),
+		"secp384r1":  ecdh.P384(),
+		"secp521r1":  ecdh.P521(),
+		"curve25519": ecdh.X25519(),
+	}
+
+	curveToKeySize := map[string]int{
+		"secp256r1":  32,
+		"secp384r1":  48,
+		"secp521r1":  66,
+		"curve25519": 32,
+	}
+
+	for _, f := range []string{
+		"ecdh_secp256r1_ecpoint_test.json",
+		"ecdh_secp384r1_ecpoint_test.json",
+		"ecdh_secp521r1_ecpoint_test.json",
+		"x25519_test.json",
+	} {
+		var root Root
+		readTestVector(t, f, &root)
+		for _, tg := range root.TestGroups {
+			if _, ok := curveToCurve[tg.Curve]; !ok {
+				continue
+			}
+			for _, tt := range tg.Tests {
+				tg, tt := tg, tt
+				t.Run(fmt.Sprintf("%s/%d", tg.Curve, tt.TcID), func(t *testing.T) {
+					t.Logf("Type: %v", tt.Result)
+					t.Logf("Flags: %q", tt.Flags)
+					t.Log(tt.Comment)
+
+					shouldPass := shouldPass(tt.Result, tt.Flags, flagsShouldPass)
+
+					curve := curveToCurve[tg.Curve]
+					p := decodeHex(tt.Public)
+					pub, err := curve.NewPublicKey(p)
+					if err != nil {
+						if shouldPass {
+							t.Errorf("NewPublicKey: %v", err)
+						}
+						return
+					}
+
+					privBytes := decodeHex(tt.Private)
+					if len(privBytes) != curveToKeySize[tg.Curve] {
+						t.Skipf("non-standard key size %d", len(privBytes))
+					}
+
+					priv, err := curve.NewPrivateKey(privBytes)
+					if err != nil {
+						if shouldPass {
+							t.Errorf("NewPrivateKey: %v", err)
+						}
+						return
+					}
+
+					shared := decodeHex(tt.Shared)
+					x, err := curve.ECDH(priv, pub)
+					if err != nil {
+						if tg.Curve == "curve25519" && !shouldPass {
+							// ECDH is expected to only return an error when using X25519,
+							// in all other cases an error is unexpected.
+							return
+						}
+						t.Fatalf("ECDH: %v", err)
+					}
+
+					if bytes.Equal(shared, x) != shouldPass {
+						if shouldPass {
+							t.Errorf("ECDH = %x, want %x", shared, x)
+						} else {
+							t.Errorf("ECDH = %x, want anything else", shared)
+						}
+					}
+				})
+			}
+		}
+	}
+}