/*
  This file is part of TALER
  Copyright (C) 2021-2023 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 wallet_signatures.c
 * @brief Utility functions for Taler wallet signatures
 * @author Christian Grothoff
 * @author Özgür Kesim
 */
#include "platform.h"
#include "taler_util.h"
#include "taler_signatures.h"
#include <gnunet/gnunet_common.h>
#include <stdint.h>


GNUNET_NETWORK_STRUCT_BEGIN

/**
 * @brief Format used to generate the signature on a request to deposit
 * a coin into the account of a merchant.
 */
struct TALER_DepositRequestPS
{
  /**
   * Purpose must be #TALER_SIGNATURE_WALLET_COIN_DEPOSIT.
   * Used for an EdDSA signature with the `struct TALER_CoinSpendPublicKeyP`.
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * Hash over the contract for which this deposit is made.
   */
  struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED;

  /**
   * Hash over the age commitment that went into the coin. Maybe all zero, if
   * age commitment isn't applicable to the denomination.
   */
  struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED;

  /**
   * Hash over optional policy extension attributes shared with the exchange.
   */
  struct TALER_ExtensionPolicyHashP h_policy GNUNET_PACKED;

  /**
   * Hash over the wiring information of the merchant.
   */
  struct TALER_MerchantWireHashP h_wire GNUNET_PACKED;

  /**
   * Hash over the denomination public key used to sign the coin.
   */
  struct TALER_DenominationHashP h_denom_pub GNUNET_PACKED;

  /**
   * Time when this request was generated.  Used, for example, to
   * assess when (roughly) the income was achieved for tax purposes.
   * Note that the Exchange will only check that the timestamp is not "too
   * far" into the future (i.e. several days).  The fact that the
   * timestamp falls within the validity period of the coin's
   * denomination key is irrelevant for the validity of the deposit
   * request, as obviously the customer and merchant could conspire to
   * set any timestamp.  Also, the Exchange must accept very old deposit
   * requests, as the merchant might have been unable to transmit the
   * deposit request in a timely fashion (so back-dating is not
   * prevented).
   */
  struct GNUNET_TIME_TimestampNBO wallet_timestamp;

  /**
   * How much time does the merchant have to issue a refund request?
   * Zero if refunds are not allowed.  After this time, the coin
   * cannot be refunded.
   */
  struct GNUNET_TIME_TimestampNBO refund_deadline;

  /**
   * Amount to be deposited, including deposit fee charged by the
   * exchange.  This is the total amount that the coin's value at the exchange
   * will be reduced by.
   */
  struct TALER_AmountNBO amount_with_fee;

  /**
   * Depositing fee charged by the exchange.  This must match the Exchange's
   * denomination key's depositing fee.  If the client puts in an
   * invalid deposit fee (too high or too low) that does not match the
   * Exchange's denomination key, the deposit operation is invalid and
   * will be rejected by the exchange.  The @e amount_with_fee minus the
   * @e deposit_fee is the amount that will be transferred to the
   * account identified by @e h_wire.
   */
  struct TALER_AmountNBO deposit_fee;

  /**
   * The Merchant's public key.  Allows the merchant to later refund
   * the transaction or to inquire about the wire transfer identifier.
   */
  struct TALER_MerchantPublicKeyP merchant;

  /**
   * Hash over a JSON containing data provided by the
   * wallet to complete the contract upon payment.
   */
  struct GNUNET_HashCode wallet_data_hash;

};

GNUNET_NETWORK_STRUCT_END

void
TALER_wallet_deposit_sign (
  const struct TALER_Amount *amount,
  const struct TALER_Amount *deposit_fee,
  const struct TALER_MerchantWireHashP *h_wire,
  const struct TALER_PrivateContractHashP *h_contract_terms,
  const struct GNUNET_HashCode *wallet_data_hash,
  const struct TALER_AgeCommitmentHash *h_age_commitment,
  const struct TALER_ExtensionPolicyHashP *h_policy,
  const struct TALER_DenominationHashP *h_denom_pub,
  const struct GNUNET_TIME_Timestamp wallet_timestamp,
  const struct TALER_MerchantPublicKeyP *merchant_pub,
  const struct GNUNET_TIME_Timestamp refund_deadline,
  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
  struct TALER_CoinSpendSignatureP *coin_sig)
{
  struct TALER_DepositRequestPS dr = {
    .purpose.size = htonl (sizeof (dr)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT),
    .h_contract_terms = *h_contract_terms,
    .h_wire = *h_wire,
    .h_denom_pub = *h_denom_pub,
    .wallet_timestamp = GNUNET_TIME_timestamp_hton (wallet_timestamp),
    .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline),
    .merchant = *merchant_pub
  };

  if (NULL != wallet_data_hash)
    dr.wallet_data_hash = *wallet_data_hash;
  if (NULL != h_age_commitment)
    dr.h_age_commitment = *h_age_commitment;
  if (NULL != h_policy)
    dr.h_policy = *h_policy;
  TALER_amount_hton (&dr.amount_with_fee,
                     amount);
  TALER_amount_hton (&dr.deposit_fee,
                     deposit_fee);
  GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
                            &dr,
                            &coin_sig->eddsa_signature);
}


enum GNUNET_GenericReturnValue
TALER_wallet_deposit_verify (
  const struct TALER_Amount *amount,
  const struct TALER_Amount *deposit_fee,
  const struct TALER_MerchantWireHashP *h_wire,
  const struct TALER_PrivateContractHashP *h_contract_terms,
  const struct GNUNET_HashCode *wallet_data_hash,
  const struct TALER_AgeCommitmentHash *h_age_commitment,
  const struct TALER_ExtensionPolicyHashP *h_policy,
  const struct TALER_DenominationHashP *h_denom_pub,
  struct GNUNET_TIME_Timestamp wallet_timestamp,
  const struct TALER_MerchantPublicKeyP *merchant_pub,
  struct GNUNET_TIME_Timestamp refund_deadline,
  const struct TALER_CoinSpendPublicKeyP *coin_pub,
  const struct TALER_CoinSpendSignatureP *coin_sig)
{
  struct TALER_DepositRequestPS dr = {
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT),
    .purpose.size = htonl (sizeof (dr)),
    .h_contract_terms = *h_contract_terms,
    .h_wire = *h_wire,
    .h_denom_pub = *h_denom_pub,
    .wallet_timestamp = GNUNET_TIME_timestamp_hton (wallet_timestamp),
    .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline),
    .merchant = *merchant_pub,
  };

  if (NULL != wallet_data_hash)
    dr.wallet_data_hash = *wallet_data_hash;
  if (NULL != h_age_commitment)
    dr.h_age_commitment = *h_age_commitment;
  if (NULL != h_policy)
    dr.h_policy = *h_policy;
  TALER_amount_hton (&dr.amount_with_fee,
                     amount);
  TALER_amount_hton (&dr.deposit_fee,
                     deposit_fee);
  if (GNUNET_OK !=
      GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT,
                                  &dr,
                                  &coin_sig->eddsa_signature,
                                  &coin_pub->eddsa_pub))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


GNUNET_NETWORK_STRUCT_BEGIN

/**
 * @brief Format used for to allow the wallet to authenticate
 * link data provided by the exchange.
 */
struct TALER_LinkDataPS
{

  /**
   * Purpose must be #TALER_SIGNATURE_WALLET_COIN_LINK.
   * Used with an EdDSA signature of a `struct TALER_CoinPublicKeyP`.
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * Hash of the denomination public key of the new coin.
   */
  struct TALER_DenominationHashP h_denom_pub;

  /**
   * Transfer public key (for which the private key was not revealed)
   */
  struct TALER_TransferPublicKeyP transfer_pub;

  /**
   * Hash of the age commitment, if applicable.  Can be all zero
   */
  struct TALER_AgeCommitmentHash h_age_commitment;

  /**
   * Hash of the blinded new coin.
   */
  struct TALER_BlindedCoinHashP coin_envelope_hash;
};

GNUNET_NETWORK_STRUCT_END

