/*
 * This file is part of the Trezor project, https://trezor.io/
 *
 * Copyright (C) 2018 Pavol Rusnak <stick@satoshilabs.com>
 *
 * This library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This library 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

void fsm_msgGetPublicKey(const GetPublicKey *msg) {
  RESP_INIT(PublicKey);

  CHECK_INITIALIZED

  CHECK_PIN

  InputScriptType script_type =
      msg->has_script_type ? msg->script_type : InputScriptType_SPENDADDRESS;

  const CoinInfo *coin = fsm_getCoin(msg->has_coin_name, msg->coin_name);
  if (!coin) return;

  const char *curve = coin->curve_name;
  if (msg->has_ecdsa_curve_name) {
    curve = msg->ecdsa_curve_name;
  }
  uint32_t fingerprint;
  HDNode *node = node = fsm_getDerivedNode(curve, msg->address_n,
                                           msg->address_n_count, &fingerprint);
  if (!node) return;
  hdnode_fill_public_key(node);

  if (msg->has_show_display && msg->show_display) {
    layoutPublicKey(node->public_key);
    if (!protectButton(ButtonRequestType_ButtonRequest_PublicKey, true)) {
      fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
      layoutHome();
      return;
    }
  }

  resp->has_node = true;
  resp->node.depth = node->depth;
  resp->node.fingerprint = fingerprint;
  resp->node.child_num = node->child_num;
  resp->node.chain_code.size = 32;
  memcpy(resp->node.chain_code.bytes, node->chain_code, 32);
  resp->node.has_private_key = false;
  resp->node.has_public_key = true;
  resp->node.public_key.size = 33;
  memcpy(resp->node.public_key.bytes, node->public_key, 33);
  if (node->public_key[0] == 1) {
    /* ed25519 public key */
    resp->node.public_key.bytes[0] = 0;
  }

  resp->has_xpub = true;
  if (coin->xpub_magic && (script_type == InputScriptType_SPENDADDRESS ||
                           script_type == InputScriptType_SPENDMULTISIG)) {
    hdnode_serialize_public(node, fingerprint, coin->xpub_magic, resp->xpub,
                            sizeof(resp->xpub));
  } else if (coin->has_segwit && coin->xpub_magic_segwit_p2sh &&
             script_type == InputScriptType_SPENDP2SHWITNESS) {
    hdnode_serialize_public(node, fingerprint, coin->xpub_magic_segwit_p2sh,
                            resp->xpub, sizeof(resp->xpub));
  } else if (coin->has_segwit && coin->xpub_magic_segwit_native &&
             script_type == InputScriptType_SPENDWITNESS) {
    hdnode_serialize_public(node, fingerprint, coin->xpub_magic_segwit_native,
                            resp->xpub, sizeof(resp->xpub));
  } else {
    fsm_sendFailure(FailureType_Failure_DataError,
                    _("Invalid combination of coin and script_type"));
    layoutHome();
    return;
  }

  msg_write(MessageType_MessageType_PublicKey, resp);
  layoutHome();
}

void fsm_msgSignTx(const SignTx *msg) {
  CHECK_INITIALIZED

  CHECK_PARAM(msg->inputs_count > 0,
              _("Transaction must have at least one input"));
  CHECK_PARAM(msg->outputs_count > 0,
              _("Transaction must have at least one output"));
  CHECK_PARAM(msg->inputs_count + msg->outputs_count >= msg->inputs_count,
              _("Value overflow"));

  CHECK_PIN

  const CoinInfo *coin = fsm_getCoin(msg->has_coin_name, msg->coin_name);
  if (!coin) return;
  const HDNode *node = fsm_getDerivedNode(coin->curve_name, NULL, 0, NULL);
  if (!node) return;

  signing_init(msg, coin, node);
}

void fsm_msgTxAck(TxAck *msg) {
  CHECK_PARAM(msg->has_tx, _("No transaction provided"));

  signing_txack(&(msg->tx));
}

