// Copyright 2026 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 ecdh_test import ( "bytes" "crypto/ecdh" "crypto/ecdsa" "crypto/internal/cryptotest/wycheproof" "crypto/x509" "fmt" "testing" ) func TestSecpECDHWycheproof(t *testing.T) { flagsShouldPass := map[string]bool{ // We don't support compressed points or public keys. "CompressedPoint": false, "CompressedPublic": false, } curveToCurve := map[string]ecdh.Curve{ "secp256r1": ecdh.P256(), "secp384r1": ecdh.P384(), "secp521r1": ecdh.P521(), } curveToKeySize := map[string]int{ "secp256r1": 32, "secp384r1": 48, "secp521r1": 66, } for _, file := range []string{ "ecdh_secp256r1_ecpoint_test.json", "ecdh_secp384r1_ecpoint_test.json", "ecdh_secp521r1_ecpoint_test.json", } { var testdata wycheproof.EcdhEcpointTestSchemaV1Json wycheproof.LoadVectorFile(t, file, &testdata) for _, tg := range testdata.TestGroups { if _, ok := curveToCurve[tg.Curve]; !ok { continue } curve := curveToCurve[tg.Curve] keySize := curveToKeySize[tg.Curve] for _, tv := range tg.Tests { testName := wycheproof.TestName(file, tv) tv := ecdhWycheproofTV{ tcID: tv.TcId, comment: tv.Comment, flags: tv.Flags, result: tv.Result, public: tv.Public, private: tv.Private, shared: tv.Shared, } t.Run(testName, func(t *testing.T) { t.Parallel() runECDHWycheproofTest(t, curve, keySize, flagsShouldPass, tv, curve.NewPublicKey) }) } } } } func TestSecpECDHSPKIWycheproof(t *testing.T) { flagsShouldPass := map[string]bool{ "CompressedPublic": false, "CompressedPoint": false, "UnnamedCurve": false, "WrongOrder": false, "UnusedParam": false, "ModifiedGenerator": false, "ModifiedCofactor": false, "ModifiedCurveParameter": false, "NoCofactor": false, "Modified curve parameter": false, "InvalidAsn": false, } parseSPKIPub := func(p []byte) (*ecdh.PublicKey, error) { pubKeyAny, err := x509.ParsePKIXPublicKey(p) if err != nil { return nil, err } ecdsaPub, ok := pubKeyAny.(*ecdsa.PublicKey) if !ok { return nil, fmt.Errorf("unexpected key type %T", pubKeyAny) } return ecdsaPub.ECDH() } curveToCurve := map[string]ecdh.Curve{ "secp256r1": ecdh.P256(), "secp384r1": ecdh.P384(), "secp521r1": ecdh.P521(), } curveToKeySize := map[string]int{ "secp256r1": 32, "secp384r1": 48, "secp521r1": 66, } for _, file := range []string{ "ecdh_secp256r1_test.json", "ecdh_secp384r1_test.json", "ecdh_secp521r1_test.json", } { var testdata wycheproof.EcdhTestSchemaV1Json wycheproof.LoadVectorFile(t, file, &testdata) for _, tg := range testdata.TestGroups { if _, ok := curveToCurve[tg.Curve]; !ok { continue } curve := curveToCurve[tg.Curve] keySize := curveToKeySize[tg.Curve] for _, tv := range tg.Tests { testName := wycheproof.TestName(file, tv) tv := ecdhWycheproofTV{ tcID: tv.TcId, comment: tv.Comment, flags: tv.Flags, result: tv.Result, public: tv.Public, private: tv.Private, shared: tv.Shared, } t.Run(testName, func(t *testing.T) { t.Parallel() runECDHWycheproofTest(t, curve, keySize, flagsShouldPass, tv, parseSPKIPub) }) } } } } func TestX25519ECDHWycheproof(t *testing.T) { flagsShouldPass := map[string]bool{ "Twist": true, "SmallPublicKey": false, "LowOrderPublic": false, "ZeroSharedSecret": false, "NonCanonicalPublic": true, "SpecialPublicKey": true, "EdgeCaseMultiplication": true, "EdgeCaseShared": true, "Ktv": true, } file := "x25519_test.json" var testdata wycheproof.XdhCompSchemaV1Json wycheproof.LoadVectorFile(t, file, &testdata) for _, tg := range testdata.TestGroups { if tg.Curve != "curve25519" { continue } for _, tv := range tg.Tests { testName := wycheproof.TestName(file, tv) tv := ecdhWycheproofTV{ tcID: tv.TcId, comment: tv.Comment, flags: tv.Flags, result: tv.Result, public: tv.Public, private: tv.Private, shared: tv.Shared, } t.Run(testName, func(t *testing.T) { t.Parallel() runECDHWycheproofTest(t, ecdh.X25519(), 32, flagsShouldPass, tv, ecdh.X25519().NewPublicKey) }) } } } // ecdhWycheproofTV is a representation common to the three different schemas // we process in this test: wycheproof.XdhCompSchemaV1Json, // wycheproof.EcdhTestSchemaV1Json and wycheproof.EcdhEcpointTestSchemaV1Json type ecdhWycheproofTV struct { tcID int comment string flags []string result wycheproof.Result public string private string shared string } // runECDHWycheproofTest runs test logic common to the three ECDH test schemas // we process in this file. func runECDHWycheproofTest( t *testing.T, curve ecdh.Curve, expectedKeySize int, flagsShouldPass map[string]bool, tv ecdhWycheproofTV, parsePub func([]byte) (*ecdh.PublicKey, error)) { t.Helper() shouldPass := wycheproof.ShouldPass(t, tv.result, tv.flags, flagsShouldPass) pub, err := parsePub(wycheproof.MustDecodeHex(tv.public)) if err != nil { if shouldPass { t.Errorf("parsePub: %v", err) } return } privBytes := wycheproof.MustDecodeHex(tv.private) priv, err := curve.NewPrivateKey(privBytes) if err != nil { if shouldPass && len(privBytes) == expectedKeySize { t.Errorf("NewPrivateKey: %v", err) } return } x, err := priv.ECDH(pub) if err != nil { if shouldPass { t.Fatalf("ECDH: %v", err) } return } shared := wycheproof.MustDecodeHex(tv.shared) if shouldPass { if !bytes.Equal(shared, x) { t.Errorf("ECDH = %x, want %x", x, shared) } } else if tv.result == "invalid" { // For invalid inputs, a correct ECDH result is a test failure. if bytes.Equal(shared, x) { t.Errorf("ECDH = %x, want anything else", x) } } }