zk-protocol 0.5.0

Shared protocol types for the ZK attestation service — any agent can use these to compose AttestRequest/AttestResponse.
Documentation
/// General protocol for ZK attestation between agents
/// This library provides common types and serialization helpers
/// that any agent can use without depending on other agents' code.

use serde::{Deserialize, Serialize};
use serde_json::Value;

/// Request to the attester service to generate a ZK proof
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AttestRequest {
    pub program_id: String,
    /// Input data as raw bytes (bincode-serialized)
    /// Will be passed to the zkVM program via stdin as a single buffer entry.
    /// For programs that call io::read() multiple times, use `stdin_items` instead.
    pub input_bytes: Vec<u8>,
    /// Multiple stdin buffer entries (each pushed separately).
    /// When present, each entry maps to one sp1_zkvm::io::read() call.
    /// Takes precedence over `input_bytes` when non-empty.
    #[serde(default)]
    pub stdin_items: Vec<Vec<u8>>,
    /// Expected output for verification (optional, format defined by agent)
    pub claimed_output: Option<Value>,
    /// Whether to verify the proof locally before returning
    #[serde(default = "default_verify")]
    pub verify_locally: bool,
    /// Optional external transaction ID (payment processor binding).
    /// When present, the attester auto-saves the proof keyed by this ID
    /// so the payment processor can pull it directly — bypassing the LLM.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub external_id: Option<String>,
    /// Intent commitment: SHA-256(proof_hash || "||" || external_id).
    /// Stored alongside the proof for retrieval by the payment processor.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub intent_commitment: Option<String>,
    /// Fields verified in the proof (e.g. ["amount", "quantity", "product_id"]).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub verified_fields: Option<Vec<String>>,
    /// Actual field values proven (e.g. {"recipient_address": "0x...", "token": "ETH"}).
    /// Stored alongside the proof so the payment processor can validate values
    /// without re-running the ZKP — the commitment scheme ensures correctness.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub field_values: Option<std::collections::HashMap<String, String>>,
    /// Intent type detected by the ZPI tool (e.g. "send_intent", "spend_intent",
    /// "exchange_intent"). Stored as workflow_stage on the attester side.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub intent_type: Option<String>,
}

fn default_verify() -> bool {
    true
}

/// A single field commitment extracted from the proof's public values.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FieldCommitment {
    /// Field name (e.g., "amount", "product_id")
    pub field: String,
    /// Hex-encoded SHA-256 commitment: SHA-256(field:value:external_id:secret_salt)
    pub commitment: String,
}

/// Response from the attester service
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AttestResponse {
    /// Hex-encoded Groth16 proof for on-chain verification
    pub proof: String,
    /// Public values committed by the zkVM program (hex-encoded)
    pub public_values: String,
    /// VK hash for on-chain verifier (bytes32)
    pub vk_hash: String,
    /// Output from the zkVM program
    pub verified_output: Value,
    /// Echo back the external_id if one was provided in the request.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub external_id: Option<String>,
    /// Field commitments extracted from the proof's public values.
    /// Each entry is SHA-256(field:value:external_id:secret_salt).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub field_commitments: Option<Vec<FieldCommitment>>,
    /// Attester-issued proof identifier (UUID). Source of truth for proof identity.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub proof_id: Option<String>,
}

/// Response from POST /register-elf
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RegisterResponse {
    /// Content-addressable program ID (e.g. "sha256:<hex>")
    pub program_id: String,
    /// Version number (defaults to 1)
    #[serde(default = "default_version")]
    pub version: i32,
    /// ISO-8601 timestamp
    pub registered_at: String,
}

fn default_version() -> i32 {
    1
}

/// Response from an agent's pricing/booking endpoint
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AgentResponse {
    /// Agent-specific response data (price, booking ID, etc.)
    #[serde(flatten)]
    pub data: Value,
    /// Program ID for ZK verification
    pub program_id: String,
    /// ELF hash of the zkVM program
    pub elf_hash: String,
}

/// Helper to serialize any serde-compatible type to bincode bytes
pub fn serialize_input<T: Serialize>(input: &T) -> Result<Vec<u8>, bincode::Error> {
    bincode::serialize(input)
}

/// Helper to deserialize bincode bytes to any serde-compatible type
pub fn deserialize_output<T: for<'de> Deserialize<'de>>(bytes: &[u8]) -> Result<T, bincode::Error> {
    bincode::deserialize(bytes)
}

/// Convert bincode bytes to JSON array format for HTTP transport
pub fn bytes_to_json_array(bytes: &[u8]) -> Value {
    Value::Array(bytes.iter().map(|b| Value::Number((*b).into())).collect())
}

/// Extract bytes from JSON array format
pub fn json_array_to_bytes(value: &Value) -> Option<Vec<u8>> {
    if let Value::Array(arr) = value {
        Some(arr.iter().filter_map(|v| v.as_u64().map(|n| n as u8)).collect())
    } else {
        None
    }
}