static bool path_mismatched(const CoinInfo *coin, const GetAddress *msg) {
  bool mismatch = false;

  // m : no path
  if (msg->address_n_count == 0) {
    return false;
  }

  // m/44' : BIP44 Legacy
  // m / purpose' / coin_type' / account' / change / address_index
  if (msg->address_n[0] == (0x80000000 + 44)) {
    mismatch |= (msg->script_type != InputScriptType_SPENDADDRESS);
    mismatch |= (msg->address_n_count != 5);
    mismatch |= (msg->address_n[1] != coin->coin_type);
    mismatch |= (msg->address_n[2] & 0x80000000) == 0;
    mismatch |= (msg->address_n[3] & 0x80000000) == 0x80000000;
    mismatch |= (msg->address_n[4] & 0x80000000) == 0x80000000;
    return mismatch;
  }

  // m/45' - BIP45 Copay Abandoned Multisig P2SH
  // m / purpose' / cosigner_index / change / address_index
  if (msg->address_n[0] == (0x80000000 + 45)) {
    mismatch |= (msg->script_type != InputScriptType_SPENDMULTISIG);
    mismatch |= (msg->address_n_count != 4);
    mismatch |= (msg->address_n[1] & 0x80000000) == 0x80000000;
    mismatch |= (msg->address_n[2] & 0x80000000) == 0x80000000;
    mismatch |= (msg->address_n[3] & 0x80000000) == 0x80000000;
    return mismatch;
  }

  // m/48' - BIP48 Copay Multisig P2SH
  // m / purpose' / coin_type' / account' / change / address_index
  // Electrum:
  // m / purpose' / coin_type' / account' / type' / change / address_index
  if (msg->address_n[0] == (0x80000000 + 48)) {
    mismatch |= (msg->script_type != InputScriptType_SPENDMULTISIG) &&
                (msg->script_type != InputScriptType_SPENDP2SHWITNESS) &&
                (msg->script_type != InputScriptType_SPENDWITNESS);
    mismatch |= (msg->address_n_count != 5) && (msg->address_n_count != 6);
    mismatch |= (msg->address_n[1] != coin->coin_type);
    mismatch |= (msg->address_n[2] & 0x80000000) == 0;
    mismatch |= (msg->address_n[4] & 0x80000000) == 0x80000000;
    return mismatch;
  }

  // m/49' : BIP49 SegWit
  // m / purpose' / coin_type' / account' / change / address_index
  if (msg->address_n[0] == (0x80000000 + 49)) {
    mismatch |= (msg->script_type != InputScriptType_SPENDP2SHWITNESS);
    mismatch |= !coin->has_segwit;
    mismatch |= !coin->has_address_type_p2sh;
    mismatch |= (msg->address_n_count != 5);
    mismatch |= (msg->address_n[1] != coin->coin_type);
    mismatch |= (msg->address_n[2] & 0x80000000) == 0;
    mismatch |= (msg->address_n[3] & 0x80000000) == 0x80000000;
    mismatch |= (msg->address_n[4] & 0x80000000) == 0x80000000;
    return mismatch;
  }

  // m/84' : BIP84 Native SegWit
  // m / purpose' / coin_type' / account' / change / address_index
  if (msg->address_n[0] == (0x80000000 + 84)) {
    mismatch |= (msg->script_type != InputScriptType_SPENDWITNESS);
    mismatch |= !coin->has_segwit;
    mismatch |= !coin->bech32_prefix;
    mismatch |= (msg->address_n_count != 5);
    mismatch |= (msg->address_n[1] != coin->coin_type);
    mismatch |= (msg->address_n[2] & 0x80000000) == 0;
    mismatch |= (msg->address_n[3] & 0x80000000) == 0x80000000;
    mismatch |= (msg->address_n[4] & 0x80000000) == 0x80000000;
    return mismatch;
  }

  return false;
}

