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

#include "policy.h"

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

size_t
sigsum_policy_verify_tree_head_itch (const struct sigsum_policy *policy)
{
  return policy->witnesses.num_keys * sizeof (bool) + policy->quorum.stack_alloc;
}

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)

  const struct sigsum_pubkey *log_key
    = sigsum_key_map_lookup (&policy->logs, log_key_hash, NULL);
  if (!log_key)
    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 (log_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->witnesses.num_keys);
  memset (found_cosignatures, 0, policy->witnesses.num_keys * sizeof (*found_cosignatures));

  for (size_t i = 0; i < num_cosignatures; i++)
    {
      size_t id;
      const struct sigsum_pubkey *witness_key
	= sigsum_key_map_lookup (&policy->witnesses,
				 &cosignatures[i].key_hash, &id);
      if (!witness_key)
	continue;
      size_t prefix_size
	= _sigsum_cosignature_prefix (cosigned_checkpoint, cosignatures[i].timestamp);

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

  if (!_sigsum_quorum_eval (&policy->quorum,
			    policy->witnesses.num_keys,
			    found_cosignatures, stack))
    FAIL ("quorum not satisfied");

  return true;
}
