Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic invoices (BP+LNP+RGB) #165

Merged
merged 1 commit into from Jan 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Expand Up @@ -43,7 +43,7 @@ bitcoin = { version = "~0.25.1", features = ["rand"] }
bitcoin_hashes = "~0.9.3" # we need macro from here
# <https://github.com/LNP-BP/LNPBPs/blob/master/lnpbp-0002.md#deterministic-public-key-extraction-from-bitcoin-script>
# We have to fix version of miniscript as required by LNPBP-2 specification
miniscript = { version = "~3.0.0", features = ["compiler"] }
miniscript = { version = "~4.0.1", features = ["compiler"] }
bech32 = "~0.7.2"
lightning-invoice = { version = "~0.3.0", optional = true }
chacha20poly1305 = "~0.7.0"
Expand Down
Empty file added src/bp/derivation/mod.rs
Empty file.
Empty file added src/bp/derivation/slip32.rs
Empty file.
128 changes: 128 additions & 0 deletions src/bp/invoice.rs
@@ -0,0 +1,128 @@
// LNP/BP Core Library implementing LNPBP specifications & standards
// Written in 2020 by
// Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
//
// To the extent possible under law, the author(s) have dedicated all
// copyright and related and neighboring rights to this software to
// the public domain worldwide. This software is distributed without
// any warranty.
//
// You should have received a copy of the MIT License
// along with this software.
// If not, see <https://opensource.org/licenses/MIT>.

use bitcoin::hashes::sha256d;
use bitcoin::secp256k1::Signature;
use bitcoin::Address;
use miniscript::{descriptor::DescriptorPublicKey, Descriptor, Miniscript};
use url::Url;

use crate::bp::blind::OutpointHash;
use crate::bp::chain::AssetId;
use crate::bp::{Chain, HashLock, P2pNetworkId, Psbt, ScriptPubkeyFormat};
use crate::lnp::{tlv, Features};
use crate::secp256k1;

#[derive(Tlv)]
#[lnpbp_crate(crate)]
pub enum FieldType {
#[tlv(type = 0x01)]
Payers,
}

#[derive(Lnp)]
#[tlv_types(FieldType)]
#[lnpbp_crate(crate)]
pub struct Invoice {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general this struct can grow to arbitrary size and, being so generic, it's probably difficult to identify a very compact encoding for it. This might make it impossible to pass it via NFC or QRcode which currently are the preferred methods for invoice sharing. Does this have a different usecase or am I missing something?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The struct is not directly encoded; its fields are TLV records parsed from TLV stream as in LN itself, meaning that most of invoices will use 0 bytes on the fields they are not using (i.e. each optional when is not specified is basically absence of the data). Thus while you will be able to add more fields with the time, this will not inflate the encoded version when these new fields are not used.

Even now you can add fields w/o changing the structure if you will use odd TLV types for them ("it's ok to be odd" rule of LN). They will go to unknown map at the end of the structure.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So is this going to be bolt-11 compliant for invoices that include lightning-only information?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated. Two unrelated stories. The first one is that this standard uses the same encoding as LN. It does not mean it makes it interoperable, it's just a timeproven way. And also this simplifies conversion of this new invoice type into a lightning message if one day someone will need that (which can't be done with current BOLT-11 by the way).

The second story is that the proposed invoice is parsable from BOLT-11 invoice or can be converted into BOLT-11 invoice. But this has nothing to do with used TLV mechanism

network: P2pNetworkId,

/// List of beneficiary dests ordered in most desirable first order
beneficiaries: Vec<Beneficiary>,

/// Optional list of payers authored to pay
#[tlv(type = FieldType::Payers)]
payers: Vec<Payer>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the meaning of this? From what I see in most cases there is no way to check who is the initiator of a payment and also it would be quite rude to refuse a payment after receiving it because "payer was not the right one"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be used only if the creator of invoice is willing to be payed by specific public keys, to whatever reason. This will indicate that some other public key will pay the invoice, the payment may be returned instead of goods being delivered.

quantity: Quantity,
price: Option<AmountExt>,
Comment on lines +45 to +46
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the meaning of 'quantity' and 'price' fields? How do they relate with the amount that a payer should pay to get the invoice fulfilled?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea comes from Offers (BOLT 14 proposal) from Rusty Russel. Basically invoice creator specifies price per item, and you can invoice multiple items by paying multiple of the invoice amount


/// If the price of the asset provided by fiat provider URL goes below this
/// limit the merchant will not accept the payment and it will become
/// expired
fiat_requirement: Option<Fiat>,
Copy link
Member

@St333p St333p Dec 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it enough that the price goes below the threshold for a while to make the invoice expired forever? Or can it become "unexpired" again if price goes above threshold later?
What if the price changes while the payment goes through?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably "expired" is not correct term (we have expiry date anyway, which is unrelated to this). So basically this indicates payer that if the current price is below this threshold the payment may be rejected by the merchant.

Again, this is another idea taken from Rusty Russel "Offers" BOLT

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, but price might change significantly while the payment goes through, and an onchain payment can't be taken back once it's done. Maybe it makes sense for a lightning-only invoice, but not if invoice can be payed onchain as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Today this situation is handled by merchants for on-chain txes anyway. This will just give them a standard way to communicate when they will be using their existing failback scenarios (like asking for the second payment on the difference, or returning payment - some fee).

