逻辑回归中的零知识证明
最后更新于
最后更新于
先来看看Delta中的逻辑回归任务的执行流程,这里我们使用横向联邦学习的方式来运行逻辑回归任务(纵向的零知识证明实现是类似的):
在逻辑回归任务中,参与方有一个"任务发起者",就是购买者,还有多个"计算节点",也就是数据持有者。图中只画出了一个计算节点,因为每个计算节点的流程都是一样的。
每个任务的执行分为多个轮次,图中画出的是其中一个。首先任务发起者将当前的模型权重发送给全部的计算节点,计算节点用其当作初始权重,在本地的隐私数据上进行训练,得到了更新的梯度值,发送回任务发起者。任务发起者将多个计算节点的梯度值求和后,得到了全局的梯度值,用全局的梯度值更新模型权重,就完成了一轮计算。如果全局梯度值不为零,说明模型还没有收敛,就继续执行下一轮。
注意计算节点发送回任务发起者的单节点梯度值,实际上不是明文发送的,因为单节点梯度值某种程度上也暴露了节点上数据的隐私。这里使用了横向联邦学习的安全聚合方法,每个计算节点给自己的梯度值加上一个掩码,再发送出来。安全聚合方法保证了所有计算节点的掩码后梯度值求和,可以得到准确的全局梯度值,但是无法获取到任何一个掩码前的单节点梯度值。
在最后一轮训练后,任务发起者发现全局梯度值等于零(小于某个设定好的阈值),即可判断模型已经收敛,本次任务已经成功结束:
在最后一轮训练结束,任务发起者判断模型已经收敛后,就要开始互相猜疑的付费过程了。任务发起者怀疑计算是假的,或者数据是假的。计算节点害怕任务发起者不付费。两方都可能出于各种原因不配合后续的流程。因此我们的验证流程需要站在"支付宝",就是区块链的角度,看看做为一个中间人,为了能够验证逻辑回归的正确执行,都需要哪些信息。
由于逻辑回归任务是一轮一轮执行的,最直接的验证流程设计,就是每一轮计算都由计算节点生成一个零知识证明,提交给区块链,证明自己按照要求完成了计算。这就是"计算过程验证"。
在逻辑回归中,每一轮计算节点的输出是一个经过掩码的梯度值,如果对这个梯度值生成零知识证明,就需要把安全聚合的整个计算过程也嵌入进去,会导致零知识证明极其复杂。同时零知识证明的生成成本也很高,速度很慢,如果每一轮都需要生成的话,系统就不可用了。
Delta中对此做了一个优化,不在每一轮都要求计算过程验证,只在最后计算完成后,要求计算节点提交一个"模型收敛"的证明。站在任务发起者的视角,只要最后得到的模型收敛,并且是在我要求的数据上收敛,结果就已经满意了,而不在意计算节点到底是怎么计算的。
这样在整个任务过程中,计算节点只需要在最后生成一个零知识证明,提交给区块链,区块链验证通过,就可以结束了。
我们回到区块链的视角,一个数据交易任务到底怎么样才算成功完成?除了模型收敛以外,需要包括从原始数据到模型传输的全部过程,其中任何一步没有完成,数据交易都不算成功:
计算节点在任务中使用了之前上链的数据(原始数据证明)
计算节点最终给出的模型收敛(模型收敛证明)
最终模型给到了任务发起者(模型接收证明)
第1、2点前文已经介绍过了。第3点是一个新的要求,因为如果计算节点仅仅是算出了正确的模型,但是没有发送给对方,那交易是失败的。
零知识证明是提交给区块链的,而模型是提交给任务发起者的,如果计算节点没有把最终的模型发送给任务发起者,只是把生成正确模型的证据给了区块链,这时候是需要区块链自行判断模型是否给到了任务发起者,如果计算节点说给了,发起者说没给,这个交易是否成功完成,就无从判断了。
除了上述3点以外,还有一点需要验证的,就是计算节点提交的证明,是针对任务发起者这次的任务。保证计算节点不能用一个以前的任务的有效证明,通过这次这个任务的验证。
接下来我们详细介绍零知识证明对这几点的实现。我们把零知识证明抽象成一组输入和输出:公开输入、秘密输入、公开输出。输入和输出是和计算步骤对应的,零知识证明一个是可以将某些输入设置为外部不可见的,在此基础上,证明提交Proof数据的人知道一组秘密输入,和公开输入一起,按照之前定义的计算步骤,确实计算出了公开输出。公开输入和公开输出能够从Proof数据中提取出来,对外部可见,秘密输入无法提取。
零知识证明的设计,就转变为了输入和输出的设计。
零知识证明的其中一个秘密输入,是原始数据。通过在零知识证明中计算原始数据的哈希值,并且把哈希值做为公开输出公布出来,就实现了对原始数据的溯源。如果Proof通过了验证,说明本次计算使用的数据,其哈希值确实是输出的值。区块链进一步将哈希值和链上记录的哈希值进行比对,就可以确认执行计算所用到的数据,确实是之前链上登记过的。
模型收敛的判断方法是所有的计算节点输出的梯度值的和小于阈值。因此单个节点生成的零知识证明无法直接证明模型收敛。因此需要在零知识证明中,将梯度值作为公开输出公布出来,区块链拿到所有节点的梯度值后求和,即可判断模型是否已经收敛。
由于只在模型收敛后计算一轮零知识证明,只公开最后一轮的梯度值,不会暴露节点的隐私数据。
这一步的证明比较巧妙。因为一般来说,如果对方抵赖的话,我们很难证明模型已经发送给了对方。
我们回头去看最后一轮的执行流程。在执行开始的时候,任务发起方需要将初始的模型权重发送给计算节点。而这一轮模型训练得到的梯度值是接近零的,就是说,这一轮任务发起方发出去的模型,其实就是最终收敛的模型。
这样我们就可以实现证明了。在零知识证明的秘密输入中,加入上一轮模型权重,然后在公开输出中输出上一轮权重的哈希。然后在轮次启动时,要求任务发起者将模型权重的哈希值上链。这样当零知识证明通过验证时,说明模型收敛,并且模型权重的哈希值是A。然后A实际上已经由任务发起者上过链了,任务发起者既然可以生成其哈希值A,说明它已经拥有哈希值A对应的收敛的模型了。
至此,零知识证明的设计已经完成了。完整的设计如下图所示:
加上区块链和零知识证明的任务执行流程如下图所示:
每一轮的执行由任务发起者开始,任务发起者首先根据自己的任务类型,选择对应的链上ZK验证合约,做为参数放入任务中。这一步是为了实现上文提到的,保证零知识证明和任务的对应关系,防止计算节点提交一个其他任务的零知识证明而欺骗系统。另外,由于目前Delta只支持逻辑回归的证明,而所有的逻辑回归任务的零知识证明结构都是一样的,只有样本维度数量的差异。通过在链上预置其验证方法,避免了任务发起者每次提交任务,都要生成一个对应的新合约。并且计算节点也不用每次都去验证这个合约的正确性。
然后开始执行任务了,任务发起者先将当前的模型权重的哈希值上链,然后将权重发送给各个计算节点,开始本轮的计算。计算节点收到初始权重后,需要自行验证其哈希值和链上一致,否则最后的零知识证明无法通过,就收不到钱。
当任务发起者判断模型收敛后,可以发起结算流程。为避免任务发起者不做为,结算流程也可由计算节点在任务超时后发起。
结算流程开始后,计算节点按照上面的零知识证明设计生成Proof数据,提交到区块链。当全部计算节点的Proof数据都上链了以后,区块链开始执行验证流程:
区块链首先用任务选定的ZK验证合约验证每一个Proof数据的正确性,保证计算节点提交的Proof数据是针对当前任务的,并且能通过验证。
然后从Proof数据中提取出隐私数据哈希、上一轮权重哈希和梯度值。
验证隐私数据哈希之前都已经上链。验证上一轮权重哈希和任务发起者之前上链的哈希值一致。
将所有计算节点的梯度值相加,判断是否小于阈值,即模型是否收敛。
如果全部验证通过,说明交易成功完成。可以走后续的转账流程了。Delta中目前没有实现转账相关的流程,到这里就标记任务成功了。
至此我们就完成了整个逻辑回归的验证流程设计。