rialo-types 0.5.0-alpha.0

Rialo Types
Documentation
// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! Domain-separation prefixes and intent constants for attestable record types.
//!
//! Every record that validators attest to (handover, snapshot, write-set,
//! ordered-block) needs two constants:
//!
//! 1. A **digest domain prefix** — a unique byte string prepended to the Blake3
//!    hash input so that two records with identical field values but different
//!    types produce different digests.
//!
//! 2. An **intent prefix** — a `[scope, version, app_id]` triple prepended to
//!    the signing message so that Ed25519 signatures are bound to a specific
//!    intent scope (mirrors `shared_crypto::intent::IntentScope` but inlined to
//!    avoid pulling `shared-crypto` into RISC-V / PDK builds).
//!
//! Adding a new record type requires adding a variant to [`RecordKind`]; the
//! exhaustive match arms in [`RecordKind::digest_prefix`] and
//! [`RecordKind::intent_prefix`] will force you to supply both constants, and
//! the module-level tests assert uniqueness automatically.
//!
//! Changing any existing value is **hash-breaking** or **signature-breaking**
//! and requires a stop-the-world deployment.

use super::attestation_framework::INTENT_PREFIX_LENGTH;

/// Enumerates every attestable record type in the system.
///
/// Used as the single source of truth for digest domain prefixes and intent
/// signing prefixes. Tests iterate [`Self::ALL`] to enforce uniqueness.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum RecordKind {
    Handover,
    Snapshot,
    WriteSet,
    OrderedBlock,
    EpochComplete,
}

impl RecordKind {
    /// Blake3 domain-separation prefix fed to `compute_record_digest`.
    pub const fn digest_prefix(self) -> &'static [u8] {
        match self {
            RecordKind::Handover => b"RIALO_HANDOVER_RECORD_V1",
            RecordKind::Snapshot => b"RIALO_SNAPSHOT_RECORD_V1",
            RecordKind::WriteSet => b"RIALO_WRITE_SET_RECORD_V1",
            RecordKind::OrderedBlock => b"RIALO_ORDERED_BLOCK_RECORD_V1",
            RecordKind::EpochComplete => b"RIALO_EPOCH_COMPLETE_V1",
        }
    }

    /// Intent scope byte (`IntentScope` discriminant from `shared-crypto`).
    pub const fn intent_scope(self) -> u8 {
        match self {
            RecordKind::Handover => 11,
            RecordKind::Snapshot => 10,
            RecordKind::WriteSet => 12,
            RecordKind::OrderedBlock => 15,
            RecordKind::EpochComplete => 13,
        }
    }

    /// `[scope, version=V0=0, app_id=Consensus=2]` triple for attestation signing.
    pub const fn intent_prefix(self) -> [u8; INTENT_PREFIX_LENGTH] {
        [self.intent_scope(), 0, 2]
    }
}

#[cfg(test)]
impl RecordKind {
    const ALL: &[RecordKind] = &[
        RecordKind::Handover,
        RecordKind::Snapshot,
        RecordKind::WriteSet,
        RecordKind::OrderedBlock,
        RecordKind::EpochComplete,
    ];
}

#[cfg(test)]
mod tests {
    use std::collections::HashSet;

    use super::*;

    #[test]
    fn all_covers_every_variant() {
        assert_eq!(
            RecordKind::ALL.len(),
            5,
            "RecordKind::ALL must list every variant; update it when adding a new record type"
        );
    }

    #[test]
    fn digest_prefixes_are_unique() {
        let prefixes: HashSet<&[u8]> = RecordKind::ALL.iter().map(|k| k.digest_prefix()).collect();
        assert_eq!(
            prefixes.len(),
            RecordKind::ALL.len(),
            "two record kinds share a digest domain prefix"
        );
    }

    #[test]
    fn intent_scopes_are_unique() {
        let scopes: HashSet<u8> = RecordKind::ALL.iter().map(|k| k.intent_scope()).collect();
        assert_eq!(
            scopes.len(),
            RecordKind::ALL.len(),
            "two record kinds share an intent scope"
        );
    }

    #[test]
    fn intent_prefixes_match_shared_crypto() {
        use shared_crypto::intent::IntentScope;

        assert_eq!(
            RecordKind::Handover.intent_scope(),
            IntentScope::HandoverAttestation as u8,
        );
        assert_eq!(
            RecordKind::Snapshot.intent_scope(),
            IntentScope::SnapshotAttestation as u8,
        );
        assert_eq!(
            RecordKind::WriteSet.intent_scope(),
            IntentScope::WriteSetAttestation as u8,
        );
        assert_eq!(
            RecordKind::OrderedBlock.intent_scope(),
            IntentScope::OrderedBlockAttestation as u8,
        );
        assert_eq!(
            RecordKind::EpochComplete.intent_scope(),
            IntentScope::EpochComplete as u8,
        );
    }
}