| package org.bouncycastle.pqc.crypto.gmss; |
| |
| import java.util.Vector; |
| |
| import org.bouncycastle.crypto.Digest; |
| import org.bouncycastle.pqc.crypto.gmss.util.GMSSRandom; |
| import org.bouncycastle.util.Integers; |
| import org.bouncycastle.util.encoders.Hex; |
| |
| |
| /** |
| * This class implements a treehash instance for the Merkle tree traversal |
| * algorithm. The first node of the stack is stored in this instance itself, |
| * additional tail nodes are stored on a tailstack. |
| */ |
| public class Treehash |
| { |
| |
| /** |
| * max height of current treehash instance. |
| */ |
| private int maxHeight; |
| |
| /** |
| * Vector element that stores the nodes on the stack |
| */ |
| private Vector tailStack; |
| |
| /** |
| * Vector element that stores the height of the nodes on the stack |
| */ |
| private Vector heightOfNodes; |
| |
| /** |
| * the first node is stored in the treehash instance itself, not on stack |
| */ |
| private byte[] firstNode; |
| |
| /** |
| * seedActive needed for the actual node |
| */ |
| private byte[] seedActive; |
| |
| /** |
| * the seed needed for the next re-initialization of the treehash instance |
| */ |
| private byte[] seedNext; |
| |
| /** |
| * number of nodes stored on the stack and belonging to this treehash |
| * instance |
| */ |
| private int tailLength; |
| |
| /** |
| * the height in the tree of the first node stored in treehash |
| */ |
| private int firstNodeHeight; |
| |
| /** |
| * true if treehash instance was already initialized, false otherwise |
| */ |
| private boolean isInitialized; |
| |
| /** |
| * true if the first node's height equals the maxHeight of the treehash |
| */ |
| private boolean isFinished; |
| |
| /** |
| * true if the nextSeed has been initialized with index 3*2^h needed for the |
| * seed scheduling |
| */ |
| private boolean seedInitialized; |
| |
| /** |
| * denotes the Message Digest used by the tree to create nodes |
| */ |
| private Digest messDigestTree; |
| |
| /** |
| * This constructor regenerates a prior treehash object |
| * |
| * @param name an array of strings, containing the name of the used hash |
| * function and PRNG and the name of the corresponding provider |
| * @param statByte status bytes |
| * @param statInt status ints |
| */ |
| public Treehash(Digest name, byte[][] statByte, int[] statInt) |
| { |
| this.messDigestTree = name; |
| |
| // decode statInt |
| this.maxHeight = statInt[0]; |
| this.tailLength = statInt[1]; |
| this.firstNodeHeight = statInt[2]; |
| |
| if (statInt[3] == 1) |
| { |
| this.isFinished = true; |
| } |
| else |
| { |
| this.isFinished = false; |
| } |
| if (statInt[4] == 1) |
| { |
| this.isInitialized = true; |
| } |
| else |
| { |
| this.isInitialized = false; |
| } |
| if (statInt[5] == 1) |
| { |
| this.seedInitialized = true; |
| } |
| else |
| { |
| this.seedInitialized = false; |
| } |
| |
| this.heightOfNodes = new Vector(); |
| for (int i = 0; i < tailLength; i++) |
| { |
| this.heightOfNodes.addElement(Integers.valueOf(statInt[6 + i])); |
| } |
| |
| // decode statByte |
| this.firstNode = statByte[0]; |
| this.seedActive = statByte[1]; |
| this.seedNext = statByte[2]; |
| |
| this.tailStack = new Vector(); |
| for (int i = 0; i < tailLength; i++) |
| { |
| this.tailStack.addElement(statByte[3 + i]); |
| } |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param tailStack a vector element where the stack nodes are stored |
| * @param maxHeight maximal height of the treehash instance |
| * @param digest an array of strings, containing the name of the used hash |
| * function and PRNG and the name of the corresponding provider |
| */ |
| public Treehash(Vector tailStack, int maxHeight, Digest digest) |
| { |
| this.tailStack = tailStack; |
| this.maxHeight = maxHeight; |
| this.firstNode = null; |
| this.isInitialized = false; |
| this.isFinished = false; |
| this.seedInitialized = false; |
| this.messDigestTree = digest; |
| |
| this.seedNext = new byte[messDigestTree.getDigestSize()]; |
| this.seedActive = new byte[messDigestTree.getDigestSize()]; |
| } |
| |
| /** |
| * Method to initialize the seeds needed for the precomputation of right |
| * nodes. Should be initialized with index 3*2^i for treehash_i |
| * |
| * @param seedIn |
| */ |
| public void initializeSeed(byte[] seedIn) |
| { |
| System.arraycopy(seedIn, 0, this.seedNext, 0, this.messDigestTree |
| .getDigestSize()); |
| this.seedInitialized = true; |
| } |
| |
| /** |
| * initializes the treehash instance. The seeds must already have been |
| * initialized to work correctly. |
| */ |
| public void initialize() |
| { |
| if (!this.seedInitialized) |
| { |
| System.err.println("Seed " + this.maxHeight + " not initialized"); |
| return; |
| } |
| |
| this.heightOfNodes = new Vector(); |
| this.tailLength = 0; |
| this.firstNode = null; |
| this.firstNodeHeight = -1; |
| this.isInitialized = true; |
| System.arraycopy(this.seedNext, 0, this.seedActive, 0, messDigestTree |
| .getDigestSize()); |
| } |
| |
| /** |
| * Calculates one update of the treehash instance, i.e. creates a new leaf |
| * and hashes if possible |
| * |
| * @param gmssRandom an instance of the PRNG |
| * @param leaf The byte value of the leaf needed for the update |
| */ |
| public void update(GMSSRandom gmssRandom, byte[] leaf) |
| { |
| |
| if (this.isFinished) |
| { |
| System.err |
| .println("No more update possible for treehash instance!"); |
| return; |
| } |
| if (!this.isInitialized) |
| { |
| System.err |
| .println("Treehash instance not initialized before update"); |
| return; |
| } |
| |
| byte[] help = new byte[this.messDigestTree.getDigestSize()]; |
| int helpHeight = -1; |
| |
| gmssRandom.nextSeed(this.seedActive); |
| |
| // if treehash gets first update |
| if (this.firstNode == null) |
| { |
| this.firstNode = leaf; |
| this.firstNodeHeight = 0; |
| } |
| else |
| { |
| // store the new node in help array, do not push it on the stack |
| help = leaf; |
| helpHeight = 0; |
| |
| // hash the nodes on the stack if possible |
| while (this.tailLength > 0 |
| && helpHeight == ((Integer)heightOfNodes.lastElement()) |
| .intValue()) |
| { |
| // put top element of the stack and help node in array |
| // 'tobehashed' |
| // and hash them together, put result again in help array |
| byte[] toBeHashed = new byte[this.messDigestTree |
| .getDigestSize() << 1]; |
| |
| // pop element from stack |
| System.arraycopy(this.tailStack.lastElement(), 0, toBeHashed, |
| 0, this.messDigestTree.getDigestSize()); |
| this.tailStack.removeElementAt(this.tailStack.size() - 1); |
| this.heightOfNodes |
| .removeElementAt(this.heightOfNodes.size() - 1); |
| |
| System.arraycopy(help, 0, toBeHashed, this.messDigestTree |
| .getDigestSize(), this.messDigestTree |
| .getDigestSize()); |
| messDigestTree.update(toBeHashed, 0, toBeHashed.length); |
| help = new byte[messDigestTree.getDigestSize()]; |
| messDigestTree.doFinal(help, 0); |
| |
| // increase help height, stack was reduced by one element |
| helpHeight++; |
| this.tailLength--; |
| } |
| |
| // push the new node on the stack |
| this.tailStack.addElement(help); |
| this.heightOfNodes.addElement(Integers.valueOf(helpHeight)); |
| this.tailLength++; |
| |
| // finally check whether the top node on stack and the first node |
| // in treehash have same height. If so hash them together |
| // and store them in treehash |
| if (((Integer)heightOfNodes.lastElement()).intValue() == this.firstNodeHeight) |
| { |
| byte[] toBeHashed = new byte[this.messDigestTree |
| .getDigestSize() << 1]; |
| System.arraycopy(this.firstNode, 0, toBeHashed, 0, |
| this.messDigestTree.getDigestSize()); |
| |
| // pop element from tailStack and copy it into help2 array |
| System.arraycopy(this.tailStack.lastElement(), 0, toBeHashed, |
| this.messDigestTree.getDigestSize(), |
| this.messDigestTree.getDigestSize()); |
| this.tailStack.removeElementAt(this.tailStack.size() - 1); |
| this.heightOfNodes |
| .removeElementAt(this.heightOfNodes.size() - 1); |
| |
| // store new element in firstNode, stack is then empty |
| messDigestTree.update(toBeHashed, 0, toBeHashed.length); |
| this.firstNode = new byte[messDigestTree.getDigestSize()]; |
| messDigestTree.doFinal(this.firstNode, 0); |
| this.firstNodeHeight++; |
| |
| // empty the stack |
| this.tailLength = 0; |
| } |
| } |
| |
| // check if treehash instance is completed |
| if (this.firstNodeHeight == this.maxHeight) |
| { |
| this.isFinished = true; |
| } |
| } |
| |
| /** |
| * Destroys a treehash instance after the top node was taken for |
| * authentication path. |
| */ |
| public void destroy() |
| { |
| this.isInitialized = false; |
| this.isFinished = false; |
| this.firstNode = null; |
| this.tailLength = 0; |
| this.firstNodeHeight = -1; |
| } |
| |
| /** |
| * Returns the height of the lowest node stored either in treehash or on the |
| * stack. It must not be set to infinity (as mentioned in the paper) because |
| * this cases are considered in the computeAuthPaths method of |
| * JDKGMSSPrivateKey |
| * |
| * @return Height of the lowest node |
| */ |
| public int getLowestNodeHeight() |
| { |
| if (this.firstNode == null) |
| { |
| return this.maxHeight; |
| } |
| else if (this.tailLength == 0) |
| { |
| return this.firstNodeHeight; |
| } |
| else |
| { |
| return Math.min(this.firstNodeHeight, ((Integer)heightOfNodes |
| .lastElement()).intValue()); |
| } |
| } |
| |
| /** |
| * Returns the top node height |
| * |
| * @return Height of the first node, the top node |
| */ |
| public int getFirstNodeHeight() |
| { |
| if (firstNode == null) |
| { |
| return maxHeight; |
| } |
| return firstNodeHeight; |
| } |
| |
| /** |
| * Method to check whether the instance has been initialized or not |
| * |
| * @return true if treehash was already initialized |
| */ |
| public boolean wasInitialized() |
| { |
| return this.isInitialized; |
| } |
| |
| /** |
| * Method to check whether the instance has been finished or not |
| * |
| * @return true if treehash has reached its maximum height |
| */ |
| public boolean wasFinished() |
| { |
| return this.isFinished; |
| } |
| |
| /** |
| * returns the first node stored in treehash instance itself |
| * |
| * @return the first node stored in treehash instance itself |
| */ |
| public byte[] getFirstNode() |
| { |
| return this.firstNode; |
| } |
| |
| /** |
| * returns the active seed |
| * |
| * @return the active seed |
| */ |
| public byte[] getSeedActive() |
| { |
| return this.seedActive; |
| } |
| |
| /** |
| * This method sets the first node stored in the treehash instance itself |
| * |
| * @param hash |
| */ |
| public void setFirstNode(byte[] hash) |
| { |
| if (!this.isInitialized) |
| { |
| this.initialize(); |
| } |
| this.firstNode = hash; |
| this.firstNodeHeight = this.maxHeight; |
| this.isFinished = true; |
| } |
| |
| /** |
| * updates the nextSeed of this treehash instance one step needed for the |
| * schedulng of the seeds |
| * |
| * @param gmssRandom the prng used for the seeds |
| */ |
| public void updateNextSeed(GMSSRandom gmssRandom) |
| { |
| gmssRandom.nextSeed(seedNext); |
| } |
| |
| /** |
| * Returns the tailstack |
| * |
| * @return the tailstack |
| */ |
| public Vector getTailStack() |
| { |
| return this.tailStack; |
| } |
| |
| /** |
| * Returns the status byte array used by the GMSSPrivateKeyASN.1 class |
| * |
| * @return The status bytes |
| */ |
| public byte[][] getStatByte() |
| { |
| |
| byte[][] statByte = new byte[3 + tailLength][this.messDigestTree |
| .getDigestSize()]; |
| statByte[0] = firstNode; |
| statByte[1] = seedActive; |
| statByte[2] = seedNext; |
| for (int i = 0; i < tailLength; i++) |
| { |
| statByte[3 + i] = (byte[])tailStack.elementAt(i); |
| } |
| return statByte; |
| } |
| |
| /** |
| * Returns the status int array used by the GMSSPrivateKeyASN.1 class |
| * |
| * @return The status ints |
| */ |
| public int[] getStatInt() |
| { |
| |
| int[] statInt = new int[6 + tailLength]; |
| statInt[0] = maxHeight; |
| statInt[1] = tailLength; |
| statInt[2] = firstNodeHeight; |
| if (this.isFinished) |
| { |
| statInt[3] = 1; |
| } |
| else |
| { |
| statInt[3] = 0; |
| } |
| if (this.isInitialized) |
| { |
| statInt[4] = 1; |
| } |
| else |
| { |
| statInt[4] = 0; |
| } |
| if (this.seedInitialized) |
| { |
| statInt[5] = 1; |
| } |
| else |
| { |
| statInt[5] = 0; |
| } |
| for (int i = 0; i < tailLength; i++) |
| { |
| statInt[6 + i] = ((Integer)heightOfNodes.elementAt(i)).intValue(); |
| } |
| return statInt; |
| } |
| |
| /** |
| * returns a String representation of the treehash instance |
| */ |
| public String toString() |
| { |
| String out = "Treehash : "; |
| for (int i = 0; i < 6 + tailLength; i++) |
| { |
| out = out + this.getStatInt()[i] + " "; |
| } |
| for (int i = 0; i < 3 + tailLength; i++) |
| { |
| if (this.getStatByte()[i] != null) |
| { |
| out = out + new String(Hex.encode((this.getStatByte()[i]))) + " "; |
| } |
| else |
| { |
| out = out + "null "; |
| } |
| } |
| out = out + " " + this.messDigestTree.getDigestSize(); |
| return out; |
| } |
| |
| } |