use ring::signature::{ED25519, UnparsedPublicKey};
use serde::Deserialize;
use sha2::{Digest, Sha256};
/// Applicability for the repository-external approved five-leaf generation
/// signing key. Production construction intentionally remains outside this
/// repository.
pub struct VerifiedGenerationKeyApplicabilityReceipt {
public_key: [u8; 32],
key_identity: [u8; 32],
source: [u8; 32],
build: [u8; 32],
machine: [u8; 32],
}
/// Nominal half issued by the signed deployment applicability verifier.
pub struct VerifiedSignedDeploymentGenerationOwner {
generation: u64,
source: [u8; 32],
build: [u8; 32],
}
/// Nominal half issued from one approved canonical five-leaf receipt.
pub struct VerifiedGeneratedComponentGenerationOwner {
generation: u64,
source: [u8; 32],
build: [u8; 32],
machine: [u8; 32],
root: [u8; 32],
actual_composed: Option<[u8; 32]>,
signed_subject: [u8; 32],
issuance_nonce: [u8; 32],
}
impl core::fmt::Debug for VerifiedGeneratedComponentGenerationOwner {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("VerifiedGeneratedComponentGenerationOwner(<redacted>)")
}
}
/// Single-use equality receipt. It carries no numeric getter and cannot be
/// constructed, cloned or serialized by a consumer.
pub struct VerifiedComponentGenerationPairingReceipt {
binding: [u8; 32],
}
pub(crate) fn deployment_generation_half(
generation: u64,
source: [u8; 32],
build: [u8; 32],
) -> VerifiedSignedDeploymentGenerationOwner {
VerifiedSignedDeploymentGenerationOwner {
generation,
source,
build,
}
}
#[cfg(test)]
pub(crate) fn fixture_generated_generation_half(
generation: u64,
) -> VerifiedGeneratedComponentGenerationOwner {
VerifiedGeneratedComponentGenerationOwner {
generation,
source: [5; 32],
build: [4; 32],
machine: [0x41; 32],
root: [0x42; 32],
actual_composed: Some([0x43; 32]),
signed_subject: [0x44; 32],
issuance_nonce: [0x45; 32],
}
}
#[cfg(test)]
pub(crate) fn fixture_generated_generation_v2_owner(
generation: u64,
) -> VerifiedGeneratedComponentGenerationV2Owner {
let subject = [0xc1; 32];
let nonce = [0xc2; 32];
VerifiedGeneratedComponentGenerationV2Owner {
aggregate: VerifiedGeneratedComponentGenerationOwner {
generation,
source: [5; 32],
build: [4; 32],
machine: [0x33; 32],
root: [0x34; 32],
actual_composed: Some([0x35; 32]),
signed_subject: subject,
issuance_nonce: nonce,
},
observability: VerifiedObservabilityPairedReceipt {
build: VerifiedObservabilityBuildStaticOwner {
subject,
issuance_nonce: nonce,
constituent: [0xc3; 32],
},
half: VerifiedFiveLeafObservabilityV2Half {
half: VerifiedObservabilityConstituentGenerationHalf {
subject,
issuance_nonce: nonce,
constituent: [0xc3; 32],
leaf: [0xc4; 32],
profile: [1; 6],
component_identities: [[0xc5; 32]; 4],
config_schema: [0xc6; 32],
config: [0xc7; 32],
config_values: [1; 5],
work: [0xc8; 32],
sizes: [1; 4],
alignments: [1; 4],
offsets: [0; 4],
aggregate_layout: [1, 1],
payload_machine_bytes: 1,
source: [5; 32],
build: [4; 32],
machine: [0x33; 32],
root: [0x34; 32],
generation,
},
},
},
}
}
#[cfg(test)]
pub(crate) fn fixture_upgrade_generated_generation_v2(
aggregate: VerifiedGeneratedComponentGenerationOwner,
) -> VerifiedGeneratedComponentGenerationV2FinalTransaction {
let mut owner = fixture_generated_generation_v2_owner(1);
owner.aggregate = aggregate;
begin_generated_component_generation_v2_final_transaction(owner)
}
/// Final consumption is deliberately fact-free: all equality and provenance
/// checks occurred while both opaque halves were present.
pub fn consume_component_generation_pairing(
receipt: VerifiedComponentGenerationPairingReceipt,
) -> Result<(), VerifiedComponentGenerationPairingReceipt> {
if receipt.binding != [0; 32] {
Ok(())
} else {
Err(receipt)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GenerationPairError {
Encoding,
Signature,
Foreign,
}
#[cfg(test)]
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct FiveLeafGenerationDocument {
schema: String,
domain: String,
usage: String,
key_identity: String,
source_identity: String,
build_identity: String,
machine_identity: String,
root_identity: String,
owner_generation: u64,
}
/// Complete signed input consumed by the only v2 verifier. The bytes and
/// detached signature are returned intact on every failure.
pub struct CanonicalSignedFiveLeafGenerationV2Input {
canonical: Vec<u8>,
signature: Vec<u8>,
}
impl CanonicalSignedFiveLeafGenerationV2Input {
#[cfg(test)]
fn new(canonical: Vec<u8>, signature: Vec<u8>) -> Self {
Self {
canonical,
signature,
}
}
}
pub(crate) const FIVE_LEAF_V2_DOMAIN: &[u8] = b"saddle/f08/five-leaf-generation/v2";
pub(crate) const FIVE_LEAF_V2_USAGE: &[u8] = b"capacity.five-leaf.generation";
pub(crate) const APPROVED_OBSERVATION_SHA256: [u8; 32] = [
0x6e, 0x6b, 0x0f, 0x8e, 0xf9, 0xed, 0xfa, 0x53, 0x0b, 0x0c, 0xfe, 0x32, 0x6f, 0xc5, 0xd3, 0x83,
0x04, 0x1c, 0xa3, 0x27, 0x73, 0x2a, 0x79, 0xb4, 0x8e, 0xda, 0x15, 0xaa, 0xb7, 0xe7, 0x5f, 0x13,
];
const APPROVED_OBSERVABILITY_LEAF: [u8; 32] = [
0xb4, 0x67, 0x26, 0xfd, 0x1a, 0x64, 0xc8, 0xb9, 0xbd, 0x50, 0xa8, 0x87, 0x0c, 0xcd, 0x1c, 0x3a,
0x3a, 0xee, 0xdb, 0x48, 0x1c, 0xb0, 0x12, 0x22, 0xbc, 0xb7, 0xf5, 0xe6, 0xe1, 0xe1, 0x4e, 0xe8,
];
/// A checked, by-value request for an external custody service. It is not a
/// signing authority and intentionally exposes no field or clone operation.
pub struct FiveLeafV2SigningRequest {
canonical: Vec<u8>,
request_nonce: [u8; 32],
observation_sha256: [u8; 32],
approved_lock: [u8; 32],
source: [u8; 32],
build: [u8; 32],
machine: [u8; 32],
root: [u8; 32],
generation: u64,
key_identity: [u8; 32],
domain: [u8; 32],
usage: [u8; 32],
approval_identity: [u8; 32],
custody_policy_identity: [u8; 32],
}
/// Untrusted detached signer output captured as one opaque value. Authority is
/// conferred only by `verify_five_leaf_v2_signing_response`.
pub struct FiveLeafV2SignerResponse {
canonical: Vec<u8>,
request_nonce: [u8; 32],
signature: Vec<u8>,
public_key: [u8; 32],
key_identity: [u8; 32],
domain: [u8; 32],
usage: [u8; 32],
approval_identity: [u8; 32],
custody_policy_identity: [u8; 32],
}
/// Approved public-key applicability. This checkpoint deliberately provides
/// no production issuer: key approval and custody remain external STOP assets.
pub struct VerifiedFiveLeafV2SignerPolicyOwner {
public_key: [u8; 32],
key_identity: [u8; 32],
approved_lock: [u8; 32],
source: [u8; 32],
build: [u8; 32],
machine: [u8; 32],
root: [u8; 32],
generation: u64,
observation_sha256: [u8; 32],
domain: [u8; 32],
usage: [u8; 32],
approval_identity: [u8; 32],
custody_policy_identity: [u8; 32],
request_nonce: [u8; 32],
}
/// Single-use request reservation produced in the same receipt transaction as
/// the signer policy. It has no raw nonce constructor or getter.
pub struct VerifiedFiveLeafV2SigningRequestApprovalOwner {
request_nonce: [u8; 32],
approval_identity: [u8; 32],
custody_policy_identity: [u8; 32],
approved_lock: [u8; 32],
observation_sha256: [u8; 32],
source: [u8; 32],
build: [u8; 32],
machine: [u8; 32],
root: [u8; 32],
generation: u64,
}
pub(crate) struct FiveLeafV2ApprovedSignerFacts {
pub(crate) public_key: [u8; 32],
pub(crate) key_identity: [u8; 32],
pub(crate) approved_lock: [u8; 32],
pub(crate) source: [u8; 32],
pub(crate) build: [u8; 32],
pub(crate) machine: [u8; 32],
pub(crate) root: [u8; 32],
pub(crate) generation: u64,
pub(crate) observation_sha256: [u8; 32],
pub(crate) domain: [u8; 32],
pub(crate) usage: [u8; 32],
pub(crate) approval_identity: [u8; 32],
pub(crate) custody_policy_identity: [u8; 32],
pub(crate) request_nonce: [u8; 32],
}
pub(crate) fn issue_five_leaf_v2_signer_policy(
facts: FiveLeafV2ApprovedSignerFacts,
) -> (
VerifiedFiveLeafV2SignerPolicyOwner,
VerifiedFiveLeafV2SigningRequestApprovalOwner,
) {
(
VerifiedFiveLeafV2SignerPolicyOwner {
public_key: facts.public_key,
key_identity: facts.key_identity,
approved_lock: facts.approved_lock,
source: facts.source,
build: facts.build,
machine: facts.machine,
root: facts.root,
generation: facts.generation,
observation_sha256: facts.observation_sha256,
domain: facts.domain,
usage: facts.usage,
approval_identity: facts.approval_identity,
custody_policy_identity: facts.custody_policy_identity,
request_nonce: facts.request_nonce,
},
VerifiedFiveLeafV2SigningRequestApprovalOwner {
request_nonce: facts.request_nonce,
approval_identity: facts.approval_identity,
custody_policy_identity: facts.custody_policy_identity,
approved_lock: facts.approved_lock,
observation_sha256: facts.observation_sha256,
source: facts.source,
build: facts.build,
machine: facts.machine,
root: facts.root,
generation: facts.generation,
},
)
}
macro_rules! redacted_signing_debug {
($type:ty, $name:literal) => {
impl core::fmt::Debug for $type {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(concat!($name, "(<redacted>)"))
}
}
};
}
redacted_signing_debug!(FiveLeafV2SigningRequest, "FiveLeafV2SigningRequest");
redacted_signing_debug!(FiveLeafV2SignerResponse, "FiveLeafV2SignerResponse");
redacted_signing_debug!(
VerifiedFiveLeafV2SignerPolicyOwner,
"VerifiedFiveLeafV2SignerPolicyOwner"
);
redacted_signing_debug!(
VerifiedFiveLeafV2SigningRequestApprovalOwner,
"VerifiedFiveLeafV2SigningRequestApprovalOwner"
);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FiveLeafV2SigningError {
Encoding,
Foreign,
Signature,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct FiveLeafV2SignerResponseDocument {
schema: String,
canonical: Vec<u8>,
request_nonce: String,
signature: Vec<u8>,
public_key: String,
key_identity: String,
domain: String,
usage: String,
approval_identity: String,
custody_policy_identity: String,
}
/// Parses the canonical transport envelope without treating it as authority.
/// A malformed envelope is returned byte-for-byte for retry or audit.
#[doc(hidden)]
pub fn capture_untrusted_five_leaf_v2_signer_response(
envelope: Vec<u8>,
) -> Result<FiveLeafV2SignerResponse, (FiveLeafV2SigningError, Vec<u8>)> {
if !canonical_json(&envelope) {
return Err((FiveLeafV2SigningError::Encoding, envelope));
}
let document: FiveLeafV2SignerResponseDocument = match serde_json::from_slice(&envelope) {
Ok(value) => value,
Err(_) => return Err((FiveLeafV2SigningError::Encoding, envelope)),
};
let Some(request_nonce) = decode_identity(&document.request_nonce) else {
return Err((FiveLeafV2SigningError::Encoding, envelope));
};
let Some(public_key) = decode_identity(&document.public_key) else {
return Err((FiveLeafV2SigningError::Encoding, envelope));
};
let Some(key_identity) = decode_identity(&document.key_identity) else {
return Err((FiveLeafV2SigningError::Encoding, envelope));
};
let Some(domain) = decode_identity(&document.domain) else {
return Err((FiveLeafV2SigningError::Encoding, envelope));
};
let Some(usage) = decode_identity(&document.usage) else {
return Err((FiveLeafV2SigningError::Encoding, envelope));
};
let Some(approval_identity) = decode_identity(&document.approval_identity) else {
return Err((FiveLeafV2SigningError::Encoding, envelope));
};
let Some(custody_policy_identity) = decode_identity(&document.custody_policy_identity) else {
return Err((FiveLeafV2SigningError::Encoding, envelope));
};
if document.schema != "saddle-f08-five-leaf-v2-signer-response/1"
|| document.signature.len() != 64
{
return Err((FiveLeafV2SigningError::Encoding, envelope));
}
Ok(FiveLeafV2SignerResponse {
canonical: document.canonical,
request_nonce,
signature: document.signature,
public_key,
key_identity,
domain,
usage,
approval_identity,
custody_policy_identity,
})
}
/// Checks the exact production observation and policy-bound subject before an
/// external signing request may leave Gate custody. The policy is returned so
/// the same non-clone owner must later authorize the response.
#[allow(clippy::result_large_err)]
pub fn prepare_five_leaf_v2_signing_request(
canonical: Vec<u8>,
production_observation: Vec<u8>,
policy: VerifiedFiveLeafV2SignerPolicyOwner,
approval: VerifiedFiveLeafV2SigningRequestApprovalOwner,
) -> Result<
(
FiveLeafV2SigningRequest,
VerifiedFiveLeafV2SignerPolicyOwner,
),
(
FiveLeafV2SigningError,
Vec<u8>,
Vec<u8>,
VerifiedFiveLeafV2SignerPolicyOwner,
VerifiedFiveLeafV2SigningRequestApprovalOwner,
),
> {
macro_rules! fail {
($error:expr) => {
return Err(($error, canonical, production_observation, policy, approval))
};
}
if approval.request_nonce == [0; 32]
|| sha256(&production_observation) != APPROVED_OBSERVATION_SHA256
|| approval.approved_lock == [0; 32]
|| approval.approved_lock != policy.approved_lock
|| approval.observation_sha256 != policy.observation_sha256
|| approval.source != policy.source
|| approval.build != policy.build
|| approval.machine != policy.machine
|| approval.root != policy.root
|| approval.generation != policy.generation
|| approval.approval_identity != policy.approval_identity
|| approval.custody_policy_identity != policy.custody_policy_identity
|| approval.request_nonce != policy.request_nonce
|| !canonical_json(&canonical)
{
fail!(FiveLeafV2SigningError::Foreign);
}
let doc: FiveLeafGenerationV2Document = match serde_json::from_slice(&canonical) {
Ok(value) => value,
Err(_) => fail!(FiveLeafV2SigningError::Encoding),
};
let Some(source) = decode_identity(&doc.source_identity) else {
fail!(FiveLeafV2SigningError::Encoding);
};
let Some(build) = decode_identity(&doc.build_identity) else {
fail!(FiveLeafV2SigningError::Encoding);
};
let Some(machine) = decode_identity(&doc.machine_identity) else {
fail!(FiveLeafV2SigningError::Encoding);
};
let Some(root) = decode_identity(&doc.root_identity) else {
fail!(FiveLeafV2SigningError::Encoding);
};
let Some(key_identity) = decode_identity(&doc.key_identity) else {
fail!(FiveLeafV2SigningError::Encoding);
};
if doc.schema != "saddle-f08-five-leaf-generation/2"
|| doc.domain != hex(&sha256(FIVE_LEAF_V2_DOMAIN))
|| doc.usage != hex(&sha256(FIVE_LEAF_V2_USAGE))
|| decode_identity(&doc.observability.leaf_identity) != Some(APPROVED_OBSERVABILITY_LEAF)
|| source != policy.source
|| build != policy.build
|| machine != policy.machine
|| root != policy.root
|| doc.owner_generation != policy.generation
|| policy.observation_sha256 != APPROVED_OBSERVATION_SHA256
|| key_identity != policy.key_identity
|| policy.key_identity != sha256(&policy.public_key)
|| policy.domain != sha256(FIVE_LEAF_V2_DOMAIN)
|| policy.usage != sha256(FIVE_LEAF_V2_USAGE)
|| doc.owner_generation == 0
{
fail!(FiveLeafV2SigningError::Foreign);
}
Ok((
FiveLeafV2SigningRequest {
canonical,
request_nonce: approval.request_nonce,
observation_sha256: APPROVED_OBSERVATION_SHA256,
approved_lock: approval.approved_lock,
source,
build,
machine,
root,
generation: doc.owner_generation,
key_identity,
domain: policy.domain,
usage: policy.usage,
approval_identity: policy.approval_identity,
custody_policy_identity: policy.custody_policy_identity,
},
policy,
))
}
/// The only authority-upgrading response verifier. Every rejection restores
/// all three opaque inputs for an original-pair retry.
#[allow(clippy::result_large_err)]
pub fn verify_five_leaf_v2_signing_response(
request: FiveLeafV2SigningRequest,
response: FiveLeafV2SignerResponse,
policy: VerifiedFiveLeafV2SignerPolicyOwner,
) -> Result<
(
CanonicalSignedFiveLeafGenerationV2Input,
VerifiedGenerationKeyApplicabilityReceipt,
),
(
FiveLeafV2SigningError,
FiveLeafV2SigningRequest,
FiveLeafV2SignerResponse,
VerifiedFiveLeafV2SignerPolicyOwner,
),
> {
macro_rules! fail {
($error:expr) => {
return Err(($error, request, response, policy))
};
}
if request.canonical != response.canonical
|| request.request_nonce != response.request_nonce
|| request.observation_sha256 != APPROVED_OBSERVATION_SHA256
|| request.approved_lock != policy.approved_lock
|| request.source != policy.source
|| request.build != policy.build
|| request.machine != policy.machine
|| request.root != policy.root
|| request.generation != policy.generation
|| request.observation_sha256 != policy.observation_sha256
|| request.key_identity != response.key_identity
|| response.key_identity != policy.key_identity
|| sha256(&response.public_key) != policy.key_identity
|| response.public_key != policy.public_key
|| request.domain != response.domain
|| response.domain != policy.domain
|| request.usage != response.usage
|| response.usage != policy.usage
|| request.approval_identity != response.approval_identity
|| response.approval_identity != policy.approval_identity
|| request.custody_policy_identity != response.custody_policy_identity
|| response.custody_policy_identity != policy.custody_policy_identity
{
fail!(FiveLeafV2SigningError::Foreign);
}
if UnparsedPublicKey::new(&ED25519, response.public_key)
.verify(&request.canonical, &response.signature)
.is_err()
{
fail!(FiveLeafV2SigningError::Signature);
}
Ok((
CanonicalSignedFiveLeafGenerationV2Input {
canonical: request.canonical,
signature: response.signature,
},
VerifiedGenerationKeyApplicabilityReceipt {
public_key: policy.public_key,
key_identity: policy.key_identity,
source: policy.source,
build: policy.build,
machine: policy.machine,
},
))
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct FiveLeafGenerationV2Document {
schema: String,
domain: String,
usage: String,
key_identity: String,
source_identity: String,
build_identity: String,
machine_identity: String,
root_identity: String,
owner_generation: u64,
observability: ObservabilityConstituentDocument,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct ObservabilityConstituentDocument {
domain: String,
leaf_identity: String,
profile_identity: String,
profile: [u64; 6],
component_identities: [String; 4],
config_schema_identity: String,
config_identity: String,
config_values: [u64; 5],
work_identity: String,
sizes: [u64; 4],
alignments: [u64; 4],
offsets: [u64; 4],
aggregate_layout: [u64; 2],
payload_machine_bytes: u64,
source_identity: String,
build_identity: String,
machine_identity: String,
root_identity: String,
owner_generation: u64,
constituent_identity: String,
}
struct VerifiedObservabilityConstituentGenerationHalf {
subject: [u8; 32],
issuance_nonce: [u8; 32],
constituent: [u8; 32],
leaf: [u8; 32],
profile: [u64; 6],
component_identities: [[u8; 32]; 4],
config_schema: [u8; 32],
config: [u8; 32],
config_values: [u64; 5],
work: [u8; 32],
sizes: [u64; 4],
alignments: [u64; 4],
offsets: [u64; 4],
aggregate_layout: [u64; 2],
payload_machine_bytes: u64,
source: [u8; 32],
build: [u8; 32],
machine: [u8; 32],
root: [u8; 32],
generation: u64,
}
pub struct VerifiedFiveLeafGenerationV2Owner {
aggregate: VerifiedGeneratedComponentGenerationOwner,
observability_build: VerifiedObservabilityBuildStaticOwner,
observability: VerifiedObservabilityConstituentGenerationHalf,
subject: [u8; 32],
issuance_nonce: [u8; 32],
}
pub struct VerifiedFiveLeafGenerationV2AggregateHalf {
owner: VerifiedGeneratedComponentGenerationOwner,
}
pub struct VerifiedObservabilityBuildStaticOwner {
subject: [u8; 32],
issuance_nonce: [u8; 32],
constituent: [u8; 32],
}
pub struct VerifiedFiveLeafObservabilityV2Half {
half: VerifiedObservabilityConstituentGenerationHalf,
}
pub struct VerifiedObservabilityPairedReceipt {
build: VerifiedObservabilityBuildStaticOwner,
half: VerifiedFiveLeafObservabilityV2Half,
}
pub struct VerifiedFiveLeafGenerationV2PairedOwner {
aggregate: VerifiedFiveLeafGenerationV2AggregateHalf,
observability: VerifiedObservabilityPairedReceipt,
view: FiveLeafGenerationV2NonAuthorityView,
}
pub struct VerifiedObservabilityCapacityView {
leaf: [u8; 32],
profile: [u64; 6],
component_identities: [[u8; 32]; 4],
sizes: [u64; 4],
alignments: [u64; 4],
offsets: [u64; 4],
aggregate_layout: [u64; 2],
payload_machine_bytes: u64,
work: [u8; 32],
machine: [u8; 32],
generation: u64,
}
pub struct VerifiedGeneratedComponentGenerationV2Owner {
#[allow(dead_code)]
aggregate: VerifiedGeneratedComponentGenerationOwner,
#[allow(dead_code)]
observability: VerifiedObservabilityPairedReceipt,
}
pub struct VerifiedGeneratedComponentGenerationV2FinalTransaction {
owner: VerifiedGeneratedComponentGenerationV2Owner,
}
pub struct FiveLeafGenerationV2NonAuthorityView {
subject: [u8; 32],
issuance_nonce: [u8; 32],
}
macro_rules! redacted_debug {
($($ty:ty),+ $(,)?) => {$ (
impl core::fmt::Debug for $ty {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(concat!(stringify!($ty), "(<redacted>)"))
}
}
)+};
}
redacted_debug!(
VerifiedGenerationKeyApplicabilityReceipt,
CanonicalSignedFiveLeafGenerationV2Input,
VerifiedFiveLeafGenerationV2Owner,
VerifiedFiveLeafGenerationV2AggregateHalf,
VerifiedObservabilityBuildStaticOwner,
VerifiedFiveLeafObservabilityV2Half,
VerifiedObservabilityPairedReceipt,
VerifiedFiveLeafGenerationV2PairedOwner,
VerifiedObservabilityCapacityView,
VerifiedGeneratedComponentGenerationV2Owner,
VerifiedGeneratedComponentGenerationV2FinalTransaction,
FiveLeafGenerationV2NonAuthorityView,
);
impl VerifiedFiveLeafGenerationV2PairedOwner {
pub fn observability_capacity_view(&self) -> VerifiedObservabilityCapacityView {
let half = &self.observability.half.half;
VerifiedObservabilityCapacityView {
leaf: half.leaf,
profile: half.profile,
component_identities: half.component_identities,
sizes: half.sizes,
alignments: half.alignments,
offsets: half.offsets,
aggregate_layout: half.aggregate_layout,
payload_machine_bytes: half.payload_machine_bytes,
work: half.work,
machine: half.machine,
generation: half.generation,
}
}
}
pub fn begin_generated_component_generation_v2_final_transaction(
owner: VerifiedGeneratedComponentGenerationV2Owner,
) -> VerifiedGeneratedComponentGenerationV2FinalTransaction {
VerifiedGeneratedComponentGenerationV2FinalTransaction { owner }
}
pub fn restore_generated_component_generation_v2_final_transaction(
transaction: VerifiedGeneratedComponentGenerationV2FinalTransaction,
) -> VerifiedGeneratedComponentGenerationV2Owner {
transaction.owner
}
impl VerifiedGeneratedComponentGenerationV2FinalTransaction {
pub(crate) fn pair_subject_recovering(
self,
subject: crate::VerifiedSignedDirectorySubjectContinuation,
) -> Result<
(
Self,
crate::VerifiedSignedDirectorySubjectContinuation,
VerifiedComponentGenerationPairingReceipt,
),
(Self, crate::VerifiedSignedDirectorySubjectContinuation),
> {
let VerifiedGeneratedComponentGenerationV2Owner {
aggregate,
observability,
} = self.owner;
match subject.authority.pair_generation_recovering(aggregate) {
Ok((authority, aggregate, receipt)) => Ok((
Self {
owner: VerifiedGeneratedComponentGenerationV2Owner {
aggregate,
observability,
},
},
crate::VerifiedSignedDirectorySubjectContinuation { authority },
receipt,
)),
Err((authority, aggregate)) => Err((
Self {
owner: VerifiedGeneratedComponentGenerationV2Owner {
aggregate,
observability,
},
},
crate::VerifiedSignedDirectorySubjectContinuation { authority },
)),
}
}
}
impl VerifiedObservabilityCapacityView {
pub fn leaf_identity(&self) -> [u8; 32] {
self.leaf
}
pub fn profile(&self) -> [u64; 6] {
self.profile
}
pub fn component_identities(&self) -> [[u8; 32]; 4] {
self.component_identities
}
pub fn sizes(&self) -> [u64; 4] {
self.sizes
}
pub fn alignments(&self) -> [u64; 4] {
self.alignments
}
pub fn offsets(&self) -> [u64; 4] {
self.offsets
}
pub fn aggregate_layout(&self) -> [u64; 2] {
self.aggregate_layout
}
pub fn payload_machine_bytes(&self) -> u64 {
self.payload_machine_bytes
}
pub fn work_identity(&self) -> [u8; 32] {
self.work
}
pub fn machine_identity(&self) -> [u8; 32] {
self.machine
}
pub fn owner_generation(&self) -> u64 {
self.generation
}
}
#[allow(clippy::result_large_err)]
pub fn pair_verified_five_leaf_generation_v2(
owner: VerifiedFiveLeafGenerationV2Owner,
) -> Result<VerifiedFiveLeafGenerationV2PairedOwner, VerifiedFiveLeafGenerationV2Owner> {
if owner.aggregate.signed_subject != owner.subject
|| owner.aggregate.issuance_nonce != owner.issuance_nonce
|| owner.observability_build.subject != owner.subject
|| owner.observability_build.issuance_nonce != owner.issuance_nonce
|| owner.observability.subject != owner.subject
|| owner.observability.issuance_nonce != owner.issuance_nonce
|| owner.observability_build.constituent != owner.observability.constituent
{
return Err(owner);
}
let VerifiedFiveLeafGenerationV2Owner {
aggregate,
observability_build,
observability,
subject,
issuance_nonce,
} = owner;
Ok(VerifiedFiveLeafGenerationV2PairedOwner {
aggregate: VerifiedFiveLeafGenerationV2AggregateHalf { owner: aggregate },
observability: VerifiedObservabilityPairedReceipt {
build: observability_build,
half: VerifiedFiveLeafObservabilityV2Half {
half: observability,
},
},
view: FiveLeafGenerationV2NonAuthorityView {
subject,
issuance_nonce,
},
})
}
#[allow(clippy::result_large_err, clippy::too_many_arguments)]
pub fn bind_generated_component_generation_v2(
mut paired: VerifiedFiveLeafGenerationV2PairedOwner,
actual_source: [u8; 32],
actual_machine: [u8; 32],
actual_root: [u8; 32],
actual_generation: u64,
actual_composed: [u8; 32],
) -> Result<VerifiedGeneratedComponentGenerationV2Owner, VerifiedFiveLeafGenerationV2PairedOwner> {
let aggregate = &mut paired.aggregate.owner;
let observability = &paired.observability.half.half;
let build = &paired.observability.build;
if aggregate.source == actual_source
&& aggregate.machine == actual_machine
&& aggregate.root == actual_root
&& aggregate.generation == actual_generation
&& aggregate.signed_subject == observability.subject
&& aggregate.issuance_nonce == observability.issuance_nonce
&& paired.view.subject == observability.subject
&& paired.view.issuance_nonce == observability.issuance_nonce
&& build.subject == observability.subject
&& build.issuance_nonce == observability.issuance_nonce
&& build.constituent == observability.constituent
&& observability.config_schema != [0; 32]
&& observability.config != [0; 32]
&& !observability.config_values.contains(&0)
&& observability.build != [0; 32]
&& observability.source == actual_source
&& observability.machine == actual_machine
&& observability.root == actual_root
&& observability.generation == actual_generation
&& actual_composed != [0; 32]
{
aggregate.actual_composed = Some(actual_composed);
Ok(VerifiedGeneratedComponentGenerationV2Owner {
aggregate: paired.aggregate.owner,
observability: paired.observability,
})
} else {
Err(paired)
}
}
#[cfg(test)]
pub(crate) fn split_verified_five_leaf_generation_v2(
owner: VerifiedFiveLeafGenerationV2Owner,
) -> (
VerifiedFiveLeafGenerationV2AggregateHalf,
VerifiedObservabilityBuildStaticOwner,
VerifiedFiveLeafObservabilityV2Half,
FiveLeafGenerationV2NonAuthorityView,
) {
let VerifiedFiveLeafGenerationV2Owner {
aggregate,
observability_build,
observability,
subject,
issuance_nonce,
} = owner;
(
VerifiedFiveLeafGenerationV2AggregateHalf { owner: aggregate },
observability_build,
VerifiedFiveLeafObservabilityV2Half {
half: observability,
},
FiveLeafGenerationV2NonAuthorityView {
subject,
issuance_nonce,
},
)
}
#[cfg(test)]
pub(crate) fn restore_verified_five_leaf_generation_v2(
aggregate: VerifiedFiveLeafGenerationV2AggregateHalf,
observability_build: VerifiedObservabilityBuildStaticOwner,
observability: VerifiedFiveLeafObservabilityV2Half,
view: FiveLeafGenerationV2NonAuthorityView,
) -> Result<
VerifiedFiveLeafGenerationV2Owner,
(
VerifiedFiveLeafGenerationV2AggregateHalf,
VerifiedObservabilityBuildStaticOwner,
VerifiedFiveLeafObservabilityV2Half,
FiveLeafGenerationV2NonAuthorityView,
),
> {
if aggregate.owner.signed_subject == view.subject
&& aggregate.owner.issuance_nonce == view.issuance_nonce
&& observability_build.subject == view.subject
&& observability_build.issuance_nonce == view.issuance_nonce
&& observability.half.subject == view.subject
&& observability.half.issuance_nonce == view.issuance_nonce
{
Ok(VerifiedFiveLeafGenerationV2Owner {
aggregate: aggregate.owner,
observability_build,
observability: observability.half,
subject: view.subject,
issuance_nonce: view.issuance_nonce,
})
} else {
Err((aggregate, observability_build, observability, view))
}
}
#[cfg(test)]
pub(crate) fn pair_observability_build_static_v2(
build: VerifiedObservabilityBuildStaticOwner,
half: VerifiedFiveLeafObservabilityV2Half,
) -> Result<
VerifiedObservabilityPairedReceipt,
(
VerifiedObservabilityBuildStaticOwner,
VerifiedFiveLeafObservabilityV2Half,
),
> {
if build.subject == half.half.subject
&& build.issuance_nonce == half.half.issuance_nonce
&& build.constituent == half.half.constituent
{
Ok(VerifiedObservabilityPairedReceipt { build, half })
} else {
Err((build, half))
}
}
struct FixedLayoutDigest([u64; 4]);
impl FixedLayoutDigest {
fn new() -> Self {
Self([
0xcbf2_9ce4_8422_2325,
0x9e37_79b9_7f4a_7c15,
0x6a09_e667_f3bc_c909,
0xbb67_ae85_84ca_a73b,
])
}
fn write(&mut self, bytes: &[u8]) {
const PRIMES: [u64; 4] = [
0x0000_0100_0000_01b3,
0x9e37_79b1_85eb_ca87,
0xc2b2_ae3d_27d4_eb4f,
0x1656_67b1_9e37_79f9,
];
for (index, byte) in bytes.iter().copied().enumerate() {
for (lane, prime) in self.0.iter_mut().zip(PRIMES) {
*lane ^= u64::from(byte).wrapping_add(index as u64);
*lane = lane.wrapping_mul(prime);
*lane ^= *lane >> 29;
}
}
}
fn finish(self) -> [u8; 32] {
let mut output = [0; 32];
for (index, lane) in self.0.into_iter().enumerate() {
output[index * 8..(index + 1) * 8].copy_from_slice(&lane.to_le_bytes());
}
output
}
}
fn approved_observability_layout(
components: [[u8; 32]; 4],
profile: [u64; 6],
config_schema: [u8; 32],
config: [u8; 32],
config_values: [u64; 5],
sizes: [u64; 4],
alignments: [u64; 4],
offsets: [u64; 4],
aggregate: [u64; 2],
payload: u64,
) -> Option<[u8; 32]> {
const EXPECTED_COMPONENTS: [&str; 4] = [
"4d4a1129bb1531eaed4b3d511684c97d989d7fc5e8238cd7f18c45c95c2990ed",
"893bb585b55ce3288f706a155aa74f018f366e66e680c4ea105706d44cf1dbad",
"0451a5bb65e79cd900a0ae7bfcb6118611f0e596e0a9d883edb82de9ea24bc54",
"6d9816b9977cd88a63234fc003786bd05eb3702a70380bbc57a38a239a673e0f",
];
let expected = EXPECTED_COMPONENTS.map(|value| decode_identity(value).unwrap());
let mut schema_digest = FixedLayoutDigest::new();
schema_digest.write(b"saddle.observability.fixed-file-config.schema.v1");
schema_digest.write(b"saddle.observability.fixed-file-config/1;rotate_bytes,retained_files,retention_age_ms,sync_bytes,sync_interval_ms");
if components != expected
|| config_schema != schema_digest.finish()
|| profile != [4, 1_024, 6, 256, 4_096, 32]
|| config_values != [134_217_728, 8, 604_800_000, 4_194_304, 1_000]
|| sizes != [4_856, 1_528, 13_880, 720]
|| alignments != [8; 4]
|| offsets[0] != 0
|| payload != profile[0].checked_mul(profile[1])?
|| payload > sizes[0]
{
return None;
}
let mut cursor = 0_u64;
let mut maximum_alignment = 1_u64;
for index in 0..4 {
let alignment = alignments[index];
if !alignment.is_power_of_two() || sizes[index] == 0 {
return None;
}
maximum_alignment = maximum_alignment.max(alignment);
let aligned = cursor.checked_add(alignment - 1)? & !(alignment - 1);
if offsets[index] != aligned {
return None;
}
cursor = offsets[index].checked_add(sizes[index])?;
}
let aggregate_size = cursor.checked_add(maximum_alignment - 1)? & !(maximum_alignment - 1);
if aggregate != [aggregate_size, maximum_alignment] {
return None;
}
let mut config_digest = FixedLayoutDigest::new();
config_digest.write(b"saddle.observability.fixed-file-config.identity.v1");
config_digest.write(&config_schema);
for value in config_values {
config_digest.write(&value.to_le_bytes());
}
if config_digest.finish() != config {
return None;
}
let mut leaf = FixedLayoutDigest::new();
leaf.write(b"saddle.observability.generated-layout.leaf.v1");
for identity in components {
leaf.write(&identity);
}
leaf.write(&config_schema);
leaf.write(&config);
for value in config_values
.into_iter()
.chain(profile)
.chain(sizes)
.chain(alignments)
.chain(offsets)
.chain(aggregate)
{
leaf.write(&value.to_le_bytes());
}
Some(leaf.finish())
}
/// Upgrades canonical signed bytes into the generated nominal half. There is
/// no raw generation, digest or caller-expected argument.
#[allow(clippy::result_large_err)]
#[cfg(test)]
fn verify_approved_five_leaf_generation_v1_fixture(
applicability: VerifiedGenerationKeyApplicabilityReceipt,
canonical: &[u8],
signature: &[u8],
) -> Result<
VerifiedGeneratedComponentGenerationOwner,
(
GenerationPairError,
VerifiedGenerationKeyApplicabilityReceipt,
),
> {
macro_rules! fail {
($e:expr) => {
return Err(($e, applicability))
};
}
if !canonical_json(canonical)
|| UnparsedPublicKey::new(&ED25519, applicability.public_key)
.verify(canonical, signature)
.is_err()
{
fail!(GenerationPairError::Signature);
}
let doc: FiveLeafGenerationDocument = match serde_json::from_slice(canonical) {
Ok(value) => value,
Err(_) => fail!(GenerationPairError::Encoding),
};
let decode = |value: &str| decode_identity(value);
if doc.schema != "saddle-f08-five-leaf-generation/1"
|| doc.domain != hex(&sha256(b"saddle/f08/five-leaf-generation/v1"))
|| doc.usage != hex(&sha256(b"capacity.five-leaf.generation"))
|| decode(&doc.key_identity) != Some(applicability.key_identity)
|| decode(&doc.source_identity) != Some(applicability.source)
|| decode(&doc.build_identity) != Some(applicability.build)
|| decode(&doc.machine_identity) != Some(applicability.machine)
|| doc.owner_generation == 0
{
fail!(GenerationPairError::Foreign);
}
let Some(root) = decode(&doc.root_identity) else {
fail!(GenerationPairError::Encoding);
};
Ok(VerifiedGeneratedComponentGenerationOwner {
generation: doc.owner_generation,
source: applicability.source,
build: applicability.build,
machine: applicability.machine,
root,
actual_composed: None,
signed_subject: sha256(canonical),
issuance_nonce: sha256(
&[applicability.key_identity.as_slice(), canonical, signature].concat(),
),
})
}
/// Verifies the only accepted v2 signed subject and returns one indivisible
/// owner containing both the aggregate and Observability constituent halves.
#[allow(clippy::result_large_err)]
pub fn verify_approved_five_leaf_generation_v2(
applicability: VerifiedGenerationKeyApplicabilityReceipt,
input: CanonicalSignedFiveLeafGenerationV2Input,
) -> Result<
VerifiedFiveLeafGenerationV2Owner,
(
GenerationPairError,
VerifiedGenerationKeyApplicabilityReceipt,
CanonicalSignedFiveLeafGenerationV2Input,
),
> {
macro_rules! fail {
($error:expr) => {
return Err(($error, applicability, input))
};
}
if !canonical_json(&input.canonical)
|| UnparsedPublicKey::new(&ED25519, applicability.public_key)
.verify(&input.canonical, &input.signature)
.is_err()
{
fail!(GenerationPairError::Signature);
}
let doc: FiveLeafGenerationV2Document = match serde_json::from_slice(&input.canonical) {
Ok(value) => value,
Err(_) => fail!(GenerationPairError::Encoding),
};
let decode = |value: &str| decode_identity(value);
const SCHEMA: &str = "saddle-f08-five-leaf-generation/2";
const DOMAIN: &[u8] = b"saddle/f08/five-leaf-generation/v2";
const OBS_DOMAIN: &[u8] = b"saddle/f08/observability-constituent/v1";
let ids = [
decode(&doc.observability.leaf_identity),
decode(&doc.observability.profile_identity),
decode(&doc.observability.config_schema_identity),
decode(&doc.observability.config_identity),
decode(&doc.observability.work_identity),
decode(&doc.observability.source_identity),
decode(&doc.observability.build_identity),
decode(&doc.observability.machine_identity),
decode(&doc.observability.root_identity),
];
if ids.iter().any(Option::is_none) || doc.observability.profile.contains(&0) {
fail!(GenerationPairError::Encoding);
}
let [
leaf,
profile_identity,
config_schema,
config,
work,
obs_source,
obs_build,
obs_machine,
obs_root,
] = ids.map(Option::unwrap);
let Some(component_identities) = doc
.observability
.component_identities
.iter()
.map(|value| decode(value))
.collect::<Option<Vec<_>>>()
.and_then(|values| values.try_into().ok())
else {
fail!(GenerationPairError::Encoding);
};
let source = decode(&doc.source_identity);
let build = decode(&doc.build_identity);
let machine = decode(&doc.machine_identity);
let root = decode(&doc.root_identity);
if doc.schema != SCHEMA
|| doc.domain != hex(&sha256(DOMAIN))
|| doc.usage != hex(&sha256(b"capacity.five-leaf.generation"))
|| decode(&doc.key_identity) != Some(applicability.key_identity)
|| source != Some(applicability.source)
|| build != Some(applicability.build)
|| machine != Some(applicability.machine)
|| root.is_none()
|| doc.owner_generation == 0
|| obs_source != applicability.source
|| obs_build != applicability.build
|| obs_machine != applicability.machine
|| Some(obs_root) != root
|| doc.observability.owner_generation != doc.owner_generation
|| doc.observability.domain != hex(&sha256(OBS_DOMAIN))
{
fail!(GenerationPairError::Foreign);
}
let Some(recomputed_leaf) = approved_observability_layout(
component_identities,
doc.observability.profile,
config_schema,
config,
doc.observability.config_values,
doc.observability.sizes,
doc.observability.alignments,
doc.observability.offsets,
doc.observability.aggregate_layout,
doc.observability.payload_machine_bytes,
) else {
fail!(GenerationPairError::Foreign);
};
if leaf != recomputed_leaf || profile_identity != component_identities[2] {
fail!(GenerationPairError::Foreign);
}
let profile_bytes = doc.observability.profile.map(u64::to_le_bytes).concat();
let components_bytes = component_identities.concat();
let config_values_bytes = doc
.observability
.config_values
.map(u64::to_le_bytes)
.concat();
let sizes_bytes = doc.observability.sizes.map(u64::to_le_bytes).concat();
let alignments_bytes = doc.observability.alignments.map(u64::to_le_bytes).concat();
let offsets_bytes = doc.observability.offsets.map(u64::to_le_bytes).concat();
let aggregate_bytes = doc
.observability
.aggregate_layout
.map(u64::to_le_bytes)
.concat();
let constituent = sha256(
&[
OBS_DOMAIN,
leaf.as_slice(),
profile_identity.as_slice(),
profile_bytes.as_slice(),
components_bytes.as_slice(),
config_schema.as_slice(),
config.as_slice(),
config_values_bytes.as_slice(),
work.as_slice(),
sizes_bytes.as_slice(),
alignments_bytes.as_slice(),
offsets_bytes.as_slice(),
aggregate_bytes.as_slice(),
&doc.observability.payload_machine_bytes.to_le_bytes(),
obs_source.as_slice(),
obs_build.as_slice(),
obs_machine.as_slice(),
obs_root.as_slice(),
&doc.owner_generation.to_le_bytes(),
]
.concat(),
);
if decode(&doc.observability.constituent_identity) != Some(constituent) {
fail!(GenerationPairError::Foreign);
}
let subject = sha256(&input.canonical);
let issuance_nonce = sha256(
&[
b"saddle/f08/five-leaf-generation/v2/issuance".as_slice(),
applicability.key_identity.as_slice(),
subject.as_slice(),
input.signature.as_slice(),
]
.concat(),
);
let aggregate = VerifiedGeneratedComponentGenerationOwner {
generation: doc.owner_generation,
source: applicability.source,
build: applicability.build,
machine: applicability.machine,
root: root.unwrap(),
actual_composed: None,
signed_subject: subject,
issuance_nonce,
};
let observability = VerifiedObservabilityConstituentGenerationHalf {
subject,
issuance_nonce,
constituent,
leaf,
profile: doc.observability.profile,
component_identities,
config_schema,
config,
config_values: doc.observability.config_values,
work,
sizes: doc.observability.sizes,
alignments: doc.observability.alignments,
offsets: doc.observability.offsets,
aggregate_layout: doc.observability.aggregate_layout,
payload_machine_bytes: doc.observability.payload_machine_bytes,
source: obs_source,
build: obs_build,
machine: obs_machine,
root: obs_root,
generation: doc.owner_generation,
};
Ok(VerifiedFiveLeafGenerationV2Owner {
aggregate,
observability_build: VerifiedObservabilityBuildStaticOwner {
subject,
issuance_nonce,
constituent,
},
observability,
subject,
issuance_nonce,
})
}
/// Consumes the signed half and binds it to Admission's actual five-leaf
/// composition. No compared fact is returned to the caller.
#[allow(clippy::result_large_err, clippy::too_many_arguments)]
pub fn bind_generated_component_generation(
mut generated: VerifiedGeneratedComponentGenerationOwner,
actual_source: [u8; 32],
actual_build: [u8; 32],
actual_machine: [u8; 32],
actual_root: [u8; 32],
actual_generation: u64,
actual_composed: [u8; 32],
) -> Result<
VerifiedGeneratedComponentGenerationOwner,
(
GenerationPairError,
VerifiedGeneratedComponentGenerationOwner,
),
> {
if generated.source == actual_source
&& generated.build == actual_build
&& generated.machine == actual_machine
&& generated.root == actual_root
&& generated.generation == actual_generation
&& actual_composed != [0; 32]
{
generated.actual_composed = Some(actual_composed);
Ok(generated)
} else {
Err((GenerationPairError::Foreign, generated))
}
}
/// Consumes both nominal halves. No raw compare oracle is exposed.
#[allow(clippy::result_large_err)]
pub fn pair_component_generation(
deployment: VerifiedSignedDeploymentGenerationOwner,
generated: VerifiedGeneratedComponentGenerationOwner,
) -> Result<
VerifiedComponentGenerationPairingReceipt,
(
GenerationPairError,
VerifiedSignedDeploymentGenerationOwner,
VerifiedGeneratedComponentGenerationOwner,
),
> {
let actual_composed = match generated.actual_composed {
Some(identity) => identity,
None => return Err((GenerationPairError::Foreign, deployment, generated)),
};
if deployment.generation == generated.generation
&& deployment.source == generated.source
&& deployment.build == generated.build
&& generated.machine != [0; 32]
&& generated.root != [0; 32]
{
Ok(VerifiedComponentGenerationPairingReceipt {
binding: sha256(
&[
deployment.source.as_slice(),
deployment.build.as_slice(),
generated.machine.as_slice(),
generated.root.as_slice(),
actual_composed.as_slice(),
]
.concat(),
),
})
} else {
Err((GenerationPairError::Foreign, deployment, generated))
}
}
/// Verifies the same equality while returning both linear halves. This is
/// used only by the later four-receipt atomic transaction, which must be able
/// to restore every outer owner when another receipt is crossed.
#[allow(clippy::result_large_err)]
pub(crate) fn pair_component_generation_recovering(
deployment: VerifiedSignedDeploymentGenerationOwner,
generated: VerifiedGeneratedComponentGenerationOwner,
) -> Result<
(
VerifiedSignedDeploymentGenerationOwner,
VerifiedGeneratedComponentGenerationOwner,
VerifiedComponentGenerationPairingReceipt,
),
(
GenerationPairError,
VerifiedSignedDeploymentGenerationOwner,
VerifiedGeneratedComponentGenerationOwner,
),
> {
let actual_composed = match generated.actual_composed {
Some(identity) => identity,
None => return Err((GenerationPairError::Foreign, deployment, generated)),
};
if deployment.generation == generated.generation
&& deployment.source == generated.source
&& deployment.build == generated.build
&& generated.machine != [0; 32]
&& generated.root != [0; 32]
{
let receipt = VerifiedComponentGenerationPairingReceipt {
binding: sha256(
&[
deployment.source.as_slice(),
deployment.build.as_slice(),
generated.machine.as_slice(),
generated.root.as_slice(),
actual_composed.as_slice(),
]
.concat(),
),
};
Ok((deployment, generated, receipt))
} else {
Err((GenerationPairError::Foreign, deployment, generated))
}
}
fn canonical_json(value: &[u8]) -> bool {
serde_json::from_slice::<serde_json::Value>(value)
.ok()
.and_then(|v| serde_json::to_vec(&v).ok())
.is_some_and(|v| v == value)
}
fn sha256(value: &[u8]) -> [u8; 32] {
Sha256::digest(value).into()
}
fn decode_identity(value: &str) -> Option<[u8; 32]> {
if value.len() != 64 {
return None;
}
let mut out = [0; 32];
for (i, pair) in value.as_bytes().chunks_exact(2).enumerate() {
out[i] = u8::from_str_radix(std::str::from_utf8(pair).ok()?, 16).ok()?;
}
Some(out)
}
fn hex(value: &[u8; 32]) -> String {
value.iter().map(|b| format!("{b:02x}")).collect()
}
#[cfg(test)]
mod tests {
use super::*;
use ring::{
rand::SystemRandom,
signature::{Ed25519KeyPair, KeyPair},
};
const PRODUCTION_OBSERVATION: &[u8] = b"component_identities=4d4a1129bb1531eaed4b3d511684c97d989d7fc5e8238cd7f18c45c95c2990ed,893bb585b55ce3288f706a155aa74f018f366e66e680c4ea105706d44cf1dbad,0451a5bb65e79cd900a0ae7bfcb6118611f0e596e0a9d883edb82de9ea24bc54,6d9816b9977cd88a63234fc003786bd05eb3702a70380bbc57a38a239a673e0f\nprofile=4,1024,6,256,4096,32\nconfig_schema_identity=64b4654edbd2fe07912a7180eefb20594fbd59028ffaa025407139d5a2ab5058\nconfig_identity=b12186b503b78674b8b0426a66356ec8a9fac1a9e0b322a0aeb425803e7077bb\nconfig_values=134217728,8,604800000,4194304,1000\nsizes=4856,1528,13880,720\nalignments=8,8,8,8\noffsets=0,4856,6384,20264\naggregate_size=20984\naggregate_alignment=8\ncanonical_leaf_identity=b46726fd1a64c8b9bd50a8870ccd1c3a3aeedb481cb01222bcb7f5e6e1e14ee8\n";
fn signing_policy(
key: &Ed25519KeyPair,
approved_lock: [u8; 32],
request_nonce: [u8; 32],
) -> (
VerifiedFiveLeafV2SignerPolicyOwner,
VerifiedFiveLeafV2SigningRequestApprovalOwner,
) {
let public_key: [u8; 32] = key.public_key().as_ref().try_into().unwrap();
issue_five_leaf_v2_signer_policy(FiveLeafV2ApprovedSignerFacts {
public_key,
key_identity: sha256(&public_key),
approved_lock,
source: [31; 32],
build: [32; 32],
machine: [33; 32],
root: [34; 32],
generation: 41,
observation_sha256: APPROVED_OBSERVATION_SHA256,
domain: sha256(FIVE_LEAF_V2_DOMAIN),
usage: sha256(FIVE_LEAF_V2_USAGE),
approval_identity: [72; 32],
custody_policy_identity: [73; 32],
request_nonce,
})
}
fn prepared_signing(
key: &Ed25519KeyPair,
nonce: [u8; 32],
) -> (
FiveLeafV2SigningRequest,
FiveLeafV2SignerResponse,
VerifiedFiveLeafV2SignerPolicyOwner,
) {
let input = v2_input(key, [34; 32], 41);
let canonical = input.canonical;
let (policy, approval) = signing_policy(key, [71; 32], nonce);
let (request, policy) = prepare_five_leaf_v2_signing_request(
canonical.clone(),
PRODUCTION_OBSERVATION.to_vec(),
policy,
approval,
)
.unwrap();
let public_key: [u8; 32] = key.public_key().as_ref().try_into().unwrap();
let envelope = serde_json::to_vec(&serde_json::json!({
"schema":"saddle-f08-five-leaf-v2-signer-response/1",
"canonical":canonical.clone(),
"request_nonce":hex(&nonce),
"signature":key.sign(&request.canonical).as_ref(),
"public_key":hex(&public_key),
"key_identity":hex(&sha256(&public_key)),
"domain":hex(&sha256(FIVE_LEAF_V2_DOMAIN)),
"usage":hex(&sha256(FIVE_LEAF_V2_USAGE)),
"approval_identity":hex(&[72;32]),
"custody_policy_identity":hex(&[73;32]),
}))
.unwrap();
let response = capture_untrusted_five_leaf_v2_signer_response(envelope).unwrap();
(request, response, policy)
}
fn signed_half(generation: u64, root: [u8; 32]) -> VerifiedGeneratedComponentGenerationOwner {
let bytes = Ed25519KeyPair::generate_pkcs8(&SystemRandom::new()).unwrap();
let key = Ed25519KeyPair::from_pkcs8(bytes.as_ref()).unwrap();
let public: [u8; 32] = key.public_key().as_ref().try_into().unwrap();
let applicability = VerifiedGenerationKeyApplicabilityReceipt {
public_key: public,
key_identity: sha256(&public),
source: [31; 32],
build: [32; 32],
machine: [33; 32],
};
let canonical = serde_json::to_vec(&serde_json::json!({
"schema":"saddle-f08-five-leaf-generation/1",
"domain":hex(&sha256(b"saddle/f08/five-leaf-generation/v1")),
"usage":hex(&sha256(b"capacity.five-leaf.generation")),
"key_identity":hex(&sha256(&public)),
"source_identity":hex(&[31;32]),"build_identity":hex(&[32;32]),
"machine_identity":hex(&[33;32]),"root_identity":hex(&root),
"owner_generation":generation,
}))
.unwrap();
match verify_approved_five_leaf_generation_v1_fixture(
applicability,
&canonical,
key.sign(&canonical).as_ref(),
) {
Ok(owner) => owner,
Err(_) => panic!("valid signed receipt must verify"),
}
}
fn v2_input(
key: &Ed25519KeyPair,
root: [u8; 32],
generation: u64,
) -> CanonicalSignedFiveLeafGenerationV2Input {
let profile = [4_u64, 1024, 6, 256, 4096, 32];
let components = [
"4d4a1129bb1531eaed4b3d511684c97d989d7fc5e8238cd7f18c45c95c2990ed",
"893bb585b55ce3288f706a155aa74f018f366e66e680c4ea105706d44cf1dbad",
"0451a5bb65e79cd900a0ae7bfcb6118611f0e596e0a9d883edb82de9ea24bc54",
"6d9816b9977cd88a63234fc003786bd05eb3702a70380bbc57a38a239a673e0f",
]
.map(|value| decode_identity(value).unwrap());
let config_values: [u64; 5] = [134_217_728, 8, 604_800_000, 4_194_304, 1_000];
let sizes: [u64; 4] = [4_856, 1_528, 13_880, 720];
let alignments: [u64; 4] = [8; 4];
let offsets: [u64; 4] = [0, 4_856, 6_384, 20_264];
let aggregate_layout: [u64; 2] = [20_984, 8];
let payload_machine_bytes: u64 = 4_096;
let mut config_schema_digest = FixedLayoutDigest::new();
config_schema_digest.write(b"saddle.observability.fixed-file-config.schema.v1");
config_schema_digest.write(b"saddle.observability.fixed-file-config/1;rotate_bytes,retained_files,retention_age_ms,sync_bytes,sync_interval_ms");
let config_schema = config_schema_digest.finish();
let mut config_digest = FixedLayoutDigest::new();
config_digest.write(b"saddle.observability.fixed-file-config.identity.v1");
config_digest.write(&config_schema);
for value in config_values {
config_digest.write(&value.to_le_bytes());
}
let config = config_digest.finish();
let recomputed_leaf = approved_observability_layout(
components,
profile,
config_schema,
config,
config_values,
sizes,
alignments,
offsets,
aggregate_layout,
payload_machine_bytes,
)
.unwrap();
let leaf = recomputed_leaf;
assert_eq!(
hex(&leaf),
"b46726fd1a64c8b9bd50a8870ccd1c3a3aeedb481cb01222bcb7f5e6e1e14ee8"
);
let profile_bytes = profile.map(u64::to_le_bytes).concat();
let components_bytes = components.concat();
let config_values_bytes = config_values.map(u64::to_le_bytes).concat();
let sizes_bytes = sizes.map(u64::to_le_bytes).concat();
let alignments_bytes = alignments.map(u64::to_le_bytes).concat();
let offsets_bytes = offsets.map(u64::to_le_bytes).concat();
let aggregate_bytes = aggregate_layout.map(u64::to_le_bytes).concat();
let work = [45; 32];
let constituent = sha256(
&[
b"saddle/f08/observability-constituent/v1".as_slice(),
leaf.as_slice(),
components[2].as_slice(),
profile_bytes.as_slice(),
components_bytes.as_slice(),
config_schema.as_slice(),
config.as_slice(),
config_values_bytes.as_slice(),
work.as_slice(),
sizes_bytes.as_slice(),
alignments_bytes.as_slice(),
offsets_bytes.as_slice(),
aggregate_bytes.as_slice(),
&payload_machine_bytes.to_le_bytes(),
[31; 32].as_slice(),
[32; 32].as_slice(),
[33; 32].as_slice(),
root.as_slice(),
&generation.to_le_bytes(),
]
.concat(),
);
let public: [u8; 32] = key.public_key().as_ref().try_into().unwrap();
let canonical = serde_json::to_vec(&serde_json::json!({
"schema":"saddle-f08-five-leaf-generation/2",
"domain":hex(&sha256(b"saddle/f08/five-leaf-generation/v2")),
"usage":hex(&sha256(b"capacity.five-leaf.generation")),
"key_identity":hex(&sha256(&public)),
"source_identity":hex(&[31;32]),"build_identity":hex(&[32;32]),
"machine_identity":hex(&[33;32]),"root_identity":hex(&root),
"owner_generation":generation,
"observability":{
"domain":hex(&sha256(b"saddle/f08/observability-constituent/v1")),
"leaf_identity":hex(&leaf),"profile_identity":hex(&components[2]),
"profile":profile,"component_identities":components.map(|value| hex(&value)),
"config_schema_identity":hex(&config_schema),
"config_identity":hex(&config),"config_values":config_values,
"work_identity":hex(&work),"sizes":sizes,"alignments":alignments,
"offsets":offsets,"aggregate_layout":aggregate_layout,
"payload_machine_bytes":payload_machine_bytes,
"source_identity":hex(&[31;32]),"build_identity":hex(&[32;32]),
"machine_identity":hex(&[33;32]),"root_identity":hex(&root),
"owner_generation":generation,"constituent_identity":hex(&constituent)
}
}))
.unwrap();
let signature = key.sign(&canonical).as_ref().to_vec();
CanonicalSignedFiveLeafGenerationV2Input::new(canonical, signature)
}
fn v2_applicability(key: &Ed25519KeyPair) -> VerifiedGenerationKeyApplicabilityReceipt {
let public: [u8; 32] = key.public_key().as_ref().try_into().unwrap();
VerifiedGenerationKeyApplicabilityReceipt {
public_key: public,
key_identity: sha256(&public),
source: [31; 32],
build: [32; 32],
machine: [33; 32],
}
}
#[test]
fn production_signing_mechanism_is_linear_recoverable_and_domain_separated() {
assert_eq!(sha256(PRODUCTION_OBSERVATION), APPROVED_OBSERVATION_SHA256);
let key = key_pair();
let (request, mut response, policy) = prepared_signing(&key, [81; 32]);
response.request_nonce = [82; 32];
let (error, request, mut response, policy) =
verify_five_leaf_v2_signing_response(request, response, policy).unwrap_err();
assert_eq!(error, FiveLeafV2SigningError::Foreign);
response.request_nonce = [81; 32];
let (input, applicability) =
verify_five_leaf_v2_signing_response(request, response, policy).unwrap();
assert!(verify_approved_five_leaf_generation_v2(applicability, input).is_ok());
let foreign = key_pair();
let (request, _, policy) = prepared_signing(&key, [83; 32]);
let foreign_public: [u8; 32] = foreign.public_key().as_ref().try_into().unwrap();
let foreign_envelope = serde_json::to_vec(&serde_json::json!({
"schema":"saddle-f08-five-leaf-v2-signer-response/1",
"canonical":request.canonical.clone(),
"request_nonce":hex(&request.request_nonce),
"signature":foreign.sign(&request.canonical).as_ref(),
"public_key":hex(&foreign_public),
"key_identity":hex(&sha256(&foreign_public)),
"domain":hex(&request.domain),
"usage":hex(&request.usage),
"approval_identity":hex(&request.approval_identity),
"custody_policy_identity":hex(&request.custody_policy_identity),
}))
.unwrap();
let foreign_response =
capture_untrusted_five_leaf_v2_signer_response(foreign_envelope).unwrap();
let (error, ..) =
verify_five_leaf_v2_signing_response(request, foreign_response, policy).unwrap_err();
assert_eq!(error, FiveLeafV2SigningError::Foreign);
let (request, mut response, policy) = prepared_signing(&key, [84; 32]);
response.canonical.push(b' ');
let (error, ..) =
verify_five_leaf_v2_signing_response(request, response, policy).unwrap_err();
assert_eq!(error, FiveLeafV2SigningError::Foreign);
let (request, mut response, policy) = prepared_signing(&key, [85; 32]);
response.usage = sha256(b"calibration.signing");
let (error, ..) =
verify_five_leaf_v2_signing_response(request, response, policy).unwrap_err();
assert_eq!(error, FiveLeafV2SigningError::Foreign);
let (request, mut response, policy) = prepared_signing(&key, [86; 32]);
response.signature[0] ^= 1;
let (error, ..) =
verify_five_leaf_v2_signing_response(request, response, policy).unwrap_err();
assert_eq!(error, FiveLeafV2SigningError::Signature);
}
#[test]
fn v2_owner_rejects_v1_and_cross_restore_but_original_restores() {
let key_a = key_pair();
let key_b = key_pair();
let owner_a = verify_approved_five_leaf_generation_v2(
v2_applicability(&key_a),
v2_input(&key_a, [34; 32], 41),
)
.unwrap();
let owner_b = verify_approved_five_leaf_generation_v2(
v2_applicability(&key_b),
v2_input(&key_b, [35; 32], 42),
)
.unwrap();
let (aggregate_a, build_a, obs_a, view_a) = split_verified_five_leaf_generation_v2(owner_a);
let (aggregate_b, build_b, obs_b, view_b) = split_verified_five_leaf_generation_v2(owner_b);
let (aggregate_a, build_a, obs_b, view_a) =
restore_verified_five_leaf_generation_v2(aggregate_a, build_a, obs_b, view_a)
.unwrap_err();
let (aggregate_b, build_b, obs_a, view_b) =
restore_verified_five_leaf_generation_v2(aggregate_b, build_b, obs_a, view_b)
.unwrap_err();
let owner_a =
restore_verified_five_leaf_generation_v2(aggregate_a, build_a, obs_a, view_a).unwrap();
let owner_b =
restore_verified_five_leaf_generation_v2(aggregate_b, build_b, obs_b, view_b).unwrap();
let (_, build_a, half_a, _) = split_verified_five_leaf_generation_v2(owner_a);
let (_, build_b, half_b, _) = split_verified_five_leaf_generation_v2(owner_b);
let (build_a, half_b) = pair_observability_build_static_v2(build_a, half_b).unwrap_err();
let (build_b, half_a) = pair_observability_build_static_v2(build_b, half_a).unwrap_err();
assert!(pair_observability_build_static_v2(build_a, half_a).is_ok());
assert!(pair_observability_build_static_v2(build_b, half_b).is_ok());
let legacy = serde_json::to_vec(&serde_json::json!({
"schema":"saddle-f08-five-leaf-generation/1"
}))
.unwrap();
let legacy_input = CanonicalSignedFiveLeafGenerationV2Input::new(
legacy.clone(),
key_a.sign(&legacy).as_ref().to_vec(),
);
assert!(
verify_approved_five_leaf_generation_v2(v2_applicability(&key_a), legacy_input,)
.is_err()
);
}
#[test]
fn v2_rejects_each_observability_physical_fact_drift_and_restores_input() {
let bytes = Ed25519KeyPair::generate_pkcs8(&SystemRandom::new()).unwrap();
let key = Ed25519KeyPair::from_pkcs8(bytes.as_ref()).unwrap();
let root = [77; 32];
for drift in [
"component",
"size",
"alignment",
"offset",
"aggregate",
"payload",
"config",
] {
let original = v2_input(&key, root, 9);
let mut value: serde_json::Value = serde_json::from_slice(&original.canonical).unwrap();
let obs = value.get_mut("observability").unwrap();
match drift {
"component" => obs["component_identities"][0] = serde_json::json!(hex(&[99; 32])),
"size" => obs["sizes"][0] = serde_json::json!(4_857),
"alignment" => obs["alignments"][0] = serde_json::json!(16),
"offset" => obs["offsets"][1] = serde_json::json!(4_864),
"aggregate" => obs["aggregate_layout"][0] = serde_json::json!(20_928),
"payload" => obs["payload_machine_bytes"] = serde_json::json!(4_095),
"config" => obs["config_values"][0] = serde_json::json!(134_217_729_u64),
_ => unreachable!(),
}
let canonical = serde_json::to_vec(&value).unwrap();
let input = CanonicalSignedFiveLeafGenerationV2Input::new(
canonical.clone(),
key.sign(&canonical).as_ref().to_vec(),
);
let (_, applicability, returned) =
verify_approved_five_leaf_generation_v2(v2_applicability(&key), input)
.expect_err("independently signed physical drift must fail closed");
assert_eq!(returned.canonical, canonical);
assert!(
verify_approved_five_leaf_generation_v2(applicability, v2_input(&key, root, 9))
.is_ok()
);
}
}
#[test]
fn v2_whole_pair_and_generation_bind_are_recoverable() {
for drift in ["source", "machine", "root", "generation", "composed"] {
let key = key_pair();
let root = [34; 32];
let owner = verify_approved_five_leaf_generation_v2(
v2_applicability(&key),
v2_input(&key, root, 41),
)
.unwrap();
let paired = pair_verified_five_leaf_generation_v2(owner).unwrap();
let view = paired.observability_capacity_view();
assert_eq!(view.profile(), [4, 1_024, 6, 256, 4_096, 32]);
assert_eq!(view.owner_generation(), 41);
let mut actual = ([31; 32], [33; 32], root, 41, [35; 32]);
match drift {
"source" => actual.0 = [99; 32],
"machine" => actual.1 = [99; 32],
"root" => actual.2 = [99; 32],
"generation" => actual.3 = 42,
"composed" => actual.4 = [0; 32],
_ => unreachable!(),
}
let paired = bind_generated_component_generation_v2(
paired, actual.0, actual.1, actual.2, actual.3, actual.4,
)
.unwrap_err();
assert!(
bind_generated_component_generation_v2(
paired, [31; 32], [33; 32], root, 41, [35; 32],
)
.is_ok()
);
}
let key_a = key_pair();
let key_b = key_pair();
let paired_a = pair_verified_five_leaf_generation_v2(
verify_approved_five_leaf_generation_v2(
v2_applicability(&key_a),
v2_input(&key_a, [34; 32], 41),
)
.unwrap(),
)
.unwrap();
let paired_b = pair_verified_five_leaf_generation_v2(
verify_approved_five_leaf_generation_v2(
v2_applicability(&key_b),
v2_input(&key_b, [35; 32], 42),
)
.unwrap(),
)
.unwrap();
let paired_a = bind_generated_component_generation_v2(
paired_a, [31; 32], [33; 32], [35; 32], 42, [36; 32],
)
.unwrap_err();
let paired_b = bind_generated_component_generation_v2(
paired_b, [31; 32], [33; 32], [34; 32], 41, [36; 32],
)
.unwrap_err();
assert!(
bind_generated_component_generation_v2(
paired_a, [31; 32], [33; 32], [34; 32], 41, [36; 32],
)
.is_ok()
);
assert!(
bind_generated_component_generation_v2(
paired_b, [31; 32], [33; 32], [35; 32], 42, [36; 32],
)
.is_ok()
);
}
fn key_pair() -> Ed25519KeyPair {
let bytes = Ed25519KeyPair::generate_pkcs8(&SystemRandom::new()).unwrap();
Ed25519KeyPair::from_pkcs8(bytes.as_ref()).unwrap()
}
#[test]
fn two_signed_generations_pair_only_with_same_deployment_generation() {
let generated = bind_generated_component_generation(
signed_half(41, [34; 32]),
[31; 32],
[32; 32],
[33; 32],
[34; 32],
41,
[35; 32],
)
.unwrap();
let deployment = deployment_generation_half(42, [31; 32], [32; 32]);
let generated = match pair_component_generation(deployment, generated) {
Err((_, _, generated)) => generated,
Ok(_) => panic!("foreign generation must fail"),
};
let receipt = match pair_component_generation(
deployment_generation_half(41, [31; 32], [32; 32]),
generated,
) {
Ok(receipt) => receipt,
Err(_) => panic!("same signed generation and provenance pairs"),
};
assert!(consume_component_generation_pairing(receipt).is_ok());
}
#[test]
fn signed_roots_cross_reject_and_original_halves_retry() {
let root_a = [0xa1; 32];
let root_b = [0xb2; 32];
let half_a = match bind_generated_component_generation(
signed_half(41, root_a),
[31; 32],
[32; 32],
[33; 32],
root_b,
41,
[0xbb; 32],
) {
Err((GenerationPairError::Foreign, half)) => half,
_ => panic!("A half must reject B root"),
};
let half_b = match bind_generated_component_generation(
signed_half(41, root_b),
[31; 32],
[32; 32],
[33; 32],
root_a,
41,
[0xaa; 32],
) {
Err((GenerationPairError::Foreign, half)) => half,
_ => panic!("B half must reject A root"),
};
assert!(
bind_generated_component_generation(
half_a, [31; 32], [32; 32], [33; 32], root_a, 41, [0xaa; 32],
)
.is_ok()
);
assert!(
bind_generated_component_generation(
half_b, [31; 32], [32; 32], [33; 32], root_b, 41, [0xbb; 32],
)
.is_ok()
);
}
}