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

#include "policy.h"

#include "checkpoint.h"
#include "crypto-internal.h"
#include "quorum.h"

size_t
sigsum_policy_verify_tree_head_itch (const struct sigsum_policy *policy)
{
  return policy->num_witnesses * sizeof (bool) + policy->quorum_stack_size;
}

bool
sigsum_policy_verify_tree_head (const struct sigsum_policy *policy,
				const struct sigsum_tree_head *tree_head,
				const struct sigsum_hash *log_key_hash,
				const struct sigsum_signature *log_signature,
				size_t num_cosignatures,
				const struct sigsum_cosignature *cosignatures,
				const char **err,
				void *scratch)
{
#define FAIL(error_msg) do {	\
    if (err)			\
      *err = error_msg;		\
    return false;		\
  } while (0)

  size_t log_id = sigsum_keyhash_lookup (policy->num_logs, policy->logs, log_key_hash);
  if (log_id == (size_t) -1)
    FAIL ("unknown log key");

  uint8_t cosigned_checkpoint[COSIGNATURE_PREFIX_MAX_SIZE + CHECKPOINT_BODY_MAX_SIZE];
  uint8_t *checkpoint = cosigned_checkpoint + COSIGNATURE_PREFIX_MAX_SIZE;
  size_t checkpoint_size = _sigsum_checkpoint_body (checkpoint, log_key_hash, tree_head);
  if (!_sigsum_signature_verify (&policy->logs[log_id].key,
				 checkpoint_size, checkpoint, log_signature))
    FAIL ("invalid log signature");

  /* Use provided scratch space to allocate witness array and stack. */
  bool *found_cosignatures = scratch;
  uint8_t *stack = (uint8_t *) (found_cosignatures + policy->num_witnesses);
  memset (found_cosignatures, 0, policy->num_witnesses * sizeof (*found_cosignatures));

  for (size_t i = 0; i < num_cosignatures; i++)
    {
      size_t id = sigsum_keyhash_lookup (policy->num_witnesses, policy->witnesses,
					 &cosignatures[i].key_hash);
      if (id == (size_t) -1)
	continue;
      size_t prefix_size
	= _sigsum_cosignature_prefix (cosigned_checkpoint, cosignatures[i].timestamp);

      if (_sigsum_signature_verify (&policy->witnesses[id].key,
				   checkpoint_size + prefix_size,
				   checkpoint - prefix_size,
				   &cosignatures[i].signature))
	found_cosignatures[id] = 1;
    }

  if (!_sigsum_quorum_eval (policy->quorum_length, policy->quorum_bytecode,
			    policy->num_witnesses,
			    found_cosignatures, policy->quorum_stack_size, stack))
    FAIL ("quorum not satisfied");

  return true;
}
