Bitcoin P2P PROTOCOL
План:
Текст:
1)
2)Пролог
Вообщем-то различные методы осуществления коммуникации между ботами в сети прежде были описаны мною
в этой статье: https://xss.pro/index.php?topic=23932 (блог https://dlab.im/blog/23.html )
Из всех предложенных способов, я решил выбрать в качестве тестов 11 метод "Bitcoin".
Транзакции в сети проходят с участием пользователей, которые считают DOUBLE-SHA256 хэш от блока, набитого
присланными данными. Эти действия(выполнение proof-of-work) позволяют поддерживать стойкость сети
к различного рода атакам(примером может служить: double-spending attack).
Подробно об устройстве вы можете почитать в официальном доке: bitcoin.org/bitcoin.pdf
3)Техническое описание протокола
Bitcoin использует TCP для передачи данных между нодами.
Существуют всего 2 сети: основная(порт 8333) и тестовая(порт 18333). Сеть поддерживает IPv4, IPv6 адреса.
Структура пакета:
Я выделил 14 основных команд: version, verack, getaddr, addr, inv, getdata, getblocks, getheaders, tx, block, headers, alert, ping, submitorder, checkorder, reply.
version - информация о версии bitcoin и количестве блоков. Передаётся при первом подключении.
verack - передаётся в ответ на пакет "version".
getaddr - запрос на список доступных нодов.
addr - список IP адресов с портами.
inv - передаётся, когда нод получает новый блок с транзакциями.
getdata - запросить блок или транзакцию по хэшу.
getblocks - запросить блоки в виде пакета "inv" в определённом промежутке.
getheaders - запросить блоки в виде пакета "headers" в определённом промежутке.
tx - отправить транзакцию. Этот пакет передаётся в ответ "getdata".
block - отправить блок. Этот пакет передаётся в ответ "getdata".
headers - отправить 2000 заголовков блоков.
alert - отправить предупреждающее сообщение(сообщение верифицируется и только ведущий разработчик,
т.е хозяин закрытого ключа может его отправить).
ping/pong - просто проверка того, что соединение до сих пор актуально.
submitorder, checkorder, reply - используется при транзакциях на IP адрес.
Существуют 2 способа(документированных) найти пиры:
(I) DNS seeds(парсинг IP из ответа TYPE_A DNS серверов).
(II) Вшитый в bitcoin клиент список IP пиров.
При подключении, мы посылаем пакет "version", который содержит в себе: версию клиента, номер блока и
локальное время. Нод верифицирует пакет и в случае успеха шлёт нам "verack", а затем свой пакет "version".
Затем, чтобы обменяться списками нодов или же получить актуальный список, мы шлём пакет "getaddr".
Нод отвечает пакетом "addr", содержащим список подключённых к нему других нодов.
До 0.6 версии был ещё способ получения пиров через IRC сеть, но его ликвидировали.
Я описал необходимый минимум для создания базового проекта, приступим к рассмотрению его частей.
4)DNS seed
Я упомянул выше, что в процессе поиска пиров используется парсинг IP из ответа TYPE_A DNS серверов.
DNS сервера для основной сети:
DNS сервера для тестовой сети:
Исходник:
Приведённая функция возвращает handle на выделенную память с адресами нодов.
В полном исходнике, который я приложу в конце статьи нет функции для создания своего пира.
В принципе ничего сложного нет - достаточно открыть порт(8333 - если основная сеть, 18333 - если
тестовая сеть) и принимать входящие соединения.
Процесс авторизации аналогичен процессу подключения (пункт 3, техническое описание протокола),
за исключением того, что:
(I) Вам придётся принимать входящие команды. Из основных я бы выделил только частоприходящие - это
"inv", "getaddr", "addr". На пакеты "inv" и "addr" в принципе можно не отвечать.
На пакет "getaddr" потребуется ответить пакетом "addr" с указанием своего IP, а также других.
(II) Время от времени надо слать ping/pong для поддержания соединения, а также свой IP в пакете "addr".
Для тех, кто заинтересован в протоколе, прикладываю также в архиве с сорцами документацию.
5)STUN: getting external IP address
В самом bitcoin для получения внешнего IP, парсится два сайта: checkip.dyndns.org и showmyip.com/simple/
Я решил пойти другим более стабильным путём, а именно через STUN(Session Traversal Utilities for NAT).
Более подробно о STUN читайте тут: tools.ietf.org/html/rfc3489
В проекте, функция лишь используется для проверки того, находится ли нода за NAT.
Акцентировать много внимания на нём я не буду, приведу сразу исходник:
6)Эпилог
Моя статья - это лишь демонстрация PoC(Proof of concept). В мои планы не входило создание самодельного
протокола для общения между ботами поверх основной сети и верификации команд через ассиметричный
криптоалгоритм. В исходниках я приложил базовые основы для создания своего пира.
В качестве примера, программа подключается к тестовой или основной сети и собирает nodes list.
При желании, вы сможете сотворить из этого нечто большее
Будут вопросы - обращайтесь.
Архив с сорцами:
Respects goes 2 all members of xss.pro/, которые так или иначе поддерживают меня.
Спасибо вам ребята за моральную поддержку! Поздравляю всех с наступающим 2014 годом!
Напоследок посоветую всем перечитать первый пункт
Постскриптум:
Честно, удивительно, что до этого никто не создал сеть подобную bitcoin. Идея, грубо говоря витала
в воздухе и любой подкованный программист выше среднего уровня с хорошим знанием криптографии, смог бы
создать такого рода систему. Может просто не было ТЗ ?
Критика сорцов - не принимается
. Писал код ровно 3 дня напролёт.
План:
1)Права, защищающие этот документ.
2)Пролог.
3)Техническое описание протокола.
4)DNS seed.
5)STUN: getting external IP address.
6)Эпилог.
Текст:
1)
Во-первых данный документ носит лишь исследовательский характер и не является призывом к совершению
неправомерных поступков, нарушающих законодательство вашей страны!
Во-вторых незаконное копирование частей текста и любых других его составляющих карается законом
по защите авторских прав собственности!
Разрешено копирование на ваш ресурс, только с указанием автора текста, а также гиперссылки на
оригинальную тему.
2)Пролог
Вообщем-то различные методы осуществления коммуникации между ботами в сети прежде были описаны мною
в этой статье: https://xss.pro/index.php?topic=23932 (блог https://dlab.im/blog/23.html )
Из всех предложенных способов, я решил выбрать в качестве тестов 11 метод "Bitcoin".
В 2х словах: bitcoin - это P2P ЭПС(электронная платёжная система) написанная на C++ с Qt Framework.
Хорошей документации, в целом, по исходному коду и протоколу нет. Основные изменения в протоколе
публикуются в основном как BIP(bitcoin improvement proposals) на github/sourceforge.
Начиная с 08 года(момент публикации автором bitcoin) альфа-версии 0.1, происходит постепенная
"рассортировка" кода(блоки кочуют из одного файла в другой), вследствие чего, весь код наслаивается
будто лапша. Впрочем, это только моё мнение![]()
Транзакции в сети проходят с участием пользователей, которые считают DOUBLE-SHA256 хэш от блока, набитого
присланными данными. Эти действия(выполнение proof-of-work) позволяют поддерживать стойкость сети
к различного рода атакам(примером может служить: double-spending attack).
Подробно об устройстве вы можете почитать в официальном доке: bitcoin.org/bitcoin.pdf
Будучи хорошо распиаренной системой, текущая стоимость всех добытых денег в системе почти 10
миллиардов долларов(статистика - coinmarketcap.com).
3)Техническое описание протокола
Bitcoin использует TCP для передачи данных между нодами.
Существуют всего 2 сети: основная(порт 8333) и тестовая(порт 18333). Сеть поддерживает IPv4, IPv6 адреса.
Структура пакета:
Я выделил 14 основных команд: version, verack, getaddr, addr, inv, getdata, getblocks, getheaders, tx, block, headers, alert, ping, submitorder, checkorder, reply.
version - информация о версии bitcoin и количестве блоков. Передаётся при первом подключении.
verack - передаётся в ответ на пакет "version".
getaddr - запрос на список доступных нодов.
addr - список IP адресов с портами.
inv - передаётся, когда нод получает новый блок с транзакциями.
getdata - запросить блок или транзакцию по хэшу.
getblocks - запросить блоки в виде пакета "inv" в определённом промежутке.
getheaders - запросить блоки в виде пакета "headers" в определённом промежутке.
tx - отправить транзакцию. Этот пакет передаётся в ответ "getdata".
block - отправить блок. Этот пакет передаётся в ответ "getdata".
headers - отправить 2000 заголовков блоков.
alert - отправить предупреждающее сообщение(сообщение верифицируется и только ведущий разработчик,
т.е хозяин закрытого ключа может его отправить).
ping/pong - просто проверка того, что соединение до сих пор актуально.
submitorder, checkorder, reply - используется при транзакциях на IP адрес.
Существуют 2 способа(документированных) найти пиры:
(I) DNS seeds(парсинг IP из ответа TYPE_A DNS серверов).
(II) Вшитый в bitcoin клиент список IP пиров.
При подключении, мы посылаем пакет "version", который содержит в себе: версию клиента, номер блока и
локальное время. Нод верифицирует пакет и в случае успеха шлёт нам "verack", а затем свой пакет "version".
Затем, чтобы обменяться списками нодов или же получить актуальный список, мы шлём пакет "getaddr".
Нод отвечает пакетом "addr", содержащим список подключённых к нему других нодов.
До 0.6 версии был ещё способ получения пиров через IRC сеть, но его ликвидировали.
Я описал необходимый минимум для создания базового проекта, приступим к рассмотрению его частей.
4)DNS seed
Я упомянул выше, что в процессе поиска пиров используется парсинг IP из ответа TYPE_A DNS серверов.
DNS сервера для основной сети:
WCHAR *MainNetDNSSeed[] = { L"seed.bitcoin.sipa.be", L"dnsseed.bluematt.me",
L"dnsseed.bitcoin.dashjr.org", L"bitseed.xf2.org" };
DNS сервера для тестовой сети:
WCHAR *TestNetDNSSeed[] = { L"testnet-seed.bitcoin.petertodd.org", L"testnet-seed.bluematt.me" };
Исходник:
#define DNS_BUFFER_SIZE 0x1000
HANDLE dns_request(WCHAR *pHost) {
HANDLE hEvent, pDnsBuf, pPeerList;
SOCKET pSocket;
DWORD DnsBufSize, RecvSize;
sockaddr_in sAddr, rAddr;
PDNS_RECORD pDnsRecord;
pDnsBuf = AllocMem(DNS_BUFFER_SIZE);
if(!pDnsBuf) {
return FALSE;
}
pPeerList = AllocMem(4096);
if(!pPeerList) {
return FALSE;
}
DnsBufSize = DNS_BUFFER_SIZE;
if(!DnsWriteQuestionToBuffer_W((PDNS_MESSAGE_BUFFER)pDnsBuf,&DnsBufSize,pHost,DNS_TYPE_A,\
GetTickCount(),TRUE)) {
goto exit_2;
}
sAddr.sin_family = AF_INET;
sAddr.sin_port = htons(53);
sAddr.sin_addr.s_addr = 0x04040808; // google public dns server
pSocket = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(pSocket == INVALID_SOCKET) {
goto exit_2;
}
hEvent = WSACreateEvent();
if(WSAEventSelect(pSocket,hEvent,FD_READ)) {
goto exit_1;
}
if( ( sendto(pSocket,(char *)pDnsBuf,DnsBufSize,NULL,(sockaddr *)&sAddr,\
sizeof(sAddr)) ) != DnsBufSize ) {
goto exit_1;
}
if(WaitForSingleObject(hEvent,5*1000)) {
goto exit_1;
}
DnsBufSize = sizeof(rAddr);
RecvSize = recvfrom(pSocket,(char *)pDnsBuf,DNS_BUFFER_SIZE,NULL,\
(sockaddr *)&rAddr,(int *)&DnsBufSize);
if(!RecvSize || RecvSize == SOCKET_ERROR) {
goto exit_1;
}
DNS_BYTE_FLIP_HEADER_COUNTS((PDNS_HEADER)pDnsBuf);
if( DnsExtractRecordsFromMessage_W((PDNS_MESSAGE_BUFFER)pDnsBuf,RecvSize,\
&pDnsRecord) ) {
goto exit_1;
}
RecvSize = (DWORD)pPeerList;
while(pDnsRecord) {
if( pDnsRecord->wType != DNS_TYPE_A) {
pDnsRecord = pDnsRecord->pNext;
continue;
}
*(int *)pPeerList = pDnsRecord->Data.A.IpAddress;
pPeerList = (int *)pPeerList + 1;
pDnsRecord = pDnsRecord->pNext;
}
DnsRecordListFree((PDNS_RECORD)pDnsRecord,DnsFreeRecordList);
exit_1:
CloseHandle(hEvent);
closesocket(pSocket);
exit_2:
FreeMem(pDnsBuf);
return (HANDLE)RecvSize;
}
Приведённая функция возвращает handle на выделенную память с адресами нодов.
В полном исходнике, который я приложу в конце статьи нет функции для создания своего пира.
В принципе ничего сложного нет - достаточно открыть порт(8333 - если основная сеть, 18333 - если
тестовая сеть) и принимать входящие соединения.
Процесс авторизации аналогичен процессу подключения (пункт 3, техническое описание протокола),
за исключением того, что:
(I) Вам придётся принимать входящие команды. Из основных я бы выделил только частоприходящие - это
"inv", "getaddr", "addr". На пакеты "inv" и "addr" в принципе можно не отвечать.
На пакет "getaddr" потребуется ответить пакетом "addr" с указанием своего IP, а также других.
(II) Время от времени надо слать ping/pong для поддержания соединения, а также свой IP в пакете "addr".
Для тех, кто заинтересован в протоколе, прикладываю также в архиве с сорцами документацию.
5)STUN: getting external IP address
В самом bitcoin для получения внешнего IP, парсится два сайта: checkip.dyndns.org и showmyip.com/simple/
Я решил пойти другим более стабильным путём, а именно через STUN(Session Traversal Utilities for NAT).
Более подробно о STUN читайте тут: tools.ietf.org/html/rfc3489
В проекте, функция лишь используется для проверки того, находится ли нода за NAT.
Акцентировать много внимания на нём я не буду, приведу сразу исходник:
#define STUN_DEFAULT_PORT 3478
#define MAPPED_ADDRESS 0x0001
#define CHANGE_REQUEST 0x0003
#define Binding_Request 0x0001
#define Binding_Response 0x0101
char *StunServers[][2] = { "stun.stunprotocol.org", 0,
"stun.ekiga.net", 0,
"stun.ideasip.com", 0,
"stun.iptel.org", 0,
"stun.rixtelecom.se", 0,
"stun.schlund.de", 0 };
#define bswap16(x) ((((x) & 0xff) << 8) | (((x) >> 8) & 0xff))
#define bswap32(x) ((((x) & 0xff) << 24) | (((x) & 0xff00) << 8) \
| (((x) >> 8) & 0xff00) | (((x) >> 24) & 0xff))
#pragma pack(push, 1)
typedef struct _STUN_MSG {
WORD msgType;
WORD msgLength;
BYTE id[16];
WORD type;
WORD length;
DWORD value;
} STUN_MSG;
#pragma pack(pop)
#define STUN_BUFFER_SIZE 0x1000
int GetExternalIP() {
SOCKET pSocket;
sockaddr_in sAddr, rAddr;
hostent *hostent_s;
int ExternalIP, len, RecvSize;
HANDLE hEvent;
STUN_MSG *pBuf;
STUN_MSG stun;
pBuf = (STUN_MSG *)AllocMem(STUN_BUFFER_SIZE);
if(!pBuf) {
return FALSE;
}
pSocket = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(pSocket == INVALID_SOCKET) {
return FALSE;
}
hEvent = WSACreateEvent();
if(WSAEventSelect(pSocket,hEvent,FD_READ)) {
goto exit_2;
}
{
for(int i = 0; i < sizeof(StunServers)/sizeof(StunServers[0]); i++) {
sAddr.sin_family = AF_INET;
if(!StunServers[ i ][1]) {
sAddr.sin_port = htons(STUN_DEFAULT_PORT);
} else {
sAddr.sin_port = htons((WORD)StunServers[ i ][1]);
}
hostent_s = gethostbyname(StunServers[ i ][0]);
if(!hostent_s) {
continue;
}
sAddr.sin_addr.s_addr = *(int *)hostent_s->h_addr_list[0];
RtlZeroMemory(&stun, sizeof(stun));
stun.msgType = bswap16(Binding_Request);
stun.msgLength = bswap16(0x8); // sizeof(type + length + value)
RAND_bytes((DWORD *)&stun.id, sizeof(stun.id)); // Transaction ID
stun.type = bswap16(CHANGE_REQUEST);
stun.length = bswap16(0x4);
back:
len = sizeof(stun);
if( sendto(pSocket,(char *)&stun,len,NULL,(sockaddr *)&sAddr,\
sizeof(sAddr)) != len ) {
continue;
}
if(WaitForSingleObject(hEvent,5*1000)) {
goto back;
}
RtlZeroMemory(pBuf, STUN_BUFFER_SIZE);
len = sizeof(rAddr);
RecvSize = recvfrom(pSocket,(char *)pBuf,STUN_BUFFER_SIZE,NULL,\
(sockaddr *)&rAddr,(int *)&len);
if(!RecvSize || RecvSize == SOCKET_ERROR) {
continue;
}
if(bswap16(pBuf->msgType) == Binding_Response) {
for(int i = 0; i < bswap16(pBuf->msgLength); ) {
if( bswap16((pBuf->type + i)) == MAPPED_ADDRESS) {
ExternalIP = *(int *)( ((int)&pBuf->value + i) + 4 ); // Family=0x01, Port = 0x****
break;
}
i += bswap16(pBuf->length) + 4; // sizeof(type) + sizeof(length)
}
break;
}
}
}
exit_2:
CloseHandle(hEvent);
closesocket(pSocket);
FreeMem(pBuf);
return ExternalIP;
}
6)Эпилог
Моя статья - это лишь демонстрация PoC(Proof of concept). В мои планы не входило создание самодельного
протокола для общения между ботами поверх основной сети и верификации команд через ассиметричный
криптоалгоритм. В исходниках я приложил базовые основы для создания своего пира.
В качестве примера, программа подключается к тестовой или основной сети и собирает nodes list.
При желании, вы сможете сотворить из этого нечто большее
Архив с сорцами:
LINK: https://www.dropbox.com/s/80xcvndix9guns8/abc.rar?dl=1
PASS: damagelab_newyear2014_UG()!*&@W()!*G)(!@*G)_!@YG)!*)JWQ
PASS: damagelab_newyear2014_UG()!*&@W()!*G)(!@*G)_!@YG)!*)JWQ
Respects goes 2 all members of xss.pro/, которые так или иначе поддерживают меня.
Спасибо вам ребята за моральную поддержку! Поздравляю всех с наступающим 2014 годом!
Напоследок посоветую всем перечитать первый пункт
Постскриптум:
Честно, удивительно, что до этого никто не создал сеть подобную bitcoin. Идея, грубо говоря витала
в воздухе и любой подкованный программист выше среднего уровня с хорошим знанием криптографии, смог бы
создать такого рода систему. Может просто не было ТЗ ?
Критика сорцов - не принимается