Skip to main content

luct_core/v1/
sth.rs

1use serde::{Deserialize, Serialize};
2
3use crate::{
4    CtLog, Version,
5    signature::{Signature, SignatureValidationError},
6    tree::HashOutput,
7    utils::codec::{CodecError, Decode, Encode},
8    v1::{SignatureType, responses::GetSthResponse},
9};
10use std::io::{Read, Write};
11
12impl CtLog {
13    pub fn validate_sth_v1(&self, sth: &SignedTreeHead) -> Result<(), SignatureValidationError> {
14        let tree_head_tbs = TreeHeadSignature::from(sth);
15        sth.tree_head_signature
16            .validate(&tree_head_tbs, &self.config.key)
17    }
18}
19
20/// Response returned by call to `/ct/v1/get-sth`
21///
22/// See RFC 6962 4.3
23#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
24pub struct SignedTreeHead {
25    pub(crate) tree_size: u64,
26    pub(crate) timestamp: u64,
27    pub(crate) sha256_root_hash: HashOutput,
28    pub(crate) tree_head_signature: Signature<TreeHeadSignature>,
29}
30
31impl std::fmt::Debug for SignedTreeHead {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        f.debug_struct("SignedTreeHead")
34            .field("tree_size", &self.tree_size)
35            .field("timestamp", &self.timestamp)
36            .field("sha256_root_hash", &hex::encode(self.sha256_root_hash))
37            .field("tree_head_signature", &self.tree_head_signature)
38            .finish()
39    }
40}
41
42impl SignedTreeHead {
43    pub fn tree_size(&self) -> u64 {
44        self.tree_size
45    }
46
47    pub fn timestamp(&self) -> u64 {
48        self.timestamp
49    }
50
51    pub fn sha256_root_hash(&self) -> &HashOutput {
52        &self.sha256_root_hash
53    }
54}
55
56impl TryFrom<GetSthResponse> for SignedTreeHead {
57    type Error = ();
58
59    fn try_from(value: GetSthResponse) -> Result<Self, Self::Error> {
60        Ok(Self {
61            tree_size: value.tree_size,
62            timestamp: value.timestamp,
63            sha256_root_hash: value.sha256_root_hash.0.try_into().map_err(|_| ())?,
64            tree_head_signature: value.tree_head_signature.0.0,
65        })
66    }
67}
68
69/// See RFC 6962 3.5
70#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
71pub(crate) struct TreeHeadSignature {
72    pub(crate) version: Version,
73    // SignatureType signature_type = tree_hash;
74    pub(crate) timestamp: u64,
75    pub(crate) tree_size: u64,
76    pub(crate) sha256_root_hash: [u8; 32],
77}
78
79impl Encode for TreeHeadSignature {
80    fn encode(&self, mut writer: impl Write) -> Result<(), CodecError> {
81        self.version.encode(&mut writer)?;
82        SignatureType::TreeHash.encode(&mut writer)?;
83        self.timestamp.encode(&mut writer)?;
84        self.tree_size.encode(&mut writer)?;
85        self.sha256_root_hash.encode(&mut writer)?;
86        Ok(())
87    }
88}
89
90impl Decode for TreeHeadSignature {
91    fn decode(mut reader: impl Read) -> Result<Self, CodecError> {
92        let version = Version::decode(&mut reader)?;
93        let signature_type = SignatureType::decode(&mut reader)?;
94        match signature_type {
95            SignatureType::CertificateTimeStamp => return Err(CodecError::UnexpectedVariant),
96            SignatureType::TreeHash => (),
97        }
98        let timestamp = u64::decode(&mut reader)?;
99        let tree_size = u64::decode(&mut reader)?;
100        let sha256_root_hash = <[u8; 32]>::decode(&mut reader)?;
101
102        Ok(Self {
103            version,
104            timestamp,
105            tree_size,
106            sha256_root_hash,
107        })
108    }
109}
110
111impl From<&SignedTreeHead> for TreeHeadSignature {
112    fn from(value: &SignedTreeHead) -> Self {
113        Self {
114            version: Version::V1,
115            timestamp: value.timestamp,
116            tree_size: value.tree_size,
117            sha256_root_hash: value.sha256_root_hash,
118        }
119    }
120}
121
122#[cfg(test)]
123mod test {
124    use super::*;
125    use crate::tests::{ARGON2025H1_STH2806, get_log_argon2025h1};
126
127    #[test]
128    fn sth_codec_roundtrip() {
129        let sth: GetSthResponse = serde_json::from_str(ARGON2025H1_STH2806).unwrap();
130        let sth_bytes = serde_json::to_string(&sth).unwrap();
131        let sth2: GetSthResponse = serde_json::from_str(&sth_bytes).unwrap();
132        assert_eq!(sth, sth2);
133    }
134
135    #[test]
136    fn validate_sth() {
137        let log = get_log_argon2025h1();
138        let sth: GetSthResponse = serde_json::from_str(ARGON2025H1_STH2806).unwrap();
139        log.validate_sth_v1(&sth.try_into().unwrap()).unwrap();
140    }
141}