mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-02-24 15:02:20 +01:00
Convert the invoice creation API to millisats and req it for parse
The BOLT 11 invalid invoice test vectors suggest failing to parse invoices which have an amount which is not a whole number of millisatoshis. lightning-invoice, however, happily parses such invoices. While we could continue to parse them, failing them makes for one less check on the user code side, so we might as well. In order to keep the invoice creation less likely to fail, we also switch the Builder amount-setting function to use millisatoshis.
This commit is contained in:
parent
181cb1103d
commit
0be428eeda
3 changed files with 30 additions and 10 deletions
|
@ -480,8 +480,9 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
|
|||
}
|
||||
}
|
||||
|
||||
/// Sets the amount in pico BTC. The optimal SI prefix is choosen automatically.
|
||||
pub fn amount_pico_btc(mut self, amount: u64) -> Self {
|
||||
/// Sets the amount in millisatoshis. The optimal SI prefix is chosen automatically.
|
||||
pub fn amount_milli_satoshis(mut self, amount_msat: u64) -> Self {
|
||||
let amount = amount_msat * 10; // Invoices are denominated in "pico BTC"
|
||||
let biggest_possible_si_prefix = SiPrefix::values_desc()
|
||||
.iter()
|
||||
.find(|prefix| amount % prefix.multiplier() == 0)
|
||||
|
@ -673,6 +674,7 @@ impl<S: tb::Bool> InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, S> {
|
|||
|
||||
invoice.check_field_counts().expect("should be ensured by type signature of builder");
|
||||
invoice.check_feature_bits().expect("should be ensured by type signature of builder");
|
||||
invoice.check_amount().expect("should be ensured by type signature of builder");
|
||||
|
||||
Ok(invoice)
|
||||
}
|
||||
|
@ -1019,6 +1021,16 @@ impl Invoice {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Check that amount is a whole number of millisatoshis
|
||||
fn check_amount(&self) -> Result<(), SemanticError> {
|
||||
if let Some(amount_pico_btc) = self.amount_pico_btc() {
|
||||
if amount_pico_btc % 10 != 0 {
|
||||
return Err(SemanticError::ImpreciseAmount);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check that feature bits are set as required
|
||||
fn check_feature_bits(&self) -> Result<(), SemanticError> {
|
||||
// "If the payment_secret feature is set, MUST include exactly one s field."
|
||||
|
@ -1099,6 +1111,7 @@ impl Invoice {
|
|||
invoice.check_field_counts()?;
|
||||
invoice.check_feature_bits()?;
|
||||
invoice.check_signature()?;
|
||||
invoice.check_amount()?;
|
||||
|
||||
Ok(invoice)
|
||||
}
|
||||
|
@ -1408,6 +1421,9 @@ pub enum SemanticError {
|
|||
|
||||
/// The invoice's signature is invalid
|
||||
InvalidSignature,
|
||||
|
||||
/// The invoice's amount was not a whole number of millisatoshis
|
||||
ImpreciseAmount,
|
||||
}
|
||||
|
||||
impl Display for SemanticError {
|
||||
|
@ -1421,6 +1437,7 @@ impl Display for SemanticError {
|
|||
SemanticError::InvalidFeatures => f.write_str("The invoice's features are invalid"),
|
||||
SemanticError::InvalidRecoveryId => f.write_str("The recovery id doesn't fit the signature/pub key"),
|
||||
SemanticError::InvalidSignature => f.write_str("The invoice's signature is invalid"),
|
||||
SemanticError::ImpreciseAmount => f.write_str("The invoice's amount was not a whole number of millisatoshis"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1670,7 +1687,7 @@ mod test {
|
|||
.current_timestamp();
|
||||
|
||||
let invoice = builder.clone()
|
||||
.amount_pico_btc(15000)
|
||||
.amount_milli_satoshis(1500)
|
||||
.build_raw()
|
||||
.unwrap();
|
||||
|
||||
|
@ -1679,7 +1696,7 @@ mod test {
|
|||
|
||||
|
||||
let invoice = builder.clone()
|
||||
.amount_pico_btc(1500)
|
||||
.amount_milli_satoshis(150)
|
||||
.build_raw()
|
||||
.unwrap();
|
||||
|
||||
|
@ -1810,7 +1827,7 @@ mod test {
|
|||
]);
|
||||
|
||||
let builder = InvoiceBuilder::new(Currency::BitcoinTestnet)
|
||||
.amount_pico_btc(123)
|
||||
.amount_milli_satoshis(123)
|
||||
.timestamp(UNIX_EPOCH + Duration::from_secs(1234567))
|
||||
.payee_pub_key(public_key.clone())
|
||||
.expiry_time(Duration::from_secs(54321))
|
||||
|
@ -1830,7 +1847,7 @@ mod test {
|
|||
assert!(invoice.check_signature().is_ok());
|
||||
assert_eq!(invoice.tagged_fields().count(), 10);
|
||||
|
||||
assert_eq!(invoice.amount_pico_btc(), Some(123));
|
||||
assert_eq!(invoice.amount_pico_btc(), Some(1230));
|
||||
assert_eq!(invoice.currency(), Currency::BitcoinTestnet);
|
||||
assert_eq!(
|
||||
invoice.timestamp().duration_since(UNIX_EPOCH).unwrap().as_secs(),
|
||||
|
|
|
@ -68,7 +68,7 @@ where
|
|||
.basic_mpp()
|
||||
.min_final_cltv_expiry(MIN_FINAL_CLTV_EXPIRY.into());
|
||||
if let Some(amt) = amt_msat {
|
||||
invoice = invoice.amount_pico_btc(amt * 10);
|
||||
invoice = invoice.amount_milli_satoshis(amt);
|
||||
}
|
||||
for hint in route_hints {
|
||||
invoice = invoice.private_route(hint);
|
||||
|
|
|
@ -49,7 +49,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawInvoice, Option<SemanticError>)> {
|
|||
k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch\
|
||||
9zw97j25emudupq63nyw24cg27h2rspfj9srp".to_owned(),
|
||||
InvoiceBuilder::new(Currency::Bitcoin)
|
||||
.amount_pico_btc(2500000000)
|
||||
.amount_milli_satoshis(250_000_000)
|
||||
.timestamp(UNIX_EPOCH + Duration::from_secs(1496314658))
|
||||
.payment_hash(sha256::Hash::from_hex(
|
||||
"0001020304050607080900010203040506070809000102030405060708090102"
|
||||
|
@ -78,7 +78,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawInvoice, Option<SemanticError>)> {
|
|||
dhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqscc6gd6ql3jrc5yzme8v4ntcewwz5cnw92tz0pc8qcuufvq7k\
|
||||
hhr8wpald05e92xw006sq94mg8v2ndf4sefvf9sygkshp5zfem29trqq2yxxz7".to_owned(),
|
||||
InvoiceBuilder::new(Currency::Bitcoin)
|
||||
.amount_pico_btc(20000000000)
|
||||
.amount_milli_satoshis(2_000_000_000)
|
||||
.timestamp(UNIX_EPOCH + Duration::from_secs(1496314658))
|
||||
.payment_hash(sha256::Hash::from_hex(
|
||||
"0001020304050607080900010203040506070809000102030405060708090102"
|
||||
|
@ -110,7 +110,7 @@ fn get_test_tuples() -> Vec<(String, SignedRawInvoice, Option<SemanticError>)> {
|
|||
"0001020304050607080900010203040506070809000102030405060708090102"
|
||||
).unwrap())
|
||||
.description("coffee beans".to_string())
|
||||
.amount_pico_btc(20000000000)
|
||||
.amount_milli_satoshis(2_000_000_000)
|
||||
.timestamp(UNIX_EPOCH + Duration::from_secs(1496314658))
|
||||
.payment_secret(PaymentSecret([42; 32]))
|
||||
.build_raw()
|
||||
|
@ -172,4 +172,7 @@ fn test_bolt_invalid_invoices() {
|
|||
assert_eq!(Invoice::from_str(
|
||||
"lnbc2500x1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpujr6jxr9gq9pv6g46y7d20jfkegkg4gljz2ea2a3m9lmvvr95tq2s0kvu70u3axgelz3kyvtp2ywwt0y8hkx2869zq5dll9nelr83zzqqpgl2zg"
|
||||
), Err(ParseOrSemanticError::ParseError(ParseError::UnknownSiPrefix)));
|
||||
assert_eq!(Invoice::from_str(
|
||||
"lnbc2500000001p1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpu7hqtk93pkf7sw55rdv4k9z2vj050rxdr6za9ekfs3nlt5lr89jqpdmxsmlj9urqumg0h9wzpqecw7th56tdms40p2ny9q4ddvjsedzcplva53s"
|
||||
), Err(ParseOrSemanticError::SemanticError(SemanticError::ImpreciseAmount)));
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue