package org.bouncycastle.pqc.crypto.gmss; import java.util.Enumeration; import java.util.Vector; import org.bouncycastle.crypto.Digest; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.Integers; import org.bouncycastle.util.encoders.Hex; /** * This class computes a whole Merkle tree and saves the needed values for * AuthPath computation. It is used for precomputation of the root of a * following tree. After initialization, 2^H updates are required to complete * the root. Every update requires one leaf value as parameter. While computing * the root all initial values for the authentication path algorithm (treehash, * auth, retain) are stored for later use. */ public class GMSSRootCalc { /** * max height of the tree */ private int heightOfTree; /** * length of the messageDigest */ private int mdLength; /** * the treehash instances of the tree */ private Treehash[] treehash; /** * stores the retain nodes for authPath computation */ private Vector[] retain; /** * finally stores the root of the tree when finished */ private byte[] root; /** * stores the authentication path y_1(i), i = 0..H-1 */ private byte[][] AuthPath; /** * the value K for the authentication path computation */ private int K; /** * Vector element that stores the nodes on the stack */ private Vector tailStack; /** * stores the height of all nodes laying on the tailStack */ private Vector heightOfNodes; /** * The hash function used for the construction of the authentication trees */ private Digest messDigestTree; /** * An array of strings containing the name of the hash function used to * construct the authentication trees and used by the OTS. */ private GMSSDigestProvider digestProvider; /** * stores the index of the current node on each height of the tree */ private int[] index; /** * true if instance was already initialized, false otherwise */ private boolean isInitialized; /** * true it instance was finished */ private boolean isFinished; /** * Integer that stores the index of the next seed that has to be omitted to * the treehashs */ private int indexForNextSeed; /** * temporary integer that stores the height of the next treehash instance * that gets initialized with a seed */ private int heightOfNextSeed; /** * This constructor regenerates a prior treehash object * * @param digest an array of strings, containing the digest of the used hash * function and PRNG and the digest of the corresponding * provider * @param statByte status bytes * @param statInt status ints */ public GMSSRootCalc(Digest digest, byte[][] statByte, int[] statInt, Treehash[] treeH, Vector[] ret) { this.messDigestTree = digestProvider.get(); this.digestProvider = digestProvider; // decode statInt this.heightOfTree = statInt[0]; this.mdLength = statInt[1]; this.K = statInt[2]; this.indexForNextSeed = statInt[3]; this.heightOfNextSeed = statInt[4]; if (statInt[5] == 1) { this.isFinished = true; } else { this.isFinished = false; } if (statInt[6] == 1) { this.isInitialized = true; } else { this.isInitialized = false; } int tailLength = statInt[7]; this.index = new int[heightOfTree]; for (int i = 0; i < heightOfTree; i++) { this.index[i] = statInt[8 + i]; } this.heightOfNodes = new Vector(); for (int i = 0; i < tailLength; i++) { this.heightOfNodes.addElement(Integers.valueOf(statInt[8 + heightOfTree + i])); } // decode statByte this.root = statByte[0]; this.AuthPath = new byte[heightOfTree][mdLength]; for (int i = 0; i < heightOfTree; i++) { this.AuthPath[i] = statByte[1 + i]; } this.tailStack = new Vector(); for (int i = 0; i < tailLength; i++) { this.tailStack.addElement(statByte[1 + heightOfTree + i]); } // decode treeH this.treehash = GMSSUtils.clone(treeH); // decode ret this.retain = GMSSUtils.clone(ret); } /** * Constructor * * @param heightOfTree maximal height of the tree * @param digestProvider an array of strings, containing the name of the used hash * function and PRNG and the name of the corresponding * provider */ public GMSSRootCalc(int heightOfTree, int K, GMSSDigestProvider digestProvider) { this.heightOfTree = heightOfTree; this.digestProvider = digestProvider; this.messDigestTree = digestProvider.get(); this.mdLength = messDigestTree.getDigestSize(); this.K = K; this.index = new int[heightOfTree]; this.AuthPath = new byte[heightOfTree][mdLength]; this.root = new byte[mdLength]; // this.treehash = new Treehash[this.heightOfTree - this.K]; this.retain = new Vector[this.K - 1]; for (int i = 0; i < K - 1; i++) { this.retain[i] = new Vector(); } } /** * Initializes the calculation of a new root * * @param sharedStack the stack shared by all treehash instances of this tree */ public void initialize(Vector sharedStack) { this.treehash = new Treehash[this.heightOfTree - this.K]; for (int i = 0; i < this.heightOfTree - this.K; i++) { this.treehash[i] = new Treehash(sharedStack, i, this.digestProvider.get()); } this.index = new int[heightOfTree]; this.AuthPath = new byte[heightOfTree][mdLength]; this.root = new byte[mdLength]; this.tailStack = new Vector(); this.heightOfNodes = new Vector(); this.isInitialized = true; this.isFinished = false; for (int i = 0; i < heightOfTree; i++) { this.index[i] = -1; } this.retain = new Vector[this.K - 1]; for (int i = 0; i < K - 1; i++) { this.retain[i] = new Vector(); } this.indexForNextSeed = 3; this.heightOfNextSeed = 0; } /** * updates the root with one leaf and stores needed values in retain, * treehash or authpath. Additionally counts the seeds used. This method is * used when performing the updates for TREE++. * * @param seed the initial seed for treehash: seedNext * @param leaf the height of the treehash */ public void update(byte[] seed, byte[] leaf) { if (this.heightOfNextSeed < (this.heightOfTree - this.K) && this.indexForNextSeed - 2 == index[0]) { this.initializeTreehashSeed(seed, this.heightOfNextSeed); this.heightOfNextSeed++; this.indexForNextSeed *= 2; } // now call the simple update this.update(leaf); } /** * Updates the root with one leaf and stores the needed values in retain, * treehash or authpath */ public void update(byte[] leaf) { if (isFinished) { System.out.print("Too much updates for Tree!!"); return; } if (!isInitialized) { System.err.println("GMSSRootCalc not initialized!"); return; } // a new leaf was omitted, so raise index on lowest layer index[0]++; // store the nodes on the lowest layer in treehash or authpath if (index[0] == 1) { System.arraycopy(leaf, 0, AuthPath[0], 0, mdLength); } else if (index[0] == 3) { // store in treehash only if K < H if (heightOfTree > K) { treehash[0].setFirstNode(leaf); } } if ((index[0] - 3) % 2 == 0 && index[0] >= 3) { // store in retain if K = H if (heightOfTree == K) // TODO: check it { retain[0].insertElementAt(leaf, 0); } } // if first update to this tree is made if (index[0] == 0) { tailStack.addElement(leaf); heightOfNodes.addElement(Integers.valueOf(0)); } else { byte[] help = new byte[mdLength]; byte[] toBeHashed = new byte[mdLength << 1]; // store the new leaf in help System.arraycopy(leaf, 0, help, 0, mdLength); int helpHeight = 0; // while top to nodes have same height while (tailStack.size() > 0 && helpHeight == ((Integer)heightOfNodes.lastElement()) .intValue()) { // help <-- hash(stack top element || help) System.arraycopy(tailStack.lastElement(), 0, toBeHashed, 0, mdLength); tailStack.removeElementAt(tailStack.size() - 1); heightOfNodes.removeElementAt(heightOfNodes.size() - 1); System.arraycopy(help, 0, toBeHashed, mdLength, mdLength); messDigestTree.update(toBeHashed, 0, toBeHashed.length); help = new byte[messDigestTree.getDigestSize()]; messDigestTree.doFinal(help, 0); // the new help node is one step higher helpHeight++; if (helpHeight < heightOfTree) { index[helpHeight]++; // add index 1 element to initial authpath if (index[helpHeight] == 1) { System.arraycopy(help, 0, AuthPath[helpHeight], 0, mdLength); } if (helpHeight >= heightOfTree - K) { if (helpHeight == 0) { System.out.println("M���P"); } // add help element to retain stack if it is a right // node // and not stored in treehash if ((index[helpHeight] - 3) % 2 == 0 && index[helpHeight] >= 3) // TODO: check it { retain[helpHeight - (heightOfTree - K)] .insertElementAt(help, 0); } } else { // if element is third in his line add it to treehash if (index[helpHeight] == 3) { treehash[helpHeight].setFirstNode(help); } } } } // push help element to the stack tailStack.addElement(help); heightOfNodes.addElement(Integers.valueOf(helpHeight)); // is the root calculation finished? if (helpHeight == heightOfTree) { isFinished = true; isInitialized = false; root = (byte[])tailStack.lastElement(); } } } /** * initializes the seeds for the treehashs of the tree precomputed by this * class * * @param seed the initial seed for treehash: seedNext * @param index the height of the treehash */ public void initializeTreehashSeed(byte[] seed, int index) { treehash[index].initializeSeed(seed); } /** * Method to check whether the instance has been initialized or not * * @return true if treehash was already initialized */ public boolean wasInitialized() { return isInitialized; } /** * Method to check whether the instance has been finished or not * * @return true if tree has reached its maximum height */ public boolean wasFinished() { return isFinished; } /** * returns the authentication path of the first leaf of the tree * * @return the authentication path of the first leaf of the tree */ public byte[][] getAuthPath() { return GMSSUtils.clone(AuthPath); } /** * returns the initial treehash instances, storing value y_3(i) * * @return the initial treehash instances, storing value y_3(i) */ public Treehash[] getTreehash() { return GMSSUtils.clone(treehash); } /** * returns the retain stacks storing all right nodes near to the root * * @return the retain stacks storing all right nodes near to the root */ public Vector[] getRetain() { return GMSSUtils.clone(retain); } /** * returns the finished root value * * @return the finished root value */ public byte[] getRoot() { return Arrays.clone(root); } /** * returns the shared stack * * @return the shared stack */ public Vector getStack() { Vector copy = new Vector(); for (Enumeration en = tailStack.elements(); en.hasMoreElements();) { copy.addElement(en.nextElement()); } return copy; } /** * Returns the status byte array used by the GMSSPrivateKeyASN.1 class * * @return The status bytes */ public byte[][] getStatByte() { int tailLength; if (tailStack == null) { tailLength = 0; } else { tailLength = tailStack.size(); } byte[][] statByte = new byte[1 + heightOfTree + tailLength][64]; //FIXME: messDigestTree.getByteLength() statByte[0] = root; for (int i = 0; i < heightOfTree; i++) { statByte[1 + i] = AuthPath[i]; } for (int i = 0; i < tailLength; i++) { statByte[1 + heightOfTree + 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 tailLength; if (tailStack == null) { tailLength = 0; } else { tailLength = tailStack.size(); } int[] statInt = new int[8 + heightOfTree + tailLength]; statInt[0] = heightOfTree; statInt[1] = mdLength; statInt[2] = K; statInt[3] = indexForNextSeed; statInt[4] = heightOfNextSeed; if (isFinished) { statInt[5] = 1; } else { statInt[5] = 0; } if (isInitialized) { statInt[6] = 1; } else { statInt[6] = 0; } statInt[7] = tailLength; for (int i = 0; i < heightOfTree; i++) { statInt[8 + i] = index[i]; } for (int i = 0; i < tailLength; i++) { statInt[8 + heightOfTree + i] = ((Integer)heightOfNodes .elementAt(i)).intValue(); } return statInt; } /** * @return a human readable version of the structure */ public String toString() { String out = ""; int tailLength; if (tailStack == null) { tailLength = 0; } else { tailLength = tailStack.size(); } for (int i = 0; i < 8 + heightOfTree + tailLength; i++) { out = out + getStatInt()[i] + " "; } for (int i = 0; i < 1 + heightOfTree + tailLength; i++) { out = out + new String(Hex.encode(getStatByte()[i])) + " "; } out = out + " " + digestProvider.get().getDigestSize(); return out; } }