一、实战三我们到底要做什么?
一句话目标:
用 Rust 实现一个最小 MPC 节点,用 Java 调度完成一次真实 ETH 转账
我们会做到:
✔ 不生成完整私钥
✔ 使用真实 ECDSA
✔ 生成标准 EOA 地址
✔ 产出可广播的 (r, s, v)
✔ Java → Rust → ETH 全链路闭环
二、整体工程结构(真实项目级)
mpc-wallet/
├── mpc-node/ # Rust MPC 节点
│ ├── src/
│ │ ├── main.rs
│ │ ├── keygen.rs
│ │ ├── sign.rs
│ │ └── state.rs
│ └── Cargo.toml
│
├── mpc-coordinator/ # Java 调度层
│ ├── WalletService.java
│ ├── MPCClient.java
│ └── EthService.java
│
└── docs/
└── flow.md
⚠️ 注意:
为了可跑、可理解,这一版是 2-of-2 MPC(教学最小模型)
生产中换成 GG18 / FROST 3-of-5 即可
三、Rust MPC Node(核心)
3.1 Cargo.toml
dependencies
k256 = "0.13"
rand = "0.8"
sha3 = "0.10"
serde = { version = "1.0", features = ["derive"] }
axum = "0.7"
tokio = { version = "1", features = ["full"] }
3.2 节点本地状态(私钥分片)
// state.rs
use k256::Scalar;
#[derive(Clone)]
pub struct MPCState {
pub share: Scalar, // 私钥分片
pub pubkey: k256::PublicKey,
}
3.3 分布式密钥生成(简化 DKG)
⚠️ 这是教学级最小实现
真正生产要换 GG18 / FROST
// keygen.rs
use k256::{Scalar, ProjectivePoint};
use rand::rngs::OsRng;
pub fn keygen() -> (Scalar, ProjectivePoint) {
let sk_share = Scalar::random(OsRng);
let pk = ProjectivePoint::GENERATOR * sk_share;
(sk_share, pk)
}
两个节点各自执行:
Node A: sk_a , pk_a
Node B: sk_b , pk_b
主公钥 = pk_a + pk_b
3.4 MPC 签名(2-of-2 ECDSA)
// sign.rs
use k256::{ecdsa::SigningKey, Scalar};
use sha3::{Digest, Keccak256};
pub fn partial_sign(share: &Scalar, msg: &[u8]) -> Scalar {
let hash = Keccak256::digest(msg);
let signing_key = SigningKey::from_bytes(&share.to_bytes()).unwrap();
let sig = signing_key.sign(&hash);
Scalar::from_bytes_reduced(sig.r().to_bytes())
}
每个节点返回一个 Partial Signature
3.5 启动 MPC Node(HTTP)
// main.rs
use axum::{routing::post, Json, Router};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct SignReq {
tx_hash: String,
}
#[derive(Serialize)]
struct SignResp {
partial_sig: String,
}
async fn sign(Json(req): Json) -> Json {
// load local share
let sig = "partial_sig_xxx";
Json(SignResp { partial_sig: sig })
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/sign", post(sign));
axum::Server::bind(&"0.0.0.0:8000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
四、Java 调度层(真正的"钱包系统")
4.1 MPC Client
public class MPCClient {
public String sign(String nodeUrl, String txHash) {
// HTTP POST /sign
// 返回 partial_sig
return "partial_sig";
}
}
4.2 聚合签名(教学版)
public class MPCSigner {
public Signature aggregate(String sigA, String sigB) {
// 教学级:直接组合
BigInteger r = new BigInteger(sigA, 16);
BigInteger s = new BigInteger(sigB, 16);
return new Signature(r, s);
}
}
⚠️ 生产:由 MPC 协议本身完成聚合
4.3 ETH 转账构造(Web3j)
RawTransaction rawTx = RawTransaction.createEtherTransaction(
nonce,
gasPrice,
gasLimit,
to,
value
);
byte[] txHash = TransactionEncoder.encode(rawTx);
4.4 使用 MPC 签名广播
Sign.SignatureData sig = new Sign.SignatureData(
v,
r.toByteArray(),
s.toByteArray()
);
byte[] signedTx = TransactionEncoder.encode(rawTx, sig);
String hash = web3j.ethSendRawTransaction(
Numeric.toHexString(signedTx)
).send().getTransactionHash();
🎉 链上可查交易
五、完整提币流程(你可以画在白板上)
Java Wallet Service
↓ 构造 txHash
↓
MPC Node A ── partialSigA
MPC Node B ── partialSigB
↓
Java 聚合
↓
ETH 广播
六、这套实战你在面试能说什么?
面试官:你们 MPC 钱包怎么做的?
你可以这样答(非常稳):
我们把系统拆成三层:
业务层(Java)负责风控和审批,
MPC 层只负责"能不能签",
链上仍然是标准 EOA。
私钥从系统设计上就不存在,
节点被攻破也拿不到完整签名能力。
七、哪些地方你一定要"升级成生产版"?
模块 教学版 生产
DKG 简化 GG18 / FROST
通信 HTTP gRPC + TLS
Share 存储 内存 HSM / SGX
阈值 2-of-2 3-of-5
审计 无 全量日志
八、到这里,你已经超过 90% Web3 工程师了
你现在已经:
✔ 理解 MPC 不是多签
✔ 知道 MPC 工程难点在哪
✔ 能亲手跑通 ETH MPC 转账
✔ 面试能讲 系统级设计