Python SDK Guide
Signing & Verification
Sign credentials as JWS tokens and verify signatures with the Python SDK.
Key Generation
Generate Ed25519 or ES256 key pairs for signing credentials.
from beltic import generate_key_pair
# Generate Ed25519 key pair (recommended)
private_key, public_key = generate_key_pair("EdDSA")
# Generate ES256 (P-256) key pair
private_key, public_key = generate_key_pair("ES256")Key Import/Export
Export Keys to PEM
from beltic import export_key_to_pem
# Export private key
private_pem = export_key_to_pem(private_key, private=True)
with open("private.pem", "wb") as f:
f.write(private_pem)
# Export public key
public_pem = export_key_to_pem(public_key, private=False)
with open("public.pem", "wb") as f:
f.write(public_pem)Import Keys from PEM
from beltic import import_key_from_pem
# Import private key
with open("private.pem", "rb") as f:
private_key = import_key_from_pem(f.read(), "EdDSA", private=True)
# Import public key
with open("public.pem", "rb") as f:
public_key = import_key_from_pem(f.read(), "EdDSA", private=False)Export/Import JWK
from beltic import export_public_key, import_public_key
# Export public key as JWK
jwk = export_public_key(public_key)
# {"kty": "OKP", "crv": "Ed25519", "x": "..."}
# Import public key from JWK
key = import_public_key(jwk, "EdDSA")Signing Credentials
Sign a DeveloperCredential
from beltic import sign_credential, SignOptions
token = sign_credential(
credential,
private_key,
SignOptions(
alg="EdDSA",
issuer_did="did:web:beltic.com",
subject_did="did:web:developer.example.com",
key_id="did:web:beltic.com#key-1",
),
)
print(token)
# eyJhbGciOiJFZERTQSIsInR5cCI6ImFwcGxpY2F0aW9uL2JlbHRpYy1kZXZlbG9wZXIrand0IiwiY...Sign an AgentCredential
token = sign_credential(
agent_credential,
private_key,
SignOptions(
alg="EdDSA",
issuer_did="did:web:beltic.com",
subject_did="did:web:agent.example.com",
key_id="did:web:beltic.com#key-1",
audience="did:web:platform.example.com", # Optional
),
)SignOptions
class SignOptions:
alg: Literal["EdDSA", "ES256"] # Signature algorithm
issuer_did: str # iss claim
subject_did: str # sub claim
key_id: str | None = None # kid header
audience: str | None = None # aud claimJWT Structure
Signed credentials follow this structure:
Header:
{
"alg": "EdDSA",
"typ": "application/beltic-developer+jwt",
"kid": "did:web:beltic.com#key-1"
}Payload:
{
"iss": "did:web:beltic.com",
"sub": "did:web:developer.example.com",
"jti": "credential-id-uuid",
"iat": 1699876800,
"nbf": 1699876800,
"exp": 1731412800,
"vc": { /* Full credential object */ }
}Verifying Credentials
Basic Verification
from beltic import verify_credential, VerifyOptions
async def key_resolver(input):
# Resolve public key from DID or key registry
# input.kid, input.alg, input.iss are available
return public_key
verified = await verify_credential(
token,
VerifyOptions(key_resolver=key_resolver),
)
print(f"Algorithm: {verified.algorithm}")
print(f"Issuer: {verified.payload['iss']}")
print(f"Credential: {verified.credential['legalName']}")Verification with Constraints
verified = await verify_credential(
token,
VerifyOptions(
key_resolver=key_resolver,
expected_issuer="did:web:beltic.com",
expected_audience="did:web:platform.example.com",
allowed_algorithms=["EdDSA", "ES256"],
),
)VerifyOptions
class VerifyOptions:
key_resolver: Callable[[KeyResolverInput], Awaitable[CryptoKey]]
expected_issuer: str | None = None # Require specific issuer
expected_audience: str | None = None # Require specific audience
allowed_algorithms: list[str] | None = None # Restrict algorithmsVerifiedCredential Result
class VerifiedCredential:
credential: dict # The verified credential (vc claim)
algorithm: str # Algorithm used (EdDSA or ES256)
header: dict # Full JWS protected header
payload: dict # Full JWT payload (iss, sub, vc, etc.)Decode Without Verification
For debugging, you can decode a token without verification:
from beltic import decode_token
decoded = decode_token(token)
print(decoded.header) # {"alg": "EdDSA", "typ": "...", "kid": "..."}
print(decoded.payload) # {"iss": "...", "sub": "...", "vc": {...}}Never trust decoded tokens without verification. Always use verify_credential for production code.
Get Credential Type
Determine if a token is a developer or agent credential:
from beltic import get_credential_type
cred_type = get_credential_type(token)
# Returns: "developer" | "agent" | NoneError Handling
SignatureError
from beltic import verify_credential, SignatureError
try:
verified = await verify_credential(token, options)
except SignatureError as e:
print(f"Step: {e.step}") # PARSE, KEY_RESOLUTION, SIGNATURE, CLAIMS, SCHEMA
print(f"Code: {e.code}") # SIG-001, SIG-008, etc.
print(f"Message: {e.message}")Common Error Codes
| Code | Description |
|---|---|
| SIG-001 | Invalid JWS structure |
| SIG-002 | Unsupported algorithm |
| SIG-003 | Algorithm "none" not allowed |
| SIG-008 | Signature verification failed |
| SIG-009 | Token expired |
| SIG-010 | Token not yet valid |
| SIG-014 | Schema validation failed |
Complete Example
import asyncio
from beltic import (
validate_developer_credential,
sign_credential,
verify_credential,
generate_key_pair,
ValidationError,
SignOptions,
VerifyOptions,
)
async def main():
# 1. Generate keys
private_key, public_key = generate_key_pair("EdDSA")
# 2. Create credential data
credential_data = {
"credentialId": "550e8400-e29b-41d4-a716-446655440000",
"legalName": "Acme AI Solutions Inc.",
"entityType": "corporation",
"kybTier": "tier_2_standard",
# ... other required fields
}
# 3. Validate
result = validate_developer_credential(credential_data)
if not result.ok:
raise ValidationError(result.errors)
# 4. Sign
token = sign_credential(
result.value,
private_key,
SignOptions(
alg="EdDSA",
issuer_did="did:web:beltic.com",
subject_did="did:web:acme.example.com",
key_id="did:web:beltic.com#key-1",
),
)
print(f"Signed token: {token[:50]}...")
# 5. Verify
async def resolver(input):
return public_key
verified = await verify_credential(
token,
VerifyOptions(key_resolver=resolver),
)
print(f"Verified: {verified.credential['legalName']}")
asyncio.run(main())