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}
62
63impl From<cdk::nuts::Proof> for Proof {
64 fn from(proof: cdk::nuts::Proof) -> Self {
65 Self {
66 amount: proof.amount.into(),
67 secret: proof.secret.to_string(),
68 c: proof.c.to_string(),
69 keyset_id: proof.keyset_id.to_string(),
70 witness: proof.witness.map(|w| w.into()),
71 dleq: proof.dleq.map(|d| d.into()),
72 }
73 }
74}
75
76impl TryFrom<Proof> for cdk::nuts::Proof {
77 type Error = FfiError;
78
79 fn try_from(proof: Proof) -> Result<Self, Self::Error> {
80 use std::str::FromStr;
81
82 use cdk::nuts::Id;
83
84 Ok(Self {
85 amount: proof.amount.into(),
86 secret: cdk::secret::Secret::from_str(&proof.secret)
87 .map_err(|e| FfiError::internal(format!("Invalid secret: {}", e)))?,
88 c: cdk::nuts::PublicKey::from_str(&proof.c)
89 .map_err(|e| FfiError::internal(format!("Invalid public key: {}", e)))?,
90 keyset_id: Id::from_str(&proof.keyset_id)
91 .map_err(|e| FfiError::internal(format!("Invalid keyset ID: {}", e)))?,
92 witness: proof.witness.map(|w| w.into()),
93 dleq: proof.dleq.map(|d| d.into()),
94 })
95 }
96}
97
98#[uniffi::export]
100pub fn proof_y(proof: &Proof) -> Result<String, FfiError> {
101 let cdk_proof: cdk::nuts::Proof = proof.clone().try_into()?;
103 Ok(cdk_proof.y()?.to_string())
104}
105
106#[uniffi::export]
108pub fn proof_is_active(proof: &Proof, active_keyset_ids: Vec<String>) -> bool {
109 use cdk::nuts::Id;
110 let ids: Vec<Id> = active_keyset_ids
111 .into_iter()
112 .filter_map(|id| Id::from_str(&id).ok())
113 .collect();
114
115 if let Ok(keyset_id) = Id::from_str(&proof.keyset_id) {
117 ids.contains(&keyset_id)
118 } else {
119 false
120 }
121}
122
123#[uniffi::export]
125pub fn proof_has_dleq(proof: &Proof) -> bool {
126 proof.dleq.is_some()
127}
128
129#[uniffi::export]
131pub fn proof_verify_htlc(proof: &Proof) -> Result<(), FfiError> {
132 let cdk_proof: cdk::nuts::Proof = proof.clone().try_into()?;
133 cdk_proof.verify_htlc().map_err(FfiError::internal)
134}
135
136#[uniffi::export]
138pub fn proof_verify_dleq(
139 proof: &Proof,
140 mint_pubkey: super::keys::PublicKey,
141) -> Result<(), FfiError> {
142 let cdk_proof: cdk::nuts::Proof = proof.clone().try_into()?;
143 let cdk_pubkey: cdk::nuts::PublicKey = mint_pubkey.try_into()?;
144 cdk_proof
145 .verify_dleq(cdk_pubkey)
146 .map_err(FfiError::internal)
147}
148
149#[uniffi::export]
151pub fn proof_sign_p2pk(proof: Proof, secret_key_hex: String) -> Result<Proof, FfiError> {
152 let mut cdk_proof: cdk::nuts::Proof = proof.try_into()?;
153 let secret_key = cdk::nuts::SecretKey::from_hex(&secret_key_hex)
154 .map_err(|e| FfiError::internal(format!("Invalid secret key: {}", e)))?;
155
156 cdk_proof
157 .sign_p2pk(secret_key)
158 .map_err(FfiError::internal)?;
159
160 Ok(cdk_proof.into())
161}
162
163pub type Proofs = Vec<Proof>;
165
166#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
168pub struct ProofDleq {
169 pub e: String,
171 pub s: String,
173 pub r: String,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
179pub struct BlindSignatureDleq {
180 pub e: String,
182 pub s: String,
184}
185
186impl From<cdk::nuts::ProofDleq> for ProofDleq {
187 fn from(dleq: cdk::nuts::ProofDleq) -> Self {
188 Self {
189 e: dleq.e.to_secret_hex(),
190 s: dleq.s.to_secret_hex(),
191 r: dleq.r.to_secret_hex(),
192 }
193 }
194}
195
196impl From<ProofDleq> for cdk::nuts::ProofDleq {
197 fn from(dleq: ProofDleq) -> Self {
198 Self {
199 e: cdk::nuts::SecretKey::from_hex(&dleq.e).expect("Invalid e hex"),
200 s: cdk::nuts::SecretKey::from_hex(&dleq.s).expect("Invalid s hex"),
201 r: cdk::nuts::SecretKey::from_hex(&dleq.r).expect("Invalid r hex"),
202 }
203 }
204}
205
206impl From<cdk::nuts::BlindSignatureDleq> for BlindSignatureDleq {
207 fn from(dleq: cdk::nuts::BlindSignatureDleq) -> Self {
208 Self {
209 e: dleq.e.to_secret_hex(),
210 s: dleq.s.to_secret_hex(),
211 }
212 }
213}
214
215impl From<BlindSignatureDleq> for cdk::nuts::BlindSignatureDleq {
216 fn from(dleq: BlindSignatureDleq) -> Self {
217 Self {
218 e: cdk::nuts::SecretKey::from_hex(&dleq.e).expect("Invalid e hex"),
219 s: cdk::nuts::SecretKey::from_hex(&dleq.s).expect("Invalid s hex"),
220 }
221 }
222}
223
224#[uniffi::export]
226pub fn proofs_total_amount(proofs: &Proofs) -> Result<Amount, FfiError> {
227 let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
228 proofs.iter().map(|p| p.clone().try_into()).collect();
229 let cdk_proofs = cdk_proofs?;
230 use cdk::nuts::ProofsMethods;
231 Ok(cdk_proofs.total_amount()?.into())
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
236pub struct Conditions {
237 pub locktime: Option<u64>,
239 pub pubkeys: Vec<String>,
241 pub refund_keys: Vec<String>,
243 pub num_sigs: Option<u64>,
245 pub sig_flag: u8,
247 pub num_sigs_refund: Option<u64>,
249}
250
251impl From<cdk::nuts::nut11::Conditions> for Conditions {
252 fn from(conditions: cdk::nuts::nut11::Conditions) -> Self {
253 Self {
254 locktime: conditions.locktime,
255 pubkeys: conditions
256 .pubkeys
257 .unwrap_or_default()
258 .into_iter()
259 .map(|p| p.to_string())
260 .collect(),
261 refund_keys: conditions
262 .refund_keys
263 .unwrap_or_default()
264 .into_iter()
265 .map(|p| p.to_string())
266 .collect(),
267 num_sigs: conditions.num_sigs,
268 sig_flag: match conditions.sig_flag {
269 cdk::nuts::nut11::SigFlag::SigInputs => 0,
270 cdk::nuts::nut11::SigFlag::SigAll => 1,
271 },
272 num_sigs_refund: conditions.num_sigs_refund,
273 }
274 }
275}
276
277impl TryFrom<Conditions> for cdk::nuts::nut11::Conditions {
278 type Error = FfiError;
279
280 fn try_from(conditions: Conditions) -> Result<Self, Self::Error> {
281 let pubkeys = if conditions.pubkeys.is_empty() {
282 None
283 } else {
284 Some(
285 conditions
286 .pubkeys
287 .into_iter()
288 .map(|s| {
289 s.parse()
290 .map_err(|e| FfiError::internal(format!("Invalid pubkey: {}", e)))
291 })
292 .collect::<Result<Vec<_>, _>>()?,
293 )
294 };
295
296 let refund_keys = if conditions.refund_keys.is_empty() {
297 None
298 } else {
299 Some(
300 conditions
301 .refund_keys
302 .into_iter()
303 .map(|s| {
304 s.parse()
305 .map_err(|e| FfiError::internal(format!("Invalid refund key: {}", e)))
306 })
307 .collect::<Result<Vec<_>, _>>()?,
308 )
309 };
310
311 let sig_flag = match conditions.sig_flag {
312 0 => cdk::nuts::nut11::SigFlag::SigInputs,
313 1 => cdk::nuts::nut11::SigFlag::SigAll,
314 _ => return Err(FfiError::internal("Invalid sig_flag value")),
315 };
316
317 Ok(Self {
318 locktime: conditions.locktime,
319 pubkeys,
320 refund_keys,
321 num_sigs: conditions.num_sigs,
322 sig_flag,
323 num_sigs_refund: conditions.num_sigs_refund,
324 })
325 }
326}
327
328impl Conditions {
329 pub fn to_json(&self) -> Result<String, FfiError> {
331 Ok(serde_json::to_string(self)?)
332 }
333}
334
335#[uniffi::export]
337pub fn decode_conditions(json: String) -> Result<Conditions, FfiError> {
338 Ok(serde_json::from_str(&json)?)
339}
340
341#[uniffi::export]
343pub fn encode_conditions(conditions: Conditions) -> Result<String, FfiError> {
344 Ok(serde_json::to_string(&conditions)?)
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
349pub enum Witness {
350 P2PK {
352 signatures: Vec<String>,
354 },
355 HTLC {
357 preimage: String,
359 signatures: Option<Vec<String>>,
361 },
362}
363
364impl From<cdk::nuts::Witness> for Witness {
365 fn from(witness: cdk::nuts::Witness) -> Self {
366 match witness {
367 cdk::nuts::Witness::P2PKWitness(p2pk) => Self::P2PK {
368 signatures: p2pk.signatures,
369 },
370 cdk::nuts::Witness::HTLCWitness(htlc) => Self::HTLC {
371 preimage: htlc.preimage,
372 signatures: htlc.signatures,
373 },
374 }
375 }
376}
377
378impl From<Witness> for cdk::nuts::Witness {
379 fn from(witness: Witness) -> Self {
380 match witness {
381 Witness::P2PK { signatures } => {
382 Self::P2PKWitness(cdk::nuts::nut11::P2PKWitness { signatures })
383 }
384 Witness::HTLC {
385 preimage,
386 signatures,
387 } => Self::HTLCWitness(cdk::nuts::nut14::HTLCWitness {
388 preimage,
389 signatures,
390 }),
391 }
392 }
393}
394
395#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
397pub enum SpendingConditions {
398 P2PK {
400 pubkey: String,
402 conditions: Option<Conditions>,
404 },
405 HTLC {
407 hash: String,
409 conditions: Option<Conditions>,
411 },
412}
413
414impl From<cdk::nuts::SpendingConditions> for SpendingConditions {
415 fn from(spending_conditions: cdk::nuts::SpendingConditions) -> Self {
416 match spending_conditions {
417 cdk::nuts::SpendingConditions::P2PKConditions { data, conditions } => Self::P2PK {
418 pubkey: data.to_string(),
419 conditions: conditions.map(Into::into),
420 },
421 cdk::nuts::SpendingConditions::HTLCConditions { data, conditions } => Self::HTLC {
422 hash: data.to_string(),
423 conditions: conditions.map(Into::into),
424 },
425 }
426 }
427}
428
429impl TryFrom<SpendingConditions> for cdk::nuts::SpendingConditions {
430 type Error = FfiError;
431
432 fn try_from(spending_conditions: SpendingConditions) -> Result<Self, Self::Error> {
433 match spending_conditions {
434 SpendingConditions::P2PK { pubkey, conditions } => {
435 let pubkey = pubkey
436 .parse()
437 .map_err(|e| FfiError::internal(format!("Invalid pubkey: {}", e)))?;
438 let conditions = conditions.map(|c| c.try_into()).transpose()?;
439 Ok(Self::P2PKConditions {
440 data: pubkey,
441 conditions,
442 })
443 }
444 SpendingConditions::HTLC { hash, conditions } => {
445 let hash = hash
446 .parse()
447 .map_err(|e| FfiError::internal(format!("Invalid hash: {}", e)))?;
448 let conditions = conditions.map(|c| c.try_into()).transpose()?;
449 Ok(Self::HTLCConditions {
450 data: hash,
451 conditions,
452 })
453 }
454 }
455 }
456}
457
458#[derive(Debug, Clone, uniffi::Record)]
460pub struct ProofInfo {
461 pub proof: Proof,
463 pub y: super::keys::PublicKey,
465 pub mint_url: MintUrl,
467 pub state: ProofState,
469 pub spending_condition: Option<SpendingConditions>,
471 pub unit: CurrencyUnit,
473 pub used_by_operation: Option<String>,
475 pub created_by_operation: Option<String>,
477}
478
479impl From<cdk::types::ProofInfo> for ProofInfo {
480 fn from(info: cdk::types::ProofInfo) -> Self {
481 Self {
482 proof: info.proof.into(),
483 y: info.y.into(),
484 mint_url: info.mint_url.into(),
485 state: info.state.into(),
486 spending_condition: info.spending_condition.map(Into::into),
487 unit: info.unit.into(),
488 used_by_operation: info.used_by_operation.map(|u| u.to_string()),
489 created_by_operation: info.created_by_operation.map(|u| u.to_string()),
490 }
491 }
492}
493
494#[uniffi::export]
496pub fn decode_proof_info(json: String) -> Result<ProofInfo, FfiError> {
497 let info: cdk::types::ProofInfo = serde_json::from_str(&json)?;
498 Ok(info.into())
499}
500
501#[uniffi::export]
503pub fn encode_proof_info(info: ProofInfo) -> Result<String, FfiError> {
504 use std::str::FromStr;
505 let cdk_info = cdk::types::ProofInfo {
507 proof: info.proof.try_into()?,
508 y: info.y.try_into()?,
509 mint_url: info.mint_url.try_into()?,
510 state: info.state.into(),
511 spending_condition: info.spending_condition.and_then(|c| c.try_into().ok()),
512 unit: info.unit.into(),
513 used_by_operation: info
514 .used_by_operation
515 .map(|id| uuid::Uuid::from_str(&id))
516 .transpose()
517 .map_err(|e| FfiError::internal(e.to_string()))?,
518 created_by_operation: info
519 .created_by_operation
520 .map(|id| uuid::Uuid::from_str(&id))
521 .transpose()
522 .map_err(|e| FfiError::internal(e.to_string()))?,
523 };
524 Ok(serde_json::to_string(&cdk_info)?)
525}
526
527#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
529pub struct ProofStateUpdate {
530 pub y: String,
532 pub state: ProofState,
534 pub witness: Option<String>,
536}
537
538impl From<cdk::nuts::nut07::ProofState> for ProofStateUpdate {
539 fn from(proof_state: cdk::nuts::nut07::ProofState) -> Self {
540 Self {
541 y: proof_state.y.to_string(),
542 state: proof_state.state.into(),
543 witness: proof_state.witness.map(|w| format!("{:?}", w)),
544 }
545 }
546}
547
548impl ProofStateUpdate {
549 pub fn to_json(&self) -> Result<String, FfiError> {
551 Ok(serde_json::to_string(self)?)
552 }
553}
554
555#[uniffi::export]
557pub fn decode_proof_state_update(json: String) -> Result<ProofStateUpdate, FfiError> {
558 Ok(serde_json::from_str(&json)?)
559}
560
561#[uniffi::export]
563pub fn encode_proof_state_update(update: ProofStateUpdate) -> Result<String, FfiError> {
564 Ok(serde_json::to_string(&update)?)
565}