blob: c15f48a4f797314c296c8a58ee5a23ff28361de9 [file] [log] [blame]
package org.bouncycastle.pqc.crypto.sphincs;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.pqc.crypto.MessageSigner;
import org.bouncycastle.util.Pack;
/**
* SPHINCS-256 signer.
* <p>
* This implementation is heavily based on the reference implementation in SUPERCOP, the main difference being the digests used
* for message hashing and tree construction are now configurable (within limits...) and that the implementation produces
* detached signatures.
* </p>
*/
public class SPHINCS256Signer
implements MessageSigner
{
private final HashFunctions hashFunctions;
private byte[] keyData;
/**
* Base constructor.
*
* @param nDigest the "n-digest" must produce 32 bytes of output - used for tree construction.
* @param twoNDigest the "2n-digest" must produce 64 bytes of output - used for initial message/key/seed hashing.
*/
public SPHINCS256Signer(Digest nDigest, Digest twoNDigest)
{
if (nDigest.getDigestSize() != 32)
{
throw new IllegalArgumentException("n-digest needs to produce 32 bytes of output");
}
if (twoNDigest.getDigestSize() != 64)
{
throw new IllegalArgumentException("2n-digest needs to produce 64 bytes of output");
}
this.hashFunctions = new HashFunctions(nDigest, twoNDigest);
}
public void init(boolean forSigning, CipherParameters param)
{
if (forSigning)
{
keyData = ((SPHINCSPrivateKeyParameters)param).getKeyData();
}
else
{
keyData = ((SPHINCSPublicKeyParameters)param).getKeyData();
}
}
public byte[] generateSignature(byte[] message)
{
return crypto_sign(hashFunctions, message, keyData);
}
public boolean verifySignature(byte[] message, byte[] signature)
{
return verify(hashFunctions, message, signature, keyData);
}
static void validate_authpath(HashFunctions hs, byte[] root, byte[] leaf, int leafidx, byte[] authpath, int auOff, byte[] masks, int height)
{
int i, j;
byte[] buffer = new byte[2 * SPHINCS256Config.HASH_BYTES];
if ((leafidx & 1) != 0)
{
for (j = 0; j < SPHINCS256Config.HASH_BYTES; j++)
{
buffer[SPHINCS256Config.HASH_BYTES + j] = leaf[j];
}
for (j = 0; j < SPHINCS256Config.HASH_BYTES; j++)
{
buffer[j] = authpath[auOff + j];
}
}
else
{
for (j = 0; j < SPHINCS256Config.HASH_BYTES; j++)
{
buffer[j] = leaf[j];
}
for (j = 0; j < SPHINCS256Config.HASH_BYTES; j++)
{
buffer[SPHINCS256Config.HASH_BYTES + j] = authpath[auOff + j];
}
}
int authOff = auOff + SPHINCS256Config.HASH_BYTES;
for (i = 0; i < height - 1; i++)
{
leafidx >>>= 1;
if ((leafidx & 1) != 0)
{
hs.hash_2n_n_mask(buffer, SPHINCS256Config.HASH_BYTES, buffer, 0, masks, 2 * (Wots.WOTS_LOG_L + i) * SPHINCS256Config.HASH_BYTES);
for (j = 0; j < SPHINCS256Config.HASH_BYTES; j++)
{
buffer[j] = authpath[authOff + j];
}
}
else
{
hs.hash_2n_n_mask(buffer, 0, buffer, 0, masks, 2 * (Wots.WOTS_LOG_L + i) * SPHINCS256Config.HASH_BYTES);
for (j = 0; j < SPHINCS256Config.HASH_BYTES; j++)
{
buffer[j + SPHINCS256Config.HASH_BYTES] = authpath[authOff + j];
}
}
authOff += SPHINCS256Config.HASH_BYTES;
}
hs.hash_2n_n_mask(root, 0, buffer, 0, masks, 2 * (Wots.WOTS_LOG_L + height - 1) * SPHINCS256Config.HASH_BYTES);
}
static void compute_authpath_wots(HashFunctions hs, byte[] root, byte[] authpath, int authOff, Tree.leafaddr a, byte[] sk, byte[] masks, int height)
{
int i, idx, j;
Tree.leafaddr ta = new Tree.leafaddr(a);
byte[] tree = new byte[2 * (1 << SPHINCS256Config.SUBTREE_HEIGHT) * SPHINCS256Config.HASH_BYTES];
byte[] seed = new byte[(1 << SPHINCS256Config.SUBTREE_HEIGHT) * SPHINCS256Config.SEED_BYTES];
byte[] pk = new byte[(1 << SPHINCS256Config.SUBTREE_HEIGHT) * Wots.WOTS_L * SPHINCS256Config.HASH_BYTES];
// level 0
for (ta.subleaf = 0; ta.subleaf < (1 << SPHINCS256Config.SUBTREE_HEIGHT); ta.subleaf++)
{
Seed.get_seed(hs, seed, (int)(ta.subleaf * SPHINCS256Config.SEED_BYTES), sk, ta);
}
Wots w = new Wots();
for (ta.subleaf = 0; ta.subleaf < (1 << SPHINCS256Config.SUBTREE_HEIGHT); ta.subleaf++)
{
w.wots_pkgen(hs, pk, (int)(ta.subleaf * Wots.WOTS_L * SPHINCS256Config.HASH_BYTES), seed, (int)(ta.subleaf * SPHINCS256Config.SEED_BYTES), masks, 0);
}
for (ta.subleaf = 0; ta.subleaf < (1 << SPHINCS256Config.SUBTREE_HEIGHT); ta.subleaf++)
{
Tree.l_tree(hs, tree, (int)((1 << SPHINCS256Config.SUBTREE_HEIGHT) * SPHINCS256Config.HASH_BYTES + ta.subleaf * SPHINCS256Config.HASH_BYTES),
pk, (int)(ta.subleaf * Wots.WOTS_L * SPHINCS256Config.HASH_BYTES), masks, 0);
}
int level = 0;
// tree
for (i = (1 << SPHINCS256Config.SUBTREE_HEIGHT); i > 0; i >>>= 1)
{
for (j = 0; j < i; j += 2)
{
hs.hash_2n_n_mask(tree, (i >>> 1) * SPHINCS256Config.HASH_BYTES + (j >>> 1) * SPHINCS256Config.HASH_BYTES,
tree, i * SPHINCS256Config.HASH_BYTES + j * SPHINCS256Config.HASH_BYTES,
masks, 2 * (Wots.WOTS_LOG_L + level) * SPHINCS256Config.HASH_BYTES);
}
level++;
}
idx = (int)a.subleaf;
// copy authpath
for (i = 0; i < height; i++)
{
System.arraycopy(tree, ((1 << SPHINCS256Config.SUBTREE_HEIGHT) >>> i) * SPHINCS256Config.HASH_BYTES + ((idx >>> i) ^ 1) * SPHINCS256Config.HASH_BYTES, authpath, authOff + i * SPHINCS256Config.HASH_BYTES, SPHINCS256Config.HASH_BYTES);
}
// copy root
System.arraycopy(tree, SPHINCS256Config.HASH_BYTES, root, 0, SPHINCS256Config.HASH_BYTES);
}
byte[] crypto_sign(HashFunctions hs, byte[] m, byte[] sk)
{
byte[] sm = new byte[SPHINCS256Config.CRYPTO_BYTES];
int i;
long leafidx;
byte[] R = new byte[SPHINCS256Config.MESSAGE_HASH_SEED_BYTES];
byte[] m_h = new byte[SPHINCS256Config.MSGHASH_BYTES];
long[] rnd = new long[8];
byte[] root = new byte[SPHINCS256Config.HASH_BYTES];
byte[] seed = new byte[SPHINCS256Config.SEED_BYTES];
byte[] masks = new byte[Horst.N_MASKS * SPHINCS256Config.HASH_BYTES];
int pk;
byte[] tsk = new byte[SPHINCS256Config.CRYPTO_SECRETKEYBYTES];
for (i = 0; i < SPHINCS256Config.CRYPTO_SECRETKEYBYTES; i++)
{
tsk[i] = sk[i];
}
// create leafidx deterministically
{
// shift scratch upwards so we can reuse msg later
int scratch = SPHINCS256Config.CRYPTO_BYTES - SPHINCS256Config.SK_RAND_SEED_BYTES;
// Copy secret random seed to scratch
System.arraycopy(tsk, SPHINCS256Config.CRYPTO_SECRETKEYBYTES - SPHINCS256Config.SK_RAND_SEED_BYTES, sm, scratch, SPHINCS256Config.SK_RAND_SEED_BYTES);
Digest d = hs.getMessageHash();
byte[] bRnd = new byte[d.getDigestSize()];
d.update(sm, scratch, SPHINCS256Config.SK_RAND_SEED_BYTES);
d.update(m, 0, m.length);
d.doFinal(bRnd, 0);
// wipe sk
zerobytes(sm, scratch, SPHINCS256Config.SK_RAND_SEED_BYTES);
for (int j = 0; j != rnd.length; j++)
{
rnd[j] = Pack.littleEndianToLong(bRnd, j * 8);
}
leafidx = rnd[0] & 0xfffffffffffffffL;
System.arraycopy(bRnd, 16, R, 0, SPHINCS256Config.MESSAGE_HASH_SEED_BYTES);
// prepare msg_hash
scratch = SPHINCS256Config.CRYPTO_BYTES - SPHINCS256Config.MESSAGE_HASH_SEED_BYTES - SPHINCS256Config.CRYPTO_PUBLICKEYBYTES;
// cpy R
System.arraycopy(R, 0, sm, scratch, SPHINCS256Config.MESSAGE_HASH_SEED_BYTES);
// construct and cpy pk
Tree.leafaddr b = new Tree.leafaddr();
b.level = SPHINCS256Config.N_LEVELS - 1;
b.subtree = 0;
b.subleaf = 0;
pk = scratch + SPHINCS256Config.MESSAGE_HASH_SEED_BYTES;
System.arraycopy(tsk, SPHINCS256Config.SEED_BYTES, sm, pk, Horst.N_MASKS * SPHINCS256Config.HASH_BYTES);
Tree.treehash(hs, sm, pk + (Horst.N_MASKS * SPHINCS256Config.HASH_BYTES), SPHINCS256Config.SUBTREE_HEIGHT, tsk, b, sm, pk);
d = hs.getMessageHash();
d.update(sm, scratch, SPHINCS256Config.MESSAGE_HASH_SEED_BYTES + SPHINCS256Config.CRYPTO_PUBLICKEYBYTES);
d.update(m, 0, m.length);
d.doFinal(m_h, 0);
}
Tree.leafaddr a = new Tree.leafaddr();
a.level = SPHINCS256Config.N_LEVELS; // Use unique value $d$ for HORST address.
a.subleaf = (int)(leafidx & ((1 << SPHINCS256Config.SUBTREE_HEIGHT) - 1));
a.subtree = leafidx >>> SPHINCS256Config.SUBTREE_HEIGHT;
for (i = 0; i < SPHINCS256Config.MESSAGE_HASH_SEED_BYTES; i++)
{
sm[i] = R[i];
}
int smOff = SPHINCS256Config.MESSAGE_HASH_SEED_BYTES;
System.arraycopy(tsk, SPHINCS256Config.SEED_BYTES, masks, 0, Horst.N_MASKS * SPHINCS256Config.HASH_BYTES);
for (i = 0; i < (SPHINCS256Config.TOTALTREE_HEIGHT + 7) / 8; i++)
{
sm[smOff + i] = (byte)((leafidx >>> 8 * i) & 0xff);
}
smOff += (SPHINCS256Config.TOTALTREE_HEIGHT + 7) / 8;
Seed.get_seed(hs, seed, 0, tsk, a);
Horst ht = new Horst();
int horst_sigbytes = ht.horst_sign(hs, sm, smOff, root, seed, masks, m_h);
smOff += horst_sigbytes;
Wots w = new Wots();
for (i = 0; i < SPHINCS256Config.N_LEVELS; i++)
{
a.level = i;
Seed.get_seed(hs, seed, 0, tsk, a); //XXX: Don't use the same address as for horst_sign here!
w.wots_sign(hs, sm, smOff, root, seed, masks);
smOff += Wots.WOTS_SIGBYTES;
compute_authpath_wots(hs, root, sm, smOff, a, tsk, masks, SPHINCS256Config.SUBTREE_HEIGHT);
smOff += SPHINCS256Config.SUBTREE_HEIGHT * SPHINCS256Config.HASH_BYTES;
a.subleaf = (int)(a.subtree & ((1 << SPHINCS256Config.SUBTREE_HEIGHT) - 1));
a.subtree >>>= SPHINCS256Config.SUBTREE_HEIGHT;
}
zerobytes(tsk, 0, SPHINCS256Config.CRYPTO_SECRETKEYBYTES);
return sm;
}
private void zerobytes(byte[] tsk, int off, int cryptoSecretkeybytes)
{
for (int i = 0; i != cryptoSecretkeybytes; i++)
{
tsk[off + i] = 0;
}
}
boolean verify(HashFunctions hs, byte[] m, byte[] sm, byte[] pk)
{
int i;
int smlen = sm.length;
long leafidx = 0;
byte[] wots_pk = new byte[ Wots.WOTS_L * SPHINCS256Config.HASH_BYTES];
byte[] pkhash = new byte[ SPHINCS256Config.HASH_BYTES];
byte[] root = new byte[ SPHINCS256Config.HASH_BYTES];
byte[] sig = new byte[ SPHINCS256Config.CRYPTO_BYTES];
int sigp;
byte[] tpk = new byte[ SPHINCS256Config.CRYPTO_PUBLICKEYBYTES];
if (smlen != SPHINCS256Config.CRYPTO_BYTES)
{
throw new IllegalArgumentException("signature wrong size");
}
byte[] m_h = new byte[ SPHINCS256Config.MSGHASH_BYTES];
for (i = 0; i < SPHINCS256Config.CRYPTO_PUBLICKEYBYTES; i++)
tpk[i] = pk[i];
// construct message hash
{
byte[] R = new byte[ SPHINCS256Config.MESSAGE_HASH_SEED_BYTES];
for (i = 0; i < SPHINCS256Config.MESSAGE_HASH_SEED_BYTES; i++)
R[i] = sm[i];
System.arraycopy(sm, 0, sig, 0, SPHINCS256Config.CRYPTO_BYTES);
Digest mHash = hs.getMessageHash();
// input R
mHash.update(R, 0, SPHINCS256Config.MESSAGE_HASH_SEED_BYTES);
// input pub key
mHash.update(tpk, 0, SPHINCS256Config.CRYPTO_PUBLICKEYBYTES);
// input message
mHash.update(m, 0, m.length);
mHash.doFinal(m_h, 0);
}
sigp = 0;
sigp += SPHINCS256Config.MESSAGE_HASH_SEED_BYTES;
smlen -= SPHINCS256Config.MESSAGE_HASH_SEED_BYTES;
for (i = 0; i < (SPHINCS256Config.TOTALTREE_HEIGHT + 7) / 8; i++)
{
leafidx ^= ((long)(sig[sigp + i] & 0xff) << (8 * i));
}
new Horst().horst_verify(hs, root, sig, sigp + (SPHINCS256Config.TOTALTREE_HEIGHT + 7) / 8,
tpk, m_h);
sigp += (SPHINCS256Config.TOTALTREE_HEIGHT + 7) / 8;
smlen -= (SPHINCS256Config.TOTALTREE_HEIGHT + 7) / 8;
sigp += Horst.HORST_SIGBYTES;
smlen -= Horst.HORST_SIGBYTES;
Wots w = new Wots();
for (i = 0; i < SPHINCS256Config.N_LEVELS; i++)
{
w.wots_verify(hs, wots_pk, sig, sigp, root, tpk);
sigp += Wots.WOTS_SIGBYTES;
smlen -= Wots.WOTS_SIGBYTES;
Tree.l_tree(hs, pkhash, 0, wots_pk, 0, tpk, 0);
validate_authpath(hs, root, pkhash, (int)(leafidx & 0x1f), sig, sigp, tpk, SPHINCS256Config.SUBTREE_HEIGHT);
leafidx >>= 5;
sigp += SPHINCS256Config.SUBTREE_HEIGHT * SPHINCS256Config.HASH_BYTES;
smlen -= SPHINCS256Config.SUBTREE_HEIGHT * SPHINCS256Config.HASH_BYTES;
}
boolean verified = true;
for (i = 0; i < SPHINCS256Config.HASH_BYTES; i++)
{
if (root[i] != tpk[i + Horst.N_MASKS * SPHINCS256Config.HASH_BYTES])
{
verified = false;
}
}
return verified;
}
}