Skip to content

Commit 3e1b78b

Browse files
committed
Require payment secrets when building and reading invoices
1 parent 2735cea commit 3e1b78b

File tree

2 files changed

+39
-18
lines changed

2 files changed

+39
-18
lines changed

lightning-invoice/src/lib.rs

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ pub fn check_platform() {
127127
///
128128
/// ```
129129
/// extern crate secp256k1;
130+
/// extern crate lightning;
130131
/// extern crate lightning_invoice;
131132
/// extern crate bitcoin_hashes;
132133
///
@@ -136,6 +137,8 @@ pub fn check_platform() {
136137
/// use secp256k1::Secp256k1;
137138
/// use secp256k1::key::SecretKey;
138139
///
140+
/// use lightning::ln::PaymentSecret;
141+
///
139142
/// use lightning_invoice::{Currency, InvoiceBuilder};
140143
///
141144
/// # fn main() {
@@ -148,10 +151,12 @@ pub fn check_platform() {
148151
/// ).unwrap();
149152
///
150153
/// let payment_hash = sha256::Hash::from_slice(&[0; 32][..]).unwrap();
154+
/// let payment_secret = PaymentSecret([42u8; 32]);
151155
///
152156
/// let invoice = InvoiceBuilder::new(Currency::Bitcoin)
153157
/// .description("Coins pls!".into())
154158
/// .payment_hash(payment_hash)
159+
/// .payment_secret(payment_secret)
155160
/// .current_timestamp()
156161
/// .min_final_cltv_expiry(144)
157162
/// .build_signed(|hash| {
@@ -634,7 +639,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T,
634639
}
635640
}
636641

637-
impl<S: tb::Bool> InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, S> {
642+
impl InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, tb::True> {
638643
/// Builds and signs an invoice using the supplied `sign_function`. This function MAY NOT fail
639644
/// and MUST produce a recoverable signature valid for the given hash and if applicable also for
640645
/// the included payee public key.
@@ -1018,6 +1023,17 @@ impl Invoice {
10181023
return Err(SemanticError::MultipleDescriptions);
10191024
}
10201025

1026+
// "A writer MUST include exactly one `s` field."
1027+
let payment_secret_count = self.tagged_fields().filter(|&tf| match *tf {
1028+
TaggedField::PaymentSecret(_) => true,
1029+
_ => false,
1030+
}).count();
1031+
if payment_secret_count < 1 {
1032+
return Err(SemanticError::NoPaymentSecret);
1033+
} else if payment_secret_count > 1 {
1034+
return Err(SemanticError::MultiplePaymentSecrets);
1035+
}
1036+
10211037
Ok(())
10221038
}
10231039

@@ -1033,32 +1049,30 @@ impl Invoice {
10331049

10341050
/// Check that feature bits are set as required
10351051
fn check_feature_bits(&self) -> Result<(), SemanticError> {
1036-
// "If the payment_secret feature is set, MUST include exactly one s field."
1052+
// "A writer MUST include exactly one `s` field."
10371053
let payment_secret_count = self.tagged_fields().filter(|&tf| match *tf {
10381054
TaggedField::PaymentSecret(_) => true,
10391055
_ => false,
10401056
}).count();
1041-
if payment_secret_count > 1 {
1057+
if payment_secret_count < 1 {
1058+
return Err(SemanticError::NoPaymentSecret);
1059+
} else if payment_secret_count > 1 {
10421060
return Err(SemanticError::MultiplePaymentSecrets);
10431061
}
10441062

10451063
// "A writer MUST set an s field if and only if the payment_secret feature is set."
1046-
let has_payment_secret = payment_secret_count == 1;
1064+
// (this requirement has been since removed, and we now require the payment secret
1065+
// feature bit always).
10471066
let features = self.tagged_fields().find(|&tf| match *tf {
10481067
TaggedField::Features(_) => true,
10491068
_ => false,
10501069
});
10511070
match features {
1052-
None if has_payment_secret => Err(SemanticError::InvalidFeatures),
1053-
None => Ok(()),
1071+
None => Err(SemanticError::InvalidFeatures),
10541072
Some(TaggedField::Features(features)) => {
10551073
if features.requires_unknown_bits() {
10561074
Err(SemanticError::InvalidFeatures)
1057-
} else if features.supports_payment_secret() && has_payment_secret {
1058-
Ok(())
1059-
} else if has_payment_secret {
1060-
Err(SemanticError::InvalidFeatures)
1061-
} else if features.supports_payment_secret() {
1075+
} else if !features.supports_payment_secret() {
10621076
Err(SemanticError::InvalidFeatures)
10631077
} else {
10641078
Ok(())
@@ -1154,8 +1168,8 @@ impl Invoice {
11541168
}
11551169

11561170
/// Get the payment secret if one was included in the invoice
1157-
pub fn payment_secret(&self) -> Option<&PaymentSecret> {
1158-
self.signed_invoice.payment_secret()
1171+
pub fn payment_secret(&self) -> &PaymentSecret {
1172+
self.signed_invoice.payment_secret().expect("was checked by constructor")
11591173
}
11601174

11611175
/// Get the invoice features if they were included in the invoice
@@ -1412,6 +1426,10 @@ pub enum SemanticError {
14121426
/// The invoice contains multiple descriptions and/or description hashes which isn't allowed
14131427
MultipleDescriptions,
14141428

1429+
/// The invoice is missing the mandatory payment secret, which all modern lightning nodes
1430+
/// should provide.
1431+
NoPaymentSecret,
1432+
14151433
/// The invoice contains multiple payment secrets
14161434
MultiplePaymentSecrets,
14171435

@@ -1435,6 +1453,7 @@ impl Display for SemanticError {
14351453
SemanticError::MultiplePaymentHashes => f.write_str("The invoice has multiple payment hashes which isn't allowed"),
14361454
SemanticError::NoDescription => f.write_str("No description or description hash are part of the invoice"),
14371455
SemanticError::MultipleDescriptions => f.write_str("The invoice contains multiple descriptions and/or description hashes which isn't allowed"),
1456+
SemanticError::NoPaymentSecret => f.write_str("The invoice is missing the mandatory payment secret"),
14381457
SemanticError::MultiplePaymentSecrets => f.write_str("The invoice contains multiple payment secrets"),
14391458
SemanticError::InvalidFeatures => f.write_str("The invoice's features are invalid"),
14401459
SemanticError::InvalidRecoveryId => f.write_str("The recovery id doesn't fit the signature/pub key"),
@@ -1651,23 +1670,23 @@ mod test {
16511670
let invoice = invoice_template.clone();
16521671
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_recoverable(hash, &private_key)))
16531672
}.unwrap();
1654-
assert!(Invoice::from_signed(invoice).is_ok());
1673+
assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret));
16551674

16561675
// No payment secret or feature bits
16571676
let invoice = {
16581677
let mut invoice = invoice_template.clone();
16591678
invoice.data.tagged_fields.push(Features(InvoiceFeatures::empty()).into());
16601679
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_recoverable(hash, &private_key)))
16611680
}.unwrap();
1662-
assert!(Invoice::from_signed(invoice).is_ok());
1681+
assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret));
16631682

16641683
// Missing payment secret
16651684
let invoice = {
16661685
let mut invoice = invoice_template.clone();
16671686
invoice.data.tagged_fields.push(Features(InvoiceFeatures::known()).into());
16681687
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_recoverable(hash, &private_key)))
16691688
}.unwrap();
1670-
assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::InvalidFeatures));
1689+
assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret));
16711690

