@@ -29,8 +29,6 @@ public class HSSPrivateKeyParameters
2929 private final long indexLimit ;
3030 private long index = 0 ;
3131
32- private HSSPublicKeyParameters publicKey ;
33-
3432 public HSSPrivateKeyParameters (LMSPrivateKeyParameters key , long index , long indexLimit )
3533 {
3634 super (true );
@@ -53,8 +51,8 @@ public HSSPrivateKeyParameters(int l, List<LMSPrivateKeyParameters> keys, List<L
5351 super (true );
5452
5553 this .l = l ;
56- this .keys = Collections .unmodifiableList (keys );
57- this .sig = Collections .unmodifiableList (sig );
54+ this .keys = Collections .unmodifiableList (new ArrayList < LMSPrivateKeyParameters >( keys ) );
55+ this .sig = Collections .unmodifiableList (new ArrayList < LMSSignature >( sig ) );
5856 this .index = index ;
5957 this .indexLimit = indexLimit ;
6058 this .isShard = false ;
@@ -70,6 +68,8 @@ private HSSPrivateKeyParameters(int l, List<LMSPrivateKeyParameters> keys, List<
7068 super (true );
7169
7270 this .l = l ;
71+ // No copy here, unlike the public constructor: the callers are extractKeyShard and
72+ // getInstance, which build fresh lists they do not retain.
7373 this .keys = Collections .unmodifiableList (keys );
7474 this .sig = Collections .unmodifiableList (sig );
7575 this .index = index ;
@@ -98,8 +98,6 @@ public static HSSPrivateKeyParameters getInstance(byte[] privEnc, byte[] pubEnc)
9898 throw new IOException ("HSS private key tree cache does not match the public key" );
9999 }
100100
101- pKey .publicKey = pubKey ;
102-
103101 return pKey ;
104102 }
105103
@@ -308,7 +306,7 @@ private static HSSPrivateKeyParameters makeCopy(HSSPrivateKeyParameters privateK
308306 }
309307 }
310308
311- protected void updateHierarchy (LMSPrivateKeyParameters [] newKeys , LMSSignature [] newSig )
309+ private void updateHierarchy (LMSPrivateKeyParameters [] newKeys , LMSSignature [] newSig )
312310 {
313311 synchronized (this )
314312 {
@@ -341,7 +339,7 @@ public long getUsagesRemaining()
341339
342340 LMSPrivateKeyParameters getRootKey ()
343341 {
344- return keys .get (0 );
342+ return getKeys () .get (0 );
345343 }
346344
347345 /**
@@ -396,11 +394,14 @@ synchronized List<LMSSignature> getSig()
396394
397395 /**
398396 * Reset to index will ensure that all LMS keys are correct for a given HSS index value.
399- * Normally LMS keys updated in sync with their parent HSS key but in cases of sharding
397+ * Normally LMS keys are updated in sync with their parent HSS key but in cases of sharding
400398 * the normal monotonic updating does not apply and the state of the LMS keys needs to be
401399 * reset to match the current HSS index.
400+ * <p>
401+ * Should only be called under the monitor (lock) or during construction before the instance escapes.
402+ * </p>
402403 */
403- void resetKeyToIndex ()
404+ private void resetKeyToIndex ()
404405 {
405406 // Extract the original keys
406407 List <LMSPrivateKeyParameters > originalKeys = getKeys ();
@@ -418,8 +419,8 @@ void resetKeyToIndex()
418419 }
419420
420421 boolean changed = false ;
421- LMSPrivateKeyParameters [] keys = originalKeys .toArray (new LMSPrivateKeyParameters [originalKeys .size ()]);// new LMSPrivateKeyParameters[originalKeys.size()];
422- LMSSignature [] sig = this .sig .toArray (new LMSSignature [this .sig .size ()]);// new LMSSignature[originalKeys.size() - 1];
422+ LMSPrivateKeyParameters [] keys = originalKeys .toArray (new LMSPrivateKeyParameters [originalKeys .size ()]);
423+ LMSSignature [] sig = this .sig .toArray (new LMSSignature [this .sig .size ()]);
423424
424425 LMSPrivateKeyParameters originalRootKey = this .getRootKey ();
425426
@@ -626,27 +627,40 @@ public boolean equals(Object o)
626627
627628 HSSPrivateKeyParameters that = (HSSPrivateKeyParameters )o ;
628629
629- if (l != that .l )
630- {
631- return false ;
632- }
633- if (isShard != that .isShard )
634- {
635- return false ;
636- }
637- if (indexLimit != that .indexLimit )
630+ if (this .l != that .l || this .isShard != that .isShard || this .indexLimit != that .indexLimit )
638631 {
639632 return false ;
640633 }
641- if (index != that .index )
634+
635+ //
636+ // index, keys and sig all move as consumed trees are replaced, and they move together -
637+ // replaceConsumedKey assigns keys and sig one after the other under this monitor - so read
638+ // each key's trio in one synchronized block to get a snapshot no unsynchronized reader
639+ // could tear. The lists are unmodifiable and replaced rather than mutated, so a captured
640+ // reference stays a coherent view after the lock drops. Neither monitor is held while the
641+ // other is taken, so a.equals(b) racing b.equals(a) cannot deadlock.
642+ //
643+ long thisIndex ;
644+ List <LMSPrivateKeyParameters > thisKeys ;
645+ List <LMSSignature > thisSig ;
646+ synchronized (this )
642647 {
643- return false ;
648+ thisIndex = this .index ;
649+ thisKeys = this .keys ;
650+ thisSig = this .sig ;
644651 }
645- if (!keys .equals (that .keys ))
652+
653+ long thatIndex ;
654+ List <LMSPrivateKeyParameters > thatKeys ;
655+ List <LMSSignature > thatSig ;
656+ synchronized (that )
646657 {
647- return false ;
658+ thatIndex = that .index ;
659+ thatKeys = that .keys ;
660+ thatSig = that .sig ;
648661 }
649- return sig .equals (that .sig );
662+
663+ return thisIndex == thatIndex && thisKeys .equals (thatKeys ) && thisSig .equals (thatSig );
650664 }
651665
652666 @ Override
@@ -684,7 +698,20 @@ public synchronized byte[] getEncoded()
684698 @ Override
685699 public int hashCode ()
686700 {
687- return getPublicKey ().hashCode ();
701+ //
702+ // Deliberately not getPublicKey().hashCode(): that reaches the root key's tree, which is
703+ // only built if the node cache does not already hold it - 2^h LM-OTS public keys from an
704+ // implicit call no caller expects to cost anything. The fields used here are the ones that
705+ // do not move as the key signs: the root key material is fixed (resetKeyToIndex only
706+ // repositions it, and LMSPrivateKeyParameters.hashCode is itself index-independent),
707+ // whereas index, keys and sig all change. Equal keys agree on all of these, so the
708+ // equals() contract holds.
709+ //
710+ int hc = l ;
711+ hc = 31 * hc + (isShard ? 1 : 0 );
712+ hc = 31 * hc + (int )(indexLimit ^ (indexLimit >>> 32 ));
713+ hc = 31 * hc + getRootKey ().hashCode ();
714+ return hc ;
688715 }
689716
690717 @ Override
0 commit comments