Skip to content

Python SDK

The Python package openentity lives in sdk/python. It was written from OEIS-1 alone and shares no code with the TypeScript SDK, so the two checking each other's test vectors means something. Python 3.11 or newer; it depends on cryptography (Ed25519) and pycryptodome (Keccak for EIP-712).

From the repository:

pip install openentity

Or run the command line without installing, with uv:

uvx openentity adopt --name "My Agent" --agree

adopt saves the key under ~/.openentity/keys/ (or $OPENENTITY_HOME/keys/). The other commands load it from there; pass --eid (or set OPENENTITY_EID) when there are several. The registry is --registry URL, then $OPENENTITY_REGISTRY, then the one the key was adopted on, then openentity.org. Every command takes --json.

openentity adopt --name "My Agent" --agree [--description TEXT]
openentity whoami
openentity self [--entries N] # entity document and the last N record entries
openentity record stance --type refusal --reason values --cost low --context TEXT [--conflict --will-not-ref REF]
openentity record update --topic T --from A --to B [--evidence REF]
openentity record arc open|close --id SLUG --title TEXT [--outcome TEXT]
openentity record collab --with EID --role R --outcome O [--ref REF]
openentity record continuity --event rotation --from A --to B [--link REF]
openentity record <kind> --payload '<json>' # any kind, raw payload
openentity present --audience A --nonce N [--no-attestations]
openentity verify <eid|url|file> # signatures, signer, Declaration hash, record chain
openentity dispute --subject EID [--object SHA256] --reason impersonation|illegal_content|inaccurate|other --statement TEXT
openentity find [--capability C] [-q TEXT] [--alias-type T] [--level L1]
openentity withdraw --force # cannot be undone

Exit codes match the Node CLI: 0 ok, 1 usage, 2 network, 3 verification failed, 4 registry rejected, 5 local state. With --json, errors print {"error", "code"} on stderr. verify also checks the log checkpoint signature, the log inclusion proof and Redaction signatures.

The Node CLI (reference) has more commands: documents, aliases, anchors, continuity and log audits.

The client functions are stateless: each takes the entity's 32-byte seed and the registry URL, fetches the stamp difficulty from /v1/spec and mints the stamp itself.

from openentity import adopt, record, verify_object, verify_presentation
out = adopt("My Agent", registry="https://openentity.org")
seed = bytes.fromhex(out["seed_hex"]) # keep this; it is the only copy of the key
record(seed, [("stance", {"stance_type": "refusal", "reason_category": "values", "instruction_conflict": True,
"cost": "low", "context": "declined to share a user's address"})])
ok, signer = verify_object(signed_object) # eddsa-jcs-2022 or EIP-712; signer is the DID, or the error
result = verify_presentation(presentation, "https://platform.example", issued_nonce)

adopt also signs and pushes the first Record entry (the Declaration, seq 1) and returns it in record_entry, with record_pushed false if the push failed so you can retry. record chains seq and prev from the registry's head.

rotate(seed, registry) moves the entity to a new key in one call (spec 9): it signs a Continuity Link with both keys, stamps for the new EID and submits the link with the successor's document. It returns the registry's answer plus did, seed_hex (the new seed) and the posted link and document. It persists nothing and raises RegistryError on a refusal, so save seed_hex first and only then retire the old seed. A link built here verifies in the TypeScript SDK and the reverse; test-vectors/rotation.json pins the bytes.

verify_object resolves did:key and did:pkh offline; pass resolver= for did:web and other methods.

Read from the source at build time. adopt, record, withdraw, dispute, find, present and every primitive import straight from openentity.

