Skip to content

Commit

Permalink
Merge pull request #1088 from breez/signer
Browse files Browse the repository at this point in the history
Implement lnurl auth signer
  • Loading branch information
roeierez authored Oct 20, 2024
2 parents fdc38ad + 3995ee8 commit 387e37f
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 37 deletions.
51 changes: 30 additions & 21 deletions libs/sdk-common/src/lnurl/specs/auth.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,51 @@
use std::str::FromStr;

use bitcoin::hashes::{hex::ToHex, sha256, Hash, HashEngine, Hmac, HmacEngine};
use bitcoin::secp256k1::{Message, Secp256k1};
use bitcoin::util::bip32::{ChildNumber, ExtendedPrivKey};
use bitcoin::KeyPair;
use bitcoin::hashes::hex::ToHex;
use bitcoin::util::bip32::{ChildNumber, ExtendedPubKey};
use reqwest::Url;

use crate::prelude::*;

pub trait LnurlAuthSigner {
fn derive_bip32_pub_key(&self, derivation_path: &[ChildNumber]) -> LnUrlResult<Vec<u8>>;
fn sign_ecdsa(&self, msg: &[u8], derivation_path: &[ChildNumber]) -> LnUrlResult<Vec<u8>>;
fn hmac_sha256(
&self,
key_derivation_path: &[ChildNumber],
input: &[u8],
) -> LnUrlResult<Vec<u8>>;
}

