@@ -179,32 +179,36 @@ impl OnionMessageContents for DNSResolverMessage {
179
179
}
180
180
}
181
181
182
+ // Note that `REQUIRED_EXTRA_LEN` includes the (implicit) trailing `.`
183
+ const REQUIRED_EXTRA_LEN : usize = ".user._bitcoin-payment." . len ( ) + 1 ;
184
+
182
185
/// A struct containing the two parts of a BIP 353 Human Readable Name - the user and domain parts.
183
186
///
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
185
188
/// non-empty.
186
189
///
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.
189
195
///
190
196
/// [Homograph Attacks]: https://en.wikipedia.org/wiki/IDN_homograph_attack
191
197
#[ derive( Clone , Debug , Hash , PartialEq , Eq ) ]
192
198
pub 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 ,
196
202
}
197
203
198
204
impl HumanReadableName {
199
205
/// Constructs a new [`HumanReadableName`] from the `user` and `domain` parts. See the
200
206
/// 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 , ( ) > {
202
208
// 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 ] ;
205
211
}
206
- // Note that `REQUIRED_EXTRA_LEN` includes the (now implicit) trailing `.`
207
- const REQUIRED_EXTRA_LEN : usize = ".user._bitcoin-payment." . len ( ) + 1 ;
208
212
if user. len ( ) + domain. len ( ) + REQUIRED_EXTRA_LEN > 255 {
209
213
return Err ( ( ) ) ;
210
214
}
@@ -214,7 +218,14 @@ impl HumanReadableName {
214
218
if !Hostname :: str_is_valid_hostname ( & user) || !Hostname :: str_is_valid_hostname ( & domain) {
215
219
return Err ( ( ) ) ;
216
220
}
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
+ } )
218
229
}
219
230
220
231
/// Constructs a new [`HumanReadableName`] from the standard encoding - `user`@`domain`.
@@ -224,49 +235,50 @@ impl HumanReadableName {
224
235
pub fn from_encoded ( encoded : & str ) -> Result < HumanReadableName , ( ) > {
225
236
if let Some ( ( user, domain) ) = encoded. strip_prefix ( '₿' ) . unwrap_or ( encoded) . split_once ( "@" )
226
237
{
227
- Self :: new ( user. to_string ( ) , domain. to_string ( ) )
238
+ Self :: new ( user, domain)
228
239
} else {
229
240
Err ( ( ) )
230
241
}
231
242
}
232
243
233
244
/// Gets the `user` part of this Human Readable Name
234
245
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" )
236
248
}
237
249
238
250
/// Gets the `domain` part of this Human Readable Name
239
251
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" )
241
255
}
242
256
}
243
257
244
258
// Serialized per the requirements for inclusion in a BOLT 12 `invoice_request`
245
259
impl Writeable for HumanReadableName {
246
260
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 ( ) )
251
265
}
252
266
}
253
267
254
268
impl Readable for HumanReadableName {
255
269
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 ] ;
258
271
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 ] ) {
262
274
Ok ( user) => user,
263
275
Err ( _) => return Err ( DecodeError :: InvalidValue ) ,
264
276
} ;
265
277
278
+ let mut domain_bytes = [ 0 ; 255 ] ;
266
279
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 ] ) {
270
282
Ok ( domain) => domain,
271
283
Err ( _) => return Err ( DecodeError :: InvalidValue ) ,
272
284
} ;
@@ -331,7 +343,7 @@ impl OMNameResolver {
331
343
& self , payment_id : PaymentId , name : HumanReadableName , entropy_source : & ES ,
332
344
) -> Result < ( DNSSECQuery , DNSResolverContext ) , ( ) > {
333
345
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( ) ) ) ;
335
347
debug_assert ! (
336
348
dns_name. is_ok( ) ,
337
349
"The HumanReadableName constructor shouldn't allow names which are too long"
0 commit comments