匿名链中的密码学

以比特币起始的各类数字货币,对应的账号只是一串数字,而这串数字背后是谁在操控则无从所知。然而由于交易流向、交易金额都是全网公开的,这就给了大数据分析和追踪的机会。

其实比特币在设计之初也考虑过账户与交易关联的匿名性,因为整个比特币的账户地址几乎是无限的,大家都可以随意创建钱包地址,这样你甚至可以做到每笔交易都拿一个新地址来接收。对于这些分散的交易你可能需要整合才能进行一笔较大的交易,而这种多输入的交易一旦出现,我们就可以将这些来源地址划分到同一账户下,继而对这些账户进行进一步追踪,这样通过对整个交易网络的分析我们就能得到很多可关联的信息。加上交易金额也是公开的,对于一些特定金额的交易我们也能找到蛛丝马迹,如果对应地址的身份得到确认,通过这些信息就可以推算出更多的地址信息。另外还有找零所使用的零钱地址也会带来信息的暴露,早期比特币的客户端就出现过零钱地址总是在输出地址的第一个的bug,通过这样层层递进我们就可以揭开那些隐藏在比特币网络下的真实身份。

同时如果你在外使用比特币支付那么别人也能看到你的账户里有多少钱,怎么想这都不太安全。

对于企业而言区块链的隐私性也是非常重要的,比如某些在区块链上与对方签订的合约,以及跟客户的交易信息等等,很多应该算是商业机密了,这些都需要得到妥善保护。

以上种种,催生了一波所谓匿名币,几个知名匿名币介绍可参看 简析主流匿名币:Dash、门罗币、Zcash、SERO。本文大部分内容来自对门罗的研究。

 

在数学领域,人们的直观感觉是,随着运算数的增大,除法比乘法的难度增长快,求对数比求幂的难度增长快,直至计算机都无法在多项式时间长度内得到正确的反向(除法、对数)运算结果。即:

\(r\xrightarrow{\times G} R\),但 \(R\stackrel{\div G}\nrightarrow r\)

这个式子虽然简单,但是当前主流密码学的基石。虽然尚没有严格的数学证明,目前流行的非对称加密算法如RSA和ECC,都是基于此客观事实。


密钥协商(key exchange)

我们请出密码学中两个知名的虚拟人物—— Alice 和 Bob ——来演示如何在众目睽睽之下完成密钥的协商。

以下 Alice 和 Bob 的对话是公开的。

Alice:我们先选出一个大数G,这个G所有人都可以知道。然后我在心里面想一个大数a,我把a乘以G的结果,假设是A告诉Bob,当然A是多少也被大家听到了。

现在大家包括Bob知道了G、A,而a只有Alice本人知道。

Bob:好的,现在我也在心里想一个大数b,并且把b乘以G的结果B告诉Alice。

现在所有人都知道G、A、B,而a只有Alice本人知道,b只有Bob本人知道。

Alice:明白了,那么最终敲定的密钥就是a乘以B的结果,这个用计算机很快就能算出来。

Bob:算出来的结果不用告诉我,因为我拿b乘以A得到的结果是一样的。
得到密钥:aB=abG=baG=bA

以上是基于乘法的密钥协商过程,基于求幂的公式就是\((g^{\color{red}a})^{\color{red}b}=(g^{\color{red}b})^{\color{red}a}\)。如果再加入一些数论中的概念,则可以得到如下式子(Diffie-Hellman密钥协商算法):

\((g^a\;mod\;p)^{b}\;mod\;p=(g^b\;mod\;p)^{a}\;mod\;p\),其中p是素数,g是p的本原根。

而ECC更进一步,将数域范围缩小到某条椭圆曲线上,再\(\;mod\;p\)后取其中一个有限群,然后再取该有限群下的一个循环子群,这样似乎破解难度更高。不过不管何种方式,所利用的原理无非是\(f(A,b)=f(B,a)\)。

维基百科上使用了一个很有趣的比喻——颜料的混合。设想这样一个场景,Alice和Bob,他们想在不见面的情况下秘密约定出一种颜色,但他们互相沟通的信息都会被公开,应该怎么办呢?

 

再来聊聊ECC的加解密,其实也用到了密钥协商的概念。

假设m为待加密信息,Alice拥有密钥对(a,A),Bob拥有密钥对(b,B),aG=A,bG=B;Alice要将m安全地传递给Bob。

  1. Alice将m编码后计算:M=m+aB。
  2. Bob拿到M计算:m=M-bA,完成。

一般为了更安全计,通信双方使用的密钥对是临时生成,用过即销毁。


stealth address

