实战:利用 Go 实现多方安全计算(MPC)协议,在不暴露数据的前提下进行协同

多方安全计算(Multi-Party Computation,MPC)是现代密码学领域的一项突破性技术,它允许多个参与方在不泄露各自私有数据的前提下,共同计算一个约定的函数。想象一下,三家公司希望计算它们的平均利润,但任何一家公司都不愿意透露自己的具体利润额;或者,几家医院想要联合分析某种疾病的治疗效果,却不能共享患者的敏感病历数据。在这些场景中,MPC提供了一个完美的解决方案。

Go语言以其并发特性、高性能和简洁的语法,成为实现网络协议和分布式系统的理想选择。本讲座将深入探讨如何利用Go语言构建一个基础的多方安全计算协议,旨在帮助您理解MPC的核心原理,并掌握在Go中实现这些原理的实践技巧。我们将从MPC的理论基础讲起,逐步深入到协议设计、Go语言实现细节,并对所实现的协议进行安全分析,最后展望MPC的未来发展和挑战。

多方安全计算:在隐私与协作之间架桥

MPC的定义与核心价值

多方安全计算(MPC),有时也称为安全多方计算(Secure Multi-Party Computation),由姚期智教授在1980年代首次提出。其核心思想是,一组互不信任的参与方,各自拥有私有输入数据,他们希望协同计算某个函数的结果,同时确保以下两个关键安全目标:

  1. 输入隐私(Input Privacy):任何一方,包括协议执行中的中间结果,都不能泄露其他参与方的私有输入数据。
  2. 正确性(Correctness):协议最终计算出的结果必须是正确的,如同所有参与方将数据明文汇集到一起,由一个可信第三方计算得到的结果一样。

MPC的出现,打破了传统数据分析和协作模式中隐私与效用之间的二元对立。在许多敏感数据场景下,例如金融交易、医疗健康、政务数据共享、隐私保护型人工智能训练、安全投票和拍卖等,MPC提供了在法律合规和道德约束下进行数据协作的可能。

MPC的底层密码学原语

要实现MPC,需要依赖一系列底层的密码学原语。它们是构建安全计算协议的基石:

  1. 秘密共享(Secret Sharing)

    • 这是MPC中最基础也是最直观的原语之一。它允许一个秘密值被分成多个“份额”(shares),分发给不同的参与方。只有当足够多的份额被汇集起来时,才能重构出原始秘密。单个或少于门限数量的份额无法推导出任何关于秘密的信息。
    • Shamir秘密共享是最经典的方案,它基于多项式插值。要分享一个秘密S给N个参与方,并要求至少K个参与方(K≤N)才能重构秘密,则可以构建一个K-1次多项式 $P(x) = S + a_1x + a2x^2 + dots + a{K-1}x^{K-1}$,其中 $a_i$ 是随机选择的系数。每个参与方 $P_i$ 获得一个点 $(i, P(i))$ 作为其份额。当K个参与方汇集各自的份额时,可以通过拉格朗日插值法唯一确定该多项式,从而恢复出 $S=P(0)$。
    • 加性秘密共享是Shamir秘密共享的一种简化形式,特别适用于计算总和。一个秘密 $S$ 可以被分解为 $S = s_1 + s_2 + dots + s_N pmod M$,其中 $s_i$ 是每个参与方持有的份额,它们是随机选择的,除了最后一个 $sN = S – sum{i=1}^{N-1} s_i pmod M$。每个份额本身不泄露任何信息。
  2. 同态加密(Homomorphic Encryption, HE)

    • 同态加密允许对加密数据直接进行计算,而无需先解密。这意味着你可以对密文执行加法或乘法操作,得到的结果仍然是加密的,并且解密后与对明文执行相同操作的结果一致。
    • 部分同态加密(PHE)支持无限次加法或有限次乘法(如Paillier加密)。
    • 全同态加密(FHE)支持任意次数的加法和乘法(即图灵完备的计算),这是密码学领域的“圣杯”之一,但计算开销巨大。
    • HE在MPC中可以用于构建两方或多方协议,尤其是在计算复杂函数时。
  3. 混淆电路(Garbled Circuits, GC)

    • 混淆电路是姚期智教授在1982年提出的,主要用于两方安全计算。它将一个布尔电路(表示要计算的函数)转化为一个“混淆”形式。双方通过交换加密的真值表和标签,在不暴露输入或中间结果的情况下共同评估电路。
    • GC可以泛化到多方,但实现和通信复杂度会增加。

