Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/cashu/src/nuts/nut13.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ pub enum Error {
#[error(transparent)]
Bip32(#[from] bitcoin::bip32::Error),
/// HMAC Error
#[error(transparent)]
Hmac(#[from] bitcoin::secp256k1::hashes::FromSliceError),
#[error("HMAC error: {0}")]
Hmac(bitcoin::secp256k1::hashes::FromSliceError),
/// SecretKey Error
#[error(transparent)]
SecpError(#[from] bitcoin::secp256k1::Error),
Expand Down
14 changes: 14 additions & 0 deletions crates/cdk-common/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,20 @@ pub struct SendOptions {
pub metadata: HashMap<String, String>,
/// Use P2BK (NUT-28)
pub use_p2bk: bool,
/// Signing keys for P2PK-locked input proofs; auto-detected from the wallet keyring if omitted
pub p2pk_signing_keys: Vec<SecretKey>,
/// How P2PK-locked input proofs should be handled during send
pub p2pk_locked_proof_send_mode: P2PKLockedProofSendMode,
}

/// Send behavior for selected P2PK-locked input proofs
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum P2PKLockedProofSendMode {
/// Swap locked proofs into fresh proofs before creating the token
#[default]
Swap,
/// Sign locked proofs and include them directly in the token
SignAndSend,
}

/// Send memo
Expand Down
43 changes: 43 additions & 0 deletions crates/cdk-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ mod tests {
assert!(!options.include_fee);
assert!(options.max_proofs.is_none());
assert!(options.metadata.is_empty());
assert!(options.p2pk_signing_keys.is_empty());
assert_eq!(
options.p2pk_locked_proof_send_mode,
P2PKLockedProofSendMode::Swap
);
}

#[test]
Expand Down Expand Up @@ -201,6 +206,8 @@ mod tests {
max_proofs: Some(10),
metadata,
use_p2bk: false,
p2pk_signing_keys: Vec::new(),
p2pk_locked_proof_send_mode: P2PKLockedProofSendMode::Swap,
};

assert!(options.memo.is_some());
Expand Down Expand Up @@ -260,6 +267,42 @@ mod tests {
assert!(result.is_err());
}

#[test]
fn test_send_options_invalid_secret_key_returns_error() {
let options = SendOptions {
p2pk_signing_keys: vec![SecretKey {
hex: "z".repeat(64),
}],
..Default::default()
};

let result: Result<cdk::wallet::SendOptions, _> = options.try_into();

assert!(result.is_err());
}

#[test]
fn test_send_options_json_defaults_new_p2pk_fields() {
let json = r#"{
"memo": null,
"conditions": null,
"amount_split_target": "None",
"send_kind": "OnlineExact",
"include_fee": false,
"use_p2bk": false,
"max_proofs": null,
"metadata": {}
}"#;

let options = crate::types::wallet::decode_send_options(json.to_string()).unwrap();

assert!(options.p2pk_signing_keys.is_empty());
assert_eq!(
options.p2pk_locked_proof_send_mode,
P2PKLockedProofSendMode::Swap
);
}

#[test]
fn test_proof_with_invalid_dleq_returns_error() {
let proof = Proof {
Expand Down
62 changes: 58 additions & 4 deletions crates/cdk-ffi/src/types/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,40 @@ impl From<cdk::wallet::SendKind> for SendKind {
}
}

/// FFI-compatible P2PK locked proof send mode
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, uniffi::Enum, Default,
)]
pub enum P2PKLockedProofSendMode {
/// Swap locked proofs into fresh proofs before creating the token
#[default]
Swap,
/// Sign locked proofs and include them directly in the token
SignAndSend,
}

impl From<P2PKLockedProofSendMode> for cdk::wallet::P2PKLockedProofSendMode {
fn from(mode: P2PKLockedProofSendMode) -> Self {
match mode {
P2PKLockedProofSendMode::Swap => cdk::wallet::P2PKLockedProofSendMode::Swap,
P2PKLockedProofSendMode::SignAndSend => {
cdk::wallet::P2PKLockedProofSendMode::SignAndSend
}
}
}
}

impl From<cdk::wallet::P2PKLockedProofSendMode> for P2PKLockedProofSendMode {
fn from(mode: cdk::wallet::P2PKLockedProofSendMode) -> Self {
match mode {
cdk::wallet::P2PKLockedProofSendMode::Swap => P2PKLockedProofSendMode::Swap,
cdk::wallet::P2PKLockedProofSendMode::SignAndSend => {
P2PKLockedProofSendMode::SignAndSend
}
}
}
}

/// FFI-compatible Send options
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
pub struct SendOptions {
Expand All @@ -160,6 +194,12 @@ pub struct SendOptions {
pub max_proofs: Option<u32>,
/// Metadata
pub metadata: HashMap<String, String>,
/// Signing keys for P2PK-locked input proofs
#[serde(default)]
pub p2pk_signing_keys: Vec<SecretKey>,
/// How P2PK-locked input proofs should be handled during send
#[serde(default)]
pub p2pk_locked_proof_send_mode: P2PKLockedProofSendMode,
}

impl Default for SendOptions {
Expand All @@ -173,13 +213,23 @@ impl Default for SendOptions {
max_proofs: None,
metadata: HashMap::new(),
use_p2bk: false,
p2pk_signing_keys: Vec::new(),
p2pk_locked_proof_send_mode: P2PKLockedProofSendMode::Swap,
}
}
}

impl From<SendOptions> for cdk::wallet::SendOptions {
fn from(opts: SendOptions) -> Self {
cdk::wallet::SendOptions {
impl TryFrom<SendOptions> for cdk::wallet::SendOptions {
type Error = FfiError;

fn try_from(opts: SendOptions) -> Result<Self, Self::Error> {
let p2pk_signing_keys = opts
.p2pk_signing_keys
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, _>>()?;

Ok(cdk::wallet::SendOptions {
memo: opts.memo.map(Into::into),
conditions: opts.conditions.and_then(|c| c.try_into().ok()),
amount_split_target: opts.amount_split_target.into(),
Expand All @@ -188,7 +238,9 @@ impl From<SendOptions> for cdk::wallet::SendOptions {
max_proofs: opts.max_proofs.map(|p| p as usize),
metadata: opts.metadata,
use_p2bk: opts.use_p2bk,
}
p2pk_signing_keys,
p2pk_locked_proof_send_mode: opts.p2pk_locked_proof_send_mode.into(),
})
}
}

Expand All @@ -203,6 +255,8 @@ impl From<cdk::wallet::SendOptions> for SendOptions {
max_proofs: opts.max_proofs.map(|p| p as u32),
metadata: opts.metadata,
use_p2bk: opts.use_p2bk,
p2pk_signing_keys: opts.p2pk_signing_keys.into_iter().map(Into::into).collect(),
p2pk_locked_proof_send_mode: opts.p2pk_locked_proof_send_mode.into(),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/cdk-ffi/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ impl Wallet {
) -> Result<std::sync::Arc<PreparedSend>, FfiError> {
let prepared = self
.inner
.prepare_send(amount.into(), options.into())
.prepare_send(amount.into(), options.try_into()?)
.await?;
Ok(std::sync::Arc::new(PreparedSend::new(
self.inner.clone(),
Expand Down
Loading
Loading