1use std::str::FromStr;
4
5use cdk::nuts::State as CdkState;
6use serde::{Deserialize, Serialize};
7
8use super::amount::{Amount, CurrencyUnit};
9use super::mint::MintUrl;
10use crate::error::FfiError;
11
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
14pub enum ProofState {
15 Unspent,
16 Pending,
17 Spent,
18 Reserved,
19 PendingSpent,
20}
21
22impl From<CdkState> for ProofState {
23 fn from(state: CdkState) -> Self {
24 match state {
25 CdkState::Unspent => ProofState::Unspent,
26 CdkState::Pending => ProofState::Pending,
27 CdkState::Spent => ProofState::Spent,
28 CdkState::Reserved => ProofState::Reserved,
29 CdkState::PendingSpent => ProofState::PendingSpent,
30 }
31 }
32}
33
34impl From<ProofState> for CdkState {
35 fn from(state: ProofState) -> Self {
36 match state {
37 ProofState::Unspent => CdkState::Unspent,
38 ProofState::Pending => CdkState::Pending,
39 ProofState::Spent => CdkState::Spent,
40 ProofState::Reserved => CdkState::Reserved,
41 ProofState::PendingSpent => CdkState::PendingSpent,
42 }
43 }
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
48pub struct Proof {
49 pub amount: Amount,
51 pub secret: String,
53 pub c: String,
55 pub keyset_id: String,
57 pub witness: Option<Witness>,
59 pub dleq: Option<ProofDleq>,
61 pub p2pk_e: Option<String>,
63}
64
65impl From<cdk::nuts::Proof> for Proof {
66 fn from(proof: cdk::nuts::Proof) -> Self {
67 Self {
68 amount: proof.amount.into(),
69 secret: proof.secret.to_string(),
70 c: proof.c.to_string(),
71 keyset_id: proof.keyset_id.to_string(),
72 witness: proof.witness.map(|w| w.into()),
73 dleq: proof.dleq.map(|d| d.into()),
74 p2pk_e: proof.p2pk_e.map(|p| p.to_string()),
75 }
76 }
77}
78
79impl TryFrom<Proof> for cdk::nuts::Proof {
80 type Error = FfiError;
81
82 fn try_from(proof: Proof) -> Result<Self, Self::Error> {
83 use std::str::FromStr;
84
85 use cdk::nuts::Id;
86
87 Ok(Self {
88 amount: proof.amount.into(),
89 secret: cdk::secret::Secret::from_str(&proof.secret)
90 .map_err(|e| FfiError::internal(format!("Invalid secret: {}", e)))?,
91 c: cdk::nuts::PublicKey::from_str(&proof.c)
92 .map_err(|e| FfiError::internal(format!("Invalid public key: {}", e)))?,
93 keyset_id: Id::from_str(&proof.keyset_id)
94 .map_err(|e| FfiError::internal(format!("Invalid keyset ID: {}", e)))?,
95 witness: proof.witness.map(|w| w.into()),
96 dleq: proof.dleq.map(TryInto::try_into).transpose()?,
97 p2pk_e: proof
98 .p2pk_e
99 .map(|p| cdk::nuts::PublicKey::from_str(&p))
100 .transpose()
101 .map_err(|e| FfiError::internal(format!("Invalid p2pk_e: {}", e)))?,
102 })
103 }
104}
105
106#[uniffi::export]
108pub fn proof_y(proof: &Proof) -> Result<String, FfiError> {
109 let cdk_proof: cdk::nuts::Proof = proof.clone().try_into()?;
111 Ok(cdk_proof.y()?.to_string())
112}
113
114#[uniffi::export]
116pub fn proof_is_active(proof: &Proof, active_keyset_ids: Vec<String>) -> bool {
117 use cdk::nuts::Id;
118 let ids: Vec<Id> = active_keyset_ids
119 .into_iter()
120 .filter_map(|id| Id::from_str(&id).ok())
121 .collect();
122
123 if let Ok(keyset_id) = Id::from_str(&proof.keyset_id) {
125 ids.contains(&keyset_id)
126 } else {
127 false
128 }
129}
130
131#[uniffi::export]
133pub fn proof_has_dleq(proof: &Proof) -> bool {
134 proof.dleq.is_some()
135}
136
137#[uniffi::export]
139pub fn proof_verify_htlc(proof: &Proof) -> Result<(), FfiError> {
140 let cdk_proof: cdk::nuts::Proof = proof.clone().try_into()?;
141 cdk_proof.verify_htlc().map_err(FfiError::internal)
142}
143
144#[uniffi::export]
146pub fn proof_verify_dleq(
147 proof: &Proof,
148 mint_pubkey: super::keys::PublicKey,
149) -> Result<(), FfiError> {
150 let cdk_proof: cdk::nuts::Proof = proof.clone().try_into()?;
151 let cdk_pubkey: cdk::nuts::PublicKey = mint_pubkey.try_into()?;
152 cdk_proof
153 .verify_dleq(cdk_pubkey)
154 .map_err(FfiError::internal)
155}
156
157#[uniffi::export]
159pub fn proof_sign_p2pk(proof: Proof, secret_key_hex: String) -> Result<Proof, FfiError> {
160 let mut cdk_proof: cdk::nuts::Proof = proof.try_into()?;
161 let secret_key = cdk::nuts::SecretKey::from_hex(&secret_key_hex)
162 .map_err(|e| FfiError::internal(format!("Invalid secret key: {}", e)))?;
163
164 cdk_proof
165 .sign_p2pk(secret_key)
166 .map_err(FfiError::internal)?;
167
168 Ok(cdk_proof.into())
169}
170
171pub type Proofs = Vec<Proof>;
173
174#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
176pub struct ProofDleq {
177 pub e: String,
179 pub s: String,
181 pub r: String,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
187pub struct BlindSignatureDleq {
188 pub e: String,
190 pub s: String,
192}
193
194impl From<cdk::nuts::ProofDleq> for ProofDleq {
195 fn from(dleq: cdk::nuts::ProofDleq) -> Self {
196 Self {
197 e: dleq.e.to_secret_hex(),
198 s: dleq.s.to_secret_hex(),
199 r: dleq.r.to_secret_hex(),
200 }
201 }
202}
203
204impl TryFrom<ProofDleq> for cdk::nuts::ProofDleq {
205 type Error = FfiError;
206
207 fn try_from(dleq: ProofDleq) -> Result<Self, Self::Error> {
208 Ok(Self {
209 e: cdk::nuts::SecretKey::from_hex(&dleq.e)
210 .map_err(|e| FfiError::internal(format!("Invalid dleq e: {}", e)))?,
211 s: cdk::nuts::SecretKey::from_hex(&dleq.s)
212 .map_err(|e| FfiError::internal(format!("Invalid dleq s: {}", e)))?,
213 r: cdk::nuts::SecretKey::from_hex(&dleq.r)
214 .map_err(|e| FfiError::internal(format!("Invalid dleq r: {}", e)))?,
215 })
216 }
217}
218
219impl From<cdk::nuts::BlindSignatureDleq> for BlindSignatureDleq {
220 fn from(dleq: cdk::nuts::BlindSignatureDleq) -> Self {
221 Self {
222 e: dleq.e.to_secret_hex(),
223 s: dleq.s.to_secret_hex(),
224 }
225 }
226}
227
228impl TryFrom<BlindSignatureDleq> for cdk::nuts::BlindSignatureDleq {
229 type Error = FfiError;
230
231 fn try_from(dleq: BlindSignatureDleq) -> Result<Self, Self::Error> {
232 Ok(Self {
233 e: cdk::nuts::SecretKey::from_hex(&dleq.e).map_err(|e| {
234 FfiError::internal(format!("Invalid blind signature dleq e: {}", e))
235 })?,
236 s: cdk::nuts::SecretKey::from_hex(&dleq.s).map_err(|e| {
237 FfiError::internal(format!("Invalid blind signature dleq s: {}", e))
238 })?,
239 })
240 }
241}
242
243#[uniffi::export]
245pub fn proofs_total_amount(proofs: &Proofs) -> Result<Amount, FfiError> {
246 let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
247 proofs.iter().map(|p| p.clone().try_into()).collect();
248 let cdk_proofs = cdk_proofs?;
249 use cdk::nuts::ProofsMethods;
250 Ok(cdk_proofs.total_amount()?.into())
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
255pub struct Conditions {
256 pub locktime: Option<u64>,
258 pub pubkeys: Vec<String>,
260 pub refund_keys: Vec<String>,
262 pub num_sigs: Option<u64>,
264 pub sig_flag: u8,
266 pub num_sigs_refund: Option<u64>,
268}
269
270impl From<cdk::nuts::nut10::Conditions> for Conditions {
271 fn from(conditions: cdk::nuts::nut10::Conditions) -> Self {
272 Self {
273 locktime: conditions.locktime,
274 pubkeys: conditions
275 .pubkeys
276 .unwrap_or_default()
277 .into_iter()
278 .map(|p| p.to_string())
279 .collect(),
280 refund_keys: conditions
281 .refund_keys
282 .unwrap_or_default()
283 .into_iter()
284 .map(|p| p.to_string())
285 .collect(),
286 num_sigs: conditions.num_sigs,
287 sig_flag: match conditions.sig_flag {
288 cdk::nuts::nut11::SigFlag::SigInputs => 0,
289 cdk::nuts::nut11::SigFlag::SigAll => 1,
290 },
291 num_sigs_refund: conditions.num_sigs_refund,
292 }
293 }
294}
295
296impl TryFrom<Conditions> for cdk::nuts::nut10::Conditions {
297 type Error = FfiError;
298
299 fn try_from(conditions: Conditions) -> Result<Self, Self::Error> {
300 let pubkeys = if conditions.pubkeys.is_empty() {
301 None
302 } else {
303 Some(
304 conditions
305 .pubkeys
306 .into_iter()
307 .map(|s| {
308 s.parse()
309 .map_err(|e| FfiError::internal(format!("Invalid pubkey: {}", e)))
310 })
311 .collect::<Result<Vec<_>, _>>()?,
312 )
313 };
314
315 let refund_keys = if conditions.refund_keys.is_empty() {
316 None
317 } else {
318 Some(
319 conditions
320 .refund_keys
321 .into_iter()
322 .map(|s| {
323 s.parse()
324 .map_err(|e| FfiError::internal(format!("Invalid refund key: {}", e)))
325 })
326 .collect::<Result<Vec<_>, _>>()?,
327 )
328 };
329
330 let sig_flag = match conditions.sig_flag {
331 0 => cdk::nuts::nut11::SigFlag::SigInputs,
332 1 => cdk::nuts::nut11::SigFlag::SigAll,
333 _ => return Err(FfiError::internal("Invalid sig_flag value")),
334 };
335
336 Ok(Self {
337 locktime: conditions.locktime,
338 pubkeys,
339 refund_keys,
340 num_sigs: conditions.num_sigs,
341 sig_flag,
342 num_sigs_refund: conditions.num_sigs_refund,
343 })
344 }
345}
346
347impl Conditions {
348 pub fn to_json(&self) -> Result<String, FfiError> {
350 Ok(serde_json::to_string(self)?)
351 }
352}
353
354#[uniffi::export]
356pub fn decode_conditions(json: String) -> Result<Conditions, FfiError> {
357 Ok(serde_json::from_str(&json)?)
358}
359
360#[uniffi::export]
362pub fn encode_conditions(conditions: Conditions) -> Result<String, FfiError> {
363 Ok(serde_json::to_string(&conditions)?)
364}
365
366#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
368pub enum Witness {
369 P2PK {
371 signatures: Vec<String>,
373 },
374 HTLC {
376 preimage: String,
378 signatures: Option<Vec<String>>,
380 },
381}
382
383impl From<cdk::nuts::Witness> for Witness {
384 fn from(witness: cdk::nuts::Witness) -> Self {
385 match witness {
386 cdk::nuts::Witness::P2PKWitness(p2pk) => Self::P2PK {
387 signatures: p2pk.signatures,
388 },
389 cdk::nuts::Witness::HTLCWitness(htlc) => Self::HTLC {
390 preimage: htlc.preimage,
391 signatures: htlc.signatures,
392 },
393 }
394 }
395}
396
397impl From<Witness> for cdk::nuts::Witness {
398 fn from(witness: Witness) -> Self {
399 match witness {
400 Witness::P2PK { signatures } => {
401 Self::P2PKWitness(cdk::nuts::nut11::P2PKWitness { signatures })
402 }
403 Witness::HTLC {
404 preimage,
405 signatures,
406 } => Self::HTLCWitness(cdk::nuts::nut14::HTLCWitness {
407 preimage,
408 signatures,
409 }),
410 }
411 }
412}
413
414#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
416pub enum SpendingConditions {
417 P2PK {
419 pubkey: String,
421 conditions: Option<Conditions>,
423 },
424 HTLC {
426 hash: String,
428 conditions: Option<Conditions>,
430 },
431}
432
433impl From<cdk::nuts::SpendingConditions> for SpendingConditions {
434 fn from(spending_conditions: cdk::nuts::SpendingConditions) -> Self {
435 match spending_conditions {
436 cdk::nuts::SpendingConditions::P2PKConditions { data, conditions } => Self::P2PK {
437 pubkey: data.to_string(),
438 conditions: conditions.map(Into::into),
439 },
440 cdk::nuts::SpendingConditions::HTLCConditions { data, conditions } => Self::HTLC {
441 hash: data.to_string(),
442 conditions: conditions.map(Into::into),
443 },
444 }
445 }
446}
447
448impl TryFrom<SpendingConditions> for cdk::nuts::SpendingConditions {
449 type Error = FfiError;
450
451 fn try_from(spending_conditions: SpendingConditions) -> Result<Self, Self::Error> {
452 match spending_conditions {
453 SpendingConditions::P2PK { pubkey, conditions } => {
454 let pubkey = pubkey
455 .parse()
456 .map_err(|e| FfiError::internal(format!("Invalid pubkey: {}", e)))?;
457 let conditions = conditions.map(|c| c.try_into()).transpose()?;
458 Ok(Self::P2PKConditions {
459 data: pubkey,
460 conditions,
461 })
462 }
463 SpendingConditions::HTLC { hash, conditions } => {
464 let hash = hash
465 .parse()
466 .map_err(|e| FfiError::internal(format!("Invalid hash: {}", e)))?;
467 let conditions = conditions.map(|c| c.try_into()).transpose()?;
468 Ok(Self::HTLCConditions {
469 data: hash,
470 conditions,
471 })
472 }
473 }
474 }
475}
476
477#[derive(Debug, Clone, uniffi::Record)]
479pub struct ProofInfo {
480 pub proof: Proof,
482 pub y: super::keys::PublicKey,
484 pub mint_url: MintUrl,
486 pub state: ProofState,
488 pub spending_condition: Option<SpendingConditions>,
490 pub unit: CurrencyUnit,
492 pub used_by_operation: Option<String>,
494 pub created_by_operation: Option<String>,
496}
497
498impl From<cdk::types::ProofInfo> for ProofInfo {
499 fn from(info: cdk::types::ProofInfo) -> Self {
500 Self {
501 proof: info.proof.into(),
502 y: info.y.into(),
503 mint_url: info.mint_url.into(),
504 state: info.state.into(),
505 spending_condition: info.spending_condition.map(Into::into),
506 unit: info.unit.into(),
507 used_by_operation: info.used_by_operation.map(|u| u.to_string()),
508 created_by_operation: info.created_by_operation.map(|u| u.to_string()),
509 }
510 }
511}
512
513#[uniffi::export]
515pub fn decode_proof_info(json: String) -> Result<ProofInfo, FfiError> {
516 let info: cdk::types::ProofInfo = serde_json::from_str(&json)?;
517 Ok(info.into())
518}
519
520#[uniffi::export]
522pub fn encode_proof_info(info: ProofInfo) -> Result<String, FfiError> {
523 use std::str::FromStr;
524 let cdk_info = cdk::types::ProofInfo {
526 proof: info.proof.try_into()?,
527 y: info.y.try_into()?,
528 mint_url: info.mint_url.try_into()?,
529 state: info.state.into(),
530 spending_condition: info.spending_condition.map(TryInto::try_into).transpose()?,
531 unit: info.unit.into(),
532 used_by_operation: info
533 .used_by_operation
534 .map(|id| uuid::Uuid::from_str(&id))
535 .transpose()
536 .map_err(|e| FfiError::internal(e.to_string()))?,
537 created_by_operation: info
538 .created_by_operation
539 .map(|id| uuid::Uuid::from_str(&id))
540 .transpose()
541 .map_err(|e| FfiError::internal(e.to_string()))?,
542 };
543 Ok(serde_json::to_string(&cdk_info)?)
544}
545
546#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
548pub struct ProofStateUpdate {
549 pub y: String,
551 pub state: ProofState,
553 pub witness: Option<String>,
555}
556
557impl From<cdk::nuts::nut07::ProofState> for ProofStateUpdate {
558 fn from(proof_state: cdk::nuts::nut07::ProofState) -> Self {
559 Self {
560 y: proof_state.y.to_string(),
561 state: proof_state.state.into(),
562 witness: proof_state.witness.map(|w| format!("{:?}", w)),
563 }
564 }
565}
566
567impl ProofStateUpdate {
568 pub fn to_json(&self) -> Result<String, FfiError> {
570 Ok(serde_json::to_string(self)?)
571 }
572}
573
574#[uniffi::export]
576pub fn decode_proof_state_update(json: String) -> Result<ProofStateUpdate, FfiError> {
577 Ok(serde_json::from_str(&json)?)
578}
579
580#[uniffi::export]
582pub fn encode_proof_state_update(update: ProofStateUpdate) -> Result<String, FfiError> {
583 Ok(serde_json::to_string(&update)?)
584}