1use crate::{operator::OperatorRecord, package::PackageRecord, ProtoEnvelope};
2use anyhow::bail;
3use serde::{Deserialize, Serialize};
4use std::fmt;
5use std::str::FromStr;
6use std::time::SystemTime;
7use warg_crypto::hash::{AnyHash, Hash, HashAlgorithm, SupportedDigest};
8use warg_crypto::prefix::VisitPrefixEncode;
9use warg_crypto::{prefix, ByteVisitor, Signable, VisitBytes};
10use wasmparser::names::KebabStr;
11
12pub type RegistryIndex = usize;
14
15pub type RegistryLen = RegistryIndex;
17
18#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct Checkpoint {
21 pub log_root: AnyHash,
22 pub log_length: RegistryLen,
23 pub map_root: AnyHash,
24}
25
26impl prefix::VisitPrefixEncode for Checkpoint {
27 fn visit_pe<BV: ?Sized + ByteVisitor>(&self, visitor: &mut prefix::PrefixEncodeVisitor<BV>) {
28 visitor.visit_str_raw("WARG-CHECKPOINT-V0");
29 visitor.visit_unsigned(self.log_length as u64);
30 visitor.visit_str(&self.log_root.to_string());
31 visitor.visit_str(&self.map_root.to_string());
32 }
33}
34
35impl VisitBytes for Checkpoint {
37 fn visit<BV: ?Sized + ByteVisitor>(&self, visitor: &mut BV) {
38 self.visit_bv(visitor);
39 }
40}
41
42#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
43#[serde(rename_all = "camelCase")]
44pub struct TimestampedCheckpoint {
45 #[serde(flatten)]
46 pub checkpoint: Checkpoint,
47 pub timestamp: u64,
48}
49
50impl TimestampedCheckpoint {
51 pub fn new(checkpoint: Checkpoint, time: SystemTime) -> anyhow::Result<Self> {
52 Ok(Self {
53 checkpoint,
54 timestamp: time.duration_since(std::time::UNIX_EPOCH)?.as_secs(),
55 })
56 }
57
58 pub fn now(checkpoint: Checkpoint) -> anyhow::Result<Self> {
59 Self::new(checkpoint, SystemTime::now())
60 }
61}
62
63impl Signable for TimestampedCheckpoint {
64 const PREFIX: &'static [u8] = b"WARG-CHECKPOINT-SIGNATURE-V0";
65}
66
67impl prefix::VisitPrefixEncode for TimestampedCheckpoint {
68 fn visit_pe<BV: ?Sized + ByteVisitor>(&self, visitor: &mut prefix::PrefixEncodeVisitor<BV>) {
69 visitor.visit_str_raw("WARG-TIMESTAMPED-CHECKPOINT-V0");
70 visitor.visit_unsigned(self.checkpoint.log_length as u64);
71 visitor.visit_str(&self.checkpoint.log_root.to_string());
72 visitor.visit_str(&self.checkpoint.map_root.to_string());
73 visitor.visit_unsigned(self.timestamp);
74 }
75}
76
77impl VisitBytes for TimestampedCheckpoint {
79 fn visit<BV: ?Sized + ByteVisitor>(&self, visitor: &mut BV) {
80 self.visit_bv(visitor);
81 }
82}
83
84#[derive(Debug, Clone, Hash, PartialEq, Eq)]
85pub struct MapLeaf {
86 pub record_id: RecordId,
87}
88
89impl prefix::VisitPrefixEncode for MapLeaf {
90 fn visit_pe<BV: ?Sized + ByteVisitor>(&self, visitor: &mut prefix::PrefixEncodeVisitor<BV>) {
91 visitor.visit_str_raw("WARG-MAP-LEAF-V0");
92 visitor.visit_str(&self.record_id.0.to_string());
93 }
94}
95
96impl VisitBytes for MapLeaf {
98 fn visit<BV: ?Sized + ByteVisitor>(&self, visitor: &mut BV) {
99 self.visit_bv(visitor);
100 }
101}
102
103#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
104#[serde(rename_all = "camelCase")]
105pub struct LogLeaf {
106 pub log_id: LogId,
107 pub record_id: RecordId,
108}
109
110impl prefix::VisitPrefixEncode for LogLeaf {
111 fn visit_pe<BV: ?Sized + ByteVisitor>(&self, visitor: &mut prefix::PrefixEncodeVisitor<BV>) {
112 visitor.visit_str_raw("WARG-LOG-LEAF-V0");
113 visitor.visit_str(&self.log_id.0.to_string());
114 visitor.visit_str(&self.record_id.0.to_string());
115 }
116}
117
118impl VisitBytes for LogLeaf {
120 fn visit<BV: ?Sized + ByteVisitor>(&self, visitor: &mut BV) {
121 self.visit_bv(visitor);
122 }
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
132pub struct PackageName {
133 package_name: String,
134 colon: usize,
135}
136
137impl PackageName {
138 pub fn new(name: impl Into<String>) -> anyhow::Result<Self> {
142 let name = name.into();
143
144 if let Some(colon) = name.rfind(':') {
145 if KebabStr::new(&name[colon + 1..]).is_some()
147 && Self::is_valid_namespace(&name[..colon])
148 && name[colon + 1..].chars().all(|c| !c.is_ascii_uppercase())
149 {
150 return Ok(Self {
151 package_name: name,
152 colon,
153 });
154 }
155 }
156
157 bail!("invalid package name `{name}`: expected format is `<namespace>:<name>` using lowercased characters")
158 }
159
160 pub fn namespace(&self) -> &str {
162 &self.package_name[..self.colon]
163 }
164
165 pub fn name(&self) -> &str {
167 &self.package_name[self.colon + 1..]
168 }
169
170 pub fn is_valid_namespace(namespace: &str) -> bool {
172 KebabStr::new(namespace).is_some() && namespace.chars().all(|c| !c.is_ascii_uppercase())
173 }
174}
175
176impl AsRef<str> for PackageName {
177 fn as_ref(&self) -> &str {
178 &self.package_name
179 }
180}
181
182impl FromStr for PackageName {
183 type Err = anyhow::Error;
184
185 fn from_str(s: &str) -> Result<Self, Self::Err> {
186 Self::new(s)
187 }
188}
189
190impl fmt::Display for PackageName {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 write!(f, "{package_name}", package_name = self.package_name)
193 }
194}
195
196impl prefix::VisitPrefixEncode for PackageName {
197 fn visit_pe<BV: ?Sized + ByteVisitor>(&self, visitor: &mut prefix::PrefixEncodeVisitor<BV>) {
198 visitor.visit_str_raw("WARG-PACKAGE-ID-V0");
199 visitor.visit_str(&self.package_name);
200 }
201}
202
203impl VisitBytes for PackageName {
204 fn visit<BV: ?Sized + ByteVisitor>(&self, visitor: &mut BV) {
205 self.visit_bv(visitor);
206 }
207}
208
209impl Serialize for PackageName {
210 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
211 serializer.serialize_str(&self.package_name)
212 }
213}
214
215impl<'de> Deserialize<'de> for PackageName {
216 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
217 let id = String::deserialize(deserializer)?;
218 PackageName::new(id).map_err(serde::de::Error::custom)
219 }
220}
221
222#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
223#[serde(transparent)]
224pub struct LogId(AnyHash);
225
226impl LogId {
227 pub fn operator_log<D: SupportedDigest>() -> Self {
228 let prefix: &[u8] = b"WARG-OPERATOR-LOG-ID-V0".as_slice();
229 let hash: Hash<D> = Hash::of(prefix);
230 Self(hash.into())
231 }
232
233 pub fn package_log<D: SupportedDigest>(name: &PackageName) -> Self {
234 let prefix: &[u8] = b"WARG-PACKAGE-LOG-ID-V0:".as_slice();
235 let hash: Hash<D> = Hash::of((prefix, name));
236 Self(hash.into())
237 }
238}
239
240impl fmt::Display for LogId {
241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242 self.0.fmt(f)
243 }
244}
245
246impl VisitBytes for LogId {
247 fn visit<BV: ?Sized + ByteVisitor>(&self, visitor: &mut BV) {
248 visitor.visit_bytes(self.0.bytes())
249 }
250}
251
252impl From<AnyHash> for LogId {
253 fn from(value: AnyHash) -> Self {
254 Self(value)
255 }
256}
257
258impl From<LogId> for AnyHash {
259 fn from(id: LogId) -> Self {
260 id.0
261 }
262}
263
264impl AsRef<[u8]> for LogId {
265 fn as_ref(&self) -> &[u8] {
266 self.0.bytes()
267 }
268}
269
270#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
271#[serde(transparent)]
272pub struct RecordId(AnyHash);
273
274impl RecordId {
275 pub fn algorithm(&self) -> HashAlgorithm {
276 self.0.algorithm()
277 }
278
279 pub fn operator_record<D: SupportedDigest>(record: &ProtoEnvelope<OperatorRecord>) -> Self {
280 let prefix: &[u8] = b"WARG-OPERATOR-LOG-RECORD-V0:".as_slice();
281 let hash: Hash<D> = Hash::of((prefix, record.content_bytes()));
282 Self(hash.into())
283 }
284
285 pub fn package_record<D: SupportedDigest>(record: &ProtoEnvelope<PackageRecord>) -> Self {
286 let prefix: &[u8] = b"WARG-PACKAGE-LOG-RECORD-V0:".as_slice();
287 let hash: Hash<D> = Hash::of((prefix, record.content_bytes()));
288 Self(hash.into())
289 }
290}
291
292impl fmt::Display for RecordId {
293 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294 self.0.fmt(f)
295 }
296}
297
298impl From<AnyHash> for RecordId {
299 fn from(value: AnyHash) -> Self {
300 Self(value)
301 }
302}
303
304impl From<RecordId> for AnyHash {
305 fn from(id: RecordId) -> Self {
306 id.0
307 }
308}
309
310impl AsRef<[u8]> for RecordId {
311 fn as_ref(&self) -> &[u8] {
312 self.0.bytes()
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319 use warg_crypto::hash::Sha256;
320 use warg_transparency::map::Map;
321
322 #[test]
323 fn log_id() {
324 let first = Map::<Sha256, LogId, &'static str>::default();
325 let second = first.insert(LogId::operator_log::<Sha256>(), "foobar");
326 let proof = second.prove(LogId::operator_log::<Sha256>()).unwrap();
327 assert_eq!(
328 second.root().clone(),
329 proof.evaluate(&LogId::operator_log::<Sha256>(), &"foobar")
330 );
331 }
332}