MPC之TSS-LIB源码分析1 --ECDSA Generate

前述

tss-lib是币安开源的关于数字货币的多签解决方案基础库。基于对椭圆曲线签名算法私钥的分片,实现了多方安全计算,即所谓的MPC。此解决方案与各个数字货币自己的多签解决方案的优劣对比,可以参考币安的官方文章threshold-signatures-explained,其中有详细解释。

TSS-LIB

目录结构

以下为代码的基本目录结构,及用途

  • common 放置了一些常用工具函数,包括hash计算、log打印、随机数生成等等
  • crypto 与加解密相关的一些算法及库函数
  • ecdsa 椭圆曲线相关的操作(主要是256k1这条曲线),包括分片私钥的生成,签名的计算,分片私钥的刷新等等
  • eddsa 爱德华曲线,椭圆曲线中的另外一类(主要是ed25519),也同样包含了分片私钥生成,签名计算,私钥刷新等
  • protob 不通Party之间,通过protobuf进行通信,此文件夹内为消息的格式定义
  • test 测试相关代码
  • tss 主要确定了各类计算的操作流程。后续我们会展开讲解具体流程的内容。

操作流程

每个分片的持有者,被称为一个Party,每个Party针对不同的操作,会有不同的流程。通过状态机的模式,对流程进行模式化,每个状态被称为一个Round,每个Round完成后,会进入下一个Round,进行不同的操作。在每个Round的过程中,Party之间会进行通信,完成信息交换。下边我们以ECDSA为例,讲解整体的运行流程。

Generate

作为一切的开始,我们首先要生成签名所需的私钥分片,来进行后续的签名工作。ecdsa 下的generate文件夹实现了这一流程,接下来我们结合代码,详细了解下这里的操作。

前置操作

在计算开始前,我们首先要确定我们要实现的多签的两个参数:

  1. 有几方即几个Party参与其中§;
  2. 解密的阈值是多少(t), 注意,这里阈值的定义与其他多签中阈值定义不太一样,这里阈值的含义是:多余t方的私钥泄露,则整体不在安全,所以传统中的2-2,对于这里t则是1

确定了上述参数后,我们进行下一步准备工作,每个Party生成自己的ID(u256的随机数),每个Party可以起一个别名,主要是为了沟通标识用,其他作用不大。
Party之间相互交换彼此的ID

Round1

参与各Party,各自单独运行。首先生成一个大小为 t + 1的随机数数组poly。 其中poly[0]就是私钥的分片。这里我们需要将这个分片共享给其他人,使得其中t个人就可以恢复分片,进行其他操作。这里采用多项式分解的方式进行秘密分享,具体讲解可以参考此篇文章密钥分享Secret Sharing介绍。多项式公式如下:

y=a+bx+cx2+dx3+...y = a + bx + cx^2 + dx^3 + ...

其中x的阶数取决于t的大小。将poly中的数值带入到公式中作为a b c等参数,将每个Party的ID作为x代入公式中,我们就得到了y,也就是每个Party对应的share, 假设p = 3, t =1。那么我们就会得到类似如下的方程组:

\begin{cases} a + 100 b + 33c = 400 \\\ a + 29b + 24c = 350 \\\ a + 55b + 6c = 600 \end{cases}

那么如果我们集齐了两个Party,自然能够解出其中的私钥分片a。

根据如上推论,我们可以理解整体真正的私钥P是由每个Party生成的分片组成的。即 P=P0+P1+P2...P = P_0 + P_1 + P_2 ...,然后每个Party又将各自的分片私钥通过多项式的方式进行加密,分享给其他的Party。即P0=P0a+P0b+P0c....P_0 = P_0a + P_0b + P_0c ....
那么每个Party都持有了N个私钥分片的分片,即P0aP1aP2a...P_0a 、 P_1a 、 P_2a ...,集齐t + 1个Party即可破解私钥。

好了,这里做了一些简单的展开,我们回到流程中来,这里生成了私钥分片的share,然后使用poly对椭圆曲线基点进行乘法操作,我们得到了数组vs。vs=polyGvs = poly * G

然后通过HashCommitment算法,对vs进行签名。通过Paillier算法对数据进行签名,这里因为不影响主流程的进行,所以不展开讲解了(其实是还没仔细看…)。

在这一轮的最后,通过广播的形式,将签名后的参数传播给其他的Party。

Round2

在Party收集到所有其他各个Party传入的Round1的Message之后,进入到Round2。

首先对Round1传入的数据进行验证,确保正确后,存储到内存中。

然后,首先通过P2P的方式,将Round1中生成的私钥分片的share,传输给对应的Party。
最后通过广播的方式将vs的数据传输给其他Party。

Round3

在收集齐Round2中的两轮通信消息后,Party进入到round3。在这个轮次中,我们会对最终的数据进行计算。

首先,我们将手中持有的属于我们自己的share进行加和,得到Xi,对于其中的Party a来说,他的计算如下:

Xi=P0a+P1a+P2a...Xi = P_0a + P_1a + P_2a ...

然后通过前几个轮次的通信,我们已经有了所有Party的Vs信息。通过计算我们可以得到Xj:

Xj=Vs[0]+Vs[1]x+Vs[2]x2...Xj = \sum Vs[0] + \sum Vs[1] * x + \sum Vs[2] * x^2 ...

其中x是Party的ID

我们前边已经解释过 P=P0+P1+...P = P_0 + P_1 + ... 也就等价于 P=Poly[0]P = \sum Poly[0], 已知公钥 Pb=PGPb = P * G,那么推导可得 Pb=Poly[0]G=Vs[0]GPb = \sum Poly[0] * G = \sum Vs[0] * G, 所以这里每个Party 就可以得到公钥的信息了。

这一轮次的最后,我们会将计算得到的公钥Pb信息,通过Paillier算法对数据进行签名,然后广播传给其他所有Party

Round4

在收集完成Round3的所有消息后,主要是对消息中计算的公钥进行验证,确认无误后,将所有需要保存的信息通过channel发送到外部业务层进行处理。 整体撕咬的生成也就结束了。

尾声

后续我会继续写签名和分片刷新部分的源码解析,此篇中关于Paillier部分没有展开讲解的, 我会在后续分析清楚后,单独写一篇讲解,Flag先立在这里。 如果有哪位朋友读到这篇文章想要一起探讨的,也欢迎写邮件与我联系。