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; } }