安全模型

在设计和分析MPC协议时,我们必须明确假设参与方的行为模式,这被称为“安全模型”:

  1. 诚实但好奇模型(Honest-but-Curious, HBU / Semi-honest)

    • 这是最常用的安全模型。在该模型下,所有参与方都严格遵守协议的每一步,不会故意偏离协议流程。
    • 然而,他们是“好奇的”,会尝试从协议执行过程中获取到的所有信息(包括自己输入、输出、中间计算结果以及收到的所有消息)中推断出其他方的私有输入。
    • 该模型下的协议通常更容易设计和实现,计算效率也更高。
  2. 恶意模型(Malicious Model)

    • 这是更强的安全模型。在该模型下,参与方可能偏离协议,发送错误消息,甚至串通作弊,试图破坏协议的正确性或隐私性。
    • 恶意模型下的协议需要额外的机制来验证参与方的行为,例如零知识证明(Zero-Knowledge Proofs, ZKP)或承诺方案(Commitment Schemes),这会显著增加协议的复杂性和计算开销。

本次讲座我们将重点关注在诚实但好奇模型下的MPC协议实现,以简化并清晰地展示MPC的核心概念。

协议选择:安全求和协议

为了具体化讲解,我们将实现一个简单的安全求和协议。这个协议的目标是:N个参与方 $P_1, P_2, ldots, P_N$ 各自持有一个私有整数 $x_1, x_2, ldots, xN$。他们希望协同计算这些整数的总和 $sum{i=1}^N x_i$,但任何一方在计算过程中都不能得知其他任何一方的私有输入 $x_j$ (其中 $j neq i$)。

我们将采用一种基于环形加性秘密共享的协议。这个协议在诚实但好奇模型下能够提供输入隐私,并且易于理解和实现。

协议描述

假设有N个参与方 $P_1, dots, P_N$,他们形成一个逻辑上的环。每个参与方 $P_i$ 持有私有输入 $x_i$。

  1. 预设阶段

    • 所有参与方共同协商并同意一个足够大的素数模数 $M$。所有的计算都将在模 $M$ 的有限域 $mathbb{Z}_M$ 中进行。这个 $M$ 必须足够大,以防止计算溢出,并确保随机数具有足够的熵。通常,我们会选择一个比所有可能的输入总和都要大的素数。
  2. 秘密分享与掩码阶段

    • 每个参与方 $P_i$ 独立地从 $mathbb{Z}_M$ 中随机选择一个秘密随机数 $r_i$。
    • $P_i$ 计算一个中间值 $y_i = (x_i + r_i) pmod M$。这个 $y_i$ 可以看作是 $x_i$ 被 $r_i$ 掩码后的值。
    • $P_i$ 将其选择的随机数 $ri$ 发送给其在环中的下一个参与方 $P{i+1}$ (对于 $P_N$,则发送给 $P_1$)。
  3. 接收与计算阶段

    • 每个参与方 $Pi$ 会从其在环中的上一个参与方 $P{i-1}$ (对于 $P_1$,则从 $PN$) 接收到一个随机数 $r{i-1}$。
    • $P_i$ 现在拥有自己的 $yi$ 和收到的 $r{i-1}$。它计算自己的最终份额 $z_i = (yi – r{i-1}) pmod M$。
    • 为了确保结果非负,如果 $yi – r{i-1}$ 结果为负,则需要加上 $M$。即 $z_i = (yi – r{i-1} + M) pmod M$。
  4. 结果揭示阶段

    • 所有参与方 $P_i$ 将自己计算出的 $z_i$ 广播给所有其他参与方。
    • 每个参与方收到所有 $N$ 个 $zj$ 值后,将它们加起来:$S = (sum{j=1}^N z_j) pmod M$。
    • 这个 $S$ 就是所有私有输入 $x_j$ 的安全总和。

协议的数学证明

我们来验证这个协议的正确性。最终的总和 $S$ 可以表示为:
$S = sum_{i=1}^N z_i pmod M$

将 $zi$ 的定义代入:
$S = sum
{i=1}^N (yi – r{i-1}) pmod M$

