Bảo mật ví MPC

Fystack sử dụng Tính toán đa bên (MPC) để loại bỏ các điểm lỗi đơn lẻ trong quản lý khóa riêng tư. Không một bên nào nắm giữ khóa riêng tư hoàn chỉnh. Thay vào đó, các phần khóa được phân tán trên nhiều node, và chữ ký được tính toán cộng tác bằng mật mã ngưỡng.

Tài liệu này trình bày cách triển khai MPC của Fystack bảo vệ khóa ví thông qua niềm tin phân tán, giao tiếp an toàn và tính toán có thể xác minh.

Kiến trúc bảo mật ví MPC của Fystack
Click to open the full-size diagram

Trách nhiệm của node

Mỗi node:

  • Chỉ lưu trữ cục bộ phần khóa đã mã hóa của chính nó.
  • Tham gia ký theo ngưỡng bằng xác minh Ed25519 và giao tiếp P2P được bảo vệ bằng ECDH.
  • Không thể tái tạo hoặc xuất toàn bộ khóa, kể cả khi bị xâm phạm.

Thuộc tính

Mô tả

Tính bảo mật

Không node hay người vận hành nào có thể truy cập toàn bộ khóa riêng tư.

Tính toàn vẹn

Mọi thông điệp giữa các node đều được xác thực bằng chữ ký Ed25519 hoặc Cloud KMS.

Khả năng chịu lỗi

Hỗ trợ n node với ngưỡng t, chịu được tối đa (t-1) node gặp sự cố.

Mã hóa khi lưu trữ

Các phần khóa được lưu cục bộ với mã hóa AES.

Mã hóa khi truyền tải

Được thực thi qua NATS TLS cùng một lớp ECDH cho các kênh P2P.


1. Tổng quan

Tính toán đa bên cho phép nhiều bên cùng thực hiện các phép toán mật mã mà không bên nào có quyền truy cập vào khóa riêng tư hoàn chỉnh. Fystack triển khai các lược đồ chữ ký ngưỡng, trong đó cần một số lượng bên tối thiểu (ngưỡng) hợp tác để tạo ra một chữ ký hợp lệ.

Thành phần

Mô tả

Node MPC

Tối thiểu 3 node tạo thành một cụm.

Ngưỡng (t)

Ví vẫn an toàn ngay cả khi có tối đa (t-1) node bị xâm phạm.

Phần khóa

Khóa riêng tư được chia về mặt toán học thành nhiều phần.

Mã hóa phần khóa

Mỗi phần khóa được mã hóa bằng AES (mã hóa mặc định của BadgerDB).

Bảo mật truyền thông điệp

Bảo vệ hai lớp: NATS TLS cùng trao đổi khóa ECDH để mã hóa P2P.

Xác thực thông điệp

Apex giao tiếp với cụm MPC bằng các thông điệp có chữ ký (Ed25519 hoặc Cloud KMS).

Lợi ích chính

  • Không có điểm xâm phạm đơn lẻ: Việc một node bị xâm phạm không làm lộ khóa riêng tư
  • Niềm tin phân tán: Nhiều bên độc lập phải hợp tác
  • Trong suốt với blockchain: Chữ ký đầu ra là chữ ký ECDSA/EdDSA tiêu chuẩn
  • Ngưỡng linh hoạt: Có thể điều chỉnh yêu cầu ký dựa trên giá trị giao dịch
  • Nhật ký kiểm toán: Mọi phiên ký đều được ghi lại và có thể truy vết

2. Tạo khóa phân tán (DKG)

Fystack sử dụng Tạo khóa phân tán để tạo khóa ví mà không bên nào từng nắm giữ khóa riêng tư hoàn chỉnh.

Quy trình DKG

Giai đoạn 1: Cam kết

  • Mỗi bên tạo một đa thức ngẫu nhiên bậc t-1 (trong đó t là ngưỡng)
  • Các bên phát quảng bá cam kết cho đa thức của mình
  • Các cam kết được xác minh bằng Feldman VSS (Verifiable Secret Sharing)

