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

#include "policy.h"

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

static void
cosignatures_verify (const struct sigsum_key_map *witnesses,
		     size_t num_cosignatures,
		     const struct sigsum_cosignature *cosignatures,
		     bool *found_witnesses,
		     /* The checkpoint must be allocated with space
			for a preceding prefix of size
			COSIGNATURE_PREFIX_MAX_SIZE. */
		     size_t checkpoint_size, uint8_t *checkpoint)
{
  /* Relies on both witnesses and cosignatures being sorted by key
     hash, so we can find the intersection as a merge operation. */
  for (size_t id = 0; id < witnesses->num_keys; id++)
    {
      found_witnesses[id] = false;
      for (; num_cosignatures > 0; num_cosignatures--, cosignatures++)
	{
	  int c = memcmp (witnesses->keys[id].hash.h, cosignatures->witness.key_hash.h,
			  sizeof (witnesses->keys[id].hash.h));
	  if (c < 0)
	    /* No cosignature for this witness. */
	    break;
	  if (c == 0)
	    {
	      /* Cosignature for a known witness; check cosignature validity. */
	      size_t prefix_size
		= _sigsum_cosignature_prefix (checkpoint - COSIGNATURE_PREFIX_MAX_SIZE,
					      cosignatures->timestamp);
	      found_witnesses[id]
		= _sigsum_signature_verify (&witnesses->keys[id].key,
					    checkpoint_size + prefix_size,
					    checkpoint - prefix_size,
					    &cosignatures->witness.signature);

	      num_cosignatures--; cosignatures++;
	      break;
	    }
	}
    }
}


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_signature_with_hash *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)

  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_signature->key_hash, tree_head);

  const struct sigsum_pubkey *log_key
    = sigsum_key_map_lookup (&policy->logs, &log_signature->key_hash, NULL);
  if (!log_key)
    FAIL ("unknown log key");

  if (!_sigsum_signature_verify (log_key, checkpoint_size, checkpoint,
				 &log_signature->signature))
    FAIL ("invalid log signature");

  /* Use provided scratch space to allocate witness array and stack. */
  bool *found_witnesses = scratch;
  uint8_t *stack = (uint8_t *) (found_witnesses + policy->witnesses.num_keys);

  cosignatures_verify (&policy->witnesses, num_cosignatures, cosignatures,
		       found_witnesses, checkpoint_size, checkpoint);

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

  return true;
#undef FAIL
}
