/*
  This file is part of TALER
  Copyright (C) 2026 Taler Systems SA

  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
  Foundation; either version 3, or (at your option) any later version.

  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

  You should have received a copy of the GNU General Public License along with
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file util/test_crypto_confirmation.c
 * @brief tests for POS confirmation computation
 * @author Bohdan Potuzhnyi
 * @author Volodymyr Potuzhnyi
 */
#include "platform.h"
#include "taler/taler_util.h"


/**
 * Number of checks that failed.
 */
static unsigned int fails;


/**
 * Record the outcome of a single check.
 *
 * @param ok true if the check passed
 * @param label human-readable description of the check
 */
static void
check (bool ok,
       const char *label)
{
  if (! ok)
  {
    fprintf (stderr,
             "FAIL %s\n",
             label);
    fails++;
    return;
  }
  fprintf (stderr,
           "ok   %s\n",
           label);
}


/**
 * Check that @a enc decodes to exactly @a len bytes.
 *
 * @param enc Crockford base32-encoded value
 * @param len expected number of bytes
 * @return true if the length matches
 */
static bool
decodes_to (const char *enc,
            size_t len)
{
  unsigned char buf[128];

  GNUNET_assert (len <= sizeof (buf));
  return (GNUNET_OK ==
          GNUNET_STRINGS_string_to_data (enc,
                                         strlen (enc),
                                         buf,
                                         len));
}


/**
 * Run the full challenge-signature test suite for one algorithm.
 *
 * @param alg algorithm to exercise
 * @param name human-readable name of @a alg
 * @param pub_len expected public key length in bytes
 */
static void
test_challenge_alg (enum TALER_MerchantConfirmationAlgorithm alg,
                    const char *name,
                    size_t pub_len)
{
  char *priv = NULL;
  char *pub = NULL;
  char *priv2 = NULL;
  char *pub2 = NULL;
  char *conf;
  char *conf2;
  struct TALER_PosChallengeP ch;
  struct TALER_PosChallengeP ch2;
  char label[256];

#define LABEL(what) \
  GNUNET_snprintf (label, sizeof (label), "%s: %s", name, what)

  fprintf (stderr,
           "-- %s --\n",
           name);
  LABEL ("keygen");
  check (GNUNET_OK ==
         TALER_otp_device_key_create (alg,
                                      &priv,
                                      &pub),
         label);
  if ( (NULL == priv) ||
       (NULL == pub) )
    return;
  LABEL ("public key has the documented length");
  check (decodes_to (pub,
                     pub_len),
         label);

  GNUNET_CRYPTO_random_block (&ch,
                              sizeof (ch));
  conf = TALER_build_pos_confirmation_sig (priv,
                                           alg,
                                           &ch);
  LABEL ("sign");
  check (NULL != conf,
         label);
  if (NULL == conf)
    return;
  LABEL ("signature is 64 bytes");
  check (decodes_to (conf,
                     64),
         label);
  LABEL ("verify accepts a fresh confirmation");
  check (GNUNET_OK ==
         TALER_check_pos_confirmation_sig (pub,
                                           alg,
                                           &ch,
                                           conf),
         label);

  /* DD 97: modifying the challenge must invalidate the confirmation */
  ch2 = ch;
  ch2.challenge[0] ^= 0x01;
  LABEL ("mutated challenge is rejected");
  check (GNUNET_OK !=
         TALER_check_pos_confirmation_sig (pub,
                                           alg,
                                           &ch2,
                                           conf),
         label);

  /* DD 97: another device's key must not accept the confirmation */
  check (GNUNET_OK ==
         TALER_otp_device_key_create (alg,
                                      &priv2,
                                      &pub2),
         "second keygen");
  LABEL ("confirmation is rejected under a different key");
  check (GNUNET_OK !=
         TALER_check_pos_confirmation_sig (pub2,
                                           alg,
                                           &ch,
                                           conf),
         label);

  conf2 = TALER_build_pos_confirmation_sig (priv2,
                                            alg,
                                            &ch);
  LABEL ("a different key yields a different confirmation");
  check ( (NULL != conf2) &&
          (0 != strcmp (conf,
                        conf2)),
          label);

  {
    char *bad = GNUNET_strdup (conf);

    bad[0] = ('A' == bad[0]) ? 'B' : 'A';
    LABEL ("mauled confirmation is rejected");
    check (GNUNET_OK !=
           TALER_check_pos_confirmation_sig (pub,
                                             alg,
                                             &ch,
                                             bad),
           label);
    GNUNET_free (bad);
  }

  /* DD 97: must not return a confirmation we cannot compute */
  LABEL ("missing challenge fails closed");
  check (NULL ==
         TALER_build_pos_confirmation_sig (priv,
                                           alg,
                                           NULL),
         label);
#undef LABEL
  GNUNET_free (conf);
  GNUNET_free (conf2);
  GNUNET_free (priv);
  GNUNET_free (pub);
  GNUNET_free (priv2);
  GNUNET_free (pub2);
}


