crypto/mldsa: remove hand-defined wycheproof structs

Take a temporary replace directive in the go.mod for the
crypto/internal/cryptotest/wycheproof/_schema go.mod on
a yet-to-be-merged upstream fix to atombender/go-jsonschema. This fixes
a limitation where schemas with certain unmodelled properties (e.g.
conditional validation requirements) would result in the generated
code using a generic interface type instead of a structure.

With this in place the ML-DSA wycheproof test can lose its manually
defined structure + unmarshalling code in favour of using the generated
structure type that's always in-sync with upstream.

Change-Id: Ibbd7b41aaa0caf6414b319b77e62762b498ed6f8
Reviewed-on: https://go-review.googlesource.com/c/go/+/795301
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Auto-Submit: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Hongxiang Jiang <hxjiang@golang.org>
diff --git a/src/crypto/internal/cryptotest/wycheproof/_schema/go.mod b/src/crypto/internal/cryptotest/wycheproof/_schema/go.mod
index a94ecb8..1253c1c 100644
--- a/src/crypto/internal/cryptotest/wycheproof/_schema/go.mod
+++ b/src/crypto/internal/cryptotest/wycheproof/_schema/go.mod
@@ -15,3 +15,5 @@
 	github.com/sanity-io/litter v1.5.8 // indirect
 	github.com/sosodev/duration v1.4.0 // indirect
 )
+
+replace github.com/atombender/go-jsonschema => github.com/filippo-claude/go-jsonschema v0.23.2-0.20260625215234-f867483bec31
diff --git a/src/crypto/internal/cryptotest/wycheproof/_schema/go.sum b/src/crypto/internal/cryptotest/wycheproof/_schema/go.sum
index 06b458e..bc5fe4e 100644
--- a/src/crypto/internal/cryptotest/wycheproof/_schema/go.sum
+++ b/src/crypto/internal/cryptotest/wycheproof/_schema/go.sum
@@ -1,12 +1,12 @@
 dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
 dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
-github.com/atombender/go-jsonschema v0.23.1 h1:hpj94ehS+gpVvd1NJSTE0BMtWIpab06ObCz57rwo37c=
-github.com/atombender/go-jsonschema v0.23.1/go.mod h1:4SrgNrwGKijxhewvpPQ8HKEbin5Q2N4Tk+pFKW77HEk=
 github.com/c2sp/wycheproof v0.0.0-20260606153636-6d7cccd0fcb1 h1:veaGluF2y7kZnV/owZzY3FuOZWZQdzqnIcJsbJpFV7A=
 github.com/c2sp/wycheproof v0.0.0-20260606153636-6d7cccd0fcb1/go.mod h1:/5JsOpi3fKmnnig5BQ2mz49TNtnrD308VfTVHcbr5x4=
 github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/filippo-claude/go-jsonschema v0.23.2-0.20260625215234-f867483bec31 h1:c2W1mlsq9K/W+GPQWycHmGxTq4/RkWgDHOY99K+rtaw=
+github.com/filippo-claude/go-jsonschema v0.23.2-0.20260625215234-f867483bec31/go.mod h1:4SrgNrwGKijxhewvpPQ8HKEbin5Q2N4Tk+pFKW77HEk=
 github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
 github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
diff --git a/src/crypto/internal/cryptotest/wycheproof/schema.go b/src/crypto/internal/cryptotest/wycheproof/schema.go
index b0a5459..50c655f 100644
--- a/src/crypto/internal/cryptotest/wycheproof/schema.go
+++ b/src/crypto/internal/cryptotest/wycheproof/schema.go
@@ -6170,7 +6170,67 @@
 	return nil
 }
 
