summary refs log tree commit diff
path: root/ripple/fossil
diff options
context:
space:
mode:
authoredef <edef@unfathomable.blue>2022-05-03 14:03:12 +0000
committeredef <edef@unfathomable.blue>2022-05-03 16:04:29 +0000
commitfdf83f8681bb3531f6441f9d0a021ca9dcd36c45 (patch)
treed8a1ec0acc662a2ab3c15568d040f9d5c5d8e6de /ripple/fossil
parent16e9d6fbbc1894b0706378a42123dc01d812e41e (diff)
ripple/fossil: add digest_from_str and digest_str
These decode digests to and from zbase32 for user-facing uses.

Change-Id: Ibd2db960044a97812d18d1a3c107521d78bd7f24
Diffstat (limited to 'ripple/fossil')
-rw-r--r--ripple/fossil/Cargo.toml1
-rw-r--r--ripple/fossil/src/lib.rs40
2 files changed, 40 insertions, 1 deletions
diff --git a/ripple/fossil/Cargo.toml b/ripple/fossil/Cargo.toml
index a1f39fb..6e1bf7b 100644
--- a/ripple/fossil/Cargo.toml
+++ b/ripple/fossil/Cargo.toml
@@ -20,6 +20,7 @@ lazy_static = "1.4.0"
 bao = "0.12.0"
 anyhow = "1.0.53"
 clap = { version = "3.1.15", features = ["derive"] }
+zbase32 = "0.1.2"
 
 [build-dependencies]
 prost-build = "0.8.0"
diff --git a/ripple/fossil/src/lib.rs b/ripple/fossil/src/lib.rs
index 53e4d7b..aa4821c 100644
--- a/ripple/fossil/src/lib.rs
+++ b/ripple/fossil/src/lib.rs
@@ -3,7 +3,7 @@
 
 pub use crate::chunker::Chunker;
 use {
-	anyhow::{Context, Result},
+	anyhow::{anyhow, bail, Context, Result},
 	byteorder::{BigEndian, ByteOrder},
 	prost::Message,
 	sled::{transaction::ConflictableTransactionError, Transactional},
@@ -474,6 +474,44 @@ impl Directory {
 	}
 }
 
+pub fn digest_str(digest: &Digest) -> String {
+	let bytes = digest.as_bytes();
+	zbase32::encode_full_bytes(bytes)
+}
+
+pub fn digest_from_str(s: &str) -> Result<Digest> {
+	let bits = DIGEST_BYTES * 8;
+	let chars = (bits + 5 - 1) / 5;
+
+	if s.len() < chars {
+		bail!("digest too short");
+	}
+
+	let bytes_vec = zbase32::decode_str(s, bits as u64).map_err(|_| anyhow!("invalid digest"))?;
+	if zbase32::encode_full_bytes(&bytes_vec) != s {
+		// non-canonical input
+		bail!("non-canonical digest");
+	}
+
+	let mut bytes = [0; DIGEST_BYTES];
+	bytes.copy_from_slice(&bytes_vec);
+
+	Ok(bytes.into())
+}
+
+#[test]
+fn digest_str_golden() {
+	let h = blake3::hash(b"hello fossil!");
+	let s = "xow7w4moszx4w5ngp7d7w3fyfk313tpw6fndik4imdhd18ynw6so";
+	assert_eq!(digest_str(&h), s);
+	assert_eq!(digest_from_str(s).ok(), Some(h));
+}
+
+#[test]
+fn digest_str_short() {
+	assert!(digest_from_str("xow7w4moszx4w5ngp7d7w3fyfk313tpw6fndik4imdhd18ynw6s").is_err());
+}
+
 #[track_caller]
 pub fn digest_from_bytes(bytes: &[u8]) -> Digest {
 	if bytes.len() != DIGEST_BYTES {