Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add public and private zaps #1040

Merged
merged 2 commits into from
Mar 18, 2024
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
42 changes: 26 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions mutiny-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ futures-util = { version = "0.3", default-features = false }
reqwest = { version = "0.11", default-features = false, features = ["multipart", "json"] }
async-trait = "0.1.68"
url = { version = "2.3.1", features = ["serde"] }
nostr = { version = "0.28.1", default-features = false, features = ["nip04", "nip05", "nip47", "nip57"] }
nostr-sdk = { version = "0.28.0", default-features = false, features = ["nip04", "nip05", "nip47", "nip57"] }
nostr = { version = "0.29.0", default-features = false, features = ["nip04", "nip05", "nip47", "nip57"] }
nostr-sdk = { version = "0.29.0", default-features = false, features = ["nip04", "nip05", "nip47", "nip57"] }
cbc = { version = "0.1", features = ["alloc"] }
aes = { version = "0.8" }
jwt-compact = { version = "0.8.0-beta.1", features = ["es256k"] }
Expand Down Expand Up @@ -84,8 +84,8 @@ gloo-net = { version = "0.4.0" }
instant = { version = "0.1", features = ["wasm-bindgen"] }
getrandom = { version = "0.2", features = ["js"] }
# add nip07 feature for wasm32
nostr = { version = "0.28.1", default-features = false, features = ["nip04", "nip05", "nip07", "nip47", "nip57"] }
nostr-sdk = { version = "0.28.0", default-features = false, features = ["nip04", "nip05", "nip07", "nip47", "nip57"] }
nostr = { version = "0.29.0", default-features = false, features = ["nip04", "nip05", "nip07", "nip47", "nip57"] }
nostr-sdk = { version = "0.29.0", default-features = false, features = ["nip04", "nip05", "nip07", "nip47", "nip57"] }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1", features = ["rt"] }
Expand Down
6 changes: 6 additions & 0 deletions mutiny-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,12 @@ impl From<nostr::event::builder::Error> for MutinyError {
}
}

impl From<nostr_sdk::signer::Error> for MutinyError {
fn from(_e: nostr_sdk::signer::Error) -> Self {
Self::NostrError
}
}

