thru_base/bintrie_proof.rs
1use crate::bintrie_types::{Hash, Pubkey};
2
3// State proof type constants (matching C implementation in tn_state_proof.h)
4pub const TN_STATE_PROOF_TYPE_EXISTING: u64 = 0x0;
5pub const TN_STATE_PROOF_TYPE_UPDATING: u64 = 0x1;
6pub const TN_STATE_PROOF_TYPE_CREATION: u64 = 0x2;
7
8/// Proof structure for existence/non-existence proofs
9#[derive(Debug, Clone)]
10pub struct Proof {
11 pub proof_indices: Vec<u8>,
12 pub sibling_hashes: Vec<Hash>,
13 pub existing_leaf_hash: Option<Hash>,
14}
15
16impl Proof {
17 pub fn new() -> Self {
18 Self {
19 proof_indices: Vec::new(),
20 sibling_hashes: Vec::new(),
21 existing_leaf_hash: None,
22 }
23 }
24 pub fn to_wire(&self, slot: u64) -> Vec<u8> {
25 // Convert proof indices to path bitset (matching C tn_state_proof_idx_list_to_path_bitset)
26 let mut path_bitset = [0u8; 32];
27 for &idx in &self.proof_indices {
28 let bit_idx = (idx % 64) as usize;
29 let word_idx = (idx / 64) as usize;
30 if word_idx < 4 {
31 // Convert to little-endian u64 array like C implementation
32 let start = word_idx * 8;
33 let mut word_bytes = [0u8; 8];
34 word_bytes.copy_from_slice(&path_bitset[start..start + 8]);
35 let mut word = u64::from_le_bytes(word_bytes);
36 word |= 1u64 << bit_idx;
37 path_bitset[start..start + 8].copy_from_slice(&word.to_le_bytes());
38 }
39 }
40
41 // Create header: type_slot (8 bytes) + path_bitset (32 bytes)
42 let mut result = Vec::with_capacity(40 + 64 + self.sibling_hashes.len() * 32);
43
44 // Encode type_slot: slot in low 62 bits, type (EXISTING=0) in high 2 bits
45 let type_slot = slot
46 | (if self.existing_leaf_hash.is_some() {
47 TN_STATE_PROOF_TYPE_UPDATING
48 } else {
49 TN_STATE_PROOF_TYPE_EXISTING
50 } << 62);
51 result.extend_from_slice(&type_slot.to_le_bytes());
52
53 // Add path bitset
54 result.extend_from_slice(&path_bitset);
55
56 if let Some(existing_leaf_hash) = self.existing_leaf_hash {
57 result.extend_from_slice(existing_leaf_hash.as_bytes());
58 }
59
60 for sibling_hash in &self.sibling_hashes {
61 result.extend_from_slice(sibling_hash.as_bytes());
62 }
63
64 result
65 }
66}
67
68/// Result of a non-existence proof
69#[derive(Debug, Clone)]
70pub struct NonExistenceProof {
71 pub proof: Proof,
72 pub existing_pubkey: Pubkey,
73 pub existing_hash: Hash,
74}
75
76impl NonExistenceProof {
77 /// Convert the non-existence proof to wire format compatible with C tn_state_proof_t
78 /// This creates a CREATION type state proof with the proof data
79 ///
80 /// Wire format layout:
81 /// - Header (40 bytes):
82 /// - type_slot (8 bytes): slot in low 62 bits, proof type (2=CREATION) in high 2 bits
83 /// - path_bitset (32 bytes): bitset indicating which proof indices are used
84 /// - Body (variable length):
85 /// - existing_leaf_pubkey (32 bytes): pubkey of the existing leaf found
86 /// - existing_leaf_hash (32 bytes): hash of the existing leaf value
87 /// - sibling_hashes (32 * n bytes): sibling hashes for the proof path
88 ///
89 /// # Arguments
90 /// * `slot` - The slot number to encode in the proof header
91 ///
92 /// # Returns
93 /// A Vec<u8> containing the binary representation compatible with C tn_state_proof_t
94 ///
95 /// # Example
96 /// ```
97 /// use thru_base::bintrie::BinTrie;
98 /// use thru_base::bintrie_types::{Pubkey, Hash};
99 ///
100 /// let mut trie = BinTrie::new();
101 /// let existing_key = Pubkey::new([1u8; 32]);
102 /// let existing_value = Hash::new([2u8; 32]);
103 /// trie.insert(existing_key, existing_value).unwrap();
104 ///
105 /// let missing_key = Pubkey::new([3u8; 32]);
106 /// let proof = trie.prove_non_existence(&missing_key).unwrap();
107 ///
108 /// let wire_data = proof.to_wire(12345);
109 /// assert!(wire_data.len() >= 104); // At least header + existing pubkey + hash
110 /// ```
111 pub fn to_wire(&self, slot: u64) -> Vec<u8> {
112 // Convert proof indices to path bitset (matching C tn_state_proof_idx_list_to_path_bitset)
113 let mut path_bitset = [0u8; 32];
114 for &idx in &self.proof.proof_indices {
115 let bit_idx = (idx % 64) as usize;
116 let word_idx = (idx / 64) as usize;
117 if word_idx < 4 {
118 // Convert to little-endian u64 array like C implementation
119 let start = word_idx * 8;
120 let mut word_bytes = [0u8; 8];
121 word_bytes.copy_from_slice(&path_bitset[start..start + 8]);
122 let mut word = u64::from_le_bytes(word_bytes);
123 word |= 1u64 << bit_idx;
124 path_bitset[start..start + 8].copy_from_slice(&word.to_le_bytes());
125 }
126 }
127
128 // Create header: type_slot (8 bytes) + path_bitset (32 bytes)
129 let mut result = Vec::with_capacity(40 + 64 + self.proof.sibling_hashes.len() * 32);
130
131 // Encode type_slot: slot in low 62 bits, type (CREATION=2) in high 2 bits
132 let type_slot = slot | (TN_STATE_PROOF_TYPE_CREATION << 62);
133 result.extend_from_slice(&type_slot.to_le_bytes());
134
135 // Add path bitset
136 result.extend_from_slice(&path_bitset);
137
138 // Add body for CREATION type:
139 // 1. existing_leaf_pubkey (32 bytes)
140 // 2. existing_leaf_hash (32 bytes)
141 // 3. sibling_hashes (32 bytes each)
142 result.extend_from_slice(self.existing_pubkey.as_bytes());
143 result.extend_from_slice(self.existing_hash.as_bytes());
144
145 for sibling_hash in &self.proof.sibling_hashes {
146 result.extend_from_slice(sibling_hash.as_bytes());
147 }
148
149 result
150 }
151}