/**
 * Known-answer vectors. These pin the on-the-wire format an offline
 * verifier depends on: a round-trip test would happily follow us if
 * the salt or the field order ever changed.
 */
#define KAT_EDDSA_PRIV "041061050R3GG28A1C60T3GF208H44RM2MB1E60S38DHR78Y3WG0"
#define KAT_EDDSA_PUB  "F6TNCBMFWSAFJG3RP49EHACBMY81Z19TWTAVXNZ0WE8GQB84JSJ0"
#define KAT_EDDSA_SIG \
  "VKTQA94GZCQ6R5AWV51X51X4P6Q4EE8F0MXWWZXMGRKJKR2KV2BRBXJG8YV4E0VY56GG" \
  "CBCG5XAMEV1C1AD9FNXHDBXK4JXNZ008E08"
#define KAT_ECDSA_PUB  "0CZV95QR7R1GKGFFDAW2N4ZMAMXTWYF4AHX7XR87F3XW5KZV9C922"
#define KAT_ECDSA_SIG \
  "03Q7RNNBBRPKG4YNH5JH92H557JHHGV92M7MMFXD9MH0MQFJG46QADH7GXNFNGTPD6MD" \
  "80Y4CC3ESKTRM7AF9PRBXVN4FMYEY7JWMM8"


/**
 * Check the confirmations we produce against fixed vectors, so that a
 * change to the signed message is caught instead of silently breaking
 * every already-deployed offline verifier.
 */
static void
test_known_answers (void)
{
  struct TALER_PosChallengeP ch;
  char *conf;

  fprintf (stderr,
           "-- known-answer vectors --\n");
  for (unsigned int i = 0; i < sizeof (ch.challenge); i++)
    ch.challenge[i] = (unsigned char) (0xF0 - i);

  /* EdDSA signing is deterministic, so the exact bytes are pinned */
  conf = TALER_build_pos_confirmation_sig (KAT_EDDSA_PRIV,
                                           TALER_MCA_EDDSA_CHALLENGE,
                                           &ch);
  check ( (NULL != conf) &&
          (0 == strcmp (conf,
                        KAT_EDDSA_SIG)),
          "EdDSA confirmation matches the known-answer vector");
  GNUNET_free (conf);
  check (GNUNET_OK ==
         TALER_check_pos_confirmation_sig (KAT_EDDSA_PUB,
                                           TALER_MCA_EDDSA_CHALLENGE,
                                           &ch,
                                           KAT_EDDSA_SIG),
         "EdDSA known-answer vector verifies");

  /* ECDSA signing is randomized, so only verification can be pinned */
  check (GNUNET_OK ==
         TALER_check_pos_confirmation_sig (KAT_ECDSA_PUB,
                                           TALER_MCA_ECDSA_CHALLENGE,
                                           &ch,
                                           KAT_ECDSA_SIG),
         "ECDSA known-answer vector verifies");
}


/**
 * Re-encode @a raw as a confirmation string.
 *
 * @param raw 64 raw signature bytes
 * @return encoded signature, to be freed by the caller
 */
static char *
encode_sig (const unsigned char *raw)
{
  return GNUNET_STRINGS_data_to_string_alloc (raw,
                                              64);
}


/**
 * Decode the confirmation @a enc into its 64 raw bytes.
 *
 * @param enc encoded signature
 * @param[out] raw where to write the raw signature
 */
static void
decode_sig (const char *enc,
            unsigned char *raw)
{
  GNUNET_assert (GNUNET_OK ==
                 GNUNET_STRINGS_string_to_data (enc,
                                                strlen (enc),
                                                raw,
                                                64));
}


/**
 * Add the Ed25519 group order L to the little-endian scalar S in
 * place.  The result is a different encoding of the same scalar
 * modulo L, which a verifier that omits the canonicality check would
 * wrongly accept.
 *
 * @param[in,out] s the 32 little-endian bytes of S
 */
static void
add_ed25519_order (unsigned char *s)
{
  static const unsigned char L[32] = {
    0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58,
    0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10
  };
  unsigned int carry = 0;

  for (unsigned int i = 0; i < 32; i++)
  {
    unsigned int v = s[i] + L[i] + carry;

    s[i] = (unsigned char) (v & 0xff);
    carry = v >> 8;
  }
}


/**
 * Replace the big-endian P-256 scalar @a s by n-s in place.  Both
 * (r,s) and (r,n-s) are mathematically valid ECDSA signatures; we
 * accept only the smaller one.
 *
 * @param[in,out] s the 32 big-endian bytes of s
 */
static void
negate_p256_scalar (unsigned char *s)
{
  static const unsigned char n[32] = {
    0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17, 0x9e, 0x84,
    0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x51
  };
  int borrow = 0;

  for (int i = 31; i >= 0; i--)
  {
    int v = (int) n[i] - (int) s[i] - borrow;

    if (v < 0)
    {
      v += 256;
      borrow = 1;
    }
    else
    {
      borrow = 0;
    }
    s[i] = (unsigned char) v;
  }
}


/**
 * A confirmation must not be transformable into a second confirmation
 * that a verifier would also accept: an offline device that tracked
 * spent confirmations instead of spent challenges would otherwise be
 * replayable.
 */
static void
test_malleability (void)
{
  char *priv;
  char *pub;
  char *conf;
  char *mauled;
  struct TALER_PosChallengeP ch;
  unsigned char raw[64];

  fprintf (stderr,
           "-- malleability --\n");
  GNUNET_CRYPTO_random_block (&ch,
                              sizeof (ch));

  /* Ed25519: S+L encodes the same scalar; RFC 8032 requires S < L */
  GNUNET_assert (GNUNET_OK ==
                 TALER_otp_device_key_create (TALER_MCA_EDDSA_CHALLENGE,
                                              &priv,
                                              &pub));
  conf = TALER_build_pos_confirmation_sig (priv,
                                           TALER_MCA_EDDSA_CHALLENGE,
                                           &ch);
  GNUNET_assert (NULL != conf);
  decode_sig (conf,
              raw);
  add_ed25519_order (&raw[32]);
  mauled = encode_sig (raw);
  check (GNUNET_OK !=
         TALER_check_pos_confirmation_sig (pub,
                                           TALER_MCA_EDDSA_CHALLENGE,
                                           &ch,
                                           mauled),
         "EdDSA non-canonical S (S+L) is rejected");
  GNUNET_free (mauled);
  GNUNET_free (conf);
  GNUNET_free (priv);
  GNUNET_free (pub);

  /* P-256: (r, n-s) is a valid signature that we reject by policy */
  GNUNET_assert (GNUNET_OK ==
                 TALER_otp_device_key_create (TALER_MCA_ECDSA_CHALLENGE,
                                              &priv,
                                              &pub));
  conf = TALER_build_pos_confirmation_sig (priv,
                                           TALER_MCA_ECDSA_CHALLENGE,
                                           &ch);
  GNUNET_assert (NULL != conf);
  decode_sig (conf,
              raw);
  negate_p256_scalar (&raw[32]);
  mauled = encode_sig (raw);
  check (GNUNET_OK !=
         TALER_check_pos_confirmation_sig (pub,
                                           TALER_MCA_ECDSA_CHALLENGE,
                                           &ch,
                                           mauled),
         "ECDSA high-s variant is rejected");
  GNUNET_free (mauled);
  GNUNET_free (conf);
  GNUNET_free (priv);
  GNUNET_free (pub);
}


/**
 * Every ECDSA signature we emit must be the canonical low-s one, and
 * the public key must be a compressed point.  Runs enough iterations
 * that a scalar with a leading zero byte -- which the fixed-width
 * encoding has to pad -- is hit with near certainty.
 */
