Skip to content

Commit e496878

Browse files
committed
add RGB amount to bolt11 invoice
1 parent dc3a738 commit e496878

File tree

5 files changed

+135
-15
lines changed

5 files changed

+135
-15
lines changed

lightning-invoice/src/de.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use bech32::{u5, FromBase32};
1111

1212
use bitcoin_hashes::Hash;
1313
use bitcoin_hashes::sha256;
14+
use rgb::ContractId;
1415
use crate::prelude::*;
1516
use lightning::ln::PaymentSecret;
1617
use lightning::routing::gossip::RoutingFees;
@@ -24,7 +25,7 @@ use secp256k1::PublicKey;
2425

2526
use super::{Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiry, Fallback, PayeePubKey, InvoiceSignature, PositiveTimestamp,
2627
SemanticError, PrivateRoute, ParseError, ParseOrSemanticError, Description, RawTaggedField, Currency, RawHrp, SiPrefix, RawInvoice,
27-
constants, SignedRawInvoice, RawDataPart, InvoiceFeatures};
28+
constants, SignedRawInvoice, RawDataPart, InvoiceFeatures, RgbAmount, RgbContractId};
2829

2930
use self::hrp_sm::parse_hrp;
3031

@@ -461,6 +462,10 @@ impl FromBase32 for TaggedField {
461462
Ok(TaggedField::PaymentSecret(PaymentSecret::from_base32(field_data)?)),
462463
constants::TAG_FEATURES =>
463464
Ok(TaggedField::Features(InvoiceFeatures::from_base32(field_data)?)),
465+
constants::TAG_RGB_AMOUNT =>
466+
Ok(TaggedField::RgbAmount(RgbAmount::from_base32(field_data)?)),
467+
constants::TAG_RGB_CONTRACT_ID =>
468+
Ok(TaggedField::RgbContractId(RgbContractId::from_base32(field_data)?)),
464469
_ => {
465470
// "A reader MUST skip over unknown fields"
466471
Err(ParseError::Skip)
@@ -619,6 +624,32 @@ impl FromBase32 for PrivateRoute {
619624
}
620625
}
621626

627+
impl FromBase32 for RgbAmount {
628+
type Err = ParseError;
629+
630+
fn from_base32(field_data: &[u5]) -> Result<RgbAmount, ParseError> {
631+
let rgb_amount = parse_int_be::<u64, u5>(field_data, 32);
632+
if let Some(rgb_amount) = rgb_amount {
633+
Ok(RgbAmount(rgb_amount))
634+
} else {
635+
Err(ParseError::IntegerOverflowError)
636+
}
637+
}
638+
}
639+
640+
impl FromBase32 for RgbContractId {
641+
type Err = ParseError;
642+
643+
fn from_base32(field_data: &[u5]) -> Result<RgbContractId, ParseError> {
644+
let bytes = Vec::<u8>::from_base32(field_data)?;
645+
let rgb_contract_id_str = String::from(str::from_utf8(&bytes)?);
646+
match ContractId::from_str(&rgb_contract_id_str) {
647+
Ok(cid) => Ok(RgbContractId(cid)),
648+
Err(_) => Err(ParseError::InvalidContractId),
649+
}
650+
}
651+
}
652+
622653
impl Display for ParseError {
623654
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
624655
match *self {
@@ -667,6 +698,9 @@ impl Display for ParseError {
667698
ParseError::Skip => {
668699
f.write_str("the tagged field has to be skipped because of an unexpected, but allowed property")
669700
},
701+
ParseError::InvalidContractId => {
702+
f.write_str("invalid RGB contract ID")
703+
},
670704
}
671705
}
672706
}

lightning-invoice/src/lib.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ use lightning::routing::gossip::RoutingFees;
5555
use lightning::routing::router::RouteHint;
5656
use lightning::util::invoice::construct_invoice_preimage;
5757

58+
use rgb::ContractId;
5859
use secp256k1::PublicKey;
5960
use secp256k1::{Message, Secp256k1};
6061
use secp256k1::ecdsa::RecoverableSignature;
@@ -121,6 +122,7 @@ pub enum ParseError {
121122
InvalidScriptHashLength,
122123
InvalidRecoveryId,
123124
InvalidSliceLength(String),
125+
InvalidContractId,
124126

125127
/// Not an error, but used internally to signal that a part of the invoice should be ignored
126128
/// according to BOLT11
@@ -416,6 +418,8 @@ pub enum TaggedField {
416418
PrivateRoute(PrivateRoute),
417419
PaymentSecret(PaymentSecret),
418420
Features(InvoiceFeatures),
421+
RgbAmount(RgbAmount),
422+
RgbContractId(RgbContractId),
419423
}
420424

421425
/// SHA-256 hash
@@ -443,6 +447,14 @@ pub struct ExpiryTime(Duration);
443447
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
444448
pub struct MinFinalCltvExpiry(pub u64);
445449

450+
/// Requested amount for the RGB asset
451+
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
452+
pub struct RgbAmount(pub u64);
453+
454+
/// Requested RGB contract ID
455+
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
456+
pub struct RgbContractId(pub ContractId);
457+
446458
// TODO: better types instead onf byte arrays
447459
/// Fallback address in case no LN payment is possible
448460
#[allow(missing_docs)]
@@ -481,6 +493,8 @@ pub mod constants {
481493
pub const TAG_PRIVATE_ROUTE: u8 = 3;
482494
pub const TAG_PAYMENT_SECRET: u8 = 16;
483495
pub const TAG_FEATURES: u8 = 5;
496+
pub const TAG_RGB_AMOUNT: u8 = 30;
497+
pub const TAG_RGB_CONTRACT_ID: u8 = 31;
484498
}
485499

486500
impl InvoiceBuilder<tb::False, tb::False, tb::False, tb::False, tb::False> {
@@ -562,6 +576,18 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBui
562576
}
563577
self
564578
}
579+
580+
/// Sets the RGB amount.
581+
pub fn rgb_amount(mut self, rgb_amount: u64) -> Self {
582+
self.tagged_fields.push(TaggedField::RgbAmount(RgbAmount(rgb_amount)));
583+
self
584+
}
585+
586+
/// Sets the RGB contract ID.
587+
pub fn rgb_contract_id(mut self, rgb_contract_id: ContractId) -> Self {
588+
self.tagged_fields.push(TaggedField::RgbContractId(RgbContractId(rgb_contract_id)));
589+
self
590+
}
565591
}
566592

567593
impl<D: tb::Bool, H: tb::Bool, C: tb::Bool, S: tb::Bool> InvoiceBuilder<D, H, tb::True, C, S> {
@@ -938,6 +964,14 @@ impl RawInvoice {
938964
find_extract!(self.known_tagged_fields(), TaggedField::Features(ref x), x)
939965
}
940966

967+
pub fn rgb_amount(&self) -> Option<&RgbAmount> {
968+
find_extract!(self.known_tagged_fields(), TaggedField::RgbAmount(ref x), x)
969+
}
970+
971+
pub fn rgb_contract_id(&self) -> Option<&RgbContractId> {
972+
find_extract!(self.known_tagged_fields(), TaggedField::RgbContractId(ref x), x)
973+
}
974+
941975
/// (C-not exported) as we don't support Vec<&NonOpaqueType>
942976
pub fn fallbacks(&self) -> Vec<&Fallback> {
943977
find_all_extract!(self.known_tagged_fields(), TaggedField::Fallback(ref x), x).collect()
@@ -1281,6 +1315,18 @@ impl Invoice {
12811315
fn amount_pico_btc(&self) -> Option<u64> {
12821316
self.signed_invoice.amount_pico_btc()
12831317
}
1318+
1319+
/// Returns the invoice's `rgb_amount` if present
1320+
pub fn rgb_amount(&self) -> Option<u64> {
1321+
self.signed_invoice.rgb_amount()
1322+
.map(|x| x.0)
1323+
}
1324+
1325+
/// Returns the invoice's `rgb_contract_id` if present
1326+
pub fn rgb_contract_id(&self) -> Option<ContractId> {
1327+
self.signed_invoice.rgb_contract_id()
1328+
.map(|x| x.0)
1329+
}
12841330
}
12851331

12861332
impl From<TaggedField> for RawTaggedField {
@@ -1303,6 +1349,8 @@ impl TaggedField {
13031349
TaggedField::PrivateRoute(_) => constants::TAG_PRIVATE_ROUTE,
13041350
TaggedField::PaymentSecret(_) => constants::TAG_PAYMENT_SECRET,
13051351
TaggedField::Features(_) => constants::TAG_FEATURES,
1352+
TaggedField::RgbAmount(_) => constants::TAG_RGB_AMOUNT,
1353+
TaggedField::RgbContractId(_) => constants::TAG_RGB_CONTRACT_ID,
13061354
};
13071355

13081356
u5::try_from_u8(tag).expect("all tags defined are <32")

lightning-invoice/src/payment.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,7 @@ where
425425
hash_map::Entry::Occupied(_) => return Err(PaymentError::Invoice("payment pending")),
426426
hash_map::Entry::Vacant(entry) => entry.insert(PaymentAttempts::new()),
427427
};
428+
write_rgb_payment_info_file(&self.ldk_data_dir, &payment_hash, invoice.rgb_contract_id().unwrap(), invoice.rgb_amount().unwrap());
428429

429430
let payment_secret = Some(invoice.payment_secret().clone());
430431
let mut payment_params = PaymentParameters::from_node_id(invoice.recover_payee_pub_key())

lightning-invoice/src/ser.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use bech32::{ToBase32, u5, WriteBase32, Base32Len};
44
use crate::prelude::*;
55

66
use super::{Invoice, Sha256, TaggedField, ExpiryTime, MinFinalCltvExpiry, Fallback, PayeePubKey, InvoiceSignature, PositiveTimestamp,
7-
PrivateRoute, Description, RawTaggedField, Currency, RawHrp, SiPrefix, constants, SignedRawInvoice, RawDataPart};
7+
PrivateRoute, Description, RawTaggedField, Currency, RawHrp, SiPrefix, constants, SignedRawInvoice, RawDataPart, RgbAmount, RgbContractId};
88

99
/// Converts a stream of bytes written to it to base32. On finalization the according padding will
1010
/// be applied. That means the results of writing two data blocks with one or two `BytesToBase32`
@@ -399,6 +399,30 @@ impl Base32Len for PrivateRoute {
399399
}
400400
}
401401

402+
impl ToBase32 for RgbAmount {
403+
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
404+
writer.write(&encode_int_be_base32(self.0))
405+
}
406+
}
407+
408+
impl Base32Len for RgbAmount {
409+
fn base32_len(&self) -> usize {
410+
encoded_int_be_base32_size(self.0)
411+
}
412+
}
413+
414+
impl ToBase32 for RgbContractId {
415+
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
416+
self.0.to_string().as_bytes().write_base32(writer)
417+
}
418+
}
419+
420+
impl Base32Len for RgbContractId {
421+
fn base32_len(&self) -> usize {
422+
self.0.to_string().as_bytes().base32_len()
423+
}
424+
}
425+
402426
impl ToBase32 for TaggedField {
403427
fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
404428
/// Writes a tagged field: tag, length and data. `tag` should be in `0..32` otherwise the
@@ -449,6 +473,12 @@ impl ToBase32 for TaggedField {
449473
TaggedField::Features(ref features) => {
450474
write_tagged_field(writer, constants::TAG_FEATURES, features)
451475
},
476+
TaggedField::RgbAmount(ref rgb_amount) => {
477+
write_tagged_field(writer, constants::TAG_RGB_AMOUNT, rgb_amount)
478+
},
479+
TaggedField::RgbContractId(ref rgb_contract_id) => {
480+
write_tagged_field(writer, constants::TAG_RGB_CONTRACT_ID, rgb_contract_id)
481+
},
452482
}
453483
}
454484
}

lightning-invoice/src/utils.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use lightning::ln::inbound_payment::{create, create_from_hash, ExpandedKey};
1717
use lightning::routing::gossip::RoutingFees;
1818
use lightning::routing::router::{InFlightHtlcs, Route, RouteHint, RouteHintHop};
1919
use lightning::util::logger::Logger;
20+
use rgb::ContractId;
2021
use secp256k1::PublicKey;
2122
use core::ops::Deref;
2223
use core::time::Duration;
@@ -234,7 +235,7 @@ where
234235
/// in excess of the current time.
235236
pub fn create_invoice_from_channelmanager<M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>(
236237
channelmanager: &ChannelManager<M, T, K, F, L>, keys_manager: K, logger: L,
237-
network: Currency, amt_msat: Option<u64>, description: String, invoice_expiry_delta_secs: u32
238+
network: Currency, amt_msat: Option<u64>, description: String, invoice_expiry_delta_secs: u32, contract_id: Option<ContractId>, amt_rgb: Option<u64>
238239
) -> Result<Invoice, SignOrCreationError<()>>
239240
where
240241
M::Target: chain::Watch<<K::Target as KeysInterface>::Signer>,
@@ -248,7 +249,7 @@ where
248249
.expect("for the foreseeable future this shouldn't happen");
249250
create_invoice_from_channelmanager_and_duration_since_epoch(
250251
channelmanager, keys_manager, logger, network, amt_msat, description, duration,
251-
invoice_expiry_delta_secs
252+
invoice_expiry_delta_secs, contract_id, amt_rgb
252253
)
253254
}
254255

@@ -265,7 +266,7 @@ where
265266
pub fn create_invoice_from_channelmanager_with_description_hash<M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>(
266267
channelmanager: &ChannelManager<M, T, K, F, L>, keys_manager: K, logger: L,
267268
network: Currency, amt_msat: Option<u64>, description_hash: Sha256,
268-
invoice_expiry_delta_secs: u32
269+
invoice_expiry_delta_secs: u32, contract_id: Option<ContractId>, amt_rgb: Option<u64>
269270
) -> Result<Invoice, SignOrCreationError<()>>
270271
where
271272
M::Target: chain::Watch<<K::Target as KeysInterface>::Signer>,
@@ -282,7 +283,7 @@ where
282283

283284
create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch(
284285
channelmanager, keys_manager, logger, network, amt_msat,
285-
description_hash, duration, invoice_expiry_delta_secs
286+
description_hash, duration, invoice_expiry_delta_secs, contract_id, amt_rgb
286287
)
287288
}
288289

@@ -292,7 +293,7 @@ where
292293
pub fn create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch<M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>(
293294
channelmanager: &ChannelManager<M, T, K, F, L>, keys_manager: K, logger: L,
294295
network: Currency, amt_msat: Option<u64>, description_hash: Sha256,
295-
duration_since_epoch: Duration, invoice_expiry_delta_secs: u32
296+
duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, contract_id: Option<ContractId>, amt_rgb: Option<u64>
296297
) -> Result<Invoice, SignOrCreationError<()>>
297298
where
298299
M::Target: chain::Watch<<K::Target as KeysInterface>::Signer>,
@@ -304,7 +305,7 @@ pub fn create_invoice_from_channelmanager_with_description_hash_and_duration_sin
304305
_create_invoice_from_channelmanager_and_duration_since_epoch(
305306
channelmanager, keys_manager, logger, network, amt_msat,
306307
InvoiceDescription::Hash(&description_hash),
307-
duration_since_epoch, invoice_expiry_delta_secs
308+
duration_since_epoch, invoice_expiry_delta_secs, contract_id, amt_rgb
308309
)
309310
}
310311