/// Performs the third and last step of LNURL-auth, as per
/// <https://github.com/lnurl/luds/blob/luds/04.md>
///
/// Linking key is derived as per LUD-05
/// https://github.com/lnurl/luds/blob/luds/05.md
///
/// See the [parse] docs for more detail on the full workflow.
pub async fn perform_lnurl_auth(
linking_keys: KeyPair,
req_data: LnUrlAuthRequestData,
pub async fn perform_lnurl_auth<S: LnurlAuthSigner>(
req_data: &LnUrlAuthRequestData,
signer: &S,
) -> LnUrlResult<LnUrlCallbackStatus> {
let k1_to_sign = Message::from_slice(
&hex::decode(req_data.k1)
let url = Url::from_str(&req_data.url).map_err(|e| LnUrlError::InvalidUri(e.to_string()))?;
let derivation_path = get_derivation_path(signer, url)?;
let sig = signer.sign_ecdsa(
&hex::decode(&req_data.k1)
.map_err(|e| LnUrlError::Generic(format!("Error decoding k1: {e}")))?,
&derivation_path,
)?;
let sig = Secp256k1::new().sign_ecdsa(&k1_to_sign, &linking_keys.secret_key());
let xpub_bytes = signer.derive_bip32_pub_key(&derivation_path)?;
let xpub = ExtendedPubKey::decode(xpub_bytes.as_slice())?;

// <LNURL_hostname_and_path>?<LNURL_existing_query_parameters>&sig=<hex(sign(utf8ToBytes(k1), linkingPrivKey))>&key=<hex(linkingKey)>
let mut callback_url =
Url::from_str(&req_data.url).map_err(|e| LnUrlError::InvalidUri(e.to_string()))?;
callback_url
.query_pairs_mut()
.append_pair("sig", &sig.serialize_der().to_hex());
.append_pair("sig", &sig.to_hex());
callback_url
.query_pairs_mut()
.append_pair("key", &linking_keys.public_key().to_hex());
.append_pair("key", &xpub.public_key.to_hex());

get_parse_and_log_response(callback_url.as_ref(), false)
.await
Expand Down Expand Up @@ -81,21 +93,18 @@ pub fn validate_request(
})
}

fn hmac_sha256(key: &[u8], input: &[u8]) -> Hmac<sha256::Hash> {
let mut engine = HmacEngine::<sha256::Hash>::new(key);
engine.input(input);
Hmac::<sha256::Hash>::from_engine(engine)
}

pub fn get_derivation_path(
hashing_key: ExtendedPrivKey,
pub fn get_derivation_path<S: LnurlAuthSigner>(
signer: &S,
url: Url,
) -> LnUrlResult<Vec<ChildNumber>> {
let domain = url
.domain()
.ok_or(LnUrlError::invalid_uri("Could not determine domain"))?;

let hmac = hmac_sha256(&hashing_key.to_priv().to_bytes(), domain.as_bytes());
let hmac = signer.hmac_sha256(
&[ChildNumber::from_hardened_idx(138)?, ChildNumber::from(0)],
domain.as_bytes(),
)?;

// m/138'/<long1>/<long2>/<long3>/<long4>
Ok(vec![
Expand Down
19 changes: 3 additions & 16 deletions libs/sdk-core/src/breez_services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ use bitcoin::hashes::{sha256, Hash};
use bitcoin::util::bip32::ChildNumber;
use chrono::Local;
use futures::TryFutureExt;
use gl_client::bitcoin::secp256k1::Secp256k1;
use log::{LevelFilter, Metadata, Record};
use reqwest::{header::CONTENT_TYPE, Body, Url};
use reqwest::{header::CONTENT_TYPE, Body};
use sdk_common::grpc;
use sdk_common::prelude::*;
use serde::Serialize;
Expand All @@ -33,6 +32,7 @@ use crate::error::{
RedeemOnchainResult, SdkError, SdkResult, SendOnchainError, SendPaymentError,
};
use crate::greenlight::{GLBackupTransport, Greenlight};
use crate::lnurl::auth::SdkLnurlAuthSigner;
use crate::lnurl::pay::*;
use crate::lsp::LspInformation;
use crate::models::{
Expand Down Expand Up @@ -577,20 +577,7 @@ impl BreezServices {
&self,
req_data: LnUrlAuthRequestData,
) -> Result<LnUrlCallbackStatus, LnUrlAuthError> {
// m/138'/0
let hashing_key = self.node_api.derive_bip32_key(vec![
ChildNumber::from_hardened_idx(138).map_err(Into::<LnUrlError>::into)?,
ChildNumber::from(0),
])?;

let url =
Url::from_str(&req_data.url).map_err(|e| LnUrlError::InvalidUri(e.to_string()))?;

let derivation_path = get_derivation_path(hashing_key, url)?;
let linking_key = self.node_api.derive_bip32_key(derivation_path)?;
let linking_keys = linking_key.to_keypair(&Secp256k1::new());

Ok(perform_lnurl_auth(linking_keys, req_data).await?)
Ok(perform_lnurl_auth(&req_data, &SdkLnurlAuthSigner::new(self.node_api.clone())).await?)
}

/// Creates an bolt11 payment request.
Expand Down
53 changes: 53 additions & 0 deletions libs/sdk-core/src/lnurl/auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use std::sync::Arc;

use gl_client::bitcoin::{
hashes::{sha256, Hash, HashEngine, Hmac, HmacEngine},
secp256k1::{Message, Secp256k1},
util::bip32::{ChildNumber, ExtendedPubKey},
};
use sdk_common::prelude::{LnUrlError, LnUrlResult, LnurlAuthSigner};

use crate::node_api::NodeAPI;

pub(crate) struct SdkLnurlAuthSigner {
node_api: Arc<dyn NodeAPI>,
}

impl SdkLnurlAuthSigner {
pub fn new(node_api: Arc<dyn NodeAPI>) -> Self {
Self { node_api }
}
}

impl LnurlAuthSigner for SdkLnurlAuthSigner {
fn derive_bip32_pub_key(&self, derivation_path: &[ChildNumber]) -> LnUrlResult<Vec<u8>> {
let xpriv = self.node_api.derive_bip32_key(derivation_path.to_vec())?;
Ok(ExtendedPubKey::from_priv(&Secp256k1::new(), &xpriv)
.encode()
.to_vec())
}

fn sign_ecdsa(&self, msg: &[u8], derivation_path: &[ChildNumber]) -> LnUrlResult<Vec<u8>> {
let xpriv = self.node_api.derive_bip32_key(derivation_path.to_vec())?;
let sig = Secp256k1::new().sign_ecdsa(
&Message::from_slice(msg).map_err(|_| LnUrlError::generic("Failed to sign"))?,
&xpriv.private_key,
);
Ok(sig.serialize_der().to_vec())
}

fn hmac_sha256(
&self,
key_derivation_path: &[ChildNumber],
input: &[u8],
) -> LnUrlResult<Vec<u8>> {
let priv_key = self
.node_api
.derive_bip32_key(key_derivation_path.to_vec())?;
let mut engine = HmacEngine::<sha256::Hash>::new(priv_key.encode().as_slice());
engine.input(input);
Ok(Hmac::<sha256::Hash>::from_engine(engine)
.as_inner()
.to_vec())
}
}
1 change: 1 addition & 0 deletions libs/sdk-core/src/lnurl/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod auth;
pub mod pay;

#[cfg(test)]
Expand Down

0 comments on commit 387e37f

Please sign in to comment.