#include "ethjet.h"
#include "tinykeccak.h"

#include <secp256k1_recovery.h>

#include <stdlib.h>
#include <stdint.h>
#include <string.h>

struct ethjet_context *
ethjet_init ()
{
  struct ethjet_context *ctx;
  ctx = malloc (sizeof *ctx);
  if (!ctx) return NULL;

  ctx->ec = secp256k1_context_create (SECP256K1_CONTEXT_VERIFY);

  return ctx;
}

void
ethjet_free (struct ethjet_context *ctx)
{
  secp256k1_context_destroy (ctx->ec);
  free (ctx);
}

/* 
 * The example contract at 0xdeadbeef just reverses its input.
 */
int
ethjet_example (struct ethjet_context *ctx,
                uint8_t *in, size_t in_size,
                uint8_t *out, size_t out_size)
{
  if (out_size != in_size)
    return 0;

  for (int i = 0; i < in_size; i++)
    out[i] = in[in_size - i - 1];

  return 1;
}

int
ethjet_ecrecover (secp256k1_context *ctx,
                  uint8_t *in, size_t in_size,
                  uint8_t *out, size_t out_size)
{
  /* Input: H V R S, all 32 bytes. */

  secp256k1_pubkey pubkey;
  secp256k1_ecdsa_recoverable_signature rsig;

  uint8_t *input64;
  uint8_t pubkey_hex[65];
  size_t hexlen = 65;

  int recid;

  if (in_size != 128)
    return 0;

  if (out_size != 32)
    return 0;

  input64 = in + 64;
  recid = in[63] - 27;

  if (recid < 0 || recid > 3)
    return 0;

  if (!secp256k1_ecdsa_recoverable_signature_parse_compact
      (ctx, &rsig, input64, recid))
    return 0;

  if (!secp256k1_ecdsa_recover (ctx, &pubkey, &rsig, in))
    return 0;

  if (!secp256k1_ec_pubkey_serialize
      (ctx, pubkey_hex, &hexlen, &pubkey, SECP256K1_EC_UNCOMPRESSED))
    return 0;

  if (sha3_256 (out, 32, pubkey_hex + 1, 64))
    return 0;

  memset (out, 0, 12);

  return 1;
}

int
ethjet (struct ethjet_context *ctx,
        enum ethjet_operation op,
        uint8_t *in, size_t in_size,
        uint8_t *out, size_t out_size)
{
  switch (op) {
  case ETHJET_ECRECOVER:
    return ethjet_ecrecover (ctx->ec, in, in_size, out, out_size);
    break;

  case ETHJET_EXAMPLE:
    return ethjet_example (ctx, in, in_size, out, out_size);

  default:
    return 0;
  }
}
