Skip to main content

Heavymeta Stellar Utilities for Python , By: Fibo Metavinci

Project description

hvym_stellar

A Python library for secure token generation and verification using Stellar keypairs with support for expiration, access control, secret sharing, and unlimited file storage via Biscuit tokens.

Features

  • Access Tokens: Macaroon-based tokens with caveats and optional expiration
  • Secret Sharing: Encrypted secret transmission between parties
  • Encryption: Hybrid (signature-based) and asymmetric (X25519) modes
  • File Storage: Biscuit-based tokens for files of any size (no 16KB limit)
  • HVYM File Format: Structured .hvym files with binary header + JSON metadata
  • JWT Authentication: Ed25519-signed JWTs for tunnel/service authentication
  • Flexible Expiration: Tokens can expire after a set time or never expire
  • Stellar Compatible: Built on Ed25519/X25519 keys from Stellar SDK

Installation

pip install hvym_stellar

For file storage support (HVYMDataToken), also install:

pip install biscuit-auth

Quick Start

from hvym_stellar import (
    Stellar25519KeyPair, StellarSharedKey, StellarSharedDecryption,
    StellarSharedKeyTokenBuilder, StellarSharedKeyTokenVerifier,
    HVYMDataToken, TokenType,
    StellarJWTToken, StellarJWTTokenVerifier, StellarJWTSession
)
from stellar_sdk import Keypair

# Create keypairs
sender_kp = Stellar25519KeyPair(Keypair.random())
receiver_kp = Stellar25519KeyPair(Keypair.random())

Usage Examples

1. Access Tokens

# Create token with caveats
token = StellarSharedKeyTokenBuilder(
    sender_kp,
    receiver_kp.public_key(),
    token_type=TokenType.ACCESS,
    expires_in=3600,
    caveats={"user_id": "123", "role": "admin"}
)
serialized = token.serialize()

# Verify token (caveats must match ALL token caveats)
verifier = StellarSharedKeyTokenVerifier(
    receiver_kp,
    serialized,
    TokenType.ACCESS,
    caveats={"user_id": "123", "role": "admin"}
)

if verifier.valid():
    print("Token valid, expires:", verifier._get_expiration_time())

2. Secret Sharing

# Sender creates secret token
token = StellarSharedKeyTokenBuilder(
    sender_kp,
    receiver_kp.public_key(),
    token_type=TokenType.SECRET,
    secret="sensitive-data",
    expires_in=300
)
serialized = token.serialize()

# Receiver extracts secret
verifier = StellarSharedKeyTokenVerifier(
    receiver_kp, serialized, TokenType.SECRET
)
if verifier.valid():
    secret = verifier.secret()

3. Encryption/Decryption (Hybrid)

Uses signature-based format: salt|nonce|signature|ciphertext

from hvym_stellar import extract_salt_from_encrypted

# Encrypt
shared_key = StellarSharedKey(sender_kp, receiver_kp.public_key())
encrypted = shared_key.encrypt(b"Secret message")

# Decrypt (from_address REQUIRED in v0.19+)
decryptor = StellarSharedDecryption(receiver_kp, sender_kp.public_key())
sender_address = sender_kp.base_stellar_keypair().public_key
decrypted = decryptor.decrypt(encrypted, from_address=sender_address)

# Extract components if needed
salt = extract_salt_from_encrypted(encrypted)

4. Asymmetric Encryption (Recommended)

Standard X25519 encryption - simpler and more secure.

# Encrypt
shared_key = StellarSharedKey(sender_kp, receiver_kp.public_key())
encrypted = shared_key.asymmetric_encrypt(b"Secret message")

# Decrypt (no from_address needed)
decryptor = StellarSharedDecryption(receiver_kp, sender_kp.public_key())
decrypted = decryptor.asymmetric_decrypt(encrypted)

# Shared secrets
secret = shared_key.asymmetric_shared_secret()  # 32 bytes

5. File Storage (HVYMDataToken)

