How Zcash Transactions Between Shielded Addresses Work

Table of Contents

1. Zcash 简介

Zcash 是一种更加注重用户隐私的加密货币。Zcash 在两类地址:隐蔽地址(这类地址以“z”开头)和透明地址(这类地址以“t”开头)。在隐蔽地址之间的转账交易是完全隐私的:链上看不到发送者、接收者、转账金额。本文将介绍 Zcash 隐私交易的基本原理。

2. Zcash 隐私交易的基本原理

在 Zcash 中,一般使用术语 note 来表示 coin。这样,BTC 中的 UTXO 对应到 Zcash 中就是 unspend note。

为了表述简单,我们假设每个 note 恰好是 1 BTC,而且一个地址最多有一个 note。这样,在一个给定时间,Zcash 节点的数据库可由一个 unspend note 的列表来表示,如:

Note_1 = (PK_1), Note_2 = (PK_2), Note_3 = (PK_3)

假设 \(PK_1\) 是 Alice 的地址,她想把她的 1 BTC note 转给 Bob 的地址 \(PK_4\) 。她发送一个消息:“转移 1 BTC 从 \(PK_1\) 到 \(PK_4\) ”给所有节点,并对这个消息使用 \(PK_1\) 对应的私钥 \(sk_1\) 进行签名。节点收到消息后,首先验证签名,然后检查 \(PK_1\) 对应地址中确实存在 1 BTC note,最后更新节点数据库为:

Note_4 = (PK_4), Note_2 = (PK_2), Note_3 = (PK_3)

上面的过程目前没有隐私可言。假设每个 note 都包含一个随机数 r,后面将看到这会给我们带来隐私。引入随机数后,节点的数据库如下所示:

Note_1 = (PK_1, r_1), Note_2 = (PK_2, r_2), Note_3 = (PK_3, r_3)

为了实现隐私,自然地容易想到让节点不直接保存 note,而是保存某种方式加密(比如哈希)后的 note,也就是说节点的数据库为:

H_1 = HASH(Note_1), H_2 = HASH(Note_2), H_3 = HASH(Note_3)

为了实现隐私,我们不能直接删除已经花费的 note 的哈希,否则根据某个时间点被删除的哈希和新加入的哈希就可以找到一些对应关系出来。也就是说节点的数据库中将保存所有已经花费和没有花费的 note 的哈希。

但是这带来一个新问题: 如何区分已经花费和没有花费的 note 呢?办法是引入一个 Nullifier 集合(注:Zerocash 原始论文中的 Serial Number 就是这里的 Nullifier),它是已经花费的 note 关联的 r 值的哈希值。 这样,每个 Zcash 节点数据库中除了保存 note 的哈希集合外,还要保存另一集合:Nullifier 集合。比如,当 \(Note_2\) 被花费后,节点的数据库如表 1 所示。

Table 1: 当 Note_2 被花费后,HASH(r_2) 会出现在 Nullifier set 中
Hashed notes Nullifier set
\(H_1 = HASH(Note_1)\) \(nf_1 = HASH(r_2)\)
\(H_2 = HASH(Note_2)\)  
\(H_3 = HASH(Note_3)\)  

2.1. 发起隐私交易

假设 Zcash 的节点数据库如表 1 的示。Alice 拥有 \(Note_1\) ,她想转移 \(Note_1\) 给 Bob,Bob 的公钥为 PK_4。为了简单起见,我们假设 Alice 和 Bob 之间有个私密通道可以传送只能让 Alice 和 Bob 知道的秘密(注:Zcash 并没有这个要求)。

简单来说,Alice 需要做两件事:一是发布一个 Nullifier,即 \(HASH(r_1)\) 以表示她的 \(Note_1\) 已经花费了,二是发布一个新的由 Bob 控制的 \(Note_4\) 。

具体来说,Alice 转换 \(Note_1\) 给 Bob 的过程如下:

  1. Alice 随机选择一个随机数 \(r_4\) ,Alice 定义新 note 为 \(Note_4 = (PK_4, r_4)\) ;
  2. Alice 通过私密通道把 \(Note_4\) 发送给 Bob;
  3. Alice 发送 \(nf_2 = HASH(r_1)\) 给所有 Zcash 节点;
  4. Alice 发送 \(H_4 = HASH(Note_4)\) 给所有 Zcash 节点。

Zcash 节点收到 \(nf_2\) 后,首先要检查 \(nf_2\) 是否在 Nullifier set 中已经存在了(是否以前花费过),如果存在了则要拒绝;如果 \(nf_2\) 在 Nullifier set 中不存在,则加入 \(H_4\) 到 Hashed notes 中(注:直接这样做有问题,后面会介绍)。这时,Zcash 的数据库看起来如表 2 所示。

Table 2: Alice 转换 \(Note_1\) 给 Bob
Hashed notes Nullifier set
\(H_1 = HASH(Note_1)\) \(nf_1 = HASH(r_2)\)
\(H_2 = HASH(Note_2)\) \(nf_2 = HASH(r_1)\)
\(H_3 = HASH(Note_3)\)  
\(H_4 = HASH(Note_4)\)  

上面过程有点问题!因为当 \(nf_2 = HASH(r_1)\) 在 Nullifier set 不存在,只能说明 \(r_1\) 对应的 note 没有花费过;但并不能说明 \(r_1\) 对应的 note 确实属于 Alice。其实节点在收到 \(nf_2\) 时甚至都无法检查 \(nf_2\) 确实是某个值的哈希。当然, 解决这个问题最简单的办法是 Alice 直接公布 \(Note_1\) 即公布 \((PK_1,r_1)\) ,不过这样做就没有隐私可言了。

Zcash 引入 Zero-Knowledge proof 的目的就是解决这个问题。 除了上面步骤外,Alice 还需要公布一个 proof \(\pi\) ,这个 \(\pi\) 可说服所有 Zcash 的节点,Alice 知道 \(PK_1,sk_1,r_1\) 的值,且它们之间满足下面要求:

  1. \(Note_1=(PK_1,r_1)\) 存在于 Hashed notes 中;
  2. \(sk_1\) 是 \(PK_1\) 的私钥,从而确保 Alice 拥有 \(Note_1\) 的控制权;
  3. \(r_1\) 的哈希是 \(nf_2\) ,从而确保往 Nullifier set 中加入的数据 \(nf_2\) 确实和 \(Note_1\) 相关。

能证明上面事情,且还不会泄露 Alice 的 \(PK_1,sk_1,r_1\) 的信息,这是 Zero-Knowledge proof 的能力。

2.2. 一些省略的细节

为了简单起见,上面只介绍了 Zcash 隐私交易的最基本原理,它和真实的情况有出入:

  1. Hashed Notes 在 Zcash 中并不是一个 list,而是 Merkle tree,它是一种更有效的存储方式;此外其中保存的也并不是 Note 的 Hash,而是 Note 的 Commitment。
  2. Zcash 中 Nullifier 的定义更加复杂一些。
  3. Zcash 并不要求有私密通道。前面介绍的例子中要求 Alice 和 Bob 之间的存在私密通道,关于如何去掉对私密通道的依赖可以参考 Zcash 文档中关于 In-band secret distribution 的部分。

3. 参考

本文基本上是 How Transactions Between Shielded Addresses Work 的翻译(有少量删减),推荐读者阅读原文。

Author: cig01

Created: <2023-03-18 Sat>

Last updated: <2023-04-08 Sat>

Creator: Emacs 27.1 (Org mode 9.4)