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> {
140 use bech32::{Bech32, Hrp};
141
142 let hrp = Hrp::parse(hrp).map_err(|e| NHashError::Bech32(e.to_string()))?;
143 bech32::encode::<Bech32>(hrp, data)
144 .map_err(|e| NHashError::Bech32(e.to_string()))
145}
146
147fn decode_bech32(s: &str) -> Result<(String, Vec<u8>), NHashError> {
149 let (hrp, data) = bech32::decode(s)
150 .map_err(|e| NHashError::Bech32(e.to_string()))?;
151
152 Ok((hrp.to_string(), data))
153}
154
155pub fn nhash_encode(hash: &Hash) -> Result<String, NHashError> {
161 encode_bech32("nhash", hash)
162}
163
164pub fn nhash_encode_full(data: &NHashData) -> Result<String, NHashError> {
166 if data.path.is_empty() && data.decrypt_key.is_none() {
168 return encode_bech32("nhash", &data.hash);
169 }
170
171 let mut tlv: std::collections::HashMap<u8, Vec<Vec<u8>>> = std::collections::HashMap::new();
173 tlv.insert(tlv::HASH, vec![data.hash.to_vec()]);
174
175 if !data.path.is_empty() {
176 tlv.insert(
177 tlv::PATH,
178 data.path.iter().map(|p| p.as_bytes().to_vec()).collect(),
179 );
180 }
181
182 if let Some(key) = &data.decrypt_key {
183 tlv.insert(tlv::DECRYPT_KEY, vec![key.to_vec()]);
184 }
185
186 encode_bech32("nhash", &encode_tlv(&tlv)?)
187}
188
189pub fn nhash_decode(code: &str) -> Result<NHashData, NHashError> {
191 let code = code.strip_prefix("hashtree:").unwrap_or(code);
193
194 let (prefix, data) = decode_bech32(code)?;
195
196 if prefix != "nhash" {
197 return Err(NHashError::InvalidPrefix {
198 expected: "nhash".into(),
199 got: prefix,
200 });
201 }
202
203 if data.len() == 32 {
205 let mut hash = [0u8; 32];
206 hash.copy_from_slice(&data);
207 return Ok(NHashData {
208 hash,
209 path: Vec::new(),
210 decrypt_key: None,
211 });
212 }
213
214 let tlv = parse_tlv(&data)?;
216
217 let hash_bytes = tlv
218 .get(&tlv::HASH)
219 .and_then(|v| v.first())
220 .ok_or_else(|| NHashError::MissingField("hash".into()))?;
221
222 if hash_bytes.len() != 32 {
223 return Err(NHashError::InvalidHashLength(hash_bytes.len()));
224 }
225
226 let mut hash = [0u8; 32];
227 hash.copy_from_slice(hash_bytes);
228
229 let path = if let Some(paths) = tlv.get(&tlv::PATH) {
231 paths
232 .iter()
233 .map(|p| String::from_utf8(p.clone()))
234 .collect::<Result<Vec<_>, _>>()?
235 } else {
236 Vec::new()
237 };
238
239 let decrypt_key = if let Some(keys) = tlv.get(&tlv::DECRYPT_KEY) {
240 if let Some(key_bytes) = keys.first() {
241 if key_bytes.len() != 32 {
242 return Err(NHashError::InvalidKeyLength(key_bytes.len()));
243 }
244 let mut key = [0u8; 32];
245 key.copy_from_slice(key_bytes);
246 Some(key)
247 } else {
248 None
249 }
250 } else {
251 None
252 };
253
254 Ok(NHashData { hash, path, decrypt_key })
255}
256
257pub fn nref_encode(data: &NRefData) -> Result<String, NHashError> {
263 let mut tlv: std::collections::HashMap<u8, Vec<Vec<u8>>> = std::collections::HashMap::new();
264
265 tlv.insert(tlv::PUBKEY, vec![data.pubkey.to_vec()]);
266 tlv.insert(tlv::TREE_NAME, vec![data.tree_name.as_bytes().to_vec()]);
267
268 if !data.path.is_empty() {
269 tlv.insert(
270 tlv::PATH,
271 data.path.iter().map(|p| p.as_bytes().to_vec()).collect(),
272 );
273 }
274
275 if let Some(key) = &data.decrypt_key {
276 tlv.insert(tlv::DECRYPT_KEY, vec![key.to_vec()]);
277 }
278
279 encode_bech32("nref", &encode_tlv(&tlv)?)
280}
281
282pub fn nref_decode(code: &str) -> Result<NRefData, NHashError> {
284 let code = code.strip_prefix("hashtree:").unwrap_or(code);
286
287 let (prefix, data) = decode_bech32(code)?;
288
289 if prefix != "nref" {
290 return Err(NHashError::InvalidPrefix {
291 expected: "nref".into(),
292 got: prefix,
293 });
294 }
295
296 let tlv = parse_tlv(&data)?;
297
298 let pubkey_bytes = tlv
300 .get(&tlv::PUBKEY)
301 .and_then(|v| v.first())
302 .ok_or_else(|| NHashError::MissingField("pubkey".into()))?;
303
304 if pubkey_bytes.len() != 32 {
305 return Err(NHashError::InvalidPubkeyLength(pubkey_bytes.len()));
306 }
307
308 let mut pubkey = [0u8; 32];
309 pubkey.copy_from_slice(pubkey_bytes);
310
311 let tree_name_bytes = tlv
313 .get(&tlv::TREE_NAME)
314 .and_then(|v| v.first())
315 .ok_or_else(|| NHashError::MissingField("tree_name".into()))?;
316
317 let tree_name = String::from_utf8(tree_name_bytes.clone())?;
318
319 let path = if let Some(paths) = tlv.get(&tlv::PATH) {
321 paths
322 .iter()
323 .map(|p| String::from_utf8(p.clone()))
324 .collect::<Result<Vec<_>, _>>()?
325 } else {
326 Vec::new()
327 };
328
329 let decrypt_key = if let Some(keys) = tlv.get(&tlv::DECRYPT_KEY) {
331 if let Some(key_bytes) = keys.first() {
332 if key_bytes.len() != 32 {
333 return Err(NHashError::InvalidKeyLength(key_bytes.len()));
334 }
335 let mut key = [0u8; 32];
336 key.copy_from_slice(key_bytes);
337 Some(key)
338 } else {
339 None
340 }
341 } else {
342 None
343 };
344
345 Ok(NRefData {
346 pubkey,
347 tree_name,
348 path,
349 decrypt_key,
350 })
351}
352
353pub fn decode(code: &str) -> Result<DecodeResult, NHashError> {
359 let code = code.strip_prefix("hashtree:").unwrap_or(code);
360
361 if code.starts_with("nhash1") {
362 return Ok(DecodeResult::NHash(nhash_decode(code)?));
363 }
364 if code.starts_with("nref1") {
365 return Ok(DecodeResult::NRef(nref_decode(code)?));
366 }
367
368 Err(NHashError::InvalidPrefix {
369 expected: "nhash1 or nref1".into(),
370 got: code.chars().take(10).collect(),
371 })
372}
373
374pub fn is_nhash(value: &str) -> bool {
380 value.starts_with("nhash1")
381}
382
383pub fn is_nref(value: &str) -> bool {
385 value.starts_with("nref1")
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391
392 #[test]
393 fn test_nhash_simple() {
394 let hash: Hash = [
395 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
396 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
397 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
398 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
399 ];
400
401 let encoded = nhash_encode(&hash).unwrap();
402 assert!(encoded.starts_with("nhash1"));
403
404 let decoded = nhash_decode(&encoded).unwrap();
405 assert_eq!(decoded.hash, hash);
406 assert!(decoded.path.is_empty());
407 assert!(decoded.decrypt_key.is_none());
408 }
409
410 #[test]
411 fn test_nhash_with_path() {
412 let hash: Hash = [0xaa; 32];
413
414 let data = NHashData {
415 hash,
416 path: vec!["folder".into(), "file.txt".into()],
417 decrypt_key: None,
418 };
419
420 let encoded = nhash_encode_full(&data).unwrap();
421 assert!(encoded.starts_with("nhash1"));
422
423 let decoded = nhash_decode(&encoded).unwrap();
424 assert_eq!(decoded.hash, hash);
425 assert_eq!(decoded.path, vec!["folder", "file.txt"]);
426 assert!(decoded.decrypt_key.is_none());
427 }
428
429 #[test]
430 fn test_nhash_with_key() {
431 let hash: Hash = [0xaa; 32];
432 let key: [u8; 32] = [0xbb; 32];
433
434 let data = NHashData {
435 hash,
436 path: vec![],
437 decrypt_key: Some(key),
438 };
439
440 let encoded = nhash_encode_full(&data).unwrap();
441 assert!(encoded.starts_with("nhash1"));
442
443 let decoded = nhash_decode(&encoded).unwrap();
444 assert_eq!(decoded.hash, hash);
445 assert!(decoded.path.is_empty());
446 assert_eq!(decoded.decrypt_key, Some(key));
447 }
448
449 #[test]
450 fn test_nhash_with_path_and_key() {
451 let hash: Hash = [0xaa; 32];
452 let key: [u8; 32] = [0xbb; 32];
453
454 let data = NHashData {
455 hash,
456 path: vec!["docs".into()],
457 decrypt_key: Some(key),
458 };
459
460 let encoded = nhash_encode_full(&data).unwrap();
461 let decoded = nhash_decode(&encoded).unwrap();
462 assert_eq!(decoded.hash, hash);
463 assert_eq!(decoded.path, vec!["docs"]);
464 assert_eq!(decoded.decrypt_key, Some(key));
465 }
466
467 #[test]
468 fn test_nref_simple() {
469 let pubkey: [u8; 32] = [0xcc; 32];
470 let data = NRefData {
471 pubkey,
472 tree_name: "home".into(),
473 path: vec![],
474 decrypt_key: None,
475 };
476
477 let encoded = nref_encode(&data).unwrap();
478 assert!(encoded.starts_with("nref1"));
479
480 let decoded = nref_decode(&encoded).unwrap();
481 assert_eq!(decoded.pubkey, pubkey);
482 assert_eq!(decoded.tree_name, "home");
483 assert!(decoded.path.is_empty());
484 assert!(decoded.decrypt_key.is_none());
485 }
486
487 #[test]
488 fn test_nref_with_path_and_key() {
489 let pubkey: [u8; 32] = [0xdd; 32];
490 let key: [u8; 32] = [0xee; 32];
491
492 let data = NRefData {
493 pubkey,
494 tree_name: "photos".into(),
495 path: vec!["vacation".into(), "beach.jpg".into()],
496 decrypt_key: Some(key),
497 };
498
499 let encoded = nref_encode(&data).unwrap();
500 assert!(encoded.starts_with("nref1"));
501
502 let decoded = nref_decode(&encoded).unwrap();
503 assert_eq!(decoded.pubkey, pubkey);
504 assert_eq!(decoded.tree_name, "photos");
505 assert_eq!(decoded.path, vec!["vacation", "beach.jpg"]);
506 assert_eq!(decoded.decrypt_key, Some(key));
507 }
508
509 #[test]
510 fn test_decode_generic() {
511 let hash: Hash = [0x11; 32];
512 let nhash = nhash_encode(&hash).unwrap();
513
514 match decode(&nhash).unwrap() {
515 DecodeResult::NHash(data) => assert_eq!(data.hash, hash),
516 _ => panic!("expected NHash"),
517 }
518
519 let pubkey: [u8; 32] = [0x22; 32];
520 let nref_data = NRefData {
521 pubkey,
522 tree_name: "test".into(),
523 path: vec![],
524 decrypt_key: None,
525 };
526 let nref = nref_encode(&nref_data).unwrap();
527
528 match decode(&nref).unwrap() {
529 DecodeResult::NRef(data) => {
530 assert_eq!(data.pubkey, pubkey);
531 assert_eq!(data.tree_name, "test");
532 }
533 _ => panic!("expected NRef"),
534 }
535 }
536
537 #[test]
538 fn test_is_nhash() {
539 assert!(is_nhash("nhash1abc"));
540 assert!(!is_nhash("nref1abc"));
541 assert!(!is_nhash("npub1abc"));
542 }
543
544 #[test]
545 fn test_is_nref() {
546 assert!(is_nref("nref1abc"));
547 assert!(!is_nref("nhash1abc"));
548 assert!(!is_nref("npub1abc"));
549 }
550}