HVYMDataToken uses Biscuit tokens internally, allowing storage of files of any size (no 16KB macaroon limitation).

# Create from file (works with any file size!)
token = HVYMDataToken.create_from_file(
    senderKeyPair=sender_kp,
    receiverPub=receiver_kp.public_key(),
    file_path="large_document.pdf",  # Can be 1MB, 10MB, or larger
    expires_in=86400
)
serialized = token.serialize()

# Create from bytes
token = HVYMDataToken.create_from_bytes(
    senderKeyPair=sender_kp,
    receiverPub=receiver_kp.public_key(),
    file_data=large_binary_data,  # No size limit!
    filename="data.bin",
    expires_in=3600
)

# Extract (static method - recommended)
file_bytes, metadata = HVYMDataToken.extract_from_token(
    serialized_token=serialized,
    receiver_keypair=receiver_kp
)

print(f"Filename: {metadata.get('filename')}")
print(f"Size: {metadata.get('size')} bytes")
print(f"Hash: {metadata.get('hash')}")

Saving & Loading Tokens from Files

# === SENDER SIDE ===
# Create a token and save it to a file for transmission
token = HVYMDataToken.create_from_file(
    senderKeyPair=sender_kp,
    receiverPub=receiver_kp.public_key(),
    file_path="secret_document.pdf",
    expires_in=86400
)

# Save the token to a file (can be emailed, uploaded, etc.)
token.save_token_to_file("document_token.hvym")
print(f"Token saved! File info: {token.get_file_info()}")

# === RECEIVER SIDE ===
# Load and extract the file from the token
file_bytes, metadata = HVYMDataToken.load_token_from_file(
    file_path="document_token.hvym",
    receiver_keypair=receiver_kp
)

# Save the extracted file
output_filename = metadata.get('filename', 'extracted_file')
with open(output_filename, 'wb') as f:
    f.write(file_bytes)
print(f"Extracted: {output_filename} ({metadata['size']} bytes)")

6. HVYM File Format (New in v0.21.0)

The new .hvym file format provides a structured binary header with JSON metadata, making it easier to work with HVYM tokens as standalone files.

# === CREATE HVYM FILE ===
# Create a token and save it as a .hvym file
token = HVYMDataToken.create_from_file(
    senderKeyPair=sender_kp,
    receiverPub=receiver_kp.public_key(),
    file_path="important_document.pdf",
    expires_in=86400
)

# Save as .hvym file (auto-adds .hvym extension if needed)
hvym_path = token.to_hvym_file("secure_document")  # Creates "secure_document.hvym"
print(f"HVYM file saved: {hvym_path}")

# === LOAD HVYM FILE ===
# Load token from .hvym file
file_bytes, metadata = HVYMDataToken.from_hvym_file(
    path="secure_document.hvym",
    receiver_keypair=receiver_kp,
    verify_hash=True  # Verify file integrity (default: True)
)

print(f"Extracted: {metadata['filename']}")
print(f"Size: {metadata['file_size']} bytes")
print(f"Created: {metadata['created_at']}")
print(f"Format: {metadata['version']}")

# === EXTRACT TO FILE ===
# Convenience method to extract directly to a file
extracted_path = HVYMDataToken.extract_to_file(
    hvym_path="secure_document.hvym",
    receiver_keypair=receiver_kp,
    output_dir="./extracted_files"  # Optional: defaults to current directory
)
print(f"File extracted to: {extracted_path}")

# === VALIDATE HVYM FILE ===
# Validate a .hvym file without loading the full token
validation_result = HVYMDataToken.validate_hvym_file("secure_document.hvym")

if validation_result['valid']:
    print(f"✅ Valid HVYM file")
    print(f"   Format: {validation_result['format']}")
    print(f"   Version: {validation_result['version']}")
    print(f"   Filename: {validation_result['original_filename']}")
    print(f"   Size: {validation_result['file_size']} bytes")
    print(f"   Created: {validation_result['created_at']}")
