Files
kms/pkg/mpc/client_decomplect_test.go
T
zeekayandHanzo Dev 7377ad63d2 fix(mpc): realign KMS↔MPC ZAP signing wire to mpcd contract; kill false-green
The KMS ZapClient and the mpcd KMS-facing ZAP server (luxfi/mpc
pkg/api/zap_kms_server.go) had drifted out of lockstep despite the
"MUST stay in lockstep" comment — each was only unit-tested against its
own stub, never against the other's real bytes. Three drifts:

1. Sign request: KMS sent {key_type, wallet_id, message}; mpcd requires
   {vault_id, wallet_id, payload} and hard-fails "vault_id and wallet_id
   required". SignRequest now carries VaultID (Manager supplies it from
   MPC_VAULT_ID) and Payload (json "payload").
2. Keygen response: mpcd returns snake_case (wallet_id/ecdsa_pub_key/
   evm_address); KMS KeygenResult was camelCase → decoded to an empty
   struct. Tags realigned to snake_case (the ZAP path is the only live
   decode; the REST Client is unused).
3. False-green: ZapClient.call() json-decoded the daemon's {"error":...}
   frame into a zero-value SignResult and returned it with nil error —
   the KMS looked MPC-backed but returned an empty signature. call() now
   surfaces a non-empty top-level "error" as a real error (fail closed).

Adds pkg/mpc/wire_contract_test.go — a cross-repo contract test that
transcribes the mpcd request/response shapes and fails CI if the KMS
side drifts again (the guard that was missing). Existing tests updated
to the corrected snake_case wire.

This is a library fix in luxfi/kms — it fixes the wire for all three
orgs (Lux/Zoo/Hanzo) once. ed25519/Corona keygen over this wire remains
a follow-up (mpcd's KMS-ZAP keygen carries no key_type/protocol).

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-14 14:15:05 -07:00

78 lines
2.3 KiB
Go

// Copyright (C) 2024-2026 Lux Industries Inc.
// SPDX-License-Identifier: BSD-3-Clause
package mpc
import (
"encoding/json"
"strings"
"testing"
)
// TestWallet_MarshalJSON_EmitsEVMAddress pins the canonical wire
// shape: only evmAddress, no legacy aliases.
func TestWallet_MarshalJSON_EmitsEVMAddress(t *testing.T) {
addr := "0xABCDef0123456789aBcdEf0123456789ABCDeF01"
w := Wallet{
ID: "w1",
KeyType: "ecdsa",
EVMAddress: &addr,
}
out, err := json.Marshal(&w)
if err != nil {
t.Fatal(err)
}
s := string(out)
if !strings.Contains(s, `"evmAddress":"`+addr+`"`) {
t.Fatalf("missing evmAddress in output: %s", s)
}
if strings.Contains(s, "keccakAddress") || strings.Contains(s, "ethAddress") {
t.Fatalf("legacy address alias leaked into output: %s", s)
}
}
// TestWallet_UnmarshalJSON_AcceptsEVMAddress pins the canonical wire
// shape on the read side.
func TestWallet_UnmarshalJSON_AcceptsEVMAddress(t *testing.T) {
addr := "0xABCDef0123456789aBcdEf0123456789ABCDeF01"
src := `{"id":"w1","keyType":"ecdsa","evmAddress":"` + addr + `"}`
var w Wallet
if err := json.Unmarshal([]byte(src), &w); err != nil {
t.Fatal(err)
}
if w.EVMAddress == nil || *w.EVMAddress != addr {
t.Fatalf("EVMAddress not populated: got %+v", w.EVMAddress)
}
}
// TestKeygenResult_EVMAddress mirrors the Wallet contract.
func TestKeygenResult_EVMAddress(t *testing.T) {
addr := "0xdeadbeefcafebabedeadbeefcafebabedeadbeef"
// Marshal: only evmAddress emitted.
k := KeygenResult{ID: "k1", EVMAddress: &addr}
out, err := json.Marshal(&k)
if err != nil {
t.Fatal(err)
}
s := string(out)
// snake_case matches the mpcd ZAP keygen response (evm_address). camelCase
// was the wire drift that silently decoded to empty — see wire_contract_test.go.
if !strings.Contains(s, `"evm_address":"`+addr+`"`) {
t.Fatalf("missing evm_address: %s", s)
}
if strings.Contains(s, "keccakAddress") || strings.Contains(s, "ethAddress") || strings.Contains(s, "evmAddress") {
t.Fatalf("legacy alias leaked: %s", s)
}
// Unmarshal: evm_address populates EVMAddress.
src := `{"id":"k2","evm_address":"` + addr + `"}`
var k2 KeygenResult
if err := json.Unmarshal([]byte(src), &k2); err != nil {
t.Fatal(err)
}
if k2.EVMAddress == nil || *k2.EVMAddress != addr {
t.Fatalf("EVMAddress not populated: %v", k2.EVMAddress)
}
}