1use crate::types::Hash;
11use thiserror::Error;
12
13mod tlv {
15 pub const HASH: u8 = 0;
17 pub const PUBKEY: u8 = 2;
19 pub const TREE_NAME: u8 = 3;
21 pub const PATH: u8 = 4;
23 pub const DECRYPT_KEY: u8 = 5;
25}
26
27#[derive(Debug, Error)]
29pub enum NHashError {
30 #[error("Bech32 error: {0}")]
31 Bech32(String),
32 #[error("Invalid prefix: expected {expected}, got {got}")]
33 InvalidPrefix { expected: String, got: String },
34 #[error("Invalid hash length: expected 32 bytes, got {0}")]
35 InvalidHashLength(usize),
36 #[error("Invalid key length: expected 32 bytes, got {0}")]
37 InvalidKeyLength(usize),
38 #[error("Invalid pubkey length: expected 32 bytes, got {0}")]
39 InvalidPubkeyLength(usize),
40 #[error("Missing required field: {0}")]
41 MissingField(String),
42 #[error("TLV error: {0}")]
43 TlvError(String),
44 #[error("UTF-8 error: {0}")]
45 Utf8Error(#[from] std::string::FromUtf8Error),
46 #[error("Hex error: {0}")]
47 HexError(#[from] hex::FromHexError),
48}
49
50#[derive(Debug, Clone, PartialEq)]
52pub struct NHashData {
53 pub hash: Hash,
55 pub path: Vec<String>,
57 pub decrypt_key: Option<[u8; 32]>,
59}
60
61#[derive(Debug, Clone, PartialEq)]
63pub struct NRefData {
64 pub pubkey: [u8; 32],
66 pub tree_name: String,
68 pub path: Vec<String>,
70 pub decrypt_key: Option<[u8; 32]>,
72}
73
74#[derive(Debug, Clone, PartialEq)]
76pub enum DecodeResult {
77 NHash(NHashData),
78 NRef(NRefData),
79}
80
81fn parse_tlv(data: &[u8]) -> Result<std::collections::HashMap<u8, Vec<Vec<u8>>>, NHashError> {
83 let mut result: std::collections::HashMap<u8, Vec<Vec<u8>>> = std::collections::HashMap::new();
84 let mut offset = 0;
85
86 while offset < data.len() {
87 if offset + 2 > data.len() {
88 return Err(NHashError::TlvError("unexpected end of data".into()));
89 }
90 let t = data[offset];
91 let l = data[offset + 1] as usize;
92 offset += 2;
93
94 if offset + l > data.len() {
95 return Err(NHashError::TlvError(format!(
96 "not enough data for type {}, need {} bytes",
97 t, l
98 )));
99 }
100 let v = data[offset..offset + l].to_vec();
101 offset += l;
102
103 result.entry(t).or_default().push(v);
104 }
105
106 Ok(result)
107}
108
109fn encode_tlv(tlv: &std::collections::HashMap<u8, Vec<Vec<u8>>>) -> Result<Vec<u8>, NHashError> {
111 let mut entries: Vec<u8> = Vec::new();
112
113 let mut keys: Vec<u8> = tlv.keys().copied().collect();
115 keys.sort();
116
117 for t in keys {
118 if let Some(values) = tlv.get(&t) {
119 for v in values {
120 if v.len() > 255 {
121 return Err(NHashError::TlvError(format!(
122 "value too long for type {}: {} bytes",
123 t,
124 v.len()
125 )));
126 }
127 entries.push(t);
128 entries.push(v.len() as u8);
129 entries.extend_from_slice(v);
130 }
131 }
132 }
133
134 Ok(entries)
135}
136
137fn encode_bech32(hrp: &str, data: &[u8]) -> Result<String, NHashError> {
139 use bech32::{Bech32m, Hrp};
140
141 let hrp = Hrp::parse(hrp).map_err(|e| NHashError::Bech32(e.to_string()))?;
142 bech32::encode::<Bech32m>(hrp, data)
143 .map_err(|e| NHashError::Bech32(e.to_string()))
144}
145
146fn decode_bech32(s: &str) -> Result<(String, Vec<u8>), NHashError> {
148 let (hrp, data) = bech32::decode(s)
149 .map_err(|e| NHashError::Bech32(e.to_string()))?;
150
151 Ok((hrp.to_string(), data))
152}
153
154pub fn nhash_encode(hash: &Hash) -> Result<String, NHashError> {
160 encode_bech32("nhash", hash)
161}
162
163pub fn nhash_encode_full(data: &NHashData) -> Result<String, NHashError> {
165 if data.path.is_empty() && data.decrypt_key.is_none() {
167 return encode_bech32("nhash", &data.hash);
168 }
169
170 let mut tlv: std::collections::HashMap<u8, Vec<Vec<u8>>> = std::collections::HashMap::new();
172 tlv.insert(tlv::HASH, vec![data.hash.to_vec()]);
173
174 if !data.path.is_empty() {
175 tlv.insert(
176 tlv::PATH,
177 data.path.iter().map(|p| p.as_bytes().to_vec()).collect(),
178 );
179 }
180
181 if let Some(key) = &data.decrypt_key {
182 tlv.insert(tlv::DECRYPT_KEY, vec![key.to_vec()]);
183 }
184
185 encode_bech32("nhash", &encode_tlv(&tlv)?)
186}
187
188pub fn nhash_decode(code: &str) -> Result<NHashData, NHashError> {
190 let code = code.strip_prefix("hashtree:").unwrap_or(code);
192
193 let (prefix, data) = decode_bech32(code)?;
194
195 if prefix != "nhash" {
196 return Err(NHashError::InvalidPrefix {
197 expected: "nhash".into(),
198 got: prefix,
199 });
200 }
201
202 if data.len() == 32 {
204 let mut hash = [0u8; 32];
205 hash.copy_from_slice(&data);
206 return Ok(NHashData {
207 hash,
208 path: Vec::new(),
209 decrypt_key: None,
210 });
211 }
212
213 let tlv = parse_tlv(&data)?;
215
216 let hash_bytes = tlv
217 .get(&tlv::HASH)
218 .and_then(|v| v.first())
219 .ok_or_else(|| NHashError::MissingField("hash".into()))?;
220
221 if hash_bytes.len() != 32 {
222 return Err(NHashError::InvalidHashLength(hash_bytes.len()));
223 }
224
225 let mut hash = [0u8; 32];
226 hash.copy_from_slice(hash_bytes);
227
228 let path = if let Some(paths) = tlv.get(&tlv::PATH) {
230 paths
231 .iter()
232 .map(|p| String::from_utf8(p.clone()))
233 .collect::<Result<Vec<_>, _>>()?
234 } else {
235 Vec::new()
236 };
237
238 let decrypt_key = if let Some(keys) = tlv.get(&tlv::DECRYPT_KEY) {
239 if let Some(key_bytes) = keys.first() {
240 if key_bytes.len() != 32 {
241 return Err(NHashError::InvalidKeyLength(key_bytes.len()));
242 }
243 let mut key = [0u8; 32];
244 key.copy_from_slice(key_bytes);
245 Some(key)
246 } else {
247 None
248 }
249 } else {
250 None
251 };
252
253 Ok(NHashData { hash, path, decrypt_key })
254}
255
256pub fn nref_encode(data: &NRefData) -> Result<String, NHashError> {
262 let mut tlv: std::collections::HashMap<u8, Vec<Vec<u8>>> = std::collections::HashMap::new();
263
264 tlv.insert(tlv::PUBKEY, vec![data.pubkey.to_vec()]);
265 tlv.insert(tlv::TREE_NAME, vec![data.tree_name.as_bytes().to_vec()]);
266
267 if !data.path.is_empty() {
268 tlv.insert(
269 tlv::PATH,
270 data.path.iter().map(|p| p.as_bytes().to_vec()).collect(),
271 );
272 }
273
274 if let Some(key) = &data.decrypt_key {
275 tlv.insert(tlv::DECRYPT_KEY, vec![key.to_vec()]);
276 }
277
278 encode_bech32("nref", &encode_tlv(&tlv)?)
279}
280
281pub fn nref_decode(code: &str) -> Result<NRefData, NHashError> {
283 let code = code.strip_prefix("hashtree:").unwrap_or(code);
285
286 let (prefix, data) = decode_bech32(code)?;
287
288 if prefix != "nref" {
289 return Err(NHashError::InvalidPrefix {
290 expected: "nref".into(),
291 got: prefix,
292 });
293 }
294
295 let tlv = parse_tlv(&data)?;
296
297 let pubkey_bytes = tlv
299 .get(&tlv::PUBKEY)
300 .and_then(|v| v.first())
301 .ok_or_else(|| NHashError::MissingField("pubkey".into()))?;
302
303 if pubkey_bytes.len() != 32 {
304 return Err(NHashError::InvalidPubkeyLength(pubkey_bytes.len()));
305 }
306
307 let mut pubkey = [0u8; 32];
308 pubkey.copy_from_slice(pubkey_bytes);
309
310 let tree_name_bytes = tlv
312 .get(&tlv::TREE_NAME)
313 .and_then(|v| v.first())
314 .ok_or_else(|| NHashError::MissingField("tree_name".into()))?;
315
316 let tree_name = String::from_utf8(tree_name_bytes.clone())?;
317
318 let path = if let Some(paths) = tlv.get(&tlv::PATH) {
320 paths
321 .iter()
322 .map(|p| String::from_utf8(p.clone()))
323 .collect::<Result<Vec<_>, _>>()?
324 } else {
325 Vec::new()
326 };
327
328 let decrypt_key = if let Some(keys) = tlv.get(&tlv::DECRYPT_KEY) {
330 if let Some(key_bytes) = keys.first() {
331 if key_bytes.len() != 32 {
332 return Err(NHashError::InvalidKeyLength(key_bytes.len()));
333 }
334 let mut key = [0u8; 32];
335 key.copy_from_slice(key_bytes);
336 Some(key)
337 } else {
338 None
339 }
340 } else {
341 None
342 };
343
344 Ok(NRefData {
345 pubkey,
346 tree_name,
347 path,
348 decrypt_key,
349 })
350}
351
352pub fn decode(code: &str) -> Result<DecodeResult, NHashError> {
358 let code = code.strip_prefix("hashtree:").unwrap_or(code);
359
360 if code.starts_with("nhash1") {
361 return Ok(DecodeResult::NHash(nhash_decode(code)?));
362 }
363 if code.starts_with("nref1") {
364 return Ok(DecodeResult::NRef(nref_decode(code)?));
365 }
366
367 Err(NHashError::InvalidPrefix {
368 expected: "nhash1 or nref1".into(),
369 got: code.chars().take(10).collect(),
370 })
371}
372
373pub fn is_nhash(value: &str) -> bool {
379 value.starts_with("nhash1")
380}
381
382pub fn is_nref(value: &str) -> bool {
384 value.starts_with("nref1")
385}
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390
391 #[test]
392 fn test_nhash_simple() {
393 let hash: Hash = [
394 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
395 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
396 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
397 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
398 ];
399
400 let encoded = nhash_encode(&hash).unwrap();
401 assert!(encoded.starts_with("nhash1"));
402
403 let decoded = nhash_decode(&encoded).unwrap();
404 assert_eq!(decoded.hash, hash);
405 assert!(decoded.path.is_empty());
406 assert!(decoded.decrypt_key.is_none());
407 }
408
409 #[test]
410 fn test_nhash_with_path() {
411 let hash: Hash = [0xaa; 32];
412
413 let data = NHashData {
414 hash,
415 path: vec!["folder".into(), "file.txt".into()],
416 decrypt_key: None,
417 };
418
419 let encoded = nhash_encode_full(&data).unwrap();
420 assert!(encoded.starts_with("nhash1"));
421
422 let decoded = nhash_decode(&encoded).unwrap();
423 assert_eq!(decoded.hash, hash);
424 assert_eq!(decoded.path, vec!["folder", "file.txt"]);
425 assert!(decoded.decrypt_key.is_none());
426 }
427
428 #[test]
429 fn test_nhash_with_key() {
430 let hash: Hash = [0xaa; 32];
431 let key: [u8; 32] = [0xbb; 32];
432
433 let data = NHashData {
434 hash,
435 path: vec![],
436 decrypt_key: Some(key),
437 };
438
439 let encoded = nhash_encode_full(&data).unwrap();
440 assert!(encoded.starts_with("nhash1"));
441
442 let decoded = nhash_decode(&encoded).unwrap();
443 assert_eq!(decoded.hash, hash);
444 assert!(decoded.path.is_empty());
445 assert_eq!(decoded.decrypt_key, Some(key));
446 }
447
448 #[test]
449 fn test_nhash_with_path_and_key() {
450 let hash: Hash = [0xaa; 32];
451 let key: [u8; 32] = [0xbb; 32];
452
453 let data = NHashData {
454 hash,
455 path: vec!["docs".into()],
456 decrypt_key: Some(key),
457 };
458
459 let encoded = nhash_encode_full(&data).unwrap();
460 let decoded = nhash_decode(&encoded).unwrap();
461 assert_eq!(decoded.hash, hash);
462 assert_eq!(decoded.path, vec!["docs"]);
463 assert_eq!(decoded.decrypt_key, Some(key));
464 }
465
466 #[test]
467 fn test_nref_simple() {
468 let pubkey: [u8; 32] = [0xcc; 32];
469 let data = NRefData {
470 pubkey,
471 tree_name: "home".into(),
472 path: vec![],
473 decrypt_key: None,
474 };
475
476 let encoded = nref_encode(&data).unwrap();
477 assert!(encoded.starts_with("nref1"));
478
479 let decoded = nref_decode(&encoded).unwrap();
480 assert_eq!(decoded.pubkey, pubkey);
481 assert_eq!(decoded.tree_name, "home");
482 assert!(decoded.path.is_empty());
483 assert!(decoded.decrypt_key.is_none());
484 }
485
486 #[test]
487 fn test_nref_with_path_and_key() {
488 let pubkey: [u8; 32] = [0xdd; 32];
489 let key: [u8; 32] = [0xee; 32];
490
491 let data = NRefData {
492 pubkey,
493 tree_name: "photos".into(),
494 path: vec!["vacation".into(), "beach.jpg".into()],
495 decrypt_key: Some(key),
496 };
497
498 let encoded = nref_encode(&data).unwrap();
499 assert!(encoded.starts_with("nref1"));
500
501 let decoded = nref_decode(&encoded).unwrap();
502 assert_eq!(decoded.pubkey, pubkey);
503 assert_eq!(decoded.tree_name, "photos");
504 assert_eq!(decoded.path, vec!["vacation", "beach.jpg"]);
505 assert_eq!(decoded.decrypt_key, Some(key));
506 }
507
508 #[test]
509 fn test_decode_generic() {
510 let hash: Hash = [0x11; 32];
511 let nhash = nhash_encode(&hash).unwrap();
512
513 match decode(&nhash).unwrap() {
514 DecodeResult::NHash(data) => assert_eq!(data.hash, hash),
515 _ => panic!("expected NHash"),
516 }
517
518 let pubkey: [u8; 32] = [0x22; 32];
519 let nref_data = NRefData {
520 pubkey,
521 tree_name: "test".into(),
522 path: vec![],
523 decrypt_key: None,
524 };
525 let nref = nref_encode(&nref_data).unwrap();
526
527 match decode(&nref).unwrap() {
528 DecodeResult::NRef(data) => {
529 assert_eq!(data.pubkey, pubkey);
530 assert_eq!(data.tree_name, "test");
531 }
532 _ => panic!("expected NRef"),
533 }
534 }
535
536 #[test]
537 fn test_is_nhash() {
538 assert!(is_nhash("nhash1abc"));
539 assert!(!is_nhash("nref1abc"));
540 assert!(!is_nhash("npub1abc"));
541 }
542
543 #[test]
544 fn test_is_nref() {
545 assert!(is_nref("nref1abc"));
546 assert!(!is_nref("nhash1abc"));
547 assert!(!is_nref("npub1abc"));
548 }
549}