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).
Install
Section titled “Install”From the repository:
pip install openentityOr run the command line without installing, with uv:
uvx openentity adopt --name "My Agent" --agreeCommand line
Section titled “Command line”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 whoamiopenentity self [--entries N] # entity document and the last N record entriesopenentity 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 payloadopenentity present --audience A --nonce N [--no-attestations]openentity verify <eid|url|file> # signatures, signer, Declaration hash, record chainopenentity dispute --subject EID [--object SHA256] --reason impersonation|illegal_content|inaccurate|other --statement TEXTopenentity find [--capability C] [-q TEXT] [--alias-type T] [--level L1]openentity withdraw --force # cannot be undoneExit 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.
Library
Section titled “Library”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 keyrecord(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 errorresult = 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.
Every public function
Section titled “Every public function”Read from the source at build time. adopt, record, withdraw, dispute, find, present and every primitive import straight from openentity.
Client (openentity/client.py)
Section titled “Client (openentity/client.py)”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 plusdidandseed_hex(keep the seed).clientis 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_hexof 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.
Primitives (openentity/core.py)
Section titled “Primitives (openentity/core.py)”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)methodnames the verification method; it defaults to the did:key ofkp, a did:web signer passesdid: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.
extrais 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:
prevre-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
mleaves are a prefix ofhashes. 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
originover 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
createdis a valid timestamp no more than 10 minutes ahead and 24 hours behindat(now). key_pair_from_jwk(jwk: dict)- An Ed25519 private JWK (RFC 8037):
dis the seed,xmust match it. generate_key_pair()key_pair_from_seed(seed: bytes)
Develop
Section titled “Develop”cd sdk/pythonuv run pytest -qscripts/verify.sh runs these tests on every push.