From 605e2188205c11af2cc03ee2cb577cd5a528b72e Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Fri, 20 Jun 2025 21:33:15 +0100 Subject: [PATCH 1/2] chore: copy across scripts from governance-actions repository --- .gitignore | 261 ++++++++++---------- scripts/.env.example | 4 + scripts/create-author-witness.sh | 138 +++++++++++ scripts/create-human-readable-from-json.sh | 67 +++++ scripts/hash-json.sh | 31 +++ scripts/ipfs.sh | 271 +++++++++++++++++++++ scripts/validate-json.sh | 91 +++++++ scripts/verify-author-witness.sh | 89 +++++++ 8 files changed, 819 insertions(+), 133 deletions(-) create mode 100644 scripts/.env.example create mode 100755 scripts/create-author-witness.sh create mode 100755 scripts/create-human-readable-from-json.sh create mode 100755 scripts/hash-json.sh create mode 100755 scripts/ipfs.sh create mode 100755 scripts/validate-json.sh create mode 100755 scripts/verify-author-witness.sh diff --git a/.gitignore b/.gitignore index 1170717..3252825 100644 --- a/.gitignore +++ b/.gitignore @@ -1,136 +1,131 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variable files +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +### Python ### +.env/ +__pycache__/ + +# Voting Node Service +node_storage/ +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Other +book/html +book/tmp +book/src/06_rust_api/rust +book/src/08_event-db/db-diagrams/*.dot +.stoplight + +/vendor + +# Used by nix +result* +/.direnv/ +/.pre-commit-config.yaml + +# Development Environments +.vscode +**/.idea/ +.temp/ + +# std +.std + +# nixago: ignore-linked-files +.prettierrc +lefthook.yml +treefmt.toml + +# local earthly Environments +local/ + +# terraform +infra/terraform/.terraform* + +# local env files .env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp -.cache - -# vitepress build output -**/.vitepress/dist - -# vitepress cache directory -**/.vitepress/cache - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port +.envrc -# Stores VSCode versions used for testing VSCode extensions -.vscode-test +test/ -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* +./scripts/.env \ No newline at end of file diff --git a/scripts/.env.example b/scripts/.env.example new file mode 100644 index 0000000..c7d010d --- /dev/null +++ b/scripts/.env.example @@ -0,0 +1,4 @@ +export NMKR_USER_ID="your_user_id" +export NMKR_API_KEY="your_api_key" +export BLOCKFROST_API_KEY="your_blockfrost_api_key" +export PINATA_API_KEY="your_pinata_api_key" \ No newline at end of file diff --git a/scripts/create-author-witness.sh b/scripts/create-author-witness.sh new file mode 100755 index 0000000..6cb9958 --- /dev/null +++ b/scripts/create-author-witness.sh @@ -0,0 +1,138 @@ +#!/bin/bash + +################################################## +DEFAULT_AUTHOR_NAME="Intersect" +DEFAULT_USE_CIP8="false" # default to false, to the script uses Ed25519 +################################################## + +# This is just a script for testing purposes. + +# This script needs a signing key to be held locally +# most setups should and will not have this. + +################################################## + +# Check if cardano-signer is installed +if ! command -v cardano-signer >/dev/null 2>&1; then + echo "Error: cardano-signer is not installed or not in your PATH." >&2 + exit 1 +fi + +# Usage message +usage() { + echo "Usage: $0 [--author-name NAME] [--use-cip8]" + echo "Options:" + echo " --author-name NAME Specify the author name (default: $DEFAULT_AUTHOR_NAME)" + echo " --use-cip8 Use CIP-8 signing algorithm (default: $DEFAULT_USE_CIP8)" + exit 1 +} + +# Initialize variables with defaults +input_path="" +input_key="" +author_name="$DEFAULT_AUTHOR_NAME" +use_cip8="$DEFAULT_USE_CIP8" + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + --author-name) + author_name="$2" + shift 2 + ;; + --use-cip8) + use_cip8="true" + shift + ;; + -h|--help) + usage + ;; + *) + if [ -z "$input_path" ]; then + input_path="$1" + elif [ -z "$input_key" ]; then + input_key="$1" + else + echo "Error: Unexpected argument '$1'" + usage + fi + shift + ;; + esac +done + +# Check for required arguments +if [ -z "$input_path" ] || [ -z "$input_key" ]; then + echo "Error: Missing required arguments" + usage +fi + +# Check if the key input file exists +if [ ! -f "$input_key" ]; then + echo "Error: Signing key file '$input_key' not found!" + exit 1 +fi + +sign_file() { + local file="$1" + local use_cip8="$2" + + if [ "$use_cip8" = "true" ]; then + echo "Signing with CIP-8 algorithm..." + + temp_vkey=$(mktemp) + temp_hash=$(mktemp) + + # Generate verification key from the provided secret key + cardano-cli key verification-key \ + --signing-key-file "$input_key" \ + --verification-key-file "$temp_vkey" + + # hash the verification key to get the public key hash + public_key_hash=$(cardano-cli address key-hash --payment-verification-key-file "$temp_vkey") + + # Clean up temporary files + rm "$temp_vkey" + rm "$temp_hash" + + echo "Signing with CIP8 algorithm..." + cardano-signer sign --cip100 \ + --data-file "$file" \ + --secret-key "$input_key" \ + --author-name "$author_name" \ + --address "$public_key_hash" \ + --out-file "${file%.jsonld}.authored.jsonld" + return + else + echo "Signing with Ed25519 algorithm..." + cardano-signer sign --cip100 \ + --data-file "$file" \ + --secret-key "$input_key" \ + --author-name "$author_name" \ + --out-file "${file%.jsonld}.authored.jsonld" + return + fi +} + +# Use cardano-signer to sign author metadata + +if [ -d "$input_path" ]; then + # If input is a directory: sign all .jsonld files + shopt -s nullglob + jsonld_files=("$input_path"/*.jsonld) + # check if any .jsonld files were found + if [ ${#jsonld_files[@]} -eq 0 ]; then + echo "Error: No .jsonld files found in directory '$input_path'." + exit 1 + fi + # for each .jsonld file in the directory, sign it + for file in "${jsonld_files[@]}"; do + sign_file "$file" "$use_cip8" + done +elif [ -f "$input_path" ]; then + # Input is a single file + sign_file "$input_path" "$use_cip8" +else + echo "Error: '$input_path' is not a valid file or directory." + exit 1 +fi \ No newline at end of file diff --git a/scripts/create-human-readable-from-json.sh b/scripts/create-human-readable-from-json.sh new file mode 100755 index 0000000..67dc834 --- /dev/null +++ b/scripts/create-human-readable-from-json.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# Function to extract fields from the JSON-LD file +extract_jsonld_data() { + local jsonld_file=$1 + + # Extract the data fields using jq + local title=$(jq -r '.body.title // empty' "$jsonld_file") + local abstract=$(jq -r '.body.abstract // empty' "$jsonld_file") + local motivation=$(jq -r '.body.motivation // empty' "$jsonld_file") + local rationale=$(jq -r '.body.rationale // empty' "$jsonld_file") + local authors=$(jq '.authors[] // empty' "$jsonld_file") + + # Extract the references and format them + local references=$(jq -r '.body.references[] | "- [\(.label)](\(.uri))" // empty' "$jsonld_file") + + # Output to a markdown file + local output_file="${jsonld_file}.md" + + # Create markdown content + cat > "$output_file" <" + exit 1 +fi + +# If the argument is a directory, process each JSON-LD file +if [ -d "$1" ]; then + for jsonld_file in "$1"/*.jsonld; do + extract_jsonld_data "$jsonld_file" + done +elif [ -f "$1" ]; then + # If it's a single file, process it + extract_jsonld_data "$1" +else + echo "Invalid input. Please provide a valid file or directory." + exit 1 +fi diff --git a/scripts/hash-json.sh b/scripts/hash-json.sh new file mode 100755 index 0000000..7128bc1 --- /dev/null +++ b/scripts/hash-json.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# Check if the correct number of arguments is provided +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Input file +input_file="$1" + +# Check if the file exists +if [ ! -f "$input_file" ]; then + echo "Error: Anchor file '$input_file' not found!" + exit 1 +fi + +# hash of the input file using b2sum +b2sum_hash=$(b2sum -l 256 "$input_file" | awk '{print $1}') + +# hash of the input file using cardano-cli +cardano_cli_hash=$(cardano-cli hash anchor-data --file-text "$(realpath "$input_file")") + +# Output the result +echo "For anchor file: $input_file" +echo +echo "BLAKE2b-256 hash (using b2sum -l 256):" +echo "$b2sum_hash" +echo +echo "BLAKE2b-256 hash (using cardano-cli hash anchor-data):" +echo "$cardano_cli_hash" \ No newline at end of file diff --git a/scripts/ipfs.sh b/scripts/ipfs.sh new file mode 100755 index 0000000..17acff6 --- /dev/null +++ b/scripts/ipfs.sh @@ -0,0 +1,271 @@ +#!/bin/bash + +###################################################### + +# Can change if you want! + +# used to by pass waiting for gateway checks +JUST_PIN="false" +JUST_CHECK="false" + +# Gateways to check if file is already hosted on IPFS +DEFAULT_GATEWAY_1="https://ipfs.io/ipfs/" +DEFAULT_GATEWAY_2="https://gateway.pinata.cloud/ipfs/" + +# Pinning services to host the file on IPFS +DEFAULT_HOST_ON_LOCAL_NODE="true" +DEFAULT_HOST_ON_NMKR="true" +DEFAULT_HOST_ON_BLOCKFROST="true" +DEFAULT_HOST_ON_PINATA="true" + +# HOST_ON_STORACHA_STORAGE="true" +# https://docs.storacha.network/faq/ + +###################################################### + +# check if user has ipfs cli installed +if ! command -v ipfs >/dev/null 2>&1; then + echo "Error: ipfs cli is not installed or not in your PATH." >&2 + exit 1 +fi + +# Usage message +usage() { + echo "Usage: $0 [--just-pin] [--just-check] [--no-local] [--no-pinata] [--no-blockfrost] [--no-nmkr]" + echo "Check if a file is on IPFS, and also pin it locally and via Blockfrost and NMKR." + echo " " + echo "Options:" + echo " Path to your file." + echo " --just-pin Don't look for the file, just pin it (default: $JUST_PIN)" + echo " --just-check Only look for the file don't try to pin it (default: $JUST_CHECK)" + echo " --no-local Don't try to pin file on local ipfs node? (default: $DEFAULT_HOST_ON_LOCAL_NODE)" + echo " --no-pinata Don't try to pin file on pinata service? (default: $DEFAULT_HOST_ON_PINATA)" + echo " --no-blockfrost Don't try to pin file on blockfrost service? (default: $DEFAULT_HOST_ON_BLOCKFROST)" + echo " --no-nmkr Don't try to pin file on NMKR service? (default: $DEFAULT_HOST_ON_NMKR_STORAGE)" + exit 1 +} + +# Initialize variables with defaults +input_path="" +just_pin="$JUST_PIN" +just_check="$JUST_CHECK" +local_host="$DEFAULT_HOST_ON_LOCAL_NODE" +pinata_host="$DEFAULT_HOST_ON_PINATA" +blockfrost_host="$DEFAULT_HOST_ON_BLOCKFROST" +nmkr_host="$DEFAULT_HOST_ON_NMKR" + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + --just-pin) + just_pin="true" + shift + ;; + --just-check) + just_check="true" + shift + ;; + --no-local) + local_host="false" + shift + ;; + --no-pinata) + pinata_host="false" + shift + ;; + --no-blockfrost) + blockfrost_host="false" + shift + ;; + --no-nmkr) + nmkr_host="false" + shift + ;; + -h|--help) + usage + ;; + *) + if [ -z "$input_path" ]; then + input_path="$1" + fi + shift + ;; + esac +done + +# Generate CID from the given file +echo "Generating CID for the file..." + +# use ipfs add to generate a CID +# use CIDv1 +ipfs_cid=$(ipfs add -Q --cid-version 1 "$input_path") +echo "CID: $ipfs_cid" + +# check two gateways if file can be accessed +echo " " +echo "Checking if file is already hosted on IPFS..." + +check_file_on_gateway() { + local gateway="$1" + local cid="$2" + local timeout="$3" + echo "Checking ${gateway}..." + if curl --silent --fail "${gateway}${cid}" >/dev/null; then + echo "File is accessible on IPFS via ${gateway}${cid}" + return 0 + else + echo "File not found at: ${gateway}${cid}" + return 1 + fi +} + +# If file can be found via gateways then exit +if [ "$just_pin" = "false" ]; then + echo "Checking if file is already hosted on IPFS..." + if check_file_on_gateway "$DEFAULT_GATEWAY_1" "$ipfs_cid" "TIMEOUT"; then + echo "File is already hosted on IPFS. No need to pin anywhere else." + exit 0 + fi + if check_file_on_gateway "$DEFAULT_GATEWAY_2" "$ipfs_cid" "TIMEOUT"; then + echo "File is already hosted on IPFS. No need to pin anywhere else." + exit 0 + fi +else + echo "Skipping check of file on ipfs..." +fi + +# If just checking then exit +if [ "$just_check" = "true" ]; then + echo "File is not hosted on IPFS, but you requested to just check. Exiting." + exit 0 +fi + +# If file is not accessible then pin it!! +echo " " +echo "File is not hosted on IPFS, so pinning it..." + +# Pin on local node +if [ "$local_host" = "true" ]; then + echo "Pinning file on local IPFS node..." + if ipfs pin add "$ipfs_cid"; then + echo "File pinned successfully on local IPFS node." + else + echo "Failed to pin file on local IPFS node." >&2 + exit 1 + fi +else + echo "Skipping pinning on local IPFS node." +fi + +# Pin on local node's remote services +# todo +local_node_pinning_services=$(ipfs pin remote service ls) + +# Pin on Pinata +echo " " +echo "Pinning file to Pinata..." + +if [ "$pinata_host" = "true" ]; then + # Check for secret environment variables + # todo, this in a nicer way + echo "Reading Pinata API key from environment variable..." + if [ -z "$PINATA_API_KEY" ]; then + echo "Error: PINATA_API_KEY environment variable is not set." >&2 + exit 1 + fi + + echo "Uploading file to Pinata service..." + response=$(curl -s -X POST "https://uploads.pinata.cloud/v3/files" \ + -H "Authorization: Bearer ${PINATA_API_KEY}" \ + -F "file=@$input_path" \ + -F "network=public" \ + ) + # Check response for errors + if echo "$response" | grep -q '"errors":'; then + echo "Error in Pinata response:" >&2 + echo "$response" | jq . >&2 + exit 1 + fi + + echo "Pinata upload successful!" +else + echo "Skipping pinning on Pinata." +fi + +# Pin on Blockfrost +echo " " +echo "Pinning file to Blockfrost..." + +if [ "$blockfrost_host" = "true" ]; then + # Check for secret environment variables + # todo, this in a nicer way + echo "Reading Blockfrost API key from environment variable..." + if [ -z "$BLOCKFROST_API_KEY" ]; then + echo "Error: BLOCKFROST_API_KEY environment variable is not set." >&2 + exit 1 + fi + + echo "Uploading file to Blockfrost service..." + response=$(curl -s -X POST "https://ipfs.blockfrost.io/api/v0/ipfs/add" \ + -H "project_id: $BLOCKFROST_API_KEY" \ + -F "file=@$input_path" \ + ) + # Check response for errors + if echo "$response" | grep -q '"errors":'; then + echo "Error in Blockfrost response:" >&2 + echo "$response" | jq . >&2 + exit 1 + fi + + echo "Blockfrost upload successful!" +else + echo "Skipping pinning on Blockfrost." +fi + +# Pin on NMKR +echo " " +echo "Pinning file to NMKR..." + +if [ "$nmkr_host" = "true" ]; then + # Check for secret environment variables + # todo, this in a nicer way + echo "Reading NMKR API key from environment variable..." + if [ -z "$NMKR_API_KEY" ]; then + echo "Error: NMKR_API_KEY environment variable is not set." >&2 + exit 1 + fi + echo "Reading NMKR user id from environment variable..." + if [ -z "$NMKR_USER_ID" ]; then + echo "Error: NMKR_USER_ID environment variable is not set." >&2 + exit 1 + fi + + # base64 encode the file because NMKR API requires it + echo "Encoding file to base64..." + base64_content=$(base64 -i "$input_path") + + echo "Uploading file to NMKR service..." + response=$(curl -s -X POST "https://studio-api.nmkr.io/v2/UploadToIpfs/${NMKR_USER_ID}" \ + -H 'accept: text/plain' \ + -H 'Content-Type: application/json' \ + -H "Authorization: Bearer ${NMKR_API_KEY}" \ + -d @- <&2 + echo "$response" | jq . >&2 + exit 1 + fi + + echo "NMKR upload successful!" +else + echo "Skipping pinning on NMKR." +fi + diff --git a/scripts/validate-json.sh b/scripts/validate-json.sh new file mode 100755 index 0000000..5a2f572 --- /dev/null +++ b/scripts/validate-json.sh @@ -0,0 +1,91 @@ +#!/bin/bash + +# Hardcoded JSON Schema URL +SCHEMA_URL="https://raw.githubusercontent.com/cardano-foundation/CIPs/refs/heads/master/CIP-0108/cip-0108.common.schema.json" + +# Check if the correct number of arguments is provided +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +INPUT_FILE="$1" +TMP_JSON_FILE="" + +# If the file ends with .jsonld, create a temporary .json copy (overwrite if exists) +if [[ "$INPUT_FILE" == *.jsonld ]]; then + if [ ! -f "$INPUT_FILE" ]; then + echo "Error: File '$INPUT_FILE' does not exist." + exit 1 + fi + TMP_JSON_FILE="/tmp/metadata.json" + cp -f "$INPUT_FILE" "$TMP_JSON_FILE" + JSON_FILE="$TMP_JSON_FILE" +else + JSON_FILE="$INPUT_FILE" +fi + +# Check if the file exists +if [ ! -f "$JSON_FILE" ]; then + echo "Error: File '$JSON_FILE' does not exist." + [ -n "$TMP_JSON_FILE" ] && rm -f "$TMP_JSON_FILE" + exit 1 +fi + +# Check if the file is valid JSON +if ! jq empty "$JSON_FILE" >/dev/null 2>&1; then + echo "Error: '$JSON_FILE' is not valid JSON." + [ -n "$TMP_JSON_FILE" ] && rm -f "$TMP_JSON_FILE" + exit 1 +fi + +# Pull the schema from the URL (suppress curl output) +TMP_SCHEMA="/tmp/cip-108-schema.json" +curl -sSfSL "$SCHEMA_URL" -o "$TMP_SCHEMA" + +# Check if the schema was retrieved and is valid JSON +if [ ! -s "$TMP_SCHEMA" ] || ! jq empty "$TMP_SCHEMA" >/dev/null 2>&1; then + echo "Error: Failed to retrieve or parse schema from $SCHEMA_URL" + rm -f "$TMP_SCHEMA" + [ -n "$TMP_JSON_FILE" ] && rm -f "$TMP_JSON_FILE" + exit 1 +fi + +# Basic spell check on key data fields (requires 'aspell' installed) +if command -v aspell >/dev/null 2>&1; then + echo " " + echo "Spell check warnings:" + # List of fields to check + for field in title abstract motivation rationale; do + # Extract field text + text=$(jq -r ".body.$field // empty" "$JSON_FILE") + if [ -n "$text" ]; then + # Use aspell to check spelling, output only misspelled words + echo "$text" | aspell list | sort -u | while read -r word; do + if [ -n "$word" ]; then + echo " Possible misspelling in '$field': $word" + fi + done + fi + done +else + echo "Warning: aspell not found, skipping spell check." +fi + +echo " " +echo "Validating JSON file against schema '$SCHEMA_URL'..." +echo " " + +# Validate JSON against the schema +ajv validate -s "$TMP_SCHEMA" -d "$JSON_FILE" --all-errors --strict=true +AJV_EXIT_CODE=$? + +# Clean up temporary files +rm -f "$TMP_SCHEMA" +rm -f "$TMP_JSON_FILE" + +echo " " +echo "Validation complete." +echo " " + +exit $AJV_EXIT_CODE \ No newline at end of file diff --git a/scripts/verify-author-witness.sh b/scripts/verify-author-witness.sh new file mode 100755 index 0000000..fc5f31b --- /dev/null +++ b/scripts/verify-author-witness.sh @@ -0,0 +1,89 @@ +#!/bin/bash + +###################################################### +# using permalink to reduce likelihood of breakage, or ability for it to change +INTERSECT_AUTHOR_PATH="https://raw.githubusercontent.com/IntersectMBO/governance-actions/b1c5603fb306623e0261c234312eb7e011ac3d38/intersect-author.json" +###################################################### + +# Check if cardano-signer is installed +if ! command -v cardano-signer >/dev/null 2>&1; then + echo "Error: cardano-signer is not installed or not in your PATH." >&2 + exit 1 +fi + +# Usage message +usage() { + echo "Usage: $0 " + exit 1 +} + +# Check correct number of arguments +if [ "$#" -lt 1 ]; then + usage +fi + +input_path="$1" + +# Check if the key input file exists +if [ ! -f "$input_path" ]; then + echo "Error: JSON file '$input_path' not found!" + exit 1 +fi + +# Get Intersect author public key +# echo " " +# echo "Fetching Intersect author public key from $INTERSECT_AUTHOR_PATH" +intersect_author_key=$(curl -s "$INTERSECT_AUTHOR_PATH" | jq -r '.publicKey') +# echo " " + +# Use cardano-signer to verify author witnesses +# https://github.com/gitmachtl/cardano-signer?tab=readme-ov-file#verify-governance-metadata-and-the-authors-signatures +verify_author_witness() { + local file="$1" + cardano-signer verify --cip100 \ + --data-file "$file" \ + --json-extended | jq '{workMode, result, errorMsg, authors, canonizedHash, fileHash}' +} + +# Give the user a warning if the author isn't Intersect +check_if_intersect_author() { + local file="$1" + author_count=$(jq '.authors | length' "$file") + + # Iterate over all author pubkeys present + for i in $(seq 0 $(($author_count - 1))); do + author_key=$(jq -r ".authors[$i].witness.publicKey" "$file") + + if [ "$author_key" == "$intersect_author_key" ]; then + echo "Author public key matches Intersect's known public key." + else + echo "Warning: Author public key does NOT match Intersect's known public key." + echo "Author public key: $author_key" + echo "Intersect's known public key: $intersect_author_key" + fi + echo " " + done +} + +if [ -d "$input_path" ]; then + # If input is a directory: verify all .jsonld files + shopt -s nullglob + jsonld_files=("$input_path"/*.jsonld) + # check if any .jsonld files were found + if [ ${#jsonld_files[@]} -eq 0 ]; then + echo "Error: No .jsonld files found in directory '$input_path'." + exit 1 + fi + # for each .jsonld file in the directory, go over it + for file in "${jsonld_files[@]}"; do + verify_author_witness "$file" + check_if_intersect_author "$file" + done +elif [ -f "$input_path" ]; then + # Input is a single file + verify_author_witness "$input_path" + check_if_intersect_author "$input_path" +else + echo "Error: '$input_path' is not a valid file or directory." + exit 1 +fi \ No newline at end of file From ef76abef7bc2a5a38af5970fca9c82178901fdac Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Fri, 20 Jun 2025 21:34:00 +0100 Subject: [PATCH 2/2] chore: add PR temlate --- .github/pull_request_template.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..626788b --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,3 @@ +## List of changes + +- Add / Fix / Change / Remove