将 $yi$ 的定义代入:
$S = sum
{i=1}^N ((x_i + ri) – r{i-1}) pmod M$
$S = sum_{i=1}^N (x_i + ri – r{i-1}) pmod M$

展开求和:
$S = (x_1 + r_1 – r_N) + (x_2 + r_2 – r_1) + (x_3 + r_3 – r_2) + dots + (x_N + rN – r{N-1}) pmod M$

观察上式,我们可以看到所有的随机数 $r_i$ 项都会相互抵消:
$r_1$ 与 $-r_1$ 抵消
$r_2$ 与 $-r_2$ 抵消

$r_N$ 与 $-r_N$ 抵消

因此,最终的结果 $S$ 简化为:
$S = sum_{i=1}^N x_i pmod M$

这证明了协议的正确性。

隐私性分析

在诚实但好奇模型下,这个协议的隐私性如下:

  • 对于 $P_i$ 自身:它知道 $x_i, ri, r{i-1}, y_i, z_i$ 以及所有其他 $P_j$ 公开的 $z_j$。

  • 对于其他方的输入 $x_j$ ($j neq i$)

    • $P_i$ 知道 $y_i = x_i + r_i$ 和 $z_i = yi – r{i-1}$。
    • $P_i$ 将 $ri$ 发送给 $P{i+1}$。
    • $Pi$ 接收 $r{i-1}$。
    • $P_i$ 接收所有 $z_j$。
    • 由于 $ri$ 和 $r{i-1}$ 都是随机数,对于任何 $P_i$ 而言,它获得的任何信息(例如 $y_i, z_i$ 或其他 $z_j$)都无法在不知道对应随机数的情况下反推出其他方的私有输入 $x_j$。例如,如果 $Pi$ 想要知道 $x{i+1}$,它需要知道 $r_{i+1}$ 和 $ri$ (因为 $z{i+1} = (x{i+1} + r{i+1}) – r_i$)。然而,$Pi$ 不知道 $r{i+1}$。
    • 因此,在没有串通的情况下,单个诚实但好奇的参与方无法推断出任何其他方的私有输入。
  • 串通攻击(Collusion)

    • 如果少于 $N-1$ 个参与方串通,他们仍然无法学习到未串通方的私有输入。例如,如果有 $N=3$ 个参与方, $P_1$ 和 $P_2$ 串通。他们知道 $x_1, r_1, r_3$ (来自 $P_1$) 和 $x_2, r_2, r_1$ (来自 $P_2$)。结合这些信息,他们可以确定 $r_1, r_2, r_3$ (因为 $r_1, r_2, r_3$ 都是随机生成的)。
    • 一旦知道所有 $r_j$,他们就可以根据 $z_3 = (x_3 + r_3) – r_2$ 来计算 $x_3 = z_3 – r_3 + r_2 pmod M$。
    • 这意味着,这个简单的环形协议在N-1个参与方串通时,会完全泄露最后一个未串通方的私有输入
    • 因此,该协议仅在少于 N-1 个参与方串通时才是安全的。对于需要抵抗多数或任意数量恶意串通的场景,需要更复杂的MPC协议。

尽管存在串通限制,但这个协议对于理解MPC的基本工作原理和Go语言实现是极好的起点。

Go语言实现细节

我们将使用Go语言构建一个命令行应用程序,每个进程代表一个参与方。参与方之间通过TCP网络进行通信,交换秘密份额。

数据结构设计