impl From<payjoin::send::CreateRequestError> for MutinyError {
fn from(_e: payjoin::send::CreateRequestError) -> Self {
Self::PayjoinCreateRequest
Expand Down
102 changes: 88 additions & 14 deletions mutiny-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,12 @@ use crate::{
};
use crate::{nostr::NostrManager, utils::sleep};
use ::nostr::nips::nip57;
#[cfg(target_arch = "wasm32")]
use ::nostr::prelude::rand::rngs::OsRng;
use ::nostr::prelude::ZapRequestData;
use ::nostr::{EventId, JsonUtil, Kind};
use ::nostr::{EventBuilder, EventId, JsonUtil, Kind};
#[cfg(target_arch = "wasm32")]
use ::nostr::{Keys, Tag};
use async_lock::RwLock;
use bdk_chain::ConfirmationTime;
use bip39::Mnemonic;
Expand All @@ -100,7 +104,7 @@ use moksha_core::primitives::{
PostMeltQuoteBolt11Response,
};
use moksha_core::token::TokenV3;
use nostr_sdk::{Client, RelayPoolNotification};
use nostr_sdk::{Client, NostrSigner, RelayPoolNotification};
use reqwest::multipart::{Form, Part};
use serde::{Deserialize, Serialize};
use serde_json::Value;
Expand Down Expand Up @@ -987,7 +991,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {
.expect("Failed to add relays");
client.connect().await;

client.subscribe(last_filters.clone()).await;
client.subscribe(last_filters.clone(), None).await;

// handle NWC requests
let mut notifications = client.notifications();
Expand Down Expand Up @@ -1015,7 +1019,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {
if event.verify().is_ok() {
match event.kind {
Kind::WalletConnectRequest => {
match nostr.handle_nwc_request(event, &self_clone).await {
match nostr.handle_nwc_request(*event, &self_clone).await {
Ok(Some(event)) => {
if let Err(e) = client.send_event(event).await {
log_warn!(logger, "Error sending NWC event: {e}");
Expand All @@ -1028,7 +1032,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {
}
}
Kind::EncryptedDirectMessage => {
if let Err(e) = nostr.handle_direct_message(event, &self_clone).await {
if let Err(e) = nostr.handle_direct_message(*event, &self_clone).await {
log_error!(logger, "Error handling dm: {e}");
}
}
Expand All @@ -1053,7 +1057,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {
if let Ok(current_filters) = nostr.get_filters() {
if !utils::compare_filters_vec(&current_filters, &last_filters) {
log_debug!(logger, "subscribing to new nwc filters");
client.subscribe(current_filters.clone()).await;
client.subscribe(current_filters.clone(), None).await;
last_filters = current_filters;
}
}
Expand Down Expand Up @@ -2178,6 +2182,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {
zap_npub: Option<::nostr::PublicKey>,
mut labels: Vec<String>,
comment: Option<String>,
privacy_level: PrivacyLevel,
) -> Result<MutinyInvoice, MutinyError> {
let response = self.lnurl_client.make_request(&lnurl.url).await?;

Expand All @@ -2186,7 +2191,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {
let msats = amount_sats * 1000;

// if user's npub is given, do an anon zap
let (zap_request, comment, privacy_level) = match zap_npub {
let (zap_request, comment) = match zap_npub {
Some(zap_npub) => {
let data = ZapRequestData {
public_key: zap_npub,
Expand All @@ -2200,15 +2205,84 @@ impl<S: MutinyStorage> MutinyWallet<S> {
event_id: None,
event_coordinate: None,
};
let event = nip57::anonymous_zap_request(data)?;

(Some(event.as_json()), None, PrivacyLevel::Anonymous)
let event = match privacy_level {
PrivacyLevel::Public => {
self.nostr
.primary_key
.sign_event_builder(EventBuilder::public_zap_request(data))
.await?
}
PrivacyLevel::Private => {
// if we have access to the keys, use those
// otherwise need to implement ourselves to use with NIP-07
match &self.nostr.primary_key {
NostrSigner::Keys(keys) => {
nip57::private_zap_request(data, keys)?
}
#[cfg(target_arch = "wasm32")]
NostrSigner::NIP07(_) => {
// Generate encryption key
// Since we are not doing deterministically, we will
// not be able to decrypt this ourself in the future.
// Unsure of how to best do this without access to the actual secret.
// Everything is saved locally in Mutiny so not the end of the world,
// however clients like Damus won't detect our own private zaps
// that we sent.
let private_zap_keys: Keys = Keys::generate();

let mut tags: Vec<Tag> =
vec![Tag::public_key(data.public_key)];
if let Some(event_id) = data.event_id {
tags.push(Tag::event(event_id));
}
let msg_builder = EventBuilder::new(
Kind::ZapPrivateMessage,
&data.message,
tags,
);
let msg = self
.nostr
.primary_key
.sign_event_builder(msg_builder)
.await?;
let created_at = msg.created_at;
let msg: String = nip57::encrypt_private_zap_message(
&mut OsRng,
private_zap_keys.secret_key().expect("just generated"),
&data.public_key,
msg.as_json(),
)?;

// Create final zap event
let mut tags: Vec<Tag> = data.into();
tags.push(Tag::Anon { msg: Some(msg) });
let private_zap_keys: Keys = Keys::generate();
EventBuilder::new(Kind::ZapRequest, "", tags)
.custom_created_at(created_at)
.to_event(&private_zap_keys)?
}
}
}
PrivacyLevel::Anonymous => nip57::anonymous_zap_request(data)?,
PrivacyLevel::NotAvailable => {
// a zap npub with the privacy level NotAvailable
// is invalid
return Err(MutinyError::InvalidArgumentsError);
}
};

(Some(event.as_json()), None)
}
None => {
// PrivacyLevel only applicable to zaps, without
// a zap npub we cannot do a zap
if privacy_level != PrivacyLevel::NotAvailable {
return Err(MutinyError::InvalidArgumentsError);
}

(None, comment.filter(|c| !c.is_empty()))
}
None => (
None,
comment.filter(|c| !c.is_empty()),
PrivacyLevel::NotAvailable,
),
};

let invoice = self
Expand Down
2 changes: 1 addition & 1 deletion mutiny-core/src/nostr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1052,7 +1052,7 @@ impl<S: MutinyStorage> NostrManager<S> {
.pubkey(secret.public_key())
.event(request_event.id);

client.subscribe(vec![filter]).await;
client.subscribe(vec![filter], None).await;

client
.send_event(request_event.clone())
Expand Down
22 changes: 16 additions & 6 deletions mutiny-core/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,18 +231,28 @@ pub fn compare_filters_vec(a: &[Filter], b: &[Filter]) -> bool {
}

// compare that we have all the same kinds
let agg_kinds_a: HashSet<Kind> = a.iter().flat_map(|i| i.kinds.clone()).collect();
let agg_kinds_b: HashSet<Kind> = b.iter().flat_map(|i| i.kinds.clone()).collect();
let agg_kinds_a: HashSet<Kind> = a
.iter()
.flat_map(|i| i.kinds.clone().unwrap_or_default())
.collect();
let agg_kinds_b: HashSet<Kind> = b
.iter()
.flat_map(|i| i.kinds.clone().unwrap_or_default())
.collect();

if agg_kinds_a != agg_kinds_b {
return false;
}

// compare same authors
let agg_authors_a: HashSet<nostr::PublicKey> =
a.iter().flat_map(|i| i.authors.clone()).collect();
let agg_authors_b: HashSet<nostr::PublicKey> =
b.iter().flat_map(|i| i.authors.clone()).collect();
let agg_authors_a: HashSet<nostr::PublicKey> = a
.iter()
.flat_map(|i| i.authors.clone().unwrap_or_default())
.collect();
let agg_authors_b: HashSet<nostr::PublicKey> = b
.iter()
.flat_map(|i| i.authors.clone().unwrap_or_default())
.collect();

if agg_authors_a != agg_authors_b {
return false;
Expand Down
4 changes: 2 additions & 2 deletions mutiny-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ lightning-invoice = { version = "0.29.0" }
thiserror = "1.0"
instant = { version = "0.1", features = ["wasm-bindgen"] }
lnurl-rs = { version = "0.4.1", default-features = false }
nostr = { version = "0.28.1", default-features = false, features = ["nip04", "nip05", "nip07", "nip47", "nip57"] }
nostr = { version = "0.29.0", default-features = false, features = ["nip04", "nip05", "nip07", "nip47", "nip57"] }
wasm-logger = "0.2.0"
log = "0.4.17"
rexie = "0.5.0"
Expand All @@ -47,7 +47,7 @@ payjoin = { version = "0.13.0", features = ["send", "base64"] }
fedimint-core = { git = "https://github.com/fedimint/fedimint", rev = "5ade2536015a12a7e003a42b159ccc4a431e1a32" }
moksha-core = { git = "https://github.com/ngutech21/moksha", rev = "18d99977965662d46ccec29fecdb0ce493745917" }

bitcoin-waila = { git = "https://github.com/mutinywallet/bitcoin-waila", rev = "b8b6a4d709e438fbadeb16bdf0c577c59be4a7f2" }
bitcoin-waila = { git = "https://github.com/mutinywallet/bitcoin-waila", rev = "311f8efcb5da9d351dd3445a4236f5f743605aa9" }

# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
Expand Down
Loading
Loading