Skip to content

Commit eef4353

Browse files
committed
Add a type to track HumanReadableNames
BIP 353 `HumanReadableName`s are represented as `₿user@domain` and can be resolved using DNS into a bitcoin: URI. In the next commit, we will add such a resolver using onion messages to fetch records from the DNS, which will rely on this new type to get name information from outside LDK.
1 parent 96cdae0 commit eef4353

File tree

1 file changed

+91
-0
lines changed

1 file changed

+91
-0
lines changed

lightning/src/onion_message/dns_resolution.rs

+91
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,94 @@ impl OnionMessageContents for DNSResolverMessage {
159159
}
160160
}
161161
}
162+
163+
/// A struct containing the two parts of a BIP 353 Human Readable Name - the user and domain parts.
164+
///
165+
/// The `user` and `domain` parts, together, cannot exceed 232 bytes in length, and both must be
166+
/// non-empty.
167+
///
168+
/// To protect against [Homograph Attacks], both parts of a Human Readable Name must be plain
169+
/// ASCII.
170+
///
171+
/// [Homograph Attacks]: https://en.wikipedia.org/wiki/IDN_homograph_attack
172+
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
173+
pub struct HumanReadableName {
174+
// TODO Remove the heap allocations given the whole data can't be more than 256 bytes.
175+
user: String,
176+
domain: String,
177+
}
178+
179+
impl HumanReadableName {
180+
/// Constructs a new [`HumanReadableName`] from the `user` and `domain` parts. See the
181+
/// struct-level documentation for more on the requirements on each.
182+
pub fn new(user: String, domain: String) -> Result<HumanReadableName, ()> {
183+
const REQUIRED_EXTRA_LEN: usize = ".user._bitcoin-payment.".len() + 1;
184+
if user.len() + domain.len() + REQUIRED_EXTRA_LEN > 255 {
185+
return Err(());
186+
}
187+
if user.is_empty() || domain.is_empty() {
188+
return Err(());
189+
}
190+
if !user.is_ascii() || !domain.is_ascii() {
191+
return Err(());
192+
}
193+
Ok(HumanReadableName { user, domain })
194+
}
195+
196+
/// Constructs a new [`HumanReadableName`] from the standard encoding - `user`@`domain`.
197+
///
198+
/// If `user` includes the standard BIP 353 ₿ prefix it is automatically removed as required by
199+
/// BIP 353.
200+
pub fn from_encoded(encoded: &str) -> Result<HumanReadableName, ()> {
201+
if let Some((user, domain)) = encoded.strip_prefix('₿').unwrap_or(encoded).split_once("@")
202+
{
203+
Self::new(user.to_string(), domain.to_string())
204+
} else {
205+
Err(())
206+
}
207+
}
208+
209+
/// Gets the `user` part of this Human Readable Name
210+
pub fn user(&self) -> &str {
211+
&self.user
212+
}
213+
214+
/// Gets the `domain` part of this Human Readable Name
215+
pub fn domain(&self) -> &str {
216+
&self.domain
217+
}
218+
}
219+
220+
// Serialized per the requirements for inclusion in a BOLT 12 `invoice_request`
221+
impl Writeable for HumanReadableName {
222+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
223+
(self.user.len() as u8).write(writer)?;
224+
writer.write_all(&self.user.as_bytes())?;
225+
(self.domain.len() as u8).write(writer)?;
226+
writer.write_all(&self.domain.as_bytes())
227+
}
228+
}
229+
230+
impl Readable for HumanReadableName {
231+
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
232+
let mut read_bytes = [0; 255];
233+
234+
let user_len: u8 = Readable::read(reader)?;
235+
reader.read_exact(&mut read_bytes[..user_len as usize])?;
236+
let user_bytes: Vec<u8> = read_bytes[..user_len as usize].into();
237+
let user = match String::from_utf8(user_bytes) {
238+
Ok(user) => user,
239+
Err(_) => return Err(DecodeError::InvalidValue),
240+
};
241+
242+
let domain_len: u8 = Readable::read(reader)?;
243+
reader.read_exact(&mut read_bytes[..domain_len as usize])?;
244+
let domain_bytes: Vec<u8> = read_bytes[..domain_len as usize].into();
245+
let domain = match String::from_utf8(domain_bytes) {
246+
Ok(domain) => domain,
247+
Err(_) => return Err(DecodeError::InvalidValue),
248+
};
249+
250+
HumanReadableName::new(user, domain).map_err(|()| DecodeError::InvalidValue)
251+
}
252+
}

0 commit comments

Comments
 (0)