I.e. today merchants have to write this on their website or on ATM, but with this this information can be also presented by the wallet like "the price is very volatile right now and there is a risk that merchant will not accept this payment"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got the chance to read Rusty's bolt12 "Offer" proposal. It includes a non-lightning currency, but it's not in the invoice itself: it's in so-called offers, and the invoice itself only contains a msat value. This way a client should still able to know deterministically whether a given payment will be accepted as paying the invoice.
If a merchant sets a value for "fiat_requirement", then the client is not able to know beforehand if a given payment will be able to pay for the invoice, since exchange rate can change while the payment goes through.

So I'm not sure I understand this correctly, but this seems to me very different from Rusty's proposal.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the approach is very different from the Rusty's, but it was his idea to take fiat into the account :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still not sure it's a good idea to have an invoice design that does not allow payer to deterministically predict whether a payment will be accepted for a given invoice. This could lead to very bad experience if user pays onchain and needs to be refunded onchain, with possibly high fees and lock of funds for a significant amount of time.

I understand that this can happen anyways, but having it as a design choice will probably make this situation more common with (in my opinion) little or no advantages.

I don't want to take this discussion further, I just wanted to give my opinion for what it matters.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I get your point, since this is exactly what we have here (and is absent in LN Offers): a payee can predict whether the merchant will accept the payment. We have an indicative exchange link to get the rate acceptable for merchant, and have a threshold rate after which the merchant MAY have to reject the payment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point is that the rate will change while the payment goes through, and the payment might take hours to be confirmed. User and merchant will not look at the exchange rate in the same moment and this breaks determinism.

But again, it's my humble opinion. If you don't agree please ignore it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My take on this: a merchant must use software (like BtcPay Server) that should check the volatility index and adjust the threshold accordingly. Then merchant can guarantee the payment and merchant risk will be measurable & insurable (or transferrable to the client). Without the proposed mechanism this will be impossible (for the merchant to guarantee QoS for a long-standing invoices) and I see no other way of achieving this (correct me if I'm wrong)

merchant: Option<String>,
asset: Option<AssetId>,
purpose: Option<String>,
details: Option<Details>,
expiry: Option<i64>,

#[tlv_unknown]
unknown: Vec<tlv::Map>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the meaning of this field?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a storage for unknown TLV records, as in any LN message according to LN rules. Pls see my first comment

signature: Option<Signature>,
}

#[non_exhaustive]
pub enum Beneficiary {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to have "push channel opening" as possible beneficiary?
Something like "pay me on lightning if you find a path or open a channel while pushing the amount otherwise". Technically payer already has the data he requires in order to open a channel (which is basically payee's nodeid + IP that he can get from gossip protocol). What's missing is the information that the invoice will be considered "payed" in case of a proper "push channel opening".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about adding such flag. But it seems that it does not make a lot of sense, since it will be up to the payee to decide whether to open direct channel or route the payment.

Basically with the current structure you are still able to create "open channel with me" type of invoices by just leaving amount equal to 0 and specifying lightning payment option with node id/ip address - and that's all.

/// Addresses are usefult when you do not like to leak public key
/// information
Address(Address),

/// Ssed by protocols that work with existing UTXOs and can assign some
/// client-validated data to them (like in RGB). We always hide the real
/// UTXO behind the hashed version (using some salt)
BlindUtxo(OutpointHash),

/// Miniscript-based descriptors allowing custom derivation & key generation
Descriptor(Descriptor<DescriptorPublicKey>),

/// Full transaction template in PSBT format
Psbt(Psbt),

/// Lightning node receiving the payment. Not the same as lightning invoice
/// since many of the invoice data now will be part of [`Invoice`] here.
Lightning(LnAddress),

/// Failback option for all future variants
Other(Vec<u8>),
}

pub struct LnAddress {
node_id: secp256k1::PublicKey,
features: Features,
hash_lock: HashLock,
min_final_cltv_expiry: Option<u16>,
path_hints: Vec<LnPathHint>,
}

/// Path hints for a lightning network payment, equal to the value of the `r`
/// key of the lightning BOLT-11 invoice
/// <https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md#tagged-fields>
pub struct LnPathHint {
node_id: secp256k1::PublicKey,
short_channel_id: ShortChannelId,
fee_base_msat: u32,
fee_proportional_millionths: u32,
cltv_expiry_delta: u16,
}

pub enum AmountExt {
Normal(u64),
Milli(u64, u16),
}

pub struct Details {
commitment: sha256d::Hash,
source: Url,
}

pub struct Fiat {
iso4217: [u8; 3],
coins: u32,
fractions: u8,
price_provider: Url,
}

pub struct Quantity {
min: Option<u32>,
max: Option<u32>,
default: u32,
}
2 changes: 2 additions & 0 deletions src/bp/mod.rs
Expand Up @@ -17,6 +17,7 @@ pub mod blind;
pub mod chain;
pub mod dbc;
pub mod hlc;
mod invoice;
pub mod lex_order;
pub mod psbt;
pub mod resolvers;
Expand All @@ -30,6 +31,7 @@ pub mod tagged_hash;
pub use bip32::{DerivationInfo, DerivationTemplate};
pub use chain::{Chain, P2pNetworkId};
pub use hlc::{HashLock, HashPreimage};
pub use invoice::Invoice;
pub use lex_order::LexOrder;
pub use psbt::Psbt;
pub use scripts::{
Expand Down