Предисловие


Да, Вы только вдумайтесь, централизованная криптовалюта на PHP, это не правильно. Но мои ограничения на идеи все же пробились, поэтому, как Вы уже догадались, решил написать об этом. В самой первой части, мы расскажем, что такое криптовалюта, обсудим первоначальные конспекты, и сделаем систему кошельков. А теперь, приступим.

Криптовалюта, что из себя представляет?


информация взята из Википедии

Криптовалю?та — разновидность цифровой валюты, учёт внутренних расчётных единиц которой обеспечивает децентрализованная платёжная система (нет внутреннего или внешнего администратора или какого-либо его аналога), работающая в полностью автоматическом режиме.

Сама по себе криптовалюта не имеет какой-либо особой материальной или электронной формы — это просто число, обозначающее количество данных расчётных единиц, которое записывается в соответствующей позиции информационного пакета протокола передачи данных и зачастую даже не подвергается шифрованию, как и вся иная информация о транзакциях между адресами системы.

Вкратце криптовалюта работает так:

  1. Пользователь сети, имея баланс, отправляет другому анонимному пользователю транзакцию.
  2. Транзакция поступает в сеть.
  3. Майнеры (или-же пользователи, занимающиеся созданием блоков) решают сгенерированную по специальному алгоритму задачу с условиями (допустим, на наличие предшествующих 10-30 нулей в хеше, или наоборот).
  4. Когда условие (или условия) соблюдаются в хэше, благодаря ему, майнер отправляет в сеть блок, оповещающий о создании нового блока и выполнении транзакции.
  5. Получатель получает средства, а майнер — сумму вознаграждения, в виде комиcсии, просчитываемого при подготовки задачи.

Имеет ли смысл писать централизованную криптовалюту, и к тому же на PHP?


Я считаю, не очень. Но, попробовать еще как стоит.

Учитываем конспекты перед разработкой


Мы будем использовать двойную систему проверки хешей используя Proof-of-Work. Имеет смысл делать проверку хешей на программном и сетевом прикладном уровне. Это связано с процессом оптимизации нагрузки на сервера. Поскольку, когда мы будем обращаться к серверу за проверкой хеша много раз, появится такая нагрузка. Поэтому, предлагаю поступить следующим методом:

  1. На программном майнере локально проверять хеш на удовлетворение условий.
  2. Когда на программном майнере условия совпадут с необходимыми, делаем проверку на прикладном сетевом уровне (на уровне сервера).

Это, по-моему лучшему мнению, оптимальный способ распределения нагрузки. Проще несколько раз проверить на компьютере локально, чем много раз спамить запросами на сервер.

Название — я придумал простое, и возможно, запоминаемое название — FlyCoin с трех-значным кодом валюты FLC.

База данных — я воспользуюсь простым и удобным вариантом, PDO.

Криптокошельки — будут генерироваться используя GUID и mt_rand, а в качестве seed, будет использоваться время POSIX.

А тебе лень делать функцию проверки хеша? — Да, поэтому я решил взять код из одного старого (с открытым исходным кодом) майнера такой (уже умершей) криптовалюты Entropy, моего друга, Никиты.

Не верится, что он создал только одну криптовалюту. А есть что-то другое?
Вообще, все его творения (которые иногда даже я использую), Вы можете найти в его группе ВКонтакте. Надеюсь, и Вам они тоже пригодятся.

Погнали!


Начнем с того, чтобы сделать каркас нашей будущей системы. Начнем, пожалуй с разработки прикладного API для нашей криптовалюты. А каркас, выглядит пока так пусто.

<?php

/*
	* The API for FlyCoin
*/

function checkHash ($hash, $exp) // Где $hash - там находится наш итоговый хэш, а $exp - сложность. Функция взята из Entropy Miner, и подверглась комментированию.
{
    $sum = 0; // Контрольная сумма нашего хеша.
    $len = strlen ($hash); // Cмотрим длину хеша.

    for ($i = 0; $i < $len; ++$i)
        $sum += 2 * base_convert ($hash[$i], 16, 10); // Проводим простую операцию для просчета контрольной суммы

    return $sum % 10 == 0 && substr ($hash, -$exp) === str_repeat ('0', $exp); // Возвращаем true, если хэш не годен, и true - если хэш годен.
}

class Block 
{
	public $id; // ID блока
	public $prev_hash; // Предыдущий хэш блока
	public $from; // От кого
	public $to; // Кому
	public $ammount; // Сумма
	public $difficult; // Сложность просчета
}

class API 
{
	protected $db;

	public function __construct ()
	{
		$dsn = "mysql:host=localhost;dbname=flc"; // DSN для связи
		$dbh = new PDO ($dsn, $username, $password); // Попытка подключиться к базе

		try
		{
			return true; // Соединение удалось.
		}
		catch (PDOException $exception)
		{
			return false; // Разрыв соединения.
		}
	}

}

