转自:http://blog.bifubao.com/2014/03/16/proof-of-reserves/
100%准备金证明
什么是准备金
说白了,准备金就是平台留存的钱。100%准备金率就是用户存100块,平台必须保留100块;10%就是存100块,平台可以只保留10块,另外的90块可以做别的事情,通常银行通过放贷等进行盈利,所以银行需要拼命吸储。
为何Off-Chain钱包需要证明
Off-Chain(链下)机制是用户的币在平台只做登记,币由平台完全控制。On-Chain(链上)机制是用户的币由自己通过私钥管理,平台无法动用。所以,只有Off-Chain才有可能低于100%准备金且需要证明,On-Chain总是保持100%准备金率。
证明机制
最简单的证明方法就是公布所有用户数据,平台的准备金率即:平台储蓄地址 / 用户总余额。该方式很直接,但易伪造。
欲保障逻辑完备性,需要证明:
- 没有伪造
- 伪造假用户
- 伪造用户余额
- 没有遗漏
- 直接遗漏用户:某个用户在公布的数据里找不到自己
- 间接遗漏用户:两个或两个以上用户对应的是同一条数据
先说伪造,伪造假用户的结果是:准备金率下降,打自己耳光;伪造用户余额的结果是:任一用户发现余额与公布的不一致即说明平台造假。还有一种就是仅伪造平台控制的用户数据(自己人的账户),若往多了吹,造成准备金率下降;若往少了说,没意义。但无法防止的情形是:伪造大量的零余额的用户,但这个不影响准备金率。
第二个点是遗漏,直接遗漏也没法弄,一旦某用户发现找不到自己则立即露馅;通常是间接遗漏,防止间接遗漏最直接的方法是公布用户Email地址,但会暴露用户隐私,通常需要设计一个Hash算法,例如:hash_value = HASH(user_id + nonce + balance)
,user_id这个字段必须每个用户唯一且固定不变,通常是选择email或者手机号码,因为天然具有唯一性且不可伪造。确定HASH算法后,用户的识别由Email地址改为hash_value。
证明方法
证明主要过程是构建Merkle Tree,当构建完该树,且根节点的余额与公布的储蓄地址余额相同,即可100%储备。证明算法参考了Proving Your Bitcoin Reserves,少许修改。
隐私问题
用户
必须在证明的同时可以保障用户财务隐私不被泄露。
- user_id的选取,上文已阐述,不再重复
- 每次构建时,用户的Nonce均为随机,即使用户两次余额不发生变化,
hash_value
依然是不一样的
上述两点可最大限度保护用户财务隐私,虽然别人可能看到你的节点数据,但他不知道你是谁。
平台
对平台来说,敏感的数据是:总储蓄额,总用户数。
总储蓄额必然公开,无需讨论。总用户数若不想那么公开,可以通过一些方法掩饰。通常Merkle Tree是平衡二叉树,根据用户树的高度可以推测用户数量(误差在2倍以内),那么就可以通过构建非常不平衡的二叉树(每次可以是任意形状)来掩饰平衡构建树的真实高度,且不破坏验证机制。例如,平衡二叉树的高度为10,则平台用户数范围是:512~1024之间(2^9=512, 2^10=1024),若构建一个高度为20的非平衡树(2^20=1048576)就可以成功掩饰实际用户数。
其他保护平台隐私方法:
- 降低公开频率,例如从每天公开一次降低为每周、每月公开一次
- 利用自有资金进出,干扰资金流向跟踪
币付宝的证明机制
我们理念是保护用户隐私的情况下,公开所有平台数据:总储蓄额、总用户数,用户可以下载所有节点数据。构建满平衡二叉树的过程是:构建用户节点 -> 迭代向上构建父节点 -> 至根节点,树构建完毕。若某一层节点数为奇数,则将最后一个节点复制,该节点称为填充节点(padding node)。
示例用户数据
User Email/ Mobile Phone | Nonce | Balance (Satoshi) |
---|---|---|
panzhibiao@bifubao.com | 139853 | 100047062 |
support@bifubao.com | 982361 | 88086042 |
13800138000 | 093823 | 3343103669 |
用户节点
用户节点hash值的算法:
1 2 3 4 5 | hexstr(
first8bytes(
sha256(str(user_id) + sprintf("%06d", nonce) + sprintf("%016lld", balance))
)
)
|
构建一个用户节点,由于字符串直接拼接,我们把user_id
与nonce
合成为下面函数中的uid
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | typedef struct Node_ { long long sum; unsigned char hash[8]; bool operator < (const struct Node_ &right) const { return memcmp(this->hash, right.hash, 8) < 0 ? true : false; } } Node; // make_user_node void make_user_node(const char *uid, long long balance, Node *node) { unsigned char hash[SHA256_DIGEST_LENGTH]; char buf[17] = {0}; node->sum = balance; sprintf(buf, "%016lld", balance); SHA256_CTX sha256; SHA256_Init(&sha256); SHA256_Update(&sha256, uid, strlen(uid)); SHA256_Update(&sha256, buf, 16); SHA256_Final(hash, &sha256); memcpy(node->hash, hash, 8); } |
用户节点都创建完毕后,按照hash
值进行排序。
父节点Hash值计算函数
将两两相邻的节点进行汇总,得到父节点,若当前层的节点数为奇数,则将最后一个节点复制,补充为偶数。
父节点的余额为左子节点与右子节点之和,父节点hash值算法:
1 2 3 4 5 | hexstr(
first8bytes(
sha256(_8bytes(left.sum + right.sum) + _8bytes(left.hash) + _8bytes(right.hash))
)
)
|
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 | // make_parent_node void make_parent_node(const Node *l, const Node *r, Node *p) { unsigned char hash[SHA256_DIGEST_LENGTH]; unsigned char buf[24]= {0}; p->sum = l->sum + r->sum; memcpy(buf, (unsigned char *)&(p->sum), 8); memcpy(buf+8, (unsigned char *)l->hash, 8); memcpy(buf+16, (unsigned char *)r->hash, 8); SHA256(buf, 24, hash); memcpy(p->hash, hash, 8); } |
Merkle Tree
构建Merkle Tree的过程,即向上递归两两合成父节点,至该层节点数为1时停止。
冷钱包地址
地址签名
1 2 3 4 5 6 7 8 | # plain text this address belongs to bifubao.com, 2014-03-04 # signature of 1PufBJk2c2HYq5wNap9yjmjSw6G3iD6mr5 HGcRqoJUq3iINmQ1jCA59KD6Iv0DzcaQxxtkIL9l/+wWo1bREPmh3h35IowYv0DU7lRT54O2wQtQ2rE7AVUxiVk= # signature of 1EQvpVvPVtZrwwrSoXY1mMrdVuCqaiVKEy G/AMpYGw6aW2gLHdHwkCh+PIHz6gwybXEostNCSmF8RBzEwAOYUFNBD5oI6XFkLRGvFrs58KRP/7Ok9GATZONW0= |
数据与源码
参考
- prove-how-(non)-fractional-your-Bitcoin-reserves-are scheme https://iwilcox.me.uk/2014/nofrac-orig
- Proving Your Bitcoin Reserves https://iwilcox.me.uk/2014/proving-bitcoin-reserves
- 图片来自:参考2中的网页