Metadata-Version: 2.4
Name: hashsign-python
Version: 0.1.0
Summary: Official Python SDK for HashSign — programmatic document hashing, signing, and Hedera ledger verification for AI agents.
Project-URL: Homepage, https://hashsign.io
Project-URL: Documentation, https://docs.hashsign.io/sdk/python
Project-URL: Repository, https://github.com/hashsign/hashsign-platform
Author: HashSign
License: Proprietary
Keywords: ai-agents,audit,hashsign,hedera,signing
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: cryptography>=43.0.0
Requires-Dist: httpx>=0.27.0
Requires-Dist: pydantic>=2.7.0
Provides-Extra: dev
Requires-Dist: mypy>=1.11.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: pyyaml>=6.0; extra == 'dev'
Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
Description-Content-Type: text/markdown

# hashsign-python

Official Python SDK for [HashSign](https://hashsign.io) — hash documents locally, request cryptographic verification payloads, and retrieve Hedera Hashgraph audit proofs. Designed for AI agents that need programmatic, auditable signing.

Requires **Python 3.10+**.

## Install

```bash
pip install hashsign-python
```

From this monorepo:

```bash
cd sdks/python
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```

## Quickstart

```python
from hashsign import HashSignClient

client = HashSignClient(api_key="hs_live_...")

document = client.documents.upload("contract.pdf")
signed = client.documents.sign(document.id, metadata={"agentId": "invoice-bot"})
proof = client.documents.verify(document.id)

print(signed.payload.signature, signed.document.status)
print(proof.hedera_transaction_id, proof.verified)
```

`sign()` returns the full `SignDocumentResponse` from the API — `.payload` is the detached signature, `.document` reflects the new status.

### Hybrid ML-DSA-65 envelope

Pass `pqc_mode=True` when the API key has `requirePqcSignatures` or when you want a `hybrid-ecdsa-ml-dsa` envelope. The post-quantum half is FIPS 204 ML-DSA-65 over the raw 32-byte SHA-256 digest (pure ML-DSA, not HashML-DSA). The signature is 3309 bytes. Hedera Consensus Service stores only the 32-byte hybrid anchor, because topic messages are capped at 1024 bytes. The landing-page example topic is `0.0.9001`; live topics come from the proof's `topic_id`.

```python
signed = client.documents.sign(document.id, pqc_mode=True)
proof = client.documents.verify(document.id)
```

Recompute the anchor locally and compare it to the HCS message field `hybridAnchor`:

```python
import hashlib

payload = signed.payload
anchor = hashlib.sha256(
    f"{payload.signature}{payload.pqc.signature}".encode()
).hexdigest()
assert anchor == payload.pqc.hybrid_anchor
```

`payload.pqc.public_key` is the 1952-byte ML-DSA-65 public key (hex). Verify the signature over the raw digest bytes with any FIPS 204 implementation. The Python SDK requests the envelope; it does not bundle a lattice library.

### Async

```python
import asyncio
from hashsign import AsyncHashSignClient

async def main() -> None:
    async with AsyncHashSignClient(api_key="hs_live_...") as client:
        document = await client.documents.upload("contract.pdf")
        await client.documents.sign(document.id)
        proof = await client.documents.verify(document.id)
        print(proof.network, proof.verified)

asyncio.run(main())
```

### Rotate agent credentials

```python
rotated = client.keys.rotate()
print(rotated.key.secret)  # shown once — persist immediately
```

## Configuration

| Argument | Default | Description |
| --- | --- | --- |
| `api_key` | required | `hs_live_...` or `hs_test_...` |
| `base_url` | `https://api.hashsign.io/v1` | API origin including `/v1` |
| `timeout` | `30.0` | httpx timeout (seconds) |
| `transport` | `None` | Custom httpx transport (tests) |

## Errors

All failures — HTTP status errors, transport failures, and malformed responses — subclass `HashSignError`, so one `except HashSignError` boundary is sufficient:

- `AuthenticationError` — invalid or revoked key (401 / 403)
- `ValidationError` — request / hash mismatch (400 / 422), or malformed local metadata
- `RateLimitError` — includes `retry_after_seconds` (429)
- `HederaNetworkError` — consensus or mirror-node outage (502 / 503)
- `NotFoundError` / `ConflictError`
- `APIConnectionError` — DNS, refused connection, TLS; wraps `httpx.TransportError`
- `APITimeoutError` — subclass of `APIConnectionError` for connect / read timeouts
- `InvalidResponseError` — 2xx body that is not JSON, empty, or fails schema validation

In the async client, file hashing runs in a worker thread (`asyncio.to_thread`) so large uploads never block the event loop.

## Local crypto

`hashsign.crypto` hashes files with SHA-256 and can produce detached Ed25519 signatures. Production deployments should swap those helpers for Hedera operator-key signing (`hedera-sdk-py`) at the marked HOOK comments.

## Tests

```bash
pytest
```

The suite uses `httpx.MockTransport` and does not call a live API or Hedera network. `tests/test_contract.py` additionally validates every request body the SDK emits against `packages/shared/openapi.yaml`, so SDK/spec drift fails CI.
