Skip to content

Commit

Permalink
LNURL support for lnurl_response
Browse files Browse the repository at this point in the history
  • Loading branch information
zhenlu committed Apr 22, 2024
1 parent 39cc783 commit 4c0fb16
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 21 deletions.
92 changes: 87 additions & 5 deletions src/protocol/lnurl_response.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use serde::{Deserialize, Serialize};

use super::{currency::Currency, kyc_status::KycStatus, payer_data::PayerDataOptions};
use super::{
counter_party_data::CounterPartyDataOptions, currency::Currency, kyc_status::KycStatus,
};

/// LnurlpResponse is the response to the LnurlpRequest.
/// It is sent by the VASP that is receiving the payment to provide information to the sender about the receiver.
Expand All @@ -18,19 +20,30 @@ pub struct LnurlpResponse {
#[serde(rename = "metadata")]
pub encoded_metadata: String,

pub currencies: Vec<Currency>,
pub currencies: Option<Vec<Currency>>,

#[serde(rename = "payerData")]
pub required_payer_data: PayerDataOptions,
pub required_payer_data: Option<CounterPartyDataOptions>,

pub compliance: LnurlComplianceResponse,
pub compliance: Option<LnurlComplianceResponse>,

/// UmaVersion is the version of the UMA protocol that VASP2 has chosen for this transaction
/// based on its own support and VASP1's specified preference in the LnurlpRequest. For the
/// version negotiation flow, see
/// https://static.swimlanes.io/87f5d188e080cb8e0494e46f80f2ae74.png
#[serde(rename = "umaVersion")]
pub uma_version: String,
pub uma_version: Option<String>,

// CommentCharsAllowed is the number of characters that the sender can include in the comment field of the pay request.
#[serde(rename = "commentCharsAllowed")]
pub comment_chars_allowed: Option<i64>,
// NostrPubkey is an optional nostr pubkey used for nostr zaps (NIP-57). If set, it should be a valid BIP-340 public
// key in hex format.
#[serde(rename = "nostrPubkey")]
pub nostr_pubkey: Option<String>,
// AllowsNostr should be set to true if the receiving VASP allows nostr zaps (NIP-57).
#[serde(rename = "allowsNostr")]
pub allows_nostr: Option<bool>,
}

/// LnurlComplianceResponse is the `compliance` field of the LnurlpResponse.
Expand Down Expand Up @@ -61,6 +74,75 @@ pub struct LnurlComplianceResponse {
}

impl LnurlpResponse {
pub fn as_uma_response(&self) -> Option<UmaLnurlpResponse> {
self.currencies.clone().and_then(|currenct| {
self.required_payer_data.clone().and_then(|payer_data| {
self.compliance.clone().and_then(|compliance| {
self.uma_version.clone().and_then(|uma_version| {
Some(UmaLnurlpResponse {
tag: self.tag.clone(),
callback: self.callback.clone(),
min_sendable: self.min_sendable,
max_sendable: self.max_sendable,
encoded_metadata: self.encoded_metadata.clone(),
currencies: currenct,
required_payer_data: payer_data,
compliance,
uma_version,
comment_chars_allowed: self.comment_chars_allowed,
nostr_pubkey: self.nostr_pubkey.clone(),
allows_nostr: self.allows_nostr,
})
})
})
})
})
}
}

/// UmaLnurlpResponse is the response to the LnurlpRequest.
/// It is sent by the VASP that is receiving the payment to provide information to the sender about the receiver.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct UmaLnurlpResponse {
pub tag: String,
pub callback: String,

#[serde(rename = "minSendable")]
pub min_sendable: i64,

#[serde(rename = "maxSendable")]
pub max_sendable: i64,

#[serde(rename = "metadata")]
pub encoded_metadata: String,

pub currencies: Vec<Currency>,

#[serde(rename = "payerData")]
pub required_payer_data: CounterPartyDataOptions,

pub compliance: LnurlComplianceResponse,

/// UmaVersion is the version of the UMA protocol that VASP2 has chosen for this transaction
/// based on its own support and VASP1's specified preference in the LnurlpRequest. For the
/// version negotiation flow, see
/// https://static.swimlanes.io/87f5d188e080cb8e0494e46f80f2ae74.png
#[serde(rename = "umaVersion")]
pub uma_version: String,

// CommentCharsAllowed is the number of characters that the sender can include in the comment field of the pay request.
#[serde(rename = "commentCharsAllowed")]
pub comment_chars_allowed: Option<i64>,
// NostrPubkey is an optional nostr pubkey used for nostr zaps (NIP-57). If set, it should be a valid BIP-340 public
// key in hex format.
#[serde(rename = "nostrPubkey")]
pub nostr_pubkey: Option<String>,
// AllowsNostr should be set to true if the receiving VASP allows nostr zaps (NIP-57).
#[serde(rename = "allowsNostr")]
pub allows_nostr: Option<bool>,
}