void
TALER_wallet_link_sign (const struct TALER_DenominationHashP *h_denom_pub,
                        const struct TALER_TransferPublicKeyP *transfer_pub,
                        const struct TALER_BlindedCoinHashP *bch,
                        const struct TALER_CoinSpendPrivateKeyP *old_coin_priv,
                        struct TALER_CoinSpendSignatureP *coin_sig)
{
  struct TALER_LinkDataPS ldp = {
    .purpose.size = htonl (sizeof (ldp)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK),
    .h_denom_pub = *h_denom_pub,
    .transfer_pub = *transfer_pub,
    .coin_envelope_hash = *bch
  };

  GNUNET_CRYPTO_eddsa_sign (&old_coin_priv->eddsa_priv,
                            &ldp,
                            &coin_sig->eddsa_signature);
}


enum GNUNET_GenericReturnValue
TALER_wallet_link_verify (
  const struct TALER_DenominationHashP *h_denom_pub,
  const struct TALER_TransferPublicKeyP *transfer_pub,
  const struct TALER_BlindedCoinHashP *h_coin_ev,
  const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
  const struct TALER_CoinSpendSignatureP *coin_sig)
{
  struct TALER_LinkDataPS ldp = {
    .purpose.size = htonl (sizeof (ldp)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK),
    .h_denom_pub = *h_denom_pub,
    .transfer_pub = *transfer_pub,
    .coin_envelope_hash = *h_coin_ev,
  };

  return
    GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_LINK,
                                &ldp,
                                &coin_sig->eddsa_signature,
                                &old_coin_pub->eddsa_pub);
}


GNUNET_NETWORK_STRUCT_BEGIN

/**
 * Signed data to request that a coin should be refunded as part of
 * the "emergency" /recoup protocol.  The refund will go back to the bank
 * account that created the reserve.
 */
struct TALER_RecoupRequestPS
{
  /**
   * Purpose is #TALER_SIGNATURE_WALLET_COIN_RECOUP
   * or #TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH.
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * Hash of the (revoked) denomination public key of the coin.
   */
  struct TALER_DenominationHashP h_denom_pub;

  /**
   * Blinding factor that was used to withdraw the coin.
   */
  union GNUNET_CRYPTO_BlindingSecretP coin_blind;

};

GNUNET_NETWORK_STRUCT_END


enum GNUNET_GenericReturnValue
TALER_wallet_recoup_verify (
  const struct TALER_DenominationHashP *h_denom_pub,
  const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
  const struct TALER_CoinSpendPublicKeyP *coin_pub,
  const struct TALER_CoinSpendSignatureP *coin_sig)
{
  struct TALER_RecoupRequestPS pr = {
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP),
    .purpose.size = htonl (sizeof (pr)),
    .h_denom_pub = *h_denom_pub,
    .coin_blind = *coin_bks
  };

  return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_RECOUP,
                                     &pr,
                                     &coin_sig->eddsa_signature,
                                     &coin_pub->eddsa_pub);
}


void
TALER_wallet_recoup_sign (
  const struct TALER_DenominationHashP *h_denom_pub,
  const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
  struct TALER_CoinSpendSignatureP *coin_sig)
{
  struct TALER_RecoupRequestPS pr = {
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP),
    .purpose.size = htonl (sizeof (pr)),
    .h_denom_pub = *h_denom_pub,
    .coin_blind = *coin_bks
  };

  GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
                            &pr,
                            &coin_sig->eddsa_signature);
}


enum GNUNET_GenericReturnValue
TALER_wallet_recoup_refresh_verify (
  const struct TALER_DenominationHashP *h_denom_pub,
  const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
  const struct TALER_CoinSpendPublicKeyP *coin_pub,
  const struct TALER_CoinSpendSignatureP *coin_sig)
{
  struct TALER_RecoupRequestPS pr = {
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH),
    .purpose.size = htonl (sizeof (pr)),
    .h_denom_pub = *h_denom_pub,
    .coin_blind = *coin_bks
  };

  return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH,
                                     &pr,
                                     &coin_sig->eddsa_signature,
                                     &coin_pub->eddsa_pub);
}


void
TALER_wallet_recoup_refresh_sign (
  const struct TALER_DenominationHashP *h_denom_pub,
  const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
  struct TALER_CoinSpendSignatureP *coin_sig)
{
  struct TALER_RecoupRequestPS pr = {
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH),
    .purpose.size = htonl (sizeof (struct TALER_RecoupRequestPS)),
    .h_denom_pub = *h_denom_pub,
    .coin_blind = *coin_bks
  };

  GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
                            &pr,
                            &coin_sig->eddsa_signature);
}


GNUNET_NETWORK_STRUCT_BEGIN

/**
 * @brief Message signed by a coin to indicate that the coin should be
 * melted.
 */
struct TALER_RefreshMeltCoinAffirmationPS
{
  /**
   * Purpose is #TALER_SIGNATURE_WALLET_COIN_MELT.
   * Used for an EdDSA signature with the `struct TALER_CoinSpendPublicKeyP`.
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * Which melt commitment is made by the wallet.
   */
  struct TALER_RefreshCommitmentP rc GNUNET_PACKED;

  /**
   * Hash over the denomination public key used to sign the coin.
   */
  struct TALER_DenominationHashP h_denom_pub GNUNET_PACKED;

  /**
   * If age commitment was provided during the withdrawal of the coin, this is
   * the hash of the age commitment vector.  It must be all zeroes if no age
   * commitment was provided.
   */
  struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED;

  /**
   * How much of the value of the coin should be melted?  This amount
   * includes the fees, so the final amount contributed to the melt is
   * this value minus the fee for melting the coin.  We include the
   * fee in what is being signed so that we can verify a reserve's
   * remaining total balance without needing to access the respective
   * denomination key information each time.
   */
  struct TALER_AmountNBO amount_with_fee;

  /**
   * Melting fee charged by the exchange.  This must match the Exchange's
   * denomination key's melting fee.  If the client puts in an invalid
   * melting fee (too high or too low) that does not match the Exchange's
   * denomination key, the melting operation is invalid and will be
   * rejected by the exchange.  The @e amount_with_fee minus the @e
   * melt_fee is the amount that will be credited to the melting
   * session.
   */
  struct TALER_AmountNBO melt_fee;
};

GNUNET_NETWORK_STRUCT_END

void
TALER_wallet_melt_sign (
  const struct TALER_Amount *amount_with_fee,
  const struct TALER_Amount *melt_fee,
  const struct TALER_RefreshCommitmentP *rc,
  const struct TALER_DenominationHashP *h_denom_pub,
  const struct TALER_AgeCommitmentHash *h_age_commitment,
  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
  struct TALER_CoinSpendSignatureP *coin_sig)
{
  struct TALER_RefreshMeltCoinAffirmationPS melt = {
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT),
    .purpose.size = htonl (sizeof (melt)),
    .rc = *rc,
    .h_denom_pub = *h_denom_pub,
    .h_age_commitment = {{{0}}},
  };

  if (NULL != h_age_commitment)
    melt.h_age_commitment = *h_age_commitment;


  TALER_amount_hton (&melt.amount_with_fee,
                     amount_with_fee);
  TALER_amount_hton (&melt.melt_fee,
                     melt_fee);
  GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
                            &melt,
                            &coin_sig->eddsa_signature);
}


enum GNUNET_GenericReturnValue
TALER_wallet_melt_verify (
  const struct TALER_Amount *amount_with_fee,
  const struct TALER_Amount *melt_fee,
  const struct TALER_RefreshCommitmentP *rc,
  const struct TALER_DenominationHashP *h_denom_pub,
  const struct TALER_AgeCommitmentHash *h_age_commitment,
  const struct TALER_CoinSpendPublicKeyP *coin_pub,
  const struct TALER_CoinSpendSignatureP *coin_sig)
{
  struct TALER_RefreshMeltCoinAffirmationPS melt = {
    .purpose.size = htonl (sizeof (melt)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT),
    .rc = *rc,
    .h_denom_pub = *h_denom_pub,
    .h_age_commitment = {{{0}}},
  };

  if (NULL != h_age_commitment)
    melt.h_age_commitment = *h_age_commitment;

  TALER_amount_hton (&melt.amount_with_fee,
                     amount_with_fee);
  TALER_amount_hton (&melt.melt_fee,
                     melt_fee);
  return GNUNET_CRYPTO_eddsa_verify (
    TALER_SIGNATURE_WALLET_COIN_MELT,
    &melt,
    &coin_sig->eddsa_signature,
    &coin_pub->eddsa_pub);
}


