Skip to content

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

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:

pip install -e ".[dev]"

Or just the runtime package:

pip install -e .

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.

from diogenes.sdk import DiogenesSDK

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.

result = sdk.verify(manifest, source_bytes="My document")
assert result.overall.value == "valid"
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.

report = sdk.evaluate_trust("sha256:abc123...")
print(report.threshold_met, report.aggregate_score)
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]

from diogenes.sdk import generate_key_pair
private_key, public_key = generate_key_pair("ed25519")

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.

from diogenes.sdk import load_private_key
key = load_private_key(pem_bytes, password=b"secret")

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 a blinded 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 a party holding both the plaintext and that entry's commitment key can verify a revealed plaintext without ever needing decryption capability.

What this claim's commitment does and does not guarantee

Scope: this section is about encrypted-claim commitments specifically. It is not a statement about redacted fields elsewhere on the log.

  • Ciphertext. ECIES against the recipient's P-256 key: only a holder of that private key recovers the plaintext.
  • Commitment (since #517). HMAC-SHA256(commitment_key, canonical_json(plaintext)), where commitment_key is derived from the same ECDH shared secret that protects the ciphertext, under a distinct HKDF label. The key is never published. The log alone therefore reveals nothing about the plaintext — which was not true before #517: the commitment was then an unkeyed SHA-256 of a 4–5 field structured plaintext, recoverable by offline brute force in seconds, and identical for equal plaintexts across entries.
  • Unlinkability. The ephemeral key is fresh per encryption, so the same plaintext sealed twice produces unrelated commitments.
  • The trade, stated plainly. The commitment is no longer verifiable by a member of the public who has not been given that entry's commitment key. A third party handed a non-matching result cannot distinguish "wrong plaintext" from "you were given the wrong key". Three things bound that: the reduction is per entry and the recipient controls it (publishing one entry's commitment key restores full public verifiability for that entry alone — selective disclosure is what the medical-records and embargoed-journalism cases actually want); the ciphertext remains the ultimate arbiter for anyone given the recipient private key; and the alternative on offer was a commitment that was publicly verifiable and publicly brute-forceable.
  • Residual leakage. Ciphertext length, entry timestamp and position, and lookup_key correlation across entries are all visible on the public log. Neither encryption nor blinding hides them.

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). reveal_encrypted_claim also requires a JWT and is rate-limited (#517), and a reveal discloses both the plaintext and that entry's commitment key to the log operator — prefer the offline path below, which discloses neither. fetch_encrypted_claims remains unauthenticated: possession of the lookup_key is the access-control token.

EncryptedClaimReceipt, EncryptedClaimEntry, RevealResult

from diogenes.sdk import (
    EncryptedClaimEntry,
    EncryptedClaimReceipt,
    RevealResult,
)
Type Fields
EncryptedClaimReceipt entry_id: int, commitment: str (64 hex), lookup_key: str (hex token), leaf_index: int, log_entry_id: int, commitment_key: str (64 hex; "" on the relay submit path)
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.

  • plaintext may be a dict (canonicalised via RFC 8785 before encryption) or bytes (auto-wrapped as {"data": "<base64>"} so the commitment shape is well-defined).
  • recipient_p256_public_key may be either a PEM string or a loaded cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey. Non-PEM strings or non-SECP256R1 keys raise KeyOperationError.
  • lookup_key is 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.

Verifying offline (preferred)

No server involved, nothing disclosed to anyone:

from diogenes.sdk import derive_claim_commitment_key, verify_commitment

entry = public_sdk.fetch_encrypted_claims(receipt.lookup_key)[0]
key = derive_claim_commitment_key(entry.payload, recipient_priv)
assert verify_commitment(entry.payload, plaintext, commitment_key=key) is True

The poster can skip the derivation entirely and keep receipt.commitment_key. A pre-#517 entry (no commitment_alg) verifies with no key at all: verify_commitment(entry.payload, plaintext).

DiogenesSDK.reveal_encrypted_claim(entry_id, plaintext, *, commitment_key=None)

Authenticated POST to /api/v1/claims/{id}/reveal (#517). The server canonicalises the submitted plaintext, recomputes the commitment under the entry's declared algorithm, and compares against the stored one. 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.

This call discloses the plaintext and that entry's commitment key to the log operator. The plaintext was always in this request body; the key adds no information about the same entry that the body did not already carry in the clear, and is per-entry, so it says nothing about any other entry. Even so, offline verification above is the documented default.

Raises: AuthError (401, no JWT); ServerError for 404 (no such entry), 409 (the entry carries a pre-#517 unsalted commitment — verify it offline instead; server-side reveal for such entries is only ever a brute-force oracle), 400 (blinded entry, no commitment_key), and 429 (per-IP or per-(entry, caller) budget spent).

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,
)
# Keep receipt.commitment_key: it cannot be recovered from the public entry
# without the recipient's private key.
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.
# (Or verify offline with derive_claim_commitment_key — see above.)
public_sdk.set_jwt(my_jwt)  # reveal requires a JWT since #517
result = public_sdk.reveal_encrypted_claim(
    receipt.entry_id, plaintext, commitment_key=receipt.commitment_key
)
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.

from diogenes.sdk import WebhookClient

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:

with WebhookClient("http://localhost:8000") as client:
    subs = client.list_webhooks()

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]

subs = client.list_webhooks()
for sub in subs:
    print(sub.id, sub.url, sub.events)

get_webhook(subscription_id)

Get a single webhook subscription by ID.

Returns: WebhookSubscription

Raises: WebhookError if not found.

sub = client.get_webhook(42)
print(sub.active, sub.healthy)

delete_webhook(subscription_id)

Delete (deactivate) a webhook subscription.

Raises: WebhookError if not found.

client.delete_webhook(42)

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 WebhookClient uses httpx for HTTP and is safe for use in both sync and async contexts (via httpx.Client).
  • Trust evaluation uses a web-of-trust model with configurable anchors and thresholds, not a certificate authority.