mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-01-18 05:12:38 +01:00
Pure import of lightning-invoice crate
Original repo: https://github.com/rust-bitcoin/rust-lightning-invoice
This commit is contained in:
parent
3d80d98339
commit
f00bb10a82
3
lightning-invoice/.gitignore
vendored
Normal file
3
lightning-invoice/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
14
lightning-invoice/.travis-kcov.sh
Normal file
14
lightning-invoice/.travis-kcov.sh
Normal file
@ -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
|
31
lightning-invoice/.travis.yml
Normal file
31
lightning-invoice/.travis.yml
Normal file
@ -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
|
20
lightning-invoice/Cargo.toml
Normal file
20
lightning-invoice/Cargo.toml
Normal file
@ -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"]
|
||||
|
202
lightning-invoice/LICENSE
Normal file
202
lightning-invoice/LICENSE
Normal file
@ -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.
|
17
lightning-invoice/README.md
Normal file
17
lightning-invoice/README.md
Normal file
@ -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
|
2
lightning-invoice/fuzz/.gitignore
vendored
Normal file
2
lightning-invoice/fuzz/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
target
|
||||
hfuzz_*
|
26
lightning-invoice/fuzz/Cargo.toml
Normal file
26
lightning-invoice/fuzz/Cargo.toml
Normal file
@ -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"
|
69
lightning-invoice/fuzz/fuzz_targets/serde_data_part.rs
Normal file
69
lightning-invoice/fuzz/fuzz_targets/serde_data_part.rs
Normal file
@ -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);
|
||||
}
|
||||
}
|
19
lightning-invoice/fuzz/travis-fuzz.sh
Executable file
19
lightning-invoice/fuzz/travis-fuzz.sh
Executable file
@ -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
|
1076
lightning-invoice/src/de.rs
Normal file
1076
lightning-invoice/src/de.rs
Normal file
File diff suppressed because it is too large
Load Diff
1581
lightning-invoice/src/lib.rs
Normal file
1581
lightning-invoice/src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
514
lightning-invoice/src/ser.rs
Normal file
514
lightning-invoice/src/ser.rs
Normal file
@ -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));
|
||||
}
|
||||
}
|
10
lightning-invoice/src/tb.rs
Normal file
10
lightning-invoice/src/tb.rs
Normal file
@ -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 {}
|
148
lightning-invoice/tests/ser_de.rs
Normal file
148
lightning-invoice/tests/ser_de.rs
Normal file
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user