3.7 KiB
luxfi/cosigner — external-custodian co-signing
github.com/luxfi/cosigner is the single, vendor-neutral co-signing layer
for external regulated custodians (Fireblocks, Utila, Anchorage). It is an
orthogonal leaf, decomplected OUT of the permissionless bridge: the bridge
(luxfi/bridge) depends on it, and it depends on nothing but the standard
library plus its own vendored secrets subpackage. There is exactly one
co-signing contract; custodian families are orthogonal Kinds behind one
FamilyDispatcher interface.
Layered cosigners let an institutional flow attach an external regulated
custodian on top of the native MPC threshold signature. Per swap the SDK posts
a cosigners[] array of public identifiers only; the consumer validates the
wire shape, fetches the secret half from KMS keyed by the public id, dispatches
a cosign request to each custodian after the native sign completes, and gates
the broadcast on all listed cosigners approving.
Packages
| Import | Purpose |
|---|---|
github.com/luxfi/cosigner |
The contract: Kind, Intent, Result, ValidateIntents, the Dispatcher / SecretStore / FamilyDispatcher interfaces, NewDefault, and the AllApproved gate. One validator + one dispatch branch per Kind. |
github.com/luxfi/cosigner/secrets |
Self-contained URI-scheme secret resolver — literal:, env:, file:, kms:. Cloud SDKs register a kms: provider via RegisterKMS; the default binary drags in none. |
Kinds (orthogonal custodian families)
KindFireblocks— PRODUCTION. Real Fireblocks REST RAW-sign flow with RS256-JWT request authentication (FireblocksRESTFamily): create the transaction, poll to a terminal state, surface the signature.KindUtila— blocked on a Go@luxfi/utilaConnect-RPC client /.protosource (UtilaConnectRPCFamily). Fails LOUD with a typed*NotImplementedErrorthat names the missing artifact — never a silent pass.KindAnchorage— blocked on the Anchorage API spec / SDK (AnchorageFamily). Same fail-loud contract.
Wire them with CompositeFamilyDispatcher (one field per family, unset slots
fall back to StubFamilyDispatcher, which also fails loud).
Secret stores
KMSSecretStore— PRODUCTION. Resolves the secret half through thesecrets.Resolver, keyed by the public identifier preserved VERBATIM (<base>/fireblocks/<api_key>/secret_pem, …). Errors name theKindonly.EnvSecretStore— dev / file-mount. Reads…_COSIGNER_PEM__<envSafe(id)>, passing the value through the same resolver sofile:/kms:schemes work.
Invariants
- Secret material NEVER crosses the wire. The SDK sends only public
identifiers;
ValidateIntentsrejects any entry carrying a secret-like field name (api_secret,private_key,jwt,token, …) as a forwarder-bug backstop. The consumer fetches the secret half from KMS at dispatch time. - Errors name the KIND, never the secret reference. A
SecretResolveErrorsurfaces which family failed; it never echoes the resolved URI, tenant id, or secret path. The resolver cause is preserved forerrors.Is(againstErrSecretNotConfiguredandsecrets.ErrKMSNotRegistered) but never rendered. - Honesty invariant. A family whose real integration is not yet available
fails loud with
ErrNotImplemented— never a stub dressed up as an approval. - All-or-nothing gate.
AllApprovedrequires every resultStatusApproved; any rejection or failure blocks the swap. Empty input → no gate (the permissionless default is zero cosigners).
Build
GOWORK=off go build ./...
GOWORK=off go test ./...