package org.bouncycastle.pqc.crypto.gmss; import org.bouncycastle.crypto.Digest; import org.bouncycastle.pqc.crypto.gmss.util.GMSSRandom; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; /** * This class implements the distributed computation of the public key of the * Winternitz one-time signature scheme (OTSS). The class is used by the GMSS * classes for calculation of upcoming leafs. */ public class GMSSLeaf { /** * The hash function used by the OTS and the PRNG */ private Digest messDigestOTS; /** * The length of the message digest and private key */ private int mdsize, keysize; /** * The source of randomness for OTS private key generation */ private GMSSRandom gmssRandom; /** * Byte array for distributed computation of the upcoming leaf */ private byte[] leaf; /** * Byte array for storing the concatenated hashes of private key parts */ private byte[] concHashs; /** * indices for distributed computation */ private int i, j; /** * storing 2^w */ private int two_power_w; /** * Winternitz parameter w */ private int w; /** * the amount of distributed computation steps when updateLeaf is called */ private int steps; /** * the internal seed */ private byte[] seed; /** * the OTS privateKey parts */ byte[] privateKeyOTS; /** * This constructor regenerates a prior GMSSLeaf object * * @param digest an array of strings, containing the name of the used hash * function and PRNG and the name of the corresponding * provider * @param otsIndex status bytes * @param numLeafs status ints */ public GMSSLeaf(Digest digest, byte[][] otsIndex, int[] numLeafs) { this.i = numLeafs[0]; this.j = numLeafs[1]; this.steps = numLeafs[2]; this.w = numLeafs[3]; messDigestOTS = digest; gmssRandom = new GMSSRandom(messDigestOTS); // calulate keysize for private key and the help array mdsize = messDigestOTS.getDigestSize(); int mdsizeBit = mdsize << 3; int messagesize = (int)Math.ceil((double)(mdsizeBit) / (double)w); int checksumsize = getLog((messagesize << w) + 1); this.keysize = messagesize + (int)Math.ceil((double)checksumsize / (double)w); this.two_power_w = 1 << w; // calculate steps // ((2^w)-1)*keysize + keysize + 1 / (2^h -1) // initialize arrays this.privateKeyOTS = otsIndex[0]; this.seed = otsIndex[1]; this.concHashs = otsIndex[2]; this.leaf = otsIndex[3]; } /** * The constructor precomputes some needed variables for distributed leaf * calculation * * @param digest an array of strings, containing the digest of the used hash * function and PRNG and the digest of the corresponding * provider * @param w the winterniz parameter of that tree the leaf is computed * for * @param numLeafs the number of leafs of the tree from where the distributed * computation is called */ GMSSLeaf(Digest digest, int w, int numLeafs) { this.w = w; messDigestOTS = digest; gmssRandom = new GMSSRandom(messDigestOTS); // calulate keysize for private key and the help array mdsize = messDigestOTS.getDigestSize(); int mdsizeBit = mdsize << 3; int messagesize = (int)Math.ceil((double)(mdsizeBit) / (double)w); int checksumsize = getLog((messagesize << w) + 1); this.keysize = messagesize + (int)Math.ceil((double)checksumsize / (double)w); this.two_power_w = 1 << w; // calculate steps // ((2^w)-1)*keysize + keysize + 1 / (2^h -1) this.steps = (int)Math .ceil((double)(((1 << w) - 1) * keysize + 1 + keysize) / (double)(numLeafs)); // initialize arrays this.seed = new byte[mdsize]; this.leaf = new byte[mdsize]; this.privateKeyOTS = new byte[mdsize]; this.concHashs = new byte[mdsize * keysize]; } public GMSSLeaf(Digest digest, int w, int numLeafs, byte[] seed0) { this.w = w; messDigestOTS = digest; gmssRandom = new GMSSRandom(messDigestOTS); // calulate keysize for private key and the help array mdsize = messDigestOTS.getDigestSize(); int mdsizeBit = mdsize << 3; int messagesize = (int)Math.ceil((double)(mdsizeBit) / (double)w); int checksumsize = getLog((messagesize << w) + 1); this.keysize = messagesize + (int)Math.ceil((double)checksumsize / (double)w); this.two_power_w = 1 << w; // calculate steps // ((2^w)-1)*keysize + keysize + 1 / (2^h -1) this.steps = (int)Math .ceil((double)(((1 << w) - 1) * keysize + 1 + keysize) / (double)(numLeafs)); // initialize arrays this.seed = new byte[mdsize]; this.leaf = new byte[mdsize]; this.privateKeyOTS = new byte[mdsize]; this.concHashs = new byte[mdsize * keysize]; initLeafCalc(seed0); } private GMSSLeaf(GMSSLeaf original) { this.messDigestOTS = original.messDigestOTS; this.mdsize = original.mdsize; this.keysize = original.keysize; this.gmssRandom = original.gmssRandom; this.leaf = Arrays.clone(original.leaf); this.concHashs = Arrays.clone(original.concHashs); this.i = original.i; this.j = original.j; this.two_power_w = original.two_power_w; this.w = original.w; this.steps = original.steps; this.seed = Arrays.clone(original.seed); this.privateKeyOTS = Arrays.clone(original.privateKeyOTS); } /** * initialize the distributed leaf calculation reset i,j and compute OTSseed * with seed0 * * @param seed0 the starting seed */ // TODO: this really looks like it should be either always called from a constructor or nextLeaf. void initLeafCalc(byte[] seed0) { this.i = 0; this.j = 0; byte[] dummy = new byte[mdsize]; System.arraycopy(seed0, 0, dummy, 0, seed.length); this.seed = gmssRandom.nextSeed(dummy); } GMSSLeaf nextLeaf() { GMSSLeaf nextLeaf = new GMSSLeaf(this); nextLeaf.updateLeafCalc(); return nextLeaf; } /** * Processes <code>steps</code> steps of distributed leaf calculation * * @return true if leaf is completed, else false */ private void updateLeafCalc() { byte[] buf = new byte[messDigestOTS.getDigestSize()]; // steps times do // TODO: this really needs to be looked at, the 10000 has been added as // prior to this the leaf value always ended up as zeros. for (int s = 0; s < steps + 10000; s++) { if (i == keysize && j == two_power_w - 1) { // [3] at last hash the // concatenation messDigestOTS.update(concHashs, 0, concHashs.length); leaf = new byte[messDigestOTS.getDigestSize()]; messDigestOTS.doFinal(leaf, 0); return; } else if (i == 0 || j == two_power_w - 1) { // [1] at the // beginning and // when [2] is // finished: get the // next private key // part i++; j = 0; // get next privKey part this.privateKeyOTS = gmssRandom.nextSeed(seed); } else { // [2] hash the privKey part messDigestOTS.update(privateKeyOTS, 0, privateKeyOTS.length); privateKeyOTS = buf; messDigestOTS.doFinal(privateKeyOTS, 0); j++; if (j == two_power_w - 1) { // after w hashes add to the // concatenated array System.arraycopy(privateKeyOTS, 0, concHashs, mdsize * (i - 1), mdsize); } } } throw new IllegalStateException("unable to updateLeaf in steps: " + steps + " " + i + " " + j); } /** * Returns the leaf value. * * @return the leaf value */ public byte[] getLeaf() { return Arrays.clone(leaf); } /** * This method returns the least integer that is greater or equal to the * logarithm to the base 2 of an integer <code>intValue</code>. * * @param intValue an integer * @return The least integer greater or equal to the logarithm to the base 2 * of <code>intValue</code> */ private int getLog(int intValue) { int log = 1; int i = 2; while (i < intValue) { i <<= 1; log++; } return log; } /** * Returns the status byte array used by the GMSSPrivateKeyASN.1 class * * @return The status bytes */ public byte[][] getStatByte() { byte[][] statByte = new byte[4][]; statByte[0] = new byte[mdsize]; statByte[1] = new byte[mdsize]; statByte[2] = new byte[mdsize * keysize]; statByte[3] = new byte[mdsize]; statByte[0] = privateKeyOTS; statByte[1] = seed; statByte[2] = concHashs; statByte[3] = leaf; return statByte; } /** * Returns the status int array used by the GMSSPrivateKeyASN.1 class * * @return The status ints */ public int[] getStatInt() { int[] statInt = new int[4]; statInt[0] = i; statInt[1] = j; statInt[2] = steps; statInt[3] = w; return statInt; } /** * Returns a String representation of the main part of this element * * @return a String representation of the main part of this element */ public String toString() { String out = ""; for (int i = 0; i < 4; i++) { out = out + this.getStatInt()[i] + " "; } out = out + " " + this.mdsize + " " + this.keysize + " " + this.two_power_w + " "; byte[][] temp = this.getStatByte(); for (int i = 0; i < 4; i++) { if (temp[i] != null) { out = out + new String(Hex.encode(temp[i])) + " "; } else { out = out + "null "; } } return out; } }