Skip to main content

cdk_sql_common/mint/auth/
mod.rs

1//! SQL Mint Auth
2
3use 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/// Mint SQL Database
27#[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    /// Creates a new instance
40    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    /// Migrate
50    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}