package org.bouncycastle.pqc.crypto.gmss;
import java.security.SecureRandom;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.pqc.crypto.MessageSigner;
import org.bouncycastle.pqc.crypto.gmss.util.GMSSRandom;
import org.bouncycastle.pqc.crypto.gmss.util.GMSSUtil;
import org.bouncycastle.pqc.crypto.gmss.util.WinternitzOTSVerify;
import org.bouncycastle.pqc.crypto.gmss.util.WinternitzOTSignature;
import org.bouncycastle.util.Arrays;
/**
* This class implements the GMSS signature scheme.
*/
public class GMSSSigner
implements MessageSigner
{
/**
* Instance of GMSSParameterSpec
*/
//private GMSSParameterSpec gmssParameterSpec;
/**
* Instance of GMSSUtilities
*/
private GMSSUtil gmssUtil = new GMSSUtil();
/**
* The raw GMSS public key
*/
private byte[] pubKeyBytes;
/**
* Hash function for the construction of the authentication trees
*/
private Digest messDigestTrees;
/**
* The length of the hash function output
*/
private int mdLength;
/**
* The number of tree layers
*/
private int numLayer;
/**
* The hash function used by the OTS
*/
private Digest messDigestOTS;
/**
* An instance of the Winternitz one-time signature
*/
private WinternitzOTSignature ots;
/**
* Array of strings containing the name of the hash function used by the OTS
* and the corresponding provider name
*/
private GMSSDigestProvider digestProvider;
/**
* The current main tree and subtree indices
*/
private int[] index;
/**
* Array of the authentication paths for the current trees of all layers
*/
private byte[][][] currentAuthPaths;
/**
* The one-time signature of the roots of the current subtrees
*/
private byte[][] subtreeRootSig;
/**
* The GMSSParameterset
*/
private GMSSParameters gmssPS;
/**
* The PRNG
*/
private GMSSRandom gmssRandom;
GMSSKeyParameters key;
// XXX needed? Source of randomness
private SecureRandom random;
/**
* The standard constructor tries to generate the MerkleTree Algorithm
* identifier with the corresponding OID.
*
* @param digest the digest to use
*/
// TODO
public GMSSSigner(GMSSDigestProvider digest)
{
digestProvider = digest;
messDigestTrees = digest.get();
messDigestOTS = messDigestTrees;
mdLength = messDigestTrees.getDigestSize();
gmssRandom = new GMSSRandom(messDigestTrees);
}
public void init(boolean forSigning,
CipherParameters param)
{
if (forSigning)
{
if (param instanceof ParametersWithRandom)
{
ParametersWithRandom rParam = (ParametersWithRandom)param;
// XXX random needed?
this.random = rParam.getRandom();
this.key = (GMSSPrivateKeyParameters)rParam.getParameters();
initSign();
}
else
{
this.random = new SecureRandom();
this.key = (GMSSPrivateKeyParameters)param;
initSign();
}
}
else
{
this.key = (GMSSPublicKeyParameters)param;
initVerify();
}
}
/**
* Initializes the signature algorithm for signing a message.
*/
private void initSign()
{
messDigestTrees.reset();
// set private key and take from it ots key, auth, tree and key
// counter, rootSign
GMSSPrivateKeyParameters gmssPrivateKey = (GMSSPrivateKeyParameters)key;
if (gmssPrivateKey.isUsed())
{
throw new IllegalStateException("Private key already used");
}
// check if last signature has been generated
if (gmssPrivateKey.getIndex(0) >= gmssPrivateKey.getNumLeafs(0))
{
throw new IllegalStateException("No more signatures can be generated");
}
// get Parameterset
this.gmssPS = gmssPrivateKey.getParameters();
// get numLayer
this.numLayer = gmssPS.getNumOfLayers();
// get OTS Instance of lowest layer
byte[] seed = gmssPrivateKey.getCurrentSeeds()[numLayer - 1];
byte[] OTSSeed = new byte[mdLength];
byte[] dummy = new byte[mdLength];
System.arraycopy(seed, 0, dummy, 0, mdLength);
OTSSeed = gmssRandom.nextSeed(dummy); // secureRandom.nextBytes(currentSeeds[currentSeeds.length-1]);secureRandom.nextBytes(OTSseed);
this.ots = new WinternitzOTSignature(OTSSeed, digestProvider.get(), gmssPS.getWinternitzParameter()[numLayer - 1]);
byte[][][] helpCurrentAuthPaths = gmssPrivateKey.getCurrentAuthPaths();
currentAuthPaths = new byte[numLayer][][];
// copy the main tree authentication path
for (int j = 0; j < numLayer; j++)
{
currentAuthPaths[j] = new byte[helpCurrentAuthPaths[j].length][mdLength];
for (int i = 0; i < helpCurrentAuthPaths[j].length; i++)
{
System.arraycopy(helpCurrentAuthPaths[j][i], 0, currentAuthPaths[j][i], 0, mdLength);
}
}
// copy index
index = new int[numLayer];
System.arraycopy(gmssPrivateKey.getIndex(), 0, index, 0, numLayer);
// copy subtreeRootSig
byte[] helpSubtreeRootSig;
subtreeRootSig = new byte[numLayer - 1][];
for (int i = 0; i < numLayer - 1; i++)
{
helpSubtreeRootSig = gmssPrivateKey.getSubtreeRootSig(i);
subtreeRootSig[i] = new byte[helpSubtreeRootSig.length];
System.arraycopy(helpSubtreeRootSig, 0, subtreeRootSig[i], 0, helpSubtreeRootSig.length);
}
gmssPrivateKey.markUsed();
}
/**
* Signs a message.
*
* @return the signature.
*/
public byte[] generateSignature(byte[] message)
{
byte[] otsSig = new byte[mdLength];
byte[] authPathBytes;
byte[] indexBytes;
otsSig = ots.getSignature(message);
// get concatenated lowest layer tree authentication path
authPathBytes = gmssUtil.concatenateArray(currentAuthPaths[numLayer - 1]);
// put lowest layer index into a byte array
indexBytes = gmssUtil.intToBytesLittleEndian(index[numLayer - 1]);
// create first part of GMSS signature
byte[] gmssSigFirstPart = new byte[indexBytes.length + otsSig.length + authPathBytes.length];
System.arraycopy(indexBytes, 0, gmssSigFirstPart, 0, indexBytes.length);
System.arraycopy(otsSig, 0, gmssSigFirstPart, indexBytes.length, otsSig.length);
System.arraycopy(authPathBytes, 0, gmssSigFirstPart, (indexBytes.length + otsSig.length), authPathBytes.length);
// --- end first part
// --- next parts of the signature
// create initial array with length 0 for iteration
byte[] gmssSigNextPart = new byte[0];
for (int i = numLayer - 1 - 1; i >= 0; i--)
{
// get concatenated next tree authentication path
authPathBytes = gmssUtil.concatenateArray(currentAuthPaths[i]);
// put next tree index into a byte array
indexBytes = gmssUtil.intToBytesLittleEndian(index[i]);
// create next part of GMSS signature
// create help array and copy actual gmssSig into it
byte[] helpGmssSig = new byte[gmssSigNextPart.length];
System.arraycopy(gmssSigNextPart, 0, helpGmssSig, 0, gmssSigNextPart.length);
// adjust length of gmssSigNextPart for adding next part
gmssSigNextPart = new byte[helpGmssSig.length + indexBytes.length + subtreeRootSig[i].length + authPathBytes.length];
// copy old data (help array) and new data in gmssSigNextPart
System.arraycopy(helpGmssSig, 0, gmssSigNextPart, 0, helpGmssSig.length);
System.arraycopy(indexBytes, 0, gmssSigNextPart, helpGmssSig.length, indexBytes.length);
System.arraycopy(subtreeRootSig[i], 0, gmssSigNextPart, (helpGmssSig.length + indexBytes.length), subtreeRootSig[i].length);
System.arraycopy(authPathBytes, 0, gmssSigNextPart, (helpGmssSig.length + indexBytes.length + subtreeRootSig[i].length), authPathBytes.length);
}
// --- end next parts
// concatenate the two parts of the GMSS signature
byte[] gmssSig = new byte[gmssSigFirstPart.length + gmssSigNextPart.length];
System.arraycopy(gmssSigFirstPart, 0, gmssSig, 0, gmssSigFirstPart.length);
System.arraycopy(gmssSigNextPart, 0, gmssSig, gmssSigFirstPart.length, gmssSigNextPart.length);
// return the GMSS signature
return gmssSig;
}
/**
* Initializes the signature algorithm for verifying a signature.
*/
private void initVerify()
{
messDigestTrees.reset();
GMSSPublicKeyParameters gmssPublicKey = (GMSSPublicKeyParameters)key;
pubKeyBytes = gmssPublicKey.getPublicKey();
gmssPS = gmssPublicKey.getParameters();
// get numLayer
this.numLayer = gmssPS.getNumOfLayers();
}
/**
* This function verifies the signature of the message that has been
* updated, with the aid of the public key.
*
* @param message the message
* @param signature the signature associated with the message
* @return true if the signature has been verified, false otherwise.
*/
public boolean verifySignature(byte[] message, byte[] signature)
{
boolean success = false;
// int halfSigLength = signature.length >>> 1;
messDigestOTS.reset();
WinternitzOTSVerify otsVerify;
int otsSigLength;
byte[] help = message;
byte[] otsSig;
byte[] otsPublicKey;
byte[][] authPath;
byte[] dest;
int nextEntry = 0;
int index;
// Verify signature
// --- begin with message = 'message that was signed'
// and then in each step message = subtree root
for (int j = numLayer - 1; j >= 0; j--)
{
otsVerify = new WinternitzOTSVerify(digestProvider.get(), gmssPS.getWinternitzParameter()[j]);
otsSigLength = otsVerify.getSignatureLength();
message = help;
// get the subtree index
index = gmssUtil.bytesToIntLittleEndian(signature, nextEntry);
// 4 is the number of bytes in integer
nextEntry += 4;
// get one-time signature
otsSig = new byte[otsSigLength];
System.arraycopy(signature, nextEntry, otsSig, 0, otsSigLength);
nextEntry += otsSigLength;
// compute public OTS key from the one-time signature
otsPublicKey = otsVerify.Verify(message, otsSig);
// test if OTSsignature is correct
if (otsPublicKey == null)
{
System.err.println("OTS Public Key is null in GMSSSignature.verify");
return false;
}
// get authentication path from the signature
authPath = new byte[gmssPS.getHeightOfTrees()[j]][mdLength];
for (int i = 0; i < authPath.length; i++)
{
System.arraycopy(signature, nextEntry, authPath[i], 0, mdLength);
nextEntry = nextEntry + mdLength;
}
// compute the root of the subtree from the authentication path
help = new byte[mdLength];
help = otsPublicKey;
int count = 1 << authPath.length;
count = count + index;
for (int i = 0; i < authPath.length; i++)
{
dest = new byte[mdLength << 1];
if ((count % 2) == 0)
{
System.arraycopy(help, 0, dest, 0, mdLength);
System.arraycopy(authPath[i], 0, dest, mdLength, mdLength);
count = count / 2;
}
else
{
System.arraycopy(authPath[i], 0, dest, 0, mdLength);
System.arraycopy(help, 0, dest, mdLength, help.length);
count = (count - 1) / 2;
}
messDigestTrees.update(dest, 0, dest.length);
help = new byte[messDigestTrees.getDigestSize()];
messDigestTrees.doFinal(help, 0);
}
}
// now help contains the root of the maintree
// test if help is equal to the GMSS public key
if (Arrays.areEqual(pubKeyBytes, help))
{
success = true;
}
return success;
}
}