org.bouncycastle.pqc.crypto.picnic.Tree Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bcprov-jdk15to18 Show documentation
Show all versions of bcprov-jdk15to18 Show documentation
The Bouncy Castle Crypto package is a Java implementation of cryptographic algorithms. This jar contains JCE provider and lightweight API for the Bouncy Castle Cryptography APIs for JDK 1.5 to JDK 1.8.
package org.bouncycastle.pqc.crypto.picnic;
import java.util.logging.Logger;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Pack;
class Tree
{
private static final Logger LOG = Logger.getLogger(Tree.class.getName());
private static final int MAX_SEED_SIZE_BYTES = 32;
// private final int MAX_AUX_BYTES;
private int depth; /* The depth of the tree */
byte[][] nodes; /* The data for each node */
private int dataSize; /* The size data at each node, in bytes */
private boolean[] haveNode; /* If we have the data (seed or hash) for node i, haveSeed[i] is 1 */
private boolean[] exists; /* Since the tree is not always complete, nodes marked 0 don't exist */
private int numNodes; /* The total number of nodes in the tree */
private int numLeaves; /* The total number of leaves in the tree */
private PicnicEngine engine;
protected byte[][] getLeaves()
{
return this.nodes;
}
protected int getLeavesOffset()
{
return this.numNodes - this.numLeaves;
}
public Tree(PicnicEngine engine, int numLeaves, int dataSize)
{
this.engine = engine;
// MAX_AUX_BYTES = ((PicnicEngine.LOWMC_MAX_AND_GATES + PicnicEngine.LOWMC_MAX_KEY_BITS) / 8 + 1);
this.depth = Utils.ceil_log2(numLeaves) + 1;
this.numNodes = ((1 << (this.depth)) - 1) - ((1 << (this.depth - 1)) - numLeaves); /* Num nodes in complete - number of missing leaves */
this.numLeaves = numLeaves;
this.dataSize = dataSize;
this.nodes = new byte[this.numNodes][dataSize];
for (int i = 0; i < this.numNodes; i++)
{
this.nodes[i] = new byte[dataSize];
}
this.haveNode = new boolean[this.numNodes];
/* Depending on the number of leaves, the tree may not be complete */
this.exists = new boolean[this.numNodes];
/* Set leaves */
Arrays.fill(this.exists, this.numNodes - this.numLeaves, this.numNodes, true);
for (int i = this.numNodes - this.numLeaves; i > 0; i--)
{
if (exists(2 * i + 1) || exists(2 * i + 2) )
{
this.exists[i] = true;
}
}
this.exists[0] = true;
}
/* Create a Merkle tree by hashing up all nodes.
* leafData must have length this.numNodes, but some may be NULL. */
protected void buildMerkleTree(byte[][] leafData, byte[] salt)
{
int firstLeaf = this.numNodes - this.numLeaves;
/* Copy data to the leaves. The actual data being committed to has already been
* hashed, according to the spec. */
for (int i = 0; i < this.numLeaves; i++)
{
if (leafData[i] != null)
{
System.arraycopy(leafData[i], 0, this.nodes[firstLeaf + i], 0, this.dataSize);
this.haveNode[firstLeaf + i] = true;
}
}
/* Starting at the leaves, work up the tree, computing the hashes for intermediate nodes */
for (int i = this.numNodes; i > 0; i--)
{
computeParentHash(i, salt);
}
}
/* verifyMerkleTree: verify for each leaf that is set */
protected int verifyMerkleTree(byte[][] leafData, byte[] salt)
{
int firstLeaf = this.numNodes - this.numLeaves;
/* Copy the leaf data, where we have it. The actual data being committed to has already been
* hashed, according to the spec. */
for (int i = 0; i < this.numLeaves; i++)
{
if (leafData[i] != null)
{
if (this.haveNode[firstLeaf + i])
{
return -1; /* A leaf was assigned from the prover for a node we've recomputed */
}
if (leafData[i] != null)
{
System.arraycopy(leafData[i], 0, this.nodes[firstLeaf + i], 0, this.dataSize);
this.haveNode[firstLeaf + i] = true;
}
}
}
/* At this point the tree has some of the leaves, and some intermediate nodes
* Work up the tree, computing all nodes we don't have that are missing. */
for (int i = this.numNodes; i > 0; i--)
{
computeParentHash(i, salt);
}
/* Fail if the root was not computed. */
if (!this.haveNode[0])
{
return -1;
}
return 0;
}
protected int reconstructSeeds(int[] hideList, int hideListSize,
byte[] input, int inputLen, byte[] salt, int repIndex)
{
int ret = 0;
// if (inputLen > INT_MAX) {
// return -1;
// }
int inLen = inputLen;
int[] revealedSize = new int[1];
revealedSize[0] = 0;
int[] revealed = this.getRevealedNodes(hideList, hideListSize, revealedSize);
for (int i = 0; i < revealedSize[0]; i++)
{
inLen -= engine.seedSizeBytes;
if (inLen < 0)
{
return -1;
}
System.arraycopy(input, i*engine.seedSizeBytes, this.nodes[revealed[i]], 0, engine.seedSizeBytes);
this.haveNode[revealed[i]] = true;
}
expandSeeds(salt, repIndex);
return ret;
}
/* Serialze the missing nodes that the verifier will require to check commitments for non-missing leaves */
protected byte[] openMerkleTree(int[] missingLeaves, int missingLeavesSize, int[] outputSizeBytes)
{
int[] revealedSize = new int[1];
int[] revealed = this.getRevealedMerkleNodes(missingLeaves, missingLeavesSize, revealedSize);
/* Serialize output */
outputSizeBytes[0] = revealedSize[0] * this.dataSize;
byte[] output = new byte[outputSizeBytes[0]];
byte[] outputBase = output;
for (int i = 0; i < revealedSize[0]; i++)
{
System.arraycopy(this.nodes[revealed[i]], 0, output, i * this.dataSize, this.dataSize);
}
return outputBase;
}
/* Returns the number of bytes written to output */
private int[] getRevealedNodes(int[] hideList, int hideListSize, int[] outputSize)
{
/* Compute paths up from hideList to root, store as sets of nodes */
int pathLen = this.depth - 1;
/* pathSets[i][0...hideListSize] stores the nodes in the path at depth i
* for each of the leaf nodes in hideListSize */
int[][] pathSets = new int[pathLen][hideListSize];
/* Compute the paths back to the root */
for (int i = 0; i < hideListSize; i++)
{
int pos = 0;
int node = hideList[i] + (this.numNodes - this.numLeaves); /* input lists leaf indexes, translate to nodes */
pathSets[pos][i] = node;
pos++;
while ( (node = getParent(node)) != 0 )
{
pathSets[pos][i] = node;
pos++;
}
}
/* Determine seeds to reveal */
int[] revealed = new int[this.numLeaves];
int revealedPos = 0;
for (int d = 0; d < pathLen; d++)
{
for (int i = 0; i < hideListSize; i++)
{
if (!hasSibling(pathSets[d][i]))
{
continue;
}
int sibling = getSibling(pathSets[d][i]);
if (!contains(pathSets[d], hideListSize, sibling ))
{
// Determine the seed to reveal
while(!hasRightChild(sibling) && !isLeafNode(sibling))
{
sibling = 2 * sibling + 1; // sibling = leftChild(sibling)
}
// Only reveal if we haven't already
if (!contains(revealed, revealedPos, sibling))
{
revealed[revealedPos] = sibling;
revealedPos++;
}
}
}
}
// free(pathSets[0]);
// free(pathSets);
outputSize[0] = revealedPos;
return revealed;
}
private int getSibling(int node)
{
// assert(node < this.numNodes);
// assert(node != 0);
// assert(hasSibling(tree, node));
if (isLeftChild(node))
{
if (node + 1 < this.numNodes)
{
return node + 1;
}
else
{
LOG.fine("getSibling: request for node with not sibling");
return 0;
}
}
else
{
return node - 1;
}
}
private boolean isLeafNode(int node)
{
return (2 * node + 1 >= this.numNodes);
}
private boolean hasSibling(int node)
{
if (!exists(node))
{
return false;
}
if (isLeftChild(node) && !exists(node + 1))
{
return false;
}
return true;
}
protected int revealSeedsSize(int[] hideList, int hideListSize)
{
int[] numNodesRevealed = new int[1];
numNodesRevealed[0] = 0;
// int[] revealed =
getRevealedNodes(hideList, hideListSize, numNodesRevealed);
return numNodesRevealed[0] * engine.seedSizeBytes;
}
protected int revealSeeds(int[] hideList, int hideListSize, byte[] output, int outputSize)
{
// byte[] outputBase = Arrays.clone(output);
int[] revealedSize = new int[1];
revealedSize[0] = 0;
// if (outputSize > Integer.MAX_VALUE)
// {
// return -1;
// }
int outLen = outputSize;
int[] revealed = getRevealedNodes(hideList, hideListSize, revealedSize);
for (int i = 0; i < revealedSize[0]; i++)
{
outLen -= engine.seedSizeBytes;
if (outLen < 0)
{
LOG.fine("Insufficient sized buffer provided to revealSeeds");
return 0;
}
System.arraycopy(this.nodes[revealed[i]], 0, output, i * engine.seedSizeBytes, engine.seedSizeBytes);
}
return output.length - outLen;
}
protected int openMerkleTreeSize(int[] missingLeaves, int missingLeavesSize)
{
int[] revealedSize = new int[1];
// int[] revealed =
getRevealedMerkleNodes(missingLeaves, missingLeavesSize, revealedSize);
return revealedSize[0] * engine.digestSizeBytes;
}
/* Note that we never output the root node */
private int[] getRevealedMerkleNodes(int[] missingLeaves, int missingLeavesSize, int[] outputSize)
{
int firstLeaf = this.numNodes - this.numLeaves;
boolean[] missingNodes = new boolean[this.numNodes];
/* Mark leaves that are missing */
for (int i = 0; i < missingLeavesSize; i++)
{
missingNodes[firstLeaf + missingLeaves[i]] = true;
}
/* For the nonleaf nodes, if both leaves are missing, mark it as missing too */
int lastNonLeaf = getParent(this.numNodes - 1);
for (int i = lastNonLeaf; i > 0; i--)
{
if (!exists(i))
{
continue;
}
if (exists( 2 * i + 2))
{
if (missingNodes[2 * i + 1] && missingNodes[2 * i + 2])
{
missingNodes[i] = true;
}
}
else
{
if (missingNodes[2 * i + 1])
{
missingNodes[i] = true;
}
}
}
/* For each missing leaf node, add the highest missing node on the path
* back to the root to the set to be revealed */
int[] revealed = new int[this.numLeaves];
int pos = 0;
for (int i = 0; i < missingLeavesSize; i++)
{
int node = missingLeaves[i] + firstLeaf; /* input is leaf indexes, translate to nodes */
do
{
if (!missingNodes[getParent(node)])
{
if (!contains(revealed, pos, node))
{
revealed[pos] = node;
pos++;
}
break;
}
} while ((node = getParent(node)) != 0);
}
// free(missingNodes);
outputSize[0] = pos;
return revealed;
}
private boolean contains(int[] list, int len, int value)
{
for (int i = 0; i < len; i++)
{
if (list[i] == value)
{
return true;
}
}
return false;
}
private void computeParentHash(int child, byte[] salt)
{
if (!exists(child))
{
return;
}
int parent = getParent(child);
if (this.haveNode[parent])
{
return;
}
/* Compute the hash for parent, if we have everything */
if (!this.haveNode[2 * parent + 1])
{
return;
}
if (exists(2 * parent + 2) && !this.haveNode[2 * parent + 2])
{
return;
}
/* Compute parent data = H(left child data || [right child data] || salt || parent idx) */
engine.digest.update((byte) 3);
engine.digest.update(this.nodes[2 * parent + 1],0, engine.digestSizeBytes);
if (hasRightChild(parent))
{
/* One node may not have a right child when there's an odd number of leaves */
engine.digest.update(this.nodes[2 * parent + 2],0, engine.digestSizeBytes);
}
engine.digest.update(salt,0, PicnicEngine.saltSizeBytes);
engine.digest.update(Pack.intToLittleEndian(parent), 0, 2);
engine.digest.doFinal(this.nodes[parent], 0, engine.digestSizeBytes);
this.haveNode[parent] = true;
}
protected byte[] getLeaf(int leafIndex)
{
// assert(leafIndex < this.numLeaves);
int firstLeaf = this.numNodes - this.numLeaves;
return this.nodes[firstLeaf + leafIndex];
}
/* addMerkleNodes: deserialize and add the data for nodes provided by the committer */
protected int addMerkleNodes(int[] missingLeaves, int missingLeavesSize, byte[] input, int inputSize)
{
// assert(missingLeavesSize < this.numLeaves);
// if (inputSize > INT_MAX) {
// return -1;
// }
int intLen = inputSize;
int[] revealedSize = new int[1];
revealedSize[0] = 0;
int[] revealed = getRevealedMerkleNodes(missingLeaves, missingLeavesSize, revealedSize);
// assert(!contains(revealed, revealedSize[0], 0));
/* Deserialize input */
for (int i = 0; i < revealedSize[0]; i++)
{
intLen -= this.dataSize;
if (intLen < 0)
{
return -1;
}
System.arraycopy(input, i * this.dataSize, this.nodes[revealed[i]], 0, this.dataSize);
this.haveNode[revealed[i]] = true;
}
if (intLen != 0)
{
return -1;
}
return 0;
}
protected void generateSeeds(byte[] rootSeed, byte[] salt, int repIndex)
{
this.nodes[0] = rootSeed;
this.haveNode[0] = true;
this.expandSeeds(salt, repIndex);
}
private void expandSeeds(byte[] salt, int repIndex)
{
byte[] tmp = new byte[2*MAX_SEED_SIZE_BYTES];
/* Walk the tree, expanding seeds where possible. Compute children of
* non-leaf nodes. */
int lastNonLeaf = getParent(this.numNodes - 1);
for (int i = 0; i <= lastNonLeaf; i++)
{
if (!this.haveNode[i])
{
continue;
}
hashSeed(tmp, this.nodes[i], salt, (byte) 1, repIndex, i);
if (!this.haveNode[2 * i + 1])
{
/* left child = H_left(seed_i || salt || t || i) */
System.arraycopy(tmp, 0, this.nodes[2 * i + 1], 0, engine.seedSizeBytes);
this.haveNode[2 * i + 1] = true;
}
/* The last non-leaf node will only have a left child when there are an odd number of leaves */
if (exists(2 * i + 2) && !this.haveNode[2 * i + 2])
{
/* right child = H_right(seed_i || salt || t || i) */
System.arraycopy(tmp, engine.seedSizeBytes, this.nodes[2 * i + 2], 0, engine.seedSizeBytes );
this.haveNode[2 * i + 2] = true;
}
}
}
private void hashSeed(byte[] digest_arr, byte[] inputSeed, byte[] salt, byte hashPrefix, int repIndex, int nodeIndex)
{
engine.digest.update(hashPrefix);
engine.digest.update(inputSeed, 0, engine.seedSizeBytes);
engine.digest.update(salt, 0, PicnicEngine.saltSizeBytes);
engine.digest.update(Pack.shortToLittleEndian((short)(repIndex & 0xffff)), 0, 2); //todo check endianness
engine.digest.update(Pack.shortToLittleEndian((short)(nodeIndex & 0xffff)), 0, 2); //todo check endianness
engine.digest.doFinal(digest_arr, 0, 2 * engine.seedSizeBytes);
// System.out.println("hash: " + Hex.toHexString(digest_arr));
}
private boolean isLeftChild(int node)
{
// assert(node != 0);
return(node % 2 == 1);
}
private boolean hasRightChild(int node)
{
return(2 * node + 2 < this.numNodes && (exists(node)));
}
boolean hasLeftChild(Tree tree, int node)
{
return(2 * node + 1 < this.numNodes);
}
private int getParent(int node)
{
// assert(node != 0);
if (isLeftChild(node))
{
return (node - 1) / 2;
}
return (node - 2) / 2;
}
private boolean exists(int i)
{
if (i >= this.numNodes)
{
return false;
}
return this.exists[i];
}
}