void fsm_msgGetAddress(const GetAddress *msg) {
  RESP_INIT(Address);

  CHECK_INITIALIZED

  CHECK_PIN

  const CoinInfo *coin = fsm_getCoin(msg->has_coin_name, msg->coin_name);
  if (!coin) return;
  HDNode *node = fsm_getDerivedNode(coin->curve_name, msg->address_n,
                                    msg->address_n_count, NULL);
  if (!node) return;
  hdnode_fill_public_key(node);

  char address[MAX_ADDR_SIZE];
  if (msg->has_multisig) {  // use progress bar only for multisig
    layoutProgress(_("Computing address"), 0);
  }
  if (!compute_address(coin, msg->script_type, node, msg->has_multisig,
                       &msg->multisig, address)) {
    fsm_sendFailure(FailureType_Failure_DataError, _("Can't encode address"));
    layoutHome();
    return;
  }

  if (msg->has_show_display && msg->show_display) {
    char desc[20];
    if (msg->has_multisig) {
      strlcpy(desc, "Multisig __ of __:", sizeof(desc));
      const uint32_t m = msg->multisig.m;
      const uint32_t n = cryptoMultisigPubkeyCount(&(msg->multisig));
      desc[9] = (m < 10) ? ' ' : ('0' + (m / 10));
      desc[10] = '0' + (m % 10);
      desc[15] = (n < 10) ? ' ' : ('0' + (n / 10));
      desc[16] = '0' + (n % 10);
    } else {
      strlcpy(desc, _("Address:"), sizeof(desc));
    }

    bool mismatch = path_mismatched(coin, msg);

    if (mismatch) {
      layoutDialogSwipe(&bmp_icon_warning, _("Abort"), _("Continue"), NULL,
                        _("Wrong address path"), _("for selected coin."), NULL,
                        _("Continue at your"), _("own risk!"), NULL);
      if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) {
        fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
        layoutHome();
        return;
      }
    }

    bool is_cashaddr = coin->cashaddr_prefix != NULL;
    bool is_bech32 = msg->script_type == InputScriptType_SPENDWITNESS;
    if (!fsm_layoutAddress(address, desc, is_cashaddr || is_bech32,
                           is_cashaddr ? strlen(coin->cashaddr_prefix) + 1 : 0,
                           msg->address_n, msg->address_n_count, false)) {
      return;
    }
  }

  strlcpy(resp->address, address, sizeof(resp->address));
  msg_write(MessageType_MessageType_Address, resp);
  layoutHome();
}

void fsm_msgSignMessage(const SignMessage *msg) {
  // CHECK_PARAM(is_ascii_only(msg->message.bytes, msg->message.size), _("Cannot
  // sign non-ASCII strings"));

  RESP_INIT(MessageSignature);

  CHECK_INITIALIZED

  layoutSignMessage(msg->message.bytes, msg->message.size);
  if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
    fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
    layoutHome();
    return;
  }

  CHECK_PIN

  const CoinInfo *coin = fsm_getCoin(msg->has_coin_name, msg->coin_name);
  if (!coin) return;
  HDNode *node = fsm_getDerivedNode(coin->curve_name, msg->address_n,
                                    msg->address_n_count, NULL);
  if (!node) return;

  layoutProgressSwipe(_("Signing"), 0);
  if (cryptoMessageSign(coin, node, msg->script_type, msg->message.bytes,
                        msg->message.size, resp->signature.bytes) == 0) {
    resp->has_address = true;
    hdnode_fill_public_key(node);
    if (!compute_address(coin, msg->script_type, node, false, NULL,
                         resp->address)) {
      fsm_sendFailure(FailureType_Failure_ProcessError,
                      _("Error computing address"));
      layoutHome();
      return;
    }
    resp->has_signature = true;
    resp->signature.size = 65;
    msg_write(MessageType_MessageType_MessageSignature, resp);
  } else {
    fsm_sendFailure(FailureType_Failure_ProcessError,
                    _("Error signing message"));
  }
  layoutHome();
}

void fsm_msgVerifyMessage(const VerifyMessage *msg) {
  CHECK_PARAM(msg->has_address, _("No address provided"));
  CHECK_PARAM(msg->has_message, _("No message provided"));

  const CoinInfo *coin = fsm_getCoin(msg->has_coin_name, msg->coin_name);
  if (!coin) return;
  layoutProgressSwipe(_("Verifying"), 0);
  if (msg->signature.size == 65 &&
      cryptoMessageVerify(coin, msg->message.bytes, msg->message.size,
                          msg->address, msg->signature.bytes) == 0) {
    layoutVerifyAddress(coin, msg->address);
    if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) {
      fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
      layoutHome();
      return;
    }
    layoutVerifyMessage(msg->message.bytes, msg->message.size);
    if (!protectButton(ButtonRequestType_ButtonRequest_Other, false)) {
      fsm_sendFailure(FailureType_Failure_ActionCancelled, NULL);
      layoutHome();
      return;
    }
    fsm_sendSuccess(_("Message verified"));
  } else {
    fsm_sendFailure(FailureType_Failure_DataError, _("Invalid signature"));
  }
  layoutHome();
}