static void
test_ecdsa_encoding (void)
{
  unsigned int low_s_failures = 0;
  unsigned int prefix_failures = 0;
  unsigned int verify_failures = 0;
  unsigned int short_scalars = 0;

  fprintf (stderr,
           "-- ECDSA encoding (300 iterations) --\n");
  for (unsigned int i = 0; i < 300; i++)
  {
    char *priv;
    char *pub;
    char *conf;
    struct TALER_PosChallengeP ch;
    unsigned char raw[64];
    unsigned char pubraw[33];
    unsigned char neg[32];

    GNUNET_assert (GNUNET_OK ==
                   TALER_otp_device_key_create (TALER_MCA_ECDSA_CHALLENGE,
                                                &priv,
                                                &pub));
    GNUNET_CRYPTO_random_block (&ch,
                                sizeof (ch));
    conf = TALER_build_pos_confirmation_sig (priv,
                                             TALER_MCA_ECDSA_CHALLENGE,
                                             &ch);
    GNUNET_assert (NULL != conf);
    GNUNET_assert (GNUNET_OK ==
                   GNUNET_STRINGS_string_to_data (pub,
                                                  strlen (pub),
                                                  pubraw,
                                                  sizeof (pubraw)));
    if ( (0x02 != pubraw[0]) &&
         (0x03 != pubraw[0]) )
      prefix_failures++;
    decode_sig (conf,
                raw);
    /* s is low exactly when it is smaller than n-s */
    memcpy (neg,
            &raw[32],
            sizeof (neg));
    negate_p256_scalar (neg);
    if (memcmp (&raw[32],
                neg,
                sizeof (neg)) >= 0)
      low_s_failures++;
    /* count fixed-width scalars that needed zero padding */
    if ( (0x00 == raw[0]) ||
         (0x00 == raw[32]) )
      short_scalars++;
    if (GNUNET_OK !=
        TALER_check_pos_confirmation_sig (pub,
                                          TALER_MCA_ECDSA_CHALLENGE,
                                          &ch,
                                          conf))
      verify_failures++;
    GNUNET_free (conf);
    GNUNET_free (priv);
    GNUNET_free (pub);
  }
  check (0 == prefix_failures,
         "every public key is a compressed point (0x02/0x03)");
  check (0 == low_s_failures,
         "every signature uses the canonical low-s form");
  check (0 == verify_failures,
         "every signature verifies");
  fprintf (stderr,
           "     (%u/300 signatures had a scalar needing zero padding)\n",
           short_scalars);
}


/**
 * Ed25519 signing is deterministic while ECDSA is randomized; a
 * repeated ECDSA nonce would be catastrophic, so the signatures must
 * differ.
 */
static void
test_signing_determinism (void)
{
  char *priv;
  char *pub;
  char *a;
  char *b;
  struct TALER_PosChallengeP ch;

  fprintf (stderr,
           "-- signing determinism --\n");
  GNUNET_CRYPTO_random_block (&ch,
                              sizeof (ch));
  GNUNET_assert (GNUNET_OK ==
                 TALER_otp_device_key_create (TALER_MCA_EDDSA_CHALLENGE,
                                              &priv,
                                              &pub));
  a = TALER_build_pos_confirmation_sig (priv,
                                        TALER_MCA_EDDSA_CHALLENGE,
                                        &ch);
  b = TALER_build_pos_confirmation_sig (priv,
                                        TALER_MCA_EDDSA_CHALLENGE,
                                        &ch);
  check ( (NULL != a) &&
          (NULL != b) &&
          (0 == strcmp (a,
                        b)),
          "EdDSA signing is deterministic");
  GNUNET_free (a);
  GNUNET_free (b);
  GNUNET_free (priv);
  GNUNET_free (pub);

  GNUNET_assert (GNUNET_OK ==
                 TALER_otp_device_key_create (TALER_MCA_ECDSA_CHALLENGE,
                                              &priv,
                                              &pub));
  a = TALER_build_pos_confirmation_sig (priv,
                                        TALER_MCA_ECDSA_CHALLENGE,
                                        &ch);
  b = TALER_build_pos_confirmation_sig (priv,
                                        TALER_MCA_ECDSA_CHALLENGE,
                                        &ch);
  check ( (NULL != a) &&
          (NULL != b) &&
          (0 != strcmp (a,
                        b)),
          "ECDSA signing uses a fresh nonce each time");
  check ( (GNUNET_OK ==
           TALER_check_pos_confirmation_sig (pub,
                                             TALER_MCA_ECDSA_CHALLENGE,
                                             &ch,
                                             a)) &&
          (GNUNET_OK ==
           TALER_check_pos_confirmation_sig (pub,
                                             TALER_MCA_ECDSA_CHALLENGE,
                                             &ch,
                                             b)),
          "both ECDSA signatures verify");
  GNUNET_free (a);
  GNUNET_free (b);
  GNUNET_free (priv);
  GNUNET_free (pub);
}