impl UmaLnurlpResponse {
pub fn signable_payload(&self) -> Vec<u8> {
let payload_string = format!(
"{}|{}|{}",
Expand Down
33 changes: 25 additions & 8 deletions src/uma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ use bitcoin::secp256k1::{
use rand_core::{OsRng, RngCore};

use crate::protocol::{
counter_party_data::CounterPartyDataOptions,
currency::Currency,
kyc_status::KycStatus,
lnurl_request::{LnurlpRequest, UmaLnurlpRequest},
lnurl_response::{LnurlComplianceResponse, LnurlpResponse},
pay_request::PayRequest,
payer_data::{CompliancePayerData, PayerData, PayerDataOptions},
payer_data::{CompliancePayerData, PayerData},
payreq_response::{PayReqResponse, PayReqResponseCompliance, PayReqResponsePaymentInfo},
pub_key_response::PubKeyResponse,
};
Expand Down Expand Up @@ -285,9 +286,11 @@ pub fn get_lnurlp_response(
encoded_metadata: &str,
min_sendable_sats: i64,
max_sendable_sats: i64,
payer_data_options: &PayerDataOptions,
payer_data_options: &CounterPartyDataOptions,
currency_options: &[Currency],
receiver_kyc_status: KycStatus,
comment_chars_allowed: Option<i64>,
nostr_pubkey: Option<String>,
) -> Result<LnurlpResponse, Error> {
let compliance_response = get_signed_compliance_respionse(
query,
Expand All @@ -300,16 +303,25 @@ pub fn get_lnurlp_response(
&version::uma_protocol_version(),
)
.map_err(|_| Error::InvalidVersion)?;

let mut allows_nostr: Option<bool> = None;
if nostr_pubkey.is_some() {
allows_nostr = Some(true);
}

Ok(LnurlpResponse {
tag: "payRequest".to_string(),
callback: callback.to_string(),
min_sendable: min_sendable_sats * 1000,
max_sendable: max_sendable_sats * 1000,
encoded_metadata: encoded_metadata.to_string(),
currencies: currency_options.to_vec(),
required_payer_data: payer_data_options.clone(),
compliance: compliance_response,
uma_version,
currencies: Some(currency_options.to_vec()),
required_payer_data: Some(payer_data_options.clone()),
compliance: Some(compliance_response.clone()),
uma_version: Some(uma_version.clone()),
comment_chars_allowed,
nostr_pubkey,
allows_nostr,
})
}

Expand Down Expand Up @@ -344,8 +356,13 @@ pub fn verify_uma_lnurlp_response_signature(
response: &LnurlpResponse,
other_vasp_pub_key: &[u8],
) -> Result<(), Error> {
let payload = response.signable_payload();
verify_ecdsa(&payload, &response.compliance.signature, other_vasp_pub_key)
let uma_response = response.as_uma_response().ok_or(Error::InvalidResponse)?;
let payload = uma_response.signable_payload();
verify_ecdsa(
&payload,
&uma_response.compliance.signature,
other_vasp_pub_key,
)
}

pub fn parse_lnurlp_response(bytes: &[u8]) -> Result<LnurlpResponse, Error> {
Expand Down
30 changes: 22 additions & 8 deletions src/uma_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@
mod tests {
use ecies::utils::generate_keypair;

use crate::protocol::counter_party_data::{
CounterPartyDataField, CounterPartyDataOption, CounterPartyDataOptions,
};
use crate::uma::{
get_lnurlp_response, get_pay_req_response, get_pay_request, get_signed_lnurlp_request_url,
is_uma_lnurl_query, parse_lnurlp_request, parse_lnurlp_response, parse_pay_req_response,
parse_pay_request, verify_pay_req_signature, verify_uma_lnurlp_query_signature,
verify_uma_lnurlp_response_signature, UmaInvoiceCreator,
};

use crate::protocol::{
currency::Currency, lnurl_request::LnurlpRequest, payer_data::PayerDataOptions,
};
use crate::protocol::{currency::Currency, lnurl_request::LnurlpRequest};

#[test]
fn test_parse() {
Expand Down Expand Up @@ -159,6 +160,21 @@ mod tests {
decimals: 2,
}];

let data_options = CounterPartyDataOptions::from([
(
CounterPartyDataField::CounterPartyDataFieldName,
CounterPartyDataOption { mandatory: false },
),
(
CounterPartyDataField::CounterPartyDataFieldEmail,
CounterPartyDataOption { mandatory: false },
),
(
CounterPartyDataField::CounterPartyDataFieldCompliance,
CounterPartyDataOption { mandatory: true },
),
]);

let response = get_lnurlp_response(
&query,
&sk2.serialize(),
Expand All @@ -167,13 +183,11 @@ mod tests {
metadata.as_str(),
1,
10_000_000,
&PayerDataOptions {
name_required: false,
email_required: false,
compliance_required: true,
},
&data_options,
&currency_options,
crate::protocol::kyc_status::KycStatus::KycStatusVerified,
None,
None,
)
.unwrap();

Expand Down

0 comments on commit 4c0fb16

Please sign in to comment.