mirror of
https://github.com/luxfi/hsm.git
synced 2026-10-30 16:22:43 +00:00
Add airgapped hardware wallet signers (Coldcard PSBT, Foundation BBQr, Keystone UR, NGRAVE UR) wired through #106's AirgapTransport protocol. Add online wallet signers shelling out to vendor CLIs (Ledger via ledgerctl, Trezor via trezorctl) — no CGO/HID dependency. GridPlus Lattice1 ships as a documented NotImpl with the full Connect-API protocol spec; the Sign path returns a clear error rather than fake output. Extend factory.go to register the new providers plus the universal PKCS#11 (Thales/Utimaco/Entrust/CloudHSM/SoftHSM2) and Nitrokey HSM 2 paths from #106. Configuration is pulled from a shared config map or matching MPC_HSM_<PROVIDER>_* environment variables. Tests: 19 new signer + factory tests using MockAirgapTransport. All pass under `go test ./...`. Real hardware tests are gated by build tags (pkcs11, nitrokey, airgap_coldcard, …) and are not exercised in CI.
93 lines
3.0 KiB
Go
93 lines
3.0 KiB
Go
// Copyright (C) 2020-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package hsm
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
// Foundation Passport (Batch 2) is a Bitcoin-only airgapped hardware
|
|
// wallet that exchanges PSBTs via QR codes only — there is no microSD
|
|
// option for signing. Larger PSBTs are split into a sequence of QR
|
|
// frames using the Foundation/Coldcard "BBQr" encoding.
|
|
//
|
|
// Architecture:
|
|
//
|
|
// mpcd ─[BBQr-encoded PSBT]─> AirgapTransport
|
|
// │
|
|
// │ animated QR / camera scan
|
|
// ▼
|
|
// Foundation Passport (offline)
|
|
// │
|
|
// │ on-device review + PIN
|
|
// ▼
|
|
// AirgapTransport ─[BBQr signed PSBT]─> mpcd
|
|
//
|
|
// BBQr framing is opaque to this signer: it only carries pre-encoded
|
|
// payloads end-to-end. Encoding/decoding is the AirgapTransport's
|
|
// responsibility because frame size depends on the rendering surface
|
|
// (e.g., 33 vs 100 bytes per frame on different displays).
|
|
|
|
// FoundationConfig configures a Foundation Passport signer.
|
|
type FoundationConfig struct {
|
|
// DeviceID is the operator-facing identifier of the target device.
|
|
DeviceID string
|
|
|
|
// Transport mediates the airgapped QR ceremony.
|
|
Transport AirgapTransport
|
|
}
|
|
|
|
// FoundationSigner produces signatures via a Foundation Passport.
|
|
type FoundationSigner struct {
|
|
cfg FoundationConfig
|
|
}
|
|
|
|
// NewFoundationSigner constructs a Foundation Passport signer.
|
|
func NewFoundationSigner(cfg FoundationConfig) (*FoundationSigner, error) {
|
|
if cfg.DeviceID == "" {
|
|
return nil, errors.New("hsm/foundation: DeviceID required")
|
|
}
|
|
return &FoundationSigner{cfg: cfg}, nil
|
|
}
|
|
|
|
// Provider returns "foundation".
|
|
func (s *FoundationSigner) Provider() string { return "foundation" }
|
|
|
|
// Sign blocks for the full BBQr round-trip and returns the signed PSBT.
|
|
// keyID is ignored — derivation is encoded in the PSBT.
|
|
func (s *FoundationSigner) Sign(ctx context.Context, _ string, message []byte) ([]byte, error) {
|
|
return airgappedSign(
|
|
ctx,
|
|
s.cfg.Transport,
|
|
s.cfg.DeviceID,
|
|
s.encodeChallenge,
|
|
s.decodeResponse,
|
|
message,
|
|
)
|
|
}
|
|
|
|
// Verify is unsupported (Bitcoin signatures verify on-chain).
|
|
func (s *FoundationSigner) Verify(_ context.Context, _ string, _, _ []byte) (bool, error) {
|
|
return false, errors.New("hsm/foundation: Verify not supported (verify on-chain)")
|
|
}
|
|
|
|
func (s *FoundationSigner) encodeChallenge(message []byte, _ string) (AirgapChallenge, error) {
|
|
if !looksLikePSBT(message) {
|
|
return AirgapChallenge{}, errors.New("hsm/foundation: message is not a PSBT")
|
|
}
|
|
return AirgapChallenge{
|
|
Format: FormatBBQr,
|
|
Payload: message,
|
|
}, nil
|
|
}
|
|
|
|
func (s *FoundationSigner) decodeResponse(response []byte) ([]byte, error) {
|
|
if !looksLikePSBT(response) {
|
|
return nil, fmt.Errorf("hsm/foundation: %w", ErrAirgapResponseInvalid)
|
|
}
|
|
return response, nil
|
|
}
|