proofmode 0.9.0

Capture, share, and preserve verifiable photos and videos
Documentation
#!/usr/bin/env python3
"""
Example of using ProofMode library from Python via UniFFI bindings.

Install the wheel first:
    pip install /path/to/target/wheels/proofmode-*.whl

Or for development:
    maturin develop
"""

import json
import os
import sys
import tempfile

import proofmode


class MyCallbacks(proofmode.ProofModeCallbacks):
    """Implement all 7 callback methods required by ProofModeCallbacks."""

    def __init__(self, output_dir):
        self.output_dir = output_dir

    def get_location(self):
        return proofmode.LocationData(
            latitude=37.7749,
            longitude=-122.4194,
            altitude=10.0,
            accuracy=5.0,
            provider="gps",
        )

    def get_device_info(self):
        return proofmode.DeviceData(
            manufacturer="Example Corp",
            model="Python App",
            os_version="Python {}".format(sys.version.split()[0]),
            device_id=None,
        )

    def get_network_info(self):
        return proofmode.NetworkData(
            network_type="wifi",
            wifi_ssid="ExampleNetwork",
            cell_info=None,
        )

    def save_data(self, hash, filename, data):
        path = os.path.join(self.output_dir, hash)
        os.makedirs(path, exist_ok=True)
        filepath = os.path.join(path, filename)
        try:
            with open(filepath, "wb") as f:
                f.write(bytes(data))
            return True
        except Exception as e:
            print("Failed to save data: {}".format(e))
            return False

    def save_text(self, hash, filename, text):
        path = os.path.join(self.output_dir, hash)
        os.makedirs(path, exist_ok=True)
        filepath = os.path.join(path, filename)
        try:
            with open(filepath, "w") as f:
                f.write(text)
            return True
        except Exception as e:
            print("Failed to save text: {}".format(e))
            return False

    def sign_data(self, data):
        # Return None to skip PGP signing in this example
        return None

    def report_progress(self, message):
        print("[progress] {}".format(message))


def main():
    print("ProofMode version: {}".format(proofmode.get_version()))

    # Hash a file
    test_data = b"Hello, ProofMode from Python!"
    file_hash = proofmode.get_file_hash(test_data)
    print("SHA-256 hash: {}".format(file_hash))

    # Generate proof
    print("\nGenerating proof...")
    with tempfile.TemporaryDirectory() as output_dir:
        callbacks = MyCallbacks(output_dir)
        config = proofmode.ProofModeConfig(
            auto_notarize=False,
            track_location=True,
            track_device_id=False,
            track_network=True,
            add_credentials=False,
        )
        metadata = {"description": "Python example proof"}

        try:
            result_hash = proofmode.generate_proof(
                test_data, metadata, config, callbacks
            )
            print("Proof generated, hash: {}".format(result_hash))

            # List generated files
            hash_dir = os.path.join(output_dir, result_hash)
            if os.path.isdir(hash_dir):
                print("Generated files:")
                for f in os.listdir(hash_dir):
                    print("  {}".format(f))
        except proofmode.ProofModeError as e:
            print("Error generating proof: {}".format(e))

    # Check files (if a proof bundle exists)
    proof_bundle = "./proof-bundle.zip"
    if os.path.exists(proof_bundle):
        print("\nVerifying proof bundle...")
        try:
            result_json = proofmode.check_files([proof_bundle], callbacks)
            result = json.loads(result_json)
            print("Verification result: {}".format(json.dumps(result, indent=2)))

            # Parse consistency discrepancies
            consistency = result.get("consistency", {})
            print("\nConsistency verified: {}".format(consistency.get("verified")))
            for fd in consistency.get("fileDiscrepancies", []):
                print("  File: {} ({} flags)".format(fd["fileName"], fd["flagCount"]))
                for flag in fd.get("flags", []):
                    print("    [{}/{}] {}: {}".format(
                        flag["severity"], flag["kind"], flag["field"], flag["message"]
                    ))
            summary = consistency.get("summary", {})
            if summary:
                print("  Summary: {}/{} files flagged, {} total flags".format(
                    summary.get("filesWithFlags", 0),
                    summary.get("totalFiles", 0),
                    summary.get("totalFlags", 0),
                ))

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

            # Parse synchrony spatial analysis
            spatial = synchrony.get("spatial")
            if spatial:
                print("\nSpatial analysis:")
                print("  Max distance: {:.2f} km".format(spatial["maxDistanceKm"]))
                print("  Coverage area: {:.2f} km^2".format(spatial["areaKm2"]))
                print("  Centroid: [{:.4f}, {:.4f}]".format(
                    spatial["centroid"][0], spatial["centroid"][1]
                ))
                for pd in spatial.get("pairwiseDistances", []):
                    print("    {} <-> {}: {:.2f} km".format(
                        pd["fileA"], pd["fileB"], pd["distanceKm"]
                    ))

        except proofmode.ProofModeError as e:
            print("Error verifying: {}".format(e))
    else:
        print("\nSkipping check (no proof-bundle.zip found)")


if __name__ == "__main__":
    main()