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}
139
140/// Size of the signature in u5 encoding.
141pub const SIGNATURE_U5_SIZE: usize = 104;
142
143/// Maximum allowed length for an invoice description.
144pub const MAX_DESCRIPTION_LENGTH: usize = 639;
145
146/// Maximum decompressed molecule payload accepted by the invoice parser.
147///
148/// Current invoices only need a few hundred bytes for fixed fields plus the
149/// 639-byte description limit. This leaves room for scripts, fallback
150/// addresses and future attributes while bounding compressed-input expansion.
151pub const MAX_INVOICE_DATA_LENGTH: usize = 16 * 1024;
152
153/// Encodes bytes and returns the compressed form.
154/// This is used for encoding the invoice data, to make the final Invoice encoded address shorter.
155pub(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
198/// Construct the invoice's HRP and signatureless data into a preimage to be hashed.
199pub 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        // add padding if data does not end at a byte boundary
206        data_part.push(u5::try_from_u8(0).expect("u5 from u8"));
207
208        // if overhang is in (1..3) we need to add u5(0) padding two times
209        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
227/// Parse the human-readable part of an invoice.
228pub 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/// The currency of the invoice, can also used to represent the CKB network chain.
249#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
250pub enum CkbInvoiceStatus {
251    /// The invoice is open and can be paid.
252    Open,
253    /// The invoice is cancelled.
254    Cancelled,
255    /// The invoice is expired.
256    Expired,
257    /// The invoice is received, but not settled yet.
258    Received,
259    /// The invoice is paid.
260    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/// The currency of the invoice, can also used to represent the CKB network chain.
276#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Default)]
277pub enum Currency {
278    /// The mainnet currency of CKB.
279    Fibb,
280    /// The testnet currency of the CKB network.
281    Fibt,
282    /// The devnet currency of the CKB network.
283    #[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/// Error for unknown currency
298#[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/// HashAlgorithm is the hash algorithm used in the hash lock.
329#[repr(u8)]
330#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default, Hash)]
331#[serde(rename_all = "snake_case")]
332pub enum HashAlgorithm {
333    /// The default hash algorithm, CkbHash
334    #[default]
335    CkbHash = 0,
336    /// The sha256 hash algorithm
337    Sha256 = 1,
338}
339
340/// Error for unknown hash algorithm
341#[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
370/// SHA-256 hash helper function.
371pub 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/// A wrapper around `ckb_types::packed::Script` with hex serialization.
387#[serde_as]
388#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
389pub struct CkbScript(#[serde_as(as = "EntityHex")] pub PackedScript);
390
391/// Recoverable signature
392#[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    /// Parse an `InvoiceSignature` from base32-encoded data, returning `InvoiceError` on failure.
524    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/// The attributes of the invoice.
550#[serde_as]
551#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
552#[serde(rename_all = "snake_case")]
553pub enum Attribute {
554    /// This attribute is deprecated since v0.6.0. The final TLC timeout, in milliseconds.
555    #[serde(with = "U64Hex")]
556    FinalHtlcTimeout(u64),
557    /// The final TLC minimum expiry delta, in milliseconds. Default is 160 minutes.
558    #[serde(with = "U64Hex")]
559    FinalHtlcMinimumExpiryDelta(u64),
560    /// The expiry time of the invoice, in seconds.
561    #[serde(with = "duration_hex")]
562    ExpiryTime(Duration),
563    /// The description of the invoice.
564    Description(String),
565    /// The fallback address of the invoice.
566    FallbackAddr(String),
567    /// The UDT type script of the invoice.
568    UdtScript(CkbScript),
569    /// The payee public key of the invoice.
570    PayeePublicKey(PublicKey),
571    /// The hash algorithm of the invoice.
572    HashAlgorithm(HashAlgorithm),
573    /// The feature flags of the invoice.
574    Feature(FeatureVector),
575    /// The payment secret of the invoice.
576    PaymentSecret(Hash256),
577}
578
579/// The metadata of the invoice.
580#[serde_as]
581#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
582pub struct InvoiceData {
583    /// The timestamp of the invoice.
584    #[serde_as(as = "U128Hex")]
585    pub timestamp: u128,
586    /// The payment hash of the invoice.
587    pub payment_hash: Hash256,
588    /// The attributes of the invoice, e.g. description, expiry time, etc.
589    pub attrs: Vec<Attribute>,
590}
591
592/// Represents a syntactically and semantically correct Fiber invoice.
593///
594/// There are three ways to construct a `CkbInvoice`:
595///  1. using `CkbInvoiceBuilder`
596///  2. using `str::parse::<CkbInvoice>(&str)` (see `CkbInvoice::from_str`)
597#[serde_as]
598#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
599pub struct CkbInvoice {
600    /// The currency of the invoice.
601    pub currency: Currency,
602    /// The amount of the invoice.
603    #[serde_as(as = "Option<U128Hex>")]
604    pub amount: Option<u128>,
605    /// The signature of the invoice.
606    pub signature: Option<InvoiceSignature>,
607    /// The invoice data, including the payment hash, timestamp and other attributes.
608    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    // Use the lossless compression algorithm to compress the invoice data.
622    // To make sure the final encoded invoice address is shorter
623    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    /// Check that the invoice is signed correctly and that key recovery works.
634    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    /// Recovers the public key used for signing the invoice from the recoverable signature.
692    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    /// Returns the payee public key if set in the invoice attributes.
707    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    /// Returns whether the invoice has a signature.
719    pub fn is_signed(&self) -> bool {
720        self.signature.is_some()
721    }
722
723    /// Returns the payment hash of the invoice.
724    pub fn payment_hash(&self) -> &Hash256 {
725        &self.data.payment_hash
726    }
727
728    /// Returns the amount of the invoice.
729    pub fn amount(&self) -> Option<u128> {
730        self.amount
731    }
732
733    /// Returns the UDT type script if set in the invoice attributes.
734    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    /// Returns the expiry time if set in the invoice attributes.
746    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    /// Returns the description if set in the invoice attributes.
758    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    /// Returns the final TLC minimum expiry delta if set in the invoice attributes.
770    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    /// Returns the fallback address if set in the invoice attributes.
782    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    /// Returns the hash algorithm if set in the invoice attributes.
794    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    /// Returns the payment secret if set in the invoice attributes.
806    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    /// Returns whether the invoice allows MPP (multi-part payments).
818    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    /// Returns whether the invoice allows trampoline routing.
826    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    /// Returns whether the invoice has expired based on the current time.
834    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    /// Returns whether the TLC expiry is too soon for the given invoice.
850    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    /// Updates the invoice signature using the provided signing function.
864    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
941/// Converts a `[u8]` slice to `[Byte; 32]`.
942fn 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
952/// Converts molecule bytes to `[u8; 32]`.
953fn 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                // This attribute is deprecated since v0.6.0, but we still keep it in molecule for consistency
1069                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                // Consider unknown algorithm as the default one.
1095                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}