Вот такой он, каркас нашего чуда. Первым делом мы реализуем регистрацию и генерацию GUID к нашим кошелькам. Я сразу же создал незаполненное определение функции API, а также нашел функцию генерации GUID в Google. Не забываем про seed. Я как раз нашел функцию получения seed, для уникального получения GUID.

        function make_seed () // Взято из онлайн документации по PHP
        {
  	        list ($usec, $sec) = explode (' ', microtime ());
  	        return $sec + $usec * 1000000;
        }

        mt_srand (make_seed ());

Прекрасно. А функция для генерации GUID для кошелька выглядит соответственно вот так (а также сама заглушка):

        public function register () // Пустая функция регистрации
	{

	}

	protected function genGUID4 () // Сила Google
	{
		return sprintf ('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand (0, 0xffff), mt_rand (0, 0xffff), mt_rand (0, 0xffff), mt_rand (0, 0x0fff) | 0x4000, mt_rand (0, 0x3fff) | 0x8000, mt_rand (0, 0xffff), mt_rand (0, 0xffff), mt_rand (0, 0xffff)); // Я даже сам не понял, как это работает.
	}

Поскольку мы делаем все анонимно, мы не будем хранить IP или E-Mail. Никакого подтверждения. Нам и так будет достаточно. За ~25 минут я написал функцию регистрации. От нас потребуется только один пароль. Функция API вернет нам кошелек.

        public function register ($password) // Где $password - там наш пароль
	{
		if ($this->db) // Защита от дурака
		{

			$walletId = 'FLC@' . $this->genGUID4 (); // Генерируем наш новый адрес кошелька
			$hash = crc32 ($walletId) . $walletId . hash_hmac ('sha512', str_rot13 ($password), 'FLC'); // Сложные вычисления пароля для хэша.
			$hash = hash_hmac ('sha512', $hash, 'FLC2'); // Сам пароль в виде хэша.

			$newUser = $this->db->prepare ("
					INSERT INTO `wallets` 
						(walletId, loginHash, balance)
					VALUES
						(:walletId, :hash, :startBalance)
				"); // Готовим запрос в базу.

			$newUser->bindParam (':walletId', $walletId, PDO::PARAM_STR); // Привязываем кошелек к запросу
			$newUser->bindParam (':hash', $hash, PDO::PARAM_STR); // Привязываем хэш к запросу
			$newUser->bindParam (':startBalance', 0, PDO::PARAM_INT); // Привязываем стартовый баланс (нету никакого стартового баланса, ставим твердую 0!) к балансу

			$newUser->execute (); // Исполняем запрос.

			return ['walletId' => $walletId]; // Даем наш номер кошелька (это логин)
		}
		else
			return false; // Не удалось зарегистрировать кошелек в системе.
	}

Здесь я кратко описал, что делает та или иная строка.

Справедливое сообщение для справедливых людей и программистов.
Если Вы оставили о себе контакты на сайте (на котором, возможно, эта криптовалюта и расположится), и Вам пишут что-то подобное:

«Привет можешь помочь? Я хранил на твоем блокчейне мани, и вдруг я потерял пароль помоги пожалуйста и будешь хорошим» — смело отклоняйте данное предложение. Суть криптовалюты — это хранение вскопанных чисел в безопасности. Никто так не докажет, что он или она является владельцем счета. Таким образом, любой злоумышленник может даже украсть чужие средства даже без участия самой жертвы! Не важно, хоть и действительно является этот человек владельцем счета, лучше отказывать. Если потерял деньги — значит не правильно хранил данные от счета. Ради дополнительной безопасности, далее мы не будем реализовывать восстановление пароля.

Функция получения хэша, представленную здесь можно представить еще так:
$inline$hash (w, pw) = crc32 (w) .w.hmac (pw)$inline$

Функция работает так:

На вход поступает пароль, проверяем, подключена ли база данных:

Да, подключена. (Давайте сделаем пароль 12345)
Генерируем адрес кошелька, и пишем в $walletId — получаем примерно такое: FLC@c1cbe61d-a19d-4c82-ba17-3a577df0aeb5 (этот GUID был сгенерирован другим сайтом, я потом к нему добавил префикс FLC@).

Просчитываем plain-text хэша. В первом череду мы получим следующее: 0087196442FLC@c1cbe61d-a19d-4c82-ba17-3a577df0aeb59ee77779db5a44ef3f60044fcb0e1170218e642cb9adc0789ddd08871ad95c7e1d3b1391c6c79774d319b5141bd3b13b717c3d389206ede659cb280949feac66 (много букв, не удивляйтесь).
Во втором череду, хэш станет сто-процентным HMAC: 5e43cbb404c81efeb35162e5bba0766f20b340fbabcc66fd5f7a6a1993a8ccd43fc6384ac53f939acca4378681e2bb17912eacf4c65b8b63acda9aa2d15f5646 (опять же, много букв, использую SHA-512 HMAC).

Записываем это все в БД.

В ответ пишем номер кошелька, через который можно авторизоваться.

Нет, не подключена.
В таком случае, ожидать чего-то стоющего не придется, возвращаем FALSE.

Так выглядит замудренная система регистрации. На деле она выглядит ну очень простой. А в плане кода — куда сложнее.

Потратим еще время, и сделаем авторизацию. С авторизацией дело обстоит по-проще. Мы ищем аккаунт, и пытаемся авторизоваться, зная уже знакомые нам алгоритмы генерации хэша пароля. Затем сверяем, и в случае верности данных, даем клиенту важную строчку, в которой хранятся данные для коммуникации между сервером и майнером.

От себя.
Звучит, как будто мы делаем не криптовалюту, а систему авторизации между PHP на сервере и PHP используя php.exe. Во второй части мы добавим к нашему «торту» второй коржик, в которой будет видно что-то, связанное с майнингом, блокчейном и транзакциями.

Спустя еще 15-25 минут была готова и функция авторизации. Лицезрейте этот код ниже:

        public function login ($walletId, $password) // Авторизация
	{
		if ($this->db) // Защита от дурака
		{

			$hash = crc32 ($walletId) . $walletId . hash_hmac ('sha512', str_rot13 ($password), 'FLC'); // Сложные вычисления пароля для хэша.
			$hash = hash_hmac ('sha512', $hash, 'FLC2'); // Сам пароль в виде хэша.

			$newUser = $this->db->prepare ("
					SELECT
						*
					FROM
						`wallets`
					WHERE
						walletId = :walletId
					AND
						loginHash = :hash
					LIMIT
						0, 1
				"); // Готовим запрос в базу.

			$newUser->bindParam (':walletId', $walletId, PDO::PARAM_STR); // Привязываем кошелек к запросу
			$newUser->bindParam (':hash', $hash, PDO::PARAM_STR); // Привязываем хэш к запросу

			$newUser->execute (); // Исполняем запрос.
			$object = $newUser->fetch_object (); // Соблюдаем использование ООП, делаем в виде объекта
			if ($object AND hash_equals ($object->hash, $hash)) // Если есть пользователь, и хэши совпадают.
			{
				$loginString = new stdClass; // Создаем пустое хранилище stdClass
				$loginString->hash = $hash; // Консервируем в него хэш и номер счета.
				$loginString->wallet = $walletId;

				return ['token' => serialize ($loginString)]; // С этой строчкой делаем что угодно, но не распространяем.
			}
			else
				return false; // Пользователь не существует/неправильный пароль.
		}
		else
			return false; // Не удалось войти
	}

Этот код — почти что регистрация, но действует в обратном алгоритме. Работа с пользователями подошла к концу.

Постусловие


За время этого занимательного времени прочтения статьи, мы сделали следующее:

  • Вкратце познакомились с криптовалютой, и как она должна строится правильно.
  • Коротко ответили на вопрос «Имеет ли смысл делать централизованную криптовалюту, и к тому же на PHP?»
  • Обсудили конспекты перед выполнением данной задачи
  • Реализовали систему крипто-счетов.

А теперь, Вы можете почитать, что было за жалюзями:

Под занавесом
Эта статья писалась около 4-5 часов (весь закат и вечер). За это время, я по мере написания и разрабатывал, но когда дело доходит написать статью на Хабрахабре, желание у меня, осуществлить мечту не угасает. Действительно, из-за простой концепции ООП, мне было проще использовать именно его, нежели MySQLi, или еще хуже, обычные функции с префиксом mysql_, которые считаются устаревшими, и более не пригодны для разработки на PHP.

Что мы сделаем в следующий раз:

  • Сделаем майнинг.
  • Рассмотрим, как устроены транзакции и блоки.
  • Сделаем транзакции, и дадим смысл пока-что не использованному классу Block.

Спасибо за прочтение! Еще увидимся! Это моя первая статья — жду критики.

Комментарии (8)


  1. dzsysop
    21.12.2019 00:20
    +3

    try
    {
    return true; // Соединение удалось.
    }

    По-моему это шедевр.


    1. RA_ZeroTech
      21.12.2019 14:07
      +1

      И комментарии «забавные»


      // Возвращаем true, если хэш не годен, и true — если хэш годен


      :)


    1. Alyoshka1976
      21.12.2019 15:05

      Сначало было решено назвать поле вот так:
      protected $db;


      Но в Google пример для соединения написан вот так:
      $dbh = new PDO ($dsn, $username, $password); // Попытка подключиться к базе


      Вот же незадача :-)


  1. Die_Gelassenheit
    21.12.2019 09:45

    Пожалуйста, хватит писать криптовалюты. Хоть на пхп, хоть на c++, хоть на абаке.


    1. AdmAlexus
      21.12.2019 17:44

      Вы слишком строги к абаку.


  1. index0h
    22.12.2019 01:28

    Имеет ли смысл писать централизованную криптовалюту, и к тому же на PHP?

    Я считаю, не очень. Но, попробовать еще как стоит.

    Если смысла нет и вы это понимаете, зачем вы этим занимаетесь? Для самообучения?


  1. index0h
    22.12.2019 01:42
    -1

    Ваш код печалит котейку, не надо так
    image


  1. BruAPAHE
    24.12.2019 13:28

    Что там по Psr'ам