summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2019-06-05 17:40:31 +0200
committerYorhel <git@yorhel.nl>2019-06-05 17:42:21 +0200
commit828cf005108b74a6c0a589e85ca3ca09ac40f941 (patch)
treee2c8d98dfba4d83bc6c7bd2583b5cc6e57c00390
parente93f0dcea107b58c240c5c065bec4e2e23ef64e1 (diff)
b2tigest/serialize: base58 -> base32, b2t:xx -> b2t.xx
As per ChiFS protocol commit f94cea48df47ce25ec92ad9f549eb18f5f25bdae Since base32 encoding/decoding can more easily work with known-sized buffers, I managed to get rid of a few memory allocations as well.
-rw-r--r--Cargo.toml2
-rw-r--r--src/b2digest.rs56
-rw-r--r--src/b2tree.rs26
-rw-r--r--src/serialize.rs12
-rwxr-xr-xtest/b2tree.py4
-rwxr-xr-xtest/blake2-test-vectors.py4
6 files changed, 57 insertions, 47 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 00f411f..4b468fb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,7 +12,7 @@ edition = "2018"
# TODO: blake2-rfc and globset aren't currently needed by chifs-hub,
# we could use feature flags to only enable what is used.
blake2-rfc = "0.2.18"
-bs58 = "0.2.2"
+data-encoding = "2.1.0"
chrono = { version = "0.4.6" }
clap = { version = "2.32.0", default-features = false }
colored = "1.7.0"
diff --git a/src/b2digest.rs b/src/b2digest.rs
index 216dc58..c7bba99 100644
--- a/src/b2digest.rs
+++ b/src/b2digest.rs
@@ -1,11 +1,14 @@
use std::fmt;
use serde::*;
use serde::de::{Visitor,Error,Unexpected};
+use data_encoding::BASE32_NOPAD;
use blake2_rfc::blake2b::{Blake2b,Blake2bResult};
// BLAKE2 digest byte length; 256 bits.
pub const DIGEST_LENGTH : usize = 32;
+pub const DIGEST_B32_LENGTH : usize = 52; // ceil(DIGEST_LENGTH*8/5)
+
// "Leaf maximal byte length" in tree mode
pub const LEAF_SIZE : u64 = 4096;
@@ -82,22 +85,28 @@ impl Digest {
impl fmt::Display for Digest {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- // TODO: Non-allocating implementation; The result fits on the stack just fine.
- write!(f, "{}", bs58::encode(&self.0[..]).into_string())
+ let mut buf = [0u8; DIGEST_B32_LENGTH];
+ BASE32_NOPAD.encode_mut(&self.0, &mut buf);
+ f.write_str(unsafe { std::str::from_utf8_unchecked(&buf) })
}
}
impl std::str::FromStr for Digest {
- type Err = bs58::decode::DecodeError;
+ type Err = data_encoding::DecodeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
+ const LEN_ERR : data_encoding::DecodeError = data_encoding::DecodeError { position: 0, kind: data_encoding::DecodeKind::Length };
+
+ let s = s.trim_start_matches("b2t.");
+ if s.len() != DIGEST_B32_LENGTH {
+ return Err(LEN_ERR);
+ }
let mut d = Digest::default();
- let len = bs58::decode(s.trim_start_matches("b2t:")).into(&mut d.0[..])?;
- if len == DIGEST_LENGTH {
- Ok(d)
- } else {
- Err(bs58::decode::DecodeError::BufferTooSmall) // Most likely the *input* buffer was too small, but whatever.
+ match BASE32_NOPAD.decode_mut(s.as_bytes(), &mut d.0[..]) {
+ Ok(len) if len == DIGEST_LENGTH => Ok(d),
+ Ok(_) => Err(LEN_ERR),
+ Err(e) => Err(e.error)
}
}
}
@@ -105,8 +114,9 @@ impl std::str::FromStr for Digest {
impl Serialize for Digest {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
- // I don't like how this requires memory allocation, might slow things down considerably
- serializer.serialize_str(&self.to_string())
+ let mut buf = [0u8; DIGEST_B32_LENGTH];
+ BASE32_NOPAD.encode_mut(&self.0, &mut buf);
+ serializer.serialize_str(unsafe { std::str::from_utf8_unchecked(&buf) })
}
}
@@ -143,27 +153,27 @@ mod tests {
use super::*;
#[test]
- fn test_base58() {
- assert_eq!(Digest::default().to_string(), String::from("11111111111111111111111111111111"));
- assert_eq!(Digest([255; DIGEST_LENGTH]).to_string(), String::from("JEKNVnkbo3jma5nREBBJCDoXFVeKkD56V3xKrvRmWxFG"));
+ fn test_base32() {
+ assert_eq!(Digest::default().to_string(), String::from("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"));
+ assert_eq!(Digest([255; DIGEST_LENGTH]).to_string(), String::from("777777777777777777777777777777777777777777777777777Q"));
assert!( "".parse::<Digest>().is_err());
- assert_eq!("11111111111111111111111111111111" .parse(), Ok(Digest::default()));
- assert!( "11111111111111111111111111110111" .parse::<Digest>().is_err());
- assert!( "1111111111111111111111111111111" .parse::<Digest>().is_err());
- assert!( "111111111111111111111111111111111".parse::<Digest>().is_err());
- assert_eq!("JEKNVnkbo3jma5nREBBJCDoXFVeKkD56V3xKrvRmWxFG".parse(), Ok(Digest([255; DIGEST_LENGTH])));
- assert!( "JEKNVnkbo3jma5nREBBJCDoXFVeKkD56V3xKrvRmWxFH".parse::<Digest>().is_err());
- assert_eq!("b2t:JEKNVnkbo3jma5nREBBJCDoXFVeKkD56V3xKrvRmWxFG".parse(), Ok(Digest([255; DIGEST_LENGTH])));
+ assert_eq!("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" .parse(), Ok(Digest::default()));
+ assert!( "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" .parse::<Digest>().is_err());
+ assert!( "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0" .parse::<Digest>().is_err());
+ assert!( "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".parse::<Digest>().is_err());
+ assert_eq!("777777777777777777777777777777777777777777777777777Q".parse(), Ok(Digest([255; DIGEST_LENGTH])));
+ assert!( "7777777777777777777777777777777777777777777777777777".parse::<Digest>().is_err());
+ assert_eq!("b2t.777777777777777777777777777777777777777777777777777Q".parse(), Ok(Digest([255; DIGEST_LENGTH])));
}
#[test]
fn test_blake2() {
// Empty file
- assert_eq!(Digest::tree_leaf(0, true, b"").to_string(), String::from("EUwSQCN39NPeuT5iDJtgvcm385EFnyZ7Mx21fvjENvNM"));
+ assert_eq!(Digest::tree_leaf(0, true, b"").to_string(), String::from("ZBICUQGJXXKBLXU3CX2PO2ASQ44FV2B2VL5N4YHBQRFQ2UUUZDRA"));
// Hello world
- assert_eq!(Digest::tree_leaf(1, false, b"Hello, world!").to_string(), String::from("4WK7w5hRgQs7b8B5fAtPfVbECNERKV1TB6o6V5KYksEt"));
+ assert_eq!(Digest::tree_leaf(1, false, b"Hello, world!").to_string(), String::from("GQKWIXNIFUICS76WLCJCXR5NFW474VPGVSPTBGLHFTRVHPWLA4KQ"));
// Combined
- assert_eq!(Digest::tree_leaf(0, false, b"").tree_combine(1, 0, true, Digest::tree_leaf(1, true, b"")).to_string(), String::from("FsTAbjPAPgCzLS7h3A7DiYJkqorKMdwPDtiVoMCqcoZY"));
+ assert_eq!(Digest::tree_leaf(0, false, b"").tree_combine(1, 0, true, Digest::tree_leaf(1, true, b"")).to_string(), String::from("3TYD4KYHLBXNIRUCTBXEEPMXWGEHKR3PGYUBLL54PW2CVWGCZIXQ"));
}
}
diff --git a/src/b2tree.rs b/src/b2tree.rs
index 4f86a7a..d5d7efd 100644
--- a/src/b2tree.rs
+++ b/src/b2tree.rs
@@ -197,29 +197,29 @@ mod tests {
#[test]
fn test_tree() {
let t0 = B2Tree::new(0, 0, 1);
- assert_eq!(t0.finalize(0).to_string(), String::from("EUwSQCN39NPeuT5iDJtgvcm385EFnyZ7Mx21fvjENvNM"));
+ assert_eq!(t0.finalize(0).to_string(), String::from("ZBICUQGJXXKBLXU3CX2PO2ASQ44FV2B2VL5N4YHBQRFQ2UUUZDRA"));
let mut t1 = B2Tree::new(0, 0, 1);
t1.update_leaf(&ZERO[..]);
- assert_eq!(t1.finalize(0).to_string(), String::from("FJEJ8QCdtNrAH1XbqPKaFuBZVy1QuMyXxrd6349kRUkb"));
+ assert_eq!(t1.finalize(0).to_string(), String::from("2RWYMFOSOTS4577JIR3KCL2DSQ2W6P3HSZWQORRNEUPKQ5Z2R7OA"));
let mut t2 = B2Tree::new(0, 0, 2);
t2.update_leaf(&ZERO[..]);
t2.update_leaf(&ZERO[..]);
- assert_eq!(t2.finalize(0).to_string(), String::from("9FwXbV9u98rc2jCH5QXTuDscFSzp2E5sudPYPVLwDUtS"));
+ assert_eq!(t2.finalize(0).to_string(), String::from("PKYUP6WIWNVTD476UTAS6HRM2QVAOFNNTFXDEHNYF7IFQITVAWJQ"));
let mut t3 = B2Tree::new(0, 0, 3);
t3.update_leaf(&ZERO[..]);
t3.update_leaf(&ZERO[..]);
t3.update_leaf(&ZERO[..]);
- assert_eq!(t3.finalize(0).to_string(), String::from("6vsb1svp7Bmm1WPKpVNjDSYZ5eSFgfRZ7k9vvx72Lgio"));
+ assert_eq!(t3.finalize(0).to_string(), String::from("LALWU4WPLPQU6OQWO47B2MBQXE2RVAZU4RDRYXDIK4YUNWAGV36A"));
let mut t4 = B2Tree::new(0, 0, 4);
t4.update_leaf(&ZERO[..]);
t4.update_leaf(&ZERO[..]);
t4.update_leaf(&ZERO[..]);
t4.update_leaf(&ZERO[..]);
- assert_eq!(t4.finalize(0).to_string(), String::from("E2vo47L5gUi6gopNhWzHvfS5K1W9PNVBY3JtR7Aaz51Y"));
+ assert_eq!(t4.finalize(0).to_string(), String::from("YGTFMAH6PX3CQSG24M6AQ6VVP4NGEZ7PELK2TLSAZDDXHUK5U7DQ"));
let mut t5 = B2Tree::new(0, 0, 5);
t5.update_leaf(&ZERO[..]);
@@ -227,7 +227,7 @@ mod tests {
t5.update_leaf(&ZERO[..]);
t5.update_leaf(&ZERO[..]);
t5.update_leaf(&ZERO[..]);
- assert_eq!(t5.finalize(0).to_string(), String::from("AtqmLw1yYiZ7zDdx9iYvyhLQqJLY7MgTcdQu7jAgdKUj"));
+ assert_eq!(t5.finalize(0).to_string(), String::from("SMAPCHHKMT7XR4VE4KIE3B4GFKUASMDAMLDYDKLXVCMCCRC23AAA"));
let mut t6 = B2Tree::new(0, 0, 6);
t6.update_leaf(&ZERO[..]);
@@ -236,7 +236,7 @@ mod tests {
t6.update_leaf(&ZERO[..]);
t6.update_leaf(&ZERO[..]);
t6.update_leaf(&ZERO[..]);
- assert_eq!(t6.finalize(0).to_string(), String::from("ERU6Zxek8o6ufx1vBsjQ1Yivh89aygqcrFmdDSZqWqGA"));
+ assert_eq!(t6.finalize(0).to_string(), String::from("Y5WIDS3QCM3T7J2QFEHD75ICFHM4GTIMEJAYLUAUMNEK6M5I3X3Q"));
}
#[test]
@@ -257,7 +257,7 @@ mod tests {
root.update_node(h01_);
root.update_node(h02_);
root.update_node(h03_);
- assert_eq!(root.finalize(0).to_string(), String::from("ERU6Zxek8o6ufx1vBsjQ1Yivh89aygqcrFmdDSZqWqGA"));
+ assert_eq!(root.finalize(0).to_string(), String::from("Y5WIDS3QCM3T7J2QFEHD75ICFHM4GTIMEJAYLUAUMNEK6M5I3X3Q"));
let mut h10 = B2Tree::new(0, 0, 6);
h10.update_leaf(&ZERO[..]);
@@ -272,24 +272,24 @@ mod tests {
let mut root = B2Tree::new(2, 0, 2);
root.update_node(h10_);
root.update_node(h20_);
- assert_eq!(root.finalize(0).to_string(), String::from("ERU6Zxek8o6ufx1vBsjQ1Yivh89aygqcrFmdDSZqWqGA"));
+ assert_eq!(root.finalize(0).to_string(), String::from("Y5WIDS3QCM3T7J2QFEHD75ICFHM4GTIMEJAYLUAUMNEK6M5I3X3Q"));
}
#[test]
fn test_leaves() {
let t0 = B2Leaves::new(0, 0, 1);
- assert_eq!(t0.finalize(0).to_string(), String::from("EUwSQCN39NPeuT5iDJtgvcm385EFnyZ7Mx21fvjENvNM"));
+ assert_eq!(t0.finalize(0).to_string(), String::from("ZBICUQGJXXKBLXU3CX2PO2ASQ44FV2B2VL5N4YHBQRFQ2UUUZDRA"));
let mut t1 = B2Leaves::new(0, 0, 1);
t1.update(&ZERO[..1]);
t1.update(&ZERO[1..]);
- assert_eq!(t1.finalize(0).to_string(), String::from("FJEJ8QCdtNrAH1XbqPKaFuBZVy1QuMyXxrd6349kRUkb"));
+ assert_eq!(t1.finalize(0).to_string(), String::from("2RWYMFOSOTS4577JIR3KCL2DSQ2W6P3HSZWQORRNEUPKQ5Z2R7OA"));
let mut t2 = B2Leaves::new(0, 0, 2);
t2.update(&ZERO[..3995]);
t2.update(&ZERO[..4000]);
t2.update(&ZERO[..5+192]);
- assert_eq!(t2.finalize(0).to_string(), String::from("9FwXbV9u98rc2jCH5QXTuDscFSzp2E5sudPYPVLwDUtS"));
+ assert_eq!(t2.finalize(0).to_string(), String::from("PKYUP6WIWNVTD476UTAS6HRM2QVAOFNNTFXDEHNYF7IFQITVAWJQ"));
let mut t5s = B2Leaves::new(0, 0, 5);
t5s.update(&ZEROLARGE[..4097]);
@@ -298,6 +298,6 @@ mod tests {
t5s.update(&ZEROLARGE[..2048]); // 10240
t5s.update(&ZEROLARGE[..2043]); // 12287
t5s.update(&ZEROLARGE[..8192]); // 20479
- assert_eq!(t5s.finalize(0).to_string(), String::from("CzHFwS3TqPDEyRHDEnAbrtyQpNbMK2gAR6YEPE278ZBV"));
+ assert_eq!(t5s.finalize(0).to_string(), String::from("WIOQW52HZL7IVUSDMOFCALLE3T2CLLT64AAUQ67PQUFSASANOE4A"));
}
}
diff --git a/src/serialize.rs b/src/serialize.rs
index ee8fba5..6f54ddc 100644
--- a/src/serialize.rs
+++ b/src/serialize.rs
@@ -54,7 +54,7 @@ pub struct Hash {
impl Serialize for Hash {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
let mut seq = serializer.serialize_seq(Some(1))?;
- seq.serialize_element(&format!("b2t:{}", self.b2t))?; // XXX: This does more allocation than necessary
+ seq.serialize_element(&format!("b2t.{}", self.b2t))?; // XXX: This string can be allocated on the stack
seq.end()
}
}
@@ -75,7 +75,7 @@ impl<'a> Deserialize<'a> for Hash {
while let Some(p) = seq.next_element()? {
let _ : String = p;
- if p.starts_with("b2t:") {
+ if p.starts_with("b2t.") {
hash.b2t = p.parse().map_err(|_| Error::custom("invalid b2t hash"))?;
}
}
@@ -102,7 +102,7 @@ mod test {
fn test_hash() {
assert_tokens(&Hash { b2t: Digest([255; DIGEST_LENGTH]) }, &[
Token::Seq { len: Some(1) },
- Token::String("b2t:JEKNVnkbo3jma5nREBBJCDoXFVeKkD56V3xKrvRmWxFG"),
+ Token::String("b2t.777777777777777777777777777777777777777777777777777Q"),
Token::SeqEnd
]);
@@ -113,19 +113,19 @@ mod test {
assert_de_tokens_error::<Hash>(&[
Token::Seq { len: Some(0) },
- Token::String("JEKNVnkbo3jma5nREBBJCDoXFVeKkD56V3xKrvRmWxFG"),
+ Token::String("777777777777777777777777777777777777777777777777777Q"),
Token::SeqEnd
], "expected b2t hash type");
assert_de_tokens_error::<Hash>(&[
Token::Seq { len: Some(0) },
- Token::String("sha1:55ca6286e3e4f4fba5d0448333fa99fc5a404a73"),
+ Token::String("sha1.55ca6286e3e4f4fba5d0448333fa99fc5a404a73"),
Token::SeqEnd
], "expected b2t hash type");
assert_de_tokens_error::<Hash>(&[
Token::Seq { len: Some(0) },
- Token::String("b2t:55ca6286e3e4f4fba5d0448333fa99fc5a404a73"),
+ Token::String("b2t.7777777777777777777777777777777777777777777777777777"),
Token::SeqEnd
], "invalid b2t hash");
}
diff --git a/test/b2tree.py b/test/b2tree.py
index b1ef1c9..8c10f3c 100755
--- a/test/b2tree.py
+++ b/test/b2tree.py
@@ -18,7 +18,7 @@ import os
import sys
import math
from hashlib import blake2b
-from base58 import b58encode
+from base64 import b32encode
# blake2b parameters
@@ -67,4 +67,4 @@ depth = math.ceil(math.log(math.ceil(max(file_size,1)/MAX_LEAF_SIZE), FANOUT))
with open(filename, 'rb') as file:
(digest, last_node, bytes_read) = hashNode(depth, 0, file, file_size, 0)
-print(b58encode(digest).decode('ascii'))
+print(b32encode(digest).decode('ascii').strip('='))
diff --git a/test/blake2-test-vectors.py b/test/blake2-test-vectors.py
index 3ae8fe9..bcb1c90 100755
--- a/test/blake2-test-vectors.py
+++ b/test/blake2-test-vectors.py
@@ -3,13 +3,13 @@
# This script provides the test vectors for src/b2.rs
from hashlib import blake2b
-from base58 import b58encode, b58decode
+from base64 import b32encode
def hash(depth, offset, last, buf):
return blake2b(buf, digest_size=32, fanout=2, depth=255, leaf_size=4096, inner_size=32, node_offset=offset, node_depth=depth, last_node=last).digest()
def printhash(desc, depth, offset, last, buf):
- print('{}: {}'.format(desc, b58encode(hash(depth, offset, last, buf)).decode('ascii')))
+ print('{}: {}'.format(desc, b32encode(hash(depth, offset, last, buf)).decode('ascii').strip('=')))
printhash('Empty file', 0, 0, True, b'')