proof of solvency(偿付能力证明)方案

1. 引言

当前proof of solvency(偿付能力证明)的方案有:

  • Merkle Sum Tree
  • Crazy Merkle Tree
  • 采用zk-SNARKs多项式承诺

2. Merkle Sum Tree

Merkle sum tree形如:
在这里插入图片描述
在Merkle sum tree中,每个节点都为a (balance, hash) pair。最底层的叶子节点代表了各个单独客户的balance及其salted username hashes。在每个更高层节点中,其balance为底层2个节点的balance之和,hash为底层2个节点的哈希值。而Merkle sum proof与Merkle proof类似,为a “branch” of the tree,包含了由该叶子节点到根节点的所有姐妹节点。

交易所将给每个用户发送其balance的Merkle sum proof,然后用户就相当于得到了 其balance被正确包含在total中的 保证。
一个简单的代码示例见:https://github.com/ethereum/research/blob/master/proof_of_solvency/merkle_sum_tree.py

# The function for computing a parent node given two child nodes
def combine_tree_nodes(L, R):
    L_hash, L_balance = L
    R_hash, R_balance = R
    assert L_balance >= 0 and R_balance >= 0
    new_node_hash = hash(
        L_hash + L_balance.to_bytes(32, 'big') +
        R_hash + R_balance.to_bytes(32, 'big')
    )
    return (new_node_hash, L_balance + R_balance)

# Builds a full Merkle tree. Stored in flattened form where
# node i is the parent of nodes 2i and 2i+1
def build_merkle_sum_tree(user_table: "List[(username, salt, balance)]"):
    tree_size = get_next_power_of_2(len(user_table))
    tree = (
        [None] * tree_size +
        [userdata_to_leaf(*user) for user in user_table] +
        [EMPTY_LEAF for _ in range(tree_size - len(user_table))]
    )
    for i in range(tree_size - 1, 0, -1):
        tree[i] = combine_tree_nodes(tree[i*2], tree[i*2+1])
    return tree

# Root of a tree is stored at index 1 in the flattened form
def get_root(tree):
    return tree[1]

