Skip to content

Python

The official Python client for [Quidnug](https://github.com/bhmortim/quidnug), a decentralized protocol for relational, per-observer trust.

full pip install quidnug clients/python →

Quidnug Python SDK

The official Python client for Quidnug, a decentralized protocol for relational, per-observer trust.

Version 2.x of this SDK covers the full protocol surface: identity, trust, titles, event streams, anchors, guardian sets, guardian recovery, cross-domain gossip, K-of-K bootstrap, fork-block activation, and compact Merkle inclusion proofs (QDPs 0001–0010).

Install

Terminal window
pip install quidnug

Requires Python 3.9+. The package ships a py.typed marker for full type-checker support under mypy / pyright.

Thirty-second example

from quidnug import Quid, QuidnugClient
client = QuidnugClient("http://localhost:8080")
alice = Quid.generate()
bob = Quid.generate()
client.register_identity(alice, name="Alice", home_domain="contractors.home")
client.register_identity(bob, name="Bob", home_domain="contractors.home")
client.grant_trust(alice, trustee=bob.id, level=0.9, domain="contractors.home")
tr = client.get_trust(alice.id, bob.id, domain="contractors.home")
print(f"{tr.trust_level:.3f} via {''.join(tr.path)}")

More runnable examples live in examples/:

FileWhat it covers
quickstart.pyTwo-party trust + relational trust query.
event_stream.pyEmit an event; read back the stream.
guardian_recovery.pyInstall a guardian set with weighted quorum.
merkle_proof.pyVerify a compact QDP-0010 inclusion proof.

What’s in the package

QuidnugClient

HTTP client for a running Quidnug node. Every node endpoint has a corresponding typed method.

AreaMethods
Health / infohealth, info, nodes
Identityregister_identity, get_identity, query_identity_registry
Trustgrant_trust, get_trust, query_relational_trust, get_trust_edges, query_trust_registry
Titleregister_title, get_title, query_title_registry
Eventsemit_event, get_event_stream, get_stream_events
Storageipfs_pin, ipfs_get
Guardianssubmit_guardian_set_update, submit_recovery_init, submit_recovery_veto, submit_recovery_commit, submit_guardian_resignation, get_guardian_set, get_pending_recovery, get_guardian_resignations
Gossipsubmit_domain_fingerprint, get_latest_domain_fingerprint, submit_anchor_gossip, push_anchor, push_fingerprint
Bootstrapsubmit_nonce_snapshot, get_latest_nonce_snapshot, bootstrap_status
Fork-blocksubmit_fork_block, fork_block_status
Blocksget_blocks, get_tentative_blocks, get_pending_transactions
Domainslist_domains, register_domain, get_node_domains, update_node_domains

Quid, cryptographic identity

alice = Quid.generate() # fresh keypair
bob = Quid.from_private_hex(stored_key_hex) # reconstruct
carol = Quid.from_public_hex(pub_hex_from_network) # read-only
sig = alice.sign(b"hello")
ok = alice.verify(b"hello", sig)

Every identity uses ECDSA P-256 with SHA-256, DER-encoded hex signatures, matching the Go reference implementation byte-for-byte.

canonical_bytes / verify_signature

Low-level primitives exposed for advanced use cases:

from quidnug import canonical_bytes
signable = canonical_bytes(tx, exclude_fields=("signature", "txId"))
tx["signature"] = quid.sign(signable)

Canonicalization follows the round-trip-through-a-generic-object rule: serialize → decode back to a plain map → serialize again with alphabetized keys. This matches Go’s encoding/json output for map[string]interface{}, which is what the node verifies against. See schemas/types/canonicalization.md for the full specification.

verify_inclusion_proof (QDP-0010)

from quidnug import verify_inclusion_proof
ok = verify_inclusion_proof(
tx_bytes=canonical_bytes(anchor_tx),
proof_frames=gossip_msg.merkle_proof,
expected_root=origin_block.transactions_root,
)

Reconstructs the Merkle root from leaf + sibling frames. Returns True if the proof is correct and binds to the expected root.

Wire dataclasses

Every protocol-level type is a plain Python dataclass in quidnug.types: TrustEdge, IdentityRecord, Title, OwnershipStake, Event, Anchor, GuardianRef, GuardianSet, GuardianSetUpdate, GuardianRecoveryInit/Veto/Commit, GuardianResignation, DomainFingerprint, Block, AnchorGossipMessage, MerkleProofFrame, NonceSnapshot, ForkBlock, TrustResult.

All fields use snake_case; the client transparently rewrites to camelCase on the wire.

Error handling

from quidnug import QuidnugClient, ConflictError, UnavailableError, NodeError
try:
client.grant_trust(alice, trustee=bob.id, level=0.9, nonce=old_nonce)
except ConflictError as e:
print(f"Node rejected: {e.message}") # e.g. NONCE_REPLAY
except UnavailableError:
print("Node still bootstrapping, retry later")
except NodeError as e:
print(f"Transport failure: HTTP {e.status_code}")
ExceptionWhen
ValidationErrorLocal precondition failed before any network call.
ConflictErrorNode logically rejected (nonce replay, quorum failure, …).
UnavailableErrorHTTP 503 or feature-not-active.
NodeErrorTransport, 5xx after retries, unexpected response shape.
CryptoErrorSignature verify / key derivation failed.

All inherit from QuidnugError, so a catch-all is safe.

Retry policy

  • GETs are retried up to max_retries times on 5xx and 429, with exponential backoff + ±100 ms jitter. Respects Retry-After.
  • POSTs (writes) are not retried by default, repeat a write only once you’ve confirmed the server’s view via a GET, to avoid double commits on non-idempotent transactions.

Tune with constructor arguments:

client = QuidnugClient(
"http://node.local",
max_retries=5,
retry_base_delay=0.5,
timeout=60.0,
)

Type hints

PEP 561–compliant. mypy and pyright will see the full type surface:

Terminal window
pip install mypy
mypy --strict your_module.py

Running the tests

Terminal window
cd clients/python
pip install -e .[dev]
pytest -v

Protocol version compatibility

SDK versionNode versionQDPs
2.x2.x0001–0010
1.x1.xidentity, trust, title only

v2 is not wire-compatible with v1. If you have a v1-era node, upgrade the node first (all v1 data migrates automatically) and then install SDK v2.

Development

Terminal window
# Format + lint
pip install ruff
ruff format quidnug tests
ruff check quidnug tests
# Type-check
pip install mypy
mypy --strict quidnug

License

Apache-2.0, see LICENSE at the repo root.