else:
    print(f"❌ Invalid HVYM file: {validation_result.get('error', 'Unknown error')}")

HVYM File Format Structure

The .hvym format consists of:

┌─────────────────────┐
│ Magic Bytes (8)     │  "HVYMTOKN"
├─────────────────────┤
│ Version Major (2)   │  1
├─────────────────────┤
│ Version Minor (2)   │  0
├─────────────────────┤
│ Flags (2)           │  Reserved for future use
├─────────────────────┤
│ Header Length (4)   │  Length of JSON header
├─────────────────────┤
│ JSON Header (var)   │  Metadata in JSON format
├─────────────────────┤
│ Token Data (var)    │  Serialized HVYM token
└─────────────────────┘

JSON Header Fields:

  • version: Format version (e.g., "1.0")
  • created_at: ISO timestamp when file was created
  • original_filename: Original filename from token
  • file_size: Size of the embedded file in bytes
  • file_hash: SHA-256 hash of the original file
  • token_type: Type of token (e.g., "biscuit")

HVYM vs Legacy Token Files

Feature Legacy (.hvym) New (.hvym) Format
Header None Structured binary + JSON
Metadata In token only In header + token
Validation Load entire file Validate header only
Auto-extension Manual Automatic
Backward compatibility
File validation

Migration from Legacy Files

# Old way (still works)
token.save_token_to_file("token.hvym")
file_bytes, metadata = HVYMDataToken.load_token_from_file("token.hvym", receiver_kp)

# New way (recommended)
hvym_path = token.to_hvym_file("token")  # Auto-adds .hvym
file_bytes, metadata = HVYMDataToken.from_hvym_file(hvym_path, receiver_kp)

# Both methods are backward compatible!

How it works internally:

  1. A random shared keypair is generated
  2. The shared keypair is encrypted and sent via a macaroon (account token)
  3. The file data is stored in a Biscuit token signed with the shared keypair
  4. Both tokens are combined into a single serialized string

This enables both sender and receiver to verify the token while supporting unlimited file sizes.

Token Expiration

All token types support optional expiration. By default, convenience methods use a 1-hour expiration, but you can create non-expiring tokens by setting expires_in=None.

# Non-expiring access token
token = StellarSharedKeyTokenBuilder(
    sender_kp,
    receiver_kp.public_key(),
    token_type=TokenType.ACCESS,
    expires_in=None,  # Never expires
    caveats={"user_id": "123"}
)

# Non-expiring secret token
token = StellarSharedKeyTokenBuilder(
    sender_kp,
    receiver_kp.public_key(),
    token_type=TokenType.SECRET,
    secret="permanent-secret",
    expires_in=None  # Never expires
)

# Non-expiring data token (must override default)
token = HVYMDataToken.create_from_bytes(
    senderKeyPair=sender_kp,
    receiverPub=receiver_kp.public_key(),
    file_data=my_data,
    filename="permanent.bin",
    expires_in=None  # Override default 1-hour expiration
)

# Or use the constructor directly
token = HVYMDataToken(
    senderKeyPair=sender_kp,
    receiverPub=receiver_kp.public_key(),
    file_data=my_data,
    filename="permanent.bin"
    # expires_in defaults to None
)

Note: The convenience methods create_from_file() and create_from_bytes() default to expires_in=3600 (1 hour). To create non-expiring tokens with these methods, explicitly pass expires_in=None.

7. JWT Authentication (New in v0.22.0)

JWT tokens signed with Stellar Ed25519 keys for tunnel and service authentication. Unlike Macaroon-based tokens, JWT tokens do not require a pre-shared secret - verification uses the public key from the sub claim.

from hvym_stellar import (
    Stellar25519KeyPair, StellarJWTToken, StellarJWTTokenVerifier,
    StellarJWTSession
)
from stellar_sdk import Keypair