/**
 * Is the compressed point @a raw refused as the public key for the
 * confirmation @a sig?
 *
 * @param raw 33 bytes to offer as a public key
 * @param ch challenge the confirmation is bound to
 * @param sig a confirmation that is valid under the real key
 * @return true if verification refused @a raw
 */
static bool
pub_rejected (const unsigned char *raw,
              const struct TALER_PosChallengeP *ch,
              const char *sig)
{
  char *enc;
  enum GNUNET_GenericReturnValue r;

  enc = GNUNET_STRINGS_data_to_string_alloc (raw,
                                             33);
  r = TALER_check_pos_confirmation_sig (enc,
                                        TALER_MCA_ECDSA_CHALLENGE,
                                        ch,
                                        sig);
  GNUNET_free (enc);
  return (GNUNET_OK != r);
}


/**
 * A public key that is not a valid curve point must be refused.  The
 * compressed encoding bounds what can even be expressed, so the cases
 * are an X with no square root, an X at or beyond the field prime,
 * and an undefined prefix byte.  libgcrypt refuses all of them today
 * and this pins that, so that changing how the point is handed to the
 * backend cannot silently start accepting garbage.
 */
static void
test_ecdsa_invalid_points (void)
{
  char *priv;
  char *pub;
  char *conf;
  struct TALER_PosChallengeP ch;
  unsigned char real[33];
  unsigned char raw[33];
  unsigned int accepted = 0;

  fprintf (stderr,
           "-- ECDSA invalid public points --\n");
  GNUNET_CRYPTO_random_block (&ch,
                              sizeof (ch));
  GNUNET_assert (GNUNET_OK ==
                 TALER_otp_device_key_create (TALER_MCA_ECDSA_CHALLENGE,
                                              &priv,
                                              &pub));
  conf = TALER_build_pos_confirmation_sig (priv,
                                           TALER_MCA_ECDSA_CHALLENGE,
                                           &ch);
  GNUNET_assert (NULL != conf);
  GNUNET_assert (GNUNET_OK ==
                 GNUNET_STRINGS_string_to_data (pub,
                                                strlen (pub),
                                                real,
                                                sizeof (real)));

  /* X = 1 has no square root modulo p, so these 33 bytes do not
     encode a point at all (cross-checked against OpenSSL) */
  memset (raw,
          0,
          sizeof (raw));
  raw[0] = 0x02;
  raw[32] = 1;
  check (pub_rejected (raw,
                       &ch,
                       conf),
         "an X with no valid Y is rejected");

  /* X = 5 is on the curve, but it is not our key: this must fail as a
     signature check rather than as a decoding error */
  memset (raw,
          0,
          sizeof (raw));
  raw[0] = 0x02;
  raw[32] = 5;
  check (pub_rejected (raw,
                       &ch,
                       conf),
         "a valid but unrelated point is rejected");

  /* X beyond the field prime */
  raw[0] = 0x02;
  memset (&raw[1],
          0xFF,
          32);
  check (pub_rejected (raw,
                       &ch,
                       conf),
         "an X beyond the field prime is rejected");

  /* undefined or wrong prefix bytes on an otherwise real key */
  memcpy (raw,
          real,
          sizeof (raw));
  raw[0] = 0x00;
  check (pub_rejected (raw,
                       &ch,
                       conf),
         "prefix 0x00 is rejected");
  memcpy (raw,
          real,
          sizeof (raw));
  raw[0] = 0x04;
  check (pub_rejected (raw,
                       &ch,
                       conf),
         "the uncompressed-point prefix 0x04 is rejected");
  memcpy (raw,
          real,
          sizeof (raw));
  raw[0] = 0x05;
  check (pub_rejected (raw,
                       &ch,
                       conf),
         "an undefined prefix is rejected");

  /* flipping the parity bit selects the other point with the same X,
     which is a valid point but the wrong public key */
  memcpy (raw,
          real,
          sizeof (raw));
  raw[0] = (0x02 == real[0]) ? 0x03 : 0x02;
  check (pub_rejected (raw,
                       &ch,
                       conf),
         "flipping the point parity is rejected");

  /* Small X values, both prefixes.  Some are points that are simply
     not our key; the rest have no square root modulo p and do not
     decode at all. None of them may verify. The prefix picks between
     +Y and -Y, so it does not affect whether a Y exists.

     on the curve:  5, 6, 8, 9, 12, 13, 17
     off the curve: 1, 2, 3, 4, 7, 10, 11, 14, 15, 16, 18, 19, 20 */
  for (unsigned int x = 1; x <= 20; x++)
  {
    for (unsigned int parity = 0; parity < 2; parity++)
    {
      memset (raw,
              0,
              sizeof (raw));
      raw[0] = (0 == parity) ? 0x02 : 0x03;
      raw[32] = (unsigned char) x;
      if (! pub_rejected (raw,
                          &ch,
                          conf))
        accepted++;
    }
  }
  check (0 == accepted,
         "40 small compressed points are all rejected");

  GNUNET_free (conf);
  GNUNET_free (priv);
  GNUNET_free (pub);
}


