@@ -1411,11 +1411,12 @@ public void testSingleLevelWrapKeepsTheKey()
14111411 assertSame ("the wrap regenerated an advanced root key" , lms , advanced .getRootKey ());
14121412 assertEquals ("the wrap moved the index" , 3 , advanced .getIndex ());
14131413
1414- // the reset itself still works: asked for a different position, it does reposition
1415- HSSPrivateKeyParameters moved = new HSSPrivateKeyParameters (lms , 1 , 1 << sigParams .getH ());
1414+ // the reset itself still works: asked for a different (later - see
1415+ // testResetKeyToIndexRefusesToRewind) position, it does reposition
1416+ HSSPrivateKeyParameters moved = new HSSPrivateKeyParameters (lms , 4 , 1 << sigParams .getH ());
14161417
14171418 assertNotSame ("the reset failed to reposition to a different index" , lms , moved .getRootKey ());
1418- assertEquals (1 , moved .getRootKey ().getIndex ());
1419+ assertEquals (4 , moved .getRootKey ().getIndex ());
14191420
14201421 // the Merkle tree is a function of I, the seed and the parameters and not of q, so the
14211422 // repositioned key is entitled to the tree it was built from rather than a rebuild costing
@@ -1439,6 +1440,87 @@ public void testSingleLevelWrapKeepsTheKey()
14391440 assertTrue ("wrapped key produced a signature that does not verify" , verifier .verifySignature (msg , sig ));
14401441 }
14411442
1443+ /**
1444+ * A component key whose identifier and seed are unchanged is the same tree; an index that would
1445+ * move it back within that tree asks for one-time keys already used, and is refused. Forward
1446+ * moves still reposition.
1447+ */
1448+ public void testResetKeyToIndexRefusesToRewind ()
1449+ throws Exception
1450+ {
1451+ LMSigParameters sigParams = LMSigParameters .lms_sha256_n32_h5 ;
1452+ LMOtsParameters otsParams = LMOtsParameters .sha256_n32_w2 ;
1453+ int twoToH = 1 << sigParams .getH ();
1454+ byte [] msg = Hex .decode ("48656c6c6f" );
1455+
1456+ // single level: the root is the last level and reads its q directly
1457+ LMSKeyPairGenerator gen = new LMSKeyPairGenerator ();
1458+ gen .init (new LMSKeyGenerationParameters (LMSParameters .create (sigParams , otsParams ), new SecureRandom ()));
1459+ LMSPrivateKeyParameters lms = (LMSPrivateKeyParameters )gen .generateKeyPair ().getPrivate ();
1460+ LMSSigner lmsSigner = new LMSSigner ();
1461+ lmsSigner .init (true , lms );
1462+ for (int i = 0 ; i < 3 ; ++i )
1463+ {
1464+ lmsSigner .generateSignature (msg );
1465+ }
1466+ assertEquals (3 , lms .getIndex ());
1467+
1468+ expectRewindRefused (lms , 2 , twoToH );
1469+ assertEquals (3 , new HSSPrivateKeyParameters (lms , 3 , twoToH ).getIndex ());
1470+ assertEquals (4 , new HSSPrivateKeyParameters (lms , 4 , twoToH ).getKeys ().get (0 ).getIndex ());
1471+
1472+ // two levels: the root is post-incremented past the child it signed, the bottom reads its q directly
1473+ HSSPrivateKeyParameters hss = HSSPrivateKeyParameters .generate (new HSSKeyGenerationParameters (
1474+ new LMSParameters []{ LMSParameters .create (sigParams , otsParams ), LMSParameters .create (sigParams , otsParams ) },
1475+ new SecureRandom ()));
1476+ for (int i = 0 ; i < twoToH + 1 ; ++i )
1477+ {
1478+ sign (hss , msg );
1479+ }
1480+ List <LMSPrivateKeyParameters > keys = hss .getKeys ();
1481+ List <LMSSignature > sig = hss .getSig ();
1482+ long limit = (long )twoToH * twoToH ;
1483+ assertEquals (2 , keys .get (0 ).getIndex ());
1484+ assertEquals (1 , keys .get (1 ).getIndex ());
1485+
1486+ // back one leaf within the current bottom tree
1487+ expectRewindRefused (2 , keys , sig , twoToH , limit );
1488+ // back into the previous bottom tree, which the root has already signed and moved past
1489+ expectRewindRefused (2 , keys , sig , 5 , limit );
1490+ // the position the keys are at, and one further on, are both fine
1491+ assertEquals (twoToH + 1 , new HSSPrivateKeyParameters (2 , keys , sig , twoToH + 1 , limit ).getIndex ());
1492+ HSSPrivateKeyParameters forward = new HSSPrivateKeyParameters (2 , keys , sig , twoToH + 8 , limit );
1493+ assertEquals (8 , forward .getKeys ().get (1 ).getIndex ());
1494+ assertTrue (verify (hss .getPublicKey (), sign (forward , msg ), msg ));
1495+ }
1496+
1497+ private static void expectRewindRefused (LMSPrivateKeyParameters lms , long index , long indexLimit )
1498+ {
1499+ try
1500+ {
1501+ new HSSPrivateKeyParameters (lms , index , indexLimit );
1502+ fail ("index " + index + " rewound the key" );
1503+ }
1504+ catch (IllegalArgumentException e )
1505+ {
1506+ assertTrue (e .getMessage (), e .getMessage ().startsWith ("HSS private key index would move level" ));
1507+ }
1508+ }
1509+
1510+ private static void expectRewindRefused (int l , List <LMSPrivateKeyParameters > keys , List <LMSSignature > sig ,
1511+ long index , long indexLimit )
1512+ {
1513+ try
1514+ {
1515+ new HSSPrivateKeyParameters (l , keys , sig , index , indexLimit );
1516+ fail ("index " + index + " rewound the key" );
1517+ }
1518+ catch (IllegalArgumentException e )
1519+ {
1520+ assertTrue (e .getMessage (), e .getMessage ().startsWith ("HSS private key index would move level" ));
1521+ }
1522+ }
1523+
14421524 /**
14431525 * The HSS index and the bottom key's one-time index q are claimed together. They are two
14441526 * records of the same position - checkIndexAgainstKeys requires them to agree at decode - and
0 commit comments