# Create keypairs
client_kp = Stellar25519KeyPair(Keypair.random())
server_kp = Stellar25519KeyPair(Keypair.random())
server_address = server_kp.base_stellar_keypair().public_key

# === CLIENT SIDE: Create JWT ===
token = StellarJWTToken(
    keypair=client_kp,
    audience=server_address,
    services=["pintheon", "ipfs"],
    expires_in=3600,
    claims={"custom_claim": "custom_value"}  # Optional custom claims
)
jwt_string = token.to_jwt()

# Inspect token structure
print(token.inspect())

# === SERVER SIDE: Verify JWT ===
verifier = StellarJWTTokenVerifier(jwt_string)

# Basic validation
if verifier.valid():
    print("Token is valid")

# Validation with audience and issuer checks
if verifier.valid(expected_audience=server_address, expected_issuer="hvym_tunnler"):
    client_address = verifier.get_stellar_address()
    services = verifier.get_services()
    claims = verifier.get_claims()
    print(f"Client: {client_address}")
    print(f"Services: {services}")

# Check expiration
if verifier.is_expired():
    print("Token has expired")

# verify() returns claims or raises ValueError
try:
    claims = verifier.verify(expected_audience=server_address)
except ValueError as e:
    print(f"Verification failed: {e}")

JWT Session Key Derivation

After verifying a JWT, establish an encrypted session between client and server:

# === SERVER SIDE: After JWT verification ===
session = StellarJWTSession(
    server_keypair=server_kp,
    client_stellar_address=client_address  # From verifier.get_stellar_address()
)

# Derive tunnel-specific key (uses JWT_SIGNING domain separation)
tunnel_key = session.derive_tunnel_key()  # 32 bytes

# Or derive key with custom domain
custom_key = session.derive_shared_key(domain=b"my_custom_domain")

# === CLIENT SIDE: Same derivation ===
client_session = StellarJWTSession(
    server_keypair=client_kp,
    client_stellar_address=server_address
)
client_tunnel_key = client_session.derive_tunnel_key()

# Both keys match!
assert tunnel_key == client_tunnel_key

JWT vs Macaroon Tokens

Feature JWT Tokens Macaroon Tokens
Signing Ed25519 (EdDSA) HMAC-SHA256
Verification Public key only Requires shared secret
Use Case Service authentication Access control with caveats
Attenuation No Yes (add caveats)
Standards RFC 7519 Custom

API Reference

StellarSharedKey

shared_key = StellarSharedKey(sender_keypair, receiver_public_key)

# Hybrid encryption (signature-based)
encrypted = shared_key.encrypt(message_bytes)

# Asymmetric encryption (recommended)
encrypted = shared_key.asymmetric_encrypt(message_bytes)

# Shared secrets
secret = shared_key.shared_secret()                    # Deterministic
secret = shared_key.shared_secret(salt=custom_salt)    # With salt
secret = shared_key.asymmetric_shared_secret()         # Raw X25519 (recommended)

# Hashes
hash_val = shared_key.hash_of_shared_secret()
hash_val = shared_key.asymmetric_hash_of_shared_secret()

StellarSharedDecryption

decryptor = StellarSharedDecryption(receiver_keypair, sender_public_key)

# Hybrid decryption (from_address REQUIRED in v0.19+)
decrypted = decryptor.decrypt(encrypted, from_address=sender_address)

# Asymmetric decryption (recommended)
decrypted = decryptor.asymmetric_decrypt(encrypted)

# Shared secrets (same as StellarSharedKey)
secret = decryptor.asymmetric_shared_secret()

Utility Functions

from hvym_stellar import (
    extract_salt_from_encrypted,      # 32 bytes
    extract_nonce_from_encrypted,     # 24 bytes
    extract_signature_from_encrypted, # 64 bytes
    extract_ciphertext_from_encrypted # Variable
)

HVYMDataToken

# Factory methods
token = HVYMDataToken.create_from_file(senderKeyPair, receiverPub, file_path, expires_in)
token = HVYMDataToken.create_from_bytes(senderKeyPair, receiverPub, file_data, filename, expires_in)

