1use crate::serde_utils::EntityHex;
4
5use crate::gen::invoice as gen_invoice;
6use arcode::bitbit::{BitReader, BitWriter, MSB};
7use arcode::{ArithmeticDecoder, ArithmeticEncoder, EOFKind, Model};
8use bech32::{encode, u5, FromBase32, ToBase32, Variant, WriteBase32};
9use ckb_hash::blake2b_256;
10use ckb_types::packed::Script as PackedScript;
11use ckb_types::prelude::{Pack, Unpack};
12use gen_invoice::{
13 Description, ExpiryTime, FallbackAddr, Feature, FinalHtlcMinimumExpiryDelta, FinalHtlcTimeout,
14 InvoiceAttr, InvoiceAttrUnion, InvoiceAttrsVec, PayeePublicKey, PaymentHash, PaymentSecret,
15 RawInvoiceDataBuilder, UdtScript,
16};
17use molecule::prelude::Byte;
18use molecule::prelude::{Builder, Entity};
19use nom::{branch::alt, combinator::opt};
20use nom::{
21 bytes::{complete::take_while1, streaming::tag},
22 IResult,
23};
24use secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
25use serde::{Deserialize, Serialize};
26use serde_with::serde_as;
27use sha2::{Digest, Sha256};
28use std::cmp::Ordering;
29use std::fmt::Display;
30use std::io::{Cursor, Result as IoResult};
31use std::num::ParseIntError;
32use std::str::FromStr;
33use thiserror::Error;
34
35#[derive(Error, Debug)]
37pub struct VerificationError(pub molecule::error::VerificationError);
38
39impl PartialEq for VerificationError {
40 fn eq(&self, _other: &Self) -> bool {
41 false
42 }
43}
44
45impl Display for VerificationError {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 self.0.fmt(f)
48 }
49}
50
51#[derive(Error, PartialEq, Debug)]
53pub enum InvoiceError {
54 #[error("Bech32 error: {0}")]
56 Bech32Error(bech32::Error),
57 #[error("Molecule error: {0}")]
59 MoleculeError(VerificationError),
60 #[error("Failed to parse amount: {0}")]
62 ParseAmountError(ParseIntError),
63 #[error("Unknown currency: {0}")]
65 UnknownCurrency(String),
66 #[error("Unknown si prefix: {0}")]
68 UnknownSiPrefix(String),
69 #[error("Parsing failed with malformed HRP: {0}")]
71 MalformedHRP(String),
72 #[error("Too short data part")]
74 TooShortDataPart,
75 #[error("Unexpected end of tagged fields")]
77 UnexpectedEndOfTaggedFields,
78 #[error("Integer overflow error")]
80 IntegerOverflowError,
81 #[error("Invalid recovery id")]
83 InvalidRecoveryId,
84 #[error("Invalid slice length: {0}")]
86 InvalidSliceLength(String),
87 #[error("Invalid signature")]
89 InvalidSignature,
90 #[error("Duplicated attribute key: {0}")]
92 DuplicatedAttributeKey(String),
93 #[error("Payment secret is required for MPP payments")]
95 PaymentSecretRequiredForMpp,
96 #[error("Both payment_hash and payment_preimage are set")]
98 BothPaymenthashAndPreimage,
99 #[error("Neither payment_hash nor payment_preimage is set")]
101 NeitherPaymenthashNorPreimage,
102 #[error("Sign error")]
104 SignError,
105 #[error("Hex decode error: {0}")]
107 HexDecodeError(#[from] hex::FromHexError),
108 #[error("Duplicated invoice found: {0}")]
110 DuplicatedInvoice(String),
111 #[error("Description with length of {0} is too long, max length is 639")]
113 DescriptionTooLong(usize),
114 #[error("Invoice not found")]
116 InvoiceNotFound,
117 #[error("Invoice already exists")]
119 InvoiceAlreadyExists,
120 #[error("Deprecated attribute: {0}")]
122 DeprecatedAttribute(String),
123 #[error("Failed to decompress invoice data: {0}")]
125 DecompressionError(String),
126 #[error("Invoice data length {len} exceeds max length {max}")]
128 InvoiceDataTooLong { len: usize, max: usize },
129 #[error("Invalid UTF-8 in invoice {0} attribute")]
131 InvalidUtf8Attribute(&'static str),
132 #[error("Invalid payee public key")]
134 InvalidPayeePublicKey,
135 #[error("Invalid signature encoding")]
137 InvalidSignatureEncoding,
138 #[error("Invoice is not signed")]
140 MissingSignature,
141}
142
143pub const SIGNATURE_U5_SIZE: usize = 104;
145
146pub const MAX_DESCRIPTION_LENGTH: usize = 639;
148
149pub const MAX_INVOICE_DATA_LENGTH: usize = 16 * 1024;
155
156pub(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
201pub 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 data_part.push(u5::try_from_u8(0).expect("u5 from u8"));
210
211 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
230pub 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#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
253pub enum CkbInvoiceStatus {
254 Open,
256 Cancelled,
258 Expired,
260 Received,
262 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#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Default)]
280pub enum Currency {
281 Fibb,
283 Fibt,
285 #[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#[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#[repr(u8)]
333#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default, Hash)]
334#[serde(rename_all = "snake_case")]
335pub enum HashAlgorithm {
336 #[default]
338 CkbHash = 0,
339 Sha256 = 1,
341}
342
343#[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
373pub 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#[serde_as]
391#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
392pub struct CkbScript(#[serde_as(as = "EntityHex")] pub PackedScript);
393
394#[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 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#[serde_as]
554#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
555#[serde(rename_all = "snake_case")]
556pub enum Attribute {
557 #[serde(with = "U64Hex")]
559 FinalHtlcTimeout(u64),
560 #[serde(with = "U64Hex")]
562 FinalHtlcMinimumExpiryDelta(u64),
563 #[serde(with = "duration_hex")]
565 ExpiryTime(Duration),
566 Description(String),
568 FallbackAddr(String),
570 UdtScript(CkbScript),
572 PayeePublicKey(PublicKey),
574 HashAlgorithm(HashAlgorithm),
576 Feature(FeatureVector),
578 PaymentSecret(Hash256),
580}
581
582#[serde_as]
584#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
585pub struct InvoiceData {
586 #[serde_as(as = "U128Hex")]
588 pub timestamp: u128,
589 pub payment_hash: Hash256,
591 pub attrs: Vec<Attribute>,
593}
594
595#[serde_as]
601#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
602pub struct CkbInvoice {
603 pub currency: Currency,
605 #[serde_as(as = "Option<U128Hex>")]
607 pub amount: Option<u128>,
608 pub signature: Option<InvoiceSignature>,
610 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 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 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 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 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 pub fn is_signed(&self) -> bool {
723 self.signature.is_some()
724 }
725
726 pub fn payment_hash(&self) -> &Hash256 {
728 &self.data.payment_hash
729 }
730
731 pub fn amount(&self) -> Option<u128> {
733 self.amount
734 }
735
736 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 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 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 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 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 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 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 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 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 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 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 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 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
961fn 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
972fn 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 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 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}