# 逻辑回归中的零知识证明

### **逻辑回归的执行流程**

先来看看Delta中的逻辑回归任务的执行流程，这里我们使用横向联邦学习的方式来运行逻辑回归任务（纵向的零知识证明实现是类似的）：

<figure><img src="https://1896031965-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MeXwJqfM10q7XP1GAyZ-1995411665%2Fuploads%2FFLuE6tvoNbJl8UgvgGiO%2Fimage.png?alt=media&#x26;token=2816a3d0-69d6-44e9-a374-e6f1a38577d8" alt=""><figcaption><p>用安全聚合执行的逻辑回归流程</p></figcaption></figure>

在逻辑回归任务中，参与方有一个"任务发起者"，就是购买者，还有多个"计算节点"，也就是数据持有者。图中只画出了一个计算节点，因为每个计算节点的流程都是一样的。

每个任务的执行分为多个轮次，图中画出的是其中一个。首先任务发起者将当前的模型权重发送给全部的计算节点，计算节点用其当作初始权重，在本地的隐私数据上进行训练，得到了更新的梯度值，发送回任务发起者。任务发起者将多个计算节点的梯度值求和后，得到了全局的梯度值，用全局的梯度值更新模型权重，就完成了一轮计算。如果全局梯度值不为零，说明模型还没有收敛，就继续执行下一轮。

注意计算节点发送回任务发起者的单节点梯度值，实际上不是明文发送的，因为单节点梯度值某种程度上也暴露了节点上数据的隐私。这里使用了横向联邦学习的安全聚合方法，每个计算节点给自己的梯度值加上一个掩码，再发送出来。安全聚合方法保证了所有计算节点的掩码后梯度值求和，可以得到准确的全局梯度值，但是无法获取到任何一个掩码前的单节点梯度值。

在最后一轮训练后，任务发起者发现全局梯度值等于零（小于某个设定好的阈值），即可判断模型已经收敛，本次任务已经成功结束：

<figure><img src="https://1896031965-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MeXwJqfM10q7XP1GAyZ-1995411665%2Fuploads%2FnFKe6DOP6vuYUEzzwehb%2Fimage.png?alt=media&#x26;token=ef0e891f-e26e-4b5c-ad78-1a354934c0f4" alt=""><figcaption><p>模型收敛的最后一轮训练</p></figcaption></figure>

### **逻辑回归任务的验证**

在最后一轮训练结束，任务发起者判断模型已经收敛后，就要开始互相猜疑的付费过程了。任务发起者怀疑计算是假的，或者数据是假的。计算节点害怕任务发起者不付费。两方都可能出于各种原因不配合后续的流程。因此我们的验证流程需要站在"支付宝"，就是区块链的角度，看看做为一个中间人，为了能够验证逻辑回归的正确执行，都需要哪些信息。

#### **从"过程验证"到"结果验证"**

由于逻辑回归任务是一轮一轮执行的，最直接的验证流程设计，就是每一轮计算都由计算节点生成一个零知识证明，提交给区块链，证明自己按照要求完成了计算。这就是"计算过程验证"。

在逻辑回归中，每一轮计算节点的输出是一个经过掩码的梯度值，如果对这个梯度值生成零知识证明，就需要把安全聚合的整个计算过程也嵌入进去，会导致零知识证明极其复杂。同时零知识证明的生成成本也很高，速度很慢，如果每一轮都需要生成的话，系统就不可用了。

Delta中对此做了一个优化，不在每一轮都要求计算过程验证，只在最后计算完成后，要求计算节点提交一个"模型收敛"的证明。站在任务发起者的视角，只要最后得到的模型收敛，并且是在我要求的数据上收敛，结果就已经满意了，而不在意计算节点到底是怎么计算的。

这样在整个任务过程中，计算节点只需要在最后生成一个零知识证明，提交给区块链，区块链验证通过，就可以结束了。

#### **零知识证明需要验证的内容**

我们回到区块链的视角，一个数据交易任务到底怎么样才算成功完成？除了模型收敛以外，需要包括从原始数据到模型传输的全部过程，其中任何一步没有完成，数据交易都不算成功：

1. 计算节点在任务中使用了之前上链的数据（原始数据证明）
2. 计算节点最终给出的模型收敛（模型收敛证明）
3. 最终模型给到了任务发起者（模型接收证明）

第1、2点前文已经介绍过了。第3点是一个新的要求，因为如果计算节点仅仅是算出了正确的模型，但是没有发送给对方，那交易是失败的。

零知识证明是提交给区块链的，而模型是提交给任务发起者的，如果计算节点没有把最终的模型发送给任务发起者，只是把生成正确模型的证据给了区块链，这时候是需要区块链自行判断模型是否给到了任务发起者，如果计算节点说给了，发起者说没给，这个交易是否成功完成，就无从判断了。

除了上述3点以外，还有一点需要验证的，就是计算节点提交的证明，是针对任务发起者这次的任务。保证计算节点不能用一个以前的任务的有效证明，通过这次这个任务的验证。

接下来我们详细介绍零知识证明对这几点的实现。我们把零知识证明抽象成一组输入和输出：公开输入、秘密输入、公开输出。输入和输出是和计算步骤对应的，零知识证明一个是可以将某些输入设置为外部不可见的，在此基础上，证明提交Proof数据的人**知道一组秘密输入，和公开输入一起，按照之前定义的计算步骤，确实计算出了公开输出。**&#x516C;开输入和公开输出能够从Proof数据中提取出来，对外部可见，秘密输入无法提取。

