Skip to content
Closed
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,11 @@
`ChannelTypeFeatures`.
- `Config::anchor_channels_config` is no longer optional, hence anchor channels can no longer be
disabled. We still negotiate legacy channels if the peer does not support anchor channels.
- The paid BOLT 12 invoice is now persisted on `PaymentKind::Bolt12Offer` and
`PaymentKind::Bolt12Refund`, and `Bolt12Payment::create_payer_proof` allows building a BOLT 12
payer proof for a previously succeeded outbound BOLT 12 payment. `PayerProofOptions` controls
which optional invoice fields are selectively disclosed. Payments that completed via a static
invoice, i.e., async payments, do not support payer proofs. (#845)

## Bug Fixes and Improvements
- Building a fresh node against a Bitcoin Core RPC or REST chain source that fails to return the
Expand Down
3 changes: 3 additions & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,8 @@ enum NodeError {
"FeerateEstimationUpdateTimeout",
"WalletOperationFailed",
"WalletOperationTimeout",
"PayerProofCreationFailed",
"PayerProofUnavailable",
"OnchainTxSigningFailed",
"TxSyncFailed",
"TxSyncTimeout",
Expand DownExpand Up@@ -247,6 +249,7 @@ enum NodeError {
"LnurlAuthTimeout",
"InvalidLnurl",
"ChainSourceNotSupported",
"InvalidPayerProof",
};

typedef dictionary NodeStatus;
Expand Down
11 changes: 11 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,10 @@ pub enum Error {
WalletOperationFailed,
/// A wallet operation timed out.
WalletOperationTimeout,
/// Creating a payer proof failed.
PayerProofCreationFailed,
/// A payer proof is unavailable for the requested payment.
PayerProofUnavailable,
/// A signing operation for transaction failed.
OnchainTxSigningFailed,
/// A transaction sync operation failed.
Expand DownExpand Up@@ -139,6 +143,8 @@ pub enum Error {
InvalidLnurl,
/// The configured chain source is not supported.
ChainSourceNotSupported,
/// The provided payer proof is invalid.
InvalidPayerProof,
}

impl fmt::Display for Error {
Expand DownExpand Up@@ -170,6 +176,10 @@ impl fmt::Display for Error {
},
Self::WalletOperationFailed => write!(f, "Failed to conduct wallet operation."),
Self::WalletOperationTimeout => write!(f, "A wallet operation timed out."),
Self::PayerProofCreationFailed => write!(f, "Failed to create payer proof."),
Self::PayerProofUnavailable => {
write!(f, "A payer proof is unavailable for the requested payment.")
},
Self::OnchainTxSigningFailed => write!(f, "Failed to sign given transaction."),
Self::TxSyncFailed => write!(f, "Failed to sync transactions."),
Self::TxSyncTimeout => write!(f, "Syncing transactions timed out."),
Expand DownExpand Up@@ -227,6 +237,7 @@ impl fmt::Display for Error {
Self::ChainSourceNotSupported => {
write!(f, "The configured chain source is not supported.")
},
Self::InvalidPayerProof => write!(f, "The provided payer proof is invalid."),
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/event.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1129,6 +1129,7 @@ where
offer_id,
payer_note,
quantity,
bolt12_invoice: None,
};

let payment = PaymentDetails::new(
Expand DownExpand Up@@ -1178,6 +1179,7 @@ where
secret: Some(payment_secret),
payer_note: None,
quantity: None,
bolt12_invoice: None,
};

let payment = PaymentDetails::new(
Expand DownExpand Up@@ -1331,6 +1333,7 @@ where
offer_id: payment_context.offer_id,
payer_note: payment_context.invoice_request.payer_note_truncated,
quantity: payment_context.invoice_request.quantity,
bolt12_invoice: None,
};
let update = PaymentDetailsUpdate {
preimage: Some(payment_preimage),
Expand All@@ -1352,6 +1355,7 @@ where
secret: Some(payment_secret),
payer_note: None,
quantity: None,
bolt12_invoice: None,
};
let update = PaymentDetailsUpdate {
preimage: Some(payment_preimage),
Expand DownExpand Up@@ -1442,12 +1446,16 @@ where
debug_assert!(false, "payment_id should always be set.");
return Ok(());
};
let bolt12_invoice = bolt12_invoice.map(Into::into);

// Only set the field if the event actually carried an invoice, to avoid
// overriding any previously-stored invoice with `None`.
let update = PaymentDetailsUpdate {
hash: Some(Some(payment_hash)),
preimage: Some(Some(payment_preimage)),
fee_paid_msat: Some(fee_paid_msat),
status: Some(PaymentStatus::Succeeded),
bolt12_invoice: bolt12_invoice.clone().map(Some),
..PaymentDetailsUpdate::new(payment_id)
};

Expand DownExpand Up@@ -1480,7 +1488,7 @@ where
payment_hash,
payment_preimage: Some(payment_preimage),
fee_paid_msat,
bolt12_invoice: bolt12_invoice.map(Into::into),
bolt12_invoice,
};

match self.event_queue.add_event(event).await {
Expand Down
111 changes: 110 additions & 1 deletion src/ffi/types.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,6 @@ use bitcoin::hashes::Hash;
use bitcoin::secp256k1::PublicKey;
pub use bitcoin::{Address, BlockHash, Network, OutPoint, ScriptBuf, Txid};
pub use lightning::chain::channelmonitor::BalanceSource;
use lightning::events::PaidBolt12Invoice as LdkPaidBolt12Invoice;
pub use lightning::events::{ClosureReason, PaymentFailureReason};
use lightning::ln::channel_state::{ChannelShutdownState, CounterpartyForwardingInfo};
use lightning::ln::channelmanager::PaymentId;
Expand All@@ -32,6 +31,9 @@ pub use lightning::ln::types::ChannelId;
use lightning::offers::invoice::Bolt12Invoice as LdkBolt12Invoice;
pub use lightning::offers::offer::OfferId;
use lightning::offers::offer::{Amount as LdkAmount, Offer as LdkOffer};
use lightning::offers::payer_proof::{
PaidBolt12Invoice as LdkPaidBolt12Invoice, PayerProof as LdkPayerProof,
};
use lightning::offers::refund::Refund as LdkRefund;
use lightning::offers::static_invoice::StaticInvoice as LdkStaticInvoice;
use lightning::onion_message::dns_resolution::HumanReadableName as LdkHumanReadableName;
Expand DownExpand Up@@ -881,6 +883,113 @@ impl Readable for PaidBolt12Invoice {
}
}

/// A cryptographic proof that a BOLT12 invoice was paid by this node.
#[derive(Debug, Clone, uniffi::Object)]
#[uniffi::export(Debug, Display)]
pub struct PayerProof {
pub(crate) inner: LdkPayerProof,
}

#[uniffi::export]
impl PayerProof {
#[uniffi::constructor]
pub fn from_bytes(proof_bytes: Vec<u8>) -> Result<Self, Error> {
let inner = LdkPayerProof::try_from(proof_bytes).map_err(|_| Error::InvalidPayerProof)?;
Ok(Self { inner })
}

/// The payment preimage proving the payment completed.
pub fn payment_preimage(&self) -> PaymentPreimage {
self.inner.payment_preimage()
}

/// The payment hash committed to by the invoice and proven by the preimage.
pub fn payment_hash(&self) -> PaymentHash {
self.inner.payment_hash()
}

/// The public key of the payer that authorized the payment.
pub fn payer_signing_pubkey(&self) -> PublicKey {
self.inner.payer_signing_pubkey()
}

/// The issuer signing public key committed to by the invoice.
pub fn issuer_signing_pubkey(&self) -> PublicKey {
self.inner.issuer_signing_pubkey()
}

/// The invoice signature bytes.
pub fn invoice_signature(&self) -> Vec<u8> {
self.inner.invoice_signature().as_ref().to_vec()
}

/// The proof signature bytes.
pub fn proof_signature(&self) -> Vec<u8> {
self.inner.proof_signature().as_ref().to_vec()
}

/// The offer description, if it was disclosed in the proof.
pub fn offer_description(&self) -> Option<String> {
self.inner.offer_description().map(|value| value.to_string())
}

/// The offer issuer, if it was disclosed in the proof.
pub fn offer_issuer(&self) -> Option<String> {
self.inner.offer_issuer().map(|value| value.to_string())
}

/// The invoice amount in millisatoshis, if it was disclosed in the proof.
pub fn invoice_amount_msats(&self) -> Option<u64> {
self.inner.invoice_amount_msats()
}

/// The invoice creation time, in seconds since the UNIX epoch, if it was disclosed in the
/// proof.
pub fn invoice_created_at(&self) -> Option<u64> {
self.inner.invoice_created_at().map(|value| value.as_secs())
}

/// The optional note attached to the proof.
pub fn proof_note(&self) -> Option<String> {
self.inner.proof_note().map(|value| value.to_string())
}

/// The Merkle root committed to by the proof.
pub fn merkle_root(&self) -> Vec<u8> {
self.inner.merkle_root().to_byte_array().to_vec()
}

/// The raw TLV bytes of the proof.
pub fn bytes(&self) -> Vec<u8> {
self.inner.bytes().to_vec()
}

/// The bech32-encoded string form of the proof.
pub fn as_string(&self) -> String {
self.inner.to_string()
}
}

impl From<LdkPayerProof> for PayerProof {
fn from(inner: LdkPayerProof) -> Self {
Self { inner }
}
}

impl Deref for PayerProof {
type Target = LdkPayerProof;

fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl std::fmt::Display for PayerProof {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner)
}
}

uniffi::custom_type!(OfferId, String, {
remote,
try_lift: |val| {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Add BOLT 12 payer proof support by vincenzopalazzo · Pull Request #845 · lightningdevkit/ldk-node · GitHub
Skip to content
Closed
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,11 @@
`ChannelTypeFeatures`.
- `Config::anchor_channels_config` is no longer optional, hence anchor channels can no longer be
disabled. We still negotiate legacy channels if the peer does not support anchor channels.
- The paid BOLT 12 invoice is now persisted on `PaymentKind::Bolt12Offer` and
`PaymentKind::Bolt12Refund`, and `Bolt12Payment::create_payer_proof` allows building a BOLT 12
payer proof for a previously succeeded outbound BOLT 12 payment. `PayerProofOptions` controls
which optional invoice fields are selectively disclosed. Payments that completed via a static
invoice, i.e., async payments, do not support payer proofs. (#845)

## Bug Fixes and Improvements
- Building a fresh node against a Bitcoin Core RPC or REST chain source that fails to return the
Expand Down
3 changes: 3 additions & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,8 @@ enum NodeError {
"FeerateEstimationUpdateTimeout",
"WalletOperationFailed",
"WalletOperationTimeout",
"PayerProofCreationFailed",
"PayerProofUnavailable",
"OnchainTxSigningFailed",
"TxSyncFailed",
"TxSyncTimeout",
Expand DownExpand Up@@ -247,6 +249,7 @@ enum NodeError {
"LnurlAuthTimeout",
"InvalidLnurl",
"ChainSourceNotSupported",
"InvalidPayerProof",
};

typedef dictionary NodeStatus;
Expand Down
11 changes: 11 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,10 @@ pub enum Error {
WalletOperationFailed,
/// A wallet operation timed out.
WalletOperationTimeout,
/// Creating a payer proof failed.
PayerProofCreationFailed,
/// A payer proof is unavailable for the requested payment.
PayerProofUnavailable,
/// A signing operation for transaction failed.
OnchainTxSigningFailed,
/// A transaction sync operation failed.
Expand DownExpand Up@@ -139,6 +143,8 @@ pub enum Error {
InvalidLnurl,
/// The configured chain source is not supported.
ChainSourceNotSupported,
/// The provided payer proof is invalid.
InvalidPayerProof,
}

impl fmt::Display for Error {
Expand DownExpand Up@@ -170,6 +176,10 @@ impl fmt::Display for Error {
},
Self::WalletOperationFailed => write!(f, "Failed to conduct wallet operation."),
Self::WalletOperationTimeout => write!(f, "A wallet operation timed out."),
Self::PayerProofCreationFailed => write!(f, "Failed to create payer proof."),
Self::PayerProofUnavailable => {
write!(f, "A payer proof is unavailable for the requested payment.")
},
Self::OnchainTxSigningFailed => write!(f, "Failed to sign given transaction."),
Self::TxSyncFailed => write!(f, "Failed to sync transactions."),
Self::TxSyncTimeout => write!(f, "Syncing transactions timed out."),
Expand DownExpand Up@@ -227,6 +237,7 @@ impl fmt::Display for Error {
Self::ChainSourceNotSupported => {
write!(f, "The configured chain source is not supported.")
},
Self::InvalidPayerProof => write!(f, "The provided payer proof is invalid."),
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/event.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1129,6 +1129,7 @@ where
offer_id,
payer_note,
quantity,
bolt12_invoice: None,
};

let payment = PaymentDetails::new(
Expand DownExpand Up@@ -1178,6 +1179,7 @@ where
secret: Some(payment_secret),
payer_note: None,
quantity: None,
bolt12_invoice: None,
};

let payment = PaymentDetails::new(
Expand DownExpand Up@@ -1331,6 +1333,7 @@ where
offer_id: payment_context.offer_id,
payer_note: payment_context.invoice_request.payer_note_truncated,
quantity: payment_context.invoice_request.quantity,
bolt12_invoice: None,
};
let update = PaymentDetailsUpdate {
preimage: Some(payment_preimage),
Expand All@@ -1352,6 +1355,7 @@ where
secret: Some(payment_secret),
payer_note: None,
quantity: None,
bolt12_invoice: None,
};
let update = PaymentDetailsUpdate {
preimage: Some(payment_preimage),
Expand DownExpand Up@@ -1442,12 +1446,16 @@ where
debug_assert!(false, "payment_id should always be set.");
return Ok(());
};
let bolt12_invoice = bolt12_invoice.map(Into::into);

// Only set the field if the event actually carried an invoice, to avoid
// overriding any previously-stored invoice with `None`.
let update = PaymentDetailsUpdate {
hash: Some(Some(payment_hash)),
preimage: Some(Some(payment_preimage)),
fee_paid_msat: Some(fee_paid_msat),
status: Some(PaymentStatus::Succeeded),
bolt12_invoice: bolt12_invoice.clone().map(Some),
..PaymentDetailsUpdate::new(payment_id)
};

Expand DownExpand Up@@ -1480,7 +1488,7 @@ where
payment_hash,
payment_preimage: Some(payment_preimage),
fee_paid_msat,
bolt12_invoice: bolt12_invoice.map(Into::into),
bolt12_invoice,
};

match self.event_queue.add_event(event).await {
Expand Down
111 changes: 110 additions & 1 deletion src/ffi/types.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,6 @@ use bitcoin::hashes::Hash;
use bitcoin::secp256k1::PublicKey;
pub use bitcoin::{Address, BlockHash, Network, OutPoint, ScriptBuf, Txid};
pub use lightning::chain::channelmonitor::BalanceSource;
use lightning::events::PaidBolt12Invoice as LdkPaidBolt12Invoice;
pub use lightning::events::{ClosureReason, PaymentFailureReason};
use lightning::ln::channel_state::{ChannelShutdownState, CounterpartyForwardingInfo};
use lightning::ln::channelmanager::PaymentId;
Expand All@@ -32,6 +31,9 @@ pub use lightning::ln::types::ChannelId;
use lightning::offers::invoice::Bolt12Invoice as LdkBolt12Invoice;
pub use lightning::offers::offer::OfferId;
use lightning::offers::offer::{Amount as LdkAmount, Offer as LdkOffer};
use lightning::offers::payer_proof::{
PaidBolt12Invoice as LdkPaidBolt12Invoice, PayerProof as LdkPayerProof,
};
use lightning::offers::refund::Refund as LdkRefund;
use lightning::offers::static_invoice::StaticInvoice as LdkStaticInvoice;
use lightning::onion_message::dns_resolution::HumanReadableName as LdkHumanReadableName;
Expand DownExpand Up@@ -881,6 +883,113 @@ impl Readable for PaidBolt12Invoice {
}
}

/// A cryptographic proof that a BOLT12 invoice was paid by this node.
#[derive(Debug, Clone, uniffi::Object)]
#[uniffi::export(Debug, Display)]
pub struct PayerProof {
pub(crate) inner: LdkPayerProof,
}

#[uniffi::export]
impl PayerProof {
#[uniffi::constructor]
pub fn from_bytes(proof_bytes: Vec<u8>) -> Result<Self, Error> {
let inner = LdkPayerProof::try_from(proof_bytes).map_err(|_| Error::InvalidPayerProof)?;
Ok(Self { inner })
}

/// The payment preimage proving the payment completed.
pub fn payment_preimage(&self) -> PaymentPreimage {
self.inner.payment_preimage()
}

/// The payment hash committed to by the invoice and proven by the preimage.
pub fn payment_hash(&self) -> PaymentHash {
self.inner.payment_hash()
}

/// The public key of the payer that authorized the payment.
pub fn payer_signing_pubkey(&self) -> PublicKey {
self.inner.payer_signing_pubkey()
}

/// The issuer signing public key committed to by the invoice.
pub fn issuer_signing_pubkey(&self) -> PublicKey {
self.inner.issuer_signing_pubkey()
}

/// The invoice signature bytes.
pub fn invoice_signature(&self) -> Vec<u8> {
self.inner.invoice_signature().as_ref().to_vec()
}

/// The proof signature bytes.
pub fn proof_signature(&self) -> Vec<u8> {
self.inner.proof_signature().as_ref().to_vec()
}

/// The offer description, if it was disclosed in the proof.
pub fn offer_description(&self) -> Option<String> {
self.inner.offer_description().map(|value| value.to_string())
}

/// The offer issuer, if it was disclosed in the proof.
pub fn offer_issuer(&self) -> Option<String> {
self.inner.offer_issuer().map(|value| value.to_string())
}

/// The invoice amount in millisatoshis, if it was disclosed in the proof.
pub fn invoice_amount_msats(&self) -> Option<u64> {
self.inner.invoice_amount_msats()
}

/// The invoice creation time, in seconds since the UNIX epoch, if it was disclosed in the
/// proof.
pub fn invoice_created_at(&self) -> Option<u64> {
self.inner.invoice_created_at().map(|value| value.as_secs())
}

/// The optional note attached to the proof.
pub fn proof_note(&self) -> Option<String> {
self.inner.proof_note().map(|value| value.to_string())
}

/// The Merkle root committed to by the proof.
pub fn merkle_root(&self) -> Vec<u8> {
self.inner.merkle_root().to_byte_array().to_vec()
}

/// The raw TLV bytes of the proof.
pub fn bytes(&self) -> Vec<u8> {
self.inner.bytes().to_vec()
}

/// The bech32-encoded string form of the proof.
pub fn as_string(&self) -> String {
self.inner.to_string()
}
}

impl From<LdkPayerProof> for PayerProof {
fn from(inner: LdkPayerProof) -> Self {
Self { inner }
}
}

impl Deref for PayerProof {
type Target = LdkPayerProof;

fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl std::fmt::Display for PayerProof {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner)
}
}

uniffi::custom_type!(OfferId, String, {
remote,
try_lift: |val| {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add BOLT 12 payer proof support by vincenzopalazzo · Pull Request #845 · lightningdevkit/ldk-node · GitHub
Skip to content
Closed
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,11 @@
`ChannelTypeFeatures`.
- `Config::anchor_channels_config` is no longer optional, hence anchor channels can no longer be
disabled. We still negotiate legacy channels if the peer does not support anchor channels.
- The paid BOLT 12 invoice is now persisted on `PaymentKind::Bolt12Offer` and
`PaymentKind::Bolt12Refund`, and `Bolt12Payment::create_payer_proof` allows building a BOLT 12
payer proof for a previously succeeded outbound BOLT 12 payment. `PayerProofOptions` controls
which optional invoice fields are selectively disclosed. Payments that completed via a static
invoice, i.e., async payments, do not support payer proofs. (#845)

## Bug Fixes and Improvements
- Building a fresh node against a Bitcoin Core RPC or REST chain source that fails to return the
Expand Down
3 changes: 3 additions & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,8 @@ enum NodeError {
"FeerateEstimationUpdateTimeout",
"WalletOperationFailed",
"WalletOperationTimeout",
"PayerProofCreationFailed",
"PayerProofUnavailable",
"OnchainTxSigningFailed",
"TxSyncFailed",
"TxSyncTimeout",
Expand DownExpand Up@@ -247,6 +249,7 @@ enum NodeError {
"LnurlAuthTimeout",
"InvalidLnurl",
"ChainSourceNotSupported",
"InvalidPayerProof",
};

typedef dictionary NodeStatus;
Expand Down
11 changes: 11 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,10 @@ pub enum Error {
WalletOperationFailed,
/// A wallet operation timed out.
WalletOperationTimeout,
/// Creating a payer proof failed.
PayerProofCreationFailed,
/// A payer proof is unavailable for the requested payment.
PayerProofUnavailable,
/// A signing operation for transaction failed.
OnchainTxSigningFailed,
/// A transaction sync operation failed.
Expand DownExpand Up@@ -139,6 +143,8 @@ pub enum Error {
InvalidLnurl,
/// The configured chain source is not supported.
ChainSourceNotSupported,
/// The provided payer proof is invalid.
InvalidPayerProof,
}

impl fmt::Display for Error {
Expand DownExpand Up@@ -170,6 +176,10 @@ impl fmt::Display for Error {
},
Self::WalletOperationFailed => write!(f, "Failed to conduct wallet operation."),
Self::WalletOperationTimeout => write!(f, "A wallet operation timed out."),
Self::PayerProofCreationFailed => write!(f, "Failed to create payer proof."),
Self::PayerProofUnavailable => {
write!(f, "A payer proof is unavailable for the requested payment.")
},
Self::OnchainTxSigningFailed => write!(f, "Failed to sign given transaction."),
Self::TxSyncFailed => write!(f, "Failed to sync transactions."),
Self::TxSyncTimeout => write!(f, "Syncing transactions timed out."),
Expand DownExpand Up@@ -227,6 +237,7 @@ impl fmt::Display for Error {
Self::ChainSourceNotSupported => {
write!(f, "The configured chain source is not supported.")
},
Self::InvalidPayerProof => write!(f, "The provided payer proof is invalid."),
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/event.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1129,6 +1129,7 @@ where
offer_id,
payer_note,
quantity,
bolt12_invoice: None,
};

let payment = PaymentDetails::new(
Expand DownExpand Up@@ -1178,6 +1179,7 @@ where
secret: Some(payment_secret),
payer_note: None,
quantity: None,
bolt12_invoice: None,
};

let payment = PaymentDetails::new(
Expand DownExpand Up@@ -1331,6 +1333,7 @@ where
offer_id: payment_context.offer_id,
payer_note: payment_context.invoice_request.payer_note_truncated,
quantity: payment_context.invoice_request.quantity,
bolt12_invoice: None,
};
let update = PaymentDetailsUpdate {
preimage: Some(payment_preimage),
Expand All@@ -1352,6 +1355,7 @@ where
secret: Some(payment_secret),
payer_note: None,
quantity: None,
bolt12_invoice: None,
};
let update = PaymentDetailsUpdate {
preimage: Some(payment_preimage),
Expand DownExpand Up@@ -1442,12 +1446,16 @@ where
debug_assert!(false, "payment_id should always be set.");
return Ok(());
};
let bolt12_invoice = bolt12_invoice.map(Into::into);

// Only set the field if the event actually carried an invoice, to avoid
// overriding any previously-stored invoice with `None`.
let update = PaymentDetailsUpdate {
hash: Some(Some(payment_hash)),
preimage: Some(Some(payment_preimage)),
fee_paid_msat: Some(fee_paid_msat),
status: Some(PaymentStatus::Succeeded),
bolt12_invoice: bolt12_invoice.clone().map(Some),
..PaymentDetailsUpdate::new(payment_id)
};

Expand DownExpand Up@@ -1480,7 +1488,7 @@ where
payment_hash,
payment_preimage: Some(payment_preimage),
fee_paid_msat,
bolt12_invoice: bolt12_invoice.map(Into::into),
bolt12_invoice,
};

match self.event_queue.add_event(event).await {
Expand Down
111 changes: 110 additions & 1 deletion src/ffi/types.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,6 @@ use bitcoin::hashes::Hash;
use bitcoin::secp256k1::PublicKey;
pub use bitcoin::{Address, BlockHash, Network, OutPoint, ScriptBuf, Txid};
pub use lightning::chain::channelmonitor::BalanceSource;
use lightning::events::PaidBolt12Invoice as LdkPaidBolt12Invoice;
pub use lightning::events::{ClosureReason, PaymentFailureReason};
use lightning::ln::channel_state::{ChannelShutdownState, CounterpartyForwardingInfo};
use lightning::ln::channelmanager::PaymentId;
Expand All@@ -32,6 +31,9 @@ pub use lightning::ln::types::ChannelId;
use lightning::offers::invoice::Bolt12Invoice as LdkBolt12Invoice;
pub use lightning::offers::offer::OfferId;
use lightning::offers::offer::{Amount as LdkAmount, Offer as LdkOffer};
use lightning::offers::payer_proof::{
PaidBolt12Invoice as LdkPaidBolt12Invoice, PayerProof as LdkPayerProof,
};
use lightning::offers::refund::Refund as LdkRefund;
use lightning::offers::static_invoice::StaticInvoice as LdkStaticInvoice;
use lightning::onion_message::dns_resolution::HumanReadableName as LdkHumanReadableName;
Expand DownExpand Up@@ -881,6 +883,113 @@ impl Readable for PaidBolt12Invoice {
}
}

/// A cryptographic proof that a BOLT12 invoice was paid by this node.
#[derive(Debug, Clone, uniffi::Object)]
#[uniffi::export(Debug, Display)]
pub struct PayerProof {
pub(crate) inner: LdkPayerProof,
}

#[uniffi::export]
impl PayerProof {
#[uniffi::constructor]
pub fn from_bytes(proof_bytes: Vec<u8>) -> Result<Self, Error> {
let inner = LdkPayerProof::try_from(proof_bytes).map_err(|_| Error::InvalidPayerProof)?;
Ok(Self { inner })
}

/// The payment preimage proving the payment completed.
pub fn payment_preimage(&self) -> PaymentPreimage {
self.inner.payment_preimage()
}

/// The payment hash committed to by the invoice and proven by the preimage.
pub fn payment_hash(&self) -> PaymentHash {
self.inner.payment_hash()
}

/// The public key of the payer that authorized the payment.
pub fn payer_signing_pubkey(&self) -> PublicKey {
self.inner.payer_signing_pubkey()
}

/// The issuer signing public key committed to by the invoice.
pub fn issuer_signing_pubkey(&self) -> PublicKey {
self.inner.issuer_signing_pubkey()
}

/// The invoice signature bytes.
pub fn invoice_signature(&self) -> Vec<u8> {
self.inner.invoice_signature().as_ref().to_vec()
}

/// The proof signature bytes.
pub fn proof_signature(&self) -> Vec<u8> {
self.inner.proof_signature().as_ref().to_vec()
}

/// The offer description, if it was disclosed in the proof.
pub fn offer_description(&self) -> Option<String> {
self.inner.offer_description().map(|value| value.to_string())
}

/// The offer issuer, if it was disclosed in the proof.
pub fn offer_issuer(&self) -> Option<String> {
self.inner.offer_issuer().map(|value| value.to_string())
}

/// The invoice amount in millisatoshis, if it was disclosed in the proof.
pub fn invoice_amount_msats(&self) -> Option<u64> {
self.inner.invoice_amount_msats()
}

/// The invoice creation time, in seconds since the UNIX epoch, if it was disclosed in the
/// proof.
pub fn invoice_created_at(&self) -> Option<u64> {
self.inner.invoice_created_at().map(|value| value.as_secs())
}

/// The optional note attached to the proof.
pub fn proof_note(&self) -> Option<String> {
self.inner.proof_note().map(|value| value.to_string())
}

/// The Merkle root committed to by the proof.
pub fn merkle_root(&self) -> Vec<u8> {
self.inner.merkle_root().to_byte_array().to_vec()
}

/// The raw TLV bytes of the proof.
pub fn bytes(&self) -> Vec<u8> {
self.inner.bytes().to_vec()
}

/// The bech32-encoded string form of the proof.
pub fn as_string(&self) -> String {
self.inner.to_string()
}
}

impl From<LdkPayerProof> for PayerProof {
fn from(inner: LdkPayerProof) -> Self {
Self { inner }
}
}

impl Deref for PayerProof {
type Target = LdkPayerProof;

fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl std::fmt::Display for PayerProof {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner)
}
}

uniffi::custom_type!(OfferId, String, {
remote,
try_lift: |val| {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add BOLT 12 payer proof support by vincenzopalazzo · Pull Request #845 · lightningdevkit/ldk-node · GitHub
Skip to content
Closed
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,11 @@
`ChannelTypeFeatures`.
- `Config::anchor_channels_config` is no longer optional, hence anchor channels can no longer be
disabled. We still negotiate legacy channels if the peer does not support anchor channels.
- The paid BOLT 12 invoice is now persisted on `PaymentKind::Bolt12Offer` and
`PaymentKind::Bolt12Refund`, and `Bolt12Payment::create_payer_proof` allows building a BOLT 12
payer proof for a previously succeeded outbound BOLT 12 payment. `PayerProofOptions` controls
which optional invoice fields are selectively disclosed. Payments that completed via a static
invoice, i.e., async payments, do not support payer proofs. (#845)

## Bug Fixes and Improvements
- Building a fresh node against a Bitcoin Core RPC or REST chain source that fails to return the
Expand Down
3 changes: 3 additions & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,8 @@ enum NodeError {
"FeerateEstimationUpdateTimeout",
"WalletOperationFailed",
"WalletOperationTimeout",
"PayerProofCreationFailed",
"PayerProofUnavailable",
"OnchainTxSigningFailed",
"TxSyncFailed",
"TxSyncTimeout",
Expand DownExpand Up@@ -247,6 +249,7 @@ enum NodeError {
"LnurlAuthTimeout",
"InvalidLnurl",
"ChainSourceNotSupported",
"InvalidPayerProof",
};

typedef dictionary NodeStatus;
Expand Down
11 changes: 11 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,10 @@ pub enum Error {
WalletOperationFailed,
/// A wallet operation timed out.
WalletOperationTimeout,
/// Creating a payer proof failed.
PayerProofCreationFailed,
/// A payer proof is unavailable for the requested payment.
PayerProofUnavailable,
/// A signing operation for transaction failed.
OnchainTxSigningFailed,
/// A transaction sync operation failed.
Expand DownExpand Up@@ -139,6 +143,8 @@ pub enum Error {
InvalidLnurl,
/// The configured chain source is not supported.
ChainSourceNotSupported,
/// The provided payer proof is invalid.
InvalidPayerProof,
}

impl fmt::Display for Error {
Expand DownExpand Up@@ -170,6 +176,10 @@ impl fmt::Display for Error {
},
Self::WalletOperationFailed => write!(f, "Failed to conduct wallet operation."),
Self::WalletOperationTimeout => write!(f, "A wallet operation timed out."),
Self::PayerProofCreationFailed => write!(f, "Failed to create payer proof."),
Self::PayerProofUnavailable => {
write!(f, "A payer proof is unavailable for the requested payment.")
},
Self::OnchainTxSigningFailed => write!(f, "Failed to sign given transaction."),
Self::TxSyncFailed => write!(f, "Failed to sync transactions."),
Self::TxSyncTimeout => write!(f, "Syncing transactions timed out."),
Expand DownExpand Up@@ -227,6 +237,7 @@ impl fmt::Display for Error {
Self::ChainSourceNotSupported => {
write!(f, "The configured chain source is not supported.")
},
Self::InvalidPayerProof => write!(f, "The provided payer proof is invalid."),
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/event.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1129,6 +1129,7 @@ where
offer_id,
payer_note,
quantity,
bolt12_invoice: None,
};

let payment = PaymentDetails::new(
Expand DownExpand Up@@ -1178,6 +1179,7 @@ where
secret: Some(payment_secret),
payer_note: None,
quantity: None,
bolt12_invoice: None,
};

let payment = PaymentDetails::new(
Expand DownExpand Up@@ -1331,6 +1333,7 @@ where
offer_id: payment_context.offer_id,
payer_note: payment_context.invoice_request.payer_note_truncated,
quantity: payment_context.invoice_request.quantity,
bolt12_invoice: None,
};
let update = PaymentDetailsUpdate {
preimage: Some(payment_preimage),
Expand All@@ -1352,6 +1355,7 @@ where
secret: Some(payment_secret),
payer_note: None,
quantity: None,
bolt12_invoice: None,
};
let update = PaymentDetailsUpdate {
preimage: Some(payment_preimage),
Expand DownExpand Up@@ -1442,12 +1446,16 @@ where
debug_assert!(false, "payment_id should always be set.");
return Ok(());
};
let bolt12_invoice = bolt12_invoice.map(Into::into);

// Only set the field if the event actually carried an invoice, to avoid
// overriding any previously-stored invoice with `None`.
let update = PaymentDetailsUpdate {
hash: Some(Some(payment_hash)),
preimage: Some(Some(payment_preimage)),
fee_paid_msat: Some(fee_paid_msat),
status: Some(PaymentStatus::Succeeded),
bolt12_invoice: bolt12_invoice.clone().map(Some),
..PaymentDetailsUpdate::new(payment_id)
};

Expand DownExpand Up@@ -1480,7 +1488,7 @@ where
payment_hash,
payment_preimage: Some(payment_preimage),
fee_paid_msat,
bolt12_invoice: bolt12_invoice.map(Into::into),
bolt12_invoice,
};

match self.event_queue.add_event(event).await {
Expand Down
111 changes: 110 additions & 1 deletion src/ffi/types.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,6 @@ use bitcoin::hashes::Hash;
use bitcoin::secp256k1::PublicKey;
pub use bitcoin::{Address, BlockHash, Network, OutPoint, ScriptBuf, Txid};
pub use lightning::chain::channelmonitor::BalanceSource;
use lightning::events::PaidBolt12Invoice as LdkPaidBolt12Invoice;
pub use lightning::events::{ClosureReason, PaymentFailureReason};
use lightning::ln::channel_state::{ChannelShutdownState, CounterpartyForwardingInfo};
use lightning::ln::channelmanager::PaymentId;
Expand All@@ -32,6 +31,9 @@ pub use lightning::ln::types::ChannelId;
use lightning::offers::invoice::Bolt12Invoice as LdkBolt12Invoice;
pub use lightning::offers::offer::OfferId;
use lightning::offers::offer::{Amount as LdkAmount, Offer as LdkOffer};
use lightning::offers::payer_proof::{
PaidBolt12Invoice as LdkPaidBolt12Invoice, PayerProof as LdkPayerProof,
};
use lightning::offers::refund::Refund as LdkRefund;
use lightning::offers::static_invoice::StaticInvoice as LdkStaticInvoice;
use lightning::onion_message::dns_resolution::HumanReadableName as LdkHumanReadableName;
Expand DownExpand Up@@ -881,6 +883,113 @@ impl Readable for PaidBolt12Invoice {
}
}

/// A cryptographic proof that a BOLT12 invoice was paid by this node.
#[derive(Debug, Clone, uniffi::Object)]
#[uniffi::export(Debug, Display)]
pub struct PayerProof {
pub(crate) inner: LdkPayerProof,
}

#[uniffi::export]
impl PayerProof {
#[uniffi::constructor]
pub fn from_bytes(proof_bytes: Vec<u8>) -> Result<Self, Error> {
let inner = LdkPayerProof::try_from(proof_bytes).map_err(|_| Error::InvalidPayerProof)?;
Ok(Self { inner })
}

/// The payment preimage proving the payment completed.
pub fn payment_preimage(&self) -> PaymentPreimage {
self.inner.payment_preimage()
}

/// The payment hash committed to by the invoice and proven by the preimage.
pub fn payment_hash(&self) -> PaymentHash {
self.inner.payment_hash()
}

/// The public key of the payer that authorized the payment.
pub fn payer_signing_pubkey(&self) -> PublicKey {
self.inner.payer_signing_pubkey()
}

/// The issuer signing public key committed to by the invoice.
pub fn issuer_signing_pubkey(&self) -> PublicKey {
self.inner.issuer_signing_pubkey()
}

/// The invoice signature bytes.
pub fn invoice_signature(&self) -> Vec<u8> {
self.inner.invoice_signature().as_ref().to_vec()
}

/// The proof signature bytes.
pub fn proof_signature(&self) -> Vec<u8> {
self.inner.proof_signature().as_ref().to_vec()
}

/// The offer description, if it was disclosed in the proof.
pub fn offer_description(&self) -> Option<String> {
self.inner.offer_description().map(|value| value.to_string())
}

/// The offer issuer, if it was disclosed in the proof.
pub fn offer_issuer(&self) -> Option<String> {
self.inner.offer_issuer().map(|value| value.to_string())
}

/// The invoice amount in millisatoshis, if it was disclosed in the proof.
pub fn invoice_amount_msats(&self) -> Option<u64> {
self.inner.invoice_amount_msats()
}

/// The invoice creation time, in seconds since the UNIX epoch, if it was disclosed in the
/// proof.
pub fn invoice_created_at(&self) -> Option<u64> {
self.inner.invoice_created_at().map(|value| value.as_secs())
}

/// The optional note attached to the proof.
pub fn proof_note(&self) -> Option<String> {
self.inner.proof_note().map(|value| value.to_string())
}

/// The Merkle root committed to by the proof.
pub fn merkle_root(&self) -> Vec<u8> {
self.inner.merkle_root().to_byte_array().to_vec()
}

/// The raw TLV bytes of the proof.
pub fn bytes(&self) -> Vec<u8> {
self.inner.bytes().to_vec()
}

/// The bech32-encoded string form of the proof.
pub fn as_string(&self) -> String {
self.inner.to_string()
}
}

impl From<LdkPayerProof> for PayerProof {
fn from(inner: LdkPayerProof) -> Self {
Self { inner }
}
}

impl Deref for PayerProof {
type Target = LdkPayerProof;

fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl std::fmt::Display for PayerProof {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner)
}
}

uniffi::custom_type!(OfferId, String, {
remote,
try_lift: |val| {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Add BOLT 12 payer proof support by vincenzopalazzo · Pull Request #845 · lightningdevkit/ldk-node · GitHub
Skip to content
Closed
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,11 @@
`ChannelTypeFeatures`.
- `Config::anchor_channels_config` is no longer optional, hence anchor channels can no longer be
disabled. We still negotiate legacy channels if the peer does not support anchor channels.
- The paid BOLT 12 invoice is now persisted on `PaymentKind::Bolt12Offer` and
`PaymentKind::Bolt12Refund`, and `Bolt12Payment::create_payer_proof` allows building a BOLT 12
payer proof for a previously succeeded outbound BOLT 12 payment. `PayerProofOptions` controls
which optional invoice fields are selectively disclosed. Payments that completed via a static
invoice, i.e., async payments, do not support payer proofs. (#845)

## Bug Fixes and Improvements
- Building a fresh node against a Bitcoin Core RPC or REST chain source that fails to return the
Expand Down
3 changes: 3 additions & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,8 @@ enum NodeError {
"FeerateEstimationUpdateTimeout",
"WalletOperationFailed",
"WalletOperationTimeout",
"PayerProofCreationFailed",
"PayerProofUnavailable",
"OnchainTxSigningFailed",
"TxSyncFailed",
"TxSyncTimeout",
Expand DownExpand Up@@ -247,6 +249,7 @@ enum NodeError {
"LnurlAuthTimeout",
"InvalidLnurl",
"ChainSourceNotSupported",
"InvalidPayerProof",
};

typedef dictionary NodeStatus;
Expand Down
11 changes: 11 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,10 @@ pub enum Error {
WalletOperationFailed,
/// A wallet operation timed out.
WalletOperationTimeout,
/// Creating a payer proof failed.
PayerProofCreationFailed,
/// A payer proof is unavailable for the requested payment.
PayerProofUnavailable,
/// A signing operation for transaction failed.
OnchainTxSigningFailed,
/// A transaction sync operation failed.
Expand DownExpand Up@@ -139,6 +143,8 @@ pub enum Error {
InvalidLnurl,
/// The configured chain source is not supported.
ChainSourceNotSupported,
/// The provided payer proof is invalid.
InvalidPayerProof,
}

impl fmt::Display for Error {
Expand DownExpand Up@@ -170,6 +176,10 @@ impl fmt::Display for Error {
},
Self::WalletOperationFailed => write!(f, "Failed to conduct wallet operation."),
Self::WalletOperationTimeout => write!(f, "A wallet operation timed out."),
Self::PayerProofCreationFailed => write!(f, "Failed to create payer proof."),
Self::PayerProofUnavailable => {
write!(f, "A payer proof is unavailable for the requested payment.")
},
Self::OnchainTxSigningFailed => write!(f, "Failed to sign given transaction."),
Self::TxSyncFailed => write!(f, "Failed to sync transactions."),
Self::TxSyncTimeout => write!(f, "Syncing transactions timed out."),
Expand DownExpand Up@@ -227,6 +237,7 @@ impl fmt::Display for Error {
Self::ChainSourceNotSupported => {
write!(f, "The configured chain source is not supported.")
},
Self::InvalidPayerProof => write!(f, "The provided payer proof is invalid."),
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/event.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1129,6 +1129,7 @@ where
offer_id,
payer_note,
quantity,
bolt12_invoice: None,
};

let payment = PaymentDetails::new(
Expand DownExpand Up@@ -1178,6 +1179,7 @@ where
secret: Some(payment_secret),
payer_note: None,
quantity: None,
bolt12_invoice: None,
};

let payment = PaymentDetails::new(
Expand DownExpand Up@@ -1331,6 +1333,7 @@ where
offer_id: payment_context.offer_id,
payer_note: payment_context.invoice_request.payer_note_truncated,
quantity: payment_context.invoice_request.quantity,
bolt12_invoice: None,
};
let update = PaymentDetailsUpdate {
preimage: Some(payment_preimage),
Expand All@@ -1352,6 +1355,7 @@ where
secret: Some(payment_secret),
payer_note: None,
quantity: None,
bolt12_invoice: None,
};
let update = PaymentDetailsUpdate {
preimage: Some(payment_preimage),
Expand DownExpand Up@@ -1442,12 +1446,16 @@ where
debug_assert!(false, "payment_id should always be set.");
return Ok(());
};
let bolt12_invoice = bolt12_invoice.map(Into::into);

// Only set the field if the event actually carried an invoice, to avoid
// overriding any previously-stored invoice with `None`.
let update = PaymentDetailsUpdate {
hash: Some(Some(payment_hash)),
preimage: Some(Some(payment_preimage)),
fee_paid_msat: Some(fee_paid_msat),
status: Some(PaymentStatus::Succeeded),
bolt12_invoice: bolt12_invoice.clone().map(Some),
..PaymentDetailsUpdate::new(payment_id)
};

Expand DownExpand Up@@ -1480,7 +1488,7 @@ where
payment_hash,
payment_preimage: Some(payment_preimage),
fee_paid_msat,
bolt12_invoice: bolt12_invoice.map(Into::into),
bolt12_invoice,
};

match self.event_queue.add_event(event).await {
Expand Down
111 changes: 110 additions & 1 deletion src/ffi/types.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,6 @@ use bitcoin::hashes::Hash;
use bitcoin::secp256k1::PublicKey;
pub use bitcoin::{Address, BlockHash, Network, OutPoint, ScriptBuf, Txid};
pub use lightning::chain::channelmonitor::BalanceSource;
use lightning::events::PaidBolt12Invoice as LdkPaidBolt12Invoice;
pub use lightning::events::{ClosureReason, PaymentFailureReason};
use lightning::ln::channel_state::{ChannelShutdownState, CounterpartyForwardingInfo};
use lightning::ln::channelmanager::PaymentId;
Expand All@@ -32,6 +31,9 @@ pub use lightning::ln::types::ChannelId;
use lightning::offers::invoice::Bolt12Invoice as LdkBolt12Invoice;
pub use lightning::offers::offer::OfferId;
use lightning::offers::offer::{Amount as LdkAmount, Offer as LdkOffer};
use lightning::offers::payer_proof::{
PaidBolt12Invoice as LdkPaidBolt12Invoice, PayerProof as LdkPayerProof,
};
use lightning::offers::refund::Refund as LdkRefund;
use lightning::offers::static_invoice::StaticInvoice as LdkStaticInvoice;
use lightning::onion_message::dns_resolution::HumanReadableName as LdkHumanReadableName;
Expand DownExpand Up@@ -881,6 +883,113 @@ impl Readable for PaidBolt12Invoice {
}
}

/// A cryptographic proof that a BOLT12 invoice was paid by this node.
#[derive(Debug, Clone, uniffi::Object)]
#[uniffi::export(Debug, Display)]
pub struct PayerProof {
pub(crate) inner: LdkPayerProof,
}

#[uniffi::export]
impl PayerProof {
#[uniffi::constructor]
pub fn from_bytes(proof_bytes: Vec<u8>) -> Result<Self, Error> {
let inner = LdkPayerProof::try_from(proof_bytes).map_err(|_| Error::InvalidPayerProof)?;
Ok(Self { inner })
}

/// The payment preimage proving the payment completed.
pub fn payment_preimage(&self) -> PaymentPreimage {
self.inner.payment_preimage()
}

/// The payment hash committed to by the invoice and proven by the preimage.
pub fn payment_hash(&self) -> PaymentHash {
self.inner.payment_hash()
}

/// The public key of the payer that authorized the payment.
pub fn payer_signing_pubkey(&self) -> PublicKey {
self.inner.payer_signing_pubkey()
}

/// The issuer signing public key committed to by the invoice.
pub fn issuer_signing_pubkey(&self) -> PublicKey {
self.inner.issuer_signing_pubkey()
}

/// The invoice signature bytes.
pub fn invoice_signature(&self) -> Vec<u8> {
self.inner.invoice_signature().as_ref().to_vec()
}

/// The proof signature bytes.
pub fn proof_signature(&self) -> Vec<u8> {
self.inner.proof_signature().as_ref().to_vec()
}

/// The offer description, if it was disclosed in the proof.
pub fn offer_description(&self) -> Option<String> {
self.inner.offer_description().map(|value| value.to_string())
}

/// The offer issuer, if it was disclosed in the proof.
pub fn offer_issuer(&self) -> Option<String> {
self.inner.offer_issuer().map(|value| value.to_string())
}

/// The invoice amount in millisatoshis, if it was disclosed in the proof.
pub fn invoice_amount_msats(&self) -> Option<u64> {
self.inner.invoice_amount_msats()
}

/// The invoice creation time, in seconds since the UNIX epoch, if it was disclosed in the
/// proof.
pub fn invoice_created_at(&self) -> Option<u64> {
self.inner.invoice_created_at().map(|value| value.as_secs())
}

/// The optional note attached to the proof.
pub fn proof_note(&self) -> Option<String> {
self.inner.proof_note().map(|value| value.to_string())
}

/// The Merkle root committed to by the proof.
pub fn merkle_root(&self) -> Vec<u8> {
self.inner.merkle_root().to_byte_array().to_vec()
}

/// The raw TLV bytes of the proof.
pub fn bytes(&self) -> Vec<u8> {
self.inner.bytes().to_vec()
}

/// The bech32-encoded string form of the proof.
pub fn as_string(&self) -> String {
self.inner.to_string()
}
}

impl From<LdkPayerProof> for PayerProof {
fn from(inner: LdkPayerProof) -> Self {
Self { inner }
}
}

impl Deref for PayerProof {
type Target = LdkPayerProof;

fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl std::fmt::Display for PayerProof {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner)
}
}

uniffi::custom_type!(OfferId, String, {
remote,
try_lift: |val| {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add BOLT 12 payer proof support by vincenzopalazzo · Pull Request #845 · lightningdevkit/ldk-node · GitHub
Skip to content
Closed
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,11 @@
`ChannelTypeFeatures`.
- `Config::anchor_channels_config` is no longer optional, hence anchor channels can no longer be
disabled. We still negotiate legacy channels if the peer does not support anchor channels.
- The paid BOLT 12 invoice is now persisted on `PaymentKind::Bolt12Offer` and
`PaymentKind::Bolt12Refund`, and `Bolt12Payment::create_payer_proof` allows building a BOLT 12
payer proof for a previously succeeded outbound BOLT 12 payment. `PayerProofOptions` controls
which optional invoice fields are selectively disclosed. Payments that completed via a static
invoice, i.e., async payments, do not support payer proofs. (#845)

## Bug Fixes and Improvements
- Building a fresh node against a Bitcoin Core RPC or REST chain source that fails to return the
Expand Down
3 changes: 3 additions & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,8 @@ enum NodeError {
"FeerateEstimationUpdateTimeout",
"WalletOperationFailed",
"WalletOperationTimeout",
"PayerProofCreationFailed",
"PayerProofUnavailable",
"OnchainTxSigningFailed",
"TxSyncFailed",
"TxSyncTimeout",
Expand DownExpand Up@@ -247,6 +249,7 @@ enum NodeError {
"LnurlAuthTimeout",
"InvalidLnurl",
"ChainSourceNotSupported",
"InvalidPayerProof",
};

typedef dictionary NodeStatus;
Expand Down
11 changes: 11 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,10 @@ pub enum Error {
WalletOperationFailed,
/// A wallet operation timed out.
WalletOperationTimeout,
/// Creating a payer proof failed.
PayerProofCreationFailed,
/// A payer proof is unavailable for the requested payment.
PayerProofUnavailable,
/// A signing operation for transaction failed.
OnchainTxSigningFailed,
/// A transaction sync operation failed.
Expand DownExpand Up@@ -139,6 +143,8 @@ pub enum Error {
InvalidLnurl,
/// The configured chain source is not supported.
ChainSourceNotSupported,
/// The provided payer proof is invalid.
InvalidPayerProof,
}

impl fmt::Display for Error {
Expand DownExpand Up@@ -170,6 +176,10 @@ impl fmt::Display for Error {
},
Self::WalletOperationFailed => write!(f, "Failed to conduct wallet operation."),
Self::WalletOperationTimeout => write!(f, "A wallet operation timed out."),
Self::PayerProofCreationFailed => write!(f, "Failed to create payer proof."),
Self::PayerProofUnavailable => {
write!(f, "A payer proof is unavailable for the requested payment.")
},
Self::OnchainTxSigningFailed => write!(f, "Failed to sign given transaction."),
Self::TxSyncFailed => write!(f, "Failed to sync transactions."),
Self::TxSyncTimeout => write!(f, "Syncing transactions timed out."),
Expand DownExpand Up@@ -227,6 +237,7 @@ impl fmt::Display for Error {
Self::ChainSourceNotSupported => {
write!(f, "The configured chain source is not supported.")
},
Self::InvalidPayerProof => write!(f, "The provided payer proof is invalid."),
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/event.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1129,6 +1129,7 @@ where
offer_id,
payer_note,
quantity,
bolt12_invoice: None,
};

let payment = PaymentDetails::new(
Expand DownExpand Up@@ -1178,6 +1179,7 @@ where
secret: Some(payment_secret),
payer_note: None,
quantity: None,
bolt12_invoice: None,
};

let payment = PaymentDetails::new(
Expand DownExpand Up@@ -1331,6 +1333,7 @@ where
offer_id: payment_context.offer_id,
payer_note: payment_context.invoice_request.payer_note_truncated,
quantity: payment_context.invoice_request.quantity,
bolt12_invoice: None,
};
let update = PaymentDetailsUpdate {
preimage: Some(payment_preimage),
Expand All@@ -1352,6 +1355,7 @@ where
secret: Some(payment_secret),
payer_note: None,
quantity: None,
bolt12_invoice: None,
};
let update = PaymentDetailsUpdate {
preimage: Some(payment_preimage),
Expand DownExpand Up@@ -1442,12 +1446,16 @@ where
debug_assert!(false, "payment_id should always be set.");
return Ok(());
};
let bolt12_invoice = bolt12_invoice.map(Into::into);

// Only set the field if the event actually carried an invoice, to avoid
// overriding any previously-stored invoice with `None`.
let update = PaymentDetailsUpdate {
hash: Some(Some(payment_hash)),
preimage: Some(Some(payment_preimage)),
fee_paid_msat: Some(fee_paid_msat),
status: Some(PaymentStatus::Succeeded),
bolt12_invoice: bolt12_invoice.clone().map(Some),
..PaymentDetailsUpdate::new(payment_id)
};

Expand DownExpand Up@@ -1480,7 +1488,7 @@ where
payment_hash,
payment_preimage: Some(payment_preimage),
fee_paid_msat,
bolt12_invoice: bolt12_invoice.map(Into::into),
bolt12_invoice,
};

match self.event_queue.add_event(event).await {
Expand Down
111 changes: 110 additions & 1 deletion src/ffi/types.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,6 @@ use bitcoin::hashes::Hash;
use bitcoin::secp256k1::PublicKey;
pub use bitcoin::{Address, BlockHash, Network, OutPoint, ScriptBuf, Txid};
pub use lightning::chain::channelmonitor::BalanceSource;
use lightning::events::PaidBolt12Invoice as LdkPaidBolt12Invoice;
pub use lightning::events::{ClosureReason, PaymentFailureReason};
use lightning::ln::channel_state::{ChannelShutdownState, CounterpartyForwardingInfo};
use lightning::ln::channelmanager::PaymentId;
Expand All@@ -32,6 +31,9 @@ pub use lightning::ln::types::ChannelId;
use lightning::offers::invoice::Bolt12Invoice as LdkBolt12Invoice;
pub use lightning::offers::offer::OfferId;
use lightning::offers::offer::{Amount as LdkAmount, Offer as LdkOffer};
use lightning::offers::payer_proof::{
PaidBolt12Invoice as LdkPaidBolt12Invoice, PayerProof as LdkPayerProof,
};
use lightning::offers::refund::Refund as LdkRefund;
use lightning::offers::static_invoice::StaticInvoice as LdkStaticInvoice;
use lightning::onion_message::dns_resolution::HumanReadableName as LdkHumanReadableName;
Expand DownExpand Up@@ -881,6 +883,113 @@ impl Readable for PaidBolt12Invoice {
}
}

/// A cryptographic proof that a BOLT12 invoice was paid by this node.
#[derive(Debug, Clone, uniffi::Object)]
#[uniffi::export(Debug, Display)]
pub struct PayerProof {
pub(crate) inner: LdkPayerProof,
}

#[uniffi::export]
impl PayerProof {
#[uniffi::constructor]
pub fn from_bytes(proof_bytes: Vec<u8>) -> Result<Self, Error> {
let inner = LdkPayerProof::try_from(proof_bytes).map_err(|_| Error::InvalidPayerProof)?;
Ok(Self { inner })
}

/// The payment preimage proving the payment completed.
pub fn payment_preimage(&self) -> PaymentPreimage {
self.inner.payment_preimage()
}

/// The payment hash committed to by the invoice and proven by the preimage.
pub fn payment_hash(&self) -> PaymentHash {
self.inner.payment_hash()
}

/// The public key of the payer that authorized the payment.
pub fn payer_signing_pubkey(&self) -> PublicKey {
self.inner.payer_signing_pubkey()
}

/// The issuer signing public key committed to by the invoice.
pub fn issuer_signing_pubkey(&self) -> PublicKey {
self.inner.issuer_signing_pubkey()
}

/// The invoice signature bytes.
pub fn invoice_signature(&self) -> Vec<u8> {
self.inner.invoice_signature().as_ref().to_vec()
}

/// The proof signature bytes.
pub fn proof_signature(&self) -> Vec<u8> {
self.inner.proof_signature().as_ref().to_vec()
}

/// The offer description, if it was disclosed in the proof.
pub fn offer_description(&self) -> Option<String> {
self.inner.offer_description().map(|value| value.to_string())
}

/// The offer issuer, if it was disclosed in the proof.
pub fn offer_issuer(&self) -> Option<String> {
self.inner.offer_issuer().map(|value| value.to_string())
}

/// The invoice amount in millisatoshis, if it was disclosed in the proof.
pub fn invoice_amount_msats(&self) -> Option<u64> {
self.inner.invoice_amount_msats()
}

/// The invoice creation time, in seconds since the UNIX epoch, if it was disclosed in the
/// proof.
pub fn invoice_created_at(&self) -> Option<u64> {
self.inner.invoice_created_at().map(|value| value.as_secs())
}

/// The optional note attached to the proof.
pub fn proof_note(&self) -> Option<String> {
self.inner.proof_note().map(|value| value.to_string())
}

/// The Merkle root committed to by the proof.
pub fn merkle_root(&self) -> Vec<u8> {
self.inner.merkle_root().to_byte_array().to_vec()
}

/// The raw TLV bytes of the proof.
pub fn bytes(&self) -> Vec<u8> {
self.inner.bytes().to_vec()
}

/// The bech32-encoded string form of the proof.
pub fn as_string(&self) -> String {
self.inner.to_string()
}
}

impl From<LdkPayerProof> for PayerProof {
fn from(inner: LdkPayerProof) -> Self {
Self { inner }
}
}

impl Deref for PayerProof {
type Target = LdkPayerProof;

fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl std::fmt::Display for PayerProof {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner)
}
}

uniffi::custom_type!(OfferId, String, {
remote,
try_lift: |val| {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Add BOLT 12 payer proof support by vincenzopalazzo · Pull Request #845 · lightningdevkit/ldk-node · GitHub
Skip to content
Closed
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,11 @@
`ChannelTypeFeatures`.
- `Config::anchor_channels_config` is no longer optional, hence anchor channels can no longer be
disabled. We still negotiate legacy channels if the peer does not support anchor channels.
- The paid BOLT 12 invoice is now persisted on `PaymentKind::Bolt12Offer` and
`PaymentKind::Bolt12Refund`, and `Bolt12Payment::create_payer_proof` allows building a BOLT 12
payer proof for a previously succeeded outbound BOLT 12 payment. `PayerProofOptions` controls
which optional invoice fields are selectively disclosed. Payments that completed via a static
invoice, i.e., async payments, do not support payer proofs. (#845)

## Bug Fixes and Improvements
- Building a fresh node against a Bitcoin Core RPC or REST chain source that fails to return the
Expand Down
3 changes: 3 additions & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,8 @@ enum NodeError {
"FeerateEstimationUpdateTimeout",
"WalletOperationFailed",
"WalletOperationTimeout",
"PayerProofCreationFailed",
"PayerProofUnavailable",
"OnchainTxSigningFailed",
"TxSyncFailed",
"TxSyncTimeout",
Expand DownExpand Up@@ -247,6 +249,7 @@ enum NodeError {
"LnurlAuthTimeout",
"InvalidLnurl",
"ChainSourceNotSupported",
"InvalidPayerProof",
};

typedef dictionary NodeStatus;
Expand Down
11 changes: 11 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,10 @@ pub enum Error {
WalletOperationFailed,
/// A wallet operation timed out.
WalletOperationTimeout,
/// Creating a payer proof failed.
PayerProofCreationFailed,
/// A payer proof is unavailable for the requested payment.
PayerProofUnavailable,
/// A signing operation for transaction failed.
OnchainTxSigningFailed,
/// A transaction sync operation failed.
Expand DownExpand Up@@ -139,6 +143,8 @@ pub enum Error {
InvalidLnurl,
/// The configured chain source is not supported.
ChainSourceNotSupported,
/// The provided payer proof is invalid.
InvalidPayerProof,
}

impl fmt::Display for Error {
Expand DownExpand Up@@ -170,6 +176,10 @@ impl fmt::Display for Error {
},
Self::WalletOperationFailed => write!(f, "Failed to conduct wallet operation."),
Self::WalletOperationTimeout => write!(f, "A wallet operation timed out."),
Self::PayerProofCreationFailed => write!(f, "Failed to create payer proof."),
Self::PayerProofUnavailable => {
write!(f, "A payer proof is unavailable for the requested payment.")
},
Self::OnchainTxSigningFailed => write!(f, "Failed to sign given transaction."),
Self::TxSyncFailed => write!(f, "Failed to sync transactions."),
Self::TxSyncTimeout => write!(f, "Syncing transactions timed out."),
Expand DownExpand Up@@ -227,6 +237,7 @@ impl fmt::Display for Error {
Self::ChainSourceNotSupported => {
write!(f, "The configured chain source is not supported.")
},
Self::InvalidPayerProof => write!(f, "The provided payer proof is invalid."),
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/event.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1129,6 +1129,7 @@ where
offer_id,
payer_note,
quantity,
bolt12_invoice: None,
};

let payment = PaymentDetails::new(
Expand DownExpand Up@@ -1178,6 +1179,7 @@ where
secret: Some(payment_secret),
payer_note: None,
quantity: None,
bolt12_invoice: None,
};

let payment = PaymentDetails::new(
Expand DownExpand Up@@ -1331,6 +1333,7 @@ where
offer_id: payment_context.offer_id,
payer_note: payment_context.invoice_request.payer_note_truncated,
quantity: payment_context.invoice_request.quantity,
bolt12_invoice: None,
};
let update = PaymentDetailsUpdate {
preimage: Some(payment_preimage),
Expand All@@ -1352,6 +1355,7 @@ where
secret: Some(payment_secret),
payer_note: None,
quantity: None,
bolt12_invoice: None,
};
let update = PaymentDetailsUpdate {
preimage: Some(payment_preimage),
Expand DownExpand Up@@ -1442,12 +1446,16 @@ where
debug_assert!(false, "payment_id should always be set.");
return Ok(());
};
let bolt12_invoice = bolt12_invoice.map(Into::into);

// Only set the field if the event actually carried an invoice, to avoid
// overriding any previously-stored invoice with `None`.
let update = PaymentDetailsUpdate {
hash: Some(Some(payment_hash)),
preimage: Some(Some(payment_preimage)),
fee_paid_msat: Some(fee_paid_msat),
status: Some(PaymentStatus::Succeeded),
bolt12_invoice: bolt12_invoice.clone().map(Some),
..PaymentDetailsUpdate::new(payment_id)
};

Expand DownExpand Up@@ -1480,7 +1488,7 @@ where
payment_hash,
payment_preimage: Some(payment_preimage),
fee_paid_msat,
bolt12_invoice: bolt12_invoice.map(Into::into),
bolt12_invoice,
};

match self.event_queue.add_event(event).await {
Expand Down
111 changes: 110 additions & 1 deletion src/ffi/types.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,6 @@ use bitcoin::hashes::Hash;
use bitcoin::secp256k1::PublicKey;
pub use bitcoin::{Address, BlockHash, Network, OutPoint, ScriptBuf, Txid};
pub use lightning::chain::channelmonitor::BalanceSource;
use lightning::events::PaidBolt12Invoice as LdkPaidBolt12Invoice;
pub use lightning::events::{ClosureReason, PaymentFailureReason};
use lightning::ln::channel_state::{ChannelShutdownState, CounterpartyForwardingInfo};
use lightning::ln::channelmanager::PaymentId;
Expand All@@ -32,6 +31,9 @@ pub use lightning::ln::types::ChannelId;
use lightning::offers::invoice::Bolt12Invoice as LdkBolt12Invoice;
pub use lightning::offers::offer::OfferId;
use lightning::offers::offer::{Amount as LdkAmount, Offer as LdkOffer};
use lightning::offers::payer_proof::{
PaidBolt12Invoice as LdkPaidBolt12Invoice, PayerProof as LdkPayerProof,
};
use lightning::offers::refund::Refund as LdkRefund;
use lightning::offers::static_invoice::StaticInvoice as LdkStaticInvoice;
use lightning::onion_message::dns_resolution::HumanReadableName as LdkHumanReadableName;
Expand DownExpand Up@@ -881,6 +883,113 @@ impl Readable for PaidBolt12Invoice {
}
}

/// A cryptographic proof that a BOLT12 invoice was paid by this node.
#[derive(Debug, Clone, uniffi::Object)]
#[uniffi::export(Debug, Display)]
pub struct PayerProof {
pub(crate) inner: LdkPayerProof,
}

#[uniffi::export]
impl PayerProof {
#[uniffi::constructor]
pub fn from_bytes(proof_bytes: Vec<u8>) -> Result<Self, Error> {
let inner = LdkPayerProof::try_from(proof_bytes).map_err(|_| Error::InvalidPayerProof)?;
Ok(Self { inner })
}

/// The payment preimage proving the payment completed.
pub fn payment_preimage(&self) -> PaymentPreimage {
self.inner.payment_preimage()
}

/// The payment hash committed to by the invoice and proven by the preimage.
pub fn payment_hash(&self) -> PaymentHash {
self.inner.payment_hash()
}

/// The public key of the payer that authorized the payment.
pub fn payer_signing_pubkey(&self) -> PublicKey {
self.inner.payer_signing_pubkey()
}

/// The issuer signing public key committed to by the invoice.
pub fn issuer_signing_pubkey(&self) -> PublicKey {
self.inner.issuer_signing_pubkey()
}

/// The invoice signature bytes.
pub fn invoice_signature(&self) -> Vec<u8> {
self.inner.invoice_signature().as_ref().to_vec()
}

/// The proof signature bytes.
pub fn proof_signature(&self) -> Vec<u8> {
self.inner.proof_signature().as_ref().to_vec()
}

/// The offer description, if it was disclosed in the proof.
pub fn offer_description(&self) -> Option<String> {
self.inner.offer_description().map(|value| value.to_string())
}

/// The offer issuer, if it was disclosed in the proof.
pub fn offer_issuer(&self) -> Option<String> {
self.inner.offer_issuer().map(|value| value.to_string())
}

/// The invoice amount in millisatoshis, if it was disclosed in the proof.
pub fn invoice_amount_msats(&self) -> Option<u64> {
self.inner.invoice_amount_msats()
}

/// The invoice creation time, in seconds since the UNIX epoch, if it was disclosed in the
/// proof.
pub fn invoice_created_at(&self) -> Option<u64> {
self.inner.invoice_created_at().map(|value| value.as_secs())
}

/// The optional note attached to the proof.
pub fn proof_note(&self) -> Option<String> {
self.inner.proof_note().map(|value| value.to_string())
}

/// The Merkle root committed to by the proof.
pub fn merkle_root(&self) -> Vec<u8> {
self.inner.merkle_root().to_byte_array().to_vec()
}

/// The raw TLV bytes of the proof.
pub fn bytes(&self) -> Vec<u8> {
self.inner.bytes().to_vec()
}

/// The bech32-encoded string form of the proof.
pub fn as_string(&self) -> String {
self.inner.to_string()
}
}

impl From<LdkPayerProof> for PayerProof {
fn from(inner: LdkPayerProof) -> Self {
Self { inner }
}
}

impl Deref for PayerProof {
type Target = LdkPayerProof;

fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl std::fmt::Display for PayerProof {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner)
}
}

uniffi::custom_type!(OfferId, String, {
remote,
try_lift: |val| {
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Add BOLT 12 payer proof support by vincenzopalazzo · Pull Request #845 · lightningdevkit/ldk-node · GitHub
Skip to content
Closed
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -32,6 +32,11 @@
`ChannelTypeFeatures`.
- `Config::anchor_channels_config` is no longer optional, hence anchor channels can no longer be
disabled. We still negotiate legacy channels if the peer does not support anchor channels.
- The paid BOLT 12 invoice is now persisted on `PaymentKind::Bolt12Offer` and
`PaymentKind::Bolt12Refund`, and `Bolt12Payment::create_payer_proof` allows building a BOLT 12
payer proof for a previously succeeded outbound BOLT 12 payment. `PayerProofOptions` controls
which optional invoice fields are selectively disclosed. Payments that completed via a static
invoice, i.e., async payments, do not support payer proofs. (#845)

## Bug Fixes and Improvements
- Building a fresh node against a Bitcoin Core RPC or REST chain source that fails to return the
Expand Down
3 changes: 3 additions & 0 deletions bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -206,6 +206,8 @@ enum NodeError {
"FeerateEstimationUpdateTimeout",
"WalletOperationFailed",
"WalletOperationTimeout",
"PayerProofCreationFailed",
"PayerProofUnavailable",
"OnchainTxSigningFailed",
"TxSyncFailed",
"TxSyncTimeout",
Expand DownExpand Up@@ -247,6 +249,7 @@ enum NodeError {
"LnurlAuthTimeout",
"InvalidLnurl",
"ChainSourceNotSupported",
"InvalidPayerProof",
};

typedef dictionary NodeStatus;
Expand Down
11 changes: 11 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -57,6 +57,10 @@ pub enum Error {
WalletOperationFailed,
/// A wallet operation timed out.
WalletOperationTimeout,
/// Creating a payer proof failed.
PayerProofCreationFailed,
/// A payer proof is unavailable for the requested payment.
PayerProofUnavailable,
/// A signing operation for transaction failed.
OnchainTxSigningFailed,
/// A transaction sync operation failed.
Expand DownExpand Up@@ -139,6 +143,8 @@ pub enum Error {
InvalidLnurl,
/// The configured chain source is not supported.
ChainSourceNotSupported,
/// The provided payer proof is invalid.
InvalidPayerProof,
}

impl fmt::Display for Error {
Expand DownExpand Up@@ -170,6 +176,10 @@ impl fmt::Display for Error {
},
Self::WalletOperationFailed => write!(f, "Failed to conduct wallet operation."),
Self::WalletOperationTimeout => write!(f, "A wallet operation timed out."),
Self::PayerProofCreationFailed => write!(f, "Failed to create payer proof."),
Self::PayerProofUnavailable => {
write!(f, "A payer proof is unavailable for the requested payment.")
},
Self::OnchainTxSigningFailed => write!(f, "Failed to sign given transaction."),
Self::TxSyncFailed => write!(f, "Failed to sync transactions."),
Self::TxSyncTimeout => write!(f, "Syncing transactions timed out."),
Expand DownExpand Up@@ -227,6 +237,7 @@ impl fmt::Display for Error {
Self::ChainSourceNotSupported => {
write!(f, "The configured chain source is not supported.")
},
Self::InvalidPayerProof => write!(f, "The provided payer proof is invalid."),
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/event.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1129,6 +1129,7 @@ where
offer_id,
payer_note,
quantity,
bolt12_invoice: None,
};

let payment = PaymentDetails::new(
Expand DownExpand Up@@ -1178,6 +1179,7 @@ where
secret: Some(payment_secret),
payer_note: None,
quantity: None,
bolt12_invoice: None,
};

let payment = PaymentDetails::new(
Expand DownExpand Up@@ -1331,6 +1333,7 @@ where
offer_id: payment_context.offer_id,
payer_note: payment_context.invoice_request.payer_note_truncated,
quantity: payment_context.invoice_request.quantity,
bolt12_invoice: None,
};
let update = PaymentDetailsUpdate {
preimage: Some(payment_preimage),
Expand All@@ -1352,6 +1355,7 @@ where
secret: Some(payment_secret),
payer_note: None,
quantity: None,
bolt12_invoice: None,
};
let update = PaymentDetailsUpdate {
preimage: Some(payment_preimage),
Expand DownExpand Up@@ -1442,12 +1446,16 @@ where
debug_assert!(false, "payment_id should always be set.");
return Ok(());
};
let bolt12_invoice = bolt12_invoice.map(Into::into);

// Only set the field if the event actually carried an invoice, to avoid
// overriding any previously-stored invoice with `None`.
let update = PaymentDetailsUpdate {
hash: Some(Some(payment_hash)),
preimage: Some(Some(payment_preimage)),
fee_paid_msat: Some(fee_paid_msat),
status: Some(PaymentStatus::Succeeded),
bolt12_invoice: bolt12_invoice.clone().map(Some),
..PaymentDetailsUpdate::new(payment_id)
};

Expand DownExpand Up@@ -1480,7 +1488,7 @@ where
payment_hash,
payment_preimage: Some(payment_preimage),
fee_paid_msat,
bolt12_invoice: bolt12_invoice.map(Into::into),
bolt12_invoice,
};

match self.event_queue.add_event(event).await {
Expand Down
111 changes: 110 additions & 1 deletion src/ffi/types.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,7 +23,6 @@ use bitcoin::hashes::Hash;
use bitcoin::secp256k1::PublicKey;
pub use bitcoin::{Address, BlockHash, Network, OutPoint, ScriptBuf, Txid};
pub use lightning::chain::channelmonitor::BalanceSource;
use lightning::events::PaidBolt12Invoice as LdkPaidBolt12Invoice;
pub use lightning::events::{ClosureReason, PaymentFailureReason};
use lightning::ln::channel_state::{ChannelShutdownState, CounterpartyForwardingInfo};
use lightning::ln::channelmanager::PaymentId;
Expand All@@ -32,6 +31,9 @@ pub use lightning::ln::types::ChannelId;
use lightning::offers::invoice::Bolt12Invoice as LdkBolt12Invoice;
pub use lightning::offers::offer::OfferId;
use lightning::offers::offer::{Amount as LdkAmount, Offer as LdkOffer};
use lightning::offers::payer_proof::{
PaidBolt12Invoice as LdkPaidBolt12Invoice, PayerProof as LdkPayerProof,
};
use lightning::offers::refund::Refund as LdkRefund;
use lightning::offers::static_invoice::StaticInvoice as LdkStaticInvoice;
use lightning::onion_message::dns_resolution::HumanReadableName as LdkHumanReadableName;
Expand DownExpand Up@@ -881,6 +883,113 @@ impl Readable for PaidBolt12Invoice {
}
}

/// A cryptographic proof that a BOLT12 invoice was paid by this node.
#[derive(Debug, Clone, uniffi::Object)]
#[uniffi::export(Debug, Display)]
pub struct PayerProof {
pub(crate) inner: LdkPayerProof,
}

#[uniffi::export]
impl PayerProof {
#[uniffi::constructor]
pub fn from_bytes(proof_bytes: Vec<u8>) -> Result<Self, Error> {
let inner = LdkPayerProof::try_from(proof_bytes).map_err(|_| Error::InvalidPayerProof)?;
Ok(Self { inner })
}

/// The payment preimage proving the payment completed.
pub fn payment_preimage(&self) -> PaymentPreimage {
self.inner.payment_preimage()
}

/// The payment hash committed to by the invoice and proven by the preimage.
pub fn payment_hash(&self) -> PaymentHash {
self.inner.payment_hash()
}

/// The public key of the payer that authorized the payment.
pub fn payer_signing_pubkey(&self) -> PublicKey {
self.inner.payer_signing_pubkey()
}

/// The issuer signing public key committed to by the invoice.
pub fn issuer_signing_pubkey(&self) -> PublicKey {
self.inner.issuer_signing_pubkey()
}

/// The invoice signature bytes.
pub fn invoice_signature(&self) -> Vec<u8> {
self.inner.invoice_signature().as_ref().to_vec()
}

/// The proof signature bytes.
pub fn proof_signature(&self) -> Vec<u8> {
self.inner.proof_signature().as_ref().to_vec()
}

/// The offer description, if it was disclosed in the proof.
pub fn offer_description(&self) -> Option<String> {
self.inner.offer_description().map(|value| value.to_string())
}

/// The offer issuer, if it was disclosed in the proof.
pub fn offer_issuer(&self) -> Option<String> {
self.inner.offer_issuer().map(|value| value.to_string())
}

/// The invoice amount in millisatoshis, if it was disclosed in the proof.
pub fn invoice_amount_msats(&self) -> Option<u64> {
self.inner.invoice_amount_msats()
}

/// The invoice creation time, in seconds since the UNIX epoch, if it was disclosed in the
/// proof.
pub fn invoice_created_at(&self) -> Option<u64> {
self.inner.invoice_created_at().map(|value| value.as_secs())
}

/// The optional note attached to the proof.
pub fn proof_note(&self) -> Option<String> {
self.inner.proof_note().map(|value| value.to_string())
}

/// The Merkle root committed to by the proof.
pub fn merkle_root(&self) -> Vec<u8> {
self.inner.merkle_root().to_byte_array().to_vec()
}

/// The raw TLV bytes of the proof.
pub fn bytes(&self) -> Vec<u8> {
self.inner.bytes().to_vec()
}

/// The bech32-encoded string form of the proof.
pub fn as_string(&self) -> String {
self.inner.to_string()
}
}

impl From<LdkPayerProof> for PayerProof {
fn from(inner: LdkPayerProof) -> Self {
Self { inner }
}
}

impl Deref for PayerProof {
type Target = LdkPayerProof;

fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl std::fmt::Display for PayerProof {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.inner)
}
}

uniffi::custom_type!(OfferId, String, {
remote,
try_lift: |val| {
Expand Down
Loading
Loading