# Gets a proof for a node at a particular index
def get_proof(tree, index):
    branch_length = log2(len(tree)) - 1
    # ^ = bitwise xor, x ^ 1 = sister node of x
    index_in_tree = index + len(tree) // 2
    return [tree[(index_in_tree // 2**i) ^ 1] for i in range(branch_length)]

# Verifies a proof (duh)
def verify_proof(username, salt, balance, index, user_table_size, root, proof):
    leaf = userdata_to_leaf(username, salt, balance)
    branch_length = log2(get_next_power_of_2(user_table_size)) - 1
    for i in range(branch_length):
        if index & (2**i):
            leaf = combine_tree_nodes(proof[i], leaf)
        else:
            leaf = combine_tree_nodes(leaf, proof[i])
    return leaf == root

这种设计方案要比fully public list的隐私泄露要少,同时,可在每次发布root时对branches进行shuffling 来进一步降低隐私泄露,但一定程度的隐私泄露仍然存在,如:

  • Chalie知道某人有164 ETH,某2人的balance之和为70ETH等。

控制多个账号的攻击者,可能可获得交易所相当数量客户的资金量。
上述方案的一个重要的微妙之处在于:

  • 可能存在负数balance的情况:
    如交易所的客户balance为1390 ETH但实际账上仅有890 ETH,若在树中插入一个balance为-500 ETH的fake账号?
    不过这种概率并不会破坏整个方案,原因在于此处设计的是Merkle sum tree,而不是常规的Merkle tree。假设Henry为交易所控制的fake账户,然后交易所试图在树中插入-500 ETH:
    在这里插入图片描述
    Greta的证明验证将失败:因交易所在其证明中包含了Henry的-500 ETH节点,该节点无效Greta可拒绝。而Fred和Eve的证明验证也将失败,因其证明中包含了Henry之上的-230 ETH中间节点,因此也是无效的。为此,为作弊成功,交易所需寄希望于整棵树的右半段没有任何一个人取检查其balance proof。
    若交易所可识别出具有500ETH的用户不去校验其balance proof,或者,不会抱怨其从未收到过相应的balance proof,则交易所可作弊成功。但是,交易所也可直接将这些用户剔除出树,也具有相同的效果。
    因此,若仅是要实现“proof of liabilities”(债务证明),以上Merkle sum tree方案就足够了。

3. Crazy Merkle Tree

不同于上面的Merkle sum tree,crazy merkle tree为常规的Merkle tree,在crazy merkle tree中,为every unit of currency(如satoshi、wei等)存在独立也怕。

crazy merkle tree的效率很高,构建proof的用时为:

  • N ∗ log ⁡ ( B ) N*\log(B) Nlog(B)

其中 N N N为用户数, B B B为total balance。无需 O ( B ) O(B) O(B)计算。

假设树中有10亿个节点[… A A B B … B C C …],且由A到B、由B到C均发生在奇数index位置。则构建次基础之上的5亿个节点仅需要3类哈希计算:

  • H(A,A)、H(B,B)、H(C,C)
  • H(A,B)
  • H(B,C)

crazy merkle tree需要的哈希更多,但相对于merkle sum tree,其具有更强的隐私性,在于:

  • crazy merkle tree不会暴露姐妹节点的balance
  • crazy merkle tree可更清晰的证明为何“negative balance attacks”不是问题

在2018-era Plasma Cash protocol iteration中使用了类似的思想。

下面代码中仅计算了Merkle root,并Computing the left-side and right-side branches to give to the user as proof of inclusion is left as an exercise to the reader:

import hashlib
import copy

def hash(x):
    return hashlib.sha256(x).digest()

def is_power_of_2(x):
    return x & (x-1) == 0

def crazy_merkle(values):
    assert is_power_of_2(sum(x[1] for x in values))
    # Base case: single value, width 1
    if len(values) == 1 and values[0][1] == 1:
        return values[0][0]
    # Recursive case
    next_layer = []
    subtract_from_next = 0
    for i in range(len(values)):
        count = values[i][1] - subtract_from_next
        if count >= 2:
            next_layer.append((hash(values[i][0] * 2), count // 2))
        if count % 2 == 1:
            next_layer.append((hash(values[i][0] + values[i+1][0]), 1))
            subtract_from_next = 1
        else:
            subtract_from_next = 0
    return crazy_merkle(next_layer)

def flatten(values):
    o = []
    for value, repeats in values:
        o.extend([value] * repeats)
    return o

def basic_merkle(items):
    assert is_power_of_2(len(items))
    o = [None] * len(items) + items
    for i in range(len(items) - 1, 0, -1):
        o[i] = hash(o[i*2] + o[i*2+1])
    return o[1]

def test():
    values = [(i.to_bytes(32, 'big'), i**2) for i in range(1, 100)]
    values.append((b'doge'*8, 2**19 - sum(i**2 for i in range(1, 100))))
    x1 = crazy_merkle(values)
    x2 = basic_merkle(flatten(values))
    assert x1 == x2
    print('root match')

if __name__ == '__main__':
    test()

4. 采用zk-SNARKs多项式承诺

zk-SNARKs之于密码学 类似于 transformers之于AI:为强大的通用技术,将为几十年前的一系列问题提供一整套特定于应用程序的技术。
借助zk-SNARKs,可大幅简化和改进“proof of liabilities”(债务证明)中的隐私问题。

最简单的方式是:

  • 1)将所有的用户存款放入一棵Merkle树中(或,更简单,采用KZG多项式承诺
  • 2)使用zk-SNARK来证明该树中的所有balance为非负数的,且总额为某声称的值。
  • 3)若添加一层hashing for privacy,则给每个用户的Merkle branch(或KZG证明)将不会泄露任何其它用户的balance信息。

在这里插入图片描述
【其中的Q多项式成立在于:“针对multiple points时,可将evaluations of a polynomial f f f on a set S ⊂ F S\subset \mathbb{F} SF 看成是 given as a polynomial r ∈ F < ∣ S ∣ [ X ] r\in\mathbb{F}_{<|S|}[X] rF<S[X] with r ( z ) = f ( z ) r(z)=f(z) r(z)=f(z) for each z ∈ S z\in S zS。此时: r ( z ) = f ( z ) r(z)=f(z) r(z)=f(z) for each z ∈ S z\in S zS,等价为, f ( X ) − r ( X ) f(X)-r(X) f(X)r(X) 可被 Z S ( X ) Z_S(X) ZS(X)整除,其中 Z S ( X ) = ∏ z ∈ S ( X − z ) Z_S(X)=\prod_{z\in S}(X-z) ZS(X)=zS(Xz)”,详细参看Efficient polynomial commitment schemes for multiple points and polynomials学习笔记的“2.3 Polynomial commitment scheme”。】
使用KZG承诺是避免隐私泄露的一种方式,因其不再需要在证明中提供“姐妹节点”;且一种简单的zk-SNARK可用于证明balance之和以及每个blance为非负数。

为证明balance总和以及每个balance为非负数,可在KZG的基础上,采用一种特殊用途的zk-SNARK,如:

  • 引入辅助多项式 I ( x ) I(x) I(x),可“builds up the bits” of each balance。如,假设balance值小于 2 15 2^{15} 215,且每16个位置tracks a running total with an offset so that it sums to zero only if the actual total matches the declared total。若 z z z为某order-128 root of unity,设计的balance I ( X ) I(X) I(X)多项式中:
    • 1)以16bit来表示balance总和以及每个balance为非负数。
    • 2)前15个bit为balance的二进制形式表示的约束:第1个值为balance二进制形式的最高1位;第2个值为balance二进制形式的最高2位;第3个值为balance二进制形式的最高3位;以此类推,第15个值即为balance值。以等式表示为:
      若  i m o d    16 ∉ { 0 , 15 } ,有  I ( z i ) − 2 ∗ I ( z i − 1 ) ∈ { 0 , 1 } 若\ i\mod 16 \notin \{0,15\},有\ I(z^i)-2*I(z^{i-1})\in\{0,1\}  imod16/{0,15},有 I(zi)2I(zi1){0,1}
      P ( x ) P(x) P(x)关联匹配的约束关系为: I ( z 16 x + 14 ) = P ( w 2 x + 1 ) I(z^{16x+14})=P(w^{2x+1}) I(z16x+14)=P(w2x+1)
      balance为非负数,即相应二进制最高位应为0,对应约束表示为: I ( z 16 x ) = 0 I(z^{16x})=0 I(z16x)=0
    • 3)balance总和约束,通过最后一个bit来体现,令用户balance均值 = 声称balance总和/用户数,有 前m个用户的balance - m * 用户balance均值m = 总用户数时,前m个用户的balance - m * 用户balance均值结果为0,具体约束表示为: I ( z 16 x + 15 ) = I ( z 16 x − 1 ) + I ( z 16 x + 14 ) − the declared total user count I(z^{16x+15})=I(z^{16x-1})+I(z^{16x+14})-\frac{\text{the declared total}}{\text{user count}} I(z16x+15)=I(z16x1)+I(z16x+14)user countthe declared total,其中 the declared total \text{the declared total} the declared total为该Merkle树中所有用户balance总和(如本例中为1480),而 user count \text{user count} user count为该Merkle树中的总用户数(如本例中为8)。【此处, I ( z 127 ) = I ( z − 1 ) = 0 I(z^{127})=I(z^{-1})=0 I(z127)=I(z1)=0。】

本例中, I ( x ) I(x) I(x)的有效值设置可为:0 0 0 0 0 0 0 0 0 0 1 2 5 10 20 -165 0 0 0 0 0 0 0 0 0 1 3 6 12 25 50 -300……

可将以上方程式转换为多项式表示,然后使用zk-SNARKs来证明。这并不是最优协议,但可充分说明借助这样的密码学证明可解决实际问题。

参考资料

[1] Having a safe CEX: proof of solvency and beyond

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值