Giai đoạn 2: Phân phối phần khóa

  • Mỗi bên tính giá trị đa thức của mình tại các điểm cụ thể
  • Các phần được gửi an toàn đến các bên tương ứng qua kênh mã hóa
  • Mỗi bên nhận một phần từ mỗi bên còn lại

Giai đoạn 3: Xác minh

  • Các bên xác minh các phần nhận được so với các cam kết đã quảng bá
  • Các phần không hợp lệ được xác định và khiếu nại được ghi nhận
  • Giao thức sẽ hủy nếu phát hiện quá nhiều phần không hợp lệ

Giai đoạn 4: Xây dựng khóa

  • Mỗi bên kết hợp các phần hợp lệ để tạo thành phần khóa của mình
  • Khóa công khai được tính từ các cam kết
  • Không bên nào biết khóa riêng tư hoàn chỉnh

Cấu hình DKG

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

mpc_threshold biểu thị ngưỡng t trong lược đồ n-of-t. Với n node MPC đang hoạt động, hệ thống bắt buộc 2 ≤ t ≤ n, chịu được tối đa (t - 1) node lỗi hoặc ngoại tuyến mà vẫn tạo được chữ ký. Ví dụ, n = 3 với mpc_threshold: 2 tạo thành một cụm 2-of-3: bất kỳ hai node đã xác thực nào cũng có thể ký, nhưng việc xâm phạm một node duy nhất không đủ để khôi phục khóa riêng tư.

Với các giao thức MPC bền vững kiểu GG18/GG20, chúng tôi còn khuyến nghị thêm t > n / 2, đảm bảo khả năng chịu lỗi Byzantine: ngay cả khi f = n - t node độc hại, ngoại tuyến hoặc thông đồng, các node trung thực vẫn chiếm đa số và có thể ngăn chặn chữ ký giả mạo hoặc việc tái tạo khóa riêng tư.

Thuộc tính bảo mật

  • Tính bền vững: Chịu được tối đa n-t bên độc hại
  • Tính có thể xác minh: Mọi bên đều có thể xác minh tính đúng đắn của các phần khóa
  • Tính riêng tư: Không có thông tin nào về khóa riêng tư bị rò rỉ
  • Không tương tác: Sau khi thiết lập, không cần chạy DKG thêm

3. Ký theo ngưỡng

Ký theo ngưỡng cho phép một tập con các bên (đáp ứng ngưỡng) cùng tạo ra chữ ký hợp lệ mà không cần tái tạo khóa riêng tư.

Giao thức ký (ECDSA)

Vòng 1: Giai đoạn cam kết

Tạo cam kết

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)

Vòng 2: Tạo chữ ký thành phần

Chữ ký thành phần

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)

Vòng 3: Tổng hợp chữ ký

Kết hợp chữ ký

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

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

Quy trình ký

Bước

Hành động

Kiểm tra bảo mật

  1. Khởi tạo phiên

Tạo phiên ký với chi tiết giao dịch

Xác minh tính duy nhất của session ID

  1. Chọn các bên

Chọn số bên tham gia bằng ngưỡng

Xác minh các bên đang trực tuyến và đã được xác thực

  1. Vòng cam kết

Trao đổi cam kết cho các khóa tạm thời

Kiểm tra định dạng và độ mới của cam kết

  1. Chữ ký thành phần

Mỗi bên tính toán và chia sẻ chữ ký thành phần

Xác minh tính hợp lệ của chữ ký thành phần

  1. Tổng hợp

Kết hợp các chữ ký thành phần thành chữ ký cuối cùng

Xác minh chữ ký cuối cùng bằng khóa công khai

  1. Phát sóng

Gửi giao dịch đã ký lên blockchain

Xác nhận giao dịch được chấp nhận


4. Bảo mật mạng

Truyền tải NATS (TLS)

Các node MPC trao đổi dữ liệu qua bus NATS dùng chung được khai báo trong config.yaml. Các bản triển khai production yêu cầu endpoint TLS cùng chứng chỉ client được ghim:

Cấu hình 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"

