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