#!/bin/bash # Git OpenTimestamp Pre-Commit Backfill Hook # Self-contained - no external dependencies # Upgrades all historical proofs before each new commit set -e # Configuration OUTPUT_DIR=".ots" STATUS_CACHE="$OUTPUT_DIR/.attestation-cache" # Ensure output directory exists mkdir -p "$OUTPUT_DIR" # Initialize cache file if it doesn't exist if [ ! -f "$STATUS_CACHE" ]; then echo "# Attestation status cache" > "$STATUS_CACHE" echo "# Format: commit-hash:status:timestamp" >> "$STATUS_CACHE" fi # Function to get 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" } # Function to generate proof using ots CLI 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 else rm -f "$temp_file" return 1 fi } # Function to check if proof is attested is_attested() { local proof_file="$1" local pending_count=$(ots info "$proof_file" 2>&1 | grep -c "PendingAttestation" || echo "0") [ "$pending_count" -eq 0 ] } # Function to upgrade proof upgrade_proof() { local proof_file="$1" ots upgrade "$proof_file" 2>/dev/null } echo "[ots] Backfilling proofs..." # Get all commit hashes (oldest to newest) 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" # Skip verbose output for brevity if [ $CURRENT -le 3 ] || [ $CURRENT -eq $TOTAL ]; then echo "[ots] Processing commit $CURRENT/$TOTAL: ${COMMIT:0:8}" elif [ $CURRENT -eq 4 ]; then echo "[ots] ... processing remaining commits ..." fi if [ -f "$PROOF_FILE" ]; then # Check cached status first CACHED_STATUS=$(get_cached_status "$COMMIT" || echo "") if [ "$CACHED_STATUS" = "attested" ]; then continue fi # Check if already attested if is_attested "$PROOF_FILE"; then 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 continue fi # Try to upgrade if upgrade_proof "$PROOF_FILE"; then cache_status "$COMMIT" "attested" UPDATED=$((UPDATED + 1)) fi else # Generate new proof if generate_proof "$COMMIT" "$PROOF_FILE"; then cache_status "$COMMIT" "pending" UPDATED=$((UPDATED + 1)) fi fi done # Update latest proof symlink LATEST_COMMIT=$(git rev-parse HEAD) if [ -f "$OUTPUT_DIR/${LATEST_COMMIT}.ots" ]; then cp "$OUTPUT_DIR/${LATEST_COMMIT}.ots" "$OUTPUT_DIR/proof.ots" fi # 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 "") if [ -n "$PREV" ]; then echo "$COMMIT:$PREV" >> "$OUTPUT_DIR/commit-chain.txt" fi done echo "[ots] Backfill complete: $UPDATED proofs updated"