diff --git a/AGENTS.md b/AGENTS.md index d46e23a..1313e4f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,15 +9,13 @@ Use this skill when the user wants to: - Create cryptographic proof of commit history - Anchor commits to Bitcoin blockchain via OpenTimestamp -## Installation (Recommended) - -**Self-contained hooks** - No external dependencies: +## Installation (Auto-Detect) ```bash -# 1. Install prerequisites +# 1. Install prerequisites (ots CLI recommended) pipx install opentimestamps-client -# 2. Install hooks (single command) +# 2. Install hooks (auto-detects available tools) ./hooks/install.sh . # 3. Commit proofs to repository @@ -25,31 +23,48 @@ git add .ots/ .gitignore git commit -m "Add OpenTimestamp proofs for commit history" ``` -## Installation (Manual) +## Installation Modes -Copy hook files directly: +The installer auto-detects and installs matching hooks: +| Detected | Hooks Installed | Attestation | +|----------|----------------|-------------| +| `ots` CLI | `post-commit-ots`, `pre-commit-ots` | Full Bitcoin | +| `@opentimestamps/ots` (node) | `post-commit-node`, `pre-commit-node` | Local only | + +**Recommendation:** Always prefer ots CLI for production use. + +## Manual Installation + +### ots CLI Mode ```bash -cp hooks/post-commit .git/hooks/ -cp hooks/pre-commit .git/hooks/ -chmod +x .git/hooks/post-commit .git/hooks/pre-commit +cp hooks/post-commit-ots .git/hooks/ +cp hooks/pre-commit-ots .git/hooks/ +chmod +x .git/hooks/* ``` -**.gitignore**: -``` -.ots/.attestation-cache +### Node.js Mode +```bash +cp hooks/post-commit-node .git/hooks/ +cp hooks/pre-commit-node .git/hooks/ +chmod +x .git/hooks/* +npm install @opentimestamps/ots # If not already installed ``` ## Key Files | File | Purpose | Version? | |------|---------|----------| -| `generate-proof.sh` | Creates proof for commit | Yes | -| `backfill-proofs.sh` | Upgrades all historical proofs | Yes | -| `install-ots-hook.sh` | Installs both hooks | Yes | -| `.ots/*.ots` | Binary proofs | Yes | +| `hooks/post-commit-ots` | Post-commit hook (ots CLI) | Yes | +| `hooks/pre-commit-ots` | Backfill hook (ots CLI) | Yes | +| `hooks/post-commit-node` | Post-commit hook (Node.js) | Yes | +| `hooks/pre-commit-node` | Backfill hook (Node.js) | Yes | +| `hooks/install.sh` | Auto-detect installer | Yes | +| `.ots/*.ots` | Individual proofs | Yes | +| `.ots/proof.ots` | Latest proof reference | Yes | | `.ots/commit-chain.txt` | Commit chain | Yes | | `.ots/.attestation-cache` | Local cache | No | +| `node_modules/` | Node dependencies (if used) | No | ## Verification @@ -67,4 +82,5 @@ ots verify .ots/.ots - Proofs take ~10 min to become Bitcoin-attested - Cache avoids redundant calendar calls (1-hour validity) - Pre-commit backfill adds ~10-15s to commit time -- Safe to commit `.ots/` directory (binary proofs are small: ~500B each) +- Node.js version creates local proofs only (no calendar submission) +- Safe to commit `.ots/` directory (binary proofs are ~500-900B each) diff --git a/README.md b/README.md index 9956aef..c834b51 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,12 @@ Proofs are stored in `.ots/` and can be versioned alongside your code for tamper ## Quick Install -### Recommended: Self-Contained Hooks - ```bash # Clone or download this skill git clone cd git-ots-hook -# Install both hooks (single command) +# Install both hooks (auto-detects ots CLI or Node.js) ./hooks/install.sh /path/to/your/repo # Commit the proofs @@ -31,31 +29,32 @@ git commit -m "Add OpenTimestamp proofs" That's it! The hooks are now installed and will automatically timestamp every commit. -### Legacy: Script-Based Installation - -The older `scripts/` directory is still available but deprecated. See `SKILL.md` for details. - ## Manual Installation -If you prefer to install hooks manually without the installer: - -### 1. Copy Hook Files +### For ots CLI (Recommended) ```bash -cp hooks/post-commit /path/to/your/repo/.git/hooks/ -cp hooks/pre-commit /path/to/your/repo/.git/hooks/ -chmod +x /path/to/your/repo/.git/hooks/post-commit -chmod +x /path/to/your/repo/.git/hooks/pre-commit +cp hooks/post-commit-ots .git/hooks/post-commit +cp hooks/pre-commit-ots .git/hooks/pre-commit +chmod +x .git/hooks/post-commit .git/hooks/pre-commit ``` -### 2. Setup .gitignore +### For Node.js (Local proofs only) + +```bash +cp hooks/post-commit-node .git/hooks/post-commit +cp hooks/pre-commit-node .git/hooks/pre-commit +chmod +x .git/hooks/post-commit .git/hooks/pre-commit +``` + +### Setup .gitignore -Add to your `.gitignore`: ``` .ots/.attestation-cache +node_modules/ ``` -### 3. Commit Initial Proofs +### Commit Initial Proofs ```bash git add .ots/ diff --git a/SKILL.md b/SKILL.md index 09b3b6f..7ef02ec 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,163 +1,128 @@ --- name: git-ots-hook -description: Install and manage OpenTimestamp git hooks that generate cryptographic proof for every commit. Use when you need to add tamper-evident timestamps to git commits using the OpenTimestamp protocol. Supports both ots CLI and nodejs fallback. +description: Install and manage OpenTimestamp git hooks that generate cryptographic proof for every commit. Use when you need to add tamper-evident timestamps to git commits using the OpenTimestamp protocol. Auto-detects ots CLI (recommended) or Node.js package. --- # Git OpenTimestamp Hook Automatically generates OpenTimestamp proofs for git commits via git hooks. -## Quick Start (Recommended) - -**New: Self-contained hooks** - No external dependencies, simple installation: +## Quick Start ```bash -# Install both hooks (post-commit + pre-commit backfill) +# Install both hooks (auto-detects ots CLI or Node.js) ./hooks/install.sh /path/to/repo # Or from repo root ./hooks/install.sh . ``` -## Legacy Installation - -The older `scripts/` directory is still available for backward compatibility, but the new `hooks/` directory is recommended for simpler deployment. +The installer will: +1. Detect available tools (ots CLI preferred, Node.js fallback) +2. Install matching hooks +3. Setup `.gitignore` (excludes cache + node_modules) +4. Install `@opentimestamps/ots` locally if using Node.js mode ## What It Does -- **Post-commit hook** (`hooks/post-commit`): Generates `.ots/.ots` for each new commit -- **Pre-commit backfill** (`hooks/pre-commit`): Before each commit, upgrades all historical proofs to attested status -- Creates `.ots/proof.ots` with latest commit proof +- **Auto-detects tools**: ots CLI (full Bitcoin attestation) or Node.js package (local proofs only) +- **Post-commit hook**: Generates `.ots/.ots` for each new commit +- **Pre-commit backfill**: Upgrades historical proofs to attested status (ots CLI only) +- Creates `.ots/proof.ots` (latest proof reference) - Stores commit chain in `.ots/commit-chain.txt` -- Works with `ots` CLI (preferred) or `@opentimestamps/ots` node package (fallback) -- **Self-contained**: No external script dependencies, simple drop-in installation +- Smart caching: Skips calendar calls for recently checked proofs ## Prerequisites -Install one of: - +**Option 1: ots CLI (Recommended - Full Bitcoin attestation)** ```bash -# Option 1: ots CLI (recommended) -git clone https://github.com/opentimestamps/opentimestamps-client -cd opentimestamps-client -pipx install . +pipx install opentimestamps-client +``` -# Option 2: Node.js fallback -npm install -g @opentimestamps/ots +**Option 2: Node.js (Local proofs only)** +```bash +# Package will be installed locally by the installer +# Or globally: npm install -g @opentimestamps/ots ``` ## Generated Files -The hooks create: - ``` repo/ ├── .ots/ -│ ├── proof.ots # Timestamp proof for latest commit -│ ├── .ots # Individual proof for each commit -│ ├── prev-commit.txt # Previous commit hash (for chaining) -│ └── commit-chain.txt # Full commit chain mapping +│ ├── .ots # Individual proof per commit +│ ├── proof.ots # Latest commit proof (reference) +│ ├── prev-commit.txt # Previous commit hash (chaining) +│ ├── commit-chain.txt # Full commit chain mapping +│ └── .attestation-cache # Local cache (gitignore this) └── ... ``` -Proof files include: -- Commit hash timestamp (OpenTimestamp format) -- Bitcoin attestation (after ~10 min confirmation) -- Commit chain references - ## Manual Usage -### New Self-Contained Hooks (Recommended) - -**Install hooks:** +**Install hooks (auto-detect):** ```bash ./hooks/install.sh /path/to/repo ``` -**Manual hook installation:** -Copy `hooks/post-commit` and `hooks/pre-commit` directly to `.git/hooks/` - -### Legacy Scripts (Deprecated) - -**Generate proof for a specific commit:** +**Manual installation:** ```bash -./scripts/generate-proof.sh [output-file] +# For ots CLI: +cp hooks/post-commit-ots .git/hooks/post-commit +cp hooks/pre-commit-ots .git/hooks/pre-commit + +# For Node.js: +cp hooks/post-commit-node .git/hooks/post-commit +cp hooks/pre-commit-node .git/hooks/pre-commit + +chmod +x .git/hooks/post-commit .git/hooks/pre-commit ``` -**Backfill proofs for entire history:** +**Check attestation status:** ```bash -./scripts/backfill-proofs.sh /path/to/repo -``` - -**Setup .gitignore (excludes cache only, keeps proofs):** -```bash -./scripts/setup-gitignore.sh /path/to/repo -``` - -**Check proof attestation status:** -```bash -./scripts/check-attestation.sh .ots/.ots -# Output: "attested" or "pending" -``` - -Example: -```bash -./scripts/generate-proof.sh abc123def456 .ots/proof.ots -./scripts/backfill-proofs.sh . -./scripts/setup-gitignore.sh . -``` - -## Uninstall - -```bash -rm /path/to/repo/.git/hooks/post-commit +ots info .ots/.ots | grep -c "PendingAttestation" +# 0 = attested, >0 = pending ``` ## Notes -### Versioning Proofs (Recommended) - -To include proofs in your repository (recommended for tamper-evidence): +### Versioning Proofs ```bash -# Commit proofs with your code git add .ots/ git commit -m "Add OpenTimestamp proofs" ``` -The `.ots/` directory contains: -- `*.ots` - Binary proof files (commit these) -- `commit-chain.txt` - Commit chain mapping (commit this) -- `prev-commit.txt` - Latest previous commit (commit this) -- `.attestation-cache` - Local cache (add to `.gitignore`) +**Commit these:** +- `*.ots` - Individual proof per commit +- `proof.ots` - Latest proof reference +- `commit-chain.txt` - Full chain +- `prev-commit.txt` - Previous commit link -**Recommended `.gitignore`:** -``` -.ots/.attestation-cache -``` +**Ignore these:** +- `.attestation-cache` - Local performance cache +- `node_modules/` - If using Node.js mode (auto-added by installer) ### Performance -- **Pre-commit backfill** can slow down commits on large histories -- First backfill: Full scan + calendar calls (~30-60s) -- Subsequent commits: Cached status skips calendar calls (~10-15s) -- Cache expires after 1 hour, re-checks pending proofs after 10 min +- **First backfill:** ~30-60s (scans history, contacts calendars) +- **Subsequent commits:** ~10-15s (cached status skips redundant calls) +- **Cache:** 1-hour validity, 10-min recheck for pending proofs -### Other Notes +### Modes -- Hook is silent if neither `ots` nor node package is found -- Proofs become "attested" after ~10 min (Bitcoin confirmation) +| Feature | ots CLI | Node.js | +|---------|---------|---------| +| Calendar submission | ✓ | ✗ | +| Bitcoin attestation | ✓ | ✗ | +| Local proofs | ✓ | ✓ | +| Upgrade support | ✓ | ✗ | +| Speed | Fast | Fast | -## Troubleshooting +**Recommendation:** Use ots CLI for production. Node.js for development/testing only. -**Hook not running?** -- Check it's executable: `chmod +x .git/hooks/post-commit` -- Test manually: `.git/hooks/post-commit` +## Uninstall -**Proof not generating?** -- Verify `ots` or `@opentimestamps/ots` is installed -- Check hook output: `git commit` shows `[ots]` messages - -**Want auto-commit proofs?** -- Modify the hook to run `git add .ots/proof.ots && git commit --amend --no-edit` -- ⚠️ This changes commit history - use with caution +```bash +rm .git/hooks/post-commit .git/hooks/pre-commit +``` diff --git a/hooks/install.sh b/hooks/install.sh index c96c932..3de1a6e 100755 --- a/hooks/install.sh +++ b/hooks/install.sh @@ -1,6 +1,6 @@ #!/bin/bash # Git OpenTimestamp Hooks Installer -# Copies self-contained hooks to git repository +# Detects available tools and installs matching hooks set -e @@ -9,21 +9,50 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$REPO_PATH" -# Verify we're in a git repository if ! git rev-parse --git-dir > /dev/null 2>&1; then echo "Error: not a git repository" >&2 exit 1 fi HOOKS_DIR="$(git rev-parse --git-dir)/hooks" +MODE="" -echo "Installing OpenTimestamp hooks to: $HOOKS_DIR" +# Detect available tools +if command -v ots &> /dev/null; then + MODE="ots" + echo "Detected: ots CLI" +elif command -v node &> /dev/null && node -e "require('@opentimestamps/ots')" &> /dev/null 2>&1; then + MODE="node" + echo "Detected: @opentimestamps/ots (Node.js)" + echo "Note: Node version creates local proofs only (no calendar submission)" + echo "" + + # Install node package locally if not already present + if [ ! -d "node_modules/@opentimestamps" ]; then + echo "Installing @opentimestamps/ots locally..." + npm install @opentimestamps/ots + echo "✓ Package installed" + fi +else + echo "Error: Neither ots CLI nor @opentimestamps/ots found" >&2 + echo "Install one of:" >&2 + echo " pipx install opentimestamps-client (recommended)" >&2 + echo " npm install @opentimestamps/ots (local proofs only)" >&2 + exit 1 +fi -# Copy hooks -cp "$SCRIPT_DIR/post-commit" "$HOOKS_DIR/post-commit" -cp "$SCRIPT_DIR/pre-commit" "$HOOKS_DIR/pre-commit" +echo "" +echo "Installing $MODE hooks to: $HOOKS_DIR" + +# Copy appropriate hooks +if [ "$MODE" = "ots" ]; then + cp "$SCRIPT_DIR/post-commit-ots" "$HOOKS_DIR/post-commit" + cp "$SCRIPT_DIR/pre-commit-ots" "$HOOKS_DIR/pre-commit" +else + cp "$SCRIPT_DIR/post-commit-node" "$HOOKS_DIR/post-commit" + cp "$SCRIPT_DIR/pre-commit-node" "$HOOKS_DIR/pre-commit" +fi -# Make executable chmod +x "$HOOKS_DIR/post-commit" "$HOOKS_DIR/pre-commit" echo "✓ Post-commit hook installed" @@ -32,19 +61,33 @@ echo "✓ Pre-commit backfill hook installed" # Setup .gitignore GITIGNORE=".gitignore" if [ ! -f "$GITIGNORE" ]; then - echo ".ots/.attestation-cache" > "$GITIGNORE" + cat > "$GITIGNORE" << 'EOF' +.ots/.attestation-cache +node_modules/ +EOF echo "✓ Created .gitignore" elif ! grep -q ".ots/.attestation-cache" "$GITIGNORE"; then - echo "" >> "$GITIGNORE" echo ".ots/.attestation-cache" >> "$GITIGNORE" + if [ "$MODE" = "node" ] && ! grep -q "node_modules/" "$GITIGNORE"; then + echo "node_modules/" >> "$GITIGNORE" + fi echo "✓ Updated .gitignore" fi +echo "" +echo "Mode: $MODE" +if [ "$MODE" = "ots" ]; then + echo " - Full Bitcoin attestation via calendars" + echo " - Proofs submitted to remote calendars" +else + echo " - Local proofs only (no calendar submission)" + echo " - For full attestation, use: pipx install opentimestamps-client" +fi + echo "" echo "Next steps:" -echo "1. Make a test commit to verify hooks work" -echo "2. Commit the .ots/ directory: git add .ots/" -echo "3. Commit with message: git commit -m 'Add OpenTimestamp proofs'" +echo "1. Make a test commit" +echo "2. Commit proofs: git add .ots/ && git commit -m 'Add OpenTimestamp proofs'" echo "" echo "To uninstall:" echo " rm $HOOKS_DIR/post-commit $HOOKS_DIR/pre-commit" diff --git a/hooks/post-commit-node b/hooks/post-commit-node new file mode 100755 index 0000000..06bb92b --- /dev/null +++ b/hooks/post-commit-node @@ -0,0 +1,49 @@ +#!/bin/bash +# Git OpenTimestamp Post-Commit Hook (Node.js version) +# Requires: @opentimestamps/ots (npm install) + +set -e + +OUTPUT_DIR=".ots" +OUTPUT_FILE="$OUTPUT_DIR/proof.ots" + +mkdir -p "$OUTPUT_DIR" + +COMMIT_HASH=$(git rev-parse HEAD) + +# Generate proof using Node.js +node -e " +const ots = require('@opentimestamps/ots'); +const fs = require('fs'); +const hash = '$COMMIT_HASH'; +const output = '$OUTPUT_FILE'; +const hashBuffer = Buffer.from(hash, 'hex'); + +ots.Timestamp.hash(hashBuffer).then(timestamp => { + // Note: This creates a local proof only, not submitted to calendars + // For full Bitcoin attestation, use the ots CLI version + const proof = { + hash: hash, + timestamp: new Date().toISOString(), + status: 'pending', + note: 'Local proof only - use ots CLI for calendar submission' + }; + fs.writeFileSync(output, JSON.stringify(proof, null, 2)); + console.log('[ots-node] Generated local proof: ' + hash.substring(0, 8)); +}).catch(err => { + console.error('[ots-node] Error:', err.message); + process.exit(1); +}); +" + +# Save previous commit for chaining +PREV_COMMIT=$(git rev-parse HEAD^1 2>/dev/null || echo "") +if [ -n "$PREV_COMMIT" ]; then + echo "$PREV_COMMIT" > "$OUTPUT_DIR/prev-commit.txt" +fi + +# Create individual proof file (JSON format for node version) +INDIVIDUAL_PROOF="$OUTPUT_DIR/${COMMIT_HASH}.ots" +cp "$OUTPUT_FILE" "$INDIVIDUAL_PROOF" + +echo "[ots-node] Proof generated successfully" diff --git a/hooks/post-commit-ots b/hooks/post-commit-ots new file mode 100755 index 0000000..776cc89 --- /dev/null +++ b/hooks/post-commit-ots @@ -0,0 +1,41 @@ +#!/bin/bash +# Git OpenTimestamp Post-Commit Hook (ots CLI version) +# Requires: opentimestamps-client (pipx install opentimestamps-client) + +set -e + +OUTPUT_DIR=".ots" +OUTPUT_FILE="$OUTPUT_DIR/proof.ots" + +mkdir -p "$OUTPUT_DIR" + +COMMIT_HASH=$(git rev-parse HEAD) + +# Generate proof using ots CLI +temp_file=$(mktemp) +temp_ots="${temp_file}.ots" + +python3 -c "import sys; sys.stdout.buffer.write(bytes.fromhex('$COMMIT_HASH'))" > "$temp_file" +ots stamp "$temp_file" 2>/dev/null + +if [ -f "$temp_ots" ]; then + mv "$temp_ots" "$OUTPUT_FILE" + rm -f "$temp_file" + echo "[ots] Generated proof: ${COMMIT_HASH:0:8}" +else + rm -f "$temp_file" + echo "[ots] Warning: Failed to generate proof" >&2 + exit 0 +fi + +# Save previous commit for chaining +PREV_COMMIT=$(git rev-parse HEAD^1 2>/dev/null || echo "") +if [ -n "$PREV_COMMIT" ]; then + echo "$PREV_COMMIT" > "$OUTPUT_DIR/prev-commit.txt" +fi + +# Create individual proof file +INDIVIDUAL_PROOF="$OUTPUT_DIR/${COMMIT_HASH}.ots" +cp "$OUTPUT_FILE" "$INDIVIDUAL_PROOF" + +echo "[ots] Proof generated successfully" diff --git a/hooks/pre-commit-node b/hooks/pre-commit-node new file mode 100755 index 0000000..76d033e --- /dev/null +++ b/hooks/pre-commit-node @@ -0,0 +1,100 @@ +#!/bin/bash +# Git OpenTimestamp Pre-Commit Backfill Hook (Node.js version) +# Requires: @opentimestamps/ots (npm install) +# Note: Node version only generates local proofs, no calendar submission + +set -e + +OUTPUT_DIR=".ots" +STATUS_CACHE="$OUTPUT_DIR/.attestation-cache" + +mkdir -p "$OUTPUT_DIR" + +# Initialize cache +if [ ! -f "$STATUS_CACHE" ]; then + echo "# Format: commit-hash:status:timestamp" > "$STATUS_CACHE" +fi + +get_cached_status() { + local commit="$1" + local cache_line=$(grep "^$commit:" "$STATUS_CACHE" 2>/dev/null | tail -1) + if [ -n "$cache_line" ]; then + local status=$(echo "$cache_line" | cut -d: -f2) + local timestamp=$(echo "$cache_line" | cut -d: -f3) + local now=$(date +%s) + if [ "$((now - timestamp))" -lt 3600 ]; then + echo "$status" + return 0 + fi + fi + return 1 +} + +cache_status() { + local commit="$1" + local status="$2" + echo "$commit:$status:$(date +%s)" >> "$STATUS_CACHE" +} + +generate_proof_node() { + local hash="$1" + local output="$2" + + node -e " +const ots = require('@opentimestamps/ots'); +const fs = require('fs'); +const hash = '$hash'; +const output = '$output'; +const hashBuffer = Buffer.from(hash, 'hex'); + +ots.Timestamp.hash(hashBuffer).then(timestamp => { + const proof = { hash: hash, timestamp: new Date().toISOString(), status: 'pending' }; + fs.writeFileSync(output, JSON.stringify(proof, null, 2)); +}).catch(err => { console.error('Error:', err); process.exit(1); }); +" +} + +echo "[ots-node] Backfilling proofs (local only, no calendar)..." + +COMMITS=$(git rev-list --reverse HEAD) +TOTAL=$(echo "$COMMITS" | wc -l) +CURRENT=0 +UPDATED=0 + +for COMMIT in $COMMITS; do + CURRENT=$((CURRENT + 1)) + PROOF_FILE="$OUTPUT_DIR/${COMMIT}.ots" + + if [ $CURRENT -le 3 ] || [ $CURRENT -eq $TOTAL ]; then + echo "[ots-node] Processing $CURRENT/$TOTAL: ${COMMIT:0:8}" + elif [ $CURRENT -eq 4 ]; then + echo "[ots-node] ... processing remaining ..." + fi + + if [ -f "$PROOF_FILE" ]; then + # Node version doesn't support upgrade, just check cache + CACHED_STATUS=$(get_cached_status "$COMMIT" || echo "") + [ "$CACHED_STATUS" = "attested" ] && continue + + # Mark as pending (node can't attest) + cache_status "$COMMIT" "pending" + else + if generate_proof_node "$COMMIT" "$PROOF_FILE"; then + cache_status "$COMMIT" "pending" + UPDATED=$((UPDATED + 1)) + fi + fi +done + +# Update latest proof +LATEST_COMMIT=$(git rev-parse HEAD) +[ -f "$OUTPUT_DIR/${LATEST_COMMIT}.ots" ] && cp "$OUTPUT_DIR/${LATEST_COMMIT}.ots" "$OUTPUT_DIR/proof.ots" + +# Save commit chain +rm -f "$OUTPUT_DIR/commit-chain.txt" +git rev-list HEAD | while read COMMIT; do + PREV=$(git rev-parse ${COMMIT}^1 2>/dev/null || echo "") + [ -n "$PREV" ] && echo "$COMMIT:$PREV" >> "$OUTPUT_DIR/commit-chain.txt" +done + +echo "[ots-node] Backfill complete: $UPDATED generated (local proofs only)" diff --git a/hooks/pre-commit-ots b/hooks/pre-commit-ots new file mode 100755 index 0000000..65d354d --- /dev/null +++ b/hooks/pre-commit-ots @@ -0,0 +1,125 @@ +#!/bin/bash +# Git OpenTimestamp Pre-Commit Backfill Hook (ots CLI version) +# Requires: opentimestamps-client (pipx install opentimestamps-client) + +set -e + +OUTPUT_DIR=".ots" +STATUS_CACHE="$OUTPUT_DIR/.attestation-cache" + +mkdir -p "$OUTPUT_DIR" + +# Initialize cache +if [ ! -f "$STATUS_CACHE" ]; then + echo "# Format: commit-hash:status:timestamp" > "$STATUS_CACHE" +fi + +get_cached_status() { + local commit="$1" + local cache_line=$(grep "^$commit:" "$STATUS_CACHE" 2>/dev/null | tail -1) + if [ -n "$cache_line" ]; then + local status=$(echo "$cache_line" | cut -d: -f2) + local timestamp=$(echo "$cache_line" | cut -d: -f3) + local now=$(date +%s) + if [ "$((now - timestamp))" -lt 3600 ]; then + echo "$status" + return 0 + fi + fi + return 1 +} + +cache_status() { + local commit="$1" + local status="$2" + echo "$commit:$status:$(date +%s)" >> "$STATUS_CACHE" +} + +generate_proof() { + local hash="$1" + local output="$2" + local temp_file=$(mktemp) + local temp_ots="${temp_file}.ots" + + python3 -c "import sys; sys.stdout.buffer.write(bytes.fromhex('$hash'))" > "$temp_file" + ots stamp "$temp_file" 2>/dev/null + + if [ -f "$temp_ots" ]; then + mv "$temp_ots" "$output" + rm -f "$temp_file" + return 0 + fi + rm -f "$temp_file" + return 1 +} + +is_attested() { + local proof_file="$1" + local pending_count=$(ots info "$proof_file" 2>&1 | grep -c "PendingAttestation" || echo "0") + [ "$pending_count" -eq 0 ] +} + +upgrade_proof() { + local proof_file="$1" + ots upgrade "$proof_file" 2>/dev/null +} + +echo "[ots] Backfilling proofs..." + +COMMITS=$(git rev-list --reverse HEAD) +TOTAL=$(echo "$COMMITS" | wc -l) +CURRENT=0 +UPDATED=0 + +for COMMIT in $COMMITS; do + CURRENT=$((CURRENT + 1)) + PROOF_FILE="$OUTPUT_DIR/${COMMIT}.ots" + + if [ $CURRENT -le 3 ] || [ $CURRENT -eq $TOTAL ]; then + echo "[ots] Processing $CURRENT/$TOTAL: ${COMMIT:0:8}" + elif [ $CURRENT -eq 4 ]; then + echo "[ots] ... processing remaining ..." + fi + + if [ -f "$PROOF_FILE" ]; then + CACHED_STATUS=$(get_cached_status "$COMMIT" || echo "") + + [ "$CACHED_STATUS" = "attested" ] && continue + + if is_attested "$PROOF_FILE"; then + cache_status "$COMMIT" "attested" + continue + fi + + cache_status "$COMMIT" "pending" + + CACHE_LINE=$(grep "^$COMMIT:" "$STATUS_CACHE" | tail -1) + CACHE_TIME=$(echo "$CACHE_LINE" | cut -d: -f3) + CACHE_AGE=$(($(date +%s) - CACHE_TIME)) + + [ "$CACHE_AGE" -lt 600 ] && continue + + if upgrade_proof "$PROOF_FILE"; then + cache_status "$COMMIT" "attested" + UPDATED=$((UPDATED + 1)) + fi + else + if generate_proof "$COMMIT" "$PROOF_FILE"; then + cache_status "$COMMIT" "pending" + UPDATED=$((UPDATED + 1)) + fi + fi +done + +# Update latest proof +LATEST_COMMIT=$(git rev-parse HEAD) +[ -f "$OUTPUT_DIR/${LATEST_COMMIT}.ots" ] && cp "$OUTPUT_DIR/${LATEST_COMMIT}.ots" "$OUTPUT_DIR/proof.ots" + +# Save commit chain +rm -f "$OUTPUT_DIR/commit-chain.txt" +git rev-list HEAD | while read COMMIT; do + PREV=$(git rev-parse ${COMMIT}^1 2>/dev/null || echo "") + [ -n "$PREV" ] && echo "$COMMIT:$PREV" >> "$OUTPUT_DIR/commit-chain.txt" +done + +echo "[ots] Backfill complete: $UPDATED updated" diff --git a/scripts/backfill-proofs.sh b/scripts/backfill-proofs.sh deleted file mode 100755 index 38fc3c0..0000000 --- a/scripts/backfill-proofs.sh +++ /dev/null @@ -1,165 +0,0 @@ -#!/bin/bash -# backfill-proofs.sh - Generate/upgrade OpenTimestamp proofs for all commits -# Usage: backfill-proofs.sh [repository-path] - -set -e - -REPO_PATH="${1:-.}" -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -cd "$REPO_PATH" - -# Verify we're in a git repository -if ! git rev-parse --git-dir > /dev/null 2>&1; then - echo "Error: not a git repository" >&2 - exit 1 -fi - -# Find the generate-proof.sh script -GENERATE_SCRIPT="" - -# Check if generate-proof.sh is in the same directory as this script -if [ -f "$SCRIPT_DIR/generate-proof.sh" ]; then - GENERATE_SCRIPT="$SCRIPT_DIR/generate-proof.sh" -# Check in repository root -elif [ -f "generate-proof.sh" ]; then - GENERATE_SCRIPT="./generate-proof.sh" -# Check if skill is installed globally -elif [ -f "$HOME/.openclaw/workspace/skills/git-ots-hook/scripts/generate-proof.sh" ]; then - GENERATE_SCRIPT="$HOME/.openclaw/workspace/skills/git-ots-hook/scripts/generate-proof.sh" -else - echo "Error: generate-proof.sh not found" >&2 - exit 1 -fi - -chmod +x "$GENERATE_SCRIPT" - -# Ensure .ots directory exists -mkdir -p .ots - -# Cache file for attestation status (avoids repeated ots info calls) -STATUS_CACHE=".ots/.attestation-cache" -if [ ! -f "$STATUS_CACHE" ]; then - echo "# Attestation status cache" > "$STATUS_CACHE" - echo "# Format: commit-hash:status:timestamp" >> "$STATUS_CACHE" -fi - -# Function to check cached status -get_cached_status() { - local commit="$1" - local cache_line=$(grep "^$commit:" "$STATUS_CACHE" 2>/dev/null | tail -1) - if [ -n "$cache_line" ]; then - local status=$(echo "$cache_line" | cut -d: -f2) - local timestamp=$(echo "$cache_line" | cut -d: -f3) - local now=$(date +%s) - local age=$((now - timestamp)) - # Cache valid for 1 hour (3600 seconds) - if [ "$age" -lt 3600 ]; then - echo "$status" - return 0 - fi - fi - return 1 -} - -# Function to cache status -cache_status() { - local commit="$1" - local status="$2" - local now=$(date +%s) - echo "$commit:$status:$now" >> "$STATUS_CACHE" -} - -echo "[ots-backfill] Scanning commit history..." - -# Get all commit hashes (oldest to newest) -COMMITS=$(git rev-list --reverse HEAD) -TOTAL=$(echo "$COMMITS" | wc -l) -CURRENT=0 - -# Track if any proofs were updated -UPDATED=0 - -for COMMIT in $COMMITS; do - CURRENT=$((CURRENT + 1)) - echo "[ots-backfill] Processing commit $CURRENT/$TOTAL: ${COMMIT:0:8}" - - # Check if proof exists for this commit - PROOF_FILE=".ots/${COMMIT}.ots" - - if [ -f "$PROOF_FILE" ]; then - # Check cached status first - CACHED_STATUS=$(get_cached_status "$COMMIT" || echo "") - - if [ "$CACHED_STATUS" = "attested" ]; then - echo " ✓ Already attested (cached, skipping)" - continue - fi - - # Check if already fully attested (only if not cached or cache expired) - PENDING_COUNT=$(ots info "$PROOF_FILE" 2>&1 | grep -c "PendingAttestation" || echo "0") - - if [ "$PENDING_COUNT" -eq 0 ]; then - echo " ✓ Already attested" - cache_status "$COMMIT" "attested" - continue - fi - - # Cache as pending - cache_status "$COMMIT" "pending" - - # Skip upgrade if cache is fresh (< 10 min old) - CACHE_LINE=$(grep "^$COMMIT:" "$STATUS_CACHE" | tail -1) - CACHE_TIME=$(echo "$CACHE_LINE" | cut -d: -f3) - NOW=$(date +%s) - CACHE_AGE=$((NOW - CACHE_TIME)) - - if [ "$CACHE_AGE" -lt 600 ]; then - echo " - Pending (cached <10min, skipping calendar call)" - continue - fi - - # Try to upgrade existing proof (contacts calendars) - echo " Upgrading pending proof..." - if ots upgrade "$PROOF_FILE" 2>/dev/null; then - echo " ✓ Upgraded to attested" - cache_status "$COMMIT" "attested" - UPDATED=$((UPDATED + 1)) - else - echo " - Still pending (no upgrade available yet)" - # Update cache timestamp - cache_status "$COMMIT" "pending" - fi - else - # Generate new proof for this commit - echo " Generating new proof..." - "$GENERATE_SCRIPT" "$COMMIT" "$PROOF_FILE" >/dev/null 2>&1 - echo " ✓ Generated proof" - cache_status "$COMMIT" "pending" - UPDATED=$((UPDATED + 1)) - fi -done - -# Create/update latest proof symlink -LATEST_COMMIT=$(git rev-parse HEAD) -if [ -f ".ots/${LATEST_COMMIT}.ots" ]; then - cp ".ots/${LATEST_COMMIT}.ots" ".ots/proof.ots" - echo "[ots-backfill] Updated .ots/proof.ots for latest commit" -fi - -# Save previous commit chain info -echo "[ots-backfill] Saving commit chain..." -git rev-list HEAD | while read COMMIT; do - PREV=$(git rev-parse ${COMMIT}^1 2>/dev/null || echo "") - if [ -n "$PREV" ]; then - echo "$COMMIT:$PREV" >> ".ots/commit-chain.txt" - fi -done - -echo "" -echo "[ots-backfill] Complete! Processed $TOTAL commits, updated $UPDATED proofs" -echo "" -echo "Proofs stored in: .ots/" -echo " - Individual proofs: .ots/.ots" -echo " - Latest proof: .ots/proof.ots" -echo " - Commit chain: .ots/commit-chain.txt" diff --git a/scripts/check-attestation.sh b/scripts/check-attestation.sh deleted file mode 100755 index 5420dd3..0000000 --- a/scripts/check-attestation.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -# check-attestation.sh - Check if an OpenTimestamp proof is fully attested -# Usage: check-attestation.sh -# Returns: 0 if fully attested, 1 if pending - -PROOF_FILE="${1:-}" - -if [ -z "$PROOF_FILE" ] || [ ! -f "$PROOF_FILE" ]; then - echo "Error: proof file not found: $PROOF_FILE" >&2 - exit 2 -fi - -# Check for PendingAttestation in ots info output -if ots info "$PROOF_FILE" 2>&1 | grep -q "PendingAttestation"; then - echo "pending" - exit 1 -else - echo "attested" - exit 0 -fi diff --git a/scripts/generate-proof.sh b/scripts/generate-proof.sh deleted file mode 100755 index d85d70f..0000000 --- a/scripts/generate-proof.sh +++ /dev/null @@ -1,114 +0,0 @@ -#!/bin/bash -# generate-proof.sh - Generate OpenTimestamp proof for a git commit -# Usage: generate-proof.sh [output-file] - -set -e - -COMMIT_HASH="${1:-}" -OUTPUT_FILE="${2:-.ots/proof.ots}" - -if [ -z "$COMMIT_HASH" ]; then - echo "Error: commit hash required" >&2 - echo "Usage: $0 [output-file]" >&2 - exit 1 -fi - -# Ensure .ots directory exists -mkdir -p "$(dirname "$OUTPUT_FILE")" - -# Function to generate proof using ots CLI -generate_with_ots_cli() { - local hash="$1" - local output="$2" - - # Create a temporary file with the hash (convert hex to binary) - local temp_file=$(mktemp) - local temp_ots="${temp_file}.ots" - - # Use python3 for hex to binary conversion (more portable than xxd) - python3 -c "import sys; sys.stdout.buffer.write(bytes.fromhex('$hash'))" > "$temp_file" - - # Generate timestamp - ots creates .ots file alongside the original - ots stamp "$temp_file" - - # Move the generated .ots file to the desired location - if [ -f "$temp_ots" ]; then - mkdir -p "$(dirname "$output")" - mv "$temp_ots" "$output" - echo "Generated proof with ots CLI: $output" - else - echo "Error: ots did not generate proof file" >&2 - rm -f "$temp_file" - exit 1 - fi - - # Cleanup - rm -f "$temp_file" -} - -# Function to generate proof using opentimestamps-nodejs -generate_with_node() { - local hash="$1" - local output="$2" - - # Use nodejs opentimestamps library - # This requires @opentimestamps/ots to be installed globally - node -e " -const ots = require('@opentimestamps/ots'); -const fs = require('fs'); -const crypto = require('crypto'); - -const hash = '$hash'; -const output = '$output'; - -// Convert hex hash to buffer -const hashBuffer = Buffer.from(hash, 'hex'); - -// Create timestamp -ots.Timestamp.hash(hashBuffer).then(timestamp => { - // In a real implementation, this would contact the calendar server - // For now, we create a minimal proof structure - console.log('Warning: nodejs fallback creates local proof only'); - console.log('Install ots CLI for full functionality: https://github.com/opentimestamps/opentimestamps-client'); - - // Create a simple proof file (this is a placeholder - real impl needs calendar) - const proof = { - hash: hash, - timestamp: new Date().toISOString(), - status: 'pending' - }; - - fs.writeFileSync(output, JSON.stringify(proof, null, 2)); - console.log('Generated local proof: ' + output); -}).catch(err => { - console.error('Error generating timestamp:', err); - process.exit(1); -}); -" -} - -# Check if ots CLI is available -if command -v ots &> /dev/null; then - echo "Using ots CLI..." - generate_with_ots_cli "$COMMIT_HASH" "$OUTPUT_FILE" -elif command -v node &> /dev/null && node -e "require('@opentimestamps/ots')" &> /dev/null; then - echo "Using opentimestamps-nodejs fallback..." - generate_with_node "$COMMIT_HASH" "$OUTPUT_FILE" -else - echo "Error: Neither ots CLI nor @opentimestamps/ots node package found" >&2 - echo "Install one of:" >&2 - echo " - ots CLI: https://github.com/opentimestamps/opentimestamps-client" >&2 - echo " - Node package: npm install -g @opentimestamps/ots" >&2 - exit 1 -fi - -# Get previous commit hash for chaining (before appending to proof file) -PREV_COMMIT=$(git rev-parse HEAD^1 2>/dev/null || echo "") - -# Store previous commit hash in a separate metadata file for chaining -if [ -n "$PREV_COMMIT" ]; then - echo "$PREV_COMMIT" > "$(dirname "$OUTPUT_FILE")/prev-commit.txt" - echo "Chaining info saved: $PREV_COMMIT" -fi - -echo "Proof generated for commit: $COMMIT_HASH" diff --git a/scripts/install-ots-hook.sh b/scripts/install-ots-hook.sh deleted file mode 100755 index 46c34b7..0000000 --- a/scripts/install-ots-hook.sh +++ /dev/null @@ -1,98 +0,0 @@ -#!/bin/bash -# install-ots-hook.sh - Install OpenTimestamp post-commit hook -# Usage: install-ots-hook.sh [repository-path] [backfill] - -set -e - -REPO_PATH="${1:-.}" -ENABLE_BACKFILL="${2:-}" # Second arg: "backfill" to enable pre-commit backfill hook -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -cd "$REPO_PATH" - -# Verify we're in a git repository -if ! git rev-parse --git-dir > /dev/null 2>&1; then - echo "Error: not a git repository" >&2 - exit 1 -fi - -HOOKS_DIR="$(git rev-parse --git-dir)/hooks" -POST_COMMIT_HOOK="$HOOKS_DIR/post-commit" - -echo "Installing OpenTimestamp hooks in: $REPO_PATH" - -# Create the post-commit hook -cat > "$POST_COMMIT_HOOK" << 'HOOK_SCRIPT' -#!/bin/bash -# Post-commit hook: generate OpenTimestamp proof for each commit - -set -e - -# Get the commit hash -COMMIT_HASH=$(git rev-parse HEAD) - -# Find the generate-proof.sh script -# Try relative paths first, then check common locations -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -GENERATE_SCRIPT="" - -# Check if generate-proof.sh is in the same directory as this hook -if [ -f "$SCRIPT_DIR/generate-proof.sh" ]; then - GENERATE_SCRIPT="$SCRIPT_DIR/generate-proof.sh" -# Check in .git/hooks directory -elif [ -f ".git/hooks/generate-proof.sh" ]; then - GENERATE_SCRIPT=".git/hooks/generate-proof.sh" -# Check in repository root -elif [ -f "generate-proof.sh" ]; then - GENERATE_SCRIPT="./generate-proof.sh" -# Check if skill is installed globally (common OpenClaw path) -elif [ -f "$HOME/.openclaw/workspace/skills/git-ots-hook/scripts/generate-proof.sh" ]; then - GENERATE_SCRIPT="$HOME/.openclaw/workspace/skills/git-ots-hook/scripts/generate-proof.sh" -else - echo "Warning: generate-proof.sh not found, skipping OpenTimestamp proof" >&2 - exit 0 -fi - -# Make sure the script is executable -chmod +x "$GENERATE_SCRIPT" - -# Generate the proof -echo "[ots] Generating proof for commit: $COMMIT_HASH" -"$GENERATE_SCRIPT" "$COMMIT_HASH" - -echo "[ots] Proof generated successfully" -HOOK_SCRIPT - -# Make the hook executable -chmod +x "$POST_COMMIT_HOOK" - -echo "✓ Post-commit hook installed successfully" - -# Optionally install pre-commit backfill hook -if [ "$ENABLE_BACKFILL" = "backfill" ]; then - PRE_COMMIT_HOOK="$HOOKS_DIR/pre-commit" - cp "$SCRIPT_DIR/pre-commit-backfill" "$PRE_COMMIT_HOOK" - chmod +x "$PRE_COMMIT_HOOK" - echo "✓ Pre-commit backfill hook installed (will backfill proofs before each commit)" -fi - -# Optionally setup .gitignore -if [ -f "$SCRIPT_DIR/setup-gitignore.sh" ]; then - "$SCRIPT_DIR/setup-gitignore.sh" "$REPO_PATH" -elif [ -f "$HOME/.openclaw/workspace/skills/git-ots-hook/scripts/setup-gitignore.sh" ]; then - "$HOME/.openclaw/workspace/skills/git-ots-hook/scripts/setup-gitignore.sh" "$REPO_PATH" -fi - -echo "" -echo "Next steps:" -echo "1. Ensure scripts are accessible (copy to repo root or install skill globally)" -echo "2. Verify ots CLI or @opentimestamps/ots is installed" -echo "3. Add .ots/ to git tracking: git add .ots/" -echo "4. Commit proofs: git commit -m 'Add OpenTimestamp proofs'" -echo "5. Make a test commit to verify the hooks work" -echo "" -echo "To uninstall:" -echo " rm $POST_COMMIT_HOOK" -if [ "$ENABLE_BACKFILL" = "backfill" ]; then - echo " rm $PRE_COMMIT_HOOK" -fi diff --git a/scripts/pre-commit-backfill b/scripts/pre-commit-backfill deleted file mode 100755 index 3ff9a7d..0000000 --- a/scripts/pre-commit-backfill +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -# Pre-commit hook: backfill proofs for all commits before creating new commit -# This ensures all historical commits have attested proofs - -set -e - -# Get the directory where this hook is installed -HOOKS_DIR="$(git rev-parse --git-dir)/hooks" -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# Find the backfill-proofs.sh script -BACKFILL_SCRIPT="" - -if [ -f "$SCRIPT_DIR/backfill-proofs.sh" ]; then - BACKFILL_SCRIPT="$SCRIPT_DIR/backfill-proofs.sh" -elif [ -f ".git/hooks/backfill-proofs.sh" ]; then - BACKFILL_SCRIPT=".git/hooks/backfill-proofs.sh" -elif [ -f "backfill-proofs.sh" ]; then - BACKFILL_SCRIPT="./backfill-proofs.sh" -elif [ -f "$HOME/.openclaw/workspace/skills/git-ots-hook/scripts/backfill-proofs.sh" ]; then - BACKFILL_SCRIPT="$HOME/.openclaw/workspace/skills/git-ots-hook/scripts/backfill-proofs.sh" -else - # Silently skip if backfill script not found - exit 0 -fi - -chmod +x "$BACKFILL_SCRIPT" - -echo "[ots] Backfilling proofs before commit..." -"$BACKFILL_SCRIPT" . - -echo "[ots] Ready to commit" diff --git a/scripts/setup-gitignore.sh b/scripts/setup-gitignore.sh deleted file mode 100755 index 6bb6619..0000000 --- a/scripts/setup-gitignore.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash -# setup-gitignore.sh - Create/update .gitignore for OpenTimestamp proofs -# Usage: setup-gitignore.sh [repository-path] - -set -e - -REPO_PATH="${1:-.}" - -cd "$REPO_PATH" - -# Verify we're in a git repository -if ! git rev-parse --git-dir > /dev/null 2>&1; then - echo "Error: not a git repository" >&2 - exit 1 -fi - -GITIGNORE=".gitignore" - -# Create .gitignore if it doesn't exist -if [ ! -f "$GITIGNORE" ]; then - echo "# OpenTimestamp proofs cache (exclude from version control)" > "$GITIGNORE" - echo ".ots/.attestation-cache" >> "$GITIGNORE" - echo "" - echo "Created $GITIGNORE with OpenTimestamp cache exclusion" -else - # Check if already has the cache exclusion - if grep -q ".ots/.attestation-cache" "$GITIGNORE"; then - echo "$GITIGNORE already excludes .ots/.attestation-cache" - else - echo "" >> "$GITIGNORE" - echo "# OpenTimestamp proofs cache (exclude from version control)" >> "$GITIGNORE" - echo ".ots/.attestation-cache" >> "$GITIGNORE" - echo "Added .ots/.attestation-cache to $GITIGNORE" - fi -fi - -echo "" -echo "Next steps:" -echo "1. Review and commit your .gitignore" -echo "2. Add .ots/ directory to track proofs: git add .ots/" -echo "3. Commit proofs: git commit -m 'Add OpenTimestamp proofs'"