bitcoin_payment_instructions/
receive.rs1use crate::split_once;
12
13use bitcoin::secp256k1::SecretKey;
14use bitcoin::Network;
15
16use lightning::offers::parse::Bolt12ParseError;
17use lightning::offers::refund::Refund;
18
19use alloc::str::FromStr;
20use alloc::string::{String, ToString};
21use alloc::vec;
22use alloc::vec::Vec;
23
24use core::time::Duration;
25
26#[derive(PartialEq, Eq, Debug, Clone)]
28pub enum ReceiveMethod {
29 PrivateKey(SecretKey),
31 Bolt12Refund(Refund),
33 }
35
36#[derive(Debug)]
38pub enum ParseError {
39 InvalidBolt12(Bolt12ParseError),
41 WrongNetwork,
43 InvalidInstructions(&'static str),
48 UnknownReceiveInstructions,
50 UnknownRequiredParameter,
52 InstructionsExpired,
54}
55
56pub(crate) fn check_expiry(_expiry: Duration) -> Result<(), ParseError> {
57 #[cfg(feature = "std")]
58 {
59 use std::time::SystemTime;
60 if let Ok(now) = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
61 if now > _expiry {
62 return Err(ParseError::InstructionsExpired);
63 }
64 }
65 }
66 Ok(())
67}
68
69#[derive(PartialEq, Eq, Debug, Clone)]
76pub struct ReceiveInstructions {
77 description: Option<String>,
78 methods: Vec<ReceiveMethod>,
79}
80
81impl ReceiveInstructions {
82 pub fn methods(&self) -> &[ReceiveMethod] {
84 &self.methods
85 }
86
87 pub fn sender_description(&self) -> Option<&str> {
92 self.description.as_deref()
93 }
94
95 pub fn parse_receive_instructions(
97 instructions: &str, network: Network,
98 ) -> Result<ReceiveInstructions, ParseError> {
99 if instructions.is_empty() {
100 return Err(ParseError::InvalidInstructions("Empty string"));
101 }
102 const BTC_URI_PFX_LEN: usize = "bitcoin:".len();
103
104 if instructions.len() >= BTC_URI_PFX_LEN
108 && instructions[..BTC_URI_PFX_LEN].eq_ignore_ascii_case("bitcoin:")
109 {
110 let (_, params) = split_once(&instructions[BTC_URI_PFX_LEN..], '?');
111 let mut methods = Vec::new();
112 let mut description = None;
113 if let Some(params) = params {
114 for param in params.split('&') {
115 let (k, v) = split_once(param, '=');
116 if k.eq_ignore_ascii_case("lnr") || k.eq_ignore_ascii_case("req-lnr") {
117 if let Some(v) = v {
118 match Refund::from_str(v) {
119 Ok(refund) => {
120 if refund.chain() != network.chain_hash() {
121 return Err(ParseError::WrongNetwork);
122 }
123
124 description = Some(refund.description().0.to_string());
125 methods.push(ReceiveMethod::Bolt12Refund(refund));
126 },
127 Err(err) => return Err(ParseError::InvalidBolt12(err)),
128 }
129 } else {
130 let err = "Missing value for a BOLT 12 refund parameter in a BIP 321 bitcoin: URI";
131 return Err(ParseError::InvalidInstructions(err));
132 }
133 } else if k.len() >= 4 && k[..4].eq_ignore_ascii_case("req-") {
134 return Err(ParseError::UnknownRequiredParameter);
135 }
136 }
137 }
138
139 if methods.is_empty() {
140 return Err(ParseError::UnknownReceiveInstructions);
141 }
142
143 return Ok(ReceiveInstructions { description, methods });
144 }
145
146 if let Ok(pk) = bitcoin::key::PrivateKey::from_wif(instructions) {
147 if pk.network != network.into() {
148 return Err(ParseError::WrongNetwork);
149 }
150
151 return Ok(ReceiveInstructions {
152 description: None,
153 methods: vec![ReceiveMethod::PrivateKey(pk.inner)],
154 });
155 }
156
157 if let Ok(refund) = Refund::from_str(instructions) {
158 if refund.chain() != network.chain_hash() {
159 return Err(ParseError::WrongNetwork);
160 }
161 if let Some(expiry) = refund.absolute_expiry() {
162 check_expiry(expiry)?;
163 }
164
165 return Ok(ReceiveInstructions {
166 description: Some(refund.description().to_string()),
167 methods: vec![ReceiveMethod::Bolt12Refund(refund)],
168 });
169 }
170
171 Err(ParseError::UnknownReceiveInstructions)
172 }
173}