Skip to content

Commit 264c404

Browse files
committed
Require payment secrets when building and readinv invoices
1 parent c1e92b4 commit 264c404

File tree

3 files changed

+38
-22
lines changed

3 files changed

+38
-22
lines changed

lightning-invoice/src/lib.rs

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T,
634634
}
635635
}
636636

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

1021+
// "A writer MUST include exactly one `s` field."
1022+
let payment_secret_count = self.tagged_fields().filter(|&tf| match *tf {
1023+
TaggedField::PaymentSecret(_) => true,
1024+
_ => false,
1025+
}).count();
1026+
if payment_secret_count < 1 {
1027+
return Err(SemanticError::NoPaymentSecret);
1028+
} else if payment_secret_count > 1 {
1029+
return Err(SemanticError::MultiplePaymentSecrets);
1030+
}
1031+
10211032
Ok(())
10221033
}
10231034

@@ -1033,32 +1044,30 @@ impl Invoice {
10331044

10341045
/// Check that feature bits are set as required
10351046
fn check_feature_bits(&self) -> Result<(), SemanticError> {
1036-
// "If the payment_secret feature is set, MUST include exactly one s field."
1047+
// "A writer MUST include exactly one `s` field."
10371048
let payment_secret_count = self.tagged_fields().filter(|&tf| match *tf {
10381049
TaggedField::PaymentSecret(_) => true,
10391050
_ => false,
10401051
}).count();
1041-
if payment_secret_count > 1 {
1052+
if payment_secret_count < 1 {
1053+
return Err(SemanticError::NoPaymentSecret);
1054+
} else if payment_secret_count > 1 {
10421055
return Err(SemanticError::MultiplePaymentSecrets);
10431056
}
10441057

10451058
// "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;
1059+
// (this requirement has been since removed, and we now require the payment secret
1060+
// feature bit always).
10471061
let features = self.tagged_fields().find(|&tf| match *tf {
10481062
TaggedField::Features(_) => true,
10491063
_ => false,
10501064
});
10511065
match features {
1052-
None if has_payment_secret => Err(SemanticError::InvalidFeatures),
1053-
None => Ok(()),
1066+
None => Err(SemanticError::InvalidFeatures),
10541067
Some(TaggedField::Features(features)) => {
10551068
if features.requires_unknown_bits() {
10561069
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() {
1070+
} else if !features.supports_payment_secret() {
10621071
Err(SemanticError::InvalidFeatures)
10631072
} else {
10641073
Ok(())
@@ -1147,8 +1156,8 @@ impl Invoice {
11471156
}
11481157

11491158
/// Get the payment secret if one was included in the invoice
1150-
pub fn payment_secret(&self) -> Option<&PaymentSecret> {
1151-
self.signed_invoice.payment_secret()
1159+
pub fn payment_secret(&self) -> &PaymentSecret {
1160+
self.signed_invoice.payment_secret().expect("was checked by constructor")
11521161
}
11531162

11541163
/// Get the invoice features if they were included in the invoice
@@ -1405,6 +1414,10 @@ pub enum SemanticError {
14051414
/// The invoice contains multiple descriptions and/or description hashes which isn't allowed
14061415
MultipleDescriptions,
14071416

1417+
/// The invoice is missing the mandatory payment secret, which all modern lightning nodes
1418+
/// should provide.
1419+
NoPaymentSecret,
1420+
14081421
/// The invoice contains multiple payment secrets
14091422
MultiplePaymentSecrets,
14101423

@@ -1428,6 +1441,7 @@ impl Display for SemanticError {
14281441
SemanticError::MultiplePaymentHashes => f.write_str("The invoice has multiple payment hashes which isn't allowed"),
14291442
SemanticError::NoDescription => f.write_str("No description or description hash are part of the invoice"),
14301443
SemanticError::MultipleDescriptions => f.write_str("The invoice contains multiple descriptions and/or description hashes which isn't allowed"),
1444+
SemanticError::NoPaymentSecret => f.write_str("The invoice is missing the mandatory payment secret"),
14311445
SemanticError::MultiplePaymentSecrets => f.write_str("The invoice contains multiple payment secrets"),
14321446
SemanticError::InvalidFeatures => f.write_str("The invoice's features are invalid"),
14331447
SemanticError::InvalidRecoveryId => f.write_str("The recovery id doesn't fit the signature/pub key"),
@@ -1644,23 +1658,23 @@ mod test {
16441658
let invoice = invoice_template.clone();
16451659
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_recoverable(hash, &private_key)))
16461660
}.unwrap();
1647-
assert!(Invoice::from_signed(invoice).is_ok());
1661+
assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret));
16481662

16491663
// No payment secret or feature bits
16501664
let invoice = {
16511665
let mut invoice = invoice_template.clone();
16521666
invoice.data.tagged_fields.push(Features(InvoiceFeatures::empty()).into());
16531667
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_recoverable(hash, &private_key)))
16541668
}.unwrap();
1655-
assert!(Invoice::from_signed(invoice).is_ok());
1669+
assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret));
16561670

16571671
// Missing payment secret
16581672
let invoice = {
16591673
let mut invoice = invoice_template.clone();
16601674
invoice.data.tagged_fields.push(Features(InvoiceFeatures::known()).into());
16611675
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_recoverable(hash, &private_key)))
16621676
}.unwrap();
1663-
assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::InvalidFeatures));
1677+
assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret));
16641678