GNUNET_NETWORK_STRUCT_BEGIN

/**
 * @brief Format used for to generate the signature on a refresh nonce,
 *  a) to prove ownership of the old coin's private key and
 *  b) to derive the planchet master secrets for the batch of fresh coins
 */
struct TALER_RefreshNonceSignaturePS
{

  /**
   * Purpose must be #TALER_SIGNATURE_WALLET_COIN_LINK
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * The nonce to sign
   */
  struct TALER_PublicRefreshNonceP nonce GNUNET_PACKED;

  /**
   * The running hash of the (hashes of) denomination public keys
   */
  struct GNUNET_HashCode h_denoms_h GNUNET_PACKED;

  /**
   * The kappa index for this signature, in NBO
   */
  uint32_t kappa_index GNUNET_PACKED;
};

GNUNET_NETWORK_STRUCT_END


void
TALER_wallet_refresh_nonce_sign (
  const struct TALER_CoinSpendPrivateKeyP *old_coin_priv,
  const struct TALER_PublicRefreshNonceP *nonce,
  size_t num_denoms_h,
  const struct TALER_DenominationHashP *denoms_h[static num_denoms_h],
  uint8_t kappa_index,
  struct TALER_PrivateRefreshNonceSignatureP *sig)
{
  struct TALER_RefreshNonceSignaturePS req = {
    .purpose.size = htonl (sizeof (req)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK),
    .nonce = *nonce,
    .kappa_index = htonl (kappa_index),
  };
  struct GNUNET_HashContext *ctx = GNUNET_CRYPTO_hash_context_start ();
  GNUNET_assert (ctx);

  for (size_t i = 0; i<num_denoms_h; i++)
    GNUNET_CRYPTO_hash_context_read (ctx,
                                     denoms_h[i],
                                     sizeof(*denoms_h[i]));

  GNUNET_CRYPTO_hash_context_finish (ctx,
                                     &req.h_denoms_h);
  GNUNET_CRYPTO_eddsa_sign (&old_coin_priv->eddsa_priv,
                            &req,
                            &sig->coin_sig.eddsa_signature);
}


enum GNUNET_GenericReturnValue
TALER_wallet_refresh_nonce_verify (
  const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
  const struct TALER_PublicRefreshNonceP *nonce,
  size_t num_denoms_h,
  struct TALER_DenominationHashP *const denoms_h[static num_denoms_h],
  uint8_t kappa_index,
  const struct TALER_PrivateRefreshNonceSignatureP *sig)
{
  struct TALER_RefreshNonceSignaturePS req = {
    .purpose.size = htonl (sizeof (req)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK),
    .nonce = *nonce,
    .kappa_index = htonl (kappa_index),
  };
  struct GNUNET_HashContext *ctx = GNUNET_CRYPTO_hash_context_start ();
  GNUNET_assert (ctx);

  for (size_t i = 0; i<num_denoms_h; i++)
    GNUNET_CRYPTO_hash_context_read (ctx,
                                     denoms_h[i],
                                     sizeof(*denoms_h[i]));

  GNUNET_CRYPTO_hash_context_finish (ctx,
                                     &req.h_denoms_h);
  return GNUNET_CRYPTO_eddsa_verify (
    TALER_SIGNATURE_WALLET_COIN_LINK,
    &req,
    &sig->coin_sig.eddsa_signature,
    &old_coin_pub->eddsa_pub);
}


GNUNET_NETWORK_STRUCT_BEGIN


/**
 * @brief Format used for to generate the signature on a request to withdraw
 * coins from a reserve.
 * @note: deprecated.  Will be removed at some point after v24 of the protocol.
 */
struct TALER_WithdrawCommitmentPre24PS
{

  /**
   * Purpose must be #TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW.
   * Used with an EdDSA signature of a `struct TALER_ReservePublicKeyP`.
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * Value of the coin being exchanged (matching the denomination key)
   * plus the transaction fee.  We include this in what is being
   * signed so that we can verify a reserve's remaining total balance
   * without needing to access the respective denomination key
   * information each time.
   */
  struct TALER_AmountNBO amount_with_fee;

  /**
   * Hash of the denomination public key for the coin that is withdrawn.
   */
  struct TALER_DenominationHashP h_denomination_pub GNUNET_PACKED;

  /**
   * Hash of the (blinded) message to be signed by the Exchange.
   */
  struct TALER_BlindedCoinHashP h_coin_envelope GNUNET_PACKED;
};


GNUNET_NETWORK_STRUCT_END

void
TALER_wallet_withdraw_sign_pre26 (
  const struct TALER_DenominationHashP *h_denom_pub,
  const struct TALER_Amount *amount_with_fee,
  const struct TALER_BlindedCoinHashP *bch,
  const struct TALER_ReservePrivateKeyP *reserve_priv,
  struct TALER_ReserveSignatureP *reserve_sig)
{
  struct TALER_WithdrawCommitmentPre24PS req = {
    .purpose.size = htonl (sizeof (req)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW),
    .h_denomination_pub = *h_denom_pub,
    .h_coin_envelope = *bch
  };

  TALER_amount_hton (&req.amount_with_fee,
                     amount_with_fee);
  GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
                            &req,
                            &reserve_sig->eddsa_signature);
}


enum GNUNET_GenericReturnValue
TALER_wallet_withdraw_verify_pre26 (
  const struct TALER_DenominationHashP *h_denom_pub,
  const struct TALER_Amount *amount_with_fee,
  const struct TALER_BlindedCoinHashP *bch,
  const struct TALER_ReservePublicKeyP *reserve_pub,
  const struct TALER_ReserveSignatureP *reserve_sig)
{
  struct TALER_WithdrawCommitmentPre24PS req = {
    .purpose.size = htonl (sizeof (req)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW),
    .h_denomination_pub = *h_denom_pub,
    .h_coin_envelope = *bch
  };

  TALER_amount_hton (&req.amount_with_fee,
                     amount_with_fee);
  return GNUNET_CRYPTO_eddsa_verify (
    TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
    &req,
    &reserve_sig->eddsa_signature,
    &reserve_pub->eddsa_pub);
}


GNUNET_NETWORK_STRUCT_BEGIN

/**
 * @brief Format used for to generate the signature on a request to withdraw
 * coins from a reserve.
 *
 */
struct TALER_WithdrawRequestPS
{
  /**
   * Purpose is #TALER_SIGNATURE_WALLET_WITHDRAW
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * Total value of all coins being exchanged (matching the denomination keys),
   * without the fee.
   * Note that the reserve must have a value of at least amount+fee.
   */
  struct TALER_AmountNBO amount;

  /**
   * Total fee for the withdrawal.
   * Note that the reserve must have a value of at least amount+fee.
   */
  struct TALER_AmountNBO fee;

  /**
   * Running SHA512 hash of all TALER_BlindedCoinHashP's
   * of the of n coins, or n*kappa candidate coins in case of age restriction.
   * In the later case, the coins' hashes are arranged [0..num_coins)...[0..num_coins),
   * i.e. the coins are grouped per kappa-index.
   * Note that each coin's TALER_BlindedCoinHashP also captures
   * the hash of the public key of the corresponding denomination.
   */
  struct TALER_HashBlindedPlanchetsP h_planchets GNUNET_PACKED;

  /**
   * If any of the denominations is of cipher type Clause-Schnorr,
   * the client had to call /blinding-prepare prior to the withdraw
   * to retrieve public R-values for the CS signature scheme.
   * The input seed for that request must be provided here.
   * Otherwise, if no CS denomination is used, the struct must be all zeros.
   */
  struct TALER_BlindingMasterSeedP blinding_seed;

  /**
   * Maximum age group that the coins are going to be restricted to.
   * MUST be 0 if no age restriction applies.
   */
  uint32_t max_age_group;

  /**
   * The mask that defines the age groups.
   * MUST be the same for all denominations.
   * MUST be 0 if no age restriction applies.
   */
  struct TALER_AgeMask mask;

};


GNUNET_NETWORK_STRUCT_END

void
TALER_wallet_blinded_planchets_hash (
  size_t num_planchets,
  const struct TALER_BlindedPlanchet blinded_planchets[static num_planchets],
  const struct TALER_DenominationHashP h_denom_pubs[static num_planchets],
  struct TALER_HashBlindedPlanchetsP *h_planchets)
{
  struct TALER_BlindedCoinHashP bch;
  struct GNUNET_HashContext *coins_hctx;

  GNUNET_assert (num_planchets > 0);
  GNUNET_assert (NULL != h_planchets);

  coins_hctx = GNUNET_CRYPTO_hash_context_start ();
  GNUNET_assert (NULL != coins_hctx);

  for (size_t i = 0; i < num_planchets; i++)
  {
    TALER_coin_ev_hash (
      &blinded_planchets[i],
      &h_denom_pubs[i],
      &bch);
    GNUNET_CRYPTO_hash_context_read (
      coins_hctx,
      &bch,
      sizeof(bch));
  }

  GNUNET_CRYPTO_hash_context_finish (
    coins_hctx,
    &h_planchets->hash);
}


void
TALER_wallet_blinded_planchet_details_hash (
  size_t num_planchets,
  const struct TALER_PlanchetDetail planchet_details[static num_planchets],
  struct TALER_HashBlindedPlanchetsP *h_planchets)
{
  struct TALER_BlindedCoinHashP bch;
  struct GNUNET_HashContext *coins_hctx;

  GNUNET_assert (num_planchets > 0);
  GNUNET_assert (NULL != h_planchets);

  coins_hctx = GNUNET_CRYPTO_hash_context_start ();
  GNUNET_assert (NULL != coins_hctx);

  for (size_t i = 0; i < num_planchets; i++)
  {
    TALER_coin_ev_hash (
      &planchet_details[i].blinded_planchet,
      &planchet_details[i].denom_pub_hash,
      &bch);
    GNUNET_CRYPTO_hash_context_read (
      coins_hctx,
      &bch,
      sizeof(bch));
  }

  GNUNET_CRYPTO_hash_context_finish (
    coins_hctx,
    &h_planchets->hash);
}


struct TALER_HashReservePublicKeyP
TALER_wallet_hash_reserve_pub (
  const struct TALER_ReservePublicKeyP *reserve_pub)
{
  struct TALER_HashReservePublicKeyP hr;

  GNUNET_CRYPTO_hash (reserve_pub,
                      sizeof(*reserve_pub),
                      &hr.hash);
  return hr;
}


void
TALER_wallet_withdraw_sign (
  const struct TALER_Amount *amount,
  const struct TALER_Amount *fee,
  const struct TALER_HashBlindedPlanchetsP *h_planchets,
  const struct TALER_BlindingMasterSeedP *blinding_seed,
  const struct TALER_AgeMask *mask,
  uint8_t max_age,
  const struct TALER_ReservePrivateKeyP *reserve_priv,
  struct TALER_ReserveSignatureP *reserve_sig)
{
  struct TALER_WithdrawRequestPS req = {
    .purpose.size = htonl (sizeof(req)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW),
  };

  GNUNET_assert (NULL != h_planchets);
  req.h_planchets = *h_planchets;
  if (NULL != mask)
  {
    req.mask = *mask;
    req.max_age_group =
      TALER_get_age_group (mask,
                           max_age);
  }
  TALER_amount_hton (&req.amount,
                     amount);
  TALER_amount_hton (&req.fee,
                     fee);
  if (NULL != blinding_seed)
    req.blinding_seed = *blinding_seed;

  GNUNET_CRYPTO_eddsa_sign (
    &reserve_priv->eddsa_priv,
    &req,
    &reserve_sig->eddsa_signature);

}


enum GNUNET_GenericReturnValue
TALER_wallet_withdraw_verify (
  const struct TALER_Amount *amount,
  const struct TALER_Amount *fee,
  const struct TALER_HashBlindedPlanchetsP *h_planchets,
  const struct TALER_BlindingMasterSeedP *blinding_seed,
  const struct TALER_AgeMask *mask,
  uint8_t max_age,
  const struct TALER_ReservePublicKeyP *reserve_pub,
  const struct TALER_ReserveSignatureP *reserve_sig)
{
  struct TALER_WithdrawRequestPS req = {
    .purpose.size = htonl (sizeof(req)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW),
  };

  GNUNET_assert (NULL != h_planchets);
  req.h_planchets = *h_planchets;
  if (NULL != mask)
  {
    req.mask = *mask;
    req.max_age_group =
      TALER_get_age_group (mask,
                           max_age);
  }
  TALER_amount_hton (&req.amount,
                     amount);
  TALER_amount_hton (&req.fee,
                     fee);
  if (NULL != blinding_seed)
    req.blinding_seed = *blinding_seed;

  return GNUNET_CRYPTO_eddsa_verify (
    TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
    &req,
    &reserve_sig->eddsa_signature,
    &reserve_pub->eddsa_pub);
}


GNUNET_NETWORK_STRUCT_BEGIN


/**
 * @brief Format used for to generate the signature on a request to withdraw
 * coins from a reserve.
 */
struct TALER_AccountSetupRequestSignaturePS
{

  /**
   * Purpose must be #TALER_SIGNATURE_WALLET_ACCOUNT_SETUP.
   * Used with an EdDSA signature of a `struct TALER_ReservePublicKeyP`.
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * Balance threshold the wallet is about to cross.
   */
  struct TALER_AmountNBO threshold;

};


GNUNET_NETWORK_STRUCT_END


void
TALER_wallet_account_setup_sign (
  const struct TALER_ReservePrivateKeyP *reserve_priv,
  const struct TALER_Amount *balance_threshold,
  struct TALER_ReserveSignatureP *reserve_sig)
{
  struct TALER_AccountSetupRequestSignaturePS asap = {
    .purpose.size = htonl (sizeof (asap)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_ACCOUNT_SETUP)
  };

  TALER_amount_hton (&asap.threshold,
                     balance_threshold);
  GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
                            &asap,
                            &reserve_sig->eddsa_signature);
}


enum GNUNET_GenericReturnValue
TALER_wallet_account_setup_verify (
  const struct TALER_ReservePublicKeyP *reserve_pub,
  const struct TALER_Amount *balance_threshold,
  const struct TALER_ReserveSignatureP *reserve_sig)
{
  struct TALER_AccountSetupRequestSignaturePS asap = {
    .purpose.size = htonl (sizeof (asap)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_ACCOUNT_SETUP)
  };

  TALER_amount_hton (&asap.threshold,
                     balance_threshold);
  return GNUNET_CRYPTO_eddsa_verify (
    TALER_SIGNATURE_WALLET_ACCOUNT_SETUP,
    &asap,
    &reserve_sig->eddsa_signature,
    &reserve_pub->eddsa_pub);
}


GNUNET_NETWORK_STRUCT_BEGIN


/**
 * Response by which a wallet requests a reserve history.
 */
struct TALER_ReserveHistoryRequestPS
{

  /**
   * Purpose is #TALER_SIGNATURE_WALLET_RESERVE_HISTORY
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * Which entries to exclude. Only return above this offset.
   */
  uint64_t start_off;

};

GNUNET_NETWORK_STRUCT_END


enum GNUNET_GenericReturnValue
TALER_wallet_reserve_history_verify (
  uint64_t start_off,
  const struct TALER_ReservePublicKeyP *reserve_pub,
  const struct TALER_ReserveSignatureP *reserve_sig)
{
  struct TALER_ReserveHistoryRequestPS rhr = {
    .purpose.size = htonl (sizeof (rhr)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_HISTORY),
    .start_off = GNUNET_htonll (start_off)
  };

  return GNUNET_CRYPTO_eddsa_verify (
    TALER_SIGNATURE_WALLET_RESERVE_HISTORY,
    &rhr,
    &reserve_sig->eddsa_signature,
    &reserve_pub->eddsa_pub);
}


void
TALER_wallet_reserve_history_sign (
  uint64_t start_off,
  const struct TALER_ReservePrivateKeyP *reserve_priv,
  struct TALER_ReserveSignatureP *reserve_sig)
{
  struct TALER_ReserveHistoryRequestPS rhr = {
    .purpose.size = htonl (sizeof (rhr)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_HISTORY),
    .start_off = GNUNET_htonll (start_off)
  };

  GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
                            &rhr,
                            &reserve_sig->eddsa_signature);
}


GNUNET_NETWORK_STRUCT_BEGIN

/**
 * Response by which a wallet requests a coin history.
 */
struct TALER_CoinHistoryRequestPS
{

  /**
   * Purpose is #TALER_SIGNATURE_WALLET_COIN_HISTORY
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * Which entries to exclude. Only return above this offset.
   */
  uint64_t start_off;

};

GNUNET_NETWORK_STRUCT_END

enum GNUNET_GenericReturnValue
TALER_wallet_coin_history_verify (
  uint64_t start_off,
  const struct TALER_CoinSpendPublicKeyP *coin_pub,
  const struct TALER_CoinSpendSignatureP *coin_sig)
{
  struct TALER_CoinHistoryRequestPS rsr = {
    .purpose.size = htonl (sizeof (rsr)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_HISTORY),
    .start_off = GNUNET_htonll (start_off)
  };

  return GNUNET_CRYPTO_eddsa_verify (
    TALER_SIGNATURE_WALLET_COIN_HISTORY,
    &rsr,
    &coin_sig->eddsa_signature,
    &coin_pub->eddsa_pub);
}


void
TALER_wallet_coin_history_sign (
  uint64_t start_off,
  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
  struct TALER_CoinSpendSignatureP *coin_sig)
{
  struct TALER_CoinHistoryRequestPS rsr = {
    .purpose.size = htonl (sizeof (rsr)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_HISTORY),
    .start_off = GNUNET_htonll (start_off)
  };

  GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
                            &rsr,
                            &coin_sig->eddsa_signature);
}


GNUNET_NETWORK_STRUCT_BEGIN

/**
 * Message signed to create a purse (without reserve).
 */
struct TALER_PurseCreatePS
{

  /**
   * Purpose is #TALER_SIGNATURE_WALLET_PURSE_CREATE
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * Time when the purse will expire if still unmerged or unpaid.
   */
  struct GNUNET_TIME_TimestampNBO purse_expiration;

  /**
   * Total amount (with fees) to be put into the purse.
   */
  struct TALER_AmountNBO purse_amount;

  /**
   * Contract this purse pays for.
   */
  struct TALER_PrivateContractHashP h_contract_terms;

  /**
   * Public key identifying the merge capability.
   */
  struct TALER_PurseMergePublicKeyP merge_pub;

  /**
   * Minimum age required for payments into this purse.
   */
  uint32_t min_age GNUNET_PACKED;

};


GNUNET_NETWORK_STRUCT_END


void
TALER_wallet_purse_create_sign (
  struct GNUNET_TIME_Timestamp purse_expiration,
  const struct TALER_PrivateContractHashP *h_contract_terms,
  const struct TALER_PurseMergePublicKeyP *merge_pub,
  uint32_t min_age,
  const struct TALER_Amount *amount,
  const struct TALER_PurseContractPrivateKeyP *purse_priv,
  struct TALER_PurseContractSignatureP *purse_sig)
{
  struct TALER_PurseCreatePS pm = {
    .purpose.size = htonl (sizeof (pm)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_CREATE),
    .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
    .h_contract_terms = *h_contract_terms,
    .merge_pub = *merge_pub,
    .min_age = htonl (min_age)
  };

  TALER_amount_hton (&pm.purse_amount,
                     amount);
  GNUNET_CRYPTO_eddsa_sign (&purse_priv->eddsa_priv,
                            &pm,
                            &purse_sig->eddsa_signature);
}


enum GNUNET_GenericReturnValue
TALER_wallet_purse_create_verify (
  struct GNUNET_TIME_Timestamp purse_expiration,
  const struct TALER_PrivateContractHashP *h_contract_terms,
  const struct TALER_PurseMergePublicKeyP *merge_pub,
  uint32_t min_age,
  const struct TALER_Amount *amount,
  const struct TALER_PurseContractPublicKeyP *purse_pub,
  const struct TALER_PurseContractSignatureP *purse_sig)
{
  struct TALER_PurseCreatePS pm = {
    .purpose.size = htonl (sizeof (pm)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_CREATE),
    .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
    .h_contract_terms = *h_contract_terms,
    .merge_pub = *merge_pub,
    .min_age = htonl (min_age)
  };

  TALER_amount_hton (&pm.purse_amount,
                     amount);
  return GNUNET_CRYPTO_eddsa_verify (
    TALER_SIGNATURE_WALLET_PURSE_CREATE,
    &pm,
    &purse_sig->eddsa_signature,
    &purse_pub->eddsa_pub);
}


GNUNET_NETWORK_STRUCT_BEGIN

/**
 * Message signed to delete a purse.
 */
struct TALER_PurseDeletePS
{

  /**
   * Purpose is #TALER_SIGNATURE_WALLET_PURSE_DELETE
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

};


GNUNET_NETWORK_STRUCT_END


void
TALER_wallet_purse_delete_sign (
  const struct TALER_PurseContractPrivateKeyP *purse_priv,
  struct TALER_PurseContractSignatureP *purse_sig)
{
  struct TALER_PurseDeletePS pm = {
    .purpose.size = htonl (sizeof (pm)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_DELETE)
  };

  GNUNET_CRYPTO_eddsa_sign (&purse_priv->eddsa_priv,
                            &pm,
                            &purse_sig->eddsa_signature);
}


enum GNUNET_GenericReturnValue
TALER_wallet_purse_delete_verify (
  const struct TALER_PurseContractPublicKeyP *purse_pub,
  const struct TALER_PurseContractSignatureP *purse_sig)
{
  struct TALER_PurseDeletePS pm = {
    .purpose.size = htonl (sizeof (pm)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_DELETE)
  };

  return GNUNET_CRYPTO_eddsa_verify (
    TALER_SIGNATURE_WALLET_PURSE_DELETE,
    &pm,
    &purse_sig->eddsa_signature,
    &purse_pub->eddsa_pub);
}


void
TALER_wallet_purse_status_sign (
  const struct TALER_PurseContractPrivateKeyP *purse_priv,
  struct TALER_PurseContractSignatureP *purse_sig)
{
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose = {
    .size = htonl (sizeof (purpose)),
    .purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_STATUS)
  };

  GNUNET_assert (GNUNET_OK ==
                 GNUNET_CRYPTO_eddsa_sign_ (&purse_priv->eddsa_priv,
                                            &purpose,
                                            &purse_sig->eddsa_signature));
}


enum GNUNET_GenericReturnValue
TALER_wallet_purse_status_verify (
  const struct TALER_PurseContractPublicKeyP *purse_pub,
  const struct TALER_PurseContractSignatureP *purse_sig)
{
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose = {
    .size = htonl (sizeof (purpose)),
    .purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_STATUS)
  };

  return GNUNET_CRYPTO_eddsa_verify_ (TALER_SIGNATURE_WALLET_PURSE_STATUS,
                                      &purpose,
                                      &purse_sig->eddsa_signature,
                                      &purse_pub->eddsa_pub);
}


GNUNET_NETWORK_STRUCT_BEGIN

/**
 * Message signed to deposit a coin into a purse.
 */
struct TALER_PurseDepositPS
{

  /**
   * Purpose is #TALER_SIGNATURE_WALLET_PURSE_DEPOSIT
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * Amount (with deposit fee) to be deposited into the purse.
   */
  struct TALER_AmountNBO coin_amount;

  /**
   * Hash over the denomination public key used to sign the coin.
   */
  struct TALER_DenominationHashP h_denom_pub GNUNET_PACKED;

  /**
   * Hash over the age commitment that went into the coin. Maybe all zero, if
   * age commitment isn't applicable to the denomination.
   */
  struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED;

  /**
   * Purse to deposit funds into.
   */
  struct TALER_PurseContractPublicKeyP purse_pub;

  /**
   * Hash of the base URL of the exchange hosting the
   * @e purse_pub.
   */
  struct GNUNET_HashCode h_exchange_base_url GNUNET_PACKED;
};

GNUNET_NETWORK_STRUCT_END

void
TALER_wallet_purse_deposit_sign (
  const char *exchange_base_url,
  const struct TALER_PurseContractPublicKeyP *purse_pub,
  const struct TALER_Amount *amount,
  const struct TALER_DenominationHashP *h_denom_pub,
  const struct TALER_AgeCommitmentHash *h_age_commitment,
  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
  struct TALER_CoinSpendSignatureP *coin_sig)
{
  struct TALER_PurseDepositPS pm = {
    .purpose.size = htonl (sizeof (pm)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_DEPOSIT),
    .purse_pub = *purse_pub,
    .h_denom_pub = *h_denom_pub,
    .h_age_commitment = *h_age_commitment
  };

  GNUNET_CRYPTO_hash (exchange_base_url,
                      strlen (exchange_base_url) + 1,
                      &pm.h_exchange_base_url);
  TALER_amount_hton (&pm.coin_amount,
                     amount);
  GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
                            &pm,
                            &coin_sig->eddsa_signature);
}


enum GNUNET_GenericReturnValue
TALER_wallet_purse_deposit_verify (
  const char *exchange_base_url,
  const struct TALER_PurseContractPublicKeyP *purse_pub,
  const struct TALER_Amount *amount,
  const struct TALER_DenominationHashP *h_denom_pub,
  const struct TALER_AgeCommitmentHash *h_age_commitment,
  const struct TALER_CoinSpendPublicKeyP *coin_pub,
  const struct TALER_CoinSpendSignatureP *coin_sig)
{
  struct TALER_PurseDepositPS pm = {
    .purpose.size = htonl (sizeof (pm)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_DEPOSIT),
    .purse_pub = *purse_pub,
    .h_denom_pub = *h_denom_pub,
    .h_age_commitment = *h_age_commitment
  };

  GNUNET_CRYPTO_hash (exchange_base_url,
                      strlen (exchange_base_url) + 1,
                      &pm.h_exchange_base_url);
  TALER_amount_hton (&pm.coin_amount,
                     amount);
  return GNUNET_CRYPTO_eddsa_verify (
    TALER_SIGNATURE_WALLET_PURSE_DEPOSIT,
    &pm,
    &coin_sig->eddsa_signature,
    &coin_pub->eddsa_pub);
}


GNUNET_NETWORK_STRUCT_BEGIN

/**
 * Message signed to merge a purse into a reserve.
 */
struct TALER_PurseMergePS
{

  /**
   * Purpose is #TALER_SIGNATURE_WALLET_PURSE_MERGE
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * Time when the purse is merged into the reserve.
   */
  struct GNUNET_TIME_TimestampNBO merge_timestamp;

  /**
   * Which purse is being merged?
   */
  struct TALER_PurseContractPublicKeyP purse_pub;

  /**
   * Which reserve should the purse be merged with.
   * Hash of the reserve's payto:// URI.
   */
  struct TALER_NormalizedPaytoHashP h_payto;

};

GNUNET_NETWORK_STRUCT_END

void
TALER_wallet_purse_merge_sign (
  const struct TALER_NormalizedPayto reserve_uri,
  struct GNUNET_TIME_Timestamp merge_timestamp,
  const struct TALER_PurseContractPublicKeyP *purse_pub,
  const struct TALER_PurseMergePrivateKeyP *merge_priv,
  struct TALER_PurseMergeSignatureP *merge_sig)
{
  struct TALER_PurseMergePS pm = {
    .purpose.size = htonl (sizeof (pm)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_MERGE),
    .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp),
    .purse_pub = *purse_pub
  };

  GNUNET_assert (0 ==
                 strncasecmp (reserve_uri.normalized_payto,
                              "payto://taler-reserve",
                              strlen ("payto://taler-reserve")));
  TALER_normalized_payto_hash (reserve_uri,
                               &pm.h_payto);
  GNUNET_CRYPTO_eddsa_sign (&merge_priv->eddsa_priv,
                            &pm,
                            &merge_sig->eddsa_signature);
}


enum GNUNET_GenericReturnValue
TALER_wallet_purse_merge_verify (
  const struct TALER_NormalizedPayto reserve_uri,
  struct GNUNET_TIME_Timestamp merge_timestamp,
  const struct TALER_PurseContractPublicKeyP *purse_pub,
  const struct TALER_PurseMergePublicKeyP *merge_pub,
  const struct TALER_PurseMergeSignatureP *merge_sig)
{
  struct TALER_PurseMergePS pm = {
    .purpose.size = htonl (sizeof (pm)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_MERGE),
    .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp),
    .purse_pub = *purse_pub
  };

  if (0 !=
      strncasecmp (reserve_uri.normalized_payto,
                   "payto://taler-reserve",
                   strlen ("payto://taler-reserve")))
  {
    GNUNET_break (0);
    return GNUNET_NO;
  }
  TALER_normalized_payto_hash (reserve_uri,
                               &pm.h_payto);
  return GNUNET_CRYPTO_eddsa_verify (
    TALER_SIGNATURE_WALLET_PURSE_MERGE,
    &pm,
    &merge_sig->eddsa_signature,
    &merge_pub->eddsa_pub);
}


GNUNET_NETWORK_STRUCT_BEGIN

/**
 * Message signed by account to merge a purse into a reserve.
 */
struct TALER_AccountMergePS
{

  /**
   * Purpose is #TALER_SIGNATURE_WALLET_ACCOUNT_MERGE
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * Time when the purse will expire if still unmerged or unpaid.
   */
  struct GNUNET_TIME_TimestampNBO purse_expiration;

  /**
   * Total amount (with fees) to be put into the purse.
   */
  struct TALER_AmountNBO purse_amount;

  /**
   * Purse creation fee to be paid by the reserve for
   * this operation.
   */
  struct TALER_AmountNBO purse_fee;

  /**
   * Contract this purse pays for.
   */
  struct TALER_PrivateContractHashP h_contract_terms;

  /**
   * Purse to merge.
   */
  struct TALER_PurseContractPublicKeyP purse_pub;

  /**
   * Time when the purse is merged into the reserve.
   */
  struct GNUNET_TIME_TimestampNBO merge_timestamp;

  /**
   * Minimum age required for payments into this purse,
   * in NBO.
   */
  uint32_t min_age GNUNET_PACKED;

  /**
   * Flags for the operation, in NBO. See
   * `enum TALER_WalletAccountMergeFlags`.
   */
  uint32_t flags GNUNET_PACKED;
};

GNUNET_NETWORK_STRUCT_END


void
TALER_wallet_account_merge_sign (
  struct GNUNET_TIME_Timestamp merge_timestamp,
  const struct TALER_PurseContractPublicKeyP *purse_pub,
  struct GNUNET_TIME_Timestamp purse_expiration,
  const struct TALER_PrivateContractHashP *h_contract_terms,
  const struct TALER_Amount *amount,
  const struct TALER_Amount *purse_fee,
  uint32_t min_age,
  enum TALER_WalletAccountMergeFlags flags,
  const struct TALER_ReservePrivateKeyP *reserve_priv,
  struct TALER_ReserveSignatureP *reserve_sig)
{
  struct TALER_AccountMergePS pm = {
    .purpose.size = htonl (sizeof (pm)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_ACCOUNT_MERGE),
    .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp),
    .purse_pub = *purse_pub,
    .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
    .h_contract_terms = *h_contract_terms,
    .min_age = htonl (min_age),
    .flags = htonl ((uint32_t) flags)
  };

  TALER_amount_hton (&pm.purse_amount,
                     amount);
  TALER_amount_hton (&pm.purse_fee,
                     purse_fee);
  GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
                            &pm,
                            &reserve_sig->eddsa_signature);
}


enum GNUNET_GenericReturnValue
TALER_wallet_account_merge_verify (
  struct GNUNET_TIME_Timestamp merge_timestamp,
  const struct TALER_PurseContractPublicKeyP *purse_pub,
  struct GNUNET_TIME_Timestamp purse_expiration,
  const struct TALER_PrivateContractHashP *h_contract_terms,
  const struct TALER_Amount *amount,
  const struct TALER_Amount *purse_fee,
  uint32_t min_age,
  enum TALER_WalletAccountMergeFlags flags,
  const struct TALER_ReservePublicKeyP *reserve_pub,
  const struct TALER_ReserveSignatureP *reserve_sig)
{
  struct TALER_AccountMergePS pm = {
    .purpose.size = htonl (sizeof (pm)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_ACCOUNT_MERGE),
    .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp),
    .purse_pub = *purse_pub,
    .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
    .h_contract_terms = *h_contract_terms,
    .min_age = htonl (min_age),
    .flags = htonl ((uint32_t) flags)
  };

  TALER_amount_hton (&pm.purse_amount,
                     amount);
  TALER_amount_hton (&pm.purse_fee,
                     purse_fee);
  return GNUNET_CRYPTO_eddsa_verify (
    TALER_SIGNATURE_WALLET_ACCOUNT_MERGE,
    &pm,
    &reserve_sig->eddsa_signature,
    &reserve_pub->eddsa_pub);
}


GNUNET_NETWORK_STRUCT_BEGIN

/**
 * Message signed by reserve key.
 */
struct TALER_ReserveOpenPS
{

  /**
   * Purpose is #TALER_SIGNATURE_WALLET_RESERVE_OPEN
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * Amount to be paid from the reserve balance to open
   * the reserve.
   */
  struct TALER_AmountNBO reserve_payment;

  /**
   * When was the request created.
   */
  struct GNUNET_TIME_TimestampNBO request_timestamp;

  /**
   * For how long should the reserve be kept open.
   * (Determines amount to be paid.)
   */
  struct GNUNET_TIME_TimestampNBO reserve_expiration;

  /**
   * How many open purses should be included with the
   * open reserve?
   * (Determines amount to be paid.)
   */
  uint32_t purse_limit GNUNET_PACKED;

};

GNUNET_NETWORK_STRUCT_END


void
TALER_wallet_reserve_open_sign (
  const struct TALER_Amount *reserve_payment,
  struct GNUNET_TIME_Timestamp request_timestamp,
  struct GNUNET_TIME_Timestamp reserve_expiration,
  uint32_t purse_limit,
  const struct TALER_ReservePrivateKeyP *reserve_priv,
  struct TALER_ReserveSignatureP *reserve_sig)
{
  struct TALER_ReserveOpenPS rop = {
    .purpose.size = htonl (sizeof (rop)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN),
    .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp),
    .reserve_expiration = GNUNET_TIME_timestamp_hton (reserve_expiration),
    .purse_limit = htonl (purse_limit)
  };

  TALER_amount_hton (&rop.reserve_payment,
                     reserve_payment);
  GNUNET_assert (GNUNET_OK ==
                 GNUNET_CRYPTO_eddsa_sign_ (&reserve_priv->eddsa_priv,
                                            &rop.purpose,
                                            &reserve_sig->eddsa_signature));
}


enum GNUNET_GenericReturnValue
TALER_wallet_reserve_open_verify (
  const struct TALER_Amount *reserve_payment,
  struct GNUNET_TIME_Timestamp request_timestamp,
  struct GNUNET_TIME_Timestamp reserve_expiration,
  uint32_t purse_limit,
  const struct TALER_ReservePublicKeyP *reserve_pub,
  const struct TALER_ReserveSignatureP *reserve_sig)
{
  struct TALER_ReserveOpenPS rop = {
    .purpose.size = htonl (sizeof (rop)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN),
    .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp),
    .reserve_expiration = GNUNET_TIME_timestamp_hton (reserve_expiration),
    .purse_limit = htonl (purse_limit)
  };

  TALER_amount_hton (&rop.reserve_payment,
                     reserve_payment);
  return GNUNET_CRYPTO_eddsa_verify_ (TALER_SIGNATURE_WALLET_RESERVE_OPEN,
                                      &rop.purpose,
                                      &reserve_sig->eddsa_signature,
                                      &reserve_pub->eddsa_pub);
}


GNUNET_NETWORK_STRUCT_BEGIN

/**
 * Message signed by
 */
struct TALER_ReserveOpenDepositPS
{

  /**
   * Purpose is #TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * Which reserve's opening signature should be paid for?
   */
  struct TALER_ReserveSignatureP reserve_sig;

  /**
   * Specifies how much of the coin's value should be spent on opening this
   * reserve.
   */
  struct TALER_AmountNBO coin_contribution;
};

GNUNET_NETWORK_STRUCT_END


// FIXME-#7267: add h_age_commitment, h_denom_pub to have proof!
void
TALER_wallet_reserve_open_deposit_sign (
  const struct TALER_Amount *coin_contribution,
  const struct TALER_ReserveSignatureP *reserve_sig,
  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
  struct TALER_CoinSpendSignatureP *coin_sig)
{
  struct TALER_ReserveOpenDepositPS rod = {
    .purpose.size = htonl (sizeof (rod)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT),
    .reserve_sig = *reserve_sig
  };

  TALER_amount_hton (&rod.coin_contribution,
                     coin_contribution);
  GNUNET_assert (GNUNET_OK ==
                 GNUNET_CRYPTO_eddsa_sign_ (&coin_priv->eddsa_priv,
                                            &rod.purpose,
                                            &coin_sig->eddsa_signature));
}


enum GNUNET_GenericReturnValue
TALER_wallet_reserve_open_deposit_verify (
  const struct TALER_Amount *coin_contribution,
  const struct TALER_ReserveSignatureP *reserve_sig,
  const struct TALER_CoinSpendPublicKeyP *coin_pub,
  const struct TALER_CoinSpendSignatureP *coin_sig)
{
  struct TALER_ReserveOpenDepositPS rod = {
    .purpose.size = htonl (sizeof (rod)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT),
    .reserve_sig = *reserve_sig
  };

  TALER_amount_hton (&rod.coin_contribution,
                     coin_contribution);
  return GNUNET_CRYPTO_eddsa_verify_ (
    TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT,
    &rod.purpose,
    &coin_sig->eddsa_signature,
    &coin_pub->eddsa_pub);
}


GNUNET_NETWORK_STRUCT_BEGIN

/**
 * Message signed by reserve key.
 */
struct TALER_ReserveClosePS
{

  /**
   * Purpose is #TALER_SIGNATURE_WALLET_RESERVE_CLOSE
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * When was the request created.
   */
  struct GNUNET_TIME_TimestampNBO request_timestamp;

  /**
   * Hash of the payto://-URI of the target account
   * for the closure, or all zeros for the reserve
   * origin account.
   */
  struct TALER_FullPaytoHashP target_account_h_payto;

};

GNUNET_NETWORK_STRUCT_END


void
TALER_wallet_reserve_close_sign (
  struct GNUNET_TIME_Timestamp request_timestamp,
  const struct TALER_FullPaytoHashP *h_payto,
  const struct TALER_ReservePrivateKeyP *reserve_priv,
  struct TALER_ReserveSignatureP *reserve_sig)
{
  struct TALER_ReserveClosePS rcp = {
    .purpose.size = htonl (sizeof (rcp)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_CLOSE),
    .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp)
  };

  if (NULL != h_payto)
    rcp.target_account_h_payto = *h_payto;
  GNUNET_assert (GNUNET_OK ==
                 GNUNET_CRYPTO_eddsa_sign_ (&reserve_priv->eddsa_priv,
                                            &rcp.purpose,
                                            &reserve_sig->eddsa_signature));
}


