Skip to content
This repository has been archived by the owner on Feb 3, 2025. It is now read-only.

Commit

Permalink
Add public and private zaps
Browse files Browse the repository at this point in the history
  • Loading branch information
benthecarman committed Feb 19, 2024
1 parent 18fb63c commit ca290a7
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 13 deletions.
6 changes: 6 additions & 0 deletions mutiny-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,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
90 changes: 80 additions & 10 deletions mutiny-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ use crate::{
};
use crate::{nostr::NostrManager, utils::sleep};
use ::nostr::nips::nip57;
use ::nostr::prelude::rand::rngs::OsRng;
use ::nostr::prelude::ZapRequestData;
use ::nostr::{Event, EventId, JsonUtil, Kind, Metadata};
use ::nostr::{Event, EventBuilder, EventId, JsonUtil, Keys, Kind, Metadata, Tag};
use async_lock::RwLock;
use bdk_chain::ConfirmationTime;
use bip39::Mnemonic;
Expand All @@ -93,7 +94,7 @@ use lightning::util::logger::Logger;
use lightning::{log_debug, log_error, log_info, log_trace, log_warn};
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription};
use lnurl::{lnurl::LnUrl, AsyncClient as LnUrlClient, LnUrlResponse, Response};
use nostr_sdk::{Client, RelayPoolNotification};
use nostr_sdk::{Client, NostrSigner, RelayPoolNotification};
use reqwest::multipart::{Form, Part};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
Expand Down Expand Up @@ -2231,6 +2232,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 @@ -2239,7 +2241,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 @@ -2253,15 +2255,83 @@ 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)?
}
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 = utils::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
39 changes: 38 additions & 1 deletion mutiny-core/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
use crate::error::MutinyError;
use aes::cipher::block_padding::Pkcs7;
use aes::cipher::crypto_common::rand_core::RngCore;
use aes::cipher::{BlockEncryptMut, KeyIvInit};
use aes::Aes256;
use bitcoin::bech32::{ToBase32, Variant};
use bitcoin::key::XOnlyPublicKey;
use bitcoin::Network;
use bitcoin::{bech32, Network};
use cbc::Encryptor;
use core::cell::{RefCell, RefMut};
use core::ops::{Deref, DerefMut};
use core::time::Duration;
Expand Down Expand Up @@ -291,3 +297,34 @@ pub fn convert_from_fedimint_invoice(
) -> Bolt11Invoice {
Bolt11Invoice::from_str(&invoice.to_string()).expect("just converting types")
}

// copied from rust-nostr until https://github.com/rust-nostr/nostr/pull/299 is released
/// Encrypt a private zap message using the given keys
pub fn encrypt_private_zap_message<R, T>(
rng: &mut R,
secret_key: &nostr::SecretKey,
public_key: &nostr::PublicKey,
msg: T,
) -> Result<String, nostr::nips::nip57::Error>
where
R: RngCore,
T: AsRef<[u8]>,
{
let key: [u8; 32] = nostr::util::generate_shared_key(secret_key, public_key);
let mut iv: [u8; 16] = [0u8; 16];
rng.fill_bytes(&mut iv);

type Aes256CbcEnc = Encryptor<Aes256>;
let cipher = Aes256CbcEnc::new(&key.into(), &iv.into());
let result: Vec<u8> = cipher.encrypt_padded_vec_mut::<Pkcs7>(msg.as_ref());

// Bech32 msg
let data = result.to_base32();
let encrypted_bech32_msg = bech32::encode("pzap", data, Variant::Bech32)?;

// Bech32 IV
let data = iv.to_base32();
let iv_bech32 = bech32::encode("iv", data, Variant::Bech32)?;

Ok(format!("{encrypted_bech32_msg}_{iv_bech32}"))
}
20 changes: 18 additions & 2 deletions mutiny-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ use mutiny_core::nostr::NostrKeySource;
use mutiny_core::storage::{DeviceLock, MutinyStorage, DEVICE_LOCK_KEY};
use mutiny_core::utils::{now, parse_npub, parse_npub_or_nip05, sleep, spawn};
use mutiny_core::vss::MutinyVssClient;
use mutiny_core::{encrypt::encryption_key_from_pass, InvoiceHandler, MutinyWalletConfigBuilder};
use mutiny_core::{
encrypt::encryption_key_from_pass, InvoiceHandler, MutinyWalletConfigBuilder, PrivacyLevel,
};
use mutiny_core::{labels::Contact, MutinyWalletBuilder};
use mutiny_core::{
labels::LabelStorage,
Expand Down Expand Up @@ -879,6 +881,7 @@ impl MutinyWallet {
zap_npub: Option<String>,
labels: Vec<String>,
comment: Option<String>,
privacy_level: Option<String>,
) -> Result<MutinyInvoice, MutinyJsError> {
let lnurl = LnUrl::from_str(&lnurl)?;

Expand All @@ -887,9 +890,22 @@ impl MutinyWallet {
None => None,
};

let privacy_level = privacy_level
.as_deref()
.map(PrivacyLevel::from_str)
.transpose()?
.unwrap_or_default(); // default to NotAvailable

Ok(self
.inner
.lnurl_pay(&lnurl, amount_sats, zap_npub, labels, comment)
.lnurl_pay(
&lnurl,
amount_sats,
zap_npub,
labels,
comment,
privacy_level,
)
.await?
.into())
}
Expand Down

0 comments on commit ca290a7

Please sign in to comment.