1use std::path::Path;
21use std::sync::Arc;
22
23use anyhow::{Context, Result, anyhow};
24use confium_transparency::entry::ArtifactType;
25use rusqlite::{Connection, params};
26use serde::{Deserialize, Serialize};
27
28pub struct RebuildRow {
31 pub sequence: u64,
32 pub timestamp: chrono::DateTime<chrono::Utc>,
33 pub artifact_type: ArtifactType,
34 pub artifact_hash: [u8; 32],
35}
36
37pub type OtsProofRow = (Vec<u8>, Option<u64>, String);
40
41#[derive(Clone)]
44pub struct Database {
45 conn: Arc<parking_lot::Mutex<Connection>>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct Entry {
50 pub sequence: u64,
51 pub artifact_type: String,
52 pub artifact_hash: String, pub timestamp: String, pub issuer_distinguished_name: Option<String>,
55 pub subject_distinguished_name: Option<String>,
56 pub fingerprint_sha256: Option<String>, pub valid_from: Option<String>,
58 pub valid_to: Option<String>,
59}
60
61impl Database {
62 pub fn open(path: &Path) -> Result<Self> {
63 let conn = Connection::open(path)
64 .with_context(|| format!("opening database at {}", path.display()))?;
65 conn.execute_batch(
67 "PRAGMA journal_mode = WAL;
68 PRAGMA synchronous = NORMAL;
69 PRAGMA foreign_keys = ON;",
70 )?;
71 Ok(Database {
72 conn: Arc::new(parking_lot::Mutex::new(conn)),
73 })
74 }
75
76 pub fn init_schema(&self) -> Result<()> {
77 let conn = self.conn.lock();
78 conn.execute_batch(
79 "CREATE TABLE IF NOT EXISTS entries (
80 sequence INTEGER PRIMARY KEY AUTOINCREMENT,
81 artifact_type TEXT NOT NULL,
82 artifact_hash TEXT NOT NULL,
83 timestamp TEXT NOT NULL,
84 issuer_dn TEXT,
85 subject_dn TEXT,
86 fingerprint_sha256 TEXT,
87 valid_from TEXT,
88 valid_to TEXT
89 );
90
91 CREATE INDEX IF NOT EXISTS idx_entries_fingerprint
92 ON entries(fingerprint_sha256);
93 CREATE INDEX IF NOT EXISTS idx_entries_issuer
94 ON entries(issuer_dn);
95 CREATE INDEX IF NOT EXISTS idx_entries_type_ts
96 ON entries(artifact_type, timestamp);
97
98 CREATE TABLE IF NOT EXISTS tree_nodes (
99 level INTEGER NOT NULL,
100 idx INTEGER NOT NULL,
101 hash TEXT NOT NULL,
102 PRIMARY KEY (level, idx)
103 );
104
105 CREATE TABLE IF NOT EXISTS tree_meta (
106 key TEXT PRIMARY KEY,
107 value TEXT NOT NULL
108 );
109
110 CREATE TABLE IF NOT EXISTS ots_proofs (
111 tree_size INTEGER PRIMARY KEY,
112 root_hash TEXT NOT NULL,
113 ots_proof BLOB NOT NULL,
114 bitcoin_height INTEGER,
115 anchor_time TEXT NOT NULL
116 );
117
118 CREATE TABLE IF NOT EXISTS witness_sigs (
119 tree_size INTEGER NOT NULL,
120 root_hash TEXT NOT NULL,
121 witness_id TEXT NOT NULL,
122 signature BLOB NOT NULL,
123 timestamp TEXT NOT NULL,
124 PRIMARY KEY (tree_size, witness_id)
125 );",
126 )?;
127 Ok(())
128 }
129
130 pub fn append(&self, entry: &Entry) -> Result<u64> {
131 let conn = self.conn.lock();
132 conn.execute(
133 "INSERT INTO entries
134 (artifact_type, artifact_hash, timestamp,
135 issuer_dn, subject_dn, fingerprint_sha256,
136 valid_from, valid_to)
137 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
138 params![
139 entry.artifact_type,
140 entry.artifact_hash,
141 entry.timestamp,
142 entry.issuer_distinguished_name,
143 entry.subject_distinguished_name,
144 entry.fingerprint_sha256,
145 entry.valid_from,
146 entry.valid_to,
147 ],
148 )?;
149 Ok((conn.last_insert_rowid() - 1) as u64)
152 }
153
154 pub fn entry_at(&self, sequence: u64) -> Result<Option<Entry>> {
155 let conn = self.conn.lock();
156 let mut stmt = conn.prepare(
157 "SELECT sequence, artifact_type, artifact_hash, timestamp,
158 issuer_dn, subject_dn, fingerprint_sha256,
159 valid_from, valid_to
160 FROM entries WHERE sequence = ?1 + 1",
161 )?;
162 let rows = stmt.query_row(params![sequence as i64], |row| {
163 Ok(Entry {
164 sequence: row.get::<_, i64>(0)? as u64,
165 artifact_type: row.get(1)?,
166 artifact_hash: row.get(2)?,
167 timestamp: row.get(3)?,
168 issuer_distinguished_name: row.get(4)?,
169 subject_distinguished_name: row.get(5)?,
170 fingerprint_sha256: row.get(6)?,
171 valid_from: row.get(7)?,
172 valid_to: row.get(8)?,
173 })
174 });
175 match rows {
176 Ok(e) => Ok(Some(e)),
177 Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
178 Err(e) => Err(e.into()),
179 }
180 }
181
182 pub fn entry_count(&self) -> Result<u64> {
183 let conn = self.conn.lock();
184 let n: i64 = conn.query_row("SELECT COUNT(*) FROM entries", [], |row| row.get(0))?;
185 Ok(n as u64)
186 }
187
188 pub fn entries_by_fingerprint(&self, fingerprint_hex: &str) -> Result<Vec<Entry>> {
189 let conn = self.conn.lock();
190 let mut stmt = conn.prepare(
191 "SELECT sequence, artifact_type, artifact_hash, timestamp,
192 issuer_dn, subject_dn, fingerprint_sha256,
193 valid_from, valid_to
194 FROM entries WHERE fingerprint_sha256 = ?1
195 ORDER BY sequence ASC",
196 )?;
197 let rows = stmt.query_map(params![fingerprint_hex], |row| {
198 Ok(Entry {
199 sequence: row.get::<_, i64>(0)? as u64,
200 artifact_type: row.get(1)?,
201 artifact_hash: row.get(2)?,
202 timestamp: row.get(3)?,
203 issuer_distinguished_name: row.get(4)?,
204 subject_distinguished_name: row.get(5)?,
205 fingerprint_sha256: row.get(6)?,
206 valid_from: row.get(7)?,
207 valid_to: row.get(8)?,
208 })
209 })?;
210 let mut out = Vec::new();
211 for row in rows {
212 out.push(row?);
213 }
214 Ok(out)
215 }
216
217 pub fn entries_by_issuer(&self, issuer_dn: &str, limit: usize) -> Result<Vec<Entry>> {
218 let conn = self.conn.lock();
219 let mut stmt = conn.prepare(
220 "SELECT sequence, artifact_type, artifact_hash, timestamp,
221 issuer_dn, subject_dn, fingerprint_sha256,
222 valid_from, valid_to
223 FROM entries WHERE issuer_dn = ?1
224 ORDER BY sequence DESC
225 LIMIT ?2",
226 )?;
227 let rows = stmt.query_map(params![issuer_dn, limit as i64], |row| {
228 Ok(Entry {
229 sequence: row.get::<_, i64>(0)? as u64,
230 artifact_type: row.get(1)?,
231 artifact_hash: row.get(2)?,
232 timestamp: row.get(3)?,
233 issuer_distinguished_name: row.get(4)?,
234 subject_distinguished_name: row.get(5)?,
235 fingerprint_sha256: row.get(6)?,
236 valid_from: row.get(7)?,
237 valid_to: row.get(8)?,
238 })
239 })?;
240 let mut out = Vec::new();
241 for row in rows {
242 out.push(row?);
243 }
244 Ok(out)
245 }
246
247 pub fn all_leaf_hashes(&self) -> Result<Vec<[u8; 32]>> {
250 let conn = self.conn.lock();
251 let mut stmt = conn.prepare("SELECT artifact_hash FROM entries ORDER BY sequence ASC")?;
252 let rows = stmt.query_map([], |row| {
253 let h: String = row.get(0)?;
254 Ok(h)
255 })?;
256 let mut out = Vec::new();
257 for row in rows {
258 let h = row?;
259 let bytes = hex::decode(&h).map_err(|e| anyhow!("hash hex decode: {e}"))?;
260 if bytes.len() != 32 {
261 return Err(anyhow!("hash must be 32 bytes, got {}", bytes.len()));
262 }
263 let mut arr = [0u8; 32];
264 arr.copy_from_slice(&bytes);
265 out.push(arr);
266 }
267 Ok(out)
268 }
269
270 pub fn all_entries_for_rebuild(&self) -> Result<Vec<RebuildRow>> {
276 let conn = self.conn.lock();
277 let mut stmt = conn.prepare(
278 "SELECT sequence, artifact_type, artifact_hash, timestamp
279 FROM entries ORDER BY sequence ASC",
280 )?;
281 let rows = stmt.query_map([], |row| {
282 Ok((
283 row.get::<_, i64>(0)?,
284 row.get::<_, String>(1)?,
285 row.get::<_, String>(2)?,
286 row.get::<_, String>(3)?,
287 ))
288 })?;
289 let mut out = Vec::new();
290 for (i, row) in rows.enumerate() {
291 let (rowid, artifact_type, artifact_hash, timestamp) = row?;
292 let bytes = hex::decode(&artifact_hash)
293 .map_err(|e| anyhow!("hash hex decode at row {rowid}: {e}"))?;
294 if bytes.len() != 32 {
295 return Err(anyhow!(
296 "hash must be 32 bytes at row {rowid}, got {}",
297 bytes.len()
298 ));
299 }
300 let mut arr = [0u8; 32];
301 arr.copy_from_slice(&bytes);
302 out.push(RebuildRow {
303 sequence: i as u64,
306 timestamp: chrono::DateTime::parse_from_rfc3339(×tamp)
307 .with_context(|| format!("parsing timestamp at row {rowid}"))?
308 .with_timezone(&chrono::Utc),
309 artifact_type: artifact_type
310 .parse()
311 .map_err(|e| anyhow!("artifact type at row {rowid}: {e}"))?,
312 artifact_hash: arr,
313 });
314 }
315 Ok(out)
316 }
317
318 pub fn store_ots_proof(
319 &self,
320 tree_size: u64,
321 root_hash: &[u8; 32],
322 ots_proof: &[u8],
323 bitcoin_height: Option<u64>,
324 ) -> Result<()> {
325 let conn = self.conn.lock();
326 conn.execute(
327 "INSERT OR REPLACE INTO ots_proofs
328 (tree_size, root_hash, ots_proof, bitcoin_height, anchor_time)
329 VALUES (?1, ?2, ?3, ?4, ?5)",
330 params![
331 tree_size as i64,
332 hex::encode(root_hash),
333 ots_proof,
334 bitcoin_height.map(|h| h as i64),
335 chrono::Utc::now().to_rfc3339(),
336 ],
337 )?;
338 Ok(())
339 }
340
341 pub fn get_ots_proof(&self, tree_size: u64) -> Result<Option<OtsProofRow>> {
342 let conn = self.conn.lock();
343 let row = conn.query_row(
344 "SELECT ots_proof, bitcoin_height, anchor_time
345 FROM ots_proofs WHERE tree_size = ?1",
346 params![tree_size as i64],
347 |row| {
348 let proof: Vec<u8> = row.get(0)?;
349 let bh: Option<i64> = row.get(1)?;
350 let at: String = row.get(2)?;
351 Ok((proof, bh.map(|h| h as u64), at))
352 },
353 );
354 match row {
355 Ok(t) => Ok(Some(t)),
356 Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
357 Err(e) => Err(e.into()),
358 }
359 }
360
361 pub fn store_witness_sig(
362 &self,
363 tree_size: u64,
364 root_hash: &[u8; 32],
365 witness_id: &str,
366 signature: &[u8],
367 ) -> Result<()> {
368 let conn = self.conn.lock();
369 conn.execute(
370 "INSERT OR REPLACE INTO witness_sigs
371 (tree_size, root_hash, witness_id, signature, timestamp)
372 VALUES (?1, ?2, ?3, ?4, ?5)",
373 params![
374 tree_size as i64,
375 hex::encode(root_hash),
376 witness_id,
377 signature,
378 chrono::Utc::now().to_rfc3339(),
379 ],
380 )?;
381 Ok(())
382 }
383
384 pub fn witness_sigs_for_size(&self, tree_size: u64) -> Result<Vec<(String, Vec<u8>, String)>> {
385 let conn = self.conn.lock();
386 let mut stmt = conn.prepare(
387 "SELECT witness_id, signature, timestamp
388 FROM witness_sigs WHERE tree_size = ?1
389 ORDER BY witness_id ASC",
390 )?;
391 let rows = stmt.query_map(params![tree_size as i64], |row| {
392 let wid: String = row.get(0)?;
393 let sig: Vec<u8> = row.get(1)?;
394 let ts: String = row.get(2)?;
395 Ok((wid, sig, ts))
396 })?;
397 let mut out = Vec::new();
398 for row in rows {
399 out.push(row?);
400 }
401 Ok(out)
402 }
403}