在 Rust 中实现零知识多方计算

在本教程中,我们将使用 Rust 和多方计算的概念实现一个简化的类似 zk-rollup 的系统。

该系统涉及三个主要组件:用于提交交易的用户界面、用于批量交易并生成证明的证明器(或操作器)以及检查这些证明的验证器(类似于主网)。

重点是确保这些组件之间的安全和高效通信,处理挑战和响应,以及为用户交互构建简单的终端用户界面(TUI)。

让我们开始吧! 🦀

多方计算(MPC)

多方计算 (MPC) 是密码学的一个子领域,它使多方能够根据其输入联合计算函数,同时保持这些输入的私密性。 MPC 的基本目标是确保在计算过程中,任何一方对另一方输入的了解不会多于从输出中推断出的信息。

核心原则

  • 隐私:各方的输入数据都是保密的,即使对于计算中的其他参与者也是如此。
  • 正确性:计算结果是正确的、可验证的,就像是由受信任的第三方计算的一样。
  • 独立性:任何一方都无法在自己输入的影响之外控制计算结果。

MPC的技术实现涉及几个关键组件和步骤:

  • 安全函数评估 (SFE):要计算的函数以允许安全评估的方式表示,通常使用加密原语。这可以包括将功能表示为逻辑门电路。
  • 秘密共享:输入被分成"份额",并在各方之间分配。每个份额本身没有意义,但总的来说,这些份额可以重建原始输入。
  • 股份计算:双方签订协议来计算其股份的函数。这通常涉及交换包含从共享中派生的加密或模糊数据的消息。
  • 结果重构:计算结束后,各方将各自的份额合并起来,得到最终的输出。该协议确保此步骤不会泄露有关各个输入的附加信息。

在本教程中,我们将探索简化的类似 zk-rollup 系统的基本概念和实际实现,在概念上与多方计算 (MPC) 相似。尽管我们的重点不是明确地放在 MPC 上,但隐私、正确性和协作计算的基本原则是这两个领域的核心。

实现证明器(操作器)

证明器首先侦听来自打算提交交易的用户的传入连接。这是通过创建绑定到特定端口的 TcpListener 来实现的:

ba 复制代码
let listener = TcpListener::bind("localhost:7878").expect("Could not bind to port 7878");

println!("Operator listening on port 7878");

This code snippet sets up the Prover to listen on port 7878, ready to accept transaction data from users.
此代码片段将 Prover 设置为侦听端口 7878,准备好接受来自用户的交易数据。

收集交易
当用户连接并提交交易时,证明器使用 BufReader 读取此数据,从而有效地管理流的数据:

ba 复制代码
for stream in listener.incoming() {
    let stream = stream.expect("Failed to accept incoming connection");

    println!("User connected");

    let mut reader = BufReader::new(stream);
   
    let mut transaction_data = String::new();
    
    reader.read_line(&mut transaction_data).expect("Failed to read from user");
    // Process transaction data...
}

该循环等待用户连接,读取每个用户发送的交易数据。为了简单起见,这里使用 read_line 方法,假设每个事务或一批事务以换行符结尾。

生成证明

对于每笔交易,证明器都会生成一个哈希值,模拟证明生成过程的一部分。然后将这些哈希值组合起来创建一个"证明"哈希值:

ba 复制代码
let transaction_hashes: Vec<String> = transactions.iter().map(|tx| {
    let tx_json = serde_json::to_string(tx).unwrap();
    let mut hasher = Sha256::new();
    hasher.update(tx_json.as_bytes());
    encode(hasher.finalize())
}).collect();

let proof = generate_merkle_root(&transaction_hashes);

此代码迭代每个事务,将其序列化为 JSON,计算其 SHA-256 哈希值,并收集这些哈希值。 generate_merkle_root 函数(此处未显示)通常会组合这些哈希值以生成表示证明的单个哈希值。

与验证器沟通

生成证明后,证明器连接到验证器并发送交易哈希值和证明:

ba 复制代码
​
let transaction_hashes: Vec<String> = transactions.iter().map(|tx| {
    let tx_json = serde_json::to_string(tx).unwrap();
    let mut hasher = Sha256::new();
    hasher.update(tx_json.as_bytes());
    encode(hasher.finalize())
}).collect();