16651679
// Multiple payment secrets
16661680
let invoice = {
@@ -1746,6 +1760,7 @@ mod test {
17461760

17471761
let sign_error_res = builder.clone()
17481762
.description("Test".into())
1763+
.payment_secret(PaymentSecret([0; 32]))
17491764
.try_build_signed(|_| {
17501765
Err("ImaginaryError")
17511766
});
@@ -1858,7 +1873,7 @@ mod test {
18581873
InvoiceDescription::Hash(&Sha256(sha256::Hash::from_slice(&[3;32][..]).unwrap()))
18591874
);
18601875
assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&[21;32][..]).unwrap());
1861-
assert_eq!(invoice.payment_secret(), Some(&PaymentSecret([42; 32])));
1876+
assert_eq!(invoice.payment_secret(), &PaymentSecret([42; 32]));
18621877
assert_eq!(invoice.features(), Some(&InvoiceFeatures::known()));
18631878

18641879
let raw_invoice = builder.build_raw().unwrap();
@@ -1874,6 +1889,7 @@ mod test {
18741889
let signed_invoice = InvoiceBuilder::new(Currency::Bitcoin)
18751890
.description("Test".into())
18761891
.payment_hash(sha256::Hash::from_slice(&[0;32][..]).unwrap())
1892+
.payment_secret(PaymentSecret([0; 32]))
18771893
.current_timestamp()
18781894
.build_raw()
18791895
.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();

lightning-invoice/tests/ser_de.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,17 +166,17 @@ fn test_bolt_invaoid_invoices() {
166166
assert_eq!(Invoice::from_str(
167167
"LNBC2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny"
168168
), Err(ParseOrSemanticError::ParseError(ParseError::Bech32Error(bech32::Error::MixedCase))));
169-
assert_eq!(Invoice::from_str(
169+
assert!(Invoice::from_str(
170170
"lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaxtrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspk28uwq"
171-
), Err(ParseOrSemanticError::SemanticError(SemanticError::InvalidSignature)));
171+
).is_err()); // This invoice is invalid for multiple reasons
172172
assert_eq!(Invoice::from_str(
173173
"lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6na6hlh"
174174
), Err(ParseOrSemanticError::ParseError(ParseError::TooShortDataPart)));
175175
assert_eq!(Invoice::from_str(
176176
"lnbc2500x1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpujr6jxr9gq9pv6g46y7d20jfkegkg4gljz2ea2a3m9lmvvr95tq2s0kvu70u3axgelz3kyvtp2ywwt0y8hkx2869zq5dll9nelr83zzqqpgl2zg"
177177
), Err(ParseOrSemanticError::ParseError(ParseError::UnknownSiPrefix)));
178178
eprintln!("GO");
179-
assert_eq!(Invoice::from_str(
179+
assert!(Invoice::from_str(
180180
"lnbc2500000001p1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpu7hqtk93pkf7sw55rdv4k9z2vj050rxdr6za9ekfs3nlt5lr89jqpdmxsmlj9urqumg0h9wzpqecw7th56tdms40p2ny9q4ddvjsedzcplva53s"
181-
), Err(ParseOrSemanticError::SemanticError(SemanticError::ImpreciseAmount)));
181+
).is_err()); // This invoice is invalid for multiple reasons
182182
}

0 commit comments

Comments
 (0)