VYPR
Critical severityNVD Advisory· Published Jul 9, 2018· Updated Nov 14, 2024

CVE-2018-1000613

CVE-2018-1000613

Description

Legion of the Bouncy Castle Legion of the Bouncy Castle Java Cryptography APIs 1.58 up to but not including 1.60 contains a CWE-470: Use of Externally-Controlled Input to Select Classes or Code ('Unsafe Reflection') vulnerability in XMSS/XMSS^MT private key deserialization that can result in Deserializing an XMSS/XMSS^MT private key can result in the execution of unexpected code. This attack appear to be exploitable via A handcrafted private key can include references to unexpected classes which will be picked up from the class path for the executing application. This vulnerability appears to have been fixed in 1.60 and later.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

An unsafe reflection vulnerability in Bouncy Castle's XMSS/XMSS^MT private key deserialization (CVE-2018-1000613) allows remote code execution via a crafted private key.

Vulnerability

CVE-2018-1000613 is a CWE-470 (Use of Externally-Controlled Input to Select Classes or Code) vulnerability in the Legion of the Bouncy Castle Java Cryptography APIs, versions 1.58 up to but not including 1.60. The flaw resides in the XMSS/XMSS^MT private key deserialization process, where externally-controlled input can select arbitrary classes during deserialization, leading to unsafe reflection [1][2][3].

Exploitation

An attacker can craft a malicious XMSS/XMSS^MT private key containing references to unexpected classes. When the victim application deserializes this private key, the referenced classes are loaded from the class path. No additional authentication or privileges are required beyond the ability to supply the crafted private key to the deserialization routine [1][2][3].

Impact

Successful exploitation results in the execution of arbitrary code in the context of the application performing deserialization. This can lead to full compromise of confidentiality, integrity, and availability, as the attacker can run any code accessible via the application's class path [1][2][3].

Mitigation

The vulnerability is fixed in version 1.60 of the Bouncy Castle Java Cryptography APIs, released on an unknown date prior to the CVE publication (2018-07-09). Users should upgrade to 1.60 or later. No workaround is available, and the package is not listed as end-of-life. The vulnerability is not listed on CISA's Known Exploited Vulnerabilities (KEV) catalog [1][2][3][4].

AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.bouncycastle:bcprov-jdk15onMaven
>= 1.57, < 1.601.60

Affected products

3

Patches

3
cd98322b171b

added full filtering for BDS data.

https://github.com/bcgit/bc-javaDavid HookMar 3, 2018via ghsa
1 file changed · +28 0
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSUtil.java+28 0 modified
    @@ -8,6 +8,8 @@
     import java.io.ObjectInputStream;
     import java.io.ObjectOutputStream;
     import java.io.ObjectStreamClass;
    +import java.util.HashSet;
    +import java.util.Set;
     
     import org.bouncycastle.crypto.Digest;
     import org.bouncycastle.util.Arrays;
    @@ -382,6 +384,24 @@ public static boolean isNewAuthenticationPathNeeded(long globalIndex, int xmssHe
         private static class CheckingStream
            extends ObjectInputStream
         {
    +        private static final Set<String> components = new HashSet<>();
    +
    +        static
    +        {
    +            components.add("java.util.TreeMap");
    +            components.add("java.lang.Integer");
    +            components.add("java.lang.Number");
    +            components.add("org.bouncycastle.pqc.crypto.xmss.BDS");
    +            components.add("java.util.ArrayList");
    +            components.add("org.bouncycastle.pqc.crypto.xmss.XMSSNode");
    +            components.add("[B");
    +            components.add("java.util.LinkedList");
    +            components.add("java.util.Stack");
    +            components.add("java.util.Vector");
    +            components.add("[Ljava.lang.Object;");
    +            components.add("org.bouncycastle.pqc.crypto.xmss.BDSTreeHash");
    +        }
    +
             private final Class mainClass;
             private boolean found = false;
     
    @@ -409,6 +429,14 @@ protected Class<?> resolveClass(ObjectStreamClass desc)
                         found = true;
                     }
                 }
    +            else
    +            {
    +                if (!components.contains(desc.getName()))
    +                {
    +                    throw new InvalidClassException(
    +                          "unexpected class: ", desc.getName());
    +                }
    +            }
                 return super.resolveClass(desc);
             }
         }
    
4092ede58da5

added additional checking to XMSS BDS tree parsing. Failures now mostly cause IOException

