# Optional Bitcoin time anchoring

`blockchain_time.py` anchors a canonical manifest of a signed 0EX checkpoint,
object hashes, code hashes, and an optional prior anchor ID through
OpenTimestamps. It preserves the local Ed25519 checkpoint as a separate claim.
The caller must verify the checkpoint signature and collect the hashes from a
locked, valid runtime graph before calling `prepare`.

The manifest's SHA-256 is its anchor ID. Manifests never change. An anchor's
detached `.ots` proof can acquire additional attestations during `upgrade`.
The manifest contains no added wall-clock field: the checkpoint's clock is
explicitly a local claim, and repeated preparation of the same input is stable.

## Installation and integration

Install `requirements-time.txt` into the same Python environment that runs 0EX.
The pinned, tested optional packages are `opentimestamps==0.4.5`,
`python-bitcoinlib==0.12.2`, and `pycryptodomex==3.23.0`. No optional package is
needed to prepare/get/export an unstamped manifest. Proof inspection, stamping,
upgrading, and verification use the optional packages. Missing dependencies
produce an explicit error; the adapter never installs software automatically.

```python
from zeroexternal.blockchain_time import BlockchainTime

time = BlockchainTime(runtime_data_dir / "time-anchors")
receipt = time.prepare(signed_checkpoint, object_hashes, code_hashes,
                       parent_id=None)
anchor_id = receipt["anchor_id"]
pending = time.stamp(anchor_id)       # Explicit network operation.
updated = time.upgrade(anchor_id)     # Explicit network operation, usually later.
observed = time.inspect(anchor_id)    # Offline proof inspection only.
bundle = time.export(anchor_id)      # Exact manifest/proof bytes as base64.
```

All inventory values and parent IDs must be lowercase 64-character SHA-256
hex strings. Inventory keys are bounded labels, not paths to read. A parent
must already be a known, unmodified local anchor. Checkpoint signature trust,
hash inventory completeness, and graph integrity remain the caller's duties.

`prepare`, `stamp`, `upgrade`, `inspect`, and `verify` return an observation:

| Field | Meaning |
| --- | --- |
| `anchor_id`, `manifest_sha256` | SHA-256 of exact canonical manifest bytes |
| `status` | See the state table below |
| `bitcoin_verified` | True only from this invocation's successful Core check |
| `proof_sha256` | Current proof hash, or null before stamping |
| `bitcoin_heights` | Heights claimed by proof attestations; absent before proof |
| `calendars` | Recognized pending calendars; never untrusted URI text |
| `verification_note`, `scope` | Interpretation and limitations |
| `bitcoin` | Only on success: height, block hash, block time, confirmations |
| `verification_source` | Only on success: `local_bitcoin_core_mainnet` |
| `verified_at_local` | Only on success: local observation time, not blockchain time |

`get_manifest(id)` returns parsed canonical manifest JSON. `export(id)` returns
`anchor_id`, `manifest`, `manifest_base64`, `manifest_filename`, `proof_filename`,
`proof_base64`, `proof_sha256`, and `scope`. Export is a byte export and makes no
fresh verification claim. Decode `manifest_base64` to `manifest.json` and
`proof_base64` to `manifest.json.ots`; do not reformat the JSON.

| State | Exact interpretation |
| --- | --- |
| `prepared` | Local immutable manifest exists; no calendar proof yet |
| `calendar_pending` | A recognized calendar receipt exists; Bitcoin unverified |
| `bitcoin_attested` | A Bitcoin attestation is present; Bitcoin unverified |
| `proof_unverified` | Only unsupported attestations/calendars are present |
| `verified` | This invocation matched a proof commitment to the configured Core node's active mainnet chain with at least six confirmations |

`inspect` never repeats a previously stored `verified` assertion. It reports
`bitcoin_attested` until a fresh `verify` performs the Core checks again. The
runtime should append observations to its graph, including the proof hash,
rather than rewrite an old verification event.

## Independent verification

Configure a synchronized Bitcoin Core mainnet node with local RPC and a cookie,
then construct the adapter with operator-provided configuration:

```python
time = BlockchainTime(runtime_data_dir / "time-anchors",
    bitcoin_cookie="/operator/configured/Bitcoin/.cookie", bitcoin_port=8332)
result = time.verify(anchor_id)
```