Script deployments/systemd/setup-config.sh sẽ từ chối chạy trên production nếu thiếu URL tls:// và các đường dẫn nats.tls.* ở trên, đảm bảo mọi node giao tiếp với NATS qua một đường hầm TLS đã xác thực.

Bus thông điệp có chữ ký

Trước khi một payload TSS được phát đi, node ký nó bằng định danh Ed25519 của mình (xem pkg/identity/fileStore):

Ký thông điệp 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
}

Mọi node đều xác minh chữ ký trước khi chấp nhận thông điệp, ngăn chặn peer giả mạo hoặc dữ liệu bị phát lại:

Xác minh thông điệp 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
}

Mã hóa điểm-điểm

Các node chạy trao đổi ECDH qua các topic NATS chuyên dụng (pkg/mpc/key_exchange_session.go) để dẫn xuất khóa đối xứng cho từng peer. Các khóa này được dùng cho mã hóa AES-GCM với mọi payload:

Mã hóa payload 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)
}

Luồng bắt tay ECDH

Phiên ECDH (pkg/mpc/key_exchange_session.go) được xây dựng phía trên bus NATS:

  1. Mỗi node tạo một cặp khóa X25519 tạm thời và đăng ký topic dùng chung ecdh:exchange.
  2. Các node phát quảng bá khóa công khai kèm dấu thời gian, được ký bằng định danh Ed25519 của mình (SignEcdhMessage).
  3. Bên nhận xác minh chữ ký, thực hiện ECDH() bằng khóa riêng của mình và dẫn xuất khóa đối xứng bằng HKDF-SHA256.
  4. Khóa 32 byte được dẫn xuất sẽ được lưu đệm theo từng peer qua identityStore.SetSymmetricKey và được các hàm hỗ trợ AES-GCM ở trên tái sử dụng.

Dẫn xuất khóa 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
}

Nếu một peer ngắt kết nối, RemovePeer sẽ xóa khóa đối xứng đã lưu đệm để bắt buộc bắt tay lại khi kết nối lại, giữ cho khóa kênh luôn mang tính tạm thời.

Cô lập mạng

Biện pháp kiểm soát

Cách triển khai

Mạng riêng

Các node MPC giao tiếp qua VPC/VPN riêng

Quy tắc tường lửa

Chỉ mở các cổng MPC, giới hạn cho IP của các node đã biết

Giới hạn tốc độ

Tốc độ thông điệp tối đa cho mỗi node để ngăn chặn DoS

Giám sát kết nối

Mọi kết nối đều được ghi log và giám sát để phát hiện bất thường


5. Bảo mật lưu trữ

Các phần khóa được lưu trữ bên trong BadgerDB. Dịch vụ MPC từ chối mở cơ sở dữ liệu nếu không được cung cấp khóa mã hóa (badger_password), và khóa đó được đưa vào cơ chế mã hóa AES-256 của Badger thông qua WithEncryptionKey(...) (pkg/kvstore/badger.go). Bản sao lưu dùng lại cùng định dạng mã hóa, nên cả tệp DB đang hoạt động lẫn các bản lưu trữ snapshot đều không thể đọc được nếu không có mật khẩu.

Mã hóa Badger

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

db, err := badger.Open(opts)

6. Tóm tắt

Triển khai MPC của Fystack loại bỏ các điểm lỗi đơn lẻ thông qua niềm tin phân tán, mật mã ngưỡng và các giao thức đa bên an toàn. Bằng cách chia khóa riêng tư thành nhiều phần và yêu cầu ký cộng tác, chúng tôi đảm bảo không một sự xâm phạm đơn lẻ nào có thể dẫn đến mất tài sản.

Các tính năng bảo mật chính:

  • Tạo khóa phân tán không có điểm tin cậy đơn lẻ
  • Chữ ký ngưỡng yêu cầu nhiều bên hợp tác
  • Giao tiếp được mã hóa với TLS + payload AES-GCM dùng khóa dẫn xuất từ ECDH
  • Mã hóa BadgerDB khi lưu trữ bằng AES-256 với mật khẩu do người vận hành cung cấp
  • Tính toán có thể xác minh để phát hiện các bên độc hại