Pure import of lightning-invoice crate
authorSebastian <github@gnet.me>
Wed, 7 Apr 2021 16:48:01 +0000 (12:48 -0400)
committerValentine Wallace <vwallace@protonmail.com>
Fri, 9 Apr 2021 14:08:27 +0000 (10:08 -0400)
Original repo: https://github.com/rust-bitcoin/rust-lightning-invoice

15 files changed:
lightning-invoice/.gitignore [new file with mode: 0644]
lightning-invoice/.travis-kcov.sh [new file with mode: 0644]
lightning-invoice/.travis.yml [new file with mode: 0644]
lightning-invoice/Cargo.toml [new file with mode: 0644]
lightning-invoice/LICENSE [new file with mode: 0644]
lightning-invoice/README.md [new file with mode: 0644]
lightning-invoice/fuzz/.gitignore [new file with mode: 0644]
lightning-invoice/fuzz/Cargo.toml [new file with mode: 0644]
lightning-invoice/fuzz/fuzz_targets/serde_data_part.rs [new file with mode: 0644]
lightning-invoice/fuzz/travis-fuzz.sh [new file with mode: 0755]
lightning-invoice/src/de.rs [new file with mode: 0644]
lightning-invoice/src/lib.rs [new file with mode: 0644]
lightning-invoice/src/ser.rs [new file with mode: 0644]
lightning-invoice/src/tb.rs [new file with mode: 0644]
lightning-invoice/tests/ser_de.rs [new file with mode: 0644]

