View Javadoc
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 org.bouncycastle.pqc.crypto.lms.LMOtsParameters;
21  import org.bouncycastle.pqc.crypto.lms.LMSParameters;
22  import org.bouncycastle.pqc.crypto.lms.LMSigParameters;
23  
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.Objects;
27  
28  /**
29   * LMS KeyTypes.
30   */
31  public class GordianLMSKeySpec {
32      /**
33       * The Separator.
34       */
35      private static final String SEP = "-";
36  
37      /**
38       * Invalid length error.
39       */
40      private static final String INVALID_LENGTH = "Invalid Length: ";
41  
42      /**
43       * The hash.
44       */
45      private final GordianLMSHash theHash;
46  
47      /**
48       * The width.
49       */
50      private final GordianLMSWidth theWidth;
51  
52      /**
53       * The height.
54       */
55      private final GordianLMSHeight theHeight;
56  
57      /**
58       * The length.
59       */
60      private final GordianLength theLength;
61  
62      /**
63       * The Parameters.
64       */
65      private final LMSParameters theParams;
66  
67      /**
68       * The Validity.
69       */
70      private final boolean isValid;
71  
72      /**
73       * The String name.
74       */
75      private String theName;
76  
77      /**
78       * Constructor.
79       *
80       * @param pHashType the hashType
81       * @param pHeight   the height
82       * @param pWidth    the width
83       * @param pLength   the length
84       */
85      public GordianLMSKeySpec(final GordianLMSHash pHashType,
86                               final GordianLMSHeight pHeight,
87                               final GordianLMSWidth pWidth,
88                               final GordianLength pLength) {
89          /* Store parameters */
90          theHash = pHashType;
91          theWidth = pWidth;
92          theHeight = pHeight;
93          theLength = pLength;
94  
95          /* Check validity */
96          isValid = checkValidity();
97  
98          /* Calculate parameters */
99          final LMSigParameters mySig = isValid ? theHeight.getSigParameter(theHash, theLength) : null;
100         final LMOtsParameters myOts = isValid ? theWidth.getOtsParameter(theHash, theLength) : null;
101         theParams = isValid ? new LMSParameters(mySig, myOts) : null;
102     }
103 
104     /**
105      * Obtain the hash.
106      *
107      * @return the hash
108      */
109     public GordianLMSHash getHash() {
110         return theHash;
111     }
112 
113     /**
114      * Obtain the width.
115      *
116      * @return the width
117      */
118     public GordianLMSHeight getHeight() {
119         return theHeight;
120     }
121 
122     /**
123      * Obtain the width.
124      *
125      * @return the width
126      */
127     public GordianLMSWidth getWidth() {
128         return theWidth;
129     }
130 
131     /**
132      * Obtain the legth.
133      *
134      * @return the width
135      */
136     public GordianLength getLength() {
137         return theLength;
138     }
139 
140     /**
141      * Obtain the parameters.
142      *
143      * @return the parameters
144      */
145     public LMSParameters getParameters() {
146         return theParams;
147     }
148 
149     /**
150      * Is the keySpec high (height ≥ 15)?
151      *
152      * @return true/false.
153      */
154     public boolean isHigh() {
155         return isValid && theHeight.isHigh();
156     }
157 
158     /**
159      * Is the keySpec valid?
160      *
161      * @return true/false.
162      */
163     public boolean isValid() {
164         return isValid;
165     }
166 
167     /**
168      * Check spec validity.
169      *
170      * @return valid true/false
171      */
172     private boolean checkValidity() {
173         if (theWidth == null || theHeight == null || theHash == null || theLength == null) {
174             return false;
175         }
176         switch (theLength) {
177             case LEN_192:
178             case LEN_256:
179                 return true;
180             default:
181                 return false;
182         }
183     }
184 
185     @Override
186     public String toString() {
187         /* If we have not yet loaded the name */
188         if (theName == null) {
189             /* If the keySpec is valid */
190             if (isValid) {
191                 /* Load the name */
192                 theName = theHash.toString() + SEP + theWidth.toString() + SEP + theHeight.toString() + SEP + theLength.toString();
193             } else {
194                 /* Report invalid spec */
195                 theName = "InvalidLMSKeySpec: " + theHash + SEP + theWidth + SEP + theHeight + SEP + theLength;
196             }
197         }
198 
199         /* return the name */
200         return theName;
201     }
202 
203     @Override
204     public boolean equals(final Object pThat) {
205         /* Handle the trivial cases */
206         if (this == pThat) {
207             return true;
208         }
209         if (pThat == null) {
210             return false;
211         }
212 
213         /* Check fields */
214         return pThat instanceof GordianLMSKeySpec myThat
215                 && theHash == myThat.theHash
216                 && theLength == myThat.theLength
217                 && theWidth == myThat.theWidth
218                 && theHeight == myThat.theHeight;
219     }
220 
221     @Override
222     public int hashCode() {
223         return Objects.hash(theHash, theHeight, theWidth, theLength);
224     }
225 
226     /**
227      * Obtain a list of all possible specs.
228      *
229      * @return the list
230      */
231     public static List<GordianLMSKeySpec> listPossibleKeySpecs() {
232         /* Create the list */
233         final List<GordianLMSKeySpec> mySpecs = new ArrayList<>();
234 
235         /* Add the specs */
236         for (final GordianLMSHeight myHeight : GordianLMSHeight.values()) {
237             for (final GordianLMSWidth myWidth : GordianLMSWidth.values()) {
238                 mySpecs.add(new GordianLMSKeySpec(GordianLMSHash.SHA256, myHeight, myWidth, GordianLength.LEN_256));
239                 mySpecs.add(new GordianLMSKeySpec(GordianLMSHash.SHA256, myHeight, myWidth, GordianLength.LEN_192));
240                 mySpecs.add(new GordianLMSKeySpec(GordianLMSHash.SHAKE256, myHeight, myWidth, GordianLength.LEN_256));
241                 mySpecs.add(new GordianLMSKeySpec(GordianLMSHash.SHAKE256, myHeight, myWidth, GordianLength.LEN_192));
242             }
243         }
244 
245         /* Return the list */
246         return mySpecs;
247     }
248 
249     /**
250      * Match keySpec against LMSParameters.
251      *
252      * @param pSigParams the sigParameters
253      * @param pOtsParams the otsParameters
254      * @return the matching keySpec
255      */
256     public static GordianLMSKeySpec determineKeySpec(final LMSigParameters pSigParams,
257                                                      final LMOtsParameters pOtsParams) {
258         final List<GordianLMSKeySpec> mySpecs = listPossibleKeySpecs();
259         for (GordianLMSKeySpec mySpec : mySpecs) {
260             if (pSigParams.equals(mySpec.getParameters().getLMSigParam())
261                     && pOtsParams.equals(mySpec.getParameters().getLMOTSParam())) {
262                 return mySpec;
263             }
264         }
265         throw new IllegalArgumentException("Unsupported LMSSpec");
266     }
267 
268     /**
269      * HSS keySpec.
270      */
271     public static class GordianHSSKeySpec {
272         /**
273          * Max depth for HSS key.
274          */
275         public static final int MAX_DEPTH = 8;
276 
277         /**
278          * The top level keySpec.
279          */
280         private final GordianLMSKeySpec theKeySpec;
281 
282         /**
283          * The tree depth.
284          */
285         private final int theDepth;
286 
287         /**
288          * The Validity.
289          */
290         private final boolean isValid;
291 
292         /**
293          * The String name.
294          */
295         private String theName;
296 
297         /**
298          * Constructor.
299          *
300          * @param pKeySpec the keySpecs
301          * @param pDepth   the tree depth
302          */
303         GordianHSSKeySpec(final GordianLMSKeySpec pKeySpec,
304                           final int pDepth) {
305             /* Create the list of keySpecs */
306             theKeySpec = pKeySpec;
307             theDepth = pDepth;
308             isValid = checkValidity();
309         }
310 
311         /**
312          * Obtain the parameters.
313          *
314          * @return the parameters.
315          */
316         public GordianLMSKeySpec getKeySpec() {
317             /* If we are valid */
318             return isValid
319                     ? theKeySpec
320                     : null;
321         }
322 
323         /**
324          * Obtain the treeDepth.
325          *
326          * @return the treeDepth.
327          */
328         public int getTreeDepth() {
329             /* If we are valid */
330             return isValid
331                     ? theDepth
332                     : -1;
333         }
334 
335         /**
336          * Is the keySpec valid?
337          *
338          * @return true/false.
339          */
340         public boolean isValid() {
341             return isValid;
342         }
343 
344         /**
345          * Check spec validity.
346          *
347          * @return valid true/false
348          */
349         private boolean checkValidity() {
350             /* Depth must be at least 1 and  no more that MAX */
351             if (theDepth < 1 || theDepth > MAX_DEPTH) {
352                 return false;
353             }
354 
355             /* Check keySpec */
356             return theKeySpec != null && theKeySpec.isValid;
357         }
358 
359         @Override
360         public String toString() {
361             /* If we have not yet loaded the name */
362             if (theName == null) {
363                 /* If the keySpec is valid */
364                 if (isValid) {
365                     /* Load the name */
366                     theName = "HSS-" + theDepth + "-" + theKeySpec;
367 
368                 } else {
369                     /* Report invalid spec */
370                     theName = "InvalidHSSKeySpec: " + theDepth + ":" + theKeySpec;
371                 }
372             }
373 
374             /* return the name */
375             return theName;
376         }
377 
378         @Override
379         public boolean equals(final Object pThat) {
380             /* Handle the trivial cases */
381             if (this == pThat) {
382                 return true;
383             }
384             if (pThat == null) {
385                 return false;
386             }
387 
388             /* Make sure that the object is hssSpec */
389             if (pThat.getClass() != this.getClass()) {
390                 return false;
391             }
392 
393             /* Access the target hssSpec */
394             final GordianHSSKeySpec myThat = (GordianHSSKeySpec) pThat;
395 
396             /* Check depth and keySpec are identical */
397             return theDepth == myThat.theDepth
398                     && Objects.equals(theKeySpec, myThat.theKeySpec);
399         }
400 
401         @Override
402         public int hashCode() {
403             return theKeySpec.hashCode() + theDepth;
404         }
405     }
406 
407     /**
408      * LMS hash.
409      */
410     public enum GordianLMSHash {
411         /**
412          * Sha256.
413          */
414         SHA256,
415 
416         /**
417          * Shake256.
418          */
419         SHAKE256;
420     }
421 
422     /**
423      * LMS height.
424      */
425     public enum GordianLMSHeight {
426         /**
427          * H5.
428          */
429         H5,
430 
431         /**
432          * H10.
433          */
434         H10,
435 
436         /**
437          * H15.
438          */
439         H15,
440 
441         /**
442          * H20.
443          */
444         H20,
445 
446         /**
447          * H25.
448          */
449         H25;
450 
451         /**
452          * Obtain the sigParameter.
453          *
454          * @param pHash   the hash
455          * @param pLength the length
456          * @return the parameter
457          */
458         private LMSigParameters getSigParameter(final GordianLMSHash pHash,
459                                                 final GordianLength pLength) {
460             switch (this) {
461                 case H5:
462                     return getH5Parameter(pHash, pLength);
463                 case H10:
464                     return getH10Parameter(pHash, pLength);
465                 case H15:
466                     return getH15Parameter(pHash, pLength);
467                 case H20:
468                     return getH20Parameter(pHash, pLength);
469                 case H25:
470                     return getH25Parameter(pHash, pLength);
471                 default:
472                     throw new IllegalStateException();
473             }
474         }
475 
476         /**
477          * Obtain the H5 sigParameter.
478          *
479          * @param pHash   the hash
480          * @param pLength the length
481          * @return the parameter
482          */
483         private LMSigParameters getH5Parameter(final GordianLMSHash pHash,
484                                                final GordianLength pLength) {
485             switch (pLength) {
486                 case LEN_192:
487                     return pHash == GordianLMSHash.SHA256 ? LMSigParameters.lms_sha256_n24_h5 : LMSigParameters.lms_shake256_n24_h5;
488                 case LEN_256:
489                     return pHash == GordianLMSHash.SHA256 ? LMSigParameters.lms_sha256_n32_h5 : LMSigParameters.lms_shake256_n32_h5;
490                 default:
491                     throw new IllegalArgumentException(INVALID_LENGTH + pLength);
492             }
493         }
494 
495         /**
496          * Obtain the H10 sigParameter.
497          *
498          * @param pHash   the hash
499          * @param pLength the length
500          * @return the parameter
501          */
502         private LMSigParameters getH10Parameter(final GordianLMSHash pHash,
503                                                 final GordianLength pLength) {
504             switch (pLength) {
505                 case LEN_192:
506                     return pHash == GordianLMSHash.SHA256 ? LMSigParameters.lms_sha256_n24_h10 : LMSigParameters.lms_shake256_n24_h10;
507                 case LEN_256:
508                     return pHash == GordianLMSHash.SHA256 ? LMSigParameters.lms_sha256_n32_h10 : LMSigParameters.lms_shake256_n32_h10;
509                 default:
510                     throw new IllegalArgumentException(INVALID_LENGTH + pLength);
511             }
512         }
513 
514         /**
515          * Obtain the H15 sigParameter.
516          *
517          * @param pHash   the hash
518          * @param pLength the length
519          * @return the parameter
520          */
521         private LMSigParameters getH15Parameter(final GordianLMSHash pHash,
522                                                 final GordianLength pLength) {
523             switch (pLength) {
524                 case LEN_192:
525                     return pHash == GordianLMSHash.SHA256 ? LMSigParameters.lms_sha256_n24_h15 : LMSigParameters.lms_shake256_n24_h15;
526                 case LEN_256:
527                     return pHash == GordianLMSHash.SHA256 ? LMSigParameters.lms_sha256_n32_h15 : LMSigParameters.lms_shake256_n32_h15;
528                 default:
529                     throw new IllegalArgumentException(INVALID_LENGTH + pLength);
530             }
531         }
532 
533         /**
534          * Obtain the H20 sigParameter.
535          *
536          * @param pHash   the hash
537          * @param pLength the length
538          * @return the parameter
539          */
540         private LMSigParameters getH20Parameter(final GordianLMSHash pHash,
541                                                 final GordianLength pLength) {
542             switch (pLength) {
543                 case LEN_192:
544                     return pHash == GordianLMSHash.SHA256 ? LMSigParameters.lms_sha256_n24_h20 : LMSigParameters.lms_shake256_n24_h20;
545                 case LEN_256:
546                     return pHash == GordianLMSHash.SHA256 ? LMSigParameters.lms_sha256_n32_h20 : LMSigParameters.lms_shake256_n32_h20;
547                 default:
548                     throw new IllegalArgumentException(INVALID_LENGTH + pLength);
549             }
550         }
551 
552         /**
553          * Obtain the H25 sigParameter.
554          *
555          * @param pHash   the hash
556          * @param pLength the length
557          * @return the parameter
558          */
559         private LMSigParameters getH25Parameter(final GordianLMSHash pHash,
560                                                 final GordianLength pLength) {
561             switch (pLength) {
562                 case LEN_192:
563                     return pHash == GordianLMSHash.SHA256 ? LMSigParameters.lms_sha256_n24_h25 : LMSigParameters.lms_shake256_n24_h25;
564                 case LEN_256:
565                     return pHash == GordianLMSHash.SHA256 ? LMSigParameters.lms_sha256_n32_h25 : LMSigParameters.lms_shake256_n32_h25;
566                 default:
567                     throw new IllegalArgumentException(INVALID_LENGTH + pLength);
568             }
569         }
570 
571         /**
572          * Is the parameter high (height &ge; 15)?
573          *
574          * @return true/false.
575          */
576         public boolean isHigh() {
577             switch (this) {
578                 case H15:
579                 case H20:
580                 case H25:
581                     return true;
582                 default:
583                     return false;
584             }
585         }
586     }
587 
588     /**
589      * LMS Width.
590      */
591     public enum GordianLMSWidth {
592         /**
593          * W1.
594          */
595         W1,
596 
597         /**
598          * W2.
599          */
600         W2,
601 
602         /**
603          * W4.
604          */
605         W4,
606 
607         /**
608          * W8.
609          */
610         W8;
611 
612         /**
613          * Obtain the otsParameter.
614          *
615          * @param pHash   the hash
616          * @param pLength the length
617          * @return the parameter
618          */
619         private LMOtsParameters getOtsParameter(final GordianLMSHash pHash,
620                                                 final GordianLength pLength) {
621             switch (this) {
622                 case W1:
623                     return getW1Parameter(pHash, pLength);
624                 case W2:
625                     return getW2Parameter(pHash, pLength);
626                 case W4:
627                     return getW4Parameter(pHash, pLength);
628                 case W8:
629                     return getW8Parameter(pHash, pLength);
630                 default:
631                     throw new IllegalStateException();
632             }
633         }
634 
635         /**
636          * Obtain the W1 otsParameter.
637          *
638          * @param pHash   the hash
639          * @param pLength the length
640          * @return the parameter
641          */
642         private LMOtsParameters getW1Parameter(final GordianLMSHash pHash,
643                                                final GordianLength pLength) {
644             switch (pLength) {
645                 case LEN_192:
646                     return pHash == GordianLMSHash.SHA256 ? LMOtsParameters.sha256_n24_w1 : LMOtsParameters.shake256_n24_w1;
647                 case LEN_256:
648                     return pHash == GordianLMSHash.SHA256 ? LMOtsParameters.sha256_n32_w1 : LMOtsParameters.shake256_n32_w1;
649                 default:
650                     throw new IllegalArgumentException(INVALID_LENGTH + pLength);
651             }
652         }
653 
654         /**
655          * Obtain the W2 otsParameter.
656          *
657          * @param pHash   the hash
658          * @param pLength the length
659          * @return the parameter
660          */
661         private LMOtsParameters getW2Parameter(final GordianLMSHash pHash,
662                                                final GordianLength pLength) {
663             switch (pLength) {
664                 case LEN_192:
665                     return pHash == GordianLMSHash.SHA256 ? LMOtsParameters.sha256_n24_w2 : LMOtsParameters.shake256_n24_w2;
666                 case LEN_256:
667                     return pHash == GordianLMSHash.SHA256 ? LMOtsParameters.sha256_n32_w2 : LMOtsParameters.shake256_n32_w2;
668                 default:
669                     throw new IllegalArgumentException(INVALID_LENGTH + pLength);
670             }
671         }
672 
673         /**
674          * Obtain the W4 otsParameter.
675          *
676          * @param pHash   the hash
677          * @param pLength the length
678          * @return the parameter
679          */
680         private LMOtsParameters getW4Parameter(final GordianLMSHash pHash,
681                                                final GordianLength pLength) {
682             switch (pLength) {
683                 case LEN_192:
684                     return pHash == GordianLMSHash.SHA256 ? LMOtsParameters.sha256_n24_w4 : LMOtsParameters.shake256_n24_w4;
685                 case LEN_256:
686                     return pHash == GordianLMSHash.SHA256 ? LMOtsParameters.sha256_n32_w4 : LMOtsParameters.shake256_n32_w4;
687                 default:
688                     throw new IllegalArgumentException(INVALID_LENGTH + pLength);
689             }
690         }
691 
692         /**
693          * Obtain the W8 otsParameter.
694          *
695          * @param pHash   the hash
696          * @param pLength the length
697          * @return the parameter
698          */
699         private LMOtsParameters getW8Parameter(final GordianLMSHash pHash,
700                                                final GordianLength pLength) {
701             switch (pLength) {
702                 case LEN_192:
703                     return pHash == GordianLMSHash.SHA256 ? LMOtsParameters.sha256_n24_w8 : LMOtsParameters.shake256_n24_w8;
704                 case LEN_256:
705                     return pHash == GordianLMSHash.SHA256 ? LMOtsParameters.sha256_n32_w8 : LMOtsParameters.shake256_n32_w8;
706                 default:
707                     throw new IllegalArgumentException(INVALID_LENGTH + pLength);
708             }
709         }
710     }
711 }