soma-som-core 0.1.0

Universal soma(som) structural primitives — Quad / Tree / Ring / Genesis / Fingerprint / TemporalLedger / CrossingRecord
Documentation
// SPDX-License-Identifier: LGPL-3.0-only

//! Abstract signing provider trait for ring crossing attestation.
//!
//! Per the boundary-attestation pattern: the signing algorithm (Ed25519, BLAKE3, CBOR view)
//! is a §13.2 implementation-variable and lives in the application tier.
//! This module defines the §13.1 trait surface: an abstract provider that
//! `Boundary` depends on, leaving algorithm selection to the application tier.
//!
//! `NoopSigner` provides a zero-signature implementation for engine unit tests
//! that do not require cryptographic correctness.

use crate::crossing::CrossingRecord;

/// Abstract crossing-record signer.
///
/// Implementors provide the cryptographic algorithm and key management.
/// The engine depends only on this trait — concrete signing (e.g. Ed25519
/// over canonical CBOR per OPUS §8) lives in the consuming application.
pub trait CrossingSigner: Send + Sync + std::fmt::Debug {
    /// Sign a crossing record and return the 64-byte signature bytes.
    ///
    /// The signing input and domain separation are defined by the implementor.
    /// Production implementations MUST follow OPUS §8 (canonical CBOR signing input):
    /// `b"crossing:" ∥ blake3(canonical_cbor(CrossingRecordSigningView))`.
    fn sign(&self, record: &CrossingRecord) -> Result<[u8; 64], SigningProviderError>;

    /// Return the raw verifying key bytes (32 bytes) for this signer.
    fn verifying_key_bytes(&self) -> [u8; 32];
}

/// Errors from the signing provider.
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum SigningProviderError {
    /// CBOR encoding of the signing view failed.
    #[error("CBOR encoding failed: {reason}")]
    CborEncoding {
        /// Failure description.
        reason: String,
    },

    /// Signing operation failed.
    #[error("Signing failed: {reason}")]
    SigningFailed {
        /// Failure description.
        reason: String,
    },
}

/// No-op signer for engine unit tests.
///
/// Returns `[0u8; 64]` for all signatures and `[0u8; 32]` as the verifying key.
/// Sufficient for tests that exercise cycle lifecycle, chain hash computation,
/// and routing without requiring cryptographic correctness.
#[derive(Debug, Default, Clone)]
pub struct NoopSigner;

impl CrossingSigner for NoopSigner {
    fn sign(&self, _record: &CrossingRecord) -> Result<[u8; 64], SigningProviderError> {
        Ok([0u8; 64])
    }

    fn verifying_key_bytes(&self) -> [u8; 32] {
        [0u8; 32]
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::{CrossingType, UnitId};

    fn dummy_record() -> CrossingRecord {
        let chain_hash = CrossingRecord::compute_chain_hash(
            UnitId::FU,
            UnitId::MU,
            1,
            1,
            CrossingType::Vertical,
            1_000_000,
            &[0u8; 32],
        );
        CrossingRecord {
            source: UnitId::FU,
            destination: UnitId::MU,
            cycle_index: 1,
            sequence_number: 1,
            crossing_type: CrossingType::Vertical,
            timestamp_ns: 1_000_000,
            prev_hash: [0u8; 32],
            chain_hash,
            signature: [0u8; 64],
        }
    }

    #[test]
    fn noop_signer_returns_zeroes() {
        let signer = NoopSigner;
        let rec = dummy_record();
        let sig = signer.sign(&rec).unwrap();
        assert_eq!(sig, [0u8; 64]);
        assert_eq!(signer.verifying_key_bytes(), [0u8; 32]);
    }
}