X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Foffers%2Frefund.rs;h=6a1d24106e906a532f6eec749d4c509d441ad02d;hb=806b7f0e312c59c87fd628fb71e7c4a77a39645a;hp=42177960868ab553ee1385f2569146dc8209e2bf;hpb=6d5c952556aefa8f61c5a2c74ff72f426f5406ac;p=rust-lightning diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index 42177960..6a1d2410 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -48,7 +48,8 @@ //! let pubkey = PublicKey::from(keys); //! //! let expiration = SystemTime::now() + Duration::from_secs(24 * 60 * 60); -//! let refund = RefundBuilder::new("coffee, large".to_string(), vec![1; 32], pubkey, 20_000)? +//! let refund = RefundBuilder::new(vec![1; 32], pubkey, 20_000)? +//! .description("coffee, large".to_string()) //! .absolute_expiry(expiration.duration_since(SystemTime::UNIX_EPOCH).unwrap()) //! .issuer("Foo Bar".to_string()) //! .path(create_blinded_path()) @@ -84,7 +85,6 @@ use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; use bitcoin::secp256k1::{PublicKey, Secp256k1, self}; -use core::convert::TryFrom; use core::hash::{Hash, Hasher}; use core::ops::Deref; use core::str::FromStr; @@ -92,7 +92,7 @@ use core::time::Duration; use crate::sign::EntropySource; use crate::io; use crate::blinded_path::BlindedPath; -use crate::ln::PaymentHash; +use crate::ln::types::PaymentHash; use crate::ln::channelmanager::PaymentId; use crate::ln::features::InvoiceRequestFeatures; use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce}; @@ -115,6 +115,7 @@ use { crate::offers::invoice::{InvoiceWithDerivedSigningPubkeyBuilder, InvoiceWithExplicitSigningPubkeyBuilder}, }; +#[allow(unused_imports)] use crate::prelude::*; #[cfg(feature = "std")] @@ -149,8 +150,8 @@ macro_rules! refund_explicit_metadata_builder_methods { () => { /// Creates a new builder for a refund using the [`Refund::payer_id`] for the public node id to /// send to if no [`Refund::paths`] are set. Otherwise, it may be a transient pubkey. /// - /// Additionally, sets the required [`Refund::description`], [`Refund::payer_metadata`], and - /// [`Refund::amount_msats`]. + /// Additionally, sets the required (empty) [`Refund::description`], [`Refund::payer_metadata`], + /// and [`Refund::amount_msats`]. /// /// # Note /// @@ -160,7 +161,7 @@ macro_rules! refund_explicit_metadata_builder_methods { () => { /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager /// [`ChannelManager::create_refund_builder`]: crate::ln::channelmanager::ChannelManager::create_refund_builder pub fn new( - description: String, metadata: Vec, payer_id: PublicKey, amount_msats: u64 + metadata: Vec, payer_id: PublicKey, amount_msats: u64 ) -> Result { if amount_msats > MAX_VALUE_MSAT { return Err(Bolt12SemanticError::InvalidAmount); @@ -169,9 +170,9 @@ macro_rules! refund_explicit_metadata_builder_methods { () => { let metadata = Metadata::Bytes(metadata); Ok(Self { refund: RefundContents { - payer: PayerContents(metadata), description, absolute_expiry: None, issuer: None, - paths: None, chain: None, amount_msats, features: InvoiceRequestFeatures::empty(), - quantity: None, payer_id, payer_note: None, + payer: PayerContents(metadata), description: String::new(), absolute_expiry: None, + issuer: None, chain: None, amount_msats, features: InvoiceRequestFeatures::empty(), + quantity: None, payer_id, payer_note: None, paths: None, }, secp_ctx: None, }) @@ -195,7 +196,7 @@ macro_rules! refund_builder_methods { ( /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey pub fn deriving_payer_id( - description: String, node_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES, + node_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'a Secp256k1<$secp_context>, amount_msats: u64, payment_id: PaymentId ) -> Result where ES::Target: EntropySource { if amount_msats > MAX_VALUE_MSAT { @@ -208,14 +209,22 @@ macro_rules! refund_builder_methods { ( let metadata = Metadata::DerivedSigningPubkey(derivation_material); Ok(Self { refund: RefundContents { - payer: PayerContents(metadata), description, absolute_expiry: None, issuer: None, - paths: None, chain: None, amount_msats, features: InvoiceRequestFeatures::empty(), - quantity: None, payer_id: node_id, payer_note: None, + payer: PayerContents(metadata), description: String::new(), absolute_expiry: None, + issuer: None, chain: None, amount_msats, features: InvoiceRequestFeatures::empty(), + quantity: None, payer_id: node_id, payer_note: None, paths: None, }, secp_ctx: Some(secp_ctx), }) } + /// Sets the [`Refund::description`]. + /// + /// Successive calls to this method will override the previous setting. + pub fn description($($self_mut)* $self: $self_type, description: String) -> $return_type { + $self.refund.description = description; + $return_value + } + /// Sets the [`Refund::absolute_expiry`] as seconds since the Unix epoch. Any expiry that has /// already passed is valid and can be checked for using [`Refund::is_expired`]. /// @@ -375,6 +384,16 @@ for RefundMaybeWithDerivedMetadataBuilder<'a> { } } +#[cfg(c_bindings)] +impl<'a> From> +for RefundBuilder<'a, secp256k1::All> { + fn from(builder: RefundMaybeWithDerivedMetadataBuilder<'a>) -> Self { + let RefundMaybeWithDerivedMetadataBuilder { refund, secp_ctx } = builder; + + Self { refund, secp_ctx } + } +} + /// A `Refund` is a request to send an [`Bolt12Invoice`] without a preceding [`Offer`]. /// /// Typically, after an invoice is paid, the recipient may publish a refund allowing the sender to @@ -400,7 +419,6 @@ pub(super) struct RefundContents { description: String, absolute_expiry: Option, issuer: Option, - paths: Option>, // invoice_request fields chain: Option, amount_msats: u64, @@ -408,6 +426,7 @@ pub(super) struct RefundContents { quantity: Option, payer_id: PublicKey, payer_note: Option, + paths: Option>, } impl Refund { @@ -724,7 +743,7 @@ impl RefundContents { description: Some(&self.description), features: None, absolute_expiry: self.absolute_expiry.map(|duration| duration.as_secs()), - paths: self.paths.as_ref(), + paths: None, issuer: self.issuer.as_ref(), quantity_max: None, node_id: None, @@ -742,6 +761,7 @@ impl RefundContents { quantity: self.quantity, payer_id: Some(&self.payer_id), payer_note: self.payer_note.as_ref(), + paths: self.paths.as_ref(), }; (payer, offer, invoice_request) @@ -810,9 +830,12 @@ impl TryFrom for RefundContents { PayerTlvStream { metadata: payer_metadata }, OfferTlvStream { chains, metadata, currency, amount: offer_amount, description, - features: offer_features, absolute_expiry, paths, issuer, quantity_max, node_id, + features: offer_features, absolute_expiry, paths: offer_paths, issuer, quantity_max, + node_id, + }, + InvoiceRequestTlvStream { + chain, amount, features, quantity, payer_id, payer_note, paths }, - InvoiceRequestTlvStream { chain, amount, features, quantity, payer_id, payer_note }, ) = tlv_stream; let payer = match payer_metadata { @@ -843,6 +866,10 @@ impl TryFrom for RefundContents { let absolute_expiry = absolute_expiry.map(Duration::from_secs); + if offer_paths.is_some() { + return Err(Bolt12SemanticError::UnexpectedPaths); + } + if quantity_max.is_some() { return Err(Bolt12SemanticError::UnexpectedQuantity); } @@ -867,8 +894,8 @@ impl TryFrom for RefundContents { }; Ok(RefundContents { - payer, description, absolute_expiry, issuer, paths, chain, amount_msats, features, - quantity, payer_id, payer_note, + payer, description, absolute_expiry, issuer, chain, amount_msats, features, quantity, + payer_id, payer_note, paths, }) } } @@ -894,9 +921,10 @@ mod tests { use bitcoin::blockdata::constants::ChainHash; use bitcoin::network::constants::Network; use bitcoin::secp256k1::{KeyPair, Secp256k1, SecretKey}; - use core::convert::TryFrom; + use core::time::Duration; - use crate::blinded_path::{BlindedHop, BlindedPath}; + + use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use crate::sign::KeyMaterial; use crate::ln::channelmanager::PaymentId; use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures}; @@ -909,6 +937,7 @@ mod tests { use crate::offers::test_utils::*; use crate::util::ser::{BigSize, Writeable}; use crate::util::string::PrintableString; + use crate::prelude::*; trait ToBytes { fn to_bytes(&self) -> Vec; @@ -924,7 +953,7 @@ mod tests { #[test] fn builds_refund_with_defaults() { - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .build().unwrap(); let mut buffer = Vec::new(); @@ -932,7 +961,7 @@ mod tests { assert_eq!(refund.bytes, buffer.as_slice()); assert_eq!(refund.payer_metadata(), &[1; 32]); - assert_eq!(refund.description(), PrintableString("foo")); + assert_eq!(refund.description(), PrintableString("")); assert_eq!(refund.absolute_expiry(), None); #[cfg(feature = "std")] assert!(!refund.is_expired()); @@ -953,7 +982,7 @@ mod tests { metadata: None, currency: None, amount: None, - description: Some(&String::from("foo")), + description: Some(&String::from("")), features: None, absolute_expiry: None, paths: None, @@ -968,6 +997,7 @@ mod tests { quantity: None, payer_id: Some(&payer_pubkey()), payer_note: None, + paths: None, }, ), ); @@ -979,7 +1009,7 @@ mod tests { #[test] fn fails_building_refund_with_invalid_amount() { - match RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), MAX_VALUE_MSAT + 1) { + match RefundBuilder::new(vec![1; 32], payer_pubkey(), MAX_VALUE_MSAT + 1) { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount), } @@ -987,7 +1017,6 @@ mod tests { #[test] fn builds_refund_with_metadata_derived() { - let desc = "foo".to_string(); let node_id = payer_pubkey(); let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); let entropy = FixedEntropy {}; @@ -995,7 +1024,7 @@ mod tests { let payment_id = PaymentId([1; 32]); let refund = RefundBuilder - ::deriving_payer_id(desc, node_id, &expanded_key, &entropy, &secp_ctx, 1000, payment_id) + ::deriving_payer_id(node_id, &expanded_key, &entropy, &secp_ctx, 1000, payment_id) .unwrap() .build().unwrap(); assert_eq!(refund.payer_id(), node_id); @@ -1042,7 +1071,6 @@ mod tests { #[test] fn builds_refund_with_derived_payer_id() { - let desc = "foo".to_string(); let node_id = payer_pubkey(); let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32])); let entropy = FixedEntropy {}; @@ -1050,7 +1078,7 @@ mod tests { let payment_id = PaymentId([1; 32]); let blinded_path = BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] }, @@ -1059,7 +1087,7 @@ mod tests { }; let refund = RefundBuilder - ::deriving_payer_id(desc, node_id, &expanded_key, &entropy, &secp_ctx, 1000, payment_id) + ::deriving_payer_id(node_id, &expanded_key, &entropy, &secp_ctx, 1000, payment_id) .unwrap() .path(blinded_path) .build().unwrap(); @@ -1111,7 +1139,7 @@ mod tests { let past_expiry = Duration::from_secs(0); let now = future_expiry - Duration::from_secs(1_000); - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .absolute_expiry(future_expiry) .build() .unwrap(); @@ -1122,7 +1150,7 @@ mod tests { assert_eq!(refund.absolute_expiry(), Some(future_expiry)); assert_eq!(tlv_stream.absolute_expiry, Some(future_expiry.as_secs())); - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .absolute_expiry(future_expiry) .absolute_expiry(past_expiry) .build() @@ -1139,7 +1167,7 @@ mod tests { fn builds_refund_with_paths() { let paths = vec![ BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] }, @@ -1147,7 +1175,7 @@ mod tests { ], }, BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] }, @@ -1156,22 +1184,22 @@ mod tests { }, ]; - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .path(paths[0].clone()) .path(paths[1].clone()) .build() .unwrap(); - let (_, offer_tlv_stream, invoice_request_tlv_stream) = refund.as_tlv_stream(); - assert_eq!(refund.paths(), paths.as_slice()); + let (_, _, invoice_request_tlv_stream) = refund.as_tlv_stream(); assert_eq!(refund.payer_id(), pubkey(42)); + assert_eq!(refund.paths(), paths.as_slice()); assert_ne!(pubkey(42), pubkey(44)); - assert_eq!(offer_tlv_stream.paths, Some(&paths)); assert_eq!(invoice_request_tlv_stream.payer_id, Some(&pubkey(42))); + assert_eq!(invoice_request_tlv_stream.paths, Some(&paths)); } #[test] fn builds_refund_with_issuer() { - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .issuer("bar".into()) .build() .unwrap(); @@ -1179,7 +1207,7 @@ mod tests { assert_eq!(refund.issuer(), Some(PrintableString("bar"))); assert_eq!(tlv_stream.issuer, Some(&String::from("bar"))); - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .issuer("bar".into()) .issuer("baz".into()) .build() @@ -1194,21 +1222,21 @@ mod tests { let mainnet = ChainHash::using_genesis_block(Network::Bitcoin); let testnet = ChainHash::using_genesis_block(Network::Testnet); - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .chain(Network::Bitcoin) .build().unwrap(); let (_, _, tlv_stream) = refund.as_tlv_stream(); assert_eq!(refund.chain(), mainnet); assert_eq!(tlv_stream.chain, None); - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .chain(Network::Testnet) .build().unwrap(); let (_, _, tlv_stream) = refund.as_tlv_stream(); assert_eq!(refund.chain(), testnet); assert_eq!(tlv_stream.chain, Some(&testnet)); - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .chain(Network::Regtest) .chain(Network::Testnet) .build().unwrap(); @@ -1219,14 +1247,14 @@ mod tests { #[test] fn builds_refund_with_quantity() { - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .quantity(10) .build().unwrap(); let (_, _, tlv_stream) = refund.as_tlv_stream(); assert_eq!(refund.quantity(), Some(10)); assert_eq!(tlv_stream.quantity, Some(10)); - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .quantity(10) .quantity(1) .build().unwrap(); @@ -1237,14 +1265,14 @@ mod tests { #[test] fn builds_refund_with_payer_note() { - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .payer_note("bar".into()) .build().unwrap(); let (_, _, tlv_stream) = refund.as_tlv_stream(); assert_eq!(refund.payer_note(), Some(PrintableString("bar"))); assert_eq!(tlv_stream.payer_note, Some(&String::from("bar"))); - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .payer_note("bar".into()) .payer_note("baz".into()) .build().unwrap(); @@ -1255,7 +1283,7 @@ mod tests { #[test] fn fails_responding_with_unknown_required_features() { - match RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + match RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .features_unchecked(InvoiceRequestFeatures::unknown()) .build().unwrap() .respond_with_no_std(payment_paths(), payment_hash(), recipient_pubkey(), now()) @@ -1267,7 +1295,7 @@ mod tests { #[test] fn parses_refund_with_metadata() { - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .build().unwrap(); if let Err(e) = refund.to_string().parse::() { panic!("error parsing refund: {:?}", e); @@ -1286,7 +1314,7 @@ mod tests { #[test] fn parses_refund_with_description() { - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .build().unwrap(); if let Err(e) = refund.to_string().parse::() { panic!("error parsing refund: {:?}", e); @@ -1305,7 +1333,7 @@ mod tests { #[test] fn parses_refund_with_amount() { - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .build().unwrap(); if let Err(e) = refund.to_string().parse::() { panic!("error parsing refund: {:?}", e); @@ -1334,7 +1362,7 @@ mod tests { #[test] fn parses_refund_with_payer_id() { - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .build().unwrap(); if let Err(e) = refund.to_string().parse::() { panic!("error parsing refund: {:?}", e); @@ -1356,7 +1384,7 @@ mod tests { let past_expiry = Duration::from_secs(0); let paths = vec![ BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] }, @@ -1364,7 +1392,7 @@ mod tests { ], }, BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] }, @@ -1373,7 +1401,7 @@ mod tests { }, ]; - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .absolute_expiry(past_expiry) .issuer("bar".into()) .path(paths[0].clone()) @@ -1402,7 +1430,7 @@ mod tests { #[test] fn fails_parsing_refund_with_unexpected_fields() { - let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap() .build().unwrap(); if let Err(e) = refund.to_string().parse::() { panic!("error parsing refund: {:?}", e); @@ -1478,7 +1506,7 @@ mod tests { fn fails_parsing_refund_with_extra_tlv_records() { let secp_ctx = Secp256k1::new(); let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); - let refund = RefundBuilder::new("foo".into(), vec![1; 32], keys.public_key(), 1000).unwrap() + let refund = RefundBuilder::new(vec![1; 32], keys.public_key(), 1000).unwrap() .build().unwrap(); let mut encoded_refund = Vec::new();