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}