Diogenes Developer SDK Reference¶
The Diogenes Developer SDK provides a high-level Python API for document signing, verification, trust evaluation, and webhook management. It wraps the core Diogenes modules behind a clean facade so you can integrate cryptographic provenance into your application with minimal boilerplate.
Installation¶
From a GitHub Release asset (recommended for partners)¶
Internal teams and partners install a pinned, versioned wheel
attached to a GitHub Release on the (private) ubiquitousthey/diogenes
repository. See Installing the Diogenes SDK for
the full guide, including CI usage, latest-release lookup, and
pyproject.toml pinning.
Quick form, for tag v0.0.2:
gh release download v0.0.2 \
--repo ubiquitousthey/diogenes \
--pattern '*.whl' \
--dir /tmp
pip install /tmp/diogenes_sdk-0.0.2-py3-none-any.whl
gh must be authenticated with a PAT (repo scope) — the repo is
private, so the raw release-asset URL is not directly pip-installable.
From a local checkout (development)¶
If you're working in the monorepo itself, install editable with dev dependencies:
Or just the runtime package:
The SDK is included in the diogenes package at diogenes.sdk.
Quickstart¶
from diogenes.sdk import DiogenesSDK, generate_key_pair, compute_fingerprint
from diogenes.core.transparency_log import TransparencyLogService
from diogenes.core.schemas import EventType
from diogenes.core.keys import _serialize_public_key, compute_fingerprint as core_fp
# 1. Set up a transparency log
log = TransparencyLogService()
# 2. Generate and register a key pair
private_key, public_key = generate_key_pair("ed25519")
fp = core_fp(public_key)
log.append_entry(EventType.KEY_REGISTRATION, {
"fingerprint": fp,
"algorithm": "ed25519",
"public_key": _serialize_public_key(public_key),
})
# 3. Create the SDK client
sdk = DiogenesSDK(transparency_log=log)
# 4. Sign a document
receipt = sdk.sign("Hello, Diogenes!", private_key)
manifest, attestation = receipt.manifest, receipt.attestation
# receipt.leaf_index → pass to GET /api/v1/log/proof/{leaf_index}
# receipt.log_entry_id → autoincrement PK on the log
# Legacy 2-tuple unpack still works: ``manifest, attestation = sdk.sign(...)``
# 5. Verify the document
result = sdk.verify(manifest, source_bytes="Hello, Diogenes!")
print(result.overall.value) # "valid"
See examples/sdk_quickstart.py for a complete runnable example including trust evaluation and webhooks.
Core Classes¶
DiogenesSDK¶
The primary entry point. Wraps signing, verification, and trust evaluation.
Constructor¶
DiogenesSDK(
transparency_log: TransparencyLogService,
endorsement_service: EndorsementService | None = None,
trust_config: TrustConfiguration | None = None,
)
| Parameter | Required | Description |
|---|---|---|
transparency_log |
Yes | The transparency log service for key registration and attestation recording. |
endorsement_service |
No | Required for evaluate_trust() and explain_trust(). |
trust_config |
No | Required for evaluate_trust() and explain_trust(). |
Methods¶
sign(content, private_key, attestation_type="authorship", predecessors=None, intent_statement=None)¶
Sign content and produce a manifest with an attestation.
| Parameter | Type | Default | Description |
|---|---|---|---|
content |
bytes \| str |
-- | The document content to sign. |
private_key |
PrivateKey |
-- | The signer's private key (must be registered). |
attestation_type |
str |
"authorship" |
One of: authorship, editorial, review, publication, custodial_transcription, translation, compilation. |
predecessors |
list[str] \| None |
None |
Predecessor attestation IDs for DAG chaining. |
intent_statement |
str \| None |
None |
Human-readable intent statement. |
Returns: SignReceipt — a frozen dataclass exposing manifest, attestation, log_entry_id, leaf_index, fingerprint, and pseudonym. Iterates as (manifest, attestation) so legacy callers can still unpack with manifest, attestation = sdk.sign(...). leaf_index is the 0-based Merkle position — pass it to GET /api/v1/log/proof/{leaf_index} to fetch an inclusion proof for offline-verification bundles (issue #375).
Raises: SigningError if signing fails or the server response is missing required receipt fields.
receipt = sdk.sign("My document", private_key)
print(receipt.attestation.id, receipt.attestation.signature)
print(receipt.log_entry_id, receipt.leaf_index)
# Legacy unpack still works:
manifest, attestation = sdk.sign("My document", private_key)
verify(manifest, source_bytes=None)¶
Verify a signed manifest.
| Parameter | Type | Default | Description |
|---|---|---|---|
manifest |
Manifest |
-- | The signed manifest to verify. |
source_bytes |
bytes \| str \| None |
None |
Original content for hash verification. If None, hash-only verification is used. |
Returns: VerificationResult with overall, layer1 (crypto), layer2 (key status), and optionally layer3 (trust) results.
Raises: VerificationError if verification encounters an error.
evaluate_trust(fingerprint)¶
Evaluate trust for a key fingerprint using the configured trust anchors and endorsement graph.
| Parameter | Type | Description |
|---|---|---|
fingerprint |
str |
SHA-256 fingerprint of the public key (with sha256: prefix). |
Returns: TrustPathReport with aggregate_score, threshold_met, and contributing_paths.
Raises: TrustEvaluationError if trust evaluation is not configured or fails.
explain_trust(fingerprint)¶
Get a human-readable trust explanation for a key.
| Parameter | Type | Description |
|---|---|---|
fingerprint |
str |
SHA-256 fingerprint of the public key. |
Returns: TrustExplanation with trusted, score, summary, contributing_paths, and recommendations.
Raises: TrustEvaluationError if trust evaluation is not configured or fails.
explanation = sdk.explain_trust("sha256:abc123...")
print(explanation.summary)
# "Yes, you should trust this key. It scores 0.90 against ..."
Key Management¶
generate_key_pair(algorithm="ed25519")¶
Generate a cryptographic key pair.
| Parameter | Type | Default | Description |
|---|---|---|---|
algorithm |
str |
"ed25519" |
One of "ed25519", "ecdsa-p256", "rsa-2048". |
Returns: tuple[PrivateKey, PublicKey]
compute_fingerprint(public_key)¶
Compute the SHA-256 fingerprint of a public key.
Returns: Hex-encoded string (64 characters).
from diogenes.sdk import compute_fingerprint
fp = compute_fingerprint(public_key)
# "a1b2c3d4e5f6..."
serialize_private_key(key, password=None)¶
Serialize a private key to PEM format, optionally encrypted.
from diogenes.sdk import serialize_private_key
pem = serialize_private_key(private_key, password=b"secret")
load_private_key(data, password=None)¶
Load a private key from PEM-encoded bytes.
Trust Configuration¶
load_trust_config(source)¶
Load a TrustConfiguration from a JSON string.
from diogenes.sdk import load_trust_config
config = load_trust_config('{"direct_signer_anchors": [{"fingerprint": "sha256:abc", "weight": 0.9}], "minimum_threshold": 0.5}')
Raises: TrustConfigError on invalid JSON or validation failure.
load_trust_config_file(path)¶
Load a TrustConfiguration from a JSON file on disk.
from diogenes.sdk import load_trust_config_file
config = load_trust_config_file("trust-policy.json")
Raises: TrustConfigError on file read or validation failure.
Encrypted Claims¶
The Diogenes server records opaque metadata claims on the public transparency
log via the encrypted-claims primitive (ECIES P-256 + AES-GCM, with an SHA-256
commitment for auditor-verifiable reveal). The SDK exposes a high-level wrapper
so consumers do not need to re-implement the cryptography against the raw
/api/v1/claims/* endpoints.
When to use it. Use cases that need opaque-on-the-log metadata — sealed contracts pre-disclosure, medical-records claims, embargoed-journalism attestations, and the Diogenes Vault sealed-manifest mode — are the natural consumers. The encrypted payload sits on the public log indefinitely; only holders of the recipient's P-256 private key can decrypt it, and auditors can verify a revealed plaintext against the on-chain commitment without ever needing decryption capability.
Auth. post_encrypted_claim requires a JWT (call sdk.login(...) first)
and the signing key must satisfy the server's hardware-signer policy
(require_hardware_signer). The fetch_* and reveal_* endpoints are
unauthenticated — possession of the lookup_key is the access-control token.
EncryptedClaimReceipt, EncryptedClaimEntry, RevealResult¶
| Type | Fields |
|---|---|
EncryptedClaimReceipt |
entry_id: int, commitment: str (SHA-256 hex), lookup_key: str (hex token) |
EncryptedClaimEntry |
id, timestamp, event_type, payload, previous_hashes, entry_hashes |
RevealResult |
valid: bool, stored_commitment: str, recomputed_commitment: str |
DiogenesSDK.post_encrypted_claim(plaintext, *, recipient_p256_public_key, lookup_key=None)¶
Encrypts the plaintext against the recipient's P-256 public key and posts the
sealed payload to the transparency log. Returns an EncryptedClaimReceipt.
plaintextmay be adict(canonicalised via RFC 8785 before encryption) orbytes(auto-wrapped as{"data": "<base64>"}so the commitment shape is well-defined).recipient_p256_public_keymay be either a PEM string or a loadedcryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey. Non-PEM strings or non-SECP256R1 keys raiseKeyOperationError.lookup_keyis optional; when omitted the SDK uses the cryptographically random token generated by the core encryption helper. Supply a value to group multiple posts under one retrieval token.
Raises: KeyOperationError (malformed key / wrong curve), AuthError
(no JWT or hardware-signer-policy failure), ServerError for any other
4xx/5xx response.
DiogenesSDK.fetch_encrypted_claims(lookup_key)¶
Unauthenticated GET. Returns a list[EncryptedClaimEntry] in ascending
id order. Empty list when no entries match.
DiogenesSDK.decrypt_claim(entry, *, recipient_private_key)¶
Decrypts an EncryptedClaimEntry entirely in-process — no HTTP call is
made and the private key never crosses the wire. recipient_private_key
must be a loaded EllipticCurvePrivateKey object (callers handle PEM
loading and passwords themselves). Returns the original plaintext dict.
DiogenesSDK.reveal_encrypted_claim(entry_id, plaintext)¶
Unauthenticated POST to /api/v1/claims/{id}/reveal. The server canonicalises
the submitted plaintext, recomputes SHA-256, and compares against the entry's
stored commitment. The SDK also performs the same recomputation locally and
bundles both commitments into a RevealResult so callers can diff them when
valid=False without parsing the server response.
Worked example: post → fetch → decrypt → reveal¶
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from diogenes.sdk import DiogenesSDK, LocalKeystoreSigner
# 1. Generate a recipient P-256 key pair locally (kept by the recipient).
recipient_priv = ec.generate_private_key(ec.SECP256R1())
recipient_pem = (
recipient_priv.public_key()
.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
.decode()
)
# 2. Log in with a hardware-backed credential (required for POST).
sdk = DiogenesSDK(server_url="https://trustdiogenes.com")
sdk.login(fingerprint=my_fp, password="hunter2")
# 3. Post the encrypted claim.
plaintext = {
"claim_type": "training_completion",
"trainee_id": "alice",
"completion_date": "2026-03-01",
}
receipt = sdk.post_encrypted_claim(
plaintext,
recipient_p256_public_key=recipient_pem,
)
print(receipt.entry_id, receipt.commitment, receipt.lookup_key)
# 4. (Recipient side) Fetch by lookup_key (no auth required).
public_sdk = DiogenesSDK(server_url="https://trustdiogenes.com")
entries = public_sdk.fetch_encrypted_claims(receipt.lookup_key)
assert len(entries) == 1
# 5. Decrypt locally — the private key never leaves this process.
revealed = public_sdk.decrypt_claim(
entries[0], recipient_private_key=recipient_priv
)
assert revealed == plaintext
# 6. (Auditor side) Verify the revealed plaintext against the commitment.
result = public_sdk.reveal_encrypted_claim(receipt.entry_id, plaintext)
assert result.valid is True
assert result.stored_commitment == result.recomputed_commitment == receipt.commitment
Posting raw bytes¶
# Raw bytes are wrapped automatically; decrypt returns the wrapper dict
# and the caller base64-decodes the "data" key to recover the bytes.
import base64
receipt = sdk.post_encrypted_claim(
b"\x00\x01\x02 binary blob",
recipient_p256_public_key=recipient_pem,
)
entry = public_sdk.fetch_encrypted_claims(receipt.lookup_key)[0]
decrypted = public_sdk.decrypt_claim(entry, recipient_private_key=recipient_priv)
original_bytes = base64.b64decode(decrypted["data"])
Webhook Management¶
WebhookClient¶
Client for managing webhook subscriptions against the Diogenes REST API.
Constructor¶
WebhookClient(
base_url: str,
api_key: str | None = None,
http_client: httpx.Client | None = None,
)
| Parameter | Type | Default | Description |
|---|---|---|---|
base_url |
str |
-- | Base URL of the Diogenes server (e.g. http://localhost:8000). |
api_key |
str \| None |
None |
Optional API key for authentication. |
http_client |
httpx.Client \| None |
None |
Optional pre-configured HTTP client. |
Supports context manager usage:
create_webhook(url, events=None, secret=None, key_fingerprint=None, document_hash=None)¶
Create a new webhook subscription.
| Parameter | Type | Default | Description |
|---|---|---|---|
url |
str |
-- | Callback URL to receive events. |
events |
list[str] \| None |
None |
Event types to subscribe to. Empty list = all events. |
secret |
str \| None |
None |
Shared secret for HMAC-SHA256 payload signatures. |
key_fingerprint |
str \| None |
None |
Filter events by key fingerprint. |
document_hash |
str \| None |
None |
Filter events by document hash. |
Returns: WebhookSubscription
Raises: WebhookError on failure.
sub = client.create_webhook(
url="https://example.com/hook",
events=["attestation.created", "key.registered"],
secret="my-shared-secret",
)
print(sub.id)
list_webhooks()¶
List all active webhook subscriptions.
Returns: list[WebhookSubscription]
get_webhook(subscription_id)¶
Get a single webhook subscription by ID.
Returns: WebhookSubscription
Raises: WebhookError if not found.
delete_webhook(subscription_id)¶
Delete (deactivate) a webhook subscription.
Raises: WebhookError if not found.
WebhookSubscription¶
Data class representing a webhook subscription.
| Field | Type | Description |
|---|---|---|
id |
int |
Unique identifier. |
url |
str |
Callback URL. |
events |
list[str] |
Subscribed event types. |
key_fingerprint |
str \| None |
Key fingerprint filter. |
document_hash |
str \| None |
Document hash filter. |
active |
bool |
Whether the subscription is active. |
healthy |
bool |
Whether the endpoint is healthy. |
retry_count |
int |
Consecutive delivery failures. |
created_at |
str \| None |
ISO-8601 creation timestamp. |
Exceptions¶
All SDK exceptions inherit from DiogenesSDKError.
| Exception | When raised |
|---|---|
DiogenesSDKError |
Base class for all SDK errors. |
SigningError |
Signing fails (e.g. unregistered or revoked key). |
VerificationError |
Verification encounters an error. |
TrustConfigError |
Trust configuration loading or validation fails. |
TrustEvaluationError |
Trust evaluation not configured or fails. |
KeyOperationError |
Key operation fails. |
WebhookError |
Webhook API call fails. |
AuthError |
Authentication against the server fails (raised by post_encrypted_claim when no JWT is set or the signing key fails the hardware-signer policy). |
ServerError |
Server returned an unexpected error response. Exposes status_code and detail attributes. |
from diogenes.sdk import DiogenesSDKError, SigningError, WebhookError
try:
sdk.sign("doc", unregistered_key)
except SigningError as e:
print(f"Signing failed: {e}")
Supported Event Types¶
The following event types can be used when creating webhook subscriptions:
| Event | Description |
|---|---|
attestation.created |
A new attestation was signed and recorded. |
key.registered |
A new key was registered on the transparency log. |
key.revoked |
A key was revoked. |
endorsement.created |
A new endorsement was accepted. |
endorsement.revoked |
An endorsement was revoked. |
Architecture Notes¶
- The SDK is a facade over the core Diogenes modules (
diogenes.core.*). Advanced users can import core modules directly for lower-level access. - Signing and verification are synchronous operations.
- The
WebhookClientuses httpx for HTTP and is safe for use in both sync and async contexts (viahttpx.Client). - Trust evaluation uses a web-of-trust model with configurable anchors and thresholds, not a certificate authority.