public final class XMSSEngine
extends java.lang.Object
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.
| Modifier and Type | Method and Description |
|---|---|
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.
|
static void |
clearSecrets(BDSStateMap bdsState)
Zeroize the secret material retained by every layer of an XMSS^MT traversal state; see
clearSecrets(BDS). |
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. |
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). |
static AsymmetricCipherKeyPair |
generateKeyPair(XMSSParameters params,
java.security.SecureRandom prng)
Generate an XMSS key pair (RFC 8391 sec. 4.1.7).
|
static AsymmetricCipherKeyPair |
generateMTKeyPair(XMSSMTParameters params,
java.security.SecureRandom prng)
Generate an XMSS^MT key pair (RFC 8391 sec. 4.2.5).
|
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). |
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). |
static BDS |
getBDSFromEncoding(byte[] encoding,
byte[] publicSeed)
Recover an XMSS BDS traversal state from a private key encoding, checking its checksum
against the owning key's public seed (github #2414).
|
static BDSStateMap |
getBDSStateMapFromEncoding(byte[] encoding,
byte[] publicSeed)
Recover an XMSS^MT BDS traversal state from a private key encoding, checking its checksum
against the owning key's public seed (github #2414).
|
static Digest |
getDigest(ASN1ObjectIdentifier oid)
A tree digest by its OID.
|
static java.lang.String |
getDigestName(ASN1ObjectIdentifier oid)
The algorithm name of a tree digest OID.
|
static ASN1ObjectIdentifier |
getDigestOID(java.lang.String name)
The OID of a digest by its algorithm name, for the tree digest of an XMSS parameter set.
|
static int |
getDigestSize(ASN1ObjectIdentifier oid)
The output size in bytes of a tree digest, the security parameter n.
|
static byte[] |
getEncodedBDSState(BDS bdsState,
byte[] publicSeed)
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).
|
static byte[] |
getEncodedBDSState(BDSStateMap bdsState,
byte[] publicSeed)
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).
|
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.
|
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. |
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).
|
static boolean |
hasTraversalState(XMSSMTPrivateKeyParameters privateKey)
Whether the key's BDS traversal state has been initialised, i.e. whether it can sign.
|
static boolean |
hasTraversalState(XMSSPrivateKeyParameters privateKey)
Whether the key's BDS traversal state has been initialised, i.e. whether it can sign.
|
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. |
static int |
lookupXMSSMTOid(java.lang.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.
|
static int |
lookupXMSSOid(java.lang.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.
|
static byte[] |
validateOrAllocate(byte[] value,
int size,
java.lang.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. |
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. |
static boolean |
verifyMTSignature(XMSSMTPublicKeyParameters publicKey,
byte[] message,
byte[] signature)
Verify an XMSS^MT signature (RFC 8391 sec. 4.2.8).
|
static boolean |
verifySignature(XMSSPublicKeyParameters publicKey,
byte[] message,
byte[] signature)
Verify an XMSS signature (RFC 8391 sec. 4.1.10).
|
public static ASN1ObjectIdentifier getDigestOID(java.lang.String name)
public static java.lang.String getDigestName(ASN1ObjectIdentifier oid)
public static int getDigestSize(ASN1ObjectIdentifier oid)
public static Digest getDigest(ASN1ObjectIdentifier oid)
public static int getWOTSPlusLen(ASN1ObjectIdentifier treeDigestOID, int digestSize)
public static int lookupXMSSOid(java.lang.String treeDigest,
int digestSize,
int winternitzParameter,
int len,
int height)
public static int lookupXMSSMTOid(java.lang.String treeDigest,
int digestSize,
int winternitzParameter,
int len,
int height,
int layers)
public static AsymmetricCipherKeyPair generateKeyPair(XMSSParameters params, java.security.SecureRandom prng)
public static AsymmetricCipherKeyPair generateMTKeyPair(XMSSMTParameters params, java.security.SecureRandom prng)
public static boolean hasTraversalState(XMSSPrivateKeyParameters privateKey)
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.
public static boolean hasTraversalState(XMSSMTPrivateKeyParameters privateKey)
public static byte[] generateSignature(XMSSPrivateKeyParameters privateKey, byte[] message)
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.
public static boolean verifySignature(XMSSPublicKeyParameters publicKey, byte[] message, byte[] signature)
public static byte[] generateMTSignature(XMSSMTPrivateKeyParameters privateKey, byte[] message)
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[]).public static boolean verifyMTSignature(XMSSMTPublicKeyParameters publicKey, byte[] message, byte[] signature)
public static BDS createBDS(XMSSParameters params, byte[] publicSeed, byte[] secretKeySeed, int index)
index, built at the all-zero OTS hash
address an XMSS key starts from.java.lang.IllegalArgumentException - if index is out of range for the parameter set.public static BDSStateMap createBDSStateMap(XMSSMTParameters params, byte[] publicSeed, byte[] secretKeySeed, long globalIndex)
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.
java.lang.IllegalArgumentException - if globalIndex is out of range for the parameter set.public static BDS getNextBDSState(BDS bdsState, byte[] publicSeed, byte[] secretKeySeed)
public static byte[] getEncodedBDSState(BDS bdsState, byte[] publicSeed) throws java.io.IOException
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.
java.io.IOExceptionpublic static byte[] getEncodedBDSState(BDSStateMap bdsState, byte[] publicSeed) throws java.io.IOException
java.io.IOExceptionpublic static void clearSecrets(BDS bdsState)
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(org.bouncycastle.crypto.params.XMSSPrivateKeyParameters) sit here.
public static void clearSecrets(BDSStateMap bdsState)
clearSecrets(BDS).public static BDSStateMap getNextBDSStateMap(BDSStateMap bdsState, XMSSMTParameters params, long globalIndex, byte[] publicSeed, byte[] secretKeySeed)
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(org.bouncycastle.crypto.signers.xmss.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.
public static BDS getBDSFromEncoding(byte[] encoding, byte[] publicSeed) throws java.io.IOException, java.lang.ClassNotFoundException
java.io.IOExceptionjava.lang.ClassNotFoundExceptionpublic static BDSStateMap getBDSStateMapFromEncoding(byte[] encoding, byte[] publicSeed) throws java.io.IOException, java.lang.ClassNotFoundException
java.io.IOExceptionjava.lang.ClassNotFoundExceptionpublic static boolean isStoredIndexValid(int height,
long index)
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.
public static byte[] validateOrAllocate(byte[] value,
int size,
java.lang.String name)
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.
public static void validateShardSize(int usageCount,
long usagesRemaining)
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.
usageCount - signatures asked for.usagesRemaining - signatures the key has left, read under the key's own monitor.