1use std::collections::HashMap;
4use std::str::FromStr;
5
6use async_trait::async_trait;
7use cdk_common::database::{self, Error, MintSignatureTransaction, MintSignaturesDatabase};
8use cdk_common::quote_id::QuoteId;
9use cdk_common::util::unix_time;
10use cdk_common::{Amount, BlindSignature, BlindSignatureDleq, Id, PublicKey, SecretKey};
11
12use super::proofs::sql_row_to_hashmap_amount;
13use super::{SQLMintDatabase, SQLTransaction};
14use crate::pool::DatabasePool;
15use crate::stmt::{query, Column};
16use crate::{column_as_nullable_string, column_as_number, column_as_string, unpack_into};
17
18pub(crate) fn sql_row_to_blind_signature(row: Vec<Column>) -> Result<BlindSignature, Error> {
19 unpack_into!(
20 let (
21 keyset_id, amount, c, dleq_e, dleq_s
22 ) = row
23 );
24
25 let dleq = match (
26 column_as_nullable_string!(dleq_e),
27 column_as_nullable_string!(dleq_s),
28 ) {
29 (Some(e), Some(s)) => Some(BlindSignatureDleq {
30 e: SecretKey::from_hex(e)?,
31 s: SecretKey::from_hex(s)?,
32 }),
33 _ => None,
34 };
35
36 let amount: u64 = column_as_number!(amount);
37
38 Ok(BlindSignature {
39 amount: Amount::from(amount),
40 keyset_id: column_as_string!(keyset_id, Id::from_str, Id::from_bytes),
41 c: column_as_string!(c, PublicKey::from_hex, PublicKey::from_slice),
42 dleq,
43 })
44}
45
46#[async_trait]
47impl<RM> MintSignatureTransaction for SQLTransaction<RM>
48where
49 RM: DatabasePool + 'static,
50{
51 type Err = Error;
52
53 async fn add_blind_signatures(
54 &mut self,
55 blinded_messages: &[PublicKey],
56 blind_signatures: &[BlindSignature],
57 quote_id: Option<QuoteId>,
58 ) -> Result<(), Self::Err> {
59 let current_time = unix_time();
60
61 if blinded_messages.len() != blind_signatures.len() {
62 return Err(database::Error::Internal(
63 "Mismatched array lengths for blinded messages and blind signatures".to_string(),
64 ));
65 }
66
67 let mut existing_rows = query(
69 r#"
70 SELECT blinded_message, c, dleq_e, dleq_s
71 FROM blind_signature
72 WHERE blinded_message IN (:blinded_messages)
73 FOR UPDATE
74 "#,
75 )?
76 .bind_vec(
77 "blinded_messages",
78 blinded_messages
79 .iter()
80 .map(|message| message.to_bytes().to_vec())
81 .collect(),
82 )
83 .fetch_all(&self.inner)
84 .await?
85 .into_iter()
86 .map(|mut row| {
87 Ok((
88 column_as_string!(&row.remove(0), PublicKey::from_hex, PublicKey::from_slice),
89 (row[0].clone(), row[1].clone(), row[2].clone()),
90 ))
91 })
92 .collect::<Result<HashMap<_, _>, Error>>()?;
93
94 for (message, signature) in blinded_messages.iter().zip(blind_signatures) {
96 match existing_rows.remove(message) {
97 None => {
98 query(
100 r#"
101 INSERT INTO blind_signature
102 (blinded_message, amount, keyset_id, c, quote_id, dleq_e, dleq_s, created_time, signed_time)
103 VALUES
104 (:blinded_message, :amount, :keyset_id, :c, :quote_id, :dleq_e, :dleq_s, :created_time, :signed_time)
105 "#,
106 )?
107 .bind("blinded_message", message.to_bytes().to_vec())
108 .bind("amount", u64::from(signature.amount) as i64)
109 .bind("keyset_id", signature.keyset_id.to_string())
110 .bind("c", signature.c.to_bytes().to_vec())
111 .bind("quote_id", quote_id.as_ref().map(|q| q.to_string()))
112 .bind(
113 "dleq_e",
114 signature.dleq.as_ref().map(|dleq| dleq.e.to_secret_hex()),
115 )
116 .bind(
117 "dleq_s",
118 signature.dleq.as_ref().map(|dleq| dleq.s.to_secret_hex()),
119 )
120 .bind("created_time", current_time as i64)
121 .bind("signed_time", current_time as i64)
122 .execute(&self.inner)
123 .await?;
124
125 query(
126 r#"
127 INSERT INTO keyset_amounts (keyset_id, total_issued, total_redeemed)
128 VALUES (:keyset_id, :amount, 0)
129 ON CONFLICT (keyset_id)
130 DO UPDATE SET total_issued = keyset_amounts.total_issued + EXCLUDED.total_issued
131 "#,
132 )?
133 .bind("amount", u64::from(signature.amount) as i64)
134 .bind("keyset_id", signature.keyset_id.to_string())
135 .execute(&self.inner)
136 .await?;
137 }
138 Some((c, _dleq_e, _dleq_s)) => {
139 match c {
141 Column::Null => {
142 query(
144 r#"
145 UPDATE blind_signature
146 SET c = :c, dleq_e = :dleq_e, dleq_s = :dleq_s, signed_time = :signed_time, amount = :amount
147 WHERE blinded_message = :blinded_message
148 "#,
149 )?
150 .bind("c", signature.c.to_bytes().to_vec())
151 .bind(
152 "dleq_e",
153 signature.dleq.as_ref().map(|dleq| dleq.e.to_secret_hex()),
154 )
155 .bind(
156 "dleq_s",
157 signature.dleq.as_ref().map(|dleq| dleq.s.to_secret_hex()),
158 )
159 .bind("blinded_message", message.to_bytes().to_vec())
160 .bind("signed_time", current_time as i64)
161 .bind("amount", u64::from(signature.amount) as i64)
162 .execute(&self.inner)
163 .await?;
164
165 query(
166 r#"
167 INSERT INTO keyset_amounts (keyset_id, total_issued, total_redeemed)
168 VALUES (:keyset_id, :amount, 0)
169 ON CONFLICT (keyset_id)
170 DO UPDATE SET total_issued = keyset_amounts.total_issued + EXCLUDED.total_issued
171 "#,
172 )?
173 .bind("amount", u64::from(signature.amount) as i64)
174 .bind("keyset_id", signature.keyset_id.to_string())
175 .execute(&self.inner)
176 .await?;
177 }
178 _ => {
179 tracing::error!(
181 "Attempting to add signature to message already signed {}",
182 message
183 );
184
185 return Err(database::Error::Duplicate);
186 }
187 }
188 }
189 }
190 }
191
192 debug_assert!(
193 existing_rows.is_empty(),
194 "Unexpected existing rows remain: {:?}",
195 existing_rows.keys().collect::<Vec<_>>()
196 );
197
198 if !existing_rows.is_empty() {
199 tracing::error!("Did not check all existing rows");
200 return Err(Error::Internal(
201 "Did not check all existing rows".to_string(),
202 ));
203 }
204
205 Ok(())
206 }
207
208 async fn get_blind_signatures(
209 &mut self,
210 blinded_messages: &[PublicKey],
211 ) -> Result<Vec<Option<BlindSignature>>, Self::Err> {
212 let mut blinded_signatures = query(
213 r#"SELECT
214 keyset_id,
215 amount,
216 c,
217 dleq_e,
218 dleq_s,
219 blinded_message
220 FROM
221 blind_signature
222 WHERE blinded_message IN (:b) AND c IS NOT NULL
223 "#,
224 )?
225 .bind_vec(
226 "b",
227 blinded_messages
228 .iter()
229 .map(|b| b.to_bytes().to_vec())
230 .collect(),
231 )
232 .fetch_all(&self.inner)
233 .await?
234 .into_iter()
235 .map(|mut row| {
236 Ok((
237 column_as_string!(
238 &row.pop().ok_or(Error::InvalidDbResponse)?,
239 PublicKey::from_hex,
240 PublicKey::from_slice
241 ),
242 sql_row_to_blind_signature(row)?,
243 ))
244 })
245 .collect::<Result<HashMap<_, _>, Error>>()?;
246 Ok(blinded_messages
247 .iter()
248 .map(|y| blinded_signatures.remove(y))
249 .collect())
250 }
251}
252
253#[async_trait]
254impl<RM> MintSignaturesDatabase for SQLMintDatabase<RM>
255where
256 RM: DatabasePool + 'static,
257{
258 type Err = Error;
259
260 async fn get_blind_signatures(
261 &self,
262 blinded_messages: &[PublicKey],
263 ) -> Result<Vec<Option<BlindSignature>>, Self::Err> {
264 let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
265 let mut blinded_signatures = query(
266 r#"SELECT
267 keyset_id,
268 amount,
269 c,
270 dleq_e,
271 dleq_s,
272 blinded_message
273 FROM
274 blind_signature
275 WHERE blinded_message IN (:b) AND c IS NOT NULL
276 "#,
277 )?
278 .bind_vec(
279 "b",
280 blinded_messages
281 .iter()
282 .map(|b_| b_.to_bytes().to_vec())
283 .collect(),
284 )
285 .fetch_all(&*conn)
286 .await?
287 .into_iter()
288 .map(|mut row| {
289 Ok((
290 column_as_string!(
291 &row.pop().ok_or(Error::InvalidDbResponse)?,
292 PublicKey::from_hex,
293 PublicKey::from_slice
294 ),
295 sql_row_to_blind_signature(row)?,
296 ))
297 })
298 .collect::<Result<HashMap<_, _>, Error>>()?;
299 Ok(blinded_messages
300 .iter()
301 .map(|y| blinded_signatures.remove(y))
302 .collect())
303 }
304
305 async fn get_blind_signatures_for_keyset(
306 &self,
307 keyset_id: &Id,
308 ) -> Result<Vec<BlindSignature>, Self::Err> {
309 let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
310 Ok(query(
311 r#"
312 SELECT
313 keyset_id,
314 amount,
315 c,
316 dleq_e,
317 dleq_s
318 FROM
319 blind_signature
320 WHERE
321 keyset_id=:keyset_id AND c IS NOT NULL
322 "#,
323 )?
324 .bind("keyset_id", keyset_id.to_string())
325 .fetch_all(&*conn)
326 .await?
327 .into_iter()
328 .map(sql_row_to_blind_signature)
329 .collect::<Result<Vec<BlindSignature>, _>>()?)
330 }
331
332 async fn get_blind_signatures_for_quote(
334 &self,
335 quote_id: &QuoteId,
336 ) -> Result<Vec<BlindSignature>, Self::Err> {
337 let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
338 Ok(query(
339 r#"
340 SELECT
341 keyset_id,
342 amount,
343 c,
344 dleq_e,
345 dleq_s
346 FROM
347 blind_signature
348 WHERE
349 quote_id=:quote_id AND c IS NOT NULL
350 "#,
351 )?
352 .bind("quote_id", quote_id.to_string())
353 .fetch_all(&*conn)
354 .await?
355 .into_iter()
356 .map(sql_row_to_blind_signature)
357 .collect::<Result<Vec<BlindSignature>, _>>()?)
358 }
359
360 async fn get_total_issued(&self) -> Result<HashMap<Id, Amount>, Self::Err> {
362 let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
363 query(
364 r#"
365 SELECT
366 keyset_id,
367 total_issued as amount
368 FROM
369 keyset_amounts
370 "#,
371 )?
372 .fetch_all(&*conn)
373 .await?
374 .into_iter()
375 .map(sql_row_to_hashmap_amount)
376 .collect()
377 }
378
379 async fn get_blinded_secrets_by_operation_id(
380 &self,
381 operation_id: &uuid::Uuid,
382 ) -> Result<Vec<PublicKey>, Self::Err> {
383 let conn = self.pool.get().map_err(|e| Error::Database(Box::new(e)))?;
384 query(
385 r#"
386 SELECT
387 blinded_message
388 FROM
389 blind_signature
390 WHERE
391 operation_id = :operation_id
392 "#,
393 )?
394 .bind("operation_id", operation_id.to_string())
395 .fetch_all(&*conn)
396 .await?
397 .into_iter()
398 .map(|row| -> Result<PublicKey, Error> {
399 Ok(column_as_string!(
400 &row[0],
401 PublicKey::from_hex,
402 PublicKey::from_slice
403 ))
404 })
405 .collect::<Result<Vec<_>, _>>()
406 }
407}