Pure-Go, stdlib-only implementation of the LIC1 wire format: - ParseKey + Verify + ParseAndVerify for offline verification - HashFingerprint helper (SHA-256, matching the daemon's contract) - LoadPublicKeyPEM for the standard PKIX-encoded Ed25519 public keys the daemon emits - Client.Validate / Client.PublicKey for online checks against a running Keysat daemon - LicensePayload struct with idiomatic Go getters (IsTrial, IsFingerprintBound, IsExpiredAt, HasEntitlement) Wire-format crosscheck against the shared tests/crosscheck/vector.json (the same file the Rust, TypeScript, Python SDKs and the daemon itself test against). All four fixtures pass — v1 legacy fingerprint-bound, v2 trial with entitlements, v2 perpetual unbound, plus end-to-end PEM-load → ParseAndVerify signature roundtrip. Confirms byte-for-byte agreement across five independent implementations. No third-party dependencies. Module path: github.com/keysat-xyz/keysat-client-go go 1.21
3.4 KiB
keysat-client-go
Go SDK for Keysat — a self-hosted, Bitcoin-paid software licensing service.
Verifies LIC1-format license keys offline against an Ed25519 public key, and optionally validates them online against a running Keysat daemon.
Install
go get github.com/keysat-xyz/keysat-client-go
Stdlib only — no third-party dependencies.
Offline verification
package main
import (
"fmt"
"log"
"time"
"github.com/keysat-xyz/keysat-client-go"
)
// Embed the daemon's PEM public key at build time. Get it from your
// Keysat admin UI or `curl https://your-keysat.example/v1/pubkey`.
const publicKeyPEM = `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA...
-----END PUBLIC KEY-----`
func main() {
pub, err := keysat.LoadPublicKeyPEM(publicKeyPEM)
if err != nil { log.Fatal(err) }
licenseKey := readKeyFromUserConfig() // however your app stores it
payload, err := keysat.ParseAndVerify(licenseKey, pub)
if err != nil { log.Fatalf("license invalid: %v", err) }
if payload.IsExpiredAt(time.Now().Unix()) {
log.Fatal("license expired")
}
if !payload.HasEntitlement("pro") {
log.Fatal("license does not include 'pro' tier")
}
fmt.Println("license OK")
}
Online validation (revocation, fingerprint binding, machine cap)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
c := keysat.NewClient("https://licensing.example.com", nil)
resp, err := c.Validate(ctx, keysat.ValidateRequest{
Key: licenseKey,
ProductSlug: "myapp",
Fingerprint: machineUUID,
})
if err != nil { log.Fatalf("daemon unreachable: %v", err) }
if !resp.OK {
log.Fatalf("license rejected: %s", resp.Reason)
}
Validate returns HTTP 200 in all cases; license failures are conveyed via resp.OK + resp.Reason (bad_signature, revoked, expired, too_many_machines, etc.).
Fingerprint binding
When a key is fingerprint-bound, the daemon's first successful online validation pins the machine's fingerprint hash to the license row. Subsequent validations from a different machine fail with fingerprint_mismatch.
The SDK exposes keysat.HashFingerprint if you need to compute the hash yourself (e.g., to compare against a key's FingerprintHash field offline):
h := keysat.HashFingerprint(machineUUID)
if h != payload.FingerprintHash {
log.Fatal("license does not belong to this machine")
}
Wire format compatibility
Every SDK + the daemon agree on the LIC1 wire format. Crosscheck tests in this package run against the shared tests/crosscheck/vector.json (alongside the daemon repo) — three independently-signed fixtures (v1 legacy, v2 trial with entitlements, v2 perpetual unbound) parse to the same field values across Rust, TypeScript, Python, and Go.
When fetched standalone via go get, the crosscheck test skips gracefully (the vector file isn't bundled into the Go module). The crosscheck only runs from the parent licensing/ workspace.
API stability
This SDK is alpha alongside Keysat v0.1.0. The wire format itself is stable and won't break compatibility — license keys issued by any v0.1 daemon will keep parsing in any future SDK. The Go API surface (function names, struct fields) may settle further before v1.0; nothing here is wildly out of line with idiomatic Go but expect minor tweaks.
License
MIT — see LICENSE.