1// Copyright 2024 The BoringSSL Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package subprocess
16
17import (
18	"bytes"
19	"encoding/hex"
20	"encoding/json"
21	"fmt"
22)
23
24// The following structures reflect the JSON of ACVP EDDSA tests. See
25// https://pages.nist.gov/ACVP/draft-celi-acvp-eddsa.html#name-test-types
26
27type eddsaTestVectorSet struct {
28	Groups []eddsaTestGroup `json:"testGroups"`
29	Mode   string           `json:"mode"`
30}
31
32type eddsaTestGroup struct {
33	ID      uint64 `json:"tgId"`
34	Type    string `json:"testType"`
35	Curve   string `json:"curve"`
36	Prehash bool   `json:"prehash"`
37	Tests   []struct {
38		ID            uint64 `json:"tcId"`
39		QHex          string `json:"q,omitempty"`
40		ContextHex    string `json:"context,omitempty"`
41		ContextLength uint64 `json:"contextLength,omitempty"`
42		MsgHex        string `json:"message,omitempty"`
43		SignatureHex  string `json:"signature,omitempty"`
44	} `json:"tests"`
45}
46
47type eddsaTestGroupResponse struct {
48	ID    uint64              `json:"tgId"`
49	Tests []eddsaTestResponse `json:"tests"`
50	QHex  string              `json:"q,omitempty"`
51}
52
53type eddsaTestResponse struct {
54	ID           uint64 `json:"tcId"`
55	DHex         string `json:"d,omitempty"`
56	QHex         string `json:"q,omitempty"`
57	Passed       *bool  `json:"testPassed,omitempty"` // using pointer so value is not omitted when it is false
58	SignatureHex string `json:"signature,omitempty"`
59}
60
61// eddsa implements an ACVP algorithm by making requests to the
62// subprocess to generate and verify EDDSA keys and signatures.
63type eddsa struct {
64	algo   string
65	curves map[string]bool // supported curve names
66}
67
68func (e *eddsa) Process(vectorSet []byte, m Transactable) (any, error) {
69	var parsed eddsaTestVectorSet
70	if err := json.Unmarshal(vectorSet, &parsed); err != nil {
71		return nil, err
72	}
73
74	var ret []eddsaTestGroupResponse
75	// See
76	// https://pages.nist.gov/ACVP/draft-celi-acvp-eddsa.html#name-test-vectors
77	// for details about the tests.
78	for _, group := range parsed.Groups {
79		group := group
80
81		if _, ok := e.curves[group.Curve]; !ok {
82			return nil, fmt.Errorf("curve %q in test group %d not supported", group.Curve, group.ID)
83		}
84
85		response := eddsaTestGroupResponse{
86			ID: group.ID,
87		}
88
89		var sigGenPrivKeySeed []byte
90		var sigGenPrivKeyQHex string
91		for _, test := range group.Tests {
92			test := test
93
94			var testResp eddsaTestResponse
95			testResp.ID = test.ID
96
97			switch parsed.Mode {
98			case "keyGen":
99				if group.Type != "AFT" {
100					return nil, fmt.Errorf("unknown test type %q in keyGen test group %d", group.Type, group.ID)
101				}
102				m.TransactAsync(e.algo+"/keyGen", 2, [][]byte{[]byte(group.Curve)}, func(result [][]byte) error {
103					testResp.DHex = hex.EncodeToString(result[0])
104					testResp.QHex = hex.EncodeToString(result[1])
105					response.Tests = append(response.Tests, testResp)
106					return nil
107				})
108
109			case "keyVer":
110				if group.Type != "AFT" {
111					return nil, fmt.Errorf("unknown test type %q in keyGen test group %d", group.Type, group.ID)
112				}
113				q, err := hex.DecodeString(test.QHex)
114				if err != nil {
115					return nil, fmt.Errorf("failed to decode q in test case %d/%d: %s", group.ID, test.ID, err)
116				}
117				m.TransactAsync(e.algo+"/keyVer", 1, [][]byte{[]byte(group.Curve), q}, func(result [][]byte) error {
118					// result[0] should be a single byte: zero if false, one if true
119					switch {
120					case bytes.Equal(result[0], []byte{00}):
121						f := false
122						testResp.Passed = &f
123					case bytes.Equal(result[0], []byte{01}):
124						t := true
125						testResp.Passed = &t
126					default:
127						return fmt.Errorf("key verification returned unexpected result: %q", result[0])
128					}
129					response.Tests = append(response.Tests, testResp)
130					return nil
131				})
132
133			case "sigGen":
134				if group.Type != "AFT" && group.Type != "BFT" {
135					return nil, fmt.Errorf("unknown test type %q in keyGen test group %d", group.Type, group.ID)
136				}
137
138				if len(sigGenPrivKeySeed) == 0 {
139					result, err := m.Transact(e.algo+"/keyGen", 2, []byte(group.Curve))
140					if err != nil {
141						return nil, fmt.Errorf("key generation failed for test case %d/%d: %s", group.ID, test.ID, err)
142					}
143
144					sigGenPrivKeySeed = result[0]
145					sigGenPrivKeyQHex = hex.EncodeToString(result[1])
146				}
147				response.QHex = sigGenPrivKeyQHex
148
149				msg, err := hex.DecodeString(test.MsgHex)
150				if err != nil {
151					return nil, fmt.Errorf("failed to decode message hex in test case %d/%d: %s", group.ID, test.ID, err)
152				}
153
154				prehash := []byte{0}
155				if group.Prehash {
156					prehash = []byte{1}
157				}
158				var context []byte
159				if test.ContextHex != "" {
160					if uint64(len(test.ContextHex)) != test.ContextLength*2 {
161						return nil, fmt.Errorf("context hex length %d does not match context length %d in test case %d/%d", len(test.ContextHex), test.ContextLength, group.ID, test.ID)
162					}
163					context, err = hex.DecodeString(test.ContextHex)
164					if err != nil {
165						return nil, fmt.Errorf("failed to decode context hex in test case %d/%d: %s", group.ID, test.ID, err)
166					}
167				}
168
169				args := [][]byte{[]byte(group.Curve), sigGenPrivKeySeed, msg, prehash, context}
170				m.TransactAsync(e.algo+"/sigGen", 1, args, func(result [][]byte) error {
171					testResp.SignatureHex = hex.EncodeToString(result[0])
172					response.Tests = append(response.Tests, testResp)
173					return nil
174				})
175
176			case "sigVer":
177				if group.Type != "AFT" {
178					return nil, fmt.Errorf("unknown test type %q in keyGen test group %d", group.Type, group.ID)
179				}
180
181				if test.ContextHex != "" {
182					return nil, fmt.Errorf("unexpected context field in sigVer test case %d/%d", group.ID, test.ID)
183				}
184
185				msg, err := hex.DecodeString(test.MsgHex)
186				if err != nil {
187					return nil, fmt.Errorf("failed to decode message hex in test case %d/%d: %s", group.ID, test.ID, err)
188				}
189				q, err := hex.DecodeString(test.QHex)
190				if err != nil {
191					return nil, fmt.Errorf("failed to decode q in test case %d/%d: %s", group.ID, test.ID, err)
192				}
193				signature, err := hex.DecodeString(test.SignatureHex)
194				if err != nil {
195					return nil, fmt.Errorf("failed to decode signature in test case %d/%d: %s", group.ID, test.ID, err)
196				}
197				prehash := []byte{0}
198				if group.Prehash {
199					prehash = []byte{1}
200				}
201
202				args := [][]byte{[]byte(group.Curve), msg, q, signature, prehash}
203				m.TransactAsync(e.algo+"/sigVer", 1, args, func(result [][]byte) error {
204					// result[0] should be a single byte: zero if false, one if true
205					switch {
206					case bytes.Equal(result[0], []byte{00}):
207						f := false
208						testResp.Passed = &f
209					case bytes.Equal(result[0], []byte{01}):
210						t := true
211						testResp.Passed = &t
212					default:
213						return fmt.Errorf("signature verification returned unexpected result: %q", result[0])
214					}
215					response.Tests = append(response.Tests, testResp)
216					return nil
217				})
218
219			default:
220				return nil, fmt.Errorf("invalid mode %q in EDDSA vector set", parsed.Mode)
221			}
222		}
223
224		m.Barrier(func() {
225			ret = append(ret, response)
226		})
227	}
228
229	if err := m.Flush(); err != nil {
230		return nil, err
231	}
232
233	return ret, nil
234}
235