Source file src/crypto/internal/cryptotest/wycheproof/wycheproof.go

     1  // Copyright 2026 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 wycheproof provides helper utilities for writing tests that
     6  // rely on Wycheproof test vector schemas and JSON vector data.
     7  // See https://github.com/C2SP/wycheproof for more information.
     8  package wycheproof
     9  
    10  import (
    11  	"crypto"
    12  	"crypto/internal/cryptotest"
    13  	"encoding/hex"
    14  	"encoding/json"
    15  	"fmt"
    16  	"internal/testenv"
    17  	"os"
    18  	"path"
    19  	"reflect"
    20  	"testing"
    21  )
    22  
    23  // LoadVectorFile unmarshals Wycheproof JSON test vector file by name.
    24  //
    25  // Typically, the value argument will be a pointer to a Wycheproof schema
    26  // type representing the in-memory structure of the JSON data.
    27  //
    28  // Panics if there is an error reading the Wycheproof JSON vector data file,
    29  // or if it can't be unmarshalled into the provided value.
    30  func LoadVectorFile(t *testing.T, filename string, value any) {
    31  	testenv.SkipIfShortAndSlow(t)
    32  
    33  	// We want to avoid a dependency on c2sp/wycheproof or the schema generator
    34  	// in this stdlib code, so we fetch the module at runtime and read the
    35  	// vector JSON from that module clone. The version is pinned to whatever
    36  	// the _schema generator was last run against (see schemaversion.go), so
    37  	// the vectors match the generated schema.go.
    38  	wycheproofDir := cryptotest.FetchModule(
    39  		t, "github.com/c2sp/wycheproof", wycheproofVersion)
    40  
    41  	content, err := os.ReadFile(path.Join(wycheproofDir, "testvectors_v1", filename))
    42  	if err != nil {
    43  		t.Fatalf("missing Wycheproof vector file %q: %v", filename, err)
    44  	}
    45  
    46  	err = json.Unmarshal(content, value)
    47  	if err != nil {
    48  		t.Fatalf("failed to unmarshal vector file %q: %v", filename, err)
    49  	}
    50  }
    51  
    52  // ShouldPass returns true if a test should pass informed by expected result
    53  // and flags.
    54  //
    55  // flagsShouldPass is a map used to determine if an "acceptable" result test
    56  // case should pass based on test's flags.
    57  // Every possible flag value that's associated with an "acceptable" result
    58  // should be explicitly specified, otherwise ShouldPass will panic.
    59  func ShouldPass(t *testing.T, result Result, flags []string, flagsShouldPass map[string]bool) bool {
    60  	switch result {
    61  	case "valid":
    62  		return true
    63  	case "invalid":
    64  		return false
    65  	case "acceptable":
    66  		for _, flag := range flags {
    67  			pass, ok := flagsShouldPass[flag]
    68  			if !ok {
    69  				t.Fatalf("unspecified flag: %q", flag)
    70  			}
    71  			if !pass {
    72  				return false
    73  			}
    74  		}
    75  		return true // There are no flags, or all are meant to pass.
    76  	default:
    77  		t.Fatalf("unexpected result: %v", result)
    78  		return false
    79  	}
    80  }
    81  
    82  // ParseHash maps from a Wycheproof hash name to a crypto.Hash implementation
    83  // It panics if the provided hash name is unknown.
    84  func ParseHash(h string) crypto.Hash {
    85  	switch h {
    86  	case "SHA-1":
    87  		return crypto.SHA1
    88  	case "SHA-256":
    89  		return crypto.SHA256
    90  	case "SHA-224":
    91  		return crypto.SHA224
    92  	case "SHA-384":
    93  		return crypto.SHA384
    94  	case "SHA-512":
    95  		return crypto.SHA512
    96  	case "SHA-512/224":
    97  		return crypto.SHA512_224
    98  	case "SHA-512/256":
    99  		return crypto.SHA512_256
   100  	case "SHA3-224":
   101  		return crypto.SHA3_224
   102  	case "SHA3-256":
   103  		return crypto.SHA3_256
   104  	case "SHA3-384":
   105  		return crypto.SHA3_384
   106  	case "SHA3-512":
   107  		return crypto.SHA3_512
   108  	default:
   109  		panic(fmt.Sprintf("unknown hash algorithm: %q", h))
   110  	}
   111  }
   112  
   113  // TestName returns a t.Run subtest name for a Wycheproof test vector.
   114  func TestName(file string, tv any) string {
   115  	v := reflect.ValueOf(tv)
   116  	if v.Kind() == reflect.Pointer {
   117  		v = v.Elem()
   118  	}
   119  	tcID := v.FieldByName("TcId").Int()
   120  	comment := v.FieldByName("Comment").String()
   121  	name := fmt.Sprintf("%s #%d", file, tcID)
   122  	if comment != "" {
   123  		name += " " + comment
   124  	}
   125  	return name
   126  }
   127  
   128  // MustDecodeHex is a helper that decodes the provided string or panics.
   129  //
   130  // Many Wycheproof vector values are hex encoded strings and in a test context
   131  // we don't intend to handle decoding errors gracefully.
   132  func MustDecodeHex(h string) []byte {
   133  	d, err := hex.DecodeString(h)
   134  	if err != nil {
   135  		panic(err)
   136  	}
   137  	return d
   138  }
   139  
   140  // MustPanic calls fn and fails the test if fn does not panic.
   141  //
   142  // This is useful for testing that invalid inputs (like incorrect nonce sizes
   143  // for AEAD ciphers) properly trigger panics rather than silently accepting
   144  // the bad input.
   145  func MustPanic(t *testing.T, name string, fn func()) {
   146  	t.Helper()
   147  	defer func() {
   148  		if r := recover(); r == nil {
   149  			t.Errorf("%s: expected panic but didn't get one", name)
   150  		}
   151  	}()
   152  	fn()
   153  }
   154  

View as plain text