MPC 钱包安全

Fystack 使用 多方计算(MPC) 消除私钥管理中的单点故障。任何一方都不会持有完整的私钥。密钥分片分布在多个节点上,签名则通过门限密码学协同计算得出。

本文介绍 Fystack 的 MPC 实现如何通过分布式信任、安全通信和可验证计算来保护钱包密钥。

Fystack MPC 钱包安全架构
Click to open the full-size diagram

节点职责

每个节点:

  • 仅在本地存储 自己的加密分片。
  • 通过 Ed25519 验证 和 ECDH 保护的 P2P 通信参与门限签名。
  • 即使被攻破,也无法重建或导出完整密钥。

属性

说明

机密性

任何单个节点或运维人员都无法访问完整私钥。

完整性

所有节点间消息均通过 Ed25519 或 Cloud KMS 签名进行认证。

韧性

支持 n 个节点、门限为 t 的配置,最多可容忍 (t-1) 个节点故障。

静态加密

密钥分片在本地以 AES 加密存储。

传输加密

通过 NATS TLS 以及 P2P 通道上的 ECDH 加密层强制实施。


1. 概述

多方计算让多个参与方能够共同执行密码学运算,而任何一方都无法访问完整私钥。Fystack 实现了门限签名方案,必须有最少数量(门限)的参与方协作,才能生成有效签名。

组件

说明

MPC 节点

至少由 3 个节点组成一个集群。

门限(t)

即使最多 (t-1) 个节点被攻破,钱包依然安全。

密钥分片

私钥在数学上被拆分为多个分片。

分片加密

每个分片均使用 AES 加密(BadgerDB 默认加密)。

消息安全

双层保护:NATS TLS 加上用于 P2P 加密的 ECDH 密钥交换。

消息认证

Apex 使用签名消息(Ed25519 或 Cloud KMS)与 MPC 集群通信。

主要优势

  • 无单点失陷: 单个节点被攻破不会暴露私钥
  • 分布式信任: 必须由多个独立参与方协作
  • 对区块链透明: 输出的签名是标准的 ECDSA/EdDSA 签名
  • 动态门限: 可根据交易金额调整签名要求
  • 审计记录: 每个签名会话都有日志记录且可追溯

2. 分布式密钥生成(DKG)

Fystack 使用 分布式密钥生成 创建钱包密钥,任何一方都不会持有完整私钥。

DKG 流程

阶段 1:承诺

  • 每个参与方生成一个 t-1 次的随机多项式(t 为门限)
  • 各参与方广播其多项式的承诺
  • 使用 Feldman VSS(可验证秘密共享)验证承诺

阶段 2:分片分发

  • 每个参与方在特定点上计算其多项式的值
  • 通过加密通道将分片安全地发送给对应的参与方
  • 每个参与方都会从其他每个参与方各收到一个分片

阶段 3:验证

  • 各参与方根据广播的承诺验证收到的分片
  • 识别无效分片并提出投诉
  • 如果检测到过多无效分片,协议将中止

阶段 4:密钥构建

  • 每个参与方组合有效分片,形成自己的密钥分片
  • 根据承诺计算出公钥
  • 任何一方都不知道完整私钥

DKG 配置

yaml
mpc_threshold: 2   # Number of MPC nodes required to sign

mpc_threshold 表示 n-of-t 方案中的门限 t。在有 n 个活跃 MPC 节点时,系统强制要求 2 ≤ t ≤ n,在最多 (t - 1) 个节点故障或离线的情况下仍可生成签名。例如,n = 3 且 mpc_threshold: 2 时构成一个 2-of-3 集群:任意两个经过认证的节点即可签名,但攻破单个节点不足以恢复私钥。

对于 GG18/GG20 风格的鲁棒 MPC 协议,我们还建议 t > n / 2,以保证拜占庭容错:即使 f = n - t 个节点存在恶意、离线或合谋,诚实节点仍占多数,可以阻止伪造签名或私钥重建。

安全特性

  • 鲁棒性: 最多可容忍 n-t 个恶意参与方
  • 可验证性: 所有参与方都可以验证分片的正确性
  • 隐私性: 不会泄露任何关于私钥的信息
  • 非交互性: 完成初始设置后无需再次执行 DKG

3. 门限签名

门限签名允许(满足门限的)部分参与方协同生成有效签名,而无需重建私钥。

签名协议(ECDSA)

第 1 轮:承诺阶段

生成承诺

pseudo
// Each signer generates commitment
const k_i = randomScalar()
const gamma_i = randomScalar()
const commitment_i = {
  K_i: k_i * G,           // Ephemeral public key
  Gamma_i: gamma_i * G,   // Blinding factor
}
broadcastCommitment(commitment_i)

第 2 轮:生成部分签名

部分签名

pseudo
// After receiving all commitments
const R = combineCommitments(allCommitments)
const r = R.x  // x-coordinate of combined point

// Each party computes partial signature
const s_i = k_i + r * keyShare_i * hash(message)
broadcastPartialSignature(s_i)

第 3 轮:签名聚合

组合签名

pseudo
// Combine partial signatures
const s = lagrangeInterpolation(partialSignatures)
const signature = { r, s }

// Verify signature
const valid = ecdsaVerify(signature, publicKey, message)

签名工作流程

步骤

操作

安全检查

  1. 会话初始化

使用交易详情创建签名会话

验证会话 ID 的唯一性

  1. 选择参与方

选择满足门限数量的参与方

验证参与方在线且已通过认证

  1. 承诺轮次

交换临时密钥的承诺

校验承诺的格式和新鲜度

  1. 部分签名

每个参与方计算并分享部分签名

