+// Prefix these with `rustdoc::` when we update our MSRV to be >= 1.52 to remove warnings.
+#![deny(broken_intra_doc_links)]
+#![deny(private_intra_doc_links)]
+
#![deny(missing_docs)]
#![deny(non_upper_case_globals)]
#![deny(non_camel_case_types)]
#![deny(non_snake_case)]
#![deny(unused_mut)]
-#![deny(broken_intra_doc_links)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
extern crate alloc;
#[cfg(any(test, feature = "std"))]
extern crate core;
+#[cfg(feature = "serde")]
+extern crate serde;
#[cfg(feature = "std")]
use std::time::SystemTime;
use lightning::ln::PaymentSecret;
use lightning::ln::features::InvoiceFeatures;
#[cfg(any(doc, test))]
-use lightning::routing::network_graph::RoutingFees;
+use lightning::routing::gossip::RoutingFees;
use lightning::routing::router::RouteHint;
use lightning::util::invoice::construct_invoice_preimage;
use core::time::Duration;
use core::str;
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Deserializer,Serialize, Serializer, de::Error};
+
mod de;
mod ser;
mod tb;
/// 1. using `InvoiceBuilder`
/// 2. using `Invoice::from_signed(SignedRawInvoice)`
/// 3. using `str::parse::<Invoice>(&str)`
-#[derive(Eq, PartialEq, Debug, Clone)]
+#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct Invoice {
signed_invoice: SignedRawInvoice,
}
///
/// # Invariants
/// The hash has to be either from the deserialized invoice or from the serialized `raw_invoice`.
-#[derive(Eq, PartialEq, Debug, Clone)]
+#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct SignedRawInvoice {
/// The rawInvoice that the signature belongs to
raw_invoice: RawInvoice,
/// De- and encoding should not lead to information loss but may lead to different hashes.
///
/// For methods without docs see the corresponding methods in `Invoice`.
-#[derive(Eq, PartialEq, Debug, Clone)]
+#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct RawInvoice {
/// human readable part
pub hrp: RawHrp,
/// Data of the `RawInvoice` that is encoded in the human readable part
///
/// (C-not exported) As we don't yet support Option<Enum>
-#[derive(Eq, PartialEq, Debug, Clone)]
+#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct RawHrp {
/// The currency deferred from the 3rd and 4th character of the bech32 transaction
pub currency: Currency,
}
/// Data of the `RawInvoice` that is encoded in the data part
-#[derive(Eq, PartialEq, Debug, Clone)]
+#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct RawDataPart {
/// generation time of the invoice
pub timestamp: PositiveTimestamp,
///
/// The Unix timestamp representing the stored time has to be positive and no greater than
/// [`MAX_TIMESTAMP`].
-#[derive(Eq, PartialEq, Debug, Clone)]
+#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct PositiveTimestamp(Duration);
/// SI prefixes for the human readable part
-#[derive(Eq, PartialEq, Debug, Clone, Copy)]
+#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
pub enum SiPrefix {
/// 10^-3
Milli,
}
/// Recoverable signature
-#[derive(Clone, Debug, Eq, PartialEq)]
+#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub struct InvoiceSignature(pub RecoverableSignature);
/// Private routing information
}
/// The hash of the `RawInvoice` that was signed.
- pub fn hash(&self) -> &[u8; 32] {
+ pub fn signable_hash(&self) -> &[u8; 32] {
&self.hash
}
hash
}
- /// Calculate the hash of the encoded `RawInvoice`
- pub fn hash(&self) -> [u8; 32] {
+ /// Calculate the hash of the encoded `RawInvoice` which should be signed.
+ pub fn signable_hash(&self) -> [u8; 32] {
use bech32::ToBase32;
RawInvoice::hash_from_parts(
pub fn sign<F, E>(self, sign_method: F) -> Result<SignedRawInvoice, E>
where F: FnOnce(&Message) -> Result<RecoverableSignature, E>
{
- let raw_hash = self.hash();
+ let raw_hash = self.signable_hash();
let hash = Message::from_slice(&raw_hash[..])
.expect("Hash is 32 bytes long, same as MESSAGE_SIZE");
let signature = sign_method(&hash)?;
}
}
+#[cfg(feature = "serde")]
+impl Serialize for Invoice {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
+ serializer.serialize_str(self.to_string().as_str())
+ }
+}
+#[cfg(feature = "serde")]
+impl<'de> Deserialize<'de> for Invoice {
+ fn deserialize<D>(deserializer: D) -> Result<Invoice, D::Error> where D: Deserializer<'de> {
+ let bolt11 = String::deserialize(deserializer)?
+ .parse::<Invoice>()
+ .map_err(|e| D::Error::custom(format!("{:?}", e)))?;
+
+ Ok(bolt11)
+ }
+}
+
#[cfg(test)]
mod test {
use bitcoin_hashes::hex::FromHex;
0xd5, 0x18, 0xe1, 0xc9
];
- assert_eq!(invoice.hash(), expected_hash)
+ assert_eq!(invoice.signable_hash(), expected_hash)
}
#[test]
}.unwrap();
assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::InvalidFeatures));
+ let mut payment_secret_features = InvoiceFeatures::empty();
+ payment_secret_features.set_payment_secret_required();
+
// Including payment secret and feature bits
let invoice = {
let mut invoice = invoice_template.clone();
invoice.data.tagged_fields.push(PaymentSecret(payment_secret).into());
- invoice.data.tagged_fields.push(Features(InvoiceFeatures::known()).into());
+ invoice.data.tagged_fields.push(Features(payment_secret_features.clone()).into());
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)))
}.unwrap();
assert!(Invoice::from_signed(invoice).is_ok());
// Missing payment secret
let invoice = {
let mut invoice = invoice_template.clone();
- invoice.data.tagged_fields.push(Features(InvoiceFeatures::known()).into());
+ invoice.data.tagged_fields.push(Features(payment_secret_features).into());
invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key)))
}.unwrap();
assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret));
);
assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&[21;32][..]).unwrap());
assert_eq!(invoice.payment_secret(), &PaymentSecret([42; 32]));
- assert_eq!(invoice.features(), Some(&InvoiceFeatures::known()));
+
+ let mut expected_features = InvoiceFeatures::empty();
+ expected_features.set_variable_length_onion_required();
+ expected_features.set_payment_secret_required();
+ expected_features.set_basic_mpp_optional();
+ assert_eq!(invoice.features(), Some(&expected_features));
let raw_invoice = builder.build_raw().unwrap();
assert_eq!(raw_invoice, *invoice.into_signed_raw().raw_invoice())
assert!(invoice.would_expire(Duration::from_secs(1234567 + DEFAULT_EXPIRY_TIME + 1)));
}
+
+ #[cfg(feature = "serde")]
+ #[test]
+ fn test_serde() {
+ let invoice_str = "lnbc100p1psj9jhxdqud3jxktt5w46x7unfv9kz6mn0v3jsnp4q0d3p2sfluzdx45tqcs\
+ h2pu5qc7lgq0xs578ngs6s0s68ua4h7cvspp5q6rmq35js88zp5dvwrv9m459tnk2zunwj5jalqtyxqulh0l\
+ 5gflssp5nf55ny5gcrfl30xuhzj3nphgj27rstekmr9fw3ny5989s300gyus9qyysgqcqpcrzjqw2sxwe993\
+ h5pcm4dxzpvttgza8zhkqxpgffcrf5v25nwpr3cmfg7z54kuqq8rgqqqqqqqq2qqqqq9qq9qrzjqd0ylaqcl\
+ j9424x9m8h2vcukcgnm6s56xfgu3j78zyqzhgs4hlpzvznlugqq9vsqqqqqqqlgqqqqqeqq9qrzjqwldmj9d\
+ ha74df76zhx6l9we0vjdquygcdt3kssupehe64g6yyp5yz5rhuqqwccqqyqqqqlgqqqqjcqq9qrzjqf9e58a\
+ guqr0rcun0ajlvmzq3ek63cw2w282gv3z5uupmuwvgjtq2z55qsqqg6qqqyqqqrtnqqqzq3cqygrzjqvphms\
+ ywntrrhqjcraumvc4y6r8v4z5v593trte429v4hredj7ms5z52usqq9ngqqqqqqqlgqqqqqqgq9qrzjq2v0v\
+ p62g49p7569ev48cmulecsxe59lvaw3wlxm7r982zxa9zzj7z5l0cqqxusqqyqqqqlgqqqqqzsqygarl9fh3\
+ 8s0gyuxjjgux34w75dnc6xp2l35j7es3jd4ugt3lu0xzre26yg5m7ke54n2d5sym4xcmxtl8238xxvw5h5h5\
+ j5r6drg6k6zcqj0fcwg";
+ let invoice = invoice_str.parse::<super::Invoice>().unwrap();
+ let serialized_invoice = serde_json::to_string(&invoice).unwrap();
+ let deserialized_invoice: super::Invoice = serde_json::from_str(serialized_invoice.as_str()).unwrap();
+ assert_eq!(invoice, deserialized_invoice);
+ assert_eq!(invoice_str, deserialized_invoice.to_string().as_str());
+ assert_eq!(invoice_str, serialized_invoice.as_str().trim_matches('\"'));
+ }
}