Skip to main content

vwh_core/
lib.rs

1pub mod crypto;
2pub mod error;
3pub mod format;
4pub mod verify;
5
6pub use error::{Error, Result};
7pub use format::{Artifact, ArtifactState, FLAG_SEALED, MAGIC};
8pub use format::{Draft, Signed, Sealed, TypedArtifact};
9
10// ArtifactId, Intent, KeyFingerprint are defined in this module and exported below
11
12use serde::{Deserialize, Serialize};
13use std::fmt;
14
15/// Artifact ID: immutable 128-bit random identifier
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
17pub struct ArtifactId(pub [u8; 16]);
18
19impl ArtifactId {
20    pub fn new() -> Self {
21        let mut id = [0u8; 16];
22        getrandom::fill(&mut id).expect("OS entropy unavailable");
23        Self(id)
24    }
25
26    pub fn from_bytes(bytes: [u8; 16]) -> Self {
27        Self(bytes)
28    }
29
30    pub fn to_hex(&self) -> String {
31        hex::encode(self.0)
32    }
33
34    pub fn from_hex(s: &str) -> Result<Self> {
35        let bytes = hex::decode(s).map_err(|_| Error::InvalidHex)?;
36        if bytes.len() != 16 {
37            return Err(Error::InvalidArtifactId);
38        }
39        let mut arr = [0u8; 16];
40        arr.copy_from_slice(&bytes);
41        Ok(Self(arr))
42    }
43
44    /// Short display (first 8 bytes)
45    pub fn short_display(&self) -> String {
46        hex::encode(&self.0[..8])
47    }
48}
49
50impl fmt::Display for ArtifactId {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        write!(f, "{}", self.to_hex())
53    }
54}
55
56impl Default for ArtifactId {
57    fn default() -> Self {
58        Self::new()
59    }
60}
61
62/// Intent enum (mandatory, no default)
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64#[repr(u8)]
65pub enum Intent {
66    Lab = 0,
67    OwnedInfra = 1,
68    AuthRedteam = 2,
69    BlueRemediation = 3,
70    Research = 4,
71}
72
73impl Intent {
74    pub fn from_u8(value: u8) -> Result<Self> {
75        match value {
76            0 => Ok(Intent::Lab),
77            1 => Ok(Intent::OwnedInfra),
78            2 => Ok(Intent::AuthRedteam),
79            3 => Ok(Intent::BlueRemediation),
80            4 => Ok(Intent::Research),
81            _ => Err(Error::InvalidIntent(value)),
82        }
83    }
84
85    pub fn to_u8(self) -> u8 {
86        self as u8
87    }
88
89    pub fn as_str(&self) -> &'static str {
90        match self {
91            Intent::Lab => "LAB",
92            Intent::OwnedInfra => "OWNED-INFRA",
93            Intent::AuthRedteam => "AUTH-REDTEAM",
94            Intent::BlueRemediation => "BLUE-REMEDIATION",
95            Intent::Research => "RESEARCH",
96        }
97    }
98
99    pub fn all() -> &'static [Intent] {
100        &[
101            Intent::Lab,
102            Intent::OwnedInfra,
103            Intent::AuthRedteam,
104            Intent::BlueRemediation,
105            Intent::Research,
106        ]
107    }
108
109    // Inherent `from_str` predates a `FromStr` impl; keep the name for API
110    // stability. (Pre-existing lint, silenced — not a refactor.)
111    #[allow(clippy::should_implement_trait)]
112    pub fn from_str(s: &str) -> Result<Self> {
113        match s.to_uppercase().as_str() {
114            "LAB" => Ok(Intent::Lab),
115            "OWNED-INFRA" | "OWNED_INFRA" => Ok(Intent::OwnedInfra),
116            "AUTH-REDTEAM" | "AUTH_REDTEAM" | "AUTHREDTEAM" => Ok(Intent::AuthRedteam),
117            "BLUE-REMEDIATION" | "BLUE_REMEDIATION" => Ok(Intent::BlueRemediation),
118            "RESEARCH" => Ok(Intent::Research),
119            _ => Err(Error::InvalidIntentString(s.to_string())),
120        }
121    }
122}
123
124impl fmt::Display for Intent {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        write!(f, "{}", self.as_str())
127    }
128}
129
130/// Public key fingerprint (BLAKE3 hash of public key bytes)
131#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
132pub struct KeyFingerprint(pub [u8; 32]);
133
134impl KeyFingerprint {
135    pub fn new(public_key_bytes: &[u8; 32]) -> Self {
136        let hash = blake3::hash(public_key_bytes);
137        Self(*hash.as_bytes())
138    }
139
140    pub fn to_hex(&self) -> String {
141        hex::encode(self.0)
142    }
143
144    pub fn short_display(&self) -> String {
145        hex::encode(&self.0[..8])
146    }
147}
148
149impl fmt::Display for KeyFingerprint {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        write!(f, "{}", self.to_hex())
152    }
153}
154
155