为了在参与方之间传递信息,我们需要定义消息结构和参与方结构。

  1. Message 结构体

    package main
    
    import (
        "math/big"
    )
    
    // MessageType 定义了参与方之间发送消息的类型
    type MessageType int
    
    const (
        MsgTypeHello  MessageType = iota // 握手消息 (可选,用于连接建立后的身份确认)
        MsgTypeShareR                    // 发送随机数 r_i
        MsgTypeShareZ                    // 广播最终份额 z_i
        MsgTypeResult                    // (可选) 最终结果通知
    )
    
    // Message 定义了参与方之间交换数据的结构
    type Message struct {
        Type        MessageType      // 消息类型
        SenderID    int              // 发送方ID
        Payload     *big.Int         // 消息负载,使用 big.Int 处理大整数
        RecipientID int              // 接收方ID (用于点对点消息)
    }
    • math/big.Int 是Go标准库中用于处理任意精度整数的类型,这对于密码学计算至关重要,因为我们可能需要处理远超 int64 范围的素数模数。
    • encoding/gob 是Go语言提供的一种高效的序列化/反序列化机制,特别适合在Go程序之间交换结构化数据。
  2. Party 结构体

    package main
    
    import (
        "bufio"
        "crypto/rand" // 用于生成安全的随机数
        "encoding/gob"
        "fmt"
        "log"
        "math/big"
        "net"
        "sync"
        "time"
    )
    
    // Modulus 定义了用于模运算的大素数。在生产环境中,应使用更大、更强的素数。
    // 这里为了演示,使用了一个稍小的素数。
    const (
        Modulus = "999999999999999989" // 一个大素数,小于 2^60
        // 更具密码学强度的大素数例子:
        // Modulus = "2305843009213693951" // 著名的 Mersenne prime (2^61-1)
    )
    
    // Party 代表一个MPC参与方
    type Party struct {
        ID          int
        Input       *big.Int          // 该方的私有输入
        Addr        string            // 该方监听的网络地址
        Peers       map[int]string    // 其他参与方ID到其网络地址的映射
        Listener    net.Listener
        Conns       map[int]net.Conn  // 与其他参与方的网络连接
        mu          sync.Mutex        // 保护并发访问 Conns 和 MPC 状态
        numParties  int               // 总参与方数量
    
        // MPC 协议特定变量
        Mod         *big.Int          // 模数 M
        r_i         *big.Int          // 本方生成的随机数 r_i
        y_i         *big.Int          // x_i + r_i (mod M)
        r_prev      *big.Int          // 从上一个参与方接收的随机数 r_{i-1}
        z_i         *big.Int          // y_i - r_prev (mod M)
        receivedZ   map[int]*big.Int  // 存储从所有其他方接收到的 z_j
        wg          sync.WaitGroup    // 用于等待异步操作完成 (例如接收所有 r_prev 或 z_j)
    }
    
    // NewParty 创建一个新的MPC参与方实例
    func NewParty(id int, input int64, addr string, peers map[int]string, numParties int) *Party {
        mod, ok := new(big.Int).SetString(Modulus, 10)
        if !ok {
            log.Fatalf("Failed to parse Modulus string: %s", Modulus)
        }
        return &Party{
            ID:          id,
            Input:       big.NewInt(input),
            Addr:        addr,
            Peers:       peers,
            Mod:         mod,
            Conns:       make(map[int]net.Conn),
            receivedZ:   make(map[int]*big.Int),
            numParties:  numParties,
        }
    }
    • crypto/rand 提供了一个密码学安全的随机数生成器,这是生成秘密共享随机数 $r_i$ 的关键。
    • net.Listenernet.Conn 用于TCP网络通信。
    • sync.Mutex 用于保护 Party 结构体中的共享状态,防止在并发操作时出现竞态条件。
    • sync.WaitGroup 用于协调多个goroutine的完成,例如等待所有秘密份额的接收。

网络通信实现

每个参与方既是服务器(监听传入连接),又是客户端(主动连接其他参与方)。

  1. 启动监听器 (startListener)

    • 每个 Party 启动一个TCP监听器,接收来自其他 Party 的连接。
    • 每个接受的连接都在一个独立的 goroutine 中处理 (handleConnection),以实现并发。
  2. 连接到其他参与方 (connectToPeers)

    • 每个 Party 尝试连接到 Peers 映射中列出的所有其他 Party
    • 使用 net.Dial 建立连接。为了应对网络启动顺序不确定性,客户端会循环尝试连接,直到成功。
    • 一旦连接建立,也为该连接启动一个独立的 goroutine 来处理传入消息。
  3. 消息处理 (handleConnection, handleMessage)

    • handleConnection 负责从单个TCP连接中读取和解码 Message。使用 gob.NewDecoder 解码数据流。
    • handleMessage 根据 Message.Type 分发处理逻辑。例如,接收到 MsgTypeShareR 时更新 r_prev,接收到 MsgTypeShareZ 时存储 z_j
    • 使用 sync.WaitGroup 来阻塞主协议流程,直到必要的秘密份额被接收。
  4. 发送消息 (SendMessage)

    • 通过查找目标 RecipientID 对应的 net.Conn,使用 gob.NewEncoderMessage 编码并发送出去。

协议流程的Go实现