https://github.com/bcgit/bc-javaDavid HookMar 3, 2018via ghsa
10 files changed · +97 66
  • core/src/main/java/org/bouncycastle/pqc/crypto/gmss/GMSSKeyPairGenerator.java+10 17 modified
    @@ -178,26 +178,19 @@ private AsymmetricCipherKeyPair genKeyPair()
             // from bottom up to the root
             for (int h = numLayer - 1; h >= 0; h--)
             {
    -            GMSSRootCalc tree = new GMSSRootCalc(this.heightOfTrees[h], this.K[h], digestProvider);
    -            try
    -            {
    -                // on lowest layer no lower root is available, so just call
    -                // the method with null as first parameter
    -                if (h == numLayer - 1)
    -                {
    -                    tree = this.generateCurrentAuthpathAndRoot(null, currentStack[h], seeds[h], h);
    -                }
    -                else
    -                // otherwise call the method with the former computed root
    -                // value
    -                {
    -                    tree = this.generateCurrentAuthpathAndRoot(currentRoots[h + 1], currentStack[h], seeds[h], h);
    -                }
    +            GMSSRootCalc tree;
     
    +            // on lowest layer no lower root is available, so just call
    +            // the method with null as first parameter
    +            if (h == numLayer - 1)
    +            {
    +                tree = this.generateCurrentAuthpathAndRoot(null, currentStack[h], seeds[h], h);
                 }
    -            catch (Exception e1)
    +            else
    +            // otherwise call the method with the former computed root
    +            // value
                 {
    -                e1.printStackTrace();
    +                tree = this.generateCurrentAuthpathAndRoot(currentRoots[h + 1], currentStack[h], seeds[h], h);
                 }
     
                 // set initial values needed for the private key construction
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/rainbow/RainbowParameters.java+5 12 modified
    @@ -44,37 +44,30 @@ public RainbowParameters()
         public RainbowParameters(int[] vi)
         {
             this.vi = vi;
    -        try
    -        {
    -            checkParams();
    -        }
    -        catch (Exception e)
    -        {
    -            e.printStackTrace();
    -        }
    +
    +        checkParams();
         }
     
         private void checkParams()
    -        throws Exception
         {
             if (vi == null)
             {
    -            throw new Exception("no layers defined.");
    +            throw new IllegalArgumentException("no layers defined.");
             }
             if (vi.length > 1)
             {
                 for (int i = 0; i < vi.length - 1; i++)
                 {
                     if (vi[i] >= vi[i + 1])
                     {
    -                    throw new Exception(
    +                    throw new IllegalArgumentException(
                             "v[i] has to be smaller than v[i+1]");
                     }
                 }
             }
             else
             {
    -            throw new Exception(
    +            throw new IllegalArgumentException(
                     "Rainbow needs at least 1 layer, such that v1 < v2.");
             }
         }
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSMTPrivateKeyParameters.java+8 11 modified
    @@ -68,21 +68,21 @@ private XMSSMTPrivateKeyParameters(Builder builder)
     			/* import BDS state */
                 byte[] bdsStateBinary = XMSSUtil.extractBytesAtOffset(privateKey, position, privateKey.length - position);
     
    -            BDSStateMap bdsImport = null;
                 try
                 {
    -                bdsImport = (BDSStateMap)XMSSUtil.deserialize(bdsStateBinary);
    +                BDSStateMap bdsImport = (BDSStateMap)XMSSUtil.deserialize(bdsStateBinary, BDSStateMap.class);
    +
    +                bdsImport.setXMSS(builder.xmss);
    +                bdsState = bdsImport;
                 }
                 catch (IOException e)
                 {
    -                e.printStackTrace();
    +                throw new IllegalArgumentException(e.getMessage(), e);
                 }
                 catch (ClassNotFoundException e)
                 {
    -                e.printStackTrace();
    +                throw new IllegalArgumentException(e.getMessage(), e);
                 }
    -            bdsImport.setXMSS(builder.xmss);
    -            bdsState = bdsImport;
             }
             else
             {
    @@ -260,17 +260,14 @@ public byte[] toByteArray()
     		/* copy root */
             XMSSUtil.copyBytesAtOffset(out, root, position);
     		/* concatenate bdsState */
    -        byte[] bdsStateOut = null;
             try
             {
    -            bdsStateOut = XMSSUtil.serialize(bdsState);
    +            return Arrays.concatenate(out, XMSSUtil.serialize(bdsState));
             }
             catch (IOException e)
             {
    -            e.printStackTrace();
    -            throw new RuntimeException("error serializing bds state");
    +            throw new IllegalStateException("error serializing bds state: " + e.getMessage(), e);
             }
    -        return Arrays.concatenate(out, bdsStateOut);
         }
     
         public long getIndex()
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSPrivateKeyParameters.java+10 11 modified
    @@ -86,26 +86,25 @@ private XMSSPrivateKeyParameters(Builder builder)
                 position += rootSize;
     			/* import BDS state */
                 byte[] bdsStateBinary = XMSSUtil.extractBytesAtOffset(privateKey, position, privateKey.length - position);
    -            BDS bdsImport = null;
                 try
                 {
    -                bdsImport = (BDS)XMSSUtil.deserialize(bdsStateBinary);
    +                BDS bdsImport = (BDS)XMSSUtil.deserialize(bdsStateBinary, BDS.class);
    +                bdsImport.setXMSS(builder.xmss);
    +                bdsImport.validate();
    +                if (bdsImport.getIndex() != index)
    +                {
    +                    throw new IllegalStateException("serialized BDS has wrong index");
    +                }
    +                bdsState = bdsImport;
                 }
                 catch (IOException e)
                 {
    -                e.printStackTrace();
    +                throw new IllegalArgumentException(e.getMessage(), e);
                 }
                 catch (ClassNotFoundException e)
                 {
    -                e.printStackTrace();
    -            }
    -            bdsImport.setXMSS(builder.xmss);
    -            bdsImport.validate();
    -            if (bdsImport.getIndex() != index)
    -            {
    -                throw new IllegalStateException("serialized BDS has wrong index");
    +                throw new IllegalArgumentException(e.getMessage(), e);
                 }
    -            bdsState = bdsImport;
             }
             else
             {
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSUtil.java+15 2 modified
    @@ -321,12 +321,25 @@ public static byte[] serialize(Object obj)
             return out.toByteArray();
         }
     
    -    public static Object deserialize(byte[] data)
    +    public static Object deserialize(byte[] data, Class clazz)
             throws IOException, ClassNotFoundException
         {
             ByteArrayInputStream in = new ByteArrayInputStream(data);
             ObjectInputStream is = new ObjectInputStream(in);
    -        return is.readObject();
    +        Object obj = is.readObject();
    +
    +        if (is.available() != 0)
    +        {
    +            throw new IOException("unexpected data found at end of ObjectInputStream");
    +        }
    +        if (clazz.isInstance(obj))
    +        {
    +            return obj;
    +        }
    +        else
    +        {
    +            throw new IOException("unexpected class found in ObjectInputStream");
    +        }
         }
     
         public static int calculateTau(int index, int height)
    
  • core/src/main/java/org/bouncycastle/pqc/math/linearalgebra/GF2nField.java+3 10 modified
    @@ -155,16 +155,9 @@ protected final GF2Polynomial[] invertMatrix(GF2Polynomial[] matrix)
             // initialize a as a copy of matrix and inv as E(inheitsmatrix)
             for (i = 0; i < mDegree; i++)
             {
    -            try
    -            {
    -                a[i] = new GF2Polynomial(matrix[i]);
    -                inv[i] = new GF2Polynomial(mDegree);
    -                inv[i].setBit(mDegree - 1 - i);
    -            }
    -            catch (RuntimeException BDNEExc)
    -            {
    -                BDNEExc.printStackTrace();
    -            }
    +            a[i] = new GF2Polynomial(matrix[i]);
    +            inv[i] = new GF2Polynomial(mDegree);
    +            inv[i].setBit(mDegree - 1 - i);
             }
             // construct triangle matrix so that for each a[i] the first i bits are
             // zero
    
  • core/src/test/java/org/bouncycastle/pqc/crypto/test/XMSSMTPrivateKeyTest.java+43 1 modified
    @@ -5,19 +5,61 @@
     
     import junit.framework.TestCase;
     import org.bouncycastle.crypto.digests.SHA256Digest;
    +import org.bouncycastle.pqc.crypto.xmss.XMSS;
     import org.bouncycastle.pqc.crypto.xmss.XMSSMT;
     import org.bouncycastle.pqc.crypto.xmss.XMSSMTParameters;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSParameters;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSPrivateKeyParameters;
     import org.bouncycastle.util.Arrays;
    +import org.bouncycastle.util.encoders.Base64;
     
     /**
      * Test cases for XMSSMTPrivateKey class.
      */
     public class XMSSMTPrivateKeyTest
         extends TestCase
     {
    +    public void testPrivateKeySerialisation()
    +        throws Exception
    +    {
    +        String stream = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArO0ABXNyACJzdW4ucm1pLnNlcnZlci5BY3RpdmF0aW9uR3JvdXBJbXBsT+r9SAwuMqcCAARaAA1ncm91cEluYWN0aXZlTAAGYWN0aXZldAAVTGphdmEvdXRpbC9IYXNodGFibGU7TAAHZ3JvdXBJRHQAJ0xqYXZhL3JtaS9hY3RpdmF0aW9uL0FjdGl2YXRpb25Hcm91cElEO0wACWxvY2tlZElEc3QAEExqYXZhL3V0aWwvTGlzdDt4cgAjamF2YS5ybWkuYWN0aXZhdGlvbi5BY3RpdmF0aW9uR3JvdXCVLvKwBSnVVAIAA0oAC2luY2FybmF0aW9uTAAHZ3JvdXBJRHEAfgACTAAHbW9uaXRvcnQAJ0xqYXZhL3JtaS9hY3RpdmF0aW9uL0FjdGl2YXRpb25Nb25pdG9yO3hyACNqYXZhLnJtaS5zZXJ2ZXIuVW5pY2FzdFJlbW90ZU9iamVjdEUJEhX14n4xAgADSQAEcG9ydEwAA2NzZnQAKExqYXZhL3JtaS9zZXJ2ZXIvUk1JQ2xpZW50U29ja2V0RmFjdG9yeTtMAANzc2Z0AChMamF2YS9ybWkvc2VydmVyL1JNSVNlcnZlclNvY2tldEZhY3Rvcnk7eHIAHGphdmEucm1pLnNlcnZlci5SZW1vdGVTZXJ2ZXLHGQcSaPM5+wIAAHhyABxqYXZhLnJtaS5zZXJ2ZXIuUmVtb3RlT2JqZWN002G0kQxhMx4DAAB4cHcSABBVbmljYXN0U2VydmVyUmVmeAAAFbNwcAAAAAAAAAAAcHAAcHBw";
    +
    +        XMSSParameters params = new XMSSParameters(10, new SHA256Digest());
    +
    +        byte[] output = Base64.decode(new String(stream).getBytes("UTF-8"));
    +
    +
    +        //Simple Exploit
    +
    +        try
    +        {
    +            new XMSSPrivateKeyParameters.Builder(params).withPrivateKey(output, params).build();
    +        }
    +        catch (IllegalArgumentException e)
    +        {
    +            assertTrue(e.getCause() instanceof IOException);
    +        }
    +
    +        //Same Exploit other method
    +
    +        XMSS xmss2 = new XMSS(params, new SecureRandom());
    +
    +        xmss2.generateKeys();
    +
    +        byte[] publicKey = xmss2.exportPublicKey();
    +
    +        try
    +        {
    +            xmss2.importState(output, publicKey);
    +        }
    +        catch (IllegalArgumentException e)
    +        {
    +            assertTrue(e.getCause() instanceof IOException);
    +        }
    +    }
     
         public void testPrivateKeyParsingSHA256()
    -        throws IOException, ClassNotFoundException
    +        throws Exception
         {
             XMSSMTParameters params = new XMSSMTParameters(20, 10, new SHA256Digest());
             XMSSMT mt = new XMSSMT(params, new SecureRandom());
    
  • docs/releasenotes.html+1 0 modified
    @@ -28,6 +28,7 @@ <h3>2.1.1 Version</h3>
     <h3>2.1.2 Defects Fixed</h3>
     <ul>
     <li>Base64/UrlBase64 would throw an exception on a zero length string. This has been fixed.</li>
    +<li>XMSS applies further validation to deserialisation of the BDS tree so that failure occurs as soon as tampering is detected.</li>
     </ul>
     <h3>2.1.3 Additional Features and Functionality</h3>
     <ul>
    
  • prov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSMTPrivateKey.java+1 1 modified
    @@ -52,7 +52,7 @@ public BCXMSSMTPrivateKey(PrivateKeyInfo keyInfo)
     
                 if (xmssMtPrivateKey.getBdsState() != null)
                 {
    -                keyBuilder.withBDSState((BDSStateMap)XMSSUtil.deserialize(xmssMtPrivateKey.getBdsState()));
    +                keyBuilder.withBDSState((BDSStateMap)XMSSUtil.deserialize(xmssMtPrivateKey.getBdsState(), BDSStateMap.class));
                 }
     
                 this.keyParams = keyBuilder.build();
    
  • prov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSPrivateKey.java+1 1 modified
    @@ -51,7 +51,7 @@ public BCXMSSPrivateKey(PrivateKeyInfo keyInfo)
     
                 if (xmssPrivateKey.getBdsState() != null)
                 {
    -                keyBuilder.withBDSState((BDS)XMSSUtil.deserialize(xmssPrivateKey.getBdsState()));
    +                keyBuilder.withBDSState((BDS)XMSSUtil.deserialize(xmssPrivateKey.getBdsState(), BDS.class));
                 }
     
                 this.keyParams = keyBuilder.build();
    
cc9f91c41be6

XMSS/XMSS^MT implementation according to draft-irtf-cfrg-xmss-hash-based-signatures-09 including BDS algorithm for efficient auth path computation

https://github.com/bcgit/bc-javasebastian rolandApr 9, 2017via ghsa
44 files changed · +8090 1
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/BDS.java+366 0 added
    @@ -0,0 +1,366 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.io.Serializable;
    +import java.util.ArrayDeque;
    +import java.util.ArrayList;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.Stack;
    +import java.util.TreeMap;
    +
    +/**
    + * BDS.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class BDS implements Serializable {
    +
    +	private static final long serialVersionUID = 1L;
    +
    +	private class TreeHash implements Serializable {
    +
    +		private static final long serialVersionUID = 1L;
    +		
    +		private XMSSNode tailNode;
    +		private final int initialHeight;
    +		private int height;
    +		private int nextIndex;
    +		private boolean initialized;
    +		private boolean finished;
    +		
    +		private TreeHash(int initialHeight) {
    +			super();
    +			this.initialHeight = initialHeight;
    +			initialized = false;
    +			finished = false;
    +		}
    +		
    +		private void initialize(int nextIndex) {
    +			tailNode = null;
    +			height = initialHeight;
    +			this.nextIndex = nextIndex;
    +			initialized = true;
    +			finished = false;
    +		}
    +		
    +		private void update(OTSHashAddress otsHashAddress) {
    +			if (otsHashAddress == null) {
    +				throw new NullPointerException("otsHashAddress == null");
    +			}
    +			if (finished || !initialized) {
    +				throw new IllegalStateException("finished or not initialized");
    +			}
    +			/* prepare addresses */
    +			otsHashAddress.setOTSAddress(nextIndex);
    +			LTreeAddress lTreeAddress = new LTreeAddress();
    +			lTreeAddress.setLayerAddress(otsHashAddress.getLayerAddress());
    +			lTreeAddress.setTreeAddress(otsHashAddress.getTreeAddress());
    +			lTreeAddress.setLTreeAddress(nextIndex);
    +			HashTreeAddress hashTreeAddress = new HashTreeAddress();
    +			hashTreeAddress.setLayerAddress(otsHashAddress.getLayerAddress());
    +			hashTreeAddress.setTreeAddress(otsHashAddress.getTreeAddress());
    +			hashTreeAddress.setTreeHeight(0);
    +			hashTreeAddress.setTreeIndex(nextIndex);
    +			
    +			/* calculate leaf node */
    +			wotsPlus.importKeys(xmss.getWOTSPlusSecretKey(otsHashAddress), xmss.getPublicSeed());
    +			WOTSPlusPublicKey wotsPlusPublicKey = wotsPlus.getPublicKey(otsHashAddress);
    +			XMSSNode node = xmss.lTree(wotsPlusPublicKey, lTreeAddress);
    +			
    +			while (!stack.isEmpty() && stack.peek().getHeight() == node.getHeight() && stack.peek().getHeight() != initialHeight) {
    +				hashTreeAddress.setTreeIndex((hashTreeAddress.getTreeIndex() - 1) / 2);
    +				node = xmss.randomizeHash(stack.pop(), node, hashTreeAddress);
    +				node.setHeight(node.getHeight() + 1);
    +				hashTreeAddress.setTreeHeight(hashTreeAddress.getTreeHeight() + 1);
    +			}
    +
    +			if (tailNode == null) {
    +				tailNode = node;
    +			} else {
    +				if (tailNode.getHeight() == node.getHeight()) {
    +					hashTreeAddress.setTreeIndex((hashTreeAddress.getTreeIndex() - 1) / 2);
    +					node = xmss.randomizeHash(tailNode, node, hashTreeAddress);
    +					node.setHeight(tailNode.getHeight() + 1);
    +					tailNode = node;
    +					hashTreeAddress.setTreeHeight(hashTreeAddress.getTreeHeight() + 1);
    +				} else {
    +					stack.push(node);
    +				}
    +			}
    +			
    +			if (tailNode.getHeight() == initialHeight) {
    +				finished = true;
    +			} else {
    +				height = node.getHeight();
    +				nextIndex++;
    +			}
    +		}
    +		
    +		private int getHeight() {
    +			if (!initialized || finished) {
    +				return Integer.MAX_VALUE;
    +			}
    +			return height;
    +		}
    +		
    +		private int getIndexLeaf() {
    +			return nextIndex;
    +		}
    +		
    +		private void setNode(XMSSNode node) {
    +			tailNode = node;
    +			height = node.getHeight();
    +			if (height == initialHeight) {
    +				finished = true;
    +			}
    +		}
    +
    +		private boolean isFinished() {
    +			return finished;
    +		}
    +		
    +		private boolean isInitialized() {
    +			return initialized;
    +		}
    +	}
    +
    +	private transient XMSS xmss;
    +	private transient WOTSPlus wotsPlus;
    +	private final int treeHeight;
    +	private int k;
    +	private XMSSNode root;
    +	private List<XMSSNode> authenticationPath;
    +	private Map<Integer, ArrayDeque<XMSSNode>> retain;
    +	private Stack<XMSSNode> stack;
    +	private List<TreeHash> treeHashInstances;
    +	private Map<Integer, XMSSNode> keep;
    +	private int index;
    +	
    +	protected BDS(XMSS xmss) {
    +		super();
    +		if (xmss == null) {
    +			throw new NullPointerException("xmss == null");
    +		}
    +		this.xmss = xmss;
    +		wotsPlus = xmss.getWOTSPlus();
    +		treeHeight = xmss.getParams().getHeight();
    +		k = xmss.getParams().getK();
    +		if (k > treeHeight || k < 2 || ((treeHeight - k) % 2) != 0) {
    +			throw new IllegalArgumentException("illegal value for BDS parameter k");
    +		}
    +		authenticationPath = new ArrayList<XMSSNode>();
    +		retain = new TreeMap<Integer, ArrayDeque<XMSSNode>>();
    +		stack = new Stack<XMSSNode>();
    +		initializeTreeHashInstances();
    +		keep = new TreeMap<Integer, XMSSNode>();
    +		index = 0;
    +	}
    +	
    +	private void initializeTreeHashInstances() {
    +		treeHashInstances = new ArrayList<TreeHash>();
    +		for (int height = 0; height < (treeHeight - k); height++) {
    +			treeHashInstances.add(new TreeHash(height));
    +		}
    +	}
    +	
    +	protected XMSSNode initialize(OTSHashAddress otsHashAddress) {
    +		if (otsHashAddress == null) {
    +			throw new NullPointerException("otsHashAddress == null");
    +		}
    +		/* prepare addresses */
    +		LTreeAddress lTreeAddress = new LTreeAddress();
    +		lTreeAddress.setLayerAddress(otsHashAddress.getLayerAddress());
    +		lTreeAddress.setTreeAddress(otsHashAddress.getTreeAddress());
    +		HashTreeAddress hashTreeAddress = new HashTreeAddress();
    +		hashTreeAddress.setLayerAddress(otsHashAddress.getLayerAddress());
    +		hashTreeAddress.setTreeAddress(otsHashAddress.getTreeAddress());
    +		
    +		/* iterate indexes */
    +		for (int indexLeaf = 0; indexLeaf < (1 << treeHeight); indexLeaf++) {
    +			/* generate leaf */
    +			otsHashAddress.setOTSAddress(indexLeaf);
    +			/* import WOTSPlusSecretKey as its needed to calculate the public key on the fly */
    +			wotsPlus.importKeys(xmss.getWOTSPlusSecretKey(otsHashAddress), xmss.getPublicSeed());
    +			WOTSPlusPublicKey wotsPlusPublicKey = wotsPlus.getPublicKey(otsHashAddress);
    +			lTreeAddress.setLTreeAddress(indexLeaf);
    +			XMSSNode node = xmss.lTree(wotsPlusPublicKey, lTreeAddress);
    +			
    +			hashTreeAddress.setTreeHeight(0);
    +			hashTreeAddress.setTreeIndex(indexLeaf);
    +			while (!stack.isEmpty() && stack.peek().getHeight() == node.getHeight()) {
    +				/* add to authenticationPath if leafIndex == 1 */
    +				int indexOnHeight = ((int)Math.floor(indexLeaf / (1 << node.getHeight())));
    +				if (indexOnHeight == 1) {
    +					authenticationPath.add(node.clone());
    +				}
    +				/* store next right authentication node */
    +				if (indexOnHeight == 3 && node.getHeight() < (treeHeight - k)) {
    +					treeHashInstances.get(node.getHeight()).setNode(node.clone());
    +				}
    +				if (indexOnHeight >= 3 && (indexOnHeight & 1) == 1 && node.getHeight() >= (treeHeight - k) && node.getHeight() <= (treeHeight - 2)) {
    +					if (retain.get(node.getHeight()) == null) {
    +						ArrayDeque<XMSSNode> queue = new ArrayDeque<XMSSNode>();
    +						queue.add(node.clone());
    +						retain.put(node.getHeight(), queue);
    +					} else {
    +						retain.get(node.getHeight()).add(node.clone());
    +					}
    +				}
    +				hashTreeAddress.setTreeIndex((hashTreeAddress.getTreeIndex() - 1) / 2);
    +				node = xmss.randomizeHash(stack.pop(), node, hashTreeAddress);
    +				node.setHeight(node.getHeight() + 1);
    +				hashTreeAddress.setTreeHeight(hashTreeAddress.getTreeHeight() + 1);
    +			}
    +			/* push to stack */
    +			stack.push(node);
    +		}
    +		root = stack.pop();
    +		return root.clone();
    +	}
    +	
    +	protected void nextAuthenticationPath(OTSHashAddress otsHashAddress) {
    +		if (otsHashAddress == null) {
    +			throw new NullPointerException("otsHashAddress == null");
    +		}
    +		if (index > ((1 << treeHeight) - 2)) {
    +			throw new IllegalStateException("index out of bounds");
    +		}
    +		/* prepare addresses */
    +		LTreeAddress lTreeAddress = new LTreeAddress();
    +		lTreeAddress.setLayerAddress(otsHashAddress.getLayerAddress());
    +		lTreeAddress.setTreeAddress(otsHashAddress.getTreeAddress());
    +		HashTreeAddress hashTreeAddress = new HashTreeAddress();
    +		hashTreeAddress.setLayerAddress(otsHashAddress.getLayerAddress());
    +		hashTreeAddress.setTreeAddress(otsHashAddress.getTreeAddress());
    +		
    +		/* determine tau */
    +		int tau = XMSSUtil.calculateTau(index, treeHeight);
    +		
    +		/* parent of leaf on height tau+1 is a left node */
    +		if (((index >> (tau + 1)) & 1) == 0 && (tau < (treeHeight - 1))) {
    +			keep.put(tau, authenticationPath.get(tau).clone());
    +		}
    +		/* leaf is a left node */
    +		if (tau == 0) {
    +			otsHashAddress.setOTSAddress(index);
    +			/* import WOTSPlusSecretKey as its needed to calculate the public key on the fly */
    +			wotsPlus.importKeys(xmss.getWOTSPlusSecretKey(otsHashAddress), xmss.getPublicSeed());
    +			WOTSPlusPublicKey wotsPlusPublicKey = wotsPlus.getPublicKey(otsHashAddress);
    +			lTreeAddress.setLTreeAddress(index);
    +			XMSSNode node = xmss.lTree(wotsPlusPublicKey, lTreeAddress);
    +			authenticationPath.set(0, node);
    +		} else {
    +			/* add new left node on height tau to authentication path */
    +			hashTreeAddress.setTreeHeight(tau - 1);
    +			hashTreeAddress.setTreeIndex(index >> tau);
    +			XMSSNode node = xmss.randomizeHash(authenticationPath.get(tau - 1), keep.get(tau - 1), hashTreeAddress);
    +			node.setHeight(node.getHeight() + 1);
    +			authenticationPath.set(tau, node);
    +			keep.remove(tau - 1);
    +			
    +			/* add new right nodes to authentication path */
    +			for (int height = 0; height < tau; height++) {
    +				if (height < (treeHeight - k)) {
    +					authenticationPath.set(height, treeHashInstances.get(height).tailNode.clone());
    +				} else {
    +					authenticationPath.set(height, retain.get(height).pop());
    +				}
    +			}
    +			
    +			/* reinitialize treehash instances */
    +			int minHeight = Math.min(tau, treeHeight - k);
    +			for (int height = 0; height < minHeight; height++) {
    +				int startIndex = index + 1 + (3 * (1 << height));
    +				if (startIndex < (1 << treeHeight)) {
    +					treeHashInstances.get(height).initialize(startIndex);
    +				}
    +			}
    +		}
    +		
    +		/* update treehash instances */
    +		for (int i = 0; i < (treeHeight - k) >> 1; i++) {
    +			TreeHash treeHash = getTreeHashInstanceForUpdate();
    +			if (treeHash != null) {
    +				treeHash.update(otsHashAddress);
    +			}
    +		}
    +		index++;
    +	}
    +	
    +	private TreeHash getTreeHashInstanceForUpdate() {
    +		TreeHash ret = null;
    +		for (TreeHash treeHash : treeHashInstances) {
    +			if (treeHash.isFinished() || !treeHash.isInitialized()) {
    +				continue;
    +			}
    +			if (ret == null) {
    +				ret = treeHash;
    +				continue;
    +			}
    +			if (treeHash.getHeight() < ret.getHeight()) {
    +				ret = treeHash;
    +				continue;
    +			}
    +			if (treeHash.getHeight() == ret.getHeight()) {
    +				if (treeHash.getIndexLeaf() < ret.getIndexLeaf()) {
    +					ret = treeHash;
    +				}
    +			}
    +		}
    +		return ret;
    +	}
    +	
    +	protected void validate(boolean isStateForRootTree) {
    +		if (treeHeight != xmss.getParams().getHeight()) {
    +			throw new IllegalStateException("wrong height");
    +		}
    +		if (isStateForRootTree) {
    +			if (!XMSSUtil.compareByteArray(root.getValue(), xmss.getRoot())) {
    +				throw new IllegalStateException("root in BDS state does not match root of public / private key");
    +			}
    +		}
    +		if (authenticationPath == null) {
    +			throw new IllegalStateException("authenticationPath == null");
    +		}
    +		if (retain == null) {
    +			throw new IllegalStateException("retain == null");
    +		}
    +		if (stack == null) {
    +			throw new IllegalStateException("stack == null");
    +		}
    +		if (treeHashInstances == null) {
    +			throw new IllegalStateException("treeHashInstances == null");
    +		}
    +		if (keep == null) {
    +			throw new IllegalStateException("keep == null");
    +		}
    +		if (!XMSSUtil.isIndexValid(treeHeight, index)) {
    +			throw new IllegalStateException("index in BDS state out of bounds");
    +		}
    +	}
    +	
    +	protected int getTreeHeight() {
    +		return treeHeight;
    +	}
    +	
    +	protected XMSSNode getRoot() {
    +		return root.clone();
    +	}
    +	
    +	protected List<XMSSNode> getAuthenticationPath() {
    +		List<XMSSNode> authenticationPath = new ArrayList<XMSSNode>();
    +		for (XMSSNode node : this.authenticationPath) {
    +			authenticationPath.add(node.clone());
    +		}
    +		return authenticationPath;
    +	}
    +	
    +	protected void setXMSS(XMSS xmss) {
    +		this.xmss = xmss;
    +		this.wotsPlus = xmss.getWOTSPlus();
    +	}
    +	
    +	protected int getIndex() {
    +		return index;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/HashTreeAddress.java+69 0 added
    @@ -0,0 +1,69 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.text.ParseException;
    +
    +/**
    + * 
    + * XMSS Hash Tree address.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class HashTreeAddress extends XMSSAddress {
    +	
    +	private static final int TYPE = 0x02;
    +	private static final int PADDING = 0x00;
    +	
    +	private int padding;
    +	private int treeHeight;
    +	private int treeIndex;
    +	
    +	public HashTreeAddress() {
    +		super(TYPE);
    +		padding = PADDING;
    +	}
    +
    +	@Override
    +	public void parseByteArray(byte[] address) throws ParseException {
    +		int type = XMSSUtil.bytesToIntBigEndian(address, 12);
    +		if (type != TYPE) {
    +			throw new ParseException("type needs to be " + TYPE, 12);
    +		}
    +		setType(type);
    +		int padding = XMSSUtil.bytesToIntBigEndian(address, 16);
    +		if (padding != PADDING) {
    +			throw new ParseException("padding needs to be " + PADDING, 16);
    +		}
    +		treeHeight = XMSSUtil.bytesToIntBigEndian(address, 20);
    +		treeIndex = XMSSUtil.bytesToIntBigEndian(address, 24);
    +		super.parseByteArray(address);
    +	}
    +	
    +	@Override
    +	public byte[] toByteArray() {
    +		byte[] byteRepresentation = getByteRepresentation();
    +		XMSSUtil.intToBytesBigEndianOffset(byteRepresentation, padding, 16);
    +		XMSSUtil.intToBytesBigEndianOffset(byteRepresentation, treeHeight, 20);
    +		XMSSUtil.intToBytesBigEndianOffset(byteRepresentation, treeIndex, 24);
    +		return super.toByteArray();
    +	}
    +	
    +	public int getPadding() {
    +		return padding;
    +	}
    +	
    +	public int getTreeHeight() {
    +		return treeHeight;
    +	}
    +
    +	public void setTreeHeight(int treeHeight) {
    +		this.treeHeight = treeHeight;
    +	}
    +
    +	public int getTreeIndex() {
    +		return treeIndex;
    +	}
    +
    +	public void setTreeIndex(int treeIndex) {
    +		this.treeIndex = treeIndex;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/KeyedHashFunctions.java+107 0 added
    @@ -0,0 +1,107 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import org.bouncycastle.crypto.Digest;
    +import org.bouncycastle.crypto.Xof;
    +
    +/**
    + * Crypto functions for XMSS.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class KeyedHashFunctions {
    +
    +	private Digest digest;
    +	private int digestSize;
    +	
    +	public KeyedHashFunctions(Digest digest, int digestSize) {
    +		super();
    +		if (digest == null) {
    +			throw new NullPointerException("digest == null");
    +		}
    +		this.digest = digest;
    +		this.digestSize = digestSize;
    +	}
    +	
    +	private byte[] coreDigest(int fixedValue, byte[] key, byte[] index) {
    +		byte[] buffer = new byte[digestSize + key.length + index.length];
    +		byte[] in = XMSSUtil.toBytesBigEndian(fixedValue, digestSize);
    +		/* fill first n byte of out buffer */
    +		for (int i = 0; i < in.length; i++) {
    +			buffer[i] = in[i];
    +		}
    +		/* add key */
    +		for (int i = 0; i < key.length; i++) {
    +			buffer[in.length + i] = key[i];
    +		}
    +		/* add index */
    +		for (int i = 0; i < index.length; i++) {
    +			buffer[in.length + key.length + i] = index[i];
    +		}
    +		digest.update(buffer, 0, buffer.length);
    +		byte[] out = new byte[digestSize];
    +		if (digest instanceof Xof) {
    +			((Xof) digest).doFinal(out, 0, digestSize);
    +		} else {
    +			digest.doFinal(out, 0);
    +		}
    +		return out;
    +	}
    +	
    +	public byte[] F(byte[] key, byte[] in) {
    +		if (key.length != digestSize) {
    +			throw new IllegalArgumentException("wrong key length");
    +		}
    +		if (in.length != digestSize) {
    +			throw new IllegalArgumentException("wrong in length");
    +		}
    +		return coreDigest(0, key, in);
    +	}
    +	
    +	public byte[] H(byte[] key, byte[] in) {
    +		if (key.length != digestSize) {
    +			throw new IllegalArgumentException("wrong key length");
    +		}
    +		if (in.length != (2 * digestSize)) {
    +			throw new IllegalArgumentException("wrong in length");
    +		}
    +		return coreDigest(1, key, in);
    +	}
    +	
    +	public byte[] H(byte[] in, byte[] pubSeed, XMSSAddress addr) {
    +		if (pubSeed.length != digestSize) {
    +			throw new IllegalArgumentException("wrong key length");
    +		}
    +		addr.setKeyAndMask(0);
    +		byte[] key = PRF(pubSeed, addr.toByteArray());
    +		addr.setKeyAndMask(1);
    +		byte[] bitmask = PRF(pubSeed, addr.toByteArray());
    +		addr.setKeyAndMask(2);
    +		byte[] bitmask2 = PRF(pubSeed, addr.toByteArray());
    +		byte[] tmpMask = new byte[2 * digestSize];
    +		for (int i = 0; i < digestSize; i++) {
    +			tmpMask[i] = (byte)(in[i] ^ bitmask[i]);
    +		}
    +		for (int i = 0; i < digestSize; i++) {
    +			tmpMask[i+digestSize] = (byte)(in[i + digestSize] ^ bitmask2[i]);
    +		}
    +		byte[] result = coreDigest(1, key, tmpMask);
    +		return result;
    +	}
    +	
    +	public byte[] HMsg(byte[] key, byte[] in) {
    +		if (key.length != (3 * digestSize)) {
    +			throw new IllegalArgumentException("wrong key length");
    +		}
    +		return coreDigest(2, key, in);
    +	}
    +	
    +	public byte[] PRF(byte[] key, byte[] address) {
    +		if (key.length != digestSize) {
    +			throw new IllegalArgumentException("wrong key length");
    +		}
    +		if (address.length != 32) {
    +			throw new IllegalArgumentException("wrong address length");
    +		}
    +		return coreDigest(3, key, address);
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/LTreeAddress.java+67 0 added
    @@ -0,0 +1,67 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.text.ParseException;
    +
    +/**
    + * 
    + * XMSS L-tree address.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class LTreeAddress extends XMSSAddress {
    +	
    +	private static final int TYPE = 0x01;
    +	private int lTreeAddress;
    +	private int treeHeight;
    +	private int treeIndex;
    +	
    +	public LTreeAddress() {
    +		super(TYPE);
    +	}
    +
    +	@Override
    +	public void parseByteArray(byte[] address) throws ParseException {
    +		int type = XMSSUtil.bytesToIntBigEndian(address, 12);
    +		if (type != TYPE) {
    +			throw new ParseException("type needs to be " + TYPE, 12);
    +		}
    +		setType(type);
    +		lTreeAddress = XMSSUtil.bytesToIntBigEndian(address, 16);
    +		treeHeight = XMSSUtil.bytesToIntBigEndian(address, 20);
    +		treeIndex = XMSSUtil.bytesToIntBigEndian(address, 24);
    +		super.parseByteArray(address);
    +	}
    +
    +	@Override
    +	public byte[] toByteArray() {
    +		byte[] byteRepresentation = getByteRepresentation();
    +		XMSSUtil.intToBytesBigEndianOffset(byteRepresentation, lTreeAddress, 16);
    +		XMSSUtil.intToBytesBigEndianOffset(byteRepresentation, treeHeight, 20);
    +		XMSSUtil.intToBytesBigEndianOffset(byteRepresentation, treeIndex, 24);
    +		return super.toByteArray();
    +	}
    +	
    +	public int getLTreeAddress() {
    +		return lTreeAddress;
    +	}
    +
    +	public void setLTreeAddress(int lTreeAddress) {
    +		this.lTreeAddress = lTreeAddress;
    +	}
    +
    +	public int getTreeHeight() {
    +		return treeHeight;
    +	}
    +
    +	public void setTreeHeight(int treeHeight) {
    +		this.treeHeight = treeHeight;
    +	}
    +
    +	public int getTreeIndex() {
    +		return treeIndex;
    +	}
    +
    +	public void setTreeIndex(int treeIndex) {
    +		this.treeIndex = treeIndex;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/NullPRNG.java+25 0 added
    @@ -0,0 +1,25 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.security.SecureRandom;
    +
    +/**
    + * Implementation of null PRNG returning zeroes only.
    + * For testing purposes only(!).
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class NullPRNG extends SecureRandom {
    +
    +	private static final long serialVersionUID = 1L;
    +
    +	public NullPRNG() {
    +		super();
    +	}
    +	
    +	@Override
    +	public void nextBytes(byte[] bytes) {
    +		for (int i = 0; i < bytes.length; i++) {
    +			bytes[i] = 0x00;
    +		}
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/OTSHashAddress.java+67 0 added
    @@ -0,0 +1,67 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.text.ParseException;
    +
    +/**
    + * 
    + * OTS Hash address.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class OTSHashAddress extends XMSSAddress {
    +	
    +	private static final int TYPE = 0x00;
    +	private int otsAddress;
    +	private int chainAddress;
    +	private int hashAddress;
    +	
    +	public OTSHashAddress() {
    +		super(TYPE);
    +	}
    +	
    +	@Override
    +	public void parseByteArray(byte[] address) throws ParseException {
    +		int type = XMSSUtil.bytesToIntBigEndian(address, 12);
    +		if (type != TYPE) {
    +			throw new ParseException("type needs to be " + TYPE, 12);
    +		}
    +		setType(type);
    +		otsAddress = XMSSUtil.bytesToIntBigEndian(address, 16);
    +		chainAddress = XMSSUtil.bytesToIntBigEndian(address, 20);
    +		hashAddress = XMSSUtil.bytesToIntBigEndian(address, 24);
    +		super.parseByteArray(address);
    +	}
    +	
    +	@Override
    +	public byte[] toByteArray() {
    +		byte[] byteRepresentation = getByteRepresentation();
    +		XMSSUtil.intToBytesBigEndianOffset(byteRepresentation, otsAddress, 16);
    +		XMSSUtil.intToBytesBigEndianOffset(byteRepresentation, chainAddress, 20);
    +		XMSSUtil.intToBytesBigEndianOffset(byteRepresentation, hashAddress, 24);
    +		return super.toByteArray();
    +	}
    +	
    +	public int getOTSAddress() {
    +		return otsAddress;
    +	}
    +
    +	public void setOTSAddress(int otsAddress) {
    +		this.otsAddress = otsAddress;
    +	}
    +
    +	public int getChainAddress() {
    +		return chainAddress;
    +	}
    +
    +	public void setChainAddress(int chainAddress) {
    +		this.chainAddress = chainAddress;
    +	}
    +
    +	public int getHashAddress() {
    +		return hashAddress;
    +	}
    +
    +	public void setHashAddress(int hashAddress) {
    +		this.hashAddress = hashAddress;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/WOTSPlus.java+329 0 added
    @@ -0,0 +1,329 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.util.ArrayList;
    +import java.util.List;
    +
    +/**
    + * This class implements the WOTS+ one-time signature system
    + * as described in draft-irtf-cfrg-xmss-hash-based-signatures-06.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class WOTSPlus {
    +
    +	/**
    +	 * WOTS+ parameters.
    +	 */
    +	private WOTSPlusParameters params;
    +	/**
    +	 * Randomization functions.
    +	 */
    +	private KeyedHashFunctions khf;
    +	/**
    +	 * WOTS+ secret key seed.
    +	 */
    +	private byte[] secretKeySeed;
    +	/**
    +	 * WOTS+ public seed.
    +	 */
    +	private byte[] publicSeed;
    +
    +	/**
    +	 * Constructs a new WOTS+ one-time signature system based on the given WOTS+ parameters.
    +	 * @param params Parameters for WOTSPlus object.
    +	 */
    +	protected WOTSPlus(WOTSPlusParameters params) {
    +		super();
    +		if (params == null) {
    +			throw new NullPointerException("params == null");
    +		}
    +		this.params = params;
    +		int n = params.getDigestSize();
    +		khf = new KeyedHashFunctions(params.getDigest(), n);
    +		secretKeySeed = new byte[n];
    +		publicSeed = new byte[n];
    +	}
    +
    +	/**
    +	 * Import keys to WOTS+ instance.
    +	 * @param secretKeySeed Secret key seed.
    +	 * @param publicSeed Public seed.
    +	 */
    +	protected void importKeys(byte[] secretKeySeed, byte[] publicSeed) {
    +		if (secretKeySeed == null) {
    +			throw new NullPointerException("secretKeySeed == null");
    +		}
    +		if (secretKeySeed.length != params.getDigestSize()) {
    +			throw new IllegalArgumentException("size of secretKeySeed needs to be equal to size of digest");
    +		}
    +		if (publicSeed == null) {
    +			throw new NullPointerException("publicSeed == null");
    +		}
    +		if (publicSeed.length != params.getDigestSize()) {
    +			throw new IllegalArgumentException("size of publicSeed needs to be equal to size of digest");
    +		}
    +		this.secretKeySeed = secretKeySeed;
    +		this.publicSeed = publicSeed;
    +	}
    +
    +	/**
    +	 * Creates a signature for the n-byte messageDigest.
    +	 * @param messageDigest Digest to sign.
    +	 * @param otsHashAddress OTS hash address for randomization.
    +	 * @return WOTS+ signature.
    +	 */
    +	protected WOTSPlusSignature sign(byte[] messageDigest, OTSHashAddress otsHashAddress) {
    +		if (messageDigest == null) {
    +			throw new NullPointerException("messageDigest == null");
    +		}
    +		if (messageDigest.length != params.getDigestSize()) {
    +			throw new IllegalArgumentException("size of messageDigest needs to be equal to size of digest");
    +		}
    +		if (otsHashAddress == null) {
    +			throw new NullPointerException("otsHashAddress == null");
    +		}
    +		List<Integer> baseWMessage = convertToBaseW(messageDigest, params.getWinternitzParameter(), params.getLen1());
    +		/* create checksum */
    +		int checksum = 0;
    +		for (int i = 0; i < params.getLen1(); i++) {
    +			checksum += params.getWinternitzParameter() - 1 - baseWMessage.get(i);
    +		}
    +		checksum <<= (8 - ((params.getLen2() * XMSSUtil.log2(params.getWinternitzParameter())) % 8));
    +		int len2Bytes = (int)Math.ceil((double)(params.getLen2() * XMSSUtil.log2(params.getWinternitzParameter())) / 8);
    +		List<Integer> baseWChecksum = convertToBaseW(XMSSUtil.toBytesBigEndian(checksum, len2Bytes), params.getWinternitzParameter(), params.getLen2());
    +		
    +		/* msg || checksum */
    +		baseWMessage.addAll(baseWChecksum);
    +
    +		/* create signature */
    +		byte[][] signature = new byte[params.getLen()][];
    +		for (int i = 0; i < params.getLen(); i++) {
    +			otsHashAddress.setChainAddress(i);
    +			signature[i] = chain(expandSecretKeySeed(i), 0, baseWMessage.get(i), otsHashAddress);
    +		}
    +		WOTSPlusSignature wotsPlusSig = new WOTSPlusSignature(params);
    +		wotsPlusSig.setSignature(signature);
    +		return wotsPlusSig;
    +	}
    +	
    +	/**
    +	 * Verifies signature on message.
    +	 * @param messageDigest The digest that was signed.
    +	 * @param signature Signature on digest.
    +	 * @param otsHashAddress OTS hash address for randomization.
    +	 * @return true if signature was correct false else.
    +	 */
    +	protected boolean verifySignature(byte[] messageDigest, WOTSPlusSignature signature, OTSHashAddress otsHashAddress) {
    +		if (messageDigest == null) {
    +			throw new NullPointerException("messageDigest == null");
    +		}
    +		if (messageDigest.length != params.getDigestSize()) {
    +			throw new IllegalArgumentException("size of messageDigest needs to be equal to size of digest");
    +		}
    +		if (signature == null) {
    +			throw new NullPointerException("signature == null");
    +		}
    +		if (otsHashAddress == null) {
    +			throw new NullPointerException("otsHashAddress == null");
    +		}
    +		byte[][] tmpPublicKey = getPublicKeyFromSignature(messageDigest, signature, otsHashAddress).toByteArray();
    +		/* compare values */
    +		return XMSSUtil.compareByteArray(tmpPublicKey, getPublicKey(otsHashAddress).toByteArray()) ? true : false;
    +	}
    +
    +	/**
    +	 * Calculates a public key based on digest and signature.
    +	 * @param messageDigest The digest that was signed.
    +	 * @param signature Signarure on digest.
    +	 * @param otsHashAddress OTS hash address for randomization.
    +	 * @return WOTS+ public key derived from digest and signature.
    +	 */
    +	protected WOTSPlusPublicKey getPublicKeyFromSignature(byte[] messageDigest, WOTSPlusSignature signature, OTSHashAddress otsHashAddress) {
    +		if (messageDigest == null) {
    +			throw new NullPointerException("messageDigest == null");
    +		}
    +		if (messageDigest.length != params.getDigestSize()) {
    +			throw new IllegalArgumentException("size of messageDigest needs to be equal to size of digest");
    +		}
    +		if (signature == null) {
    +			throw new NullPointerException("signature == null");
    +		}
    +		if (otsHashAddress == null) {
    +			throw new NullPointerException("otsHashAddress == null");
    +		}
    +		List<Integer> baseWMessage = convertToBaseW(messageDigest, params.getWinternitzParameter(), params.getLen1());
    +		/* create checksum */
    +		int checksum = 0;
    +		for (int i = 0; i < params.getLen1(); i++) {
    +			checksum += params.getWinternitzParameter() - 1 - baseWMessage.get(i);
    +		}
    +		checksum <<= (8 - ((params.getLen2() * XMSSUtil.log2(params.getWinternitzParameter())) % 8));
    +		int len2Bytes = (int)Math.ceil((double)(params.getLen2() * XMSSUtil.log2(params.getWinternitzParameter())) / 8);
    +		List<Integer> baseWChecksum = convertToBaseW(XMSSUtil.toBytesBigEndian(checksum, len2Bytes), params.getWinternitzParameter(), params.getLen2());
    +		
    +		/* msg || checksum */
    +		baseWMessage.addAll(baseWChecksum);
    +		
    +		byte[][] publicKey = new byte[params.getLen()][];
    +		for (int i = 0; i < params.getLen(); i++) {
    +			otsHashAddress.setChainAddress(i);
    +			publicKey[i] = chain(signature.toByteArray()[i], baseWMessage.get(i), params.getWinternitzParameter() - 1 - baseWMessage.get(i), otsHashAddress);
    +		}
    +		WOTSPlusPublicKey wotsPlusPublicKey = new WOTSPlusPublicKey(params);
    +		wotsPlusPublicKey.setPublicKey(publicKey);
    +		return wotsPlusPublicKey;
    +	}
    +	
    +	/**
    +	 * Computes an iteration of F on an n-byte input using outputs of PRF.
    +	 * @param startHash Starting point.
    +	 * @param startIndex Start index.
    +	 * @param steps Steps to take.
    +	 * @param otsHashAddress OTS hash address for randomization.
    +	 * @return Value obtained by iterating F for steps times on input startHash, using the outputs of PRF.
    +	 */
    +	private byte[] chain(byte[] startHash, int startIndex, int steps, OTSHashAddress otsHashAddress) {
    +		int n = params.getDigestSize();
    +		if (startHash == null) {
    +			throw new NullPointerException("startHash == null");
    +		}
    +		if (startHash.length != n) {
    +			throw new IllegalArgumentException("startHash needs to be " + n + "bytes");
    +		}
    +		if (otsHashAddress == null) {
    +			throw new NullPointerException("otsHashAddress == null");
    +		}
    +		if (otsHashAddress.toByteArray() == null) {
    +			throw new NullPointerException("otsHashAddress byte array == null");
    +		}
    +		if ((startIndex + steps) > params.getWinternitzParameter() - 1) {
    +			throw new IllegalArgumentException("max chain length must not be greater than w");
    +		}
    +		
    +		if (steps == 0) {
    +			return startHash;
    +		}
    +		
    +		byte[] tmp = chain(startHash, startIndex, steps - 1, otsHashAddress);
    +		otsHashAddress.setHashAddress(startIndex + steps - 1);
    +		otsHashAddress.setKeyAndMask(0);
    +		byte[] key = khf.PRF(publicSeed, otsHashAddress.toByteArray());
    +		otsHashAddress.setKeyAndMask(1);
    +		byte[] bitmask = khf.PRF(publicSeed, otsHashAddress.toByteArray());
    +		byte[] tmpMasked = new byte[n];
    +		for (int i = 0; i < n; i++) {
    +			tmpMasked[i] = (byte)(tmp[i] ^ bitmask[i]);
    +		}
    +		tmp = khf.F(key, tmpMasked);
    +		return tmp;
    +	}
    +	
    +	/**
    +	 * Obtain base w values from Input.
    +	 * @param messageDigest Input data.
    +	 * @param w Base.
    +	 * @param outLength Length of output.
    +	 * @return outLength-length list of base w integers. 
    +	 */
    +	private List<Integer> convertToBaseW(byte[] messageDigest, int w, int outLength) {
    +		if (messageDigest == null) {
    +			throw new NullPointerException("msg == null");
    +		}
    +		if (w != 4 && w != 16) {
    +			throw new IllegalArgumentException("w needs to be 4 or 16");
    +		}
    +		int logW = XMSSUtil.log2(w);
    +		if (outLength > ((8 * messageDigest.length) / logW)) {
    +			throw new IllegalArgumentException("outLength too big");
    +		}
    +		
    +		ArrayList<Integer> res = new ArrayList<Integer>();
    +		for (int i = 0; i < messageDigest.length; i++) {
    +			for (int j = 8 - logW; j >= 0; j -= logW) {
    +				res.add((messageDigest[i] >> j) & (w-1));
    +				if (res.size() == outLength) {
    +					return res;
    +				}
    +			}
    +		}
    +		return res;
    +	}
    +	
    +	/**
    +	 * Derive private key at index from secret key seed.
    +	 * @param index Index.
    +	 * @return Private key at index.
    +	 */
    +	private byte[] expandSecretKeySeed(int index) {
    +		if (index < 0 || index >= params.getLen()) {
    +			throw new IllegalArgumentException("index out of bounds");
    +		}
    +		return khf.PRF(secretKeySeed, XMSSUtil.toBytesBigEndian(index, 32));
    +	}
    +	
    +	/**
    +	 * Getter parameters.
    +	 * @return params.
    +	 */
    +	protected WOTSPlusParameters getParams() {
    +		return params;
    +	}
    +	
    +	/**
    +	 * Getter keyed hash functions.
    +	 * @return keyed hash functions.
    +	 */
    +	protected KeyedHashFunctions getKhf() {
    +		return khf;
    +	}
    +
    +	/**
    +	 * Getter secret key seed.
    +	 * @return secret key seed.
    +	 */
    +	protected byte[] getSecretKeySeed() {
    +		return secretKeySeed;
    +	}
    +
    +	/**
    +	 * Getter public seed.
    +	 * @return public seed.
    +	 */
    +	protected byte[] getPublicSeed() {
    +		return publicSeed;
    +	}
    +	
    +	/**
    +	 * Getter private key.
    +	 * @return WOTS+ private key.
    +	 */
    +	protected WOTSPlusPrivateKey getPrivateKey() {
    +		byte[][] privateKey = new byte[params.getLen()][];
    +		for (int i = 0; i < privateKey.length; i++) {
    +			privateKey[i] = expandSecretKeySeed(i);
    +		}
    +		WOTSPlusPrivateKey wotsPlusPrivateKey = new WOTSPlusPrivateKey(params);
    +		wotsPlusPrivateKey.setPrivateKey(privateKey);
    +		return wotsPlusPrivateKey;
    +	}
    +	
    +	/**
    +	 * Calculates a new public key based on the state of secretKeySeed, publicSeed and otsHashAddress.
    +	 * @param otsHashAddress OTS hash address for randomization.
    +	 * @return WOTS+ public key.
    +	 */
    +	protected WOTSPlusPublicKey getPublicKey(OTSHashAddress otsHashAddress) {
    +		if (otsHashAddress == null) {
    +			throw new NullPointerException("otsHashAddress == null");
    +		}
    +		byte[][] publicKey = new byte[params.getLen()][];
    +		/* derive public key from secretKeySeed */
    +		for (int i = 0; i < params.getLen(); i++) {
    +			otsHashAddress.setChainAddress(i);
    +			publicKey[i] = chain(expandSecretKeySeed(i), 0, params.getWinternitzParameter() - 1, otsHashAddress);
    +		}
    +		WOTSPlusPublicKey wotsPlusPublicKey = new WOTSPlusPublicKey(params);
    +		wotsPlusPublicKey.setPublicKey(publicKey);
    +		return wotsPlusPublicKey;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/WOTSPlusOid.java+86 0 added
    @@ -0,0 +1,86 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.util.Collections;
    +import java.util.HashMap;
    +import java.util.Map;
    +
    +/**
    + * WOTS+ OID class.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class WOTSPlusOid implements XMSSOidInterface {
    +
    +	/**
    +	 * XMSS OID lookup table.
    +	 */
    +	private static final Map<String, WOTSPlusOid> oidLookupTable;
    +	static {
    +		Map<String, WOTSPlusOid> map = new HashMap<String, WOTSPlusOid>();
    +		map.put(createKey("SHA-256", 32, 16, 67), new WOTSPlusOid(0x01000001, "WOTSP_SHA2-256_W16"));
    +		map.put(createKey("SHA-512", 64, 16, 131), new WOTSPlusOid(0x02000002, "WOTSP_SHA2-512_W16"));
    +		map.put(createKey("SHAKE128", 32, 16, 67), new WOTSPlusOid(0x03000003, "WOTSP_SHAKE128_W16"));
    +		map.put(createKey("SHAKE256", 64, 16, 131), new WOTSPlusOid(0x04000004, "WOTSP_SHAKE256_W16"));
    +		oidLookupTable = Collections.unmodifiableMap(map);
    +	}
    +	
    +	/**
    +	 * OID.
    +	 */
    +	private int oid;
    +	/**
    +	 * String representation of OID.
    +	 */
    +	private String stringRepresentation;
    +
    +	/**
    +	 * Constructor...
    +	 * @param oid OID.
    +	 * @param stringRepresentation String representation of OID.
    +	 */
    +	private WOTSPlusOid(int oid, String stringRepresentation) {
    +		super();
    +		this.oid = oid;
    +		this.stringRepresentation = stringRepresentation;
    +	}
    +	
    +	/**
    +	 * Lookup OID.
    +	 * @param algorithmName Algorithm name.
    +	 * @param winternitzParameter Winternitz parameter.
    +	 * @return WOTS+ OID if parameters were found, null else.
    +	 */
    +	protected static WOTSPlusOid lookup(String algorithmName, int digestSize, int winternitzParameter, int len) {
    +		if (algorithmName == null) {
    +			throw new NullPointerException("algorithmName == null");
    +		}
    +		return oidLookupTable.get(createKey(algorithmName, digestSize, winternitzParameter, len));
    +	}
    +	
    +	/**
    +	 * Create a key based on parameters.
    +	 * @param algorithmName Algorithm name.
    +	 * @param winternitzParameter Winternitz Parameter.
    +	 * @return String representation of parameters for lookup table.
    +	 */
    +	private static String createKey(String algorithmName, int digestSize, int winternitzParameter, int len) {
    +		if (algorithmName == null) {
    +			throw new NullPointerException("algorithmName == null");
    +		}
    +		return algorithmName + "-" + digestSize + "-" + winternitzParameter + "-" + len;
    +	}
    +
    +	/**
    +	 * Getter OID.
    +	 * @return OID.
    +	 */
    +	@Override
    +	public int getOid() {
    +		return oid;
    +	}
    +
    +	@Override
    +	public String toString() {
    +		return stringRepresentation;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/WOTSPlusParameters.java+126 0 added
    @@ -0,0 +1,126 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.security.InvalidParameterException;
    +
    +import org.bouncycastle.crypto.Digest;
    +
    +/**
    + * Parameters for the WOTS+ one-time signature system as described in draft-irtf-cfrg-xmss-hash-based-signatures-06.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class WOTSPlusParameters {
    +
    +	/**
    +	 * OID.
    +	 */
    +	private XMSSOidInterface oid;
    +	/**
    +	 * Digest used in WOTS+.
    +	 */
    +	private Digest digest;
    +	/**
    +	 * The message digest size.
    +	 */
    +	private int digestSize;
    +	/**
    +	 * The Winternitz parameter (currently fixed to 16).
    +	 */
    +	private int winternitzParameter;
    +	/**
    +	 * The number of n-byte string elements in a WOTS+ secret key, public key, and signature.
    +	 */
    +	private int len;
    +	/**
    +	 * len1.
    +	 */
    +	private int len1;
    +	/**
    +	 * len2.
    +	 */
    +	private int len2;
    +	
    +	/**
    +	 * Constructor...
    +	 * @param digest The digest used for WOTS+.
    +	 */
    +	protected WOTSPlusParameters(Digest digest) {
    +		super();
    +		if (digest == null) {
    +			throw new NullPointerException("digest == null");
    +		}
    +		this.digest = digest;
    +		digestSize = XMSSUtil.getDigestSize(digest);
    +		winternitzParameter = 16;
    +		calculateLen();
    +		oid = WOTSPlusOid.lookup(digest.getAlgorithmName(), digestSize, winternitzParameter, len);
    +		if (oid == null) {
    +			throw new InvalidParameterException();
    +		}
    +	}
    +	
    +	/**
    +	 * Sets the len values from the message digest size and Winternitz parameter.
    +	 */
    +	private void calculateLen() {
    +		len1 = (int)Math.ceil((double)(8 * digestSize) / XMSSUtil.log2(winternitzParameter));
    +		len2 = (int)Math.floor(XMSSUtil.log2(len1 * (winternitzParameter - 1)) / XMSSUtil.log2(winternitzParameter)) + 1;
    +		len = len1 + len2;
    +	}
    +	
    +	/**
    +	 * Getter OID.
    +	 * @return WOTS+ OID.
    +	 */
    +	protected XMSSOidInterface getOid() {
    +		return oid;
    +	}
    +	
    +	/**
    +	 * Getter digest.
    +	 * @return digest.
    +	 */
    +	protected Digest getDigest() {
    +		return digest;
    +	}
    +	
    +	/**
    +	 * Getter digestSize.
    +	 * @return digestSize.
    +	 */
    +	protected int getDigestSize() {
    +		return digestSize;
    +	}
    +	
    +	/**
    +	 * Getter WinternitzParameter.
    +	 * @return winternitzParameter.
    +	 */
    +	protected int getWinternitzParameter() {
    +		return winternitzParameter;
    +	}
    +	
    +	/**
    +	 * Getter len.
    +	 * @return len.
    +	 */
    +	public int getLen() {
    +		return len;
    +	}
    +	
    +	/**
    +	 * Getter len1.
    +	 * @return len1.
    +	 */
    +	protected int getLen1() {
    +		return len1;
    +	}
    +	
    +	/**
    +	 * Getter len2.
    +	 * @return len2.
    +	 */
    +	protected int getLen2() {
    +		return len2;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/WOTSPlusPrivateKey.java+47 0 added
    @@ -0,0 +1,47 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +/**
    + * WOTS+ private key.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class WOTSPlusPrivateKey {
    +
    +	private WOTSPlusParameters params;
    +	private byte[][] privateKey;
    +	
    +	protected WOTSPlusPrivateKey(WOTSPlusParameters params) {
    +		super();
    +		if (params == null) {
    +			throw new NullPointerException("params == null");
    +		}
    +		this.params = params;
    +		privateKey = new byte[params.getLen()][params.getDigestSize()];
    +	}
    +	
    +	public byte[][] getPrivateKey() {
    +		return privateKey;
    +	}
    +	
    +	public void setPrivateKey(byte[][] privateKey) {
    +		if (privateKey == null) {
    +			throw new NullPointerException("privateKey == null");
    +		}
    +		if (XMSSUtil.hasNullPointer(privateKey)) {
    +			throw new NullPointerException("privateKey byte array == null");
    +		}
    +		if (privateKey.length != params.getLen()) {
    +			throw new IllegalArgumentException("wrong privateKey format");
    +		}
    +		for (int i = 0; i < privateKey.length; i++) {
    +			if (privateKey[i].length != params.getDigestSize()) {
    +				throw new IllegalArgumentException("wrong privateKey format");
    +			}
    +		}
    +		this.privateKey = privateKey;
    +	}
    +
    +	public byte[][] toByteArray() {
    +		return XMSSUtil.cloneArray(privateKey);
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/WOTSPlusPublicKey.java+47 0 added
    @@ -0,0 +1,47 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +/**
    + * WOTS+ public key.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class WOTSPlusPublicKey {
    +
    +	private WOTSPlusParameters params;
    +	private byte[][] publicKey;
    +	
    +	protected WOTSPlusPublicKey(WOTSPlusParameters params) {
    +		super();
    +		if (params == null) {
    +			throw new NullPointerException("params == null");
    +		}
    +		this.params = params;
    +		publicKey = new byte[params.getLen()][params.getDigestSize()];
    +	}
    +	
    +	public byte[][] getPublicKey() {
    +		return publicKey;
    +	}
    +	
    +	public void setPublicKey(byte[][] publicKey) {
    +		if (publicKey == null) {
    +			throw new NullPointerException("publicKey == null");
    +		}
    +		if (XMSSUtil.hasNullPointer(publicKey)) {
    +			throw new NullPointerException("publicKey byte array == null");
    +		}
    +		if (publicKey.length != params.getLen()) {
    +			throw new IllegalArgumentException("wrong publicKey size");
    +		}
    +		for (int i = 0; i < publicKey.length; i++) {
    +			if (publicKey[i].length != params.getDigestSize()) {
    +				throw new IllegalArgumentException("wrong publicKey format");
    +			}
    +		}
    +		this.publicKey = publicKey;
    +	}
    +	
    +	public byte[][] toByteArray() {
    +		return XMSSUtil.cloneArray(publicKey);
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/WOTSPlusSignature.java+47 0 added
    @@ -0,0 +1,47 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +/**
    + * WOTS+ signature.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class WOTSPlusSignature {
    +
    +	private WOTSPlusParameters params;
    +	private byte[][] signature;
    +	
    +	protected WOTSPlusSignature(WOTSPlusParameters params) {
    +		super();
    +		if (params == null) {
    +			throw new NullPointerException("params == null");
    +		}
    +		this.params = params;
    +		signature = new byte[params.getLen()][params.getDigestSize()];
    +	}
    +	
    +	public byte[][] getSignature() {
    +		return signature;
    +	}
    +	
    +	public void setSignature(byte[][] signature) {
    +		if (signature == null) {
    +			throw new NullPointerException("signature == null");
    +		}
    +		if (XMSSUtil.hasNullPointer(signature)) {
    +			throw new NullPointerException("signature byte array == null");
    +		}
    +		if (signature.length != params.getLen()) {
    +			throw new IllegalArgumentException("wrong signature size");
    +		}
    +		for (int i = 0; i < signature.length; i++) {
    +			if (signature[i].length != params.getDigestSize()) {
    +				throw new IllegalArgumentException("wrong signature format");
    +			}
    +		}
    +		this.signature = signature;
    +	}
    +	
    +	public byte[][] toByteArray() {
    +		return XMSSUtil.cloneArray(signature);
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSAddress.java+76 0 added
    @@ -0,0 +1,76 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.text.ParseException;
    +
    +/**
    + * 
    + * XMSS Address.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public abstract class XMSSAddress {
    +
    +	private int layerAddress;
    +	private long treeAddress;
    +	private int type;
    +	private int keyAndMask;
    +	private byte[] byteRepresentation;
    +	
    +	protected XMSSAddress(int type) {
    +		this.type = type;
    +		byteRepresentation = new byte[32];
    +	}
    +	
    +	protected void parseByteArray(byte[] address) throws ParseException {
    +		if (address.length != 32) {
    +			throw new IllegalArgumentException("address needs to be 32 byte");
    +		}
    +		layerAddress = XMSSUtil.bytesToIntBigEndian(address, 0);
    +		treeAddress = XMSSUtil.bytesToLongBigEndian(address, 4);
    +		keyAndMask = XMSSUtil.bytesToIntBigEndian(address, 28);
    +	}
    +
    +	public byte[] toByteArray() {
    +		XMSSUtil.intToBytesBigEndianOffset(byteRepresentation, layerAddress, 0);
    +		XMSSUtil.longToBytesBigEndianOffset(byteRepresentation, treeAddress, 4);
    +		XMSSUtil.intToBytesBigEndianOffset(byteRepresentation, type, 12);
    +		XMSSUtil.intToBytesBigEndianOffset(byteRepresentation, keyAndMask, 28);
    +		return byteRepresentation;
    +	}
    +	
    +	public int getLayerAddress() {
    +		return layerAddress;
    +	}
    +
    +	public void setLayerAddress(int layerAddress) {
    +		this.layerAddress = layerAddress;
    +	}
    +
    +	public long getTreeAddress() {
    +		return treeAddress;
    +	}
    +
    +	public void setTreeAddress(long treeAddress) {
    +		this.treeAddress = treeAddress;
    +	}
    +
    +	public int getType() {
    +		return type;
    +	}
    +	
    +	protected void setType(int type) {
    +		this.type = type;
    +	}
    +
    +	public long getKeyAndMask() {
    +		return keyAndMask;
    +	}
    +
    +	public void setKeyAndMask(int keyAndMask) {
    +		this.keyAndMask = keyAndMask;
    +	}
    +	
    +	protected byte[] getByteRepresentation() {
    +		return byteRepresentation;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSS.java+502 0 added
    @@ -0,0 +1,502 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.io.IOException;
    +import java.security.SecureRandom;
    +import java.text.ParseException;
    +
    +/**
    + * XMSS.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSS {
    +
    +	/**
    +	 * XMSS parameters.
    +	 */
    +	private XMSSParameters params;
    +	/**
    +	 * WOTS+ instance.
    +	 */
    +	private WOTSPlus wotsPlus;
    +	/**
    +	 * PRNG.
    +	 */
    +	private SecureRandom prng;
    +	/**
    +	 * Randomization functions.
    +	 */
    +	private KeyedHashFunctions khf;
    +	/**
    +	 * XMSS private key.
    +	 */
    +	private XMSSPrivateKey privateKey;
    +	/**
    +	 * XMSS public key.
    +	 */
    +	private XMSSPublicKey publicKey;
    +	/**
    +	 * BDS.
    +	 */
    +	private BDS bdsState;
    +	
    +	/**
    +	 * XMSS constructor...
    +	 * @param params XMSSParameters.
    +	 */
    +	public XMSS(XMSSParameters params) {
    +		super();
    +		if (params == null) {
    +			throw new NullPointerException("params == null");
    +		}
    +		this.params = params;
    +		wotsPlus = params.getWOTSPlus();
    +		prng = params.getPRNG();
    +		khf = wotsPlus.getKhf();
    +		privateKey = new XMSSPrivateKey(params);
    +		publicKey = new XMSSPublicKey(params);
    +		bdsState = new BDS(this);
    +	}
    +
    +	/**
    +	 * Generate new keys.
    +	 */
    +	public void generateKeys() {
    +		/* generate private key */
    +		privateKey = generatePrivateKey();
    +		wotsPlus.importKeys(new byte[params.getDigestSize()], privateKey.getPublicSeed());
    +		
    +		XMSSNode root = bdsState.initialize(new OTSHashAddress());
    +		privateKey.setRoot(root.getValue());
    +		
    +		/* generate public key */
    +		publicKey = new XMSSPublicKey(params);
    +		publicKey.setRoot(root.getValue());
    +		publicKey.setPublicSeed(getPublicSeed());
    +	}
    +	
    +
    +	/**
    +	 * Generate an XMSS private key.
    +	 * @return XMSS private key.
    +	 */
    +	private XMSSPrivateKey generatePrivateKey() {
    +		int n = params.getDigestSize();
    +		byte[] publicSeed = new byte[n];
    +		prng.nextBytes(publicSeed);
    +		byte[] secretKeySeed = new byte[n];
    +		prng.nextBytes(secretKeySeed);
    +		byte[] secretKeyPRF = new byte[n];
    +		prng.nextBytes(secretKeyPRF);
    +		
    +		XMSSPrivateKey privateKey = new XMSSPrivateKey(params);
    +		privateKey.setPublicSeed(publicSeed);
    +		privateKey.setSecretKeySeed(secretKeySeed);
    +		privateKey.setSecretKeyPRF(secretKeyPRF);
    +		return privateKey;
    +	}
    +
    +	/**
    +	 * Import state.
    +	 * @param privateKey XMSS private key.
    +	 * @param publicKey XMSS public key.
    +	 * @param bdsState BDS state.
    +	 * @throws IOException 
    +	 * @throws ClassNotFoundException 
    +	 */
    +	public void importState(byte[] privateKey, byte[] publicKey, byte[] bdsState) throws ParseException, ClassNotFoundException, IOException {
    +		if (privateKey == null) {
    +			throw new NullPointerException("privateKey == null");
    +		}
    +		if (publicKey == null) {
    +			throw new NullPointerException("publicKey == null");
    +		}
    +		if (bdsState == null) {
    +			throw new NullPointerException("bdsState == null");
    +		}
    +		/* import keys */
    +		importKeys(privateKey, publicKey);
    +		
    +		/* import BDS state */
    +		BDS bdsImport = (BDS)XMSSUtil.deserialize(bdsState);
    +		bdsImport.setXMSS(this);
    +		bdsImport.validate(true);
    +		this.bdsState = bdsImport;
    +	}
    +	
    +	protected void importKeys(byte[] privateKey, byte[] publicKey) throws ParseException {
    +		if (privateKey == null) {
    +			throw new NullPointerException("privateKey == null");
    +		}
    +		if (publicKey == null) {
    +			throw new NullPointerException("publicKey == null");
    +		}
    +		/* validate private / public key */
    +		XMSSPrivateKey tmpPrivateKey = new XMSSPrivateKey(params);
    +		tmpPrivateKey.parseByteArray(privateKey);
    +		XMSSPublicKey tmpPublicKey = new XMSSPublicKey(params);
    +		tmpPublicKey.parseByteArray(publicKey);
    +		if (!XMSSUtil.compareByteArray(tmpPrivateKey.getRoot(), tmpPublicKey.getRoot())) {
    +			throw new IllegalStateException("root of private key and public key do not match");
    +		}
    +		if (!XMSSUtil.compareByteArray(tmpPrivateKey.getPublicSeed(), tmpPublicKey.getPublicSeed())) {
    +			throw new IllegalStateException("public seed of private key and public key do not match");
    +		}
    +		/* import */
    +		this.privateKey = tmpPrivateKey;
    +		this.publicKey = tmpPublicKey;
    +		wotsPlus.importKeys(new byte[params.getDigestSize()], this.privateKey.getPublicSeed());
    +	}
    +
    +	/**
    +	 * Sign message.
    +	 * @param message Message to sign.
    +	 * @return XMSS signature on digest of message.
    +	 */
    +	public byte[] sign(byte[] message) {
    +		if (message == null) {
    +			throw new NullPointerException("message == null");
    +		}
    +		if (bdsState.getAuthenticationPath().isEmpty()) {
    +			throw new IllegalStateException("not initialized");
    +		}
    +		int index = privateKey.getIndex();
    +		if (!XMSSUtil.isIndexValid(getParams().getHeight(), index)) {
    +			throw new IllegalArgumentException("index out of bounds");
    +		}
    +		
    +		/* create (randomized keyed) messageDigest of message */
    +		byte[] random = khf.PRF(privateKey.getSecretKeyPRF(), XMSSUtil.toBytesBigEndian(index, 32));
    +		byte[] concatenated = XMSSUtil.concat(random, privateKey.getRoot(), XMSSUtil.toBytesBigEndian(index, params.getDigestSize()));
    +		byte[] messageDigest = khf.HMsg(concatenated, message);
    +		
    +		/* create signature for messageDigest */
    +		OTSHashAddress otsHashAddress = new OTSHashAddress();
    +		otsHashAddress.setOTSAddress(index);
    +		XMSSSignature signature = treeSig(messageDigest, otsHashAddress);
    +		signature.setIndex(index);
    +		signature.setRandom(random);
    +		signature.setAuthPath(bdsState.getAuthenticationPath());
    +		
    +		/* prepare authentication path for next leaf */
    +		int treeHeight = this.getParams().getHeight();
    +		if (index < ((1 << treeHeight) - 1)) {
    +			bdsState.nextAuthenticationPath(new OTSHashAddress());
    +		}
    +
    +		/* update index */
    +		privateKey.setIndex(index + 1);
    +
    +		return signature.toByteArray();
    +	}
    +	
    +	/**
    +	 * Verify an XMSS signature using the corresponding XMSS public key and a message.
    +	 * @param message Message.
    +	 * @param signature XMSS signature.
    +	 * @param publicKey XMSS public key.
    +	 * @return true if signature is valid false else.
    +	 */
    +	public boolean verifySignature(byte[] message, byte[] signature, byte[] publicKey) throws ParseException {
    +		if (message == null) {
    +			throw new NullPointerException("message == null");
    +		}
    +		if (signature == null) {
    +			throw new NullPointerException("signature == null");
    +		}
    +		if (publicKey == null) {
    +			throw new NullPointerException("publicKey == null");
    +		}
    +		/* parse signature and public key */
    +		XMSSSignature sig = new XMSSSignature(params);
    +		sig.parseByteArray(signature);
    +		XMSSPublicKey pubKey = new XMSSPublicKey(params);
    +		pubKey.parseByteArray(publicKey);
    +
    +		/* save state */
    +		int savedIndex = privateKey.getIndex();
    +		byte[] savedPublicSeed = privateKey.getPublicSeed();
    +		
    +		/* set index / public seed */
    +		int index = sig.getIndex();
    +		setIndex(index);
    +		setPublicSeed(pubKey.getPublicSeed());
    +
    +		/* reinitialize WOTS+ object */
    +		wotsPlus.importKeys(new byte[params.getDigestSize()], getPublicSeed());
    +		
    +		/* create message digest */
    +		byte[] concatenated = XMSSUtil.concat(sig.getRandom(), pubKey.getRoot(), XMSSUtil.toBytesBigEndian(index, params.getDigestSize()));
    +		byte[] messageDigest = khf.HMsg(concatenated, message);
    +		
    +		/* create addresses */
    +		OTSHashAddress otsHashAddress = new OTSHashAddress();
    +		otsHashAddress.setOTSAddress(index);
    +		
    +		/* get root from signature */
    +		XMSSNode rootNodeFromSignature = getRootNodeFromSignature(messageDigest, sig, otsHashAddress);
    +		
    +		/* reset state */
    +		setIndex(savedIndex);
    +		setPublicSeed(savedPublicSeed);
    +		return XMSSUtil.compareByteArray(rootNodeFromSignature.getValue(), pubKey.getRoot());
    +	}
    +	
    +	/**
    +	 * Export XMSS private key.
    +	 * @return XMSS private key.
    +	 */
    +	public byte[] exportPrivateKey() {
    +		return privateKey.toByteArray();
    +    }
    +
    +	/**
    +	 * Export XMSS public key.
    +	 * @return XMSS public key.
    +	 */
    +	public byte[] exportPublicKey() {
    +		return publicKey.toByteArray();
    +	}
    +	
    +	/**
    +	 * Export XMSS BDS state.
    +	 * @return XMSS BDS state.
    +	 * @throws IOException 
    +	 */
    +	public byte[] exportBDSState() throws IOException {
    +		return XMSSUtil.serialize(bdsState);
    +	}
    +	
    +	/**
    +	 * Randomization of nodes in binary tree.
    +	 * @param left Left node.
    +	 * @param right Right node.
    +	 * @param hashTreeAddress Address.
    +	 * @return Randomized hash of parent of left / right node.
    +	 */
    +	protected XMSSNode randomizeHash(XMSSNode left, XMSSNode right, XMSSAddress address) {
    +		if (left == null) {
    +			throw new NullPointerException("left == null");
    +		}
    +		if (right == null) {
    +			throw new NullPointerException("right == null");
    +		}
    +		if (left.getHeight() != right.getHeight()) {
    +			throw new IllegalStateException("height of both nodes must be equal");
    +		}
    +		if (address == null) {
    +			throw new NullPointerException("address == null");
    +		}
    +		byte[] publicSeed = getPublicSeed();
    +		address.setKeyAndMask(0);
    +		byte[] key = khf.PRF(publicSeed, address.toByteArray());
    +		address.setKeyAndMask(1);
    +		byte[] bitmask0 = khf.PRF(publicSeed, address.toByteArray());
    +		address.setKeyAndMask(2);
    +		byte[] bitmask1 = khf.PRF(publicSeed, address.toByteArray());
    +		int n = params.getDigestSize();
    +		byte[] tmpMask = new byte[2 * n];
    +		for (int i = 0; i < n; i++) {
    +			tmpMask[i] = (byte)(left.getValue()[i] ^ bitmask0[i]);
    +		}
    +		for (int i = 0; i < n; i++) {
    +			tmpMask[i+n] = (byte)(right.getValue()[i] ^ bitmask1[i]);
    +		}
    +		byte[] out = khf.H(key, tmpMask);
    +		return new XMSSNode(left.getHeight(), out);
    +	}
    +	
    +	/**
    +	 * Compresses a WOTS+ public key to a single n-byte string.
    +	 * @param publicKey WOTS+ public key to compress.
    +	 * @param address Address.
    +	 * @return Compressed n-byte string of public key.
    +	 */
    +	protected XMSSNode lTree(WOTSPlusPublicKey publicKey, LTreeAddress address) {
    +		if (publicKey == null) {
    +			throw new NullPointerException("publicKey == null");
    +		}
    +		if (address == null) {
    +			throw new NullPointerException("address == null");
    +		}
    +		int len = wotsPlus.getParams().getLen();
    +		/* duplicate public key to XMSSNode Array */
    +		byte[][] publicKeyBytes = publicKey.toByteArray();
    +		XMSSNode[] publicKeyNodes = new XMSSNode[publicKeyBytes.length];
    +		for (int i = 0; i < publicKeyBytes.length; i++) {
    +			publicKeyNodes[i] = new XMSSNode(0, publicKeyBytes[i]);
    +		}
    +		address.setTreeHeight(0);
    +		while (len > 1) {
    +			for (int i = 0; i < (int)Math.floor(len / 2); i++) {
    +				address.setTreeIndex(i);
    +				publicKeyNodes[i] = randomizeHash(publicKeyNodes[2 * i], publicKeyNodes[(2 * i) + 1], address);
    +			}
    +			if (len % 2 == 1) {
    +				publicKeyNodes[(int)Math.floor(len / 2)] = publicKeyNodes[len - 1];
    +			}
    +			len = (int)Math.ceil((double) len / 2);
    +			address.setTreeHeight(address.getTreeHeight() + 1);
    +		}
    +		return publicKeyNodes[0];
    +	}
    +	
    +	/**
    +	 * Generate a WOTS+ signature on a message without the corresponding authentication path
    +	 * @param messageDigest Message digest of length n.
    +	 * @param address OTS hash address.
    +	 * @return XMSS signature.
    +	 */
    +	protected XMSSSignature treeSig(byte[] messageDigest, OTSHashAddress otsHashAddress) {
    +		if (messageDigest.length != params.getDigestSize()) {
    +			throw new IllegalArgumentException("size of messageDigest needs to be equal to size of digest");
    +		}
    +		if (otsHashAddress == null) {
    +			throw new NullPointerException("otsHashAddress == null");
    +		}
    +		/* (re)initialize WOTS+ instance */
    +		wotsPlus.importKeys(getWOTSPlusSecretKey(otsHashAddress), getPublicSeed());
    +		/* create WOTS+ signature */
    +		WOTSPlusSignature wotsSignature = wotsPlus.sign(messageDigest, otsHashAddress);
    +		
    +		/* assemble temp signature */
    +		XMSSSignature tmpSignature = new XMSSSignature(params);
    +		tmpSignature.setSignature(wotsSignature);
    +		return tmpSignature;
    +	}
    +
    +
    +	/**
    +	 * Compute a root node from a tree signature.
    +	 * @param messageDigest Message digest.
    +	 * @param signature XMSS signature.
    +	 * @return Root node calculated from signature.
    +	 */
    +	protected XMSSNode getRootNodeFromSignature(byte[] messageDigest, XMSSReducedSignature signature, OTSHashAddress otsHashAddress) {
    +		if (messageDigest.length != params.getDigestSize()) {
    +			throw new IllegalArgumentException("size of messageDigest needs to be equal to size of digest");
    +		}
    +		if (signature == null) {
    +			throw new NullPointerException("signature == null");
    +		}
    +		if (otsHashAddress == null) {
    +			throw new NullPointerException("otsHashAddress == null");
    +		}
    +		
    +		/* prepare adresses */
    +		LTreeAddress lTreeAddress = new LTreeAddress();
    +		lTreeAddress.setLayerAddress(otsHashAddress.getLayerAddress());
    +		lTreeAddress.setTreeAddress(otsHashAddress.getTreeAddress());
    +		lTreeAddress.setLTreeAddress(otsHashAddress.getOTSAddress());
    +		HashTreeAddress hashTreeAddress = new HashTreeAddress();
    +		hashTreeAddress.setLayerAddress(otsHashAddress.getLayerAddress());
    +		hashTreeAddress.setTreeAddress(otsHashAddress.getTreeAddress());
    +		hashTreeAddress.setTreeIndex(otsHashAddress.getOTSAddress());
    +		
    +		/* calculate WOTS+ public key and compress to obtain original leaf hash */
    +		WOTSPlusPublicKey wotsPlusPK = wotsPlus.getPublicKeyFromSignature(messageDigest, signature.getSignature(), otsHashAddress);
    +		XMSSNode[] node = new XMSSNode[2];
    +		node[0] = lTree(wotsPlusPK, lTreeAddress);
    +		
    +		for (int k = 0; k < params.getHeight(); k++){
    +			hashTreeAddress.setTreeHeight(k);
    +			if (Math.floor(privateKey.getIndex() / (1 << k)) % 2 == 0) {
    +				hashTreeAddress.setTreeIndex(hashTreeAddress.getTreeIndex() / 2);
    +				node[1] = randomizeHash(node[0], signature.getAuthPath().get(k), hashTreeAddress);
    +				node[1].setHeight(node[1].getHeight() + 1);
    +			} else {
    +				hashTreeAddress.setTreeIndex((hashTreeAddress.getTreeIndex() - 1) / 2);
    +				node[1] = randomizeHash(signature.getAuthPath().get(k), node[0], hashTreeAddress);
    +				node[1].setHeight(node[1].getHeight() + 1);
    +			}
    +			node[0] = node[1];
    +		}
    +		return node[0];
    +	}
    +	
    +	/**
    +	 * Derive WOTS+ secret key for specific index according to draft.
    +	 * @param index Index.
    +	 * @return WOTS+ secret key at index.
    +	 */
    +	/*
    +	protected byte[] getWOTSPlusSecretKey(int index) {
    +		return khf.PRF(privateKey.getSecretKeySeed(), XMSSUtil.toBytesBigEndian(index, 32));
    +	}
    +	*/
    +	
    +	/**
    +	 * Derive WOTS+ secret key for specific index as in XMSS ref impl Andreas Huelsing.
    +	 * @param index Index.
    +	 * @return WOTS+ secret key at index.
    +	 */
    +	protected byte[] getWOTSPlusSecretKey(OTSHashAddress otsHashAddress) {
    +		otsHashAddress.setChainAddress(0);
    +		otsHashAddress.setHashAddress(0);
    +		otsHashAddress.setKeyAndMask(0);
    +		return khf.PRF(privateKey.getSecretKeySeed(), otsHashAddress.toByteArray());
    +	}
    +	
    +	/**
    +	 * Getter XMSS params.
    +	 * @return XMSS params.
    +	 */
    +	public XMSSParameters getParams() {
    +		return params;
    +	}
    +	
    +	/**
    +	 * Getter WOTS+.
    +	 * @return WOTS+ instance.
    +	 */
    +	protected WOTSPlus getWOTSPlus() {
    +		return wotsPlus;
    +	}
    +	
    +	protected KeyedHashFunctions getKhf() {
    +		return khf;
    +	}
    +	
    +	/**
    +	 * Getter Root.
    +	 * @return Root of binary tree.
    +	 */
    +	protected byte[] getRoot() {
    +		return privateKey.getRoot();
    +	}
    +	
    +	protected void setRoot(byte[] root) {
    +		privateKey.setRoot(root);
    +		publicKey.setRoot(root);
    +	}
    +	
    +	/**
    +	 * Getter index.
    +	 * @return Index.
    +	 */
    +	public int getIndex() {
    +		return privateKey.getIndex();
    +	}
    +	
    +	protected void setIndex(int index) {
    +		privateKey.setIndex(index);
    +	}
    +	
    +	/**
    +	 * Getter public seed.
    +	 * @return Public seed.
    +	 */
    +	protected byte[] getPublicSeed() {
    +		return privateKey.getPublicSeed();
    +	}
    +	
    +	protected void setPublicSeed(byte[] publicSeed) {
    +		privateKey.setPublicSeed(publicSeed);
    +		publicKey.setPublicSeed(publicSeed);
    +		wotsPlus.importKeys(new byte[params.getDigestSize()], publicSeed);
    +	}
    +	
    +	protected BDS getBDS() {
    +		return bdsState;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSMT.java+344 0 added
    @@ -0,0 +1,344 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.io.IOException;
    +import java.security.SecureRandom;
    +import java.text.ParseException;
    +import java.util.Map;
    +import java.util.TreeMap;
    +
    +/**
    + * XMSS^MT.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSMT {
    +
    +	private XMSSMTParameters params;
    +	private XMSS xmss;
    +	private Map<Integer, BDS> bdsState;
    +	private SecureRandom prng;
    +	private KeyedHashFunctions khf;
    +	private XMSSMTPrivateKey privateKey;
    +	private XMSSMTPublicKey publicKey;
    +	
    +
    +	public XMSSMT(XMSSMTParameters params) {
    +		super();
    +		if (params == null) {
    +			throw new NullPointerException("params == null");
    +		}
    +		this.params = params;
    +		xmss = params.getXMSS();
    +		bdsState = new TreeMap<Integer, BDS>();
    +		prng = params.getXMSS().getParams().getPRNG();
    +		khf = xmss.getKhf();
    +		privateKey = new XMSSMTPrivateKey(params);
    +		publicKey = new XMSSMTPublicKey(params);
    +	}
    +	
    +	public void generateKeys() {
    +		/* generate private key */
    +		privateKey = generatePrivateKey();
    +		
    +		/* init global xmss */
    +		XMSSPrivateKey xmssPrivateKey = new XMSSPrivateKey(xmss.getParams());
    +		xmssPrivateKey.setSecretKeySeed(privateKey.getSecretKeySeed());
    +		xmssPrivateKey.setSecretKeyPRF(privateKey.getSecretKeyPRF());
    +		xmssPrivateKey.setPublicSeed(privateKey.getPublicSeed());
    +		xmssPrivateKey.setRoot(new byte[params.getDigestSize()]);
    +
    +		XMSSPublicKey xmssPublicKey = new XMSSPublicKey(xmss.getParams());
    +		xmssPublicKey.setPublicSeed(privateKey.getPublicSeed());
    +		xmssPublicKey.setRoot(new byte[params.getDigestSize()]);
    +		
    +		/* import to xmss */
    +		try {
    +			xmss.importKeys(xmssPrivateKey.toByteArray(), xmssPublicKey.toByteArray());
    +		} catch (ParseException e) {
    +			e.printStackTrace();
    +		}
    +
    +		/* get root */
    +		int rootLayerIndex = params.getLayers() - 1;
    +		OTSHashAddress otsHashAddress = new OTSHashAddress();
    +		otsHashAddress.setLayerAddress(rootLayerIndex);
    +		otsHashAddress.setTreeAddress(0);
    +		
    +		/* store BDS instance of root xmss instance */
    +		BDS bdsRoot = new BDS(xmss);
    +		XMSSNode root = bdsRoot.initialize(otsHashAddress);
    +		bdsState.put(rootLayerIndex, bdsRoot);
    +		xmss.setRoot(root.getValue());
    +		
    +		/* set XMSS^MT root */
    +		privateKey.setRoot(xmss.getRoot());
    +		
    +		/* create XMSS^MT public key */
    +		publicKey = new XMSSMTPublicKey(params);
    +		publicKey.setPublicSeed(xmss.getPublicSeed());
    +		publicKey.setRoot(xmss.getRoot());
    +	}
    +	
    +	private XMSSMTPrivateKey generatePrivateKey() {
    +		int n = params.getDigestSize();
    +		byte[] publicSeed = new byte[n];
    +		prng.nextBytes(publicSeed);
    +		byte[] secretKeySeed = new byte[n];
    +		prng.nextBytes(secretKeySeed);
    +		byte[] secretKeyPRF = new byte[n];
    +		prng.nextBytes(secretKeyPRF);
    +		
    +		XMSSMTPrivateKey privateKey = new XMSSMTPrivateKey(params);
    +		privateKey.setPublicSeed(publicSeed);
    +		privateKey.setSecretKeySeed(secretKeySeed);
    +		privateKey.setSecretKeyPRF(secretKeyPRF);
    +		return privateKey;
    +	}
    +	
    +	public void importState(byte[] privateKey, byte[] publicKey, byte[] bdsState) throws ParseException, ClassNotFoundException, IOException {
    +		if (privateKey == null) {
    +			throw new NullPointerException("privateKey == null");
    +		}
    +		if (publicKey == null) {
    +			throw new NullPointerException("publicKey == null");
    +		}
    +		if (bdsState == null) {
    +			throw new NullPointerException("bdsState == null");
    +		}
    +		XMSSMTPrivateKey xmssMTPrivateKey = new XMSSMTPrivateKey(params);
    +		xmssMTPrivateKey.parseByteArray(privateKey);
    +		XMSSMTPublicKey xmssMTPublicKey = new XMSSMTPublicKey(params);
    +		xmssMTPublicKey.parseByteArray(publicKey);
    +		if (!XMSSUtil.compareByteArray(xmssMTPrivateKey.getRoot(), xmssMTPublicKey.getRoot())) {
    +			throw new IllegalStateException("root of private key and public key do not match");
    +		}
    +		if (!XMSSUtil.compareByteArray(xmssMTPrivateKey.getPublicSeed(), xmssMTPublicKey.getPublicSeed())) {
    +			throw new IllegalStateException("public seed of private key and public key do not match");
    +		}
    +
    +		/* init global xmss */
    +		XMSSPrivateKey xmssPrivateKey = new XMSSPrivateKey(xmss.getParams());
    +		xmssPrivateKey.setSecretKeySeed(xmssMTPrivateKey.getSecretKeySeed());
    +		xmssPrivateKey.setSecretKeyPRF(xmssMTPrivateKey.getSecretKeyPRF());
    +		xmssPrivateKey.setPublicSeed(xmssMTPrivateKey.getPublicSeed());
    +		xmssPrivateKey.setRoot(xmssMTPrivateKey.getRoot());
    +
    +		XMSSPublicKey xmssPublicKey = new XMSSPublicKey(xmss.getParams());
    +		xmssPublicKey.setPublicSeed(xmssMTPrivateKey.getPublicSeed());
    +		xmssPublicKey.setRoot(xmssMTPrivateKey.getRoot());
    +		
    +		/* import to xmss */
    +		xmss.importKeys(xmssPrivateKey.toByteArray(), xmssPublicKey.toByteArray());
    +		this.privateKey = xmssMTPrivateKey;
    +		this.publicKey = xmssMTPublicKey;
    +		
    +		/* import BDS state */
    +		@SuppressWarnings("unchecked")
    +		Map<Integer, BDS> bdsStatesImport = (TreeMap<Integer, BDS>)XMSSUtil.deserialize(bdsState);
    +		for (Integer key : bdsStatesImport.keySet()) {
    +			BDS bds = bdsStatesImport.get(key);
    +			bds.setXMSS(xmss);
    +			if (key == (params.getLayers() - 1)) {
    +				bds.validate(true);
    +			} else {
    +				bds.validate(false);
    +			}
    +		}
    +		this.bdsState = bdsStatesImport;
    +	}
    +	
    +	public byte[] sign(byte[] message) {
    +		if (message == null) {
    +			throw new NullPointerException("message == null");
    +		}
    +		if (bdsState.isEmpty()) {
    +			throw new IllegalStateException("not initialized");
    +		}
    +		long globalIndex = getIndex();
    +		int totalHeight = params.getHeight();
    +		int xmssHeight = xmss.getParams().getHeight();
    +		if (!XMSSUtil.isIndexValid(totalHeight, globalIndex)) {
    +			throw new IllegalArgumentException("index out of bounds");
    +		}
    +		XMSSMTSignature signature = new XMSSMTSignature(params);
    +		signature.setIndex(globalIndex);
    +
    +		/* compress message */
    +		byte[] random =  khf.PRF(privateKey.getSecretKeyPRF(), XMSSUtil.toBytesBigEndian(globalIndex, 32));
    +		signature.setRandom(random);
    +		byte[] concatenated = XMSSUtil.concat(random, privateKey.getRoot(), XMSSUtil.toBytesBigEndian(globalIndex, params.getDigestSize()));
    +		byte[] messageDigest = khf.HMsg(concatenated, message);
    +		
    +		/* layer 0 */
    +		long indexTree = XMSSUtil.getTreeIndex(globalIndex, xmssHeight);
    +		int indexLeaf = XMSSUtil.getLeafIndex(globalIndex, xmssHeight);
    +		
    +		/* reset xmss */
    +		xmss.setIndex(indexLeaf);
    +		xmss.setPublicSeed(getPublicSeed());
    +		
    +		/* create signature with XMSS tree on layer 0 */
    +
    +		/* adjust addresses */
    +		OTSHashAddress otsHashAddress = new OTSHashAddress();
    +		otsHashAddress.setLayerAddress(0);
    +		otsHashAddress.setTreeAddress(indexTree);
    +		otsHashAddress.setOTSAddress(indexLeaf);
    +		
    +		/* sign message digest */
    +		XMSSSignature tmpSignature = xmss.treeSig(messageDigest, otsHashAddress);
    +		/* get authentication path from BDS */
    +		if (bdsState.get(0) == null || indexLeaf == 0) {
    +			bdsState.put(0, new BDS(xmss));
    +			bdsState.get(0).initialize(otsHashAddress);
    +		}
    +		
    +		XMSSReducedSignature reducedSignature = new XMSSReducedSignature(xmss.getParams());
    +		reducedSignature.setSignature(tmpSignature.getSignature());
    +		reducedSignature.setAuthPath(bdsState.get(0).getAuthenticationPath());
    +		signature.getReducedSignatures().add(reducedSignature);
    +		
    +		/* prepare authentication path for next leaf */
    +		if (indexLeaf < ((1 << xmssHeight) - 1)) {
    +			bdsState.get(0).nextAuthenticationPath(otsHashAddress);
    +		}
    +
    +		/* loop over remaining layers */
    +		for (int layer = 1; layer < params.getLayers(); layer++) {
    +			/* get root of layer - 1 */
    +			XMSSNode root = bdsState.get(layer - 1).getRoot();
    +
    +			indexLeaf = XMSSUtil.getLeafIndex(indexTree, xmssHeight);
    +			indexTree = XMSSUtil.getTreeIndex(indexTree, xmssHeight);
    +			xmss.setIndex(indexLeaf);
    +			
    +			/* reinitialize WOTS+ object */
    +			otsHashAddress.setLayerAddress(layer);
    +			otsHashAddress.setTreeAddress(indexTree);
    +			otsHashAddress.setOTSAddress(indexLeaf);
    +			
    +			/* sign root digest of layer - 1 */
    +			tmpSignature = xmss.treeSig(root.getValue(), otsHashAddress);
    +			/* get authentication path from BDS */
    +			if (bdsState.get(layer) == null || XMSSUtil.isNewBDSInitNeeded(globalIndex, xmssHeight, layer)) {
    +				bdsState.put(layer, new BDS(xmss));
    +				bdsState.get(layer).initialize(otsHashAddress);
    +			}
    +			reducedSignature = new XMSSReducedSignature(xmss.getParams());
    +			reducedSignature.setSignature(tmpSignature.getSignature());
    +			reducedSignature.setAuthPath(bdsState.get(layer).getAuthenticationPath());
    +			signature.getReducedSignatures().add(reducedSignature);
    +			
    +			/* prepare authentication path for next leaf */
    +			if (indexLeaf < ((1 << xmssHeight) - 1) && XMSSUtil.isNewAuthenticationPathNeeded(globalIndex, xmssHeight, layer)) {
    +				bdsState.get(layer).nextAuthenticationPath(otsHashAddress);
    +			}
    +		}
    +		
    +		/* update private key */
    +		privateKey.setIndex(globalIndex + 1);
    +		
    +		return signature.toByteArray();
    +	}
    +	
    +	public boolean verifySignature(byte[] message, byte[] signature, byte[] publicKey) throws ParseException {
    +		if (message == null) {
    +			throw new NullPointerException("message == null");
    +		}
    +		if (signature == null) {
    +			throw new NullPointerException("signature == null");
    +		}
    +		if (publicKey == null) {
    +			throw new NullPointerException("publicKey == null");
    +		}
    +		/* (re)create compressed message */
    +		XMSSMTSignature sig = new XMSSMTSignature(params);
    +		sig.parseByteArray(signature);
    +		XMSSMTPublicKey pubKey = new XMSSMTPublicKey(params);
    +		pubKey.parseByteArray(publicKey);
    +		
    +		byte[] concatenated = XMSSUtil.concat(sig.getRandom(), pubKey.getRoot(), XMSSUtil.toBytesBigEndian(sig.getIndex(), params.getDigestSize()));
    +		byte[] messageDigest = khf.HMsg(concatenated, message);
    +
    +		long globalIndex = sig.getIndex();
    +		int xmssHeight = xmss.getParams().getHeight();
    +		long indexTree = XMSSUtil.getTreeIndex(globalIndex, xmssHeight);
    +		int indexLeaf = XMSSUtil.getLeafIndex(globalIndex, xmssHeight);
    +		
    +		/* adjust xmss */
    +		xmss.setIndex(indexLeaf);
    +		xmss.setPublicSeed(pubKey.getPublicSeed());
    +		
    +		/* prepare addresses */
    +		OTSHashAddress otsHashAddress = new OTSHashAddress();
    +		otsHashAddress.setLayerAddress(0);
    +		otsHashAddress.setTreeAddress(indexTree);
    +		otsHashAddress.setOTSAddress(indexLeaf);
    +		
    +		/* get root node on layer 0 */
    +		XMSSReducedSignature xmssMTSignature = sig.getReducedSignatures().get(0);
    +		XMSSNode rootNode = xmss.getRootNodeFromSignature(messageDigest, xmssMTSignature, otsHashAddress);
    +		for (int layer = 1; layer < params.getLayers(); layer++) {
    +			xmssMTSignature = sig.getReducedSignatures().get(layer);
    +			indexLeaf = XMSSUtil.getLeafIndex(indexTree, xmssHeight);
    +			indexTree = XMSSUtil.getTreeIndex(indexTree, xmssHeight);
    +			xmss.setIndex(indexLeaf);
    +			
    +			/* adjust address */
    +			otsHashAddress.setLayerAddress(layer);
    +			otsHashAddress.setTreeAddress(indexTree);
    +			otsHashAddress.setOTSAddress(indexLeaf);
    +			
    +			/* get root node */
    +			rootNode = xmss.getRootNodeFromSignature(rootNode.getValue(), xmssMTSignature, otsHashAddress);
    +		}
    +		
    +		/* compare roots */
    +		return XMSSUtil.compareByteArray(rootNode.getValue(), pubKey.getRoot());
    +	}
    +	
    +	/**
    +	 * Export XMSS^MT private key.
    +	 * @return XMSS^MT private key.
    +	 */
    +	public byte[] exportPrivateKey() {
    +		return privateKey.toByteArray();
    +	}
    +
    +	/**
    +	 * Export XMSS^MT public key.
    +	 * @return XMSS^MT public key.
    +	 */
    +	public byte[] exportPublicKey() {
    +		return publicKey.toByteArray();
    +	}
    +	
    +	/**
    +	 * Export XMSS^MT BDS state.
    +	 * @return XMSS^MT BDS state.
    +	 * @throws IOException 
    +	 */
    +	public byte[] exportBDSState() throws IOException {
    +		return XMSSUtil.serialize(bdsState);
    +	}
    +	
    +	public XMSSMTParameters getParams() {
    +		return params;
    +	}
    +	
    +	public long getIndex() {
    +		return privateKey.getIndex();
    +	}
    +
    +	protected byte[] getPublicSeed() {
    +		return privateKey.getPublicSeed();
    +	}
    +	
    +	protected Map<Integer, BDS> getBDS() {
    +		return bdsState;
    +	}
    +	
    +	protected XMSS getXMSS() {
    +		return xmss;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSMTOid.java+116 0 added
    @@ -0,0 +1,116 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.util.Collections;
    +import java.util.HashMap;
    +import java.util.Map;
    +
    +/**
    + * XMSSOid^MT class.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSMTOid implements XMSSOidInterface {
    +
    +	/**
    +	 * XMSS^MT OID lookup table.
    +	 */
    +	private static final Map<String, XMSSMTOid> oidLookupTable;
    +	static {
    +		Map<String, XMSSMTOid> map = new HashMap<String, XMSSMTOid>();
    +		map.put(createKey("SHA-256", 32, 16, 67, 20, 2), new XMSSMTOid(0x01000001, "XMSSMT_SHA2-256_W16_H20_D2"));
    +		map.put(createKey("SHA-256", 32, 16, 67, 20, 4), new XMSSMTOid(0x01000001, "XMSSMT_SHA2-256_W16_H20_D4"));
    +		map.put(createKey("SHA-256", 32, 16, 67, 40, 2), new XMSSMTOid(0x01000001, "XMSSMT_SHA2-256_W16_H40_D2"));
    +		map.put(createKey("SHA-256", 32, 16, 67, 40, 2), new XMSSMTOid(0x01000001, "XMSSMT_SHA2-256_W16_H40_D4"));
    +		map.put(createKey("SHA-256", 32, 16, 67, 40, 4), new XMSSMTOid(0x01000001, "XMSSMT_SHA2-256_W16_H40_D8"));
    +		map.put(createKey("SHA-256", 32, 16, 67, 60, 8), new XMSSMTOid(0x01000001, "XMSSMT_SHA2-256_W16_H60_D3"));
    +		map.put(createKey("SHA-256", 32, 16, 67, 60, 6), new XMSSMTOid(0x01000001, "XMSSMT_SHA2-256_W16_H60_D6"));
    +		map.put(createKey("SHA-256", 32, 16, 67, 60, 12), new XMSSMTOid(0x01000001, "XMSSMT_SHA2-256_W16_H60_D12"));
    +		map.put(createKey("SHA2-512", 64, 16, 131, 20, 2), new XMSSMTOid(0x01000001, "XMSSMT_SHA2-512_W16_H20_D2"));
    +		map.put(createKey("SHA2-512", 64, 16, 131, 20, 4), new XMSSMTOid(0x01000001, "XMSSMT_SHA2-512_W16_H20_D4"));
    +		map.put(createKey("SHA2-512", 64, 16, 131, 40, 2), new XMSSMTOid(0x01000001, "XMSSMT_SHA2-512_W16_H40_D2"));
    +		map.put(createKey("SHA2-512", 64, 16, 131, 40, 4), new XMSSMTOid(0x01000001, "XMSSMT_SHA2-512_W16_H40_D4"));
    +		map.put(createKey("SHA2-512", 64, 16, 131, 40, 8), new XMSSMTOid(0x01000001, "XMSSMT_SHA2-512_W16_H40_D8"));
    +		map.put(createKey("SHA2-512", 64, 16, 131, 60, 3), new XMSSMTOid(0x01000001, "XMSSMT_SHA2-512_W16_H60_D3"));
    +		map.put(createKey("SHA2-512", 64, 16, 131, 60, 6), new XMSSMTOid(0x01000001, "XMSSMT_SHA2-512_W16_H60_D6"));
    +		map.put(createKey("SHA2-512", 64, 16, 131, 60, 12), new XMSSMTOid(0x01000001, "XMSSMT_SHA2-512_W16_H60_D12"));
    +		map.put(createKey("SHAKE128", 32, 16, 67, 20, 2), new XMSSMTOid(0x01000001, "XMSSMT_SHAKE128_W16_H20_D2"));
    +		map.put(createKey("SHAKE128", 32, 16, 67, 20, 4), new XMSSMTOid(0x01000001, "XMSSMT_SHAKE128_W16_H20_D4"));
    +		map.put(createKey("SHAKE128", 32, 16, 67, 40, 2), new XMSSMTOid(0x01000001, "XMSSMT_SHAKE128_W16_H40_D2"));
    +		map.put(createKey("SHAKE128", 32, 16, 67, 40, 4), new XMSSMTOid(0x01000001, "XMSSMT_SHAKE128_W16_H40_D4"));
    +		map.put(createKey("SHAKE128", 32, 16, 67, 40, 8), new XMSSMTOid(0x01000001, "XMSSMT_SHAKE128_W16_H40_D8"));
    +		map.put(createKey("SHAKE128", 32, 16, 67, 60, 3), new XMSSMTOid(0x01000001, "XMSSMT_SHAKE128_W16_H60_D3"));
    +		map.put(createKey("SHAKE128", 32, 16, 67, 60, 6), new XMSSMTOid(0x01000001, "XMSSMT_SHAKE128_W16_H60_D6"));
    +		map.put(createKey("SHAKE128", 32, 16, 67, 60, 12), new XMSSMTOid(0x01000001, "XMSSMT_SHAKE128_W16_H60_D12"));
    +		map.put(createKey("SHAKE256", 64, 16, 131, 20, 2), new XMSSMTOid(0x01000001, "XMSSMT_SHAKE256_W16_H20_D2"));
    +		map.put(createKey("SHAKE256", 64, 16, 131, 20, 4), new XMSSMTOid(0x01000001, "XMSSMT_SHAKE256_W16_H20_D4"));
    +		map.put(createKey("SHAKE256", 64, 16, 131, 40, 2), new XMSSMTOid(0x01000001, "XMSSMT_SHAKE256_W16_H40_D2"));
    +		map.put(createKey("SHAKE256", 64, 16, 131, 40, 4), new XMSSMTOid(0x01000001, "XMSSMT_SHAKE256_W16_H40_D4"));
    +		map.put(createKey("SHAKE256", 64, 16, 131, 40, 8), new XMSSMTOid(0x01000001, "XMSSMT_SHAKE256_W16_H40_D8"));
    +		map.put(createKey("SHAKE256", 64, 16, 131, 60, 3), new XMSSMTOid(0x01000001, "XMSSMT_SHAKE256_W16_H60_D3"));
    +		map.put(createKey("SHAKE256", 64, 16, 131, 60, 6), new XMSSMTOid(0x01000001, "XMSSMT_SHAKE256_W16_H60_D6"));
    +		map.put(createKey("SHAKE256", 64, 16, 131, 60, 12), new XMSSMTOid(0x01000001, "XMSSMT_SHAKE256_W16_H60_D12"));
    +		oidLookupTable = Collections.unmodifiableMap(map);
    +	}
    +	
    +	/**
    +	 * OID.
    +	 */
    +	private int oid;
    +	/**
    +	 * String representation of OID.
    +	 */
    +	private String stringRepresentation;
    +	
    +	/**
    +	 * Constructor...
    +	 * @param oid OID.
    +	 * @param stringRepresentation String representation of OID.
    +	 */
    +	private XMSSMTOid(int oid, String stringRepresentation) {
    +		super();
    +		this.oid = oid;
    +		this.stringRepresentation = stringRepresentation;
    +	}
    +	
    +	/**
    +	 * Lookup OID.
    +	 * @param algorithmName Algorithm name.
    +	 * @param winternitzParameter Winternitz parameter.
    +	 * @param height Binary tree height.
    +	 * @return XMSS OID if parameters were found, null else.
    +	 */
    +	public static XMSSMTOid lookup(String algorithmName, int digestSize, int winternitzParameter, int len, int height, int layers) {
    +		if (algorithmName == null) {
    +			throw new NullPointerException("algorithmName == null");
    +		}
    +		return oidLookupTable.get(createKey(algorithmName, digestSize, winternitzParameter, len, height, layers));
    +	}
    +	
    +	/**
    +	 * Create a key based on parameters.
    +	 * @param algorithmName Algorithm name.
    +	 * @param winternitzParameter Winternitz Parameter.
    +	 * @param height Binary tree height.
    +	 * @return String representation of parameters for lookup table.
    +	 */
    +	private static String createKey(String algorithmName, int digestSize, int winternitzParameter, int len, int height, int layers) {
    +		if (algorithmName == null) {
    +			throw new NullPointerException("algorithmName == null");
    +		}
    +		return algorithmName + "-" + digestSize + "-" + winternitzParameter + "-" + len + "-" + height + "-" + layers;
    +	}
    +
    +	/**
    +	 * Getter OID.
    +	 * @return OID.
    +	 */
    +	@Override
    +	public int getOid() {
    +		return oid;
    +	}
    +
    +	@Override
    +	public String toString() {
    +		return stringRepresentation;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSMTParameters.java+77 0 added
    @@ -0,0 +1,77 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.security.SecureRandom;
    +
    +import org.bouncycastle.crypto.Digest;
    +
    +/**
    + * XMSS^MT Parameters.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + *
    + */
    +public class XMSSMTParameters {
    +	
    +	private XMSSOidInterface oid;
    +	private XMSS xmss;
    +	private int height;
    +	private int layers;
    +
    +	public XMSSMTParameters(int height, int layers, Digest digest, SecureRandom prng) {
    +		super();
    +		this.height = height;
    +		this.layers = layers;
    +		this.xmss = new XMSS(new XMSSParameters(xmssTreeHeight(height, layers), digest, prng));
    +		oid = XMSSMTOid.lookup(getDigest().getAlgorithmName(), getDigestSize(), getWinternitzParameter(), getLen(), getHeight(), layers);
    +		/*
    +		if (oid == null) {
    +			throw new InvalidParameterException();
    +		}
    +		*/
    +	}
    +	
    +	private static int xmssTreeHeight(int height, int layers) throws IllegalArgumentException {
    +		if (height < 2) {
    +			throw new IllegalArgumentException("totalHeight must be > 1");
    +		}
    +		if (height % layers != 0){
    +			throw new IllegalArgumentException("layers must divide totalHeight without remainder");
    +		}
    +		if (height / layers == 1) {
    +			throw new IllegalArgumentException("height / layers must be greater than 1");
    +		}
    +		return height / layers;
    +	}
    +
    +	public int getHeight() {
    +		return height;
    +	}
    +	
    +	public int getLayers() {
    +		return layers;
    +	}
    +	
    +	protected XMSS getXMSS() {
    +		return xmss;
    +	}
    +	
    +	protected WOTSPlus getWOTSPlus() {
    +		return xmss.getWOTSPlus();
    +	}
    +	
    +	protected Digest getDigest() {
    +		return xmss.getParams().getDigest();
    +	}
    +	
    +	protected int getDigestSize() {
    +		return xmss.getParams().getDigestSize();
    +	}
    +	
    +	protected int getWinternitzParameter() {
    +		return xmss.getParams().getWinternitzParameter();
    +	}
    +	
    +	protected int getLen() {
    +		return xmss.getWOTSPlus().getParams().getLen();
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSMTPrivateKey.java+157 0 added
    @@ -0,0 +1,157 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.text.ParseException;
    +
    +/**
    + * XMSSMT Private Key.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSMTPrivateKey implements XMSSStoreableObjectInterface {
    +	
    +	private XMSSMTParameters params;
    +	private long index;
    +	private byte[] secretKeySeed;
    +	private byte[] secretKeyPRF;
    +	private byte[] publicSeed;
    +	private byte[] root;
    +	
    +	public XMSSMTPrivateKey(XMSSMTParameters params) {
    +		super();
    +		if (params == null) {
    +			throw new NullPointerException("params == null");
    +		}
    +		this.params = params;
    +		index = 0;
    +		int n = params.getDigestSize();
    +		secretKeySeed = new byte[n];
    +		secretKeyPRF = new byte[n];
    +		publicSeed = new byte[n];
    +		root = new byte[n];
    +	}
    +	
    +	@Override
    +	public byte[] toByteArray() {
    +		/* index || secretKeySeed || secretKeyPRF || publicSeed || root */
    +		int n = params.getDigestSize();
    +		int indexSize = (int)Math.ceil(params.getHeight() / (double) 8);
    +		int secretKeySize = n;
    +		int secretKeyPRFSize = n;
    +		int publicSeedSize = n;
    +		int rootSize = n;
    +		int totalSize = indexSize + secretKeySize + secretKeyPRFSize + publicSeedSize + rootSize;
    +		byte[] out = new byte[totalSize];
    +		int position = 0;
    +		/* copy index */
    +		byte[] indexBytes = XMSSUtil.toBytesBigEndian(index, indexSize);
    +		XMSSUtil.copyBytesAtOffset(out, indexBytes, position);
    +		position += indexSize;
    +		/* copy secretKeySeed */
    +		XMSSUtil.copyBytesAtOffset(out, secretKeySeed, position);
    +		position += secretKeySize;
    +		/* copy secretKeyPRF */
    +		XMSSUtil.copyBytesAtOffset(out, secretKeyPRF, position);
    +		position += secretKeyPRFSize;
    +		/* copy publicSeed */
    +		XMSSUtil.copyBytesAtOffset(out, publicSeed, position);
    +		position += publicSeedSize;
    +		/* copy root */
    +		XMSSUtil.copyBytesAtOffset(out, root, position);
    +		return out;
    +	}
    +	
    +	@Override
    +	public void parseByteArray(byte[] in) throws ParseException {
    +		if (in == null) {
    +			throw new NullPointerException("in == null");
    +		}
    +		int n = params.getDigestSize();
    +		int totalHeight = params.getHeight();
    +		int indexSize = (int)Math.ceil(totalHeight / (double) 8);
    +		int secretKeySize = n;
    +		int secretKeyPRFSize = n;
    +		int publicSeedSize = n;
    +		int rootSize = n;
    +		int totalSize = indexSize + secretKeySize + secretKeyPRFSize + publicSeedSize + rootSize;
    +		if (in.length != totalSize) {
    +			throw new ParseException("private key has wrong size", 0);
    +		}
    +		int position = 0;
    +		index = XMSSUtil.bytesToXBigEndian(in, position, indexSize);
    +		if (!XMSSUtil.isIndexValid(totalHeight, index)) {
    +			throw new ParseException("index out of bounds", 0);
    +		}
    +		position += indexSize;
    +		secretKeySeed = XMSSUtil.extractBytesAtOffset(in, position, secretKeySize);
    +		position += secretKeySize;
    +		secretKeyPRF = XMSSUtil.extractBytesAtOffset(in, position, secretKeyPRFSize);
    +		position += secretKeyPRFSize;
    +		publicSeed = XMSSUtil.extractBytesAtOffset(in, position, publicSeedSize);
    +		position += publicSeedSize;
    +		root = XMSSUtil.extractBytesAtOffset(in, position, rootSize);
    +	}
    +	
    +	public long getIndex() {
    +		return index;
    +	}
    +
    +	public void setIndex(long index) {
    +		this.index = index;
    +	}
    +
    +	public byte[] getSecretKeySeed() {
    +		return secretKeySeed;
    +	}
    +
    +	public void setSecretKeySeed(byte[] secretKeySeed) {
    +		if (secretKeySeed == null) {
    +			throw new NullPointerException("secretKeySeed == null");
    +		}
    +		if (secretKeySeed.length != params.getDigestSize()) {
    +			throw new IllegalArgumentException("size of secretKeySeed needs to be equal size of digest");
    +		}
    +		this.secretKeySeed = secretKeySeed;
    +	}
    +
    +	public byte[] getSecretKeyPRF() {
    +		return secretKeyPRF;
    +	}
    +
    +	public void setSecretKeyPRF(byte[] secretKeyPRF) {
    +		if (secretKeyPRF == null) {
    +			throw new NullPointerException("secretKeyPRF == null");
    +		}
    +		if (secretKeyPRF.length != params.getDigestSize()) {
    +			throw new IllegalArgumentException("size of secretKeyPRF needs to be equal size of digest");
    +		}
    +		this.secretKeyPRF = secretKeyPRF;
    +	}
    +
    +	public byte[] getPublicSeed() {
    +		return publicSeed;
    +	}
    +
    +	public void setPublicSeed(byte[] publicSeed) {
    +		if (publicSeed == null) {
    +			throw new NullPointerException("publicSeed == null");
    +		}
    +		if (publicSeed.length != params.getDigestSize()) {
    +			throw new IllegalArgumentException("size of publicSeed needs to be equal size of digest");
    +		}
    +		this.publicSeed = publicSeed;
    +	}
    +
    +	public byte[] getRoot() {
    +		return root;
    +	}
    +
    +	public void setRoot(byte[] root) {
    +		if (root == null) {
    +			throw new NullPointerException("root == null");
    +		}
    +		if (root.length != params.getDigestSize()) {
    +			throw new IllegalArgumentException("size of root needs to be equal size of digest");
    +		}
    +		this.root = root;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSMTPublicKey.java+104 0 added
    @@ -0,0 +1,104 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.text.ParseException;
    +
    +/**
    + * XMSSMT Public Key.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSMTPublicKey implements XMSSStoreableObjectInterface {
    +	
    +	private int oid;
    +	private byte[] root;
    +	private byte[] publicSeed;
    +	private XMSSMTParameters params;
    +	
    +	public XMSSMTPublicKey(XMSSMTParameters params) {
    +		super();
    +		if (params == null) {
    +			throw new NullPointerException("params == null");
    +		}	
    +		this.params = params;
    +		int n = params.getDigestSize();
    +		root = new byte[n];
    +		publicSeed = new byte[n];
    +	}
    +	
    +	public byte[] toByteArray() {
    +		/* oid || root || seed */
    +		int n = params.getDigestSize();
    +		//int oidSize = 4;
    +		int rootSize = n;
    +		int publicSeedSize = n;
    +		int totalSize = rootSize + publicSeedSize;
    +		//int totalSize = oidSize + rootSize + publicSeedSize;
    +		byte[] out = new byte[totalSize];
    +		int position = 0;
    +		/* copy oid */
    +		/*
    +		XMSSUtil.intToBytesBigEndianOffset(out, oid, position);
    +		position += oidSize;
    +		*/
    +		/* copy root */
    +		XMSSUtil.copyBytesAtOffset(out, root, position);
    +		position += rootSize;
    +		/* copy public seed */
    +		XMSSUtil.copyBytesAtOffset(out, publicSeed, position);
    +		return out;
    +	}
    +	
    +	@Override
    +	public void parseByteArray(byte[] in) throws ParseException {
    +		if (in == null) {
    +			throw new NullPointerException("in == null");
    +		}
    +		int n = params.getDigestSize();
    +		//int oidSize = 4;
    +		int rootSize = n;
    +		int publicSeedSize = n;
    +		int totalSize = rootSize + publicSeedSize;
    +		if (in.length != totalSize) {
    +			throw new ParseException("public key has wrong size", 0);
    +		}
    +		int position = 0;
    +		/*
    +		oid = XMSSUtil.bytesToIntBigEndian(in, position);
    +		if (oid != params.getOid().getOid()) {
    +			throw new ParseException("wrong oid", 0);
    +		}
    +		position += oidSize;
    +		*/
    +		root = XMSSUtil.extractBytesAtOffset(in, position, rootSize);
    +		position += rootSize;
    +		publicSeed = XMSSUtil.extractBytesAtOffset(in, position, publicSeedSize);
    +	}
    +	
    +	public byte[] getRoot() {
    +		return root;
    +	}
    +	
    +	public void setRoot(byte[] root) {
    +		if (root == null) {
    +			throw new NullPointerException("root == null");
    +		}
    +		if (root.length != params.getDigestSize()) {
    +			throw new IllegalArgumentException("size of root needs to be equal size of digest");
    +		}
    +		this.root = root;
    +	}
    +
    +	public byte[] getPublicSeed() {
    +		return publicSeed;
    +	}
    +
    +	public void setPublicSeed(byte[] publicSeed) {
    +		if (publicSeed == null) {
    +			throw new NullPointerException("publicSeed == null");
    +		}
    +		if (publicSeed.length != params.getDigestSize()) {
    +			throw new IllegalArgumentException("size of publicSeed needs to be equal size of digest");
    +		}
    +		this.publicSeed = publicSeed;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSMTSignature.java+112 0 added
    @@ -0,0 +1,112 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.text.ParseException;
    +import java.util.ArrayList;
    +import java.util.List;
    +
    +/**
    + * XMSSMT Signature.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSMTSignature implements XMSSStoreableObjectInterface {
    +	
    +	private XMSSMTParameters params;
    +	private long index;
    +	private byte[] random;
    +	private List<XMSSReducedSignature> reducedSignatures;
    +	
    +	public XMSSMTSignature(XMSSMTParameters params) {
    +		super();
    +		if (params == null) {
    +			throw new NullPointerException("params == null");
    +		}
    +		this.params = params;
    +		random = new byte[params.getDigestSize()];
    +		reducedSignatures = new ArrayList<XMSSReducedSignature>();
    +	}
    +
    +	@Override
    +	public byte[] toByteArray() {
    +		/* index || random || reduced signatures */
    +		int n = params.getDigestSize();
    +		int len = params.getWOTSPlus().getParams().getLen();
    +		int indexSize = (int)Math.ceil(params.getHeight() / (double) 8);
    +		int randomSize = n;
    +		int reducedSignatureSizeSingle = ((params.getHeight() / params.getLayers()) + len) * n;
    +		int reducedSignaturesSizeTotal = reducedSignatureSizeSingle * params.getLayers();
    +		int totalSize = indexSize + randomSize + reducedSignaturesSizeTotal;
    +		byte[] out = new byte[totalSize];
    +		int position = 0;
    +		/* copy index */
    +		byte[] indexBytes = XMSSUtil.toBytesBigEndian(index, indexSize);
    +		XMSSUtil.copyBytesAtOffset(out, indexBytes, position);
    +		position += indexSize;
    +		/* copy random */
    +		XMSSUtil.copyBytesAtOffset(out, random, position);
    +		position += randomSize;
    +		/* copy reduced signatures */
    +		for(XMSSReducedSignature reducedSignature : reducedSignatures) {
    +			byte[] signature = reducedSignature.toByteArray();
    +			XMSSUtil.copyBytesAtOffset(out, signature, position);
    +			position += reducedSignatureSizeSingle;
    +		}
    +		return out;
    +	}
    +
    +	@Override
    +	public void parseByteArray(byte[] in) throws ParseException {
    +		if (in == null) {
    +			throw new NullPointerException("in == null");
    +		}
    +		int n = params.getDigestSize();
    +		int len = params.getWOTSPlus().getParams().getLen();
    +		int indexSize = (int)Math.ceil(params.getHeight() / (double) 8);
    +		int randomSize = n;
    +		int reducedSignatureSizeSingle = ((params.getHeight() / params.getLayers()) + len) * n;
    +		int reducedSignaturesSizeTotal = reducedSignatureSizeSingle * params.getLayers();
    +		int totalSize = indexSize + randomSize + reducedSignaturesSizeTotal;
    +		if (in.length != totalSize) {
    +			throw new ParseException("signature has wrong size", 0);
    +		}
    +		int position = 0;
    +		index = XMSSUtil.bytesToXBigEndian(in, position, indexSize);
    +		if (!XMSSUtil.isIndexValid(params.getHeight(), index)) {
    +			throw new ParseException("index out of bounds", 0);
    +		}
    +		position += indexSize;
    +		random = XMSSUtil.extractBytesAtOffset(in, position, randomSize);
    +		position += randomSize;
    +		reducedSignatures = new ArrayList<XMSSReducedSignature>();
    +		while (position < in.length) {
    +			XMSSReducedSignature xmssSig = new XMSSReducedSignature(params.getXMSS().getParams());
    +			xmssSig.parseByteArray(XMSSUtil.extractBytesAtOffset(in, position, reducedSignatureSizeSingle));
    +			reducedSignatures.add(xmssSig);
    +			position += reducedSignatureSizeSingle;
    +		}
    +	}
    +
    +	public long getIndex() {
    +		return index;
    +	}
    +
    +	public void setIndex(long index) {
    +		this.index = index;
    +	}
    +
    +	public byte[] getRandom() {
    +		return random;
    +	}
    +
    +	public void setRandom(byte[] random) {
    +		this.random = random;
    +	}
    +
    +	public List<XMSSReducedSignature> getReducedSignatures() {
    +		return reducedSignatures;
    +	}
    +
    +	public void setReducedSignatures(List<XMSSReducedSignature> reducedSignatures) {
    +		this.reducedSignatures = reducedSignatures;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSNode.java+43 0 added
    @@ -0,0 +1,43 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.io.Serializable;
    +
    +/**
    + * Node of the binary tree.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSNode implements Serializable {
    +
    +	private static final long serialVersionUID = 1L;
    +	
    +	private int height;
    +	private byte[] value;
    +	
    +	public XMSSNode(int height, byte[] value) {
    +		super();
    +		this.height = height;
    +		this.value = value;
    +	}
    +
    +	public int getHeight() {
    +		return height;
    +	}
    +	
    +	public void setHeight(int height) {
    +		this.height = height;
    +	}
    +
    +	public byte[] getValue() {
    +		return XMSSUtil.cloneArray(value);
    +	}
    +	
    +	public void setValue(byte[] value) {
    +		this.value = value;
    +	}
    +	
    +	@Override
    +    public XMSSNode clone() {
    +		return new XMSSNode(getHeight(), getValue());
    +    }
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSOidInterface.java+7 0 added
    @@ -0,0 +1,7 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +public interface XMSSOidInterface {
    +
    +	public int getOid();
    +	public String toString();
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSOid.java+96 0 added
    @@ -0,0 +1,96 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.util.Collections;
    +import java.util.HashMap;
    +import java.util.Map;
    +
    +/**
    + * XMSSOid class.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSOid implements XMSSOidInterface {
    +
    +	/**
    +	 * XMSS OID lookup table.
    +	 */
    +	private static final Map<String, XMSSOid> oidLookupTable;
    +	static {
    +		Map<String, XMSSOid> map = new HashMap<String, XMSSOid>();
    +		map.put(createKey("SHA-256", 32, 16, 67, 10), new XMSSOid(0x01000001, "XMSS_SHA2-256_W16_H10"));
    +		map.put(createKey("SHA-256", 32, 16, 67, 16), new XMSSOid(0x02000002, "XMSS_SHA2-256_W16_H16"));
    +		map.put(createKey("SHA-256", 32, 16, 67, 20), new XMSSOid(0x03000003, "XMSS_SHA2-256_W16_H20"));
    +		map.put(createKey("SHA-512", 64, 16, 131, 10), new XMSSOid(0x04000004, "XMSS_SHA2-512_W16_H10"));
    +		map.put(createKey("SHA-512", 64, 16, 131, 16), new XMSSOid(0x05000005, "XMSS_SHA2-512_W16_H16"));
    +		map.put(createKey("SHA-512", 64, 16, 131, 20), new XMSSOid(0x06000006, "XMSS_SHA2-512_W16_H20"));
    +		map.put(createKey("SHAKE128", 32, 16, 67, 10), new XMSSOid(0x07000007, "XMSS_SHAKE128_W16_H10"));
    +		map.put(createKey("SHAKE128", 32, 16, 67, 16), new XMSSOid(0x08000008, "XMSS_SHAKE128_W16_H16"));
    +		map.put(createKey("SHAKE128", 32, 16, 67, 20), new XMSSOid(0x09000009, "XMSS_SHAKE128_W16_H20"));
    +		map.put(createKey("SHAKE256", 64, 16, 131, 10), new XMSSOid(0x0a00000a, "XMSS_SHAKE256_W16_H10"));
    +		map.put(createKey("SHAKE256", 64, 16, 131, 16), new XMSSOid(0x0b00000b, "XMSS_SHAKE256_W16_H16"));
    +		map.put(createKey("SHAKE256", 64, 16, 131, 20), new XMSSOid(0x0c00000c, "XMSS_SHAKE256_W16_H20"));
    +		oidLookupTable = Collections.unmodifiableMap(map);
    +	}
    +	
    +	/**
    +	 * OID.
    +	 */
    +	private int oid;
    +	/**
    +	 * String representation of OID.
    +	 */
    +	private String stringRepresentation;
    +	
    +	/**
    +	 * Constructor...
    +	 * @param oid OID.
    +	 * @param stringRepresentation String representation of OID.
    +	 */
    +	private XMSSOid(int oid, String stringRepresentation) {
    +		super();
    +		this.oid = oid;
    +		this.stringRepresentation = stringRepresentation;
    +	}
    +	
    +	/**
    +	 * Lookup OID.
    +	 * @param algorithmName Algorithm name.
    +	 * @param winternitzParameter Winternitz parameter.
    +	 * @param height Binary tree height.
    +	 * @return XMSS OID if parameters were found, null else.
    +	 */
    +	public static XMSSOid lookup(String algorithmName, int digestSize, int winternitzParameter, int len, int height) {
    +		if (algorithmName == null) {
    +			throw new NullPointerException("algorithmName == null");
    +		}
    +		return oidLookupTable.get(createKey(algorithmName, digestSize, winternitzParameter, len, height));
    +	}
    +	
    +	/**
    +	 * Create a key based on parameters.
    +	 * @param algorithmName Algorithm name.
    +	 * @param winternitzParameter Winternitz Parameter.
    +	 * @param height Binary tree height.
    +	 * @return String representation of parameters for lookup table.
    +	 */
    +	private static String createKey(String algorithmName, int digestSize, int winternitzParameter, int len, int height) {
    +		if (algorithmName == null) {
    +			throw new NullPointerException("algorithmName == null");
    +		}
    +		return algorithmName + "-" + digestSize + "-" + winternitzParameter + "-" + len + "-" + height;
    +	}
    +
    +	/**
    +	 * Getter OID.
    +	 * @return OID.
    +	 */
    +	@Override
    +	public int getOid() {
    +		return oid;
    +	}
    +
    +	@Override
    +	public String toString() {
    +		return stringRepresentation;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSParameters.java+85 0 added
    @@ -0,0 +1,85 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.security.SecureRandom;
    +
    +import org.bouncycastle.crypto.Digest;
    +
    +/**
    + * XMSS Parameters.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSParameters {
    +
    +	private XMSSOidInterface oid;
    +	private WOTSPlus wotsPlus;
    +	private SecureRandom prng;
    +	private int height;
    +	private int k;
    +
    +	/**
    +	 * XMSS Constructor...
    +	 * @param height Height of tree.
    +	 * @param digest Digest to use.
    +	 * @param winternitzParameter Winternitz parameter.
    +	 */
    +	public XMSSParameters(int height, Digest digest, SecureRandom prng) {
    +		super();
    +		if (height < 2) {
    +			throw new IllegalArgumentException("height must be >= 2");
    +		}
    +		if (digest == null) {
    +			throw new NullPointerException("digest == null");
    +		}
    +		if (prng == null) {
    +			throw new NullPointerException("prng == null");
    +		}
    +		wotsPlus = new WOTSPlus(new WOTSPlusParameters(digest));
    +		this.prng = prng;
    +		this.height = height;
    +		this.k = determineMinK();
    +		oid = XMSSOid.lookup(getDigest().getAlgorithmName(), getDigestSize(), getWinternitzParameter(), wotsPlus.getParams().getLen(), height);
    +		/*
    +		if (oid == null) {
    +			throw new InvalidParameterException();
    +		}
    +		*/
    +	}
    +	
    +	private int determineMinK() {
    +		for (int k = 2; k <= height; k++) {
    +			if ((height - k) % 2 == 0) {
    +				return k;
    +			}
    +		}
    +		throw new IllegalStateException("should never happen...");
    +	}
    +	
    +	protected Digest getDigest() {
    +		return wotsPlus.getParams().getDigest();
    +	}
    +	
    +	protected SecureRandom getPRNG() {
    +		return prng;
    +	}
    +	
    +	public int getDigestSize() {
    +		return wotsPlus.getParams().getDigestSize();
    +	}
    +	
    +	public int getWinternitzParameter() {
    +		return wotsPlus.getParams().getWinternitzParameter();
    +	}
    +	
    +	public int getHeight() {
    +		return height;
    +	}
    +	
    +	protected WOTSPlus getWOTSPlus() {
    +		return wotsPlus;
    +	}
    +	
    +	protected int getK() {
    +		return k;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSPrivateKey.java+174 0 added
    @@ -0,0 +1,174 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.text.ParseException;
    +
    +/**
    + * XMSS Private Key.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSPrivateKey implements XMSSStoreableObjectInterface {
    +
    +	/**
    +	 * XMSS parameters object.
    +	 */
    +	private XMSSParameters params;
    +	/**
    +	 * Index for WOTS+ keys (randomization factor).
    +	 */
    +	private int index;
    +	/**
    +	 * Secret for the derivation of WOTS+ secret keys.
    +	 */
    +	private byte[] secretKeySeed;
    +	/**
    +	 * Secret for the randomization of message digests during signature creation.
    +	 */
    +	private byte[] secretKeyPRF;
    +	/**
    +	 * Public seed for the randomization of hashes.
    +	 */
    +	private byte[] publicSeed;
    +	/**
    +	 * Public root of binary tree.
    +	 */
    +	private byte[] root;
    +	
    +	public XMSSPrivateKey(XMSSParameters params) {
    +		super();
    +		if (params == null) {
    +			throw new NullPointerException("params == null");
    +		}
    +		this.params = params;
    +		index = 0;
    +		int n = params.getDigestSize();
    +		secretKeySeed = new byte[n];
    +		secretKeyPRF = new byte[n];
    +		publicSeed = new byte[n];
    +		root = new byte[n];
    +	}
    +	
    +	@Override
    +	public byte[] toByteArray() {
    +		/* index || secretKeySeed || secretKeyPRF || publicSeed || root */
    +		int n = params.getDigestSize();
    +		int indexSize = 4;
    +		int secretKeySize = n;
    +		int secretKeyPRFSize = n;
    +		int publicSeedSize = n;
    +		int rootSize = n;
    +		int totalSize = indexSize + secretKeySize + secretKeyPRFSize + publicSeedSize + rootSize;
    +		byte[] out = new byte[totalSize];
    +		int position = 0;
    +		/* copy index */
    +		XMSSUtil.intToBytesBigEndianOffset(out, index, position);
    +		position += indexSize;
    +		/* copy secretKeySeed */
    +		XMSSUtil.copyBytesAtOffset(out, secretKeySeed, position);
    +		position += secretKeySize;
    +		/* copy secretKeyPRF */
    +		XMSSUtil.copyBytesAtOffset(out, secretKeyPRF, position);
    +		position += secretKeyPRFSize;
    +		/* copy publicSeed */
    +		XMSSUtil.copyBytesAtOffset(out, publicSeed, position);
    +		position += publicSeedSize;
    +		/* copy root */
    +		XMSSUtil.copyBytesAtOffset(out, root, position);
    +		return out;
    +	}
    +
    +	@Override
    +	public void parseByteArray(byte[] in) throws ParseException {
    +		if (in == null) {
    +			throw new NullPointerException("in == null");
    +		}
    +		int n = params.getDigestSize();
    +		int height = params.getHeight();
    +		int indexSize = 4;
    +		int secretKeySize = n;
    +		int secretKeyPRFSize = n;
    +		int publicSeedSize = n;
    +		int rootSize = n;
    +		int totalSize = indexSize + secretKeySize + secretKeyPRFSize + publicSeedSize + rootSize;
    +		if (in.length != totalSize) {
    +			throw new ParseException("private key has wrong size", 0);
    +		}
    +		int position = 0;
    +		index = XMSSUtil.bytesToIntBigEndian(in, position);
    +		if (!XMSSUtil.isIndexValid(height, index)) {
    +			throw new ParseException("index out of bounds", 0);
    +		}
    +		position += indexSize;
    +		secretKeySeed = XMSSUtil.extractBytesAtOffset(in, position, secretKeySize);
    +		position += secretKeySize;
    +		secretKeyPRF = XMSSUtil.extractBytesAtOffset(in, position, secretKeyPRFSize);
    +		position += secretKeyPRFSize;
    +		publicSeed = XMSSUtil.extractBytesAtOffset(in, position, publicSeedSize);
    +		position += publicSeedSize;
    +		root = XMSSUtil.extractBytesAtOffset(in, position, rootSize);
    +	}
    +
    +	public int getIndex() {
    +		return index;
    +	}
    +	
    +	public void setIndex(int index) {
    +		this.index = index;
    +	}
    +	
    +	public byte[] getSecretKeySeed() {
    +		return XMSSUtil.cloneArray(secretKeySeed);
    +	}
    +	
    +	public void setSecretKeySeed(byte[] secretKeySeed) {
    +		if (secretKeySeed == null) {
    +			throw new NullPointerException("secretKeySeed == null");
    +		}
    +		if (secretKeySeed.length != params.getDigestSize()) {
    +			throw new IllegalArgumentException("size of secretKeySeed needs to be equal size of digest");
    +		}
    +		this.secretKeySeed = secretKeySeed;
    +	}
    +
    +	public byte[] getSecretKeyPRF() {
    +		return XMSSUtil.cloneArray(secretKeyPRF);
    +	}
    +	
    +	public void setSecretKeyPRF(byte[] secretKeyPRF) {
    +		if (secretKeyPRF == null) {
    +			throw new NullPointerException("secretKeyPRF == null");
    +		}
    +		if (secretKeyPRF.length != params.getDigestSize()) {
    +			throw new IllegalArgumentException("size of secretKeyPRF needs to be equal size of digest");
    +		}
    +		this.secretKeyPRF = secretKeyPRF;
    +	}
    +
    +	public byte[] getPublicSeed() {
    +		return XMSSUtil.cloneArray(publicSeed);
    +	}
    +	
    +	public void setPublicSeed(byte[] publicSeed) {
    +		if (publicSeed == null) {
    +			throw new NullPointerException("publicSeed == null");
    +		}
    +		if (publicSeed.length != params.getDigestSize()) {
    +			throw new IllegalArgumentException("size of publicSeed needs to be equal size of digest");
    +		}
    +		this.publicSeed = publicSeed;
    +	}
    +
    +	public byte[] getRoot() {
    +		return XMSSUtil.cloneArray(root);
    +	}
    +	
    +	public void setRoot(byte[] root) {
    +		if (root == null) {
    +			throw new NullPointerException("root == null");
    +		}
    +		if (root.length != params.getDigestSize()) {
    +			throw new IllegalArgumentException("size of root needs to be equal size of digest");
    +		}
    +		this.root = root;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSPublicKey.java+109 0 added
    @@ -0,0 +1,109 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.text.ParseException;
    +
    +/**
    + * XMSS Public Key.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSPublicKey implements XMSSStoreableObjectInterface {
    +
    +	/**
    +	 * XMSS parameters object.
    +	 */
    +	private int oid;
    +	private byte[] root;
    +	private byte[] publicSeed;
    +	private XMSSParameters params;
    +	
    +	public XMSSPublicKey(XMSSParameters params) {
    +		super();
    +		if (params == null) {
    +			throw new NullPointerException("params == null");
    +		}
    +		this.params = params;
    +		int n = params.getDigestSize();
    +		root = new byte[n];
    +		publicSeed = new byte[n];
    +	}
    +	
    +	@Override
    +	public byte[] toByteArray() {
    +		/* oid || root || seed */
    +		int n = params.getDigestSize();
    +		//int oidSize = 4;
    +		int rootSize = n;
    +		int publicSeedSize = n;
    +		//int totalSize = oidSize + rootSize + publicSeedSize;
    +		int totalSize = rootSize + publicSeedSize;
    +		byte[] out = new byte[totalSize];
    +		int position = 0;
    +		/* copy oid */
    +		/*
    +		XMSSUtil.intToBytesBigEndianOffset(out, oid, position);
    +		position += oidSize;
    +		*/
    +		/* copy root */
    +		XMSSUtil.copyBytesAtOffset(out, root, position);
    +		position += rootSize;
    +		/* copy public seed */
    +		XMSSUtil.copyBytesAtOffset(out, publicSeed, position);
    +		return out;
    +	}
    +
    +	@Override
    +	public void parseByteArray(byte[] in) throws ParseException {
    +		if (in == null) {
    +			throw new NullPointerException("in == null");
    +		}
    +		int n = params.getDigestSize();
    +		//int oidSize = 4;
    +		int rootSize = n;
    +		int publicSeedSize = n;
    +		//int totalSize = oidSize + rootSize + publicSeedSize;
    +		int totalSize = rootSize + publicSeedSize;
    +		if (in.length != totalSize) {
    +			throw new ParseException("public key has wrong size", 0);
    +		}
    +		int position = 0;
    +		/*
    +		oid = XMSSUtil.bytesToIntBigEndian(in, position);
    +		if (oid != xmss.getParams().getOid().getOid()) {
    +			throw new ParseException("public key not compatible with current instance parameters", 0);
    +		}
    +		position += oidSize;
    +		*/
    +		root = XMSSUtil.extractBytesAtOffset(in, position, rootSize);
    +		position += rootSize;
    +		publicSeed = XMSSUtil.extractBytesAtOffset(in, position, publicSeedSize);
    +	}
    +	
    +	public byte[] getRoot() {
    +		return XMSSUtil.cloneArray(root);
    +	}
    +	
    +	public void setRoot(byte[] root) {
    +		if (root == null) {
    +			throw new NullPointerException("root == null");
    +		}
    +		if (root.length != params.getDigestSize()) {
    +			throw new IllegalArgumentException("length of root must be equal to length of digest");
    +		}
    +		this.root = root;
    +	}
    +	
    +	public byte[] getPublicSeed() {
    +		return XMSSUtil.cloneArray(publicSeed);
    +	}
    +	
    +	public void setPublicSeed(byte[] publicSeed) {
    +		if (publicSeed == null) {
    +			throw new NullPointerException("publicSeed == null");
    +		}
    +		if (publicSeed.length != params.getDigestSize()) {
    +			throw new IllegalArgumentException("size of publicSeed needs to be equal size of digest");
    +		}
    +		this.publicSeed = publicSeed;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSReducedSignature.java+145 0 added
    @@ -0,0 +1,145 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.text.ParseException;
    +import java.util.ArrayList;
    +import java.util.List;
    +
    +/**
    + * Reduced XMSS Signature for MT variant.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSReducedSignature implements XMSSStoreableObjectInterface {
    +	
    +	/**
    +	 * XMSS object.
    +	 */
    +	private XMSSParameters params;
    +	/**
    +	 * WOTS+ signature.
    +	 */
    +	private WOTSPlusSignature signature;
    +	/**
    +	 * Authentication path.
    +	 */
    +	private List<XMSSNode> authPath;
    +	
    +	/**
    +	 * Constructor...
    +	 * @param signature The WOTS+ signature.
    +	 * @param authPath The authentication path.
    +	 */
    +	public XMSSReducedSignature(XMSSParameters params) {
    +		super();
    +		if (params == null) {
    +			throw new NullPointerException("params == null");
    +		}
    +		this.params = params;
    +		signature = new WOTSPlusSignature(params.getWOTSPlus().getParams());
    +		authPath = new ArrayList<XMSSNode>();
    +	}
    +
    +	@Override
    +	public byte[] toByteArray() {
    +		/* signature || authentication path */
    +		int n = params.getDigestSize();
    +		int signatureSize = params.getWOTSPlus().getParams().getLen() * n;
    +		int authPathSize = params.getHeight() * n;
    +		int totalSize = signatureSize + authPathSize;
    +		byte[] out = new byte[totalSize];
    +		int position = 0;
    +		/* copy signature */
    +		byte[][] signature = this.signature.toByteArray();
    +		for (int i = 0; i < signature.length; i++) {
    +			XMSSUtil.copyBytesAtOffset(out, signature[i], position);
    +			position += n;
    +		}
    +		/* copy authentication path */
    +		for (int i = 0; i < authPath.size(); i++) {
    +			byte[] value = authPath.get(i).getValue();
    +			XMSSUtil.copyBytesAtOffset(out, value, position);
    +			position += n;
    +		}
    +		return out;
    +	}
    +
    +	@Override
    +	public void parseByteArray(byte[] in) throws ParseException {
    +		if (in == null) {
    +			throw new NullPointerException("in == null");
    +		}
    +		int n = params.getDigestSize();
    +		int len = params.getWOTSPlus().getParams().getLen();
    +		int height = params.getHeight();
    +		int signatureSize = len * n;
    +		int authPathSize = height * n;
    +		int totalSize = signatureSize + authPathSize;
    +		if (in.length != totalSize) {
    +			throw new ParseException("signature has wrong size", 0);
    +		}
    +		int position = 0;
    +		byte[][] wotsPlusSignature = new byte[len][];
    +		for (int i = 0; i < wotsPlusSignature.length; i++) {
    +			wotsPlusSignature[i] = XMSSUtil.extractBytesAtOffset(in, position, n);
    +			position += n;
    +		}
    +		signature = new WOTSPlusSignature(params.getWOTSPlus().getParams());
    +		signature.setSignature(wotsPlusSignature);
    +		
    +		List<XMSSNode> nodeList = new ArrayList<XMSSNode>();
    +		for (int i = 0; i < height; i++) {
    +			nodeList.add(new XMSSNode(i, XMSSUtil.extractBytesAtOffset(in, position, n)));
    +			position += n;
    +		}
    +		authPath = nodeList;
    +	}
    +
    +	/**
    +	 * Getter params.
    +	 * @return XMSS Parameters.
    +	 */
    +	protected XMSSParameters getParams() {
    +		return params;
    +	}
    +
    +	/**
    +	 * Getter signature.
    +	 * @return WOTS+ signature.
    +	 */
    +	public WOTSPlusSignature getSignature() {
    +		return signature;
    +	}
    +	
    +	/**
    +	 * Setter WOTS+ signature
    +	 * @param signature WOTS+ signature.
    +	 */
    +	public void setSignature(WOTSPlusSignature signature) {
    +		if (signature == null) {
    +			throw new NullPointerException("signature == null");
    +		}
    +		this.signature = signature;
    +	}
    +
    +	/**
    +	 * Getter authentication path.
    +	 * @return Authentication path.
    +	 */
    +	public List<XMSSNode> getAuthPath() {
    +		return authPath;
    +	}
    +	
    +	/**
    +	 * Setter authentication path.
    +	 * @param authPath Authentication path.
    +	 */
    +	public void setAuthPath(List<XMSSNode> authPath) {
    +		if (authPath == null) {
    +			throw new NullPointerException("authPath == null");
    +		}
    +		if (authPath.size() != params.getHeight()) {
    +			throw new IllegalArgumentException("size of authPath needs to be equal to height of tree");
    +		}
    +		this.authPath = authPath;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSSignature.java+147 0 added
    @@ -0,0 +1,147 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.text.ParseException;
    +import java.util.ArrayList;
    +import java.util.List;
    +
    +/**
    + * XMSS Signature.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSSignature extends XMSSReducedSignature implements XMSSStoreableObjectInterface {
    +
    +	/**
    +	 * Index of signature.
    +	 */
    +	private int index;
    +	
    +	/**
    +	 * Random used to create digest of message.
    +	 */
    +	private byte[] random;
    +	
    +	/**
    +	 * Constructor...
    +	 * @param signature The WOTS+ signature.
    +	 * @param authPath The authentication path.
    +	 */
    +	public XMSSSignature(XMSSParameters params) {
    +		super(params);
    +		if (params == null) {
    +			throw new NullPointerException("params == null");
    +		}
    +		random = new byte[params.getDigestSize()];
    +	}
    +
    +	@Override
    +	public byte[] toByteArray() {
    +		/* index || random || signature || authentication path */
    +		int n = getParams().getDigestSize();
    +		int indexSize = 4;
    +		int randomSize = n;
    +		int signatureSize = getParams().getWOTSPlus().getParams().getLen() * n;
    +		int authPathSize = getParams().getHeight() * n;
    +		int totalSize = indexSize + randomSize + signatureSize + authPathSize;
    +		byte[] out = new byte[totalSize];
    +		int position = 0;
    +		/* copy index */
    +		XMSSUtil.intToBytesBigEndianOffset(out, index, position);
    +		position += indexSize;
    +		/* copy random */
    +		XMSSUtil.copyBytesAtOffset(out, random, position);
    +		position += randomSize;
    +		/* copy signature */
    +		byte[][] signature = getSignature().toByteArray();
    +		for (int i = 0; i < signature.length; i++) {
    +			XMSSUtil.copyBytesAtOffset(out, signature[i], position);
    +			position += n;
    +		}
    +		/* copy authentication path */
    +		for (int i = 0; i < getAuthPath().size(); i++) {
    +			byte[] value = getAuthPath().get(i).getValue();
    +			XMSSUtil.copyBytesAtOffset(out, value, position);
    +			position += n;
    +		}
    +		return out;
    +	}
    +
    +	@Override
    +	public void parseByteArray(byte[] in) throws ParseException {
    +		if (in == null) {
    +			throw new NullPointerException("in == null");
    +		}
    +		int n = getParams().getDigestSize();
    +		int len = getParams().getWOTSPlus().getParams().getLen();
    +		int height = getParams().getHeight();
    +		int indexSize = 4;
    +		int randomSize = n;
    +		int signatureSize = len * n;
    +		int authPathSize = height * n;
    +		int totalSize = indexSize + randomSize + signatureSize + authPathSize;
    +		if (in.length != totalSize) {
    +			throw new ParseException("signature has wrong size", 0);
    +		}
    +		int position = 0;
    +		index = XMSSUtil.bytesToIntBigEndian(in, position);
    +		if (!XMSSUtil.isIndexValid(height, index)) {
    +			throw new ParseException("index out of bounds", 0);
    +		}
    +		position += indexSize;
    +		random = XMSSUtil.extractBytesAtOffset(in, position, randomSize);
    +		position += randomSize;
    +		byte[][] wotsPlusSignature = new byte[len][];
    +		for (int i = 0; i < wotsPlusSignature.length; i++) {
    +			wotsPlusSignature[i] = XMSSUtil.extractBytesAtOffset(in, position, n);
    +			position += n;
    +		}
    +		WOTSPlusSignature wotsPlusSig = new WOTSPlusSignature(getParams().getWOTSPlus().getParams());
    +		wotsPlusSig.setSignature(wotsPlusSignature);
    +		setSignature(wotsPlusSig);
    +		
    +		List<XMSSNode> nodeList = new ArrayList<XMSSNode>();
    +		for (int i = 0; i < height; i++) {
    +			nodeList.add(new XMSSNode(i, XMSSUtil.extractBytesAtOffset(in, position, n)));
    +			position += n;
    +		}
    +		setAuthPath(nodeList);
    +	}
    +
    +	/**
    +	 * Getter index.
    +	 * @return index.
    +	 */
    +	public int getIndex() {
    +		return index;
    +	}
    +
    +	/**
    +	 * Setter index.
    +	 * @param index
    +	 */
    +	public void setIndex(int index) {
    +		this.index = index;
    +	}
    +
    +	/**
    +	 * Getter random.
    +	 * @return random.
    +	 */
    +	public byte[] getRandom() {
    +		return XMSSUtil.cloneArray(random);
    +	}
    +
    +	/**
    +	 * Setter random.
    +	 * @param random random.
    +	 */
    +	public void setRandom(byte[] random) {
    +		if (random == null) {
    +			throw new NullPointerException("random == null");
    +		}
    +		if (random.length != getParams().getDigestSize()) {
    +			throw new IllegalArgumentException("size of random needs to be equal to size of digest");
    +		}
    +		this.random = random;
    +	}
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSStoreableObjectInterface.java+24 0 added
    @@ -0,0 +1,24 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.text.ParseException;
    +
    +/**
    + * Interface for XMSS objects that need to be storeable as a byte array.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public interface XMSSStoreableObjectInterface {
    +
    +	/**
    +	 * Create byte representation of object.
    +	 * @return Byte representation of object.
    +	 */
    +	public byte[] toByteArray();
    +
    +	/**
    +	 * Fill object from byte representation.
    +	 * @param in Byte representation of object.
    +	 * @throws ParseException
    +	 */
    +	public void parseByteArray(byte[] in) throws ParseException;
    +}
    
  • core/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSUtil.java+390 0 added
    @@ -0,0 +1,390 @@
    +package org.bouncycastle.pqc.crypto.xmss;
    +
    +import java.io.ByteArrayInputStream;
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.io.ObjectInputStream;
    +import java.io.ObjectOutputStream;
    +
    +import org.bouncycastle.crypto.Digest;
    +import org.bouncycastle.util.encoders.Hex;
    +
    +/**
    + * 
    + * Utils for XMSS implementation.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSUtil {
    +
    +	/**
    +	 * Calculates the logarithm base 2 for a given Integer.
    +	 * @param n Number.
    +	 * @return Logarithm to base 2 of {@code n}.
    +	 */
    +    public static int log2(int n) {
    +        int log = 0;
    +        while ((n >>= 1) != 0) {
    +            log++;
    +        }
    +        return log;
    +    }
    +
    +    /**
    +     * Convert int/long to n-byte array.
    +     * @param value int/long value.
    +     * @param sizeInByte Size of byte array in byte.
    +     * @return int/long as big-endian byte array of size {@code sizeInByte}.
    +     */
    +    public static byte[] toBytesBigEndian(long value, int sizeInByte) {
    +    	byte[] out = new byte[sizeInByte];
    +    	for (int i = (sizeInByte - 1); i >= 0; i--) {
    +    		out[i] = (byte)value;
    +    		value >>>= 8;
    +    	}
    +    	return out;
    +    }
    +
    +    /**
    +     * Copy int to byte array in big-endian at specific offset.
    +     * @param Byte array.
    +     * @param Integer to put.
    +     * @param Offset in {@code in}.
    +     */
    +    public static void intToBytesBigEndianOffset(byte[] in, int value, int offset) {
    +    	if (in == null) {
    +    		throw new NullPointerException("in == null");
    +    	}
    +    	if ((in.length - offset) < 4) {
    +    		throw new IllegalArgumentException("not enough space in array");
    +    	}
    +    	in[offset] = (byte)((value >> 24) & 0xff);
    +    	in[offset + 1] = (byte)((value >> 16) & 0xff);
    +    	in[offset + 2] = (byte)((value >> 8) & 0xff);
    +    	in[offset + 3] = (byte)((value) & 0xff);
    +    }
    +    
    +    /**
    +     * Copy long to byte array in big-endian at specific offset.
    +     * @param Byte array.
    +     * @param Long to put.
    +     * @param Offset in {@code in}.
    +     */
    +    public static void longToBytesBigEndianOffset(byte[] in, long value, int offset) {
    +    	if (in == null) {
    +    		throw new NullPointerException("in == null");
    +    	}
    +    	if ((in.length - offset) < 8) {
    +    		throw new IllegalArgumentException("not enough space in array");
    +    	}
    +    	in[offset] = (byte)((value >> 56) & 0xff);
    +    	in[offset + 1] = (byte)((value >> 48) & 0xff);
    +    	in[offset + 2] = (byte)((value >> 40) & 0xff);
    +    	in[offset + 3] = (byte)((value >> 32) & 0xff);
    +    	in[offset + 4] = (byte)((value >> 24) & 0xff);
    +    	in[offset + 5] = (byte)((value >> 16) & 0xff);
    +    	in[offset + 6] = (byte)((value >> 8) & 0xff);
    +    	in[offset + 7] = (byte)((value) & 0xff);
    +    }
    +    
    +    /**
    +     * Convert from big endian byte array to int.
    +     * @param 4 byte array.
    +     * @return Integer.
    +     */
    +    public static int bytesToIntBigEndian(byte[] in, int offset) {
    +    	if (in == null) {
    +    		throw new NullPointerException("in == null");
    +    	}
    +		if ((offset + 4) > in.length) {
    +			throw new IllegalArgumentException("out of bounds");
    +		}
    +		return (int)bytesToXBigEndian(in, offset, 4);
    +	}
    +	
    +    /**
    +     * Convert from big endian byte array to long.
    +     * @param 4 byte array.
    +     * @return Long.
    +     */
    +    public static long bytesToLongBigEndian(byte[] in, int offset) {
    +    	if (in == null) {
    +    		throw new NullPointerException("in == null");
    +    	}
    +		if ((offset + 8) > in.length) {
    +			throw new IllegalArgumentException("out of bounds");
    +		}
    +		return bytesToXBigEndian(in, offset, 8);
    +	}
    +    
    +    /**
    +     * Generic convert from big endian byte array to long.
    +     * @param x-byte array
    +     * @param offset.
    +     * @param size.
    +     * @return Long.
    +     */
    +    public static long bytesToXBigEndian(byte[] in, int offset, int size) {
    +    	if (in == null) {
    +    		throw new NullPointerException("in == null");
    +    	}
    +		long res = 0;
    +		for (int i = offset; i < (offset + size); i++) {
    +		   res = (res << 8) | (in[i] & 0xff);
    +		}
    +		return res;
    +    }
    +    
    +    /**
    +     * Clone a byte array.
    +     * @param in byte array.
    +     * @return Copy of byte array.
    +     */
    +	public static byte[] cloneArray(byte[] in) {
    +    	if (in == null) {
    +    		throw new NullPointerException("in == null");
    +    	}
    +		byte[] out = new byte[in.length];
    +		for (int i = 0; i < in.length; i++) {
    +			out[i] = in[i];
    +		}
    +		return out;
    +	}
    +	
    +    /**
    +     * Clone a 2d byte array.
    +     * @param in 2d byte array.
    +     * @return Copy of 2d byte array.
    +     */
    +	public static byte[][] cloneArray(byte[][] in) {
    +    	if (hasNullPointer(in)) {
    +    		throw new NullPointerException("in has null pointers");
    +    	}
    +		byte[][] out = new byte[in.length][];
    +		for (int i = 0; i < in.length; i++) {
    +			out[i] = new byte[in[i].length];
    +			for (int j = 0; j < in[i].length; j++) {
    +				out[i][j] = in[i][j];
    +			}
    +		}
    +		return out;
    +	}
    +	
    +	/**
    +	 * Concatenates an arbitrary number of byte arrays.
    +	 * @param arrays Arrays that shall be concatenated.
    +	 * @return Concatenated array.
    +	 */
    +	public static byte[] concat(byte[]... arrays) {
    +		int totalLength = 0;
    +	    for (int i = 0; i < arrays.length; i++) {
    +	        totalLength += arrays[i].length;
    +	    }
    +	    byte[] result = new byte[totalLength];
    +	    int currentIndex = 0;
    +	    for (int i = 0; i < arrays.length; i++) {
    +	        System.arraycopy(arrays[i], 0, result, currentIndex, arrays[i].length);
    +	        currentIndex += arrays[i].length;
    +	    }
    +	    return result;
    +	}
    +	
    +	/**
    +	 * Compares two byte arrays.
    +	 * @param a byte array 1.
    +	 * @param b byte array 2.
    +	 * @return true if all values in byte array are equal false else.
    +	 */
    +	public static boolean compareByteArray(byte[] a, byte[] b) {
    +		if (a == null || b == null) {
    +			throw new NullPointerException("a or b == null");
    +		}
    +		if (a.length != b.length) {
    +			throw new IllegalArgumentException("size of a and b must be equal");
    +		}
    +		for (int i = 0; i < a.length; i++) {
    +			if (a[i] != b[i]) {
    +				return false;
    +			}
    +		}
    +		return true;
    +	}
    +	
    +	/**
    +	 * Compares two 2d-byte arrays.
    +	 * @param a 2d-byte array 1.
    +	 * @param b 2d-byte array 2.
    +	 * @return true if all values in 2d-byte array are equal false else.
    +	 */
    +	public static boolean compareByteArray(byte[][] a, byte[][] b) {
    +		if (hasNullPointer(a) || hasNullPointer(b)) {
    +			throw new NullPointerException("a or b == null");
    +		}
    +		for (int i = 0; i < a.length; i++) {
    +			if (!compareByteArray(a[i], b[i])) {
    +				return false;
    +			}
    +		}
    +		return true;
    +	}
    +	
    +	/**
    +	 * Dump content of 2d byte array.
    +	 * @param x byte array.
    +	 */
    +	public static void dumpByteArray(byte[][] x) {
    +		if (hasNullPointer(x)) {
    +			throw new NullPointerException("x has null pointers");
    +		}
    +		for (int i = 0; i < x.length; i++) {
    +			System.out.println(Hex.toHexString(x[i]));
    +		}
    +	}
    +
    +	/**
    +	 * Checks whether 2d byte array has null pointers.
    +	 * @param in 2d byte array.
    +	 * @return true if at least one null pointer is found false else.
    +	 */
    +	public static boolean hasNullPointer(byte[][] in) {
    +		if (in == null) {
    +			return true;
    +		}
    +		for (int i = 0; i < in.length; i++) {
    +			if (in[i] == null) {
    +				return true;
    +			}
    +		}
    +		return false;
    +	}
    +	
    +	/**
    +	 * Copy src byte array to dst byte array at offset.
    +	 * @param dst Destination.
    +	 * @param src Source.
    +	 * @param offset Destination offset.
    +	 */
    +	public static void copyBytesAtOffset(byte[] dst, byte[] src, int offset) {
    +		if (dst == null) {
    +			throw new NullPointerException("dst == null");
    +		}
    +		if (src == null) {
    +			throw new NullPointerException("src == null");
    +		}
    +		if (offset < 0) {
    +			throw new IllegalArgumentException("offset hast to be >= 0");
    +		}
    +		if ((src.length + offset) > dst.length) {
    +			throw new IllegalArgumentException("src length + offset must not be greater than size of destination");
    +		}
    +		for (int i = 0; i < src.length; i++) {
    +			dst[offset + i] = src[i];
    +		}
    +	}
    +	
    +	/**
    +	 * Copy length bytes at position offset from src.
    +	 * @param src Source byte array.
    +	 * @param offset Offset in source byte array.
    +	 * @param length Length of bytes to copy.
    +	 * @return New byte array.
    +	 */
    +	public static byte[] extractBytesAtOffset(byte[] src, int offset, int length) {
    +		if (src == null) {
    +			throw new NullPointerException("src == null");
    +		}
    +		if (offset < 0) {
    +			throw new IllegalArgumentException("offset hast to be >= 0");
    +		}
    +		if (length < 0) {
    +			throw new IllegalArgumentException("length hast to be >= 0");
    +		}
    +		if ((offset + length) > src.length) {
    +			throw new IllegalArgumentException("offset + length must not be greater then size of source array");
    +		}
    +		byte[] out = new byte[length];
    +		for (int i = 0; i < out.length; i++) {
    +			out[i] = src[offset + i];
    +		}
    +		return out;
    +	}
    +	
    +	/**
    +	 * Check whether an index is valid or not.
    +	 * @param height Height of binary tree.
    +	 * @param index Index to validate.
    +	 * @return true if index is valid false else.
    +	 */
    +	public static boolean isIndexValid(int height, long index) {
    +		if (index < 0) {
    +			throw new IllegalStateException("index must not be negative");
    +		}
    +		return index < (1L << height);
    +	}
    +	
    +	/**
    +	 * Determine digest size of digest.
    +	 * @param digest Digest.
    +	 * @return Digest size.
    +	 */
    +	public static int getDigestSize(Digest digest) {
    +		if (digest == null) {
    +			throw new NullPointerException("digest == null");
    +		}
    +		String algorithmName = digest.getAlgorithmName();
    +		if (algorithmName.equals("SHAKE128")) {
    +			return 32;
    +		}
    +		if (algorithmName.equals("SHAKE256")) {
    +			return 64;
    +		}
    +		return digest.getDigestSize();
    +	}
    +
    +	public static long getTreeIndex(long index, int xmssTreeHeight) {
    +		return index >> xmssTreeHeight;
    +	}
    +
    +	public static int getLeafIndex(long index, int xmssTreeHeight) {
    +		return (int)(index & ((1L << xmssTreeHeight) - 1L));
    +	}
    +	
    +	public static byte[] serialize(Object obj) throws IOException {
    +		ByteArrayOutputStream out = new ByteArrayOutputStream();
    +		ObjectOutputStream oos = new ObjectOutputStream(out);
    +		oos.writeObject(obj);
    +		oos.flush();
    +		return out.toByteArray();
    +	}
    +	
    +	public static Object deserialize(byte[] data) throws IOException, ClassNotFoundException {
    +		ByteArrayInputStream in = new ByteArrayInputStream(data);
    +		ObjectInputStream is = new ObjectInputStream(in);
    +		return is.readObject();
    +	}
    +	
    +	public static int calculateTau(int index, int height) {
    +		int tau = 0;
    +		for (int i = 0; i < height; i++) {
    +			if (((index >> i) & 1) == 0) {
    +				tau = i;
    +				break;
    +			}
    +		}
    +		return tau;
    +	}
    +	
    +	public static boolean isNewBDSInitNeeded(long globalIndex, int xmssHeight, int layer) {
    +		if (globalIndex == 0) {
    +			return false;
    +		}
    +		return (globalIndex % (long)Math.pow((1 << xmssHeight), layer + 1) == 0) ? true : false;
    +	}
    +	
    +	public static boolean isNewAuthenticationPathNeeded(long globalIndex, int xmssHeight, int layer) {
    +		if (globalIndex == 0) {
    +			return false;
    +		}
    +		return ((globalIndex + 1) % (long)Math.pow((1 << xmssHeight), layer) == 0) ? true : false;
    +	}
    +}
    
  • core/src/test/java/org/bouncycastle/pqc/crypto/test/AllTests.java+15 1 modified
    @@ -1,10 +1,11 @@
     package org.bouncycastle.pqc.crypto.test;
     
    +import org.bouncycastle.util.test.SimpleTestResult;
    +
     import junit.extensions.TestSetup;
     import junit.framework.Test;
     import junit.framework.TestCase;
     import junit.framework.TestSuite;
    -import org.bouncycastle.util.test.SimpleTestResult;
     
     public class AllTests
         extends TestCase
    @@ -20,12 +21,25 @@ public static Test suite()
     
             suite.addTestSuite(BitStringTest.class);
             suite.addTestSuite(EncryptionKeyTest.class);
    +        suite.addTestSuite(KeyedHashFunctionsTest.class);
             suite.addTestSuite(NTRUEncryptionParametersTest.class);
             suite.addTestSuite(NTRUEncryptTest.class);
             suite.addTestSuite(NTRUSignatureParametersTest.class);
             suite.addTestSuite(NTRUSignatureKeyTest.class);
             suite.addTestSuite(NTRUSignerTest.class);
             suite.addTestSuite(NTRUSigningParametersTest.class);
    +        suite.addTestSuite(XMSSAddressTest.class);
    +        suite.addTestSuite(XMSSMTPrivateKeyTest.class);
    +        suite.addTestSuite(XMSSMTPublicKeyTest.class);
    +        suite.addTestSuite(XMSSMTSignatureTest.class);
    +        suite.addTestSuite(XMSSMTTest.class);
    +        suite.addTestSuite(XMSSOidTest.class);
    +        suite.addTestSuite(XMSSPrivateKeyTest.class);
    +        suite.addTestSuite(XMSSPublicKeyTest.class);
    +        suite.addTestSuite(XMSSReducedSignatureTest.class);
    +        suite.addTestSuite(XMSSSignatureTest.class);
    +        suite.addTestSuite(XMSSTest.class);
    +        suite.addTestSuite(XMSSUtilTest.class);
             suite.addTestSuite(AllTests.SimpleTestTest.class);
     
             return new BCTestSetup(suite);
    
  • core/src/test/java/org/bouncycastle/pqc/crypto/test/KeyedHashFunctionsTest.java+74 0 added
    @@ -0,0 +1,74 @@
    +package org.bouncycastle.pqc.crypto.test;
    +
    +import java.util.Arrays;
    +
    +import org.bouncycastle.crypto.Digest;
    +import org.bouncycastle.crypto.digests.SHA256Digest;
    +import org.bouncycastle.crypto.digests.SHA512Digest;
    +import org.bouncycastle.pqc.crypto.xmss.HashTreeAddress;
    +import org.bouncycastle.pqc.crypto.xmss.KeyedHashFunctions;
    +import org.bouncycastle.pqc.crypto.xmss.LTreeAddress;
    +import org.bouncycastle.pqc.crypto.xmss.OTSHashAddress;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSAddress;
    +import org.bouncycastle.util.encoders.Hex;
    +
    +import junit.framework.TestCase;
    +
    +/**
    + * Test cases for KeyedHashFunctions class.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class KeyedHashFunctionsTest extends TestCase {
    +
    +	KeyedHashFunctions khfSHA256;
    +	KeyedHashFunctions khfSHA512;
    +	private byte[] key1;
    +	private byte[] key2;
    +	private byte[] key3;
    +	private byte[] key4;
    +	private byte[] key5;
    +	private byte[] key6;
    +	private XMSSAddress addr1;
    +	private XMSSAddress addr2;
    +	private XMSSAddress addr3;
    +	
    +	public void setUp() {
    +		Digest sha256 = new SHA256Digest();
    +		Digest sha512 = new SHA512Digest();
    +		khfSHA256 = new KeyedHashFunctions(sha256, sha256.getDigestSize());
    +		khfSHA512 = new KeyedHashFunctions(sha512, sha512.getDigestSize());
    +		key1 = new byte[32];
    +		key2 = new byte[32];
    +		key3 = new byte[32];
    +		key4 = new byte[64];
    +		key5 = new byte[64];
    +		key6 = new byte[64];
    +		Arrays.fill(key1, (byte) 0x00);
    +		Arrays.fill(key2, (byte) 0xff);
    +		Arrays.fill(key3, (byte) 0xab);
    +		Arrays.fill(key4, (byte) 0x00);
    +		Arrays.fill(key5, (byte) 0xff);
    +		Arrays.fill(key6, (byte) 0xab);
    +		addr1 = new OTSHashAddress();
    +		addr2 = new LTreeAddress();
    +		addr3 = new HashTreeAddress();
    +	}
    +	
    +	public void testPRF() {
    +		// SHA256
    +		byte[] hash = khfSHA256.PRF(key1, addr1.toByteArray());
    +		assertEquals("6945a6f13aa83e598cb8d0abebb5cddbd87e576226517f9001c1d36bb320bf80", Hex.toHexString(hash));
    +		hash = khfSHA256.PRF(key2, addr2.toByteArray());
    +		assertEquals("fd4016a59da88676579096a957312a4d12d9c35ba5a350640b5403cc71d8e181", Hex.toHexString(hash));
    +		hash = khfSHA256.PRF(key3, addr3.toByteArray());
    +		assertEquals("26a47454f97535b34b0b2aea9eec8f06a9feca6de21591302d1986823bd0b02d", Hex.toHexString(hash));
    +		// SHA512
    +		hash = khfSHA512.PRF(key4, addr1.toByteArray());
    +		assertEquals("25fc9eb157c443b49dcaf5b76d21086c79dd06fa474fd2b1046bc975855484b9618a442b4f2377a549eaa657c4a2a0dc9b7ea329a93382ef777a2ed402c88973", Hex.toHexString(hash));
    +		hash = khfSHA512.PRF(key5, addr2.toByteArray());
    +		assertEquals("6f2eb1015e70231d14e8e4ef944740c25752a4d6ef1b4f2b0bd3ce437bc8b933b3733386e688f780a829603814cc983ba97b8c852762d735925d6e5691c192a0", Hex.toHexString(hash));
    +		hash = khfSHA512.PRF(key6, addr3.toByteArray());
    +		assertEquals("296c99385cccf2a635a464e92dcc5e34046b1c2bc963caf780c624b710ce837be1b71936c140ce143d10bcb4b2a0d7c9e7e630e9edc1009fef2ec8a315ff404a", Hex.toHexString(hash));
    +	}
    +}
    
  • core/src/test/java/org/bouncycastle/pqc/crypto/test/XMSSAddressTest.java+279 0 added
    @@ -0,0 +1,279 @@
    +package org.bouncycastle.pqc.crypto.test;
    +
    +import java.text.ParseException;
    +import java.util.Arrays;
    +
    +import org.bouncycastle.pqc.crypto.xmss.HashTreeAddress;
    +import org.bouncycastle.pqc.crypto.xmss.LTreeAddress;
    +import org.bouncycastle.pqc.crypto.xmss.OTSHashAddress;
    +
    +import junit.framework.TestCase;
    +
    +/**
    + * Test cases for XMSSAddress classes.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSAddressTest extends TestCase {
    +
    +	public void testOTSHashAddressToByteArray() {
    +		OTSHashAddress address = new OTSHashAddress();
    +		assertEquals(0x00, address.getType());
    +		address.setLayerAddress(0x00);
    +		address.setTreeAddress(0x11);
    +		address.setOTSAddress(0x22);
    +		address.setChainAddress(0x33);
    +		address.setHashAddress(0x44);
    +		address.setKeyAndMask(0x55);
    +		byte[] out = address.toByteArray();
    +		assertEquals(0x00, out[0]);
    +		assertEquals(0x00, out[1]);
    +		assertEquals(0x00, out[2]);
    +		assertEquals(0x00, out[3]);
    +		assertEquals(0x00, out[4]);
    +		assertEquals(0x00, out[5]);
    +		assertEquals(0x00, out[6]);
    +		assertEquals(0x00, out[7]);
    +		assertEquals(0x00, out[8]);
    +		assertEquals(0x00, out[9]);
    +		assertEquals(0x00, out[10]);
    +		assertEquals(0x11, out[11]);
    +		assertEquals(0x00, out[12]);
    +		assertEquals(0x00, out[13]);
    +		assertEquals(0x00, out[14]);
    +		assertEquals(0x00, out[15]);
    +		assertEquals(0x00, out[16]);
    +		assertEquals(0x00, out[17]);
    +		assertEquals(0x00, out[18]);
    +		assertEquals(0x22, out[19]);
    +		assertEquals(0x00, out[20]);
    +		assertEquals(0x00, out[21]);
    +		assertEquals(0x00, out[22]);
    +		assertEquals(0x33, out[23]);
    +		assertEquals(0x00, out[24]);
    +		assertEquals(0x00, out[25]);
    +		assertEquals(0x00, out[26]);
    +		assertEquals(0x44, out[27]);
    +		assertEquals(0x00, out[28]);
    +		assertEquals(0x00, out[29]);
    +		assertEquals(0x00, out[30]);
    +		assertEquals(0x55, out[31]);
    +	}
    +	
    +	public void testLTreeAddressToByteArray() {
    +		LTreeAddress address = new LTreeAddress();
    +		assertEquals(0x01, address.getType());
    +		address.setLayerAddress(0x00);
    +		address.setTreeAddress(0x11);
    +		address.setLTreeAddress(0x22);
    +		address.setTreeHeight(0x33);
    +		address.setTreeIndex(0x44);
    +		address.setKeyAndMask(0x55);
    +		byte[] out = address.toByteArray();
    +		assertEquals(0x00, out[0]);
    +		assertEquals(0x00, out[1]);
    +		assertEquals(0x00, out[2]);
    +		assertEquals(0x00, out[3]);
    +		assertEquals(0x00, out[4]);
    +		assertEquals(0x00, out[5]);
    +		assertEquals(0x00, out[6]);
    +		assertEquals(0x00, out[7]);
    +		assertEquals(0x00, out[8]);
    +		assertEquals(0x00, out[9]);
    +		assertEquals(0x00, out[10]);
    +		assertEquals(0x11, out[11]);
    +		assertEquals(0x00, out[12]);
    +		assertEquals(0x00, out[13]);
    +		assertEquals(0x00, out[14]);
    +		assertEquals(0x01, out[15]);
    +		assertEquals(0x00, out[16]);
    +		assertEquals(0x00, out[17]);
    +		assertEquals(0x00, out[18]);
    +		assertEquals(0x22, out[19]);
    +		assertEquals(0x00, out[20]);
    +		assertEquals(0x00, out[21]);
    +		assertEquals(0x00, out[22]);
    +		assertEquals(0x33, out[23]);
    +		assertEquals(0x00, out[24]);
    +		assertEquals(0x00, out[25]);
    +		assertEquals(0x00, out[26]);
    +		assertEquals(0x44, out[27]);
    +		assertEquals(0x00, out[28]);
    +		assertEquals(0x00, out[29]);
    +		assertEquals(0x00, out[30]);
    +		assertEquals(0x55, out[31]);
    +	}
    +	
    +	public void testHashTreeAddressToByteArray() {
    +		HashTreeAddress address = new HashTreeAddress();
    +		assertEquals(0x02, address.getType());
    +		address.setLayerAddress(0x00);
    +		address.setTreeAddress(0x11);
    +		address.setTreeHeight(0x33);
    +		address.setTreeIndex(0x44);
    +		address.setKeyAndMask(0x55);
    +		byte[] out = address.toByteArray();
    +		assertEquals(0x00, out[0]);
    +		assertEquals(0x00, out[1]);
    +		assertEquals(0x00, out[2]);
    +		assertEquals(0x00, out[3]);
    +		assertEquals(0x00, out[4]);
    +		assertEquals(0x00, out[5]);
    +		assertEquals(0x00, out[6]);
    +		assertEquals(0x00, out[7]);
    +		assertEquals(0x00, out[8]);
    +		assertEquals(0x00, out[9]);
    +		assertEquals(0x00, out[10]);
    +		assertEquals(0x11, out[11]);
    +		assertEquals(0x00, out[12]);
    +		assertEquals(0x00, out[13]);
    +		assertEquals(0x00, out[14]);
    +		assertEquals(0x02, out[15]);
    +		assertEquals(0x00, out[16]);
    +		assertEquals(0x00, out[17]);
    +		assertEquals(0x00, out[18]);
    +		assertEquals(0x00, out[19]);
    +		assertEquals(0x00, out[20]);
    +		assertEquals(0x00, out[21]);
    +		assertEquals(0x00, out[22]);
    +		assertEquals(0x33, out[23]);
    +		assertEquals(0x00, out[24]);
    +		assertEquals(0x00, out[25]);
    +		assertEquals(0x00, out[26]);
    +		assertEquals(0x44, out[27]);
    +		assertEquals(0x00, out[28]);
    +		assertEquals(0x00, out[29]);
    +		assertEquals(0x00, out[30]);
    +		assertEquals(0x55, out[31]);
    +	}
    +
    +	public void testXAdressParseByteParamException() {
    +		OTSHashAddress hash = new OTSHashAddress();
    +		byte[] in = new byte[31];
    +		try {
    +			hash.parseByteArray(in);
    +			fail();
    +		} catch (Exception ex) { }
    +	}
    +	
    +	public void testOTSHashAddressParseByteArrayTypeException() {
    +		OTSHashAddress hash = new OTSHashAddress();
    +		byte[] in = new byte[32];
    +		in[15] = 0x11;
    +		try {
    +			hash.parseByteArray(in);
    +			fail();
    +		} catch (Exception ex) { }
    +	}
    +	
    +	public void testOTSHashAddressParseByteArray() {
    +		byte[] in = {
    +				0x11, 0x11, 0x11, 0x11,
    +				0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
    +				0x00, 0x00, 0x00, 0x00,
    +				0x33, 0x33, 0x33, 0x33,
    +				0x44, 0x44, 0x44, 0x44,
    +				0x55, 0x55, 0x55, 0x55,
    +				0x66, 0x66, 0x66, 0x66
    +		};
    +		OTSHashAddress hash = new OTSHashAddress();
    +		try {
    +			hash.parseByteArray(in);
    +		} catch (ParseException ex) {
    +			fail();
    +		}
    +		assertEquals(0x11111111, hash.getLayerAddress());
    +		assertEquals(0x2222222222222222L, hash.getTreeAddress());
    +		assertEquals(0x00, hash.getType());
    +		assertEquals(0x33333333, hash.getOTSAddress());
    +		assertEquals(0x44444444, hash.getChainAddress());
    +		assertEquals(0x55555555, hash.getHashAddress());
    +		assertEquals(0x66666666, hash.getKeyAndMask());
    +		byte[] out = hash.toByteArray();
    +		assertEquals(true, Arrays.equals(in, out));
    +	}
    +	
    +	public void testLTreeAddressParseByteArrayTypeException() {
    +		LTreeAddress lTree = new LTreeAddress();
    +		byte[] in = new byte[32];
    +		in[15] = 0x11;
    +		try {
    +			lTree.parseByteArray(in);
    +			fail();
    +		} catch (Exception ex) { }
    +	}
    +	
    +	public void testLTreeAddressParseByteArray() {
    +		byte[] in = {
    +				0x11, 0x11, 0x11, 0x11,
    +				0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
    +				0x00, 0x00, 0x00, 0x01,
    +				0x33, 0x33, 0x33, 0x33,
    +				0x44, 0x44, 0x44, 0x44,
    +				0x55, 0x55, 0x55, 0x55,
    +				0x66, 0x66, 0x66, 0x66
    +		};
    +		LTreeAddress hash = new LTreeAddress();
    +		try {
    +			hash.parseByteArray(in);
    +		} catch (ParseException ex) {
    +			fail();
    +		}
    +		assertEquals(0x11111111, hash.getLayerAddress());
    +		assertEquals(0x2222222222222222L, hash.getTreeAddress());
    +		assertEquals(0x01, hash.getType());
    +		assertEquals(0x33333333, hash.getLTreeAddress());
    +		assertEquals(0x44444444, hash.getTreeHeight());
    +		assertEquals(0x55555555, hash.getTreeIndex());
    +		assertEquals(0x66666666, hash.getKeyAndMask());
    +		byte[] out = hash.toByteArray();
    +		assertEquals(true, Arrays.equals(in, out));
    +	}
    +	
    +	public void testHashTreeAddressParseByteArrayTypeException() {
    +		HashTreeAddress hash = new HashTreeAddress();
    +		byte[] in = new byte[32];
    +		in[15] = 0x11;
    +		try {
    +			hash.parseByteArray(in);
    +			fail();
    +		} catch (Exception ex) { }
    +	}
    +	
    +	public void testHashTreeAddressParseByteArrayPaddingException() {
    +		HashTreeAddress hash = new HashTreeAddress();
    +		byte[] in = new byte[32];
    +		in[16] = 0x11;
    +		try {
    +			hash.parseByteArray(in);
    +			fail();
    +		} catch (Exception ex) { }
    +	}
    +	
    +	public void testHashTreeAddressParseByteArray() {
    +		byte[] in = {
    +				0x11, 0x11, 0x11, 0x11,
    +				0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
    +				0x00, 0x00, 0x00, 0x02,
    +				0x00, 0x00, 0x00, 0x00,
    +				0x44, 0x44, 0x44, 0x44,
    +				0x55, 0x55, 0x55, 0x55,
    +				0x66, 0x66, 0x66, 0x66
    +		};
    +		HashTreeAddress hash = new HashTreeAddress();
    +		try {
    +			hash.parseByteArray(in);
    +		} catch (ParseException ex) {
    +			fail();
    +		}
    +		assertEquals(0x11111111, hash.getLayerAddress());
    +		assertEquals(0x2222222222222222L, hash.getTreeAddress());
    +		assertEquals(0x02, hash.getType());
    +		assertEquals(0x00, hash.getPadding());
    +		assertEquals(0x44444444, hash.getTreeHeight());
    +		assertEquals(0x55555555, hash.getTreeIndex());
    +		assertEquals(0x66666666, hash.getKeyAndMask());
    +		byte[] out = hash.toByteArray();
    +		assertEquals(true, Arrays.equals(in, out));
    +	}
    +}
    
  • core/src/test/java/org/bouncycastle/pqc/crypto/test/XMSSMTPrivateKeyTest.java+35 0 added
    @@ -0,0 +1,35 @@
    +package org.bouncycastle.pqc.crypto.test;
    +
    +import java.io.IOException;
    +import java.text.ParseException;
    +
    +import org.bouncycastle.crypto.digests.SHA256Digest;
    +import org.bouncycastle.pqc.crypto.xmss.NullPRNG;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSMT;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSMTParameters;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSUtil;
    +
    +import junit.framework.TestCase;
    +
    +/**
    + * Test cases for XMSSMTPrivateKey class.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSMTPrivateKeyTest extends TestCase {
    +
    +	public void testPrivateKeyParsingSHA256() throws IOException, ClassNotFoundException {
    +		XMSSMTParameters params = new XMSSMTParameters(20, 10, new SHA256Digest(), new NullPRNG());
    +		XMSSMT mt = new XMSSMT(params);
    +		mt.generateKeys();
    +		byte[] privateKey = mt.exportPrivateKey();
    +		byte[] publicKey = mt.exportPublicKey();
    +		byte[] bdsStates = mt.exportBDSState();
    +		try {
    +			mt.importState(privateKey, publicKey, bdsStates);
    +		} catch (ParseException e) {
    +			e.printStackTrace();
    +		}
    +		assertTrue(XMSSUtil.compareByteArray(privateKey, mt.exportPrivateKey()));
    +	}
    +}
    
  • core/src/test/java/org/bouncycastle/pqc/crypto/test/XMSSMTPublicKeyTest.java+47 0 added
    @@ -0,0 +1,47 @@
    +package org.bouncycastle.pqc.crypto.test;
    +
    +import java.io.IOException;
    +import java.text.ParseException;
    +
    +import org.bouncycastle.crypto.digests.SHA256Digest;
    +import org.bouncycastle.pqc.crypto.xmss.NullPRNG;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSMT;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSMTParameters;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSMTPublicKey;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSUtil;
    +
    +import junit.framework.TestCase;
    +
    +/**
    + * Test cases for XMSSPublicKey class.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSMTPublicKeyTest extends TestCase {
    +
    +	public void testPublicKeyParsingSHA256() throws IOException, ClassNotFoundException {
    +		XMSSMTParameters params = new XMSSMTParameters(20, 10, new SHA256Digest(), new NullPRNG());
    +		XMSSMT mt = new XMSSMT(params);
    +		mt.generateKeys();
    +		byte[] privateKey = mt.exportPrivateKey();
    +		byte[] publicKey = mt.exportPublicKey();
    +		byte[] bdsStates = mt.exportBDSState();
    +		
    +		try {
    +			mt.importState(privateKey, publicKey, bdsStates);
    +		} catch (ParseException e) {
    +			e.printStackTrace();
    +		}
    +		assertTrue(XMSSUtil.compareByteArray(publicKey, mt.exportPublicKey()));
    +	}
    +	
    +	public void testConstructor() {
    +		XMSSMTParameters params = new XMSSMTParameters(20, 10, new SHA256Digest(), new NullPRNG());
    +		XMSSMTPublicKey pk = new XMSSMTPublicKey(params);
    +		byte[] pkByte = pk.toByteArray();
    +		/* check everything is 0 */
    +		for (int i = 0; i < pkByte.length; i++) {
    +			assertEquals(0x00, pkByte[i]);
    +		}
    +	}
    +}
    
  • core/src/test/java/org/bouncycastle/pqc/crypto/test/XMSSMTSignatureTest.java+49 0 added
    @@ -0,0 +1,49 @@
    +package org.bouncycastle.pqc.crypto.test;
    +
    +import java.text.ParseException;
    +
    +import org.bouncycastle.crypto.digests.SHA256Digest;
    +import org.bouncycastle.pqc.crypto.xmss.NullPRNG;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSMT;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSMTParameters;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSMTSignature;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSUtil;
    +
    +import junit.framework.TestCase;
    +
    +/**
    + * Test cases for XMSS^MT signature class.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSMTSignatureTest extends TestCase {
    +
    +	public void testSignatureParsingSHA256() {
    +		int totalHeight = 6;
    +		int layers = 3;
    +		byte[] message = new byte[1024];
    +		XMSSMTParameters params = new XMSSMTParameters(totalHeight, layers, new SHA256Digest(), new NullPRNG());
    +		XMSSMT xmssMT = new XMSSMT(params);
    +		xmssMT.generateKeys();
    +		byte[] signature1 = xmssMT.sign(message);
    +		XMSSMTSignature mtSignature = new XMSSMTSignature(params);
    +		try {
    +			mtSignature.parseByteArray(signature1);
    +			byte[] signature2 = mtSignature.toByteArray();
    +			assertTrue(XMSSUtil.compareByteArray(signature1, signature2));
    +		} catch (ParseException e) {
    +			e.printStackTrace();
    +			fail();
    +		}
    +	}
    +	
    +	public void testConstructor() {
    +		XMSSMTParameters params = new XMSSMTParameters(20, 10, new SHA256Digest(), new NullPRNG());
    +		XMSSMTSignature sig = new XMSSMTSignature(params);
    +		byte[] sigByte = sig.toByteArray();
    +		/* check everything is 0 */
    +		for (int i = 0; i < sigByte.length; i++) {
    +			assertEquals(0x00, sigByte[i]);
    +		}
    +	}
    +}
    
  • core/src/test/java/org/bouncycastle/pqc/crypto/test/XMSSMTTest.java+1512 0 added
  • core/src/test/java/org/bouncycastle/pqc/crypto/test/XMSSOidTest.java+68 0 added
    @@ -0,0 +1,68 @@
    +package org.bouncycastle.pqc.crypto.test;
    +
    +import org.bouncycastle.pqc.crypto.xmss.XMSSOid;
    +
    +import junit.framework.TestCase;
    +
    +/**
    + * Test cases for {@link XMSSOid} class.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + *
    + */
    +public class XMSSOidTest extends TestCase {
    +
    +	public void testXMSSOidException1() {
    +		XMSSOid xmssOid = XMSSOid.lookup("SHA-256", 32, 16, 67, -1);
    +		assertEquals(xmssOid, null);
    +	}
    +	
    +	public void testXMSSOidException2() {
    +		XMSSOid xmssOid = XMSSOid.lookup("SHA-256", 32, 16, 67, 8);
    +		assertEquals(xmssOid, null);
    +	}
    +	
    +	public void testXMSSOidException3() {
    +		XMSSOid xmssOid = XMSSOid.lookup("SHA-256", 32, 4, 67, 10);
    +		assertEquals(xmssOid, null);
    +	}
    +	
    +	public void testXMSSOid() {
    +		XMSSOid xmssOid = XMSSOid.lookup("SHA-256", 32, 16, 67, 10);
    +		assertEquals(0x01000001, xmssOid.getOid());
    +		assertEquals("XMSS_SHA2-256_W16_H10", xmssOid.toString());
    +		xmssOid = XMSSOid.lookup("SHA-256", 32, 16, 67, 16);
    +		assertEquals(0x02000002, xmssOid.getOid());
    +		assertEquals("XMSS_SHA2-256_W16_H16", xmssOid.toString());
    +		xmssOid = XMSSOid.lookup("SHA-256", 32, 16, 67, 20);
    +		assertEquals(0x03000003, xmssOid.getOid());
    +		assertEquals("XMSS_SHA2-256_W16_H20", xmssOid.toString());
    +		xmssOid = XMSSOid.lookup("SHA-512", 64, 16, 131, 10);
    +		assertEquals(0x04000004, xmssOid.getOid());
    +		assertEquals("XMSS_SHA2-512_W16_H10", xmssOid.toString());
    +		xmssOid = XMSSOid.lookup("SHA-512", 64, 16, 131, 16);
    +		assertEquals(0x05000005, xmssOid.getOid());
    +		assertEquals("XMSS_SHA2-512_W16_H16", xmssOid.toString());
    +		xmssOid = XMSSOid.lookup("SHA-512", 64, 16, 131, 20);
    +		assertEquals(0x06000006, xmssOid.getOid());
    +		assertEquals("XMSS_SHA2-512_W16_H20", xmssOid.toString());
    +		xmssOid = XMSSOid.lookup("SHAKE128", 32, 16, 67, 10);
    +		assertEquals(0x07000007, xmssOid.getOid());
    +		assertEquals("XMSS_SHAKE128_W16_H10", xmssOid.toString());
    +		xmssOid = XMSSOid.lookup("SHAKE128", 32, 16, 67, 16);
    +		assertEquals(0x08000008, xmssOid.getOid());
    +		assertEquals("XMSS_SHAKE128_W16_H16", xmssOid.toString());
    +		xmssOid = XMSSOid.lookup("SHAKE128", 32, 16, 67, 20);
    +		assertEquals(0x09000009, xmssOid.getOid());
    +		assertEquals("XMSS_SHAKE128_W16_H20", xmssOid.toString());
    +		xmssOid = XMSSOid.lookup("SHAKE256", 64, 16, 131, 10);
    +		assertEquals(0x0a00000a, xmssOid.getOid());
    +		assertEquals("XMSS_SHAKE256_W16_H10", xmssOid.toString());
    +		xmssOid = XMSSOid.lookup("SHAKE256", 64, 16, 131, 16);
    +		assertEquals(0x0b00000b, xmssOid.getOid());
    +		assertEquals("XMSS_SHAKE256_W16_H16", xmssOid.toString());
    +		xmssOid = XMSSOid.lookup("SHAKE256", 64, 16, 131, 20);
    +		assertEquals(0x0c00000c, xmssOid.getOid());
    +		assertEquals("XMSS_SHAKE256_W16_H20", xmssOid.toString());
    +	}
    +}
    
  • core/src/test/java/org/bouncycastle/pqc/crypto/test/XMSSPrivateKeyTest.java+60 0 added
    @@ -0,0 +1,60 @@
    +package org.bouncycastle.pqc.crypto.test;
    +
    +import java.text.ParseException;
    +
    +import org.bouncycastle.crypto.digests.SHA256Digest;
    +import org.bouncycastle.pqc.crypto.xmss.NullPRNG;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSParameters;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSPrivateKey;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSUtil;
    +
    +import junit.framework.TestCase;
    +
    +/**
    + * Test cases for XMSSPrivateKey class.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSPrivateKeyTest extends TestCase {
    +
    +	public void testPrivateKeyParsing() {
    +		XMSSParameters params = new XMSSParameters(10, new SHA256Digest(), new NullPRNG());
    +		int n = params.getDigestSize();
    +		XMSSPrivateKey privateKey = new XMSSPrivateKey(params);
    +		privateKey.setIndex(0xaa);
    +		byte[] root = {
    +			(byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07,
    +			(byte) 0x08, (byte) 0x09, (byte) 0x0a, (byte) 0x0b, (byte) 0x0c, (byte) 0x0d, (byte) 0x0e, (byte) 0x0f,
    +			(byte) 0x10, (byte) 0x20, (byte) 0x30, (byte) 0x03, (byte) 0x40, (byte) 0x50, (byte) 0x60, (byte) 0x70,
    +			(byte) 0x80, (byte) 0x90, (byte) 0xa0, (byte) 0xb0, (byte) 0xc0, (byte) 0xd0, (byte) 0xe0, (byte) 0xf0
    +		};
    +		privateKey.setSecretKeySeed(new byte[n]);
    +		privateKey.setSecretKeyPRF(new byte[n]);
    +		privateKey.setPublicSeed(new byte[n]);
    +		privateKey.setRoot(root);
    +		byte[] export = privateKey.toByteArray();
    +		
    +		XMSSPrivateKey privateKey2 = new XMSSPrivateKey(params);
    +		try {
    +			privateKey2.parseByteArray(export);
    +		} catch (ParseException ex) {
    +			ex.printStackTrace();
    +			fail();
    +		}
    +		assertEquals(privateKey.getIndex(), privateKey2.getIndex());
    +		assertEquals(true, XMSSUtil.compareByteArray(privateKey.getSecretKeySeed(), privateKey2.getSecretKeySeed()));
    +		assertEquals(true, XMSSUtil.compareByteArray(privateKey.getSecretKeyPRF(), privateKey2.getSecretKeyPRF()));
    +		assertEquals(true, XMSSUtil.compareByteArray(privateKey.getPublicSeed(), privateKey2.getPublicSeed()));
    +		assertEquals(true, XMSSUtil.compareByteArray(privateKey.getRoot(), privateKey2.getRoot()));
    +	}
    +	
    +	public void testConstructor() {
    +		XMSSParameters params = new XMSSParameters(10, new SHA256Digest(), new NullPRNG());
    +		XMSSPrivateKey pk = new XMSSPrivateKey(params);
    +		byte[] pkByte = pk.toByteArray();
    +		/* check everything is 0 */
    +		for (int i = 0; i < pkByte.length; i++) {
    +			assertEquals(0x00, pkByte[i]);
    +		}
    +	}
    +}
    
  • core/src/test/java/org/bouncycastle/pqc/crypto/test/XMSSPublicKeyTest.java+84 0 added
    @@ -0,0 +1,84 @@
    +package org.bouncycastle.pqc.crypto.test;
    +
    +import java.text.ParseException;
    +
    +import org.bouncycastle.crypto.digests.SHA256Digest;
    +import org.bouncycastle.crypto.digests.SHA512Digest;
    +import org.bouncycastle.pqc.crypto.xmss.NullPRNG;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSParameters;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSPublicKey;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSUtil;
    +
    +import junit.framework.TestCase;
    +
    +/**
    + * Test cases for XMSSPublicKey class.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSPublicKeyTest extends TestCase {
    +
    +	public void testPublicKeyParsingSHA256() {
    +		XMSSParameters params = new XMSSParameters(10, new SHA256Digest(), new NullPRNG());
    +		int n = params.getDigestSize();
    +		XMSSPublicKey publicKey = new XMSSPublicKey(params);
    +		byte[] root = {
    +			(byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07,
    +			(byte) 0x08, (byte) 0x09, (byte) 0x0a, (byte) 0x0b, (byte) 0x0c, (byte) 0x0d, (byte) 0x0e, (byte) 0x0f,
    +			(byte) 0x10, (byte) 0x20, (byte) 0x30, (byte) 0x03, (byte) 0x40, (byte) 0x50, (byte) 0x60, (byte) 0x70,
    +			(byte) 0x80, (byte) 0x90, (byte) 0xa0, (byte) 0xb0, (byte) 0xc0, (byte) 0xd0, (byte) 0xe0, (byte) 0xf0
    +		};
    +		publicKey.setRoot(root);
    +		publicKey.setPublicSeed(new byte[n]);
    +		byte[] export = publicKey.toByteArray();
    +		
    +		XMSSPublicKey publicKey2 = new XMSSPublicKey(params);
    +		try {
    +			publicKey2.parseByteArray(export);
    +		} catch (ParseException ex) {
    +			ex.printStackTrace();
    +			fail();
    +		}
    +		assertEquals(true, XMSSUtil.compareByteArray(publicKey.getRoot(), publicKey2.getRoot()));
    +		assertEquals(true, XMSSUtil.compareByteArray(publicKey.getPublicSeed(), publicKey2.getPublicSeed()));
    +	}
    +	
    +	public void testPublicKeyParsingSHA512() {
    +		XMSSParameters params = new XMSSParameters(10, new SHA512Digest(), new NullPRNG());
    +		int n = params.getDigestSize();
    +		XMSSPublicKey publicKey = new XMSSPublicKey(params);
    +		byte[] root = {
    +			(byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07,
    +			(byte) 0x08, (byte) 0x09, (byte) 0x0a, (byte) 0x0b, (byte) 0x0c, (byte) 0x0d, (byte) 0x0e, (byte) 0x0f,
    +			(byte) 0x10, (byte) 0x20, (byte) 0x30, (byte) 0x03, (byte) 0x40, (byte) 0x50, (byte) 0x60, (byte) 0x70,
    +			(byte) 0x80, (byte) 0x90, (byte) 0xa0, (byte) 0xb0, (byte) 0xc0, (byte) 0xd0, (byte) 0xe0, (byte) 0xf0,
    +			(byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07,
    +			(byte) 0x08, (byte) 0x09, (byte) 0x0a, (byte) 0x0b, (byte) 0x0c, (byte) 0x0d, (byte) 0x0e, (byte) 0x0f,
    +			(byte) 0x10, (byte) 0x20, (byte) 0x30, (byte) 0x03, (byte) 0x40, (byte) 0x50, (byte) 0x60, (byte) 0x70,
    +			(byte) 0x80, (byte) 0x90, (byte) 0xa0, (byte) 0xb0, (byte) 0xc0, (byte) 0xd0, (byte) 0xe0, (byte) 0xf0
    +		};
    +		publicKey.setPublicSeed(new byte[n]);
    +		publicKey.setRoot(root);
    +		byte[] export = publicKey.toByteArray();
    +		
    +		XMSSPublicKey publicKey2 = new XMSSPublicKey(params);
    +		try {
    +			publicKey2.parseByteArray(export);
    +		} catch (ParseException ex) {
    +			ex.printStackTrace();
    +			fail();
    +		}
    +		assertEquals(true, XMSSUtil.compareByteArray(publicKey.getRoot(), publicKey2.getRoot()));
    +		assertEquals(true, XMSSUtil.compareByteArray(publicKey.getPublicSeed(), publicKey2.getPublicSeed()));
    +	}
    +	
    +	public void testConstructor() {
    +		XMSSParameters params = new XMSSParameters(10, new SHA256Digest(), new NullPRNG());
    +		XMSSPublicKey pk = new XMSSPublicKey(params);
    +		byte[] pkByte = pk.toByteArray();
    +		/* check everything is 0 */
    +		for (int i = 0; i < pkByte.length; i++) {
    +			assertEquals(0x00, pkByte[i]);
    +		}
    +	}
    +}
    
  • core/src/test/java/org/bouncycastle/pqc/crypto/test/XMSSReducedSignatureTest.java+78 0 added
    @@ -0,0 +1,78 @@
    +package org.bouncycastle.pqc.crypto.test;
    +
    +import java.text.ParseException;
    +
    +import org.bouncycastle.crypto.digests.SHA256Digest;
    +import org.bouncycastle.crypto.digests.SHA512Digest;
    +import org.bouncycastle.pqc.crypto.xmss.NullPRNG;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSMT;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSMTParameters;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSMTSignature;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSParameters;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSReducedSignature;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSUtil;
    +
    +import junit.framework.TestCase;
    +
    +/**
    + * Test cases for XMSSReducedSignature class.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSReducedSignatureTest extends TestCase {
    +
    +	public void testSignatureParsingSHA256() {
    +		XMSSMTParameters params = new XMSSMTParameters(8, 2, new SHA256Digest(), new NullPRNG());
    +		XMSSMT mt = new XMSSMT(params);
    +		mt.generateKeys();
    +		byte[] message = new byte[1024];
    +		byte[] sig1 = mt.sign(message);
    +		XMSSMTSignature sig2 = new XMSSMTSignature(params);
    +		try {
    +			sig2.parseByteArray(sig1);
    +		} catch (ParseException e) {
    +			e.printStackTrace();
    +		}
    +		XMSSReducedSignature reducedSignature1 = sig2.getReducedSignatures().get(0);
    +		byte[] reducedSignatureBinary = reducedSignature1.toByteArray();
    +		XMSSReducedSignature reducedSignature2 = new XMSSReducedSignature(new XMSSParameters(4, new SHA256Digest(), new NullPRNG()));
    +		try {
    +			reducedSignature2.parseByteArray(reducedSignatureBinary);
    +		} catch (ParseException e) {
    +			e.printStackTrace();
    +		}
    +		assertTrue(XMSSUtil.compareByteArray(reducedSignatureBinary, reducedSignature2.toByteArray()));
    +	}
    +		
    +	public void testSignatureParsingSHA512() {
    +		XMSSMTParameters params = new XMSSMTParameters(4, 2, new SHA512Digest(), new NullPRNG());
    +		XMSSMT mt = new XMSSMT(params);
    +		mt.generateKeys();
    +		byte[] message = new byte[1024];
    +		byte[] sig1 = mt.sign(message);
    +		XMSSMTSignature sig2 = new XMSSMTSignature(params);
    +		try {
    +			sig2.parseByteArray(sig1);
    +		} catch (ParseException e) {
    +			e.printStackTrace();
    +		}
    +		XMSSReducedSignature reducedSignature1 = sig2.getReducedSignatures().get(0);
    +		byte[] reducedSignatureBinary = reducedSignature1.toByteArray();
    +		XMSSReducedSignature reducedSignature2 = new XMSSReducedSignature(new XMSSParameters(2, new SHA512Digest(), new NullPRNG()));
    +		try {
    +			reducedSignature2.parseByteArray(reducedSignatureBinary);
    +		} catch (ParseException e) {
    +			e.printStackTrace();
    +		}
    +		assertTrue(XMSSUtil.compareByteArray(reducedSignatureBinary, reducedSignature2.toByteArray()));
    +	}
    +	
    +	public void testConstructor() {
    +		XMSSReducedSignature sig = new XMSSReducedSignature(new XMSSParameters(4, new SHA512Digest(), new NullPRNG()));
    +		byte[] sigByte = sig.toByteArray();
    +		/* check everything is 0 */
    +		for (int i = 0; i < sigByte.length; i++) {
    +			assertEquals(0x00, sigByte[i]);
    +		}
    +	}
    +}
    
  • core/src/test/java/org/bouncycastle/pqc/crypto/test/XMSSSignatureTest.java+65 0 added
    @@ -0,0 +1,65 @@
    +package org.bouncycastle.pqc.crypto.test;
    +
    +import java.text.ParseException;
    +
    +import org.bouncycastle.crypto.digests.SHA256Digest;
    +import org.bouncycastle.crypto.digests.SHA512Digest;
    +import org.bouncycastle.pqc.crypto.xmss.NullPRNG;
    +import org.bouncycastle.pqc.crypto.xmss.XMSS;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSParameters;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSSignature;
    +import org.bouncycastle.pqc.crypto.xmss.XMSSUtil;
    +
    +import junit.framework.TestCase;
    +
    +/**
    + * Test cases for XMSSSignature class.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSSignatureTest extends TestCase {
    +
    +	public void testSignatureParsingSHA256() {
    +		XMSSParameters params = new XMSSParameters(10, new SHA256Digest(), new NullPRNG());
    +		XMSS xmss = new XMSS(params);
    +		xmss.generateKeys();
    +		byte[] message = new byte[1024];
    +		byte[] sig1 = xmss.sign(message);
    +		XMSSSignature sig2 = new XMSSSignature(params);
    +		try {
    +			sig2.parseByteArray(sig1);
    +		} catch (ParseException ex) {
    +			ex.printStackTrace();
    +			fail();
    +		}
    +		byte[] sig3 = sig2.toByteArray();
    +		assertEquals(true, XMSSUtil.compareByteArray(sig1, sig3));
    +	}
    +	
    +	public void testSignatureParsingSHA512() {
    +		XMSSParameters params = new XMSSParameters(10, new SHA512Digest(), new NullPRNG());
    +		XMSS xmss = new XMSS(params);
    +		xmss.generateKeys();
    +		byte[] message = new byte[1024];
    +		byte[] sig1 = xmss.sign(message);
    +		XMSSSignature sig2 = new XMSSSignature(params);
    +		try {
    +			sig2.parseByteArray(sig1);
    +		} catch (ParseException ex) {
    +			ex.printStackTrace();
    +			fail();
    +		}
    +		byte[] sig3 = sig2.toByteArray();
    +		assertEquals(true, XMSSUtil.compareByteArray(sig1, sig3));
    +	}
    +	
    +	public void testConstructor() {
    +		XMSSParameters params = new XMSSParameters(10, new SHA256Digest(), new NullPRNG());
    +		XMSSSignature sig = new XMSSSignature(params);
    +		byte[] sigByte = sig.toByteArray();
    +		/* check everything is 0 */
    +		for (int i = 0; i < sigByte.length; i++) {
    +			assertEquals(0x00, sigByte[i]);
    +		}
    +	}
    +}
    
  • core/src/test/java/org/bouncycastle/pqc/crypto/test/XMSSTest.java+1421 0 added
  • core/src/test/java/org/bouncycastle/pqc/crypto/test/XMSSUtilTest.java+212 0 added
    @@ -0,0 +1,212 @@
    +package org.bouncycastle.pqc.crypto.test;
    +
    +import java.util.Arrays;
    +
    +import org.bouncycastle.pqc.crypto.xmss.XMSSUtil;
    +
    +import junit.framework.TestCase;
    +
    +/**
    + * Test cases for XMSSUtil class.
    + * 
    + * @author Sebastian Roland <seroland86@gmail.com>
    + */
    +public class XMSSUtilTest extends TestCase {
    +
    +	public void testLog2() {
    +		assertEquals(3, XMSSUtil.log2(8));
    +		assertEquals(3, XMSSUtil.log2(10));
    +		assertEquals(26, XMSSUtil.log2(100010124));
    +	}
    +	
    +	public void testIntToBytesBigEndian() {
    +		byte[] b = XMSSUtil.toBytesBigEndian(1, 4);
    +		assertEquals(4, b.length);
    +		assertEquals((byte) 0x00, b[0]);
    +		assertEquals((byte) 0x00, b[1]);
    +		assertEquals((byte) 0x00, b[2]);
    +		assertEquals((byte) 0x01, b[3]);
    +		b = XMSSUtil.toBytesBigEndian(1, 6);
    +		assertEquals(6, b.length);
    +		assertEquals((byte) 0x00, b[0]);
    +		assertEquals((byte) 0x00, b[1]);
    +		assertEquals((byte) 0x00, b[2]);
    +		assertEquals((byte) 0x00, b[3]);
    +		assertEquals((byte) 0x00, b[4]);
    +		assertEquals((byte) 0x01, b[5]);
    +		b = XMSSUtil.toBytesBigEndian(1, 32);
    +		assertEquals(32, b.length);
    +		for (int i = 0; i < 31; i++) {
    +			assertEquals((byte) 0x00, b[i]);
    +		}
    +		b = XMSSUtil.toBytesBigEndian(12345, 5);
    +		assertEquals(5, b.length);
    +		assertEquals((byte) 0x00, b[0]);
    +		assertEquals((byte) 0x00, b[1]);
    +		assertEquals((byte) 0x00, b[2]);
    +		assertEquals((byte) 0x30, b[3]);
    +		assertEquals((byte) 0x39, b[4]);
    +	}
    +	
    +	public void testLongToBytesBigEndian() {
    +		byte[] b = XMSSUtil.toBytesBigEndian(1, 8);
    +		assertEquals(8, b.length);
    +		assertEquals((byte) 0x00, b[0]);
    +		assertEquals((byte) 0x00, b[1]);
    +		assertEquals((byte) 0x00, b[2]);
    +		assertEquals((byte) 0x00, b[3]);
    +		assertEquals((byte) 0x00, b[4]);
    +		assertEquals((byte) 0x00, b[5]);
    +		assertEquals((byte) 0x00, b[6]);
    +		assertEquals((byte) 0x01, b[7]);
    +		b = XMSSUtil.toBytesBigEndian(1, 10);
    +		assertEquals(10, b.length);
    +		assertEquals((byte) 0x00, b[0]);
    +		assertEquals((byte) 0x00, b[1]);
    +		assertEquals((byte) 0x00, b[2]);
    +		assertEquals((byte) 0x00, b[3]);
    +		assertEquals((byte) 0x00, b[4]);
    +		assertEquals((byte) 0x00, b[5]);
    +		assertEquals((byte) 0x00, b[6]);
    +		assertEquals((byte) 0x00, b[7]);
    +		assertEquals((byte) 0x00, b[8]);
    +		assertEquals((byte) 0x01, b[9]);
    +		b = XMSSUtil.toBytesBigEndian(1, 32);
    +		for (int i = 0; i < 31; i++) {
    +			assertEquals((byte) 0x00, b[i]);
    +		}
    +		assertEquals((byte) 0x01, b[31]);
    +		b = XMSSUtil.toBytesBigEndian(12345, 9);
    +		assertEquals(9, b.length);
    +		assertEquals((byte) 0x00, b[0]);
    +		assertEquals((byte) 0x00, b[1]);
    +		assertEquals((byte) 0x00, b[2]);
    +		assertEquals((byte) 0x00, b[3]);
    +		assertEquals((byte) 0x00, b[4]);
    +		assertEquals((byte) 0x00, b[5]);
    +		assertEquals((byte) 0x00, b[6]);
    +		assertEquals((byte) 0x30, b[7]);
    +		assertEquals((byte) 0x39, b[8]);
    +	}
    +	
    +	public void testIntToBytesBigEndianOffsetException() {
    +		byte[] in = new byte[4];
    +		try {
    +			XMSSUtil.intToBytesBigEndianOffset(in, 1, 1);
    +			fail();
    +		} catch (Exception ex) { }
    +	}
    +	
    +	public void testIntToBytesBigEndianOffset() {
    +		byte[] in = new byte[32];
    +		XMSSUtil.intToBytesBigEndianOffset(in, 12345, 5);
    +		assertEquals((byte) 0x00, in[0]);
    +		assertEquals((byte) 0x00, in[1]);
    +		assertEquals((byte) 0x00, in[2]);
    +		assertEquals((byte) 0x00, in[3]);
    +		assertEquals((byte) 0x00, in[4]);
    +		assertEquals((byte) 0x00, in[5]);
    +		assertEquals((byte) 0x00, in[6]);
    +		assertEquals((byte) 0x30, in[7]);
    +		assertEquals((byte) 0x39, in[8]);
    +		for (int i = 9; i < in.length; i++) {
    +			assertEquals((byte) 0x00, in[i]);
    +		}
    +		in = new byte[32];
    +		XMSSUtil.intToBytesBigEndianOffset(in, 12345, 28);
    +		for (int i = 0; i < 28; i++) {
    +			assertEquals((byte) 0x00, in[i]);
    +		}
    +		assertEquals((byte) 0x00, in[28]);
    +		assertEquals((byte) 0x00, in[29]);
    +		assertEquals((byte) 0x30, in[30]);
    +		assertEquals((byte) 0x39, in[31]);
    +	}
    +	
    +	public void testLongToBytesBigEndianOffsetException() {
    +		try {
    +			byte[] in = new byte[8];
    +			XMSSUtil.longToBytesBigEndianOffset(in, 1, 1);
    +			fail();
    +		} catch (Exception ex) { }
    +	}
    +	
    +	public void testLongToBytesBigEndianOffset() {
    +		byte[] in = new byte[32];
    +		XMSSUtil.longToBytesBigEndianOffset(in, 12345, 5);
    +		assertEquals((byte) 0x00, in[0]);
    +		assertEquals((byte) 0x00, in[1]);
    +		assertEquals((byte) 0x00, in[2]);
    +		assertEquals((byte) 0x00, in[3]);
    +		assertEquals((byte) 0x00, in[4]);
    +		assertEquals((byte) 0x00, in[5]);
    +		assertEquals((byte) 0x00, in[6]);
    +		assertEquals((byte) 0x00, in[7]);
    +		assertEquals((byte) 0x00, in[8]);
    +		assertEquals((byte) 0x00, in[9]);
    +		assertEquals((byte) 0x00, in[10]);
    +		assertEquals((byte) 0x30, in[11]);
    +		assertEquals((byte) 0x39, in[12]);
    +		for (int i = 14; i < in.length; i++) {
    +			assertEquals((byte) 0x00, in[i]);
    +		}
    +		in = new byte[32];
    +		XMSSUtil.longToBytesBigEndianOffset(in, 12345, 24);
    +		for (int i = 0; i < 24; i++) {
    +			assertEquals((byte) 0x00, in[i]);
    +		}
    +		assertEquals((byte) 0x00, in[28]);
    +		assertEquals((byte) 0x00, in[29]);
    +		assertEquals((byte) 0x30, in[30]);
    +		assertEquals((byte) 0x39, in[31]);
    +	}
    +	
    +	public void testBytesToIntBigEndianException() {
    +		byte[] in = new byte[4];
    +		try {
    +			XMSSUtil.bytesToIntBigEndian(in, 1);
    +			fail();
    +		} catch (Exception ex) { }
    +	}
    +	
    +	public void testBytesToIntBigEndian() {
    +		byte[] in1 = { 0x00, (byte)0xff, 0x00, (byte)0xff };
    +		int out = XMSSUtil.bytesToIntBigEndian(in1, 0);
    +		assertEquals(16711935, out);
    +		byte[] in2 = { (byte)0xab, (byte)0xcd, (byte)0xef, (byte)0xaa };
    +		out = XMSSUtil.bytesToIntBigEndian(in2, 0);
    +		assertEquals("2882400170", Integer.toUnsignedString(out));
    +		byte[] in3 = new byte[100];
    +		Arrays.fill(in3, (byte) 0xaa);
    +		for (int i = 35; i < 39; i++) {
    +			in3[i] = (byte) 0xff;
    +		}
    +		out = XMSSUtil.bytesToIntBigEndian(in3, 35);
    +		assertEquals(Integer.parseUnsignedInt("4294967295"), out);
    +	}
    +	
    +	public void testBytesToLongBigEndianException() {
    +		byte[] in = new byte[10];
    +		try {
    +			XMSSUtil.bytesToLongBigEndian(in, 3);
    +			fail();
    +		} catch (Exception ex) { }
    +	}
    +	
    +	public void testBytesToLongBigEndian() {
    +		byte[] in1 = { 0x00, (byte)0xff, 0x00, (byte)0xff, 0x00, (byte)0xff, 0x00, (byte)0xff };
    +		long out = XMSSUtil.bytesToLongBigEndian(in1, 0);
    +		assertEquals(71777214294589695L, out);
    +		byte[] in2 = { (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff };
    +		out = XMSSUtil.bytesToLongBigEndian(in2, 0);
    +		assertEquals("18446744073709551615", Long.toUnsignedString(out));
    +	}
    +	
    +	public void testCalculateTau() {
    +		int height = 10;
    +		for (int index = 0; index < (1 << 10); index += 2) {
    +			assertEquals(0, XMSSUtil.calculateTau(index, height));
    +		}
    +		assertEquals(9, XMSSUtil.calculateTau(511, height));
    +	}
    +}
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

16

News mentions

0

No linked articles in our index yet.