零知识证明的设计，就转变为了输入和输出的设计。

#### **原始数据证明**

零知识证明的其中一个秘密输入，是原始数据。通过在零知识证明中计算原始数据的哈希值，并且把哈希值做为公开输出公布出来，就实现了对原始数据的溯源。如果Proof通过了验证，说明本次计算使用的数据，其哈希值确实是输出的值。区块链进一步将哈希值和链上记录的哈希值进行比对，就可以确认执行计算所用到的数据，确实是之前链上登记过的。

#### **模型收敛证明**

模型收敛的判断方法是所有的计算节点输出的梯度值的和小于阈值。因此单个节点生成的零知识证明无法直接证明模型收敛。因此需要在零知识证明中，将梯度值作为公开输出公布出来，区块链拿到所有节点的梯度值后求和，即可判断模型是否已经收敛。

由于只在模型收敛后计算一轮零知识证明，只公开最后一轮的梯度值，不会暴露节点的隐私数据。

#### **模型接收证明**

这一步的证明比较巧妙。因为一般来说，如果对方抵赖的话，我们很难证明模型已经发送给了对方。

我们回头去看最后一轮的执行流程。在执行开始的时候，任务发起方需要将初始的模型权重发送给计算节点。而这一轮模型训练得到的梯度值是接近零的，就是说，这一轮任务发起方发出去的模型，其实就是最终收敛的模型。

这样我们就可以实现证明了。在零知识证明的秘密输入中，加入上一轮模型权重，然后在公开输出中输出上一轮权重的哈希。然后在轮次启动时，要求任务发起者将模型权重的哈希值上链。这样当零知识证明通过验证时，说明模型收敛，并且模型权重的哈希值是A。然后A实际上已经由任务发起者上过链了，任务发起者既然可以生成其哈希值A，说明它已经拥有哈希值A对应的收敛的模型了。

至此，零知识证明的设计已经完成了。完整的设计如下图所示：

<figure><img src="https://1896031965-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MeXwJqfM10q7XP1GAyZ-1995411665%2Fuploads%2FqTW64eBB9jeP1lf7iYlI%2Fimage.png?alt=media&#x26;token=7bf43b5b-3088-4d74-a458-72bfa33690db" alt=""><figcaption><p>Delta中零知识证明的设计</p></figcaption></figure>

加上区块链和零知识证明的任务执行流程如下图所示：

<figure><img src="https://1896031965-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MeXwJqfM10q7XP1GAyZ-1995411665%2Fuploads%2FU0G9UNcKN8AGjVrc5O6t%2F1e39f5fc518537cf77648407f874600.jpg?alt=media&#x26;token=cf211f1f-6e1b-4734-a389-5ae5164ab8ec" alt=""><figcaption><p>带零知识证明的逻辑回归任务执行流程</p></figcaption></figure>

每一轮的执行由任务发起者开始，任务发起者首先根据自己的任务类型，选择对应的链上ZK验证合约，做为参数放入任务中。这一步是为了实现上文提到的，保证零知识证明和任务的对应关系，防止计算节点提交一个其他任务的零知识证明而欺骗系统。另外，由于目前Delta只支持逻辑回归的证明，而所有的逻辑回归任务的零知识证明结构都是一样的，只有样本维度数量的差异。通过在链上预置其验证方法，避免了任务发起者每次提交任务，都要生成一个对应的新合约。并且计算节点也不用每次都去验证这个合约的正确性。

然后开始执行任务了，任务发起者先将当前的模型权重的哈希值上链，然后将权重发送给各个计算节点，开始本轮的计算。计算节点收到初始权重后，需要自行验证其哈希值和链上一致，否则最后的零知识证明无法通过，就收不到钱。

当任务发起者判断模型收敛后，可以发起结算流程。为避免任务发起者不做为，结算流程也可由计算节点在任务超时后发起。

结算流程开始后，计算节点按照上面的零知识证明设计生成Proof数据，提交到区块链。当全部计算节点的Proof数据都上链了以后，区块链开始执行验证流程：

<figure><img src="https://1896031965-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MeXwJqfM10q7XP1GAyZ-1995411665%2Fuploads%2FBmRJdh0xLwMvgY5QLO5V%2Fimage.png?alt=media&#x26;token=921c9e48-4cdc-4482-ba77-69e71234afe3" alt=""><figcaption><p>区块链上的逻辑回归任务验证流程</p></figcaption></figure>

区块链首先用任务选定的ZK验证合约验证每一个Proof数据的正确性，保证计算节点提交的Proof数据是针对当前任务的，并且能通过验证。

然后从Proof数据中提取出隐私数据哈希、上一轮权重哈希和梯度值。

验证隐私数据哈希之前都已经上链。验证上一轮权重哈希和任务发起者之前上链的哈希值一致。

将所有计算节点的梯度值相加，判断是否小于阈值，即模型是否收敛。

如果全部验证通过，说明交易成功完成。可以走后续的转账流程了。Delta中目前没有实现转账相关的流程，到这里就标记任务成功了。

至此我们就完成了整个逻辑回归的验证流程设计。