Party.Run() 方法将协调整个MPC协议的执行:

// Run 启动参与方的操作
func (p *Party) Run() {
    log.Printf("Party %d: Starting at %s with input %s", p.ID, p.Addr, p.Input.String())

    // 1. 启动监听器
    p.startListener()

    // 2. 连接到其他参与方
    p.connectToPeers()

    // 等待所有连接建立。这是为了确保在协议开始前,所有网络通道都已就绪。
    // 在实际生产中,可能需要更健壮的握手协议来确认所有方都已准备好。
    for len(p.Conns) < p.numParties-1 {
        log.Printf("Party %d: Waiting for %d connections... (have %d)", p.ID, p.numParties-1, len(p.Conns))
        time.Sleep(100 * time.Millisecond) // 短暂等待
    }
    log.Printf("Party %d: All %d connections established.", p.ID, len(p.Conns))

    // 3. 执行MPC协议
    p.executeMPCProtocol()

    // 4. 清理资源
    p.teardown()
    log.Printf("Party %d: Finished.", p.ID)
}

// executeMPCProtocol 实现了安全求和协议的逻辑
func (p *Party) executeMPCProtocol() {
    log.Printf("Party %d: Starting MPC protocol.", p.ID)

    // 步骤 1: 每个参与方 P_i 生成 r_i 和 y_i = x_i + r_i (mod M)
    // 使用密码学安全的随机数生成器生成 r_i
    var err error
    p.r_i, err = rand.Int(rand.Reader, p.Mod)
    if err != nil {
        log.Fatalf("Party %d: Failed to generate random number: %v", p.ID, err)
    }
    p.r_i = p.r_i.Mod(p.r_i, p.Mod) // 确保 r_i 在 [0, Mod-1] 范围内

    // 计算 y_i = x_i + r_i (mod M)
    p.y_i = new(big.Int).Add(p.Input, p.r_i)
    p.y_i.Mod(p.y_i, p.Mod)
    log.Printf("Party %d: Generated r_i = %s, computed y_i = %s", p.ID, p.r_i.String(), p.y_i.String())

    // 步骤 2: P_i 将 r_i 发送给 P_{i+1} (环形传递)
    // 确定 r_i 的接收方ID: (当前ID % 总方数) + 1,如果当前是最后一个方,则发给第一个方
    recipientID := (p.ID % p.numParties) + 1
    if p.numParties == 1 { // 特殊情况:只有一方时,自己给自己发 r_i (尽管没有实际意义)
        recipientID = p.ID
    }

    p.wg.Add(1) // 等待接收 r_prev

    log.Printf("Party %d: Sending r_i (%s) to Party %d", p.ID, p.r_i.String(), recipientID)
    if err := p.SendMessage(recipientID, Message{
        Type:      MsgTypeShareR,
        SenderID:  p.ID,
        Payload:   p.r_i,
        RecipientID: recipientID,
    }); err != nil {
        log.Fatalf("Party %d: Failed to send r_i: %v", p.ID, err)
    }

    // 等待从 P_{i-1} 接收到 r_prev
    // 对于只有一个参与者的情况,r_prev 应该等于 r_i
    if p.numParties == 1 {
        p.r_prev = p.r_i // 自己就是上一个方
        p.wg.Done()
    }
    p.wg.Wait() // 阻塞直到 handleMessage 接收到 r_prev 并调用 wg.Done()
    if p.r_prev == nil {
        log.Fatalf("Party %d: Did not receive r_prev. This should not happen.", p.ID)
    }

    // 步骤 3: 每个 P_i 计算 z_i = y_i - r_{i-1} (mod M)
    p.z_i = new(big.Int).Sub(p.y_i, p.r_prev)
    p.z_i.Mod(p.z_i, p.Mod)
    // 确保结果在 [0, M-1] 范围内,因为 Sub 操作可能产生负数
    if p.z_i.Sign() == -1 {
        p.z_i.Add(p.z_i, p.Mod)
    }
    log.Printf("Party %d: Computed z_i = %s (y_i=%s, r_prev=%s)", p.ID, p.z_i.String(), p.y_i.String(), p.r_prev.String())

    // 步骤 4: 所有参与方广播 z_i
    p.mu.Lock()
    p.receivedZ[p.ID] = p.z_i // 将自己的 z_i 也加入到已接收的列表中
    p.mu.Unlock()

    // 等待接收所有其他方的 z_j。我们已经有了自己的 z_i。
    // 所以需要等待 numParties - 1 个外部 z_j 消息。
    if p.numParties > 1 {
        p.wg.Add(1) // 只需要等待一次信号,因为 handleMessage 会在收到所有 z_j 后调用 wg.Done()
    } else { // 只有一个参与者时,不需要等待其他 z_j
        p.wg.Done()
    }

    for peerID := range p.Conns { // 遍历所有连接,广播给每个对等方
        log.Printf("Party %d: Broadcasting z_i (%s) to Party %d", p.ID, p.z_i.String(), peerID)
        if err := p.SendMessage(peerID, Message{
            Type:      MsgTypeShareZ,
            SenderID:  p.ID,
            Payload:   p.z_i,
            RecipientID: peerID,
        }); err != nil {
            log.Printf("Party %d: Failed to broadcast z_i to Party %d: %v", p.ID, peerID, err)
        }
    }

    p.wg.Wait() // 阻塞直到收到所有 z_j

    // 步骤 5: 每个参与方计算最终总和
    finalSum := big.NewInt(0)
    p.mu.Lock() // 保护 receivedZ
    for _, zVal := range p.receivedZ {
        finalSum.Add(finalSum, zVal)
    }
    p.mu.Unlock()
    finalSum.Mod(finalSum, p.Mod)

    log.Printf("Party %d: Final computed sum is %s", p.ID, finalSum.String())
}