/**
 * Malformed and mismatched inputs must be refused rather than
 * misinterpreted.
 */
static void
test_bad_inputs (void)
{
  char *ed_priv;
  char *ed_pub;
  char *ec_priv;
  char *ec_pub;
  char *ed_sig;
  char *ec_sig;
  struct TALER_PosChallengeP ch;

  fprintf (stderr,
           "-- malformed and mismatched inputs --\n");
  GNUNET_CRYPTO_random_block (&ch,
                              sizeof (ch));

  /* key generation is only defined for the challenge algorithms */
  {
    char *k = NULL;
    char *p = NULL;

    check (GNUNET_OK !=
           TALER_otp_device_key_create (TALER_MCA_NONE,
                                        &k,
                                        &p),
           "keygen refuses NONE");
    check (GNUNET_OK !=
           TALER_otp_device_key_create (TALER_MCA_WITHOUT_PRICE,
                                        &k,
                                        &p),
           "keygen refuses TOTP_WITHOUT_PRICE");
    check (GNUNET_OK !=
           TALER_otp_device_key_create (TALER_MCA_WITH_PRICE,
                                        &k,
                                        &p),
           "keygen refuses TOTP_WITH_PRICE");
  }

  GNUNET_assert (GNUNET_OK ==
                 TALER_otp_device_key_create (TALER_MCA_EDDSA_CHALLENGE,
                                              &ed_priv,
                                              &ed_pub));
  GNUNET_assert (GNUNET_OK ==
                 TALER_otp_device_key_create (TALER_MCA_ECDSA_CHALLENGE,
                                              &ec_priv,
                                              &ec_pub));
  ed_sig = TALER_build_pos_confirmation_sig (ed_priv,
                                             TALER_MCA_EDDSA_CHALLENGE,
                                             &ch);
  ec_sig = TALER_build_pos_confirmation_sig (ec_priv,
                                             TALER_MCA_ECDSA_CHALLENGE,
                                             &ch);
  GNUNET_assert ( (NULL != ed_sig) &&
                  (NULL != ec_sig) );

  check (GNUNET_OK !=
         TALER_check_pos_confirmation_sig (ed_pub,
                                           TALER_MCA_WITH_PRICE,
                                           &ch,
                                           ed_sig),
         "verify refuses a TOTP algorithm");
  /* an Ed25519 key is 32 bytes and a P-256 key 33, so the encodings
     are not interchangeable even before the curve differs */
  check (GNUNET_OK !=
         TALER_check_pos_confirmation_sig (ed_pub,
                                           TALER_MCA_ECDSA_CHALLENGE,
                                           &ch,
                                           ed_sig),
         "an EdDSA confirmation does not verify as ECDSA");
  check (GNUNET_OK !=
         TALER_check_pos_confirmation_sig (ec_pub,
                                           TALER_MCA_EDDSA_CHALLENGE,
                                           &ch,
                                           ec_sig),
         "an ECDSA confirmation does not verify as EdDSA");
  check (GNUNET_OK !=
         TALER_check_pos_confirmation_sig (ec_pub,
                                           TALER_MCA_ECDSA_CHALLENGE,
                                           &ch,
                                           ed_sig),
         "a confirmation made with the wrong key is rejected");
  check (GNUNET_OK !=
         TALER_check_pos_confirmation_sig ("!!!not base32!!!",
                                           TALER_MCA_EDDSA_CHALLENGE,
                                           &ch,
                                           ed_sig),
         "a malformed public key is rejected");
  check (GNUNET_OK !=
         TALER_check_pos_confirmation_sig (ed_pub,
                                           TALER_MCA_EDDSA_CHALLENGE,
                                           &ch,
                                           "!!!not base32!!!"),
         "a malformed confirmation is rejected");
  {
    char *truncated = GNUNET_strdup (ed_sig);

    truncated[strlen (truncated) - 4] = '\0';
    check (GNUNET_OK !=
           TALER_check_pos_confirmation_sig (ed_pub,
                                             TALER_MCA_EDDSA_CHALLENGE,
                                             &ch,
                                             truncated),
           "a truncated confirmation is rejected");
    GNUNET_free (truncated);
  }
  {
    char *truncated = GNUNET_strdup (ed_pub);

    truncated[strlen (truncated) - 4] = '\0';
    check (GNUNET_OK !=
           TALER_check_pos_confirmation_sig (truncated,
                                             TALER_MCA_EDDSA_CHALLENGE,
                                             &ch,
                                             ed_sig),
           "a truncated public key is rejected");
    GNUNET_free (truncated);
  }
  /* signing must refuse a key that is not the right size */
  check (NULL ==
         TALER_build_pos_confirmation_sig ("TOOSHORT",
                                           TALER_MCA_EDDSA_CHALLENGE,
                                           &ch),
         "signing refuses a short EdDSA key");
  check (NULL ==
         TALER_build_pos_confirmation_sig ("TOOSHORT",
                                           TALER_MCA_ECDSA_CHALLENGE,
                                           &ch),
         "signing refuses a short ECDSA key");

  GNUNET_free (ed_sig);
  GNUNET_free (ec_sig);
  GNUNET_free (ed_priv);
  GNUNET_free (ed_pub);
  GNUNET_free (ec_priv);
  GNUNET_free (ec_pub);
}


