专栏首页区块链入门蚂蚁区块链第7课 零知识证明隐私保护原理和蚂蚁BAAS接口调用实现

蚂蚁区块链第7课 零知识证明隐私保护原理和蚂蚁BAAS接口调用实现

1,摘要

本文试图普及隐私保护和零知识证明的相关技术知识,尝试使用更简单的描述来理解复杂的数学算法和技术原理。同时,也提供了蚂蚁区块链已经实现的隐私保护的接口函数说明。 本文涉及的专业知识有零知识证明,zk-SNARKs和 BulletProofs(防弹证明),佩德森承诺等。

2,零知识证明隐私保护概要

零知识证明,英文名为Zero-Knowledge Proof,是由S.Goldwasser、S.Micali以及C.Rackoff在20世纪80年代初提出的。它指的是证明者(被验证者)能够在不向验证者提供任何有用的信息的情况下,使验证者相信某个论断是正确的。零知识证明实质上是一种涉及两方或更多方的协议,即两方或更多方完成一项任务所需采取的一系列步骤。

网上有一个被很多人引用的例子,即“阿里巴巴的零知识证明”,可以帮助我们理解“零知识证明”的原理。

一天,阿里巴巴被强盗抓住了,强盗向阿里巴巴拷问进入山洞的咒语。面对强盗,阿里巴巴是这么想的:如果我把咒语告诉了他们,他们就会认为我没有价值了,就会杀了我省粮食;但如果我死活不说,他们也会认为我没有价值而杀了我。怎样才能做到既让他们确信我知道咒语,但又一丁点咒语内容也不泄露给他们呢?

这的确是一个令人纠结的问题,但阿里巴巴想了一个好办法,当强盗向他拷问打开山洞石门的咒语时,他对强盗说:“你们在离开我一箭远的地方,用弓箭指着我,当你们举起右手我就念咒语打开石门,举起左手我就念咒语关上石门,如果我做不到或逃跑,你们就用弓箭射死我。”

强盗们当然会同意,因为这个方案不仅对他们没有任何损失,而且还能帮助他们搞清楚阿里巴巴到底是不是真的知道咒语这个问题。阿里巴巴也没有损失,因为处于一箭之地的强盗们听不到他念的咒语,不必担心泄露了秘密,同时他又确信自己的咒语有效,也不会发生被射死的杯具。

强盗举起了右手,只见阿里巴巴的嘴动了几下,石门果真打开了,强盗举起了左手,阿里巴巴的嘴动了几下后石门又关上了。强盗还是有点不信,说不准这是巧合呢,他们不断地换着节奏举右手举左手,石门跟着他们的节奏开开关关,最后强盗们想,如果还认为这只是巧合,自己未免是个傻瓜,那还是相信了阿里巴巴吧。

这样,阿里巴巴既没有告诉强盗进入山洞石门的咒语,同时又向强盗们证明了,他是知识这个咒语的。

这就是零知识证明的一个重要实例。

零知识证明需要满足三个属性: 1、如果语句为真,诚实的验证者(即:正确遵循协议的验证者)将由诚实的证明者确信这一事实。 2、如果语句为假,不排除有概率欺骗者可以说服诚实的验证者它是真的。 3、如果语句为真,证明者的目的就是向验证者证明并使验证者相信自己知道或拥有某一消息,而在证明过程中不可向验证者泄漏任何有关被证明消息的内容。

零知识证明并不是数学意义上的证明,因为它存在小概率的误差,欺骗者有可能通过虚假陈述骗过证明者。换句话来说,零知识证明是概率证明而不是确定性证明。但是也存在有技术能将误差降低到可以忽略的值。 零知识的形式定义必须使用一些计算模型,最常见的是图灵机的计算模型。

3,零知识证明的3种典型技术和应用

zk-SNARKs, Zk-STARKs和 BulletProofs(防弹证明)是零知识证明用于区块链隐私技术的3种主要技术。对比来看:

(1) Bulletproofs 和 Zk-STARKs 不需要可信设置,zk-SNARKs则需要可信设置; zk-STARKs:通过证明者与验证者之间的交互来执行,以一种有效的数学方法,使得验证者通过验证每一个步骤,最终确信证明者确实知道某个信息或者拥有某种权益。其特点是:证明快、验证快,但证明体积大 SNARK指无需双方交互,证明人单方出具即可,不需要反复在双方之间传递信息。其特点是:证明慢、验证快,证明体积小。

(2) 证明速度对比:Zk-STARKs > zk-SNARKs > Bulletproofs

(3) 文件大小:zk-SNARKs < Bulletproofs <Zk-STARKs