这回Alice要给Bob转一笔钱,而Bob不希望其他人知道是他得到这笔钱,又该怎么办呢?

  1. Bob向Alice公开了他的标准地址(比如说扫一扫),这个地址不是直接用于收款的,而是存储了两个公钥(\(B_1、B_2\)),任何人都能获得,他自己则保留对应的两个私钥(\(b_1、b_2\))。\(B_1=b_1G,B_2=b_2G\),这里G是椭圆曲线上选取的基点,全网统一,同样的算法基点都是一样的.
    门罗标准地址格式:网络编码(1 byte)+public spend key(32 byte)+public view key(32 byte)+校验和(4 byte)
  2. Alice随机生成了一个整数r作为另一个私钥,套用公式\(P=H_s(rB_1)G+B_2\)得[一次性]公钥P,其中\(H_s\)是一个哈希函数。P就是Bob的一次性收款地址(stealth address)。
    思考\(H_s\)的作用?
  3. Alice使用P作为转账的目标地址,同时将\(R=rG\)打包进此次交易。注意她可以使用相同的r进行不同目标地址的转账。
  4. Alice将交易发送至链,因此P、R也一同被公开到链上。
    P中的\(H_s\)不但规范了P的数据结构(长度),同时也避免第三方将P与Bob关联起来。试想,若无\(H_s\),那么\(P=rB_1G+B_2=RB_1+B_2\),第三方很容易通过试算得到实际的收款方。
  5. Bob扫描区块链上[自上次扫描以来的]最近交易,计算\(P'=H_s(b_1R)G+B_2\)。如果P’=P,则说明这笔转账是给Bob的,因为\(b_1R=b_1rG=rb_1G=rB_1\)。
  6. Bob要花这笔钱(转账给别人),则首先要计算出与P对应的私钥x,即xG=P,由\(P=P'=H_s(b_1R)G+B_2\)可得:\(x=H_s(b_1R)+b_2\)。这个私钥连给钱的Alice也无法推出(因为有\(b_2\)项),日后Bob就可以使用这个私钥来花这笔钱了。

环签名(Ring Signatures)

使用多个公钥及其中一个对应私钥,构建一个正确的方程(在不知其中任意一个私钥的情况下很难构建这样的方程)。在别人看来,你必是知道其中某个公钥的私钥,但是无法推定你知道哪一个。算法有很多,关键是构造的方程形式,举例:

验证方要求构建的方程形如\(f(\mathbb{P};\mathbb{X})=g_1(P_1,x_1)+g_2(P_2,x_2)+\cdots +g_n(P_n,x_n)=v\),其中\(v\)是验证方随意取的一个大数,\(\mathbb{P}\)是[证明方选取的]公钥集合,\(\mathbb{X}\)是[证明方给出的]值集合,\(g_i\)是\(P_i\)对\(x_i\)的加密函数(为了方便表述,得到的结果记为\(y_i\))。

在不知道任何一个私钥的情况下,得到合适的\(\mathbb{X}\)基本不可能,而只要拥有其中一个私钥,这就不是问题。假设我们拥有\(P_s\)的私钥,给除\(x_s\)之外的所有\(x_i\)随意赋值,得到除\(y_s\)之外的所有\(y_i\),要使等式成立,则\(y_s=v-\sum\limits_{i=1,i\not=s}^ny_i=g_s(P_s,x_s)\),由于\(g_s\)是对\(x_s\)的加密,只要拿私钥解密即可得到合适的\(x_s\),补足了\(\mathbb{X}\)。

证明方将\(\mathbb{P}\)、\(\mathbb{X}\)发给验证方,验证方验算即可。

上述过程需要验证方事先给出\(v\),未免麻烦。有人想到了这个\(v\)可以由证明方设置,前提是\(v\)要参与到式子左边的计算,计算完成之后又得到它本身,首尾相等,便是所谓的环签名。下面是monero中Adam Back提出的一个算法(值得一提的是,这个算法并非Adam Back所创,提出这个算法的是Joseph K. Liu, Victor K. Wei, and Duncan S. Wong,看名字似乎是中国人):

  1. 随机选取n-1个公钥,连同自己的公钥\(P_s\)组成n个公钥集合\(\{P_1,P_2,\cdots ,P_s\cdots ,P_n\}\);
  2. 随机选取n-1个值\(\{x_1,x_2,\cdots ,x_n\}\),其中\(x_s\)暂缺,后面会使用计算得到的合适值补上;
    这两步和之前的例子一样。
  3. 设一未知数\(v_1\),再设\(Y_1=x_1G+v_1P_1\);
  4. 设一未知数\(l\),再设\(R_1=x_1H_p(P_1)+v_1l\);
    注意\(l,R_i\)存在与否并不影响环签名,而是另有考虑,可看下节Key Image,此节可忽略。
  5. 计算\(v_2=H_s(m,Y_1,R_1)\),其中\(H_p\)和\(H_s\)是两个不同的哈希函数,m是需要签名的数据,根据业务需求来;
  6. 同上依次计算\(Y_2=x_2G+v_2P_2;R_2=x_2H_p(P_2)+v_2l;v_3=H_s(m,Y_2,R_2);\cdots ;Y_n=x_nG+v_nP_n;R_n=x_nH_p(P_n)+v_nl;v_{n+1}=H_s(m,Y_n,R_n)\);
  7. 令\(\underline{v_{n+1}=v_1}\),这就是我们要构造的方程,为了使方程成立,关键落在寻找合适的\(x_s\)及未知数\(v_1,l\)上;
  8. 虽然每一环的\(v\)由上一环而来,但是由于其首尾相连,我们无法由任一环推出其值,也就是说其值是不定/任意的;那么我们可以先随意设置某环的计算结果,再反推出符合该结果的\(v\)值。因此任选一个数a,使得\(Y_s=aG,R_s=aH_p(P_s)\),\(Y_s,R_s\)一旦确定,\(v_{s+1}=H_s(m,Y_s,R_s)\)也跟着确定下来,继而\(v_{s+2},v_{s+3},\cdots ,v_n,v_1,\cdots ,v_{s}\)都可依次推得;
  9. 又\(Y_s=x_sG+v_sP_s\),易得\(x_s=a-v_ss_s\),其中\(s_s\)是\(P_s\)对应的私钥。如此,\(\mathbb{X}\)被补足,现在只剩\(l\)未知;
  10. 由\(R_s=x_sH_p(P_s)+v_sl=aH_p(P_s)\)及\(a=x_s+v_ss_s\),易得\(l=s_sH_p(P_s)\)。
  11. 证明方将\(\mathbb{P}\)、\(\mathbb{X}\)、\(v_1\)、\(l\)公开到链上,作为交易的发起者;矿工验证。

Key Image

细心的读者会发现,把上节中的\(l,R_i\)在环签名过程中去掉,似乎也不影响签名的有效性。再看最终得到的\(l=s_sH_p(P_s)\),很明显\(l\)的值避开了签名过程中产生的各种变量,只跟发起方的密钥对有关,这使得\(l\)是个确定的数值,无法捏造。显然\(R\)的公式是经过精心设计的。那么monero把它们放在环签名中应该也是刻意为之,作用是为了防止双花。

双花的概念是数字货币独有的,而具体细节不同的数字货币又有所不同。

在比特币中,Alice 将存在于自己同笔UTXO中的1个BTC在相隔很短的时间内转账给Bob和Tom,一部分矿工先收到了给Bob的那笔交易\(T_b\),一部分矿工先收到给Tom的那笔交易\(T_t\)。每个矿工都会检查UTXO是否未被花费,也就是本节点数据库和待打包交易池中不存在该UTXO,因此矿工要么打包\(T_b\),要么打包\(T_t\)。假设其中一个打包\(T_b\)的矿工出块成功,那么收到新块通知的矿工会将本地交易池中的\(T_b\)\(T_t\)移除。万一有打包\(T_t\)的矿工未收到新块通知,并且也在同一高度出块成功,那么就形成了分叉。依据比特币的最长链规则,除非两方的算力一直相同(各占50%,这种情况即使出现也会很快失去平衡),否则总会是某条链胜出。这样最终就概率性解决了双花问题。

monero也使用UTXO,毕竟匿名性就决定了交易/余额不能基于账户模型,但与比特币不同,由于环签名的缘故,矿工无法确定一个交易中的实际UTXO是哪个,自然无法将其从数据库中标记删除,从这个角度说,UTXO一旦产生,就永远有效,这样显然不合理。门罗币使用Key Image解决这个问题。简单来说,一个Key Image对应一个UTXO(UTXO被花费的同时产生对应的Key Image),矿工只要查看这次交易产生的Key Image是否存在,即可判断UTXO是否被重复消费。上述的\(l\)就承担了Key Image的职责。

随着交易量的增长,Key Image的数量也会跟着膨胀,这会带来一定的存储问题和或者可能的检索效率问题(在合适数据结构和不考虑存储空间大小的情况下,检索时间复杂度可一直维持在同一数量级)。

下面我们同样以例子来梳理整个交易流程,以便更清晰的了解Key Image如何起作用。

回顾 stealth address 一节内容,monero的UTXO都是一次性地址,所以环签名一节说到的公钥集合\(\mathbb{P}\)其实都是一次性公钥,而非标准公钥。可知,环签名中的\(s_s\)就是 stealth address 中的\(x=H_s(b_1R)+b_2\)。

Alice 在给 Bob发送XMR的时候,其实是将自己的一笔或多笔UTXO变成Bob的一笔UTXO(当然一般还包括矿工费及找零,此处忽略);

而Alice的UTXO又是别人转给她的,以任一笔来说,公钥地址\(P_s=H_s(a_1R)G+A_2\),对应私钥是\(s_s=H_s(a_1R)+a_2\),其中\(a_1,a_2\)是Alice的标准私钥,R是上家给出的随机数公钥(参看stealth address 一节)。Alice持有\(s_s\)就表明可以合法使用这笔UTXO;

除了给出花费的UTXO 的真实地址\(P_s\)之外,还会随机选取链上其它未花费的UTXO添加到交易的inputs中,并进行环签名,环签名用到了\(s_s\)。由\(P_s,s_s\)公式可知,只要上家给出的R不重复,Key Image也[几乎]不会重复,而\(R=rG\),说到底防止双花的关键落在上家给的随机数\(r\)上,只要\(r\)位数够长,且足够随机,那么对同一个接收方(此处是Alice)来说重复的概率微乎其微。

2017年1月10日Monero中正式使用了RingCTs。而在此之前,Monero中假如Alice需要向Bob转12.5个XMR,则需要将自己的UTXO分别发送至三个地址,分别转账10XMR、2XMR和0.5XMR,这是因为Monero中要求转账的XMR格式为A×10BA×10B的格式,环签名中诱骗的UTXO也要相同数额。使用了RingCTs之后,在区块链上隐藏了交易数额,这时候Alice挑选mixins的时候,不用考虑mixins的XMR的余额,提升了交易的隐私性。

Alice需要花费的真实UTXO有几笔,该过程就重复几次。

有些人会认为\(s_s\)只有发起方知道,如果发起方给出的\(l\)不合法也无从判断。需要注意的是\(l\)并非发起方主动计算,而是在签名过程中推导出的结果,它的值是唯一的,且正好等于\(s_sH_p(P_s)\),如若捏造一个非法数值,将不会通过矿工的校验。

结合以上所有内容,我们可以窥得并理解monero一次交易的全貌:


Ring Confidential Transactions

OK,目前为止,我们实现了在一次转账中:

  • 隐藏转账方[UTXO[一次性]公钥地址](环签名)。
  • 隐藏了收款方[标准公钥地址](一次性地址)。
  • 防止双花(Key Image)。

如果能把转账金额也给隐藏了,那就做到了完全匿名。外部无法知道谁转给谁,转了多少钱,但是这笔转账又是可验证合法的。我们来考虑怎么判断转账金额没问题。

  1. 首先,输入=输出。这很好理解,Alice支出的钱必须得等于(Bob收到的钱+矿工费+找零),收支平衡,只要保证货币初始来源合法(如挖矿),那么追根溯源,后续的UTXO必然合法,然而这还不够。
  2. 假如Alice构造了一笔交易输入=1BTC,输出=1000BTC+(-999)BTC,不考虑矿工费,其中1000BTC转给自己的另一个地址,-999BTC转给原地址作为找零,后续她可以不管原地址,而使用新地址,如此则凭空多出了巨额财富。要避免这种情况很简单,只要规定(每笔输入输出>0)。

满足以上两点,就可以判定交易金额没问题。幸运的是,上述两点并不要求转账的具体数额向验证者公开,这就使隐藏金额有了可行性。那么具体如何做呢?

同态加密

无需解密,就能够对密文进行计算。在[计算者]计算完毕后[私钥拥有者]再解密,可避免接触到原始私密数据而得到正确结果。支持加法计算的叫加法同态(ECC),支持乘法的叫乘法同态(RSA),支持任意计算的则称为全同态(基于理想格,2009,Gentry)。

Pedersen commitments

承诺场景让你把一段数据作为私密保存,但是要承诺它,使得你后来不能改变该数据。一个简单的承诺场景用哈希函数构建如:承诺=SHA256(盲化因子||数据)。如果你仅告诉别人承诺,别人没法确定你承诺了什么数据。但你后来揭露了盲化因子和数据,别人可以运行该哈希函数来验证是否与你以前的承诺相匹配。盲化因子必须存在,否则别人可以试图猜测数据。如果你的数据比较少而简单,猜测成功可能性比较大。

佩德森承诺与以上场景中的承诺类似,但是附加一个特性:承诺可以相加,多个承诺的总和等于数据总和的承诺。即加法同态:\(C(a+b)=C(a)+C(b)\)。对于ECC来说,就是\(aG+bG=(a+b)G\),很直白吧。

因此对于输入=输出,即\(\sum\limits_i a_i=\sum\limits_j b_j\),其中\(a_i\)为其中一笔输入,\(b_j\)为其中一笔输出,它们总和相等。可构造佩德森承诺\(\sum\limits_i C_{i,in}=\sum\limits_j C_{j,out}\),其中每一项\(C\)又该如何构造?如前所说,每个承诺都需要一个盲化因子,以输入为例,\(C=(x+a)G\),x为盲化因子。

思考为什么不直接\(C=x+a\),而要乘以G。

又到举例子时间。假设有一笔输入,两笔输出,如下:

\(C_{in}=xG+aG\Longrightarrow \left\{\begin{aligned}C_{out-1}=y_1G+b_1G\\C_{out-2}=y_2G+b_2G\end{aligned}\right.\)。

显然,当\(x=y_1+y_2,\ a=b_1+b_2\)时,\(C_{in}=C_{out-1}+C_{out-2}\),这是我们期望的。然而再一细想,可取任意数值n,在\(x-n=y_1+y_2,\ a+n=b_1+b_2\)时,该等式都成立,这就使得交易发起者可以伪造输出而不被发现(输出比输入多了n个币)。

看来需要有个约束\(a=b_1+b_2\)的方案。研究上式,可以发现n存在是因为各项因子相同(都是G),自然可以这项减那项加,只要加减数值一致,等式便永远相等。于是我们可改变\(a,b_1,b_2\)的因子,并使其不能与\(x,y_1,y_2\)的因子相互转换,即不能有\(G_{new}\Longleftrightarrow G\)。于是我们又想到了最初的那个公式,\(G_{new}=rG\),只要r的数值没有任何人知道,那么\(G_{new}\)和\(G\)的关系就无人知晓。

第一节里提到ECC是基于椭圆曲线下的某循环子群,因此只要随机选取该群中任意一点H,它都满足\(H=rG\)(r不可知)。Confidential Transactions中给了一个选取方法:\(H=to\_point(hash(G))\),hash(G)计算得到的值作为坐标x,代入曲线方程计算得到H,即我们需要的\(G_{new}\)。

注意并不是所有hash函数都能恰好得到某个群元素的x坐标。

最终输入=输出的佩德森承诺为:\(C_{in}-\sum\limits_{i=1}^2C_{out-i}=xG+aH-y_1G-b_1H-y_2G-b_2H=0\)。

monero构造上述承诺稍有不同,留待以后讲解。

接着构造金额>0的佩德森承诺,所谓范围证明(Range Proof)。以输出\(C=yG+bH\)为例,看如何改造。

  1. 将金额b表示成二进制形式:\(b=b_02^0+b_12^1+\cdots +b_n2^n,\ b_i\in \{0,1\}\);
  2. 将私钥y对应拆分为n个随机数之和:\(y=y_0+y_1+\cdots +y_n\);
  3. 根据加法同态,C可表示为:\(C=C_0+C_1+\cdots +C_n\),其中\(C_i=y_iG+(b_i2^i)H\);
  4. 针对每个私钥\(y_i\),取两个公钥\(\{C_i,C_i-2^iH\}\),两者必有一个等于\(y_iG\),所以真正的密钥对为\(<y_i,y_iG>\)。将该公钥对进行环签名形成二元环签名,表明构造者持有这两个公钥的其中一个私钥。有多少个私钥,就对应多少个二元环签名;
  5. 矿工只需进行两步验证:1、\(C_i\)之和是否等于C;2、各个环签名是否正确。

范围证明技术只认无符号正整数,假设采用8bit无符号整数表达金额,若b为负数(-1),那么在补码表示法中,高位全部都是1,b就会被误认为是255进行处理,这就会导致\(C\ne \sum C_i\),无法通过验证,这就确保了b的值没有二意性,只能为正整数。

 


 

参考资料:

https://cryptonote.org/whitepaper.pdf

Ring signature  环签名(Ring signature)

monero门罗币ringsignatures环签名算法浅解:(2)AdamBack提出的改进算法

Zero to Monero   Ring Confidential Transactions

 

 

转载请注明本文出处:https://www.cnblogs.com/newton/p/11376002.html

posted @ 2019-08-30 16:12  莱布尼茨  阅读(963)  评论(2编辑  收藏  举报