#!/bin/bash # License Verification ZKP - Circuit Setup Script # This script compiles Circom circuits and generates proving/verification keys set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuration CIRCUIT_NAME="license_verification" CIRCUIT_DIR="${CIRCUIT_DIR:-/app/circuits}" KEYS_DIR="${KEYS_DIR:-/app/keys}" PTAU_DIR="${PTAU_DIR:-/app/ptau}" BUILD_DIR="${CIRCUIT_DIR}/build" CONSTRAINT_SIZE=14 # 2^14 constraints (~16k) # Function to print colored output print_info() { echo -e "${BLUE}[INFO]${NC} $1" } print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } # Function to check if a command exists command_exists() { command -v "$1" >/dev/null 2>&1 } # Function to create directories setup_directories() { print_info "Setting up directories..." mkdir -p "$BUILD_DIR" mkdir -p "$KEYS_DIR" mkdir -p "$PTAU_DIR" print_success "Directories created" } # Function to check prerequisites check_prerequisites() { print_info "Checking prerequisites..." if ! command_exists circom; then print_error "Circom is not installed" print_info "Installing circom..." # Try to install circom if command_exists cargo; then git clone https://github.com/iden3/circom.git /tmp/circom cd /tmp/circom cargo build --release sudo mv target/release/circom /usr/local/bin/ cd - rm -rf /tmp/circom else print_error "Rust/Cargo not found. Please install circom manually" exit 1 fi fi if ! command_exists snarkjs; then print_warning "snarkjs not found globally, will use npx" SNARKJS="npx snarkjs" else SNARKJS="snarkjs" fi if ! command_exists node; then print_error "Node.js is not installed" exit 1 fi print_success "Prerequisites check complete" } # Function to create the circuit file if it doesn't exist create_circuit() { local circuit_file="${CIRCUIT_DIR}/${CIRCUIT_NAME}.circom" if [ ! -f "$circuit_file" ]; then print_info "Creating circuit file..." cat > "$circuit_file" << 'CIRCOM' pragma circom 2.1.0; include "../node_modules/circomlib/circuits/poseidon.circom"; include "../node_modules/circomlib/circuits/comparators.circom"; include "../node_modules/circomlib/circuits/mux1.circom"; template LicenseVerification() { // Private inputs - License data signal input licenseNumber; signal input practitionerName; signal input issuedDate; signal input expiryDate; signal input jurisdiction; // Private inputs - Merkle proof (depth 20) signal input pathElements[20]; signal input pathIndices[20]; // Public inputs signal input merkleRoot; signal input currentTimestamp; signal input minExpiryTimestamp; // Hash the license data using Poseidon component hasher = Poseidon(5); hasher.inputs[0] <== licenseNumber; hasher.inputs[1] <== practitionerName; hasher.inputs[2] <== issuedDate; hasher.inputs[3] <== expiryDate; hasher.inputs[4] <== jurisdiction; signal leafHash <== hasher.out; // Verify Merkle proof component merkleProof[20]; component mux[20]; signal computedHash[21]; computedHash[0] <== leafHash; for (var i = 0; i < 20; i++) { merkleProof[i] = Poseidon(2); mux[i] = Mux1(); mux[i].c[0] <== computedHash[i]; mux[i].c[1] <== pathElements[i]; mux[i].s <== pathIndices[i]; merkleProof[i].inputs[0] <== mux[i].out; merkleProof[i].inputs[1] <== pathElements[i] - mux[i].out + computedHash[i]; computedHash[i + 1] <== merkleProof[i].out; } // Check merkle root matches signal rootMatch <== computedHash[20] - merkleRoot; rootMatch === 0; // Check license is not expired component notExpired = GreaterEqThan(64); notExpired.in[0] <== expiryDate; notExpired.in[1] <== minExpiryTimestamp; // Check license was issued before current time component wasIssued = LessEqThan(64); wasIssued.in[0] <== issuedDate; wasIssued.in[1] <== currentTimestamp; // Output signals signal output isValid; isValid <== notExpired.out * wasIssued.out; } component main = LicenseVerification(); CIRCOM print_success "Circuit file created" else print_info "Circuit file already exists" fi } # Function to compile the circuit compile_circuit() { print_info "Compiling circuit '${CIRCUIT_NAME}'..." cd "$CIRCUIT_DIR" # Check if circomlib is installed if [ ! -d "node_modules/circomlib" ]; then print_info "Installing circomlib..." npm install circomlib@2.0.5 fi # Compile with circom print_info "Running circom compiler..." circom "${CIRCUIT_NAME}.circom" \ --r1cs \ --wasm \ --sym \ --c \ -o "$BUILD_DIR" \ -l node_modules if [ -f "${BUILD_DIR}/${CIRCUIT_NAME}.r1cs" ]; then print_success "Circuit compiled successfully" # Print circuit info print_info "Circuit statistics:" $SNARKJS r1cs info "${BUILD_DIR}/${CIRCUIT_NAME}.r1cs" else print_error "Circuit compilation failed" exit 1 fi } # Function to download or generate Powers of Tau setup_powers_of_tau() { local ptau_file="${PTAU_DIR}/pot${CONSTRAINT_SIZE}_final.ptau" if [ -f "$ptau_file" ]; then print_info "Powers of Tau file already exists" return 0 fi print_info "Setting up Powers of Tau..." # Option 1: Download from Hermez ceremony (recommended for production) if [ "$USE_HERMEZ_PTAU" = "true" ]; then print_info "Downloading Powers of Tau from Hermez ceremony..." curl -L "https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_${CONSTRAINT_SIZE}.ptau" \ -o "$ptau_file" print_success "Downloaded trusted Powers of Tau" else # Option 2: Generate locally (for testing only!) print_warning "Generating Powers of Tau locally (NOT SECURE FOR PRODUCTION!)" # Start new ceremony $SNARKJS powersoftau new bn128 ${CONSTRAINT_SIZE} "${PTAU_DIR}/pot${CONSTRAINT_SIZE}_0000.ptau" # Contribute to ceremony $SNARKJS powersoftau contribute \ "${PTAU_DIR}/pot${CONSTRAINT_SIZE}_0000.ptau" \ "${PTAU_DIR}/pot${CONSTRAINT_SIZE}_0001.ptau" \ --name="First contribution" \ -v -e="$(head -n 1024 /dev/urandom | sha256sum | head -c 64)" # Add beacon $SNARKJS powersoftau beacon \ "${PTAU_DIR}/pot${CONSTRAINT_SIZE}_0001.ptau" \ "${PTAU_DIR}/pot${CONSTRAINT_SIZE}_beacon.ptau" \ "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" 10 \ -n="Final Beacon" # Prepare phase 2 $SNARKJS powersoftau prepare phase2 \ "${PTAU_DIR}/pot${CONSTRAINT_SIZE}_beacon.ptau" \ "$ptau_file" \ -v # Cleanup intermediate files rm -f "${PTAU_DIR}/pot${CONSTRAINT_SIZE}_0000.ptau" rm -f "${PTAU_DIR}/pot${CONSTRAINT_SIZE}_0001.ptau" rm -f "${PTAU_DIR}/pot${CONSTRAINT_SIZE}_beacon.ptau" print_success "Generated Powers of Tau (TEST ONLY)" fi # Verify the ptau file print_info "Verifying Powers of Tau..." $SNARKJS powersoftau verify "$ptau_file" } # Function to generate proving and verification keys generate_keys() { print_info "Generating proving and verification keys..." local r1cs_file="${BUILD_DIR}/${CIRCUIT_NAME}.r1cs" local ptau_file="${PTAU_DIR}/pot${CONSTRAINT_SIZE}_final.ptau" local zkey_0="${KEYS_DIR}/${CIRCUIT_NAME}_0000.zkey" local zkey_1="${KEYS_DIR}/${CIRCUIT_NAME}_0001.zkey" local zkey_final="${KEYS_DIR}/${CIRCUIT_NAME}.zkey" local vkey_file="${KEYS_DIR}/${CIRCUIT_NAME}_verification_key.json" # Check if keys already exist if [ -f "$zkey_final" ] && [ -f "$vkey_file" ]; then print_warning "Keys already exist. Use --force to regenerate" if [ "$1" != "--force" ]; then return 0 fi fi # Setup groth16 print_info "Running Groth16 setup..." $SNARKJS groth16 setup \ "$r1cs_file" \ "$ptau_file" \ "$zkey_0" # Contribute to the ceremony print_info "Contributing to phase 2 ceremony..." $SNARKJS zkey contribute \ "$zkey_0" \ "$zkey_1" \ --name="License Verification Contribution" \ -v -e="$(date +%s | sha256sum | head -c 64)" # Add beacon print_info "Adding beacon to finalize..." $SNARKJS zkey beacon \ "$zkey_1" \ "$zkey_final" \ "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" 10 \ -n="License Verification Final Beacon" # Export verification key print_info "Exporting verification key..." $SNARKJS zkey export verificationkey \ "$zkey_final" \ "$vkey_file" # Verify the final zkey print_info "Verifying final zkey..." $SNARKJS zkey verify \ "$r1cs_file" \ "$ptau_file" \ "$zkey_final" # Cleanup intermediate files rm -f "$zkey_0" "$zkey_1" print_success "Proving and verification keys generated" # Print key sizes print_info "Key file sizes:" ls -lh "$zkey_final" "$vkey_file" | awk '{print " " $9 ": " $5}' } # Function to generate a test proof test_proof() { print_info "Generating test proof..." local wasm_file="${BUILD_DIR}/${CIRCUIT_NAME}_js/${CIRCUIT_NAME}.wasm" local zkey_file="${KEYS_DIR}/${CIRCUIT_NAME}.zkey" local vkey_file="${KEYS_DIR}/${CIRCUIT_NAME}_verification_key.json" # Create test input cat > /tmp/test_input.json << JSON { "licenseNumber": "12345678901234567890", "practitionerName": "98765432109876543210", "issuedDate": "1609459200", "expiryDate": "1735689600", "jurisdiction": "11111111111111111111", "pathElements": [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20" ], "pathIndices": [ "0", "1", "0", "1", "0", "1", "0", "1", "0", "1", "0", "1", "0", "1", "0", "1", "0", "1", "0", "1" ], "merkleRoot": "123456789", "currentTimestamp": "1609459200", "minExpiryTimestamp": "1609545600" } JSON # Generate witness print_info "Calculating witness..." node "${BUILD_DIR}/${CIRCUIT_NAME}_js/generate_witness.js" \ "$wasm_file" \ /tmp/test_input.json \ /tmp/witness.wtns # Generate proof print_info "Generating proof..." local start_time=$(date +%s%N) $SNARKJS groth16 prove \ "$zkey_file" \ /tmp/witness.wtns \ /tmp/proof.json \ /tmp/public.json local end_time=$(date +%s%N) local duration=$((($end_time - $start_time) / 1000000)) print_success "Proof generated in ${duration}ms" # Verify proof print_info "Verifying proof..." $SNARKJS groth16 verify \ "$vkey_file" \ /tmp/public.json \ /tmp/proof.json if [ $? -eq 0 ]; then print_success "Proof verified successfully!" else print_error "Proof verification failed" exit 1 fi # Cleanup rm -f /tmp/test_input.json /tmp/witness.wtns /tmp/proof.json /tmp/public.json } # Function to display summary display_summary() { echo "" echo "=========================================" echo -e "${GREEN}Circuit Setup Complete!${NC}" echo "=========================================" echo "" echo "Generated files:" echo " Circuit: ${BUILD_DIR}/${CIRCUIT_NAME}.r1cs" echo " WASM: ${BUILD_DIR}/${CIRCUIT_NAME}_js/${CIRCUIT_NAME}.wasm" echo " ZKey: ${KEYS_DIR}/${CIRCUIT_NAME}.zkey" echo " VKey: ${KEYS_DIR}/${CIRCUIT_NAME}_verification_key.json" echo "" # Get file sizes if [ -f "${KEYS_DIR}/${CIRCUIT_NAME}.zkey" ]; then local zkey_size=$(ls -lh "${KEYS_DIR}/${CIRCUIT_NAME}.zkey" | awk '{print $5}') local vkey_size=$(ls -lh "${KEYS_DIR}/${CIRCUIT_NAME}_verification_key.json" | awk '{print $5}') echo "File sizes:" echo " ZKey: $zkey_size" echo " VKey: $vkey_size" echo "" fi echo "Next steps:" echo " 1. Start the ZKP service: npm start" echo " 2. Run benchmarks: npm run benchmark" echo " 3. Access test UI: http://localhost:3000" echo "" } # Main execution main() { echo "=========================================" echo "License Verification Circuit Setup" echo "=========================================" echo "" # Parse arguments FORCE_REBUILD=false SKIP_TEST=false for arg in "$@"; do case $arg in --force) FORCE_REBUILD=true print_warning "Force rebuild enabled" ;; --skip-test) SKIP_TEST=true ;; --use-hermez) USE_HERMEZ_PTAU=true ;; --help) echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" echo " --force Force rebuild of all components" echo " --skip-test Skip test proof generation" echo " --use-hermez Use Hermez trusted setup (recommended)" echo " --help Show this help message" exit 0 ;; esac done # Run setup steps check_prerequisites setup_directories create_circuit compile_circuit setup_powers_of_tau generate_keys $FORCE_REBUILD # Test the setup if [ "$SKIP_TEST" != "true" ]; then test_proof fi display_summary } # Run main function main "$@"