chio-settle 0.1.2

Settlement runtime for Chio web3 escrow and bond execution
Documentation
use chio_core::canonical::canonical_json_bytes;
#[cfg(test)]
use chio_core::crypto::Keypair;
use chio_core::crypto::{sha256_hex, PublicKey, Signature};
use chio_core::economic_continuity::EconomicTerminalResultV1;
use chio_core::receipt::body::ChioReceipt;
use serde::{Deserialize, Serialize};

use super::validation::{validate_digest, validate_positive};
use super::{ChannelError, VerifiedAdmittedChannelReservationV1};

pub const CHANNEL_TERMINAL_OUTCOME_COMMITMENT_SCHEMA: &str =
    "chio.channel.terminal-outcome-commitment.v1";

const CHANNEL_TERMINAL_OUTCOME_SIGNING_DOMAIN: &[u8] =
    b"CHIO-CHANNEL-TERMINAL-OUTCOME-COMMITMENT-V1\0";

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ChannelTerminalOutcomeCommitmentBodyV1 {
    pub schema: String,
    pub operation_id: String,
    pub reservation_id: String,
    pub reservation_digest: String,
    pub receipt_id: String,
    pub receipt_digest: String,
    pub terminal_result: EconomicTerminalResultV1,
    pub outcome_recorded_at_unix_ms: u64,
    pub terminalized_at_unix_ms: u64,
}

impl ChannelTerminalOutcomeCommitmentBodyV1 {
    pub fn validate(&self) -> Result<(), ChannelError> {
        if self.schema != CHANNEL_TERMINAL_OUTCOME_COMMITMENT_SCHEMA {
            return Err(ChannelError::InvalidField(
                "channel_terminal_outcome_schema",
            ));
        }
        validate_digest("channel_terminal_outcome_operation_id", &self.operation_id)?;
        validate_digest(
            "channel_terminal_outcome_reservation_id",
            &self.reservation_id,
        )?;
        validate_digest(
            "channel_terminal_outcome_reservation_digest",
            &self.reservation_digest,
        )?;
        super::validation::validate_text("channel_terminal_outcome_receipt_id", &self.receipt_id)?;
        validate_digest(
            "channel_terminal_outcome_receipt_digest",
            &self.receipt_digest,
        )?;
        self.terminal_result
            .validate()
            .map_err(|_| ChannelError::AuthorityVerification)?;
        validate_positive(
            "channel_terminal_outcome_recorded_at",
            self.outcome_recorded_at_unix_ms,
        )?;
        validate_positive(
            "channel_terminal_outcome_terminalized_at",
            self.terminalized_at_unix_ms,
        )?;
        if self.outcome_recorded_at_unix_ms > self.terminalized_at_unix_ms {
            return Err(ChannelError::AuthorityVerification);
        }
        Ok(())
    }

