Skip to main content

fiber_types/
invoice.rs

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