ZERO项目CTO段学鹏给出的3种技术的对比图,更加清晰描述3个技术的区别:

算法对比

简单一句话:Zk-STARKs 太大,bulletproofs太慢,优点是不用预装,zk-SNARKs最优。

3.1 zk-SNARK技术原理

zk-SNARKs 是 zero knowledge Succinct Non-interactive Argument of Knowledge 的缩写。其中的词语分别解释如下:

  • zero knowledge: 零知识证明。即验证者(Verifier)不需知道“内情”即可相信证明者(Prover)。在zk-SNARKs中具体指:证明者(Prover)声称自己手中有一个炒鸡复杂多项式方程的解s,验证者不需要知道s具体是什么就可以相信证明者(Prover)。是不是听起来很不可思议?将会在下面会具体说明。
  • Succinct: 简洁。主要指在验证过程中传输的数据量不那么大且验证方法简单。
  • Non-interactive:无交互。证明者只需要提供一些信息,公开后任何人都可以直接进行验证而不需要跟证明者进行交互。这对区块链来说极为重要,因为其意味着可以放在链上给矿工(Miners)验证。

zk-SNARKs只适合解决QAP问题(Quadratic Arithmetic Programs),通俗地讲,就是问题中得含有多项式,为什么得含有多项式我们后面再说。现在的主要矛盾是,如何将一个普通的问题转化为QAP问题。

举个例子,现在有一个方程:

x ^ 3 + x ^ 2 + x = 14

我们很容易就知道它的解是 x=2。下面我们将这个问题转化为QAP问题。

  1. 首先我们引入一些变量,将上面的方程转化为若干基本简单算式。这些简单算式要么是 x = y 要么是 x = y (op) z 的形式,其中op代表加减乘除(+,-,*,/)四种运算符。这些运算是可以利用数字电路完成的。 假如我们引入的变量设为 sym_1,sym_2,sym_3和~out,那么这些基本简单算式如下:

sym_1 = x * x sym_2 = sym_1 * x sym_3 = sym_2 + sym_1 ~out = sym_3 + x

  1. 接下来我们用向量內积的思想表达上面的基本简单算式。为了表达加法,还需要引入一个虚拟变量~one,看了下面就会懂的。

设解向量为s,具体表达式如下。然后将这些简单算式表示为 s.a = s.b * s.c 的形式。其中, . 表示內积,这样 s.b 是一个1*4的列向量。a,b,c是系数矩阵。

s = [~one, x, ~out, sym_1, sym_2, sym_3 ]

那么,对于第一个简单算式 sym_1 = x * x 而言,就可以表示为:

s . [0,0,0,1,0,0] = (s . [0,1,0,0,0,0]) * (s . [0,1,0,0,0,0])

对于含有加法的简单算式,比如第三个 sym_3 = sym_2 + sym_1,就可以表示为:

s . [0,0,0,0,0,1] = s . [0,0,0,1,1,0] * s . [1,0,0,0,0,0]

有四个等式,将这四个式子按照原来的顺序排起来,a,b,c 就会组成三个矩阵 A,B,C。与上面的例子原理一样,A,B,C可分别经过计算得出,结果为:

A = [0,0,0,1,0,0 B = [0,1,0,0,0,0 C = [0,1,0,0,0,0 0,0,0,0,1,0 0,0,0,1,0,0 0,1,0,0,0,0 0,0,0,0,0,1 0,0,0,1,1,0 1,0,0,0,0,0 0,0,1,0,0,0] 0,1,0,0,0,1] 1,0,0,0,0,0]

3.接下来的工作就是将A,B,C三个矩阵表示为多项式形式。例如 A → A(n)=[A1(n), A2(n), A3(n), A4(n), A5(n), A6(n)],每一个代表一列。方法是对矩阵A的每一列使用拉格朗日插值法。例如阵A的第三列为[0,0,0,1]T,也就是寻找一个多项式 A3(n),使得当 n = 1,2,3,4 时,A3(n) 的值分别为0,0,0,1。

按照拉格朗日插值法,A3(n) 可以视为四个式子之和:

A3_1(n) = k(n-2)(n-3)(n-4) A3_2(n) = l(n-1)(n-3)(n-4) A3_3(n) = m(n-1)(n-2)(n-4) A3_4(n) = t(n-1)(n-2)(n-3)

容易得到,A3(n) = A3_1(n) + A3_2(n) + Ac(n) + A3_4(n). 且有,当 n=i 时,A3(i) = A3_i(n). 故,n = 1,2,3 时,A3_1(n), A3_2(n), A3_3(n) = 0, 故 k,l,m = 0. n = 4时,A3_4(4) = t * 3 * 2 * 1 = 1,故 t = 1/6。

