@@ -46,7 +46,7 @@ public class LMSPrivateKeyParameters
4646 // Two tiers of Merkle tree nodes are kept, neither of them secret: every node is published in some
4747 // signature or recomputed by every verifier.
4848 //
49- // tCache holds nodes 1 .. maxCacheR - 1 (at most 63, about 2 KB), computed on demand and kept for the
49+ // tCache holds nodes 1 .. tCache.length - 1 (at most 63, about 2 KB), computed on demand and kept for the
5050 // life of the key. It is the tier the encoding persists, so a decoded key resumes with it. Caching
5151 // deeper nodes buys nothing that lasts: each leaf is consumed once by its parent, and an unbounded
5252 // cache of every node reaches 2 GB at h = 25. (The WeakHashMap this replaced keyed the deeper nodes
@@ -66,7 +66,6 @@ public class LMSPrivateKeyParameters
6666 // modified or wiped.
6767 //
6868 private final byte [][] tCache ;
69- private final int maxCacheR ;
7069 private RetainedPath retained ;
7170
7271 /**
@@ -127,7 +126,18 @@ public static LMSPrivateKeyParameters generate(LMSParameters lmsParameters, Secu
127126 */
128127 static LMSPrivateKeyParameters createPlaceholder (LMSParameters lmsParameters , int maxQ )
129128 {
130- return new PlaceholderLMSPrivateKey (lmsParameters , maxQ );
129+ return new LMSPrivateKeyParameters (lmsParameters , maxQ );
130+ }
131+
132+ /**
133+ * The node cache for a tree of the given height: room for nodes 1 .. length - 1, where length is
134+ * the whole tree (2^(h + 1) nodes) or CACHE_TOP_LIMIT, whichever is smaller.
135+ */
136+ private static byte [][] createCache (LMSigParameters sigParameters )
137+ {
138+ int length = Math .min (CACHE_TOP_LIMIT , 1 << (sigParameters .getH () + 1 ));
139+
140+ return new byte [length ][];
131141 }
132142
133143 /**
@@ -174,16 +184,15 @@ public LMSPrivateKeyParameters(LMSigParameters sigParameters, LMOtsParameters ot
174184 this .I = I ;
175185 this .maxQ = maxQ ;
176186 this .masterSecret = masterSecret ;
177- this .maxCacheR = Math .min (CACHE_TOP_LIMIT , 1 << (sigParameters .getH () + 1 ));
178- this .tCache = new byte [maxCacheR ][];
187+ this .tCache = createCache (sigParameters );
179188 }
180189
181190 /**
182191 * A key with no position, identifier or seed of its own - the placeholder an HSS hierarchy is
183192 * built with for the levels below the root, each of which resetKeyToIndex replaces from the
184193 * level above before the key is used. The sentinel values are deliberately ones the public
185194 * constructor refuses, so a placeholder can never be mistaken for a key that was merely built
186- * carelessly; a subclass using this must not present the result as a usable key .
195+ * carelessly, and the key itself refuses to sign, publish, encode or derive from itself .
187196 *
188197 * @deprecated This class is not intended to be subclassed; the constructor will be removed.
189198 */
@@ -204,27 +213,26 @@ private LMSPrivateKeyParameters(LMSParameters lmsParameters, int maxQ)
204213 this .I = new byte [0 ];
205214 this .maxQ = maxQ ;
206215 this .masterSecret = new byte [0 ];
207- this .maxCacheR = Math .min (CACHE_TOP_LIMIT , 1 << (lmsParameters .getLMSigParam ().getH () + 1 ));
208- this .tCache = new byte [maxCacheR ][];
209- }
210-
211- private LMSPrivateKeyParameters (LMSPrivateKeyParameters parent , int q , int maxQ )
212- {
213- this (parent , q , maxQ , Math .min (CACHE_TOP_LIMIT , 1 << parent .lmsParameters .getLMSigParam ().getH ()));
216+ // No tree to cache: resetKeyToIndex replaces a placeholder before anything reaches its nodes. The
217+ // absent cache is also what marks the key as a placeholder (checkNotPlaceholder), so one that leaked
218+ // past the reset fails at its first use rather than encoding a bogus key.
219+ this .tCache = null ;
214220 }
215221
216222 // Called under the parent's lock (extractKeyShard, repositionTo), which is what makes reading its
217- // retained path safe. I, masterSecret and tCache are shared by reference with the parent; the retained
218- // path too, but it is immutable and holds no secrets.
219- private LMSPrivateKeyParameters (LMSPrivateKeyParameters parent , int q , int maxQ , int maxCacheR )
223+ // retained path and destroyed flag safe. I, masterSecret and tCache are shared by reference with the
224+ // parent; the retained path too, but it is immutable and holds no secrets.
225+ private LMSPrivateKeyParameters (LMSPrivateKeyParameters parent , int q , int maxQ )
220226 {
221227 super (true );
222228 this .lmsParameters = parent .lmsParameters ;
223229 this .q = q ;
224230 this .I = parent .I ;
225231 this .maxQ = maxQ ;
226232 this .masterSecret = parent .masterSecret ;
227- this .maxCacheR = maxCacheR ;
233+ // the flag travels with the array it describes: a copy of a destroyed key must not present the
234+ // zeroed seed as a live one
235+ this .destroyed = parent .destroyed ;
228236 this .tCache = parent .tCache ;
229237 this .retained = parent .retained ;
230238 this .publicKey = parent .publicKey ;
@@ -254,7 +262,7 @@ synchronized LMSPrivateKeyParameters repositionTo(int q)
254262 "LMS private key q out of range: q=" + q + " 2^h=" + twoToH );
255263 }
256264
257- return new LMSPrivateKeyParameters (this , q , twoToH , maxCacheR );
265+ return new LMSPrivateKeyParameters (this , q , twoToH );
258266 }
259267
260268 public static LMSPrivateKeyParameters getInstance (byte [] privEnc , byte [] pubEnc )
@@ -519,7 +527,7 @@ byte[][] deriveChildKey()
519527 {
520528 synchronized (this )
521529 {
522- checkDestroyed ();
530+ checkUsable ();
523531
524532 if (q >= maxQ )
525533 {
@@ -548,7 +556,7 @@ byte[][] deriveChildKey(int q)
548556
549557 synchronized (this )
550558 {
551- checkDestroyed ();
559+ checkUsable ();
552560
553561 return LMSEngine .deriveChildKey (lmsParameters .getLMOTSParam (), I , masterSecret , q );
554562 }
@@ -563,6 +571,7 @@ boolean hasIdentity(byte[] I, byte[] masterSecret)
563571 {
564572 synchronized (this )
565573 {
574+ // not checkUsable: resetKeyToIndex asks a placeholder this, and its empty I answers no
566575 checkDestroyed ();
567576
568577 return Arrays .areEqual (this .I , I ) && Arrays .constantTimeAreEqual (this .masterSecret , masterSecret );
@@ -586,10 +595,6 @@ synchronized void incIndex()
586595
587596 public LMSContext generateLMSContext ()
588597 {
589- // Step 1.
590- LMSigParameters sigParameters = this .getSigParameters ();
591-
592- // Step 2
593598 int q ;
594599 byte [][] path ;
595600
@@ -600,7 +605,7 @@ public LMSContext generateLMSContext()
600605 //
601606 synchronized (this )
602607 {
603- checkDestroyed ();
608+ checkUsable ();
604609
605610 if (this .q >= maxQ )
606611 {
@@ -610,7 +615,7 @@ public LMSContext generateLMSContext()
610615 path = advanceRetainedPath (q );
611616 }
612617
613- return LMSEngine .generateSignContext (sigParameters , lmsParameters . getLMOTSParam (), I , q , masterSecret , path );
618+ return LMSEngine .generateSignContext (getSigParameters (), getOtsParameters (), I , q , masterSecret , path );
614619 }
615620
616621 public byte [] generateSignature (LMSContext context )
@@ -691,7 +696,7 @@ public byte[] getMasterSecret()
691696
692697 // clone first, check second: a destroy() that lands in between has set the flag before
693698 // it clears the array, so a stale copy is never handed out.
694- checkDestroyed ();
699+ checkUsable ();
695700
696701 return rv ;
697702 }
@@ -722,6 +727,16 @@ public boolean isDestroyed()
722727 return destroyed ;
723728 }
724729
730+ /**
731+ * Refuses a key that cannot act as one: a destroyed key, or the placeholder an HSS hierarchy holds for
732+ * a level not yet built (marked by having no node cache).
733+ */
734+ private void checkUsable ()
735+ {
736+ checkDestroyed ();
737+ checkNotPlaceholder ();
738+ }
739+
725740 private void checkDestroyed ()
726741 {
727742 if (destroyed )
@@ -730,6 +745,14 @@ private void checkDestroyed()
730745 }
731746 }
732747
748+ private void checkNotPlaceholder ()
749+ {
750+ if (tCache == null )
751+ {
752+ throw new IllegalStateException ("placeholder only" );
753+ }
754+ }
755+
733756 public int getIndexLimit ()
734757 {
735758 return maxQ ;
@@ -757,6 +780,9 @@ public LMSPublicKeyParameters getPublicKey()
757780 LMSPublicKeyParameters pk = publicKey ;
758781 if (pk == null )
759782 {
783+ // not checkUsable: a destroyed key still publishes its root where it is cached
784+ checkNotPlaceholder ();
785+
760786 retainFirstPath ();
761787
762788 // Tree nodes and I are immutable once published, so the public key shares them rather than copying.
@@ -813,7 +839,7 @@ synchronized boolean isPathRetained()
813839
814840 byte [] findT (int r )
815841 {
816- if (r >= maxCacheR )
842+ if (r >= tCache . length )
817843 {
818844 return calcT (r );
819845 }
@@ -906,7 +932,7 @@ private byte[][] advanceRetainedPath(int q)
906932 for (int i = 0 ; i < fresh ; ++i )
907933 {
908934 int node = r >> i ;
909- if (node < maxCacheR && tCache [node ] == null )
935+ if (node < tCache . length && tCache [node ] == null )
910936 {
911937 tCache [node ] = anc [i ];
912938 }
@@ -945,9 +971,10 @@ private byte[] calcT(int r)
945971 // These can be pre generated at the time of key generation and held within the private key.
946972 // However it will cost memory to have them stick around.
947973 //
948- checkDestroyed ();
949974
950- return LMSEngine .computeLeaf (tDigest , lmsParameters .getLMOTSParam (), I , r , r - twoToh , masterSecret );
975+ checkUsable ();
976+
977+ return LMSEngine .computeLeaf (tDigest , getOtsParameters (), I , r , r - twoToh , masterSecret );
951978 }
952979
953980 byte [] t2r = findT (2 * r );
@@ -967,7 +994,9 @@ void primeTreeCache(byte[][] cachedT)
967994 {
968995 synchronized (tCache )
969996 {
970- for (int r = 1 ; r < cachedT .length && r < tCache .length ; r ++)
997+ int limit = Math .min (cachedT .length , tCache .length );
998+
999+ for (int r = 1 ; r < limit ; r ++)
9711000 {
9721001 if (cachedT [r ] != null )
9731002 {
@@ -1048,7 +1077,7 @@ public int hashCode()
10481077 public byte [] getEncoded ()
10491078 throws IOException
10501079 {
1051- checkDestroyed ();
1080+ checkUsable ();
10521081
10531082 int q = getIndex ();
10541083
@@ -1080,7 +1109,7 @@ public byte[] getEncoded()
10801109
10811110 // The whole of the in-memory cache is eligible, so a decoded key resumes with the cache it was
10821111 // encoded with; findT computes any node not yet there.
1083- int cacheTop = maxCacheR ;
1112+ int cacheTop = tCache . length ;
10841113
10851114 ByteArrayOutputStream bOut = new ByteArrayOutputStream ();
10861115
@@ -1101,23 +1130,4 @@ public byte[] getEncoded()
11011130
11021131 return bOut .toByteArray ();
11031132 }
1104-
1105- private static class PlaceholderLMSPrivateKey
1106- extends LMSPrivateKeyParameters
1107- {
1108- PlaceholderLMSPrivateKey (LMSParameters lmsParameters , int maxQ )
1109- {
1110- super (lmsParameters , maxQ );
1111- }
1112-
1113- public LMSContext generateLMSContext ()
1114- {
1115- throw new RuntimeException ("placeholder only" );
1116- }
1117-
1118- public LMSPublicKeyParameters getPublicKey ()
1119- {
1120- throw new RuntimeException ("placeholder only" );
1121- }
1122- }
11231133}
0 commit comments