验证部分签名的有效性

  1. 聚合

将部分签名组合成最终签名

使用公钥验证最终签名

  1. 广播

将已签名的交易提交到区块链

确认交易已被接受


4. 网络安全

NATS 传输(TLS)

MPC 节点通过 config.yaml 中声明的共享 NATS 总线交换流量。生产部署需要 TLS endpoint 以及锁定的客户端证书:

NATS TLS 配置

yaml
nats:
  url: tls://nats.example.com:4222
  username: ""
  password: ""
  tls:
    client_cert: "/etc/mpcium/certs/client-cert.pem"
    client_key: "/etc/mpcium/certs/client-key.pem"
    ca_cert: "/etc/mpcium/certs/rootCA.pem"

如果没有上述 tls:// URL 和 nats.tls.* 路径,deployments/systemd/setup-config.sh 脚本会拒绝在生产环境中运行,从而确保每个节点都通过经过认证的 TLS 隧道与 NATS 通信。

签名消息总线

在发布 TSS 载荷之前,节点会使用其 Ed25519 身份对其进行签名(参见 pkg/identity/fileStore):

签署 TSS 消息

go
func (s *fileStore) SignMessage(msg *types.TssMessage) ([]byte, error) {
	msgBytes, err := msg.MarshalForSigning()
	if err != nil {
		return nil, fmt.Errorf("failed to marshal message for signing: %w", err)
	}
	signature := ed25519.Sign(s.privateKey, msgBytes)
	return signature, nil
}

每个节点在接受消息前都会验证签名,以防止伪造的对等方或重放数据:

验证 TSS 消息

go
func (s *fileStore) VerifyMessage(msg *types.TssMessage) error {
	if msg.Signature == nil {
		return fmt.Errorf("message has no signature")
	}
	publicKey, err := s.GetPublicKey(partyIDToNodeID(msg.From))
	if err != nil {
		return fmt.Errorf("failed to get sender's public key: %w", err)
	}
	msgBytes, err := msg.MarshalForSigning()
	if err != nil {
		return fmt.Errorf("failed to marshal message for verification: %w", err)
	}
	if !ed25519.Verify(publicKey, msgBytes, msg.Signature) {
		return fmt.Errorf("invalid signature")
	}
	return nil
}

点对点加密

节点通过专用的 NATS 主题(pkg/mpc/key_exchange_session.go)执行 ECDH 交换,为每个对等方派生对称密钥。这些密钥用于对每个载荷进行 AES-GCM 加密:

加密 TSS 载荷

go
func (s *fileStore) EncryptMessage(plaintext []byte, peerID string) ([]byte, error) {
	key, err := s.GetSymmetricKey(peerID)
	if err != nil {
		return nil, err
	}
	if key == nil {
		return nil, fmt.Errorf("no symmetric key for peer %s", peerID)
	}
	return encryption.EncryptAESGCMWithNonceEmbed(plaintext, key)
}

ECDH 握手流程

ECDH 会话(pkg/mpc/key_exchange_session.go)构建在 NATS 总线之上:

  1. 每个节点生成一个临时 X25519 密钥对,并订阅共享的 ecdh:exchange 主题。
  2. 节点广播其公钥和时间戳,并使用其 Ed25519 身份进行签名(SignEcdhMessage)。
  3. 监听方验证签名,使用自己的私钥执行 ECDH(),并通过 HKDF-SHA256 派生对称密钥。
  4. 派生出的 32 字节密钥通过 identityStore.SetSymmetricKey 按对等方缓存,并由上述 AES-GCM 辅助函数复用。

HKDF 密钥派生

go
func (e *ecdhSession) deriveSymmetricKey(secret []byte, peerID string) []byte {
	info := deriveConsistentInfo(e.nodeID, peerID) // stable ordering of IDs
	hkdfReader := hkdf.New(sha256.New, secret, nil, info)
	key := make([]byte, 32) // AES-256 key
	if _, err := hkdfReader.Read(key); err != nil {
		e.errCh <- err
		return nil
	}
	return key
}

如果对等方断开连接,RemovePeer 会丢弃缓存的对称密钥,因此重新连接时需要重新握手,从而确保通道密钥的临时性。

网络隔离

控制措施

实现方式

私有网络

MPC 节点通过私有 VPC/VPN 通信

防火墙规则

仅开放 MPC 端口,并限制为已知节点 IP

速率限制

限制每个节点的最大消息速率,以防范 DoS

连接监控

记录所有连接并监控异常


5. 存储安全

密钥分片持久化存储在 BadgerDB 中。除非提供加密密钥(badger_password),否则 MPC 服务会拒绝打开数据库,该密钥会通过 WithEncryptionKey(...)(pkg/kvstore/badger.go)传入 Badger 的 AES-256 加密。备份沿用相同的加密格式,因此没有密码时,无论是运行中的数据库文件还是快照归档都无法读取。

Badger 加密

go
opts := badger.DefaultOptions(config.DBPath).
  WithEncryptionKey(config.EncryptionKey).
  WithCompression(options.ZSTD)

db, err := badger.Open(opts)

6. 总结

Fystack 的 MPC 实现通过分布式信任、门限密码学和安全的多方协议消除了单点故障。通过将私钥拆分为分片并要求协同签名,我们确保任何单一环节被攻破都不会导致资产损失。

关键安全特性:

  • 分布式密钥生成,无单一信任点
  • 门限签名,需要多个参与方协作
  • 加密通信,采用 TLS 以及基于 ECDH 派生密钥的 AES-GCM 载荷加密
  • BadgerDB 静态加密,使用 AES-256 及运维人员提供的密码
  • 可验证计算,用于检测恶意参与方