Skip to main content

luct_core/v1/
proof.rs

1use crate::{
2    tree::{AuditProof, ConsistencyProof, ProofValidationError, TreeHead},
3    v1::{
4        responses::{GetProofByHashResponse, GetSthConsistencyResponse},
5        sth::{SignedTreeHead, TreeHeadSignature},
6    },
7};
8
9impl TryFrom<GetSthConsistencyResponse> for ConsistencyProof {
10    type Error = ProofValidationError;
11
12    fn try_from(value: GetSthConsistencyResponse) -> Result<Self, Self::Error> {
13        Ok(ConsistencyProof {
14            path: value
15                .consistency
16                .into_iter()
17                .map(|elem| {
18                    elem.0.try_into().map_err(|vec: Vec<u8>| {
19                        ProofValidationError::InvalidHashLength {
20                            expected: 32,
21                            received: vec.len(),
22                        }
23                    })
24                })
25                .collect::<Result<Vec<[u8; 32]>, ProofValidationError>>()?,
26        })
27    }
28}
29
30impl From<TreeHeadSignature> for TreeHead {
31    fn from(value: TreeHeadSignature) -> Self {
32        Self {
33            tree_size: value.tree_size,
34            head: value.sha256_root_hash,
35        }
36    }
37}
38
39impl From<&SignedTreeHead> for TreeHead {
40    fn from(value: &SignedTreeHead) -> Self {
41        let sth = TreeHeadSignature::from(value);
42        sth.into()
43    }
44}
45
46impl TryFrom<GetProofByHashResponse> for AuditProof {
47    type Error = ProofValidationError;
48
49    fn try_from(value: GetProofByHashResponse) -> Result<Self, Self::Error> {
50        Ok(Self {
51            index: value.leaf_index,
52            path: value
53                .audit_path
54                .into_iter()
55                .map(|elem| {
56                    elem.0.try_into().map_err(|vec: Vec<u8>| {
57                        ProofValidationError::InvalidHashLength {
58                            expected: 32,
59                            received: vec.len(),
60                        }
61                    })
62                })
63                .collect::<Result<Vec<[u8; 32]>, ProofValidationError>>()?,
64        })
65    }
66}
67
68#[cfg(test)]
69mod tests {
70
71    use super::*;
72    use crate::{
73        CertificateChain,
74        tests::{
75            ARGON2025H1_STH2806, ARGON2025H1_STH2906, CERT_CHAIN_GOOGLE_COM, get_log_argon2025h2,
76        },
77        v1::responses::{GetProofByHashResponse, GetSthResponse},
78    };
79
80    const GOOGLE_AUDIT_PROOF: &str =
81        include_str!("../../../testdata/google-precert-audit-proof.json");
82    const GOOGLE_STH_CONSISTENCY_PROOF: &str =
83        include_str!("../../../testdata/sth-consistency-proof.json");
84
85    const ARGON2025H2_STH_0506: &str = "{
86        \"tree_size\":1329315675,
87        \"timestamp\":1751738269891,
88        \"sha256_root_hash\":\"NEFqldTJt2+wE/aaaQuXeADdWVV8IGbwhLublI7QaMY=\",
89        \"tree_head_signature\":\"BAMARjBEAiA9rna9/avaKTald7hHrldq8FfB4FDAaNyB44pplv71agIgeD0jj2AhLnvlaWavfFZ3BdUglauz36rFpGLYuLBs/O8=\"
90    }";
91
92    #[test]
93    fn validate_sth_consistency() {
94        let old_sth: GetSthResponse = serde_json::from_str(ARGON2025H1_STH2806).unwrap();
95        let old_tree_head = TreeHead::from(&old_sth.try_into().unwrap());
96
97        let new_sth: GetSthResponse = serde_json::from_str(ARGON2025H1_STH2906).unwrap();
98        let proof: GetSthConsistencyResponse =
99            serde_json::from_str(GOOGLE_STH_CONSISTENCY_PROOF).unwrap();
100        let proof = ConsistencyProof::try_from(proof).unwrap();
101
102        proof
103            .validate(
104                &old_tree_head,
105                &TreeHead::from(&new_sth.try_into().unwrap()),
106            )
107            .unwrap();
108    }
109
110    #[test]
111    fn audit_sct() {
112        let cert = CertificateChain::from_pem_chain(CERT_CHAIN_GOOGLE_COM).unwrap();
113        cert.verify_chain().unwrap();
114        let scts = cert.cert().extract_scts_v1().unwrap();
115
116        let log = get_log_argon2025h2();
117        assert_eq!(log.log_id(), &scts[0].log_id());
118
119        let leaf = cert.as_leaf_v1(&scts[0], true).unwrap();
120
121        let sth: GetSthResponse = serde_json::from_str(ARGON2025H2_STH_0506).unwrap();
122        let tree_head = TreeHead::from(&sth.try_into().unwrap());
123
124        let audit_proof: GetProofByHashResponse = serde_json::from_str(GOOGLE_AUDIT_PROOF).unwrap();
125        let proof = AuditProof::try_from(audit_proof).unwrap();
126
127        proof.validate(&tree_head, &leaf).unwrap();
128    }
129}