| package subprocess | |
| import ( | |
| "encoding/hex" | |
| "encoding/json" | |
| "errors" | |
| "fmt" | |
| "strings" | |
| ) | |
| // Common top-level structure to parse mode | |
| type mlkemTestVectorSet struct { | |
| Algorithm string `json:"algorithm"` | |
| Mode string `json:"mode"` | |
| Revision string `json:"revision"` | |
| } | |
| // Key generation specific structures | |
| type mlkemKeyGenTestVectorSet struct { | |
| Algorithm string `json:"algorithm"` | |
| Mode string `json:"mode"` | |
| Revision string `json:"revision"` | |
| Groups []mlkemKeyGenTestGroup `json:"testGroups"` | |
| } | |
| type mlkemKeyGenTestGroup struct { | |
| ID uint64 `json:"tgId"` | |
| TestType string `json:"testType"` | |
| ParameterSet string `json:"parameterSet"` | |
| Tests []mlkemKeyGenTest `json:"tests"` | |
| } | |
| type mlkemKeyGenTest struct { | |
| ID uint64 `json:"tcId"` | |
| Z string `json:"z"` | |
| D string `json:"d"` | |
| } | |
| type mlkemKeyGenTestGroupResponse struct { | |
| ID uint64 `json:"tgId"` | |
| Tests []mlkemKeyGenTestResponse `json:"tests"` | |
| } | |
| type mlkemKeyGenTestResponse struct { | |
| ID uint64 `json:"tcId"` | |
| EK string `json:"ek"` | |
| DK string `json:"dk"` | |
| } | |
| type mlkemEncapDecapTestVectorSet struct { | |
| Algorithm string `json:"algorithm"` | |
| Mode string `json:"mode"` | |
| Revision string `json:"revision"` | |
| Groups []mlkemEncapDecapTestGroup `json:"testGroups"` | |
| } | |
| type mlkemEncapDecapTestGroup struct { | |
| ID uint64 `json:"tgId"` | |
| TestType string `json:"testType"` | |
| ParameterSet string `json:"parameterSet"` | |
| Function string `json:"function"` | |
| Tests []mlkemEncapDecapTest `json:"tests"` | |
| } | |
| type mlkemEncapDecapTest struct { | |
| ID uint64 `json:"tcId"` | |
| EK string `json:"ek,omitempty"` | |
| DK string `json:"dk,omitempty"` | |
| M string `json:"m,omitempty"` | |
| C string `json:"c,omitempty"` | |
| } | |
| type mlkemEncapDecapTestGroupResponse struct { | |
| ID uint64 `json:"tgId"` | |
| Tests []mlkemEncapDecapTestResponse `json:"tests"` | |
| } | |
| type mlkemEncapDecapTestResponse struct { | |
| ID uint64 `json:"tcId"` | |
| C string `json:"c,omitempty"` | |
| K string `json:"k,omitempty"` | |
| } | |
| func decodeNonEmptyHex(in string) ([]byte, error) { | |
| ret, err := hex.DecodeString(in) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if len(ret) == 0 { | |
| return nil, errors.New("empty string") | |
| } | |
| return ret, nil | |
| } | |
| type mlkem struct{} | |
| func (m *mlkem) Process(vectorSet []byte, t Transactable) (any, error) { | |
| var common mlkemTestVectorSet | |
| if err := json.Unmarshal(vectorSet, &common); err != nil { | |
| return nil, fmt.Errorf("failed to unmarshal vector set: %v", err) | |
| } | |
| switch common.Mode { | |
| case "keyGen": | |
| return m.processKeyGen(vectorSet, t) | |
| case "encapDecap": | |
| return m.processEncapDecap(vectorSet, t) | |
| default: | |
| return nil, fmt.Errorf("unsupported ML-KEM mode: %q", common.Mode) | |
| } | |
| } | |
| func (m *mlkem) processKeyGen(vectorSet []byte, t Transactable) (any, error) { | |
| var parsed mlkemKeyGenTestVectorSet | |
| if err := json.Unmarshal(vectorSet, &parsed); err != nil { | |
| return nil, fmt.Errorf("failed to unmarshal keyGen vector set: %v", err) | |
| } | |
| var ret []mlkemKeyGenTestGroupResponse | |
| for _, group := range parsed.Groups { | |
| response := mlkemKeyGenTestGroupResponse{ | |
| ID: group.ID, | |
| } | |
| if !strings.HasPrefix(group.ParameterSet, "ML-KEM-") { | |
| return nil, fmt.Errorf("invalid parameter set: %s", group.ParameterSet) | |
| } | |
| cmdName := group.ParameterSet + "/keyGen" | |
| for _, test := range group.Tests { | |
| // Concatenate d and z to form the seed | |
| dBytes, err := hex.DecodeString(test.D) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to decode d in test case %d/%d: %s", | |
| group.ID, test.ID, err) | |
| } | |
| zBytes, err := hex.DecodeString(test.Z) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to decode z in test case %d/%d: %s", | |
| group.ID, test.ID, err) | |
| } | |
| seed := make([]byte, len(dBytes)+len(zBytes)) | |
| copy(seed, dBytes) | |
| copy(seed[len(dBytes):], zBytes) | |
| result, err := t.Transact(cmdName, 2, seed) | |
| if err != nil { | |
| return nil, fmt.Errorf("key generation failed for test case %d/%d: %s", | |
| group.ID, test.ID, err) | |
| } | |
| response.Tests = append(response.Tests, mlkemKeyGenTestResponse{ | |
| ID: test.ID, | |
| EK: hex.EncodeToString(result[0]), | |
| DK: hex.EncodeToString(result[1]), | |
| }) | |
| } | |
| ret = append(ret, response) | |
| } | |
| return ret, nil | |
| } | |
| func (m *mlkem) processEncapDecap(vectorSet []byte, t Transactable) (any, error) { | |
| var parsed mlkemEncapDecapTestVectorSet | |
| if err := json.Unmarshal(vectorSet, &parsed); err != nil { | |
| return nil, fmt.Errorf("failed to unmarshal encapDecap vector set: %v", err) | |
| } | |
| var ret []mlkemEncapDecapTestGroupResponse | |
| for _, group := range parsed.Groups { | |
| response := mlkemEncapDecapTestGroupResponse{ | |
| ID: group.ID, | |
| } | |
| if !strings.HasPrefix(group.ParameterSet, "ML-KEM-") { | |
| return nil, fmt.Errorf("invalid parameter set: %s", group.ParameterSet) | |
| } | |
| switch group.Function { | |
| case "encapsulation": | |
| cmdName := group.ParameterSet + "/encap" | |
| for _, test := range group.Tests { | |
| ek, err := hex.DecodeString(test.EK) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to decode ek in test case %d/%d: %s", | |
| group.ID, test.ID, err) | |
| } | |
| m, err := hex.DecodeString(test.M) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to decode m in test case %d/%d: %s", | |
| group.ID, test.ID, err) | |
| } | |
| result, err := t.Transact(cmdName, 2, ek, m) | |
| if err != nil { | |
| return nil, fmt.Errorf("encapsulation failed for test case %d/%d: %s", | |
| group.ID, test.ID, err) | |
| } | |
| response.Tests = append(response.Tests, mlkemEncapDecapTestResponse{ | |
| ID: test.ID, | |
| C: hex.EncodeToString(result[0]), | |
| K: hex.EncodeToString(result[1]), | |
| }) | |
| } | |
| case "decapsulation": | |
| cmdName := group.ParameterSet + "/decap" | |
| for _, test := range group.Tests { | |
| dk, err := decodeNonEmptyHex(test.DK) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to decode dk in test case %d/%d: %s", | |
| group.ID, test.ID, err) | |
| } | |
| c, err := decodeNonEmptyHex(test.C) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to decode c in test case %d/%d: %s", | |
| group.ID, test.ID, err) | |
| } | |
| result, err := t.Transact(cmdName, 1, dk, c) | |
| if err != nil { | |
| return nil, fmt.Errorf("decapsulation failed for test case %d/%d: %s", | |
| group.ID, test.ID, err) | |
| } | |
| response.Tests = append(response.Tests, mlkemEncapDecapTestResponse{ | |
| ID: test.ID, | |
| K: hex.EncodeToString(result[0]), | |
| }) | |
| } | |
| default: | |
| return nil, fmt.Errorf("unsupported function: %s", group.Function) | |
| } | |
| ret = append(ret, response) | |
| } | |
| return ret, nil | |
| } |