diff --git a/.docker/frost-ci.Dockerfile b/.docker/frost-ci.Dockerfile new file mode 100644 index 0000000..0084401 --- /dev/null +++ b/.docker/frost-ci.Dockerfile @@ -0,0 +1,45 @@ +FROM ubuntu:22.04 + +# Avoid interactive prompts during package installation +ENV DEBIAN_FRONTEND=noninteractive + +# Install system dependencies +# curl - Download tools and Rust installer +# build-essential - GCC compiler and build tools for Rust +# pkg-config - Helps find system libraries during compilation +# libssl-dev - SSL/TLS support required by Rust networking crates +# ca-certificates - Trusted certificate authorities for HTTPS +# git - Version control needed by cargo for git dependencies +RUN apt-get update && apt-get install -y \ + curl \ + build-essential \ + pkg-config \ + libssl-dev \ + ca-certificates \ + git \ + && rm -rf /var/lib/apt/lists/* + +# Install Rust programming language and toolchain +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable +ENV PATH="/root/.cargo/bin:${PATH}" + +# Install mkcert for generating local TLS certificates (required by frostd server) +RUN curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64" \ + && chmod +x mkcert-v*-linux-amd64 \ + && mv mkcert-v*-linux-amd64 /usr/local/bin/mkcert + +# Setup mkcert CA (this needs to be done at runtime for the examples) +RUN mkcert -install + +# Pre-install frostd server +RUN cargo install --git https://github.com/ZcashFoundation/frost-zcash-demo.git --locked frostd + +# Create workspace directory +WORKDIR /workspace + +# Set environment variables +ENV CARGO_TERM_COLOR=always +ENV RUST_LOG=warn + +# Default command +CMD ["/bin/bash"] diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a115f12 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,33 @@ +# Git files + +.git +.gitignore + +# Documentation + +README.md +\*.md + +# CI files (except the docker directory) + +.github +!.docker + +# Build outputs + +target/ +frost-client/target/ + +# Generated examples + +frost-client/examples/\*/generated/ + +# IDE files + +.vscode/ +.idea/ + +# Temporary files + +_.tmp +_.log diff --git a/.github/workflows/build-ci-image.yml b/.github/workflows/build-ci-image.yml new file mode 100644 index 0000000..f973046 --- /dev/null +++ b/.github/workflows/build-ci-image.yml @@ -0,0 +1,67 @@ +# This workflow builds a Docker image with pre-installed dependencies for FROST examples. +# +# This workflow runs: +# - Weekly (to get latest security updates) +# - You manually trigger it on GH actions (with latest label from main and pr specific label if made from branch) +# - You push changes to the image to main + +name: Build CI Image + +on: + workflow_dispatch: # Allow for manual dispatch + schedule: + - cron: "0 2 * * 0" # Weekly rebuild on Sundays at 2 AM to get latest deps + push: + branches: ["main"] + paths: [".docker/frost-ci.Dockerfile"] # Also trigger when this workflow changes + +env: + REGISTRY: ghcr.io + IMAGE_NAME: raspberry-devs/mina-multi-sig/frost-ci + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + # Get the source code including the Dockerfile + - name: Checkout code + uses: actions/checkout@v4 + + # Authenticate with GitHub Container Registry using the built-in token + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Generate appropriate tags and labels for the Docker image + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=raw,value=latest,enable={{is_default_branch}} + + # Set up Docker Buildx for advanced building features (caching, multi-platform) + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + # Build the Docker image with FROST dependencies and push to registry + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: .docker/frost-ci.Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/client-commands.yml b/.github/workflows/client-commands.yml new file mode 100644 index 0000000..9a428fb --- /dev/null +++ b/.github/workflows/client-commands.yml @@ -0,0 +1,82 @@ +name: Client Commands + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + paths: + - "frost-client/**" + +env: + CARGO_TERM_COLOR: always + +jobs: + test-frost-examples: + runs-on: ubuntu-latest + container: + # @TODO: Once PR 76 is merged change the image to frost-ci:latest + # (it will only be created after merge to main) + image: ghcr.io/raspberry-devs/mina-multi-sig/frost-ci:pr-76 + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + # Add Cargo caching for speed boost + - name: Cache Cargo build artifacts + uses: actions/cache@v3 + with: + path: | + /root/.cargo/registry/index/ + /root/.cargo/registry/cache/ + /root/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-container-${{ hashFiles('frost-client/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-container- + + # Pre-build once instead of building for each example + - name: Build frost-client (release mode) + run: | + cd frost-client + cargo build --release --bin frost-client + + # Run all examples that test if the basic usecases of the client don't break + - name: Run Trusted Dealer Example + run: | + cd frost-client/examples/trusted_dealer_example + timeout 300 ./trusted_dealer_example.sh + + - name: Run DKG Example + run: | + cd frost-client/examples/dkg_example + timeout 300 ./dkg_example.sh + + - name: Run Signing Example + run: | + cd frost-client/examples/signing_example + timeout 300 ./signing_example.sh + + - name: Verify generated artifacts + run: | + test -f frost-client/examples/trusted_dealer_example/generated/alice.toml + test -f frost-client/examples/trusted_dealer_example/generated/bob.toml + test -f frost-client/examples/trusted_dealer_example/generated/eve.toml + test -f frost-client/examples/dkg_example/generated/alice.toml + test -f frost-client/examples/dkg_example/generated/bob.toml + test -f frost-client/examples/dkg_example/generated/eve.toml + test -f frost-client/examples/signing_example/generated/signature.json + test -f frost-client/examples/signing_example/generated/message.json + + - name: Upload artifacts on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: frost-example-outputs + path: | + frost-client/examples/*/generated/ + retention-days: 7 diff --git a/frost-client/examples/README.md b/frost-client/examples/README.md new file mode 100644 index 0000000..72a2584 --- /dev/null +++ b/frost-client/examples/README.md @@ -0,0 +1,46 @@ +# FROST Client Examples + +Three examples demonstrating FROST threshold signatures for the Mina protocol. + +## Examples + +### 1. Trusted Dealer (`trusted_dealer_example/`) + +Single party generates and distributes key shares to 3 participants (threshold t=2). + +```bash +cd trusted_dealer_example/ +./trusted_dealer_example.sh +``` + +### 2. Distributed Key Generation (`dkg_example/`) + +Participants collaboratively generate keys without any single party knowing the complete private key. + +```bash +cd dkg_example/ +./dkg_example.sh +``` + +### 3. Signing (`signing_example/`) + +Create threshold signatures using keys from previous examples. + +```bash +# Then run signing example +cd ../signing_example/ +./signing_example.sh +``` + +## Development + +Remember to add executable permisions for each example before running: + +```bash +chmod +x trusted_dealer_example/trusted_dealer_example.sh +chmod +x dkg_example/dkg_example.sh +chmod +x signing_example/signing_example.sh +``` + +Examples use `cargo run` to run the client (always uses your latest code changes). + diff --git a/frost-client/examples/dkg_example/2_out_of_2.sh b/frost-client/examples/dkg_example/2_out_of_2.sh deleted file mode 100644 index e69de29..0000000 diff --git a/frost-client/examples/dkg_example/dkg_example.sh b/frost-client/examples/dkg_example/dkg_example.sh index e339a63..928ac5f 100755 --- a/frost-client/examples/dkg_example/dkg_example.sh +++ b/frost-client/examples/dkg_example/dkg_example.sh @@ -1,21 +1,28 @@ #!/bin/bash +# Strict error handling - exit on any error, undefined variable, or pipe failure +set -euo pipefail + # Get the directory where the script is located SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" GENERATED_DIR="$SCRIPT_DIR/generated" +HELPERS_DIR="$SCRIPT_DIR/../helpers" + +cd "$SCRIPT_DIR" # Default server URL SERVER_URL="localhost:2744" -SERVER_PID="" + +# Source helpers +source "$HELPERS_DIR/init_frostd.sh" +source "$HELPERS_DIR/file_generation.sh" +# Remove release binary if you want the script to use cargo run +source "$HELPERS_DIR/use_frost_client.sh" # Function to cleanup on exit cleanup() { echo "Cleaning up..." - if [ ! -z "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then - echo "Stopping frostd server (PID: $SERVER_PID)..." - kill "$SERVER_PID" - wait "$SERVER_PID" 2>/dev/null - fi + stop_frostd "$FROSTD_SERVER_PID" exit } @@ -23,139 +30,53 @@ cleanup() { trap cleanup EXIT INT TERM echo "=========================================" -echo "Starting FROST DKG Example" +echo "FROST DKG Example" echo "=========================================" -# Clean up generated directory if it exists -if [ -d "$GENERATED_DIR" ]; then - echo "Cleaning up existing generated directory..." - rm -rf "$GENERATED_DIR" -fi +# Setup +setup_generated_dir "$GENERATED_DIR" -# Create directory for generated files -mkdir -p "$GENERATED_DIR" - -# Setting up the tls certificates -cd $GENERATED_DIR -mkcert localhost 127.0.0.1 ::1 2>/dev/null || { - echo "ERROR: mkcert failed. Please install mkcert first:" - echo " # On Ubuntu/Debian:" - echo " sudo apt install mkcert" - echo " # On macOS:" - echo " brew install mkcert" - echo " Also ensure you have run 'mkcert -install' to set up the local CA." +# Start server +init_frostd "$GENERATED_DIR" "$SERVER_URL" || { + echo "ERROR: Failed to initialize server" exit 1 } -cd .. - -pwd - -echo "" -echo "=========================================" -echo "Starting frostd server" -echo "=========================================" - -# Check if frostd is installed -if ! command -v frostd &> /dev/null; then - echo "ERROR: frostd is not installed or not in PATH!" - echo "" - echo "Please install frostd first. You can build it from the FROST server repository:" - echo " cargo install --git https://github.com/ZcashFoundation/frost-zcash-demo.git --locked frostd" - exit 1 -fi - -# Start frostd server in the background -echo "Starting frostd server on $SERVER_URL..." -echo "Using TLS cert: $GENERATED_DIR/localhost+2.pem" -echo "Using TLS key: $GENERATED_DIR/localhost+2-key.pem" -frostd --tls-cert "$GENERATED_DIR/localhost+2.pem" --tls-key "$GENERATED_DIR/localhost+2-key.pem" & -SERVER_PID=$! -echo "Server PID: $SERVER_PID" - -# Wait a moment for the server to start -sleep 2 -echo "frostd server started with PID: $SERVER_PID" - -echo "" -echo "=========================================" -echo "Initializing participant configurations" -echo "=========================================" - -# Initialize configs for three users -echo "Initializing configs for users..." -cargo run --bin frost-client -- init -c "$GENERATED_DIR/alice.toml" -cargo run --bin frost-client -- init -c "$GENERATED_DIR/bob.toml" -cargo run --bin frost-client -- init -c "$GENERATED_DIR/eve.toml" - -echo "" -echo "=========================================" -echo "Generating contact strings" -echo "=========================================" - -# Generate contact strings for each participant -echo "Generating contact strings..." -echo "Generating Alice's contact..." -ALICE_CONTACT=$(cargo run --bin frost-client -- export --name 'Alice' -c "$GENERATED_DIR/alice.toml" 2>&1 | grep "^minafrost" || true) -echo "Alice's contact: '$ALICE_CONTACT'" +echo "Initializing participants..." +use_frost_client init -c "$GENERATED_DIR/alice.toml" +use_frost_client init -c "$GENERATED_DIR/bob.toml" +use_frost_client init -c "$GENERATED_DIR/eve.toml" -echo "Generating Bob's contact..." -BOB_CONTACT=$(cargo run --bin frost-client -- export --name 'Bob' -c "$GENERATED_DIR/bob.toml" 2>&1 | grep "^minafrost" || true) -echo "Bob's contact: '$BOB_CONTACT'" +echo "Generating contacts..." +ALICE_CONTACT=$(use_frost_client export --name 'Alice' -c "$GENERATED_DIR/alice.toml" 2>&1 | grep "^minafrost" || true) +BOB_CONTACT=$(use_frost_client export --name 'Bob' -c "$GENERATED_DIR/bob.toml" 2>&1 | grep "^minafrost" || true) +EVE_CONTACT=$(use_frost_client export --name 'Eve' -c "$GENERATED_DIR/eve.toml" 2>&1 | grep "^minafrost" || true) -echo "Generating Eve's contact..." -EVE_CONTACT=$(cargo run --bin frost-client -- export --name 'Eve' -c "$GENERATED_DIR/eve.toml" 2>&1 | grep "^minafrost" || true) -echo "Eve's contact: '$EVE_CONTACT'" - -echo "" -echo "=========================================" -echo "Importing contacts for each participant" -echo "=========================================" - -# Import contacts for each participant -echo "Importing contacts..." - -# Validate contact strings are not empty if [ -z "$ALICE_CONTACT" ] || [ -z "$BOB_CONTACT" ] || [ -z "$EVE_CONTACT" ]; then - echo "Error: One or more contact strings are empty!" - echo "Alice: '$ALICE_CONTACT'" - echo "Bob: '$BOB_CONTACT'" - echo "Eve: '$EVE_CONTACT'" + echo "ERROR: Failed to generate contact strings" exit 1 fi -echo "Importing contacts for Alice..." -cargo run --bin frost-client -- import -c "$GENERATED_DIR/alice.toml" "$BOB_CONTACT" -cargo run --bin frost-client -- import -c "$GENERATED_DIR/alice.toml" "$EVE_CONTACT" - -echo "Importing contacts for Bob..." -cargo run --bin frost-client -- import -c "$GENERATED_DIR/bob.toml" "$ALICE_CONTACT" -cargo run --bin frost-client -- import -c "$GENERATED_DIR/bob.toml" "$EVE_CONTACT" - -echo "Importing contacts for Eve..." -cargo run --bin frost-client -- import -c "$GENERATED_DIR/eve.toml" "$ALICE_CONTACT" -cargo run --bin frost-client -- import -c "$GENERATED_DIR/eve.toml" "$BOB_CONTACT" +echo "Importing contacts..." +use_frost_client import -c "$GENERATED_DIR/alice.toml" "$BOB_CONTACT" +use_frost_client import -c "$GENERATED_DIR/alice.toml" "$EVE_CONTACT" +use_frost_client import -c "$GENERATED_DIR/bob.toml" "$ALICE_CONTACT" +use_frost_client import -c "$GENERATED_DIR/bob.toml" "$EVE_CONTACT" +use_frost_client import -c "$GENERATED_DIR/eve.toml" "$ALICE_CONTACT" +use_frost_client import -c "$GENERATED_DIR/eve.toml" "$BOB_CONTACT" echo "" -echo "=========================================" -echo "Starting DKG process" -echo "=========================================" - -# Run DKG process echo "Starting DKG process..." # Extract public keys from contacts -echo "Extracting public keys from contacts..." -CONTACTS_OUTPUT=$(cargo run --bin frost-client -- contacts -c "$GENERATED_DIR/alice.toml" 2>&1) - -# Define group name +CONTACTS_OUTPUT=$(use_frost_client contacts -c "$GENERATED_DIR/alice.toml" 2>&1 | grep -A2 "Name:") GROUP_NAME="Alice, Bob and Eve" BOB_PUBLIC_KEY=$(echo "$CONTACTS_OUTPUT" | grep -A1 "Name: Bob" | grep "Public Key:" | cut -d' ' -f3) EVE_PUBLIC_KEY=$(echo "$CONTACTS_OUTPUT" | grep -A1 "Name: Eve" | grep "Public Key:" | cut -d' ' -f3) -# Alice initiates the DKG -echo "Alice initiating DKG..." -cargo run --bin frost-client -- dkg \ +# Run DKG processes with proper timing +echo "Starting Alice (coordinator) DKG process..." +use_frost_client dkg \ -d "$GROUP_NAME" \ -s "$SERVER_URL" \ -S "$BOB_PUBLIC_KEY,$EVE_PUBLIC_KEY" \ @@ -163,35 +84,33 @@ cargo run --bin frost-client -- dkg \ -c "$GENERATED_DIR/alice.toml" & ALICE_DKG_PID=$! -# Wait a moment for Alice to start -sleep 3 +# Give Alice time to create the DKG session +echo "Waiting for Alice to create DKG session..." +sleep 5 -# Bob joins the DKG -echo "" -echo "Bob joining DKG..." -cargo run --bin frost-client -- dkg \ +echo "Starting Bob DKG process..." +use_frost_client dkg \ -d "$GROUP_NAME" \ -s "$SERVER_URL" \ -t 2 \ -c "$GENERATED_DIR/bob.toml" & BOB_DKG_PID=$! -# Wait a moment -sleep 3 +# Brief delay before starting Eve +sleep 2 -# Eve joins the DKG -echo "" -echo "Eve joining DKG..." -cargo run --bin frost-client -- dkg \ +echo "Starting Eve DKG process..." +use_frost_client dkg \ -d "$GROUP_NAME" \ -s "$SERVER_URL" \ -t 2 \ -c "$GENERATED_DIR/eve.toml" & EVE_DKG_PID=$! -# Wait for all DKG processes to complete -echo "" echo "Waiting for DKG processes to complete..." +echo "This may take up to 2-3 minutes..." + +# Wait for completion wait $ALICE_DKG_PID ALICE_EXIT=$? wait $BOB_DKG_PID @@ -199,27 +118,34 @@ BOB_EXIT=$? wait $EVE_DKG_PID EVE_EXIT=$? -echo "" -echo "=========================================" -echo "DKG Results" -echo "=========================================" - -# Check if DKG completed successfully +# Check results if [ $ALICE_EXIT -eq 0 ] && [ $BOB_EXIT -eq 0 ] && [ $EVE_EXIT -eq 0 ]; then - echo "DKG completed successfully!" - echo "" - echo "Checking generated groups..." - echo "Alice's groups:" - cargo run --bin frost-client -- groups -c "$GENERATED_DIR/alice.toml" - echo "" - echo "Bob's groups:" - cargo run --bin frost-client -- groups -c "$GENERATED_DIR/bob.toml" - echo "" - echo "Eve's groups:" - cargo run --bin frost-client -- groups -c "$GENERATED_DIR/eve.toml" - echo "" - echo "DKG process complete. Check the generated directory for the config files." + echo "✅ DKG completed successfully!" + + # Validate that DKG actually worked by checking for group keys + echo "Validating DKG results..." + for config in "$GENERATED_DIR/alice.toml" "$GENERATED_DIR/bob.toml" "$GENERATED_DIR/eve.toml"; do + if ! grep -q "key_package" "$config" 2>/dev/null; then + echo "❌ DKG validation failed: $config missing key_package" >&2 + exit 1 + fi + if ! grep -q "public_key_package" "$config" 2>/dev/null; then + echo "❌ DKG validation failed: $config missing public_key_package" >&2 + exit 1 + fi + done + + # Show one group info as confirmation + GROUP_INFO=$(use_frost_client groups -c "$GENERATED_DIR/alice.toml" | head -2) + echo "$GROUP_INFO" + echo "📁 Config files saved to: $GENERATED_DIR/" else - echo "DKG process failed. Exit codes: Alice=$ALICE_EXIT, Bob=$BOB_EXIT, Eve=$EVE_EXIT" + echo "❌ DKG failed (exit codes: A=$ALICE_EXIT, B=$BOB_EXIT, E=$EVE_EXIT)" + + # Print some debug info to help diagnose the issue + echo "Debug info:" + echo "Alice config exists: $(test -f "$GENERATED_DIR/alice.toml" && echo "yes" || echo "no")" + echo "Bob config exists: $(test -f "$GENERATED_DIR/bob.toml" && echo "yes" || echo "no")" + echo "Eve config exists: $(test -f "$GENERATED_DIR/eve.toml" && echo "yes" || echo "no")" exit 1 fi diff --git a/frost-client/examples/helpers/file_generation.sh b/frost-client/examples/helpers/file_generation.sh new file mode 100644 index 0000000..2d37e93 --- /dev/null +++ b/frost-client/examples/helpers/file_generation.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Helper functions for managing generated directories in FROST examples +# Strict error handling - exit on any error, undefined variable, or pipe failure +set -euo pipefail + +# Function to clean and recreate a generated directory +# Usage: setup_generated_dir "/path/to/generated" +setup_generated_dir() { + local generated_dir="$1" + + if [ -z "$generated_dir" ]; then + echo "ERROR: Generated directory path is required" + return 1 + fi + + echo "Setting up generated directory: $generated_dir" + + # Remove existing directory if it exists + if [ -d "$generated_dir" ]; then + rm -rf "$generated_dir" + fi + + # Create fresh directory + mkdir -p "$generated_dir" + + echo "Generated directory ready: $generated_dir" +} diff --git a/frost-client/examples/helpers/init_frostd.sh b/frost-client/examples/helpers/init_frostd.sh new file mode 100644 index 0000000..0d60a91 --- /dev/null +++ b/frost-client/examples/helpers/init_frostd.sh @@ -0,0 +1,98 @@ +#!/bin/bash + +# FROST Server Initialization Helper +# This script handles starting the frostd server with TLS certificates +# Strict error handling - exit on any error, undefined variable, or pipe failure +set -euo pipefail + +# Get the directory where the script is located +HELPER_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# Function to initialize and start frostd server +init_frostd() { + local generated_dir="$1" + local server_url="${2:-localhost:2744}" + + if [ -z "$generated_dir" ]; then + echo "ERROR: Generated directory path is required!" + echo "Usage: init_frostd [server_url]" + return 1 + fi + + echo "Setting up TLS certificates" + + # Create generated directory if it doesn't exist + mkdir -p "$generated_dir" + + # Ensure mkcert CA is installed (needed for Docker containers) + if ! mkcert -CAROOT >/dev/null 2>&1 || [ ! -f "$(mkcert -CAROOT)/rootCA.pem" ]; then + echo "Installing mkcert CA..." + mkcert -install >/dev/null 2>&1 || true + fi + + # Generate TLS certificates + cd "$generated_dir" + mkcert localhost 127.0.0.1 ::1 2>/dev/null || { + echo "ERROR: mkcert failed. Please install mkcert first:" + echo " # On Ubuntu/Debian:" + echo " sudo apt install mkcert" + echo " # On macOS:" + echo " brew install mkcert" + echo " Also ensure you have run 'mkcert -install' to set up the local CA." + cd - > /dev/null + return 1 + } + cd - > /dev/null + + echo "Starting frostd server" + + # Check if frostd is installed + if ! command -v frostd &> /dev/null; then + echo "ERROR: frostd is not installed or not in PATH!" + echo "" + echo "Please install frostd first. You can build it from the FROST server repository:" + echo " cargo install --git https://github.com/ZcashFoundation/frost-zcash-demo.git --locked frostd" + return 1 + fi + + # Start frostd server in the background + echo "Starting frostd server on $server_url..." + + frostd --tls-cert "$generated_dir/localhost+2.pem" --tls-key "$generated_dir/localhost+2-key.pem" & + local server_pid=$! + + # Wait for the server to start + sleep 3 + + # Verify the server is running + if kill -0 "$server_pid" 2>/dev/null; then + echo "frostd server started successfully with PID: $server_pid" + # Export the PID for the calling script to use + export FROSTD_SERVER_PID="$server_pid" + return 0 + else + echo "ERROR: Failed to start frostd server!" + return 1 + fi +} + +# Function to stop frostd server +stop_frostd() { + local server_pid="$1" + + if [ -z "$server_pid" ]; then + server_pid="$FROSTD_SERVER_PID" + fi + + if [ ! -z "$server_pid" ] && kill -0 "$server_pid" 2>/dev/null; then + echo "Stopping frostd server (PID: $server_pid)..." + kill "$server_pid" + wait "$server_pid" 2>/dev/null || true + echo "frostd server stopped." + fi +} + +# If script is run directly (not sourced), call init_frostd with arguments +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + init_frostd "$@" +fi diff --git a/frost-client/examples/helpers/use_frost_client.sh b/frost-client/examples/helpers/use_frost_client.sh new file mode 100644 index 0000000..b3fbded --- /dev/null +++ b/frost-client/examples/helpers/use_frost_client.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Helper method for running frost commands +# Strict error handling - exit on any error, undefined variable, or pipe failure +set -euo pipefail + +use_frost_client() { + if ! cargo run --bin frost-client -- "$@"; then + echo "Error: frost-client command failed with arguments: $*" >&2 + return 1 + fi +} diff --git a/frost-client/examples/signing_example/2_out_of_2.sh b/frost-client/examples/signing_example/2_out_of_2.sh deleted file mode 100644 index e69de29..0000000 diff --git a/frost-client/examples/signing_example/signing_example.sh b/frost-client/examples/signing_example/signing_example.sh index 641e511..501e259 100755 --- a/frost-client/examples/signing_example/signing_example.sh +++ b/frost-client/examples/signing_example/signing_example.sh @@ -1,22 +1,29 @@ #!/bin/bash +# Strict error handling - exit on any error, undefined variable, or pipe failure +set -euo pipefail + # Get the directory where the script is located SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" GENERATED_DIR="$SCRIPT_DIR/generated" TRUSTED_DEALER_DIR="$SCRIPT_DIR/../trusted_dealer_example/generated" +HELPERS_DIR="$SCRIPT_DIR/../helpers" + +cd "$SCRIPT_DIR" # Default server URL SERVER_URL="localhost:2744" -SERVER_PID="" + +# Source helpers +source "$HELPERS_DIR/init_frostd.sh" +source "$HELPERS_DIR/file_generation.sh" +# Remove release binary if you want the script to use cargo run +source "$HELPERS_DIR/use_frost_client.sh" # Function to cleanup on exit cleanup() { echo "Cleaning up..." - if [ ! -z "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then - echo "Stopping frostd server (PID: $SERVER_PID)..." - kill "$SERVER_PID" - wait "$SERVER_PID" 2>/dev/null - fi + stop_frostd "$FROSTD_SERVER_PID" exit } @@ -26,148 +33,64 @@ trap cleanup EXIT INT TERM echo "=========================================" echo "FROST Signing Example" echo "=========================================" -echo "This example demonstrates the FROST signing process using" -echo "keys generated by the trusted dealer example." -echo "" # Check if trusted dealer keys exist -if [ ! -d "$TRUSTED_DEALER_DIR" ]; then - echo "ERROR: Trusted dealer keys not found!" - echo "Please run the trusted dealer example first:" - echo " cd ../trusted_dealer_example && ./trusted_dealer_example.sh" - exit 1 -fi - -if [ ! -f "$TRUSTED_DEALER_DIR/alice.toml" ] || [ ! -f "$TRUSTED_DEALER_DIR/bob.toml" ] || [ ! -f "$TRUSTED_DEALER_DIR/eve.toml" ]; then - echo "ERROR: Trusted dealer config files not found!" - echo "Please run the trusted dealer example first to generate the key shares." - exit 1 -fi - -# Clean up generated directory if it exists -if [ -d "$GENERATED_DIR" ]; then - echo "Cleaning up existing generated directory..." - rm -rf "$GENERATED_DIR" +if [ ! -d "$TRUSTED_DEALER_DIR" ] || [ ! -f "$TRUSTED_DEALER_DIR/alice.toml" ] || [ ! -f "$TRUSTED_DEALER_DIR/bob.toml" ] || [ ! -f "$TRUSTED_DEALER_DIR/eve.toml" ]; then + echo "Setting up trusted dealer keys..." + + # Run the trusted dealer example + if [ -f "$SCRIPT_DIR/../trusted_dealer_example/trusted_dealer_example.sh" ]; then + "$SCRIPT_DIR/../trusted_dealer_example/trusted_dealer_example.sh" + TRUSTED_DEALER_EXIT=$? + + if [ $TRUSTED_DEALER_EXIT -ne 0 ]; then + echo "ERROR: Failed to set up trusted dealer keys" + exit 1 + fi + echo "✓ Keys ready" + else + echo "ERROR: Trusted dealer script not found!" + exit 1 + fi fi -# Create directory for generated files -mkdir -p "$GENERATED_DIR" - -# Copy trusted dealer configs to our working directory -echo "Copying trusted dealer configurations..." -cp "$TRUSTED_DEALER_DIR/alice.toml" "$GENERATED_DIR/" -cp "$TRUSTED_DEALER_DIR/bob.toml" "$GENERATED_DIR/" -cp "$TRUSTED_DEALER_DIR/eve.toml" "$GENERATED_DIR/" +# Clean up and copy configs +setup_generated_dir "$GENERATED_DIR" +cp "$TRUSTED_DEALER_DIR"/*.toml "$GENERATED_DIR/" -# Setting up the TLS certificates -echo "Setting up TLS certificates..." -cd "$GENERATED_DIR" -mkcert localhost 127.0.0.1 ::1 2>/dev/null || { - echo "ERROR: mkcert failed. Please install mkcert first:" - echo " # On Ubuntu/Debian:" - echo " sudo apt install mkcert" - echo " # On macOS:" - echo " brew install mkcert" - echo " Also ensure you have run 'mkcert -install' to set up the local CA." +# Start server +init_frostd "$GENERATED_DIR" "$SERVER_URL" || { + echo "ERROR: Failed to initialize server" exit 1 } -cd .. - -echo "" -echo "=========================================" -echo "Starting frostd server" -echo "=========================================" - -# Check if frostd is installed -if ! command -v frostd &> /dev/null; then - echo "ERROR: frostd is not installed or not in PATH!" - echo "" - echo "Please install frostd first. You can build it from the FROST server repository:" - echo " cargo install --git https://github.com/ZcashFoundation/frost-zcash-demo.git --locked frostd" - exit 1 -fi - -# Start frostd server in the background -echo "Starting frostd server on $SERVER_URL..." -echo "Using TLS cert: $GENERATED_DIR/localhost+2.pem" -echo "Using TLS key: $GENERATED_DIR/localhost+2-key.pem" -frostd --tls-cert "$GENERATED_DIR/localhost+2.pem" --tls-key "$GENERATED_DIR/localhost+2-key.pem" & -SERVER_PID=$! -echo "Server PID: $SERVER_PID" - -# Wait for the server to start -sleep 3 -echo "frostd server started with PID: $SERVER_PID" -echo "" -echo "=========================================" -echo "Checking group information" -echo "=========================================" - -# Check groups for Alice (they should all have the same group) -echo "Checking Alice's groups..." -ALICE_GROUPS=$(cargo run --bin frost-client groups -c "$GENERATED_DIR/alice.toml" 2>&1) -echo "Alice ggg: $ALICE_GROUPS" - -# Extract the group public key (first group's public key) +# Get group information +ALICE_GROUPS=$(use_frost_client groups -c "$GENERATED_DIR/alice.toml" 2>&1) GROUP_PUBLIC_KEY=$(echo "$ALICE_GROUPS" | grep "Public key (hex format):" | head -1 | sed 's/.*Public key (hex format): \([a-f0-9]*\).*/\1/') if [ -z "$GROUP_PUBLIC_KEY" ]; then - echo "ERROR: Could not extract group public key from Alice's groups!" - echo "Make sure the trusted dealer example was run successfully." + echo "ERROR: Could not extract group public key" exit 1 fi -echo "" -echo "Group public key: $GROUP_PUBLIC_KEY" - -echo "" -echo "=========================================" -echo "Generating message to sign" -echo "=========================================" - -# Create a test message to sign -TEST_MESSAGE=$(cat message.json) -echo "Message to sign: $TEST_MESSAGE" - -# Save message to file for coordinator +# Prepare message +TEST_MESSAGE=$(cat "$SCRIPT_DIR/message.json") echo -n "$TEST_MESSAGE" > "$GENERATED_DIR/message.json" -echo "" -echo "=========================================" -echo "Starting FROST signing process" -echo "=========================================" -echo "This will demonstrate a 2-of-3 threshold signature." -echo "Alice will act as the coordinator, and Bob and Eve will participate." -echo "" +echo "Starting signing process..." -# Get Alice's public key for coordination -ALICE_CONTACTS=$(cargo run --bin frost-client contacts -c "$GENERATED_DIR/alice.toml" 2>&1) -ALICE_PUBLIC_KEY=$(echo "$ALICE_CONTACTS" | grep -A1 "Name: Alice" | grep "Public Key:" | cut -d' ' -f3 || echo "") - -# Get Bob's public key +# Get participant public keys +ALICE_CONTACTS=$(use_frost_client contacts -c "$GENERATED_DIR/alice.toml" 2>&1) BOB_PUBLIC_KEY=$(echo "$ALICE_CONTACTS" | grep -A1 "Name: Bob" | grep "Public Key:" | cut -d' ' -f3) - -# Get Eve's public key EVE_PUBLIC_KEY=$(echo "$ALICE_CONTACTS" | grep -A1 "Name: Eve" | grep "Public Key:" | cut -d' ' -f3) -echo "Alice's public key: $ALICE_PUBLIC_KEY" -echo "Bob's public key: $BOB_PUBLIC_KEY" -echo "Eve's public key: $EVE_PUBLIC_KEY" - if [ -z "$BOB_PUBLIC_KEY" ] || [ -z "$EVE_PUBLIC_KEY" ]; then - echo "ERROR: Could not extract participant public keys!" - echo "The trusted dealer example may not have been run correctly." + echo "ERROR: Could not extract participant public keys" exit 1 fi -echo "" -echo "=========================================" -echo "Step 1: Alice starts as coordinator" -echo "=========================================" - -# Alice acts as coordinator - signs with Bob and Eve (2-of-3 threshold) -echo "Alice starting coordinator process..." -cargo run --bin frost-client -- coordinator \ +# Start coordinator and participants +echo "Starting coordinator..." +use_frost_client coordinator \ -c "$GENERATED_DIR/alice.toml" \ --server-url "$SERVER_URL" \ --group "$GROUP_PUBLIC_KEY" \ @@ -176,92 +99,44 @@ cargo run --bin frost-client -- coordinator \ -o "$GENERATED_DIR/signature.json" & COORDINATOR_PID=$! -# Wait for coordinator to start +# Give coordinator time to create the signing session +echo "Waiting for coordinator to create signing session..." sleep 5 -echo "" -echo "=========================================" -echo "Step 2: Bob joins as participant" -echo "=========================================" - -echo "Bob joining the signing session..." -echo "y" | cargo run --bin frost-client -- participant \ +echo "Starting participant (Bob)..." +echo "y" | use_frost_client participant \ -c "$GENERATED_DIR/bob.toml" \ --server-url "$SERVER_URL" \ --group "$GROUP_PUBLIC_KEY" & BOB_PID=$! -# Wait a moment -sleep 3 - -echo "" -echo "=========================================" -echo "Step 3: Eve joins as participant" -echo "=========================================" - -echo "Eve joining the signing session..." -echo "y" | cargo run --bin frost-client -- participant \ +echo "Starting participant (Eve)..." +echo "y" | use_frost_client participant \ -c "$GENERATED_DIR/eve.toml" \ --server-url "$SERVER_URL" \ --group "$GROUP_PUBLIC_KEY" & EVE_PID=$! -echo "" -echo "=========================================" -echo "Waiting for signing process to complete" -echo "=========================================" - -# Wait for all processes to complete -echo "Waiting for coordinator to complete..." +# Wait for completion +echo "Waiting for signing process to complete..." wait $COORDINATOR_PID COORDINATOR_EXIT=$? - -echo "Waiting for Bob to complete..." wait $BOB_PID BOB_EXIT=$? - -echo "Waiting for Eve to complete..." wait $EVE_PID EVE_EXIT=$? -echo "" -echo "=========================================" -echo "Signing Results" -echo "=========================================" - -# Check if signing completed successfully +# Check results if [ $COORDINATOR_EXIT -eq 0 ] && [ $BOB_EXIT -eq 0 ] && [ $EVE_EXIT -eq 0 ]; then - echo "✅ FROST signing completed successfully!" - echo "" - echo "Original message: $TEST_MESSAGE" - echo "" - + echo "✅ Signing completed successfully!" if [ -f "$GENERATED_DIR/signature.json" ]; then - SIGNATURE=$(cat "$GENERATED_DIR/signature.json") - echo "Generated signature: $SIGNATURE" - echo "" - echo "Signature saved to: $GENERATED_DIR/signature.json" + echo "📄 Signature saved to: $GENERATED_DIR/signature.json" + echo "🔑 Group public key: $GROUP_PUBLIC_KEY" else - echo "⚠️ Signature file not found, but processes completed successfully." + echo "⚠️ Signature file not found" + exit 1 fi - - echo "" - echo "=========================================" - echo "Signature verification" - echo "=========================================" - echo "Group public key: $GROUP_PUBLIC_KEY" - echo "This signature can be verified using the group public key" - echo "against the original message using standard signature verification." - echo "" - echo "Files generated:" - echo " - $GENERATED_DIR/message.json (original message)" - echo " - $GENERATED_DIR/signature.json (FROST signature)" - echo " - $GENERATED_DIR/alice.toml (Alice's config)" - echo " - $GENERATED_DIR/bob.toml (Bob's config)" - echo " - $GENERATED_DIR/eve.toml (Eve's config)" - else - echo "❌ FROST signing process failed!" - echo "Exit codes: Coordinator=$COORDINATOR_EXIT, Bob=$BOB_EXIT, Eve=$EVE_EXIT" + echo "❌ Signing failed (exit codes: C=$COORDINATOR_EXIT, B=$BOB_EXIT, E=$EVE_EXIT)" exit 1 fi diff --git a/frost-client/examples/trusted_dealer_example/trusted_dealer_example.sh b/frost-client/examples/trusted_dealer_example/trusted_dealer_example.sh index fa10205..c1250d4 100755 --- a/frost-client/examples/trusted_dealer_example/trusted_dealer_example.sh +++ b/frost-client/examples/trusted_dealer_example/trusted_dealer_example.sh @@ -1,27 +1,30 @@ #!/bin/bash +# Strict error handling - exit on any error, undefined variable, or pipe failure +set -euo pipefail + # Get the directory where the script is located SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" GENERATED_DIR="$SCRIPT_DIR/generated" +HELPERS_DIR="$SCRIPT_DIR/../helpers" -# Clean up generated directory if it exists -if [ -d "$GENERATED_DIR" ]; then - echo "Cleaning up existing generated directory..." - rm -rf "$GENERATED_DIR" -fi +cd "$SCRIPT_DIR" + +# Source helpers +source "$HELPERS_DIR/file_generation.sh" +source "$HELPERS_DIR/use_frost_client.sh" -# Create directory for generated files -mkdir -p "$GENERATED_DIR" +# Setup +setup_generated_dir "$GENERATED_DIR" # Initialize configs for three users -echo "Initializing configs for users..." -cargo run --bin frost-client -- init -c "$GENERATED_DIR/alice.toml" -cargo run --bin frost-client -- init -c "$GENERATED_DIR/bob.toml" -cargo run --bin frost-client -- init -c "$GENERATED_DIR/eve.toml" +use_frost_client init -c "$GENERATED_DIR/alice.toml" +use_frost_client init -c "$GENERATED_DIR/bob.toml" +use_frost_client init -c "$GENERATED_DIR/eve.toml" +echo "Generating FROST key shares..." # Generate FROST key shares using trusted dealer -echo "Generating FROST key shares using trusted dealer..." -cargo run --bin frost-client -- trusted-dealer \ +use_frost_client trusted-dealer \ -d "Alice, Bob and Eve's group" \ --names Alice,Bob,Eve \ -c "$GENERATED_DIR/alice.toml" \ @@ -29,4 +32,19 @@ cargo run --bin frost-client -- trusted-dealer \ -c "$GENERATED_DIR/eve.toml" \ -t 2 -echo "Key generation complete. Check the generated directory for the config files." +# Validate that key generation was successful +echo "Validating generated configuration files..." +if [[ ! -f "$GENERATED_DIR/alice.toml" ]] || [[ ! -f "$GENERATED_DIR/bob.toml" ]] || [[ ! -f "$GENERATED_DIR/eve.toml" ]]; then + echo "ERROR: Key generation failed - configuration files not found" >&2 + exit 1 +fi + +# Verify the config files contain key data (not just empty files) +for config in "$GENERATED_DIR/alice.toml" "$GENERATED_DIR/bob.toml" "$GENERATED_DIR/eve.toml"; do + if ! grep -q "key_package" "$config" 2>/dev/null; then + echo "ERROR: Configuration file $config appears to be invalid (no key_package found)" >&2 + exit 1 + fi +done + +echo "✅ Key generation complete. Check the generated directory for the config files."