main.go 启动程序

main.go 负责解析命令行参数,初始化 Party 实例,并启动其 Run 方法。

package main

import (
    "fmt"
    "log"
    "os"
    "strconv"
    "strings"
    "sync"
    "time"
)

func main() {
    log.SetFlags(log.LstdFlags | log.Lshortfile) // 配置日志输出文件和行号

    if len(os.Args) < 5 {
        fmt.Println("Usage: go run . <party_id> <input_value> <num_parties> <peer_addrs_comma_separated>")
        fmt.Println("Example: go run . 1 10 3 127.0.0.1:8001,127.0.0.1:8002,127.0.0.1:8003")
        os.Exit(1)
    }

    partyID, err := strconv.Atoi(os.Args[1])
    if err != nil {
        log.Fatalf("Invalid Party ID: %v", err)
    }

    inputValue, err := strconv.ParseInt(os.Args[2], 10, 64)
    if err != nil {
        log.Fatalf("Invalid Input Value: %v", err)
    }

    numParties, err := strconv.Atoi(os.Args[3])
    if err != nil {
        log.Fatalf("Invalid Number of Parties: %v", err)
    }

    peerAddrsStr := os.Args[4]
    peerAddrs := strings.Split(peerAddrsStr, ",")
    if len(peerAddrs) != numParties {
        log.Fatalf("Number of peer addresses (%d) must match num_parties (%d)", len(peerAddrs), numParties)
    }

    // 构建 party ID 到地址的映射
    peers := make(map[int]string)
    for i, addr := range peerAddrs {
        peers[i+1] = addr // Party IDs 是从 1 开始的
    }

    // 获取当前 party 自己的地址
    myAddr, ok := peers[partyID]
    if !ok {
        log.Fatalf("Address for Party ID %d not found in peer addresses list", partyID)
    }

    // 从 peer 列表中移除自己的地址,因为不需要连接自己
    peersForConnection := make(map[int]string)
    for id, addr := range peers {
        if id != partyID {
            peersForConnection[id] = addr
        }
    }

    party := NewParty(partyID, inputValue, myAddr, peersForConnection, numParties)

    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()
        party.Run()
    }()

    // 给所有参与方一些时间来启动和建立连接
    // 在生产环境中,可能需要更精确的同步机制
    time.Sleep(2 * time.Second)

    // 等待 party 的 Run 方法完成
    wg.Wait()
    log.Printf("Main: Party %d application exiting.", partyID)
}

运行示例

假设我们有3个参与方,它们的输入分别是10, 20, 30。模数 $M$ 设定为 "999999999999999989"
总和应为 $10 + 20 + 30 = 60$。

您需要打开三个独立的终端窗口,分别运行以下命令:

终端 1 (Party 1, Input 10):

go run main.go 1 10 3 127.0.0.1:8001,127.0.0.1:8002,127.0.0.1:8003