16721691
// Multiple payment secrets
16731692
let invoice = {
@@ -1753,6 +1772,7 @@ mod test {
17531772

17541773
let sign_error_res = builder.clone()
17551774
.description("Test".into())
1775+
.payment_secret(PaymentSecret([0; 32]))
17561776
.try_build_signed(|_| {
17571777
Err("ImaginaryError")
17581778
});
@@ -1865,7 +1885,7 @@ mod test {
18651885
InvoiceDescription::Hash(&Sha256(sha256::Hash::from_slice(&[3;32][..]).unwrap()))
18661886
);
18671887
assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&[21;32][..]).unwrap());
1868-
assert_eq!(invoice.payment_secret(), Some(&PaymentSecret([42; 32])));
1888+
assert_eq!(invoice.payment_secret(), &PaymentSecret([42; 32]));
18691889
assert_eq!(invoice.features(), Some(&InvoiceFeatures::known()));
18701890

18711891
let raw_invoice = builder.build_raw().unwrap();
@@ -1881,6 +1901,7 @@ mod test {
18811901
let signed_invoice = InvoiceBuilder::new(Currency::Bitcoin)
18821902
.description("Test".into())
18831903
.payment_hash(sha256::Hash::from_slice(&[0;32][..]).unwrap())
1904+
.payment_secret(PaymentSecret([0; 32]))
18841905
.current_timestamp()
18851906
.build_raw()
18861907
.unwrap()

lightning-invoice/src/utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ mod test {
132132
let payment_event = {
133133
let mut payment_hash = PaymentHash([0; 32]);
134134
payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]);
135-
nodes[0].node.send_payment(&route, payment_hash, &Some(invoice.payment_secret().unwrap().clone())).unwrap();
135+
nodes[0].node.send_payment(&route, payment_hash, &Some(invoice.payment_secret().clone())).unwrap();
136136
let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap();
137137
assert_eq!(added_monitors.len(), 1);
138138
added_monitors.clear();

0 commit comments

Comments
 (0)