@@ -179,32 +179,36 @@ impl OnionMessageContents for DNSResolverMessage {
179179 }
180180}
181181
182+ // Note that `REQUIRED_EXTRA_LEN` includes the (implicit) trailing `.`
183+ const REQUIRED_EXTRA_LEN : usize = ".user._bitcoin-payment." . len ( ) + 1 ;
184+
182185/// A struct containing the two parts of a BIP 353 Human Readable Name - the user and domain parts.
183186///
184- /// The `user` and `domain` parts, together, cannot exceed 232 bytes in length, and both must be
187+ /// The `user` and `domain` parts, together, cannot exceed 231 bytes in length, and both must be
185188/// non-empty.
186189///
187- /// To protect against [Homograph Attacks], both parts of a Human Readable Name must be plain
188- /// ASCII.
190+ /// If you intend to handle non-ASCII `user` or `domain` parts, you must handle [Homograph Attacks]
191+ /// and do punycode en-/de-coding yourself. This struct will always handle only plain ASCII `user`
192+ /// and `domain` parts.
193+ ///
194+ /// This struct can also be used for LN-Address recipients.
189195///
190196/// [Homograph Attacks]: https://en.wikipedia.org/wiki/IDN_homograph_attack
191197#[ derive( Clone , Debug , Hash , PartialEq , Eq ) ]
192198pub struct HumanReadableName {
193- // TODO Remove the heap allocations given the whole data can't be more than 256 bytes.
194- user : String ,
195- domain : String ,
199+ contents : [ u8 ; 255 - REQUIRED_EXTRA_LEN ] ,
200+ user_len : u8 ,
201+ domain_len : u8 ,
196202}
197203
198204impl HumanReadableName {
199205 /// Constructs a new [`HumanReadableName`] from the `user` and `domain` parts. See the
200206 /// struct-level documentation for more on the requirements on each.
201- pub fn new ( user : String , mut domain : String ) -> Result < HumanReadableName , ( ) > {
207+ pub fn new ( user : & str , mut domain : & str ) -> Result < HumanReadableName , ( ) > {
202208 // First normalize domain and remove the optional trailing `.`
203- if domain. ends_with ( "." ) {
204- domain. pop ( ) ;
209+ if domain. ends_with ( '.' ) {
210+ domain = & domain [ ..domain . len ( ) - 1 ] ;
205211 }
206- // Note that `REQUIRED_EXTRA_LEN` includes the (now implicit) trailing `.`
207- const REQUIRED_EXTRA_LEN : usize = ".user._bitcoin-payment." . len ( ) + 1 ;
208212 if user. len ( ) + domain. len ( ) + REQUIRED_EXTRA_LEN > 255 {
209213 return Err ( ( ) ) ;
210214 }
@@ -214,7 +218,14 @@ impl HumanReadableName {
214218 if !Hostname :: str_is_valid_hostname ( & user) || !Hostname :: str_is_valid_hostname ( & domain) {
215219 return Err ( ( ) ) ;
216220 }
217- Ok ( HumanReadableName { user, domain } )
221+ let mut contents = [ 0 ; 255 - REQUIRED_EXTRA_LEN ] ;
222+ contents[ ..user. len ( ) ] . copy_from_slice ( user. as_bytes ( ) ) ;
223+ contents[ user. len ( ) ..user. len ( ) + domain. len ( ) ] . copy_from_slice ( domain. as_bytes ( ) ) ;
224+ Ok ( HumanReadableName {
225+ contents,
226+ user_len : user. len ( ) as u8 ,
227+ domain_len : domain. len ( ) as u8 ,
228+ } )
218229 }
219230
220231 /// Constructs a new [`HumanReadableName`] from the standard encoding - `user`@`domain`.
@@ -224,49 +235,50 @@ impl HumanReadableName {
224235 pub fn from_encoded ( encoded : & str ) -> Result < HumanReadableName , ( ) > {
225236 if let Some ( ( user, domain) ) = encoded. strip_prefix ( '₿' ) . unwrap_or ( encoded) . split_once ( "@" )
226237 {
227- Self :: new ( user. to_string ( ) , domain. to_string ( ) )
238+ Self :: new ( user, domain)
228239 } else {
229240 Err ( ( ) )
230241 }
231242 }
232243
233244 /// Gets the `user` part of this Human Readable Name
234245 pub fn user ( & self ) -> & str {
235- & self . user
246+ let bytes = & self . contents [ ..self . user_len as usize ] ;
247+ core:: str:: from_utf8 ( bytes) . expect ( "Checked in constructor" )
236248 }
237249
238250 /// Gets the `domain` part of this Human Readable Name
239251 pub fn domain ( & self ) -> & str {
240- & self . domain
252+ let user_len = self . user_len as usize ;
253+ let bytes = & self . contents [ user_len..user_len + self . domain_len as usize ] ;
254+ core:: str:: from_utf8 ( bytes) . expect ( "Checked in constructor" )
241255 }
242256}
243257
244258// Serialized per the requirements for inclusion in a BOLT 12 `invoice_request`
245259impl Writeable for HumanReadableName {
246260 fn write < W : Writer > ( & self , writer : & mut W ) -> Result < ( ) , io:: Error > {
247- ( self . user . len ( ) as u8 ) . write ( writer) ?;
248- writer. write_all ( & self . user . as_bytes ( ) ) ?;
249- ( self . domain . len ( ) as u8 ) . write ( writer) ?;
250- writer. write_all ( & self . domain . as_bytes ( ) )
261+ ( self . user ( ) . len ( ) as u8 ) . write ( writer) ?;
262+ writer. write_all ( & self . user ( ) . as_bytes ( ) ) ?;
263+ ( self . domain ( ) . len ( ) as u8 ) . write ( writer) ?;
264+ writer. write_all ( & self . domain ( ) . as_bytes ( ) )
251265 }
252266}
253267
254268impl Readable for HumanReadableName {
255269 fn read < R : io:: Read > ( reader : & mut R ) -> Result < Self , DecodeError > {
256- let mut read_bytes = [ 0 ; 255 ] ;
257-
270+ let mut user_bytes = [ 0 ; 255 ] ;
258271 let user_len: u8 = Readable :: read ( reader) ?;
259- reader. read_exact ( & mut read_bytes[ ..user_len as usize ] ) ?;
260- let user_bytes: Vec < u8 > = read_bytes[ ..user_len as usize ] . into ( ) ;
261- let user = match String :: from_utf8 ( user_bytes) {
272+ reader. read_exact ( & mut user_bytes[ ..user_len as usize ] ) ?;
273+ let user = match core:: str:: from_utf8 ( & user_bytes[ ..user_len as usize ] ) {
262274 Ok ( user) => user,
263275 Err ( _) => return Err ( DecodeError :: InvalidValue ) ,
264276 } ;
265277
278+ let mut domain_bytes = [ 0 ; 255 ] ;
266279 let domain_len: u8 = Readable :: read ( reader) ?;
267- reader. read_exact ( & mut read_bytes[ ..domain_len as usize ] ) ?;
268- let domain_bytes: Vec < u8 > = read_bytes[ ..domain_len as usize ] . into ( ) ;
269- let domain = match String :: from_utf8 ( domain_bytes) {
280+ reader. read_exact ( & mut domain_bytes[ ..domain_len as usize ] ) ?;
281+ let domain = match core:: str:: from_utf8 ( & domain_bytes[ ..domain_len as usize ] ) {
270282 Ok ( domain) => domain,
271283 Err ( _) => return Err ( DecodeError :: InvalidValue ) ,
272284 } ;
@@ -331,7 +343,7 @@ impl OMNameResolver {
331343 & self , payment_id : PaymentId , name : HumanReadableName , entropy_source : & ES ,
332344 ) -> Result < ( DNSSECQuery , DNSResolverContext ) , ( ) > {
333345 let dns_name =
334- Name :: try_from ( format ! ( "{}.user._bitcoin-payment.{}." , name. user, name. domain) ) ;
346+ Name :: try_from ( format ! ( "{}.user._bitcoin-payment.{}." , name. user( ) , name. domain( ) ) ) ;
335347 debug_assert ! (
336348 dns_name. is_ok( ) ,
337349 "The HumanReadableName constructor shouldn't allow names which are too long"
0 commit comments