Skip to main content

heddle_object_model/object/
thread_genesis_admission.rs

1//! Hosted testimony about an original account-owned Thread genesis. This proves
2//! first admission; it grants neither current disclosure nor account enrollment.
3//! The comparisons below remain structural prerequisites. HYBRID verification
4//! adds independently selected owner/delegation and fresh witness-set evidence;
5//! boundary acceptance requires the exact API witness binding and native selection.
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9use super::{
10    ContentHash,
11    thread_replication::{GenesisOwner, ThreadGenesis, integration::TrustedHostedExecutor},
12};
13use crate::error::{HeddleError, Result};
14pub const FORMAT: &str = "heddle-thread-genesis-admission-v2";
15pub const ENVELOPE_FORMAT: &str = "heddle-thread-genesis-authority-v1";
16pub const MAX_BYTES: usize = 2048;
17#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
18#[serde(deny_unknown_fields)]
19pub struct ThreadGenesisAdmission {
20    pub version: u16,
21    pub basis: super::original_boundary_acceptance::AdmissionBasis,
22    pub spool: Uuid,
23    pub spool_genesis: ContentHash,
24    pub thread: ContentHash,
25    pub owner: Uuid,
26    pub creator: [u8; 32],
27    pub authority_digest: ContentHash,
28    pub executor: [u8; 32],
29    pub admitted_at_ms: i64,
30}
31impl ThreadGenesisAdmission {
32    pub fn encode(&self) -> Result<Vec<u8>> {
33        if self.version != 2
34            || self.spool.is_nil()
35            || self.owner.is_nil()
36            || self.creator == [0; 32]
37            || self.executor == [0; 32]
38            || self.admitted_at_ms < 0
39        {
40            return Err(invalid("invalid original genesis admission"));
41        }
42        let bytes = rmp_serde::to_vec_named(self)?;
43        if bytes.len() > MAX_BYTES {
44            return Err(invalid("genesis admission exceeds bound"));
45        }
46        Ok(bytes)
47    }
48    pub fn decode(bytes: &[u8]) -> Result<Self> {
49        if bytes.is_empty() || bytes.len() > MAX_BYTES {
50            return Err(invalid("genesis admission exceeds bound"));
51        }
52        let value: Self = rmp_serde::from_slice(bytes)?;
53        if value.encode()? != bytes {
54            return Err(invalid("noncanonical genesis admission"));
55        }
56        Ok(value)
57    }
58    pub fn authorize(
59        &self,
60        genesis: &ThreadGenesis,
61        envelope: &[u8],
62        trust: &TrustedHostedExecutor,
63    ) -> Result<()> {
64        self.authorize_with_acceptance(genesis, envelope, trust, None)
65    }
66    pub fn authorize_with_acceptance(
67        &self,
68        genesis: &ThreadGenesis,
69        envelope: &[u8],
70        trust: &TrustedHostedExecutor,
71        evidence: Option<&super::original_boundary_acceptance::OriginalBoundaryAcceptance>,
72    ) -> Result<()> {
73        self.basis.authorize_evidence(
74            evidence,
75            self.spool,
76            self.owner,
77            Some(super::original_boundary_acceptance::BoundaryOriginalKind::AccountGenesis),
78        )?;
79        self.encode()?;
80        if self.spool != trust.spool
81            || self.spool_genesis != trust.spool_genesis
82            || self.executor != trust.executor
83        {
84            return Err(invalid(
85                "genesis admission differs from independently pinned executor",
86            ));
87        }
88        if envelope.is_empty()
89            || envelope.len() > 64 * 1024
90            || self.thread != genesis.id()?
91            || self.spool.to_string() != genesis.spool
92            || genesis.owner != GenesisOwner::Account(self.owner)
93            || self.creator != genesis.creator
94            || self.authority_digest != ContentHash::compute_typed(ENVELOPE_FORMAT, envelope)
95        {
96            return Err(invalid(
97                "genesis admission differs from original creator proof",
98            ));
99        }
100        Ok(())
101    }
102}
103fn invalid(message: &str) -> HeddleError {
104    HeddleError::InvalidObject(message.into())
105}