newton-core 0.4.16

newton protocol core sdk
//! `newt_signedRead` wire schema — Path A* of PDS §9.2.
//!
//! Three pure-data types crossing the gateway / operator boundary:
//!
//! - [`ResponseStatus`] — `Pending` (operator-default, gateway-default before
//!   `commit_sig` overlay) or `Confirmed` (gateway has attached an on-chain
//!   `StateCommitRegistry` cert proving the response root is finalized).
//! - [`SignedReadRequest`] — the *inner* payload of an
//!   `Authenticated<SignedReadRequest>` envelope. The gateway wraps it in an
//!   `OperatorRpcCall` EIP-712 envelope before forwarding to the operator,
//!   binding `(method, paramsHash, chainId, expiresAt, taskManager)` to the
//!   gateway signer's identity. The request itself carries no signature,
//!   nonce, or timestamp — replay protection is the envelope's `expiresAt`.
//!   Carries the raw leaf-key preimage; the operator constructs the typed
//!   `NamespaceKey` server-side so a malicious client cannot bypass the
//!   namespace prefix by submitting a hand-crafted 32-byte hash.
//! - [`SignedReadResponse`] — operator → gateway → caller. Operator-signed
//!   (Ed25519 over a domain-separated digest) so the proof bundle is
//!   self-verifying against an on-chain BLS-committed state root. The gateway
//!   overlays `commit_sig` and promotes `status` to `Confirmed` when it can
//!   anchor the response's `(state_root, sequence_no)` to a finalized
//!   `StateCommitRegistry` entry; otherwise `status` stays `Pending` and
//!   `commit_sig` echoes the BLS aggregate held in Redis pre-finalization.
//!
//! The response-digest helper (`signed_read_response_digest`) lives in
//! `crates/operator/src/rpc.rs` — it is a handler concern, not wire schema,
//! and it pulls operator-internal types like `state_tree::NamespaceKey`.
//! Caller-identity authentication and rate-limiting run at the gateway under
//! the gateway-authoritative auth model; the operator only verifies the
//! gateway envelope signature and per-namespace integrity invariants. The
//! `commit_sig` and `status` overlay logic lives in the gateway; the operator
//! always emits `status: Pending` and `commit_sig: None`, and the gateway
//! upgrades both on the way out.

use alloy::primitives::{Address, Bytes, B256};
use serde::{Deserialize, Serialize};

/// Gateway-set finalization status for a signed-read response.
///
/// Operators always emit [`ResponseStatus::Pending`]; the gateway promotes
/// the field to [`ResponseStatus::Confirmed`] when it can anchor the
/// response's `(state_root, sequence_no)` to a finalized
/// `StateCommitRegistry` entry. The status is NOT bound into the operator's
/// digest — verifiers check `commit_sig` independently.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ResponseStatus {
    /// `commit_sig` is either absent or sourced from Redis (BLS quorum
    /// aggregate held by the gateway pre-finalization). The
    /// `(state_root, sequence_no)` could be replaced by the next on-chain
    /// commit; not cross-chain verifiable. Suitable for real-time UI reads.
    #[default]
    Pending,
    /// `commit_sig` is sourced from on-chain `StateCommitRegistry`; the
    /// `(state_root, sequence_no)` is finalized and cross-chain verifiable.
    /// Suitable for audit trails and dispute resolution.
    Confirmed,
}

/// Read request for a single JMT leaf, addressed to a per-chain operator.
///
/// This is the *inner* payload of an `Authenticated<SignedReadRequest>`
/// envelope. The gateway is the only authorized caller — it wraps this
/// request in an `OperatorRpcCall` EIP-712 envelope and forwards under
/// Path A*. The envelope's `paramsHash = keccak256(bincode::serialize(req))`
/// binds the request bytes to the gateway signer's signature; replay
/// protection is the envelope's `expiresAt`, not a per-request nonce.
///
/// `leaf_key` carries the *raw* preimage bytes — the operator constructs the
/// typed `NamespaceKey` server-side so a malicious client cannot bypass the
/// namespace prefix by submitting a hand-crafted 32-byte key hash.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SignedReadRequest {
    /// Chain id the read is scoped to. Bound into the envelope's
    /// `chainId` so a request authorized for chain A cannot be replayed
    /// against chain B's operator.
    pub chain_id: u64,
    /// Namespace selector as a raw byte. Validated server-side via
    /// `NamespaceId::from_byte`. Carried as `u8` because `NamespaceId` does
    /// not derive serde — keeps the wire schema in `core` independent of
    /// the operator's `state_tree` dependency.
    pub namespace: u8,
    /// Raw preimage bytes for the JMT key (NOT the 32-byte key hash).
    pub leaf_key: Bytes,
    /// Policy client this request is associated with. Bound into the
    /// operator-side namespace integrity check per PDS §9.2 (request must
    /// match the on-chain link or grant); identity-of-caller is enforced
    /// at the gateway, not here.
    pub policy_client: Address,
}

/// Operator-signed response payload.
///
/// `value`, `root`, `sequence_no`, `content_hash`, `operator_sig`, and
/// `operator_ed25519_pubkey` are populated by the operator. `proof_bytes` is
/// populated for any non-empty tree; it is `None` only when the JMT has no
/// committed version yet. `status` and `commit_sig` are gateway-set overlay
/// fields — the operator always emits `Pending` / `None` and the gateway
/// promotes both when it can anchor the response to a `StateCommitRegistry`
/// entry. Neither `status` nor `commit_sig` is bound into the operator's
/// digest; verifiers check `commit_sig` against the operator-quorum APK
/// independently.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SignedReadResponse {
    /// Leaf value bytes, or `None` if the key is absent.
    pub value: Option<Bytes>,
    /// Borsh-encoded `LeafProof`. `None` only for empty trees (no committed
    /// JMT version yet); populated for every served read otherwise.
    pub proof_bytes: Option<Bytes>,
    /// Root the proof verifies against. For an empty tree this is the JMT
    /// placeholder root.
    pub root: B256,
    /// JMT sequence number / version this proof was generated at. Zero for
    /// an empty tree.
    pub sequence_no: u64,
    /// Domain-separated keccak256 of the leaf value (or absent marker). Lets
    /// callers verify content integrity from `value` alone without
    /// recomputing the full response digest.
    pub content_hash: B256,
    /// 64-byte Ed25519 signature over `signed_read_response_digest`.
    pub operator_sig: Bytes,
    /// 32-byte Ed25519 verifying key used to verify `operator_sig`.
    /// Convenience echo only. Per PDS §9.2, verifiers SHOULD re-derive this
    /// locally from the operator's on-chain ECDSA key in `OperatorRegistry`
    /// (HKDF-SHA256, salt `b"newton-pds-signed-read-v1"`) and reject any
    /// response where the echo diverges from the locally-derived key.
    pub operator_ed25519_pubkey: B256,
    /// Gateway-set finalization status. Operators always emit
    /// [`ResponseStatus::Pending`]; the gateway promotes to `Confirmed`
    /// when it has an on-chain `StateCommitRegistry` cert for
    /// `(root, sequence_no)`.
    #[serde(default)]
    pub status: ResponseStatus,
    /// Gateway-overlayed BLS aggregate over `(root, sequence_no)`. `None`
    /// from the operator; `Some` after gateway overlay (sourced from Redis
    /// for `Pending`, from on-chain `StateCommitRegistry` for `Confirmed`).
    /// Verifiers check this against the operator-quorum APK independently —
    /// it is NOT bound into the operator's `operator_sig` digest.
    #[serde(default)]
    pub commit_sig: Option<Bytes>,
}