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