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 #[error("Failed to decompress invoice data: {0}")]
125 DecompressionError(String),
126 #[error("Invoice data length {len} exceeds max length {max}")]
128 InvoiceDataTooLong { len: usize, max: usize },
129 #[error("Invalid UTF-8 in invoice {0} attribute")]
131 InvalidUtf8Attribute(&'static str),
132 #[error("Invalid payee public key")]
134 InvalidPayeePublicKey,
135 #[error("Invalid signature encoding")]
137 InvalidSignatureEncoding,
138}
139
140pub const SIGNATURE_U5_SIZE: usize = 104;
142
143pub const MAX_DESCRIPTION_LENGTH: usize = 639;
145
146pub const MAX_INVOICE_DATA_LENGTH: usize = 16 * 1024;
152
153pub(crate) fn ar_encompress(data: &[u8]) -> IoResult<Vec<u8>> {
156 let mut model = Model::builder().num_bits(8).eof(EOFKind::EndAddOne).build();
157 let mut compressed_writer = BitWriter::new(Cursor::new(vec![]));
158 let mut encoder = ArithmeticEncoder::new(48);
159 for &sym in data {
160 encoder.encode(sym as u32, &model, &mut compressed_writer)?;
161 model.update_symbol(sym as u32);
162 }
163
164 encoder.encode(model.eof(), &model, &mut compressed_writer)?;
165 encoder.finish_encode(&mut compressed_writer)?;
166 compressed_writer.pad_to_byte()?;
167
168 Ok(compressed_writer.get_ref().get_ref().clone())
169}
170
171fn ar_decompress_with_limit(data: &[u8], max_len: usize) -> Result<Vec<u8>, InvoiceError> {
172 let mut model = Model::builder().num_bits(8).eof(EOFKind::EndAddOne).build();
173 let mut input_reader = BitReader::<_, MSB>::new(data);
174 let mut decoder = ArithmeticDecoder::new(48);
175 let mut decompressed_data = vec![];
176
177 while !decoder.finished() {
178 let sym = decoder
179 .decode(&model, &mut input_reader)
180 .map_err(|err| InvoiceError::DecompressionError(err.to_string()))?;
181 model.update_symbol(sym);
182 decompressed_data.push(sym as u8);
183
184 if !decoder.finished() && decompressed_data.len() > max_len {
185 return Err(InvoiceError::InvoiceDataTooLong {
186 len: decompressed_data.len(),
187 max: max_len,
188 });
189 }
190 }
191
192 decompressed_data
193 .pop()
194 .ok_or_else(|| InvoiceError::DecompressionError("missing EOF marker".to_string()))?;
195 Ok(decompressed_data)
196}
197
198pub fn construct_invoice_preimage(hrp_bytes: &[u8], data_without_signature: &[u5]) -> Vec<u8> {
200 let mut preimage = Vec::<u8>::from(hrp_bytes);
201
202 let mut data_part = Vec::from(data_without_signature);
203 let overhang = (data_part.len() * 5) % 8;
204 if overhang > 0 {
205 data_part.push(u5::try_from_u8(0).expect("u5 from u8"));
207
208 if overhang < 3 {
210 data_part.push(u5::try_from_u8(0).expect("u5 from u8"));
211 }
212 }
213
214 preimage.extend_from_slice(
215 &Vec::<u8>::from_base32(&data_part)
216 .expect("No padding error may occur due to appended zero above."),
217 );
218 preimage
219}
220
221fn nom_scan_hrp(input: &str) -> IResult<&str, (&str, Option<&str>)> {
222 let (input, currency) = alt((tag("fibb"), tag("fibt"), tag("fibd")))(input)?;
223 let (input, amount) = opt(take_while1(|c: char| c.is_numeric()))(input)?;
224 Ok((input, (currency, amount)))
225}
226
227pub fn parse_hrp(input: &str) -> Result<(Currency, Option<u128>), InvoiceError> {
229 match nom_scan_hrp(input) {
230 Ok((left, (currency, amount))) => {
231 if !left.is_empty() {
232 return Err(InvoiceError::MalformedHRP(format!(
233 "{}, unexpected ending `{}`",
234 input, left
235 )));
236 }
237 let currency =
238 Currency::from_str(currency).map_err(|e| InvoiceError::UnknownCurrency(e.0))?;
239 let amount = amount
240 .map(|x| x.parse().map_err(InvoiceError::ParseAmountError))
241 .transpose()?;
242 Ok((currency, amount))
243 }
244 Err(_) => Err(InvoiceError::MalformedHRP(input.to_string())),
245 }
246}
247
248#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
250pub enum CkbInvoiceStatus {
251 Open,
253 Cancelled,
255 Expired,
257 Received,
259 Paid,
261}
262
263impl Display for CkbInvoiceStatus {
264 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
265 match self {
266 CkbInvoiceStatus::Open => write!(f, "Open"),
267 CkbInvoiceStatus::Cancelled => write!(f, "Cancelled"),
268 CkbInvoiceStatus::Expired => write!(f, "Expired"),
269 CkbInvoiceStatus::Received => write!(f, "Received"),
270 CkbInvoiceStatus::Paid => write!(f, "Paid"),
271 }
272 }
273}
274
275#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Default)]
277pub enum Currency {
278 Fibb,
280 Fibt,
282 #[default]
284 Fibd,
285}
286
287impl Display for Currency {
288 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
289 match self {
290 Currency::Fibb => write!(f, "fibb"),
291 Currency::Fibt => write!(f, "fibt"),
292 Currency::Fibd => write!(f, "fibd"),
293 }
294 }
295}
296
297#[derive(thiserror::Error, Debug)]
299#[error("Unknown currency: {0}")]
300pub struct UnknownCurrencyError(pub String);
301
302impl FromStr for Currency {
303 type Err = UnknownCurrencyError;
304
305 fn from_str(s: &str) -> Result<Self, Self::Err> {
306 match s {
307 "fibb" => Ok(Self::Fibb),
308 "fibt" => Ok(Self::Fibt),
309 "fibd" => Ok(Self::Fibd),
310 _ => Err(UnknownCurrencyError(s.to_string())),
311 }
312 }
313}
314
315impl TryFrom<u8> for Currency {
316 type Error = UnknownCurrencyError;
317
318 fn try_from(byte: u8) -> Result<Self, Self::Error> {
319 match byte {
320 0 => Ok(Self::Fibb),
321 1 => Ok(Self::Fibt),
322 2 => Ok(Self::Fibd),
323 _ => Err(UnknownCurrencyError(byte.to_string())),
324 }
325 }
326}
327
328#[repr(u8)]
330#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default, Hash)]
331#[serde(rename_all = "snake_case")]
332pub enum HashAlgorithm {
333 #[default]
335 CkbHash = 0,
336 Sha256 = 1,
338}
339
340#[derive(thiserror::Error, Debug)]
342#[error("Unknown Hash Algorithm: {0}")]
343pub struct UnknownHashAlgorithmError(pub u8);
344
345impl TryFrom<u8> for HashAlgorithm {
346 type Error = UnknownHashAlgorithmError;
347
348 fn try_from(value: u8) -> Result<Self, Self::Error> {
349 match value {
350 0 => Ok(HashAlgorithm::CkbHash),
351 1 => Ok(HashAlgorithm::Sha256),
352 _ => Err(UnknownHashAlgorithmError(value)),
353 }
354 }
355}
356
357impl HashAlgorithm {
358 pub fn supported_algorithms() -> Vec<HashAlgorithm> {
359 vec![HashAlgorithm::CkbHash, HashAlgorithm::Sha256]
360 }
361
362 pub fn hash<T: AsRef<[u8]>>(&self, s: T) -> [u8; 32] {
363 match self {
364 HashAlgorithm::CkbHash => blake2b_256(s),
365 HashAlgorithm::Sha256 => sha256(s),
366 }
367 }
368}
369
370pub fn sha256<T: AsRef<[u8]>>(s: T) -> [u8; 32] {
372 let mut hasher = Sha256::new();
373 hasher.update(s.as_ref());
374 hasher.finalize().into()
375}
376
377impl TryFrom<Byte> for HashAlgorithm {
378 type Error = UnknownHashAlgorithmError;
379
380 fn try_from(value: Byte) -> Result<Self, Self::Error> {
381 let value: u8 = value.into();
382 value.try_into()
383 }
384}
385
386#[serde_as]
388#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
389pub struct CkbScript(#[serde_as(as = "EntityHex")] pub PackedScript);
390
391#[derive(Clone, Debug, Eq, PartialEq)]
393pub struct InvoiceSignature(pub RecoverableSignature);
394
395impl PartialOrd for InvoiceSignature {
396 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
397 Some(self.cmp(other))
398 }
399}
400
401impl Ord for InvoiceSignature {
402 fn cmp(&self, other: &Self) -> Ordering {
403 self.0
404 .serialize_compact()
405 .1
406 .cmp(&other.0.serialize_compact().1)
407 }
408}
409
410impl Serialize for InvoiceSignature {
411 fn serialize<S>(
412 &self,
413 serializer: S,
414 ) -> Result<<S as serde::Serializer>::Ok, <S as serde::Serializer>::Error>
415 where
416 S: serde::Serializer,
417 {
418 let base32: Vec<u8> = self.to_base32().iter().map(|x| x.to_u8()).collect();
419 let hex_str = hex::encode(base32);
420 hex_str.serialize(serializer)
421 }
422}
423
424impl<'de> Deserialize<'de> for InvoiceSignature {
425 fn deserialize<D>(deserializer: D) -> Result<Self, <D as serde::Deserializer<'de>>::Error>
426 where
427 D: serde::Deserializer<'de>,
428 {
429 let signature_hex: String = String::deserialize(deserializer)?;
430 let signature_bytes = hex::decode(signature_hex).map_err(serde::de::Error::custom)?;
431 let base32_values = signature_bytes
432 .iter()
433 .map(|x| u5::try_from_u8(*x))
434 .collect::<Result<Vec<u5>, _>>()
435 .map_err(serde::de::Error::custom)?;
436 InvoiceSignature::from_base32(&base32_values).map_err(serde::de::Error::custom)
437 }
438}
439
440struct BytesToBase32<'a, W: WriteBase32 + 'a> {
441 writer: &'a mut W,
442 buffer: u8,
443 buffer_bits: u8,
444}
445
446impl<'a, W: WriteBase32> BytesToBase32<'a, W> {
447 fn new(writer: &'a mut W) -> Self {
448 BytesToBase32 {
449 writer,
450 buffer: 0,
451 buffer_bits: 0,
452 }
453 }
454
455 fn append(&mut self, byte: u8) -> Result<(), <W as WriteBase32>::Err> {
456 let mut bits_remaining = 8;
457 while bits_remaining > 0 {
458 let bits_to_take = std::cmp::min(5 - self.buffer_bits, bits_remaining);
459 self.buffer <<= bits_to_take;
460 self.buffer |= (byte >> (bits_remaining - bits_to_take)) & ((1 << bits_to_take) - 1);
461 self.buffer_bits += bits_to_take;
462 bits_remaining -= bits_to_take;
463
464 if self.buffer_bits == 5 {
465 self.writer
466 .write_u5(u5::try_from_u8(self.buffer).expect("buffer is 5 bits"))?;
467 self.buffer = 0;
468 self.buffer_bits = 0;
469 }
470 }
471 Ok(())
472 }
473
474 fn finalize(mut self) -> Result<(), <W as WriteBase32>::Err> {
475 if self.buffer_bits > 0 {
476 self.buffer <<= 5 - self.buffer_bits;
477 self.writer
478 .write_u5(u5::try_from_u8(self.buffer).expect("buffer is at most 5 bits"))?;
479 }
480 Ok(())
481 }
482}
483
484impl ToBase32 for InvoiceSignature {
485 fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
486 let mut converter = BytesToBase32::new(writer);
487 let (recovery_id, signature) = self.0.serialize_compact();
488 for v in signature
489 .iter()
490 .chain(std::iter::once(&(i32::from(recovery_id) as u8)))
491 {
492 converter.append(*v)?;
493 }
494 converter.finalize()
495 }
496}
497
498impl FromBase32 for InvoiceSignature {
499 type Err = anyhow::Error;
500
501 fn from_base32(field_data: &[u5]) -> Result<InvoiceSignature, Self::Err> {
502 if field_data.len() < 104 {
503 return Err(anyhow::anyhow!(
504 "InvoiceSignature TryFrom<[u5]> failed: unexpected length {}",
505 field_data.len()
506 ));
507 }
508
509 let raw_bytes = Vec::<u8>::from_base32(field_data)?;
510 if raw_bytes.len() != 65 {
511 return Err(anyhow::anyhow!(
512 "InvoiceSignature TryFrom<[u5]> failed: unexpected byte length {}",
513 raw_bytes.len()
514 ));
515 }
516 let recovery_id = RecoveryId::try_from(raw_bytes[64] as i32)?;
517 let signature = RecoverableSignature::from_compact(&raw_bytes[0..64], recovery_id)?;
518 Ok(InvoiceSignature(signature))
519 }
520}
521
522impl InvoiceSignature {
523 pub fn from_base32_checked(signature: &[u5]) -> Result<Self, InvoiceError> {
525 if signature.len() != SIGNATURE_U5_SIZE {
526 return Err(InvoiceError::InvalidSliceLength(
527 "InvoiceSignature::from_base32_checked()".into(),
528 ));
529 }
530 let recoverable_signature_bytes =
531 Vec::<u8>::from_base32(signature).map_err(InvoiceError::Bech32Error)?;
532 let sig = &recoverable_signature_bytes[0..64];
533 let recovery_id = RecoveryId::try_from(recoverable_signature_bytes[64] as i32)
534 .map_err(|_| InvoiceError::InvalidRecoveryId)?;
535
536 Ok(InvoiceSignature(
537 RecoverableSignature::from_compact(sig, recovery_id)
538 .map_err(|_| InvoiceError::InvalidSignature)?,
539 ))
540 }
541}
542
543use crate::protocol::FeatureVector;
544use crate::serde_utils::{duration_hex, U128Hex, U64Hex};
545use crate::Hash256;
546use secp256k1::PublicKey;
547use std::time::Duration;
548
549#[serde_as]
551#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
552#[serde(rename_all = "snake_case")]
553pub enum Attribute {
554 #[serde(with = "U64Hex")]
556 FinalHtlcTimeout(u64),
557 #[serde(with = "U64Hex")]
559 FinalHtlcMinimumExpiryDelta(u64),
560 #[serde(with = "duration_hex")]
562 ExpiryTime(Duration),
563 Description(String),
565 FallbackAddr(String),
567 UdtScript(CkbScript),
569 PayeePublicKey(PublicKey),
571 HashAlgorithm(HashAlgorithm),
573 Feature(FeatureVector),
575 PaymentSecret(Hash256),
577}
578
579#[serde_as]
581#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
582pub struct InvoiceData {
583 #[serde_as(as = "U128Hex")]
585 pub timestamp: u128,
586 pub payment_hash: Hash256,
588 pub attrs: Vec<Attribute>,
590}
591
592#[serde_as]
598#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
599pub struct CkbInvoice {
600 pub currency: Currency,
602 #[serde_as(as = "Option<U128Hex>")]
604 pub amount: Option<u128>,
605 pub signature: Option<InvoiceSignature>,
607 pub data: InvoiceData,
609}
610
611impl CkbInvoice {
612 fn hrp_part(&self) -> String {
613 format!(
614 "{}{}",
615 self.currency,
616 self.amount
617 .map_or_else(|| "".to_string(), |x| x.to_string()),
618 )
619 }
620
621 fn data_part(&self) -> Vec<u5> {
624 let invoice_data = gen_invoice::RawInvoiceData::from(self.data.clone());
625 let compressed = ar_encompress(invoice_data.as_slice()).expect("compress invoice data");
626 let mut base32 = Vec::with_capacity(compressed.len());
627 compressed
628 .write_base32(&mut base32)
629 .expect("encode in base32");
630 base32
631 }
632
633 pub fn check_signature(&self) -> Result<(), InvoiceError> {
635 if self.signature.is_none() {
636 return Ok(());
637 }
638 match self.recover_payee_pub_key() {
639 Err(secp256k1::Error::InvalidRecoveryId) => {
640 return Err(InvoiceError::InvalidRecoveryId);
641 }
642 Err(secp256k1::Error::InvalidSignature) => return Err(InvoiceError::InvalidSignature),
643 Err(e) => panic!("no other error may occur, got {:?}", e),
644 Ok(_) => {}
645 }
646
647 if !self.validate_signature() {
648 return Err(InvoiceError::InvalidSignature);
649 }
650
651 Ok(())
652 }
653
654 fn validate_signature(&self) -> bool {
655 let Some(signature) = self.signature.as_ref() else {
656 return true;
657 };
658 let included_pub_key = self.payee_pub_key();
659
660 let mut recovered_pub_key = Option::None;
661 if included_pub_key.is_none() {
662 let recovered = match self.recover_payee_pub_key() {
663 Ok(pk) => pk,
664 Err(_) => return false,
665 };
666 recovered_pub_key = Some(recovered);
667 }
668
669 let Some(pub_key) = included_pub_key.or(recovered_pub_key.as_ref()) else {
670 return false;
671 };
672
673 let hash = secp256k1::Message::from_digest_slice(&self.hash()[..])
674 .expect("Hash is 32 bytes long, same as MESSAGE_SIZE");
675
676 let verification_result =
677 secp256k1::SECP256K1.verify_ecdsa(&hash, &signature.0.to_standard(), pub_key);
678 match verification_result {
679 Ok(()) => true,
680 Err(_) => false,
681 }
682 }
683
684 fn hash(&self) -> [u8; 32] {
685 let hrp = self.hrp_part();
686 let data = self.data_part();
687 let preimage = construct_invoice_preimage(hrp.as_bytes(), &data);
688 sha256(&preimage)
689 }
690
691 pub fn recover_payee_pub_key(&self) -> Result<PublicKey, secp256k1::Error> {
693 let hash = secp256k1::Message::from_digest_slice(&self.hash()[..])
694 .expect("Hash is 32 bytes long, same as MESSAGE_SIZE");
695
696 secp256k1::SECP256K1.recover_ecdsa(
697 &hash,
698 &self
699 .signature
700 .as_ref()
701 .ok_or(secp256k1::Error::InvalidSignature)?
702 .0,
703 )
704 }
705
706 pub fn payee_pub_key(&self) -> Option<&PublicKey> {
708 self.data
709 .attrs
710 .iter()
711 .filter_map(|attr| match attr {
712 Attribute::PayeePublicKey(val) => Some(val),
713 _ => None,
714 })
715 .next()
716 }
717
718 pub fn is_signed(&self) -> bool {
720 self.signature.is_some()
721 }
722
723 pub fn payment_hash(&self) -> &Hash256 {
725 &self.data.payment_hash
726 }
727
728 pub fn amount(&self) -> Option<u128> {
730 self.amount
731 }
732
733 pub fn udt_type_script(&self) -> Option<&PackedScript> {
735 self.data
736 .attrs
737 .iter()
738 .filter_map(|attr| match attr {
739 Attribute::UdtScript(script) => Some(&script.0),
740 _ => None,
741 })
742 .next()
743 }
744
745 pub fn expiry_time(&self) -> Option<&Duration> {
747 self.data
748 .attrs
749 .iter()
750 .filter_map(|attr| match attr {
751 Attribute::ExpiryTime(val) => Some(val),
752 _ => None,
753 })
754 .next()
755 }
756
757 pub fn description(&self) -> Option<&String> {
759 self.data
760 .attrs
761 .iter()
762 .filter_map(|attr| match attr {
763 Attribute::Description(val) => Some(val),
764 _ => None,
765 })
766 .next()
767 }
768
769 pub fn final_tlc_minimum_expiry_delta(&self) -> Option<&u64> {
771 self.data
772 .attrs
773 .iter()
774 .filter_map(|attr| match attr {
775 Attribute::FinalHtlcMinimumExpiryDelta(val) => Some(val),
776 _ => None,
777 })
778 .next()
779 }
780
781 pub fn fallback_address(&self) -> Option<&String> {
783 self.data
784 .attrs
785 .iter()
786 .filter_map(|attr| match attr {
787 Attribute::FallbackAddr(val) => Some(val),
788 _ => None,
789 })
790 .next()
791 }
792
793 pub fn hash_algorithm(&self) -> Option<&HashAlgorithm> {
795 self.data
796 .attrs
797 .iter()
798 .filter_map(|attr| match attr {
799 Attribute::HashAlgorithm(val) => Some(val),
800 _ => None,
801 })
802 .next()
803 }
804
805 pub fn payment_secret(&self) -> Option<&Hash256> {
807 self.data
808 .attrs
809 .iter()
810 .filter_map(|attr| match attr {
811 Attribute::PaymentSecret(val) => Some(val),
812 _ => None,
813 })
814 .next()
815 }
816
817 pub fn allow_mpp(&self) -> bool {
819 self.data
820 .attrs
821 .iter()
822 .any(|attr| matches!(attr, Attribute::Feature(feature) if feature.supports_basic_mpp()))
823 }
824
825 pub fn allow_trampoline_routing(&self) -> bool {
827 self.data
828 .attrs
829 .iter()
830 .any(|attr| matches!(attr, Attribute::Feature(feature) if feature.supports_trampoline_routing()))
831 }
832
833 pub fn is_expired(&self) -> bool {
835 self.expiry_time().is_some_and(|expiry| {
836 self.data
837 .timestamp
838 .checked_add(expiry.as_millis())
839 .is_some_and(|expiry_time| {
840 let now = crate::crate_time::UNIX_EPOCH
841 .elapsed()
842 .expect("Duration since unix epoch")
843 .as_millis();
844 expiry_time < now
845 })
846 })
847 }
848
849 pub fn is_tlc_expire_too_soon(&self, tlc_expiry: u64) -> bool {
851 let now = crate::crate_time::UNIX_EPOCH
852 .elapsed()
853 .expect("Duration since unix epoch")
854 .as_millis();
855 let required_expiry = now
856 + (self
857 .final_tlc_minimum_expiry_delta()
858 .cloned()
859 .unwrap_or_default() as u128);
860 (tlc_expiry as u128) < required_expiry
861 }
862
863 pub fn update_signature<F>(&mut self, sign_function: F) -> Result<(), InvoiceError>
865 where
866 F: FnOnce(&secp256k1::Message) -> RecoverableSignature,
867 {
868 let hash = self.hash();
869 let message =
870 secp256k1::Message::from_digest_slice(&hash).expect("message from digest slice");
871 let signature = sign_function(&message);
872 self.signature = Some(InvoiceSignature(signature));
873 self.check_signature()?;
874 Ok(())
875 }
876}
877
878impl Display for CkbInvoice {
879 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
880 let hrp = self.hrp_part();
881 let mut data = self.data_part();
882 data.insert(
883 0,
884 u5::try_from_u8(if self.signature.is_some() { 1 } else { 0 }).expect("u5 from u8"),
885 );
886 if let Some(signature) = &self.signature {
887 data.extend_from_slice(&signature.to_base32());
888 }
889 write!(
890 f,
891 "{}",
892 encode(&hrp, data, Variant::Bech32m).expect("encode invoice using Bech32m")
893 )
894 }
895}
896
897impl FromStr for CkbInvoice {
898 type Err = InvoiceError;
899
900 fn from_str(s: &str) -> Result<Self, Self::Err> {
901 let (hrp, data, var) = bech32::decode(s).map_err(InvoiceError::Bech32Error)?;
902
903 if var == bech32::Variant::Bech32 {
904 return Err(InvoiceError::Bech32Error(bech32::Error::InvalidChecksum));
905 }
906
907 if data.len() < SIGNATURE_U5_SIZE {
908 return Err(InvoiceError::TooShortDataPart);
909 }
910 let (currency, amount) = parse_hrp(&hrp)?;
911 let is_signed = data[0].to_u8() == 1;
912 let data_end = if is_signed {
913 data.len() - SIGNATURE_U5_SIZE
914 } else {
915 data.len()
916 };
917 let data_part =
918 Vec::<u8>::from_base32(&data[1..data_end]).map_err(InvoiceError::Bech32Error)?;
919 let data_part = ar_decompress_with_limit(&data_part, MAX_INVOICE_DATA_LENGTH)?;
920 let invoice_data = gen_invoice::RawInvoiceData::from_slice(&data_part)
921 .map_err(|err| InvoiceError::MoleculeError(VerificationError(err)))?;
922 let signature = if is_signed {
923 Some(InvoiceSignature::from_base32(
924 &data[data.len() - SIGNATURE_U5_SIZE..],
925 )?)
926 } else {
927 None
928 };
929
930 let invoice = CkbInvoice {
931 currency,
932 amount,
933 signature,
934 data: invoice_data.try_into()?,
935 };
936 invoice.check_signature()?;
937 Ok(invoice)
938 }
939}
940
941fn u8_slice_to_bytes(slice: &[u8]) -> Result<[Byte; 32], &'static str> {
943 let vec: Vec<Byte> = slice.iter().map(|&x| Byte::new(x)).collect();
944 let boxed_slice = vec.into_boxed_slice();
945 let boxed_array: Box<[Byte; 32]> = match boxed_slice.try_into() {
946 Ok(ba) => ba,
947 Err(_) => return Err("Slice length doesn't match array length"),
948 };
949 Ok(*boxed_array)
950}
951
952fn bytes_to_u8_array(array: &molecule::bytes::Bytes) -> [u8; 32] {
954 let mut res = [0u8; 32];
955 res.copy_from_slice(array);
956 res
957}
958
959impl From<InvoiceData> for gen_invoice::RawInvoiceData {
960 fn from(data: InvoiceData) -> Self {
961 RawInvoiceDataBuilder::default()
962 .timestamp(data.timestamp.pack())
963 .payment_hash(
964 PaymentHash::new_builder()
965 .set(
966 u8_slice_to_bytes(data.payment_hash.as_ref()).expect("bytes from u8 slice"),
967 )
968 .build(),
969 )
970 .attrs(
971 InvoiceAttrsVec::new_builder()
972 .set(
973 data.attrs
974 .iter()
975 .map(|a| a.to_owned().into())
976 .collect::<Vec<InvoiceAttr>>(),
977 )
978 .build(),
979 )
980 .build()
981 }
982}
983
984impl TryFrom<gen_invoice::RawInvoiceData> for InvoiceData {
985 type Error = InvoiceError;
986
987 fn try_from(data: gen_invoice::RawInvoiceData) -> Result<Self, Self::Error> {
988 Ok(InvoiceData {
989 timestamp: data.timestamp().unpack(),
990 payment_hash: bytes_to_u8_array(&data.payment_hash().as_bytes()).into(),
991 attrs: data
992 .attrs()
993 .into_iter()
994 .map(Attribute::try_from)
995 .collect::<Result<Vec<Attribute>, InvoiceError>>()?,
996 })
997 }
998}
999
1000impl From<Attribute> for InvoiceAttr {
1001 fn from(attr: Attribute) -> Self {
1002 let a = match attr {
1003 Attribute::ExpiryTime(x) => {
1004 let seconds = x.as_secs();
1005 let value = ExpiryTime::new_builder().value(seconds.pack()).build();
1006 InvoiceAttrUnion::ExpiryTime(value)
1007 }
1008 Attribute::Description(value) => InvoiceAttrUnion::Description(
1009 Description::new_builder().value(value.pack()).build(),
1010 ),
1011 Attribute::FinalHtlcTimeout(value) => InvoiceAttrUnion::FinalHtlcTimeout(
1012 FinalHtlcTimeout::new_builder().value(value.pack()).build(),
1013 ),
1014 Attribute::FinalHtlcMinimumExpiryDelta(value) => {
1015 InvoiceAttrUnion::FinalHtlcMinimumExpiryDelta(
1016 FinalHtlcMinimumExpiryDelta::new_builder()
1017 .value(value.pack())
1018 .build(),
1019 )
1020 }
1021 Attribute::FallbackAddr(value) => InvoiceAttrUnion::FallbackAddr(
1022 FallbackAddr::new_builder().value(value.pack()).build(),
1023 ),
1024 Attribute::Feature(value) => InvoiceAttrUnion::Feature(
1025 Feature::new_builder().value(value.bytes().pack()).build(),
1026 ),
1027 Attribute::UdtScript(script) => {
1028 InvoiceAttrUnion::UdtScript(UdtScript::new_builder().value(script.0).build())
1029 }
1030 Attribute::PayeePublicKey(pubkey) => InvoiceAttrUnion::PayeePublicKey(
1031 PayeePublicKey::new_builder()
1032 .value(pubkey.serialize().pack())
1033 .build(),
1034 ),
1035 Attribute::HashAlgorithm(hash_algorithm) => InvoiceAttrUnion::HashAlgorithm(
1036 gen_invoice::HashAlgorithm::new_builder()
1037 .value(Byte::new(hash_algorithm as u8))
1038 .build(),
1039 ),
1040 Attribute::PaymentSecret(payment_secret) => InvoiceAttrUnion::PaymentSecret(
1041 PaymentSecret::new_builder()
1042 .value(payment_secret.into())
1043 .build(),
1044 ),
1045 };
1046 InvoiceAttr::new_builder().set(a).build()
1047 }
1048}
1049
1050impl TryFrom<InvoiceAttr> for Attribute {
1051 type Error = InvoiceError;
1052
1053 fn try_from(attr: InvoiceAttr) -> Result<Self, Self::Error> {
1054 let attr = match attr.to_enum() {
1055 InvoiceAttrUnion::Description(x) => {
1056 let value: Vec<u8> = x.value().unpack();
1057 Attribute::Description(
1058 String::from_utf8(value)
1059 .map_err(|_| InvoiceError::InvalidUtf8Attribute("description"))?,
1060 )
1061 }
1062 InvoiceAttrUnion::ExpiryTime(x) => {
1063 let seconds: u64 = x.value().unpack();
1064 Attribute::ExpiryTime(Duration::from_secs(seconds))
1065 }
1066
1067 InvoiceAttrUnion::FinalHtlcTimeout(x) => {
1068 Attribute::FinalHtlcTimeout(x.value().unpack())
1070 }
1071 InvoiceAttrUnion::FinalHtlcMinimumExpiryDelta(x) => {
1072 Attribute::FinalHtlcMinimumExpiryDelta(x.value().unpack())
1073 }
1074 InvoiceAttrUnion::FallbackAddr(x) => {
1075 let value: Vec<u8> = x.value().unpack();
1076 Attribute::FallbackAddr(
1077 String::from_utf8(value)
1078 .map_err(|_| InvoiceError::InvalidUtf8Attribute("fallback_addr"))?,
1079 )
1080 }
1081 InvoiceAttrUnion::Feature(x) => {
1082 Attribute::Feature(FeatureVector::from(x.value().unpack()))
1083 }
1084 InvoiceAttrUnion::UdtScript(x) => Attribute::UdtScript(CkbScript(x.value())),
1085 InvoiceAttrUnion::PayeePublicKey(x) => {
1086 let value: Vec<u8> = x.value().unpack();
1087 Attribute::PayeePublicKey(
1088 PublicKey::from_slice(&value)
1089 .map_err(|_| InvoiceError::InvalidPayeePublicKey)?,
1090 )
1091 }
1092 InvoiceAttrUnion::HashAlgorithm(x) => {
1093 let value = x.value();
1094 let hash_algorithm = value.try_into().unwrap_or_default();
1096 Attribute::HashAlgorithm(hash_algorithm)
1097 }
1098 InvoiceAttrUnion::PaymentSecret(x) => Attribute::PaymentSecret(x.value().into()),
1099 };
1100 Ok(attr)
1101 }
1102}
1103
1104impl From<anyhow::Error> for InvoiceError {
1105 fn from(_err: anyhow::Error) -> Self {
1106 InvoiceError::InvalidSignature
1107 }
1108}
1109
1110impl TryFrom<gen_invoice::RawCkbInvoice> for CkbInvoice {
1111 type Error = InvoiceError;
1112
1113 fn try_from(invoice: gen_invoice::RawCkbInvoice) -> Result<Self, Self::Error> {
1114 Ok(CkbInvoice {
1115 currency: (u8::from(invoice.currency()))
1116 .try_into()
1117 .map_err(|e: UnknownCurrencyError| InvoiceError::UnknownCurrency(e.0))?,
1118 amount: invoice.amount().to_opt().map(|x| x.unpack()),
1119 signature: invoice
1120 .signature()
1121 .to_opt()
1122 .map(|x| {
1123 let signature = x
1124 .as_bytes()
1125 .into_iter()
1126 .map(|x| {
1127 u5::try_from_u8(x).map_err(|_| InvoiceError::InvalidSignatureEncoding)
1128 })
1129 .collect::<Result<Vec<u5>, InvoiceError>>()?;
1130 InvoiceSignature::from_base32_checked(&signature)
1131 })
1132 .transpose()?,
1133 data: InvoiceData::try_from(invoice.data())?,
1134 })
1135 }
1136}
1137
1138impl From<CkbInvoice> for gen_invoice::RawCkbInvoice {
1139 fn from(invoice: CkbInvoice) -> Self {
1140 gen_invoice::RawCkbInvoiceBuilder::default()
1141 .currency((invoice.currency as u8).into())
1142 .amount(
1143 gen_invoice::AmountOpt::new_builder()
1144 .set(invoice.amount.map(|x| x.pack()))
1145 .build(),
1146 )
1147 .signature(
1148 gen_invoice::SignatureOpt::new_builder()
1149 .set({
1150 invoice.signature.map(|x| {
1151 let bytes: [Byte; SIGNATURE_U5_SIZE] = x
1152 .to_base32()
1153 .iter()
1154 .map(|x| Byte::new(x.to_u8()))
1155 .collect::<Vec<_>>()
1156 .as_slice()
1157 .try_into()
1158 .expect("[Byte; 104] from [Byte] slice");
1159 gen_invoice::Signature::new_builder().set(bytes).build()
1160 })
1161 })
1162 .build(),
1163 )
1164 .data(invoice.data.into())
1165 .build()
1166 }
1167}
1168
1169#[cfg(test)]
1170#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1171#[cfg_attr(not(target_arch = "wasm32"), test)]
1172fn test_parse_hrp() {
1173 use super::InvoiceError;
1174
1175 let res = parse_hrp("fibb1280");
1176 assert_eq!(res, Ok((Currency::Fibb, Some(1280))));
1177
1178 let res = parse_hrp("fibb");
1179 assert_eq!(res, Ok((Currency::Fibb, None)));
1180
1181 let res = parse_hrp("fibt1023");
1182 assert_eq!(res, Ok((Currency::Fibt, Some(1023))));
1183
1184 let res = parse_hrp("fibt10");
1185 assert_eq!(res, Ok((Currency::Fibt, Some(10))));
1186
1187 let res = parse_hrp("fibt");
1188 assert_eq!(res, Ok((Currency::Fibt, None)));
1189
1190 let res = parse_hrp("xnfibb");
1191 assert_eq!(res, Err(InvoiceError::MalformedHRP("xnfibb".to_string())));
1192
1193 let res = parse_hrp("lxfibt");
1194 assert_eq!(res, Err(InvoiceError::MalformedHRP("lxfibt".to_string())));
1195
1196 let res = parse_hrp("fibt");
1197 assert_eq!(res, Ok((Currency::Fibt, None)));
1198
1199 let res = parse_hrp("fixt");
1200 assert_eq!(res, Err(InvoiceError::MalformedHRP("fixt".to_string())));
1201
1202 let res = parse_hrp("fibtt");
1203 assert_eq!(
1204 res,
1205 Err(InvoiceError::MalformedHRP(
1206 "fibtt, unexpected ending `t`".to_string()
1207 ))
1208 );
1209
1210 let res = parse_hrp("fibt1x24");
1211 assert_eq!(
1212 res,
1213 Err(InvoiceError::MalformedHRP(
1214 "fibt1x24, unexpected ending `x24`".to_string()
1215 ))
1216 );
1217
1218 let res = parse_hrp("fibt000");
1219 assert_eq!(res, Ok((Currency::Fibt, Some(0))));
1220
1221 let res = parse_hrp("fibt1024444444444444444444444444444444444444444444444444444444444444");
1222 assert!(matches!(res, Err(InvoiceError::ParseAmountError(_))));
1223
1224 let res = parse_hrp("fibt0x");
1225 assert!(matches!(res, Err(InvoiceError::MalformedHRP(_))));
1226
1227 let res = parse_hrp("");
1228 assert!(matches!(res, Err(InvoiceError::MalformedHRP(_))));
1229}
1230
1231#[cfg(test)]
1232#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1233#[cfg_attr(not(target_arch = "wasm32"), test)]
1234fn test_compress() {
1235 let input = "hrp1gyqsqqq5qqqqq9gqqqqp6qqqqq0qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq2qqqqqqqqqqqyvqsqqqsqqqqqvqqqqq8";
1236 let bytes = input.as_bytes();
1237 let compressed = ar_encompress(input.as_bytes()).unwrap();
1238
1239 let decompressed = ar_decompress_with_limit(&compressed, MAX_INVOICE_DATA_LENGTH).unwrap();
1240 let decompressed_str = std::str::from_utf8(&decompressed).unwrap();
1241 assert_eq!(input, decompressed_str);
1242 assert!(compressed.len() < bytes.len());
1243}
1244
1245#[cfg(test)]
1246fn raw_invoice_data_with_attrs(attrs: Vec<InvoiceAttr>) -> gen_invoice::RawInvoiceData {
1247 RawInvoiceDataBuilder::default()
1248 .timestamp(0u128.pack())
1249 .payment_hash(PaymentHash::new_builder().set([Byte::new(0); 32]).build())
1250 .attrs(InvoiceAttrsVec::new_builder().set(attrs).build())
1251 .build()
1252}
1253
1254#[cfg(test)]
1255fn encode_unsigned_invoice(raw_invoice_data: gen_invoice::RawInvoiceData) -> String {
1256 let compressed = ar_encompress(raw_invoice_data.as_slice()).unwrap();
1257 let mut data = vec![u5::try_from_u8(0).unwrap()];
1258 data.extend(compressed.to_base32());
1259 assert!(data.len() >= SIGNATURE_U5_SIZE);
1260 encode("fibb", data, Variant::Bech32m).unwrap()
1261}
1262
1263#[cfg(test)]
1264#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1265#[cfg_attr(not(target_arch = "wasm32"), test)]
1266fn test_parse_malformed_compressed_invoice_returns_error_without_panic() {
1267 let mut data = vec![u5::try_from_u8(0).unwrap()];
1268 data.extend(std::iter::repeat(u5::try_from_u8(31).unwrap()).take(SIGNATURE_U5_SIZE));
1269 let invoice = encode("fibb", data, Variant::Bech32m).unwrap();
1270
1271 let result = std::panic::catch_unwind(|| CkbInvoice::from_str(&invoice));
1272
1273 assert!(result.is_ok());
1274 assert!(result.unwrap().is_err());
1275}
1276
1277#[cfg(test)]
1278#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1279#[cfg_attr(not(target_arch = "wasm32"), test)]
1280fn test_decompressed_invoice_data_length_is_limited() {
1281 let payload = vec![0u8; MAX_INVOICE_DATA_LENGTH + 1];
1282 let compressed = ar_encompress(&payload).unwrap();
1283
1284 let result = ar_decompress_with_limit(&compressed, MAX_INVOICE_DATA_LENGTH);
1285
1286 assert!(matches!(
1287 result,
1288 Err(InvoiceError::InvoiceDataTooLong {
1289 len,
1290 max: MAX_INVOICE_DATA_LENGTH,
1291 }) if len > MAX_INVOICE_DATA_LENGTH
1292 ));
1293}
1294
1295#[cfg(test)]
1296#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1297#[cfg_attr(not(target_arch = "wasm32"), test)]
1298fn test_malformed_text_attribute_returns_error_without_panic() {
1299 let attr = InvoiceAttr::new_builder()
1300 .set(InvoiceAttrUnion::Description(
1301 Description::new_builder()
1302 .value(vec![0xff; 200].pack())
1303 .build(),
1304 ))
1305 .build();
1306 let invoice = encode_unsigned_invoice(raw_invoice_data_with_attrs(vec![attr]));
1307
1308 let result = std::panic::catch_unwind(|| CkbInvoice::from_str(&invoice));
1309
1310 assert!(matches!(
1311 result,
1312 Ok(Err(InvoiceError::InvalidUtf8Attribute("description")))
1313 ));
1314}
1315
1316#[cfg(test)]
1317#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1318#[cfg_attr(not(target_arch = "wasm32"), test)]
1319fn test_malformed_payee_public_key_returns_error_without_panic() {
1320 let attr = InvoiceAttr::new_builder()
1321 .set(InvoiceAttrUnion::PayeePublicKey(
1322 PayeePublicKey::new_builder()
1323 .value(vec![1, 2, 3].pack())
1324 .build(),
1325 ))
1326 .build();
1327 let raw_invoice_data = raw_invoice_data_with_attrs(vec![attr]);
1328
1329 let result = std::panic::catch_unwind(|| InvoiceData::try_from(raw_invoice_data));
1330
1331 assert!(matches!(
1332 result,
1333 Ok(Err(InvoiceError::InvalidPayeePublicKey))
1334 ));
1335}
1336
1337#[cfg(test)]
1338#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
1339#[cfg_attr(not(target_arch = "wasm32"), test)]
1340fn test_malformed_raw_invoice_signature_returns_error_without_panic() {
1341 let signature = gen_invoice::Signature::new_builder()
1342 .set([Byte::new(32); SIGNATURE_U5_SIZE])
1343 .build();
1344 let raw_invoice = gen_invoice::RawCkbInvoiceBuilder::default()
1345 .currency(Byte::new(Currency::Fibb as u8))
1346 .signature(
1347 gen_invoice::SignatureOpt::new_builder()
1348 .set(Some(signature))
1349 .build(),
1350 )
1351 .data(raw_invoice_data_with_attrs(vec![]))
1352 .build();
1353
1354 let result = std::panic::catch_unwind(|| CkbInvoice::try_from(raw_invoice));
1355
1356 assert!(matches!(
1357 result,
1358 Ok(Err(InvoiceError::InvalidSignatureEncoding))
1359 ));
1360}