From a7c0f825be4491ecec199ca7b983ee46525853e5 Mon Sep 17 00:00:00 2001 From: Otto Date: Sat, 7 Mar 2026 17:17:25 +0100 Subject: [PATCH] Test self-contained unified hooks --- .gitignore | 1 + .ots/.attestation-cache | 19 +++ ...f5ab21764bc9a4507e25e0bc8de2989d4febad.ots | Bin 0 -> 595 bytes .ots/commit-chain.txt | 19 +++ ...eb0ad6782d2bc003206521cc66ea370dcccd9f.ots | Bin 0 -> 560 bytes .ots/prev-commit.txt | 2 +- .ots/proof.ots | Bin 630 -> 595 bytes hooks/install.sh | 50 ++++++ hooks/post-commit | 91 ++++++++++ hooks/pre-commit | 156 ++++++++++++++++++ test9.txt | 1 + 11 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 .ots/4bf5ab21764bc9a4507e25e0bc8de2989d4febad.ots create mode 100644 .ots/f4eb0ad6782d2bc003206521cc66ea370dcccd9f.ots create mode 100755 hooks/install.sh create mode 100755 hooks/post-commit create mode 100755 hooks/pre-commit create mode 100644 test9.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a8e2c1a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.ots/.attestation-cache diff --git a/.ots/.attestation-cache b/.ots/.attestation-cache index aa496dc..45164d2 100644 --- a/.ots/.attestation-cache +++ b/.ots/.attestation-cache @@ -15,3 +15,22 @@ c0685dabfb48360a3abc103b75357f94e9f054b2:pending:1772897691 810d26b7af9c5d306e77fec290d360c7ac876b2e:pending:1772897694 3b54e0cb8c611d3f3525ad2386368f60200891f1:pending:1772897698 ed2cd259e918344c5a21ecf884b3178b4256ea74:pending:1772897704 +392ee723c3cf626d0e5281aa94771d7133bb345e:pending:1772897724 +db6f29e01a33d8ed8f127ff169d9f91d55e8a229:pending:1772897725 +4a6f5ed0c12315b0bc8a0fa5815ada1bd20e5963:pending:1772897726 +46aded7b9582bbed673843e2cf8a3f8fa742ad91:pending:1772897727 +c0685dabfb48360a3abc103b75357f94e9f054b2:pending:1772897728 +810d26b7af9c5d306e77fec290d360c7ac876b2e:pending:1772897728 +3b54e0cb8c611d3f3525ad2386368f60200891f1:pending:1772897729 +ed2cd259e918344c5a21ecf884b3178b4256ea74:pending:1772897730 +f4eb0ad6782d2bc003206521cc66ea370dcccd9f:pending:1772897732 +392ee723c3cf626d0e5281aa94771d7133bb345e:pending:1772900099 +db6f29e01a33d8ed8f127ff169d9f91d55e8a229:pending:1772900100 +4a6f5ed0c12315b0bc8a0fa5815ada1bd20e5963:pending:1772900100 +46aded7b9582bbed673843e2cf8a3f8fa742ad91:pending:1772900101 +c0685dabfb48360a3abc103b75357f94e9f054b2:pending:1772900102 +810d26b7af9c5d306e77fec290d360c7ac876b2e:pending:1772900103 +3b54e0cb8c611d3f3525ad2386368f60200891f1:pending:1772900104 +ed2cd259e918344c5a21ecf884b3178b4256ea74:pending:1772900105 +f4eb0ad6782d2bc003206521cc66ea370dcccd9f:pending:1772900106 +4bf5ab21764bc9a4507e25e0bc8de2989d4febad:pending:1772900109 diff --git a/.ots/4bf5ab21764bc9a4507e25e0bc8de2989d4febad.ots b/.ots/4bf5ab21764bc9a4507e25e0bc8de2989d4febad.ots new file mode 100644 index 0000000000000000000000000000000000000000..7277f907950c595054b8fef26a48a32d8699ae63 GIT binary patch literal 595 zcmZSZFG$S`$;?eHE=kNSC}v;?D9X=IW7yyM=tawmNmCd(;-{Mo@>&&xhD^3Z7=;GaR1n@x59xZCULC#_ET@S=|YbGA2_DAa%kx7yDY`= zLEu(=>w-;B|Lv{3DZcHbQN}iok1UyM{CPicXzcdiv@lAgk)iqiV_v!am-anmRD!2OI5jBP7ZyRgk#mZ_fJd#A>^vD1Fb<8R~GOI148)7rh2ac{+-p7xtw3@(eR#t-9 zjN;|w#1f!|i4{PXT4 zUp{P1x#6Il?Q!mQ%;ZzIoF}9zuKZUe6cE@x2k5}2n{OtRzYly_`P8~>_rdv{Iy&mL p9(M%Fszc`;&QE-w438K0T4!%Fx4T|&2kPm<9GIAsnVd=}W&m|d5V8OO literal 0 HcmV?d00001 diff --git a/.ots/commit-chain.txt b/.ots/commit-chain.txt index 5c48dc5..8f04c9f 100644 --- a/.ots/commit-chain.txt +++ b/.ots/commit-chain.txt @@ -28,3 +28,22 @@ c0685dabfb48360a3abc103b75357f94e9f054b2:46aded7b9582bbed673843e2cf8a3f8fa742ad9 4a6f5ed0c12315b0bc8a0fa5815ada1bd20e5963:db6f29e01a33d8ed8f127ff169d9f91d55e8a229 db6f29e01a33d8ed8f127ff169d9f91d55e8a229:392ee723c3cf626d0e5281aa94771d7133bb345e 392ee723c3cf626d0e5281aa94771d7133bb345e:392ee723c3cf626d0e5281aa94771d7133bb345e^1 +f4eb0ad6782d2bc003206521cc66ea370dcccd9f:ed2cd259e918344c5a21ecf884b3178b4256ea74 +ed2cd259e918344c5a21ecf884b3178b4256ea74:3b54e0cb8c611d3f3525ad2386368f60200891f1 +3b54e0cb8c611d3f3525ad2386368f60200891f1:810d26b7af9c5d306e77fec290d360c7ac876b2e +810d26b7af9c5d306e77fec290d360c7ac876b2e:c0685dabfb48360a3abc103b75357f94e9f054b2 +c0685dabfb48360a3abc103b75357f94e9f054b2:46aded7b9582bbed673843e2cf8a3f8fa742ad91 +46aded7b9582bbed673843e2cf8a3f8fa742ad91:4a6f5ed0c12315b0bc8a0fa5815ada1bd20e5963 +4a6f5ed0c12315b0bc8a0fa5815ada1bd20e5963:db6f29e01a33d8ed8f127ff169d9f91d55e8a229 +db6f29e01a33d8ed8f127ff169d9f91d55e8a229:392ee723c3cf626d0e5281aa94771d7133bb345e +392ee723c3cf626d0e5281aa94771d7133bb345e:392ee723c3cf626d0e5281aa94771d7133bb345e^1 +4bf5ab21764bc9a4507e25e0bc8de2989d4febad:f4eb0ad6782d2bc003206521cc66ea370dcccd9f +f4eb0ad6782d2bc003206521cc66ea370dcccd9f:ed2cd259e918344c5a21ecf884b3178b4256ea74 +ed2cd259e918344c5a21ecf884b3178b4256ea74:3b54e0cb8c611d3f3525ad2386368f60200891f1 +3b54e0cb8c611d3f3525ad2386368f60200891f1:810d26b7af9c5d306e77fec290d360c7ac876b2e +810d26b7af9c5d306e77fec290d360c7ac876b2e:c0685dabfb48360a3abc103b75357f94e9f054b2 +c0685dabfb48360a3abc103b75357f94e9f054b2:46aded7b9582bbed673843e2cf8a3f8fa742ad91 +46aded7b9582bbed673843e2cf8a3f8fa742ad91:4a6f5ed0c12315b0bc8a0fa5815ada1bd20e5963 +4a6f5ed0c12315b0bc8a0fa5815ada1bd20e5963:db6f29e01a33d8ed8f127ff169d9f91d55e8a229 +db6f29e01a33d8ed8f127ff169d9f91d55e8a229:392ee723c3cf626d0e5281aa94771d7133bb345e +392ee723c3cf626d0e5281aa94771d7133bb345e:392ee723c3cf626d0e5281aa94771d7133bb345e^1 diff --git a/.ots/f4eb0ad6782d2bc003206521cc66ea370dcccd9f.ots b/.ots/f4eb0ad6782d2bc003206521cc66ea370dcccd9f.ots new file mode 100644 index 0000000000000000000000000000000000000000..cebc5f6e0147d8214427f5793d549804dcce9553 GIT binary patch literal 560 zcmZSZFG$S`$;?eHE=kNSC}v;?D9X=IW7yyM=tawmNmCd((*CXUtF5WLlQqp)S?$h= zDgAcmO_yli|9AbEUPTaZG4}_7?s_-(Yf-0S8GGE@TbCY|=J@}Cqb^)>=gQ<`i#a|B zEcFd{IePzkZA+rS{@~-5?>IiPWUg^N@_{30$4t&8*Z)N_G~a*BtM`+qPe(hWq@Ma-UvfIb z(qoI&&DCAwM{Zb+j4@d;MOni(VOUT zEsYm$y`C=2`ox^f-5`k{l-m8-QV=^ODs>mxB^$NY|^RyW%sp|iOVeNIbv^p-_ARi z;~!ANoVMDgd1&E zxT$;46Qm@GQ^|PmiE9sx*Urmb_)MatDNR#R|Lux`6Q3eE!=r6^?}tlppsvUwpHA_Ykle6vXAdQ6V9*GbyO`qV7P2q#M%vN p69h7r!M&@kvFCL1_eCmjdo?vON=gcft@QQNGV}6MCr2|$0|3+fx{v?> delta 422 zcmcc2@{MJJVtv}bb$+!qm3OkH87r&ZIWeW*?!4&|&HMkZAJeM{;w|R>AaLh1FKdF@ z`x-@&4XdU)mUwdf|G=^Gt=ue|$zc;XJ}O+hng1~BXSYj*@3~%sWk=KBU$EXcsc9*v zY`T-x-_`8fIe;cCiC?k*uKh~u1-osmHP+1F_{fsE#&y*P4#N{{29*lue_x(>#X?Eo z@TpTLCR)Dz((Cbx|B7O@6 /dev/null 2>&1; then + echo "Error: not a git repository" >&2 + exit 1 +fi + +HOOKS_DIR="$(git rev-parse --git-dir)/hooks" + +echo "Installing OpenTimestamp hooks to: $HOOKS_DIR" + +# Copy hooks +cp "$SCRIPT_DIR/post-commit" "$HOOKS_DIR/post-commit" +cp "$SCRIPT_DIR/pre-commit" "$HOOKS_DIR/pre-commit" + +# Make executable +chmod +x "$HOOKS_DIR/post-commit" "$HOOKS_DIR/pre-commit" + +echo "✓ Post-commit hook installed" +echo "✓ Pre-commit backfill hook installed" + +# Setup .gitignore +GITIGNORE=".gitignore" +if [ ! -f "$GITIGNORE" ]; then + echo ".ots/.attestation-cache" > "$GITIGNORE" + echo "✓ Created .gitignore" +elif ! grep -q ".ots/.attestation-cache" "$GITIGNORE"; then + echo "" >> "$GITIGNORE" + echo ".ots/.attestation-cache" >> "$GITIGNORE" + echo "✓ Updated .gitignore" +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 "" +echo "To uninstall:" +echo " rm $HOOKS_DIR/post-commit $HOOKS_DIR/pre-commit" diff --git a/hooks/post-commit b/hooks/post-commit new file mode 100755 index 0000000..044dfe3 --- /dev/null +++ b/hooks/post-commit @@ -0,0 +1,91 @@ +#!/bin/bash +# Git OpenTimestamp Post-Commit Hook +# Self-contained - no external dependencies +# Generates cryptographic proof for each commit using OpenTimestamp + +set -e + +# Configuration +OUTPUT_DIR=".ots" +OUTPUT_FILE="$OUTPUT_DIR/proof.ots" + +# Ensure output directory exists +mkdir -p "$OUTPUT_DIR" + +# Get the current commit hash +COMMIT_HASH=$(git rev-parse HEAD) + +# 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" 2>/dev/null + + # Move the generated .ots file to the desired location + 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 generate proof using nodejs fallback +generate_with_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)); + console.log('Generated local proof (nodejs fallback)'); +}).catch(err => { console.error('Error:', err); process.exit(1); }); +" +} + +# Check for available tools and generate proof +if command -v ots &> /dev/null; then + if generate_with_ots_cli "$COMMIT_HASH" "$OUTPUT_FILE"; then + echo "[ots] Generated proof with ots CLI: ${COMMIT_HASH:0:8}" + else + echo "[ots] Warning: ots CLI failed" >&2 + exit 0 + fi +elif command -v node &> /dev/null && node -e "require('@opentimestamps/ots')" &> /dev/null 2>&1; then + generate_with_node "$COMMIT_HASH" "$OUTPUT_FILE" + echo "[ots] Generated proof with nodejs fallback: ${COMMIT_HASH:0:8}" +else + echo "[ots] Warning: Neither ots CLI nor @opentimestamps/ots found, skipping proof" >&2 + exit 0 +fi + +# Save previous commit hash 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 for this commit +INDIVIDUAL_PROOF="$OUTPUT_DIR/${COMMIT_HASH}.ots" +if [ -f "$OUTPUT_FILE" ]; then + cp "$OUTPUT_FILE" "$INDIVIDUAL_PROOF" +fi + +echo "[ots] Proof generated successfully" diff --git a/hooks/pre-commit b/hooks/pre-commit new file mode 100755 index 0000000..9337782 --- /dev/null +++ b/hooks/pre-commit @@ -0,0 +1,156 @@ +#!/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" diff --git a/test9.txt b/test9.txt new file mode 100644 index 0000000..10e6a35 --- /dev/null +++ b/test9.txt @@ -0,0 +1 @@ +# Test self-contained hooks