Files
mpc/pkg/zap/handshake_test.go
zeekay aa697bfc10 feat(mpcd): expose ZAP server on :9970 for KMS handshake
Adds threshold-signing transport so KMS can use mpcd as MPC backend.
Reuses ML-KEM-768 hybrid handshake from luxfi/kms/pkg/zap (#55) by
vendoring the same construction into mpc/pkg/zap — wire constants
(opcodes 0x00F0/0x00F1, HKDF info "kms/zap/v1/session", direction
tags) stay in lockstep with KMS.

New surface:
- pkg/zap: vendored handshake + session (forward-only convergent)
- pkg/api/zap_kms_server.go: KMS-facing ZAP node (opcodes
  0x0001/0x0010/0x0011/0x0012/0x0020) wired onto MPCBackend
- cmd/mpcd: --kms-zap-listen flag (default :9970), enabled out of the
  box; default-disable by passing empty string

Existing pkg/api/zap_server.go (dashboard event bus, opcodes 0x0060+)
is unchanged — different consumer, different port.

Closes the secrets-only mode gap on mainnet (#70).
2026-05-01 01:35:13 -07:00

93 lines
2.6 KiB
Go

// Smoke test for the vendored ML-KEM-768 hybrid handshake. The audited
// reference suite lives in luxfi/kms/pkg/zap. This file pins three
// invariants that MUST hold for KMS↔MPC interop:
//
// 1. NewClient → ServerRespond → ClientFinish round-trips with hybrid=true
// when both sides advertise CapMLKEM768.
// 2. Both sides derive the same 32-byte session key.
// 3. Sealing under that key on one side opens on the other.
//
// If any of these break, KMS clients can no longer talk to MPC servers
// and the secrets-only mode regression returns.
package zap
import (
"bytes"
"testing"
)
func TestHandshakeHybridRoundTrip(t *testing.T) {
cli, helloWire, err := NewClient(CapMLKEM768)
if err != nil {
t.Fatalf("NewClient: %v", err)
}
replyWire, srvResult, err := ServerRespond(CapMLKEM768, helloWire)
if err != nil {
t.Fatalf("ServerRespond: %v", err)
}
cliResult, err := cli.ClientFinish(replyWire)
if err != nil {
t.Fatalf("ClientFinish: %v", err)
}
if !srvResult.Hybrid || !cliResult.Hybrid {
t.Fatalf("expected hybrid both sides; srv=%v cli=%v", srvResult.Hybrid, cliResult.Hybrid)
}
if !bytes.Equal(srvResult.SessionKey, cliResult.SessionKey) {
t.Fatalf("session key mismatch")
}
if len(srvResult.SessionKey) != SessionKeySize {
t.Fatalf("session key size: want %d got %d", SessionKeySize, len(srvResult.SessionKey))
}
}
func TestSessionSealOpen(t *testing.T) {
cli, helloWire, err := NewClient(CapMLKEM768)
if err != nil {
t.Fatalf("NewClient: %v", err)
}
replyWire, srv, err := ServerRespond(CapMLKEM768, helloWire)
if err != nil {
t.Fatalf("ServerRespond: %v", err)
}
cliRes, err := cli.ClientFinish(replyWire)
if err != nil {
t.Fatalf("ClientFinish: %v", err)
}
cliSess, err := NewSession(cliRes.SessionKey, cliRes.Hybrid)
if err != nil {
t.Fatalf("NewSession cli: %v", err)
}
srvSess, err := NewSession(srv.SessionKey, srv.Hybrid)
if err != nil {
t.Fatalf("NewSession srv: %v", err)
}
// C → S
plaintext := []byte("threshold-sign-payload")
sealed, err := cliSess.Seal(DirClientToServer, plaintext)
if err != nil {
t.Fatalf("Seal: %v", err)
}
pt, err := srvSess.Open(DirClientToServer, sealed)
if err != nil {
t.Fatalf("Open: %v", err)
}
if !bytes.Equal(pt, plaintext) {
t.Fatalf("plaintext mismatch")
}
// S → C uses the opposite direction tag.
srvReply := []byte("ok")
sealedReply, err := srvSess.Seal(DirServerToClient, srvReply)
if err != nil {
t.Fatalf("Seal reply: %v", err)
}
openedReply, err := cliSess.Open(DirServerToClient, sealedReply)
if err != nil {
t.Fatalf("Open reply: %v", err)
}
if !bytes.Equal(openedReply, srvReply) {
t.Fatalf("reply mismatch")
}
}