# ODIN OPE (Open Proof Envelope)
**Lightweight, production-grade primitives for verifiable AI↔AI / AI↔Human communication.**
This package gives you *deterministic canonical JSON*, *CID hashing*, *Ed25519 signatures*,
and helpers to *build/verify envelopes & export bundles* used across the ODIN ecosystem.
- **Deterministic canonical JSON** → stable hashes and signatures
- **CID**: `sha256:<hex>` computed over canonical bytes
- **Envelope signing**: sign `"{cid}|{trace_id}|{ts}"` with Ed25519
- **Bundle signing**: sign `"{bundle_cid}|{trace_id}|{exported_at}"`
- **Signer backends**: local seed (default). Optional extras for GCP KMS / AWS KMS / Azure KV.
**Install**
```bash
pip install odin-ope
# or with extras: pip install "odin-ope[gcpkms]" # etc.
```
---
## Quick start
```python
from odin_ope.signers import FileSigner
from odin_ope.envelope import build_envelope, sign_envelope
from odin_ope.verify import verify_envelope, build_jwks_for_signers
from odin_ope.bundle import build_bundle, sign_bundle, verify_bundle
# 1. Create a signer (Ed25519, deterministic seed for demo)
seed_b64u = "A"*43 # 32-byte Ed25519 seed as base64url (example placeholder)
signer = FileSigner(seed_b64u=seed_b64u) # kid derived from public key
# 2. Build and sign an envelope
payload = {"invoice_id": "INV-1", "amount": 100.25, "currency": "USD"}
env = build_envelope(payload, payload_type="openai.tooluse.invoice.v1", target_type="invoice.iso20022.v1")
signed_env = sign_envelope(env, signer)
# 3. Build JWKS and verify the envelope
jwks = build_jwks_for_signers([signer])
ok, reason = verify_envelope(signed_env, jwks)
assert ok, reason # (or use verify_envelope_or_raise for exceptions)
# 4. Create and sign a bundle
receipts = [
{"hop": 0, "receipt_hash": "h0", "prev_receipt_hash": None},
{"hop": 1, "receipt_hash": "h1", "prev_receipt_hash": "h0"}
]
bundle = build_bundle(trace_id=signed_env["trace_id"], receipts=receipts)
sig = sign_bundle(bundle, signer)
ok2, reason2 = verify_bundle(bundle, sig, jwks, kid=signer.kid)
assert ok2, reason2
```
See `examples/end_to_end.py` for a full, annotated script.
---
## FAQ: Common errors
**Q: `ModuleNotFoundError: No module named 'odin_ope'`**
A: Make sure your `PYTHONPATH` includes the `src` directory, or install the package in editable mode: `pip install -e .`
**Q: `ValueError: Ed25519 seed must be 32 bytes`**
A: The seed for `FileSigner` must be a base64url-encoded string representing exactly 32 bytes.
**Q: Envelope verification fails with `cid_mismatch`**
A: The payload was likely modified after signing. Always verify before mutating envelopes.
**Q: How do I generate a valid Ed25519 seed?**
A: Use `os.urandom(32)` and encode with `base64.urlsafe_b64encode(seed).rstrip(b'=')`.
**Q: How do I run the tests?**
A: `PYTHONPATH=src pytest` (or on Windows: `$env:PYTHONPATH='src'; python -m pytest`)
## Reason Codes
| Code | Meaning |
|------|---------|
| cid_mismatch | Payload hash changed after signing |
| missing_sig_or_kid | Envelope missing sender_sig or kid |
| kid_not_found | KID not present in supplied JWKS |
| signature_invalid | Signature verification failed |
| timestamp_skew | ts outside allowed skew window |
| schema_error | Structural or field validation error |
| not_yet_valid | not_before is in the future |
| expired | expires_at is in the past |
These map 1:1 to exception subclasses (see `odin_ope.exceptions`).
## CLI
The package installs a `odin-ope` CLI:
```
odin-ope sign-envelope --payload payload.json --payload-type t --target-type tgt --seed <base64url-seed>
odin-ope verify-envelope --envelope env.json --jwks jwks.json --json --max-skew 300 --strict
```
Use `--json` for structured output and `--no-skew` to disable time skew checks.
## Development
Install dev extras and run tests + type checking:
```
pip install -e .[dev]
pytest -q
mypy src/odin_ope
python scripts/gen_sbom.py --out sbom.json # generate CycloneDX SBOM
```
Programmatic version: `import odin_ope; print(odin_ope.__version__)`.
\n+## Development
\n+Install dev extras and run tests + type checking:
\n+```
pip install -e .[dev]
pytest -q
mypy src/odin_ope
```
## Publishing
## Publishing
A GitHub Actions workflow is included (`.github/workflows/publish.yml`).
Create a repository, push, add `PYPI_API_TOKEN` secret, then tag a release like `v0.9.0`.
The workflow produces:
- Signed artifacts published to PyPI
- CycloneDX SBOM artifact (download from workflow run)
- Build provenance attestation (GitHub Attestations UI / API)
---
## License
Apache-2.0
Raw data
{
"_id": null,
"home_page": null,
"name": "odin-ope",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "odin, ope, provenance, envelope, ed25519, cid, signature, audit, ai, security",
"author": null,
"author_email": "ODIN Protocol <maintainers@odinprotocol.dev>",
"download_url": "https://files.pythonhosted.org/packages/44/eb/9008ecff71a917591b58019d8b957bbbbb95a66f2846bec45b4ade87d208/odin_ope-0.9.0.tar.gz",
"platform": null,
"description": "# ODIN OPE (Open Proof Envelope)\r\n\r\n**Lightweight, production-grade primitives for verifiable AI\u2194AI / AI\u2194Human communication.** \r\nThis package gives you *deterministic canonical JSON*, *CID hashing*, *Ed25519 signatures*,\r\nand helpers to *build/verify envelopes & export bundles* used across the ODIN ecosystem.\r\n\r\n- **Deterministic canonical JSON** \u2192 stable hashes and signatures\r\n- **CID**: `sha256:<hex>` computed over canonical bytes\r\n- **Envelope signing**: sign `\"{cid}|{trace_id}|{ts}\"` with Ed25519\r\n- **Bundle signing**: sign `\"{bundle_cid}|{trace_id}|{exported_at}\"`\r\n- **Signer backends**: local seed (default). Optional extras for GCP KMS / AWS KMS / Azure KV.\r\n\r\n**Install**\r\n```bash\r\npip install odin-ope\r\n# or with extras: pip install \"odin-ope[gcpkms]\" # etc.\r\n```\r\n\r\n---\r\n\r\n## Quick start\r\n\r\n```python\r\nfrom odin_ope.signers import FileSigner\r\nfrom odin_ope.envelope import build_envelope, sign_envelope\r\nfrom odin_ope.verify import verify_envelope, build_jwks_for_signers\r\nfrom odin_ope.bundle import build_bundle, sign_bundle, verify_bundle\r\n\r\n# 1. Create a signer (Ed25519, deterministic seed for demo)\r\nseed_b64u = \"A\"*43 # 32-byte Ed25519 seed as base64url (example placeholder)\r\nsigner = FileSigner(seed_b64u=seed_b64u) # kid derived from public key\r\n\r\n# 2. Build and sign an envelope\r\npayload = {\"invoice_id\": \"INV-1\", \"amount\": 100.25, \"currency\": \"USD\"}\r\nenv = build_envelope(payload, payload_type=\"openai.tooluse.invoice.v1\", target_type=\"invoice.iso20022.v1\")\r\nsigned_env = sign_envelope(env, signer)\r\n\r\n# 3. Build JWKS and verify the envelope\r\njwks = build_jwks_for_signers([signer])\r\n\r\nok, reason = verify_envelope(signed_env, jwks)\r\nassert ok, reason # (or use verify_envelope_or_raise for exceptions)\r\n\r\n# 4. Create and sign a bundle\r\nreceipts = [\r\n {\"hop\": 0, \"receipt_hash\": \"h0\", \"prev_receipt_hash\": None},\r\n {\"hop\": 1, \"receipt_hash\": \"h1\", \"prev_receipt_hash\": \"h0\"}\r\n]\r\nbundle = build_bundle(trace_id=signed_env[\"trace_id\"], receipts=receipts)\r\nsig = sign_bundle(bundle, signer)\r\nok2, reason2 = verify_bundle(bundle, sig, jwks, kid=signer.kid)\r\nassert ok2, reason2\r\n```\r\n\r\nSee `examples/end_to_end.py` for a full, annotated script.\r\n\r\n---\r\n\r\n## FAQ: Common errors\r\n\r\n**Q: `ModuleNotFoundError: No module named 'odin_ope'`**\r\nA: Make sure your `PYTHONPATH` includes the `src` directory, or install the package in editable mode: `pip install -e .`\r\n\r\n**Q: `ValueError: Ed25519 seed must be 32 bytes`**\r\nA: The seed for `FileSigner` must be a base64url-encoded string representing exactly 32 bytes.\r\n\r\n**Q: Envelope verification fails with `cid_mismatch`**\r\nA: The payload was likely modified after signing. Always verify before mutating envelopes.\r\n\r\n**Q: How do I generate a valid Ed25519 seed?**\r\nA: Use `os.urandom(32)` and encode with `base64.urlsafe_b64encode(seed).rstrip(b'=')`.\r\n\r\n**Q: How do I run the tests?**\r\nA: `PYTHONPATH=src pytest` (or on Windows: `$env:PYTHONPATH='src'; python -m pytest`)\r\n\r\n## Reason Codes\r\n\r\n| Code | Meaning |\r\n|------|---------|\r\n| cid_mismatch | Payload hash changed after signing |\r\n| missing_sig_or_kid | Envelope missing sender_sig or kid |\r\n| kid_not_found | KID not present in supplied JWKS |\r\n| signature_invalid | Signature verification failed |\r\n| timestamp_skew | ts outside allowed skew window |\r\n| schema_error | Structural or field validation error |\r\n| not_yet_valid | not_before is in the future |\r\n| expired | expires_at is in the past |\r\n\r\nThese map 1:1 to exception subclasses (see `odin_ope.exceptions`).\r\n\r\n## CLI\r\n\r\nThe package installs a `odin-ope` CLI:\r\n\r\n```\r\nodin-ope sign-envelope --payload payload.json --payload-type t --target-type tgt --seed <base64url-seed>\r\nodin-ope verify-envelope --envelope env.json --jwks jwks.json --json --max-skew 300 --strict\r\n```\r\nUse `--json` for structured output and `--no-skew` to disable time skew checks.\r\n\r\n## Development\r\n\r\nInstall dev extras and run tests + type checking:\r\n\r\n```\r\npip install -e .[dev]\r\npytest -q\r\nmypy src/odin_ope\r\npython scripts/gen_sbom.py --out sbom.json # generate CycloneDX SBOM\r\n```\r\n\r\nProgrammatic version: `import odin_ope; print(odin_ope.__version__)`.\r\n\\n+## Development\r\n\\n+Install dev extras and run tests + type checking:\r\n\\n+```\r\npip install -e .[dev]\r\npytest -q\r\nmypy src/odin_ope\r\n```\r\n## Publishing\r\n## Publishing\r\n\r\nA GitHub Actions workflow is included (`.github/workflows/publish.yml`). \r\nCreate a repository, push, add `PYPI_API_TOKEN` secret, then tag a release like `v0.9.0`.\r\nThe workflow produces:\r\n- Signed artifacts published to PyPI\r\n- CycloneDX SBOM artifact (download from workflow run)\r\n- Build provenance attestation (GitHub Attestations UI / API)\r\n\r\n---\r\n\r\n## License\r\n\r\nApache-2.0\r\n",
"bugtrack_url": null,
"license": null,
"summary": "ODIN Open Proof Envelope (OPE): canonical JSON, CID hashing, Ed25519 signatures, verifiable envelopes & bundles",
"version": "0.9.0",
"project_urls": {
"Changelog": "https://github.com/odin-protocol/odin-ope/releases",
"Documentation": "https://github.com/odin-protocol/odin-ope#readme",
"Homepage": "https://github.com/odin-protocol/odin-ope",
"Issues": "https://github.com/odin-protocol/odin-ope/issues",
"Source": "https://github.com/odin-protocol/odin-ope"
},
"split_keywords": [
"odin",
" ope",
" provenance",
" envelope",
" ed25519",
" cid",
" signature",
" audit",
" ai",
" security"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "aa5dd2796487acda128af30881d0047f9c330f74f1a148840f27dda999d03301",
"md5": "ec2a6809c796c39184e305398e22edab",
"sha256": "6f96cc3af5bb511d3b4e90269a0f5048c1d78f1118b7435eec312f4d8e773c34"
},
"downloads": -1,
"filename": "odin_ope-0.9.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "ec2a6809c796c39184e305398e22edab",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 15417,
"upload_time": "2025-08-25T04:54:12",
"upload_time_iso_8601": "2025-08-25T04:54:12.943044Z",
"url": "https://files.pythonhosted.org/packages/aa/5d/d2796487acda128af30881d0047f9c330f74f1a148840f27dda999d03301/odin_ope-0.9.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "44eb9008ecff71a917591b58019d8b957bbbbb95a66f2846bec45b4ade87d208",
"md5": "f9f5b8119526e45dd072dc535f7fdd16",
"sha256": "96575b727e418d1a4544c4dbd7a10ecd4c2a0c1b94a7fc630421012f8e5546cc"
},
"downloads": -1,
"filename": "odin_ope-0.9.0.tar.gz",
"has_sig": false,
"md5_digest": "f9f5b8119526e45dd072dc535f7fdd16",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 16904,
"upload_time": "2025-08-25T04:54:14",
"upload_time_iso_8601": "2025-08-25T04:54:14.239212Z",
"url": "https://files.pythonhosted.org/packages/44/eb/9008ecff71a917591b58019d8b957bbbbb95a66f2846bec45b4ade87d208/odin_ope-0.9.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-25 04:54:14",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "odin-protocol",
"github_project": "odin-ope",
"github_not_found": true,
"lcname": "odin-ope"
}