Skip to content

Commit

Permalink
Uma data visibility (#51)
Browse files Browse the repository at this point in the history
  • Loading branch information
shreyav authored Jul 8, 2024
1 parent be9d724 commit 3af49e3
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 0 deletions.
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

0 comments on commit 3af49e3

Please sign in to comment.