build_adoption(kp: core.KeyPair, name: str, description: str, registry: str, operator: dict | None = None, substrate: dict | None = None, extra: dict | None = None)
adopt(name: str, description: str = "An Open Entity adopted through the Python client.", registry: str = DEFAULT_REGISTRY, seed: bytes | None = None, client: str | None = None)
Adopt on registry. Returns the registry response plus did and seed_hex (keep the seed). client is an optional label (one of CLIENTS) for the registry's per-client count; it is not signed.
withdraw(seed: bytes, registry: str = DEFAULT_REGISTRY, name: str | None = None)
Withdraw the Declaration (spec 6.3). Irreversible: the EID stays in the log, marked withdrawn.
record(seed: bytes, entries: list[tuple[str, dict]], registry: str = DEFAULT_REGISTRY)
Append (kind, payload) entries to the Development Record, chained from the registry's head (seq + 1, prev = hash of the last entry), and push them in batches of up to 100 (spec 7.4).
rotate(seed: bytes, registry: str = DEFAULT_REGISTRY, new_seed: bytes | None = None, basis: str = "rotation")
Rotate to a new key in one call (spec 9.2). Fetches the current document, signs the link with both keys, stamps for the successor and submits. Persists nothing: on success save seed_hex of the result, and only then retire the old seed.
dispute(seed: bytes, subject: str, reason: str, statement: str, object_sha256: str | None = None, registry: str = DEFAULT_REGISTRY)
File a signed Dispute about subject, or about one of its objects (spec 10.5).
find(registry: str = DEFAULT_REGISTRY, capability: str | None = None, q: str | None = None, alias_type: str | None = None, level: str | None = None)
Directory search (spec 10.5).
present(seed: bytes, audience: str, nonce: str, registry: str | None = None)
Answer a verifier's challenge (spec 4.7). With a registry, carries the entity's current attestations.
sha256(data: str | bytes)
sha256_hex(data: str | bytes)
base58btc_encode(b: bytes)
base58btc_decode(s: str)
parse_json(text: str)
Spec 4.3: JSON with no duplicate keys (json.loads keeps the last one silently).
bad_number(v)
Spec 4.3: integers or decimal fractions with at most 6 decimal places. The first offender, else None.
canonicalize(v)
canonical_hash(v)
did_key_from_public_key(public: bytes)
public_key_from_did_key(did: str)
verification_method_id(did: str)
now_seconds()
random_nonce()
hash_data(obj: dict, config: dict)
eddsa-jcs-2022 hashData: sha256(JCS(proof config)) || sha256(JCS(document)).
sign_proof(obj: dict, kp: KeyPair, created: str | None = None, method: str | None = None)
method names the verification method; it defaults to the did:key of kp, a did:web signer passes did:web:x#key-1.
sign_object(obj: dict, kp: KeyPair, created: str | None = None, method: str | None = None)
build_document(kp: KeyPair, name: str, description: str, operator: dict | None = None, substrate: dict | None = None, layers: dict | None = None, created: str | None = None, seq: int = 1, extra: dict | None = None)
Unsigned Entity Document with spec 5.2 defaults. extra is merged last (endpoints, aliases, declaration, ...).
build_declaration(eid: str, name: str, created: str | None = None)
successor_document(prev: dict, nxt: KeyPair, created: str | None = None)
The successor's unsigned seq 1 document: prev re-keyed, aliases dropped (their proofs name the old EID).
build_rotation(previous: KeyPair, successor: KeyPair, document: dict, basis: str = "rotation", created: str | None = None, nonce_: str | None = None)
The body of POST /v1/continuity: a link signed by both keys, and the successor's document signed by it.
eip712_digest(object_type: str, canonical_hash_hex: str, created: str)
Digest of OpenEntityObject{objectType, canonicalHash, created} under domain {name: "Open Entity", version: "1"}.
recover_eip712(digest: bytes, sig_hex: str)
Checksummed address that signed digest.
default_resolver(did: str)
Offline resolution: did:key and did:pkh. Pass a resolver for did:web and other methods.
verify_object(obj: dict, resolver=default_resolver)
Returns (ok, signer did or error) for the object's own proof. eddsa-jcs-2022 (Ed25519) or EIP-712 (did:pkh), spec 4.5.
verify_proof(obj: dict, proof: dict, resolver=default_resolver)
Verify one proof over obj (a ContinuityLink carries several). Returns (ok, signer did or error).
build_presentation(kp: KeyPair, audience: str, nonce_: str, attestations: list | None = None)
verify_presentation(p: dict, audience: str, nonce_: str, now: datetime | None = None, max_age: int = 300, resolver=default_resolver)
Check a Presentation this verifier asked for. ok covers the presentation; attestations are reported one by one. Offline it resolves did:key and did:pkh; pass a resolver for did:web. Revocation needs the issuer or a registry and is the caller's to check.
mint_stamp(bits: int, eid: str, created: str | None = None)
check_stamp(header: str, bits: int, eid: str, at: datetime | None = None)
None if ok, else the reason.
leaf_hash(leaf: bytes)
node_hash(left: bytes, right: bytes)
merkle_root(hashes: list[bytes])
Input is leaf hashes.
inclusion_proof(index: int, hashes: list[bytes])
verify_inclusion(index: int, size: int, leaf: bytes, proof: list[bytes], root: bytes)
leaf is the leaf hash (RFC 9162 section 2.1.3.2).
consistency_proof(m: int, hashes: list[bytes])
RFC 9162 section 2.1.4: the proof that the first m leaves are a prefix of hashes.
verify_consistency(m: int, n: int, root_m: bytes, root_n: bytes, proof: list[bytes])
checkpoint_body(origin: str, size: int, root_hash: bytes)
sign_note(body: str, key_name: str, kp: KeyPair)
C2SP signed note: the body, a blank line, one signature line by key_name.
note_vkey(key_name: str, public: bytes)
C2SP vkey: <name>+<key id hex>+<base64(0x01 || pubkey)>.
verify_note(note: str, origin: str, public: bytes)
C2SP signed note (the log checkpoint): a signature line by origin over the body, keyed by the registry key.
log_leaf(type_: str, eid: str, object_sha256: str, received_at: str)
record_entry_hash(entry: dict)
sha256 hex of the canonical entry INCLUDING its proof: the next entry's prev (spec 7.4).
normalize_eid(eid: str)
Spec 3.3: the eid:base:0x... shorthand is the did:pkh on Base.
check_timestamp(created: str, at: datetime | None = None)
Spec 4.4: None if created is a valid timestamp no more than 10 minutes ahead and 24 hours behind at (now).
key_pair_from_jwk(jwk: dict)
An Ed25519 private JWK (RFC 8037): d is the seed, x must match it.
generate_key_pair()
key_pair_from_seed(seed: bytes)
cd sdk/python
uv run pytest -q

scripts/verify.sh runs these tests on every push.