1use std::collections::HashMap;
4use std::fmt::Debug;
5use std::str::FromStr;
6use std::sync::Arc;
7
8use async_trait::async_trait;
9use cdk_common::database::{self, MintAuthDatabase, MintAuthTransaction};
10use cdk_common::mint::MintKeySetInfo;
11use cdk_common::nuts::{AuthProof, BlindSignature, Id, PublicKey, State};
12use cdk_common::{AuthRequired, ProtectedEndpoint};
13use migrations::MIGRATIONS;
14use tracing::instrument;
15
16use super::SQLTransaction;
17use crate::column_as_string;
18use crate::common::migrate;
19use crate::database::{ConnectionWithTransaction, DatabaseExecutor};
20use crate::mint::keys::sql_row_to_keyset_info;
21use crate::mint::signatures::sql_row_to_blind_signature;
22use crate::mint::Error;
23use crate::pool::{DatabasePool, Pool, PooledResource};
24use crate::stmt::query;
25
26#[derive(Debug, Clone)]
28pub struct SQLMintAuthDatabase<RM>
29where
30 RM: DatabasePool + 'static,
31{
32 pool: Arc<Pool<RM>>,
33}
34
35impl<RM> SQLMintAuthDatabase<RM>
36where
37 RM: DatabasePool + 'static,
38{
39 pub async fn new<X>(db: X) -> Result<Self, Error>
41 where
42 X: Into<RM::Config>,
43 {
44 let pool = Pool::new(db.into());
45 Self::migrate(pool.get().await.map_err(|e| Error::Database(Box::new(e)))?).await?;
46 Ok(Self { pool })
47 }
48
49 async fn migrate(conn: PooledResource<RM>) -> Result<(), Error> {
51 let tx = ConnectionWithTransaction::new(conn).await?;
52 migrate(&tx, RM::Connection::name(), MIGRATIONS).await?;
53 tx.commit().await?;
54 Ok(())
55 }
56}
57
58#[rustfmt::skip]
59mod migrations {
60 include!(concat!(env!("OUT_DIR"), "/migrations_mint_auth.rs"));
61}
62
63#[async_trait]
64impl<RM> MintAuthTransaction<database::Error> for SQLTransaction<RM>
65where
66 RM: DatabasePool + 'static,
67{
68 #[instrument(skip(self))]
69 async fn set_active_keyset(&mut self, id: Id) -> Result<(), database::Error> {
70 tracing::info!("Setting auth keyset {id} active");
71 query(
72 r#"
73 UPDATE keyset
74 SET active = CASE
75 WHEN id = :id THEN TRUE
76 ELSE FALSE
77 END;
78 "#,
79 )?
80 .bind("id", id.to_string())
81 .execute(&self.inner)
82 .await?;
83
84 Ok(())
85 }
86
87 async fn add_keyset_info(&mut self, keyset: MintKeySetInfo) -> Result<(), database::Error> {
88 query(
89 r#"
90 INSERT INTO
91 keyset (
92 id, unit, active, valid_from, valid_to, derivation_path,
93 amounts, input_fee_ppk, derivation_path_index
94 )
95 VALUES (
96 :id, :unit, :active, :valid_from, :valid_to, :derivation_path,
97 :amounts, :input_fee_ppk, :derivation_path_index
98 )
99 ON CONFLICT(id) DO UPDATE SET
100 unit = excluded.unit,
101 active = excluded.active,
102 valid_from = excluded.valid_from,
103 valid_to = excluded.valid_to,
104 derivation_path = excluded.derivation_path,
105 amounts = excluded.amounts,
106 input_fee_ppk = excluded.input_fee_ppk,
107 derivation_path_index = excluded.derivation_path_index
108 "#,
109 )?
110 .bind("id", keyset.id.to_string())
111 .bind("unit", keyset.unit.to_string())
112 .bind("active", keyset.active)
113 .bind("valid_from", keyset.valid_from as i64)
114 .bind("valid_to", keyset.final_expiry.map(|v| v as i64))
115 .bind("derivation_path", keyset.derivation_path.to_string())
116 .bind("amounts", serde_json::to_string(&keyset.amounts).ok())
117 .bind("input_fee_ppk", keyset.input_fee_ppk as i64)
118 .bind("derivation_path_index", keyset.derivation_path_index)
119 .execute(&self.inner)
120 .await?;
121
122 Ok(())
123 }
124
125 async fn add_proof(&mut self, proof: AuthProof) -> Result<(), database::Error> {
126 let y = proof.y()?;
127 let rows_affected = query(
128 r#"
129 INSERT INTO proof
130 (y, keyset_id, secret, c, state)
131 VALUES
132 (:y, :keyset_id, :secret, :c, :state)
133 ON CONFLICT(y) DO NOTHING
134 "#,
135 )?
136 .bind("y", y.to_bytes().to_vec())
137 .bind("keyset_id", proof.keyset_id.to_string())
138 .bind("secret", proof.secret.to_string())
139 .bind("c", proof.c.to_bytes().to_vec())
140 .bind("state", State::Spent.to_string())
141 .execute(&self.inner)
142 .await?;
143
144 if rows_affected != 1 {
145 return Err(database::Error::Duplicate);
146 }
147
148 Ok(())
149 }
150
151 async fn update_proof_state(
152 &mut self,
153 y: &PublicKey,
154 proofs_state: State,
155 ) -> Result<Option<State>, Self::Err> {
156 let current_state = query(r#"SELECT state FROM proof WHERE y = :y FOR UPDATE"#)?
157 .bind("y", y.to_bytes().to_vec())
158 .pluck(&self.inner)
159 .await?
160 .map(|state| Ok::<_, Error>(column_as_string!(state, State::from_str)))
161 .transpose()?;
162
163 query(r#"UPDATE proof SET state = :new_state WHERE y = :y"#)?
164 .bind("y", y.to_bytes().to_vec())
165 .bind("new_state", proofs_state.to_string())
166 .execute(&self.inner)
167 .await?;
168
169 Ok(current_state)
170 }
171
172 async fn add_blind_signatures(
173 &mut self,
174 blinded_messages: &[PublicKey],
175 blind_signatures: &[BlindSignature],
176 ) -> Result<(), database::Error> {
177 for (message, signature) in blinded_messages.iter().zip(blind_signatures) {
178 query(
179 r#"
180 INSERT
181 INTO blind_signature
182 (blinded_message, amount, keyset_id, c)
183 VALUES
184 (:blinded_message, :amount, :keyset_id, :c)
185 "#,
186 )?
187 .bind("blinded_message", message.to_bytes().to_vec())
188 .bind("amount", u64::from(signature.amount) as i64)
189 .bind("keyset_id", signature.keyset_id.to_string())
190 .bind("c", signature.c.to_bytes().to_vec())
191 .execute(&self.inner)
192 .await?;
193 }
194
195 Ok(())
196 }
197
198 async fn add_protected_endpoints(
199 &mut self,
200 protected_endpoints: HashMap<ProtectedEndpoint, AuthRequired>,
201 ) -> Result<(), database::Error> {
202 for (endpoint, auth) in protected_endpoints.iter() {
203 if let Err(err) = query(
204 r#"
205 INSERT INTO protected_endpoints
206 (endpoint, auth)
207 VALUES (:endpoint, :auth)
208 ON CONFLICT (endpoint) DO UPDATE SET
209 auth = EXCLUDED.auth;
210 "#,
211 )?
212 .bind("endpoint", serde_json::to_string(endpoint)?)
213 .bind("auth", serde_json::to_string(auth)?)
214 .execute(&self.inner)
215 .await
216 {
217 tracing::debug!(
218 "Attempting to add protected endpoint. Skipping.... {:?}",
219 err
220 );
221 }
222 }
223
224 Ok(())
225 }
226 async fn remove_protected_endpoints(
227 &mut self,
228 protected_endpoints: Vec<ProtectedEndpoint>,
229 ) -> Result<(), database::Error> {
230 query(r#"DELETE FROM protected_endpoints WHERE endpoint IN (:endpoints)"#)?
231 .bind_vec(
232 "endpoints",
233 protected_endpoints
234 .iter()
235 .map(serde_json::to_string)
236 .collect::<Result<_, _>>()?,
237 )?
238 .execute(&self.inner)
239 .await?;
240 Ok(())
241 }
242}
243
244#[async_trait]
245impl<RM> MintAuthDatabase for SQLMintAuthDatabase<RM>
246where
247 RM: DatabasePool + 'static,
248{
249 type Err = database::Error;
250
251 async fn begin_transaction<'a>(
252 &'a self,
253 ) -> Result<Box<dyn MintAuthTransaction<database::Error> + Send + Sync + 'a>, database::Error>
254 {
255 Ok(Box::new(SQLTransaction {
256 inner: ConnectionWithTransaction::new(
257 self.pool
258 .get()
259 .await
260 .map_err(|e| Error::Database(Box::new(e)))?,
261 )
262 .await?,
263 }))
264 }
265
266 async fn get_active_keyset_id(&self) -> Result<Option<Id>, Self::Err> {
267 let conn = self
268 .pool
269 .get()
270 .await
271 .map_err(|e| Error::Database(Box::new(e)))?;
272 Ok(query(
273 r#"
274 SELECT
275 id
276 FROM
277 keyset
278 WHERE
279 active = :active;
280 "#,
281 )?
282 .bind("active", true)
283 .pluck(&*conn)
284 .await?
285 .map(|id| Ok::<_, Error>(column_as_string!(id, Id::from_str, Id::from_bytes)))
286 .transpose()?)
287 }
288
289 async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err> {
290 let conn = self
291 .pool
292 .get()
293 .await
294 .map_err(|e| Error::Database(Box::new(e)))?;
295 Ok(query(
296 r#"SELECT
297 id,
298 unit,
299 active,
300 valid_from,
301 valid_to,
302 derivation_path,
303 derivation_path_index,
304 amounts,
305 input_fee_ppk
306 FROM
307 keyset
308 WHERE id=:id"#,
309 )?
310 .bind("id", id.to_string())
311 .fetch_one(&*conn)
312 .await?
313 .map(sql_row_to_keyset_info)
314 .transpose()?)
315 }
316
317 async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err> {
318 let conn = self
319 .pool
320 .get()
321 .await
322 .map_err(|e| Error::Database(Box::new(e)))?;
323 Ok(query(
324 r#"SELECT
325 id,
326 unit,
327 active,
328 valid_from,
329 valid_to,
330 derivation_path,
331 derivation_path_index,
332 amounts,
333 input_fee_ppk
334 FROM
335 keyset
336 WHERE id=:id"#,
337 )?
338 .fetch_all(&*conn)
339 .await?
340 .into_iter()
341 .map(sql_row_to_keyset_info)
342 .collect::<Result<Vec<_>, _>>()?)
343 }
344
345 async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err> {
346 let conn = self
347 .pool
348 .get()
349 .await
350 .map_err(|e| Error::Database(Box::new(e)))?;
351 let mut current_states = query(r#"SELECT y, state FROM proof WHERE y IN (:ys)"#)?
352 .bind_vec("ys", ys.iter().map(|y| y.to_bytes().to_vec()).collect())?
353 .fetch_all(&*conn)
354 .await?
355 .into_iter()
356 .map(|row| {
357 Ok((
358 column_as_string!(&row[0], PublicKey::from_hex, PublicKey::from_slice),
359 column_as_string!(&row[1], State::from_str),
360 ))
361 })
362 .collect::<Result<HashMap<_, _>, Error>>()?;
363
364 Ok(ys.iter().map(|y| current_states.remove(y)).collect())
365 }
366
367 async fn get_blind_signatures(
368 &self,
369 blinded_messages: &[PublicKey],
370 ) -> Result<Vec<Option<BlindSignature>>, Self::Err> {
371 let conn = self
372 .pool
373 .get()
374 .await
375 .map_err(|e| Error::Database(Box::new(e)))?;
376 let mut blinded_signatures = query(
377 r#"SELECT
378 keyset_id,
379 amount,
380 c,
381 dleq_e,
382 dleq_s,
383 blinded_message,
384 FROM
385 blind_signature
386 WHERE blinded_message IN (:blinded_message)
387 "#,
388 )?
389 .bind_vec(
390 "blinded_message",
391 blinded_messages
392 .iter()
393 .map(|bm| bm.to_bytes().to_vec())
394 .collect(),
395 )?
396 .fetch_all(&*conn)
397 .await?
398 .into_iter()
399 .map(|mut row| {
400 Ok((
401 column_as_string!(
402 &row.pop().ok_or(Error::InvalidDbResponse)?,
403 PublicKey::from_hex,
404 PublicKey::from_slice
405 ),
406 sql_row_to_blind_signature(row)?,
407 ))
408 })
409 .collect::<Result<HashMap<_, _>, Error>>()?;
410 Ok(blinded_messages
411 .iter()
412 .map(|bm| blinded_signatures.remove(bm))
413 .collect())
414 }
415
416 async fn get_auth_for_endpoint(
417 &self,
418 protected_endpoint: ProtectedEndpoint,
419 ) -> Result<Option<AuthRequired>, Self::Err> {
420 let conn = self
421 .pool
422 .get()
423 .await
424 .map_err(|e| Error::Database(Box::new(e)))?;
425 Ok(
426 query(r#"SELECT auth FROM protected_endpoints WHERE endpoint = :endpoint"#)?
427 .bind("endpoint", serde_json::to_string(&protected_endpoint)?)
428 .pluck(&*conn)
429 .await?
430 .map(|auth| {
431 Ok::<_, Error>(column_as_string!(
432 auth,
433 serde_json::from_str,
434 serde_json::from_slice
435 ))
436 })
437 .transpose()?,
438 )
439 }
440
441 async fn get_auth_for_endpoints(
442 &self,
443 ) -> Result<HashMap<ProtectedEndpoint, Option<AuthRequired>>, Self::Err> {
444 let conn = self
445 .pool
446 .get()
447 .await
448 .map_err(|e| Error::Database(Box::new(e)))?;
449 Ok(query(r#"SELECT endpoint, auth FROM protected_endpoints"#)?
450 .fetch_all(&*conn)
451 .await?
452 .into_iter()
453 .map(|row| {
454 let endpoint =
455 column_as_string!(&row[0], serde_json::from_str, serde_json::from_slice);
456 let auth = column_as_string!(&row[1], serde_json::from_str, serde_json::from_slice);
457 Ok((endpoint, Some(auth)))
458 })
459 .collect::<Result<HashMap<_, _>, Error>>()?)
460 }
461}