根据上面的原理,可以求出Ai(n),进而求出A(n)。类似的,还可以求出A(n),B(n),C(n)。

这样问题便转化为计算问题便转换为求取解向量s,使得等式s . C(n) - s . A(n) * s . B(n) = 0 在n=1,2,3,4时成立,等价于:

存在一个多项式H(n),使得s . C(n) - s . A(n) * s . B(n) = H(n) * Z(n),其中, Z(n) = (n-1)(n-2)(n-3)(n-4)。各位请注意: 方程式中如愿以偿地出现了多项式!

那么,为什么要费尽心思搞一个多项式呢?就是为了搞出零知识来。试想一下,P要向V证明自己知道方程: x ^ 3 + x ^ 2 + x = 14 的解,似乎除了告诉他解 x = 2 似乎没有别的方法,但如果采用多项式表达的话,另有手段。

给出方程之后,A(n),B(n),C(n)就确定了,同时也是公开的。如果令多项式 P(n) = s . C(n) - s . A(n) * s . B(n) , 事实上,由于s是一个向量,內积过后仍为多项式,我们这里就用C(n)替代 s . C(n),下文皆是如此。也就是,P(n) = C(n) - A(n) * B(n)。那么 Prover 只需要利用自己知道的s计算出P(n),然后计算 H(n) = P(n) / Z(n),并将P(n)和H(n)发给Verifier,V通过验证 P(n) ?= H(n) * Z(n) 即可相信P拥有方程的解s。(?= 的含义是是否等于)

3.2 零知识证明zk-SNARK技术在ZCASH中使用

假设,Alice要向Bob转一个单位的数字货币(BTC/ZEC),即Alice要向Bob转移一个单位的资产所有权。这时有以下两个方法:

(一)比特币中的做法:Alice拥有一张1BTC的支票,要转账给Bob时,先给Bob新建一张1BTC的支票,同时当着Bob的面将自己原先的支票撕毁。

(二)ZCash中的做法:Alice拥有一张1ZEC的支票,要转账给Bob时,先给Bob新建一张1ZEC的支票,然后在一张约定有效的作废列表中,记录下Alice的发票的代号,证明Alice的支票已经失效。

ZCash的方法属于零知识证明。整个交易过程中,Bob并没有见过Alice的支票,但是还是实现了资产所有权的转移。在ZCash的整个交易系统中,Alice和Bob的交易还有其他见证者,即负责记录交易信息的矿工。同样道理,矿工也不必看到Alice的支票,只要能确定代号为r1的支票已经作废了就行。

有了上述铺垫,就可以进一步解释ZCash的匿名交易过程了。

还是那个例子:Alice转1 个ZEC给Bob。这个例子中有涉及到的角色有转账双方Alice和Bob,以及记账者(矿工)。

首先是Alice和Bob都有了一张支票,如图6。

图6

这两张“支票”都是有效的。Alice的支票开始就存在于整个ZCash网络,Bob的支票在生成后也会被广播到全网。

为了隐藏交易者信息,要对两张支票进行加密处理。在全网中存在的“支票”其实是这样子的,如图7。

信息都是被加密的,可以通过拥有者的私钥解密

图7

同时,因为资产只能有一份,所有矿工手里还有一个作废列表。Alice要同时广播自己的“发票代号”,录入作废列表中。发票代号也是加密的。所以矿工们能看到的信息其实是这样的。其中Alice的支票是原先存在的,Alice的支票代号r1和Bob的支票是在交易过程中被Alice广播的。如图8。

图8

矿工们能获取的信息相当有限,但是这并不影响对矿工对交易有效性的判断。

判断的逻辑相当简单:矿工拿到Alice给的支票代号r1,去作废列表中检索,假如作废列表中已经存在r1,则证明r1所对应的的支票早已失效;若作废列表中并不存在r1,则证明r1对应的支票仍旧有效,此时矿工把r1录入作废列表中,把新生成的支票录入支票列表中。所以记账的过程就是对原有支票登记失效,并存入现有支票票的过程。

在这个过程中,我们不难发现,每笔交易矿工能接收到的东西只有一个发票代号,和一张新的发票,而且这两样东西都是被加密的。所以矿工并不知道转账双方是谁,也不知道转账金额是多少。

Bulletproofs(防弹证明)技术原理