@@ -314,7 +315,7 @@ pub fn create_invoice_from_channelmanager_with_description_hash_and_duration_sin
314315
pub fn create_invoice_from_channelmanager_and_duration_since_epoch<M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>(
315316
channelmanager: &ChannelManager<M, T, K, F, L>, keys_manager: K, logger: L,
316317
network: Currency, amt_msat: Option<u64>, description: String, duration_since_epoch: Duration,
317-
invoice_expiry_delta_secs: u32
318+
invoice_expiry_delta_secs: u32, contract_id: Option<ContractId>, amt_rgb: Option<u64>
318319
) -> Result<Invoice, SignOrCreationError<()>>
319320
where
320321
M::Target: chain::Watch<<K::Target as KeysInterface>::Signer>,
@@ -328,14 +329,14 @@ pub fn create_invoice_from_channelmanager_and_duration_since_epoch<M: Deref, T:
328329
InvoiceDescription::Direct(
329330
&Description::new(description).map_err(SignOrCreationError::CreationError)?,
330331
),
331-
duration_since_epoch, invoice_expiry_delta_secs
332+
duration_since_epoch, invoice_expiry_delta_secs, contract_id, amt_rgb
332333
)
333334
}
334335

335336
fn _create_invoice_from_channelmanager_and_duration_since_epoch<M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>(
336337
channelmanager: &ChannelManager<M, T, K, F, L>, keys_manager: K, logger: L,
337338
network: Currency, amt_msat: Option<u64>, description: InvoiceDescription,
338-
duration_since_epoch: Duration, invoice_expiry_delta_secs: u32
339+
duration_since_epoch: Duration, invoice_expiry_delta_secs: u32, contract_id: Option<ContractId>, amt_rgb: Option<u64>
339340
) -> Result<Invoice, SignOrCreationError<()>>
340341
where
341342
M::Target: chain::Watch<<K::Target as KeysInterface>::Signer>,
@@ -350,7 +351,7 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch<M: Deref, T: Der
350351
.create_inbound_payment(amt_msat, invoice_expiry_delta_secs)
351352
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?;
352353
_create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash(
353-
channelmanager, keys_manager, logger, network, amt_msat, description, duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret)
354+
channelmanager, keys_manager, logger, network, amt_msat, description, duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret, contract_id, amt_rgb)
354355
}
355356

356357
/// See [`create_invoice_from_channelmanager_and_duration_since_epoch`]
@@ -360,7 +361,7 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch<M: Deref, T: Der
360361
pub fn create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash<M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>(
361362
channelmanager: &ChannelManager<M, T, K, F, L>, keys_manager: K, logger: L,
362363
network: Currency, amt_msat: Option<u64>, description: String, duration_since_epoch: Duration,
363-
invoice_expiry_delta_secs: u32, payment_hash: PaymentHash
364+
invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, contract_id: Option<ContractId>, amt_rgb: Option<u64>
364365
) -> Result<Invoice, SignOrCreationError<()>>
365366
where
366367
M::Target: chain::Watch<<K::Target as KeysInterface>::Signer>,
@@ -377,14 +378,14 @@ pub fn create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_
377378
InvoiceDescription::Direct(
378379
&Description::new(description).map_err(SignOrCreationError::CreationError)?,
379380
),
380-
duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret
381+
duration_since_epoch, invoice_expiry_delta_secs, payment_hash, payment_secret, contract_id, amt_rgb
381382
)
382383
}
383384

384385
fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash<M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>(
385386
channelmanager: &ChannelManager<M, T, K, F, L>, keys_manager: K, logger: L,
386387
network: Currency, amt_msat: Option<u64>, description: InvoiceDescription, duration_since_epoch: Duration,
387-
invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, payment_secret: PaymentSecret
388+
invoice_expiry_delta_secs: u32, payment_hash: PaymentHash, payment_secret: PaymentSecret, contract_id: Option<ContractId>, amt_rgb: Option<u64>
388389
) -> Result<Invoice, SignOrCreationError<()>>
389390
where
390391
M::Target: chain::Watch<<K::Target as KeysInterface>::Signer>,
@@ -416,6 +417,12 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_has
416417
if let Some(amt) = amt_msat {
417418
invoice = invoice.amount_milli_satoshis(amt);
418419
}
420+
if let Some(cid) = contract_id {
421+
invoice = invoice.rgb_contract_id(cid);
422+
}
423+
if let Some(amt) = amt_rgb {
424+
invoice = invoice.rgb_amount(amt);
425+
}
419426

420427
let route_hints = filter_channels(channels, amt_msat, &logger);
421428
for hint in route_hints {

0 commit comments

Comments
 (0)