// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/connectivity/bluetooth/core/bt-host/sm/ecdh_key.h"

#include <cstring>
#include <memory>

#include "gtest/gtest.h"
#include "src/connectivity/bluetooth/core/bt-host/common/uint256.h"

namespace bt {
namespace sm {
namespace {

TEST(SMP_EcdhKeyTest, ParseSerializedKey) {
  // Debug ECDH key given in V5.1 Vol. 3 Part H Section 2.3.5.6.1
  const UInt256 kDebugPubKeyX{0xE6, 0x9D, 0x35, 0x0E, 0x48, 0x01, 0x03, 0xCC, 0xDB, 0xFD, 0xF4,
                              0xAC, 0x11, 0x91, 0xF4, 0xEF, 0xB9, 0xA5, 0xF9, 0xE9, 0xA7, 0x83,
                              0x2C, 0x5E, 0x2C, 0xBE, 0x97, 0xF2, 0xD2, 0x03, 0xB0, 0x20};
  const UInt256 kDebugPubKeyY{0x8B, 0xD2, 0x89, 0x15, 0xD0, 0x8E, 0x1C, 0x74, 0x24, 0x30, 0xED,
                              0x8F, 0xC2, 0x45, 0x63, 0x76, 0x5C, 0x15, 0x52, 0x5A, 0xBF, 0x9A,
                              0x32, 0x63, 0x6D, 0xEB, 0x2A, 0x65, 0x49, 0x9C, 0x80, 0xDC};
  // Debug ECDH key given in V5.1 Vol. 3 Part H Section 2.3.5.6.1, converted to little-endian to
  // match transport format.
  const sm::PairingPublicKeyParams kSerializedKey{
      .x = {0xE6, 0x9D, 0x35, 0x0E, 0x48, 0x01, 0x03, 0xCC, 0xDB, 0xFD, 0xF4,
            0xAC, 0x11, 0x91, 0xF4, 0xEF, 0xB9, 0xA5, 0xF9, 0xE9, 0xA7, 0x83,
            0x2C, 0x5E, 0x2C, 0xBE, 0x97, 0xF2, 0xD2, 0x03, 0xB0, 0x20},
      .y = {0x8B, 0xD2, 0x89, 0x15, 0xD0, 0x8E, 0x1C, 0x74, 0x24, 0x30, 0xED,
            0x8F, 0xC2, 0x45, 0x63, 0x76, 0x5C, 0x15, 0x52, 0x5A, 0xBF, 0x9A,
            0x32, 0x63, 0x6D, 0xEB, 0x2A, 0x65, 0x49, 0x9C, 0x80, 0xDC}};
  auto new_key = EcdhKey::ParseFromPublicKey(kSerializedKey);
  ASSERT_TRUE(new_key.has_value());
  ASSERT_EQ(kDebugPubKeyX, new_key->GetPublicKeyX());
  ASSERT_EQ(kDebugPubKeyY, new_key->GetPublicKeyY());
}

TEST(SMP_EcdhKeyTest, PointOffP256CurveXValueParsesToNullopt) {
  // These values come from the debug ECDH key in V5.1 Vol. 3 Part H Section 2.3.5.6.1 (converted
  // to little-endian). The debug ECDH key values are on the P-256 curve, but by changing only the
  // X-coordinate's most-significant byte from 0x20 to 0x00, we create a point off the P-256 curve.
  const sm::PairingPublicKeyParams kSerializedKey{
      .x = {0xE6, 0x9D, 0x35, 0x0E, 0x48, 0x01, 0x03, 0xCC, 0xDB, 0xFD, 0xF4,
            0xAC, 0x11, 0x91, 0xF4, 0xEF, 0xB9, 0xA5, 0xF9, 0xE9, 0xA7, 0x83,
            0x2C, 0x5E, 0x2C, 0xBE, 0x97, 0xF2, 0xD2, 0x03, 0xB0, 0x00},
      .y = {0x8B, 0xD2, 0x89, 0x15, 0xD0, 0x8E, 0x1C, 0x74, 0x24, 0x30, 0xED,
            0x8F, 0xC2, 0x45, 0x63, 0x76, 0x5C, 0x15, 0x52, 0x5A, 0xBF, 0x9A,
            0x32, 0x63, 0x6D, 0xEB, 0x2A, 0x65, 0x49, 0x9C, 0x80, 0xDC}};
  auto new_key = EcdhKey::ParseFromPublicKey(kSerializedKey);
  ASSERT_EQ(new_key, std::nullopt);
}

TEST(SMP_EcdhKeyTest, PointOffP256CurveYValueParsesToNullopt) {
  // These values come from the debug ECDH key in V5.1 Vol. 3 Part H Section 2.3.5.6.1 (converted
  // to little-endian). The debug ECDH key values are on the P-256 curve, but by changing only the
  // Y-coordinate's most-significant byte from 0xDC to 0x00, we create a point off the P-256 curve.
  const sm::PairingPublicKeyParams kSerializedKey{
      .x = {0xE6, 0x9D, 0x35, 0x0E, 0x48, 0x01, 0x03, 0xCC, 0xDB, 0xFD, 0xF4,
            0xAC, 0x11, 0x91, 0xF4, 0xEF, 0xB9, 0xA5, 0xF9, 0xE9, 0xA7, 0x83,
            0x2C, 0x5E, 0x2C, 0xBE, 0x97, 0xF2, 0xD2, 0x03, 0xB0, 0x20},
      .y = {0x8B, 0xD2, 0x89, 0x15, 0xD0, 0x8E, 0x1C, 0x74, 0x24, 0x30, 0xED,
            0x8F, 0xC2, 0x45, 0x63, 0x76, 0x5C, 0x15, 0x52, 0x5A, 0xBF, 0x9A,
            0x32, 0x63, 0x6D, 0xEB, 0x2A, 0x65, 0x49, 0x9C, 0x80, 0x00}};
  auto new_key = EcdhKey::ParseFromPublicKey(kSerializedKey);
  ASSERT_EQ(new_key, std::nullopt);
}

TEST(SMP_EcdhKeyTest, CreateGivesValidKey) {
  std::optional<LocalEcdhKey> new_key = LocalEcdhKey::Create();
  ASSERT_TRUE(new_key.has_value());
  auto serialized_pub_key = new_key->GetSerializedPublicKey();
  std::optional<EcdhKey> parsed_key = EcdhKey::ParseFromPublicKey(serialized_pub_key);
  ASSERT_TRUE(parsed_key.has_value());
}

// Test vector taken from NIST ECDH P-256 test vector 0 in first link, described in second link:
// https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/components/ecccdhtestvectors.zip
// Described here:
// https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/components/ecccdhvs.pdf
// Local private key is dIUT value, Peer X and Y are taken from QCAVSx and QCAVSy, ExpectedDHKey
// is ZIUT. The examples are given in human-readable big-endian format, but here we've converted
// them to little-endian format for consistency with the bt-host stack.
TEST(SMP_EcdhKeyTest, CalculateDhKeyWorks) {
  std::optional<LocalEcdhKey> local_key = LocalEcdhKey::Create();
  ASSERT_TRUE(local_key.has_value());
  const UInt256 kSamplePrivateKey{0x34, 0xA5, 0xC1, 0x2B, 0xB6, 0xAD, 0x0B, 0xD8, 0x2E, 0xD2, 0xB6,
                                  0x1F, 0xAF, 0x58, 0x90, 0x3D, 0xE0, 0xEA, 0x2E, 0x63, 0x14, 0x62,
                                  0x0D, 0xF8, 0xDA, 0x9D, 0xB2, 0x1E, 0xF7, 0xC5, 0x7D, 0x7D};
  local_key->SetPrivateKeyForTesting(kSamplePrivateKey);

  const sm::PairingPublicKeyParams kSerializedKey{
      .x = {0x87, 0xD2, 0x33, 0x88, 0x83, 0xCC, 0xE7, 0x2C, 0xB4, 0xF6, 0x4D,
            0x3A, 0xCE, 0xAC, 0x6B, 0x1B, 0xB9, 0x0D, 0x64, 0x65, 0xCA, 0x32,
            0xC6, 0x5C, 0x4C, 0x58, 0x56, 0x7F, 0xF7, 0x48, 0x0C, 0x70},
      .y = {0xAC, 0xA4, 0x5F, 0xB8, 0xCA, 0x82, 0x17, 0x44, 0xE0, 0xDF, 0x40,
            0xF6, 0xFB, 0x46, 0x8D, 0x94, 0xC5, 0xDC, 0x51, 0x5C, 0xBA, 0x20,
            0xDB, 0x0D, 0x06, 0x9B, 0xFD, 0xE3, 0x09, 0xE5, 0x71, 0xDB}};

  auto public_key = EcdhKey::ParseFromPublicKey(kSerializedKey);
  ASSERT_TRUE(public_key.has_value());
  UInt256 dhkey = local_key->CalculateDhKey(*public_key);
  const UInt256 kExpectedDhKey{0x7B, 0xBD, 0x97, 0x89, 0x77, 0xD7, 0x0D, 0x04, 0x68, 0x1E, 0x56,
                               0x60, 0x20, 0x85, 0xC5, 0xCC, 0x25, 0x2D, 0xDD, 0xFB, 0x34, 0xA4,
                               0x54, 0x2E, 0x01, 0xFF, 0x20, 0x64, 0x10, 0x62, 0xFC, 0x46};
  ASSERT_EQ(kExpectedDhKey, dhkey);
}

TEST(SMP_EcdhKeyTest, PublicKeyXAndYComparisonSameKey) {
  // Debug ECDH key given in V5.1 Vol. 3 Part H Section 2.3.5.6.1, converted to little-endian.
  const sm::PairingPublicKeyParams kSerializedKey{
      .x = {0xE6, 0x9D, 0x35, 0x0E, 0x48, 0x01, 0x03, 0xCC, 0xDB, 0xFD, 0xF4,
            0xAC, 0x11, 0x91, 0xF4, 0xEF, 0xB9, 0xA5, 0xF9, 0xE9, 0xA7, 0x83,
            0x2C, 0x5E, 0x2C, 0xBE, 0x97, 0xF2, 0xD2, 0x03, 0xB0, 0x20},
      .y = {0x8B, 0xD2, 0x89, 0x15, 0xD0, 0x8E, 0x1C, 0x74, 0x24, 0x30, 0xED,
            0x8F, 0xC2, 0x45, 0x63, 0x76, 0x5C, 0x15, 0x52, 0x5A, 0xBF, 0x9A,
            0x32, 0x63, 0x6D, 0xEB, 0x2A, 0x65, 0x49, 0x9C, 0x80, 0xDC}};
  auto ecdh_key = EcdhKey::ParseFromPublicKey(kSerializedKey);
  auto same_ecdh_key = EcdhKey::ParseFromPublicKey(kSerializedKey);
  ASSERT_TRUE(ecdh_key.has_value());
  ASSERT_TRUE(same_ecdh_key.has_value());
  ASSERT_EQ(ecdh_key->GetPublicKeyX(), same_ecdh_key->GetPublicKeyX());
  ASSERT_EQ(ecdh_key->GetPublicKeyY(), same_ecdh_key->GetPublicKeyY());
}

TEST(SMP_EcdhKeyTest, PublicKeyXAndYComparisonDifferentKeys) {
  // Debug ECDH key given in V5.1 Vol. 3 Part H Section 2.3.5.6.1, converted to little-endian.
  const sm::PairingPublicKeyParams kSpecSampleSerializedKey{
      .x = {0xE6, 0x9D, 0x35, 0x0E, 0x48, 0x01, 0x03, 0xCC, 0xDB, 0xFD, 0xF4,
            0xAC, 0x11, 0x91, 0xF4, 0xEF, 0xB9, 0xA5, 0xF9, 0xE9, 0xA7, 0x83,
            0x2C, 0x5E, 0x2C, 0xBE, 0x97, 0xF2, 0xD2, 0x03, 0xB0, 0x20},
      .y = {0x8B, 0xD2, 0x89, 0x15, 0xD0, 0x8E, 0x1C, 0x74, 0x24, 0x30, 0xED,
            0x8F, 0xC2, 0x45, 0x63, 0x76, 0x5C, 0x15, 0x52, 0x5A, 0xBF, 0x9A,
            0x32, 0x63, 0x6D, 0xEB, 0x2A, 0x65, 0x49, 0x9C, 0x80, 0xDC}};
  // Test vector taken from NIST ECDH P-256 test vector 0, same as in CalculateDhKeyWorks test.
  const sm::PairingPublicKeyParams kNistSampleSerializedKey{
      .x = {0x87, 0xD2, 0x33, 0x88, 0x83, 0xCC, 0xE7, 0x2C, 0xB4, 0xF6, 0x4D,
            0x3A, 0xCE, 0xAC, 0x6B, 0x1B, 0xB9, 0x0D, 0x64, 0x65, 0xCA, 0x32,
            0xC6, 0x5C, 0x4C, 0x58, 0x56, 0x7F, 0xF7, 0x48, 0x0C, 0x70},
      .y = {0xAC, 0xA4, 0x5F, 0xB8, 0xCA, 0x82, 0x17, 0x44, 0xE0, 0xDF, 0x40,
            0xF6, 0xFB, 0x46, 0x8D, 0x94, 0xC5, 0xDC, 0x51, 0x5C, 0xBA, 0x20,
            0xDB, 0x0D, 0x06, 0x9B, 0xFD, 0xE3, 0x09, 0xE5, 0x71, 0xDB}};

  auto spec_key = EcdhKey::ParseFromPublicKey(kSpecSampleSerializedKey);
  auto nist_key = EcdhKey::ParseFromPublicKey(kNistSampleSerializedKey);
  ASSERT_TRUE(spec_key.has_value());
  ASSERT_TRUE(nist_key.has_value());
  ASSERT_NE(spec_key->GetPublicKeyX(), nist_key->GetPublicKeyX());
  ASSERT_NE(spec_key->GetPublicKeyY(), nist_key->GetPublicKeyY());
}

}  // namespace
}  // namespace sm
}  // namespace bt
