confium_transparency/
entry.rs1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use sha2::{Digest, Sha256};
6
7#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
9#[serde(rename_all = "snake_case")]
10pub enum ArtifactType {
11 CertificateIssuance,
13 CertificateRevocation,
15 ThresholdSignature,
17 ThresholdEncryption,
19 DirectorRotation,
21 QuorumPolicy,
23 DirectorIdentity,
25 ArchiveRenewal,
27}
28
29impl ArtifactType {
30 pub const fn as_str(self) -> &'static str {
35 match self {
36 ArtifactType::CertificateIssuance => "certificate_issuance",
37 ArtifactType::CertificateRevocation => "certificate_revocation",
38 ArtifactType::ThresholdSignature => "threshold_signature",
39 ArtifactType::ThresholdEncryption => "threshold_encryption",
40 ArtifactType::DirectorRotation => "director_rotation",
41 ArtifactType::QuorumPolicy => "quorum_policy",
42 ArtifactType::DirectorIdentity => "director_identity",
43 ArtifactType::ArchiveRenewal => "archive_renewal",
44 }
45 }
46
47 pub const ALL: &[ArtifactType] = &[
50 ArtifactType::CertificateIssuance,
51 ArtifactType::CertificateRevocation,
52 ArtifactType::ThresholdSignature,
53 ArtifactType::ThresholdEncryption,
54 ArtifactType::DirectorRotation,
55 ArtifactType::QuorumPolicy,
56 ArtifactType::DirectorIdentity,
57 ArtifactType::ArchiveRenewal,
58 ];
59}
60
61impl std::fmt::Display for ArtifactType {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 f.write_str(self.as_str())
64 }
65}
66
67impl std::str::FromStr for ArtifactType {
68 type Err = UnknownArtifactType;
69
70 fn from_str(s: &str) -> Result<Self, Self::Err> {
71 for variant in ArtifactType::ALL {
72 if variant.as_str() == s {
73 return Ok(*variant);
74 }
75 }
76 Err(UnknownArtifactType {
77 input: s.to_string(),
78 })
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
85#[error("unknown artifact_type '{input}' (expected one of: {})", ArtifactType::ALL.iter().map(|v| v.as_str()).collect::<Vec<_>>().join(", "))]
86pub struct UnknownArtifactType {
87 input: String,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct MerkleEntry {
93 pub sequence: u64,
95 pub timestamp: DateTime<Utc>,
97 pub artifact_type: ArtifactType,
99 pub artifact_hash: [u8; 32],
101 #[serde(default)]
103 pub metadata: serde_json::Value,
104}
105
106impl MerkleEntry {
107 pub fn new(sequence: u64, artifact_type: ArtifactType, artifact_hash: [u8; 32]) -> Self {
109 Self {
110 sequence,
111 timestamp: Utc::now(),
112 artifact_type,
113 artifact_hash,
114 metadata: serde_json::Value::Null,
115 }
116 }
117
118 pub fn entry_hash(&self) -> [u8; 32] {
120 let mut hasher = Sha256::new();
121 hasher.update(self.sequence.to_le_bytes());
122 let ts_micros = self.timestamp.timestamp_micros();
124 hasher.update(ts_micros.to_le_bytes());
125 hasher.update(self.artifact_hash);
126 let result = hasher.finalize();
127 let mut out = [0u8; 32];
128 out.copy_from_slice(&result);
129 out
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn entry_hash_is_deterministic() {
139 let e1 = MerkleEntry::new(1, ArtifactType::CertificateIssuance, [0u8; 32]);
140 let e2 = MerkleEntry::new(1, ArtifactType::CertificateIssuance, [0u8; 32]);
141 let now = Utc::now();
144 let mut a = e1;
145 let mut b = e2;
146 a.timestamp = now;
147 b.timestamp = now;
148 assert_eq!(a.entry_hash(), b.entry_hash());
149 }
150
151 #[test]
152 fn different_entries_different_hashes() {
153 let now = Utc::now();
154 let mut e1 = MerkleEntry::new(1, ArtifactType::CertificateIssuance, [0u8; 32]);
155 let mut e2 = MerkleEntry::new(2, ArtifactType::CertificateIssuance, [0u8; 32]);
156 e1.timestamp = now;
157 e2.timestamp = now;
158 assert_ne!(e1.entry_hash(), e2.entry_hash());
159 }
160
161 #[test]
162 fn artifact_type_as_str_roundtrips() {
163 use std::str::FromStr;
164 for variant in ArtifactType::ALL {
165 let s = variant.as_str();
166 let parsed = ArtifactType::from_str(s).unwrap();
167 assert_eq!(parsed, *variant);
168 }
169 }
170
171 #[test]
172 fn artifact_type_display_matches_as_str() {
173 for variant in ArtifactType::ALL {
174 assert_eq!(variant.to_string(), variant.as_str());
175 }
176 }
177
178 #[test]
179 fn artifact_type_unknown_string_fails() {
180 use std::str::FromStr;
181 let result = ArtifactType::from_str("not_a_real_type");
182 assert!(result.is_err());
183 let err = result.unwrap_err();
184 assert!(err.to_string().contains("not_a_real_type"));
185 assert!(err.to_string().contains("certificate_issuance"));
186 }
187}