diff --git a/lightning-invoice/.gitignore b/lightning-invoice/.gitignore
new file mode 100644 (file)
index 0000000..f798150
--- /dev/null
@@ -0,0 +1,3 @@
+target
+**/*.rs.bk
+Cargo.lock
\ No newline at end of file
diff --git a/lightning-invoice/.travis-kcov.sh b/lightning-invoice/.travis-kcov.sh
new file mode 100644 (file)
index 0000000..235f921
--- /dev/null
@@ -0,0 +1,14 @@
+shopt -s extglob
+
+rm -r target
+cargo test
+mkdir target/kcov target/kcov/unit target/kcov/integration target/kcov/merged
+ls target
+kcov --verify target/kcov/unit target/debug/lightning_invoice-!(*.d)
+kcov --verify target/kcov/integration target/debug/ser_de-!(*.d)
+kcov --include-pattern="$(pwd)/src" --merge target/kcov/merged target/kcov/unit target/kcov/integration
+find . -type l | xargs -n 1 rm
+
+git add -f target/kcov
+git commit -m "last kcov result"
+git push -f https://sgeisler:$GITHUB_TOKEN@github.com/rust-bitcoin/rust-lightning-invoice.git HEAD:gh-pages
\ No newline at end of file
diff --git a/lightning-invoice/.travis.yml b/lightning-invoice/.travis.yml
new file mode 100644 (file)
index 0000000..0438050
--- /dev/null
@@ -0,0 +1,31 @@
+language: rust
+sudo: required
+rust:
+  - nightly
+  - beta
+  - stable
+cache: cargo
+
+jobs:
+  include:
+  - rust: 1.29.0
+    script:
+      - cargo generate-lockfile --verbose
+      - cargo update -p cc --precise "1.0.41" --verbose
+      - cargo build
+      - cargo test
+  - stage: fuzz
+    before_install:
+      - sudo apt-get -qq update
+      - sudo apt-get install -y binutils-dev libunwind8-dev
+    rust: stable
+    script: cd fuzz && cargo test --verbose && ./travis-fuzz.sh
+  - stage: coverage
+    if: type = cron || type = push
+    before_install:
+      - sudo apt-get -qq update
+      - sudo apt-get install cmake g++ pkg-config jq libcurl4-openssl-dev libelf-dev libdw-dev binutils-dev libiberty-dev
+      - cargo install -f cargo-kcov
+      - for i in {0..10}; do echo "retry $i"; (cargo kcov --print-install-kcov-sh | sh) && break; done
+    rust: stable
+    script: bash .travis-kcov.sh
\ No newline at end of file
diff --git a/lightning-invoice/Cargo.toml b/lightning-invoice/Cargo.toml
new file mode 100644 (file)
index 0000000..e19c7ac
--- /dev/null
@@ -0,0 +1,20 @@
+[package]
+name = "lightning-invoice"
+description = "Data structures to parse and serialize BOLT11 lightning invoices"
+version = "0.4.0"
+authors = ["Sebastian Geisler <sgeisler@wh2.tu-dresden.de>"]
+license = "Apache-2.0"
+documentation = "https://docs.rs/lightning-invoice/"
+repository = "https://github.com/rust-bitcoin/rust-lightning-invoice"
+keywords = [ "lightning", "bitcoin", "invoice", "BOLT11" ]
+readme = "README.md"
+
+[dependencies]
+bech32 = "0.7"
+secp256k1 = { version = "0.20", features = ["recovery"] }
+num-traits = "0.2.8"
+bitcoin_hashes = "0.9.4"
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
diff --git a/lightning-invoice/LICENSE b/lightning-invoice/LICENSE
new file mode 100644 (file)
index 0000000..d645695
--- /dev/null
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/lightning-invoice/README.md b/lightning-invoice/README.md
new file mode 100644 (file)
index 0000000..9fb8508
--- /dev/null
@@ -0,0 +1,17 @@
+# lightning-invoice
+[![Build Status](https://travis-ci.org/rust-bitcoin/rust-lightning-invoice.svg?branch=master)](https://travis-ci.org/rust-bitcoin/rust-lightning-invoice)
+[![Coverage Report](https://img.shields.io/badge/dynamic/json.svg?label=Coverage&url=https%3A%2F%2Frust-bitcoin.github.io%2Frust-lightning-invoice%2Ftarget%2Fkcov%2Fmerged%2Fkcov-merged%2Fcoverage.json&query=%24.percent_covered&colorB=blue&suffix=%25)](https://rust-bitcoin.github.io/rust-lightning-invoice/target/kcov/merged/)
+[![Crates.io Release](https://img.shields.io/badge/crates.io-v0.4.0-orange.svg?longCache=true)](https://crates.io/crates/lightning-invoice)
+[![Docs.rs](https://docs.rs/lightning-invoice/badge.svg)](https://docs.rs/lightning-invoice/)
+
+This repo provides data structures for BOLT 11 lightning invoices and
+functions to parse and serialize these from and to bech32.
+
+**Please be sure to run the test suite since we need to check assumptions
+regarding `SystemTime`'s bounds on your platform. You can also call `check_platform`
+on startup or in your test suite to do so.**
+
+## Contributing
+* same coding style standard as [rust-bitcoin/rust-lightning](https://github.com/rust-bitcoin/rust-lightning)
+* use tabs and spaces (appropriately)
+* no unnecessary dependencies
diff --git a/lightning-invoice/fuzz/.gitignore b/lightning-invoice/fuzz/.gitignore
new file mode 100644 (file)
index 0000000..38a9008
--- /dev/null
@@ -0,0 +1,2 @@
+target
+hfuzz_*
diff --git a/lightning-invoice/fuzz/Cargo.toml b/lightning-invoice/fuzz/Cargo.toml
new file mode 100644 (file)
index 0000000..68a0d4e
--- /dev/null
@@ -0,0 +1,26 @@
+[package]
+name = "lightning-invoice-fuzz"
+version = "0.0.1"
+authors = ["Automatically generated"]
+publish = false
+
+[package.metadata]
+cargo-fuzz = true
+
+[features]
+afl_fuzz = ["afl"]
+honggfuzz_fuzz = ["honggfuzz"]
+
+[dependencies]
+honggfuzz = { version = "0.5", optional = true }
+afl = { version = "0.4", optional = true }
+lightning-invoice = { path = ".."}
+bech32 = "0.7"
+
+# Prevent this from interfering with workspaces
+[workspace]
+members = ["."]
+
+[[bin]]
+name = "serde_data_part"
+path = "fuzz_targets/serde_data_part.rs"
diff --git a/lightning-invoice/fuzz/fuzz_targets/serde_data_part.rs b/lightning-invoice/fuzz/fuzz_targets/serde_data_part.rs
new file mode 100644 (file)
index 0000000..406f967
--- /dev/null
@@ -0,0 +1,69 @@
+extern crate lightning_invoice;
+extern crate bech32;
+
+use lightning_invoice::RawDataPart;
+use bech32::{FromBase32, ToBase32, u5};
+
+fn do_test(data: &[u8]) {
+    let bech32 = data.iter().map(|x| u5::try_from_u8(x % 32).unwrap()).collect::<Vec<_>>();
+    let invoice = match RawDataPart::from_base32(&bech32) {
+        Ok(invoice) => invoice,
+        Err(_) => return,
+    };
+
+    // Our encoding is not worse than the input
+    assert!(invoice.to_base32().len() <= bech32.len());
+
+    // Our serialization is loss-less
+    assert_eq!(
+        RawDataPart::from_base32(&invoice.to_base32()).expect("faild parsing out own encoding"),
+        invoice
+    );
+}
+
+#[cfg(feature = "afl")]
+#[macro_use] extern crate afl;
+#[cfg(feature = "afl")]
+fn main() {
+    fuzz!(|data| {
+        do_test(&data);
+    });
+}
+
+#[cfg(feature = "honggfuzz")]
+#[macro_use] extern crate honggfuzz;
+#[cfg(feature = "honggfuzz")]
+fn main() {
+    loop {
+        fuzz!(|data| {
+            do_test(data);
+        });
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    fn extend_vec_from_hex(hex: &str, out: &mut Vec<u8>) {
+        let mut b = 0;
+        for (idx, c) in hex.as_bytes().iter().filter(|&&c| c != b'\n').enumerate() {
+            b <<= 4;
+            match *c {
+                b'A'...b'F' => b |= c - b'A' + 10,
+                b'a'...b'f' => b |= c - b'a' + 10,
+                b'0'...b'9' => b |= c - b'0',
+                _ => panic!("Bad hex"),
+            }
+            if (idx & 1) == 1 {
+                out.push(b);
+                b = 0;
+            }
+        }
+    }
+
+    #[test]
+    fn duplicate_crash() {
+        let mut a = Vec::new();
+        extend_vec_from_hex("000000", &mut a);
+        super::do_test(&a);
+    }
+}
diff --git a/lightning-invoice/fuzz/travis-fuzz.sh b/lightning-invoice/fuzz/travis-fuzz.sh
new file mode 100755 (executable)
index 0000000..ae85ea9
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/bash
+set -e
+cargo install --force honggfuzz
+for TARGET in fuzz_targets/*; do
+    FILENAME=$(basename $TARGET)
+       FILE="${FILENAME%.*}"
+       if [ -d hfuzz_input/$FILE ]; then
+           HFUZZ_INPUT_ARGS="-f hfuzz_input/$FILE/input"
+       fi
+       HFUZZ_BUILD_ARGS="--features honggfuzz_fuzz" HFUZZ_RUN_ARGS="-N1000000 --exit_upon_crash -v $HFUZZ_INPUT_ARGS" cargo hfuzz run $FILE
+
+       if [ -f hfuzz_workspace/$FILE/HONGGFUZZ.REPORT.TXT ]; then
+               cat hfuzz_workspace/$FILE/HONGGFUZZ.REPORT.TXT
+               for CASE in hfuzz_workspace/$FILE/SIG*; do
+                       cat $CASE | xxd -p
+               done
+               exit 1
+       fi
+done
diff --git a/lightning-invoice/src/de.rs b/lightning-invoice/src/de.rs
new file mode 100644 (file)
index 0000000..d6cb920
--- /dev/null
@@ -0,0 +1,1076 @@
+use std::error;
+use std::fmt;
+use std::fmt::{Display, Formatter};
+use std::num::ParseIntError;
+use std::str;
+use std::str::FromStr;
+
+use bech32;
+use bech32::{u5, FromBase32};
+
+use bitcoin_hashes::Hash;
+use bitcoin_hashes::sha256;
+
+use num_traits::{CheckedAdd, CheckedMul};
+
+use secp256k1;
+use secp256k1::recovery::{RecoveryId, RecoverableSignature};
+use secp256k1::key::PublicKey;
+
+use super::*;
+
+use self::hrp_sm::parse_hrp;
+
+/// State machine to parse the hrp
+mod hrp_sm {
+       use std::ops::Range;
+
+       #[derive(PartialEq, Eq, Debug)]
+       enum States {
+               Start,
+               ParseL,
+               ParseN,
+               ParseCurrencyPrefix,
+               ParseAmountNumber,
+               ParseAmountSiPrefix,
+       }
+
+       impl States {
+               fn next_state(&self, read_symbol: char) -> Result<States, super::ParseError> {
+                       match *self {
+                               States::Start => {
+                                       if read_symbol == 'l' {
+                                               Ok(States::ParseL)
+                                       } else {
+                                               Err(super::ParseError::MalformedHRP)
+                                       }
+                               }
+                               States::ParseL => {
+                                       if read_symbol == 'n' {
+                                               Ok(States::ParseN)
+                                       } else {
+                                               Err(super::ParseError::MalformedHRP)
+                                       }
+                               },
+                               States::ParseN => {
+                                       if !read_symbol.is_numeric() {
+                                               Ok(States::ParseCurrencyPrefix)
+                                       } else {
+                                               Ok(States::ParseAmountNumber)
+                                       }
+                               },
+                               States::ParseCurrencyPrefix => {
+                                       if !read_symbol.is_numeric() {
+                                               Ok(States::ParseCurrencyPrefix)
+                                       } else {
+                                               Ok(States::ParseAmountNumber)
+                                       }
+                               },
+                               States::ParseAmountNumber => {
+                                       if read_symbol.is_numeric() {
+                                               Ok(States::ParseAmountNumber)
+                                       } else if ['m', 'u', 'n', 'p'].contains(&read_symbol) {
+                                               Ok(States::ParseAmountSiPrefix)
+                                       } else {
+                                               Err(super::ParseError::MalformedHRP)
+                                       }
+                               },
+                               States::ParseAmountSiPrefix => Err(super::ParseError::MalformedHRP),
+                       }
+               }
+
+               fn is_final(&self) -> bool {
+                       !(*self == States::ParseL || *self == States::ParseN)
+               }
+       }
+
+
+       struct StateMachine {
+               state: States,
+               position: usize,
+               currency_prefix: Option<Range<usize>>,
+               amount_number: Option<Range<usize>>,
+               amount_si_prefix: Option<Range<usize>>,
+       }
+
+       impl StateMachine {
+               fn new() -> StateMachine {
+                       StateMachine {
+                               state: States::Start,
+                               position: 0,
+                               currency_prefix: None,
+                               amount_number: None,
+                               amount_si_prefix: None,
+                       }
+               }
+
+               fn update_range(range: &mut Option<Range<usize>>, position: usize) {
+                       let new_range = match *range {
+                               None => Range {start: position, end: position + 1},
+                               Some(ref r) => Range {start: r.start, end: r.end + 1},
+                       };
+                       *range = Some(new_range);
+               }
+
+               fn step(&mut self, c: char) -> Result<(), super::ParseError> {
+                       let next_state = self.state.next_state(c)?;
+                       match next_state {
+                               States::ParseCurrencyPrefix => {
+                                       StateMachine::update_range(&mut self.currency_prefix, self.position)
+                               }
+                               States::ParseAmountNumber => {
+                                       StateMachine::update_range(&mut self.amount_number, self.position)
+                               },
+                               States::ParseAmountSiPrefix => {
+                                       StateMachine::update_range(&mut self.amount_si_prefix, self.position)
+                               },
+                               _ => {}
+                       }
+
+                       self.position += 1;
+                       self.state = next_state;
+                       Ok(())
+               }
+
+               fn is_final(&self) -> bool {
+                       self.state.is_final()
+               }
+
+               fn currency_prefix(&self) -> &Option<Range<usize>> {
+                       &self.currency_prefix
+               }
+
+               fn amount_number(&self) -> &Option<Range<usize>> {
+                       &self.amount_number
+               }
+
+               fn amount_si_prefix(&self) -> &Option<Range<usize>> {
+                       &self.amount_si_prefix
+               }
+       }
+
+       pub fn parse_hrp(input: &str) -> Result<(&str, &str, &str), super::ParseError> {
+               let mut sm = StateMachine::new();
+               for c in input.chars() {
+                       sm.step(c)?;
+               }
+
+               if !sm.is_final() {
+                       return Err(super::ParseError::MalformedHRP);
+               }
+
+               let currency = sm.currency_prefix().clone()
+                       .map(|r| &input[r]).unwrap_or("");
+               let amount = sm.amount_number().clone()
+                       .map(|r| &input[r]).unwrap_or("");
+               let si = sm.amount_si_prefix().clone()
+                       .map(|r| &input[r]).unwrap_or("");
+
+               Ok((currency, amount, si))
+       }
+}
+
+
+impl FromStr for super::Currency {
+       type Err = ParseError;
+
+       fn from_str(currency_prefix: &str) -> Result<Self, ParseError> {
+               match currency_prefix {
+                       "bc" => Ok(Currency::Bitcoin),
+                       "tb" => Ok(Currency::BitcoinTestnet),
+                       "bcrt" => Ok(Currency::Regtest),
+                       "sb" => Ok(Currency::Simnet),
+                       _ => Err(ParseError::UnknownCurrency)
+               }
+       }
+}
+
+impl FromStr for SiPrefix {
+       type Err = ParseError;
+
+       fn from_str(currency_prefix: &str) -> Result<Self, ParseError> {
+               use SiPrefix::*;
+               match currency_prefix {
+                       "m" => Ok(Milli),
+                       "u" => Ok(Micro),
+                       "n" => Ok(Nano),
+                       "p" => Ok(Pico),
+                       _ => Err(ParseError::UnknownSiPrefix)
+               }
+       }
+}
+
+/// ```
+/// use lightning_invoice::Invoice;
+///
+/// let invoice = "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp\
+///    l2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d7\
+///    3gafnh3cax9rn449d9p5uxz9ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ec\
+///    ky03ylcqca784w";
+///
+/// assert!(invoice.parse::<Invoice>().is_ok());
+/// ```
+impl FromStr for Invoice {
+       type Err = ParseOrSemanticError;
+
+       fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
+               let signed = s.parse::<SignedRawInvoice>()?;
+               Ok(Invoice::from_signed(signed)?)
+       }
+}
+
+/// ```
+/// use lightning_invoice::*;
+///
+/// let invoice = "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp\
+///    l2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d7\
+///    3gafnh3cax9rn449d9p5uxz9ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ec\
+///    ky03ylcqca784w";
+///
+/// let parsed_1 = invoice.parse::<Invoice>();
+///
+/// let parsed_2 = match invoice.parse::<SignedRawInvoice>() {
+///    Ok(signed) => match Invoice::from_signed(signed) {
+///            Ok(invoice) => Ok(invoice),
+///            Err(e) => Err(ParseOrSemanticError::SemanticError(e)),
+///    },
+///    Err(e) => Err(ParseOrSemanticError::ParseError(e)),
+/// };
+///
+/// assert!(parsed_1.is_ok());
+/// assert_eq!(parsed_1, parsed_2);
+/// ```
+impl FromStr for SignedRawInvoice {
+       type Err = ParseError;
+
+       fn from_str(s: &str) -> Result<Self, Self::Err> {
+               let (hrp, data) = bech32::decode(s)?;
+
+               if data.len() < 104 {
+                       return Err(ParseError::TooShortDataPart);
+               }
+
+               let raw_hrp: RawHrp = hrp.parse()?;
+               let data_part = RawDataPart::from_base32(&data[..data.len()-104])?;
+
+               Ok(SignedRawInvoice {
+                       raw_invoice: RawInvoice {
+                               hrp: raw_hrp,
+                               data: data_part,
+                       },
+                       hash: RawInvoice::hash_from_parts(
+                               hrp.as_bytes(),
+                               &data[..data.len()-104]
+                       ),
+                       signature: Signature::from_base32(&data[data.len()-104..])?,
+               })
+       }
+}
+
+impl FromStr for RawHrp {
+       type Err = ParseError;
+
+       fn from_str(hrp: &str) -> Result<Self, <Self as FromStr>::Err> {
+               let parts = parse_hrp(hrp)?;
+
+               let currency = parts.0.parse::<Currency>()?;
+
+               let amount = if !parts.1.is_empty() {
+                       Some(parts.1.parse::<u64>()?)
+               } else {
+                       None
+               };
+
+               let si_prefix: Option<SiPrefix> = if parts.2.is_empty() {
+                       None
+               } else {
+                       let si: SiPrefix = parts.2.parse()?;
+                       if let Some(amt) = amount {
+                               if amt.checked_mul(si.multiplier()).is_none() {
+                                       return Err(ParseError::IntegerOverflowError);
+                               }
+                       }
+                       Some(si)
+               };
+
+               Ok(RawHrp {
+                       currency: currency,
+                       raw_amount: amount,
+                       si_prefix: si_prefix,
+               })
+       }
+}
+
+impl FromBase32 for RawDataPart {
+       type Err = ParseError;
+
+       fn from_base32(data: &[u5]) -> Result<Self, Self::Err> {
+               if data.len() < 7 { // timestamp length
+                       return Err(ParseError::TooShortDataPart);
+               }
+
+               let timestamp = PositiveTimestamp::from_base32(&data[0..7])?;
+               let tagged = parse_tagged_parts(&data[7..])?;
+
+               Ok(RawDataPart {
+                       timestamp: timestamp,
+                       tagged_fields: tagged,
+               })
+       }
+}
+
+impl FromBase32 for PositiveTimestamp {
+       type Err = ParseError;
+
+       fn from_base32(b32: &[u5]) -> Result<Self, Self::Err> {
+               if b32.len() != 7 {
+                       return Err(ParseError::InvalidSliceLength("PositiveTimestamp::from_base32()".into()));
+               }
+               let timestamp: u64 = parse_int_be(b32, 32)
+                       .expect("7*5bit < 64bit, no overflow possible");
+               match PositiveTimestamp::from_unix_timestamp(timestamp) {
+                       Ok(t) => Ok(t),
+                       Err(CreationError::TimestampOutOfBounds) => Err(ParseError::TimestampOverflow),
+                       Err(_) => unreachable!(),
+               }
+       }
+}
+
+impl FromBase32 for Signature {
+       type Err = ParseError;
+       fn from_base32(signature: &[u5]) -> Result<Self, Self::Err> {
+               if signature.len() != 104 {
+                       return Err(ParseError::InvalidSliceLength("Signature::from_base32()".into()));
+               }
+               let recoverable_signature_bytes = Vec::<u8>::from_base32(signature)?;
+               let signature = &recoverable_signature_bytes[0..64];
+               let recovery_id = RecoveryId::from_i32(recoverable_signature_bytes[64] as i32)?;
+
+               Ok(Signature(RecoverableSignature::from_compact(
+                       signature,
+                       recovery_id
+               )?))
+       }
+}
+
+fn parse_int_be<T, U>(digits: &[U], base: T) -> Option<T>
+       where T: CheckedAdd + CheckedMul + From<u8> + Default,
+             U: Into<u8> + Copy
+{
+       digits.iter().fold(Some(Default::default()), |acc, b|
+               acc
+                       .and_then(|x| x.checked_mul(&base))
+                       .and_then(|x| x.checked_add(&(Into::<u8>::into(*b)).into()))
+       )
+}
+
+fn parse_tagged_parts(data: &[u5]) -> Result<Vec<RawTaggedField>, ParseError> {
+       let mut parts = Vec::<RawTaggedField>::new();
+       let mut data = data;
+
+       while !data.is_empty() {
+               if data.len() < 3 {
+                       return Err(ParseError::UnexpectedEndOfTaggedFields);
+               }
+
+               // Ignore tag at data[0], it will be handled in the TaggedField parsers and
+               // parse the length to find the end of the tagged field's data
+               let len = parse_int_be(&data[1..3], 32).expect("can't overflow");
+               let last_element = 3 + len;
+
+               if data.len() < last_element {
+                       return Err(ParseError::UnexpectedEndOfTaggedFields);
+               }
+
+               // Get the tagged field's data slice
+               let field = &data[0..last_element];
+
+               // Set data slice to remaining data
+               data = &data[last_element..];
+
+               match TaggedField::from_base32(field) {
+                       Ok(field) => {
+                               parts.push(RawTaggedField::KnownSemantics(field))
+                       },
+                       Err(ParseError::Skip) => {
+                               parts.push(RawTaggedField::UnknownSemantics(field.into()))
+                       },
+                       Err(e) => {return Err(e)}
+               }
+       }
+       Ok(parts)
+}
+
+impl FromBase32 for TaggedField {
+       type Err = ParseError;
+
+       fn from_base32(field: &[u5]) -> Result<TaggedField, ParseError> {
+               if field.len() < 3 {
+                       return Err(ParseError::UnexpectedEndOfTaggedFields);
+               }
+
+               let tag = field[0];
+               let field_data =  &field[3..];
+
+               match tag.to_u8() {
+                       constants::TAG_PAYMENT_HASH =>
+                               Ok(TaggedField::PaymentHash(Sha256::from_base32(field_data)?)),
+                       constants::TAG_DESCRIPTION =>
+                               Ok(TaggedField::Description(Description::from_base32(field_data)?)),
+                       constants::TAG_PAYEE_PUB_KEY =>
+                               Ok(TaggedField::PayeePubKey(PayeePubKey::from_base32(field_data)?)),
+                       constants::TAG_DESCRIPTION_HASH =>
+                               Ok(TaggedField::DescriptionHash(Sha256::from_base32(field_data)?)),
+                       constants::TAG_EXPIRY_TIME =>
+                               Ok(TaggedField::ExpiryTime(ExpiryTime::from_base32(field_data)?)),
+                       constants::TAG_MIN_FINAL_CLTV_EXPIRY =>
+                               Ok(TaggedField::MinFinalCltvExpiry(MinFinalCltvExpiry::from_base32(field_data)?)),
+                       constants::TAG_FALLBACK =>
+                               Ok(TaggedField::Fallback(Fallback::from_base32(field_data)?)),
+                       constants::TAG_ROUTE =>
+                               Ok(TaggedField::Route(Route::from_base32(field_data)?)),
+                       constants::TAG_PAYMENT_SECRET =>
+                               Ok(TaggedField::PaymentSecret(PaymentSecret::from_base32(field_data)?)),
+                       _ => {
+                               // "A reader MUST skip over unknown fields"
+                               Err(ParseError::Skip)
+                       }
+               }
+       }
+}
+
+impl FromBase32 for Sha256 {
+       type Err = ParseError;
+
+       fn from_base32(field_data: &[u5]) -> Result<Sha256, ParseError> {
+               if field_data.len() != 52 {
+                       // "A reader MUST skip over […] a p, [or] h […] field that does not have data_length 52 […]."
+                       Err(ParseError::Skip)
+               } else {
+                       Ok(Sha256(sha256::Hash::from_slice(&Vec::<u8>::from_base32(field_data)?)
+                               .expect("length was checked before (52 u5 -> 32 u8)")))
+               }
+       }
+}
+
+impl FromBase32 for Description {
+       type Err = ParseError;
+
+       fn from_base32(field_data: &[u5]) -> Result<Description, ParseError> {
+               let bytes = Vec::<u8>::from_base32(field_data)?;
+               let description = String::from(str::from_utf8(&bytes)?);
+               Ok(Description::new(description).expect(
+                       "Max len is 639=floor(1023*5/8) since the len field is only 10bits long"
+               ))
+       }
+}
+
+impl FromBase32 for PayeePubKey {
+       type Err = ParseError;
+
+       fn from_base32(field_data: &[u5]) -> Result<PayeePubKey, ParseError> {
+               if field_data.len() != 53 {
+                       // "A reader MUST skip over […] a n […] field that does not have data_length 53 […]."
+                       Err(ParseError::Skip)
+               } else {
+                       let data_bytes = Vec::<u8>::from_base32(field_data)?;
+                       let pub_key = PublicKey::from_slice(&data_bytes)?;
+                       Ok(pub_key.into())
+               }
+       }
+}
+
+impl FromBase32 for PaymentSecret {
+       type Err = ParseError;
+
+       fn from_base32(field_data: &[u5]) -> Result<PaymentSecret, ParseError> {
+               if field_data.len() != 52 {
+                       Err(ParseError::Skip)
+               } else {
+                       let data_bytes = Vec::<u8>::from_base32(field_data)?;
+                       let mut payment_secret = [0; 32];
+                       payment_secret.copy_from_slice(&data_bytes);
+                       Ok(PaymentSecret(payment_secret))
+               }
+       }
+}
+
+impl FromBase32 for ExpiryTime {
+       type Err = ParseError;
+
+       fn from_base32(field_data: &[u5]) -> Result<ExpiryTime, ParseError> {
+               match parse_int_be::<u64, u5>(field_data, 32)
+                       .and_then(|t| ExpiryTime::from_seconds(t).ok()) // ok, since the only error is out of bounds
+               {
+                       Some(t) => Ok(t),
+                       None => Err(ParseError::IntegerOverflowError),
+               }
+       }
+}
+
+impl FromBase32 for MinFinalCltvExpiry {
+       type Err = ParseError;
+
+       fn from_base32(field_data: &[u5]) -> Result<MinFinalCltvExpiry, ParseError> {
+               let expiry = parse_int_be::<u64, u5>(field_data, 32);
+               if let Some(expiry) = expiry {
+                       Ok(MinFinalCltvExpiry(expiry))
+               } else {
+                       Err(ParseError::IntegerOverflowError)
+               }
+       }
+}
+
+impl FromBase32 for Fallback {
+       type Err = ParseError;
+
+       fn from_base32(field_data: &[u5]) -> Result<Fallback, ParseError> {
+               if field_data.len() < 1 {
+                       return Err(ParseError::UnexpectedEndOfTaggedFields);
+               }
+
+               let version = field_data[0];
+               let bytes = Vec::<u8>::from_base32(&field_data[1..])?;
+
+               match version.to_u8() {
+                       0..=16 => {
+                               if bytes.len() < 2 || bytes.len() > 40 {
+                                       return Err(ParseError::InvalidSegWitProgramLength);
+                               }
+
+                               Ok(Fallback::SegWitProgram {
+                                       version: version,
+                                       program: bytes
+                               })
+                       },
+                       17 => {
+                               if bytes.len() != 20 {
+                                       return Err(ParseError::InvalidPubKeyHashLength);
+                               }
+                               //TODO: refactor once const generics are available
+                               let mut pkh = [0u8; 20];
+                               pkh.copy_from_slice(&bytes);
+                               Ok(Fallback::PubKeyHash(pkh))
+                       }
+                       18 => {
+                               if bytes.len() != 20 {
+                                       return Err(ParseError::InvalidScriptHashLength);
+                               }
+                               let mut sh = [0u8; 20];
+                               sh.copy_from_slice(&bytes);
+                               Ok(Fallback::ScriptHash(sh))
+                       }
+                       _ => Err(ParseError::Skip)
+               }
+       }
+}
+
+impl FromBase32 for Route {
+       type Err = ParseError;
+
+       fn from_base32(field_data: &[u5]) -> Result<Route, ParseError> {
+               let bytes = Vec::<u8>::from_base32(field_data)?;
+
+               if bytes.len() % 51 != 0 {
+                       return Err(ParseError::UnexpectedEndOfTaggedFields);
+               }
+
+               let mut route_hops = Vec::<RouteHop>::new();
+
+               let mut bytes = bytes.as_slice();
+               while !bytes.is_empty() {
+                       let hop_bytes = &bytes[0..51];
+                       bytes = &bytes[51..];
+
+                       let mut channel_id: [u8; 8] = Default::default();
+                       channel_id.copy_from_slice(&hop_bytes[33..41]);
+
+                       let hop = RouteHop {
+                               pubkey: PublicKey::from_slice(&hop_bytes[0..33])?,
+                               short_channel_id: channel_id,
+                               fee_base_msat: parse_int_be(&hop_bytes[41..45], 256).expect("slice too big?"),
+                               fee_proportional_millionths: parse_int_be(&hop_bytes[45..49], 256).expect("slice too big?"),
+                               cltv_expiry_delta: parse_int_be(&hop_bytes[49..51], 256).expect("slice too big?")
+                       };
+
+                       route_hops.push(hop);
+               }
+
+               Ok(Route(route_hops))
+       }
+}
+
+/// Errors that indicate what is wrong with the invoice. They have some granularity for debug
+/// reasons, but should generally result in an "invalid BOLT11 invoice" message for the user.
+#[allow(missing_docs)]
+#[derive(PartialEq, Debug, Clone)]
+pub enum ParseError {
+       Bech32Error(bech32::Error),
+       ParseAmountError(ParseIntError),
+       MalformedSignature(secp256k1::Error),
+       BadPrefix,
+       UnknownCurrency,
+       UnknownSiPrefix,
+       MalformedHRP,
+       TooShortDataPart,
+       UnexpectedEndOfTaggedFields,
+       DescriptionDecodeError(str::Utf8Error),
+       PaddingError,
+       IntegerOverflowError,
+       InvalidSegWitProgramLength,
+       InvalidPubKeyHashLength,
+       InvalidScriptHashLength,
+       InvalidRecoveryId,
+       InvalidSliceLength(String),
+
+       /// Not an error, but used internally to signal that a part of the invoice should be ignored
+       /// according to BOLT11
+       Skip,
+       TimestampOverflow,
+}
+
+/// Indicates that something went wrong while parsing or validating the invoice. Parsing errors
+/// should be mostly seen as opaque and are only there for debugging reasons. Semantic errors
+/// like wrong signatures, missing fields etc. could mean that someone tampered with the invoice.
+#[derive(PartialEq, Debug, Clone)]
+pub enum ParseOrSemanticError {
+       /// The invoice couldn't be decoded
+       ParseError(ParseError),
+
+       /// The invoice could be decoded but violates the BOLT11 standard
+       SemanticError(::SemanticError),
+}
+
+impl Display for ParseError {
+       fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+               match *self {
+                       // TODO: find a way to combine the first three arms (e as error::Error?)
+                       ParseError::Bech32Error(ref e) => {
+                               write!(f, "Invalid bech32: {}", e)
+                       }
+                       ParseError::ParseAmountError(ref e) => {
+                               write!(f, "Invalid amount in hrp ({})", e)
+                       }
+                       ParseError::MalformedSignature(ref e) => {
+                               write!(f, "Invalid secp256k1 signature: {}", e)
+                       }
+                       ParseError::DescriptionDecodeError(ref e) => {
+                               write!(f, "Description is not a valid utf-8 string: {}", e)
+                       }
+                       ParseError::InvalidSliceLength(ref function) => {
+                               write!(f, "Slice in function {} had the wrong length", function)
+                       }
+                       ParseError::BadPrefix => f.write_str("did not begin with 'ln'"),
+                       ParseError::UnknownCurrency => f.write_str("currency code unknown"),
+                       ParseError::UnknownSiPrefix => f.write_str("unknown SI prefix"),
+                       ParseError::MalformedHRP => f.write_str("malformed human readable part"),
+                       ParseError::TooShortDataPart => {
+                               f.write_str("data part too short (should be at least 111 bech32 chars long)")
+                       },
+                       ParseError::UnexpectedEndOfTaggedFields => {
+                               f.write_str("tagged fields part ended unexpectedly")
+                       },
+                       ParseError::PaddingError => f.write_str("some data field had bad padding"),
+                       ParseError::IntegerOverflowError => {
+                               f.write_str("parsed integer doesn't fit into receiving type")
+                       },
+                       ParseError::InvalidSegWitProgramLength => {
+                               f.write_str("fallback SegWit program is too long or too short")
+                       },
+                       ParseError::InvalidPubKeyHashLength => {
+                               f.write_str("fallback public key hash has a length unequal 20 bytes")
+                       },
+                       ParseError::InvalidScriptHashLength => {
+                               f.write_str("fallback script hash has a length unequal 32 bytes")
+                       },
+                       ParseError::InvalidRecoveryId => {
+                               f.write_str("recovery id is out of range (should be in [0,3])")
+                       },
+                       ParseError::Skip => {
+                               f.write_str("the tagged field has to be skipped because of an unexpected, but allowed property")
+                       },
+                       ParseError::TimestampOverflow => {
+                f.write_str("the invoice's timestamp could not be represented as SystemTime")
+            },
+               }
+       }
+}
+
+impl Display for ParseOrSemanticError {
+       fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+               match self {
+                       ParseOrSemanticError::ParseError(err) => err.fmt(f),
+                       ParseOrSemanticError::SemanticError(err) => err.fmt(f),
+               }
+       }
+}
+
+impl error::Error for ParseError {}
+
+impl error::Error for ParseOrSemanticError {}
+
+macro_rules! from_error {
+    ($my_error:expr, $extern_error:ty) => {
+        impl From<$extern_error> for ParseError {
+            fn from(e: $extern_error) -> Self {
+                $my_error(e)
+            }
+        }
+    }
+}
+
+from_error!(ParseError::MalformedSignature, secp256k1::Error);
+from_error!(ParseError::ParseAmountError, ParseIntError);
+from_error!(ParseError::DescriptionDecodeError, str::Utf8Error);
+
+impl From<bech32::Error> for ParseError {
+       fn from(e: bech32::Error) -> Self {
+               match e {
+                       bech32::Error::InvalidPadding => ParseError::PaddingError,
+                       _ => ParseError::Bech32Error(e)
+               }
+       }
+}
+
+impl From<ParseError> for ParseOrSemanticError {
+       fn from(e: ParseError) -> Self {
+               ParseOrSemanticError::ParseError(e)
+       }
+}
+
+impl From<::SemanticError> for ParseOrSemanticError {
+       fn from(e: SemanticError) -> Self {
+               ParseOrSemanticError::SemanticError(e)
+       }
+}
+
+#[cfg(test)]
+mod test {
+       use de::ParseError;
+       use secp256k1::PublicKey;
+       use bech32::u5;
+       use bitcoin_hashes::hex::FromHex;
+       use bitcoin_hashes::sha256;
+
+       const CHARSET_REV: [i8; 128] = [
+               -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+               -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+               -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+               15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1,
+               -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
+               1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1,
+               -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
+               1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1
+       ];
+
+       fn from_bech32(bytes_5b: &[u8]) -> Vec<u5> {
+               bytes_5b
+                       .iter()
+                       .map(|c| u5::try_from_u8(CHARSET_REV[*c as usize] as u8).unwrap())
+                       .collect()
+       }
+
+       #[test]
+       fn test_parse_currency_prefix() {
+               use Currency;
+
+               assert_eq!("bc".parse::<Currency>(), Ok(Currency::Bitcoin));
+               assert_eq!("tb".parse::<Currency>(), Ok(Currency::BitcoinTestnet));
+               assert_eq!("bcrt".parse::<Currency>(), Ok(Currency::Regtest));
+               assert_eq!("sb".parse::<Currency>(), Ok(Currency::Simnet));
+               assert_eq!("something_else".parse::<Currency>(), Err(ParseError::UnknownCurrency))
+       }
+
+       #[test]
+       fn test_parse_int_from_bytes_be() {
+               use de::parse_int_be;
+
+               assert_eq!(parse_int_be::<u32, u8>(&[1, 2, 3, 4], 256), Some(16909060));
+               assert_eq!(parse_int_be::<u32, u8>(&[1, 3], 32), Some(35));
+               assert_eq!(parse_int_be::<u32, u8>(&[255, 255, 255, 255], 256), Some(4294967295));
+               assert_eq!(parse_int_be::<u32, u8>(&[1, 0, 0, 0, 0], 256), None);
+       }
+
+       #[test]
+       fn test_parse_sha256_hash() {
+               use Sha256;
+               use bech32::FromBase32;
+
+               let input = from_bech32(
+                       "qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypq".as_bytes()
+               );
+
+               let hash = sha256::Hash::from_hex(
+                       "0001020304050607080900010203040506070809000102030405060708090102"
+               ).unwrap();
+               let expected = Ok(Sha256(hash));
+
+               assert_eq!(Sha256::from_base32(&input), expected);
+
+               // make sure hashes of unknown length get skipped
+               let input_unexpected_length = from_bech32(
+                       "qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypyq".as_bytes()
+               );
+               assert_eq!(Sha256::from_base32(&input_unexpected_length), Err(ParseError::Skip));
+       }
+
+       #[test]
+       fn test_parse_description() {
+               use ::Description;
+               use bech32::FromBase32;
+
+               let input = from_bech32("xysxxatsyp3k7enxv4js".as_bytes());
+               let expected = Ok(Description::new("1 cup coffee".to_owned()).unwrap());
+               assert_eq!(Description::from_base32(&input), expected);
+       }
+
+       #[test]
+       fn test_parse_payee_pub_key() {
+               use ::PayeePubKey;
+               use bech32::FromBase32;
+
+               let input = from_bech32("q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66".as_bytes());
+               let pk_bytes = [
+                       0x03, 0xe7, 0x15, 0x6a, 0xe3, 0x3b, 0x0a, 0x20, 0x8d, 0x07, 0x44, 0x19, 0x91, 0x63,
+                       0x17, 0x7e, 0x90, 0x9e, 0x80, 0x17, 0x6e, 0x55, 0xd9, 0x7a, 0x2f, 0x22, 0x1e, 0xde,
+                       0x0f, 0x93, 0x4d, 0xd9, 0xad
+               ];
+               let expected = Ok(PayeePubKey(
+                       PublicKey::from_slice(&pk_bytes[..]).unwrap()
+               ));
+
+               assert_eq!(PayeePubKey::from_base32(&input), expected);
+
+               // expects 33 bytes
+               let input_unexpected_length = from_bech32(
+                       "q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhvq".as_bytes()
+               );
+               assert_eq!(PayeePubKey::from_base32(&input_unexpected_length), Err(ParseError::Skip));
+       }
+
+       #[test]
+       fn test_parse_expiry_time() {
+               use ::ExpiryTime;
+               use bech32::FromBase32;
+
+               let input = from_bech32("pu".as_bytes());
+               let expected = Ok(ExpiryTime::from_seconds(60).unwrap());
+               assert_eq!(ExpiryTime::from_base32(&input), expected);
+
+               let input_too_large = from_bech32("sqqqqqqqqqqqq".as_bytes());
+               assert_eq!(ExpiryTime::from_base32(&input_too_large), Err(ParseError::IntegerOverflowError));
+       }
+
+       #[test]
+       fn test_parse_min_final_cltv_expiry() {
+               use ::MinFinalCltvExpiry;
+               use bech32::FromBase32;
+
+               let input = from_bech32("pr".as_bytes());
+               let expected = Ok(MinFinalCltvExpiry(35));
+
+               assert_eq!(MinFinalCltvExpiry::from_base32(&input), expected);
+       }
+
+       #[test]
+       fn test_parse_fallback() {
+               use Fallback;
+               use bech32::FromBase32;
+
+               let cases = vec![
+                       (
+                               from_bech32("3x9et2e20v6pu37c5d9vax37wxq72un98".as_bytes()),
+                               Ok(Fallback::PubKeyHash([
+                                       0x31, 0x72, 0xb5, 0x65, 0x4f, 0x66, 0x83, 0xc8, 0xfb, 0x14, 0x69, 0x59, 0xd3,
+                                       0x47, 0xce, 0x30, 0x3c, 0xae, 0x4c, 0xa7
+                               ]))
+                       ),
+                       (
+                               from_bech32("j3a24vwu6r8ejrss3axul8rxldph2q7z9".as_bytes()),
+                               Ok(Fallback::ScriptHash([
+                                       0x8f, 0x55, 0x56, 0x3b, 0x9a, 0x19, 0xf3, 0x21, 0xc2, 0x11, 0xe9, 0xb9, 0xf3,
+                                       0x8c, 0xdf, 0x68, 0x6e, 0xa0, 0x78, 0x45
+                               ]))
+                       ),
+                       (
+                               from_bech32("qw508d6qejxtdg4y5r3zarvary0c5xw7k".as_bytes()),
+                               Ok(Fallback::SegWitProgram {
+                                       version: u5::try_from_u8(0).unwrap(),
+                                       program: Vec::from(&[
+                                               0x75u8, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c, 0x45,
+                                               0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6
+                                       ][..])
+                               })
+                       ),
+                       (
+                               vec![u5::try_from_u8(21).unwrap(); 41],
+                               Err(ParseError::Skip)
+                       ),
+                       (
+                               vec![],
+                               Err(ParseError::UnexpectedEndOfTaggedFields)
+                       ),
+                       (
+                               vec![u5::try_from_u8(1).unwrap(); 81],
+                               Err(ParseError::InvalidSegWitProgramLength)
+                       ),
+                       (
+                               vec![u5::try_from_u8(17).unwrap(); 1],
+                               Err(ParseError::InvalidPubKeyHashLength)
+                       ),
+                       (
+                               vec![u5::try_from_u8(18).unwrap(); 1],
+                               Err(ParseError::InvalidScriptHashLength)
+                       )
+               ];
+
+               for (input, expected) in cases.into_iter() {
+                       assert_eq!(Fallback::from_base32(&input), expected);
+               }
+       }
+
+       #[test]
+       fn test_parse_route() {
+               use RouteHop;
+               use ::Route;
+               use bech32::FromBase32;
+
+               let input = from_bech32(
+                       "q20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqa\
+                       fqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4grqszsvpcgpy9qqqqqqgqqqqq7qqzq".as_bytes()
+               );
+
+               let mut expected = Vec::<RouteHop>::new();
+               expected.push(RouteHop {
+                       pubkey: PublicKey::from_slice(
+                               &[
+                                       0x02u8, 0x9e, 0x03, 0xa9, 0x01, 0xb8, 0x55, 0x34, 0xff, 0x1e, 0x92, 0xc4, 0x3c,
+                                       0x74, 0x43, 0x1f, 0x7c, 0xe7, 0x20, 0x46, 0x06, 0x0f, 0xcf, 0x7a, 0x95, 0xc3,
+                                       0x7e, 0x14, 0x8f, 0x78, 0xc7, 0x72, 0x55
+                               ][..]
+                       ).unwrap(),
+                       short_channel_id: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08],
+                       fee_base_msat: 1,
+                       fee_proportional_millionths: 20,
+                       cltv_expiry_delta: 3
+               });
+               expected.push(RouteHop {
+                       pubkey: PublicKey::from_slice(
+                               &[
+                                       0x03u8, 0x9e, 0x03, 0xa9, 0x01, 0xb8, 0x55, 0x34, 0xff, 0x1e, 0x92, 0xc4, 0x3c,
+                                       0x74, 0x43, 0x1f, 0x7c, 0xe7, 0x20, 0x46, 0x06, 0x0f, 0xcf, 0x7a, 0x95, 0xc3,
+                                       0x7e, 0x14, 0x8f, 0x78, 0xc7, 0x72, 0x55
+                               ][..]
+                       ).unwrap(),
+                       short_channel_id: [0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a],
+                       fee_base_msat: 2,
+                       fee_proportional_millionths: 30,
+                       cltv_expiry_delta: 4
+               });
+
+               assert_eq!(Route::from_base32(&input), Ok(Route(expected)));
+
+               assert_eq!(
+                       Route::from_base32(&[u5::try_from_u8(0).unwrap(); 40][..]),
+                       Err(ParseError::UnexpectedEndOfTaggedFields)
+               );
+       }
+
+       #[test]
+       fn test_payment_secret_deserialization() {
+               use bech32::CheckBase32;
+               use secp256k1::recovery::{RecoveryId, RecoverableSignature};
+               use TaggedField::*;
+               use {SiPrefix, SignedRawInvoice, Signature, RawInvoice, RawTaggedField, RawHrp, RawDataPart,
+                                Currency, Sha256, PositiveTimestamp};
+
+               assert_eq!( // BOLT 11 payment secret invoice. The unknown fields are invoice features.
+                       "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q5sqqqqqqqqqqqqqqqpqsq67gye39hfg3zd8rgc80k32tvy9xk2xunwm5lzexnvpx6fd77en8qaq424dxgt56cag2dpt359k3ssyhetktkpqh24jqnjyw6uqd08sgptq44qu".parse(),
+                       Ok(SignedRawInvoice {
+                                       raw_invoice: RawInvoice {
+                                               hrp: RawHrp {
+                                                       currency: Currency::Bitcoin,
+                                                       raw_amount: Some(25),
+                                                       si_prefix: Some(SiPrefix::Milli)
+                                               },
+                                               data: RawDataPart {
+                                                       timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(),
+                                                       tagged_fields: vec ! [
+                                                               PaymentHash(Sha256(sha256::Hash::from_hex(
+                                                                       "0001020304050607080900010203040506070809000102030405060708090102"
+                                                               ).unwrap())).into(),
+                                                               Description(::Description::new("coffee beans".to_owned()).unwrap()).into(),
+                                                               PaymentSecret(::PaymentSecret([17; 32])).into(),
+                                                               RawTaggedField::UnknownSemantics(vec![5, 0, 20, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                                                                                                                                                                                                                       0, 0, 0, 0, 1, 0, 16,
+                                                                                                                                                                                                                       0].check_base32().unwrap())],
+                                                                       }
+                                                               },
+                                       hash: [0xb1, 0x96, 0x46, 0xc3, 0xbc, 0x56, 0x76, 0x1d, 0x20, 0x65, 0x6e, 0x0e, 0x32,
+                                                                       0xec, 0xd2, 0x69, 0x27, 0xb7, 0x62, 0x6e, 0x2a, 0x8b, 0xe6, 0x97, 0x71, 0x9f,
+                                                                       0xf8, 0x7e, 0x44, 0x54, 0x55, 0xb9],
+                                       signature: Signature(RecoverableSignature::from_compact(
+                                                                               &[0xd7, 0x90, 0x4c, 0xc4, 0xb7, 0x4a, 0x22, 0x26, 0x9c, 0x68, 0xc1, 0xdf, 0x68,
+                                                                                       0xa9, 0x6c, 0x21, 0x4d, 0x65, 0x1b, 0x93, 0x76, 0xe9, 0xf1, 0x64, 0xd3, 0x60,
+                                                                                       0x4d, 0xa4, 0xb7, 0xde, 0xcc, 0xce, 0x0e, 0x82, 0xaa, 0xab, 0x4c, 0x85, 0xd3,
+                                                                                       0x58, 0xea, 0x14, 0xd0, 0xae, 0x34, 0x2d, 0xa3, 0x08, 0x12, 0xf9, 0x5d, 0x97,
+                                                                                       0x60, 0x82, 0xea, 0xac, 0x81, 0x39, 0x11, 0xda, 0xe0, 0x1a, 0xf3, 0xc1],
+                                                                               RecoveryId::from_i32(1).unwrap()
+                                                               ).unwrap()),
+                       })
+               )
+       }
+
+       #[test]
+       fn test_raw_signed_invoice_deserialization() {
+               use TaggedField::*;
+               use secp256k1::recovery::{RecoveryId, RecoverableSignature};
+               use {SignedRawInvoice, Signature, RawInvoice, RawHrp, RawDataPart, Currency, Sha256,
+                        PositiveTimestamp};
+
+               assert_eq!(
+                       "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmw\
+                       wd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d73gafnh3cax9rn449d9p5uxz9\
+                       ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ecky03ylcqca784w".parse(),
+                       Ok(SignedRawInvoice {
+                               raw_invoice: RawInvoice {
+                                       hrp: RawHrp {
+                                               currency: Currency::Bitcoin,
+                                               raw_amount: None,
+                                               si_prefix: None,
+                                       },
+                                       data: RawDataPart {
+                                       timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(),
+                                       tagged_fields: vec ! [
+                                               PaymentHash(Sha256(sha256::Hash::from_hex(
+                                                       "0001020304050607080900010203040506070809000102030405060708090102"
+                                               ).unwrap())).into(),
+                                               Description(
+                                                       ::Description::new(
+                                                               "Please consider supporting this project".to_owned()
+                                                       ).unwrap()
+                                               ).into(),
+                                       ],
+                                       },
+                                       },
+                               hash: [
+                                       0xc3, 0xd4, 0xe8, 0x3f, 0x64, 0x6f, 0xa7, 0x9a, 0x39, 0x3d, 0x75, 0x27,
+                                       0x7b, 0x1d, 0x85, 0x8d, 0xb1, 0xd1, 0xf7, 0xab, 0x71, 0x37, 0xdc, 0xb7,
+                                       0x83, 0x5d, 0xb2, 0xec, 0xd5, 0x18, 0xe1, 0xc9
+                               ],
+                               signature: Signature(RecoverableSignature::from_compact(
+                                       & [
+                                               0x38u8, 0xec, 0x68, 0x91, 0x34, 0x5e, 0x20, 0x41, 0x45, 0xbe, 0x8a,
+                                               0x3a, 0x99, 0xde, 0x38, 0xe9, 0x8a, 0x39, 0xd6, 0xa5, 0x69, 0x43,
+                                               0x4e, 0x18, 0x45, 0xc8, 0xaf, 0x72, 0x05, 0xaf, 0xcf, 0xcc, 0x7f,
+                                               0x42, 0x5f, 0xcd, 0x14, 0x63, 0xe9, 0x3c, 0x32, 0x88, 0x1e, 0xad,
+                                               0x0d, 0x6e, 0x35, 0x6d, 0x46, 0x7e, 0xc8, 0xc0, 0x25, 0x53, 0xf9,
+                                               0xaa, 0xb1, 0x5e, 0x57, 0x38, 0xb1, 0x1f, 0x12, 0x7f
+                                       ],
+                                       RecoveryId::from_i32(0).unwrap()
+                               ).unwrap()),
+                               }
+                       )
+               )
+       }
+}
diff --git a/lightning-invoice/src/lib.rs b/lightning-invoice/src/lib.rs
new file mode 100644 (file)
index 0000000..62e5bf1
--- /dev/null
@@ -0,0 +1,1581 @@
+#![deny(missing_docs)]
+#![deny(non_upper_case_globals)]
+#![deny(non_camel_case_types)]
+#![deny(non_snake_case)]
+#![deny(unused_mut)]
+
+#![cfg_attr(feature = "strict", deny(warnings))]
+
+//! This crate provides data structures to represent
+//! [lightning BOLT11](https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md)
+//! invoices and functions to create, encode and decode these. If you just want to use the standard
+//! en-/decoding functionality this should get you started:
+//!
+//!   * For parsing use `str::parse::<Invoice>(&self)` (see the docs of `impl FromStr for Invoice`)
+//!   * For constructing invoices use the `InvoiceBuilder`
+//!   * For serializing invoices use the `Display`/`ToString` traits
+
+extern crate bech32;
+extern crate bitcoin_hashes;
+extern crate num_traits;
+extern crate secp256k1;
+
+use bech32::u5;
+use bitcoin_hashes::Hash;
+use bitcoin_hashes::sha256;
+
+use secp256k1::key::PublicKey;
+use secp256k1::{Message, Secp256k1};
+use secp256k1::recovery::RecoverableSignature;
+use std::ops::Deref;
+
+use std::iter::FilterMap;
+use std::slice::Iter;
+use std::time::{SystemTime, Duration, UNIX_EPOCH};
+use std::fmt::{Display, Formatter, self};
+
+mod de;
+mod ser;
+mod tb;
+
+pub use de::{ParseError, ParseOrSemanticError};
+
+// TODO: fix before 2037 (see rust PR #55527)
+/// Defines the maximum UNIX timestamp that can be represented as `SystemTime`. This is checked by
+/// one of the unit tests, please run them.
+const SYSTEM_TIME_MAX_UNIX_TIMESTAMP: u64 = std::i32::MAX as u64;
+
+/// Allow the expiry time to be up to one year. Since this reduces the range of possible timestamps
+/// it should be rather low as long as we still have to support 32bit time representations
+const MAX_EXPIRY_TIME: u64 = 60 * 60 * 24 * 356;
+
+/// This function is used as a static assert for the size of `SystemTime`. If the crate fails to
+/// compile due to it this indicates that your system uses unexpected bounds for `SystemTime`. You
+/// can remove this functions and run the test `test_system_time_bounds_assumptions`. In any case,
+/// please open an issue. If all tests pass you should be able to use this library safely by just
+/// removing this function till we patch it accordingly.
+fn __system_time_size_check() {
+       // Use 2 * sizeof(u64) as expected size since the expected underlying implementation is storing
+       // a `Duration` since `SystemTime::UNIX_EPOCH`.
+       unsafe { std::mem::transmute::<SystemTime, [u8; 16]>(UNIX_EPOCH); }
+}
+
+
+/// **Call this function on startup to ensure that all assumptions about the platform are valid.**
+///
+/// Unfortunately we have to make assumptions about the upper bounds of the `SystemTime` type on
+/// your platform which we can't fully verify at compile time and which isn't part of it's contract.
+/// To our best knowledge our assumptions hold for all platforms officially supported by rust, but
+/// since this check is fast we recommend to do it anyway.
+///
+/// If this function fails this is considered a bug. Please open an issue describing your
+/// platform and stating your current system time.
+///
+/// # Panics
+/// If the check fails this function panics. By calling this function on startup you ensure that
+/// this wont happen at an arbitrary later point in time.
+pub fn check_platform() {
+    // The upper and lower bounds of `SystemTime` are not part of its public contract and are
+    // platform specific. That's why we have to test if our assumptions regarding these bounds
+    // hold on the target platform.
+    //
+    // If this test fails on your platform, please don't use the library and open an issue
+    // instead so we can resolve the situation. Currently this library is tested on:
+    //   * Linux (64bit)
+    let fail_date = UNIX_EPOCH + Duration::from_secs(SYSTEM_TIME_MAX_UNIX_TIMESTAMP);
+    let year = Duration::from_secs(60 * 60 * 24 * 365);
+
+    // Make sure that the library will keep working for another year
+    assert!(fail_date.duration_since(SystemTime::now()).unwrap() > year);
+
+    let max_ts = PositiveTimestamp::from_unix_timestamp(
+        SYSTEM_TIME_MAX_UNIX_TIMESTAMP - MAX_EXPIRY_TIME
+    ).unwrap();
+    let max_exp = ::ExpiryTime::from_seconds(MAX_EXPIRY_TIME).unwrap();
+
+    assert_eq!(
+        (*max_ts.as_time() + *max_exp.as_duration()).duration_since(UNIX_EPOCH).unwrap().as_secs(),
+        SYSTEM_TIME_MAX_UNIX_TIMESTAMP
+    );
+}
+
+
+/// Builder for `Invoice`s. It's the most convenient and advised way to use this library. It ensures
+/// that only a semantically and syntactically correct Invoice can be built using it.
+///
+/// ```
+/// extern crate secp256k1;
+/// extern crate lightning_invoice;
+/// extern crate bitcoin_hashes;
+///
+/// use bitcoin_hashes::Hash;
+/// use bitcoin_hashes::sha256;
+///
+/// use secp256k1::Secp256k1;
+/// use secp256k1::key::SecretKey;
+///
+/// use lightning_invoice::{Currency, InvoiceBuilder};
+///
+/// # fn main() {
+/// let private_key = SecretKey::from_slice(
+///            &[
+///                    0xe1, 0x26, 0xf6, 0x8f, 0x7e, 0xaf, 0xcc, 0x8b, 0x74, 0xf5, 0x4d, 0x26, 0x9f,
+///                    0xe2, 0x06, 0xbe, 0x71, 0x50, 0x00, 0xf9, 0x4d, 0xac, 0x06, 0x7d, 0x1c, 0x04,
+///            0xa8, 0xca, 0x3b, 0x2d, 0xb7, 0x34
+///    ][..]
+///    ).unwrap();
+///
+/// let payment_hash = sha256::Hash::from_slice(&[0; 32][..]).unwrap();
+///
+/// let invoice = InvoiceBuilder::new(Currency::Bitcoin)
+///    .description("Coins pls!".into())
+///    .payment_hash(payment_hash)
+///    .current_timestamp()
+///    .build_signed(|hash| {
+///            Secp256k1::new().sign_recoverable(hash, &private_key)
+///    })
+///    .unwrap();
+///
+/// assert!(invoice.to_string().starts_with("lnbc1"));
+/// # }
+/// ```
+///
+/// # Type parameters
+/// The two parameters `D` and `H` signal if the builder already contains the correct amount of the
+/// given field:
+///  * `D`: exactly one `Description` or `DescriptionHash`
+///  * `H`: exactly one `PaymentHash`
+///  * `T`: the timestamp is set
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct InvoiceBuilder<D: tb::Bool, H: tb::Bool, T: tb::Bool> {
+       currency: Currency,
+       amount: Option<u64>,
+       si_prefix: Option<SiPrefix>,
+       timestamp: Option<PositiveTimestamp>,
+       tagged_fields: Vec<TaggedField>,
+       error: Option<CreationError>,
+
+       phantom_d: std::marker::PhantomData<D>,
+       phantom_h: std::marker::PhantomData<H>,
+       phantom_t: std::marker::PhantomData<T>,
+}
+
+/// Represents a syntactically and semantically correct lightning BOLT11 invoice.
+///
+/// There are three ways to construct an `Invoice`:
+///  1. using `InvoiceBuilder`
+///  2. using `Invoice::from_signed(SignedRawInvoice)`
+///  3. using `str::parse::<Invoice>(&str)`
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct Invoice {
+       signed_invoice: SignedRawInvoice,
+}
+
+/// Represents the description of an invoice which has to be either a directly included string or
+/// a hash of a description provided out of band.
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub enum InvoiceDescription<'f> {
+       /// Reference to the directly supplied description in the invoice
+       Direct(&'f Description),
+
+       /// Reference to the description's hash included in the invoice
+       Hash(&'f Sha256),
+}
+
+/// Represents a signed `RawInvoice` with cached hash. The signature is not checked and may be
+/// invalid.
+///
+/// # Invariants
+/// The hash has to be either from the deserialized invoice or from the serialized `raw_invoice`.
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct SignedRawInvoice {
+       /// The rawInvoice that the signature belongs to
+       raw_invoice: RawInvoice,
+
+       /// Hash of the `RawInvoice` that will be used to check the signature.
+       ///
+       /// * if the `SignedRawInvoice` was deserialized the hash is of from the original encoded form,
+       /// since it's not guaranteed that encoding it again will lead to the same result since integers
+       /// could have been encoded with leading zeroes etc.
+       /// * if the `SignedRawInvoice` was constructed manually the hash will be the calculated hash
+       /// from the `RawInvoice`
+       hash: [u8; 32],
+
+       /// signature of the payment request
+       signature: Signature,
+}
+
+/// Represents an syntactically correct Invoice for a payment on the lightning network,
+/// but without the signature information.
+/// 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)]
+pub struct RawInvoice {
+       /// human readable part
+       pub hrp: RawHrp,
+
+       /// data part
+       pub data: RawDataPart,
+}
+
+/// Data of the `RawInvoice` that is encoded in the human readable part
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct RawHrp {
+       /// The currency deferred from the 3rd and 4th character of the bech32 transaction
+       pub currency: Currency,
+
+       /// The amount that, multiplied by the SI prefix, has to be payed
+       pub raw_amount: Option<u64>,
+
+       /// SI prefix that gets multiplied with the `raw_amount`
+       pub si_prefix: Option<SiPrefix>,
+}
+
+/// Data of the `RawInvoice` that is encoded in the data part
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct RawDataPart {
+       /// generation time of the invoice
+       pub timestamp: PositiveTimestamp,
+
+       /// tagged fields of the payment request
+       pub tagged_fields: Vec<RawTaggedField>,
+}
+
+/// A timestamp that refers to a date after 1 January 1970 which means its representation as UNIX
+/// timestamp is positive.
+///
+/// # Invariants
+/// The UNIX timestamp representing the stored time has to be positive and small enough so that
+/// a `EpiryTime` can be added to it without an overflow.
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct PositiveTimestamp(SystemTime);
+
+/// SI prefixes for the human readable part
+#[derive(Eq, PartialEq, Debug, Clone, Copy)]
+pub enum SiPrefix {
+       /// 10^-3
+       Milli,
+       /// 10^-6
+       Micro,
+       /// 10^-9
+       Nano,
+       /// 10^-12
+       Pico,
+}
+
+impl SiPrefix {
+       /// Returns the multiplier to go from a BTC value to picoBTC implied by this SiPrefix.
+       /// This is effectively 10^12 * the prefix multiplier
+       pub fn multiplier(&self) -> u64 {
+               match *self {
+                       SiPrefix::Milli => 1_000_000_000,
+                       SiPrefix::Micro => 1_000_000,
+                       SiPrefix::Nano => 1_000,
+                       SiPrefix::Pico => 1,
+               }
+       }
+
+       /// Returns all enum variants of `SiPrefix` sorted in descending order of their associated
+       /// multiplier.
+       pub fn values_desc() -> &'static [SiPrefix] {
+               use SiPrefix::*;
+               static VALUES: [SiPrefix; 4] = [Milli, Micro, Nano, Pico];
+               &VALUES
+       }
+}
+
+/// Enum representing the crypto currencies (or networks) supported by this library
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub enum Currency {
+       /// Bitcoin mainnet
+       Bitcoin,
+
+       /// Bitcoin testnet
+       BitcoinTestnet,
+
+       /// Bitcoin regtest
+       Regtest,
+
+       /// Bitcoin simnet/signet
+       Simnet,
+}
+
+/// Tagged field which may have an unknown tag
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub enum RawTaggedField {
+       /// Parsed tagged field with known tag
+       KnownSemantics(TaggedField),
+       /// tagged field which was not parsed due to an unknown tag or undefined field semantics
+       UnknownSemantics(Vec<u5>),
+}
+
+/// Tagged field with known tag
+///
+/// For descriptions of the enum values please refer to the enclosed type's docs.
+#[allow(missing_docs)]
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub enum TaggedField {
+       PaymentHash(Sha256),
+       Description(Description),
+       PayeePubKey(PayeePubKey),
+       DescriptionHash(Sha256),
+       ExpiryTime(ExpiryTime),
+       MinFinalCltvExpiry(MinFinalCltvExpiry),
+       Fallback(Fallback),
+       Route(Route),
+       PaymentSecret(PaymentSecret),
+}
+
+/// SHA-256 hash
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct Sha256(pub sha256::Hash);
+
+/// Description string
+///
+/// # Invariants
+/// The description can be at most 639 __bytes__ long
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct Description(String);
+
+/// Payee public key
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct PayeePubKey(pub PublicKey);
+
+/// 256-bit payment secret
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct PaymentSecret(pub [u8; 32]);
+
+/// Positive duration that defines when (relatively to the timestamp) in the future the invoice
+/// expires
+///
+/// # Invariants
+/// The number of seconds this expiry time represents has to be in the range
+/// `0...(SYSTEM_TIME_MAX_UNIX_TIMESTAMP - MAX_EXPIRY_TIME)` to avoid overflows when adding it to a
+/// timestamp
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct ExpiryTime(Duration);
+
+/// `min_final_cltv_expiry` to use for the last HTLC in the route
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct MinFinalCltvExpiry(pub u64);
+
+// TODO: better types instead onf byte arrays
+/// Fallback address in case no LN payment is possible
+#[allow(missing_docs)]
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub enum Fallback {
+       SegWitProgram {
+               version: u5,
+               program: Vec<u8>,
+       },
+       PubKeyHash([u8; 20]),
+       ScriptHash([u8; 20]),
+}
+
+/// Recoverable signature
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct Signature(pub RecoverableSignature);
+
+/// Private routing information
+///
+/// # Invariants
+/// The encoded route has to be <1024 5bit characters long (<=639 bytes or <=12 hops)
+///
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct Route(Vec<RouteHop>);
+
+/// Node on a private route
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct RouteHop {
+       /// Node's public key
+       pub pubkey: PublicKey,
+
+       /// Which channel of this node we would be using
+       pub short_channel_id: [u8; 8],
+
+       /// Fee charged by this node per transaction
+       pub fee_base_msat: u32,
+
+       /// Fee charged by this node proportional to the amount routed
+       pub fee_proportional_millionths: u32,
+
+       /// Delta substracted by this node from incoming cltv_expiry value
+       pub cltv_expiry_delta: u16,
+}
+
+/// Tag constants as specified in BOLT11
+#[allow(missing_docs)]
+pub mod constants {
+       pub const TAG_PAYMENT_HASH: u8 = 1;
+       pub const TAG_DESCRIPTION: u8 = 13;
+       pub const TAG_PAYEE_PUB_KEY: u8 = 19;
+       pub const TAG_DESCRIPTION_HASH: u8 = 23;
+       pub const TAG_EXPIRY_TIME: u8 = 6;
+       pub const TAG_MIN_FINAL_CLTV_EXPIRY: u8 = 24;
+       pub const TAG_FALLBACK: u8 = 9;
+       pub const TAG_ROUTE: u8 = 3;
+       pub const TAG_PAYMENT_SECRET: u8 = 16;
+}
+
+impl InvoiceBuilder<tb::False, tb::False, tb::False> {
+       /// Construct new, empty `InvoiceBuilder`. All necessary fields have to be filled first before
+       /// `InvoiceBuilder::build(self)` becomes available.
+       pub fn new(currrency: Currency) -> Self {
+               InvoiceBuilder {
+                       currency: currrency,
+                       amount: None,
+                       si_prefix: None,
+                       timestamp: None,
+                       tagged_fields: Vec::new(),
+                       error: None,
+
+                       phantom_d: std::marker::PhantomData,
+                       phantom_h: std::marker::PhantomData,
+                       phantom_t: std::marker::PhantomData,
+               }
+       }
+}
+
+impl<D: tb::Bool, H: tb::Bool, T: tb::Bool> InvoiceBuilder<D, H, T> {
+       /// Helper function to set the completeness flags.
+       fn set_flags<DN: tb::Bool, HN: tb::Bool, TN: tb::Bool>(self) -> InvoiceBuilder<DN, HN, TN> {
+               InvoiceBuilder::<DN, HN, TN> {
+                       currency: self.currency,
+                       amount: self.amount,
+                       si_prefix: self.si_prefix,
+                       timestamp: self.timestamp,
+                       tagged_fields: self.tagged_fields,
+                       error: self.error,
+
+                       phantom_d: std::marker::PhantomData,
+                       phantom_h: std::marker::PhantomData,
+                       phantom_t: std::marker::PhantomData,
+               }
+       }
+
+       /// Sets the amount in pico BTC. The optimal SI prefix is choosen automatically.
+       pub fn amount_pico_btc(mut self, amount: u64) -> Self {
+               let biggest_possible_si_prefix = SiPrefix::values_desc()
+                       .iter()
+                       .find(|prefix| amount % prefix.multiplier() == 0)
+                       .expect("Pico should always match");
+               self.amount = Some(amount / biggest_possible_si_prefix.multiplier());
+               self.si_prefix = Some(*biggest_possible_si_prefix);
+               self
+       }
+
+       /// Sets the payee's public key.
+       pub fn payee_pub_key(mut self, pub_key: PublicKey) -> Self {
+               self.tagged_fields.push(TaggedField::PayeePubKey(PayeePubKey(pub_key)));
+               self
+       }
+
+       /// Sets the payment secret
+       pub fn payment_secret(mut self, payment_secret: PaymentSecret) -> Self {
+               self.tagged_fields.push(TaggedField::PaymentSecret(payment_secret));
+               self
+       }
+
+       /// Sets the expiry time
+       pub fn expiry_time(mut self, expiry_time: Duration) -> Self {
+        match ExpiryTime::from_duration(expiry_time) {
+            Ok(t) => self.tagged_fields.push(TaggedField::ExpiryTime(t)),
+            Err(e) => self.error = Some(e),
+        };
+               self
+       }
+
+       /// Sets `min_final_cltv_expiry`.
+       pub fn min_final_cltv_expiry(mut self, min_final_cltv_expiry: u64) -> Self {
+               self.tagged_fields.push(TaggedField::MinFinalCltvExpiry(MinFinalCltvExpiry(min_final_cltv_expiry)));
+               self
+       }
+
+       /// Adds a fallback address.
+       pub fn fallback(mut self, fallback: Fallback) -> Self {
+               self.tagged_fields.push(TaggedField::Fallback(fallback));
+               self
+       }
+
+       /// Adds a private route.
+       pub fn route(mut self, route: Vec<RouteHop>) -> Self {
+               match Route::new(route) {
+                       Ok(r) => self.tagged_fields.push(TaggedField::Route(r)),
+                       Err(e) => self.error = Some(e),
+               }
+               self
+       }
+}
+
+impl<D: tb::Bool, H: tb::Bool> InvoiceBuilder<D, H, tb::True> {
+       /// Builds a `RawInvoice` if no `CreationError` occurred while construction any of the fields.
+       pub fn build_raw(self) -> Result<RawInvoice, CreationError> {
+
+               // If an error occurred at any time before, return it now
+               if let Some(e) = self.error {
+                       return Err(e);
+               }
+
+               let hrp = RawHrp {
+                       currency: self.currency,
+                       raw_amount: self.amount,
+                       si_prefix: self.si_prefix,
+               };
+
+               let timestamp = self.timestamp.expect("ensured to be Some(t) by type T");
+
+               let tagged_fields = self.tagged_fields.into_iter().map(|tf| {
+                       RawTaggedField::KnownSemantics(tf)
+               }).collect::<Vec<_>>();
+
+               let data = RawDataPart {
+                       timestamp: timestamp,
+                       tagged_fields: tagged_fields,
+               };
+
+               Ok(RawInvoice {
+                       hrp: hrp,
+                       data: data,
+               })
+       }
+}
+
+impl<H: tb::Bool, T: tb::Bool> InvoiceBuilder<tb::False, H, T> {
+       /// Set the description. This function is only available if no description (hash) was set.
+       pub fn description(mut self, description: String) -> InvoiceBuilder<tb::True, H, T> {
+               match Description::new(description) {
+                       Ok(d) => self.tagged_fields.push(TaggedField::Description(d)),
+                       Err(e) => self.error = Some(e),
+               }
+               self.set_flags()
+       }
+
+       /// Set the description hash. This function is only available if no description (hash) was set.
+       pub fn description_hash(mut self, description_hash: sha256::Hash) -> InvoiceBuilder<tb::True, H, T> {
+               self.tagged_fields.push(TaggedField::DescriptionHash(Sha256(description_hash)));
+               self.set_flags()
+       }
+}
+
+impl<D: tb::Bool, T: tb::Bool> InvoiceBuilder<D, tb::False, T> {
+       /// Set the payment hash. This function is only available if no payment hash was set.
+       pub fn payment_hash(mut self, hash: sha256::Hash) -> InvoiceBuilder<D, tb::True, T> {
+               self.tagged_fields.push(TaggedField::PaymentHash(Sha256(hash)));
+               self.set_flags()
+       }
+}
+
+impl<D: tb::Bool, H: tb::Bool> InvoiceBuilder<D, H, tb::False> {
+       /// Sets the timestamp.
+       pub fn timestamp(mut self, time: SystemTime) -> InvoiceBuilder<D, H, tb::True> {
+               match PositiveTimestamp::from_system_time(time) {
+                       Ok(t) => self.timestamp = Some(t),
+                       Err(e) => self.error = Some(e),
+               }
+
+               self.set_flags()
+       }
+
+       /// Sets the timestamp to the current UNIX timestamp.
+       pub fn current_timestamp(mut self) -> InvoiceBuilder<D, H, tb::True> {
+               let now = PositiveTimestamp::from_system_time(SystemTime::now());
+               self.timestamp = Some(now.expect("for the foreseeable future this shouldn't happen"));
+               self.set_flags()
+       }
+}
+
+impl InvoiceBuilder<tb::True, tb::True, tb::True> {
+       /// Builds and signs an invoice using the supplied `sign_function`. This function MAY NOT fail
+       /// and MUST produce a recoverable signature valid for the given hash and if applicable also for
+       /// the included payee public key.
+       pub fn build_signed<F>(self, sign_function: F) -> Result<Invoice, CreationError>
+               where F: FnOnce(&Message) -> RecoverableSignature
+       {
+               let invoice = self.try_build_signed::<_, ()>(|hash| {
+                       Ok(sign_function(hash))
+               });
+
+               match invoice {
+                       Ok(i) => Ok(i),
+                       Err(SignOrCreationError::CreationError(e)) => Err(e),
+                       Err(SignOrCreationError::SignError(())) => unreachable!(),
+               }
+       }
+
+       /// Builds and signs an invoice using the supplied `sign_function`. This function MAY fail with
+       /// an error of type `E` and MUST produce a recoverable signature valid for the given hash and
+       /// if applicable also for the included payee public key.
+       pub fn try_build_signed<F, E>(self, sign_function: F) -> Result<Invoice, SignOrCreationError<E>>
+               where F: FnOnce(&Message) -> Result<RecoverableSignature, E>
+       {
+               let raw = match self.build_raw() {
+                       Ok(r) => r,
+                       Err(e) => return Err(SignOrCreationError::CreationError(e)),
+               };
+
+               let signed = match raw.sign(sign_function) {
+                       Ok(s) => s,
+                       Err(e) => return Err(SignOrCreationError::SignError(e)),
+               };
+
+               let invoice = Invoice {
+                       signed_invoice: signed,
+               };
+
+               invoice.check_field_counts().expect("should be ensured by type signature of builder");
+
+               Ok(invoice)
+       }
+}
+
+
+impl SignedRawInvoice {
+       /// Disassembles the `SignedRawInvoice` into its three parts:
+       ///  1. raw invoice
+       ///  2. hash of the raw invoice
+       ///  3. signature
+       pub fn into_parts(self) -> (RawInvoice, [u8; 32], Signature) {
+               (self.raw_invoice, self.hash, self.signature)
+       }
+
+       /// The `RawInvoice` which was signed.
+       pub fn raw_invoice(&self) -> &RawInvoice {
+               &self.raw_invoice
+       }
+
+       /// The hash of the `RawInvoice` that was signed.
+       pub fn hash(&self) -> &[u8; 32] {
+               &self.hash
+       }
+
+       /// Signature for the invoice.
+       pub fn signature(&self) -> &Signature {
+               &self.signature
+       }
+
+       /// Recovers the public key used for signing the invoice from the recoverable signature.
+       pub fn recover_payee_pub_key(&self) -> Result<PayeePubKey, secp256k1::Error> {
+               let hash = Message::from_slice(&self.hash[..])
+                       .expect("Hash is 32 bytes long, same as MESSAGE_SIZE");
+
+               Ok(PayeePubKey(Secp256k1::new().recover(
+                       &hash,
+                       &self.signature
+               )?))
+       }
+
+       /// Checks if the signature is valid for the included payee public key or if none exists if it's
+       /// valid for the recovered signature (which should always be true?).
+       pub fn check_signature(&self) -> bool {
+               let included_pub_key = self.raw_invoice.payee_pub_key();
+
+               let mut recovered_pub_key = Option::None;
+               if recovered_pub_key.is_none() {
+                       let recovered = match self.recover_payee_pub_key() {
+                               Ok(pk) => pk,
+                               Err(_) => return false,
+                       };
+                       recovered_pub_key = Some(recovered);
+               }
+
+               let pub_key = included_pub_key.or_else(|| recovered_pub_key.as_ref())
+                       .expect("One is always present");
+
+               let hash = Message::from_slice(&self.hash[..])
+                       .expect("Hash is 32 bytes long, same as MESSAGE_SIZE");
+
+               let secp_context = Secp256k1::new();
+               let verification_result = secp_context.verify(
+                       &hash,
+                       &self.signature.to_standard(),
+                       pub_key
+               );
+
+               match verification_result {
+                       Ok(()) => true,
+                       Err(_) => false,
+               }
+       }
+}
+
+/// Finds the first element of an enum stream of a given variant and extracts one member of the
+/// variant. If no element was found `None` gets returned.
+///
+/// The following example would extract the first
+/// ```
+/// use Enum::*
+///
+/// enum Enum {
+///    A(u8),
+///    B(u16)
+/// }
+///
+/// let elements = vec![A(1), A(2), B(3), A(4)]
+///
+/// assert_eq!(find_extract!(elements.iter(), Enum::B(ref x), x), Some(3u16))
+/// ```
+macro_rules! find_extract {
+    ($iter:expr, $enm:pat, $enm_var:ident) => {
+       $iter.filter_map(|tf| match *tf {
+                       $enm => Some($enm_var),
+                       _ => None,
+               }).next()
+    };
+}
+
+#[allow(missing_docs)]
+impl RawInvoice {
+       /// Hash the HRP as bytes and signatureless data part.
+       fn hash_from_parts(hrp_bytes: &[u8], data_without_signature: &[u5]) -> [u8; 32] {
+               use bech32::FromBase32;
+
+               let mut preimage = Vec::<u8>::from(hrp_bytes);
+
+               let mut data_part = Vec::from(data_without_signature);
+               let overhang = (data_part.len() * 5) % 8;
+               if overhang > 0 {
+                       // add padding if data does not end at a byte boundary
+                       data_part.push(u5::try_from_u8(0).unwrap());
+
+                       // if overhang is in (1..3) we need to add u5(0) padding two times
+                       if overhang < 3 {
+                               data_part.push(u5::try_from_u8(0).unwrap());
+                       }
+               }
+
+               preimage.extend_from_slice(&Vec::<u8>::from_base32(&data_part)
+                       .expect("No padding error may occur due to appended zero above."));
+
+               let mut hash: [u8; 32] = Default::default();
+               hash.copy_from_slice(&sha256::Hash::hash(&preimage)[..]);
+               hash
+       }
+
+       /// Calculate the hash of the encoded `RawInvoice`
+       pub fn hash(&self) -> [u8; 32] {
+               use bech32::ToBase32;
+
+               RawInvoice::hash_from_parts(
+                       self.hrp.to_string().as_bytes(),
+                       &self.data.to_base32()
+               )
+       }
+
+       /// Signs the invoice using the supplied `sign_function`. This function MAY fail with an error
+       /// of type `E`. Since the signature of a `SignedRawInvoice` is not required to be valid there
+       /// are no constraints regarding the validity of the produced signature.
+       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 hash = Message::from_slice(&raw_hash[..])
+                       .expect("Hash is 32 bytes long, same as MESSAGE_SIZE");
+               let signature = sign_method(&hash)?;
+
+               Ok(SignedRawInvoice {
+                       raw_invoice: self,
+                       hash: raw_hash,
+                       signature: Signature(signature),
+               })
+       }
+
+       /// Returns an iterator over all tagged fields with known semantics.
+       pub fn known_tagged_fields(&self)
+               -> FilterMap<Iter<RawTaggedField>, fn(&RawTaggedField) -> Option<&TaggedField>>
+       {
+               // For 1.14.0 compatibility: closures' types can't be written an fn()->() in the
+               // function's type signature.
+               // TODO: refactor once impl Trait is available
+               fn match_raw(raw: &RawTaggedField) -> Option<&TaggedField> {
+                       match *raw {
+                               RawTaggedField::KnownSemantics(ref tf) => Some(tf),
+                               _ => None,
+                       }
+               }
+
+               self.data.tagged_fields.iter().filter_map(match_raw )
+       }
+
+       pub fn payment_hash(&self) -> Option<&Sha256> {
+               find_extract!(self.known_tagged_fields(), TaggedField::PaymentHash(ref x), x)
+       }
+
+       pub fn description(&self) -> Option<&Description> {
+               find_extract!(self.known_tagged_fields(), TaggedField::Description(ref x), x)
+       }
+
+       pub fn payee_pub_key(&self) -> Option<&PayeePubKey> {
+               find_extract!(self.known_tagged_fields(), TaggedField::PayeePubKey(ref x), x)
+       }
+
+       pub fn description_hash(&self) -> Option<&Sha256> {
+               find_extract!(self.known_tagged_fields(), TaggedField::DescriptionHash(ref x), x)
+       }
+
+       pub fn expiry_time(&self) -> Option<&ExpiryTime> {
+               find_extract!(self.known_tagged_fields(), TaggedField::ExpiryTime(ref x), x)
+       }
+
+       pub fn min_final_cltv_expiry(&self) -> Option<&MinFinalCltvExpiry> {
+               find_extract!(self.known_tagged_fields(), TaggedField::MinFinalCltvExpiry(ref x), x)
+       }
+
+       pub fn payment_secret(&self) -> Option<&PaymentSecret> {
+               find_extract!(self.known_tagged_fields(), TaggedField::PaymentSecret(ref x), x)
+       }
+
+       pub fn fallbacks(&self) -> Vec<&Fallback> {
+               self.known_tagged_fields().filter_map(|tf| match tf {
+                       &TaggedField::Fallback(ref f) => Some(f),
+                       _ => None,
+               }).collect::<Vec<&Fallback>>()
+       }
+
+       pub fn routes(&self) -> Vec<&Route> {
+               self.known_tagged_fields().filter_map(|tf| match tf {
+                       &TaggedField::Route(ref r) => Some(r),
+                       _ => None,
+               }).collect::<Vec<&Route>>()
+       }
+
+       pub fn amount_pico_btc(&self) -> Option<u64> {
+               self.hrp.raw_amount.map(|v| {
+                       v * self.hrp.si_prefix.as_ref().map_or(1_000_000_000_000, |si| { si.multiplier() })
+               })
+       }
+
+       pub fn currency(&self) -> Currency {
+               self.hrp.currency.clone()
+       }
+}
+
+impl PositiveTimestamp {
+       /// Create a new `PositiveTimestamp` from a unix timestamp in the Range
+       /// `0...SYSTEM_TIME_MAX_UNIX_TIMESTAMP - MAX_EXPIRY_TIME`, otherwise return a
+       /// `CreationError::TimestampOutOfBounds`.
+       pub fn from_unix_timestamp(unix_seconds: u64) -> Result<Self, CreationError> {
+               if unix_seconds > SYSTEM_TIME_MAX_UNIX_TIMESTAMP - MAX_EXPIRY_TIME {
+                       Err(CreationError::TimestampOutOfBounds)
+               } else {
+                       Ok(PositiveTimestamp(UNIX_EPOCH + Duration::from_secs(unix_seconds)))
+               }
+       }
+
+       /// Create a new `PositiveTimestamp` from a `SystemTime` with a corresponding unix timestamp in
+       /// the Range `0...SYSTEM_TIME_MAX_UNIX_TIMESTAMP - MAX_EXPIRY_TIME`, otherwise return a
+       /// `CreationError::TimestampOutOfBounds`.
+       pub fn from_system_time(time: SystemTime) -> Result<Self, CreationError> {
+               if time
+                       .duration_since(UNIX_EPOCH)
+                       .map(|t| t.as_secs() <= SYSTEM_TIME_MAX_UNIX_TIMESTAMP - MAX_EXPIRY_TIME)
+                       .unwrap_or(true)
+                       {
+                               Ok(PositiveTimestamp(time))
+                       } else {
+                       Err(CreationError::TimestampOutOfBounds)
+               }
+       }
+
+       /// Returns the UNIX timestamp representing the stored time
+       pub fn as_unix_timestamp(&self) -> u64 {
+               self.0.duration_since(UNIX_EPOCH)
+                       .expect("ensured by type contract/constructors")
+                       .as_secs()
+       }
+
+       /// Returns a reference to the internal `SystemTime` time representation
+       pub fn as_time(&self) -> &SystemTime {
+               &self.0
+       }
+}
+
+impl Into<SystemTime> for PositiveTimestamp {
+       fn into(self) -> SystemTime {
+               self.0
+       }
+}
+
+impl Deref for PositiveTimestamp {
+       type Target = SystemTime;
+
+       fn deref(&self) -> &Self::Target {
+               &self.0
+       }
+}
+
+impl Invoice {
+       /// Transform the `Invoice` into it's unchecked version
+       pub fn into_signed_raw(self) -> SignedRawInvoice {
+               self.signed_invoice
+       }
+
+       /// Check that all mandatory fields are present
+       fn check_field_counts(&self) -> Result<(), SemanticError> {
+               // "A writer MUST include exactly one p field […]."
+               let payment_hash_cnt = self.tagged_fields().filter(|&tf| match *tf {
+                       TaggedField::PaymentHash(_) => true,
+                       _ => false,
+               }).count();
+               if payment_hash_cnt < 1 {
+                       return Err(SemanticError::NoPaymentHash);
+               } else if payment_hash_cnt > 1 {
+                       return Err(SemanticError::MultiplePaymentHashes);
+               }
+
+               // "A writer MUST include either exactly one d or exactly one h field."
+               let description_cnt = self.tagged_fields().filter(|&tf| match *tf {
+                       TaggedField::Description(_) | TaggedField::DescriptionHash(_) => true,
+                       _ => false,
+               }).count();
+               if  description_cnt < 1 {
+                       return Err(SemanticError::NoDescription);
+               } else if description_cnt > 1 {
+                       return  Err(SemanticError::MultipleDescriptions);
+               }
+
+               Ok(())
+       }
+
+       /// Check that the invoice is signed correctly and that key recovery works
+       pub fn check_signature(&self) -> Result<(), SemanticError> {
+               match self.signed_invoice.recover_payee_pub_key() {
+                       Err(secp256k1::Error::InvalidRecoveryId) =>
+                               return Err(SemanticError::InvalidRecoveryId),
+                       Err(_) => panic!("no other error may occur"),
+                       Ok(_) => {},
+               }
+
+               if !self.signed_invoice.check_signature() {
+                       return Err(SemanticError::InvalidSignature);
+               }
+
+               Ok(())
+       }
+
+       /// Constructs an `Invoice` from a `SignedInvoice` by checking all its invariants.
+       /// ```
+       /// use lightning_invoice::*;
+       ///
+       /// let invoice = "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp\
+       ///     l2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d7\
+       ///     3gafnh3cax9rn449d9p5uxz9ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ec\
+       ///     ky03ylcqca784w";
+       ///
+       /// let signed = invoice.parse::<SignedRawInvoice>().unwrap();
+       ///
+       /// assert!(Invoice::from_signed(signed).is_ok());
+       /// ```
+       pub fn from_signed(signed_invoice: SignedRawInvoice) -> Result<Self, SemanticError> {
+               let invoice = Invoice {
+                       signed_invoice: signed_invoice,
+               };
+               invoice.check_field_counts()?;
+               invoice.check_signature()?;
+
+               Ok(invoice)
+       }
+
+       /// Returns the `Invoice`'s timestamp (should equal it's creation time)
+       pub fn timestamp(&self) -> &SystemTime {
+               self.signed_invoice.raw_invoice().data.timestamp.as_time()
+       }
+
+       /// Returns an iterator over all tagged fields of this Invoice.
+       pub fn tagged_fields(&self)
+               -> FilterMap<Iter<RawTaggedField>, fn(&RawTaggedField) -> Option<&TaggedField>> {
+               self.signed_invoice.raw_invoice().known_tagged_fields()
+       }
+
+       /// Returns the hash to which we will receive the preimage on completion of the payment
+       pub fn payment_hash(&self) -> &sha256::Hash {
+               &self.signed_invoice.payment_hash().expect("checked by constructor").0
+       }
+
+       /// Return the description or a hash of it for longer ones
+       pub fn description(&self) -> InvoiceDescription {
+               if let Some(ref direct) = self.signed_invoice.description() {
+                       return InvoiceDescription::Direct(direct);
+               } else if let Some(ref hash) = self.signed_invoice.description_hash() {
+                       return InvoiceDescription::Hash(hash);
+               }
+               unreachable!("ensured by constructor");
+       }
+
+       /// Get the payee's public key if one was included in the invoice
+       pub fn payee_pub_key(&self) -> Option<&PublicKey> {
+               self.signed_invoice.payee_pub_key().map(|x| &x.0)
+       }
+
+    /// Get the payment secret if one was included in the invoice
+    pub fn payment_secret(&self) -> Option<&PaymentSecret> {
+        self.signed_invoice.payment_secret()
+    }
+
+       /// Recover the payee's public key (only to be used if none was included in the invoice)
+       pub fn recover_payee_pub_key(&self) -> PublicKey {
+               self.signed_invoice.recover_payee_pub_key().expect("was checked by constructor").0
+       }
+
+       /// Returns the invoice's expiry time if present
+       pub fn expiry_time(&self) -> Duration {
+               self.signed_invoice.expiry_time()
+                       .map(|x| x.0)
+                       .unwrap_or(Duration::from_secs(3600))
+       }
+
+       /// Returns the invoice's `min_cltv_expiry` time if present
+       pub fn min_final_cltv_expiry(&self) -> Option<&u64> {
+               self.signed_invoice.min_final_cltv_expiry().map(|x| &x.0)
+       }
+
+       /// Returns a list of all fallback addresses
+       pub fn fallbacks(&self) -> Vec<&Fallback> {
+               self.signed_invoice.fallbacks()
+       }
+
+       /// Returns a list of all routes included in the invoice
+       pub fn routes(&self) -> Vec<&Route> {
+               self.signed_invoice.routes()
+       }
+
+       /// Returns the currency for which the invoice was issued
+       pub fn currency(&self) -> Currency {
+               self.signed_invoice.currency()
+       }
+
+       /// Returns the amount if specified in the invoice as pico <currency>.
+       pub fn amount_pico_btc(&self) -> Option<u64> {
+               self.signed_invoice.amount_pico_btc()
+       }
+}
+
+impl From<TaggedField> for RawTaggedField {
+       fn from(tf: TaggedField) -> Self {
+               RawTaggedField::KnownSemantics(tf)
+       }
+}
+
+impl TaggedField {
+       /// Numeric representation of the field's tag
+       pub fn tag(&self) -> u5 {
+               let tag = match *self {
+                       TaggedField::PaymentHash(_) => constants::TAG_PAYMENT_HASH,
+                       TaggedField::Description(_) => constants::TAG_DESCRIPTION,
+                       TaggedField::PayeePubKey(_) => constants::TAG_PAYEE_PUB_KEY,
+                       TaggedField::DescriptionHash(_) => constants::TAG_DESCRIPTION_HASH,
+                       TaggedField::ExpiryTime(_) => constants::TAG_EXPIRY_TIME,
+                       TaggedField::MinFinalCltvExpiry(_) => constants::TAG_MIN_FINAL_CLTV_EXPIRY,
+                       TaggedField::Fallback(_) => constants::TAG_FALLBACK,
+                       TaggedField::Route(_) => constants::TAG_ROUTE,
+                       TaggedField::PaymentSecret(_) => constants::TAG_PAYMENT_SECRET,
+               };
+
+               u5::try_from_u8(tag).expect("all tags defined are <32")
+       }
+}
+
+impl Description {
+
+       /// Creates a new `Description` if `description` is at most 1023 __bytes__ long,
+       /// returns `CreationError::DescriptionTooLong` otherwise
+       ///
+       /// Please note that single characters may use more than one byte due to UTF8 encoding.
+       pub fn new(description: String) -> Result<Description, CreationError> {
+               if description.len() > 639 {
+                       Err(CreationError::DescriptionTooLong)
+               } else {
+                       Ok(Description(description))
+               }
+       }
+
+       /// Returns the underlying description `String`
+       pub fn into_inner(self) -> String {
+               self.0
+       }
+}
+
+impl Into<String> for Description {
+       fn into(self) -> String {
+               self.into_inner()
+       }
+}
+
+impl Deref for Description {
+       type Target = str;
+
+       fn deref(&self) -> &str {
+               &self.0
+       }
+}
+
+impl From<PublicKey> for PayeePubKey {
+       fn from(pk: PublicKey) -> Self {
+               PayeePubKey(pk)
+       }
+}
+
+impl Deref for PayeePubKey {
+       type Target = PublicKey;
+
+       fn deref(&self) -> &PublicKey {
+               &self.0
+       }
+}
+
+impl ExpiryTime {
+       /// Construct an `ExpiryTime` from seconds. If there exists a `PositiveTimestamp` which would
+       /// overflow on adding the `EpiryTime` to it then this function will return a
+       /// `CreationError::ExpiryTimeOutOfBounds`.
+       pub fn from_seconds(seconds: u64) -> Result<ExpiryTime, CreationError> {
+               if seconds <= MAX_EXPIRY_TIME {
+                       Ok(ExpiryTime(Duration::from_secs(seconds)))
+               } else {
+                       Err(CreationError::ExpiryTimeOutOfBounds)
+               }
+       }
+
+       /// Construct an `ExpiryTime` from a `Duration`. If there exists a `PositiveTimestamp` which
+       /// would overflow on adding the `EpiryTime` to it then this function will return a
+       /// `CreationError::ExpiryTimeOutOfBounds`.
+       pub fn from_duration(duration: Duration) -> Result<ExpiryTime, CreationError> {
+               if duration.as_secs() <= MAX_EXPIRY_TIME {
+                       Ok(ExpiryTime(duration))
+               } else {
+                       Err(CreationError::ExpiryTimeOutOfBounds)
+               }
+       }
+
+       /// Returns the expiry time in seconds
+       pub fn as_seconds(&self) -> u64 {
+               self.0.as_secs()
+       }
+
+       /// Returns a reference to the underlying `Duration` (=expiry time)
+       pub fn as_duration(&self) -> &Duration {
+               &self.0
+       }
+}
+
+impl Route {
+       /// Create a new (partial) route from a list of hops
+       pub fn new(hops: Vec<RouteHop>) -> Result<Route, CreationError> {
+               if hops.len() <= 12 {
+                       Ok(Route(hops))
+               } else {
+                       Err(CreationError::RouteTooLong)
+               }
+       }
+
+       /// Returrn the underlying vector of hops
+       pub fn into_inner(self) -> Vec<RouteHop> {
+               self.0
+       }
+}
+
+impl Into<Vec<RouteHop>> for Route {
+       fn into(self) -> Vec<RouteHop> {
+               self.into_inner()
+       }
+}
+
+impl Deref for Route {
+       type Target = Vec<RouteHop>;
+
+       fn deref(&self) -> &Vec<RouteHop> {
+               &self.0
+       }
+}
+
+impl Deref for Signature {
+       type Target = RecoverableSignature;
+
+       fn deref(&self) -> &RecoverableSignature {
+               &self.0
+       }
+}
+
+impl Deref for SignedRawInvoice {
+       type Target = RawInvoice;
+
+       fn deref(&self) -> &RawInvoice {
+               &self.raw_invoice
+       }
+}
+
+/// Errors that may occur when constructing a new `RawInvoice` or `Invoice`
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub enum CreationError {
+       /// The supplied description string was longer than 639 __bytes__ (see [`Description::new(…)`](./struct.Description.html#method.new))
+       DescriptionTooLong,
+
+       /// The specified route has too many hops and can't be encoded
+       RouteTooLong,
+
+       /// The unix timestamp of the supplied date is <0 or can't be represented as `SystemTime`
+       TimestampOutOfBounds,
+
+       /// The supplied expiry time could cause an overflow if added to a `PositiveTimestamp`
+       ExpiryTimeOutOfBounds,
+}
+
+impl Display for CreationError {
+       fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+               match self {
+                       CreationError::DescriptionTooLong => f.write_str("The supplied description string was longer than 639 bytes"),
+                       CreationError::RouteTooLong => f.write_str("The specified route has too many hops and can't be encoded"),
+                       CreationError::TimestampOutOfBounds => f.write_str("The unix timestamp of the supplied date is <0 or can't be represented as `SystemTime`"),
+                       CreationError::ExpiryTimeOutOfBounds => f.write_str("The supplied expiry time could cause an overflow if added to a `PositiveTimestamp`"),
+               }
+       }
+}
+
+impl std::error::Error for CreationError { }
+
+/// Errors that may occur when converting a `RawInvoice` to an `Invoice`. They relate to the
+/// requirements sections in BOLT #11
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub enum SemanticError {
+       /// The invoice is missing the mandatory payment hash
+       NoPaymentHash,
+
+       /// The invoice has multiple payment hashes which isn't allowed
+       MultiplePaymentHashes,
+
+       /// No description or description hash are part of the invoice
+       NoDescription,
+
+       /// The invoice contains multiple descriptions and/or description hashes which isn't allowed
+       MultipleDescriptions,
+
+       /// The recovery id doesn't fit the signature/pub key
+       InvalidRecoveryId,
+
+       /// The invoice's signature is invalid
+       InvalidSignature,
+}
+
+impl Display for SemanticError {
+       fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+               match self {
+                       SemanticError::NoPaymentHash => f.write_str("The invoice is missing the mandatory payment hash"),
+                       SemanticError::MultiplePaymentHashes => f.write_str("The invoice has multiple payment hashes which isn't allowed"),
+                       SemanticError::NoDescription => f.write_str("No description or description hash are part of the invoice"),
+                       SemanticError::MultipleDescriptions => f.write_str("The invoice contains multiple descriptions and/or description hashes which isn't allowed"),
+                       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"),
+               }
+       }
+}
+
+impl std::error::Error for SemanticError { }
+
+/// When signing using a fallible method either an user-supplied `SignError` or a `CreationError`
+/// may occur.
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub enum SignOrCreationError<S> {
+       /// An error occurred during signing
+       SignError(S),
+
+       /// An error occurred while building the transaction
+       CreationError(CreationError),
+}
+
+impl<S> Display for SignOrCreationError<S> {
+       fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+               match self {
+                       SignOrCreationError::SignError(_) => f.write_str("An error occurred during signing"),
+                       SignOrCreationError::CreationError(err) => err.fmt(f),
+               }
+       }
+}
+
+#[cfg(test)]
+mod test {
+       use bitcoin_hashes::hex::FromHex;
+       use bitcoin_hashes::sha256;
+
+       #[test]
+       fn test_system_time_bounds_assumptions() {
+               ::check_platform();
+
+        assert_eq!(
+            ::PositiveTimestamp::from_unix_timestamp(::SYSTEM_TIME_MAX_UNIX_TIMESTAMP + 1),
+            Err(::CreationError::TimestampOutOfBounds)
+        );
+
+        assert_eq!(
+            ::ExpiryTime::from_seconds(::MAX_EXPIRY_TIME + 1),
+            Err(::CreationError::ExpiryTimeOutOfBounds)
+        );
+       }
+
+       #[test]
+       fn test_calc_invoice_hash() {
+               use ::{RawInvoice, RawHrp, RawDataPart, Currency, PositiveTimestamp};
+               use ::TaggedField::*;
+
+               let invoice = RawInvoice {
+                       hrp: RawHrp {
+                               currency: Currency::Bitcoin,
+                               raw_amount: None,
+                               si_prefix: None,
+                       },
+                       data: RawDataPart {
+                               timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(),
+                               tagged_fields: vec![
+                                       PaymentHash(::Sha256(sha256::Hash::from_hex(
+                                               "0001020304050607080900010203040506070809000102030405060708090102"
+                                       ).unwrap())).into(),
+                                       Description(::Description::new(
+                                               "Please consider supporting this project".to_owned()
+                                       ).unwrap()).into(),
+                               ],
+                       },
+               };
+
+               let expected_hash = [
+                       0xc3, 0xd4, 0xe8, 0x3f, 0x64, 0x6f, 0xa7, 0x9a, 0x39, 0x3d, 0x75, 0x27, 0x7b, 0x1d,
+                       0x85, 0x8d, 0xb1, 0xd1, 0xf7, 0xab, 0x71, 0x37, 0xdc, 0xb7, 0x83, 0x5d, 0xb2, 0xec,
+                       0xd5, 0x18, 0xe1, 0xc9
+               ];
+
+               assert_eq!(invoice.hash(), expected_hash)
+       }
+
+       #[test]
+       fn test_check_signature() {
+               use TaggedField::*;
+               use secp256k1::Secp256k1;
+               use secp256k1::recovery::{RecoveryId, RecoverableSignature};
+               use secp256k1::key::{SecretKey, PublicKey};
+               use {SignedRawInvoice, Signature, RawInvoice, RawHrp, RawDataPart, Currency, Sha256,
+                        PositiveTimestamp};
+
+               let invoice = SignedRawInvoice {
+                       raw_invoice: RawInvoice {
+                               hrp: RawHrp {
+                                       currency: Currency::Bitcoin,
+                                       raw_amount: None,
+                                       si_prefix: None,
+                               },
+                               data: RawDataPart {
+                                       timestamp: PositiveTimestamp::from_unix_timestamp(1496314658).unwrap(),
+                                       tagged_fields: vec ! [
+                                               PaymentHash(Sha256(sha256::Hash::from_hex(
+                                                       "0001020304050607080900010203040506070809000102030405060708090102"
+                                               ).unwrap())).into(),
+                                               Description(
+                                                       ::Description::new(
+                                                               "Please consider supporting this project".to_owned()
+                                                       ).unwrap()
+                                               ).into(),
+                                       ],
+                               },
+                       },
+                       hash: [
+                               0xc3, 0xd4, 0xe8, 0x3f, 0x64, 0x6f, 0xa7, 0x9a, 0x39, 0x3d, 0x75, 0x27,
+                               0x7b, 0x1d, 0x85, 0x8d, 0xb1, 0xd1, 0xf7, 0xab, 0x71, 0x37, 0xdc, 0xb7,
+                               0x83, 0x5d, 0xb2, 0xec, 0xd5, 0x18, 0xe1, 0xc9
+                       ],
+                       signature: Signature(RecoverableSignature::from_compact(
+                               & [
+                                       0x38u8, 0xec, 0x68, 0x91, 0x34, 0x5e, 0x20, 0x41, 0x45, 0xbe, 0x8a,
+                                       0x3a, 0x99, 0xde, 0x38, 0xe9, 0x8a, 0x39, 0xd6, 0xa5, 0x69, 0x43,
+                                       0x4e, 0x18, 0x45, 0xc8, 0xaf, 0x72, 0x05, 0xaf, 0xcf, 0xcc, 0x7f,
+                                       0x42, 0x5f, 0xcd, 0x14, 0x63, 0xe9, 0x3c, 0x32, 0x88, 0x1e, 0xad,
+                                       0x0d, 0x6e, 0x35, 0x6d, 0x46, 0x7e, 0xc8, 0xc0, 0x25, 0x53, 0xf9,
+                                       0xaa, 0xb1, 0x5e, 0x57, 0x38, 0xb1, 0x1f, 0x12, 0x7f
+                               ],
+                               RecoveryId::from_i32(0).unwrap()
+                       ).unwrap()),
+               };
+
+               assert!(invoice.check_signature());
+
+               let private_key = SecretKey::from_slice(
+                       &[
+                               0xe1, 0x26, 0xf6, 0x8f, 0x7e, 0xaf, 0xcc, 0x8b, 0x74, 0xf5, 0x4d, 0x26, 0x9f, 0xe2,
+                               0x06, 0xbe, 0x71, 0x50, 0x00, 0xf9, 0x4d, 0xac, 0x06, 0x7d, 0x1c, 0x04, 0xa8, 0xca,
+                               0x3b, 0x2d, 0xb7, 0x34
+                       ][..]
+               ).unwrap();
+               let public_key = PublicKey::from_secret_key(&Secp256k1::new(), &private_key);
+
+               assert_eq!(invoice.recover_payee_pub_key(), Ok(::PayeePubKey(public_key)));
+
+               let (raw_invoice, _, _) = invoice.into_parts();
+               let new_signed = raw_invoice.sign::<_, ()>(|hash| {
+                       Ok(Secp256k1::new().sign_recoverable(hash, &private_key))
+               }).unwrap();
+
+               assert!(new_signed.check_signature());
+       }
+
+       #[test]
+       fn test_builder_amount() {
+               use ::*;
+
+               let builder = InvoiceBuilder::new(Currency::Bitcoin)
+                       .description("Test".into())
+                       .payment_hash(sha256::Hash::from_slice(&[0;32][..]).unwrap())
+                       .current_timestamp();
+
+               let invoice = builder.clone()
+                       .amount_pico_btc(15000)
+                       .build_raw()
+                       .unwrap();
+
+               assert_eq!(invoice.hrp.si_prefix, Some(SiPrefix::Nano));
+               assert_eq!(invoice.hrp.raw_amount, Some(15));
+
+
+               let invoice = builder.clone()
+                       .amount_pico_btc(1500)
+                       .build_raw()
+                       .unwrap();
+
+               assert_eq!(invoice.hrp.si_prefix, Some(SiPrefix::Pico));
+               assert_eq!(invoice.hrp.raw_amount, Some(1500));
+       }
+
+       #[test]
+       fn test_builder_fail() {
+               use ::*;
+               use std::iter::FromIterator;
+               use secp256k1::key::PublicKey;
+
+               let builder = InvoiceBuilder::new(Currency::Bitcoin)
+                       .payment_hash(sha256::Hash::from_slice(&[0;32][..]).unwrap())
+                       .current_timestamp();
+
+               let too_long_string = String::from_iter(
+                       (0..1024).map(|_| '?')
+               );
+
+               let long_desc_res = builder.clone()
+                       .description(too_long_string)
+                       .build_raw();
+               assert_eq!(long_desc_res, Err(CreationError::DescriptionTooLong));
+
+               let route_hop = RouteHop {
+                       pubkey: PublicKey::from_slice(
+                                       &[
+                                               0x03, 0x9e, 0x03, 0xa9, 0x01, 0xb8, 0x55, 0x34, 0xff, 0x1e, 0x92, 0xc4,
+                                               0x3c, 0x74, 0x43, 0x1f, 0x7c, 0xe7, 0x20, 0x46, 0x06, 0x0f, 0xcf, 0x7a,
+                                               0x95, 0xc3, 0x7e, 0x14, 0x8f, 0x78, 0xc7, 0x72, 0x55
+                                       ][..]
+                               ).unwrap(),
+                       short_channel_id: [0; 8],
+                       fee_base_msat: 0,
+                       fee_proportional_millionths: 0,
+                       cltv_expiry_delta: 0,
+               };
+               let too_long_route = vec![route_hop; 13];
+               let long_route_res = builder.clone()
+                       .description("Test".into())
+                       .route(too_long_route)
+                       .build_raw();
+               assert_eq!(long_route_res, Err(CreationError::RouteTooLong));
+
+               let sign_error_res = builder.clone()
+                       .description("Test".into())
+                       .try_build_signed(|_| {
+                               Err("ImaginaryError")
+                       });
+               assert_eq!(sign_error_res, Err(SignOrCreationError::SignError("ImaginaryError")));
+       }
+
+       #[test]
+       fn test_builder_ok() {
+               use ::*;
+               use secp256k1::Secp256k1;
+               use secp256k1::key::{SecretKey, PublicKey};
+               use std::time::{UNIX_EPOCH, Duration};
+
+               let secp_ctx = Secp256k1::new();
+
+               let private_key = SecretKey::from_slice(
+                       &[
+                               0xe1, 0x26, 0xf6, 0x8f, 0x7e, 0xaf, 0xcc, 0x8b, 0x74, 0xf5, 0x4d, 0x26, 0x9f, 0xe2,
+                               0x06, 0xbe, 0x71, 0x50, 0x00, 0xf9, 0x4d, 0xac, 0x06, 0x7d, 0x1c, 0x04, 0xa8, 0xca,
+                               0x3b, 0x2d, 0xb7, 0x34
+                       ][..]
+               ).unwrap();
+               let public_key = PublicKey::from_secret_key(&secp_ctx, &private_key);
+
+               let route_1 = vec![
+                       RouteHop {
+                               pubkey: public_key.clone(),
+                               short_channel_id: [123; 8],
+                               fee_base_msat: 2,
+                               fee_proportional_millionths: 1,
+                               cltv_expiry_delta: 145,
+                       },
+                       RouteHop {
+                               pubkey: public_key.clone(),
+                               short_channel_id: [42; 8],
+                               fee_base_msat: 3,
+                               fee_proportional_millionths: 2,
+                               cltv_expiry_delta: 146,
+                       }
+               ];
+
+               let route_2 = vec![
+                       RouteHop {
+                               pubkey: public_key.clone(),
+                               short_channel_id: [0; 8],
+                               fee_base_msat: 4,
+                               fee_proportional_millionths: 3,
+                               cltv_expiry_delta: 147,
+                       },
+                       RouteHop {
+                               pubkey: public_key.clone(),
+                               short_channel_id: [1; 8],
+                               fee_base_msat: 5,
+                               fee_proportional_millionths: 4,
+                               cltv_expiry_delta: 148,
+                       }
+               ];
+
+               let builder = InvoiceBuilder::new(Currency::BitcoinTestnet)
+                       .amount_pico_btc(123)
+                       .timestamp(UNIX_EPOCH + Duration::from_secs(1234567))
+                       .payee_pub_key(public_key.clone())
+                       .expiry_time(Duration::from_secs(54321))
+                       .min_final_cltv_expiry(144)
+                       .min_final_cltv_expiry(143)
+                       .fallback(Fallback::PubKeyHash([0;20]))
+                       .route(route_1.clone())
+                       .route(route_2.clone())
+                       .description_hash(sha256::Hash::from_slice(&[3;32][..]).unwrap())
+                       .payment_hash(sha256::Hash::from_slice(&[21;32][..]).unwrap());
+
+               let invoice = builder.clone().build_signed(|hash| {
+                       secp_ctx.sign_recoverable(hash, &private_key)
+               }).unwrap();
+
+               assert!(invoice.check_signature().is_ok());
+               assert_eq!(invoice.tagged_fields().count(), 9);
+
+               assert_eq!(invoice.amount_pico_btc(), Some(123));
+               assert_eq!(invoice.currency(), Currency::BitcoinTestnet);
+               assert_eq!(
+                       invoice.timestamp().duration_since(UNIX_EPOCH).unwrap().as_secs(),
+                       1234567
+               );
+               assert_eq!(invoice.payee_pub_key(), Some(&public_key));
+               assert_eq!(invoice.expiry_time(), Duration::from_secs(54321));
+               assert_eq!(invoice.min_final_cltv_expiry(), Some(&144));
+               assert_eq!(invoice.fallbacks(), vec![&Fallback::PubKeyHash([0;20])]);
+               assert_eq!(invoice.routes(), vec![&Route(route_1), &Route(route_2)]);
+               assert_eq!(
+                       invoice.description(),
+                       InvoiceDescription::Hash(&Sha256(sha256::Hash::from_slice(&[3;32][..]).unwrap()))
+               );
+               assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&[21;32][..]).unwrap());
+
+               let raw_invoice = builder.build_raw().unwrap();
+               assert_eq!(raw_invoice, *invoice.into_signed_raw().raw_invoice())
+       }
+}
diff --git a/lightning-invoice/src/ser.rs b/lightning-invoice/src/ser.rs
new file mode 100644 (file)
index 0000000..2b4332f
--- /dev/null
@@ -0,0 +1,514 @@
+use std::fmt;
+use std::fmt::{Display, Formatter};
+use bech32::{ToBase32, u5, WriteBase32, Base32Len};
+
+use ::*;
+
+/// Converts a stream of bytes written to it to base32. On finalization the according padding will
+/// be applied. That means the results of writing two data blocks with one or two `BytesToBase32`
+/// converters will differ.
+struct BytesToBase32<'a, W: WriteBase32 + 'a> {
+       /// Target for writing the resulting `u5`s resulting from the written bytes
+       writer: &'a mut W,
+       /// Holds all unwritten bits left over from last round. The bits are stored beginning from
+       /// the most significant bit. E.g. if buffer_bits=3, then the byte with bits a, b and c will
+       /// look as follows: [a, b, c, 0, 0, 0, 0, 0]
+       buffer: u8,
+       /// Amount of bits left over from last round, stored in buffer.
+       buffer_bits: u8,
+}
+
+impl<'a, W: WriteBase32> BytesToBase32<'a, W> {
+       /// Create a new bytes-to-base32 converter with `writer` as  a sink for the resulting base32
+       /// data.
+       pub fn new(writer: &'a mut W) -> BytesToBase32<'a, W> {
+               BytesToBase32 {
+                       writer,
+                       buffer: 0,
+                       buffer_bits: 0,
+               }
+       }
+
+       /// Add more bytes to the current conversion unit
+       pub fn append(&mut self, bytes: &[u8]) -> Result<(), W::Err> {
+               for b in bytes {
+                       self.append_u8(*b)?;
+               }
+               Ok(())
+       }
+
+       pub fn append_u8(&mut self, byte: u8) -> Result<(), W::Err> {
+               // Write first u5 if we have to write two u5s this round. That only happens if the
+               // buffer holds too many bits, so we don't have to combine buffer bits with new bits
+               // from this rounds byte.
+               if self.buffer_bits >= 5 {
+                       self.writer.write_u5(
+                               u5::try_from_u8((self.buffer & 0b11111000) >> 3 ).expect("<32")
+                       )?;
+                       self.buffer = self.buffer << 5;
+                       self.buffer_bits -= 5;
+               }
+
+               // Combine all bits from buffer with enough bits from this rounds byte so that they fill
+               // a u5. Save reamining bits from byte to buffer.
+               let from_buffer = self.buffer >> 3;
+               let from_byte = byte >> (3 + self.buffer_bits); // buffer_bits <= 4
+
+               self.writer.write_u5(u5::try_from_u8(from_buffer | from_byte).expect("<32"))?;
+               self.buffer = byte << (5 - self.buffer_bits);
+               self.buffer_bits = 3 + self.buffer_bits;
+
+               Ok(())
+       }
+
+       pub fn finalize(mut self) ->  Result<(), W::Err> {
+               self.inner_finalize()?;
+               std::mem::forget(self);
+               Ok(())
+       }
+
+       fn inner_finalize(&mut self) -> Result<(), W::Err>{
+               // There can be at most two u5s left in the buffer after processing all bytes, write them.
+               if self.buffer_bits >= 5 {
+                       self.writer.write_u5(
+                               u5::try_from_u8((self.buffer & 0b11111000) >> 3).expect("<32")
+                       )?;
+                       self.buffer = self.buffer << 5;
+                       self.buffer_bits -= 5;
+               }
+
+               if self.buffer_bits != 0 {
+                       self.writer.write_u5(u5::try_from_u8(self.buffer >> 3).expect("<32"))?;
+               }
+
+               Ok(())
+       }
+}
+
+impl<'a, W: WriteBase32> Drop for BytesToBase32<'a, W> {
+       fn drop(&mut self) {
+               self.inner_finalize()
+                       .expect("Unhandled error when finalizing conversion on drop. User finalize to handle.")
+       }
+}
+
+/// Calculates the base32 encoded size of a byte slice
+fn bytes_size_to_base32_size(byte_size: usize) -> usize {
+       let bits = byte_size * 8;
+       if bits % 5 == 0 {
+               // without padding bits
+               bits / 5
+       } else {
+               // with padding bits
+               bits / 5 + 1
+       }
+}
+
+impl Display for Invoice {
+       fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
+               self.signed_invoice.fmt(f)
+       }
+}
+
+impl Display for SignedRawInvoice {
+       fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
+               let hrp = self.raw_invoice.hrp.to_string();
+               let mut data  = self.raw_invoice.data.to_base32();
+               data.extend_from_slice(&self.signature.to_base32());
+
+               bech32::encode_to_fmt(f, &hrp, data).expect("HRP is valid")?;
+
+               Ok(())
+       }
+}
+
+impl Display for RawHrp {
+       fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
+               let amount = match self.raw_amount {
+                       Some(ref amt) => amt.to_string(),
+                       None => String::new(),
+               };
+
+               let si_prefix = match self.si_prefix {
+                       Some(ref si) => si.to_string(),
+                       None => String::new(),
+               };
+
+               write!(
+                       f,
+                       "ln{}{}{}",
+                       self.currency,
+                       amount,
+                       si_prefix
+               )
+       }
+}
+
+impl Display for Currency {
+       fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
+               let currency_code = match *self {
+                       Currency::Bitcoin => "bc",
+                       Currency::BitcoinTestnet => "tb",
+                       Currency::Regtest => "bcrt",
+                       Currency::Simnet => "sb",
+               };
+               write!(f, "{}", currency_code)
+       }
+}
+
+impl Display for SiPrefix {
+       fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
+               write!(f, "{}",
+                       match *self {
+                               SiPrefix::Milli => "m",
+                               SiPrefix::Micro => "u",
+                               SiPrefix::Nano => "n",
+                               SiPrefix::Pico => "p",
+                       }
+               )
+       }
+}
+
+fn encode_int_be_base32(int: u64) -> Vec<u5> {
+       let base = 32u64;
+
+       let mut out_vec = Vec::<u5>::new();
+
+       let mut rem_int = int;
+       while rem_int != 0 {
+               out_vec.push(u5::try_from_u8((rem_int % base) as u8).expect("always <32"));
+               rem_int /= base;
+       }
+
+       out_vec.reverse();
+       out_vec
+}
+
+fn encoded_int_be_base32_size(int: u64) -> usize {
+       for pos in (0..13).rev() {
+               if int & (0x1f << (5 * pos)) != 0 {
+                       return (pos + 1) as usize;
+               }
+       }
+       0usize
+}
+
+fn encode_int_be_base256<T: Into<u64>>(int: T) -> Vec<u8> {
+       let base = 256u64;
+
+       let mut out_vec = Vec::<u8>::new();
+
+       let mut rem_int: u64 = int.into();
+       while rem_int != 0 {
+               out_vec.push((rem_int % base) as u8);
+               rem_int /= base;
+       }
+
+       out_vec.reverse();
+       out_vec
+}
+
+/// Appends the default value of `T` to the front of the `in_vec` till it reaches the length
+/// `target_length`. If `in_vec` already is too lang `None` is returned.
+fn try_stretch<T>(mut in_vec: Vec<T>, target_len: usize) -> Option<Vec<T>>
+       where T: Default + Copy
+{
+       if in_vec.len() > target_len {
+               None
+       } else if in_vec.len() == target_len {
+               Some(in_vec)
+       } else {
+               let mut out_vec = Vec::<T>::with_capacity(target_len);
+               out_vec.append(&mut vec![T::default(); target_len - in_vec.len()]);
+               out_vec.append(&mut in_vec);
+               Some(out_vec)
+       }
+}
+
+impl ToBase32 for RawDataPart {
+       fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
+               // encode timestamp
+               self.timestamp.write_base32(writer)?;
+
+               // encode tagged fields
+               for tagged_field in self.tagged_fields.iter() {
+                       tagged_field.write_base32(writer)?;
+               }
+
+               Ok(())
+       }
+}
+
+impl ToBase32 for PositiveTimestamp {
+       fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
+               // FIXME: use writer for int encoding
+               writer.write(
+                       &try_stretch(encode_int_be_base32(self.as_unix_timestamp()), 7)
+                               .expect("Can't be longer due than 7 u5s due to timestamp bounds")
+               )
+       }
+}
+
+impl ToBase32 for RawTaggedField {
+       fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
+               match *self {
+                       RawTaggedField::UnknownSemantics(ref content) => {
+                               writer.write(content)
+                       },
+                       RawTaggedField::KnownSemantics(ref tagged_field) => {
+                               tagged_field.write_base32(writer)
+                       }
+               }
+       }
+}
+
+impl ToBase32 for Sha256 {
+       fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
+               (&self.0[..]).write_base32(writer)
+       }
+}
+impl Base32Len for Sha256 {
+       fn base32_len(&self) -> usize {
+               (&self.0[..]).base32_len()
+       }
+}
+
+impl ToBase32 for Description {
+       fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
+               self.as_bytes().write_base32(writer)
+       }
+}
+
+impl Base32Len for Description {
+       fn base32_len(&self) -> usize {
+               self.0.as_bytes().base32_len()
+       }
+}
+
+impl ToBase32 for PayeePubKey {
+       fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
+               (&self.serialize()[..]).write_base32(writer)
+       }
+}
+
+impl Base32Len for PayeePubKey {
+       fn base32_len(&self) -> usize {
+               bytes_size_to_base32_size(secp256k1::constants::PUBLIC_KEY_SIZE)
+       }
+}
+
+impl ToBase32 for PaymentSecret {
+       fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
+               (&self.0[..]).write_base32(writer)
+       }
+}
+
+impl Base32Len for PaymentSecret {
+       fn base32_len(&self) -> usize {
+               bytes_size_to_base32_size(32)
+       }
+}
+
+impl ToBase32 for ExpiryTime {
+       fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
+               writer.write(&encode_int_be_base32(self.as_seconds()))
+       }
+}
+
+impl Base32Len for ExpiryTime {
+       fn base32_len(&self) -> usize {
+               encoded_int_be_base32_size(self.0.as_secs())
+       }
+}
+
+impl ToBase32 for MinFinalCltvExpiry {
+       fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
+               writer.write(&encode_int_be_base32(self.0))
+       }
+}
+
+impl Base32Len for MinFinalCltvExpiry {
+       fn base32_len(&self) -> usize {
+               encoded_int_be_base32_size(self.0)
+       }
+}
+
+impl ToBase32 for Fallback {
+       fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
+               match *self {
+                       Fallback::SegWitProgram {version: v, program: ref p} => {
+                               writer.write_u5(v)?;
+                               p.write_base32(writer)
+                       },
+                       Fallback::PubKeyHash(ref hash) => {
+                               writer.write_u5(u5::try_from_u8(17).expect("17 < 32"))?;
+                               (&hash[..]).write_base32(writer)
+                       },
+                       Fallback::ScriptHash(ref hash) => {
+                               writer.write_u5(u5::try_from_u8(18).expect("18 < 32"))?;
+                               (&hash[..]).write_base32(writer)
+                       }
+               }
+       }
+}
+
+impl Base32Len for Fallback {
+       fn base32_len(&self) -> usize {
+               match *self {
+                       Fallback::SegWitProgram {program: ref p, ..} => {
+                               bytes_size_to_base32_size(p.len()) + 1
+                       },
+                       Fallback::PubKeyHash(_) | Fallback::ScriptHash(_) => {
+                               33
+                       },
+               }
+       }
+}
+
+impl ToBase32 for Route {
+       fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
+               let mut converter = BytesToBase32::new(writer);
+
+               for hop in self.iter() {
+                       converter.append(&hop.pubkey.serialize()[..])?;
+                       converter.append(&hop.short_channel_id[..])?;
+
+                       let fee_base_msat = try_stretch(
+                               encode_int_be_base256(hop.fee_base_msat),
+                               4
+                       ).expect("sizeof(u32) == 4");
+                       converter.append(&fee_base_msat)?;
+
+                       let fee_proportional_millionths = try_stretch(
+                               encode_int_be_base256(hop.fee_proportional_millionths),
+                               4
+                       ).expect("sizeof(u32) == 4");
+                       converter.append(&fee_proportional_millionths)?;
+
+                       let cltv_expiry_delta = try_stretch(
+                               encode_int_be_base256(hop.cltv_expiry_delta),
+                               2
+                       ).expect("sizeof(u16) == 2");
+                       converter.append(&cltv_expiry_delta)?;
+               }
+
+               converter.finalize()?;
+               Ok(())
+       }
+}
+
+impl Base32Len for Route {
+       fn base32_len(&self) -> usize {
+               bytes_size_to_base32_size(self.0.len() * 51)
+       }
+}
+
+impl ToBase32 for TaggedField {
+       fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
+               /// Writes a tagged field: tag, length and data. `tag` should be in `0..32` otherwise the
+               /// function will panic.
+               fn write_tagged_field<W, P>(writer: &mut W, tag: u8, payload: &P) -> Result<(), W::Err>
+                       where W: WriteBase32,
+                                 P: ToBase32 + Base32Len,
+               {
+                       let len = payload.base32_len();
+                       assert!(len < 1024, "Every tagged field data can be at most 1023 bytes long.");
+
+                       writer.write_u5(u5::try_from_u8(tag).expect("invalid tag, not in 0..32"))?;
+                       writer.write(&try_stretch(
+                               encode_int_be_base32(len as u64),
+                               2
+                       ).expect("Can't be longer than 2, see assert above."))?;
+                       payload.write_base32(writer)
+               }
+
+               match *self {
+                       TaggedField::PaymentHash(ref hash) => {
+                               write_tagged_field(writer, constants::TAG_PAYMENT_HASH, hash)
+                       },
+                       TaggedField::Description(ref description) => {
+                               write_tagged_field(writer, constants::TAG_DESCRIPTION, description)
+                       },
+                       TaggedField::PayeePubKey(ref pub_key) => {
+                               write_tagged_field(writer, constants::TAG_PAYEE_PUB_KEY, pub_key)
+                       },
+                       TaggedField::DescriptionHash(ref hash) => {
+                               write_tagged_field(writer, constants::TAG_DESCRIPTION_HASH, hash)
+                       },
+                       TaggedField::ExpiryTime(ref duration) => {
+                               write_tagged_field(writer, constants::TAG_EXPIRY_TIME, duration)
+                       },
+                       TaggedField::MinFinalCltvExpiry(ref expiry) => {
+                               write_tagged_field(writer, constants::TAG_MIN_FINAL_CLTV_EXPIRY, expiry)
+                       },
+                       TaggedField::Fallback(ref fallback_address) => {
+                               write_tagged_field(writer, constants::TAG_FALLBACK, fallback_address)
+                       },
+                       TaggedField::Route(ref route_hops) => {
+                               write_tagged_field(writer, constants::TAG_ROUTE, route_hops)
+                       },
+                       TaggedField::PaymentSecret(ref payment_secret) => {
+                                 write_tagged_field(writer, constants::TAG_PAYMENT_SECRET, payment_secret)
+                       },
+
+               }
+       }
+}
+
+impl ToBase32 for Signature {
+       fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
+               let mut converter = BytesToBase32::new(writer);
+               let (recovery_id, signature) = self.0.serialize_compact();
+               converter.append(&signature[..])?;
+               converter.append_u8(recovery_id.to_i32() as u8)?;
+               converter.finalize()
+       }
+}
+
+#[cfg(test)]
+mod test {
+       use bech32::CheckBase32;
+
+       #[test]
+       fn test_currency_code() {
+               use Currency;
+
+               assert_eq!("bc", Currency::Bitcoin.to_string());
+               assert_eq!("tb", Currency::BitcoinTestnet.to_string());
+               assert_eq!("bcrt", Currency::Regtest.to_string());
+               assert_eq!("sb", Currency::Simnet.to_string());
+       }
+
+       #[test]
+       fn test_raw_hrp() {
+               use ::{Currency, RawHrp, SiPrefix};
+
+               let hrp = RawHrp {
+                       currency: Currency::Bitcoin,
+                       raw_amount: Some(100),
+                       si_prefix: Some(SiPrefix::Micro),
+               };
+
+               assert_eq!(hrp.to_string(), "lnbc100u");
+       }
+
+       #[test]
+       fn test_encode_int_be_base32() {
+               use ser::encode_int_be_base32;
+
+               let input: u64 = 33764;
+               let expected_out = CheckBase32::check_base32(&[1, 0, 31, 4]).unwrap();
+
+               assert_eq!(expected_out, encode_int_be_base32(input));
+       }
+
+       #[test]
+       fn test_encode_int_be_base256() {
+               use ser::encode_int_be_base256;
+
+               let input: u64 = 16842530;
+               let expected_out = vec![1, 0, 255, 34];
+
+               assert_eq!(expected_out, encode_int_be_base256(input));
+       }
+}
diff --git a/lightning-invoice/src/tb.rs b/lightning-invoice/src/tb.rs
new file mode 100644 (file)
index 0000000..dde8a53
--- /dev/null
@@ -0,0 +1,10 @@
+pub trait Bool {}
+
+#[derive(Copy, Clone)]
+pub struct True {}
+
+#[derive(Copy, Clone)]
+pub struct False {}
+
+impl Bool for True {}
+impl Bool for False {}
\ No newline at end of file
diff --git a/lightning-invoice/tests/ser_de.rs b/lightning-invoice/tests/ser_de.rs
new file mode 100644 (file)
index 0000000..403f8f1
--- /dev/null
@@ -0,0 +1,148 @@
+extern crate bitcoin_hashes;
+extern crate lightning_invoice;
+extern crate secp256k1;
+
+use bitcoin_hashes::hex::FromHex;
+use bitcoin_hashes::sha256;
+use lightning_invoice::*;
+use secp256k1::Secp256k1;
+use secp256k1::key::SecretKey;
+use secp256k1::recovery::{RecoverableSignature, RecoveryId};
+use std::time::{Duration, UNIX_EPOCH};
+
+// TODO: add more of the examples from BOLT11 and generate ones causing SemanticErrors
+
+fn get_test_tuples() -> Vec<(String, SignedRawInvoice, Option<SemanticError>)> {
+       vec![
+               (
+                       "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmw\
+                       wd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d73gafnh3cax9rn449d9p5uxz9\
+                       ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ecky03ylcqca784w".to_owned(),
+                       InvoiceBuilder::new(Currency::Bitcoin)
+                               .timestamp(UNIX_EPOCH + Duration::from_secs(1496314658))
+                               .payment_hash(sha256::Hash::from_hex(
+                                               "0001020304050607080900010203040506070809000102030405060708090102"
+                               ).unwrap())
+                               .description("Please consider supporting this project".to_owned())
+                               .build_raw()
+                               .unwrap()
+                               .sign(|_| {
+                                       RecoverableSignature::from_compact(
+                                               & [
+                                                       0x38u8, 0xec, 0x68, 0x91, 0x34, 0x5e, 0x20, 0x41, 0x45, 0xbe, 0x8a,
+                                                       0x3a, 0x99, 0xde, 0x38, 0xe9, 0x8a, 0x39, 0xd6, 0xa5, 0x69, 0x43,
+                                                       0x4e, 0x18, 0x45, 0xc8, 0xaf, 0x72, 0x05, 0xaf, 0xcf, 0xcc, 0x7f,
+                                                       0x42, 0x5f, 0xcd, 0x14, 0x63, 0xe9, 0x3c, 0x32, 0x88, 0x1e, 0xad,
+                                                       0x0d, 0x6e, 0x35, 0x6d, 0x46, 0x7e, 0xc8, 0xc0, 0x25, 0x53, 0xf9,
+                                                       0xaa, 0xb1, 0x5e, 0x57, 0x38, 0xb1, 0x1f, 0x12, 0x7f
+                                               ],
+                                               RecoveryId::from_i32(0).unwrap()
+                                       )
+                               }).unwrap(),
+                       None
+               ),
+               (
+                       "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3\
+                       k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch\
+                       9zw97j25emudupq63nyw24cg27h2rspfj9srp".to_owned(),
+                       InvoiceBuilder::new(Currency::Bitcoin)
+                               .amount_pico_btc(2500000000)
+                               .timestamp(UNIX_EPOCH + Duration::from_secs(1496314658))
+                               .payment_hash(sha256::Hash::from_hex(
+                                       "0001020304050607080900010203040506070809000102030405060708090102"
+                               ).unwrap())
+                               .description("1 cup coffee".to_owned())
+                               .expiry_time(Duration::from_secs(60))
+                               .build_raw()
+                               .unwrap()
+                               .sign(|_| {
+                                       RecoverableSignature::from_compact(
+                                               & [
+                                                       0xe8, 0x96, 0x39, 0xba, 0x68, 0x14, 0xe3, 0x66, 0x89, 0xd4, 0xb9, 0x1b,
+                                                       0xf1, 0x25, 0xf1, 0x03, 0x51, 0xb5, 0x5d, 0xa0, 0x57, 0xb0, 0x06, 0x47,
+                                                       0xa8, 0xda, 0xba, 0xeb, 0x8a, 0x90, 0xc9, 0x5f, 0x16, 0x0f, 0x9d, 0x5a,
+                                                       0x6e, 0x0f, 0x79, 0xd1, 0xfc, 0x2b, 0x96, 0x42, 0x38, 0xb9, 0x44, 0xe2,
+                                                       0xfa, 0x4a, 0xa6, 0x77, 0xc6, 0xf0, 0x20, 0xd4, 0x66, 0x47, 0x2a, 0xb8,
+                                                       0x42, 0xbd, 0x75, 0x0e
+                                               ],
+                                               RecoveryId::from_i32(1).unwrap()
+                                       )
+                               }).unwrap(),
+                       None
+               ),
+               (
+                       "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qq\
+                       dhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqscc6gd6ql3jrc5yzme8v4ntcewwz5cnw92tz0pc8qcuufvq7k\
+                       hhr8wpald05e92xw006sq94mg8v2ndf4sefvf9sygkshp5zfem29trqq2yxxz7".to_owned(),
+                       InvoiceBuilder::new(Currency::Bitcoin)
+                               .amount_pico_btc(20000000000)
+                               .timestamp(UNIX_EPOCH + Duration::from_secs(1496314658))
+                               .payment_hash(sha256::Hash::from_hex(
+                                       "0001020304050607080900010203040506070809000102030405060708090102"
+                               ).unwrap())
+                               .description_hash(sha256::Hash::from_hex(
+                                       "3925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c1"
+                               ).unwrap())
+                               .build_raw()
+                               .unwrap()
+                               .sign(|_| {
+                                       RecoverableSignature::from_compact(
+                                               & [
+                                                       0xc6, 0x34, 0x86, 0xe8, 0x1f, 0x8c, 0x87, 0x8a, 0x10, 0x5b, 0xc9, 0xd9,
+                                                       0x59, 0xaf, 0x19, 0x73, 0x85, 0x4c, 0x4d, 0xc5, 0x52, 0xc4, 0xf0, 0xe0,
+                                                       0xe0, 0xc7, 0x38, 0x96, 0x03, 0xd6, 0xbd, 0xc6, 0x77, 0x07, 0xbf, 0x6b,
+                                                       0xe9, 0x92, 0xa8, 0xce, 0x7b, 0xf5, 0x00, 0x16, 0xbb, 0x41, 0xd8, 0xa9,
+                                                       0xb5, 0x35, 0x86, 0x52, 0xc4, 0x96, 0x04, 0x45, 0xa1, 0x70, 0xd0, 0x49,
+                                                       0xce, 0xd4, 0x55, 0x8c
+                                               ],
+                                               RecoveryId::from_i32(0).unwrap()
+                                       )
+                               }).unwrap(),
+                       None
+               ),
+               (
+                       "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp59g4z52329g4z52329g4z52329g4z52329g4z52329g4z52329g4q9gkzyrw8zhfxmrcxsx7hj40yejq6lkvn75l9yjmapjv94haz8x8jy2tvmgex8rnyqkj825csd2t64fu0p4ctad2cf4tgy5gh2fns6ygp6pnc3y".to_owned(),
+                       InvoiceBuilder::new(Currency::Bitcoin)
+                               .payment_hash(sha256::Hash::from_hex(
+                                       "0001020304050607080900010203040506070809000102030405060708090102"
+                               ).unwrap())
+                               .description("coffee beans".to_string())
+                               .amount_pico_btc(20000000000)
+                               .timestamp(UNIX_EPOCH + Duration::from_secs(1496314658))
+                               .payment_secret(PaymentSecret([42; 32]))
+                               .build_signed(|msg_hash| {
+                                       let privkey = SecretKey::from_slice(&[41; 32]).unwrap();
+                                       let secp_ctx = Secp256k1::new();
+                                       secp_ctx.sign_recoverable(msg_hash, &privkey)
+                               })
+                               .unwrap()
+                               .into_signed_raw(),
+                       None
+               )
+       ]
+}
+
+
+#[test]
+fn serialize() {
+       for (serialized, deserialized, _) in get_test_tuples() {
+               assert_eq!(deserialized.to_string(), serialized);
+       }
+}
+
+#[test]
+fn deserialize() {
+       for (serialized, deserialized, maybe_error) in get_test_tuples() {
+               let parsed = serialized.parse::<SignedRawInvoice>().unwrap();
+
+               assert_eq!(parsed, deserialized);
+
+               let validated = Invoice::from_signed(parsed);
+
+               if let Some(error) = maybe_error {
+                       assert_eq!(Err(error), validated);
+               } else {
+                       assert!(validated.is_ok());
+               }
+       }
+}