1 /*
2 * GordianKnot: Security Suite
3 * Copyright 2012-2026. Tony Washer
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6 * use this file except in compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 * License for the specific language governing permissions and limitations under
15 * the License.
16 */
17 package io.github.tonywasher.joceanus.gordianknot.api.keypair;
18
19 import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
20 import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestSpec;
21 import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestSpecBuilder;
22
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.Objects;
26
27 /**
28 * XMSS KeySpec.
29 */
30 public class GordianXMSSKeySpec {
31 /**
32 * The Separator.
33 */
34 private static final String SEP = "-";
35
36 /**
37 * The key type.
38 */
39 private final GordianXMSSKeyType theKeyType;
40
41 /**
42 * The digest type.
43 */
44 private final GordianXMSSDigestType theDigestType;
45
46 /**
47 * The height.
48 */
49 private final GordianXMSSHeight theHeight;
50
51 /**
52 * The layers.
53 */
54 private final GordianXMSSMTLayers theLayers;
55
56 /**
57 * The Validity.
58 */
59 private final boolean isValid;
60
61 /**
62 * The String name.
63 */
64 private String theName;
65
66 /**
67 * Constructor.
68 *
69 * @param pDigestType the digestType
70 * @param pHeight the height
71 */
72 public GordianXMSSKeySpec(final GordianXMSSDigestType pDigestType,
73 final GordianXMSSHeight pHeight) {
74 this(GordianXMSSKeyType.XMSS, pDigestType, pHeight, null);
75 }
76
77 /**
78 * Constructor.
79 *
80 * @param pDigestType the digestType
81 * @param pHeight the height
82 * @param pLayers the layers
83 */
84 public GordianXMSSKeySpec(final GordianXMSSDigestType pDigestType,
85 final GordianXMSSHeight pHeight,
86 final GordianXMSSMTLayers pLayers) {
87 this(GordianXMSSKeyType.XMSSMT, pDigestType, pHeight, pLayers);
88 }
89
90 /**
91 * Constructor.
92 *
93 * @param pKeyType the keyType
94 * @param pDigestType the digestType
95 * @param pHeight the height
96 * @param pLayers the layers
97 */
98 public GordianXMSSKeySpec(final GordianXMSSKeyType pKeyType,
99 final GordianXMSSDigestType pDigestType,
100 final GordianXMSSHeight pHeight,
101 final GordianXMSSMTLayers pLayers) {
102 theKeyType = pKeyType;
103 theDigestType = pDigestType;
104 theHeight = pHeight;
105 theLayers = pLayers;
106 isValid = checkValidity();
107 }
108
109 /**
110 * Is this key MT?
111 *
112 * @return true/false
113 */
114 public boolean isMT() {
115 return theKeyType.isMT();
116 }
117
118 /**
119 * Obtain the keyType.
120 *
121 * @return the keyType
122 */
123 public GordianXMSSKeyType getKeyType() {
124 return theKeyType;
125 }
126
127 /**
128 * Obtain the digestType.
129 *
130 * @return the digestType
131 */
132 public GordianXMSSDigestType getDigestType() {
133 return theDigestType;
134 }
135
136 /**
137 * Obtain the height.
138 *
139 * @return the height
140 */
141 public GordianXMSSHeight getHeight() {
142 return theHeight;
143 }
144
145 /**
146 * Obtain the layers.
147 *
148 * @return the layers
149 */
150 public GordianXMSSMTLayers getLayers() {
151 return theLayers;
152 }
153
154 /**
155 * Is the keySpec valid?
156 *
157 * @return true/false.
158 */
159 public boolean isValid() {
160 return isValid;
161 }
162
163 /**
164 * Is the keySpec high (height > 15)?
165 *
166 * @return true/false.
167 */
168 public boolean isHigh() {
169 return isValid && theHeight.isHigh(theKeyType);
170 }
171
172 /**
173 * Create XMSS keySpec.
174 *
175 * @param pDigestType the digestType
176 * @param pHeight the height
177 * @return the keySpec
178 */
179 public static GordianXMSSKeySpec xmss(final GordianXMSSDigestType pDigestType,
180 final GordianXMSSHeight pHeight) {
181 return new GordianXMSSKeySpec(pDigestType, pHeight);
182 }
183
184 /**
185 * Create XMSS keySpec.
186 *
187 * @param pDigestType the digestType
188 * @param pHeight the height
189 * @param pLayers the layers
190 * @return the keySpec
191 */
192 public static GordianXMSSKeySpec xmssmt(final GordianXMSSDigestType pDigestType,
193 final GordianXMSSHeight pHeight,
194 final GordianXMSSMTLayers pLayers) {
195 return new GordianXMSSKeySpec(pDigestType, pHeight, pLayers);
196 }
197
198 /**
199 * Check spec validity.
200 *
201 * @return valid true/false
202 */
203 private boolean checkValidity() {
204 /* Check that required elements are present */
205 if (theKeyType == null || theDigestType == null || theHeight == null) {
206 return false;
207 }
208
209 /* Check that the height is valid for the keyType */
210 if (!theHeight.validForKeyType(theKeyType)) {
211 return false;
212 }
213
214 /* Check layers is valid for keyType/height */
215 return theKeyType == GordianXMSSKeyType.XMSS
216 ? theLayers == null
217 : theLayers != null && theHeight.hasValidLayers(theLayers);
218 }
219
220 @Override
221 public String toString() {
222 /* If we have not yet loaded the name */
223 if (theName == null) {
224 /* If the keySpec is valid */
225 if (isValid) {
226 /* Load the name */
227 theName = theKeyType.toString()
228 + SEP + theDigestType.toString()
229 + SEP + theHeight.toString();
230 if (isMT()) {
231 theName += SEP + theLayers.toString();
232 }
233
234 } else {
235 /* Report invalid spec */
236 theName = "InvalidXMSSKeySpec: " + theKeyType + ":" + theDigestType
237 + ":" + theHeight + ":" + theLayers;
238 }
239 }
240
241 /* return the name */
242 return theName;
243 }
244
245 @Override
246 public boolean equals(final Object pThat) {
247 /* Handle the trivial cases */
248 if (this == pThat) {
249 return true;
250 }
251 if (pThat == null) {
252 return false;
253 }
254
255 /* Check KeyType, digestType, height and layers */
256 return pThat instanceof GordianXMSSKeySpec myThat
257 && theKeyType == myThat.getKeyType()
258 && theDigestType == myThat.getDigestType()
259 && theHeight == myThat.getHeight()
260 && theLayers == myThat.getLayers();
261 }
262
263 @Override
264 public int hashCode() {
265 return Objects.hash(theKeyType, theDigestType, theHeight, theLayers);
266 }
267
268 /**
269 * Obtain a list of all possible specs.
270 *
271 * @return the list
272 */
273 public static List<GordianXMSSKeySpec> listPossibleKeySpecs() {
274 /* Create the list */
275 final List<GordianXMSSKeySpec> mySpecs = new ArrayList<>();
276
277 /* Add the specs */
278 for (final GordianXMSSDigestType myType : GordianXMSSDigestType.values()) {
279 mySpecs.addAll(listPossibleKeySpecs(myType));
280 }
281
282 /* Return the list */
283 return mySpecs;
284 }
285
286 /**
287 * Obtain a list of all possible specs.
288 *
289 * @param pDigestType the digestType
290 * @return the list
291 */
292 public static List<GordianXMSSKeySpec> listPossibleKeySpecs(final GordianXMSSDigestType pDigestType) {
293 /* Create the list */
294 final List<GordianXMSSKeySpec> mySpecs = new ArrayList<>();
295
296 /* For all heights */
297 for (final GordianXMSSHeight myHeight : GordianXMSSHeight.values()) {
298 /* Add XMSS Spec if valid */
299 if (myHeight.validForKeyType(GordianXMSSKeyType.XMSS)) {
300 mySpecs.add(xmss(pDigestType, myHeight));
301 }
302
303 /* Add XMSSMT Specs if valid */
304 if (myHeight.validForKeyType(GordianXMSSKeyType.XMSSMT)) {
305 /* For all heights */
306 for (final GordianXMSSMTLayers myLayers : myHeight.getValidLayers()) {
307 mySpecs.add(xmssmt(pDigestType, myHeight, myLayers));
308 }
309 }
310 }
311
312 /* Return the list */
313 return mySpecs;
314 }
315
316 /**
317 * XMSS keyTypes.
318 */
319 public enum GordianXMSSKeyType {
320 /**
321 * XMSS.
322 */
323 XMSS,
324
325 /**
326 * XMSSMT.
327 */
328 XMSSMT;
329
330 /**
331 * Is this key MT?
332 *
333 * @return true/false
334 */
335 public boolean isMT() {
336 return this == XMSSMT;
337 }
338
339 @Override
340 public String toString() {
341 return this == XMSSMT ? "XMSS^MT" : "XMSS";
342 }
343 }
344
345 /**
346 * XMSS digestType.
347 */
348 public enum GordianXMSSDigestType {
349 /**
350 * SHA256.
351 */
352 SHA256,
353
354 /**
355 * SHA512.
356 */
357 SHA512,
358
359 /**
360 * SHAKE128.
361 */
362 SHAKE128,
363
364 /**
365 * SHAKE256.
366 */
367 SHAKE256;
368
369 /**
370 * Obtain the required digestSpec.
371 *
372 * @return the digestSpec
373 */
374 public GordianDigestSpec getDigestSpec() {
375 switch (this) {
376 case SHA256:
377 return GordianDigestSpecBuilder.sha2(GordianLength.LEN_256);
378 case SHA512:
379 return GordianDigestSpecBuilder.sha2(GordianLength.LEN_512);
380 case SHAKE128:
381 return GordianDigestSpecBuilder.shake128();
382 case SHAKE256:
383 return GordianDigestSpecBuilder.shake256();
384 default:
385 throw new IllegalStateException();
386 }
387 }
388 }
389
390 /**
391 * XMSS Height.
392 */
393 public enum GordianXMSSHeight {
394 /**
395 * 10.
396 */
397 H10(10),
398
399 /**
400 * 16.
401 */
402 H16(16),
403
404 /**
405 * 20.
406 */
407 H20(20, new GordianXMSSMTLayers[]{GordianXMSSMTLayers.L2, GordianXMSSMTLayers.L4}),
408
409 /**
410 * 40.
411 */
412 H40(40, new GordianXMSSMTLayers[]{GordianXMSSMTLayers.L2, GordianXMSSMTLayers.L4, GordianXMSSMTLayers.L8}),
413
414 /**
415 * 12.
416 */
417 H60(12, new GordianXMSSMTLayers[]{GordianXMSSMTLayers.L3, GordianXMSSMTLayers.L6, GordianXMSSMTLayers.L12});
418
419 /**
420 * The Height.
421 */
422 private final int theHeight;
423
424 /**
425 * The Layers.
426 */
427 private final GordianXMSSMTLayers[] theLayers;
428
429 /**
430 * Constructor.
431 *
432 * @param pHeight the height
433 */
434 GordianXMSSHeight(final int pHeight) {
435 this(pHeight, null);
436 }
437
438 /**
439 * Constructor.
440 *
441 * @param pHeight the height
442 * @param pLayers the layers
443 */
444 GordianXMSSHeight(final int pHeight,
445 final GordianXMSSMTLayers[] pLayers) {
446 theHeight = pHeight;
447 theLayers = pLayers;
448 }
449
450 /**
451 * Obtain the height.
452 *
453 * @return the height
454 */
455 public int getHeight() {
456 return theHeight;
457 }
458
459
460 /**
461 * Is the height valid for the keyType.
462 *
463 * @param pKeyType the keyType
464 * @return true/false
465 */
466 boolean validForKeyType(final GordianXMSSKeyType pKeyType) {
467 switch (this) {
468 case H10:
469 case H16:
470 return pKeyType == GordianXMSSKeyType.XMSS;
471 case H40:
472 case H60:
473 return pKeyType == GordianXMSSKeyType.XMSSMT;
474 default:
475 return true;
476 }
477 }
478
479 /**
480 * Is the layers valid for the height.
481 *
482 * @param pLayers the layers
483 * @return true/false
484 */
485 boolean hasValidLayers(final GordianXMSSMTLayers pLayers) {
486 if (theLayers != null) {
487 for (GordianXMSSMTLayers myLayers : theLayers) {
488 if (myLayers == pLayers) {
489 return true;
490 }
491 }
492 }
493 return false;
494 }
495
496 /**
497 * Obtain the valid XMSSMT layers.
498 *
499 * @return the height
500 */
501 GordianXMSSMTLayers[] getValidLayers() {
502 return theLayers;
503 }
504
505 /**
506 * Is the parameter high?
507 *
508 * @param pKeyType the keyTypoe
509 * @return true/false.
510 */
511 public boolean isHigh(final GordianXMSSKeyType pKeyType) {
512 switch (this) {
513 case H16:
514 case H20:
515 return pKeyType == GordianXMSSKeyType.XMSS;
516 case H40:
517 case H60:
518 return true;
519 default:
520 return false;
521 }
522 }
523 }
524
525 /**
526 * XMSSMT Layers.
527 */
528 public enum GordianXMSSMTLayers {
529 /**
530 * 2.
531 */
532 L2(2),
533
534 /**
535 * 3.
536 */
537 L3(3),
538
539 /**
540 * 4.
541 */
542 L4(4),
543
544 /**
545 * 6.
546 */
547 L6(6),
548
549 /**
550 * 40.
551 */
552 L8(8),
553
554 /**
555 * 12.
556 */
557 L12(12);
558
559 /**
560 * The layers.
561 */
562 private final int theLayers;
563
564 /**
565 * Constructor.
566 *
567 * @param pLayers the layers
568 */
569 GordianXMSSMTLayers(final int pLayers) {
570 theLayers = pLayers;
571 }
572
573 /**
574 * Obtain the layers.
575 *
576 * @return the layers
577 */
578 public int getLayers() {
579 return theLayers;
580 }
581 }
582 }