零知识证明是目前区块链研究最为火热的领域之一,有望在扩容和隐私保护两个方向为区块链带来巨大的突破。
众所周知,在区块链上存储的数据是全部公开的,任何人都可以获取、解析所有的区块数据。这极大地限制了区块链的应用场景。例如,在比特币等采用UTXO模型设计的区块链系统中,每一笔UTXO的流动都是公开的,匿名性的保证是通过隐藏人和地址的对应关系来实现,一旦某一个地址和人的对应关系暴露,再配合一些链上数据分析工具,基本上可以追踪到这个人的全部交易记录和资金数量。在以太坊等智能合约区块链中,每一个合约的代码、每一次合约调用的参数也都是完全公开的,所有数据对所有人可见。
而零知识证明恰好可以用来解决这个问题。关于零知识证明的原理在本文中不再赘述。通过一些巧妙的方案设计,我们可以使用零知识证明来保证链上没有任何公开的数据,同时不影响区块链的正常功能。比如ZCash项目,就通过零知识证明设计了交易记录混合方案,实现了和比特币一样的功能,但是链上不存储任何一笔交易的具体信息(发送人、接收人、金额等)。
区块链的应用远远不止Token转账这一种。人类社会正在经历信息革命带来的巨大冲击,从企业的视角来看,大部分企业正在经历从制造型企业到创新型企业的转型。企业的核心生产资料,正在从资金、厂房、原料等转变为人、知识(信息)、知识形成的网络,专业的说法叫“社会资本”。在这样的大背景下,知识类资料、成果等数字资产的保护和有效流通对企业来说就变得尤其重要。
区块链在帮助数字资产流通上有着天生的优势,但是却在保护数字资产的隐私性上有很大的劣势。以太坊上的ERC-721、ERC-1155提案实现了对非同质化Token(NFT)的标准化,使得在区块链上进行数字资产的发行、交易变得非常容易。但是资产的所有权、转移记录,对外都是完全公开的,这无疑暴露了企业的很多敏感信息。
安永(EY)之前发布的Nightfall项目,实现了对以太坊上ERC-721类资产私密转账的支持。但是ERC-721的使用使得资产的注册是必须公开的,在资产注册以后转入一个私密合约,之后的转移操作才是对外不可见的。另外,对于文章、图片等类型的数字资产,最重要的流通方式不是所有权的转移,而是使用权的购买。比如文章的转载权购买、图片的购买使用等。对于这类数字资产,使用NFT模型抽象是不够的,也就更加无法实现使用权的私密购买。
针对这样的场景,我们设计了数字资产的私密发布和私密授权协议。基于零知识证明实现了如下的功能:
1. 可以在链上发布数字资产,但是隐藏数字资产的所有者。
用发布图片来举例子。比如,我可以在链上注册一张图片,链上没有这张图片的原始内容,只有图片的哈希值和ID。从链上也看不出这张图片是我发的。
下一步,假如我还需要证明我对图片的所有权,我在某个媒体上发布这张图片时可以附带一段“文本”,将这段文本拿到区块链上查询,可以证明我是这张图片的作者。但是单独通过链上数据,任何人都无法查到我还有哪些其他图片。其他人即使有了这段文本,也无法伪造所有权证明,也无法对图片进行售卖。
2. 可以在链上进行数字资产的使用授权,但是隐藏授权的购买者、所有者以及数字资产信息。
比如,我在媒体上看到一张图片,可以付费购买使用权,但是没有人知道我从谁手里买了什么。在我的博客里使用购买的图片时我也可以附带一段“文本”,通过这段文本可以在链上确认我的付费购买的有效性,但是无法查到我的其他任何购买记录,也无法查到我是从谁手里购买了这张图片。
通过上述的功能,我们实现了图片发布、购买的全流程隐私保护。同时也支持在必要时公开一些信息,用以证明所有权,并且不暴露任何其他的额外隐私信息。
需要注意的是,虽然我们用了图片来举例子,但是这个协议实现的功能可以用在任何类型的数字资产的注册和授权上。
在本文的后半部分,我们将对实现上述功能的零知识证明协议进行详细描述。
在我们现有的代码实现中,使用了ZoKrates工具包,具体的零知识证明算法使用了Groth 16。由于零知识证明的通用性,具体使用的算法几乎不影响上层的协议设计。因此实现协议时也可以使用Bellman工具包,或者将算法替换为Bulletproofs或者是Sonic算法,以实现更好的性能,以及去除对可信初始化的依赖。
我们在协议中用到的变量如下:
符号 | 描述 |
---|---|
组织A, B, C | |
组织A的注册名 | |
sha256哈希函数 | |
组织A的区块链公钥和地址 | |
组织A的区块链私钥 | |
组织A的资产公钥和私钥,其中$pk_A^R=h(sk_A^R)$ | |
代表一个非同质数字资产的唯一id | |
组织A对资产$\alpha$的注册记录 | |
将资产授权给B的一条记录 | |
零知识证明的证据数据 | |
Merkle Tree上从叶子节点L到根节点的路径上的所有节点值 | |
Merkle Tree上从叶子节点L到根节点的路径上的所有节点的兄弟节点值 | |
Merkle Tree的根节点计算函数,输入为${\Psi}_L$和L | |
用于记录资产注册记录的Merkle Tree | |
用于记录资产授权记录的Merkle Tree |
每个组织拥有一对代表组织资产所有权身份的公-私钥对,长度均为32字节,分别记为$pk^R,sk^R$,满足关系: $$ pk^R =h(sk^R) $$ 组织还拥有一对代表其区块链身份的公-私钥对和对应的区块链地址,分别记为$pk^E,sk^E,addr$,是用标准的椭圆曲线签名算法生成的。组织需要谨慎保管两组公-私钥对中的私钥,不可泄露。
组织还拥有一个组织名n,组织名会在验证授权信息时提供给查询方,形式上应该是代表组织身份的自然语义信息。
组织需要根据协议完成组织注册流程,才能实现对数字资产的匿名授权。组织注册实际上是在区块链上记录了组织的一些重要信息,将组织的$pk^R,addr,n$三种信息做了一个绑定。这些信息有的不可更改,例如组织的区块链地址$addr$和代表组织的资产所有权身份的$pk^R$;还有的信息需要权限管理,例如组织的注册名信息n。
我们需要保证以下几点:
- 只有拥有$sk^R$的组织才能为对应的$pk^R$进行组织注册操作;
- 一旦组织完成注册,只有拥有$pk^E$对应的私钥$sk^E$的地址才能修改组织的注册名。
注意,对于上述两点我们都有一个隐含的需求:组织的私钥不能公开。
第1点,我们用零知识证明来实现;第2点则依赖于椭圆曲线签名算法机制。
下面以组织A为例,具体介绍组织注册的流程:
- 在安全环境下生成需要的两组公-私钥对和其他组织信息数据;
- 生成零知识证明$\pi$,包含约束:
$pk_A^R==h(sk_A^R)$
- 公开输入:$[pk_A^R]$;
- 私有输入:$[sk_A^R]$;
- 调用合约方法:$Organization.register(\pi, pk_A^R, n_A, addr_A)$
注:在ZoKrates的零知识证明生成过程中,我们可以设置输入变量是否公开,公开的变量会出现在生成的证明中,而私有的变量则不会出现。
完成注册后的组织需要根据协议对自己的数字资产完成注册流程,才能对这些资产进行匿名授权。对于任意的数字资产,组织需要先为其生成一个唯一的数字id,记为$\alpha$。在本协议中,数字id可以是不超过32字节的任意数据。
资产注册实际上是在区块链上记录了组织的资产公钥$pk^R$和$\alpha$的对应关系,我们需要保证以下几点:
- 只有拥有$sk^R$的组织才能为对应的$pk^R$注册资产;
- 同样的$\alpha$只能被注册一次。
同样的,零知识证明可以帮助我们实现第1点。
下面我们以组织A注册资产$\alpha$为例,具体介绍资产注册的流程:
-
生成一个随机数${\sigma}_A$
-
计算$R_A^{\alpha}=h(\alpha|pk_A^R|{\sigma}_A)$;
-
生成零知识证明$\pi$,包含约束:
-
$pk_A^R:=h(sk_A^R)$ -
$R_A^{\alpha}==h(\alpha|pk_A^R|{\sigma}_A)$
-
-
公开输入:$[R_A^{\alpha},\alpha]$;
-
私有输入:$[sk_A^R,{\sigma}_A]$
-
调用合约方法:$Shield.register(\pi, R_A^{\alpha},\alpha)$。
注册后的组织可以将注册后的资产匿名授权给另一个注册后的组织。授权的本质是在区块链上生成了一条授权记录,记录中隐含了资产$\alpha$以及被授权方的信息,我们需要保证以下几点:
- 只有经过注册的资产$\alpha$才可以被授权;
- 授权方必须拥有$\alpha$在注册时对应的资产私钥$sk^R$;
- 资产$\alpha$、授权方信息和被授权方信息不能公开。
其中:
- 第1点需要我们给出资产$\alpha$的注册记录存在的零知识证明;
- 第2点需要我们给出授权方拥有$sk^R$的零知识证明;
- 只要给出上述零知识证明,第3点自然就成立了。
下面以组织A授权资产$\alpha$给组织B为例,具体介绍资产授权的流程:
- 获取组织B的资产公钥$pk_B^R$
- 生成一个随机数${\sigma}_{AB}$,这个随机数将会通过加密信道发送给组织B
- 计算$Z_B^{\alpha}=h(\alpha|pk_A^R|pk_B^R|{\sigma}_{AB})$;
- 获取${\psi}_{R_A^{\alpha}}$,即$R_A^{\alpha}$在$MT^R$上的兄弟节点路径值;
- 获取$MT^R$的根节点$root_R;$
- 生成零知识证明$\pi$,包含约束:
$pk_A^R:=h(sk_A^R)$ $R_A^{\alpha}:=h(\alpha|pk_A^R|{\sigma}_{A})$ $root_R==M({\psi}_{R_A^{\alpha}},R_A^{\alpha})$ $Z_B^{\alpha}==h(\alpha|pk_A^R|pk_B^R|{\sigma}_{AB})$
- 公开输入:$[Z_B^{\alpha},root_R]$;
- 私有输入:$[pk_B^R,sk_A^R,\alpha,{\psi}_{R_A^{\alpha}},{\sigma}A,{\sigma}{AB}];$
- 调用合约方法:$Shield.authorize(\pi,Z_B^{\alpha},root_R)$。
组织为了向第三方展示自己获取了某个数字资产的授权,需要生成一个授权证明。授权证明本质上是证明了组织与区块链上的授权记录的对应关系,即:
- 区块链上存在$\alpha$和$pk^R$对应的授权记录$Z^{\alpha}$;
- 组织拥有$pk^R$对应的私钥$sk^R$。
下面以组织B生成资产$\alpha$的授权证明为例,具体介绍生成授权证明的流程:
-
获取$pk_A^R$和${\sigma}_{AB}$
-
计算$Z_B^{\alpha}=h(\alpha|pk_A^R|pk_B^R|{\sigma}_{AB})$
-
获取${\psi}_{Z_B^{\alpha}}$,即$Z_B^{\alpha}$在$MT^Z$上的兄弟节点路径值;
-
获取$MT^Z$的根节点$root_Z$;
-
生成零知识证明$\pi$,包含约束:
$pk_B^R==h(sk_B^R)$ $Z_B^{\alpha}:=h(\alpha|pk_A^R|pk_B^R|{\sigma}_{AB})$ $root_Z==M({\psi}_{Z_B^{\alpha}},Z_B^{\alpha})$
-
公开输入:$[pk_A^R,pk_B^R,\alpha,root_Z]$
-
私有输入:$[sk_B^R,{\psi}{Z_B^{\alpha}},{\sigma}{AB}]$;
-
$[\pi,pk_A^R, pk_B^R,\alpha,root_Z]$ 向所有人公开,作为组织B拥有来自A的资产$\alpha$授权的证明。
对于其他组织提供的授权证明,可以通过调用智能合约的方法来验证授权证明的有效性:
方法返回:
- B是否获得A的授权;
- 授权方A的组织信息;
本协议使用了一系列智能合约来实现与区块链的交互,主要包含以下几类:
- 组织相关:用于组织注册、组织名修改等;
- Shield合约:用于组织发起对资产的注册、授权和验证等;
- Verifier合约:使用椭圆曲线配对函数来验证零知识证明,作为library供其他合约调用。
下面具体介绍两个重要的合约内容:
-
$register(\pi, pk_A^R, n_A, addr_A$ ):- 在区块链上注册组织,记录组织的注册名、资产公钥和区块链地址的对应关系。
-
$resetName(n_A)$ :- 修改组织名,只有组织注册的区块链地址有权限调用。
-
$get(pk^R)$ :- 获得资产公钥$pk^R$对应的组织注册名。
-
$register(\pi, R_A^{\alpha},\alpha)$ :- 在区块链上注册数字资产$\alpha$,这里组织A的信息是隐藏的;
-
$R_A^{\alpha}$ 将被保存到$MT^R$中去; - 更新$MT^R$;
- 生成一个$\alpha$对应的ERC-721 token,所有者为Shield合约。
-
$authorize(\pi,Z_B^{\alpha},root_R)$ :- 将数字资产$\alpha$授权给B,这里数字资产$\alpha$的信息是隐藏的;
- 合约需要验证root曾经或刚好是$MT^R$的根节点。
-
$verify(\pi, inputs)$ :- 通过调用Verifier合约的方法来验证一个证据$\pi$和对应的输入inputs是否匹配。
-
$authorizeCheck(\pi,pk_A^R, pk_B^R,\alpha)$ :- 验证授权证明信息。