aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml4
-rw-r--r--Cargo.toml6
-rw-r--r--src/guts.rs163
3 files changed, 165 insertions, 8 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0eb486a..44852d4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -41,8 +41,8 @@ jobs:
- name: print instruction set support
run: cargo run --quiet
working-directory: ./tools/instruction_set_support
- # Default tests plus Rayon.
- - run: cargo test --features=rayon
+ # Default tests plus Rayon and Serde.
+ - run: cargo test --features=rayon,serde_derive
# no_std tests.
- run: cargo test --no-default-features
diff --git a/Cargo.toml b/Cargo.toml
index 3114148..c2aad54 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -30,6 +30,10 @@ std = ["digest/std"]
# perform multi-threaded hashing. However, even if this feature is enabled, all
# other APIs remain single-threaded.
+# Implements `serde::{Serialize, Deserialize}` for `ExportedHasher` in the
+# undocumented guts module. Most callers don't need this.
+serde_derive = ["serde", "serde_json", "arrayvec/serde"]
+
# ---------- Features below this line are for internal testing only. ----------
# By default on x86_64, this crate uses Samuel Neves' hand-written assembly
@@ -77,6 +81,8 @@ rayon = { version = "1.2.1", optional = true }
cfg-if = "0.1.10"
digest = "0.8.1"
crypto-mac = "0.7.0"
+serde = { version = "1.0", features = ["derive"], optional = true }
+serde_json = { version = "1.0", optional = true }
[dev-dependencies]
hex = "0.4.2"
diff --git a/src/guts.rs b/src/guts.rs
index 88dcc86..0b54963 100644
--- a/src/guts.rs
+++ b/src/guts.rs
@@ -1,8 +1,31 @@
-// This module is for incremental use cases like the `bao` crate, which need to
-// get their hands on internal chunk and parent chaining values. The vast
-// majority of users should ignore this and use the publicly documented
-// interface instead.
+//! Semi-public, semi-stable APIs for "peeking under the hood."
+//!
+//! This module is hidden from the docs, and the vast majority of callers
+//! should not use it. These APIs are experimental, and much more likely to
+//! change than the rest of the crate.
+//!
+//! This moduls supports manipulating subtree chaining values directly, and
+//! serializing `Hasher` state to external storage. All of these tools have the
+//! potential to give you incorrect finalized hashes if they're misused, which
+//! creates very tricky bugs and violates all sorts of security invariants. If
+//! you're thinking about using these, consider [filing a GitHub
+//! issue](https://github.com/BLAKE3-team/BLAKE3/issues/new) to discuss your
+//! use case. We'd love to hear about it.
+use crate::platform::{le_bytes_from_words_32, words_from_le_bytes_32};
+use arrayvec::ArrayVec;
+
+#[cfg(feature = "serde_derive")]
+use serde::{Deserialize, Serialize};
+
+/// An incremental state like `Hasher`, for computing possibly-non-root
+/// chaining values of single chunks in the interior of the tree.
+///
+/// This currently supports only the default hashing mode. If an incremental
+/// user needs to expose `keyed_hash` or `derive_key`, we can add that.
+///
+/// NOTE: This type is probably going to be replaced. See [this
+/// discussion](https://github.com/BLAKE3-team/BLAKE3/issues/82).
#[derive(Clone, Debug)]
pub struct ChunkState(crate::ChunkState);
@@ -39,8 +62,10 @@ impl ChunkState {
}
}
-// As above, this currently assumes the regular hash mode. If an incremental
-// user needs keyed_hash or derive_key, we can add that.
+/// Similar to `ChunkState`, but for parent chaining values.
+///
+/// This currently supports only the default hashing mode. If an incremental
+/// user needs to expose `keyed_hash` or `derive_key`, we can add that.
pub fn parent_cv(
left_child: &crate::Hash,
right_child: &crate::Hash,
@@ -60,6 +85,73 @@ pub fn parent_cv(
}
}
+/// A complete copy of the internal state of a `Hasher`, for serialization
+/// purposes. This struct can contain buffered input bytes, and if you use a
+/// secret key, a copy of that key. Is is very security sensitive.
+#[derive(Clone)]
+#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))]
+pub struct ExportedHasher {
+ pub key: [u8; 32],
+ pub cv_stack: ArrayVec<[[u8; 32]; crate::MAX_DEPTH + 1]>,
+ pub chunk_cv: [u8; 32],
+ pub chunk_counter: u64,
+ pub chunk_buf: ArrayVec<[u8; 64]>,
+ pub blocks_compressed: u8,
+ pub flags: u8,
+}
+
+/// Copy all the internal state of a `Hasher` to construct an `ExportedHasher`.
+/// This is very security sensitive.
+pub fn export_hasher(hasher: &crate::Hasher) -> ExportedHasher {
+ let mut chunk_buf = ArrayVec::new();
+ chunk_buf
+ .try_extend_from_slice(&hasher.chunk_state.buf[..hasher.chunk_state.buf_len as usize])
+ .unwrap();
+ ExportedHasher {
+ key: le_bytes_from_words_32(&hasher.key),
+ cv_stack: hasher.cv_stack.clone(),
+ chunk_cv: le_bytes_from_words_32(&hasher.chunk_state.cv),
+ chunk_counter: hasher.chunk_state.chunk_counter,
+ chunk_buf,
+ blocks_compressed: hasher.chunk_state.blocks_compressed,
+ flags: hasher.chunk_state.flags,
+ }
+}
+
+/// Reconstitute a `Hasher` from an `ExportedHasher`. The `ExportedHasher` must
+/// come from a call to `export_hasher`, and its contents must not be modified
+/// in any way. The input to this `unsafe` function is fully trusted. If a bug
+/// or an attacker corrupts the input, it might trigger UB. If it doesn't
+/// trigger UB today, this crate might be modified such that it triggers UB in
+/// the future.
+pub unsafe fn import_hasher(exported: &ExportedHasher) -> crate::Hasher {
+ // We don't assert everything here, but these are some essentials.
+ assert_eq!(
+ exported.chunk_counter.count_ones() as usize,
+ exported.cv_stack.len(),
+ "wrong number of entries in the CV stack",
+ );
+ let allowed_flags = crate::KEYED_HASH | crate::DERIVE_KEY_MATERIAL;
+ assert_eq!(0, exported.flags & !allowed_flags, "unexpected flags");
+
+ let mut chunk_buf = [0; crate::BLOCK_LEN];
+ chunk_buf[..exported.chunk_buf.len()].copy_from_slice(&exported.chunk_buf);
+ let chunk_state = crate::ChunkState {
+ cv: words_from_le_bytes_32(&exported.chunk_cv),
+ chunk_counter: exported.chunk_counter,
+ buf: chunk_buf,
+ buf_len: exported.chunk_buf.len() as u8,
+ blocks_compressed: exported.blocks_compressed,
+ flags: exported.flags,
+ platform: crate::platform::Platform::detect(),
+ };
+ crate::Hasher {
+ key: words_from_le_bytes_32(&exported.key),
+ chunk_state,
+ cv_stack: exported.cv_stack.clone(),
+ }
+}
+
#[cfg(test)]
mod test {
use super::*;
@@ -92,4 +184,63 @@ mod test {
let root = parent_cv(&parent, &chunk2_cv, true);
assert_eq!(hasher.finalize(), root);
}
+
+ #[test]
+ fn test_export_and_import_hasher() {
+ let mut input = [0; 23456]; // results in a non-zero block counter
+ crate::test::paint_test_input(&mut input);
+ let mut test_key = [0; 32];
+ test_key.copy_from_slice(&input[1000..1032]);
+ let mut hasher = crate::Hasher::new_keyed(&test_key);
+ hasher.update(&input);
+
+ let exported = export_hasher(&hasher);
+ let mut imported = unsafe { import_hasher(&exported) };
+
+ hasher.update(&input);
+ imported.update(&input);
+ assert_eq!(hasher.finalize(), imported.finalize());
+ }
+
+ #[test]
+ #[cfg(feature = "serde_derive")]
+ fn test_serialize_hasher() {
+ let mut input = [0; 23456]; // results in a non-zero block counter
+ crate::test::paint_test_input(&mut input);
+ let test_context = "BLAKE3 2020-05-06 12:21:23 test serde";
+ let mut hasher = crate::Hasher::new_derive_key(test_context);
+ hasher.update(&input);
+
+ let exported = export_hasher(&hasher);
+ let serialized = serde_json::to_string_pretty(&exported).unwrap();
+ let deserialized = serde_json::from_str(&serialized).unwrap();
+ let mut imported = unsafe { import_hasher(&deserialized) };
+
+ hasher.update(&input);
+ imported.update(&input);
+ assert_eq!(hasher.finalize(), imported.finalize());
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_bad_chunk_counter_panics() {
+ let mut exported = export_hasher(&crate::Hasher::new());
+ // The chunk_counter needs to correspond to the number of chaining
+ // values in the CV stack.
+ exported.chunk_counter += 1;
+ unsafe {
+ import_hasher(&exported);
+ }
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_bad_flags_panic() {
+ let mut exported = export_hasher(&crate::Hasher::new());
+ // It shouldn't be possible to set random bitflags.
+ exported.flags |= 1 << 7;
+ unsafe {
+ import_hasher(&exported);
+ }
+ }
}