Cookie configuration must come from local operator settings, never an HTTP
request. The adapter does not search for credentials. Its only RPC host is
`127.0.0.1`; the cookie is read only for an explicit verify operation containing
a Bitcoin attestation. The only RPC methods used are `getblockchaininfo`,
`getblockhash`, and `getblockheader`. It uses the OpenTimestamps library's
`verify_against_blockheader` check, checks the header hash and active-chain
height mapping, and rejects non-mainnet or initial-sync nodes.

Verification does not retrieve calendar data. Without explicit Core
configuration, a complete proof stays `bitcoin_attested` with
`bitcoin_verified: false`. A pruned Core node can verify block headers. A
different trusted verifier can independently check the exported standard `.ots`
file. The upstream `opentimestamps-client` provides the `ots verify` command
(tested package version 0.7.2); that separate CLI has its own network behavior.

A verified proof establishes that exact manifest bytes existed by an
approximate Bitcoin block time. It does not prove exact creation time,
authorship, scientific truth, or safety. A self-presented checkpoint key still
requires independent pinning. Bitcoin reorganizations, host compromise,
dependency compromise, and an untrusted or stale Core node remain limitations.

## Boundaries

- Each network operation runs in a fresh isolated Python subprocess with a
  25-second wall deadline and a 10-second CPU limit. Linux also limits address
  space to 512 MiB; macOS retains the deadline, CPU, file and structural bounds.
- Manifests: 2 MiB, 10,000 object hashes, 5,000 code hashes. Proofs/calendar
  responses: 1 MiB. Storage: 1,000 anchor directories. Proof structure: 4,096
  nodes and 128 attestations. At most eight upgrade lookups and eight Bitcoin
  attestation candidates are checked per operation.
- Stamping submits a nonced commitment to the fixed HTTPS endpoints
  `a.pool.opentimestamps.org` and `b.pool.opentimestamps.org`. Calendar operators
  see the commitment and connection metadata, not the manifest, object bytes,
  author key, or code. There are no wallet calls or per-user transaction keys.
- Upgrades accept only those two endpoints and
  `alice.btc.calendar.opentimestamps.org` / `bob.btc.calendar.opentimestamps.org`.
  The HTTP transport disables redirects and environment proxies. Calendar
  availability is not guaranteed; an allowlist change requires a code update.
- No arbitrary URLs, executable paths, imported proofs, or caller-supplied
  file paths are accepted. IDs are validated, storage files must be regular,
  symlinks are rejected, and per-anchor locks prevent concurrent operations.
- Network/parse failures do not overwrite the last usable proof. Remote error
  text and RPC credentials are omitted from error messages.

## Validation performed

Offline tests cover canonical identity/exports, parent links, tamper and path
rejection, FIFO handling, digest mismatch, trailing proof bytes, untrusted
embedded calendar URIs, pending-versus-attested states, actual OTS serialization,
worker execution, and synthetic Core cases (matching/mismatched Merkle root,
wrong network, and unsynchronized node). Synthetic calendar and Core fixtures
test protocol logic; they are not real blockchain evidence. No live calendar
submission or real Bitcoin verification was performed during development.

## Primary references

- [OpenTimestamps client usage and verification requirements](https://github.com/opentimestamps/opentimestamps-client)
- [OpenTimestamps proof data structures](https://github.com/opentimestamps/python-opentimestamps/blob/master/opentimestamps/core/timestamp.py)
- [OpenTimestamps Bitcoin attestations](https://github.com/opentimestamps/python-opentimestamps/blob/master/opentimestamps/core/notary.py)
- [OpenTimestamps calendar protocol client](https://github.com/opentimestamps/python-opentimestamps/blob/master/opentimestamps/calendar.py)
- [python-bitcoinlib RPC implementation](https://github.com/petertodd/python-bitcoinlib/blob/master/bitcoin/rpc.py)
- [Pinned OpenTimestamps package](https://pypi.org/project/opentimestamps/0.4.5/)
- [Pinned Bitcoin library package](https://pypi.org/project/python-bitcoinlib/0.12.2/)
- [Pinned crypto package](https://pypi.org/project/pycryptodomex/3.23.0/)
