Gdzieś w tatrach

Protokół Bitcoina od środka: Budujemy nagłówek

W poprzednim wpisie szczegółowo opisałem budowę wiadomości, które są konieczne, aby zainicjować komunikację z siecią Bitcoin. Aby moć wykorzystać poznaną teorię w praktyce, czas przedstawić przykładową implementację tych wiadomości. W niniejszym wpisie zaczniemy od nagłówka, który jest podstawą każdego pakietu. Zobaczmy, jak zbudowany jest nagłówek wiadomości w protokole Bitcoina.

Przykłady, które tutaj się pojawią będą przedstawione w języku C#, jednak implementując swoją wersję aplikacji nie powinieneś mieć problemów z przepisaniem ich do języka, którego Ty używasz na co dzień. No to lecimy 🙂

Z poprzedniego wpisu wiemy, że nagłówek składa się z czterech elementów: „magic”, „command”, „length” i „checksum”. Według specyfikacji rozmiaru poszczególnych pól, ich reprezentacja mogłaby wyglądać następująco:

public class Header
{
  public uint Magic { get; }
  public string Command { get; }
  public uint PayloadLength { get; }
  public uint Checksum { get; }
    
  //...
}

Dla pól zajmujących dokładnie 4 bajty idealnie nada się zmienna typu unsigned int, zaś dla pola „command” oznaczającego nazwę wiadomości wykorzystamy zmienną typu string. Pole to powinno zajmować dokładnie 12 bajtów, dlatego w późniejszym procesie jego przetwarzania brakujące bajty zostaną uzupełnione zerami.

Zajmijmy się tworzeniem pustego nagłówka dla wiadomości o podanej nazwie. Do tego celu użyjemy konstruktora, w którym jako parametr podamy nazwę wiadomości:

public class Header
{
  //...

  public Header(string command)
  {
    var payload = new byte[] { };

    Magic = 0xD9B4BEF9;
    Command = command.PadRight(12, '\0');
    PayloadLength = 0;
    Checksum = BitcoinHelper.CalculateChecksum(payload);
  }

  //...
}

Wiadomość, póki co, nie ma zawartości, dlatego jej treść to pusta tablica bajtów. Komentarza wymaga linijka licząca sumę kontrolną. Wedługo specyfikacji suma kontrolna wiadomości, to pierwsze 8 bajtów podwójnie zastosowanego algorytmu SHA-256. Czynność ta została wydelegowana do metody w statycznej klasie BitcoinHelper:

public static class BitcoinHelper
{
  //...

  public static uint CalculateChecksum(byte[] bytes)
  {
    var sha256Managed = new SHA256Managed();
    var hash = sha256Managed.ComputeHash(sha256Managed.ComputeHash(bytes));

    return BitConverter.ToUInt32(hash, 0);
  }

  //...
}

Aby komunikować się z siecią Bitcoin, potrzebujemy metody, która przekształci zawartość nagłówka w ciąg bajtów. Przy każdym wysyłaniu komunikatu, będziemy tworzyć obiekt klasy reprezentującej wiadomość, następnie przekształcać go na ciąg bajtów, aby w kolejnym kroku wysłać do wybranego węzła sieci Bitcoin. Implementacja wspomnianej metody może wyglądać następująco:

public byte[] ToBytes()
{
  var bytes = BitConverter.GetBytes(Magic)
      .Concat(Encoding.ASCII.GetBytes(Command))
      .Concat(BitConverter.GetBytes(PayloadLength))
      .Concat(BitConverter.GetBytes(Checksum))
      .ToArray();

    return bytes;
}

Przyda się także metoda, która deserializuje ciąg bajtów otrzymany od sieci. Każda wiadomość zaczyna się nagłówkiem, dlatego każdy ciąg bajtów przybywający od węzła będzie zaczynał swoją przygodę w jego konstruktorze:

public Header(byte[] bytes)
{
  Magic = BitConverter.ToUInt32(bytes, 0);
  Command = Encoding.ASCII.GetString(bytes, 4, 12);
  PayloadLength = BitConverter.ToUInt32(bytes, 16);
  Checksum = BitConverter.ToUInt32(bytes, 20);
}

Tym sposobem możemy już budować nagłówek dowolnej wiadomości oraz tworzyć jego obiekt na podstawie przychodzącego od węzła ciągu bajtów. Nie jest to ostateczna wersja klasy nagłówka, w następnych etapach powstawania aplikacji wymagane funkcjonalności będą dodawane na bieżąco. W kolejnym wpisie skupimy się na wykorzystaniu klasy nagłówka do utworzenia pierwszej wiadomości typu „version” i wysłaniu jej do węzła sieci Bitcoin w celu otrzymania pozwolenia na komunikację.

Poniżej znajduje się pełny kod źródłowy omawianych we wpisie klas: