Przez ostatnie miesiące omawiałem różne techniki kryptograficzne. Nadeszła pora aby pokazać w praktyce ich ciekawe zastosowania. Już w tytule zdradziłem o czym będzie poniższy wpis. Zacznę od blockchain czyli łańcucha bloków. Blockchain kojarzy się głównie z Bitcoinem oraz innymi kryptowalutami, ale to mechanizm, który pozwala również realizować inne interesujące pomysły. Umożliwia on zabezpieczenie danych poprzez budowanie rozproszonego, integralnego rejestru wykorzystującego między innymi funkcje skrótu oraz podpisy cyfrowe .
Łańcuch bloków i kopanie
Bardzo często tworzymy różne rejestry, czyli zapisy świadczące o kolejno wykonanych operacjach. Jeśli będziemy je zapisywali bez żadnych zabezpieczeń to w każdej chwili taki rejestr może zostać zmodyfikowany i nie będziemy w stanie wykryć takiego zakłócenia. Atakujący może zmienić dany wpis lub usunąć te z nich, które uzna za niewygodne. Dotyczy to na przykład logów systemowych. Włamywacz często usuwa z nich zapisy pozwalające na wykrycie ataku i identyfikację atakującego. Rozwiązaniem tego problemu może być taki sposób budowania rejestru, aby kolejno zapisywane bloki miały zapewnioną integralność. Wtedy nie będzie możliwości zmiany pojedynczego bloku. To jednak nie wystarczy, bo nadal atakujący będzie mógł po prostu je usuwać. Integralność danego bloku należy więc zapewnić z uwzględnieniem wszystkich jego poprzedników. Przy wyliczaniu funkcji skrótu nie trzeba wyliczać jej dla całego rejestru, wystarczy skorzystać z ostatnio wyliczonej wartości ponieważ ona zapewnia integralność poprzedników.
Idea ta przedstawiona jest na poniższym rysunku. Pierwszy skrót wyliczany jest z danych pierwszego bloku. Kolejne skróty obejmują skrót poprzedniego bloku oraz nowe dane. Dzięki własności funkcji skrótu zapewniona jest integralność całego rejestru. Każde jego zakłócenie wpłynie na kolejne bloki. Danych nie da się również łatwo podrobić, czyli zastąpić innymi danymi dającymi ten sam skrót. Funkcje skrótu są odporne na takie ataki.
Rejestr pokazany na rysunku to blockchain. Jeśli przechowujemy go wyłącznie lokalnie to atakujący może jeszcze przeliczyć funkcje skrótu po wykonaniu swoich zmian. Jednak, jeśli rejestr będzie rozproszony, czyli jego kopia będzie przechowywana w wielu miejscach i na bieżąco synchronizowana to taka zmiana będzie utrudniona. Węzły sieci wykryją, że coś, co już było rozpowszechnione, zostało zmienione. Właścicielem łańcucha bloków powinna być cała sieć i dzięki temu, że przechowywany będzie w różnych miejscach jego modyfikacja będzie trudna. W razie ataku węzły sieci wykryją zmiany w poprzednich blokach i większość węzłów je odrzuci. Im więcej nowych bloków jest za blokiem, z którego dane nas interesują, tym trudniej taki atak przeprowadzić. Trzeba zakłócić więcej bloków i starać się je przekazać do innych węzłów.
Skoro rejestr jest rozproszony to kto powinien zatwierdzać kolejne bloki czyli wyliczać ich funkcje skrótu? Ma do tego prawo każdy węzeł sieci. Ponieważ funkcja skrótu jest łatwa do wyliczenia to niemożliwa byłaby efektywna synchronizacja takiego rejestru z uwagi na zbyt często pojawiające się nowe bloki. Stosuje się nieco zmodyfikowane rozwiązanie. Najpopularniejszym jest zastosowanie tak zwanego dowodu pracy (ang. proof of work). Tylko węzeł, który wykona pewną pracę, zatwierdzoną przez inne węzły, ma prawo do potwierdzenia bloku. Popularnie wykonywanie tego zadania nazywa się kopaniem (ang. mining). Za wykonanie pracy (wykopanie bloku) węzeł sieci jest wynagradzany. Praca ta polega najczęściej na rozwiązaniu pewnego problemu, który jest skomplikowany obliczeniowo. Na przykład w sieci Bitcoin zadanie polega na zaatakowaniu funkcji skrótu. Tylko węzeł, który znajdzie liczbę dającą razem z zatwierdzanym blokiem wartość skrótu rozpoczynającą się od pewnej liczby zer jest uznawany za zwycięzcę w kopaniu. Zatem blockchain zrealizowany w praktyce to nie tylko sposób przechowywania danych ale również algorytmy, które zarządzają siecią węzłów.
Transakcje
Rejestr to seria zapisów dowolnych informacji, które pozostaną w blockchain na zawsze. Można w nim umieścić na przykład swoją książkę czy opis wynalazku i jeśli taki łańcuch bloków będzie rejestrem uznawanym przez wszystkich to otrzymamy strukturę zapewniającą dowód autorstwa. Rejestry kojarzą się głównie z zapisami księgowymi. Aby zrealizować zabezpieczone transakcje nie wystarczy przechowywać samych sald powiązanych z kontami, ponieważ każdy będzie mógł to saldo zmodyfikować. Użytkownik musi panować nad swoim kontem.
Wykorzystajmy do tego znany już mechanizm podpisu cyfrowego. Konto użytkownika będzie identyfikowane jego kluczem publicznym i z tym kluczem będzie powiązany stan jego środków. Zlecając transakcję tworzymy paczkę zawierającą:
- konto źródłowe (identyfikowane przez klucz publiczny konta nadawcy),
- konto docelowe (identyfikowane przez klucz publiczny konta odbiorcy),
- dane transakcji (na przykład kwota przelewu),
- podpis transakcji (wykonany za pomocą klucza prywatnego nadawcy).
Analizując całą historię w łańcuchu bloków można na podstawie wpisów transakcyjnych wyliczyć saldo każdego z kont (saldo przypisane do kluczy publicznych). Taka jest idea działania kryptowalut. Zauważmy, że nie musimy nigdzie zakładać konta, wystarczy że ktoś wykona przelew na nasz klucz publiczny i dopiero wtedy pojawimy się w blockchain z niezerowym saldem. Żeby skorzystać z tych środków niezbędne jest posiadanie odpowiadającego mu klucza prywatnego.
Bezpieczeństwo naszego konta związane jest z bezpieczeństwem klucza prywatnego. Oznacza to, że jeżeli utracimy nasz klucz prywatny lub ktoś go nam wykradnie to tracimy pełną kontrolę nad naszymi aktywami. Podobnie jeśli zlecimy przekaz i popełnimy błąd w docelowym kluczu publicznym. Nie ma wtedy możliwości odzyskania takich środków. Spójność bloków i jasne zasady są (a przynajmniej powinny być) na pierwszym miejscu.
Blockchain w Ethereum
Sprawdźmy w praktyce jak działa blockchain. Wykorzystamy do tego jedną z jego implementacji jaką jest Ethereum. Aby utworzyć prywatny łańcuch potrzebne będzie oprogramowanie realizujące zadania węzła. Program ten nazywa się geth
i można go pobrać z repozytorium Ethereum. Do przygotowania poniższych przykładów używałem wersji 1.6.0. Może to być istotne, ponieważ projekt jest intensywnie rozwijany i pewne ustawienia konfiguracyjne mogą się zmieniać. Przyda nam się również klient Mist, pozwalający na wygodne tworzenie kont i zlecanie transakcji. Zalecam pobieranie wersji w pełni zgodnej z architekturą komputera jaki używamy (32 lub 64 bit).
Ethereum jest rozbudowanym łańcuchem bloków z wieloma funkcjami, ale na początek skorzystamy z możliwości przechowywania danych powiązanych z kontem użytkownika. W szczególności będą to zapisy o liczbie zgromadzonych etherów (w skrócie ETH). Jest to jednostka rozliczeniowa w tej sieci. Każdy ether to $$10^{18}$$ wei. Wei jest najmniejszą jednostką rozliczeniową w Ethereum. Wykorzystywaną funkcją skrótu w Ethereum jest Keccak-256.
W celu wystartowania prywatnego łańcucha niezbędne jest utworzenie pierwszego bloku. Poniżej prezentuję podstawowy plik konfiguracyjny dla bloku genesis w Ethereum (należy go zapisać do pliku genesis.json
). Podane w nim opcje określają:
- trudność obliczeniową zadania wykonywanego przy zatwierdzaniu bloku - przykładowa wartość pozwala na zatwierdzanie ich przy stosunkowo niewielkich zasobach obliczeniowych w kilkanaście sekund, w razie potrzeby można ją zmniejszyć,
- limit wykonywanych obliczeń dla transakcji - założeniem Ethereum jest zużywanie przez każdą transakcję paliwa (ang. gas), które przeliczane jest na opłatę w etherach,
- konfigurację - identyfikator sieci (100) oraz wersję używanej struktury bloków,
- początkowe środki dla wybranych kont (brak).
1{ 2 "difficulty": "0x400000", 3 "gasLimit": "0x8000000", 4 "config": { 5 "chainId": 100, 6 "homesteadBlock": 0, 7 "eip155Block": 0, 8 "eip158Block": 0 }, 9 "alloc": { } 10}
Polecenie
1geth --datadir ./eth init genesis.json
Wygeneruje w katalogu eth
nasz prywatny blockchain na podstawie bloku genesis. Będzie w nim tylko jeden blok.
W kolejnym etapie trzeba uruchomić lokalny węzeł. Należy użyć polecenia
1geth --datadir ./eth --networkid 100
Po uruchomieniu węzła można się z nim komunikować za pomocą poleceń RPC wysyłanych w formacie JSON. Dostępne są następujące kana ły:
- lokalna komunikacja z węzłem za pomocą IPC,
- zdalna i lokalna komunikacja z węzłem za pomocą protokołu HTTP (domyślnie jest to port 8545).
Komunikując się z węzłem możemy odczytywać aktualny stan łańcucha bloków, przeglądać dane w nim zawarte oraz zlecać nowe transakcje. W szczególności możemy uruchomić konsolę geth
za pomocą polecenia geth attach
oraz klienta Mist. Węzeł komunikuje się z innymi węzłami (tworzy sieć P2P) używając portu 30303. Dzięki sieci P2P synchronizowany jest budowany blockchain oraz przekazywane są informacje o zleconych transakcjach do innych węzłów.
Do rozpoczęcia korzystania z utworzonego blockchain konieczne będzie rozpoczęcie kopania. Nim to jednak zrobimy trzeba upewnić się, że mamy na dysku co najmniej 2GB wolnego miejsca i utworzyć pierwsze konto, aby otrzymywać wynagrodzenie za wykopane bloki. Możemy to zrobić uruchamiając klienta Mist. Połączy się on z naszym lokalnym węzłem i wykryje, że działamy w prywatnej sieci. Po wygenerowaniu nowego konta (wygenerowaniu pary kluczy algorytmu ECDSA) ekran aplikacji będzie prezentował się jak na poniższym rysunku.
Mając założone główne konto (jego identyfikator to wyliczona funkcja skrótu z klucza publicznego, klucze przechowywane są w katalogu ./eth/keystore
) możemy rozpocząć kopanie. Uruchamiamy konsolę geth
(polecenie geth attach
) i wykonujemy w niej polecenie miner.start(1)
. Uruchomi to proces kopania z użyciem jednego wątku wykorzystując jeden rdzeń naszego procesora. Pierwsze uruchomienie wiąże się również z wygenerowaniem dużego grafu na którym będzie realizowany algorytm. Graf zostanie zapamiętany w pliku i regenerowany co kilka dni. Może to trwać kilkanaście minut. Potem co kilka sekund w konsoli zaczną pojawiać się wpisy oznaczające utworzenie nowego bloku, a na naszym koncie zacznie przybywać po pięć etherów za każdy blok.
1Successfully sealed new block number=1 hash=0743ae3155b3 2🔨 mined potential block number=1 hash=0743ae3155b3 3Commit new mining work number=1 txs=0 uncles=0 elapsed=0s 4...
Warto utworzyć teraz drugie konto i spróbować wykonać transakcję między nimi. Przy zlecaniu transakcji podajemy adresy kont źródłowego i docelowego, przekazywaną liczbę etherów oraz proponowaną opłatę za transakcję. Otrzyma ją węzeł, który wykopie blok, czyli w tym przypadku my sami. Węzły rozgłaszają między sobą zlecone transakcje oraz wykopane bloki. Będąc węzłem nie ma obowiązku kopania, ale w naszym przypadku jest to niezbędne, aby tworzył się łańcuch - jesteśmy na razie jedynym węzłem w naszej sieci.
Ekran zatwierdzania zlecenia określa ile paliwa oraz w związku z tym ile etherów będzie kosztowała nasza transakcja. Ustawiony limit jest większy niż przewidywane zużycie, więc powinno wystarczyć paliwa, aby transakcja dotarła do odbiorcy.
Po zatwierdzeniu transakcji i wykopaniu bloku, który będzie ją zawierał, środki pokażą się na koncie odbiorcy. Proszę zaobserwować, że przy transakcji w Mist oznaczana jest na bieżąco liczba wykopanych kolejnych bloków. Przyjmuje się, że jeśli powstanie ich co najmniej sześć, to transakcja jest już pewna, ponieważ blockchain z dużym prawdopodobieństwem został rozgłoszony w całej sieci.
Zachęcam do utworzenia jeszcze jednego konta i wykonania kilku transakcji. Proces kopania przerywamy za pomocą miner.stop()
. Węzeł jest nadal aktywny, przyjmuje transakcje, ale nie są one zatwierdzane. Proces kopania można również uruchamiać łącznie z węzłem używając polecenia
1geth --datadir ./eth --networkid 100 --mine --minerthreads=1 --etherbase=0x0000000000000000000000000000000000000000
w którym wstawiamy adres konta przyjmującego ethery za wykopanie bloku.
Podsumowanie
Dotychczas w utworzonym rejestrze przechowywaliśmy zapisy związane ze stanem kont reprezentowanych przez klucze publiczne ich właścicieli. Można iść krok dalej - nie przechowywać w blockchain wyłącznie statycznych danych, ale umieścić tam aplikacje, które mogą zostać wykonane przy określonych warunkach. W ten sposób otrzymamy kontrakt, który zadziała tak, jak został zaimplementowany i będzie niezależnym bytem żyjącym w łańcuchu. Przykłady takich kontraktów w Ethereum przedstawię w kolejnym wpisie.