阅读建议
Honey Badger BFT应用了很多前人的研究,进行了巧妙的构造和优化,初次学习往往难以理解。在阅读时可以先大致了解各个构造块的基本作用,再了解总体的共识过程。之后回过头来深入研究各个构造块的原理,特别是BA算法,是整个协议的核心内容。
背景知识
FLP定理指出,在异步网络中,不可能存在一个确定性的共识协议。在FLP定理的指导下,共识协议设计往往需要作出妥协——要么弱化网络条件的限制,要么引入随机性。我们常见的PBFT、Raft等,就是在半同步网络下实现了一致性,但这类共识在网络条件变化时,其吞吐量会显著降低。而以往的异步共识协议同样效率低下,完全无法满足实际需求。本文介绍的Honey Badger BFT则是第一个具备可行性的异步共识协议。
基本构造块
阈值加密(threshold encryption)
传统的非对称加密算法包含一对密钥(公钥
p
k
pk
pk和私钥
s
k
sk
sk),其中
p
k
pk
pk用于消息加密,
s
k
sk
sk用于解密。
而门限加密则将私钥
s
k
sk
sk分为
n
n
n份(
s
k
i
,
i
∈
(
1
,
.
.
.
,
n
)
sk_i,i \in(1, ..., n)
ski,i∈(1,...,n)),对于
p
k
pk
pk加密的密文
C
C
C,每个子密钥
s
k
i
sk_i
ski都可以生成一个解密块
σ
i
\sigma_i
σi,至少
t
t
t个解密块合成后才能最终解密得到原始消息
m
m
m。阈值签名算法类似,只是把加密改成了签名。
可靠广播协议(RBC,Reliable broadcast)
由于在分布式系统中,并不存在这样的广播信道,使得节点只需要发送一次消息,就能使网络中的所有节点都接收到,消息任然需要系统成员之间通过点对点的通信来传播。
而 RBC协议则是用来保证消息传播的一致性,各节点遵循RBC协议进行通信,最终能达到广播消息的效果,即网络中的所有节点都能以相同的顺序接收到相同的消息。
Honey Badger论文中使用了基于纠删码的可靠广播协议(Reliable broadcast with erasure codes)。
如图所示,消息发送方
P
i
P_i
Pi发送消息
v
v
v的步骤如下:
- P i P_i Pi用纠删码将消息 v v v分为 N N N块,每块以 s j s_j sj表示;
- P i P_i Pi以 s j s_j sj为叶子节点构造Merkle树, h h h为Merkle根, b j b_j bj为 s j s_j sj的验证路径;
- P i P_i Pi将 s j s_j sj和对应的Merkle树验证信息分别发送给其他所有节点,表示为 VAL ( h , b j , s j ) \texttt{VAL}(h,b_j,s_j) VAL(h,bj,sj);
- 收到 VAL \texttt{VAL} VAL消息后,将其广播 ECHO ( h , b j , s j ) \texttt{ECHO}(h,b_j,s_j) ECHO(h,bj,sj);
- 收到 ECHO ( h , b j , s j ) \texttt{ECHO}(h,b_j,s_j) ECHO(h,bj,sj)消息,验证Merkle树对应的路径是否正确,若不正确则忽略;
- 收到 N − f N-f N−f个不同的 ECHO \texttt{ECHO} ECHO消息后,从中选择%N-2f%个,重新计算Merkle根 h ′ h' h′,判断是否满足 h ′ = h h'=h h′=h,若等式成立则广播 READY ( h ) \texttt{READY}(h) READY(h);
- 收到 f + 1 f+1 f+1个 READY ( h ) \texttt{READY}(h) READY(h)消息后,如果没有发送过 READY ( h ) \texttt{READY}(h) READY(h),那么广播 READY ( h ) \texttt{READY}(h) READY(h);
- 收到 2 f + 1 2f+1 2f+1个 READY ( h ) \texttt{READY}(h) READY(h)消息后,待接收到 N − 2 f N-2f N−2f个 ECHO ( h , b j , s j ) \texttt{ECHO}(h,b_j,s_j) ECHO(h,bj,sj)消息后,即可解码出消息 v v v。
说明:
- RBC协议的核心在于通过 Echo \texttt{Echo} Echo传递消息,通过 Reday \texttt{Reday} Reday表明消息已经发送完毕。
- 在基于纠删码的RBC协议中, s j s_j sj的 N N N个消息中有 2 f 2f 2f个是冗余消息,用于防止恶意和失效节点。该协议通过调动全网节点发送消息分块 s j s_j sj来代替单个leader直接向全网广播消息,避免了leader的带宽瓶颈。
- 论文中的算法表述容易造成误导,比如图中红框部分,实际含义是:在节点 P i P_i Pi构造出 VAL ( h , b j , s j ) \texttt{VAL}(h,b_j,s_j) VAL(h,bj,sj)后,将其作为RBC协议的输入,“upon receiving”是指RBC协议收到 VAL \texttt{VAL} VAL后,相当于 P i P_i Pi调用了RBC协议,而不是说将 VAL \texttt{VAL} VAL发送给了其他人,后续的算法也要注意这种表述。
-
- 该协议的复杂度为
O
(
N
∣
v
∣
+
λ
N
2
log
N
)
O\left(N|v|+\lambda N^{2} \log N\right)
O(N∣v∣+λN2logN),当消息本身足够大时(
∣
v
∣
≫
λ
N
2
log
N
|v| \gg{\lambda N^{2} \log N}
∣v∣≫λN2logN ),复杂度可以表示为
O
(
N
∣
v
∣
)
O(N|v|)
O(N∣v∣),等价于发送方向所有节点一对一直接发送消息的复杂度,因此说是渐进最优的。
- 该协议的复杂度为
O
(
N
∣
v
∣
+
λ
N
2
log
N
)
O\left(N|v|+\lambda N^{2} \log N\right)
O(N∣v∣+λN2logN),当消息本身足够大时(
∣
v
∣
≫
λ
N
2
log
N
|v| \gg{\lambda N^{2} \log N}
∣v∣≫λN2logN ),复杂度可以表示为
O
(
N
∣
v
∣
)
O(N|v|)
O(N∣v∣),等价于发送方向所有节点一对一直接发送消息的复杂度,因此说是渐进最优的。
二进制共识(BA,Binary Agreement)和公共硬币(Common Coin)
BA算法使全网节点对一个二进制比特的值达成共识,即全网共同生成0或1。BA算法满足以下属性:
- 一致性(Agreement):如果任何正确的节点输出比特 b b b,那么每个正确的节点都会输出 b b b。
- 中止性(Termination):如果所有正确的节点都接收到了输入,则每个正确的节点都能产生输出。
- 有效性(Validity):如果有任何正确的节点输出了 b b b,则至少有一个正确的节点接收 b b b作为输入。
BA算法流程如下:
- 接收输入
b
i
n
p
u
t
b_{input}
binput后,设置
est
0
:
=
b
i
n
p
u
t
\texttt{est}_0:=b_{input}
est0:=binput,并在后续每轮中进行如下操作(以第
r
r
r轮为例):
- 广播 BVAL r ( b ) \texttt{BVAL}_r(b) BVALr(b)
- 设置 bin_values r ( b ) : = { } \texttt{bin{\_}values}_r(b):=\{\} bin_valuesr(b):={}
- 从 f + 1 f+1 f+1个节点接收到 BVAL r ( b ) \texttt{BVAL}_r(b) BVALr(b)后,如果还没有发送过 BVAL r ( b ) \texttt{BVAL}_r(b) BVALr(b),那么广播 BVAL r ( b ) \texttt{BVAL}_r(b) BVALr(b)
- 从 2 f + 1 2f+1 2f+1个节点接收到 BVAL r ( b ) \texttt{BVAL}_r(b) BVALr(b)后,设置 bin_values r ( b ) = bin_values r ∪ { b } \texttt{bin{\_}values}_r(b)=\texttt{bin{\_}values}_r\cup\{b\} bin_valuesr(b)=bin_valuesr∪{b}
- 当
bin_values
r
(
b
)
≠
∅
\texttt{bin{\_}values}_r(b)\neq \emptyset
bin_valuesr(b)=∅时
- 广播 AUX r ( w ) \texttt{AUX}_r(w) AUXr(w),其中 w ∈ bin_values r w \in \texttt{bin{\_}values}_r w∈bin_valuesr
- 等至少接收到 N − f N-f N−f个 AUX r \texttt{AUX}_r AUXr消息后,此时这些消息中包含的 b b b值的集合 vals \texttt{vals} vals为 bin_values r \texttt{bin{\_}values}_r bin_valuesr的子集(因为在本步骤运行的时候,可能还会收到其他的 BVAL r \texttt{BVAL}_r BVALr加入 bin_values r \texttt{bin{\_}values}_r bin_valuesr)
- s ← Coin r ⋅ GetCoin ( ) s \leftarrow \texttt{Coin}_{r} \cdot \texttt{GetCoin}() s←Coinr⋅GetCoin()
- 如果
vals
=
{
b
}
\texttt{vals}=\{b\}
vals={b},则
- est r + 1 : = b \texttt{est}_{r+1}:=b estr+1:=b
- 如果 ( b = s % 2 ) (b=s\%2) (b=s%2)则输出 b b b
- 否则 est r + 1 : = s % 2 \texttt{est}_{r+1}:=s\%2 estr+1:=s%2
- 继续循环,直到在某一轮输出值
b
b
b,且对于
r
′
>
r
r'>r
r′>r,有
Coin
r
′
=
b
\texttt{Coin}_{r'}=b
Coinr′=b。
论文中使用了基于阈值签名的公共硬币方案实现BA算法(即上述的 GetCoin ( ) \texttt{GetCoin}() GetCoin())。
算法中 sid \texttt{sid} sid是一个唯一的随机数,可以看做是 coin \texttt{coin} coin的名字 - 可信设置环节:由一个可信的分发方运行 p k , { s k i } ← ThresholdSetup pk,\{sk_i\} \leftarrow \texttt{ThresholdSetup} pk,{ski}←ThresholdSetup来生成公共公钥和私钥碎片 { s k i } \{sk_i\} {ski}, s k i sk_i ski对应发放给 P i P_i Pi
- 当调用 GetCoincoin \texttt{GetCoincoin} GetCoincoin时,广播 ThresholdSign p k ( s k i , sid ) \texttt{ThresholdSign}_{pk}(sk_i,\texttt{sid}) ThresholdSignpk(ski,sid)
- 在接收到至少
f
+
1
f+1
f+1个签名碎片后,将其合成为完整签名:
sig
←
ThresholdCombine
p
k
(
s
k
i
,
sid
)
\texttt{sig} \leftarrow \texttt{ThresholdCombine}_{pk}(sk_i,\texttt{sid})
sig←ThresholdCombinepk(ski,sid),并用公钥验证
ThresholdVerify
p
k
(
sid
)
\texttt{ThresholdVerify}_{pk}(\texttt{sid})
ThresholdVerifypk(sid),若合法,则生成签名
说明: - 各方首先生成一个同样的一个二进制数 b b b
- 之后通过一个公共的硬币来判断是否要取这个 b b b作为最终的输出,如果不满足判断条件,那就进入下一轮,重复上述步骤
异步公共子集(ACS,Asynchronous Common Subset)
简而言之,网络中的各节点通过各自的输入,最终能经过ACS达成共识,生成一个一致的结果。ACS满足以下属性:
- Validity(有效性):如果一个正确的节点输出了集合 v \textbf{v} v,那么 ∣ v ∣ > N − f |\textbf{v}|>N-f ∣v∣>N−f,且 v \textbf{v} v至少包含 N − 2 f N-2f N−2f个正确节点的输入
- Agreement(一致性):如果一个正确的节点输出了集合 v \textbf{v} v,那么每个节点都会输出集合 v \textbf{v} v。
- Totality(全局性):如果 N − f N-f N−f个正确节点收到了输入,那么所有正确节点都会产生输出。
而ACS又是基于上述的RBC协议和BA算法来实现的。各个节点通过RBC协议广播自己对BA算法的输入,即所有节点并发地运行BA算法,最终形成一个长度为
N
N
N的二进制值列表,由这个二进制列表来决定最终提交哪些交易。
ACS流程如下:
- 以 { RBC i } N \{\texttt{RBC}_i\}_N {RBCi}N来表示RBC协议的 N N N个实例, P i P_i Pi对应为 { RBC i } \{\texttt{RBC}_i\} {RBCi}的发送方。 { BA i } N \{\texttt{BA}_i\}_N {BAi}N代表BA算法的 N N N个实例。
- 接收到输入 v i v_i vi后,将其输入到 RBC i \texttt{RBC}_i RBCi广播
- 在收到从 RBC j \texttt{RBC}_j RBCj发送的 v j v_j vj后,如果给 BA i \texttt{BA}_i BAi输入,那么对 BA i \texttt{BA}_i BAi输入1
- 如果已经接收到了至少 N − f N-f N−f个 BA \texttt{BA} BA实例传递的值 1 ,那么后续所有还未输入的 BA \texttt{BA} BA实例都输入为0
- 一旦所有的 BA \texttt{BA} BA实例都完成,令 C ⊂ [ 1 , . . . , N ] C \subset{[1,...,N]} C⊂[1,...,N]表示为每个生成 1 的BA 实例的索引。等待每个 RBC j \texttt{RBC}_j RBCj的输出 v j v_j vj,其中 j ∈ C j \in C j∈C。最终输出 U j ∈ C v j U_{j \in C^{v_j}} Uj∈Cvj。
论文对ACS的具体执行给出了图例解释:
从节点0的角度,他会收到来自节点
1
∼
3
(
N
=
4
,
f
=
1
)
1\sim3(N=4,f=1)
1∼3(N=4,f=1)的RBC广播,图中给出了三种不同的情况
- 正常情况下,节点0收到了节点1的广播 RBC 1 \texttt{RBC}_1 RBC1,则对 BA 1 \texttt{BA}_1 BA1输入1(1等价于yes)
- 节点0接收到 RBC 2 \texttt{RBC}_2 RBC2时已经收到了 N − f N-f N−f个 BA \texttt{BA} BA输出1,因此他对 BA 2 \texttt{BA}_2 BA2输入0,但由于其他节点已经收到了 RBC 2 \texttt{RBC}_2 RBC2并为 BA 2 \texttt{BA}_2 BA2输入1,因此最终 BA 2 \texttt{BA}_2 BA2仍然输出1
- RBC 3 \texttt{RBC}_3 RBC3还未完成, BA 3 \texttt{BA}_3 BA3就已经被输入0,且由于其他节点也没有为 BA 3 \texttt{BA}_3 BA3投票,因此最终 BA 3 \texttt{BA}_3 BA3输出0
说明
- 各方用RBC广播将自己的值 v i v_i vi,也会从RBC中接收其他节点的值 v j v_j vj,同时运行 N N N个 B A BA BA算法,如果接收到了 v j v_j vj,那对应的 BA j \texttt{BA}_j BAj输入1
- 如果在 N − f N-f N−f个 BA \texttt{BA} BA算法中都已经输入了1,那后续其他的 BA \texttt{BA} BA算法都输出0
- N N N个 B A BA BA算法完成后,节点选择那些输出为1的BA算法所对应的 v k v_k vk消息,由于 B A BA BA算法的一致性,网络中所有节点都会作出相同的选择,即最终所有节点都选择了相同的消息 v k v_k vk
共识流程
在了解了上述的构建块后,下面以节点 P i P_i Pi的角度,阐述Honey Badger BFT的共识流程。其中 B B B是系统设定参数, r r r表示在第 r r r轮。
- 节点 P i P_i Pi从自己的交易池的前 B B B个交易中随机选择 ⌊ B / N ⌋ \lfloor B / N\rfloor ⌊B/N⌋个作为提案( proposed \texttt{proposed} proposed)
- 将提案加密 x : = TPKE.Enc(PK,proposed) x:=\texttt{TPKE.Enc(PK,proposed)} x:=TPKE.Enc(PK,proposed)
- 将 x x x作为 ACS [ r ] \texttt{ACS}[r] ACS[r]的输入
- 从 ACS [ r ] \texttt{ACS}[r] ACS[r]中接收 { v j } j ∈ S \{v_j\}_{j \in S} {vj}j∈S,其中 S ⊂ [ 1 , . . . , N ] S \subset [1,...,N] S⊂[1,...,N],
- 对于所有
j
∈
S
j \in S
j∈S:
- 令 e j : = TPKE.DecShare ( S K i , v j ) e_j:=\texttt{TPKE.DecShare}(SK_i,v_j) ej:=TPKE.DecShare(SKi,vj)
- 广播 DEC ( r , j , i , e j ) \texttt{DEC}(r,j,i,e_j) DEC(r,j,i,ej)
- 从 DEC ( r , j , i , e ( j , k ) ) \texttt{DEC}(r,j,i,e_(j,k)) DEC(r,j,i,e(j,k))接收至少 f + 1 f+1 f+1个消息
- 解码 y j : = TPKE.Dec ( P K i , { k , e j , k } ) y_j:=\texttt{TPKE.Dec}(PK_i,\{k,e_{j,k}\}) yj:=TPKE.Dec(PKi,{k,ej,k})
- 令 block r : = sorted ( U j ∈ C v j ) \texttt{block}_r:=\texttt{sorted}(U_{j \in C^{v_j}}) blockr:=sorted(Uj∈Cvj), sorted \texttt{sorted} sorted即对收集的交易排序,组成为区块 block r \texttt{block}_r blockr
- 设置
buf
:
=
buf-block
r
\texttt{buf}:=\texttt{buf-block}_r
buf:=buf-blockr
说明: - 第一步中,随机选择交易是为了各个节点选择的交易尽可能不同(同一笔交易可能被发给了不同节点拿来构造区块)
- 将提案加密是为了防止审查攻击,以防止一些恶意节点在ACS中故意不给包含特定交易的节点投票
- 最终通过ACS生成一个共识列表(即在ACS中,BA输出为1的节点),各节点对成功共识的提案进行解密,生成解密碎片(share),收集到足够的碎片即可解密出交易内容
- 将交易构造成一个区块后即可上链,同时各节点将已经成功共识的交易从自己的交易池中删去。