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