防弹证明来自于2016年Bootle等发明的基于离散对数的更节省空间的零知识证明,而防弹证明比其更加节省空间。这种证明同时还有提供对Pedersen承诺(Pedersen Commitment)、公钥等已提交数值的原生支持,这对于解决我们的问题非常重要。这使得我们能够在该普通零知识证明框架中执行范围证明,而无需在零知识中安装庞大复杂的椭圆曲线算术。

更强大。为了限制交易大小,我们旧的范围证明将交易输出限制在2^32内。这就将交易输出限制在了43 BTC内,不过我们也可以将证明粒度从1中本聪提高到10或者100,或者将最小值改为大于零,以此来提高交易输出的限制。这些方法都是可行的,但执行量非常巨大,削弱了系统自带的隐私性能。

这些32位的范围证明大约有2.5 KB大,使用Adam Back提出的压缩方法后可以压缩到2 KB,而如果是防弹证明的话则只有610字节大。如果体积只有这么小,那我们不妨将范围增加到64位,这样就不需要进行任何会损害隐私性能的调整了。增加到64位之后会导致大小从610字节增加到1220字节,对吧?不是的。实际上64位防弹证明只有674字节大。

更小。我们能够让范围大小增加一倍,而证明大小只增加64字节,因为体积是以对数形式增长的。这是通过Bootle等人于2016年发表的一篇论文中的内积论证的变体而实现的。(Jonathan Bootle也为Benedikt和Dan开发防弹证明提供了帮助。)该论文提出的对数大小的内积论证,在防弹证明中甚至更小,从6log(N)曲线点下降到了2log(N)。

使用同样的方法,可以让在同一笔交易内的多个范围证明压缩成为一个,而体积只有小幅增加。两个范围证明聚合在一起大小为738个字节,4个则为802字节,8个则为866字节。而8个64位的经典范围证明的体积则将超过40000字节!

更快。能够节省空间固然很好,但我们对该项技术的初步分析显示这会使得验证速度比旧的范围证明还要慢。要验证单一的64位证明需要超过200个标量点乘法运算,每个运算需要50微秒,而旧的范围证明只需要128。

防弹证明可以用于在零知识中证明任意陈述。防弹证明的力量和SNARKSTARK相当,但可以原生支持椭圆曲线(EC)公钥和Pedersen承诺(所以就不需要在被证明的程序中安装椭圆曲线数学了)。此外,和SNARK不一样,防弹证明在标准假设下拥有全部128位安全系统,不需要可启动信任。

3.4 佩德森承诺和佩德森承诺应用

3.4.1 佩德森的承诺

CT( Confidential Transactions,机密交易)的基础密码学工具是佩德森的承诺。

承诺场景让你把一段数据作为私密保存,但是要承诺它,使得你后来不能改变该数据。一个简单的承诺场景用哈希函数构建如下:

承诺 = SHA256(盲化因子||数据)

如果你仅告诉别人承诺,别人没法确定你承诺了什么数据(对哈希表的属性给定某些假设)。但你后来揭露了盲化因子和数据,别人可以运行该哈希函数来验证是否与你以前的承诺相匹配。盲化因子必须存在,否则别人可以试图猜测数据。如果你的数据比较少而简单,猜测成功可能性比较大。

佩德森承诺与以上场景中的承诺类似,但是附加一个特性:承诺可以相加,多个承诺的总和等于数据总和的承诺(盲化因子的集合即盲化因子总和):

C(BF1, data1) + C(BF2, data2) == C(BF1 + BF2, data1 + data2) C(BF1, data1) - C(BF1, data1) == 0

换句话说,加法律和交换律适用于承诺。

If data_n = {1,1,2} and BF_n = {5,10,15} then: C(BF1, data1) + C(BF2, data2) - C(BF3, data3) == 0

我们用椭圆曲线点来构建具体的佩德森承诺(读者无需理解椭圆曲线密码学体系,当成黑盒行为来了解就可以了)。 通常,ECC公钥由私钥x乘以基点G生成。

PUB = xG

结果通常保存为33字节的数组。 ECC公钥遵守以前描述过的加法同态性:

PUB1 + PUB2 = (x1 + x2 ( mod n ))G

(以上特性被BIP32分层确定性钱包用来允许第三方生成新的比特币地址)

佩德森承诺的额外基点(我们叫H点)生成办法,因而没人知道H对G的离散对数(反之亦然),意思是说没人知道x,且xG = H。我们使用G哈希来选择H:

H = to_point(SHA256(ENCODE(G)))

这里to_point把输入当成椭圆曲线上某个点的x值,并且计算出y值。 给定两个基点我们能构建如下承诺场景:

承诺 = xG +aH

