Skip to content

keysat-xyz/keysat-client-python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

keysat-licensing-client (Python)

Python client for Keysat — a Bitcoin-native self-hosted software licensing service that runs on Start9.

Verifies signed license keys offline using the issuer's public key, and (optionally) wraps the HTTP API for live validation, purchase, and free-license redemption.

Install

pip install keysat-licensing-client            # offline only
pip install keysat-licensing-client[online]    # + httpx for the online client

Requires Python 3.10+.

Five-line offline check

from keysat_licensing_client import Verifier, PublicKey

ISSUER_PUBKEY_PEM = open("assets/issuer.pub").read()  # bake this into your app
verifier = Verifier(PublicKey.from_pem(ISSUER_PUBKEY_PEM))

ok = verifier.verify(key_from_user)  # raises LicensingError on bad sig
print(f"licensed for product {ok.product_id}, expires {ok.expires_at}")

Online check (with revocation + fingerprint binding)

from keysat_licensing_client import Client

client = Client("https://license.example.com")
r = client.validate(
    key_from_user,
    product_slug="my-product",
    fingerprint="machine-fingerprint",
)
if not r.ok:
    print("server rejected:", r.reason)
    # 'revoked', 'fingerprint_mismatch', 'not_found', 'product_mismatch', etc.

The recommended pattern is offline-first, online-augmented: do the offline Verifier.verify() at boot. If that succeeds, also do an async/background client.validate() to catch revocations and seat mismatches. If the network fails, treat it as "status unknown" — don't gate the user on your server's uptime.

Purchase flow (drives the whole BTCPay round trip)

from keysat_licensing_client import Client, StartPurchaseOptions
import webbrowser

client = Client("https://license.example.com")
session = client.start_purchase(
    "my-product",
    StartPurchaseOptions(buyer_email="bob@example.com"),
)
webbrowser.open(session.checkout_url)
license_key = client.wait_for_license(session.invoice_id)
# Save license_key wherever you decided to store keys (config dir, keychain, env).

Free-license code redemption

For codes the seller created with kind free_license (no payment):

result = client.redeem_free_license(
    "my-product",
    "PRESSPASS",
)
print("redeemed:", result.license_key)

Fingerprint binding

The SDK doesn't decide WHAT to use as a fingerprint — that's a product choice. Common sources, ordered by robustness:

  • Linux: /etc/machine-id
  • macOS: ioreg -d2 -c IOPlatformExpertDevice
  • Windows: registry MachineGuid
  • Fallback: random UUID written into your app's config dir on first run

Mix in a per-product salt so fingerprints from your app can't be replayed against someone else's licensing server:

fp_input = f"{APP_NAME}|{machine_id}"
# The SDK SHA-256s this for you when you pass it to validate(...).

License

MIT OR Apache-2.0. See the upstream LICENSE file at github.com/keysat-xyz/keysat.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages