saddle-core 0.2.0-rc.20

Shared contracts for Saddle components
Documentation
use super::*;
use ring::rand::SystemRandom;
use ring::signature::{Ed25519KeyPair, KeyPair};

fn key() -> Ed25519KeyPair {
    let bytes = Ed25519KeyPair::generate_pkcs8(&SystemRandom::new()).unwrap();
    Ed25519KeyPair::from_pkcs8(bytes.as_ref()).unwrap()
}
fn hex(value: [u8; 32]) -> String {
    value.iter().map(|byte| format!("{byte:02x}")).collect()
}
fn id(value: u8) -> String { hex([value; 32]) }

fn fixture(key_identity: [u8; 32], authority_identity: [u8; 32]) -> RendezvousManifestDocument {
    let common = [id(1), id(2), id(3)];
    RendezvousManifestDocument {
        schema: SCHEMA.into(), signature_domain: DOMAIN.into(), usage: USAGE.into(),
        algorithm: "Ed25519".into(), key_identity: hex(key_identity),
        authority_identity: hex(authority_identity), five_leaf_v2_subject_identity: id(4),
        five_leaf_v2_observation_identity: id(5), approved_lock_identity: id(6),
        root_schema_identity: id(7), composer_schema_identity: id(8),
        common_identities: common.clone(), owner_generation: 9,
        leaves: LeavesDocument {
            service: ServiceDocument { leaf_identity: id(10), common_identities: common.clone(),
                owner_generation: 9, route_type_closure_identity: id(11),
                routes: vec![ServiceRouteDocument { ordinal: 0, route_id: 12,
                    managed_commitment_bytes: 13, managed_objects_peak: 14,
                    db_connections: 1, db_operations: 1 }] },
            runtime: RuntimeDocument { leaf_identity: id(15), common_identities: common.clone(),
                owner_generation: 9, component_proof_identity: id(16),
                expected_calibration_provenance: core::array::from_fn(|index| id(17 + index as u8)),
                solver_schema_identity: id(26), admission_layout_identity: id(27),
                build_costs: [[1; 7]; 4], support_limits: [1; 4], topology_constants: [1; 3],
                routes: vec![RuntimeRouteDocument { ordinal: 0, route_id: 12,
                    framework_bytes: 28, task_storage_bytes: 29, response_carrier_bytes: 30 }] },
            calibration: CalibrationDocument { leaf_identity: id(31), common_identities: common.clone(),
                owner_generation: 9, provenance: core::array::from_fn(|index| id(32 + index as u8)),
                fixed_costs: [1; 7], allocator_costs: [1; 2], calibrated_constants: [1; 3] },
            admission: AdmissionDocument { leaf_identity: id(41), common_identities: common.clone(),
                owner_generation: 9, solver_schema_identity: id(42), checked_relation_identity: id(43),
                admission_layout_identity: id(44) },
            observability: ObservabilityDocument { leaf_identity: id(45), common_identities: common.clone(),
                owner_generation: 9, machine_identity: id(46),
                component_identities: [id(47), id(48), id(49), id(50)], profile: [1; 6],
                config_schema_identity: id(51), config_identity: id(52), config_values: [1; 5],
                work_identity: id(53), sizes: [1; 4], alignments: [1; 4], offsets: [0, 1, 2, 3],
                aggregate_layout: [4, 1], payload_machine_bytes: 1, source_identity: id(54),
                build_identity: id(55), root_identity: id(56), constituent_identity: id(57),
                queue_state_layout: QueueStateLayoutDocument { schema_identity: id(58),
                    type_identity: id(59), target_identity: id(60), size_bytes: 1,
                    alignment_bytes: 1, block_count: 1, reserve_bytes: 1,
                    authority_receipt_identity: id(61) } },
        },
        normalized: NormalizedDocument { costs: [[1; 7]; 5], allocator_costs: [1; 2],
            support_limits: [1; 4], closure_constants: [1; 6],
            routes: vec![NormalizedRouteDocument { ordinal: 0, route_id: 12,
                managed_commitment_bytes: 13, managed_objects_peak: 14, framework_bytes: 28,
                task_storage_bytes: 29, response_carrier_bytes: 30, db_connections: 1, db_operations: 1 }],
            observability_layout_digest_values: [1; 20], root_identity: id(62),
            costs_attestation_identity: id(63), support_attestation_identity: id(64),
            route_set_attestation_identity: id(65) },
        termination: TerminationDocument { leaf_identity: id(66), common_identities: common,
            owner_generation: 9, topology: [1; 4], topology_identity: id(67),
            db_normal_return_work_identity: id(68), writer_work_identity: id(69),
            runtime_work_identity: id(70), composed_identity: id(71) },
        constituent_commitments: CommitmentsDocument { service_capacity: id(72),
            runtime_capacity: id(73), runtime_work: id(74), admission_relation: id(75),
            db_normal_return_work: id(76), writer_work: id(77), observability: id(78) },
    }
}

fn applicability(key: &Ed25519KeyPair) -> VerifiedRendezvousManifestKeyApplicabilityReceipt {
    VerifiedRendezvousManifestKeyApplicabilityReceipt {
        public_key: key.public_key().as_ref().try_into().unwrap(),
        key_identity: sha256(key.public_key().as_ref()), authority_identity: [80; 32],
        other_key_identities: core::array::from_fn(|index| [81 + index as u8; 32]),
        other_authority_identities: core::array::from_fn(|index| [90 + index as u8; 32]),
        domain: sha256(DOMAIN.as_bytes()), usage: sha256(USAGE.as_bytes()),
    }
}
fn signed(document: &RendezvousManifestDocument, key: &Ed25519KeyPair) -> CanonicalSignedRendezvousManifestInput {
    let canonical = serde_json::to_vec(document).unwrap();
    let signature = key.sign(&canonical).as_ref().to_vec();
    CanonicalSignedRendezvousManifestInput { canonical, signature }
}

#[test]
fn canonical_signed_structure_accepts_only_original_route_and_identity_shape() {
    let signer = key();
    let document = fixture(sha256(signer.public_key().as_ref()), [80; 32]);
    assert!(verify_rendezvous_manifest_structural_preflight(applicability(&signer), signed(&document, &signer)).is_ok());
    let mut drift = fixture(sha256(signer.public_key().as_ref()), [80; 32]);
    drift.leaves.runtime.routes[0].route_id += 1;
    assert_eq!(verify_rendezvous_manifest_structural_preflight(applicability(&signer), signed(&drift, &signer)).unwrap_err().0, RendezvousManifestPreflightError::Shape);
    let mut uppercase = fixture(sha256(signer.public_key().as_ref()), [80; 32]);
    uppercase.leaves.service.leaf_identity = "AA".repeat(32);
    assert_eq!(verify_rendezvous_manifest_structural_preflight(applicability(&signer), signed(&uppercase, &signer)).unwrap_err().0, RendezvousManifestPreflightError::Shape);
}

#[test]
fn noncanonical_and_foreign_signature_fail_closed() {
    let signer = key();
    let document = fixture(sha256(signer.public_key().as_ref()), [80; 32]);
    let mut input = signed(&document, &signer); input.canonical.push(b' ');
    assert_eq!(verify_rendezvous_manifest_structural_preflight(applicability(&signer), input).unwrap_err().0, RendezvousManifestPreflightError::Encoding);
    let foreign = key();
    assert_eq!(verify_rendezvous_manifest_structural_preflight(applicability(&signer), signed(&document, &foreign)).unwrap_err().0, RendezvousManifestPreflightError::Signature);
}

#[test]
fn applicability_rejects_duplicate_key_and_authority_before_parsing() {
    let applicability = VerifiedRendezvousManifestKeyApplicabilityReceipt {
        public_key: [1; 32], key_identity: sha256(&[1; 32]), authority_identity: [2; 32],
        other_key_identities: [sha256(&[1; 32]); 8], other_authority_identities: [[2; 32]; 8],
        domain: sha256(DOMAIN.as_bytes()), usage: sha256(USAGE.as_bytes()),
    };
    let input = CanonicalSignedRendezvousManifestInput { canonical: b"{}".to_vec(), signature: vec![0; 64] };
    assert_eq!(verify_rendezvous_manifest_structural_preflight(applicability, input).unwrap_err().0, RendezvousManifestPreflightError::Foreign);
}