Metadata-Version: 2.4
Name: vera-anchor
Version: 0.2.0
Summary: Local Python SDK for Vera Anchor
Author-email: Andrew McClure <andrew@veraanchor.com>
License-Expression: LicenseRef-VeraAnchor-MIT-Commons-Clause
Project-URL: Homepage, https://veraanchor.com
Project-URL: Repository, https://github.com/veraanchor/vera-anchor-py
Project-URL: Issues, https://github.com/veraanchor/vera-anchor-py/issues
Project-URL: Changelog, https://github.com/veraanchor/vera-anchor-py/blob/main/CHANGELOG.md
Project-URL: Documentation, https://github.com/veraanchor/vera-anchor-py/blob/main/README.md
Keywords: vera,anchor,sdk,hashing,ingest,evidence,hedera
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
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: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Security
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx<1.0,>=0.27
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: mypy>=1.8; extra == "dev"
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Dynamic: license-file

````markdown
# vera-anchor

Local-first Python SDK for deterministic evidence generation, ingest workflows, and dataset anchoring for [Hash Factory](https://hf.veraanchor.com). Part of the [Vera Anchor](https://veraanchor.com) ecosystem.

Raw data never leaves your machine. Only derived evidence packages are submitted to Hash Factory when you choose to do so.

> **License:** Source-available under MIT with Commons Clause.  
> See https://github.com/VeraAnchor/vera-anchor-py/blob/main/LICENSE.

## Installation

```bash
pip install vera-anchor
```

Requires Python 3.10+.

## What it does

`vera-anchor` builds deterministic evidence packages on your machine composed of SHA3-512 hashes, Merkle proofs, bundle manifests, fingerprints, and receipts. It can then optionally submit that evidence to Hash Factory for registration, HCS anchoring, publication, and certificate issuance.

Two operating modes:

- **Local only** — build evidence, inspect it, and keep raw files private. No Hash Factory or Hedera network calls.
- **Local then submit** — build evidence locally, then submit the derived evidence package to Hash Factory.

## Quickstart (no API key needed)

Generate a local evidence package for any directory — no account and no network required:

```python
import asyncio
import pathlib
import tempfile

from vera_anchor.datasets.remote import (
    ExecuteDatasetAnchorLocalOnlyInput,
    execute_dataset_anchor_local_only,
)
from vera_anchor.datasets.types import DatasetIdentity


async def main():
    root = pathlib.Path(tempfile.mkdtemp())
    (root / "hello.txt").write_text("hello world")
    (root / "data.json").write_text('{"x": 1}')

    result = await execute_dataset_anchor_local_only(
        ExecuteDatasetAnchorLocalOnlyInput(
            identity=DatasetIdentity(
                dataset_key="my-org.demo.quickstart",
                program="demo",
                version_label="v1",
            ),
            root_dir=str(root),
            evidence_pointer=f"file://{root}",
            hooks=None,
        )
    )

    print("merkle_root:  ", result.local.evidence.merkle_root)
    print("bundle_digest:", result.local.evidence.bundle_digest)
    print("receipt_id:   ", result.local.receipt["receipt_id"])


asyncio.run(main())
```

Run the same directory twice with the same identity and rules and the deterministic content hashes are identical.

## Dataset flow

For directory-backed datasets.

Dataset content identity is network-independent. The same canonical dataset produces the same file hashes, Merkle root, bundle manifest, bundle digest, dataset fingerprint, and evidence idempotency key regardless of whether the evidence is later registered on Hedera testnet or mainnet.

For `register_and_anchor` operations, `hedera_network` identifies the target ledger and becomes part of the network-bound plan and receipt identity. For new integrations, explicitly provide either:

```python
hedera_network="testnet"
```

or:

```python
hedera_network="mainnet"
```

Hash Factory may retain organization-policy fallback behavior for compatibility, but explicit network selection is the preferred SDK contract.

### Local only

Local-only dataset evidence is not bound to a Hedera network.

```python
import asyncio

from vera_anchor.datasets.remote import (
    ExecuteDatasetAnchorLocalOnlyInput,
    execute_dataset_anchor_local_only,
)
from vera_anchor.datasets.types import DatasetIdentity


async def main():
    result = await execute_dataset_anchor_local_only(
        ExecuteDatasetAnchorLocalOnlyInput(
            identity=DatasetIdentity(
                dataset_key="<org_id>.<program>.<name>",
                program="my_program",
                version_label="v1",
            ),
            root_dir="/path/to/dataset",
            evidence_pointer="file:///path/to/dataset",
            hooks=None,
        )
    )

    print(result.local.receipt)
    print(result.local.evidence)


asyncio.run(main())
```

A local hash-only receipt does not contain `hedera_network`. The resulting evidence can later be submitted to testnet or mainnet without changing its dataset fingerprint, bundle digest, Merkle root, or evidence idempotency key.

### Plan a network-bound dataset anchor

You can inspect the deterministic network-bound operation before submitting evidence:

```python
import asyncio
import os

from vera_anchor import HfLocalAuth, HfLocalClientConfig
from vera_anchor.datasets.remote import plan_dataset_anchor_remote


async def main():
    config = HfLocalClientConfig(
        base_url="https://hfapi.veraanchor.com",
        auth=HfLocalAuth(apiKey=os.environ["HF_API_KEY"]),
    )

    plan = await plan_dataset_anchor_remote(
        config,
        {
            "mode": "register_and_anchor",
            "identity": {
                "dataset_key": "<org_id>.<program>.<name>",
                "program": "my_program",
                "version_label": "v1",
            },
            "hedera_network": "testnet",
            "issue_certificate": False,
        },
    )

    print(plan["hedera_network"])
    print(plan["plan_id"])
    print(plan["steps"])


asyncio.run(main())
```

Explicit testnet and mainnet plans have distinct network-bound plan identities while leaving the underlying dataset content identity unchanged.

### Local then submit to Hash Factory

```python
import asyncio
import os

from vera_anchor import HfLocalAuth, HfLocalClientConfig
from vera_anchor.datasets.remote import (
    ExecuteDatasetAnchorLocalThenSubmitInput,
    execute_dataset_anchor_local_then_submit,
)
from vera_anchor.datasets.types import DatasetIdentity


async def main():
    config = HfLocalClientConfig(
        base_url="https://hfapi.veraanchor.com",
        auth=HfLocalAuth(apiKey=os.environ["HF_API_KEY"]),
    )

    result = await execute_dataset_anchor_local_then_submit(
        config,
        ExecuteDatasetAnchorLocalThenSubmitInput(
            identity=DatasetIdentity(
                dataset_key="<org_id>.<program>.<name>",
                program="my_program",
                version_label="v1",
            ),
            hedera_network="testnet",
            root_dir="/path/to/dataset",
            evidence_pointer="file:///path/to/dataset",
            display_name="My Dataset",
            publish_visibility="unlisted",
            set_active=True,
            issue_certificate=False,
            hooks=None,
        ),
    )

    print(result.local.receipt)
    print(result.remote["receipt"])
    print(result.remote.get("hedera_network"))


asyncio.run(main())
```

Use `hedera_network="mainnet"` only when you intend the registration and anchor operation to target Hedera mainnet.

The local portion of the workflow remains network-independent. The remote plan, registration, publication, and receipt are bound to the selected Hedera network.

### Verify

```python
import asyncio
import os

from vera_anchor import HfLocalAuth, HfLocalClientConfig
from vera_anchor.datasets.remote import verify_dataset_anchor_remote


async def main():
    config = HfLocalClientConfig(
        base_url="https://hfapi.veraanchor.com",
        auth=HfLocalAuth(apiKey=os.environ["HF_API_KEY"]),
    )

    result = await verify_dataset_anchor_remote(
        config,
        {
            "receipt": receipt,  # from a previous run
            "bundle": bundle,    # from a previous run
            "root_dir": "/path/to/dataset",  # optional local consistency check
        },
    )

    print(result)


asyncio.run(main())
```

A network-bound receipt carries its Hedera network identity inside the receipt itself. The network is part of the deterministic receipt integrity contract and does not need to be supplied separately for ordinary receipt verification.

## Dataset network identity

Vera Anchor intentionally separates dataset content identity from ledger operation identity.

| Property | Network-independent | Network-bound |
|---|---:|---:|
| File hashes | Yes | No |
| Merkle root | Yes | No |
| Bundle manifest | Yes | No |
| Bundle digest | Yes | No |
| Dataset fingerprint | Yes | No |
| Evidence idempotency key | Yes | No |
| Explicit anchor plan | No | Yes |
| Hedera registration/publication | No | Yes |
| Network-bound receipt | No | Yes |

This allows the same dataset content to retain the same deterministic identity across testnet and mainnet while still producing unambiguous records of where each registration and anchor operation occurred.

## Ingest flow

For generic evidence objects — `file_set`, `file`, `text`, or `json`.

Ingest content identity is network-independent. The same canonical material produces the same item hashes, canonical leaf hashes, Merkle root, bundle digest, fingerprint, and evidence idempotency key regardless of whether it is later anchored on Hedera testnet or mainnet.

For `register_and_anchor` operations, `hedera_network` identifies the target ledger and becomes part of the explicit network-bound plan and final receipt identity. For new integrations, explicitly provide either `"testnet"` or `"mainnet"` when planning or submitting an ingest anchor.

For submit flows, use an org-scoped ingest domain:

```text
hf:ingest|org:<org_uuid>
```

For `text` and `json` material, set an explicit `evidence_pointer`. These material kinds do not infer a default pointer from a filesystem path.

### Local only

```python
import asyncio

from vera_anchor.ingest.remote import (
    ExecuteIngestLocalOnlyInput,
    execute_ingest_local_only,
)


async def main():
    result = await execute_ingest_local_only(
        ExecuteIngestLocalOnlyInput(
            request={
                "mode": "merkle_only",
                "identity": {
                    "object_key": "my_object",
                    "object_kind": "file_set",
                    "program": "my_program",
                    "version_label": "v1",
                },
                "material": {
                    "kind": "file_set",
                    "root_dir": "/path/to/input",
                    "rules": {"follow_symlinks": False},
                },
                "evidence_pointer": "file:///path/to/input",
            },
            hooks=None,
        )
    )

    print(result.local.receipt)
    print(result.local.evidence)


asyncio.run(main())
```

Local-only ingest evidence and the local receipt are not bound to a Hedera network.

### Plan an ingest anchor

You can inspect the explicit network-bound plan before submitting evidence:

```python
import asyncio
import os

from vera_anchor import HfLocalAuth, HfLocalClientConfig
from vera_anchor.ingest.remote import plan_ingest_remote


async def main():
    config = HfLocalClientConfig(
        base_url="https://hfapi.veraanchor.com",
        auth=HfLocalAuth(apiKey=os.environ["HF_API_KEY"]),
    )

    plan = await plan_ingest_remote(
        config,
        {
            "mode": "register_and_anchor",
            "identity": {
                "object_key": "my_object",
                "object_kind": "file_set",
                "program": "my_program",
                "version_label": "v1",
            },
            "material": {
                "kind": "file_set",
                "root_dir": "/path/to/input",
                "rules": {"follow_symlinks": False},
            },
            "domain": "hf:ingest|org:<org_uuid>",
            "proof_date": "2026-09-09",
            "hedera_network": "testnet",
            "issue_certificate": False,
        },
    )

    print(plan.hedera_network)
    print(plan.plan_id)
    print(plan.steps)


asyncio.run(main())
```

Explicit testnet and mainnet plans have distinct network-bound plan identities while leaving the underlying Ingest content evidence unchanged.

### Local then submit

```python
import asyncio
import os

from vera_anchor import HfLocalAuth, HfLocalClientConfig
from vera_anchor.ingest.remote import (
    ExecuteIngestLocalThenSubmitInput,
    execute_ingest_local_then_submit,
)


async def main():
    config = HfLocalClientConfig(
        base_url="https://hfapi.veraanchor.com",
        auth=HfLocalAuth(apiKey=os.environ["HF_API_KEY"]),
    )

    result = await execute_ingest_local_then_submit(
        config,
        ExecuteIngestLocalThenSubmitInput(
            request={
                "mode": "register_and_anchor",
                "identity": {
                    "object_key": "my_object",
                    "object_kind": "file_set",
                    "program": "my_program",
                    "version_label": "v1",
                },
                "material": {
                    "kind": "file_set",
                    "root_dir": "/path/to/input",
                    "rules": {"follow_symlinks": False},
                },
                "evidence_pointer": "file:///path/to/input",
                "domain": "hf:ingest|org:<org_uuid>",
                "proof_date": "2026-03-23",
                "hedera_network": "testnet",
                "issue_certificate": False,
            },
            hooks=None,
        ),
    )

    print(result.local.receipt)
    print(result.remote)


asyncio.run(main())
```

The local evidence and local pre-submit receipt remain network-independent. The remote result and final receipt are bound to the selected Hedera network.

### Verify

```python
import asyncio
import os

from vera_anchor import HfLocalAuth, HfLocalClientConfig
from vera_anchor.ingest.remote import verify_ingest_remote


async def main():
    config = HfLocalClientConfig(
        base_url="https://hfapi.veraanchor.com",
        auth=HfLocalAuth(apiKey=os.environ["HF_API_KEY"]),
    )

    result = await verify_ingest_remote(
        config,
        {
            "receipt": receipt,  # from a previous anchored run
            "bundle": bundle,    # from a previous run
            "root_dir": "/path/to/input",  # optional local file_set check
        },
        hedera_network="testnet",
    )

    print(result.get("receipt_verify", {}).get("ok"))
    print(result.get("bundle_verify", {}).get("ok"))
    print(result.get("artifact_binding", {}).get("ok"))
    print(result.get("network_binding", {}).get("ok"))
    print(result.get("local_verify", {}).get("ok"))


asyncio.run(main())
```

The `hedera_network` keyword is optional. When supplied, the SDK sends it as the HF verification network selector. HF then verifies that the network-bound receipt agrees with that requested network. The selector does not become part of the bundle or content evidence.

## Ingest network identity

Vera Anchor intentionally separates Ingest content evidence from ledger operation identity.

Network-independent Ingest evidence includes:

- item content hashes
- canonical ingest leaf hashes
- Merkle root
- bundle manifest and bundle digest
- fingerprint
- evidence idempotency key
- local receipt

Network-bound operation evidence includes:

- explicit `register_and_anchor` plan
- Hedera network
- anchor and publication records
- final network-bound receipt

This lets identical canonical content retain the same deterministic evidence across testnet and mainnet while producing unambiguous records of where each ledger operation occurred.

## Example scripts

The package includes runnable example scripts:

| Script | Description |
|---|---|
| `scripts/example_dataset_local_only.py` | Local-only dataset evidence generation |
| `scripts/example_dataset_local_submit.py` | Local build + explicit network-bound plan and submit |
| `scripts/example_dataset_verify.py` | Verify a dataset receipt and bundle |
| `scripts/example_ingest_local_only.py` | Local-only, network-independent ingest evidence generation |
| `scripts/example_ingest_local_submit.py` | Local evidence + explicit network-bound plan and submit to Hash Factory |
| `scripts/example_ingest_verify.py` | Verify receipt, bundle, artifact binding, network binding, and optional local material |

Copy `.env.example` to `.env` and fill in your values before running any submit or verify script.

### Dataset submit

An explicit Hedera network is required by the dataset submit example:

```bash
HF_API_KEY=your_key \
HF_BASE_URL=https://hfapi.veraanchor.com \
TEST_HEDERA_NETWORK=testnet \
TEST_ROOT_DIR=/path/to/dataset \
TEST_DATASET_KEY=<org_id>.<program>.<name> \
TEST_EVIDENCE_POINTER=s3://your-bucket/path \
TEST_ISSUE_CERTIFICATE=false \
python scripts/example_dataset_local_submit.py
```

Use `TEST_HEDERA_NETWORK=mainnet` only when you intend to register and anchor against Hedera mainnet.

The submit example verifies that the plan, remote response, receipt, Core dataset, and Core version agree on the requested network while the local and remote dataset fingerprint, bundle digest, and Merkle root remain identical. It also verifies that the local hash-only receipt contains no Hedera network.

The example writes both per-run artifacts and a `latest` directory:

```text
local-receipt.json
local-evidence.json
remote-plan.json
remote-receipt.json
remote-bundle.json
remote-payload.json
run-meta.json
```

### Dataset verify

```bash
HF_API_KEY=your_key \
HF_BASE_URL=https://hfapi.veraanchor.com \
TEST_RECEIPT_PATH=./vera_anchor_dataset_receipts/latest/remote-receipt.json \
TEST_BUNDLE_PATH=./vera_anchor_dataset_receipts/latest/remote-bundle.json \
TEST_EXPECTED_HEDERA_NETWORK=testnet \
python scripts/example_dataset_verify.py
```

`TEST_EXPECTED_HEDERA_NETWORK` is optional. When supplied, it checks the receipt network before remote verification. It is a smoke-test assertion only and is not sent to the verification endpoint as a network selector.

### Dataset local only

```bash
TEST_ROOT_DIR=/path/to/dataset \
TEST_DATASET_KEY=<org_id>.<program>.<name> \
TEST_EVIDENCE_POINTER=file:///path/to/dataset \
python scripts/example_dataset_local_only.py
```

`TEST_HEDERA_NETWORK` may optionally be supplied as a future submit target, but it is deliberately not incorporated into the local evidence or local hash-only receipt.

### Ingest submit

```bash
HF_API_KEY=your_key \
HF_BASE_URL=https://hfapi.veraanchor.com \
TEST_OBJECT_KIND=file_set \
TEST_OBJECT_KEY=my_object \
TEST_PROGRAM=my_program \
TEST_VERSION_LABEL=v1 \
TEST_ROOT_DIR=/path/to/input \
TEST_DOMAIN='hf:ingest|org:<org_uuid>' \
TEST_HEDERA_NETWORK=testnet \
TEST_EVIDENCE_POINTER=file:///path/to/input \
TEST_ISSUE_CERTIFICATE=false \
python scripts/example_ingest_local_submit.py
```

`TEST_HEDERA_NETWORK` is required by the ingest submit example and must be either `testnet` or `mainnet`. Use `mainnet` only when you intend the example to create a mainnet ledger operation.

The ingest submit example checks that:

- the explicit plan identifies the requested Hedera network;
- local and remote fingerprints match;
- local and remote bundle digests match;
- local and remote Merkle roots match;
- the remote result and final receipt identify the requested network;
- any returned Core network claims agree with the requested network;
- the local pre-submit receipt remains network-independent.

The submit example writes both per-run artifacts and a `latest` directory:

```text
remote-plan.json
local-receipt.json
local-evidence.json
remote-receipt.json
remote-bundle.json
remote-payload.json
run-meta.json
```

### Ingest verify

```bash
HF_API_KEY=your_key \
HF_BASE_URL=https://hfapi.veraanchor.com \
TEST_RECEIPT_PATH=./vera_anchor_ingest_receipts/latest/remote-receipt.json \
TEST_BUNDLE_PATH=./vera_anchor_ingest_receipts/latest/remote-bundle.json \
TEST_EXPECTED_HEDERA_NETWORK=testnet \
python scripts/example_ingest_verify.py
```

`TEST_EXPECTED_HEDERA_NETWORK` is optional. When supplied, `TEST_RECEIPT_PATH` is required and the SDK forwards the expected network to HF verification. The example requires the returned `network_binding` result to confirm that the receipt and requested network match.

### Ingest local only

`TEST_HEDERA_NETWORK` may optionally be supplied as a future submit target. It is shown in the script output but is deliberately not passed into local evidence generation or incorporated into the local receipt.

## Hash Factory

[hf.veraanchor.com](https://hf.veraanchor.com) — live deployment.

Hash Factory is the web interface where users onboard, manage evidence packages, register and anchor datasets, view HCS anchors, and receive supported HTS certificate NFTs on Hedera.

## License

Source-available under the MIT License with Commons Clause. Commercial resale of the software itself is restricted. See https://github.com/VeraAnchor/vera-anchor-py/blob/main/LICENSE.
````