    #[doc(hidden)]
    pub fn signing_bytes(&self) -> Result<Vec<u8>, ChannelError> {
        self.validate()?;
        let canonical = canonical_json_bytes(self)
            .map_err(|error| ChannelError::Canonicalization(error.to_string()))?;
        let mut bytes =
            Vec::with_capacity(CHANNEL_TERMINAL_OUTCOME_SIGNING_DOMAIN.len() + canonical.len());
        bytes.extend_from_slice(CHANNEL_TERMINAL_OUTCOME_SIGNING_DOMAIN);
        bytes.extend_from_slice(&canonical);
        Ok(bytes)
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct SignedChannelTerminalOutcomeCommitmentV1 {
    pub body: ChannelTerminalOutcomeCommitmentBodyV1,
    #[serde(deserialize_with = "super::signed::deserialize_canonical_public_key")]
    pub kernel_key: PublicKey,
    #[serde(deserialize_with = "super::signed::deserialize_canonical_signature")]
    pub kernel_signature: Signature,
}

impl SignedChannelTerminalOutcomeCommitmentV1 {
    #[cfg(test)]
    pub(crate) fn sign_for_test(
        reservation: &VerifiedAdmittedChannelReservationV1,
        receipt: &ChioReceipt,
        terminal_result: EconomicTerminalResultV1,
        outcome_recorded_at_unix_ms: u64,
        terminalized_at_unix_ms: u64,
        kernel_keypair: &Keypair,
    ) -> Result<Self, ChannelError> {
        let artifact = reservation.artifact();
        let body = ChannelTerminalOutcomeCommitmentBodyV1 {
            schema: CHANNEL_TERMINAL_OUTCOME_COMMITMENT_SCHEMA.to_owned(),
            operation_id: artifact.body.operation_id.clone(),
            reservation_id: artifact.body.reservation_id.clone(),
            reservation_digest: artifact.digest()?,
            receipt_id: receipt.id.clone(),
            receipt_digest: receipt_digest(receipt)?,
            terminal_result,
            outcome_recorded_at_unix_ms,
            terminalized_at_unix_ms,
        };
        let kernel_signature = kernel_keypair.sign(&body.signing_bytes()?);
        Ok(Self {
            body,
            kernel_key: kernel_keypair.public_key(),
            kernel_signature,
        })
    }

    pub fn verify_signature(&self) -> Result<(), ChannelError> {
        if self
            .kernel_key
            .verify(&self.body.signing_bytes()?, &self.kernel_signature)
        {
            Ok(())
        } else {
            Err(ChannelError::AuthorityVerification)
        }
    }
}

#[derive(Debug, Clone)]
pub struct VerifiedChannelTerminalOutcomeCommitmentV1 {
    artifact: SignedChannelTerminalOutcomeCommitmentV1,
}

impl VerifiedChannelTerminalOutcomeCommitmentV1 {
    #[must_use]
    pub const fn artifact(&self) -> &SignedChannelTerminalOutcomeCommitmentV1 {
        &self.artifact
    }

    #[must_use]
    pub const fn terminal_result(&self) -> &EconomicTerminalResultV1 {
        &self.artifact.body.terminal_result
    }

    #[must_use]
    pub const fn outcome_recorded_at_unix_ms(&self) -> u64 {
        self.artifact.body.outcome_recorded_at_unix_ms
    }

    #[must_use]
    pub const fn terminalized_at_unix_ms(&self) -> u64 {
        self.artifact.body.terminalized_at_unix_ms
    }
}

pub fn verify_channel_terminal_outcome_commitment(
    artifact: &SignedChannelTerminalOutcomeCommitmentV1,
    trusted_kernel_key: &PublicKey,
    reservation: &VerifiedAdmittedChannelReservationV1,
    receipt: &ChioReceipt,
) -> Result<VerifiedChannelTerminalOutcomeCommitmentV1, ChannelError> {
    artifact.body.validate()?;
    artifact.verify_signature()?;
    let reservation = reservation.artifact();
    if &artifact.kernel_key != trusted_kernel_key
        || &receipt.kernel_key != trusted_kernel_key
        || !receipt
            .verify_signature()
            .map_err(|_| ChannelError::AuthorityVerification)?
        || artifact.body.operation_id != reservation.body.operation_id
        || artifact.body.reservation_id != reservation.body.reservation_id
        || artifact.body.reservation_digest != reservation.digest()?
        || artifact.body.receipt_id != receipt.id
        || artifact.body.receipt_digest != receipt_digest(receipt)?
        || receipt.timestamp != artifact.body.terminalized_at_unix_ms / 1_000
    {
        return Err(ChannelError::AuthorityVerification);
    }
    Ok(VerifiedChannelTerminalOutcomeCommitmentV1 {
        artifact: artifact.clone(),
    })
}

fn receipt_digest(receipt: &ChioReceipt) -> Result<String, ChannelError> {
    Ok(sha256_hex(&canonical_json_bytes(receipt).map_err(
        |error| ChannelError::Canonicalization(error.to_string()),
    )?))
}