610 views
# 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.