这里x是私密盲化因子,a是我们要承诺的金额,你可以用加法交换律验证加法同态承诺场景中的相关关系。 佩德森承诺是信息理论上的隐私,你看到的所有承诺,总能找到一些盲化因子,可以和任意金额一起匹配该承诺。如果你的盲化因子真的随机的话,甚至连具有无穷计算力的攻击者都不能分辨你承诺的金额。这些承诺对于假冒承诺来说是计算安全的,你实际上不能计算出任意映射。如果你做到,这就意味着你能找到两个基点相对于彼此的离散对数,意味着承诺群的安全性受到损害。

3.4.2 佩德森承诺应用

利用该工具,我们替换比特币交易中的8字节整数金额为32字节佩德森承诺.

如果一个交易的发起人认真选择他们的盲化因子,以便正确相加,然后网络还能通过承诺相加为0来验证该交易。

(In1 + In2 + In3 + plaintext_input_amountH...) - (Out1 + Out2 + Out3 + ... feesH) == 0

以上公式需要明确的交易费用,在实际交易中,这点没有问题。 生成承诺和承诺验证非常简单,不幸的是,如果没有附加的措施这个场景是不安全的。

问题在于该群是循环群,加法要 mod P(一个256位的质数,用于定义群的秩),结果大数的加法会“溢出”,从而像个负数金额,因而当有些输出金额为负时,承诺加起来为0的特点依然存在,导致可凭空创造5个比特币。

(1 + 1) - (-5 + 7) == 0

以上式子可以被解释成“有人花了2个比特币,得到-5个比特币和7个比特币”。 为了防止产生这种情况,交易中有多输出的时候,我们必须证明每个承诺输出金额都在允许范围(如[0,2^64])内且没有溢出。

我们可以公开金额和盲化因子,以便网络能检查,但是这样一来就损失了所有隐私。因而,我们要证明承诺的金额在允许范围内,除此之外不透露任何信息:我们需要额外密码学系统来证明佩德森承诺的范围,我们使用类似于Schoenmakers二元分解的技术,但是进行了许多优化(包括不使用二元)。

我们从基本的EC签名开始,如果生成了一个签名,签名的“消息”是公钥的哈希,该签名证明签名者知道私钥,即公钥对于某些基点的离散对数。

对于一个类似“公钥”的P = xG + aH ,因为基点H的存在,没有人知道P对于基点G的离散对数,因为没人知道x 使得xG = H,除非a为0。如果a为0,则P = xG ,离散对数恰好是x,有人会为该公钥签名。

把承诺当成公钥,对承诺的哈希值签名,通过这种方法,某个佩德森承诺可以被证明是对0值的承诺。在签名中使用公钥用于防止把签名设置成任意值并且破解出承诺。签名使用的私钥正是盲化因子。

更进一步,假定我想证明C是对金额1的承诺,但不告诉你盲化因子,你能做的就是计算:

C' = C - 1H

然后向我要公钥C'的签名(相对于基点G的签名),如果我能做到,则C一定是对金额1的承诺(否则我就破解了EC离散对数的安全性)。

4,零知识证明隐私保护样例代码分析

蚂蚁区块链 JAVA SDK 隐私保护提供以下功能:

  • 提供加密算法,加密和解密交易的输入金额和输出金额;平台无法解密加密后的金额,只有金额的所有者才能解密(调用 PedersenCommitment、ValueHiding 接口);
  • 提供范围证明用来证明金额在合理范围内,比如证明输出金额大于 0(调用 Proofs::AddRangeProof 接口);
  • 提供证明工具证明输入金额总数和输出金额总数相等(调用 PedersenCommitment::ComputeBlindDelta、Proofs::SetBlindDelta 接口);
  • 提供工具向交易附上证明数据(调用 Transaction 的 extensions 字段)。

客户端隐私保护交易创建流程:

  1. 向平台查询自己的资产余额,并且使用自己的私钥解密金额,选择要使用的资产。
  2. 向平台查询转账对方的 encryption_key。
  3. 根据要使用的资产,计算找零金额和转账金额,并且分别用自己的和对方的 encryption_key 创建 PedersenCommitment,调用 Proofs::AddCommitment 添加 key 创建 PedersenCommitment。
  4. 使用 PedersenCommitment::ComputeBlindDelta 计算盲因子之差,并调用 Proofs::SetBlindDelta 设置到 Proofs 对象里。
  5. 使用 Proofs::AddRangeProof 为找零金额和转账金额创建范围证明。
  6. 设置 Transaction 的 extensions 字段为 Proofs 序列化后的字节数组。

查看 Java SDK 隐私保护接口说明,了解更多信息。

