odin-ope


Nameodin-ope JSON
Version 0.9.0 PyPI version JSON
download
home_pageNone
SummaryODIN Open Proof Envelope (OPE): canonical JSON, CID hashing, Ed25519 signatures, verifiable envelopes & bundles
upload_time2025-08-25 04:54:14
maintainerNone
docs_urlNone
authorNone
requires_python>=3.10
licenseNone
keywords odin ope provenance envelope ed25519 cid signature audit ai security
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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"
}
        
Elapsed time: 1.69791s