mirror of
https://github.com/luxfi/mpc.git
synced 2026-09-24 00:26:24 +00:00
mpcd: author missing buildHSMSignerConfig helper (refs #227)
Commitaf4139d("deployment-mode flag + audit dispatcher") referenced buildHSMSignerConfig at the HSM signer wiring site but the helper file was never staged — same pattern as the missing airgap files repaired under #208. The current main.go works around the gap by passing nil to hsm.NewSigner, which loses the operator-supplied --hsm-signer-key-id and prevents per-provider config promotion. cmd/mpcd/hsm_signer_config.go: - validates provider name against the luxfi/hsm v1.1.3 factory list (aws/gcp/azure, zymbit, yubihsm, nitrokey, pkcs11, kmip, coldcard, foundation, keystone, ngrave, gridplus/lattice, ledger, trezor, tr31, mldsa, local) and the documented aliases. - promotes --hsm-signer-key-id into the canonical config key for each cloud KMS provider (aws.key_id, gcp.crypto_key, azure.key_name). - leaves vendor-specific surface (slot/pin/library, vault URL, transport) to the factory's MPC_HSM_<P>_* env-var fallback so the CLI flag surface stays minimal. cmd/mpcd/main.go: route --hsm-signer through the helper and surface config errors before reaching hsm.NewSigner. cmd/mpcd/hsm_signer_config_test.go: covers cloud-KMS key-id promotion, hardware-wallet empty-map case, alias acceptance, case-insensitive provider names, and rejection of empty + unknown providers. Helper + tests pass under `go test -race -count=1` in isolation. go.sum: refresh github.com/luxfi/edwards25519 v0.1.0 hashes to match proxy.golang.org. Pre-existing checksum drift blocked all `go build` attempts on this branch and on main. Note: cmd/mpcd as a whole still does not compile on this branch — the sameaf4139dcommit also dropped pkg/api/handlers_approval.go without adding the Server.approval field, and pkg/policy/fhe_threshold_decryptor.go references symbols that never landed. Both are out of scope for #227 and will be repaired under follow-up tickets in the same #208-style cleanup.
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
// Copyright (C) 2020-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
// buildHSMSignerConfig assembles the per-provider configuration map that
|
||||
// hsm.NewSigner consumes. The luxfi/hsm factory reads each provider's
|
||||
// config keys from the supplied map first, then falls back to its
|
||||
// documented MPC_HSM_<PROVIDER>_* environment variables. The job of this
|
||||
// helper is therefore narrow:
|
||||
//
|
||||
// 1. Reject unknown provider names early — a typo at startup is much
|
||||
// cheaper to surface here than as a "no signer wired" failure when
|
||||
// the first co-signing intent arrives.
|
||||
// 2. Promote the operator-facing CLI flags (--hsm-signer-key-id) into
|
||||
// the canonical config keys for the providers that consume a single
|
||||
// opaque key reference (cloud KMS).
|
||||
// 3. Leave the remaining provider-specific knobs (slot/pin/library for
|
||||
// PKCS#11, work-dir for airgap wallets, vault URL for Azure) to the
|
||||
// factory's environment-variable fallback. We do NOT mint new CLI
|
||||
// flags for every provider's tuning surface — there is one and only
|
||||
// one place those values live, the documented MPC_HSM_<P>_* names.
|
||||
//
|
||||
// The returned map is always non-nil so the factory cannot observe an
|
||||
// ambiguous "provider configured but no overrides" state.
|
||||
func buildHSMSignerConfig(c *cli.Command, provider string) (map[string]string, error) {
|
||||
provider = strings.TrimSpace(strings.ToLower(provider))
|
||||
if provider == "" {
|
||||
return nil, fmt.Errorf("hsm signer: provider not specified")
|
||||
}
|
||||
if !isSupportedHSMSignerProvider(provider) {
|
||||
return nil, fmt.Errorf("hsm signer: unsupported provider %q (supported: %s)",
|
||||
provider, strings.Join(supportedHSMSignerProviders(), ", "))
|
||||
}
|
||||
|
||||
cfg := map[string]string{}
|
||||
keyID := strings.TrimSpace(c.String("hsm-signer-key-id"))
|
||||
|
||||
switch provider {
|
||||
case "aws":
|
||||
// AWS KMS signer reads region from config or AWS_REGION; the key
|
||||
// ARN is identified at sign-time via the operator-supplied
|
||||
// hsm-signer-key-id. We pass it through under the canonical key
|
||||
// name AWSKMSSigner consults.
|
||||
if keyID != "" {
|
||||
cfg["key_id"] = keyID
|
||||
}
|
||||
case "gcp":
|
||||
// GCP KMS signer derives project/location/keyring/key from env
|
||||
// (GCP_PROJECT_ID, GCP_KMS_LOCATION, GCP_KMS_KEYRING, GCP_KMS_KEY).
|
||||
// hsm-signer-key-id, when supplied, becomes the crypto key name
|
||||
// override.
|
||||
if keyID != "" {
|
||||
cfg["crypto_key"] = keyID
|
||||
}
|
||||
case "azure":
|
||||
// Azure Key Vault signer reads vault_url from env (AZURE_VAULT_URL).
|
||||
// hsm-signer-key-id maps to the key name within the vault.
|
||||
if keyID != "" {
|
||||
cfg["key_name"] = keyID
|
||||
}
|
||||
case "zymbit", "yubihsm", "yubico", "yubi", "nitrokey",
|
||||
"coldcard", "foundation", "keystone", "ngrave",
|
||||
"gridplus", "lattice", "ledger", "trezor",
|
||||
"pkcs11", "kmip", "tr31",
|
||||
"mldsa", "pq", "post-quantum",
|
||||
"local":
|
||||
// Each of these providers reads its full configuration surface
|
||||
// from the documented MPC_HSM_<PROVIDER>_* environment variables.
|
||||
// The factory handles env fallback per key, so we deliberately
|
||||
// do not duplicate that surface as CLI flags. Operators wire
|
||||
// vendor-specific knobs (slots, pins, library paths, vault URLs,
|
||||
// transport implementations) through env vars provisioned out of
|
||||
// band, typically via KMS-synced secrets.
|
||||
default:
|
||||
// isSupportedHSMSignerProvider guards this path; this is dead
|
||||
// code retained as a defensive switch fall-through so the
|
||||
// compiler proves exhaustiveness when the provider list grows.
|
||||
return nil, fmt.Errorf("hsm signer: provider %q passed validation but has no config branch", provider)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// supportedHSMSignerProviders lists the canonical signer provider names
|
||||
// accepted by luxfi/hsm.NewSigner. Kept in lockstep with that factory's
|
||||
// switch — adding an entry here without a matching factory case is a
|
||||
// configuration lie.
|
||||
func supportedHSMSignerProviders() []string {
|
||||
return []string{
|
||||
"aws", "gcp", "azure",
|
||||
"zymbit",
|
||||
"yubihsm", "nitrokey",
|
||||
"pkcs11", "kmip",
|
||||
"coldcard", "foundation", "keystone", "ngrave",
|
||||
"gridplus", "ledger", "trezor",
|
||||
"tr31", "mldsa",
|
||||
"local",
|
||||
}
|
||||
}
|
||||
|
||||
// isSupportedHSMSignerProvider returns true when the given provider name
|
||||
// (already lower-cased + trimmed) is one luxfi/hsm.NewSigner will accept.
|
||||
// Aliases honored by the factory (yubico/yubi for yubihsm, lattice for
|
||||
// gridplus, pq/post-quantum for mldsa) are accepted here so the surface
|
||||
// stays consistent between the helper and the factory.
|
||||
func isSupportedHSMSignerProvider(provider string) bool {
|
||||
switch provider {
|
||||
case "aws", "gcp", "azure",
|
||||
"zymbit",
|
||||
"yubihsm", "yubico", "yubi",
|
||||
"nitrokey",
|
||||
"pkcs11", "kmip",
|
||||
"coldcard", "foundation", "keystone", "ngrave",
|
||||
"gridplus", "lattice",
|
||||
"ledger", "trezor",
|
||||
"tr31",
|
||||
"mldsa", "pq", "post-quantum",
|
||||
"local":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
// Copyright (C) 2020-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
// newCmdWithFlags builds an *cli.Command with the same hsm-signer-key-id
|
||||
// flag the real start subcommand registers, then runs it once with the
|
||||
// supplied argv so that c.String("hsm-signer-key-id") returns the value
|
||||
// the test wired in. We do this through the Action callback because
|
||||
// urfave/cli/v3 does not expose a way to hand-construct a populated
|
||||
// *cli.Command outside of an invocation.
|
||||
func newCmdWithFlags(t *testing.T, keyID string) *cli.Command {
|
||||
t.Helper()
|
||||
var captured *cli.Command
|
||||
cmd := &cli.Command{
|
||||
Name: "test",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{Name: "hsm-signer-key-id"},
|
||||
},
|
||||
Action: func(_ context.Context, c *cli.Command) error {
|
||||
captured = c
|
||||
return nil
|
||||
},
|
||||
}
|
||||
args := []string{"test"}
|
||||
if keyID != "" {
|
||||
args = append(args, "--hsm-signer-key-id", keyID)
|
||||
}
|
||||
if err := cmd.Run(context.Background(), args); err != nil {
|
||||
t.Fatalf("cli run: %v", err)
|
||||
}
|
||||
if captured == nil {
|
||||
t.Fatalf("cli action did not fire")
|
||||
}
|
||||
return captured
|
||||
}
|
||||
|
||||
func TestBuildHSMSignerConfig_AWSPromotesKeyID(t *testing.T) {
|
||||
c := newCmdWithFlags(t, "arn:aws:kms:us-east-1:111122223333:key/abcd")
|
||||
cfg, err := buildHSMSignerConfig(c, "aws")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got := cfg["key_id"]; got != "arn:aws:kms:us-east-1:111122223333:key/abcd" {
|
||||
t.Fatalf("aws key_id = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildHSMSignerConfig_GCPPromotesKeyID(t *testing.T) {
|
||||
c := newCmdWithFlags(t, "projects/p/locations/l/keyRings/r/cryptoKeys/k")
|
||||
cfg, err := buildHSMSignerConfig(c, "gcp")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got := cfg["crypto_key"]; got != "projects/p/locations/l/keyRings/r/cryptoKeys/k" {
|
||||
t.Fatalf("gcp crypto_key = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildHSMSignerConfig_AzurePromotesKeyID(t *testing.T) {
|
||||
c := newCmdWithFlags(t, "intent-cosigner")
|
||||
cfg, err := buildHSMSignerConfig(c, "azure")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got := cfg["key_name"]; got != "intent-cosigner" {
|
||||
t.Fatalf("azure key_name = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildHSMSignerConfig_HardwareWalletReturnsEmptyMap(t *testing.T) {
|
||||
c := newCmdWithFlags(t, "")
|
||||
for _, p := range []string{
|
||||
"coldcard", "foundation", "keystone", "ngrave",
|
||||
"ledger", "trezor", "gridplus", "lattice",
|
||||
"yubihsm", "nitrokey", "zymbit",
|
||||
"pkcs11", "kmip", "tr31",
|
||||
"mldsa", "local",
|
||||
} {
|
||||
cfg, err := buildHSMSignerConfig(c, p)
|
||||
if err != nil {
|
||||
t.Fatalf("provider %s: %v", p, err)
|
||||
}
|
||||
if cfg == nil {
|
||||
t.Fatalf("provider %s: nil cfg", p)
|
||||
}
|
||||
if len(cfg) != 0 {
|
||||
t.Fatalf("provider %s: expected empty cfg, got %v", p, cfg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildHSMSignerConfig_EmptyProviderRejected(t *testing.T) {
|
||||
c := newCmdWithFlags(t, "")
|
||||
if _, err := buildHSMSignerConfig(c, ""); err == nil {
|
||||
t.Fatalf("expected error for empty provider")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildHSMSignerConfig_UnknownProviderRejected(t *testing.T) {
|
||||
c := newCmdWithFlags(t, "")
|
||||
if _, err := buildHSMSignerConfig(c, "luna-network-hsm"); err == nil {
|
||||
t.Fatalf("expected error for unknown provider")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildHSMSignerConfig_ProviderCaseInsensitive(t *testing.T) {
|
||||
c := newCmdWithFlags(t, "key-1")
|
||||
cfg, err := buildHSMSignerConfig(c, " AWS ")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got := cfg["key_id"]; got != "key-1" {
|
||||
t.Fatalf("aws key_id = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSupportedHSMSignerProviders_NonEmpty(t *testing.T) {
|
||||
if len(supportedHSMSignerProviders()) == 0 {
|
||||
t.Fatalf("supportedHSMSignerProviders returned empty list")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsSupportedHSMSignerProvider_AcceptsAliases(t *testing.T) {
|
||||
for _, alias := range []string{"yubico", "yubi", "lattice", "pq", "post-quantum"} {
|
||||
if !isSupportedHSMSignerProvider(alias) {
|
||||
t.Fatalf("alias %q rejected", alias)
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-6
@@ -725,15 +725,24 @@ func runNodeConsensus(ctx context.Context, c *cli.Command) error {
|
||||
apiServer := mpcapi.NewServer(database, mpcBackend, jwtSecret)
|
||||
apiServer.StartScheduler(ctx)
|
||||
|
||||
// Wire HSM signer for intent co-signing
|
||||
// Wire HSM signer for intent co-signing.
|
||||
// Provider-specific config is folded into a single map; each
|
||||
// underlying provider reads only the keys it understands
|
||||
// and falls back to MPC_HSM_<PROVIDER>_* env vars for the
|
||||
// rest of its tuning surface.
|
||||
signerType := c.String("hsm-signer")
|
||||
if signerType != "" {
|
||||
signer, signerErr := hsm.NewSigner(signerType, nil)
|
||||
if signerErr != nil {
|
||||
logger.Error("Failed to create HSM signer", signerErr, "provider", signerType)
|
||||
signerConfig, cfgErr := buildHSMSignerConfig(c, signerType)
|
||||
if cfgErr != nil {
|
||||
logger.Error("Failed to build HSM signer config", cfgErr, "provider", signerType)
|
||||
} else {
|
||||
apiServer.SetHSM(signer)
|
||||
logger.Info("HSM signer configured for co-signing", "provider", signer.Provider())
|
||||
signer, signerErr := hsm.NewSigner(signerType, signerConfig)
|
||||
if signerErr != nil {
|
||||
logger.Error("Failed to create HSM signer", signerErr, "provider", signerType)
|
||||
} else {
|
||||
apiServer.SetHSM(signer)
|
||||
logger.Info("HSM signer configured for co-signing", "provider", signer.Provider())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -370,8 +370,8 @@ github.com/luxfi/crypto v1.17.45 h1:uGK0y4+aLipE/M0YIQ5hcsWv0ZG0E4cPv03a94K/eLE=
|
||||
github.com/luxfi/crypto v1.17.45/go.mod h1:GnAkhQ7HNs3X0Tzx5nOONS3kl0yRmWHbDcRO5ffILsg=
|
||||
github.com/luxfi/database v1.17.44 h1:hfiTls7sqbweW+o4iaZqB8P997paC+vpgWmhN6v5MJ0=
|
||||
github.com/luxfi/database v1.17.44/go.mod h1:6Ey5y3I0WNLHbxIlIdFqUuKfBg+b0fAgTA8FgRgQ8zg=
|
||||
github.com/luxfi/edwards25519 v0.1.0 h1:zuSrBtgV4NUiJ8dzzJiLU4o+A0AHGnztRLUvZ24mAYk=
|
||||
github.com/luxfi/edwards25519 v0.1.0/go.mod h1:tFuuyXvI+xqUoHz9pwC+kqsALCGHSO1SymL1NjCOmFM=
|
||||
github.com/luxfi/edwards25519 v0.1.0 h1:YPoT831TZMslvNyy/KuTtpHi4BGZWgpwak06RCaYLzo=
|
||||
github.com/luxfi/edwards25519 v0.1.0/go.mod h1:MBoV+bPEz1tSMADPMSUaYdZ+agY3syG8B6z7RmJBH0A=
|
||||
github.com/luxfi/fhe v1.7.7 h1:RP26i3CxvNy2y/3LKiJPgd9uR5MrlG/MvQnv5rkQFhk=
|
||||
github.com/luxfi/fhe v1.7.7/go.mod h1:FHfGNgKL5ZLcnKVKFVNLDJOBbQyQm5DGpVrMnY8ASJo=
|
||||
github.com/luxfi/geth v1.16.79 h1:MtP8ZUuSVZDjmZDa1kTJCl0PpG8+wGbQft652GO6a3A=
|
||||
|
||||
Reference in New Issue
Block a user