Implementacja protokołu opartego na pracy Cheng Wang (EPFL)
Problem Bizantyjskiego Porozumienia (Byzantine Agreement) jest fundamentalnym problemem w informatyce rozproszonej, wprowadzonym przez Pease, Shostak i Lamport w latach 80. Problem można sformułować następująco:
Danych jest \(n\) procesów, z których \(t\) może być bizantyjskich (złośliwych). Każdy poprawny proces ma wartość wejściową (bit: 0 lub 1). Protokół ABA musi spełniać:
W modelu asynchronicznym komunikacja między procesami może trwać dowolnie długo (ale skończenie). Nie ma globalnego zegara ani synchronizacji. Cechy modelu:
Klasyczne wyniki teoretyczne:
Te wyniki prowadzą do dwóch kierunków badań: protokoły randomizowane oraz protokoły z optymalną odpornością (\(n > 3t\)).
Wyobraźmy sobie sieć blockchain z 10 węzłami walidującymi (\(n = 10\)), z których maksymalnie 3 mogą być kontrolowane przez przeciwnika (\(t = 3\)). Ponieważ \(n = 10 > 3t = 9\), spełniona jest optymalna odporność.
Sytuacja początkowa:
Działanie protokołu:
Wynik: Wszystkie 7 poprawnych węzłów akceptuje blok A, mimo że 3 węzły były bizantyjskie.
Historia protokołów bizantyjskiego porozumienia pokazuje ewolucję od rozwiązań wykładniczych do coraz bardziej efektywnych algorytmów. Kluczowe pytania to:
| Protokół | Rok | Odporność | Oczekiwany czas | Uwagi |
|---|---|---|---|---|
| Ben-Or | 1983 | \(n > 5t\) | \(O(2^n)\) | Pierwszy randomizowany protokół, słaba odporność |
| Bracha | 1984 | \(n > 3t\) | \(O(2^n)\) | Optymalna odporność, ale wykładnicza złożoność |
| Feldman-Micali | 1988 | \(n > 4t\) | \(O(1)\) | Stały czas! Ale wymaga \(n > 4t\) |
| Canetti-Rabin | 1993 | \(n > 3t\) | \(O(1)\) | Stały czas, ale tylko z przytłaczającym prawdopodobieństwem (nie prawie na pewno) |
| Abraham-Dolev-Halpern | 2008 | \(n > 3t\) | \(O(n^2)\) | Optymalna odporność + wielomianowy czas + prawie pewna terminacja |
| Wang (ten protokół) | 2013 | \(n > 3t\) | \(O(t)\) | Najlepszy wynik: optymalna odporność + liniowy czas! |
| Wang (ten protokół) | 2013 | \(n > (3+\varepsilon)t\) | \(O(1/\varepsilon)\) | Prawie stały czas dla \(n\) niewiele większego niż \(3t\) |
Dlaczego ten protokół jest ważny?
Porównajmy protokoły dla konkretnego przykładu: \(n = 100\), \(t = 33\) (maksymalna odporność).
| Protokół | Oczekiwana liczba rund | Przybliżona wartość |
|---|---|---|
| Bracha (1984) | \(O(2^{100})\) | \(\approx 10^{30}\) rund – niemożliwe! |
| Abraham et al. (2008) | \(O(100^2)\) | \(10,000\) rund |
| Ten protokół | \(O(33)\) | ~33 rundy |
Dla \(n = 100\), \(t = 24\) (mamy \(n = (3 + \varepsilon)t\) gdzie \(\varepsilon = 1/12 \approx 0.083\)):
| Protokół | Oczekiwana liczba rund |
|---|---|
| Abraham et al. (2008) | \(O(10,000)\) |
| Ten protokół | \(O(1/0.083) \approx 12\) rund |
Złożoność czasowa protokołu mierzy oczekiwaną liczbę rund komunikacji. Jedna runda to czas potrzebny, aby wiadomość przeszła od nadawcy do odbiorcy.
Definicja rundów (formalna):
Rozważmy wirtualny "zegar globalny" mierzący czas w sieci. Niech opóźnienie wiadomości oznacza czas od jej wysłania do odebrania. Okres wykonania protokołu to najdłuższe opóźnienie wiadomości w tym wykonaniu. Czas trwania wykonania to całkowity czas zmierzony przez zegar globalny podzielony przez okres.
Oczekiwany czas działania protokołu to maksimum (po wszystkich wejściach i adversariach) średniej czasu trwania wykonań protokołu.
| Parametr | Wartość | Oczekiwane rundy | Czas przy 100ms RTT |
|---|---|---|---|
| \(n=10, t=3\) | Mała sieć | \(\approx 3\) | 0.3 sekundy |
| \(n=100, t=33\) | Średnia sieć (opt. odporność) | \(\approx 33\) | 3.3 sekundy |
| \(n=100, t=24\) | Średnia sieć (\(n=4t\)) | \(\approx 12\) | 1.2 sekundy |
| \(n=1000, t=333\) | Duża sieć (opt. odporność) | \(\approx 333\) | 33 sekundy |
Porównanie z poprzednim best-case \(O(n^2)\): Dla \(n=100\), mielibyśmy 10,000 rund = 1000 sekund = 16.7 minuty!
Tradycyjne Verifiable Secret Sharing (VSS) gwarantuje, że wszyscy poprawni procesy odtworzą ten sam sekret. IVSS osłabia to wymaganie, ale dodaje potężną właściwość:
Jeśli poprawne procesy nie odtworzą tego samego sekretu, to protokół wykryje co najmniej \(t(n - 3t)\) błędnych par procesów.
Błędna para \(\{i, j\}\) oznacza, że co najmniej jeden z procesów \(i\) lub \(j\) jest bizantyjski.
Dlaczego to jest potężne?
Dealer dzieli sekret \(s\) wybierając losowy symetryczny dwuwymiarowy wielomian \(F(x, y)\) stopnia \(t\), gdzie:
\[F(x, y) = F(y, x) \quad \text{i} \quad F(0, 0) = s\]
Każdy proces \(i\) otrzymuje przekrój wielomianu: \(f_i(y) = F(i, y)\).
Kluczowa właściwość symetrii:
Dla poprawnego dealera: \(f_i(j) = F(i, j) = F(j, i) = f_j(i)\)
Jeśli proces \(i\) twierdzi, że \(f_i(j) = v_1\) a proces \(j\) twierdzi, że \(f_j(i) = v_2\) gdzie \(v_1 \neq v_2\), to co najmniej jeden z nich jest bizantyjski!
Każdy proces wysyła swój punkt do innych, więc możemy wykryć \(O(n^2)\) potencjalnych konfliktów. Ale bizantyjskich jest tylko \(t\), więc wykrywamy \(\Omega(t \cdot n)\) błędnych par.
Nowy pomysł: pamiętaj historię wszystkich instancji IVSS z przeszłych rund!
Efekt: Bizantyjskie procesy nie mogą "uciec" od wykrycia. Historia ich złego zachowania jest zapamiętywana i sprawdzana w przyszłych rundach.
Kluczowa obserwacja: Protokół kończy się, gdy wystarczająco wiele błędnych par zostanie wykrytych.
Krok 1: Ile jest błędnych par?
Jest \(t\) procesów bizantyjskich i \(n-t\) poprawnych. Maksymalna liczba błędnych par:
\[\text{Faulty pairs} \leq t \cdot (n - t) + \binom{t}{2} \leq t \cdot n\]
Krok 2: Ile par wykrywamy na rundę?
Jeśli IVSS nie powiedzie się (procesy odtworzą różne sekrety), wykrywamy co najmniej \(t(n - 3t)\) błędnych par (z własności IVSS).
Krok 3: Ile rund potrzeba?
W każdej rundzie uruchamiamy \(n\) instancji IVSS (jedna na proces jako dealer). W pesymistycznym przypadku, co rundę wykrywamy:
\[\text{Detected per round} \geq t(n - 3t)\]
Liczba rund potrzebnych do wykrycia wszystkich błędnych par:
\[\text{Rounds} \leq \frac{t \cdot n}{t(n - 3t)} = \frac{n}{n - 3t}\]
Dla \(n > 3t\), mamy \(n - 3t > 0\). W szczególności:
Wniosek: Protokół kończy się w \(O(t)\) oczekiwanych rundach dla \(n > 3t\).
Protokół składa się z kilku warstw, z IVSS (Inferable Verifiable Secret Sharing) jako kluczowym komponentem. IVSS dzieli się na fazę dzielenia (Sharing) i fazę rekonstrukcji (Reconstruction).
Pole skończone: Wszystkie operacje wykonywane są w \(\mathbb{Z}_p\) dla dużej liczby pierwszej \(p\).
TYPE FieldElement
// Elementy w Z_p dla dużej liczby pierwszej p
// Wszystkie operacje arytmetyczne są mod p
p : Prime // np. 2^256 - 189
Symetryczny dwuwymiarowy wielomian stopnia \(t\):
CLASS SymmetricBivariatePolynomial
degree : Integer = t
coefficients : Matrix[t+1][t+1]
// a_{ij} gdzie i+j ≤ t
// WARUNEK: a_{ij} = a_{ji} (symetria)
FUNCTION Evaluate(x, y) → FieldElement
RETURN Σ_{i,j: i+j≤t} a_{ij} · x^i · y^j
FUNCTION GetUnivariate(x_fixed) → UnivariatePolynomial
// Zwraca f(y) = F(x_fixed, y)
RETURN wielomian w y stopnia t
Jednowymiarowy wielomian (przekrój):
CLASS UnivariatePolynomial
degree : Integer = t
coefficients : Array[t+1] // [c_0, c_1, ..., c_t]
FUNCTION Evaluate(y) → FieldElement
RETURN Σ_{i=0}^{t} c_i · y^i
FUNCTION InterpolateFrom(points) → UnivariatePolynomial
// Interpolacja Lagrange'a z t+1 punktów
// Danych: {(x_1, y_1), ..., (x_{t+1}, y_{t+1})}
RETURN unikalny wielomian stopnia ≤ t
Cel: Dealer \(d\) dzieli sekret \(s\) między \(n\) procesów w taki sposób, że może być bezpiecznie zrekonstruowany lub błędy zostaną wykryte.
FUNCTION IVSS_Share(secret s, round r)
│
├─ // Krok 1: Wybierz symetryczny wielomian dwuwymiarowy
│ SELECT random symmetric bivariate polynomial F(x, y)
│ WHERE degree(F) = t AND F(0, 0) = s
│
├─ // Krok 2: Wyślij przekroje do procesów
│ FOR k = 1 TO n DO
│ f_k(y) ← F(k, y) // Jednowymiarowy wielomian
│ A-Cast f_k to process k // Niezawodny broadcast
│
├─ // Krok 3: Czekaj na potwierdzenia równości
│ WAIT FOR candidate set M WHERE:
│ 1. |M| ≥ n - t
│ 2. ∀i,j ∈ M: received A-Cast "EQUAL:(i,j)"
│ 3. ∀i,j,p,q ∈ M: received A-Cast "CHECKED_r: p, q, {i,j}" from p
│ // To sprawdza, że {i,j} nie jest błędną parą w historii
│
├─ // Krok 4: Ogłoś zbiór kandydatów
│ A-Cast "CANDIDATE_SET: M"
│
└─ COMPLETE sharing phase
// Stan lokalny
LOCAL STATE:
received_poly : Polynomial ← null
consistent_peers : Set[ProcessID] ← ∅
─────────────────────────────────────────────────────
UPON RECEIVE polynomial f_k FROM dealer
│
├─ received_poly ← f_k
│
└─ FOR j = 1 TO n DO
point ← f_k(j) // Ewaluuj wielomian w j
SEND point to process j // Bezpośrednia wiadomość
─────────────────────────────────────────────────────
UPON RECEIVE point p_j FROM process j
│
├─ IF received_poly(j) = p_j THEN // Test spójności
│ // f_k(j) powinno równać się f_j(k) (symetria!)
│ A-Cast "EQUAL:(k, j)"
│
└─ ELSE
// Niezgodność! Co najmniej jeden z {k, j} jest bizantyjski
// Nie wysyłamy EQUAL
─────────────────────────────────────────────────────
UPON RECEIVE "CANDIDATE_SET: M" FROM dealer
│
├─ // Weryfikacja zbioru kandydatów
│ VERIFY:
│ 1. |M| ≥ n - t
│ 2. ∀i,j ∈ M: received "EQUAL:(i,j)" via A-Cast
│ 3. ∀i,j,p,q ∈ M: received "CHECKED_r: p, q, {i,j}" from p
│ 4. ∀i,j ∈ M: {i,j} ∉ FP_k
│ // Sprawdź lokalną listę błędnych par
│
└─ IF verification passes THEN
OUTPUT "Sharing Complete"
STORE (M, received_poly) // Do użycia w rekonstrukcji
Kluczowe punkty IVSS-S:
Warunek wstępny: Faza dzielenia zakończona pomyślnie (mamy zapisane \(M\) i received_poly).
Cel: Odtworzyć sekret \(s = F(0,0)\) z wielomianu \(F(x,y)\) używając przekrojów od procesów w \(M\), lub wykryć błędne pary jeśli rekonstrukcja się nie powiedzie.
FUNCTION IVSS_Reconstruct(instance_id) → secret
│
├─ // FAZA 1: Broadcast wielomianów
│ IF I am in set M THEN
│ A-Cast my_polynomial f_k
│ // Wysyłam wielomian, który otrzymałem od dealera
│
├─ // FAZA 2: Zbierz i interpoluj
│ WAIT FOR polynomials from set IS WHERE:
│ • |IS| ≥ n - 2t
│ • IS ⊆ M
│ • All polynomials in IS are consistent:
│ ∀i,j ∈ IS: f_i(j) = f_j(i) // Test symetrii!
│
├─ // Jeśli test symetrii zawiedzie dla pary {i,j}:
│ IF ∃ i,j ∈ candidates : f_i(j) ≠ f_j(i) THEN
│ ADD {i, j} to FP_k // Wykryto błędną parę!
│ // Kontynuuj szukanie większego spójnego zbioru IS
│
├─ // Interpolacja dwuwymiarowego wielomianu
│ INTERPOLATE symmetric bivariate polynomial F
│ from {f_i | i ∈ IS}
│ // Używamy własności: F(i,y) = f_i(y) dla każdego i ∈ IS
│ // Ponieważ |IS| ≥ n-2t > t, możemy zrekonstruować F stopnia t
│
├─ reconstructed_secret ← F(0, 0)
│
├─ // FAZA 3: Zatwierdzenie w historii
│ A-Cast "READY_TO_COMPLETE"
│ ADD instance_id to CoreInvocations_k^r
│ // Ta instancja staje się częścią historii
│
└─ // FAZA 4: Finalizacja
WAIT FOR (n - t) "READY_TO_COMPLETE" messages
RETURN reconstructed_secret
FUNCTION ReconstructBivariate(slices : {f_1, f_2, ..., f_k}) → F(x,y)
│ // slices: zbiór k ≥ t+1 wielomianów, gdzie f_i(y) = F(i, y)
│
├─ // Krok 1: Weryfikacja spójności (test symetrii)
│ FOR EACH pair (i, j) in slices DO
│ ASSERT f_i(j) = f_j(i) // Warunek symetrii
│ IF NOT satisfied THEN
│ DETECT_FAULTY_PAIR({i, j})
│ RETURN ERROR
│
├─ // Krok 2: Interpolacja Lagrange'a
│ // Dla każdej pary (i,j), znamy wartość F(i,j) = f_i(j)
│ points ← {(i, j, f_i(j)) | i,j ∈ indices of slices}
│
├─ // Krok 3: Zbuduj F(x,y) stopnia t
│ F(x, y) ← BivariateLagrangeInterpolation(points, degree=t)
│ // Interpolujemy z więcej niż (t+1)² punktów
│ // Symetria gwarantuje unikalność
│
└─ RETURN F
Kluczowe punkty IVSS-R:
Protokół certyfikacji działa w tle przez cały czas działania ABA, śledząc historię invocations i wykrywając błędne pary.
// Stan globalny
GLOBAL STATE:
FP_k : Set[{ProcessID, ProcessID}] ← ∅
// Błędne pary: nieuporządkowane pary {i,j} gdzie ≥1 jest bizantyjski
CoreInvocations_k^r : List[InstanceID] ← []
// Historia pomyślnie zakończonych instancji IVSS w rundzie r
─────────────────────────────────────────────────────
FUNCTION InitCertification()
FP_k ← ∅
CoreInvocations_k^0 ← []
START background monitoring
─────────────────────────────────────────────────────
AT START OF ROUND r (r > 1)
│
└─ A-Cast CoreInvocations_k^{r-1}
// Informuj wszystkich o swoich akcjach z poprzedniej rundy
─────────────────────────────────────────────────────
UPON RECEIVE CoreInvocations_l^r FROM process l
│
├─ // Sprawdź wszystkie instancje z historii procesu l
│ FOR EACH instance I in CoreInvocations_l^r DO
│ │
│ ├─ // Pobierz zbiór M z instancji I
│ │ LET M = candidate_set(I)
│ │
│ └─ // Sprawdź spójność wielomianów dla wszystkich par w M
│ FOR EACH i, j ∈ M DO
│ IF received f_i and f_j in IVSS-R of I THEN
│ IF f_i(j) ≠ f_j(i) THEN
│ // WYKRYTO NIEZGODNOŚĆ!
│ ADD {i, j} to FP_k
│ LOG "Faulty pair detected: {i, j} in instance " + I
│
└─ // Zapewnij wykonanie kroków protokołu dla historii l
FOR EACH invocation I in CoreInvocations_l^r DO
COMPLETE sharing protocol of I
COMPLETE Step 1 of IVSS[r]-R of I
// Różne procesy mogą zakończyć różne instancje
─────────────────────────────────────────────────────
FUNCTION VerifySet(M : Set[ProcessID], round r) → Boolean
│ // Sprawdź, czy zbiór M może być zaakceptowany w rundzie r
│
├─ // Warunek a: Otrzymano historię od wszystkich w M
│ FOR EACH l ∈ M DO
│ IF NOT received CoreInvocations_l^{r'} for all r' < r THEN
│ RETURN false
│
├─ // Warunek b: Otrzymano wielomiany od wszystkich w historii
│ FOR EACH l ∈ M DO
│ FOR EACH instance I in ⋃_{r'DO
│ LET M_I = candidate_set(I)
│ FOR EACH i ∈ M_I DO
│ IF NOT received f_i in IVSS-R of I THEN
│ RETURN false
│
├─ // Warunek c: Żadna para w M nie jest w FP_k
│ FOR EACH i, j ∈ M WHERE i ≠ j DO
│ IF {i, j} ∈ FP_k THEN
│ RETURN false // Odrzuć: zawiera błędną parę
│
└─ RETURN true // Wszystkie pary zweryfikowane
─────────────────────────────────────────────────────
WHEN VerifySet(M, r) returns true FOR processes k, l and pair {i,j}
│
└─ A-Cast "CHECKED_r: k, l, {i, j}"
// Potwierdzam, że {i,j} nie jest błędną parą według historii l przed r
Kluczowe punkty Certyfikacji:
Protokół Vote określa, czy istnieje większość procesów głosujących na konkretną wartość. Zwraca wartość oraz siłę większości.
FUNCTION Vote(round r, input_bit x_k) → (value, confidence)
│
├─ // FAZA 1: Wymiana wartości wejściowych
│ A-Cast "INPUT: (k, x_k)"
│
│ WAIT FOR (n - t) INPUT messages
│ LET A_k = {senders of received INPUT messages}
│
│ my_vote_1 ← MAJORITY_BIT(A_k) // 0 jeśli remis
│
│ A-Cast "VOTE1: (k, A_k, my_vote_1)"
│
├─ // FAZA 2: Pierwsze głosowanie
│ WAIT FOR (n - t) VOTE1 messages
│ LET B_k = {j | received VOTE1 from j AND A_j ⊆ A_k}
│
│ my_vote_2 ← MAJORITY_BIT(votes in B_k)
│
│ A-Cast "REVOTE: (k, B_k, my_vote_2)"
│
├─ // FAZA 3: Zbieranie ponownych głosów
│ WAIT FOR (n - t) REVOTE messages
│ LET C_k = {j | received REVOTE from j AND B_j ⊆ B_k}
│
└─ // LOGIKA DECYZYJNA
IF all votes in B_k equal σ THEN
RETURN (σ, 2) // Silna większość
ELSE IF all revotes in C_k equal σ THEN
RETURN (σ, 1) // Słaba większość
ELSE
RETURN (⊥, 0) // Brak większości
─────────────────────────────────────────────────────
FUNCTION MAJORITY_BIT(set S) → {0, 1}
│
├─ count_0 ← |{j ∈ S | bit[j] = 0}|
├─ count_1 ← |{j ∈ S | bit[j] = 1}|
│
└─ RETURN (count_1 > count_0) ? 1 : 0 // Domyślnie 0 przy remisie
Poziomy pewności (confidence):
Generuje wspólny losowy bit dla wszystkich procesów, gdy nie osiągnięto większości w głosowaniu.
FUNCTION GetCommonCoin(round r) → {0, 1}
│
├─ // FAZA 1: Dzielenie sekretów
│ // Każdy proces jest dealerem dla n instancji IVSS
│ FOR j = 1 TO n DO
│ IVSS[r, j]-S.Start(RANDOM_VALUE())
│ // Proces j dzieli losową wartość
│
├─ // FAZA 2: Zbierz zestawy załączników
│ WAIT UNTIL (t + 1) IVSS-S instances complete for me
│ LET T_k = {dealers of completed secrets}
│
│ A-Cast "ATTACH: T_k"
│
├─ // FAZA 3: Akceptacja
│ A_k ← ∅
│
│ FOR EACH "ATTACH: T_j" received FROM j DO
│ IF T_j ⊆ T_k THEN
│ ADD j to A_k
│
│ WAIT UNTIL |A_k| ≥ n - t
│
│ A-Cast "ACCEPT: A_k"
│
├─ // FAZA 4: Włącz rekonstrukcję
│ WAIT FOR (n - t) "ACCEPT: A_j" messages
│ LET S_k = {senders}
│
│ A-Cast "RECONSTRUCT_ENABLED: (H_k = A_k, S_k)"
│
├─ // FAZA 5: Rekonstruuj sekrety
│ FOR EACH j ∈ A_k DO
│ FOR EACH k ∈ T_j DO
│ RUN IVSS[r, k]-R
│
└─ // FAZA 6: Oblicz monetę
WAIT UNTIL all reconstructions complete
u ← ⌈0.87 × n⌉ // Próg probabilistyczny
FOR EACH k ∈ H_k DO
value_k ← reconstructed_secret[k] mod u
IF EXISTS k ∈ H_k : value_k = 0 THEN
RETURN 0
ELSE
RETURN 1
Właściwości ICC:
To punkt wejścia protokołu, wywoływany przez aplikację. Łączy wszystkie komponenty w jeden spójny algorytm.
FUNCTION ABA(input_bit x_k) → {0, 1}
│
├─ // INICJALIZACJA
│ round ← 0
│ estimate ← x_k
│ START CertificationProtocol() in background
│ // Protokół certyfikacji działa równolegle przez cały czas
│
└─ // GŁÓWNA PĘTLA
LOOP forever
│
├─ round ← round + 1
│
├─ // FAZA 1: Głosowanie
│ (vote_val, vote_strength) ← Vote(round, estimate)
│
├─ // FAZA 2: Wspólna moneta
│ coin_val ← ICC(round)
│
├─ // FAZA 3: Logika decyzyjna (Canetti & Rabin)
│ IF vote_strength = 2 THEN // Silna większość
│ estimate ← vote_val
│ A-Cast "COMPLETE: estimate"
│ // Uwaga: Kontynuuj do następnej rundy, aby pomóc innym
│
│ ELSE IF vote_strength = 1 THEN // Słaba większość
│ estimate ← vote_val
│
│ ELSE // Brak większości
│ estimate ← coin_val
│
└─ // FAZA 4: Sprawdzenie terminacji
UPON receiving (t + 1) "COMPLETE: v" messages
OUTPUT v
TERMINATE
Kluczowe punkty ABA:
A-Cast to podstawowy prymityw używany przez wszystkie inne protokoły. Zapewnia niezawodne rozgłaszanie nawet gdy nadawca jest bizantyjski.
// Nadawca
FUNCTION Broadcast(value)
SEND MSG(value) to all processes
─────────────────────────────────────────────────────
// Odbiorcy (Proces k)
LOCAL STATE:
received_echo : Set[ProcessID] ← ∅ // Kto wysłał ECHO
received_ready : Set[ProcessID] ← ∅ // Kto wysłał READY
sent_echo : Boolean ← false
sent_ready : Boolean ← false
delivered : Boolean ← false
─────────────────────────────────────────────────────
UPON RECEIVE MSG(val) FROM sender
│
└─ IF NOT sent_echo THEN
SEND ECHO(val) to all processes
sent_echo ← true
─────────────────────────────────────────────────────
UPON RECEIVE ECHO(val) FROM process j
│
├─ ADD j to received_echo[val]
│
└─ IF |received_echo[val]| ≥ n - t AND NOT sent_ready THEN
// Próg: n - t (większość + t)
SEND READY(val) to all processes
sent_ready ← true
─────────────────────────────────────────────────────
UPON RECEIVE READY(val) FROM process j
│
├─ ADD j to received_ready[val]
│
├─ IF |received_ready[val]| ≥ t + 1 AND NOT sent_ready THEN
│ // Amplifikacja: t + 1
│ SEND READY(val) to all processes
│ sent_ready ← true
│
└─ IF |received_ready[val]| ≥ 2t + 1 AND NOT delivered THEN
// Dostarczenie: 2t + 1
delivered ← true
TRIGGER A-Cast-Complete(val)
Właściwości A-Cast:
W tej sekcji przedstawiamy formalne dowody kluczowych właściwości protokołu. Wszystkie twierdzenia oparte są na pracy Cheng Wang (EPFL).
Nieuporządkowana para \(\{i, j\}\) procesów nazywana jest błędną parą, jeśli co najmniej jeden z procesów \(i\) lub \(j\) jest bizantyjski.
Jeśli \(i, j, k\) są poprawnymi procesami, to nieuporządkowana para \(\{i, j\}\) nie zostanie nigdy dodana do \(\text{FP}_k\).
Para \(\{i, j\}\) jest dodawana do \(\text{FP}_k\) tylko wtedy, gdy istnieje instancja IVSS[r] taka, że \(i, j \in M\) oraz wielomiany \(\hat{f}_i\) i \(\hat{f}_j\) rozgłoszone przez \(i\) i \(j\) w Kroku 1 IVSS[r]-R spełniają:
\[\hat{f}_i(j) \neq \hat{f}_j(i)\]
Jednakże, jeśli \(i, j \in M\), to oba \(i\) i \(j\) musiały rozgłosić "EQUAL: \((i, j)\)" oraz "EQUAL: \((j, i)\)" w IVSS[r]-S, co oznacza, że sprawdziły warunek:
\[\hat{f}_i(j) = \hat{f}_j(i)\]
Ponieważ poprawne procesy nie zmieniają swoich wielomianów między fazą dzielenia a rekonstrukcją, warunek ten pozostaje prawdziwy. Zatem \(\{i, j\}\) nie zostanie dodana do \(\text{FP}_k\). \(\square\)
W rundzie \(r > 1\), jeśli \(i, j, k, l\) są poprawnymi procesami, to \(k\) ostatecznie rozgłosi "CHECKED\(_r\): \(k, l, \{i, j\}\)".
Z Lematu 1 wiemy, że \(\{i, j\} \notin \text{FP}_k\). Musimy sprawdzić warunki (a) i (b) Kroku 5 protokołu certyfikacji:
Warunek (a): Ponieważ \(l\) jest poprawny, rozgłosi \(\text{CoreInvocations}_l^{r'}\) na początku rundy \(r\) dla wszystkich \(r' < r\). Z właściwości poprawności A-Cast, \(k\) otrzyma te wiadomości.
Warunek (b): Załóżmy, że \(i\) jest w zbiorze \(M\) instancji IVSS[r']
w \(\bigcup_{r'
Ponieważ oba warunki są spełnione, \(k\) rozgłosi "CHECKED\(_r\): \(k, l, \{i, j\}\)".
\(\square\)
Niech \(N \subseteq \{1, \ldots, n\}\) i \(|N| > t + 1\). Niech \(\{f_i\}_{i \in N}\) będzie zbiorem wielomianów jednowymiarowych stopnia \(t\). Jeśli \(f_i(j) = f_j(i)\) dla wszystkich \(i, j \in N\), to istnieje unikalny symetryczny dwuwymiarowy wielomian \(F(x,y)\) stopnia \(t\) taki, że \(F(i, j) = f_i(j)\) dla wszystkich \(i, j \in N\).
Wybierzmy dowolny podzbiór \(N_0 \subseteq N\) taki, że \(|N_0| = t + 1\). Definiujemy:
\[F_0(x, y) = \sum_{i \in N_0, j \in N_0} \frac{\prod_{k \in N_0, k \neq i}(x - k)}{\prod_{k \in N_0, k \neq i}(i - k)} \cdot \frac{\prod_{k \in N_0, k \neq j}(y - k)}{\prod_{k \in N_0, k \neq j}(j - k)} \cdot f_i(j)\]
Z interpolacji Lagrange'a, \(F_0(i, j) = f_i(j)\) dla wszystkich \(i, j \in N_0\). Ponieważ \(f_i(j) = f_j(i)\), wielomian \(F_0\) jest symetryczny.
Dla dowolnego \(i \in N\), mamy \(f_i(j) = f_j(i) = F_0(j, i)\) dla wszystkich \(j \in N_0\). Z symetrii \(F_0\), \(f_i(j) = F_0(i, j)\) dla wszystkich \(j \in N_0\). Ponieważ \(|N_0| = t + 1\), wielomiany \(f_i(y)\) i \(F_0(i, y)\) są identyczne dla wszystkich \(y\).
W szczególności, \(f_i(j) = F_0(i, j)\) dla wszystkich \(j \in N\). Zatem \(F_0\) spełnia warunek. Unikalność wynika z interpolacji Lagrange'a. \(\square\)
Załóżmy \(n > 3t\). Para (IVSS[r]-S, IVSS[r]-R) spełnia wszystkie własności IVSS:
Terminacja (1): Załóżmy, że dealer jest poprawny i wszystkie poprawne procesy uczestniczą w IVSS[r]-S. Każdy poprawny proces otrzyma poprawne wiadomości od dealera. Dla każdej pary \((i, j)\) poprawnych procesów, \(i\) rozgłosi "EQUAL: \((i, j)\)". Z Lematu 2, dla poprawnych procesów \(i, j, k, l\), "CHECKED\(_r\): \(k, l, \{i, j\}\)" zostanie rozgłoszone przez \(k\). Zatem zbiór poprawnych procesów spełni warunki Kroku 4 IVSS[r]-S. Poprawny dealer rozgłosi zbiór \(M\). Wszystkie poprawne procesy otrzymają \(M\) i zakończą IVSS[r]-S.
Terminacja (2): Jeśli poprawny proces zakończy IVSS[r]-S, to wszystkie wymagane wiadomości w Kroku 5 IVSS[r]-S zostały wysłane przez A-Cast. Każdy poprawny proces uczestniczący otrzyma te wiadomości i zakończy IVSS[r]-S.
Terminacja (3): Jeśli jakiś poprawny proces zakończył IVSS[r]-S i wszystkie poprawne procesy zaczynają IVSS[r]-R, niech \(C\) będzie zbiorem poprawnych procesów w \(M\). Ponieważ \(|M| \geq n - t\), mamy \(|C| \geq n - 2t\). Niech \(\hat{f}_i\) będzie wielomianem otrzymanym przez \(i \in C\) od dealera. Ponieważ \(C \subseteq M\), mamy \(\hat{f}_i(j) = \hat{f}_j(i)\) dla wszystkich \(i, j \in C\). Z Lematu 3, istnieje symetryczny dwuwymiarowy wielomian \(\bar{F}\) stopnia \(t\) taki, że \(\bar{F}(i, j) = \hat{f}_i(j)\) dla wszystkich \(i, j \in C\). Zatem \(C\) spełnia warunki Kroku 2 IVSS[r]-R. Każdy poprawny proces zakończy Krok 2 i rozgłosi "READY_TO_COMPLETE", więc otrzyma \(n - t\) takich wiadomości i zakończy IVSS[r]-R.
Poprawność (1): Załóżmy, że poprawny proces zakończył protokół dzielenia. Z Lematu 3, istnieje \(\bar{F}\) takie, że \(\bar{F}(i, j) = \hat{f}_i(j)\) dla wszystkich \(i, j \in C\), gdzie \(C\) to zbiór poprawnych procesów w \(M\). Oznaczamy \(v = \bar{F}(0, 0)\).
Jeśli jakiś poprawny proces \(k\) zakończy IVSS[r]-R i zwróci wartość różną od \(v\), to jego zbiór interpolacji \(\text{IS}_k\) musi być różny od \(C\). Musi istnieć proces \(i \in \text{IS}_k\) i proces \(j \in C\) taki, że \(\bar{F}(i, j) \neq \hat{f}_i(j)\), w przeciwnym razie \(\text{IS}_k\) interpolowałby również \(\bar{F}\). Ponieważ \(\bar{F}(i, j) = \bar{F}(j, i) = \hat{f}_j(i)\), mamy \(\hat{f}_i(j) \neq \hat{f}_j(i)\), co oznacza, że błędna para \(\{i, j\}\) zostanie wykryta.
Poprawność (2): Jeśli dealer jest poprawny, wybrał symetryczny wielomian \(F\) taki, że \(F(0, 0) = s\). Poprawne procesy otrzymują \(f_i(y) = F(i, y)\). Z powyższego, \(\bar{F} = F\), więc \(v = \bar{F}(0, 0) = F(0, 0) = s\).
Sekretność: Bizantyjskie procesy (maksymalnie \(t\)) otrzymują maksymalnie \(t\) przekrojów \(f_i(y)\) symetrycznego wielomianu \(F(x, y)\) stopnia \(t\). Do interpolacji \(F\) potrzeba co najmniej \(t + 1\) punktów w każdym wymiarze. Zatem bizantyjskie procesy nie mogą odtworzyć \(F(0, 0) = s\). \(\square\)
Jeśli IVSS[r]-R nie powiedzie się (poprawne procesy odtworzą różne sekrety), to co najmniej \(t(n - 3t)\) błędnych par zostanie wykrytych.
Niech \(C\) będzie zbiorem poprawnych procesów w \(M\), gdzie \(|C| \geq n - 2t\). Niech \(k\) będzie poprawnym procesem, który odtwarza wartość różną od \(v = \bar{F}(0, 0)\), gdzie \(\bar{F}\) jest wielomianem interpolowanym z \(C\).
Proces \(k\) używa zbioru interpolacji \(\text{IS}_k\) takiego, że \(|\text{IS}_k| \geq n - 2t\). Niech \(B = \text{IS}_k \setminus C\) będzie zbiorem procesów w \(\text{IS}_k\), które nie są w \(C\). Każdy proces w \(B\) jest bizantyjski (ponieważ wszyscy poprawni procesy w \(M\) są w \(C\)).
Mamy \(|B| \leq t\) (maksymalna liczba bizantyjskich procesów). Dla każdego \(i \in B\) i każdego \(j \in C\), jeśli \(\hat{f}_i(j) \neq \hat{f}_j(i)\), para \(\{i, j\}\) jest błędną parą i zostanie wykryta.
Ponieważ \(k\) odtwarza wartość różną od \(v\), musi istnieć co najmniej jeden proces \(i \in B\) taki, że dla wielu \(j \in C\), \(\hat{f}_i(j) \neq \bar{F}(i, j) = \hat{f}_j(i)\). W pesymistycznym przypadku, każdy z \(t\) bizantyjskich procesów jest niezgodny z każdym z \(n - 3t\) poprawnych procesów w \(C\) (ponieważ \(|C| \geq n - 2t\) i co najwyżej \(t\) procesów w \(C\) może być "zagrożonych" przez bizantyjskie procesy).
Zatem liczba wykrytych błędnych par wynosi co najmniej:
\[t \cdot (n - 3t)\]
Dla \(n = 4t\), daje to \(t \cdot t = t^2\) wykrytych par – kwadratowo wiele! \(\square\)
⚠️ Częste nieporozumienie:
Na pierwszy rzut oka może się wydawać, że protokół musi znaleźć klikę w grafie (zbiór \(M\) gdzie wszystkie pary są sprawdzone), co jest problemem NP-zupełnym i wymaga złożoności wykładniczej \(O(2^n)\) lub silniowej \(O(n!)\).
To NIE JEST prawda! Oto dlaczego:
Problem kliki (NP-zupełny):
Nasz protokół (wielomianowy):
Krok 1: Dealer wysyła wielomiany
Dealer (poprawny lub bizantyjski) wysyła \(f_i\) do każdego procesu \(i\). Procesy wymieniają punkty i A-Cast wiadomości "EQUAL:(i,j)".
Krok 2: Dealer zbiera wiadomości "EQUAL" i "CHECKED"
Dealer czeka aż otrzyma wystarczająco wiele wiadomości. To jest pasywne czekanie, nie aktywne przeszukiwanie!
\[\text{Zbiór kandydatów: } C = \{i : \text{dealer otrzymał wiele wiadomości o } i\}\]
Krok 3: Weryfikacja kandydatów (O(|C|²) = O(n²))
FOR EACH i ∈ C DO
FOR EACH j ∈ C WHERE j > i DO
// Sprawdź czy otrzymano "EQUAL:(i,j)" i "CHECKED" dla {i,j}
IF NOT received_equal(i, j) THEN
REMOVE i OR j from C
BREAK
IF NOT all_checked(i, j) THEN
REMOVE i OR j from C
BREAK
M ← C // To jest nasz candidate set!
Złożoność tego kroku: \(O(n^2)\) - dwie pętle po maksymalnie \(n\) elementach.
Krok 4: Jeśli |M| ≥ n-t, akceptuj
Dealer NIE szuka największego \(M\). Wystarczy DOWOLNY zbiór \(M\) spełniający:
To jest warunek spełnialności, nie optymalizacji!
| Aspekt | Problem kliki (NP-hard) | Nasz protokół (Wielomianowy) |
|---|---|---|
| Graf wejściowy | Dowolny, adversarialny | Konstruowany przez uczciwe procesy - gwarantowana struktura |
| Cel | Znaleźć NAJWIĘKSZĄ klikę | Znaleźć DOWOLNĄ klikę rozmiaru ≥ n-t |
| Metoda | Przeszukiwanie wszystkich podzbiorów | Inkrementalne budowanie z weryfikacją |
| Gwarancja istnienia | Brak (może nie być kliki) | Zawsze istnieje (zbiór poprawnych procesów!) |
| Złożoność | \(O(2^n)\) lub \(O(n!)\) | \(O(n^2)\) - wielomianowa! |
Kluczowa obserwacja:
Jeśli dealer jest poprawny, to zbiór wszystkich poprawnych procesów automatycznie tworzy kandydata dla \(M\):
Dealer po prostu zbiera te wiadomości (operacja \(O(n^2)\)) i weryfikuje warunki (operacja \(O(n^2)\)). Nie przeszukuje wykładniczo wielu kombinacji!
Jeśli dealer jest bizantyjski, może:
W obu przypadkach nie ma wykładniczego przeszukiwania. Bizantyjski dealer po prostu wybiera dowolny zbiór (może losowo!) i sprawdza warunki - \(O(n^2)\).
| Operacja | Gdzie | Złożoność |
|---|---|---|
| Wysłanie wielomianów | IVSS-S, dealer | \(O(n)\) wiadomości |
| Wymiana punktów | IVSS-S, procesy | \(O(n^2)\) wymian |
| Weryfikacja par dla M | IVSS-S, dealer | \(O(n^2)\) sprawdzeń |
| A-Cast candidate set | IVSS-S, dealer | \(O(n^2)\) wiadomości (A-Cast) |
| Interpolacja wielomianu | IVSS-R | \(O(n^3)\) (Lagrange) |
| Całkowita na rundę | Jedna runda ABA | \(O(n^3)\) |
Wniosek: Protokół ma złożoność wielomianową \(O(n^3)\) na rundę komunikacji, a nie wykładniczą. Dla \(n = 100\) to jest ~1,000,000 operacji na rundę (wykonalne), nie \(2^{100}\) (niemożliwe).
Protokół ABA kończy się w \(O(t)\) oczekiwanych rundach dla \(n > 3t\).
Krok 1: Górne ograniczenie na liczbę błędnych par
Maksymalna liczba błędnych par w systemie to:
\[\text{Maksymalna liczba błędnych par} = t \cdot (n - t) + \binom{t}{2}\]
Pierwszy składnik to pary (bizantyjski, poprawny), drugi to pary (bizantyjski, bizantyjski). Upraszając:
\[t(n - t) + \frac{t(t-1)}{2} = t \cdot n - t^2 + \frac{t^2 - t}{2} = t \cdot n - \frac{t^2 + t}{2} \leq t \cdot n\]
Krok 2: Liczba wykrywanych par na rundę
W każdej rundzie uruchamiamy \(n\) instancji IVSS (jedna na proces jako dealer). Z Twierdzenia 2, jeśli instancja IVSS nie powiedzie się, wykrywamy co najmniej \(t(n - 3t)\) błędnych par.
W najgorszym przypadku (dla analizy), załóżmy że w każdej rundzie wykrywamy dokładnie \(t(n - 3t)\) nowych błędnych par (pesymistyczne założenie).
Krok 3: Liczba potrzebnych rund
Liczba rund potrzebnych do wykrycia wszystkich błędnych par:
\[\text{Liczba rund} \leq \frac{t \cdot n}{t(n - 3t)} = \frac{n}{n - 3t}\]
Krok 4: Analiza dla różnych wartości n
Przypadek 1: Dla \(n = 3t + 1\) (minimalna optymalna odporność):
\[\frac{3t + 1}{(3t + 1) - 3t} = \frac{3t + 1}{1} = 3t + 1 = O(t)\]
Przypadek 2: Dla \(n = 4t\):
\[\frac{4t}{4t - 3t} = \frac{4t}{t} = 4 = O(1)\]
Przypadek 3: Dla \(n = (3 + \varepsilon)t\) gdzie \(\varepsilon > 0\):
\[\frac{(3 + \varepsilon)t}{(3 + \varepsilon)t - 3t} = \frac{(3 + \varepsilon)t}{\varepsilon t} = \frac{3 + \varepsilon}{\varepsilon} = O\left(\frac{1}{\varepsilon}\right)\]
Krok 5: Analiza probabilistyczna konsensusu
Gdy wystarczająco wiele błędnych par zostanie wykrytych, procesy bizantyjskie tracą wpływ na protokół Vote. W każdej rundzie, z prawdopodobieństwem \(p > 0\) (zależnym od nieprzewidywalności ICC), poprawne procesy osiągają silną większość (confidence = 2) i kończą protokół.
Oczekiwana liczba rund do osiągnięcia konsensusu po wykryciu błędnych par wynosi \(O(1/p)\). Łączna oczekiwana liczba rund:
\[E[\text{rundy}] = \frac{n}{n - 3t} + O(1) = O\left(\frac{n}{n - 3t}\right)\]
Dla \(n > 3t\), mamy \(n - 3t > 0\), więc dla \(n = \Theta(t)\), oczekiwana liczba rund wynosi \(O(t)\). \(\square\)
Protokół ABA spełnia wszystkie wymagane właściwości:
Terminacja: Z Twierdzenia 3, protokół kończy się w \(O(t)\) oczekiwanych rundach. Ponieważ każda runda ma niezerowe prawdopodobieństwo zakończenia (dzięki ICC), prawdopodobieństwo braku zakończenia po \(k\) rundach zmniejsza się wykładniczo: \(P(\text{nie zakończono}) < (1-p)^k\). Dla \(k \to \infty\), to dąży do 0, więc z prawdopodobieństwem 1 protokół kończy się.
Zgodność: Proces kończy się tylko po otrzymaniu \(t+1\) wiadomości "COMPLETE: v" dla tej samej wartości \(v\). Ponieważ jest maksymalnie \(t\) bizantyjskich procesów, co najmniej jeden poprawny proces wysłał "COMPLETE: v". Poprawny proces wysyła "COMPLETE: v" tylko gdy osiągnął silną większość (confidence = 2) dla \(v\) w protokole Vote. Z własności Vote i A-Cast, wszystkie poprawne procesy, które kończą rundę, również zobaczą tę samą silną większość lub przyjmą \(v\) jako estimate. Przez indukcję, wszystkie poprawne procesy ostatecznie zgodzą się na \(v\).
Poprawność: Jeśli wszystkie poprawne procesy zaczynają z tym samym input_bit = \(v\), to w pierwszej rundzie protokołu Vote, wszystkie poprawne procesy w zbiorze \(A_k\) (który zawiera co najmniej \(n-t\) procesów, z których co najmniej \(n-2t > t\) jest poprawnych) będą głosować na \(v\). Z własności Vote, osiągną silną większość dla \(v\). Zatem wszyscy poprawni procesy ustawią estimate = \(v\) i wyślą "COMPLETE: v" w pierwszej rundzie. Po otrzymaniu \(t+1\) takich wiadomości, wszyscy zwrócą \(v\). \(\square\)
Konfiguracja: n=20, t=6, bizantyjskie: {3, 7, 11, 15, 17, 19}
Dealer wysyła wielomiany, procesy wymieniają punkty...
// Dealer (przypuśćmy proces 1, poprawny) otrzymuje wiadomości:
received_equal = {
(1,2), (1,4), (1,5), (1,6), (1,8), ..., // Para (1, poprawny proces)
(2,4), (2,5), (2,6), (2,8), ..., // Para (poprawny, poprawny)
...
// BRAK: (3,x) dla wielu x - proces 3 jest bizantyjski
// BRAK: (7,x) dla wielu x - proces 7 jest bizantyjski
}
// Algorytm dealera (WIELOMIANOWY!):
candidates = {1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 16, 18, 20} // Poprawne procesy
M = ∅
// Sprawdź każdy kandydat (O(n) iteracji):
FOR EACH i ∈ candidates DO
is_valid = true
// Sprawdź z już dodanymi do M (O(|M|) iteracji):
FOR EACH j ∈ M DO
IF NOT (received_equal.contains((i,j)) AND
all_checked(i,j)) THEN
is_valid = false
BREAK
IF is_valid THEN
M.add(i)
// Sprawdź próg
IF |M| ≥ n - t THEN // ≥ 14
BREAK // Sukces! Mamy M
// Wynik: M = {1,2,4,5,6,8,9,10,12,13,14,16,18,20}
// Rozmiar: 14 ≥ 14 = n-t ✓
// Złożoność: O(n²) - WIELOMIANOWA!
Analiza:
| Właściwość | Wynik | Porównanie z poprzednimi |
|---|---|---|
| Odporność | \(n > 3t\) | Optymalna (niemożliwe dla \(n \leq 3t\)) |
| Złożoność czasowa | \(O(t)\) rund | Ulepsza \(O(n^2)\) → \(O(t)\) |
| Dla \(n = (3+\varepsilon)t\) | \(O(1/\varepsilon)\) rund | Prawie stały czas dla małego \(\varepsilon\) |
| Detekcja błędów | \(t(n-3t)\) par/rundę | Liniowa vs \(O(1)\) w poprzednich |
| Terminacja | Prawie na pewno (prob. 1) | Lepsze niż "przytłaczające prawdopodobieństwo" |
| Złożoność komunikacyjna | \(O(n^2)\) per runda | Standardowa dla A-Cast |
Pełna implementacja protokołu w języku Go jest dostępna w repozytorium GitHub:
📦 github.com/poneciak57/async-agreement-protocolasync-agreement-protocol-3/
│
├── services/
│ ├── aba.go // Główna pętla ABA
│ ├── acast.go // A-Cast (Bracha's broadcast)
│ ├── certification.go // Protokół certyfikacji
│ ├── icc.go // Inferable Common Coin
│ ├── ivss.go // IVSS-S i IVSS-R
│ ├── vote.go // Protokół głosowania
│ └── network.go // Warstwa komunikacji
│
├── utils/
│ ├── polynomial.go // Operacje na wielomianach
│ └── logger.go // Logowanie i debugowanie
│
├── tests/
│ ├── ivss_test.go // Testy jednostkowe IVSS
│ ├── icc_test.go // Testy ICC
│ ├── vote_test.go // Testy Vote
│ └── acast_test.go // Testy A-Cast
│
├── pseudocode/ // Dokumentacja pseudokodu
├── docs/ // Dodatkowa dokumentacja
├── gen.go // Generator danych testowych
├── test.sh // Skrypt testowy (100 testów)
└── main.go // Punkt wejścia symulacji
# Klonowanie repozytorium
git clone https://github.com/poneciak57/async-agreement-protocol.git
cd async-agreement-protocol/async-agreement-protocol-3
# Kompilacja
go build -o aba-sim main.go
# Uruchomienie symulacji (czyta ze standardowego wejścia)
echo "10 3 1 0 1 1 0 1 0" | ./aba-sim
# Format wejścia:
# Linia 1: n t (liczba procesów, liczba bizantyjskich)
# Linia 2: input_1 input_2 ... input_{n-t} (bity wejściowe dla uczciwych procesów)
# Przykład z plikiem wejściowym:
cat > input.txt << EOF
10 3
1 0 1 1 0 1 0
EOF
./aba-sim < input.txt
# Tryb cichy (tylko wynik końcowy):
./aba-sim -silent < input.txt
Repozytorium zawiera skrypt test.sh uruchamiający 100 losowych testów:
# Uruchom wszystkie testy automatyczne
./test.sh
# Skrypt wykonuje:
# 1. Kompiluje generator (gen.go) i program główny (main.go)
# 2. Dla każdego testu (1-100):
# - Generuje losowe wejście (n, t, bity)
# - Uruchamia symulację w trybie -silent
# - Weryfikuje zgodność wyników wszystkich uczciwych procesów
# 3. Wyświetla podsumowanie (PASSED/FAILED)
# Przykładowe wyjście:
Starting tests...
Test 1 PASSED (n=10, t=3)
Test 2 PASSED (n=15, t=4)
...
Test 100 PASSED (n=20, t=6)
All 100 tests passed!
# Alternatywnie - testy jednostkowe Go:
go test -v ./tests/...
Implementacja symuluje pełny system rozproszony z następującymi cechami:
========== Symulacja ABA ==========
Parametry: n=10, t=3, bizantyjskie: [2, 5, 7]
--- Runda 1 ---
Vote: Silna większość dla 1 (confidence=2)
Procesy poprawne: wysyłają COMPLETE:1
Wykryte błędne pary: 12
--- Runda 2 ---
Procesy otrzymują (t+1)=4 wiadomości COMPLETE:1
Wszystkie poprawne procesy TERMINUJĄ z wartością: 1
========== Wyniki ==========
✓ Konsensus osiągnięty: 1
✓ Liczba rund: 2
✓ Wszystkie poprawne procesy zgodne: TAK
✓ Całkowita liczba wykrytych błędnych par: 12
✓ Czas symulacji: 1.23s
========== Symulacja ABA ==========
Parametry: n=10, t=3, wejścia: [1,0,1,0,1,0,1,1,0,1]
--- Runda 1 ---
Vote: Brak większości (confidence=0)
ICC: Generowanie wspólnej monety...
Wspólna moneta: 0
Estimate poprawnych procesów: 0
Wykryte błędne pary: 8
--- Runda 2 ---
Vote: Słaba większość dla 0 (confidence=1)
ICC: Generowanie wspólnej monety...
Wspólna moneta: 1
Estimate pozostaje: 0
Wykryte błędne pary: 15
--- Runda 3 ---
Vote: Silna większość dla 0 (confidence=2)
Procesy poprawne: wysyłają COMPLETE:0
Wykryte błędne pary: 18
--- Runda 4 ---
Procesy otrzymują (t+1)=4 wiadomości COMPLETE:0
Wszystkie poprawne procesy TERMINUJĄ z wartością: 0
========== Wyniki ==========
✓ Konsensus osiągnięty: 0
✓ Liczba rund: 4
✓ Wszystkie poprawne procesy zgodne: TAK
✓ Całkowita liczba wykrytych błędnych par: 18
✓ Czas symulacji: 2.87s
Projekt zawiera kompleksowe testy jednostkowe dla wszystkich komponentów:
# Wszystkie testy:
go test -v ./tests/...
# Testy IVSS (podstawowe, stresowe, bizantyjskie):
go test -v ./tests/*ivss*.go -timeout 60s
# Testy ICC (Inferable Common Coin):
go test -v ./tests/*icc*.go
# Testy Vote Protocol:
go test -v ./tests/*vote*.go
# Testy A-Cast (Bracha's broadcast):
go test -v ./tests/*acast*.go
# Przykładowe scenariusze testowe:
# - TestIVSS_NormalExecution: Standardowy przebieg IVSS
# - TestIVSS_Byzantine_Reconstruction_BadShare: Tolerancja bizantyjskich węzłów
# - TestIVSS_Stress_Concurrent: 20 równoległych instancji IVSS
# - TestICC_Basic: Podstawowa funkcjonalność wspólnej monety
# - TestVote_StrongMajority: Głosowanie z silną większością
go modivss.go (usunięcie wykładniczego szukania kliki),
protokół ma złożoność O(n³) na rundę i skaluje się do sieci z 100+ węzłami.
Zobacz szczegółowe komentarze w kodzie dla wyjaśnienia optymalizacji.