newton-core 0.4.16

newton protocol core sdk
//! PCR0 commitment provider for state-commit proposals.
//!
//! [`Pcr0Provider`] is the trait the state-commit orchestrator consults each
//! 120s tick to obtain the `pcr0Commitment` field of `StateCommit`. The trait
//! isolates the aggregator (orchestrator consumer) from the operator (enclave
//! owner) so both crates depend on `core` without a cross-edge.
//!
//! The production implementation is `EnclavePcr0Provider` in
//! `crates/operator/src/enclave.rs` (NEWT-1116), which delegates to
//! `EnclaveClient::pcr0_hash()` — a cached VSOCK/loopback attestation fetch.
//!
//! ## Crate placement
//!
//! Lives in `core` because both the aggregator (orchestrator) and the operator
//! (`state_commit_rpc::OperatorContext::pcr0_provider`) consume the trait.
//! Hosting it in `aggregator` would create an `operator → aggregator` crate
//! cross-edge between sibling service crates that otherwise both depend
//! cleanly on `core` + `chainio`.
//!
//! ## Async + timeout convention
//!
//! [`Pcr0Provider::pcr0_commitment`] is `async` because the production
//! `EnclavePcr0Provider` (NEWT-1116) round-trips a request over VSOCK to
//! fetch a fresh attestation document from the Nitro Enclave. Implementations
//! may cache aggressively but the trait makes no synchrony guarantee.
//! Call-sites MUST wrap the call in [`tokio::time::timeout`] with a duration
//! well below the 120s commit cadence — recommended 5s — and treat timeout
//! as [`Pcr0Error::Unavailable`]. The orchestrator skips the tick on any
//! [`Pcr0Error`] variant rather than poisoning the registry view.
//!
//! ## Stub safety model (`dev-stub` feature)
//!
//! [`StubPcr0Provider`] returns
//! [`crate::pcr0_sentinels::STATE_COMMIT_STUB_PCR0_HASH`], a sentinel never
//! seeded into any `EnclaveVersionRegistry`. The Phase 1
//! `InvalidPcr0Commitment` (`0x6dfbfc74`) check reverts only on `bytes32(0)`,
//! so a non-zero stub hash does NOT surface as a typed on-chain revert today
//! — full whitelist enforcement is deferred to Phase 3 (see
//! `docs/PRIVATE_DATA_STORAGE.md` §7 for the registry error catalog and the
//! `StateCommitRegistry` contract source). Phase 1 wrong-environment safety
//! depends on operators refusing to BLS-sign a `StateCommit` whose
//! `pcr0Commitment` is unrecognized, plus off-chain
//! `StateTreeAnomalyDetected(anomalyKind = 0x04 tee_pcr0_unknown)` events
//! (`docs/PRIVATE_DATA_STORAGE.md` §7.5). Treat stub leakage to stagef/mainnet
//! as a deployment regression operators MUST refuse to sign — not as
//! something the registry catches on its own.
//!
//! The stub type is gated behind `#[cfg(any(test, feature = "dev-stub"))]` —
//! release binaries omit the `dev-stub` feature so a deployment regression
//! cannot accidentally link the stub. Test builds activate it implicitly via
//! `cfg(test)`.
//!
//! ## Error semantics
//!
//! The orchestrator treats every [`Pcr0Error`] variant as a *skip-this-tick*
//! signal, not a poison signal. The next tick re-reads the registry view and
//! rebuilds the proposal from scratch — consistent with the broader rule that
//! state-commit ticks are stateless across iterations.

use alloy::primitives::B256;
use async_trait::async_trait;
use thiserror::Error;

#[cfg(any(test, feature = "dev-stub"))]
use crate::pcr0_sentinels::STATE_COMMIT_STUB_PCR0_HASH;

