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 (i, (message, signature)) in blinded_messages.iter().zip(blind_signatures).enumerate() {
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, order_index)
103 VALUES
104 (:blinded_message, :amount, :keyset_id, :c, :quote_id, :dleq_e, :dleq_s, :created_time, :signed_time, :order_index)
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 .bind("order_index", i as i64)
123 .execute(&self.inner)
124 .await?;
125
126 query(
127 r#"
128 INSERT INTO keyset_amounts (keyset_id, total_issued, total_redeemed)
129 VALUES (:keyset_id, :amount, 0)
130 ON CONFLICT (keyset_id)
131 DO UPDATE SET total_issued = keyset_amounts.total_issued + EXCLUDED.total_issued
132 "#,
133 )?
134 .bind("amount", u64::from(signature.amount) as i64)
135 .bind("keyset_id", signature.keyset_id.to_string())
136 .execute(&self.inner)
137 .await?;
138 }
139 Some((c, _dleq_e, _dleq_s)) => {
140 match c {
142 Column::Null => {
143 query(
145 r#"
146 UPDATE blind_signature
147 SET c = :c, dleq_e = :dleq_e, dleq_s = :dleq_s, signed_time = :signed_time, amount = :amount
148 WHERE blinded_message = :blinded_message
149 "#,
150 )?
151 .bind("c", signature.c.to_bytes().to_vec())
152 .bind(
153 "dleq_e",
154 signature.dleq.as_ref().map(|dleq| dleq.e.to_secret_hex()),
155 )
156 .bind(
157 "dleq_s",
158 signature.dleq.as_ref().map(|dleq| dleq.s.to_secret_hex()),
159 )
160 .bind("blinded_message", message.to_bytes().to_vec())
161 .bind("signed_time", current_time as i64)
162 .bind("amount", u64::from(signature.amount) as i64)
163 .execute(&self.inner)
164 .await?;
165
166 query(
167 r#"
168 INSERT INTO keyset_amounts (keyset_id, total_issued, total_redeemed)
169 VALUES (:keyset_id, :amount, 0)
170 ON CONFLICT (keyset_id)
171 DO UPDATE SET total_issued = keyset_amounts.total_issued + EXCLUDED.total_issued
172 "#,
173 )?
174 .bind("amount", u64::from(signature.amount) as i64)
175 .bind("keyset_id", signature.keyset_id.to_string())
176 .execute(&self.inner)
177 .await?;
178 }
179 _ => {
180 tracing::error!(
182 "Attempting to add signature to message already signed {}",
183 message
184 );
185
186 return Err(database::Error::Duplicate);
187 }
188 }
189 }
190 }
191 }
192
193 debug_assert!(
194 existing_rows.is_empty(),
195 "Unexpected existing rows remain: {:?}",
196 existing_rows.keys().collect::<Vec<_>>()
197 );
198
199 if !existing_rows.is_empty() {
200 tracing::error!("Did not check all existing rows");
201 return Err(Error::Internal(
202 "Did not check all existing rows".to_string(),
203 ));
204 }
205
206 Ok(())
207 }
208
209 async fn get_blind_signatures(
210 &mut self,
211 blinded_messages: &[PublicKey],
212 ) -> Result<Vec<Option<BlindSignature>>, Self::Err> {
213 let mut blinded_signatures = query(
214 r#"SELECT
215 keyset_id,
216 amount,
217 c,
218 dleq_e,
219 dleq_s,
220 blinded_message
221 FROM
222 blind_signature
223 WHERE blinded_message IN (:b) AND c IS NOT NULL
224 "#,
225 )?
226 .bind_vec(
227 "b",
228 blinded_messages
229 .iter()
230 .map(|b| b.to_bytes().to_vec())
231 .collect(),
232 )?
233 .fetch_all(&self.inner)
234 .await?
235 .into_iter()
236 .map(|mut row| {
237 Ok((
238 column_as_string!(
239 &row.pop().ok_or(Error::InvalidDbResponse)?,
240 PublicKey::from_hex,
241 PublicKey::from_slice
242 ),
243 sql_row_to_blind_signature(row)?,
244 ))
245 })
246 .collect::<Result<HashMap<_, _>, Error>>()?;
247 Ok(blinded_messages
248 .iter()
249 .map(|y| blinded_signatures.remove(y))
250 .collect())
251 }
252}
253
254#[async_trait]
255impl<RM> MintSignaturesDatabase for SQLMintDatabase<RM>
256where
257 RM: DatabasePool + 'static,
258{
259 type Err = Error;
260
261 async fn get_blind_signatures(
262 &self,
263 blinded_messages: &[PublicKey],
264 ) -> Result<Vec<Option<BlindSignature>>, Self::Err> {
265 let conn = self
266 .pool
267 .get()
268 .await
269 .map_err(|e| Error::Database(Box::new(e)))?;
270 let mut blinded_signatures = query(
271 r#"SELECT
272 keyset_id,
273 amount,
274 c,
275 dleq_e,
276 dleq_s,
277 blinded_message
278 FROM
279 blind_signature
280 WHERE blinded_message IN (:b) AND c IS NOT NULL
281 "#,
282 )?
283 .bind_vec(
284 "b",
285 blinded_messages
286 .iter()
287 .map(|b_| b_.to_bytes().to_vec())
288 .collect(),
289 )?
290 .fetch_all(&*conn)
291 .await?
292 .into_iter()
293 .map(|mut row| {
294 Ok((
295 column_as_string!(
296 &row.pop().ok_or(Error::InvalidDbResponse)?,
297 PublicKey::from_hex,
298 PublicKey::from_slice
299 ),
300 sql_row_to_blind_signature(row)?,
301 ))
302 })
303 .collect::<Result<HashMap<_, _>, Error>>()?;
304 Ok(blinded_messages
305 .iter()
306 .map(|y| blinded_signatures.remove(y))
307 .collect())
308 }
309
310 async fn get_blind_signatures_for_keyset(
311 &self,
312 keyset_id: &Id,
313 ) -> Result<Vec<BlindSignature>, Self::Err> {
314 let conn = self
315 .pool
316 .get()
317 .await
318 .map_err(|e| Error::Database(Box::new(e)))?;
319 Ok(query(
320 r#"
321 SELECT
322 keyset_id,
323 amount,
324 c,
325 dleq_e,
326 dleq_s
327 FROM
328 blind_signature
329 WHERE
330 keyset_id=:keyset_id AND c IS NOT NULL
331 "#,
332 )?
333 .bind("keyset_id", keyset_id.to_string())
334 .fetch_all(&*conn)
335 .await?
336 .into_iter()
337 .map(sql_row_to_blind_signature)
338 .collect::<Result<Vec<BlindSignature>, _>>()?)
339 }
340
341 async fn get_blind_signatures_for_quote(
343 &self,
344 quote_id: &QuoteId,
345 ) -> Result<Vec<BlindSignature>, Self::Err> {
346 let conn = self
347 .pool
348 .get()
349 .await
350 .map_err(|e| Error::Database(Box::new(e)))?;
351 Ok(query(
352 r#"
353 SELECT
354 keyset_id,
355 amount,
356 c,
357 dleq_e,
358 dleq_s
359 FROM
360 blind_signature
361 WHERE
362 quote_id=:quote_id AND c IS NOT NULL
363 ORDER BY order_index ASC
364 "#,
365 )?
366 .bind("quote_id", quote_id.to_string())
367 .fetch_all(&*conn)
368 .await?
369 .into_iter()
370 .map(sql_row_to_blind_signature)
371 .collect::<Result<Vec<BlindSignature>, _>>()?)
372 }
373
374 async fn get_total_issued(&self) -> Result<HashMap<Id, Amount>, Self::Err> {
376 let conn = self
377 .pool
378 .get()
379 .await
380 .map_err(|e| Error::Database(Box::new(e)))?;
381 query(
382 r#"
383 SELECT
384 keyset_id,
385 total_issued as amount
386 FROM
387 keyset_amounts
388 "#,
389 )?
390 .fetch_all(&*conn)
391 .await?
392 .into_iter()
393 .map(sql_row_to_hashmap_amount)
394 .collect()
395 }
396
397 async fn get_blinded_secrets_by_operation_id(
398 &self,
399 operation_id: &uuid::Uuid,
400 ) -> Result<Vec<PublicKey>, Self::Err> {
401 let conn = self
402 .pool
403 .get()
404 .await
405 .map_err(|e| Error::Database(Box::new(e)))?;
406 query(
407 r#"
408 SELECT
409 blinded_message
410 FROM
411 blind_signature
412 WHERE
413 operation_id = :operation_id
414 "#,
415 )?
416 .bind("operation_id", operation_id.to_string())
417 .fetch_all(&*conn)
418 .await?
419 .into_iter()
420 .map(|row| -> Result<PublicKey, Error> {
421 Ok(column_as_string!(
422 &row[0],
423 PublicKey::from_hex,
424 PublicKey::from_slice
425 ))
426 })
427 .collect::<Result<Vec<_>, _>>()
428 }
429}