Class XMSSEngine

java.lang.Object
org.bouncycastle.crypto.signers.xmss.XMSSEngine

public final class XMSSEngine extends Object
The XMSS and XMSS^MT (RFC 8391) operations the key parameter classes, the key pair generators and the signers are built on: key pair generation, signature generation and verification, the WOTS+ and OID parameter derivations, and the handful of encoding helpers the key classes need.

This is the only public class of the package besides the two opaque BDS traversal-state types (BDS and BDSStateMap, which an XMSS / XMSS^MT private key carries as a field and so cannot be package-private). Everything else here - WOTS+, the hash addressing scheme, the keyed hash functions, the tree arithmetic and the signature structures - is implementation detail, and no compatibility is promised for it.

  • Method Details

    • getDigestOID

      public static ASN1ObjectIdentifier getDigestOID(String name)
      The OID of a digest by its algorithm name, for the tree digest of an XMSS parameter set.
    • getDigestName

      public static String getDigestName(ASN1ObjectIdentifier oid)
      The algorithm name of a tree digest OID.
    • getDigestSize

      public static int getDigestSize(ASN1ObjectIdentifier oid)
      The output size in bytes of a tree digest, the security parameter n.
    • getDigest

      public static Digest getDigest(ASN1ObjectIdentifier oid)
      A tree digest by its OID. Note the SP 800-208 SHAKE256/192 and SHAKE256/256 parameter sets name id-shake256-len, which no other algorithm in this package uses.
    • getWOTSPlusLen

      public static int getWOTSPlusLen(ASN1ObjectIdentifier treeDigestOID, int digestSize)
      The number of n-byte elements in a WOTS+ key or signature, len = len1 + len2 (RFC 8391 sec. 3.1.1). Rejects a tree digest and security parameter that are not a registered WOTS+ combination, which is where an unusable XMSS parameter set is caught.
    • lookupXMSSOid

      public static int lookupXMSSOid(String treeDigest, int digestSize, int winternitzParameter, int len, int height)
      The RFC 8391 sec. 5.3 XMSS parameter-set identifier for this combination, or 0 when the combination is not one of the registered sets.
    • lookupXMSSMTOid

      public static int lookupXMSSMTOid(String treeDigest, int digestSize, int winternitzParameter, int len, int height, int layers)
      The RFC 8391 sec. 5.4 XMSS^MT parameter-set identifier for this combination, or 0 when the combination is not one of the registered sets.
    • generateKeyPair

      public static AsymmetricCipherKeyPair generateKeyPair(XMSSParameters params, SecureRandom prng)
      Generate an XMSS key pair (RFC 8391 sec. 4.1.7).
    • generateMTKeyPair

      public static AsymmetricCipherKeyPair generateMTKeyPair(XMSSMTParameters params, SecureRandom prng)
      Generate an XMSS^MT key pair (RFC 8391 sec. 4.2.5).
    • hasTraversalState

      public static boolean hasTraversalState(XMSSPrivateKeyParameters privateKey)
      Whether the key's BDS traversal state has been initialised, i.e. whether it can sign. A key decoded from an encoding that carried no traversal state has none.

      This and the XMSS^MT overload below are the only place the question is asked. What counts as no state differs between the two families - a lone BDS with no authentication path, a state map with no layers in it - and the signature methods below had a copy of the answer for their own family inlined beside the copy the signers were already calling, so widening what counts would have had to be applied to a public helper and to two private call sites that did not go through it.

    • hasTraversalState

      public static boolean hasTraversalState(XMSSMTPrivateKeyParameters privateKey)
      Whether the key's BDS traversal state has been initialised, i.e. whether it can sign.
    • generateSignature

      public static byte[] generateSignature(XMSSPrivateKeyParameters privateKey, byte[] message)
      Generate an XMSS signature over message and advance the key's traversal state (RFC 8391 sec. 4.1.9).

      The key is held locked across the whole check, sign and advance sequence. RFC 8391 sec. 1.1 requires each one-time key to be used exactly once, and this method both reads the index and rolls the key past it: two threads entering with the same key and nothing serializing them sign different messages under the same one-time key, and a WOTS+ key used twice discloses enough of itself to forge. The key's own accessors are individually synchronized, which does not make the compound sequence atomic, so the lock is taken here rather than left to the caller - this is a public entry point and cannot assume one. XMSSSigner holds the same monitor and Java monitors are reentrant, so that path is unchanged; LMS takes the equivalent lock inside the key itself.

    • verifySignature

      public static boolean verifySignature(XMSSPublicKeyParameters publicKey, byte[] message, byte[] signature)
      Verify an XMSS signature (RFC 8391 sec. 4.1.10). A signature that will not decode at all is reported as a failed verification rather than raised, per the JCA contract the signers above this present. An absent one is not the same thing: there are no bytes to decode, so a null signature is the caller's mistake and is raised.
    • generateMTSignature

      public static byte[] generateMTSignature(XMSSMTPrivateKeyParameters privateKey, byte[] message)
      Generate an XMSS^MT signature over message and advance the key's traversal state (RFC 8391 sec. 4.2.7). The key is held locked for the whole sequence, for the reason given on generateSignature(XMSSPrivateKeyParameters, byte[]).
    • verifyMTSignature

      public static boolean verifyMTSignature(XMSSMTPublicKeyParameters publicKey, byte[] message, byte[] signature)
      Verify an XMSS^MT signature (RFC 8391 sec. 4.2.8). As with XMSS above, a signature that will not decode is reported as a failed verification rather than raised, and an absent one is raised.
    • createBDS

      public static BDS createBDS(XMSSParameters params, byte[] publicSeed, byte[] secretKeySeed, int index)
      A fresh BDS traversal state for a key at index, built at the all-zero OTS hash address an XMSS key starts from.
      Throws:
      IllegalArgumentException - if index is out of range for the parameter set.
    • createBDSStateMap

      public static BDSStateMap createBDSStateMap(XMSSMTParameters params, byte[] publicSeed, byte[] secretKeySeed, long globalIndex)
      A fresh BDS traversal state map for an XMSS^MT key at globalIndex, the multi-tree counterpart of createBDS(XMSSParameters, byte[], byte[], int).

      The map a key at index 0 gets is empty, and stays that way until a signature needs a layer: XMSS^MT builds each layer's state lazily. So an empty map is not a missing one, and nothing downstream can tell an out-of-range index by looking at what this returns - the structural check in BDSStateMap.validate() passes over a map with no layers in it, and the index check walks the layers it does have. That is what makes the bound here the only place the index is answered, rather than one of two.

      Throws:
      IllegalArgumentException - if globalIndex is out of range for the parameter set.
    • getNextBDSState

      public static BDS getNextBDSState(BDS bdsState, byte[] publicSeed, byte[] secretKeySeed)
      The traversal state for the next index, i.e. with the authentication path advanced one leaf.
    • getEncodedBDSState

      public static byte[] getEncodedBDSState(BDS bdsState, byte[] publicSeed) throws IOException
      The BDS traversal state as it is carried in an XMSS private key encoding, with its checksum bound to the owning key's public seed (github #2414). The form is the versioned one BDSStateCodec defines, and this is where every one written comes from: the key parameters class reaches it for toByteArray() and for the state half of equals(), and XmssKeyUtil for the PKCS#8 encoding.

      The legacy Java-serialized form is a decode-side concern only. A key written before that codec existed carries one and getBDSFromEncoding(byte[], byte[]) still reads it, but nothing has written one since.

      Throws:
      IOException
    • getEncodedBDSState

      public static byte[] getEncodedBDSState(BDSStateMap bdsState, byte[] publicSeed) throws IOException
      The BDS traversal state as it is carried in an XMSS^MT private key encoding, with its checksum bound to the owning key's public seed (github #2414).
      Throws:
      IOException
    • clearSecrets

      public static void clearSecrets(BDS bdsState)
      Zeroize the secret material a BDS traversal state retains - the WOTS+ secret key its hash-function instance imported for the last leaf it processed, which is a future leaf's one-time key while the tree is not exhausted. The tree nodes are kept; they are not secret.

      Here rather than as a public method on BDS because the state classes are the engine's and the caller is XMSSPrivateKeyParameters.destroy() in another package: this is the same reason getEncodedBDSState(BDS, byte[]) and hasTraversalState(XMSSPrivateKeyParameters) sit here.

    • clearSecrets

      public static void clearSecrets(BDSStateMap bdsState)
      Zeroize the secret material retained by every layer of an XMSS^MT traversal state; see clearSecrets(BDS).
    • getNextBDSStateMap

      public static BDSStateMap getNextBDSStateMap(BDSStateMap bdsState, XMSSMTParameters params, long globalIndex, byte[] publicSeed, byte[] secretKeySeed)
      The XMSS^MT traversal state for the leaf after globalIndex, for the key parameters class rolling its own key on. The state passed in is left where it is and a new one comes back, as getNextBDSState(BDS, byte[], byte[]) does for the single tree.

      That it returns the advanced state rather than advancing the one it is given is what makes it safe to be public - and public it has to be, the key parameters class being in another package and Java having no way to say "callable from these two packages only". A state map is reachable from a live private key through its getBDSState(), so a version of this that advanced its argument let a holder move a key's state out from under the index the key still reported. A key whose two records of its position disagree signs a second message under a one-time key it has already spent, against RFC 8391 sec. 1.1, and that signature verifies.

      With the state replaced instead, the two records move together or not at all: the key assigns both, a failure part way through leaves it on the index it was already on, and a holder calling this gets a state map of its own while the key keeps the one it had. Nothing is left for the signer to check, and the one remaining way for a key's index and state to part - a stored key written or restored wrongly - is refused when the key is built.

    • getBDSFromEncoding

      public static BDS getBDSFromEncoding(byte[] encoding, byte[] publicSeed) throws IOException, ClassNotFoundException
      Recover an XMSS BDS traversal state from a private key encoding, checking its checksum against the owning key's public seed (github #2414).
      Throws:
      IOException
      ClassNotFoundException
    • getBDSStateMapFromEncoding

      public static BDSStateMap getBDSStateMapFromEncoding(byte[] encoding, byte[] publicSeed) throws IOException, ClassNotFoundException
      Recover an XMSS^MT BDS traversal state from a private key encoding, checking its checksum against the owning key's public seed (github #2414).
      Throws:
      IOException
      ClassNotFoundException
    • isStoredIndexValid

      public static boolean isStoredIndexValid(int height, long index)
      Whether index is in range for the index field of a stored private key over a tree of this height, i.e. 0 <= index <= 2^height.

      The bound is one leaf wider than the 0 <= index < 2^height an index a signature can be made at satisfies, because a key that has been used up carries the index one past its last leaf: that is the placeholder traversal state BDS installs when the final one-time key is consumed, it is what makes the key report no usages remaining, and both BDS.validate() and the BDS serialization admit it. A key's final state is the one state that most needs to survive being written out and read back, so the private key encoding has to carry it rather than reject it. The tighter bound against the key's own maximum index is applied by BDS.validate() once the traversal state itself has been recovered.

    • validateOrAllocate

      public static byte[] validateOrAllocate(byte[] value, int size, String name)
      Return value once it is confirmed to be size bytes long, or a freshly allocated all-zero array of that size if value is null. name is how the field is named in the message a wrong-sized one is refused with.

      Shared by the four key parameter classes and the two signature classes, which take their optional n-byte fields on the same terms and so must say the same thing about one that is the wrong size.

    • validateShardSize

      public static void validateShardSize(int usageCount, long usagesRemaining)
      Refuse a request for a key shard of usageCount signatures from a key with usagesRemaining left: a shard of none, and a shard of more than the key has.

      Shared by the two private key classes, which mean the same thing by both and had said it in the same words twice - and the words are the contract, since a message is what a caller catching IllegalArgumentException has to tell the two cases apart by. Beside validateOrAllocate above for the same reason it is here: the four key classes and the two signature classes take their optional n-byte fields on the same terms, and this is the terms both key classes hand out a shard on.

      Parameters:
      usageCount - signatures asked for.
      usagesRemaining - signatures the key has left, read under the key's own monitor.