let proof = generate_merkle_root(&transaction_hashes);

​

此代码片段建立与验证器的连接(假设正在侦听端口 7879),将交易哈希值和证明打包到 JSON 对象中,将其序列化,然后通过 TCP 流发送。

Handling the Verifier's Challenge

应对验证器的挑战

最后,证明器侦听来自验证器的质询,以请求的数据进行响应,并关闭连接:

ba 复制代码
let mut challenge = String::new();

reader.read_line(&mut challenge).expect("Failed to read challenge from verifier");

let challenge_index: usize = challenge.trim().parse().expect("Failed to parse challenge");

let response = transaction_hashes[challenge_index].clone();

verifier_stream.write_all(response.as_bytes()).expect("Failed to respond to challenge");

在此阶段,证明器读取验证器发出的质询(为简单起见,我们可以假设它是请求特定交易哈希的索引),从 transaction_hashes 检索相应的哈希,并将该哈希发送回作为回应。

实施验证器

探讨验证器在简化的类似 zk-rollup 的系统中的作用,并辅以相关代码片段来说明实现细节。

启动验证器

验证器首先设置一个 TcpListener 来监听来自证明器的传入连接,指示新一批交易的到达以及相应的证明:

ba 复制代码
let listener = TcpListener::bind("localhost:7879").expect("Could not bind to port 7879");

println!("Mainnet listening on port 7879");

此代码片段建立验证器侦听端口 7879,准备接收来自证明器的数据。

接收证明和交易哈希

连接后,验证器使用 BufReader 读取传输的证明和交易摘要,以实现高效的数据处理:

ba 复制代码
for stream in listener.incoming() {

    let stream = stream.expect("Failed to accept incoming connection");
    
    println!("Operator connected");

    let mut reader = BufReader::new(stream);
    
    let mut proof_data_string = String::new();
    
    reader.read_line(&mut proof_data_string).expect("Failed to read from stream");
    
    let proof_data: Value = serde_json::from_str(&proof_data_string).expect("Failed to parse proof data");
    // Further processing...
}

该循环侦听来自证明器的连接并读取传输的 JSON 数据,其中包括交易哈希值和生成的证明。

发起质疑挑战

然后验证器向证明器发出挑战。这通常涉及请求特定信息来验证证明,例如特定的交易哈希:

ba 复制代码
let challenge = thread_rng().gen_range(0..transaction_summary.len());

write!(stream, "{}\n", challenge).expect("Failed to send challenge to operator");

此代码片段选择一个随机交易哈希索引作为质询,并将该索引发送回证明器,期望证明器以相应的交易哈希进行响应。

验证响应

收到证明工具的响应后,验证工具将提供的交易哈希与其记录进行比较以验证证明:

ba 复制代码
let mut response = String::new();

reader.read_line(&mut response).expect("Failed to read response from operator");

let response = response.trim_end();

let verified = transaction_summary.get(challenge).map_or(false, |expected_hash| {
    expected_hash.trim_matches('"') == response
});

在这里,验证器读取证明器的响应并修剪任何潜在的换行符。验证涉及将接收到的哈希(响应)与交易摘要中的预期哈希进行比较。如果它们匹配,则该证明被认为是有效的。

记录和最终确定

最后,验证工具记录验证过程的结果,并根据结果采取适当的操作:

ba 复制代码
if verified {
    println!("Verification Success: Transactions committed to the blockchain.");
} else {
    println!("Verification Failed: Invalid proof. Transactions not committed.");
}

此结论代码记录验证是否成功或失败。成功的验证意味着交易是有效的并且可以"提交"到区块链。相反,失败表明证明或批量交易存在问题,从而阻止了它们的承诺。

整合到一起

prover.rs

ba 复制代码
use std::net::{TcpListener, TcpStream};
use std::io::{BufRead, BufReader, Write};
use serde::{Serialize, Deserialize};
use serde_json;
use sha2::{Digest, Sha256};
use hex::encode;

#[derive(Serialize, Deserialize, Debug)]
struct Transaction {
    from: String,
    to: String,
    amount: u64,
}

fn main() {
    let listener = TcpListener::bind("localhost:7878").expect("Could not bind to port 7878");
    println!("Operator listening on port 7878");

    loop {
        // Accepting transactions from users
        let (user_stream, _) = listener.accept().expect("Failed to accept user connection");
        println!("User connected");

        let mut user_reader = BufReader::new(user_stream);
        let mut transactions = Vec::new();
        let mut line = String::new();

        // Read transactions from the user
        while user_reader.read_line(&mut line).expect("Failed to read from user") > 0 {
            if let Ok(tx) = serde_json::from_str::<Transaction>(&line) {
                transactions.push(tx);
            }
            line.clear();
        }

        if !transactions.is_empty() {
            // Process transactions and generate proof
            let transaction_hashes = generate_transaction_hashes(&transactions);
            let proof = generate_merkle_root(&transaction_hashes);

            // Connect to the verifier and send proof along with transaction hashes
            let mut verifier_stream = TcpStream::connect("localhost:7879").expect("Could not connect to verifier");
            let proof_data = serde_json::json!({
                "transaction_summary": transaction_hashes,
                "proof": proof
            });

            let serialized = serde_json::to_string(&proof_data).unwrap();
            verifier_stream.write_all(serialized.as_bytes()).expect("Failed to write to verifier stream");
            verifier_stream.write_all(b"\n").expect("Failed to write newline to verifier stream");
            println!("Proof and transaction hashes sent to verifier.");

            // Listen for a challenge from the verifier and respond
            let mut verifier_reader = BufReader::new(verifier_stream);
            let mut challenge = String::new();
            verifier_reader.read_line(&mut challenge).expect("Failed to read challenge from verifier");
            let challenge: usize = challenge.trim().parse().expect("Failed to parse challenge");

            if challenge < transaction_hashes.len() {
                println!("Sending response hash: {}", transaction_hashes[challenge]);
                verifier_reader.get_mut().write_all(transaction_hashes[challenge].as_bytes()).expect("Failed to respond to challenge");
                verifier_reader.get_mut().write_all(b"\n").expect("Failed to write newline after challenge response");
            }
        }
    }
}

fn generate_transaction_hashes(transactions: &[Transaction]) -> Vec<String> {
    transactions.iter().map(|tx| {
        let tx_json = serde_json::to_string(tx).unwrap();
        let mut hasher = Sha256::new();
        hasher.update(tx_json.as_bytes());
        encode(hasher.finalize())
    }).collect()
}

fn generate_merkle_root(transaction_hashes: &[String]) -> String {
    let concatenated_hashes = transaction_hashes.concat();
    let mut hasher = Sha256::new();
    hasher.update(concatenated_hashes.as_bytes());
    encode(hasher.finalize())
}

user_tui.rs

ba 复制代码
use cursive::views::{Dialog, EditView, LinearLayout, TextView};
use cursive::{Cursive, CursiveExt};
use std::net::TcpStream;
use std::io::Write;
use cursive::traits::Resizable;
use serde_json::json;
use cursive::view::Nameable;
fn main() {
    let mut siv = Cursive::default();

    siv.add_layer(
        Dialog::new()
            .title("Send Transaction")
            .content(
                LinearLayout::vertical()
                    .child(TextView::new("From:"))
                    .child(EditView::new().with_name("from").fixed_width(20))
                    .child(TextView::new("To:"))
                    .child(EditView::new().with_name("to").fixed_width(20))
                    .child(TextView::new("Amount:"))
                    .child(EditView::new().with_name("amount").fixed_width(20)),
            )
            .button("Send", |s| {
                let from = s.call_on_name("from", |v: &mut EditView| v.get_content()).unwrap();
                let to = s.call_on_name("to", |v: &mut EditView| v.get_content()).unwrap();
                let amount = s.call_on_name("amount", |v: &mut EditView| v.get_content()).unwrap();

                if let Ok(amount) = amount.parse::<u64>() {
                    send_transaction(&from, &to, amount);
                    s.add_layer(Dialog::info("Transaction sent!"));
                } else {
                    s.add_layer(Dialog::info("Invalid amount!"));
                }
            })
            .button("Quit", |s| s.quit()),
    );

    siv.run();
}