# Extraction (auto-detects biscuit vs legacy macaroon format)
file_bytes, metadata = HVYMDataToken.extract_from_token(serialized_token, receiver_keypair)

# HVYM File Format Methods (NEW in v0.21.0)
hvym_path = token.to_hvym_file(path, auto_extension=True)  # Save as .hvym file
file_bytes, metadata = HVYMDataToken.from_hvym_file(path, receiver_keypair, verify_hash=True)
extracted_path = HVYMDataToken.extract_to_file(hvym_path, receiver_keypair, output_dir=None)
validation_result = HVYMDataToken.validate_hvym_file(path)  # Validate without loading

# Legacy File Methods (still supported)
token.save_token_to_file("token.hvym")
file_bytes, metadata = HVYMDataToken.load_token_from_file("token.hvym", receiver_kp)

# Caveats (added as Biscuit facts)
token.add_file_type_caveat("pdf")
token.add_file_size_caveat(1048576)
token.add_file_hash_caveat("sha256_hash")

# Get file info
info = token.get_file_info()  # Returns dict with size, hash, filename, etc.

StellarJWTToken

# Create JWT token
token = StellarJWTToken(
    keypair,                    # Stellar25519KeyPair - signing key
    audience,                   # Stellar address of intended recipient
    services=None,              # Optional list of services (e.g., ["pintheon", "ipfs"])
    expires_in=3600,            # Seconds until expiration (None for no expiration)
    issuer="hvym_tunnler",      # JWT issuer (default: "hvym_tunnler")
    claims=None                 # Optional dict of custom claims
)

jwt_string = token.to_jwt()     # Generate signed JWT string
claims = token.get_claims()     # Get payload claims as dict
print(token.inspect())          # Human-readable token structure

StellarJWTTokenVerifier

# Verify JWT token
verifier = StellarJWTTokenVerifier(
    jwt_string,                 # JWT string to verify
    max_age_seconds=None        # Optional max age constraint
)

# Validation methods
verifier.valid()                                    # Basic validation
verifier.valid(expected_audience=addr)              # With audience check
verifier.valid(expected_issuer="hvym_tunnler")      # With issuer check
verifier.valid(expected_audience=addr, expected_issuer="hvym_tunnler")  # Both

# Returns claims or raises ValueError
claims = verifier.verify(expected_audience=addr)

# Getters
verifier.get_stellar_address()  # Subject (signer's Stellar address)
verifier.get_claims()           # All payload claims
verifier.get_services()         # Services list from claims
verifier.is_expired()           # Check expiration status
print(verifier.inspect())       # Human-readable verification result

StellarJWTSession

# Establish encrypted session after JWT verification
session = StellarJWTSession(
    server_keypair,             # Your Stellar25519KeyPair
    client_stellar_address      # Remote party's Stellar address
)

# Key derivation
key = session.derive_shared_key()              # Basic 32-byte shared key
key = session.derive_shared_key(domain=b"x")   # With custom domain separation
tunnel_key = session.derive_tunnel_key()       # Uses JWT_SIGNING domain

StellarSharedAccountTokenBuilder (Advanced)

For direct access to the shared keypair mechanism:

from hvym_stellar import StellarSharedAccountTokenBuilder

# Create shared account token
account_token = StellarSharedAccountTokenBuilder(
    senderKeyPair=sender_kp,
    receiverPub=receiver_kp.public_key(),
    expires_in=3600
)

# Sender gets the shared keypair
shared_kp = account_token.shared_keypair
print(f"Shared account: {shared_kp.public_key}")

# Serialize and send to receiver
serialized = account_token.serialize()

# Receiver extracts shared keypair
shared_kp = StellarSharedAccountTokenBuilder.extract_shared_keypair(
    serialized_token=serialized,
    receiverKeyPair=receiver_kp
)
# Now both parties have the same keypair for signing/verifying

