-
-
Notifications
You must be signed in to change notification settings - Fork 31
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 { | ||
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>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. So I'm not sure I understand this correctly, but this seems to me very different from Rusty's proposal. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 :) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is the meaning of this field? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it make sense to have "push channel opening" as possible beneficiary? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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