1use crate::serde_utils::EntityHex;
4
5use crate::gen::invoice as gen_invoice;
6use arcode::bitbit::{BitReader, BitWriter, MSB};
7use arcode::{ArithmeticDecoder, ArithmeticEncoder, EOFKind, Model};
8use bech32::{encode, u5, FromBase32, ToBase32, Variant, WriteBase32};
9use ckb_hash::blake2b_256;
10use ckb_types::packed::Script as PackedScript;
11use ckb_types::prelude::{Pack, Unpack};
12use gen_invoice::{
13 Description, ExpiryTime, FallbackAddr, Feature, FinalHtlcMinimumExpiryDelta, FinalHtlcTimeout,
14 InvoiceAttr, InvoiceAttrUnion, InvoiceAttrsVec, PayeePublicKey, PaymentHash, PaymentSecret,
15 RawInvoiceDataBuilder, UdtScript,
16};
17use molecule::prelude::Byte;
18use molecule::prelude::{Builder, Entity};
19use nom::{branch::alt, combinator::opt};
20use nom::{
21 bytes::{complete::take_while1, streaming::tag},
22 IResult,
23};
24use secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
25use serde::{Deserialize, Serialize};
26use serde_with::serde_as;
27use sha2::{Digest, Sha256};
28use std::cmp::Ordering;
29use std::fmt::Display;
30use std::io::{Cursor, Result as IoResult};
31use std::num::ParseIntError;
32use std::str::FromStr;
33use thiserror::Error;
34
35#[derive(Error, Debug)]
37pub struct VerificationError(pub molecule::error::VerificationError);
38
39impl PartialEq for VerificationError {
40 fn eq(&self, _other: &Self) -> bool {
41 false
42 }
43}
44
45impl Display for VerificationError {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 self.0.fmt(f)
48 }
49}
50
51#[derive(Error, PartialEq, Debug)]
53pub enum InvoiceError {
54 #[error("Bech32 error: {0}")]
56 Bech32Error(bech32::Error),
57 #[error("Molecule error: {0}")]
59 MoleculeError(VerificationError),
60 #[error("Failed to parse amount: {0}")]
62 ParseAmountError(ParseIntError),
63 #[error("Unknown currency: {0}")]
65 UnknownCurrency(String),
66 #[error("Unknown si prefix: {0}")]
68 UnknownSiPrefix(String),
69 #[error("Parsing failed with malformed HRP: {0}")]
71 MalformedHRP(String),
72 #[error("Too short data part")]
74 TooShortDataPart,
75 #[error("Unexpected end of tagged fields")]
77 UnexpectedEndOfTaggedFields,
78 #[error("Integer overflow error")]
80 IntegerOverflowError,
81 #[error("Invalid recovery id")]
83 InvalidRecoveryId,
84 #[error("Invalid slice length: {0}")]
86 InvalidSliceLength(String),
87 #[error("Invalid signature")]
89 InvalidSignature,
90 #[error("Duplicated attribute key: {0}")]
92 DuplicatedAttributeKey(String),
93 #[error("Payment secret is required for MPP payments")]
95 PaymentSecretRequiredForMpp,
96 #[error("Both payment_hash and payment_preimage are set")]
98 BothPaymenthashAndPreimage,
99 #[error("Neither payment_hash nor payment_preimage is set")]
101 NeitherPaymenthashNorPreimage,
102 #[error("Sign error")]
104 SignError,
105 #[error("Hex decode error: {0}")]
107 HexDecodeError(#[from] hex::FromHexError),
108 #[error("Duplicated invoice found: {0}")]
110 DuplicatedInvoice(String),
111 #[error("Description with length of {0} is too long, max length is 639")]
113 DescriptionTooLong(usize),
114 #[error("Invoice not found")]
116 InvoiceNotFound,
117 #[error("Invoice already exists")]
119 InvoiceAlreadyExists,
120 #[error("Deprecated attribute: {0}")]
122 DeprecatedAttribute(String),
123}
124
125pub const SIGNATURE_U5_SIZE: usize = 104;
127
128pub const MAX_DESCRIPTION_LENGTH: usize = 639;
130
131pub(crate) fn ar_encompress(data: &[u8]) -> IoResult<Vec<u8>> {
134 let mut model = Model::builder().num_bits(8).eof(EOFKind::EndAddOne).build();
135 let mut compressed_writer = BitWriter::new(Cursor::new(vec![]));
136 let mut encoder = ArithmeticEncoder::new(48);
137 for &sym in data {
138 encoder.encode(sym as u32, &model, &mut compressed_writer)?;
139 model.update_symbol(sym as u32);
140 }
141
142 encoder.encode(model.eof(), &model, &mut compressed_writer)?;
143 encoder.finish_encode(&mut compressed_writer)?;
144 compressed_writer.pad_to_byte()?;
145
146 Ok(compressed_writer.get_ref().get_ref().clone())
147}
148
149pub(crate) fn ar_decompress(data: &[u8]) -> IoResult<Vec<u8>> {
151 let mut model = Model::builder().num_bits(8).eof(EOFKind::EndAddOne).build();
152 let mut input_reader = BitReader::<_, MSB>::new(data);
153 let mut decoder = ArithmeticDecoder::new(48);
154 let mut decompressed_data = vec![];
155
156 while !decoder.finished() {
157 let sym = decoder.decode(&model, &mut input_reader)?;
158 model.update_symbol(sym);
159 decompressed_data.push(sym as u8);
160 }
161
162 decompressed_data.pop(); Ok(decompressed_data)
164}
165
166pub fn construct_invoice_preimage(hrp_bytes: &[u8], data_without_signature: &[u5]) -> Vec<u8> {
168 let mut preimage = Vec::<u8>::from(hrp_bytes);
169
170 let mut data_part = Vec::from(data_without_signature);
171 let overhang = (data_part.len() * 5) % 8;
172 if overhang > 0 {
173 data_part.push(u5::try_from_u8(0).expect("u5 from u8"));
175
176 if overhang < 3 {
178 data_part.push(u5::try_from_u8(0).expect("u5 from u8"));
179 }
180 }
181
182 preimage.extend_from_slice(
183 &Vec::<u8>::from_base32(&data_part)
184 .expect("No padding error may occur due to appended zero above."),
185 );
186 preimage
187}
188
189fn nom_scan_hrp(input: &str) -> IResult<&str, (&str, Option<&str>)> {
190 let (input, currency) = alt((tag("fibb"), tag("fibt"), tag("fibd")))(input)?;
191 let (input, amount) = opt(take_while1(|c: char| c.is_numeric()))(input)?;
192 Ok((input, (currency, amount)))
193}
194
195pub fn parse_hrp(input: &str) -> Result<(Currency, Option<u128>), InvoiceError> {
197 match nom_scan_hrp(input) {
198 Ok((left, (currency, amount))) => {
199 if !left.is_empty() {
200 return Err(InvoiceError::MalformedHRP(format!(
201 "{}, unexpected ending `{}`",
202 input, left
203 )));
204 }
205 let currency =
206 Currency::from_str(currency).map_err(|e| InvoiceError::UnknownCurrency(e.0))?;
207 let amount = amount
208 .map(|x| x.parse().map_err(InvoiceError::ParseAmountError))
209 .transpose()?;
210 Ok((currency, amount))
211 }
212 Err(_) => Err(InvoiceError::MalformedHRP(input.to_string())),
213 }
214}
215
216#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
218pub enum CkbInvoiceStatus {
219 Open,
221 Cancelled,
223 Expired,
225 Received,
227 Paid,
229}
230
231impl Display for CkbInvoiceStatus {
232 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233 match self {
234 CkbInvoiceStatus::Open => write!(f, "Open"),
235 CkbInvoiceStatus::Cancelled => write!(f, "Cancelled"),
236 CkbInvoiceStatus::Expired => write!(f, "Expired"),
237 CkbInvoiceStatus::Received => write!(f, "Received"),
238 CkbInvoiceStatus::Paid => write!(f, "Paid"),
239 }
240 }
241}
242
243#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Default)]
245pub enum Currency {
246 Fibb,
248 Fibt,
250 #[default]
252 Fibd,
253}
254
255impl Display for Currency {
256 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
257 match self {
258 Currency::Fibb => write!(f, "fibb"),
259 Currency::Fibt => write!(f, "fibt"),
260 Currency::Fibd => write!(f, "fibd"),
261 }
262 }
263}
264
265#[derive(thiserror::Error, Debug)]
267#[error("Unknown currency: {0}")]
268pub struct UnknownCurrencyError(pub String);
269
270impl FromStr for Currency {
271 type Err = UnknownCurrencyError;
272
273 fn from_str(s: &str) -> Result<Self, Self::Err> {
274 match s {
275 "fibb" => Ok(Self::Fibb),
276 "fibt" => Ok(Self::Fibt),
277 "fibd" => Ok(Self::Fibd),
278 _ => Err(UnknownCurrencyError(s.to_string())),
279 }
280 }
281}
282
283impl TryFrom<u8> for Currency {
284 type Error = UnknownCurrencyError;
285
286 fn try_from(byte: u8) -> Result<Self, Self::Error> {
287 match byte {
288 0 => Ok(Self::Fibb),
289 1 => Ok(Self::Fibt),
290 2 => Ok(Self::Fibd),
291 _ => Err(UnknownCurrencyError(byte.to_string())),
292 }
293 }
294}
295
296#[repr(u8)]
298#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default, Hash)]
299#[serde(rename_all = "snake_case")]
300pub enum HashAlgorithm {
301 #[default]
303 CkbHash = 0,
304 Sha256 = 1,
306}
307
308#[derive(thiserror::Error, Debug)]
310#[error("Unknown Hash Algorithm: {0}")]
311pub struct UnknownHashAlgorithmError(pub u8);
312
313impl TryFrom<u8> for HashAlgorithm {
314 type Error = UnknownHashAlgorithmError;
315
316 fn try_from(value: u8) -> Result<Self, Self::Error> {
317 match value {
318 0 => Ok(HashAlgorithm::CkbHash),
319 1 => Ok(HashAlgorithm::Sha256),
320 _ => Err(UnknownHashAlgorithmError(value)),
321 }
322 }
323}
324
325impl HashAlgorithm {
326 pub fn supported_algorithms() -> Vec<HashAlgorithm> {
327 vec![HashAlgorithm::CkbHash, HashAlgorithm::Sha256]
328 }
329
330 pub fn hash<T: AsRef<[u8]>>(&self, s: T) -> [u8; 32] {
331 match self {
332 HashAlgorithm::CkbHash => blake2b_256(s),
333 HashAlgorithm::Sha256 => sha256(s),
334 }
335 }
336}
337
338pub fn sha256<T: AsRef<[u8]>>(s: T) -> [u8; 32] {
340 let mut hasher = Sha256::new();
341 hasher.update(s.as_ref());
342 hasher.finalize().into()
343}
344
345impl TryFrom<Byte> for HashAlgorithm {
346 type Error = UnknownHashAlgorithmError;
347
348 fn try_from(value: Byte) -> Result<Self, Self::Error> {
349 let value: u8 = value.into();
350 value.try_into()
351 }
352}
353
354#[serde_as]
356#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
357pub struct CkbScript(#[serde_as(as = "EntityHex")] pub PackedScript);
358
359#[derive(Clone, Debug, Eq, PartialEq)]
361pub struct InvoiceSignature(pub RecoverableSignature);
362
363impl PartialOrd for InvoiceSignature {
364 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
365 Some(self.cmp(other))
366 }
367}
368
369impl Ord for InvoiceSignature {
370 fn cmp(&self, other: &Self) -> Ordering {
371 self.0
372 .serialize_compact()
373 .1
374 .cmp(&other.0.serialize_compact().1)
375 }
376}
377
378impl Serialize for InvoiceSignature {
379 fn serialize<S>(
380 &self,
381 serializer: S,
382 ) -> Result<<S as serde::Serializer>::Ok, <S as serde::Serializer>::Error>
383 where
384 S: serde::Serializer,
385 {
386 let base32: Vec<u8> = self.to_base32().iter().map(|x| x.to_u8()).collect();
387 let hex_str = hex::encode(base32);
388 hex_str.serialize(serializer)
389 }
390}
391
392impl<'de> Deserialize<'de> for InvoiceSignature {
393 fn deserialize<D>(deserializer: D) -> Result<Self, <D as serde::Deserializer<'de>>::Error>
394 where
395 D: serde::Deserializer<'de>,
396 {
397 let signature_hex: String = String::deserialize(deserializer)?;
398 let signature_bytes = hex::decode(signature_hex).map_err(serde::de::Error::custom)?;
399 let base32_values = signature_bytes
400 .iter()
401 .map(|x| u5::try_from_u8(*x))
402 .collect::<Result<Vec<u5>, _>>()
403 .map_err(serde::de::Error::custom)?;
404 InvoiceSignature::from_base32(&base32_values).map_err(serde::de::Error::custom)
405 }
406}
407
408struct BytesToBase32<'a, W: WriteBase32 + 'a> {
409 writer: &'a mut W,
410 buffer: u8,
411 buffer_bits: u8,
412}
413
414impl<'a, W: WriteBase32> BytesToBase32<'a, W> {
415 fn new(writer: &'a mut W) -> Self {
416 BytesToBase32 {
417 writer,
418 buffer: 0,
419 buffer_bits: 0,
420 }
421 }
422
423 fn append(&mut self, byte: u8) -> Result<(), <W as WriteBase32>::Err> {
424 let mut bits_remaining = 8;
425 while bits_remaining > 0 {
426 let bits_to_take = std::cmp::min(5 - self.buffer_bits, bits_remaining);
427 self.buffer <<= bits_to_take;
428 self.buffer |= (byte >> (bits_remaining - bits_to_take)) & ((1 << bits_to_take) - 1);
429 self.buffer_bits += bits_to_take;
430 bits_remaining -= bits_to_take;
431
432 if self.buffer_bits == 5 {
433 self.writer
434 .write_u5(u5::try_from_u8(self.buffer).expect("buffer is 5 bits"))?;
435 self.buffer = 0;
436 self.buffer_bits = 0;
437 }
438 }
439 Ok(())
440 }
441
442 fn finalize(mut self) -> Result<(), <W as WriteBase32>::Err> {
443 if self.buffer_bits > 0 {
444 self.buffer <<= 5 - self.buffer_bits;
445 self.writer
446 .write_u5(u5::try_from_u8(self.buffer).expect("buffer is at most 5 bits"))?;
447 }
448 Ok(())
449 }
450}
451
452impl ToBase32 for InvoiceSignature {
453 fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
454 let mut converter = BytesToBase32::new(writer);
455 let (recovery_id, signature) = self.0.serialize_compact();
456 for v in signature
457 .iter()
458 .chain(std::iter::once(&(i32::from(recovery_id) as u8)))
459 {
460 converter.append(*v)?;
461 }
462 converter.finalize()
463 }
464}
465
466impl FromBase32 for InvoiceSignature {
467 type Err = anyhow::Error;
468
469 fn from_base32(field_data: &[u5]) -> Result<InvoiceSignature, Self::Err> {
470 if field_data.len() < 104 {
471 return Err(anyhow::anyhow!(
472 "InvoiceSignature TryFrom<[u5]> failed: unexpected length {}",
473 field_data.len()
474 ));
475 }
476
477 let raw_bytes = Vec::<u8>::from_base32(field_data)?;
478 if raw_bytes.len() != 65 {
479 return Err(anyhow::anyhow!(
480 "InvoiceSignature TryFrom<[u5]> failed: unexpected byte length {}",
481 raw_bytes.len()
482 ));
483 }
484 let recovery_id = RecoveryId::try_from(raw_bytes[64] as i32)?;
485 let signature = RecoverableSignature::from_compact(&raw_bytes[0..64], recovery_id)?;
486 Ok(InvoiceSignature(signature))
487 }
488}
489
490impl InvoiceSignature {
491 pub fn from_base32_checked(signature: &[u5]) -> Result<Self, InvoiceError> {
493 if signature.len() != SIGNATURE_U5_SIZE {
494 return Err(InvoiceError::InvalidSliceLength(
495 "InvoiceSignature::from_base32_checked()".into(),
496 ));
497 }
498 let recoverable_signature_bytes =
499 Vec::<u8>::from_base32(signature).expect("bytes from base32");
500 let sig = &recoverable_signature_bytes[0..64];
501 let recovery_id = RecoveryId::try_from(recoverable_signature_bytes[64] as i32)
502 .expect("Recovery ID from i32");
503
504 Ok(InvoiceSignature(
505 RecoverableSignature::from_compact(sig, recovery_id).expect("signature from compact"),
506 ))
507 }
508}
509
510use crate::protocol::FeatureVector;
511use crate::serde_utils::{duration_hex, U128Hex, U64Hex};
512use crate::Hash256;
513use secp256k1::PublicKey;
514use std::time::Duration;
515
516#[serde_as]
518#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
519#[serde(rename_all = "snake_case")]
520pub enum Attribute {
521 #[serde(with = "U64Hex")]
523 FinalHtlcTimeout(u64),
524 #[serde(with = "U64Hex")]
526 FinalHtlcMinimumExpiryDelta(u64),
527 #[serde(with = "duration_hex")]
529 ExpiryTime(Duration),
530 Description(String),
532 FallbackAddr(String),
534 UdtScript(CkbScript),
536 PayeePublicKey(PublicKey),
538 HashAlgorithm(HashAlgorithm),
540 Feature(FeatureVector),
542 PaymentSecret(Hash256),
544}
545
546#[serde_as]
548#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
549pub struct InvoiceData {
550 #[serde_as(as = "U128Hex")]
552 pub timestamp: u128,
553 pub payment_hash: Hash256,
555 pub attrs: Vec<Attribute>,
557}
558
559#[serde_as]
565#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
566pub struct CkbInvoice {
567 pub currency: Currency,
569 #[serde_as(as = "Option<U128Hex>")]
571 pub amount: Option<u128>,
572 pub signature: Option<InvoiceSignature>,
574 pub data: InvoiceData,
576}
577
578impl CkbInvoice {
579 fn hrp_part(&self) -> String {
580 format!(
581 "{}{}",
582 self.currency,
583 self.amount
584 .map_or_else(|| "".to_string(), |x| x.to_string()),
585 )
586 }
587
588 fn data_part(&self) -> Vec<u5> {
591 let invoice_data = gen_invoice::RawInvoiceData::from(self.data.clone());
592 let compressed = ar_encompress(invoice_data.as_slice()).expect("compress invoice data");
593 let mut base32 = Vec::with_capacity(compressed.len());
594 compressed
595 .write_base32(&mut base32)
596 .expect("encode in base32");
597 base32
598 }
599
600 pub fn check_signature(&self) -> Result<(), InvoiceError> {
602 if self.signature.is_none() {
603 return Ok(());
604 }
605 match self.recover_payee_pub_key() {
606 Err(secp256k1::Error::InvalidRecoveryId) => {
607 return Err(InvoiceError::InvalidRecoveryId);
608 }
609 Err(secp256k1::Error::InvalidSignature) => return Err(InvoiceError::InvalidSignature),
610 Err(e) => panic!("no other error may occur, got {:?}", e),
611 Ok(_) => {}
612 }
613
614 if !self.validate_signature() {
615 return Err(InvoiceError::InvalidSignature);
616 }
617
618 Ok(())
619 }
620
621 fn validate_signature(&self) -> bool {
622 if self.signature.is_none() {
623 return true;
624 }
625 let signature = self.signature.as_ref().expect("expect signature");
626 let included_pub_key = self.payee_pub_key();
627
628 let mut recovered_pub_key = Option::None;
629 if included_pub_key.is_none() {
630 let recovered = match self.recover_payee_pub_key() {
631 Ok(pk) => pk,
632 Err(_) => return false,
633 };
634 recovered_pub_key = Some(recovered);
635 }
636
637 let pub_key = included_pub_key
638 .or(recovered_pub_key.as_ref())
639 .expect("One is always present");
640
641 let hash = secp256k1::Message::from_digest_slice(&self.hash()[..])
642 .expect("Hash is 32 bytes long, same as MESSAGE_SIZE");
643
644 let verification_result =
645 secp256k1::SECP256K1.verify_ecdsa(&hash, &signature.0.to_standard(), pub_key);
646 match verification_result {
647 Ok(()) => true,
648 Err(_) => false,
649 }
650 }
651
652 fn hash(&self) -> [u8; 32] {
653 let hrp = self.hrp_part();
654 let data = self.data_part();
655 let preimage = construct_invoice_preimage(hrp.as_bytes(), &data);
656 sha256(&preimage)
657 }
658
659 pub fn recover_payee_pub_key(&self) -> Result<PublicKey, secp256k1::Error> {
661 let hash = secp256k1::Message::from_digest_slice(&self.hash()[..])
662 .expect("Hash is 32 bytes long, same as MESSAGE_SIZE");
663
664 secp256k1::SECP256K1.recover_ecdsa(
665 &hash,
666 &self
667 .signature
668 .as_ref()
669 .expect("signature must be present")
670 .0,
671 )
672 }
673
674 pub fn payee_pub_key(&self) -> Option<&PublicKey> {
676 self.data
677 .attrs
678 .iter()
679 .filter_map(|attr| match attr {
680 Attribute::PayeePublicKey(val) => Some(val),
681 _ => None,
682 })
683 .next()
684 }
685
686 pub fn is_signed(&self) -> bool {
688 self.signature.is_some()
689 }
690
691 pub fn payment_hash(&self) -> &Hash256 {
693 &self.data.payment_hash
694 }
695
696 pub fn amount(&self) -> Option<u128> {
698 self.amount
699 }
700
701 pub fn udt_type_script(&self) -> Option<&PackedScript> {
703 self.data
704 .attrs
705 .iter()
706 .filter_map(|attr| match attr {
707 Attribute::UdtScript(script) => Some(&script.0),
708 _ => None,
709 })
710 .next()
711 }
712
713 pub fn expiry_time(&self) -> Option<&Duration> {
715 self.data
716 .attrs
717 .iter()
718 .filter_map(|attr| match attr {
719 Attribute::ExpiryTime(val) => Some(val),
720 _ => None,
721 })
722 .next()
723 }
724
725 pub fn description(&self) -> Option<&String> {
727 self.data
728 .attrs
729 .iter()
730 .filter_map(|attr| match attr {
731 Attribute::Description(val) => Some(val),
732 _ => None,
733 })
734 .next()
735 }
736
737 pub fn final_tlc_minimum_expiry_delta(&self) -> Option<&u64> {
739 self.data
740 .attrs
741 .iter()
742 .filter_map(|attr| match attr {
743 Attribute::FinalHtlcMinimumExpiryDelta(val) => Some(val),
744 _ => None,
745 })
746 .next()
747 }
748
749 pub fn fallback_address(&self) -> Option<&String> {
751 self.data
752 .attrs
753 .iter()
754 .filter_map(|attr| match attr {
755 Attribute::FallbackAddr(val) => Some(val),
756 _ => None,
757 })
758 .next()
759 }
760
761 pub fn hash_algorithm(&self) -> Option<&HashAlgorithm> {
763 self.data
764 .attrs
765 .iter()
766 .filter_map(|attr| match attr {
767 Attribute::HashAlgorithm(val) => Some(val),
768 _ => None,
769 })
770 .next()
771 }
772
773 pub fn payment_secret(&self) -> Option<&Hash256> {
775 self.data
776 .attrs
777 .iter()
778 .filter_map(|attr| match attr {
779 Attribute::PaymentSecret(val) => Some(val),
780 _ => None,
781 })
782 .next()
783 }
784
785 pub fn allow_mpp(&self) -> bool {
787 self.data
788 .attrs
789 .iter()
790 .any(|attr| matches!(attr, Attribute::Feature(feature) if feature.supports_basic_mpp()))
791 }
792
793 pub fn allow_trampoline_routing(&self) -> bool {
795 self.data
796 .attrs
797 .iter()
798 .any(|attr| matches!(attr, Attribute::Feature(feature) if feature.supports_trampoline_routing()))
799 }
800
801 pub fn is_expired(&self) -> bool {
803 self.expiry_time().is_some_and(|expiry| {
804 self.data
805 .timestamp
806 .checked_add(expiry.as_millis())
807 .is_some_and(|expiry_time| {
808 let now = crate::crate_time::UNIX_EPOCH
809 .elapsed()
810 .expect("Duration since unix epoch")
811 .as_millis();
812 expiry_time < now
813 })
814 })
815 }
816
817 pub fn is_tlc_expire_too_soon(&self, tlc_expiry: u64) -> bool {
819 let now = crate::crate_time::UNIX_EPOCH
820 .elapsed()
821 .expect("Duration since unix epoch")
822 .as_millis();
823 let required_expiry = now
824 + (self
825 .final_tlc_minimum_expiry_delta()
826 .cloned()
827 .unwrap_or_default() as u128);
828 (tlc_expiry as u128) < required_expiry
829 }
830
831 pub fn update_signature<F>(&mut self, sign_function: F) -> Result<(), InvoiceError>
833 where
834 F: FnOnce(&secp256k1::Message) -> RecoverableSignature,
835 {
836 let hash = self.hash();
837 let message =
838 secp256k1::Message::from_digest_slice(&hash).expect("message from digest slice");
839 let signature = sign_function(&message);
840 self.signature = Some(InvoiceSignature(signature));
841 self.check_signature()?;
842 Ok(())
843 }
844}
845
846impl Display for CkbInvoice {
847 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
848 let hrp = self.hrp_part();
849 let mut data = self.data_part();
850 data.insert(
851 0,
852 u5::try_from_u8(if self.signature.is_some() { 1 } else { 0 }).expect("u5 from u8"),
853 );
854 if let Some(signature) = &self.signature {
855 data.extend_from_slice(&signature.to_base32());
856 }
857 write!(
858 f,
859 "{}",
860 encode(&hrp, data, Variant::Bech32m).expect("encode invoice using Bech32m")
861 )
862 }
863}
864
865impl FromStr for CkbInvoice {
866 type Err = InvoiceError;
867
868 fn from_str(s: &str) -> Result<Self, Self::Err> {
869 let (hrp, data, var) = bech32::decode(s).map_err(InvoiceError::Bech32Error)?;
870
871 if var == bech32::Variant::Bech32 {
872 return Err(InvoiceError::Bech32Error(bech32::Error::InvalidChecksum));
873 }
874
875 if data.len() < SIGNATURE_U5_SIZE {
876 return Err(InvoiceError::TooShortDataPart);
877 }
878 let (currency, amount) = parse_hrp(&hrp)?;
879 let is_signed = data[0].to_u8() == 1;
880 let data_end = if is_signed {
881 data.len() - SIGNATURE_U5_SIZE
882 } else {
883 data.len()
884 };
885 let data_part =
886 Vec::<u8>::from_base32(&data[1..data_end]).map_err(InvoiceError::Bech32Error)?;
887 let data_part = ar_decompress(&data_part).expect("decompress invoice data");
888 let invoice_data = gen_invoice::RawInvoiceData::from_slice(&data_part)
889 .map_err(|err| InvoiceError::MoleculeError(VerificationError(err)))?;
890 let signature = if is_signed {
891 Some(InvoiceSignature::from_base32(
892 &data[data.len() - SIGNATURE_U5_SIZE..],
893 )?)
894 } else {
895 None
896 };
897
898 let invoice = CkbInvoice {
899 currency,
900 amount,
901 signature,
902 data: invoice_data.try_into().expect("pack invoice data"),
903 };
904 invoice.check_signature()?;
905 Ok(invoice)
906 }
907}
908
909fn u8_slice_to_bytes(slice: &[u8]) -> Result<[Byte; 32], &'static str> {
911 let vec: Vec<Byte> = slice.iter().map(|&x| Byte::new(x)).collect();
912 let boxed_slice = vec.into_boxed_slice();
913 let boxed_array: Box<[Byte; 32]> = match boxed_slice.try_into() {
914 Ok(ba) => ba,
915 Err(_) => return Err("Slice length doesn't match array length"),
916 };
917 Ok(*boxed_array)
918}
919
920fn bytes_to_u8_array(array: &molecule::bytes::Bytes) -> [u8; 32] {
922 let mut res = [0u8; 32];
923 res.copy_from_slice(array);
924 res
925}
926
927impl From<InvoiceData> for gen_invoice::RawInvoiceData {
928 fn from(data: InvoiceData) -> Self {
929 RawInvoiceDataBuilder::default()
930 .timestamp(data.timestamp.pack())
931 .payment_hash(
932 PaymentHash::new_builder()
933 .set(
934 u8_slice_to_bytes(data.payment_hash.as_ref()).expect("bytes from u8 slice"),
935 )
936 .build(),
937 )
938 .attrs(
939 InvoiceAttrsVec::new_builder()
940 .set(
941 data.attrs
942 .iter()
943 .map(|a| a.to_owned().into())
944 .collect::<Vec<InvoiceAttr>>(),
945 )
946 .build(),
947 )
948 .build()
949 }
950}
951
952impl TryFrom<gen_invoice::RawInvoiceData> for InvoiceData {
953 type Error = VerificationError;
954
955 fn try_from(data: gen_invoice::RawInvoiceData) -> Result<Self, Self::Error> {
956 Ok(InvoiceData {
957 timestamp: data.timestamp().unpack(),
958 payment_hash: bytes_to_u8_array(&data.payment_hash().as_bytes()).into(),
959 attrs: data
960 .attrs()
961 .into_iter()
962 .map(|a| a.into())
963 .collect::<Vec<Attribute>>(),
964 })
965 }
966}
967
968impl From<Attribute> for InvoiceAttr {
969 fn from(attr: Attribute) -> Self {
970 let a = match attr {
971 Attribute::ExpiryTime(x) => {
972 let seconds = x.as_secs();
973 let value = ExpiryTime::new_builder().value(seconds.pack()).build();
974 InvoiceAttrUnion::ExpiryTime(value)
975 }
976 Attribute::Description(value) => InvoiceAttrUnion::Description(
977 Description::new_builder().value(value.pack()).build(),
978 ),
979 Attribute::FinalHtlcTimeout(value) => InvoiceAttrUnion::FinalHtlcTimeout(
980 FinalHtlcTimeout::new_builder().value(value.pack()).build(),
981 ),
982 Attribute::FinalHtlcMinimumExpiryDelta(value) => {
983 InvoiceAttrUnion::FinalHtlcMinimumExpiryDelta(
984 FinalHtlcMinimumExpiryDelta::new_builder()
985 .value(value.pack())
986 .build(),
987 )
988 }
989 Attribute::FallbackAddr(value) => InvoiceAttrUnion::FallbackAddr(
990 FallbackAddr::new_builder().value(value.pack()).build(),
991 ),
992 Attribute::Feature(value) => InvoiceAttrUnion::Feature(
993 Feature::new_builder().value(value.bytes().pack()).build(),
994 ),
995 Attribute::UdtScript(script) => {
996 InvoiceAttrUnion::UdtScript(UdtScript::new_builder().value(script.0).build())
997 }
998 Attribute::PayeePublicKey(pubkey) => InvoiceAttrUnion::PayeePublicKey(
999 PayeePublicKey::new_builder()
1000 .value(pubkey.serialize().pack())
1001 .build(),
1002 ),
1003 Attribute::HashAlgorithm(hash_algorithm) => InvoiceAttrUnion::HashAlgorithm(
1004 gen_invoice::HashAlgorithm::new_builder()
1005 .value(Byte::new(hash_algorithm as u8))
1006 .build(),
1007 ),
1008 Attribute::PaymentSecret(payment_secret) => InvoiceAttrUnion::PaymentSecret(
1009 PaymentSecret::new_builder()
1010 .value(payment_secret.into())
1011 .build(),
1012 ),
1013 };
1014 InvoiceAttr::new_builder().set(a).build()
1015 }
1016}
1017
1018impl From<InvoiceAttr> for Attribute {
1019 fn from(attr: InvoiceAttr) -> Self {
1020 match attr.to_enum() {
1021 InvoiceAttrUnion::Description(x) => {
1022 let value: Vec<u8> = x.value().unpack();
1023 Attribute::Description(
1024 String::from_utf8(value).expect("decode utf8 string from bytes"),
1025 )
1026 }
1027 InvoiceAttrUnion::ExpiryTime(x) => {
1028 let seconds: u64 = x.value().unpack();
1029 Attribute::ExpiryTime(Duration::from_secs(seconds))
1030 }
1031
1032 InvoiceAttrUnion::FinalHtlcTimeout(x) => {
1033 Attribute::FinalHtlcTimeout(x.value().unpack())
1035 }
1036 InvoiceAttrUnion::FinalHtlcMinimumExpiryDelta(x) => {
1037 Attribute::FinalHtlcMinimumExpiryDelta(x.value().unpack())
1038 }
1039 InvoiceAttrUnion::FallbackAddr(x) => {
1040 let value: Vec<u8> = x.value().unpack();
1041 Attribute::FallbackAddr(
1042 String::from_utf8(value).expect("decode utf8 string from bytes"),
1043 )
1044 }
1045 InvoiceAttrUnion::Feature(x) => {
1046 Attribute::Feature(FeatureVector::from(x.value().unpack()))
1047 }
1048 InvoiceAttrUnion::UdtScript(x) => Attribute::UdtScript(CkbScript(x.value())),
1049 InvoiceAttrUnion::PayeePublicKey(x) => {
1050 let value: Vec<u8> = x.value().unpack();
1051 Attribute::PayeePublicKey(
1052 PublicKey::from_slice(&value).expect("Public key from slice"),
1053 )
1054 }
1055 InvoiceAttrUnion::HashAlgorithm(x) => {
1056 let value = x.value();
1057 let hash_algorithm = value.try_into().unwrap_or_default();
1059 Attribute::HashAlgorithm(hash_algorithm)
1060 }
1061 InvoiceAttrUnion::PaymentSecret(x) => Attribute::PaymentSecret(x.value().into()),
1062 }
1063 }
1064}
1065
1066impl From<anyhow::Error> for InvoiceError {
1067 fn from(_err: anyhow::Error) -> Self {
1068 InvoiceError::InvalidSignature
1069 }
1070}
1071
1072impl TryFrom<gen_invoice::RawCkbInvoice> for CkbInvoice {
1073 type Error = InvoiceError;
1074
1075 fn try_from(invoice: gen_invoice::RawCkbInvoice) -> Result<Self, Self::Error> {
1076 Ok(CkbInvoice {
1077 currency: (u8::from(invoice.currency()))
1078 .try_into()
1079 .map_err(|e: UnknownCurrencyError| InvoiceError::UnknownCurrency(e.0))?,
1080 amount: invoice.amount().to_opt().map(|x| x.unpack()),
1081 signature: invoice.signature().to_opt().map(|x| {
1082 InvoiceSignature::from_base32_checked(
1083 &x.as_bytes()
1084 .into_iter()
1085 .map(|x| u5::try_from_u8(x).expect("u5 from u8"))
1086 .collect::<Vec<u5>>(),
1087 )
1088 .expect("signature must be present")
1089 }),
1090 data: InvoiceData::try_from(invoice.data()).map_err(InvoiceError::MoleculeError)?,
1091 })
1092 }
1093}
1094
1095impl From<CkbInvoice> for gen_invoice::RawCkbInvoice {
1096 fn from(invoice: CkbInvoice) -> Self {
1097 gen_invoice::RawCkbInvoiceBuilder::default()
1098 .currency((invoice.currency as u8).into())
1099 .amount(
1100 gen_invoice::AmountOpt::new_builder()
1101 .set(invoice.amount.map(|x| x.pack()))
1102 .build(),
1103 )
1104 .signature(
1105 gen_invoice::SignatureOpt::new_builder()
1106 .set({
1107 invoice.signature.map(|x| {
1108 let bytes: [Byte; SIGNATURE_U5_SIZE] = x
1109 .to_base32()
1110 .iter()
1111 .map(|x| Byte::new(x.to_u8()))
1112 .collect::<Vec<_>>()
1113 .as_slice()
1114 .try_into()
1115 .expect("[Byte; 104] from [Byte] slice");
1116 gen_invoice::Signature::new_builder().set(bytes).build()
1117 })
1118 })
1119 .build(),
1120 )
1121 .data(invoice.data.into())
1122 .build()
1123 }
1124}
1125
1126#[cfg(test)]
1127#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1128#[cfg_attr(not(target_arch = "wasm32"), test)]
1129fn test_parse_hrp() {
1130 use super::InvoiceError;
1131
1132 let res = parse_hrp("fibb1280");
1133 assert_eq!(res, Ok((Currency::Fibb, Some(1280))));
1134
1135 let res = parse_hrp("fibb");
1136 assert_eq!(res, Ok((Currency::Fibb, None)));
1137
1138 let res = parse_hrp("fibt1023");
1139 assert_eq!(res, Ok((Currency::Fibt, Some(1023))));
1140
1141 let res = parse_hrp("fibt10");
1142 assert_eq!(res, Ok((Currency::Fibt, Some(10))));
1143
1144 let res = parse_hrp("fibt");
1145 assert_eq!(res, Ok((Currency::Fibt, None)));
1146
1147 let res = parse_hrp("xnfibb");
1148 assert_eq!(res, Err(InvoiceError::MalformedHRP("xnfibb".to_string())));
1149
1150 let res = parse_hrp("lxfibt");
1151 assert_eq!(res, Err(InvoiceError::MalformedHRP("lxfibt".to_string())));
1152
1153 let res = parse_hrp("fibt");
1154 assert_eq!(res, Ok((Currency::Fibt, None)));
1155
1156 let res = parse_hrp("fixt");
1157 assert_eq!(res, Err(InvoiceError::MalformedHRP("fixt".to_string())));
1158
1159 let res = parse_hrp("fibtt");
1160 assert_eq!(
1161 res,
1162 Err(InvoiceError::MalformedHRP(
1163 "fibtt, unexpected ending `t`".to_string()
1164 ))
1165 );
1166
1167 let res = parse_hrp("fibt1x24");
1168 assert_eq!(
1169 res,
1170 Err(InvoiceError::MalformedHRP(
1171 "fibt1x24, unexpected ending `x24`".to_string()
1172 ))
1173 );
1174
1175 let res = parse_hrp("fibt000");
1176 assert_eq!(res, Ok((Currency::Fibt, Some(0))));
1177
1178 let res = parse_hrp("fibt1024444444444444444444444444444444444444444444444444444444444444");
1179 assert!(matches!(res, Err(InvoiceError::ParseAmountError(_))));
1180
1181 let res = parse_hrp("fibt0x");
1182 assert!(matches!(res, Err(InvoiceError::MalformedHRP(_))));
1183
1184 let res = parse_hrp("");
1185 assert!(matches!(res, Err(InvoiceError::MalformedHRP(_))));
1186}
1187
1188#[cfg(test)]
1189#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1190#[cfg_attr(not(target_arch = "wasm32"), test)]
1191fn test_compress() {
1192 let input = "hrp1gyqsqqq5qqqqq9gqqqqp6qqqqq0qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq2qqqqqqqqqqqyvqsqqqsqqqqqvqqqqq8";
1193 let bytes = input.as_bytes();
1194 let compressed = ar_encompress(input.as_bytes()).unwrap();
1195
1196 let decompressed = ar_decompress(&compressed).unwrap();
1197 let decompressed_str = std::str::from_utf8(&decompressed).unwrap();
1198 assert_eq!(input, decompressed_str);
1199 assert!(compressed.len() < bytes.len());
1200}