Encryption Methods Comparison

Method Security Use Case
asymmetric_encrypt() HIGH - Industry standard X25519 Recommended for new code
encrypt() MODERATE - Hybrid with signatures Backward compatible

Token Comparison

Feature Access Tokens Secret Tokens Data Tokens JWT Tokens
Backend Macaroon Macaroon Biscuit + Macaroon Standard JWT
Max Size ~16KB ~16KB Unlimited ~8KB
Signing HMAC-SHA256 HMAC-SHA256 Ed25519 Ed25519 (EdDSA)
Verification Shared secret Shared secret Shared secret Public key only
File Storage No Limited Yes No
Expiration Optional Optional Optional (default: 1hr) Optional
Use Case Access control Secret sharing File transmission Service auth

Security

  • Ed25519 signatures authenticate encryption parameters
  • SHA-256 hash verification for file tokens
  • Tamper-evident checksums on serialized tokens
  • 256-bit security foundation

Version History

  • 0.22.0: JWT Authentication Support
    • New StellarJWTToken for creating Ed25519-signed JWT tokens
    • New StellarJWTTokenVerifier for verifying JWT tokens using public key
    • New StellarJWTSession for deriving shared keys after JWT authentication
    • Added TokenType.TUNNEL for JWT tunnel authentication
    • Added DomainSeparation.JWT_SIGNING for domain-separated key derivation
    • No shared secret required for verification (uses public key from sub claim)
  • 0.21.0: HVYM File Format Support
    • New .hvym file format with structured binary header + JSON metadata
    • Added to_hvym_file(), from_hvym_file(), extract_to_file(), validate_hvym_file() methods
    • Automatic .hvym extension handling
    • File validation without loading full token
    • Backward compatible with legacy token files
    • Enhanced crypto security tests for file format integrity
  • 0.20.0: HVYMDataToken now uses Biscuit tokens
    • Unlimited file size support (no more 16KB macaroon limit)
    • New StellarSharedAccountTokenBuilder for shared keypair exchange
    • Backward compatible - can still read old macaroon-based tokens
    • Requires biscuit-auth package for file storage
  • 0.19.0: BREAKING CHANGES - See HVYM_STELLAR.md
    • recieverPubreceiverPub, recieverKeyPairreceiverKeyPair
    • decrypt() now requires from_address parameter
  • 0.18.1: Improved crypto methods, nonce signing
  • 0.18.0: Added HVYMDataToken for file storage
  • 0.17.0: Asymmetric encryption support
  • 0.16.0: Asymmetric key derivation

License

MIT License - See LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hvym_stellar-0.23.0.tar.gz (35.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hvym_stellar-0.23.0-py2.py3-none-any.whl (30.9 kB view details)

Uploaded Python 2Python 3

File details

Details for the file hvym_stellar-0.23.0.tar.gz.

File metadata

  • Download URL: hvym_stellar-0.23.0.tar.gz
  • Upload date:
  • Size: 35.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.25

File hashes

Hashes for hvym_stellar-0.23.0.tar.gz
Algorithm Hash digest
SHA256 2c31fbb4034d4fbaed36157c878b0d040f2a4b8c44d6521ce6fe8422d6e1492d
MD5 ab4b4fb4d204a4b580783cac1b19b9c9
BLAKE2b-256 3d03687a1bf1f1a925dd6e17501eb270b9f7b844e1189bd56789de7c9dfd6f05

See more details on using hashes here.

File details

Details for the file hvym_stellar-0.23.0-py2.py3-none-any.whl.

File metadata

File hashes

Hashes for hvym_stellar-0.23.0-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 afc06a4452c02f6b0dc25e699a4edadfaa957e81cfac914a71519a7809178513
MD5 0dd571e066c674c98d8e09edb832d961
BLAKE2b-256 f0b306c6a5b8617a28e7858c727bcade62dc7ac4b76372e26dae9b82f6de4546

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page