蚂蚁区块链平台隐私保护提供的功能,提供智能合约指令进行范围校验,恒等证明校验:

  • verify_commitment:校验输出和输出承诺
  • verify_balance:校验输出总金额和输出总金额是否相等
  • verify_range:校验输出金额是否大于 0

查看 Solidity 隐私接口函数说明,了解更多信息。

说明:蚂蚁BAAS实现方案中,用PedersenCommitment在JAVA SDK来隐藏金额,用零知识证明(防弹证明)来在solidity智能合约中验证。

4.1 smart_asset.sol样例代码

pragma solidity ^0.4.0;
contract SmartAsset {
    struct Utxo {
        bool exist_;
        bytes value_;
        bool used_;
    }
 
    uint64 seq_ = 1;
    mapping(identity => uint64[]) seqs_;
 
    mapping(identity => mapping(uint64 => Utxo)) assets_;
 
    function getNumUtxos() public view returns (uint32 len) {
        len = uint32(seqs_[msg.sender].length);
    }
 
    uint constant utxo_num = 5;
 
    function getUtxos(uint256 start) public view returns (uint64[utxo_num] seqs, uint256[utxo_num] lens, bytes values) {
        uint64 offset = 0;
        uint i;
        uint256 total_len = 0;
        for (i = start; i < seqs_[msg.sender].length && i < start + utxo_num; i++) {
            seqs[i - start] = seqs_[msg.sender][i];
            lens[i - start] = assets_[msg.sender][seqs[i - start]].value_.length;
            total_len += lens[i - start];
        }
        values = new bytes(total_len);
        for (i = start; i < seqs_[msg.sender].length && i < start + utxo_num; i++) {
            for (uint j = 0; j < lens[i - start]; ++j) {
                values[offset++] = assets_[msg.sender][seqs[i - start]].value_[j];
            }
        }
    }
 
    uint64 zero = 0;
 
    function recharge(identity to, bytes out) public returns(uint256 len, bytes value) {
        if (!verify_commitment("OUT", zero, to, out)) {
            return;
        }
        if (!verify_range(zero, zero)) {
            return;
        }
        seqs_[to].push(seq_);
        assets_[to][seq_] = Utxo(true, out, false);
        seq_++;
 
        // t = to;
        len = seqs_[to].length;
        value = out;
    }
 
    uint one = 1;
    function transfer(identity to, uint64[] utxos, bytes refund, bytes out) public {
        if (!verify_commitment("OUT", zero, to, refund)) {
            return;
        }
        if (!verify_commitment("OUT", one, msg.sender, out)) {
            return;
        }
        for (uint i = 0; i < utxos.length; i++) {
            if (!assets_[msg.sender][utxos[i]].exist_ || assets_[msg.sender][utxos[i]].used_) {
                return;
            }
            if (!verify_commitment("IN", zero, msg.sender, assets_[msg.sender][utxos[i]].value_)) {
                return;
            }
        }
        if (!verify_balance(zero)) {
            return;
        }
        if (!verify_range(zero, zero)) {
            return;
        }
        for (i = 0; i < utxos.length; i++) {
            assets_[msg.sender][utxos[i]].used_ = true;
        }
 
        uint64[] memory seqs = seqs_[msg.sender];
        seqs_[msg.sender] = new uint64[](0);
 
        for (i = 0; i < seqs.length; ++i) {
            bool remove = false;
            for (uint j = 0; j < utxos.length; j++) {
                if (seqs[i] == utxos[j]) {
                    remove = true;
                    break;
                }
            }
            if (!remove) {
                seqs_[msg.sender].push(seqs[i]);
            }
        }
 
        seqs_[to].push(seq_);
        assets_[to][seq_] = Utxo(true, out, false);
        seq_++;
 
        seqs_[msg.sender].push(seq_);
        assets_[msg.sender][seq_] = Utxo(true, refund, false);
        seq_++;
    }
}

合约提供的方法:

  • getNumUtxos:获取调用者 utxo 数
  • getUtxos:获取调用者可用 utxo;每次取 utxo_num 个
  • recharge:给 to 所指定账户充值
  • transfer:转账

解释: 以transfer(identity to, uint64[] utxos, bytes refund, bytes out)为例,sdk提交的信息:

  1. evm调用参数:to(转账对象),utxos(数组,转账人要花费的commitment的唯一ids), refund(commitment,找零金额),out(commitment,转账金额)
  2. 交易数据:commitment(refund和out),refund和out的顺序(从0开始),deta,范围证明(refund和out大于等于0的证明)

