auths_transparency/
checkpoint.rs1use auths_verifier::{Ed25519PublicKey, Ed25519Signature};
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4
5use crate::types::{LogOrigin, MerkleHash};
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25#[allow(missing_docs)]
26pub struct Checkpoint {
27 pub origin: LogOrigin,
28 pub size: u64,
29 pub root: MerkleHash,
30 pub timestamp: DateTime<Utc>,
31}
32
33impl Checkpoint {
34 pub fn to_note_body(&self) -> String {
36 format!(
37 "{}\n{}\n{}\n",
38 self.origin,
39 self.size,
40 self.root.to_base64()
41 )
42 }
43
44 pub fn from_note_body(
46 body: &str,
47 timestamp: DateTime<Utc>,
48 ) -> Result<Self, crate::error::TransparencyError> {
49 let lines: Vec<&str> = body.lines().collect();
50 if lines.len() < 3 {
51 return Err(crate::error::TransparencyError::InvalidNote(
52 "checkpoint body must have at least 3 lines".into(),
53 ));
54 }
55 let origin = LogOrigin::new(lines[0])?;
56 let size: u64 = lines[1].parse().map_err(|e: std::num::ParseIntError| {
57 crate::error::TransparencyError::InvalidNote(e.to_string())
58 })?;
59 let root = MerkleHash::from_base64(lines[2])?;
60 Ok(Self {
61 origin,
62 size,
63 root,
64 timestamp,
65 })
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
87#[allow(missing_docs)]
88pub struct SignedCheckpoint {
89 pub checkpoint: Checkpoint,
90 pub log_signature: Ed25519Signature,
91 pub log_public_key: Ed25519PublicKey,
92 #[serde(default, skip_serializing_if = "Vec::is_empty")]
93 pub witnesses: Vec<WitnessCosignature>,
94 #[serde(default, skip_serializing_if = "Option::is_none")]
97 pub ecdsa_checkpoint_signature: Option<auths_verifier::EcdsaP256Signature>,
98 #[serde(default, skip_serializing_if = "Option::is_none")]
100 pub ecdsa_checkpoint_key: Option<auths_verifier::EcdsaP256PublicKey>,
101}
102
103#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
108#[allow(missing_docs)]
109pub struct WitnessCosignature {
110 pub witness_name: String,
111 pub witness_public_key: Ed25519PublicKey,
112 pub signature: Ed25519Signature,
113 pub timestamp: DateTime<Utc>,
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 #[test]
121 fn checkpoint_note_body_roundtrip() {
122 let ts = chrono::DateTime::parse_from_rfc3339("2025-06-01T00:00:00Z")
123 .unwrap()
124 .with_timezone(&Utc);
125 let cp = Checkpoint {
126 origin: LogOrigin::new("auths.dev/log").unwrap(),
127 size: 42,
128 root: MerkleHash::from_bytes([0xab; 32]),
129 timestamp: ts,
130 };
131 let body = cp.to_note_body();
132 let parsed = Checkpoint::from_note_body(&body, ts).unwrap();
133 assert_eq!(cp.origin, parsed.origin);
134 assert_eq!(cp.size, parsed.size);
135 assert_eq!(cp.root, parsed.root);
136 }
137
138 #[test]
139 fn checkpoint_json_roundtrip() {
140 let ts = chrono::DateTime::parse_from_rfc3339("2025-06-01T00:00:00Z")
141 .unwrap()
142 .with_timezone(&Utc);
143 let cp = Checkpoint {
144 origin: LogOrigin::new("auths.dev/log").unwrap(),
145 size: 100,
146 root: MerkleHash::from_bytes([0x01; 32]),
147 timestamp: ts,
148 };
149 let json = serde_json::to_string(&cp).unwrap();
150 let back: Checkpoint = serde_json::from_str(&json).unwrap();
151 assert_eq!(cp, back);
152 }
153}