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

Uma data visibility #51

Merged
merged 1 commit into from
Jul 8, 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
133 changes: 133 additions & 0 deletions lightspark/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::str::FromStr;

use bitcoin::bip32::{DerivationPath, ExtendedPrivKey};
use bitcoin::secp256k1::Secp256k1;
use chrono::{DateTime, Datelike, Utc};
use serde_json::Value;
use sha2::{Digest, Sha256};

Expand Down Expand Up @@ -866,19 +867,41 @@ impl<K: OperationSigningKey> LightsparkClient<K> {
amount_msats: i64,
metadata: &str,
expiry_secs: Option<i32>,
) -> Result<Invoice, Error> {
self.create_uma_invoice_with_receiver_identifier(
node_id,
amount_msats,
metadata,
expiry_secs,
None,
None,
)
.await
}

pub async fn create_uma_invoice_with_receiver_identifier(
&self,
node_id: &str,
amount_msats: i64,
metadata: &str,
expiry_secs: Option<i32>,
signing_private_key: Option<&[u8]>,
receiver_identifier: Option<&str>,
) -> Result<Invoice, Error> {
let mutation = format!(
"mutation CreateUmaInvoice(
$node_id: ID!
$amount_msats: Long!
$metadata_hash: String!
$expiry_secs: Int
$receiver_hash: String = null
) {{
create_uma_invoice(input: {{
node_id: $node_id
amount_msats: $amount_msats
metadata_hash: $metadata_hash
expiry_secs: $expiry_secs
receiver_hash: $receiver_hash
}}) {{
invoice {{
...InvoiceFragment
Expand All @@ -891,6 +914,21 @@ impl<K: OperationSigningKey> LightsparkClient<K> {
invoice::FRAGMENT
);

let receiver_hash = if let Some(receiver_identifier) = receiver_identifier {
if signing_private_key.is_none() {
return Err(Error::InvalidArgumentError(
"receiver identifier provided without signing private key".to_owned(),
));
}
Some(Self::hash_uma_identifier(
receiver_identifier,
signing_private_key.unwrap(),
chrono::Utc::now(),
))
} else {
None
};

let mut hasher = Sha256::new();
hasher.update(metadata.as_bytes());

Expand All @@ -903,6 +941,9 @@ impl<K: OperationSigningKey> LightsparkClient<K> {
if let Some(expiry_secs) = expiry_secs {
variables.insert("expiry_secs", expiry_secs.into());
}
if let Some(receiver_hash) = receiver_hash {
variables.insert("receiver_hash", receiver_hash.into());
}

let value = serde_json::to_value(variables).map_err(Error::ConversionError)?;
let json = self
Expand All @@ -922,6 +963,28 @@ impl<K: OperationSigningKey> LightsparkClient<K> {
timeout_secs: i32,
maximum_fees_msats: i64,
amount_msats: Option<i64>,
) -> Result<OutgoingPayment, Error> {
self.pay_uma_invoice_with_sender_identifier(
node_id,
encoded_invoice,
timeout_secs,
maximum_fees_msats,
amount_msats,
None,
None,
)
.await
}

pub async fn pay_uma_invoice_with_sender_identifier(
&self,
node_id: &str,
encoded_invoice: &str,
timeout_secs: i32,
maximum_fees_msats: i64,
amount_msats: Option<i64>,
signing_private_key: Option<&[u8]>,
sender_identifier: Option<&str>,
) -> Result<OutgoingPayment, Error> {
let operation = format!(
"mutation PayUmaInvoice(
Expand All @@ -930,13 +993,15 @@ impl<K: OperationSigningKey> LightsparkClient<K> {
$timeout_secs: Int!
$maximum_fees_msats: Long!
$amount_msats: Long
$sender_hash: String = null
) {{
pay_uma_invoice(input: {{
node_id: $node_id
encoded_invoice: $encoded_invoice
timeout_secs: $timeout_secs
maximum_fees_msats: $maximum_fees_msats
amount_msats: $amount_msats
sender_hash: $sender_hash
}}) {{
payment {{
...OutgoingPaymentFragment
Expand All @@ -949,6 +1014,21 @@ impl<K: OperationSigningKey> LightsparkClient<K> {
outgoing_payment::FRAGMENT
);

let sender_hash = if let Some(sender_identifier) = sender_identifier {
if signing_private_key.is_none() {
return Err(Error::InvalidArgumentError(
"sender identifier provided without signing private key".to_owned(),
));
}
Some(Self::hash_uma_identifier(
sender_identifier,
signing_private_key.unwrap(),
chrono::Utc::now(),
))
} else {
None
};

let mut variables: HashMap<&str, Value> = HashMap::new();
variables.insert("node_id", node_id.into());
variables.insert("encoded_invoice", encoded_invoice.into());
Expand All @@ -957,6 +1037,9 @@ impl<K: OperationSigningKey> LightsparkClient<K> {
}
variables.insert("timeout_secs", timeout_secs.into());
variables.insert("maximum_fees_msats", maximum_fees_msats.into());
if let Some(sender_hash) = sender_hash {
variables.insert("sender_hash", sender_hash.into());
}

let value = serde_json::to_value(variables).map_err(Error::ConversionError)?;

Expand Down Expand Up @@ -1214,6 +1297,23 @@ impl<K: OperationSigningKey> LightsparkClient<K> {
Ok(result)
}

pub fn hash_uma_identifier(
identifier: &str,
signing_private_key: &[u8],
now: DateTime<Utc>,
) -> String {
let input_data = format!(
"{}{}-{}{}",
identifier,
now.month(),
now.year(),
hex::encode(signing_private_key)
);
let mut hasher = Sha256::new();
hasher.update(input_data.as_bytes());
hex::encode(hasher.finalize())
}

fn hash_phone_number(phone_number_e164: &str) -> Result<String, Error> {
let e164_regex = regex::Regex::new(r"^\+[1-9]\d{1,14}$").unwrap();
if !e164_regex.is_match(phone_number_e164) {
Expand All @@ -1224,3 +1324,36 @@ impl<K: OperationSigningKey> LightsparkClient<K> {
Ok(hex::encode(hasher.finalize()))
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::key::Secp256k1SigningKey;
use chrono::prelude::*;

#[test]
fn test_hash_uma_identifier() {
let signing_key = "xyz".as_bytes();
let mock_time_jan = Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap();
let mock_time_feb = Utc.with_ymd_and_hms(2021, 2, 1, 0, 0, 0).unwrap();

let hashed_uma = LightsparkClient::<Secp256k1SigningKey>::hash_uma_identifier(
"[email protected]",
signing_key,
mock_time_jan,
);
let hashed_uma_same_month = LightsparkClient::<Secp256k1SigningKey>::hash_uma_identifier(
"[email protected]",
signing_key,
mock_time_jan,
);
assert_eq!(hashed_uma, hashed_uma_same_month);

let hashed_uma_diff_month = LightsparkClient::<Secp256k1SigningKey>::hash_uma_identifier(
"[email protected]",
signing_key,
mock_time_feb,
);
assert_ne!(hashed_uma, hashed_uma_diff_month);
}
}
2 changes: 2 additions & 0 deletions lightspark/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub enum Error {
SigningKeyNotFound,
InvalidCurrencyConversion,
InvalidPhoneNumber,
InvalidArgumentError(String),
}

impl fmt::Display for Error {
Expand All @@ -35,6 +36,7 @@ impl fmt::Display for Error {
Self::SigningKeyNotFound => write!(f, "Signing key not found"),
Self::InvalidCurrencyConversion => write!(f, "Invalid currency conversion"),
Self::InvalidPhoneNumber => write!(f, "Invalid phone number. Must be E.164 format."),
Self::InvalidArgumentError(err) => write!(f, "Invalid argument error {}", err),
}
}
}
Expand Down
Loading