proofmode 0.9.0

Capture, share, and preserve verifiable photos and videos
Documentation
#!/usr/bin/env ruby
# Example of using ProofMode library from Ruby via UniFFI bindings.
#
# Prerequisites:
#   gem install ffi
#   cargo make ruby-build
#
# Make sure ruby/lib/ contains both proofmode.rb and libproofmode.so,
# then run:
#   ruby -I../../ruby/lib examples/ruby/example.rb

require "json"
require "tmpdir"
require "fileutils"
require "proofmode"
require "proofmode_callbacks"

class MyCallbacks < Proofmode::ProofModeCallbacks
  def initialize(output_dir)
    @output_dir = output_dir
  end

  def get_location
    Proofmode::LocationData.new(
      latitude: 37.7749,
      longitude: -122.4194,
      altitude: 10.0,
      accuracy: 5.0,
      provider: "gps"
    )
  end

  def get_device_info
    Proofmode::DeviceData.new(
      manufacturer: "Example Corp",
      model: "Ruby App",
      os_version: "Ruby #{RUBY_VERSION}",
      device_id: nil
    )
  end

  def get_network_info
    Proofmode::NetworkData.new(
      network_type: "wifi",
      wifi_ssid: "ExampleNetwork",
      cell_info: nil
    )
  end

  def save_data(hash, filename, data)
    dir = File.join(@output_dir, hash)
    FileUtils.mkdir_p(dir)
    filepath = File.join(dir, filename)
    File.open(filepath, "wb") { |f| f.write(data) }
    true
  rescue => e
    puts "Failed to save data: #{e.message}"
    false
  end

  def save_text(hash, filename, text)
    dir = File.join(@output_dir, hash)
    FileUtils.mkdir_p(dir)
    filepath = File.join(dir, filename)
    File.write(filepath, text)
    true
  rescue => e
    puts "Failed to save text: #{e.message}"
    false
  end

  def sign_data(data)
    # Return nil to skip PGP signing in this example
    nil
  end

  def report_progress(message)
    puts "[progress] #{message}"
  end
end

def main
  puts "ProofMode version: #{Proofmode.get_version}"

  # Hash some data
  test_data = "Hello, ProofMode from Ruby!"
  file_hash = Proofmode.get_file_hash(test_data)
  puts "SHA-256 hash: #{file_hash}"

  # Generate proof
  puts "\nGenerating proof..."
  Dir.mktmpdir do |output_dir|
    callbacks = MyCallbacks.new(output_dir)
    config = Proofmode::ProofModeConfig.new(
      auto_notarize: false,
      track_location: true,
      track_device_id: false,
      track_network: true,
      add_credentials: false
    )
    metadata = { "description" => "Ruby example proof" }

    begin
      result_hash = Proofmode.generate_proof(test_data, metadata, config, callbacks)
      puts "Proof generated, hash: #{result_hash}"

      # List generated files
      hash_dir = File.join(output_dir, result_hash)
      if Dir.exist?(hash_dir)
        puts "Generated files:"
        Dir.entries(hash_dir).reject { |f| f.start_with?(".") }.each do |f|
          puts "  #{f}"
        end
      end
    rescue Proofmode::ProofModeError => e
      puts "Error generating proof: #{e.message}"
    end
  end

  # Check files (if a proof bundle exists)
  proof_bundle = "./proof-bundle.zip"
  if File.exist?(proof_bundle)
    puts "\nVerifying proof bundle..."
    begin
      callbacks = MyCallbacks.new(Dir.tmpdir)
      result_json = Proofmode.check_files([proof_bundle], callbacks)
      result = JSON.parse(result_json)
      puts "Verification result: #{JSON.pretty_generate(result)}"

      # Parse consistency discrepancies
      consistency = result["consistency"] || {}
      puts "\nConsistency verified: #{consistency["verified"]}"
      (consistency["fileDiscrepancies"] || []).each do |fd|
        puts "  File: #{fd["fileName"]} (#{fd["flagCount"]} flags)"
        (fd["flags"] || []).each do |flag|
          puts "    [#{flag["severity"]}/#{flag["kind"]}] #{flag["field"]}: #{flag["message"]}"
        end
      end
      summary = consistency["summary"]
      if summary
        puts "  Summary: #{summary["filesWithFlags"]}/#{summary["totalFiles"]} files flagged, #{summary["totalFlags"]} total flags"
      end

      # Parse synchrony temporal analysis
      synchrony = result["synchrony"] || {}
      temporal = synchrony["temporal"]
      if temporal
        puts "\nTemporal analysis:"
        puts "  Range: #{temporal["earliest"]} to #{temporal["latest"]}"
        puts "  Elapsed: #{temporal["elapsedSeconds"].round(1)}s across #{temporal["fileCount"]} files"
        patterns = temporal["patterns"]
        if patterns
          puts "  Mean interval: #{patterns["meanIntervalSeconds"].round(1)}s (std #{patterns["stdIntervalSeconds"].round(1)}s)"
          puts "  Bursts: #{patterns["burstCount"]}, Gaps: #{patterns["gapCount"]}"
        end
      end

      # Parse synchrony spatial analysis
      spatial = synchrony["spatial"]
      if spatial
        puts "\nSpatial analysis:"
        puts "  Max distance: #{"%.2f" % spatial["maxDistanceKm"]} km"
        puts "  Coverage area: #{"%.2f" % spatial["areaKm2"]} km^2"
        puts "  Centroid: [#{"%.4f" % spatial["centroid"][0]}, #{"%.4f" % spatial["centroid"][1]}]"
        (spatial["pairwiseDistances"] || []).each do |pd|
          puts "    #{pd["fileA"]} <-> #{pd["fileB"]}: #{"%.2f" % pd["distanceKm"]} km"
        end
      end

    rescue Proofmode::ProofModeError => e
      puts "Error verifying: #{e.message}"
    end
  else
    puts "\nSkipping check (no proof-bundle.zip found)"
  end
end

main if __FILE__ == $0