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