智能合约:

  1. verify_commitment("OUT",...)验证参数out和refund是否和交易数据里的out和refund相同;验证的顺序和客户端提交commitment的顺序相同;OUT类型的commitment会用于verify balance和verify range

VerifyCommitment(string name, uint index, identity to,const bytes value_enc) verify_commitment("OUT", zero, to, refund)

  1. verify_commitment("IN",...),这里实际上并未验证,只是设置了要花费的commitment,用于verify balance
  2. verify balance 验证utxos中id所表示的commitment之和=refund + out + f(delata) VerifyBalance(uint range) returns(bool); verify_balance(zero)
  3. verify range 验证范围证明里refund和out大于等于0 VerifyRange(uint index, int min_value) verify_range(zero, zero)

4.2 SOLIDITY 隐私接口说明

VerifyCommitment

VerifyCommitment 为承诺验证函数。

函数原型

VerifyCommitment(string name, uint index, identity to,const bytes value_enc) returns(bool result);

请求参数

参数

必选

类型

说明

name

string

承诺类型,0 表示 input,1 表示 output【辉哥纠错】官网应写作,"OUT"表示输出,"IN"表示输入

index

uint

承诺索引

to

identity

承诺所属账户

value_enc

bytes

承诺数值

返回值

参数

必选

类型

说明

result

bool

方法返回值,成功为 true,否则为 false

VerifyBalance

balance 校验函数。

函数原型

VerifyBalance(uint range) returns(bool);

请求参数

参数

必选

类型

说明

index

uint

balance 验证索引

返回值

参数

必选

类型

说明

result

bool

方法返回值,成功为 true,否则为 false

VerifyRange

VerifyRange 是验证范围函数。

函数原型

VerifyRange(uint index, int min_value) returns(bool);

请求参数

参数

必选

类型

说明

index

uint32

验证索引范围

min_value

int64

验证范围最小值

返回值

参数

必选

类型

说明

result

bool

方法返回值,成功为 true,否则为 false

4.3 Java SDK 隐私保护接口说明

Java SDK 提供隐私保护的接口,用来隐藏智能合约交易中的交易金额。隐私保护接口主要有 3 个:

  • ValueHiding:用于加密交易金额。
  • PedersenCommitment:用于创建交易金额的承诺,以及使用 ValueHiding 加密交易金额。
  • Proofs:用于创建交易所需的证明。

ValueHiding

函数原型

函数说明

ValueHiding()

创建一个默认的 ValueHiding。

ValueHiding(final byte[] pub, final byte[] priv, HidingType type)

创建一个 ValueHiding,pub 为公钥,priv 为私钥。通过 type 指定加解密方式,可以为 HidingType.ECIES 或者 HidingType.RSA_2048。

该函数可以通过 priv 推导出 pub;如果仅提供 pub,那么 ValueHiding 只能用来加密。

public boolean loadKey(final String path, final String passwd)

使用 path 指定秘钥文件初始化 ValueHiding;passwd 为秘钥文件对应的密码。

public static ValueHiding genKeyPair(HidingType type)

随机一个生成 ValueHiding;通过 type 指定加解密方式,可以为 HidingType.ECIES 或者 HidingType.RSA_2048。

PedersenCommitment

函数原型

函数说明

public native static byte[] genBlind()

生成盲因子,用于创建 Pedersen commitment(佩德森承诺)和范围证明。

public static PedersenCommitment createCommitment(long value, final byte[] blind, ValueHiding keypair, final byte[] extensions)

为交易金额 value 创建承诺,blind 为盲因子,keypair 用于加密交易金额和 blind。

public static long getDecryptedValue(final byte[] commitment, ValueHiding keypair)

使用 keypair 从commitment 表示的承诺中解密交易金额。

public static byte[] getDecryptedBlind(final byte[] commitment, ValueHiding keypair)

使用 keypair 从 commitment 表示的承诺中解密交易盲因子。

public static byte[] computeBlindDelta(final List inputs, final List outputs)

计算 inputs 表示的盲因子列表和 outputs 表示的盲因子列表之差,盲因子之差用于证明输入金额总数和输出金额总数相等。

Proofs

函数原型

函数说明

Proofs()

创建Proofs对象

public boolean addCommitment(PedersenCommitment commitment)

添加一个创建好的输出金额的 Pedersen commitment。

public void setBlindDelta(final byte[] delta)

设置输入总金额和输出总金额的盲因子之差。

public boolean addRangeProof(final List values, final List minValues, final List blindValues,final List commitments)

