use std::str::FromStr;
use cdk::nuts::State as CdkState;
use serde::{Deserialize, Serialize};
use super::amount::{Amount, CurrencyUnit};
use super::mint::MintUrl;
use crate::error::FfiError;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
pub enum ProofState {
Unspent,
Pending,
Spent,
Reserved,
PendingSpent,
}
impl From<CdkState> for ProofState {
fn from(state: CdkState) -> Self {
match state {
CdkState::Unspent => ProofState::Unspent,
CdkState::Pending => ProofState::Pending,
CdkState::Spent => ProofState::Spent,
CdkState::Reserved => ProofState::Reserved,
CdkState::PendingSpent => ProofState::PendingSpent,
}
}
}
impl From<ProofState> for CdkState {
fn from(state: ProofState) -> Self {
match state {
ProofState::Unspent => CdkState::Unspent,
ProofState::Pending => CdkState::Pending,
ProofState::Spent => CdkState::Spent,
ProofState::Reserved => CdkState::Reserved,
ProofState::PendingSpent => CdkState::PendingSpent,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
pub struct Proof {
pub amount: Amount,
pub secret: String,
pub c: String,
pub keyset_id: String,
pub witness: Option<Witness>,
pub dleq: Option<ProofDleq>,
pub p2pk_e: Option<String>,
}
impl From<cdk::nuts::Proof> for Proof {
fn from(proof: cdk::nuts::Proof) -> Self {
Self {
amount: proof.amount.into(),
secret: proof.secret.to_string(),
c: proof.c.to_string(),
keyset_id: proof.keyset_id.to_string(),
witness: proof.witness.map(|w| w.into()),
dleq: proof.dleq.map(|d| d.into()),
p2pk_e: proof.p2pk_e.map(|p| p.to_string()),
}
}
}
impl TryFrom<Proof> for cdk::nuts::Proof {
type Error = FfiError;
fn try_from(proof: Proof) -> Result<Self, Self::Error> {
use std::str::FromStr;
use cdk::nuts::Id;
Ok(Self {
amount: proof.amount.into(),
secret: cdk::secret::Secret::from_str(&proof.secret)
.map_err(|e| FfiError::internal(format!("Invalid secret: {}", e)))?,
c: cdk::nuts::PublicKey::from_str(&proof.c)
.map_err(|e| FfiError::internal(format!("Invalid public key: {}", e)))?,
keyset_id: Id::from_str(&proof.keyset_id)
.map_err(|e| FfiError::internal(format!("Invalid keyset ID: {}", e)))?,
witness: proof.witness.map(|w| w.into()),
dleq: proof.dleq.map(TryInto::try_into).transpose()?,
p2pk_e: proof
.p2pk_e
.map(|p| cdk::nuts::PublicKey::from_str(&p))
.transpose()
.map_err(|e| FfiError::internal(format!("Invalid p2pk_e: {}", e)))?,
})
}
}
#[uniffi::export]
pub fn proof_y(proof: &Proof) -> Result<String, FfiError> {
let cdk_proof: cdk::nuts::Proof = proof.clone().try_into()?;
Ok(cdk_proof.y()?.to_string())
}
#[uniffi::export]
pub fn proof_is_active(proof: &Proof, active_keyset_ids: Vec<String>) -> bool {
use cdk::nuts::Id;
let ids: Vec<Id> = active_keyset_ids
.into_iter()
.filter_map(|id| Id::from_str(&id).ok())
.collect();
if let Ok(keyset_id) = Id::from_str(&proof.keyset_id) {
ids.contains(&keyset_id)
} else {
false
}
}
#[uniffi::export]
pub fn proof_has_dleq(proof: &Proof) -> bool {
proof.dleq.is_some()
}
#[uniffi::export]
pub fn proof_verify_htlc(proof: &Proof) -> Result<(), FfiError> {
let cdk_proof: cdk::nuts::Proof = proof.clone().try_into()?;
cdk_proof.verify_htlc().map_err(FfiError::internal)
}
#[uniffi::export]
pub fn proof_verify_dleq(
proof: &Proof,
mint_pubkey: super::keys::PublicKey,
) -> Result<(), FfiError> {
let cdk_proof: cdk::nuts::Proof = proof.clone().try_into()?;
let cdk_pubkey: cdk::nuts::PublicKey = mint_pubkey.try_into()?;
cdk_proof
.verify_dleq(cdk_pubkey)
.map_err(FfiError::internal)
}
#[uniffi::export]
pub fn proof_sign_p2pk(proof: Proof, secret_key_hex: String) -> Result<Proof, FfiError> {
let mut cdk_proof: cdk::nuts::Proof = proof.try_into()?;
let secret_key = cdk::nuts::SecretKey::from_hex(&secret_key_hex)
.map_err(|e| FfiError::internal(format!("Invalid secret key: {}", e)))?;
cdk_proof
.sign_p2pk(secret_key)
.map_err(FfiError::internal)?;
Ok(cdk_proof.into())
}
pub type Proofs = Vec<Proof>;
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
pub struct ProofDleq {
pub e: String,
pub s: String,
pub r: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
pub struct BlindSignatureDleq {
pub e: String,
pub s: String,
}
impl From<cdk::nuts::ProofDleq> for ProofDleq {
fn from(dleq: cdk::nuts::ProofDleq) -> Self {
Self {
e: dleq.e.to_secret_hex(),
s: dleq.s.to_secret_hex(),
r: dleq.r.to_secret_hex(),
}
}
}
impl TryFrom<ProofDleq> for cdk::nuts::ProofDleq {
type Error = FfiError;
fn try_from(dleq: ProofDleq) -> Result<Self, Self::Error> {
Ok(Self {
e: cdk::nuts::SecretKey::from_hex(&dleq.e)
.map_err(|e| FfiError::internal(format!("Invalid dleq e: {}", e)))?,
s: cdk::nuts::SecretKey::from_hex(&dleq.s)
.map_err(|e| FfiError::internal(format!("Invalid dleq s: {}", e)))?,
r: cdk::nuts::SecretKey::from_hex(&dleq.r)
.map_err(|e| FfiError::internal(format!("Invalid dleq r: {}", e)))?,
})
}
}
impl From<cdk::nuts::BlindSignatureDleq> for BlindSignatureDleq {
fn from(dleq: cdk::nuts::BlindSignatureDleq) -> Self {
Self {
e: dleq.e.to_secret_hex(),
s: dleq.s.to_secret_hex(),
}
}
}
impl TryFrom<BlindSignatureDleq> for cdk::nuts::BlindSignatureDleq {
type Error = FfiError;
fn try_from(dleq: BlindSignatureDleq) -> Result<Self, Self::Error> {
Ok(Self {
e: cdk::nuts::SecretKey::from_hex(&dleq.e).map_err(|e| {
FfiError::internal(format!("Invalid blind signature dleq e: {}", e))
})?,
s: cdk::nuts::SecretKey::from_hex(&dleq.s).map_err(|e| {
FfiError::internal(format!("Invalid blind signature dleq s: {}", e))
})?,
})
}
}
#[uniffi::export]
pub fn proofs_total_amount(proofs: &Proofs) -> Result<Amount, FfiError> {
let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
proofs.iter().map(|p| p.clone().try_into()).collect();
let cdk_proofs = cdk_proofs?;
use cdk::nuts::ProofsMethods;
Ok(cdk_proofs.total_amount()?.into())
}
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
pub struct Conditions {
pub locktime: Option<u64>,
pub pubkeys: Vec<String>,
pub refund_keys: Vec<String>,
pub num_sigs: Option<u64>,
pub sig_flag: u8,
pub num_sigs_refund: Option<u64>,
}
impl From<cdk::nuts::nut10::Conditions> for Conditions {
fn from(conditions: cdk::nuts::nut10::Conditions) -> Self {
Self {
locktime: conditions.locktime,
pubkeys: conditions
.pubkeys
.unwrap_or_default()
.into_iter()
.map(|p| p.to_string())
.collect(),
refund_keys: conditions
.refund_keys
.unwrap_or_default()
.into_iter()
.map(|p| p.to_string())
.collect(),
num_sigs: conditions.num_sigs,
sig_flag: match conditions.sig_flag {
cdk::nuts::nut11::SigFlag::SigInputs => 0,
cdk::nuts::nut11::SigFlag::SigAll => 1,
},
num_sigs_refund: conditions.num_sigs_refund,
}
}
}
impl TryFrom<Conditions> for cdk::nuts::nut10::Conditions {
type Error = FfiError;
fn try_from(conditions: Conditions) -> Result<Self, Self::Error> {
let pubkeys = if conditions.pubkeys.is_empty() {
None
} else {
Some(
conditions
.pubkeys
.into_iter()
.map(|s| {
s.parse()
.map_err(|e| FfiError::internal(format!("Invalid pubkey: {}", e)))
})
.collect::<Result<Vec<_>, _>>()?,
)
};
let refund_keys = if conditions.refund_keys.is_empty() {
None
} else {
Some(
conditions
.refund_keys
.into_iter()
.map(|s| {
s.parse()
.map_err(|e| FfiError::internal(format!("Invalid refund key: {}", e)))
})
.collect::<Result<Vec<_>, _>>()?,
)
};
let sig_flag = match conditions.sig_flag {
0 => cdk::nuts::nut11::SigFlag::SigInputs,
1 => cdk::nuts::nut11::SigFlag::SigAll,
_ => return Err(FfiError::internal("Invalid sig_flag value")),
};
Ok(Self {
locktime: conditions.locktime,
pubkeys,
refund_keys,
num_sigs: conditions.num_sigs,
sig_flag,
num_sigs_refund: conditions.num_sigs_refund,
})
}
}
impl Conditions {
pub fn to_json(&self) -> Result<String, FfiError> {
Ok(serde_json::to_string(self)?)
}
}
#[uniffi::export]
pub fn decode_conditions(json: String) -> Result<Conditions, FfiError> {
Ok(serde_json::from_str(&json)?)
}
#[uniffi::export]
pub fn encode_conditions(conditions: Conditions) -> Result<String, FfiError> {
Ok(serde_json::to_string(&conditions)?)
}
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
pub enum Witness {
P2PK {
signatures: Vec<String>,
},
HTLC {
preimage: String,
signatures: Option<Vec<String>>,
},
}
impl From<cdk::nuts::Witness> for Witness {
fn from(witness: cdk::nuts::Witness) -> Self {
match witness {
cdk::nuts::Witness::P2PKWitness(p2pk) => Self::P2PK {
signatures: p2pk.signatures,
},
cdk::nuts::Witness::HTLCWitness(htlc) => Self::HTLC {
preimage: htlc.preimage,
signatures: htlc.signatures,
},
}
}
}
impl From<Witness> for cdk::nuts::Witness {
fn from(witness: Witness) -> Self {
match witness {
Witness::P2PK { signatures } => {
Self::P2PKWitness(cdk::nuts::nut11::P2PKWitness { signatures })
}
Witness::HTLC {
preimage,
signatures,
} => Self::HTLCWitness(cdk::nuts::nut14::HTLCWitness {
preimage,
signatures,
}),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
pub enum SpendingConditions {
P2PK {
pubkey: String,
conditions: Option<Conditions>,
},
HTLC {
hash: String,
conditions: Option<Conditions>,
},
}
impl From<cdk::nuts::SpendingConditions> for SpendingConditions {
fn from(spending_conditions: cdk::nuts::SpendingConditions) -> Self {
match spending_conditions {
cdk::nuts::SpendingConditions::P2PKConditions { data, conditions } => Self::P2PK {
pubkey: data.to_string(),
conditions: conditions.map(Into::into),
},
cdk::nuts::SpendingConditions::HTLCConditions { data, conditions } => Self::HTLC {
hash: data.to_string(),
conditions: conditions.map(Into::into),
},
}
}
}
impl TryFrom<SpendingConditions> for cdk::nuts::SpendingConditions {
type Error = FfiError;
fn try_from(spending_conditions: SpendingConditions) -> Result<Self, Self::Error> {
match spending_conditions {
SpendingConditions::P2PK { pubkey, conditions } => {
let pubkey = pubkey
.parse()
.map_err(|e| FfiError::internal(format!("Invalid pubkey: {}", e)))?;
let conditions = conditions.map(|c| c.try_into()).transpose()?;
Ok(Self::P2PKConditions {
data: pubkey,
conditions,
})
}
SpendingConditions::HTLC { hash, conditions } => {
let hash = hash
.parse()
.map_err(|e| FfiError::internal(format!("Invalid hash: {}", e)))?;
let conditions = conditions.map(|c| c.try_into()).transpose()?;
Ok(Self::HTLCConditions {
data: hash,
conditions,
})
}
}
}
}
#[derive(Debug, Clone, uniffi::Record)]
pub struct ProofInfo {
pub proof: Proof,
pub y: super::keys::PublicKey,
pub mint_url: MintUrl,
pub state: ProofState,
pub spending_condition: Option<SpendingConditions>,
pub unit: CurrencyUnit,
pub used_by_operation: Option<String>,
pub created_by_operation: Option<String>,
}
impl From<cdk::types::ProofInfo> for ProofInfo {
fn from(info: cdk::types::ProofInfo) -> Self {
Self {
proof: info.proof.into(),
y: info.y.into(),
mint_url: info.mint_url.into(),
state: info.state.into(),
spending_condition: info.spending_condition.map(Into::into),
unit: info.unit.into(),
used_by_operation: info.used_by_operation.map(|u| u.to_string()),
created_by_operation: info.created_by_operation.map(|u| u.to_string()),
}
}
}
#[uniffi::export]
pub fn decode_proof_info(json: String) -> Result<ProofInfo, FfiError> {
let info: cdk::types::ProofInfo = serde_json::from_str(&json)?;
Ok(info.into())
}
#[uniffi::export]
pub fn encode_proof_info(info: ProofInfo) -> Result<String, FfiError> {
use std::str::FromStr;
let cdk_info = cdk::types::ProofInfo {
proof: info.proof.try_into()?,
y: info.y.try_into()?,
mint_url: info.mint_url.try_into()?,
state: info.state.into(),
spending_condition: info.spending_condition.map(TryInto::try_into).transpose()?,
unit: info.unit.into(),
used_by_operation: info
.used_by_operation
.map(|id| uuid::Uuid::from_str(&id))
.transpose()
.map_err(|e| FfiError::internal(e.to_string()))?,
created_by_operation: info
.created_by_operation
.map(|id| uuid::Uuid::from_str(&id))
.transpose()
.map_err(|e| FfiError::internal(e.to_string()))?,
};
Ok(serde_json::to_string(&cdk_info)?)
}
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
pub struct ProofStateUpdate {
pub y: String,
pub state: ProofState,
pub witness: Option<String>,
}
impl From<cdk::nuts::nut07::ProofState> for ProofStateUpdate {
fn from(proof_state: cdk::nuts::nut07::ProofState) -> Self {
Self {
y: proof_state.y.to_string(),
state: proof_state.state.into(),
witness: proof_state.witness.map(|w| format!("{:?}", w)),
}
}
}
impl ProofStateUpdate {
pub fn to_json(&self) -> Result<String, FfiError> {
Ok(serde_json::to_string(self)?)
}
}
#[uniffi::export]
pub fn decode_proof_state_update(json: String) -> Result<ProofStateUpdate, FfiError> {
Ok(serde_json::from_str(&json)?)
}
#[uniffi::export]
pub fn encode_proof_state_update(update: ProofStateUpdate) -> Result<String, FfiError> {
Ok(serde_json::to_string(&update)?)
}