Modules declare go 1.27.1 and the build images follow. go fix applied the modernizers: slices.Contains and range over int. omitzero is not applied, since it changes what the JSON says. Co-authored-by: Hanzo Dev <[email protected]>
luxfi/license
Runtime, offline-verifiable license tokens for gating commercial features in Lux binaries.
The API is open source (BSD-3-Clause). The tokens it verifies are signed by Lux Industries' private Ed25519 key and embed the entitlements (customer, expiry, scopes, features) for a particular deployment. Customers who hold a valid token can run commercial-only Lux features; everyone else gets the same code, but the gated features refuse to start.
Modelled after HashiCorp Vault Enterprise's licensing package: detect a
token from a well-known set of locations, verify its signature against an
embedded public key, check expiry, and expose a single HasFeature(scope)
predicate that the rest of the binary calls.
import "github.com/luxfi/license"
func main() {
license.HasFeatureOrDie("gpu") // aborts with a clear stderr message if the
// user has no token, an expired token, or
// a token without the "gpu" scope.
runGPUPipeline()
}
Why this exists
Some Lux components — CUDA / Metal-accelerated EVM, the order-book DEX engine, FHE / homomorphic primitives, FPGA SHA-3 — are commercial features. The OSS build is fully functional for the non-commercial paths; the commercial paths check for a valid license at startup and refuse to run without one.
This is the "honor-system + tamper detection" middle ground:
- Honor-system: no phone-home, no DRM, no kernel modules. A motivated attacker with the source can patch out the check.
- Tamper detection: tokens are signed; expired or tampered tokens fail loudly. Removing the check requires modifying our open source code, which is a license violation customers don't want to commit to.
This mirrors HashiCorp's Vault Enterprise model, which has been adequate deterrence in practice.
Token shape
Tokens are framed binary (not JWT, not CBOR — stdlib only):
LUXL magic [4]
1 version uint8
claims_len uint32 big-endian
claims_bytes [claims_len] deterministic encoding of Claims
signature [64] Ed25519(SHA-512(claims_bytes))
The Claims payload is itself a deterministic, length-prefixed binary layout
(see claims.go). Encoding sorts scopes and feature keys ascending so that a
given Claims struct always serialises identically and round-trips byte-for-
byte through Encode / DecodeClaims.
Tokens are exchanged with customers as standard base64 (so they fit in env
vars and .env files).
Claims
| Field | Type | Meaning |
|---|---|---|
CustomerID |
string | Human-readable customer identifier ("Acme Inc") |
LicenseID |
string | Unique ID for this token; used for revocation logs |
IssuedAt |
time.Time | When the token was signed |
Expiry |
time.Time (or zero) | After this time, VerifyToken returns ErrExpired |
Scope |
[]string | Authorized feature flags (see scopes below) |
Features |
map[string]string | Free-form attributes (tier, max_nodes, etc.) |
Notes |
string | Free-form note from the issuer |
Scopes (initial set; extensible)
| Scope | Gates |
|---|---|
gpu |
CUDA / Metal acceleration in luxfi/evm, luxfi/cevm |
cuda |
CUDA-specific kernels (subset of gpu) |
fhe-mlx |
OpenFHE Metal NTT acceleration |
dex |
Commercial order-book engine in luxfi/dex |
fpga |
FPGA SHA-3 / signature kernels in luxfi/fpga |
Adding a new scope is a string change. There is no enum on the Lux side.
Go API
Runtime (every binary)
// Discover and verify a license from env / disk. Cached for the lifetime
// of the process.
claims, err := license.LoadFromEnv()
// Non-fatal check: false on missing/invalid license or missing scope.
ok := license.HasFeature("gpu")
// Fatal check: prints a clear error to stderr and os.Exit(1)s if the
// license is missing or does not entitle the named scope.
license.HasFeatureOrDie("gpu")
Issuance (Lux operators only)
priv := loadIssuerPrivateKey() // from KMS / Vault / hardware token
tok, err := license.Sign(&license.Claims{
CustomerID: "Acme Inc",
LicenseID: "lic_abc123",
IssuedAt: time.Now().UTC(),
Expiry: time.Now().Add(365 * 24 * time.Hour),
Scope: []string{"gpu", "dex"},
Features: map[string]string{"tier": "enterprise", "max_nodes": "10"},
}, priv)
fmt.Println(license.EncodeBase64(tok))
Errors
| Error | Meaning |
|---|---|
license.ErrNoLicense |
No token in env / file / default path |
license.ErrBadMagic |
Input is not a Lux license token |
license.ErrBadVersion |
Unknown token version (future tokens, old binary) |
license.ErrTruncated |
Token bytes incomplete |
license.ErrBadSig |
Signature does not match IssuerPublicKey |
license.ErrExpired |
Token signature is valid but Expiry has passed |
C / C++ ABI
The capi/ subpackage exposes a stable C ABI:
#include "lux_license.h"
LuxLicenseClaims* c = lux_license_load_from_env();
if (c == NULL) {
fprintf(stderr, "%s\n", lux_license_last_error());
return 1;
}
if (lux_license_has_feature(c, "gpu") != 1) {
fprintf(stderr, "no gpu entitlement\n");
return 1;
}
fprintf(stdout, "licensed to %s\n", lux_license_customer_id(c));
lux_license_free(c);
C++17+ RAII wrapper:
#include "lux_license.hpp"
try {
auto lic = lux::License::load_from_env();
if (!lic.has_feature("gpu")) {
throw std::runtime_error("gpu not licensed");
}
} catch (const lux::license_error& e) {
std::cerr << "license: " << e.what() << "\n";
return 1;
}
Or the one-liner mirroring Go's HasFeatureOrDie:
lux::License::require_feature_or_die("gpu");
Building the C library
cd capi
go build -buildmode=c-archive -o liblux_license.a .
# or
go build -buildmode=c-shared -o liblux_license.so .
The build emits liblux_license.a (or .so) plus a generated
liblux_license.h (a superset of the stable header in include/; consumers
should include include/lux_license.h for forward compatibility).
CLI
cmd/lux-license is a thin issuer / inspector:
# Generate an Ed25519 issuer keypair (paste the pub key into pubkey.go).
lux-license keygen --out ./issuer.key
# Issue a token signed by the new key.
lux-license issue \
--customer "Acme Inc" \
--expiry 2027-12-31 \
--scope gpu,dex,fpga \
--features tier=enterprise,max_nodes=10 \
--notes "Primary commercial license" \
--private-key ./issuer.key \
> acme-license.txt
# Inspect (no signature check) — useful for support tickets.
lux-license inspect acme-license.txt
# Verify against the embedded public key.
lux-license verify acme-license.txt
--dev is accepted in place of --private-key to sign with the embedded
development key (testing only; tokens will refuse to verify on any build that
has swapped in a production key).
License discovery order
LoadFromEnv consults these locations in order, returning the first one that
contains a valid token:
$LUX_LICENSE— base64-encoded token, in-band.$LUX_LICENSE_FILE— path to a binary or base64 token file.$HOME/.lux/license.jwt— default path for installer-managed tokens.$HOME/.lux/license— alternate default.
Why the LUX_ prefix on these specific env vars
The standing convention in our codebases is to drop the LUX_ prefix on
env vars (so we don't double-namespace inside lux/* binaries). These two
are an explicit exception: LICENSE and LICENSE_FILE are catastrophically
overloaded across the Unix ecosystem (every Java app, Adobe / IBM / Oracle
installer, etc.), and customer ops staff need a name they can grep for in
their own shell rc files without false positives. The LUX_ prefix exists
purely to occupy a clean namespace in the customer's environment.
Key management
The Lux production signing key is never stored in this repository. Suggested custodianship:
- Generate on an air-gapped machine, or inside HashiCorp Vault's transit
engine, or inside the Lux KMS (
~/work/lux/kms). - Distribute the public key to all build pipelines that produce Lux
binaries (paste into
pubkey.go:IssuerPublicKey). - Treat the private key like a code-signing key: split-knowledge / dual control, hardware token, audit log of every issuance.
The current IssuerPublicKey is a development key derived deterministically
from a fixed seed. The library emits a one-time warning to stderr the first
time VerifyToken is called on any build that hasn't replaced it. CI should
fail if license.IsUsingDevKey() returns true in a release build.
Wire-up plan (downstream)
This repository ships only the library + CLI. Each commercial-gated binary adds its own one-line opt-in:
luxfi/cevm (CUDA / Metal EVM)
// in cmd/cevm/main.go, before any GPU work
license.HasFeatureOrDie("gpu")
luxfi/dex (commercial order-book)
// in cmd/dexd/main.go
license.HasFeatureOrDie("dex")
luxfi/fpga
// in cmd/fpgad/main.go
license.HasFeatureOrDie("fpga")
luxcpp/cevm (C++ kernels)
// in src/main.cpp
lux::License::require_feature_or_die("gpu");
Those one-liners are the only edits to the downstream codebases — no imports of issuer logic, no key material, no business of license discovery duplicated elsewhere.
The wire-up is intentionally not in this PR; it's tracked in separate follow-up changes (one per downstream repository) so each commercial gate ships with its own CHANGELOG entry and customer-visible message.
Testing
go test ./... # 22 Go tests
CGO_ENABLED=1 go test ./... # 22 Go + 1 cgo round-trip
The cgo round-trip test builds liblux_license.a, links a small C program
against it (capi/testdata/test_capi.c), issues a dev-signed token, and
verifies the C side accepts the valid token and rejects a tampered one.
Layout
.
├── LICENSE BSD-3
├── README.md this file
├── go.mod module github.com/luxfi/license
├── doc.go package doc
├── claims.go Claims struct + deterministic encoding
├── token.go Sign / VerifyToken / framing
├── pubkey.go embedded Ed25519 public key + dev key
├── runtime.go LoadFromEnv / HasFeature / HasFeatureOrDie
├── license_test.go 22 Go tests
├── cmd/lux-license/main.go issuer + inspector + verifier CLI
└── capi/
├── lux_license.go cgo bridge (package main)
├── include/lux_license.h stable C ABI header
├── include/lux_license.hpp C++17 RAII wrapper
├── testdata/test_capi.c round-trip test driver
└── internal/captest/captest_test.go cgo round-trip Go test
License
BSD 3-Clause. See LICENSE.
The library is BSD. The tokens it verifies are issued by Lux Industries Inc. under a separate commercial license; possessing this library does not grant any right to commercial Lux features.