-type MlDsaSignTestVector interface{}
+type MlDsaSignTestVector struct {
+	// A brief description of the test case
+	Comment string `json:"comment"`
+
+	// [optional] The additional context string (if omitted, the context input is
+	// empty)
+	Ctx *string `json:"ctx,omitempty,omitzero"`
+
+	// A list of flags
+	Flags []string `json:"flags"`
+
+	// [optional] The message to sign (if omitted, mu is provided to use with
+	// Sign_internal)
+	Msg *string `json:"msg,omitempty,omitzero"`
+
+	// [optional] The 64-byte μ value (omitted in case of expected failure)
+	Mu *string `json:"mu,omitempty,omitzero"`
+
+	// Result corresponds to the JSON schema field "result".
+	Result Result `json:"result"`
+
+	// [optional] The 32-byte random value (if omitted, implicitly all zeroes for
+	// deterministic signing)
+	Rnd *string `json:"rnd,omitempty,omitzero"`
+
+	// The encoded signature (empty in case of expected failure)
+	Sig string `json:"sig"`
+
+	// Identifier of the test case
+	TcId int `json:"tcId"`
+}
+
+// UnmarshalJSON implements json.Unmarshaler.
+func (j *MlDsaSignTestVector) UnmarshalJSON(value []byte) error {
+	var raw map[string]interface{}
+	if err := json.Unmarshal(value, &raw); err != nil {
+		return err
+	}
+	if _, ok := raw["comment"]; raw != nil && !ok {
+		return fmt.Errorf("field comment in MlDsaSignTestVector: required")
+	}
+	if _, ok := raw["flags"]; raw != nil && !ok {
+		return fmt.Errorf("field flags in MlDsaSignTestVector: required")
+	}
+	if _, ok := raw["result"]; raw != nil && !ok {
+		return fmt.Errorf("field result in MlDsaSignTestVector: required")
+	}
+	if _, ok := raw["sig"]; raw != nil && !ok {
+		return fmt.Errorf("field sig in MlDsaSignTestVector: required")
+	}
+	if _, ok := raw["tcId"]; raw != nil && !ok {
+		return fmt.Errorf("field tcId in MlDsaSignTestVector: required")
+	}
+	type Plain MlDsaSignTestVector
+	var plain Plain
+	if err := json.Unmarshal(value, &plain); err != nil {
+		return err
+	}
+	*j = MlDsaSignTestVector(plain)
+	return nil
+}
 
 type MlDsaVerifyTestGroup struct {
 	// Encoded ML-DSA public key
diff --git a/src/crypto/mldsa/mldsa_wycheproof_test.go b/src/crypto/mldsa/mldsa_wycheproof_test.go
index 8abef04..9a5bcd3 100644
--- a/src/crypto/mldsa/mldsa_wycheproof_test.go
+++ b/src/crypto/mldsa/mldsa_wycheproof_test.go
@@ -12,7 +12,6 @@
 	"crypto/internal/cryptotest/wycheproof"
 	internalmldsa "crypto/internal/fips140/mldsa"
 	"crypto/mldsa"
-	"encoding/json"
 	"slices"
 	"testing"
 )
@@ -95,9 +94,7 @@
 				expectedPublicKey = wycheproof.MustDecodeHex(pk)
 			}
 
-			for _, raw := range tg.Tests {
-				tv := decodeMLDSASignTestVector(t, raw)
-
+			for _, tv := range tg.Tests {
 				t.Run(wycheproof.TestName(file, tv), func(t *testing.T) {
 					t.Parallel()
 
@@ -128,7 +125,7 @@
 	}
 }
 
-func runSignTest(t *testing.T, priv *mldsa.PrivateKey, tv mldsaSignTestVector, shouldPass bool) {
+func runSignTest(t *testing.T, priv *mldsa.PrivateKey, tv wycheproof.MlDsaSignTestVector, shouldPass bool) {
 	t.Helper()
 
 	var msg, μ []byte
@@ -213,9 +210,7 @@
 				expectedPublicKey = wycheproof.MustDecodeHex(pk)
 			}
 
-			for _, raw := range tg.Tests {
-				tv := decodeMLDSASignTestVector(t, raw)
-
+			for _, tv := range tg.Tests {
 				t.Run(wycheproof.TestName(file, tv), func(t *testing.T) {
 					t.Parallel()
 
@@ -242,7 +237,7 @@
 	}
 }
 
-func runRandomizedSignTest(t *testing.T, priv *internalmldsa.PrivateKey, tv mldsaSignTestVector, shouldPass bool) {
+func runRandomizedSignTest(t *testing.T, priv *internalmldsa.PrivateKey, tv wycheproof.MlDsaSignTestVector, shouldPass bool) {
 	t.Helper()
 
 	var msg, μ []byte
@@ -334,8 +329,7 @@
 				expectedPublicKey = wycheproof.MustDecodeHex(pk)
 			}
 
-			for _, raw := range tg.Tests {
-				tv := decodeMLDSASignTestVector(t, raw)
+			for _, tv := range tg.Tests {
 				t.Run(wycheproof.TestName(file, tv), func(t *testing.T) {
 					t.Parallel()
 
@@ -381,34 +375,3 @@
 	t.Fatalf("unknown algorithm: %s", algorithm)
 	return nil
 }
-
-// mldsaSignTestVector is a typed view of wycheproof.MlDsaSignTestVector,
-// which the schema generator emits as interface{} because of the schema's
-// conditional clauses.
-type mldsaSignTestVector struct {
-	TcId    int               `json:"tcId"`
-	Comment string            `json:"comment"`
-	Msg     *string           `json:"msg,omitempty"`
-	Ctx     *string           `json:"ctx,omitempty"`
-	Mu      *string           `json:"mu,omitempty"`
-	Rnd     *string           `json:"rnd,omitempty"`
-	Sig     string            `json:"sig"`
-	Result  wycheproof.Result `json:"result"`
-	Flags   []string          `json:"flags"`
-}
-
-// decodeMLDSASignTestVector roundtrips an interface{} typed raw
-// MlDsaSignTestVector to produce a typed MLDSASignTestVector.
-// This is a workaround for a limitation of the schema generator.
-func decodeMLDSASignTestVector(t *testing.T, raw wycheproof.MlDsaSignTestVector) mldsaSignTestVector {
-	t.Helper()
-	b, err := json.Marshal(raw)
-	if err != nil {
-		t.Fatalf("re-marshal sign test vector: %v", err)
-	}
-	var tv mldsaSignTestVector
-	if err := json.Unmarshal(b, &tv); err != nil {
-		t.Fatalf("decode sign test vector: %v", err)
-	}
-	return tv
-}