treeship_core/merkle/
checkpoint.rs1use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
2use ed25519_dalek::{Signature, Verifier, VerifyingKey};
3use serde::{Deserialize, Serialize};
4
5use crate::attestation::{Signer, SignerError};
6use crate::statements::unix_to_rfc3339;
7
8use super::tree::MerkleTree;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Checkpoint {
13 pub index: u64,
14 pub root: String,
16 pub tree_size: usize,
17 pub height: usize,
18 pub signed_at: String,
20 pub signer: String,
22 pub public_key: String,
24 pub signature: String,
26}
27
28#[derive(Debug)]
30pub enum CheckpointError {
31 EmptyTree,
32 Signing(SignerError),
33}
34
35impl std::fmt::Display for CheckpointError {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 match self {
38 Self::EmptyTree => write!(f, "cannot checkpoint an empty tree"),
39 Self::Signing(e) => write!(f, "checkpoint signing failed: {}", e),
40 }
41 }
42}
43
44impl std::error::Error for CheckpointError {}
45impl From<SignerError> for CheckpointError {
46 fn from(e: SignerError) -> Self {
47 Self::Signing(e)
48 }
49}
50
51impl Checkpoint {
52 pub fn create(
56 index: u64,
57 tree: &MerkleTree,
58 signer: &dyn Signer,
59 ) -> Result<Self, CheckpointError> {
60 let root_bytes = tree.root().ok_or(CheckpointError::EmptyTree)?;
61 let root = format!("sha256:{}", hex::encode(root_bytes));
62
63 let secs = std::time::SystemTime::now()
64 .duration_since(std::time::UNIX_EPOCH)
65 .unwrap_or_default()
66 .as_secs();
67 let signed_at = unix_to_rfc3339(secs);
68
69 let canonical = format!("{}|{}|{}", root, tree.len(), signed_at);
70 let sig_bytes = signer.sign(canonical.as_bytes())?;
71 let signature = URL_SAFE_NO_PAD.encode(&sig_bytes);
72 let public_key = URL_SAFE_NO_PAD.encode(signer.public_key_bytes());
73
74 Ok(Self {
75 index,
76 root,
77 tree_size: tree.len(),
78 height: tree.height(),
79 signed_at,
80 signer: signer.key_id().to_string(),
81 public_key,
82 signature,
83 })
84 }
85
86 pub fn verify(&self) -> bool {
89 let pub_bytes = match URL_SAFE_NO_PAD.decode(&self.public_key) {
90 Ok(b) => b,
91 Err(_) => return false,
92 };
93 let pub_array: [u8; 32] = match pub_bytes.as_slice().try_into() {
94 Ok(a) => a,
95 Err(_) => return false,
96 };
97 let vk = match VerifyingKey::from_bytes(&pub_array) {
98 Ok(k) => k,
99 Err(_) => return false,
100 };
101
102 let canonical = format!("{}|{}|{}", self.root, self.tree_size, self.signed_at);
103
104 let sig_bytes = match URL_SAFE_NO_PAD.decode(&self.signature) {
105 Ok(b) => b,
106 Err(_) => return false,
107 };
108 let sig_array: [u8; 64] = match sig_bytes.as_slice().try_into() {
109 Ok(a) => a,
110 Err(_) => return false,
111 };
112 let sig = Signature::from_bytes(&sig_array);
113
114 vk.verify(canonical.as_bytes(), &sig).is_ok()
115 }
116}