多方安全计算(Multi-Party Computation,MPC)是现代密码学领域的一项突破性技术,它允许多个参与方在不泄露各自私有数据的前提下,共同计算一个约定的函数。想象一下,三家公司希望计算它们的平均利润,但任何一家公司都不愿意透露自己的具体利润额;或者,几家医院想要联合分析某种疾病的治疗效果,却不能共享患者的敏感病历数据。在这些场景中,MPC提供了一个完美的解决方案。
Go语言以其并发特性、高性能和简洁的语法,成为实现网络协议和分布式系统的理想选择。本讲座将深入探讨如何利用Go语言构建一个基础的多方安全计算协议,旨在帮助您理解MPC的核心原理,并掌握在Go中实现这些原理的实践技巧。我们将从MPC的理论基础讲起,逐步深入到协议设计、Go语言实现细节,并对所实现的协议进行安全分析,最后展望MPC的未来发展和挑战。
多方安全计算:在隐私与协作之间架桥
MPC的定义与核心价值
多方安全计算(MPC),有时也称为安全多方计算(Secure Multi-Party Computation),由姚期智教授在1980年代首次提出。其核心思想是,一组互不信任的参与方,各自拥有私有输入数据,他们希望协同计算某个函数的结果,同时确保以下两个关键安全目标:
- 输入隐私(Input Privacy):任何一方,包括协议执行中的中间结果,都不能泄露其他参与方的私有输入数据。
- 正确性(Correctness):协议最终计算出的结果必须是正确的,如同所有参与方将数据明文汇集到一起,由一个可信第三方计算得到的结果一样。
MPC的出现,打破了传统数据分析和协作模式中隐私与效用之间的二元对立。在许多敏感数据场景下,例如金融交易、医疗健康、政务数据共享、隐私保护型人工智能训练、安全投票和拍卖等,MPC提供了在法律合规和道德约束下进行数据协作的可能。
MPC的底层密码学原语
要实现MPC,需要依赖一系列底层的密码学原语。它们是构建安全计算协议的基石:
-
秘密共享(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$。每个份额本身不泄露任何信息。
-
同态加密(Homomorphic Encryption, HE):
- 同态加密允许对加密数据直接进行计算,而无需先解密。这意味着你可以对密文执行加法或乘法操作,得到的结果仍然是加密的,并且解密后与对明文执行相同操作的结果一致。
- 部分同态加密(PHE)支持无限次加法或有限次乘法(如Paillier加密)。
- 全同态加密(FHE)支持任意次数的加法和乘法(即图灵完备的计算),这是密码学领域的“圣杯”之一,但计算开销巨大。
- HE在MPC中可以用于构建两方或多方协议,尤其是在计算复杂函数时。
-
混淆电路(Garbled Circuits, GC):
- 混淆电路是姚期智教授在1982年提出的,主要用于两方安全计算。它将一个布尔电路(表示要计算的函数)转化为一个“混淆”形式。双方通过交换加密的真值表和标签,在不暴露输入或中间结果的情况下共同评估电路。
- GC可以泛化到多方,但实现和通信复杂度会增加。
安全模型
在设计和分析MPC协议时,我们必须明确假设参与方的行为模式,这被称为“安全模型”:
-
诚实但好奇模型(Honest-but-Curious, HBU / Semi-honest):
- 这是最常用的安全模型。在该模型下,所有参与方都严格遵守协议的每一步,不会故意偏离协议流程。
- 然而,他们是“好奇的”,会尝试从协议执行过程中获取到的所有信息(包括自己输入、输出、中间计算结果以及收到的所有消息)中推断出其他方的私有输入。
- 该模型下的协议通常更容易设计和实现,计算效率也更高。
-
恶意模型(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$。
-
预设阶段:
- 所有参与方共同协商并同意一个足够大的素数模数 $M$。所有的计算都将在模 $M$ 的有限域 $mathbb{Z}_M$ 中进行。这个 $M$ 必须足够大,以防止计算溢出,并确保随机数具有足够的熵。通常,我们会选择一个比所有可能的输入总和都要大的素数。
-
秘密分享与掩码阶段:
- 每个参与方 $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$)。
-
接收与计算阶段:
- 每个参与方 $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$。
-
结果揭示阶段:
- 所有参与方 $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网络进行通信,交换秘密份额。
数据结构设计
为了在参与方之间传递信息,我们需要定义消息结构和参与方结构。
-
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程序之间交换结构化数据。
-
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.Listener和net.Conn用于TCP网络通信。sync.Mutex用于保护Party结构体中的共享状态,防止在并发操作时出现竞态条件。sync.WaitGroup用于协调多个goroutine的完成,例如等待所有秘密份额的接收。
网络通信实现
每个参与方既是服务器(监听传入连接),又是客户端(主动连接其他参与方)。
-
启动监听器 (
startListener):- 每个
Party启动一个TCP监听器,接收来自其他Party的连接。 - 每个接受的连接都在一个独立的
goroutine中处理 (handleConnection),以实现并发。
- 每个
-
连接到其他参与方 (
connectToPeers):- 每个
Party尝试连接到Peers映射中列出的所有其他Party。 - 使用
net.Dial建立连接。为了应对网络启动顺序不确定性,客户端会循环尝试连接,直到成功。 - 一旦连接建立,也为该连接启动一个独立的
goroutine来处理传入消息。
- 每个
-
消息处理 (
handleConnection,handleMessage):handleConnection负责从单个TCP连接中读取和解码Message。使用gob.NewDecoder解码数据流。handleMessage根据Message.Type分发处理逻辑。例如,接收到MsgTypeShareR时更新r_prev,接收到MsgTypeShareZ时存储z_j。- 使用
sync.WaitGroup来阻塞主协议流程,直到必要的秘密份额被接收。
-
发送消息 (
SendMessage):- 通过查找目标
RecipientID对应的net.Conn,使用gob.NewEncoder将Message编码并发送出去。
- 通过查找目标
协议流程的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