添加范围证明,values 为输入金额列表;min_value 一般设置为 0,表示范围证明要证明的最小值;blind_values 为创建输入金额对应的 Pedersen commitment 时所用的盲因子;commitments 为 values 里金额对应的 Pedersen commitment 在 Proofs 对象中的索引值。索引值从 0 开始,Pedersen commitment 对应的索引值和调用 AddCommitment 时次序对应。

public final byte[] toBytes()

将 Proofs 对象序列化为 bytes;创建隐私保护交易时需要将 Proofs 序列化后的 bytes 添加到 Transaction 里的 extensions 字段。

5,参考

(1)隐私保护 https://tech.antfin.com/docs/2/102019 (2)隐私接口函数示例 https://tech.antfin.com/docs/2/101988 (3)隐私保护接口(JAVA SDK) https://tech.antfin.com/docs/2/102034 (4)Commitment scheme https://en.wikipedia.org/wiki/Commitment_scheme (5)防弹证明:更快的范围证明及其他 https://zhuanlan.zhihu.com/p/33958018 (6)零知识证明 - 百度词条 https://baike.baidu.com/item/%E9%9B%B6%E7%9F%A5%E8%AF%86%E8%AF%81%E6%98%8E/8804311?fr=aladdin (7)区块链解读-零知识证明 https://blog.csdn.net/sxjinmingjie/article/details/77746232 零知识证明的几个案例不错 (8)一文读懂区块链之 - 零知识证明 http://www.qukuaiwang.com.cn/news/3179.html 讲了ZCASH的零知识证明【有个有趣的阿里巴巴的故事】 (9)不是程序员也能看懂的ZCash零知识证明 https://zhuanlan.zhihu.com/p/24440530 ZCash的原理讲得透 (10)SERO | 智能合约教程——匿名Token发行原理 https://mp.weixin.qq.com/s/JQchZiKB2BNScizb-YAbmg (11)SERO-支持智能合约的零知识证明隐私保护公链的技术创新 https://www.jianshu.com/p/ec46e3032c9a (12)小白也能看懂的零知识证明与zk-SNARKs https://blog.csdn.net/whrunningduck/article/details/83866893 (13)深度 || 下一代区块链中最重要及主流的隐私应用方向 https://bihu.com/article/1637850 (14) 一个更优的零知识证明:Bulletproofs https://www.jianshu.com/p/47c105f356f5 (15)侧链:私密交易 https://www.8btc.com/article/58380

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 半小时读懂互联网广告新生态

    互联网广告产业链的概念普及,涉及广告网络(Ad Network),互联网广告交易平台(Ad Exchange),  实时竞价(RTB,Real Time Bid...

    辉哥
  • 【深度知识】跨链的3种机制和COSMOS/POLKDOT实现分析

    本文介绍了跨链的3种机制:公证人机制(Notary schemes),哈希锁定(Hash-locking),侧链/中继链(Sidechains / Relays...

    辉哥
  • 【链安科技】Token合约F_E漏洞

    有相关安全公司风险监控平台于今日发现,ERC20代币合约F_E由于业务逻辑实现漏洞,任何人都可以随意转出他人账户中的Token。并且该Token已经上线交易所,...

    辉哥
  • [日常] 算法-旋转字符串-暴力移位法

    给定一个字符串,要求把字符串前面的若干个字符移动到字符串的尾部,如把字符串“abcdef”前面的2个字符'a'和'b'移动到字符串的尾部,使得原字符串变成字符串...

    陶士涵
  • 最趁手的数据可视化工具Tableau

    作为一个数据分析从业者,不但要从杂七杂八的数据中提取有用的数据,而且还要生成漂亮的图表展示出来。

    有福
  • NVIDIA Jetson nano可以处理4K相机吗?来验证编码性能吧(上)

    https://dream-soft.mydns.jp/blog/developper/smarthome/2020/09/2291/?fbclid=IwAR3...

    GPUS Lady
  • 字符串:简单的反转还不够!

    给定一个字符串 s 和一个整数 k,你需要对从字符串开头算起的每隔 2k 个字符的前 k 个字符进行反转。

    代码随想录
  • 生产环境下 RocketMQ 为什么不能开启自动创建主题?

    很多网友会问,为什么明明集群中有多台Broker服务器,autoCreateTopicEnable设置为true,表示开启Topic自动创建,但新创建的Topi...

    冯杰宁
  • 计算2/1+3/2+5/3+8/5+..

    py3study
  • RocketMQ实战:生产环境中,autoCreateTopicEnable为什么不能设置为true

    很多网友会问,为什么明明集群中有多台Broker服务器,autoCreateTopicEnable设置为true,表示开启Topic自动创建,但新创建的Topi...

    丁威

扫码关注云+社区

领取腾讯云代金券