/**
 * The challenge algorithms must not have disturbed the TOTP
 * algorithms, which stay bit-for-bit as they were.
 */
static void
test_totp_unchanged (void)
{
  /* RFC 3548 base32, as a TOTP application would provide it */
  const char *pos_key = "JBSWY3DPEHPK3PXP";
  struct TALER_Amount total;
  char *code;

  fprintf (stderr,
           "-- TOTP regression --\n");
  code = TALER_build_pos_confirmation (pos_key,
                                       TALER_MCA_WITHOUT_PRICE,
                                       NULL,
                                       GNUNET_TIME_UNIT_ZERO_TS);
  check (NULL != code,
         "TOTP_WITHOUT_PRICE still produces a code");
  GNUNET_free (code);

  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("EUR:1.5",
                                         &total));
  code = TALER_build_pos_confirmation (pos_key,
                                       TALER_MCA_WITH_PRICE,
                                       &total,
                                       GNUNET_TIME_UNIT_ZERO_TS);
  check (NULL != code,
         "TOTP_WITH_PRICE still produces a code");
  GNUNET_free (code);

  /* an invalid amount must fail closed rather than sign nothing */
  code = TALER_build_pos_confirmation (pos_key,
                                       TALER_MCA_WITH_PRICE,
                                       NULL,
                                       GNUNET_TIME_UNIT_ZERO_TS);
  check (NULL == code,
         "TOTP_WITH_PRICE without an amount fails closed");
  GNUNET_free (code);
}


int
main (int argc,
      char *const *argv)
{
  (void) argc;
  (void) argv;
  GNUNET_log_setup ("test-crypto-confirmation",
                    "WARNING",
                    NULL);
  test_challenge_alg (TALER_MCA_EDDSA_CHALLENGE,
                      "EDDSA_CHALLENGE",
                      32);
  test_challenge_alg (TALER_MCA_ECDSA_CHALLENGE,
                      "ECDSA_CHALLENGE",
                      33);
  test_known_answers ();
  test_malleability ();
  test_ecdsa_encoding ();
  test_signing_determinism ();
  test_ecdsa_invalid_points ();
  test_bad_inputs ();
  test_totp_unchanged ();
  return (0 == fails) ? 0 : 1;
}


/* end of test_crypto_confirmation.c */