/// Errors surfaced by a [`Pcr0Provider`] when the PCR0 commitment cannot be
/// produced for the current tick.
#[derive(Debug, Error)]
pub enum Pcr0Error {
    /// The attestation source replied but the document was unparseable
    /// (CBOR shape mismatch, truncated payload, signature decode failure).
    #[error("attestation document parse failed: {0}")]
    Parse(String),

    /// The attestation source is unreachable (transport error, timeout, no
    /// enclave running). Skip the tick rather than poison the registry view.
    #[error("attestation source unavailable: {0}")]
    Unavailable(String),

    /// The attestation was retrieved but PCR0 was missing or zero. Indicates
    /// a misconfigured or non-Nitro enclave image.
    #[error("PCR0 missing or zero in attestation document")]
    InvalidPcr0,
}

/// Source of `pcr0Commitment` values for state-commit proposals.
///
/// Implementations may cache aggressively; the orchestrator calls
/// [`pcr0_commitment`](Pcr0Provider::pcr0_commitment) every 120s tick and
/// expects bounded latency. Call-sites MUST wrap the call in
/// [`tokio::time::timeout`] (recommended 5s) and treat timeout as
/// [`Pcr0Error::Unavailable`] — the trait makes no synchrony guarantee.
///
/// `Send + Sync + 'static` lets the orchestrator hold the provider behind
/// [`std::sync::Arc`] across `tokio::spawn` boundaries.
#[async_trait]
pub trait Pcr0Provider: Send + Sync + 'static {
    /// Returns the PCR0 commitment hash to embed in the next state-commit
    /// proposal.
    ///
    /// # Errors
    ///
    /// Any error variant signals the orchestrator to skip the current tick;
    /// the next tick reattempts after re-reading the registry view.
    async fn pcr0_commitment(&self) -> Result<B256, Pcr0Error>;
}

/// Development stub returning [`STATE_COMMIT_STUB_PCR0_HASH`] — a sentinel
/// hash that is never whitelisted in any `EnclaveVersionRegistry`.
///
/// Use only in unit tests, integration tests, or local devnet runs. The
/// `dev-stub` Cargo feature is **off** by default so this type cannot link
/// into a release binary; tests activate it implicitly via `cfg(test)`.
/// Production binaries receive [`Pcr0Provider`] via dependency injection
/// from `EnclavePcr0Provider` in `crates/operator/src/enclave.rs`.
#[cfg(any(test, feature = "dev-stub"))]
#[derive(Debug, Default, Clone, Copy)]
pub struct StubPcr0Provider;

#[cfg(any(test, feature = "dev-stub"))]
impl StubPcr0Provider {
    /// Constructs a new stub provider. `const` so call-sites can place it in
    /// `static` slots if needed.
    pub const fn new() -> Self {
        Self
    }
}

#[cfg(any(test, feature = "dev-stub"))]
#[async_trait]
impl Pcr0Provider for StubPcr0Provider {
    async fn pcr0_commitment(&self) -> Result<B256, Pcr0Error> {
        Ok(*STATE_COMMIT_STUB_PCR0_HASH)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::Arc;

    #[tokio::test]
    async fn stub_returns_sentinel_hash() {
        let stub = StubPcr0Provider::new();
        let pcr0 = stub.pcr0_commitment().await.expect("stub never errors");
        assert_eq!(pcr0, *STATE_COMMIT_STUB_PCR0_HASH);
    }

    #[tokio::test]
    async fn trait_is_object_safe_behind_arc() {
        let provider: Arc<dyn Pcr0Provider> = Arc::new(StubPcr0Provider::new());
        let pcr0 = provider.pcr0_commitment().await.expect("stub never errors");
        assert_eq!(pcr0, *STATE_COMMIT_STUB_PCR0_HASH);
    }

    #[tokio::test]
    async fn stub_is_deterministic_across_calls() {
        let stub = StubPcr0Provider::new();
        let first = stub.pcr0_commitment().await.expect("stub never errors");
        let second = stub.pcr0_commitment().await.expect("stub never errors");
        assert_eq!(first, second);
    }
}