终端 2 (Party 2, Input 20):

go run main.go 2 20 3 127.0.0.1:8001,127.0.0.1:8002,127.0.0.1:8003

终端 3 (Party 3, Input 30):

go run main.go 3 30 3 127.0.0.1:8001,127.0.0.1:8002,127.0.0.1:8003

每个终端最终都会输出类似 Party X: Final computed sum is 60 的日志。

通过上面的代码和运行步骤,我们成功地在Go语言中实现了一个基础的多方安全求和协议。

安全性、挑战与进阶

安全分析回顾

我们实现的协议在诚实但好奇(Semi-honest)模型下,对输入隐私正确性提供保障,但存在以下限制:

  • 输入隐私:每个参与方在协议执行过程中,除了自己的输入和最终结果,无法推断出其他任何方的私有输入。这是通过随机掩码 $r_i$ 和模运算实现的。
  • 正确性:通过数学证明,我们确认了协议最终计算出的总和是所有私有输入的正确和。
  • 串通攻击:如果少于 $N-1$ 个参与方串通,他们无法学习到未串通方的私有输入。然而,如果 $N-1$ 个参与方串通,他们可以联合所有已知信息来推导出最后一个未串通方的私有输入。例如,在3方协议中,P1和P2串通,可以计算出P3的输入。这是该简单协议的一个固有局限性。

| 特性 | 描述 “`


        package main

        import (
            "log"
            "net"
            "time"
            "sync"
            "fmt"
            "encoding/gob"
            "crypto/rand"
            "math/big"
        )

        // Modulus for arithmetic operations.
        // For illustration, a smaller prime is used. In production, use much larger.
        const (
            Modulus = "999999999999999989" // A large prime number
        )

        // MessageType defines the type of message being sent between parties
        type MessageType int

        const (
            MsgTypeHello MessageType = iota // Optional: for initial handshake
            MsgTypeShareR                   // Sending r_i (random share)
            MsgTypeShareZ                   // Broadcasting z_i (final share)
            MsgTypeResult                   // Optional: for final result notification
        )

        // Message defines the structure of data exchanged between parties
        type Message struct {
            Type        MessageType
            SenderID    int
            Payload     *big.Int // Using big.Int for cryptographic values
            RecipientID int      // Optional: for direct messages
        }

        // Party represents an MPC participant
        type Party struct {
            ID          int
            Input       *big.Int
            Addr        string
            Peers       map[int]string // Map of PartyID to Address of other parties
            Listener    net.Listener
            Conns       map[int]net.Conn // Connections to other parties
            mu          sync.Mutex

            // MPC specific variables
            Mod         *big.Int
            r_i         *big.Int // Random share generated by this party
            y_i         *big.Int // x_i + r_i (mod M)
            r_prev      *big.Int // r from previous party in the ring
            z_i         *big.Int // y_i - r_prev (mod M)
            receivedZ   map[int]*big.Int // All z_j values received from other parties
            wg          sync.WaitGroup
            numParties  int
        }

        // NewParty creates a new MPC party
        func NewParty(id int, input int64, addr string, peers map[int]string, numParties int) *Party {
            mod, ok := new(big.Int).SetString(Modulus, 10)
            if !ok {
                log.Fatalf("Failed to parse Modulus string: %s", Modulus)
            }
            return &Party{
                ID:          id,
                Input:       big.NewInt(input),
                Addr:        addr,
                Peers:       peers,
                Mod:         mod,
                Conns:       make(map[int]net.Conn),
                receivedZ:   make(map[int]*big.Int),
                numParties:  numParties,
            }
        }

        // Run starts the party's operations, coordinating network and MPC protocol.
        func (p *Party) Run() {
            log.Printf("Party %d: Starting at %s with input %s", p.ID, p.Addr, p.Input.String())

            // 1. Start listening for incoming connections
            p.startListener()

            // 2. Connect to other parties
            p.connectToPeers()

            // Wait for all connections to be established. This is a basic synchronization.
            // In a production system, a more robust handshake or leader-election might be needed.
            for len(p.Conns) < p.numParties-1 {
                log.Printf("Party %d: Waiting for %d connections... (have %d)", p.ID, p.numParties-1, len(p.Conns))
                time.Sleep(100 * time.Millisecond)
            }
            log.Printf("Party %d

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注