在本教程中,我们将使用 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.");
}
此结论代码记录验证是否成功或失败。成功的验证意味着交易是有效的并且可以"提交"到区块链。相反,失败表明证明或批量交易存在问题,从而阻止了它们的承诺。
整合到一起
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)