/* Merkle tree operations, using an RFC 9162 Merkle tree. */

#include "merkle.h"

#include "crypto-internal.h"

#include <assert.h>
#include <string.h>

static bool
hash_equal (const struct sigsum_hash *a, const struct sigsum_hash *b)
{
  return memcmp (a->h, b->h, sizeof (a->h)) == 0;
}

static void
hash_interior_node (struct sigsum_hash_ctx *ctx, struct sigsum_hash *out,
		    const struct sigsum_hash *left, const struct sigsum_hash *right)
{
  static const uint8_t prefix = 1;
  _sigsum_hash_update (ctx, 1, &prefix);
  _sigsum_hash_update (ctx, sizeof (left->h), left->h);
  _sigsum_hash_update (ctx, sizeof (right->h), right->h);
  _sigsum_hash_digest (ctx, out);
}

#define ODD(x) ((x) & 1)
#define EVEN(x) (!ODD (x))

/* Verify an RFC 9162 inclusion proof, see section 2.1.3.2. */
bool
sigsum_verify_inclusion (const struct sigsum_hash *leaf_hash, sigsum_size_t leaf_index,
			 const struct sigsum_tree_head *tree_head, unsigned path_length,
			 const struct sigsum_hash *path)
{
  struct sigsum_hash_ctx ctx;
  struct sigsum_hash r;
  sigsum_size_t fn, sn;

  if (leaf_index >= tree_head->size) /* Always true if tree_size == 0 */
    return false;

  _sigsum_hash_init (&ctx);

  for (fn = leaf_index, sn = tree_head->size - 1, r = *leaf_hash;
       path_length > 0;
       path_length--, path++, fn >>= 1, sn >>= 1)
    {
      if (sn == 0)
	return false;

      if (ODD (fn) || fn == sn)
	{
	  /* Node on path is left sibling. */
	  hash_interior_node (&ctx, &r, path, &r);
	  /* For the case that fn == sn (right edge), the node is
	     moved upward until it's on the same level as its left
	     sibling. */
	  while (EVEN (fn))
	    {
	      /* The RFC 9162 algorithm includes a redundant check for
		 fn == 0 in step 4(ii)b. But we can have an even fn
		 here only when fn == sn. And since sn == 0 was
		 rejected above, fn stays non-zero and eventually
		 becomes odd. */
	      assert (fn > 0);
	      fn >>= 1; sn >>= 1;
	    }
	}
      else
	{
	  /* Node on path is right sibling. */
	  hash_interior_node (&ctx, &r, &r, path);
	}
    }
  return sn == 0 && hash_equal (&r, &tree_head->root_hash);
}

/* Verify an RFC 9162 consistency proof, see section 2.1.4.2.
 *
 * As an extension, this function can be called with any tree heads:
 * The RFC 9161 algorithm is applied when
 *
 *   0 < first size < second size.
 *
 * For other sizes, a consistency proof is considered valid if and
 * only if it is empty and either first and second trees are equal
 * (same size, same root hash), or the first tree is empty and second
 * is non-empty.
 */
bool
sigsum_verify_consistency (const struct sigsum_tree_head *first_tree,
			   const struct sigsum_tree_head *second_tree,
			   unsigned path_length,
			   const struct sigsum_hash *path)
{
  /* SHA256("") */
  static const struct sigsum_hash empty_root = {
    {
      0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14,
      0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
      0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c,
      0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55,
    }
  };

  struct sigsum_hash_ctx ctx;
  struct sigsum_hash fr, sr;
  const struct sigsum_hash *start;
  sigsum_size_t fn, sn;

  if (first_tree->size > second_tree->size)
    return false;

  /* When the first tree is empty, enforce correct root hash. */
  if (first_tree->size == 0 && !hash_equal (&first_tree->root_hash, &empty_root))
    return false;

  if (first_tree->size == second_tree->size)
    /* Consistent if and only if roots are equal. No path. */
    return path_length == 0 && hash_equal (&first_tree->root_hash, &second_tree->root_hash);

  if (first_tree->size == 0)
    /* Anything is consistent with the empty tree. No path. */
    return path_length == 0;

  /* The RFC 9162 prerequisites. */
  assert (second_tree->size > first_tree->size);
  assert (first_tree->size > 0);

  if (path_length == 0)
    return false;

  fn = first_tree->size - 1;
  sn = second_tree->size - 1;

  /* The last leaf of the first tree is at index fn. Eliminate bottom
     layers of the tree, until fn points at a subtree that is a left
     child; that subtree is included as-is also in the second tree,
     and that is the starting point for the traversal. */
  while (ODD (fn))
    {
      fn >>= 1; sn >>= 1;
    }

  if (fn == 0)
    {
      /* fn == 0 here if and only if first_tree->size is a power of two. */
      start = &first_tree->root_hash;
    }
  else
    {
      start = path++;
      path_length--;
    }

  _sigsum_hash_init (&ctx);

  /* We reconstruct the root hash of the second tree, from the start
     hash and the path, in exactly the same way as for an inclusion
     proof. To prove consistency with the first tree, we use the same
     left siblings to also reconstruct the root hash of the first
     tree. */
  for (fr = sr = *start; path_length > 0;
       path_length--, path++, fn >>= 1, sn >>= 1)
    {
      if (sn == 0)
	return false;

      if (ODD (fn) || fn == sn)
	{
	  /* Node on path is left sibling for both trees. */
	  hash_interior_node (&ctx, &fr, path, &fr);
	  hash_interior_node (&ctx, &sr, path, &sr);
	  /* For the case that fn == sn (right edge), the node is
	     moved upward until it's on the same level as its left
	     sibling. */
	  while (EVEN (fn))
	    {
	      /* The RFC 9162 algorithm includes a redundant check for
		 fn == 0 in step 6(iii)b. But we can have an even fn
		 here only when fn == sn. And since sn == 0 was
		 rejected above, fn stays non-zero and eventually
		 becomes odd. */
	      assert (fn > 0);
	      fn >>= 1; sn >>= 1;
	    }
	}
      else
	{
	  /* Node on path is right sibling for the second tree. */
	  hash_interior_node (&ctx, &sr, &sr, path);
	}
    }

  return sn == 0
    && hash_equal (&fr, &first_tree->root_hash)
    && hash_equal (&sr, &second_tree->root_hash);
}
