Предисловие
Да, Вы только вдумайтесь, централизованная криптовалюта на PHP, это не правильно. Но мои ограничения на идеи все же пробились, поэтому, как Вы уже догадались, решил написать об этом. В самой первой части, мы расскажем, что такое криптовалюта, обсудим первоначальные конспекты, и сделаем систему кошельков. А теперь, приступим.
Криптовалюта, что из себя представляет?
информация взята из Википедии
Криптовалю?та — разновидность цифровой валюты, учёт внутренних расчётных единиц которой обеспечивает децентрализованная платёжная система (нет внутреннего или внешнего администратора или какого-либо его аналога), работающая в полностью автоматическом режиме.
Сама по себе криптовалюта не имеет какой-либо особой материальной или электронной формы — это просто число, обозначающее количество данных расчётных единиц, которое записывается в соответствующей позиции информационного пакета протокола передачи данных и зачастую даже не подвергается шифрованию, как и вся иная информация о транзакциях между адресами системы.
Вкратце криптовалюта работает так:
- Пользователь сети, имея баланс, отправляет другому анонимному пользователю транзакцию.
- Транзакция поступает в сеть.
- Майнеры (или-же пользователи, занимающиеся созданием блоков) решают сгенерированную по специальному алгоритму задачу с условиями (допустим, на наличие предшествующих 10-30 нулей в хеше, или наоборот).
- Когда условие (или условия) соблюдаются в хэше, благодаря ему, майнер отправляет в сеть блок, оповещающий о создании нового блока и выполнении транзакции.
- Получатель получает средства, а майнер — сумму вознаграждения, в виде комиcсии, просчитываемого при подготовки задачи.
Имеет ли смысл писать централизованную криптовалюту, и к тому же на PHP?
Я считаю, не очень. Но, попробовать еще как стоит.
Учитываем конспекты перед разработкой
Мы будем использовать двойную систему проверки хешей используя Proof-of-Work. Имеет смысл делать проверку хешей на программном и сетевом прикладном уровне. Это связано с процессом оптимизации нагрузки на сервера. Поскольку, когда мы будем обращаться к серверу за проверкой хеша много раз, появится такая нагрузка. Поэтому, предлагаю поступить следующим методом:
- На программном майнере локально проверять хеш на удовлетворение условий.
- Когда на программном майнере условия совпадут с необходимыми, делаем проверку на прикладном сетевом уровне (на уровне сервера).
Это, по-моему лучшему мнению, оптимальный способ распределения нагрузки. Проще несколько раз проверить на компьютере локально, чем много раз спамить запросами на сервер.
Название — я придумал простое, и возможно, запоминаемое название — 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; // Не удалось зарегистрировать кошелек в системе.
}
Здесь я кратко описал, что делает та или иная строка.
Справедливое сообщение для справедливых людей и программистов.
Если Вы оставили о себе контакты на сайте (на котором, возможно, эта криптовалюта и расположится), и Вам пишут что-то подобное:
«Привет можешь помочь? Я хранил на твоем блокчейне мани, и вдруг я потерял пароль помоги пожалуйста и будешь хорошим» — смело отклоняйте данное предложение. Суть криптовалюты — это хранение вскопанных чисел в безопасности. Никто так не докажет, что он или она является владельцем счета. Таким образом, любой злоумышленник может даже украсть чужие средства даже без участия самой жертвы! Не важно, хоть и действительно является этот человек владельцем счета, лучше отказывать. Если потерял деньги — значит не правильно хранил данные от счета. Ради дополнительной безопасности, далее мы не будем реализовывать восстановление пароля.
«Привет можешь помочь? Я хранил на твоем блокчейне мани, и вдруг я потерял пароль помоги пожалуйста и будешь хорошим» — смело отклоняйте данное предложение. Суть криптовалюты — это хранение вскопанных чисел в безопасности. Никто так не докажет, что он или она является владельцем счета. Таким образом, любой злоумышленник может даже украсть чужие средства даже без участия самой жертвы! Не важно, хоть и действительно является этот человек владельцем счета, лучше отказывать. Если потерял деньги — значит не правильно хранил данные от счета. Ради дополнительной безопасности, далее мы не будем реализовывать восстановление пароля.
Функция получения хэша, представленную здесь можно представить еще так:
Функция работает так:
На вход поступает пароль, проверяем, подключена ли база данных:
Да, подключена. (Давайте сделаем пароль 12345)
Генерируем адрес кошелька, и пишем в $walletId — получаем примерно такое: FLC@c1cbe61d-a19d-4c82-ba17-3a577df0aeb5 (этот GUID был сгенерирован другим сайтом, я потом к нему добавил префикс FLC@).
Просчитываем plain-text хэша. В первом череду мы получим следующее: 0087196442FLC@c1cbe61d-a19d-4c82-ba17-3a577df0aeb59ee77779db5a44ef3f60044fcb0e1170218e642cb9adc0789ddd08871ad95c7e1d3b1391c6c79774d319b5141bd3b13b717c3d389206ede659cb280949feac66 (много букв, не удивляйтесь).
Во втором череду, хэш станет сто-процентным HMAC: 5e43cbb404c81efeb35162e5bba0766f20b340fbabcc66fd5f7a6a1993a8ccd43fc6384ac53f939acca4378681e2bb17912eacf4c65b8b63acda9aa2d15f5646 (опять же, много букв, использую SHA-512 HMAC).
Записываем это все в БД.
В ответ пишем номер кошелька, через который можно авторизоваться.
Просчитываем 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)
Die_Gelassenheit
21.12.2019 09:45Пожалуйста, хватит писать криптовалюты. Хоть на пхп, хоть на c++, хоть на абаке.
index0h
22.12.2019 01:28Имеет ли смысл писать централизованную криптовалюту, и к тому же на PHP?
Я считаю, не очень. Но, попробовать еще как стоит.Если смысла нет и вы это понимаете, зачем вы этим занимаетесь? Для самообучения?
dzsysop
try
{
return true; // Соединение удалось.
}
По-моему это шедевр.
RA_ZeroTech
И комментарии «забавные»
…
// Возвращаем true, если хэш не годен, и true — если хэш годен
…
:)
Alyoshka1976
Сначало было решено назвать поле вот так:
protected $db;
Но в Google пример для соединения написан вот так:
$dbh = new PDO ($dsn, $username, $password); // Попытка подключиться к базе
Вот же незадача :-)