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::Serialization { msg: e.to_string() })?,
88 c: cdk::nuts::PublicKey::from_str(&proof.c)
89 .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?,
90 keyset_id: Id::from_str(&proof.keyset_id)
91 .map_err(|e| FfiError::Serialization { msg: e.to_string() })?,
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
134 .verify_htlc()
135 .map_err(|e| FfiError::Generic { msg: e.to_string() })
136}
137
138#[uniffi::export]
140pub fn proof_verify_dleq(
141 proof: &Proof,
142 mint_pubkey: super::keys::PublicKey,
143) -> Result<(), FfiError> {
144 let cdk_proof: cdk::nuts::Proof = proof.clone().try_into()?;
145 let cdk_pubkey: cdk::nuts::PublicKey = mint_pubkey.try_into()?;
146 cdk_proof
147 .verify_dleq(cdk_pubkey)
148 .map_err(|e| FfiError::Generic { msg: e.to_string() })
149}
150
151#[uniffi::export]
153pub fn proof_sign_p2pk(proof: Proof, secret_key_hex: String) -> Result<Proof, FfiError> {
154 let mut cdk_proof: cdk::nuts::Proof = proof.try_into()?;
155 let secret_key = cdk::nuts::SecretKey::from_hex(&secret_key_hex)
156 .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?;
157
158 cdk_proof
159 .sign_p2pk(secret_key)
160 .map_err(|e| FfiError::Generic { msg: e.to_string() })?;
161
162 Ok(cdk_proof.into())
163}
164
165pub type Proofs = Vec<Proof>;
167
168#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
170pub struct ProofDleq {
171 pub e: String,
173 pub s: String,
175 pub r: String,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
181pub struct BlindSignatureDleq {
182 pub e: String,
184 pub s: String,
186}
187
188impl From<cdk::nuts::ProofDleq> for ProofDleq {
189 fn from(dleq: cdk::nuts::ProofDleq) -> Self {
190 Self {
191 e: dleq.e.to_secret_hex(),
192 s: dleq.s.to_secret_hex(),
193 r: dleq.r.to_secret_hex(),
194 }
195 }
196}
197
198impl From<ProofDleq> for cdk::nuts::ProofDleq {
199 fn from(dleq: ProofDleq) -> Self {
200 Self {
201 e: cdk::nuts::SecretKey::from_hex(&dleq.e).expect("Invalid e hex"),
202 s: cdk::nuts::SecretKey::from_hex(&dleq.s).expect("Invalid s hex"),
203 r: cdk::nuts::SecretKey::from_hex(&dleq.r).expect("Invalid r hex"),
204 }
205 }
206}
207
208impl From<cdk::nuts::BlindSignatureDleq> for BlindSignatureDleq {
209 fn from(dleq: cdk::nuts::BlindSignatureDleq) -> Self {
210 Self {
211 e: dleq.e.to_secret_hex(),
212 s: dleq.s.to_secret_hex(),
213 }
214 }
215}
216
217impl From<BlindSignatureDleq> for cdk::nuts::BlindSignatureDleq {
218 fn from(dleq: BlindSignatureDleq) -> Self {
219 Self {
220 e: cdk::nuts::SecretKey::from_hex(&dleq.e).expect("Invalid e hex"),
221 s: cdk::nuts::SecretKey::from_hex(&dleq.s).expect("Invalid s hex"),
222 }
223 }
224}
225
226#[uniffi::export]
228pub fn proofs_total_amount(proofs: &Proofs) -> Result<Amount, FfiError> {
229 let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
230 proofs.iter().map(|p| p.clone().try_into()).collect();
231 let cdk_proofs = cdk_proofs?;
232 use cdk::nuts::ProofsMethods;
233 Ok(cdk_proofs.total_amount()?.into())
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
238pub struct Conditions {
239 pub locktime: Option<u64>,
241 pub pubkeys: Vec<String>,
243 pub refund_keys: Vec<String>,
245 pub num_sigs: Option<u64>,
247 pub sig_flag: u8,
249 pub num_sigs_refund: Option<u64>,
251}
252
253impl From<cdk::nuts::nut11::Conditions> for Conditions {
254 fn from(conditions: cdk::nuts::nut11::Conditions) -> Self {
255 Self {
256 locktime: conditions.locktime,
257 pubkeys: conditions
258 .pubkeys
259 .unwrap_or_default()
260 .into_iter()
261 .map(|p| p.to_string())
262 .collect(),
263 refund_keys: conditions
264 .refund_keys
265 .unwrap_or_default()
266 .into_iter()
267 .map(|p| p.to_string())
268 .collect(),
269 num_sigs: conditions.num_sigs,
270 sig_flag: match conditions.sig_flag {
271 cdk::nuts::nut11::SigFlag::SigInputs => 0,
272 cdk::nuts::nut11::SigFlag::SigAll => 1,
273 },
274 num_sigs_refund: conditions.num_sigs_refund,
275 }
276 }
277}
278
279impl TryFrom<Conditions> for cdk::nuts::nut11::Conditions {
280 type Error = FfiError;
281
282 fn try_from(conditions: Conditions) -> Result<Self, Self::Error> {
283 let pubkeys = if conditions.pubkeys.is_empty() {
284 None
285 } else {
286 Some(
287 conditions
288 .pubkeys
289 .into_iter()
290 .map(|s| {
291 s.parse().map_err(|e| FfiError::InvalidCryptographicKey {
292 msg: format!("Invalid pubkey: {}", e),
293 })
294 })
295 .collect::<Result<Vec<_>, _>>()?,
296 )
297 };
298
299 let refund_keys = if conditions.refund_keys.is_empty() {
300 None
301 } else {
302 Some(
303 conditions
304 .refund_keys
305 .into_iter()
306 .map(|s| {
307 s.parse().map_err(|e| FfiError::InvalidCryptographicKey {
308 msg: format!("Invalid refund key: {}", e),
309 })
310 })
311 .collect::<Result<Vec<_>, _>>()?,
312 )
313 };
314
315 let sig_flag = match conditions.sig_flag {
316 0 => cdk::nuts::nut11::SigFlag::SigInputs,
317 1 => cdk::nuts::nut11::SigFlag::SigAll,
318 _ => {
319 return Err(FfiError::Generic {
320 msg: "Invalid sig_flag value".to_string(),
321 })
322 }
323 };
324
325 Ok(Self {
326 locktime: conditions.locktime,
327 pubkeys,
328 refund_keys,
329 num_sigs: conditions.num_sigs,
330 sig_flag,
331 num_sigs_refund: conditions.num_sigs_refund,
332 })
333 }
334}
335
336impl Conditions {
337 pub fn to_json(&self) -> Result<String, FfiError> {
339 Ok(serde_json::to_string(self)?)
340 }
341}
342
343#[uniffi::export]
345pub fn decode_conditions(json: String) -> Result<Conditions, FfiError> {
346 Ok(serde_json::from_str(&json)?)
347}
348
349#[uniffi::export]
351pub fn encode_conditions(conditions: Conditions) -> Result<String, FfiError> {
352 Ok(serde_json::to_string(&conditions)?)
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
357pub enum Witness {
358 P2PK {
360 signatures: Vec<String>,
362 },
363 HTLC {
365 preimage: String,
367 signatures: Option<Vec<String>>,
369 },
370}
371
372impl From<cdk::nuts::Witness> for Witness {
373 fn from(witness: cdk::nuts::Witness) -> Self {
374 match witness {
375 cdk::nuts::Witness::P2PKWitness(p2pk) => Self::P2PK {
376 signatures: p2pk.signatures,
377 },
378 cdk::nuts::Witness::HTLCWitness(htlc) => Self::HTLC {
379 preimage: htlc.preimage,
380 signatures: htlc.signatures,
381 },
382 }
383 }
384}
385
386impl From<Witness> for cdk::nuts::Witness {
387 fn from(witness: Witness) -> Self {
388 match witness {
389 Witness::P2PK { signatures } => {
390 Self::P2PKWitness(cdk::nuts::nut11::P2PKWitness { signatures })
391 }
392 Witness::HTLC {
393 preimage,
394 signatures,
395 } => Self::HTLCWitness(cdk::nuts::nut14::HTLCWitness {
396 preimage,
397 signatures,
398 }),
399 }
400 }
401}
402
403#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
405pub enum SpendingConditions {
406 P2PK {
408 pubkey: String,
410 conditions: Option<Conditions>,
412 },
413 HTLC {
415 hash: String,
417 conditions: Option<Conditions>,
419 },
420}
421
422impl From<cdk::nuts::SpendingConditions> for SpendingConditions {
423 fn from(spending_conditions: cdk::nuts::SpendingConditions) -> Self {
424 match spending_conditions {
425 cdk::nuts::SpendingConditions::P2PKConditions { data, conditions } => Self::P2PK {
426 pubkey: data.to_string(),
427 conditions: conditions.map(Into::into),
428 },
429 cdk::nuts::SpendingConditions::HTLCConditions { data, conditions } => Self::HTLC {
430 hash: data.to_string(),
431 conditions: conditions.map(Into::into),
432 },
433 }
434 }
435}
436
437impl TryFrom<SpendingConditions> for cdk::nuts::SpendingConditions {
438 type Error = FfiError;
439
440 fn try_from(spending_conditions: SpendingConditions) -> Result<Self, Self::Error> {
441 match spending_conditions {
442 SpendingConditions::P2PK { pubkey, conditions } => {
443 let pubkey = pubkey
444 .parse()
445 .map_err(|e| FfiError::InvalidCryptographicKey {
446 msg: format!("Invalid pubkey: {}", e),
447 })?;
448 let conditions = conditions.map(|c| c.try_into()).transpose()?;
449 Ok(Self::P2PKConditions {
450 data: pubkey,
451 conditions,
452 })
453 }
454 SpendingConditions::HTLC { hash, conditions } => {
455 let hash = hash
456 .parse()
457 .map_err(|e| FfiError::InvalidCryptographicKey {
458 msg: format!("Invalid hash: {}", e),
459 })?;
460 let conditions = conditions.map(|c| c.try_into()).transpose()?;
461 Ok(Self::HTLCConditions {
462 data: hash,
463 conditions,
464 })
465 }
466 }
467 }
468}
469
470#[derive(Debug, Clone, uniffi::Record)]
472pub struct ProofInfo {
473 pub proof: Proof,
475 pub y: super::keys::PublicKey,
477 pub mint_url: MintUrl,
479 pub state: ProofState,
481 pub spending_condition: Option<SpendingConditions>,
483 pub unit: CurrencyUnit,
485}
486
487impl From<cdk::types::ProofInfo> for ProofInfo {
488 fn from(info: cdk::types::ProofInfo) -> Self {
489 Self {
490 proof: info.proof.into(),
491 y: info.y.into(),
492 mint_url: info.mint_url.into(),
493 state: info.state.into(),
494 spending_condition: info.spending_condition.map(Into::into),
495 unit: info.unit.into(),
496 }
497 }
498}
499
500#[uniffi::export]
502pub fn decode_proof_info(json: String) -> Result<ProofInfo, FfiError> {
503 let info: cdk::types::ProofInfo = serde_json::from_str(&json)?;
504 Ok(info.into())
505}
506
507#[uniffi::export]
509pub fn encode_proof_info(info: ProofInfo) -> Result<String, FfiError> {
510 let cdk_info = cdk::types::ProofInfo {
512 proof: info.proof.try_into()?,
513 y: info.y.try_into()?,
514 mint_url: info.mint_url.try_into()?,
515 state: info.state.into(),
516 spending_condition: info.spending_condition.and_then(|c| c.try_into().ok()),
517 unit: info.unit.into(),
518 };
519 Ok(serde_json::to_string(&cdk_info)?)
520}
521
522#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
524pub struct ProofStateUpdate {
525 pub y: String,
527 pub state: ProofState,
529 pub witness: Option<String>,
531}
532
533impl From<cdk::nuts::nut07::ProofState> for ProofStateUpdate {
534 fn from(proof_state: cdk::nuts::nut07::ProofState) -> Self {
535 Self {
536 y: proof_state.y.to_string(),
537 state: proof_state.state.into(),
538 witness: proof_state.witness.map(|w| format!("{:?}", w)),
539 }
540 }
541}
542
543impl ProofStateUpdate {
544 pub fn to_json(&self) -> Result<String, FfiError> {
546 Ok(serde_json::to_string(self)?)
547 }
548}
549
550#[uniffi::export]
552pub fn decode_proof_state_update(json: String) -> Result<ProofStateUpdate, FfiError> {
553 Ok(serde_json::from_str(&json)?)
554}
555
556#[uniffi::export]
558pub fn encode_proof_state_update(update: ProofStateUpdate) -> Result<String, FfiError> {
559 Ok(serde_json::to_string(&update)?)
560}