# Proof-of-Capture (MVP)
* **Proof-of-capture** for any computer-connected camera on Linux.
* **Per-frame hashing → streaming Merkle tree (MMR)** → **segment roots** every 1s → **OpenTimestamps** anchoring (pending → upgradeable to BTC).
* **Self-contained file**: video container embeds all receipts + signed segment headers (no external DB needed for verification).
* **Software key now, hardware keys later** (same API).
* **Two deliverables**:
1. `btcp-rec` — capture/record from webcam, produce a stamped, self-verifying file.
2. `btcp-verify` — recompute, verify signatures & OTS, show which Bitcoin block(s) anchor the recording.
---
# High-level Architecture
```
[Camera (UVC) → V4L2]
│
(GStreamer pipeline: encode to H.264/AV1, AU-aligned)
├─→ [Matroska mux → out.mkv] (primary branch)
└─→ [Appsink → Rust hasher: per-frame → MMR → Segment roots]
├─→ [COSE sign Segment Header]
├─→ [Submit OTS; store pending receipt]
└─→ [Append to in-file “btcp” attachment/index]
```
* **Why GStreamer for MVP?** It’s rock-solid on Linux Mint, easy to make an AU-aligned branch of the *encoded* stream for hashing, and avoids writing an FFmpeg filter day one. You can add an FFmpeg filter later without touching the core library.
---
# Core Data Model
## Per-frame digest (hashed on **encoded access unit**)
* Byte payload = the encoder’s access unit (AU/NAL set/OBU) as seen at the `appsink`.
* Digest:
```
h_i = SHA256(
AU_payload
|| LE64(frame_index)
|| LE64(pts_microseconds)
|| h_(i-1)
)
```
* Purpose: chaining exposes drops/inserts/reordering.
## Streaming tree (MMR)
* Append each `h_i` to an **MMR** (Merkle Mountain Range).
* At **segment boundaries** (default: every 1s or N frames), snapshot the **MMR frontier root** as the **segment root**.
## Segment Header (COSE\_Sign1 payload; CBOR map)
```
{
"v": 1, // schema version
"seg": uint, // segment index
"first_pts": int, // µs
"last_pts": int, // µs
"n": uint, // frames in segment
"mmr_root": bstr(32), // segment root
"prev_seg_root": bstr(32), // links segments
"codec_params_hash": bstr(32), // hash of SPS/PPS/VPS/SeqHdr
"btc_header_hint": { "height": uint, "header": bstr(80) }? // optional
}
```
* **COSE\_Sign1** wraps this payload with `alg=EdDSA` (Ed25519) and your current **device/host pubkey**.
* Later: swap signer to TPM/YubiKey/SE05x without changing this payload.
## OTS receipt
* Binary **OpenTimestamps** receipt returned at submission time (can be “pending” now; “upgraded” later).
* Store alongside the signed Segment Header.
## In-file index (one attachment per recording)
* Single compact **CBOR** blob embedded in the container:
```
{
"v": 1,
"algo": "sha256",
"seg_ms": 1000, // nominal segment length
"device": {
"kid": "base64", // key id / label
"pub": bstr, // Ed25519 public key
"alg": "Ed25519"
},
"calendars": [ "ots:calendarA", "ots:calendarB" ], // identifiers/URLs
"segments": [
{
"seg": uint,
"hdr_cose": bstr, // COSE_Sign1 over Segment Header payload
"ots_receipt": bstr, // OTS receipt (pending or anchored)
"anchor_hint": { "status": "pending|anchored", "txid": tstr?, "height": uint? }?
},
...
],
"final": {
"mmr_root": bstr(32),
"total_frames": uint,
"hdr_cose": bstr, // COSE over a FinalHeader payload
"ots_receipt": bstr
}
}
```
---
# Containers & Embedding (MVP → production)
* **MVP**: **Matroska (MKV)** for simplicity.
* Store index as a single **Attachment** named `btcp.index.cbor`.
* Also write human-readable **Tag** `BTC_PROOF=present` with version.
* **Next**: **MP4/ISOBMFF** with a custom `uuid` box (type `btcp`) per recording (and optional per-segment chunks for very long clips).
* Both keep the exact same CBOR content; only the embedding differs.
---
# Key Management (MVP)
* **Software keypair** (Ed25519) generated on first run:
* Stored in `~/.config/btcp/keys/host_ed25519.key` (0600).
* Public key mirrored in `~/.config/btcp/keys/host_ed25519.pub`.
* Optional passphrase; integrate with `libsecret` (GNOME Keyring) later.
* **Signer trait** (pluggable):
* `SoftwareSigner` (default),
* `Pkcs11Signer` (YubiKey/HSM),
* `TpmSigner` (Linux TPM2),
* `SeSigner` (I²C/SPI secure element) — future.
---
# Networking & Offline
* **OTS client** posts segment roots immediately to 2+ calendars.
* If offline:
* Queue requests (disk), embed **pending receipts** placeholders.
* Also embed the **latest observed Bitcoin header** (`height + 80-byte header`) if a local node is reachable, to give a capture-time lower bound.
* Background “upgrader” task converts pending receipts to full Bitcoin-anchored proofs later, and **updates the in-file index in-place** (Matroska lets you rewrite attachments safely; or write an adjacent `.upgrade` companion then merge).
---
# Verification (CLI flow)
`btcp-verify file.mkv`:
1. Parse container → load `btcp.index.cbor`.
2. Extract **codec parameters** (SPS/PPS/SeqHdr) → recompute `codec_params_hash`.
3. Demux video → iterate **encoded AUs** in exact mux order:
* recompute `h_i` (with chaining), update MMR, detect segment boundaries (by PTS against index).
4. For each segment:
* Recompute **segment root** → compare.
* Verify **COSE** signature against embedded pubkey.
* Validate **OTS receipt**:
* If pending: optionally **upgrade** (if network available).
* If anchored: verify hash path to a Bitcoin block; optionally check block header via a local node (`bitcoind` RPC, Electrum, or a header snapshot).
5. For final root: same steps.
6. Output:
* **Integrity**: OK / mismatch at segment X (frame Y)
* **Provenance**: signature valid (pubkey fingerprint)
* **Anchors**: list of segments with `block_height`, `txid`, `block_time` (from node)
* Machine-readable `--json` report.
---
# GStreamer Capture Pipeline (MVP)
* Launch from Rust using `gstreamer`/`gstreamer-video` crates:
```
v4l2src device=/dev/video0 !
video/x-raw, width=1920, height=1080, framerate=30/1 !
queue !
x264enc tune=zerolatency key-int-max=60 bitrate=6000 speed-preset=veryfast !
video/x-h264, stream-format=byte-stream, alignment=au !
tee name=t
t. ! queue ! h264parse config-interval=-1 ! matroskamux ! filesink location=out.mkv
t. ! queue ! appsink name=hashsink emit-signals=true caps=video/x-h264,stream-format=byte-stream,alignment=au
```
* The `appsink` callback receives **each AU**; the hasher library consumes payload bytes & timestamps.
> Swap `x264enc` for `vaapih264enc` (Intel) or `nvv4l2h264enc` (NVIDIA) if present; the hasher doesn’t change.
---
# Rust Project Layout
```
crates/
btcp-core/ # hashing (SHA-256), MMR, segmenting, codec hash
btcp-cose/ # COSE_Sign1 (Ed25519) + CBOR serde
btcp-ots/ # OTS client + receipt (pending/upgrade) + calendar pool
btcp-gst/ # GStreamer recorder (pipeline builder + appsink)
btcp-mkv/ # MKV embed/extract index (attachments + tags)
btcp-cli/ # `btcp-rec`, `btcp-verify`, `btcp-key`, `btcp-upgrade`
```
* **Traits**:
* `Signer` → `sign(payload) -> cose_sign1` / `pubkey()`
* `Timestamper` → `submit(root) -> receipt` / `upgrade(receipt) -> receipt`
* `Tree` → `append(h_i)` / `frontier_root()`
* **No\_std** option in `btcp-core` (future embedded builds).
---
# Build & Run (Linux Mint 21/22)
### Dependencies
```bash
sudo apt update
sudo apt install -y build-essential pkg-config cmake \
libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav \
libssl-dev
# Install Rust
curl https://sh.rustup.rs -sSf | sh
source $HOME/.cargo/env
```
### Build
```bash
cargo build --release
```
### Key (first run or explicit)
```bash
./target/release/btcp-cli key gen --label "mint-laptop" # writes ~/.config/btcp/keys/...
```
### Record
```bash
./target/release/btcp-cli rec \
--device /dev/video0 \
--size 1920x1080 --fps 30 \
--seg-ms 1000 \
--outfile ~/Videos/proof.mkv \
--cal "poolA" --cal "poolB" \
--log-level info
```
### Verify
```bash
./target/release/btcp-cli verify ~/Videos/proof.mkv --json
```
*(Optionally)* point to a local node for stronger anchor checks:
```bash
./target/release/btcp-cli verify proof.mkv \
--rpc http://user:
[email protected]:8332
```
---
# MVP Scope (what ships)
* **Recording**: H.264\@1080p30 default; audio optional (ignored in proofs).
* **Per-frame hashing** with chaining; **MMR**; **1s segments**.
* **COSE-signed Segment Headers** (Ed25519 software key).
* **OTS submission** to 2 calendars; store **pending receipts**; CLI upgrader.
* **Self-contained MKV**: one `btcp.index.cbor` attachment + tag.
* **Verifier**: recompute, verify COSE, verify/upgrade OTS, optionally check block via node RPC; JSON report.
**Out of scope (next milestones)**: MP4 embedding, C2PA claim mirror, TPM/YubiKey signer, per-excerpt multiproofs export, GUI.
---
# Acceptance Criteria
* Recording a 10-minute 1080p30 clip:
* **No frame drops** attributable to hashing/OTS on a mid-range laptop CPU.
* Resulting `.mkv` contains `btcp.index.cbor` with ≥600 segment entries and a final root.
* `btcp-verify` passes integrity + signature checks.
* After running `btcp-cli upgrade proof.mkv`, ≥95% segments show **anchored** with `block_height` & `txid`.
* Tamper test: flip 1 byte in video payload → verifier flags first affected segment.
* Re-encode test: export a 12-second excerpt with included multiproof (phase 2) → verifier validates excerpt without the full file.
---
# Performance Targets (guidance)
* **Hashing**: ≥500 MB/s on modern x86; ≥150 MB/s on Apple M-class/ARM64; far above typical encoded bitrates.
* **Overhead**: CBOR index growth ≈ **\~4–8 KB per segment** (header + OTS receipt), i.e., **\~30 MB per 1,000 segments** (≈16–17 min at 1 s cadence). Negligible vs video size.
* **Latency**: Segment header emit ≤10 ms after boundary; OTS submission async.
---
# Security & Privacy
* Keys stored 0600; optional passphrase.
* No media leaves the machine—only **hash roots** go to OTS.
* Calendar selection configurable; optional Tor SOCKS proxy in `btcp-ots`.
* Provenance today = **host key**; later: TPM/SE key or inline signer box.
---
# Testing Strategy
* **Unit tests**: hashing chain, MMR math, COSE sign/verify, OTS receipt parser.
* **Fixture videos**: tiny deterministic streams (H.264, AV1); golden CBOR indexes.
* **Pipeline tests**: headless GStreamer (v4l2loopback) to simulate camera.
* **Fuzz**: CBOR/COSE/OTS parsers.
* **Integration**: record → corrupt → verify; offline queue → upgrade.
---
# Roadmap (post-MVP)
1. **MP4 embedding** (`uuid` box),
2. **C2PA claim** mirroring (so newsrooms see “Content Credentials”),
3. **Signer backends**: TPM2, YubiKey/PKCS#11, NXP SE05x,
4. **Inline signer box** (Pi 4/CM4) using the same `btcp-core`,
5. **OBS plugin** (use `btcp-core` via C ABI),
6. **Multiproof excerpts** export tool,
7. **TEE attestation** (SGX/SEV/TrustZone) mode on capable hosts.