fn send_transaction(from: &str, to: &str, amount: u64) {
    let transaction = json!({
        "from": from,
        "to": to,
        "amount": amount
    });

    if let Ok(mut stream) = TcpStream::connect("localhost:7878") {
        let serialized = serde_json::to_string(&transaction).unwrap() + "\n";
        if let Err(e) = stream.write_all(serialized.as_bytes()) {
            eprintln!("Failed to send transaction: {}", e);
        }
    } else {
        eprintln!("Could not connect to prover");
    }
}

更新后的 Cargo.toml:

ba 复制代码
[package]
name = "rust-mpc"
version = "0.1.0"
edition = "2021"
authors = ["Luis Soares"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sha2 = "0.10.2"
hex = "0.4.3"
rand = "0.8.5"
cursive = "0.20.0"

[[bin]]
name = "prover"
path = "src/prover.rs"

[[bin]]
name = "verifier"
path = "src/verifier.rs"

[[bin]]
name = "user_tui"
path = "src/user_tui.rs"

现在来测试一下

1.启动验证器

首先启动验证器,它监听来自证明器的证明和交易摘要,并发出验证挑战。

  • 打开终端窗口。
  • 导航到您的项目目录。
  • 使用 Cargo 启动验证器:
ba 复制代码
cargo run --bin verifier
  • Keep this terminal open to monitor the Verifier's activity and outputs.
    保持该终端打开以监控验证器的活动和输出。

2.启动证明器

当验证器运行时,启动证明器,它聚合来自用户的交易,生成证明,并与验证器交互以进行验证。

  • 打开一个新的终端窗口。
  • 导航到您的项目目录。
  • 运行证明器:
ba 复制代码
cargo run --bin prover
  • 监控该终端以观察证明器的操作,包括接收用户的交易以及与验证器的通信。

3. 使用用户界面发送交易

如果您已经实现了用户界面(例如前面讨论的 TUI),请启动它以开始提交交易。如果 TUI 不可用,您可以通过使用 nc (netcat) 等工具手动将 JSON 格式的交易数据发送到 Prover 来模拟用户交易。

  • 打开用户界面的另一个终端窗口。
  • 如果使用 TUI,请使用以下命令启动它:
ba 复制代码
cargo run --bin user_tui
  • 如果手动发送交易,请使用 nc 连接到证明器并输入交易数据:
ba 复制代码
nc localhost 7878 
{"from":"Alice","to":"Bob","amount":100} 
{"from":"Charlie","to":"Dave","amount":50}
  • 每次交易后按 Enter ,然后使用 Ctrl+D (Linux/Mac) 或 Ctrl+Z ,然后使用 Enter (Windows) 结束传输。

观察互动

  • 在验证器的终端上:观察来自证明器的连接、传入的交易哈希值和证明、发出的质询以及验证过程的结果。
  • 在证明器的终端上:查找来自用户的连接、传入交易、证明生成过程、与验证器的通信、收到的质询以及发送的响应。
  • 在用户界面终端上:如果使用 TUI,请按照提示输入和发送交易。如果手动发送交易,请确保 JSON 数据格式正确。

Github 项目地址:luishsr/rust-mpc: A Rust Multi-party Computation example (github.com)

相关推荐
爱上电路设计3 小时前
有趣的算法
开发语言·c++·算法
studyForMokey3 小时前
kotlin 函数类型接口lambda写法
android·开发语言·kotlin
2401_858120263 小时前
探索sklearn文本向量化:从词袋到深度学习的转变
开发语言·python·机器学习
与墨学长4 小时前
Rust破界:前端革新与Vite重构的深度透视(中)
开发语言·前端·rust·前端框架·wasm
虫小宝4 小时前
Java中的软件架构重构与升级策略
java·开发语言·重构
软件工程小施同学5 小时前
区块链可投会议CCF C--TrustCom 2024 截止9.1 附去年录用文章
区块链·区块链会议·区块链论文·区块链投稿
jerry-895 小时前
MySql中每行多值属性的计数值
大数据·数据库·mysql
CTGU_daffodil5 小时前
matlab 绘制高等数学中的二维函数示例
开发语言·matlab
立秋67895 小时前
使用Python绘制堆积柱形图
开发语言·python
逸群不凡5 小时前
C++|哈希应用->布隆过滤器
开发语言·数据结构·c++·算法·哈希算法