enum GNUNET_GenericReturnValue
TALER_wallet_reserve_close_verify (
  struct GNUNET_TIME_Timestamp request_timestamp,
  const struct TALER_FullPaytoHashP *h_payto,
  const struct TALER_ReservePublicKeyP *reserve_pub,
  const struct TALER_ReserveSignatureP *reserve_sig)
{
  struct TALER_ReserveClosePS rcp = {
    .purpose.size = htonl (sizeof (rcp)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_CLOSE),
    .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp)
  };

  if (NULL != h_payto)
    rcp.target_account_h_payto = *h_payto;
  return GNUNET_CRYPTO_eddsa_verify_ (TALER_SIGNATURE_WALLET_RESERVE_CLOSE,
                                      &rcp.purpose,
                                      &reserve_sig->eddsa_signature,
                                      &reserve_pub->eddsa_pub);
}


GNUNET_NETWORK_STRUCT_BEGIN

/**
 * Message signed by reserve private key.
 */
struct TALER_ReserveAttestRequestPS
{

  /**
   * Purpose is #TALER_SIGNATURE_WALLET_ATTEST_REQUEST
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * When was the request created.
   */
  struct GNUNET_TIME_TimestampNBO request_timestamp;

  /**
   * Hash over the JSON array of requested attributes.
   */
  struct GNUNET_HashCode h_details;

};

GNUNET_NETWORK_STRUCT_END


void
TALER_wallet_reserve_attest_request_sign (
  struct GNUNET_TIME_Timestamp request_timestamp,
  const json_t *details,
  const struct TALER_ReservePrivateKeyP *reserve_priv,
  struct TALER_ReserveSignatureP *reserve_sig)
{
  struct TALER_ReserveAttestRequestPS rcp = {
    .purpose.size = htonl (sizeof (rcp)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_ATTEST_DETAILS),
    .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp)
  };

  TALER_json_hash (details,
                   &rcp.h_details);
  GNUNET_assert (GNUNET_OK ==
                 GNUNET_CRYPTO_eddsa_sign_ (&reserve_priv->eddsa_priv,
                                            &rcp.purpose,
                                            &reserve_sig->eddsa_signature));
}


enum GNUNET_GenericReturnValue
TALER_wallet_reserve_attest_request_verify (
  struct GNUNET_TIME_Timestamp request_timestamp,
  const json_t *details,
  const struct TALER_ReservePublicKeyP *reserve_pub,
  const struct TALER_ReserveSignatureP *reserve_sig)
{
  struct TALER_ReserveAttestRequestPS rcp = {
    .purpose.size = htonl (sizeof (rcp)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_ATTEST_DETAILS),
    .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp)
  };

  TALER_json_hash (details,
                   &rcp.h_details);
  return GNUNET_CRYPTO_eddsa_verify_ (
    TALER_SIGNATURE_WALLET_RESERVE_ATTEST_DETAILS,
    &rcp.purpose,
    &reserve_sig->eddsa_signature,
    &reserve_pub->eddsa_pub);
}


GNUNET_NETWORK_STRUCT_BEGIN

/**
 * Message signed by purse to associate an encrypted contract.
 */
struct TALER_PurseContractPS
{

  /**
   * Purpose is #TALER_SIGNATURE_WALLET_PURSE_ECONTRACT
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * Hash over the encrypted contract.
   */
  struct GNUNET_HashCode h_econtract;

  /**
   * Public key to decrypt the contract.
   */
  struct TALER_ContractDiffiePublicP contract_pub;
};

GNUNET_NETWORK_STRUCT_END

void
TALER_wallet_econtract_upload_sign (
  const void *econtract,
  size_t econtract_size,
  const struct TALER_ContractDiffiePublicP *contract_pub,
  const struct TALER_PurseContractPrivateKeyP *purse_priv,
  struct TALER_PurseContractSignatureP *purse_sig)
{
  struct TALER_PurseContractPS pc = {
    .purpose.size = htonl (sizeof (pc)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_ECONTRACT),
    .contract_pub = *contract_pub
  };

  GNUNET_CRYPTO_hash (econtract,
                      econtract_size,
                      &pc.h_econtract);
  GNUNET_assert (GNUNET_OK ==
                 GNUNET_CRYPTO_eddsa_sign_ (&purse_priv->eddsa_priv,
                                            &pc.purpose,
                                            &purse_sig->eddsa_signature));
}


enum GNUNET_GenericReturnValue
TALER_wallet_econtract_upload_verify2 (
  const struct GNUNET_HashCode *h_econtract,
  const struct TALER_ContractDiffiePublicP *contract_pub,
  const struct TALER_PurseContractPublicKeyP *purse_pub,
  const struct TALER_PurseContractSignatureP *purse_sig)
{
  struct TALER_PurseContractPS pc = {
    .purpose.size = htonl (sizeof (pc)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_ECONTRACT),
    .contract_pub = *contract_pub,
    .h_econtract = *h_econtract
  };

  return GNUNET_CRYPTO_eddsa_verify_ (TALER_SIGNATURE_WALLET_PURSE_ECONTRACT,
                                      &pc.purpose,
                                      &purse_sig->eddsa_signature,
                                      &purse_pub->eddsa_pub);
}


enum GNUNET_GenericReturnValue
TALER_wallet_econtract_upload_verify (
  const void *econtract,
  size_t econtract_size,
  const struct TALER_ContractDiffiePublicP *contract_pub,
  const struct TALER_PurseContractPublicKeyP *purse_pub,
  const struct TALER_PurseContractSignatureP *purse_sig)
{
  struct GNUNET_HashCode h_econtract;

  GNUNET_CRYPTO_hash (econtract,
                      econtract_size,
                      &h_econtract);
  return TALER_wallet_econtract_upload_verify2 (&h_econtract,
                                                contract_pub,
                                                purse_pub,
                                                purse_sig);
}


GNUNET_NETWORK_STRUCT_BEGIN

/**
 * Message signed by wallet to confirm usage of a token for a transaction.
 */
struct TALER_TokenUseRequestPS
{

  /**
   * Purpose is #TALER_SIGNATURE_WALLET_TOKEN_USE
   */
  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;

  /**
   * Hash over the contract for which this token is used.
   */
  struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED;

  /**
   * Hash over a JSON containing data provided by the
   * wallet to complete the contract upon payment.
   */
  struct GNUNET_HashCode wallet_data_hash;

};

GNUNET_NETWORK_STRUCT_END


void
TALER_wallet_token_use_sign (
  const struct TALER_PrivateContractHashP *h_contract_terms,
  const struct GNUNET_HashCode *wallet_data_hash,
  const struct TALER_TokenUsePrivateKeyP *token_use_priv,
  struct TALER_TokenUseSignatureP *token_sig)
{
  struct TALER_TokenUseRequestPS tur = {
    .purpose.size = htonl (sizeof (tur)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_TOKEN_USE),
    .h_contract_terms = *h_contract_terms,
    .wallet_data_hash = *wallet_data_hash
  };

  GNUNET_CRYPTO_eddsa_sign (&token_use_priv->private_key,
                            &tur,
                            &token_sig->signature);
}


enum GNUNET_GenericReturnValue
TALER_wallet_token_use_verify (
  const struct TALER_PrivateContractHashP *h_contract_terms,
  const struct GNUNET_HashCode *wallet_data_hash,
  const struct TALER_TokenUsePublicKeyP *token_use_pub,
  const struct TALER_TokenUseSignatureP *token_sig)
{
  struct TALER_TokenUseRequestPS tur = {
    .purpose.size = htonl (sizeof (tur)),
    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_TOKEN_USE),
    .h_contract_terms = *h_contract_terms,
    .wallet_data_hash = *wallet_data_hash
  };

  return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_TOKEN_USE,
                                     &tur,
                                     &token_sig->signature,
                                     &token_use_pub->public_key);
}


/* end of wallet_signatures.c */
