From e164a1fc80c30d9446404a61b05fd995d7d88c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Kr=C3=BCger?= Date: Fri, 21 Jul 2023 13:46:05 +0200 Subject: [PATCH] feat: Switch from SHA3-256 to BLAKE3-256 (#306) Release-As: 0.1.23 * feat: Switch from SHA3-256 to BLAKE3-256 * Also use Blake3 by default in `BlockStore::create_cid` * Also use Blake3 in skip ratchet key derivation * Make use of `blake3::derive_key` algorithm * Un-expose temporal key bytes * Update domain separation string * Update prime hash fixture * Fix block naming consistency * Dedicated APIs for key structs & cleanup * Store a `base_name` in `ExternalFileContent` This ensures you can re-generate all block labels, even if you don't have access to the PrivateNodeHeader`, e.g. when you only have snapshot access. * Lint * Give tests more stack space * Fix wasm-wnfs * Make external file content encoding more spec-adhering * Add a hiding segment to `base_name` * Depend on released skip ratchet crate --- .github/workflows/checks.yaml | 4 +- wnfs-bench/Cargo.toml | 2 +- wnfs-bench/nameaccumulator.rs | 7 +- wnfs-common/src/blockstore.rs | 4 +- wnfs-hamt/Cargo.toml | 2 +- wnfs-hamt/src/diff.rs | 18 ++--- wnfs-hamt/src/hamt.rs | 3 +- wnfs-hamt/src/hash.rs | 12 +--- wnfs-hamt/src/node.rs | 11 +-- wnfs-hamt/src/pointer.rs | 6 +- wnfs-nameaccumulator/Cargo.toml | 2 +- wnfs-nameaccumulator/src/fns.rs | 71 +++++++++++++------ wnfs-nameaccumulator/src/name.rs | 45 ++++++------ wnfs-wasm/src/fs/private/access_key.rs | 28 +++++++- wnfs/Cargo.toml | 4 +- wnfs/examples/mnemonic_based.rs | 6 +- wnfs/src/private/file.rs | 88 +++++++++++++++--------- wnfs/src/private/forest/hamt.rs | 13 ++-- wnfs/src/private/forest/traits.rs | 4 +- wnfs/src/private/keys/access.rs | 7 ++ wnfs/src/private/keys/privateref.rs | 6 +- wnfs/src/private/node/header.rs | 11 ++- wnfs/src/private/node/keys.rs | 94 +++++++++++++++----------- wnfs/src/private/node/node.rs | 4 +- wnfs/src/private/previous.rs | 2 +- wnfs/src/private/share.rs | 12 ++-- 26 files changed, 276 insertions(+), 190 deletions(-) diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 510833f4..c616a033 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -83,8 +83,8 @@ jobs: - name: Cache Project uses: Swatinem/rust-cache@v2 - - name: Run Tests - run: cargo test --all-features + - name: Run Tests # 3MB of stack space + run: RUST_MIN_STACK=3000000 cargo test --all-features wasm-js-tests: strategy: diff --git a/wnfs-bench/Cargo.toml b/wnfs-bench/Cargo.toml index f6dd28bd..d0e555e9 100644 --- a/wnfs-bench/Cargo.toml +++ b/wnfs-bench/Cargo.toml @@ -8,11 +8,11 @@ license = "Apache-2.0" [dev-dependencies] async-std = { version = "1.11", features = ["attributes"] } +blake3 = { version = "1.4", features = ["traits-preview"] } chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } criterion = { version = "0.4", features = ["async_std"] } proptest = "1.1" rand = "0.8" -sha3 = "0.10" wnfs = { path = "../wnfs" } wnfs-common = { path = "../wnfs-common", features = ["test_utils"] } wnfs-hamt = { path = "../wnfs-hamt", features = ["test_utils"] } diff --git a/wnfs-bench/nameaccumulator.rs b/wnfs-bench/nameaccumulator.rs index b5dc9ddd..cfd11484 100644 --- a/wnfs-bench/nameaccumulator.rs +++ b/wnfs-bench/nameaccumulator.rs @@ -1,13 +1,12 @@ use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use rand::{thread_rng, Rng}; -use sha3::Digest; use wnfs_nameaccumulator::{AccumulatorSetup, NameAccumulator, NameSegment}; fn name_segment_from_digest(c: &mut Criterion) { - c.bench_function("NameSegment::from_digest", |b| { + c.bench_function("NameSegment::new_hashed", |b| { b.iter_batched( - || sha3::Sha3_256::new().chain_update(thread_rng().gen::<[u8; 32]>()), - NameSegment::from_digest, + || thread_rng().gen::<[u8; 32]>(), + |sth| NameSegment::new_hashed("wnfs benchmarks", sth), BatchSize::SmallInput, ); }); diff --git a/wnfs-common/src/blockstore.rs b/wnfs-common/src/blockstore.rs index c40cb635..9e1c3249 100644 --- a/wnfs-common/src/blockstore.rs +++ b/wnfs-common/src/blockstore.rs @@ -72,8 +72,8 @@ pub trait BlockStore: Sized { bail!(BlockStoreError::MaximumBlockSizeExceeded(bytes.len())) } - // Compute the SHA256 hash of the bytes - let hash = Code::Sha2_256.digest(bytes); + // Compute the Blake3 hash of the bytes + let hash = Code::Blake3_256.digest(bytes); // Represent the hash as a V1 CID let cid = Cid::new(Version::V1, codec, hash)?; diff --git a/wnfs-hamt/Cargo.toml b/wnfs-hamt/Cargo.toml index 13bc86d5..95fd6d00 100644 --- a/wnfs-hamt/Cargo.toml +++ b/wnfs-hamt/Cargo.toml @@ -22,6 +22,7 @@ async-once-cell = "0.4" async-recursion = "1.0" async-trait = "0.1" bitvec = { version = "1.0", features = ["serde"] } +blake3 = { version = "1.4", features = ["traits-preview"] } chrono = { version = "0.4.23", default-features = false, features = ["clock", "std"] } either = "1.8" futures = "0.3" @@ -33,7 +34,6 @@ proptest = { version = "1.1", optional = true } rand_core = "0.6" semver = { version = "1.0", features = ["serde"] } serde = { version = "1.0", features = ["rc"] } -sha3 = "0.10" thiserror = "1.0" wnfs-common = { path = "../wnfs-common", version = "0.1.22" } diff --git a/wnfs-hamt/src/diff.rs b/wnfs-hamt/src/diff.rs index e3d64673..51971bfe 100644 --- a/wnfs-hamt/src/diff.rs +++ b/wnfs-hamt/src/diff.rs @@ -11,7 +11,7 @@ use wnfs_common::{BlockStore, Link}; //-------------------------------------------------------------------------------------------------- /// This type represents the different kinds of changes to a node. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum ChangeType { Add, Remove, @@ -19,7 +19,7 @@ pub enum ChangeType { } /// Represents a change to some key-value pair of a HAMT node. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct KeyValueChange { pub r#type: ChangeType, pub key: K, @@ -300,7 +300,7 @@ where mod tests { use super::{ChangeType::*, *}; use helper::*; - use std::rc::Rc; + use std::{collections::BTreeSet, rc::Rc}; use wnfs_common::MemoryBlockStore; mod helper { @@ -358,8 +358,8 @@ mod tests { .unwrap(); assert_eq!( - changes, - vec![ + changes.into_iter().collect::>(), + BTreeSet::from([ KeyValueChange { r#type: Add, key: [2, 0, 0, 0,], @@ -372,7 +372,7 @@ mod tests { value1: Some(String::from("1")), value2: None, }, - ] + ]) ); let changes = diff( @@ -384,8 +384,8 @@ mod tests { .unwrap(); assert_eq!( - changes, - vec![ + changes.into_iter().collect::>(), + BTreeSet::from([ KeyValueChange { r#type: Remove, key: [2, 0, 0, 0,], @@ -398,7 +398,7 @@ mod tests { value1: Some(String::from("1")), value2: None, }, - ] + ]) ); } diff --git a/wnfs-hamt/src/hamt.rs b/wnfs-hamt/src/hamt.rs index 69b144e1..c3e5ff2d 100644 --- a/wnfs-hamt/src/hamt.rs +++ b/wnfs-hamt/src/hamt.rs @@ -9,7 +9,6 @@ use serde::{ ser::Error as SerError, Deserialize, Deserializer, Serialize, Serializer, }; -use sha3::Sha3_256; use std::{collections::BTreeMap, hash::Hash, rc::Rc, str::FromStr}; use wnfs_common::{AsyncSerialize, BlockStore, Link}; @@ -31,7 +30,7 @@ use wnfs_common::{AsyncSerialize, BlockStore, Link}; /// println!("HAMT: {:?}", hamt); /// ``` #[derive(Debug, Clone)] -pub struct Hamt +pub struct Hamt where H: Hasher, { diff --git a/wnfs-hamt/src/hash.rs b/wnfs-hamt/src/hash.rs index c93a663f..9898cec8 100644 --- a/wnfs-hamt/src/hash.rs +++ b/wnfs-hamt/src/hash.rs @@ -1,6 +1,5 @@ use crate::error::HamtError; use anyhow::{bail, Result}; -use sha3::{Digest, Sha3_256}; use std::fmt::Debug; use wnfs_common::{utils, HashOutput, HASH_BYTE_SIZE}; @@ -22,7 +21,6 @@ pub const MAX_HASH_NIBBLE_LENGTH: usize = HASH_BYTE_SIZE * 2; /// # Examples /// /// ``` -/// use sha3::{Digest, Sha3_256}; /// use wnfs_hamt::Hasher; /// use wnfs_common::HashOutput; /// @@ -30,9 +28,7 @@ pub const MAX_HASH_NIBBLE_LENGTH: usize = HASH_BYTE_SIZE * 2; /// /// impl Hasher for MyHasher { /// fn hash>(data: &D) -> HashOutput { -/// let mut hasher = Sha3_256::new(); -/// hasher.update(data.as_ref()); -/// hasher.finalize().into() +/// blake3::hash(data.as_ref()).into() /// } /// } /// ``` @@ -152,11 +148,9 @@ impl Debug for HashNibbles<'_> { } } -impl Hasher for Sha3_256 { +impl Hasher for blake3::Hasher { fn hash>(data: &D) -> HashOutput { - let mut hasher = Self::default(); - hasher.update(data.as_ref()); - hasher.finalize().into() + blake3::hash(data.as_ref()).into() } } diff --git a/wnfs-hamt/src/node.rs b/wnfs-hamt/src/node.rs index fdfcc3df..3e8f2e12 100644 --- a/wnfs-hamt/src/node.rs +++ b/wnfs-hamt/src/node.rs @@ -19,7 +19,6 @@ use serde::{ ser::Error as SerError, Deserializer, Serialize, Serializer, }; -use sha3::Sha3_256; use std::{ collections::HashMap, fmt::{self, Debug, Formatter}, @@ -50,7 +49,7 @@ pub type BitMaskType = [u8; HAMT_BITMASK_BYTE_SIZE]; /// /// assert!(node.is_empty()); /// ``` -pub struct Node +pub struct Node where H: Hasher, { @@ -223,7 +222,6 @@ where /// /// ``` /// use std::rc::Rc; - /// use sha3::Sha3_256; /// use wnfs_hamt::{Node, Hasher}; /// use wnfs_common::MemoryBlockStore; /// @@ -234,7 +232,7 @@ where /// /// node.set("key".into(), 42, store).await.unwrap(); /// - /// let key_hash = &Sha3_256::hash(&String::from("key")); + /// let key_hash = &blake3::Hasher::hash(&String::from("key")); /// assert_eq!(node.get_by_hash(key_hash, store).await.unwrap(), Some(&42)); /// } /// ``` @@ -262,7 +260,6 @@ where /// /// ``` /// use std::rc::Rc; - /// use sha3::Sha3_256; /// use wnfs_hamt::{Node, Hasher, Pair}; /// use wnfs_common::MemoryBlockStore; /// @@ -274,7 +271,7 @@ where /// node.set("key".into(), 42, store).await.unwrap(); /// assert_eq!(node.get(&String::from("key"), store).await.unwrap(), Some(&42)); /// - /// let key_hash = &Sha3_256::hash(&String::from("key")); + /// let key_hash = &blake3::Hasher::hash(&String::from("key")); /// let value = node.remove_by_hash(key_hash, store).await.unwrap(); /// /// assert_eq!(value, Some(Pair::new("key".into(), 42))); @@ -613,7 +610,6 @@ where /// /// ``` /// use std::rc::Rc; - /// use sha3::Sha3_256; /// use wnfs_hamt::{Node, HashPrefix, Hasher}; /// use wnfs_common::{MemoryBlockStore, utils}; /// @@ -694,7 +690,6 @@ where /// /// ``` /// use std::rc::Rc; - /// use sha3::Sha3_256; /// use wnfs_hamt::{Node, Hasher}; /// use wnfs_common::MemoryBlockStore; /// diff --git a/wnfs-hamt/src/pointer.rs b/wnfs-hamt/src/pointer.rs index 2507707e..2f43631f 100644 --- a/wnfs-hamt/src/pointer.rs +++ b/wnfs-hamt/src/pointer.rs @@ -248,13 +248,12 @@ where #[cfg(test)] mod tests { use super::*; - use sha3::Sha3_256; use wnfs_common::{dagcbor, MemoryBlockStore}; #[async_std::test] async fn pointer_can_encode_decode_as_cbor() { let store = &MemoryBlockStore::default(); - let pointer: Pointer = Pointer::Values(vec![ + let pointer: Pointer = Pointer::Values(vec![ Pair { key: "James".into(), value: 4500, @@ -267,7 +266,8 @@ mod tests { let encoded_pointer = dagcbor::async_encode(&pointer, store).await.unwrap(); let decoded_pointer = - dagcbor::decode::>(encoded_pointer.as_ref()).unwrap(); + dagcbor::decode::>(encoded_pointer.as_ref()) + .unwrap(); assert_eq!(pointer, decoded_pointer); } diff --git a/wnfs-nameaccumulator/Cargo.toml b/wnfs-nameaccumulator/Cargo.toml index 39f110a3..00139dac 100644 --- a/wnfs-nameaccumulator/Cargo.toml +++ b/wnfs-nameaccumulator/Cargo.toml @@ -18,6 +18,7 @@ authors = ["The Fission Authors"] [dependencies] anyhow = "1.0" +blake3 = { version = "1.4", features = ["traits-preview"] } libipld = { version = "0.16", features = ["dag-cbor", "derive", "serde-codec"] } num-bigint-dig = { version = "0.8.2", features = ["prime", "zeroize"] } num-integer = "0.1.45" @@ -26,7 +27,6 @@ once_cell = "1.0" rand_core = "0.6" serde = { version = "1.0", features = ["rc"] } serde_bytes = "0.11.9" -sha3 = "0.10" thiserror = "1.0" zeroize = "1.6" diff --git a/wnfs-nameaccumulator/src/fns.rs b/wnfs-nameaccumulator/src/fns.rs index 4c8aabc6..f4d82901 100644 --- a/wnfs-nameaccumulator/src/fns.rs +++ b/wnfs-nameaccumulator/src/fns.rs @@ -1,6 +1,9 @@ +use blake3::traits::digest::{ExtendableOutput, ExtendableOutputReset}; use num_bigint_dig::{prime::probably_prime, BigUint}; use num_traits::One; -use sha3::Digest; + +#[cfg(test)] +const TEST_DSI: &str = "rs-wnfs tests"; /// Computes the function "MultiExp" from the paper /// "Batching Techniques for Accumulators with Applications to IOPs and Stateless Blockchains" @@ -41,15 +44,22 @@ pub(crate) fn nlogn_product(factors: &[A], f: fn(&A) -> &BigUint) -> BigUint /// /// The output includes both the prime and a 32-bit counter /// that helps verifying the prime digest. -pub(crate) fn prime_digest(hasher: impl Digest + Clone, bytes: usize) -> (BigUint, u32) { +pub(crate) fn blake3_prime_digest( + domain_separation_info: &str, + bytes: impl AsRef<[u8]>, + hash_len: usize, +) -> (BigUint, u32) { let mut counter: u32 = 0; + let mut hasher = blake3::Hasher::new_derive_key(domain_separation_info); + let mut hash = vec![0u8; hash_len]; loop { - let hash = hasher - .clone() - .chain_update(counter.to_be_bytes()) - .finalize(); + // We reuse the same `Hasher` struct between iterations to minimize + // stack usage. Each `Hasher` allocation is ~2kB for Blake3. + hasher.update(bytes.as_ref()); + hasher.update(&counter.to_le_bytes()); + hasher.finalize_xof_reset_into(&mut hash); - let mut candidate = BigUint::from_bytes_be(&hash[..bytes]); + let mut candidate = BigUint::from_bytes_le(&hash); candidate |= BigUint::one(); @@ -64,14 +74,19 @@ pub(crate) fn prime_digest(hasher: impl Digest + Clone, bytes: usize) -> (BigUin /// Finalizes a digest fast, if it has been computed before given the counter from /// a previous invocation of `prime_digest`. /// This will make sure that the returned digest is prime. -pub(crate) fn prime_digest_fast( - hasher: impl Digest, - bytes: usize, +pub(crate) fn blake3_prime_digest_fast( + domain_separation_info: &str, + bytes: impl AsRef<[u8]>, + hash_len: usize, counter: u32, ) -> Option { - let hash = hasher.chain_update(counter.to_be_bytes()).finalize(); + let mut hash = vec![0u8; hash_len]; + let mut hasher = blake3::Hasher::new_derive_key(domain_separation_info); + hasher.update(bytes.as_ref()); + hasher.update(&counter.to_le_bytes()); + hasher.finalize_xof_into(&mut hash); - let mut to_verify = BigUint::from_bytes_be(&hash[..bytes]); + let mut to_verify = BigUint::from_bytes_le(&hash); to_verify |= BigUint::one(); if !probably_prime(&to_verify, 20) { @@ -81,10 +96,27 @@ pub(crate) fn prime_digest_fast( } } +#[cfg(test)] +mod tests { + use super::{blake3_prime_digest, TEST_DSI}; + + /// This test makes sure we don't accidentally (only intentionally) + /// change hash outputs between versions. + #[test] + fn test_fixture_prime_hash() { + let (output, counter) = blake3_prime_digest(TEST_DSI, "Hello, World!", 16); + assert_eq!( + (output.to_str_radix(16), counter), + ("9ef50db608f1e61acedaf2fe6ad982ed".into(), 6) + ); + } +} + #[cfg(test)] mod proptests { - use super::nlogn_product; - use crate::fns::{multi_exp, prime_digest, prime_digest_fast}; + use crate::fns::{ + blake3_prime_digest, blake3_prime_digest_fast, multi_exp, nlogn_product, TEST_DSI, + }; use num_bigint_dig::{prime::probably_prime, BigUint, RandPrime}; use num_traits::One; use proptest::{ @@ -92,17 +124,16 @@ mod proptests { }; use rand_chacha::ChaCha12Rng; use rand_core::SeedableRng; - use sha3::Digest; use test_strategy::proptest; #[proptest(cases = 1000)] fn test_prime_digest(#[strategy(vec(any::(), 0..100))] bytes: Vec) { - let mut hasher = sha3::Sha3_256::new(); - hasher.update(bytes); - - let (prime_hash, inc) = prime_digest(hasher.clone(), 16); + let (prime_hash, inc) = blake3_prime_digest(TEST_DSI, &bytes, 16); prop_assert!(probably_prime(&prime_hash, 20)); - prop_assert_eq!(prime_digest_fast(hasher, 16, inc), Some(prime_hash)); + prop_assert_eq!( + blake3_prime_digest_fast(TEST_DSI, &bytes, 16, inc), + Some(prime_hash) + ); } #[proptest(cases = 100)] diff --git a/wnfs-nameaccumulator/src/name.rs b/wnfs-nameaccumulator/src/name.rs index 0c59e8ad..c8aa820e 100644 --- a/wnfs-nameaccumulator/src/name.rs +++ b/wnfs-nameaccumulator/src/name.rs @@ -1,6 +1,6 @@ use crate::{ error::VerificationError, - fns::{multi_exp, nlogn_product, prime_digest, prime_digest_fast}, + fns::{blake3_prime_digest, blake3_prime_digest_fast, multi_exp, nlogn_product}, uint256_serde_be::to_bytes_helper, }; use anyhow::Result; @@ -10,10 +10,11 @@ use num_traits::One; use once_cell::sync::OnceCell; use rand_core::CryptoRngCore; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use sha3::{Digest, Sha3_256}; use std::{hash::Hash, str::FromStr}; use zeroize::Zeroize; +const L_HASH_DSI: &str = "wnfs/PoKE*/l 128-bit hash derivation"; + //-------------------------------------------------------------------------------------------------- // Type Definitions //-------------------------------------------------------------------------------------------------- @@ -227,8 +228,8 @@ impl NameAccumulator { let witness = self.state.clone(); self.state = self.state.modpow(&product, &setup.modulus); - let hasher = poke_fiat_shamir_l_hash(&setup.modulus, &witness, &self.state); - let (l, l_hash_inc) = prime_digest(hasher, 16); + let data = poke_fiat_shamir_l_hash_data(&setup.modulus, &witness, &self.state); + let (l, l_hash_inc) = blake3_prime_digest(L_HASH_DSI, data, 16); let (q, r) = product.div_mod_floor(&l); @@ -277,12 +278,17 @@ impl NameAccumulator { } } -fn poke_fiat_shamir_l_hash(modulus: &BigUint, base: &BigUint, commitment: &BigUint) -> Sha3_256 { - let mut hasher = sha3::Sha3_256::new(); - hasher.update(to_bytes_helper::<256>(modulus)); - hasher.update(to_bytes_helper::<256>(base)); - hasher.update(to_bytes_helper::<256>(commitment)); - hasher +fn poke_fiat_shamir_l_hash_data( + modulus: &BigUint, + base: &BigUint, + commitment: &BigUint, +) -> impl AsRef<[u8]> { + [ + to_bytes_helper::<256>(modulus), + to_bytes_helper::<256>(base), + to_bytes_helper::<256>(commitment), + ] + .concat() } impl AccumulatorSetup { @@ -345,15 +351,9 @@ impl NameSegment { Self(rng.gen_prime(256)) } - /// Derive a name segment by finishing a hasher state - /// (which is repeatedly re-hashed with a counter to find a prime). - pub fn from_digest(digest: impl Digest + Clone) -> Self { - Self(prime_digest(digest, 32).0) - } - - /// Derive a name segment from a seed secret - pub fn from_seed(seed: impl AsRef<[u8]>) -> Self { - Self::from_digest(Sha3_256::new().chain_update(seed)) + /// Derive a name segment as the hash from some data + pub fn new_hashed(domain_separation_info: &str, data: impl AsRef<[u8]>) -> Self { + Self(blake3_prime_digest(domain_separation_info, data, 32).0) } } @@ -396,8 +396,9 @@ impl<'a> BatchedProofVerification<'a> { commitment: &NameAccumulator, proof_part: &UnbatchableProofPart, ) -> Result<()> { - let hasher = poke_fiat_shamir_l_hash(&self.setup.modulus, &base.state, &commitment.state); - let l = prime_digest_fast(hasher, 16, proof_part.l_hash_inc) + let hasher = + poke_fiat_shamir_l_hash_data(&self.setup.modulus, &base.state, &commitment.state); + let l = blake3_prime_digest_fast(L_HASH_DSI, hasher, 16, proof_part.l_hash_inc) .ok_or(VerificationError::LHashNonPrime)?; if proof_part.r >= l { @@ -532,7 +533,7 @@ impl Ord for NameAccumulator { impl PartialOrd for NameAccumulator { fn partial_cmp(&self, other: &Self) -> Option { - self.state.partial_cmp(&other.state) + Some(self.cmp(other)) } } diff --git a/wnfs-wasm/src/fs/private/access_key.rs b/wnfs-wasm/src/fs/private/access_key.rs index b86e1a66..3a57e1f3 100644 --- a/wnfs-wasm/src/fs/private/access_key.rs +++ b/wnfs-wasm/src/fs/private/access_key.rs @@ -14,26 +14,50 @@ pub struct AccessKey(pub(crate) WnfsAccessKey); #[wasm_bindgen] impl AccessKey { + /// Return the label in the forest, used for + /// accessing the ciphertext that can be decrypted with + /// this access key. #[wasm_bindgen(js_name = "getLabel")] pub fn get_label(&self) -> Vec { self.0.get_label().to_vec() } + /// Returns the temporal key or null, in case this + /// access key only gives access to the shapshot level. #[wasm_bindgen(js_name = "getTemporalKey")] - pub fn get_temporal_key(&self) -> Vec { - self.0.get_temporal_key().unwrap().0.to_vec() + pub fn get_temporal_key(&self) -> Option> { + self.0 + .get_temporal_key() + .ok() + .map(|k| k.as_bytes().to_vec()) } + /// Returns the snapshot key. + /// May derive the key on-the-fly in case this + /// AccessKey also gives access to the temporal access level. + #[wasm_bindgen(js_name = "getSnapshotKey")] + pub fn get_snapshot_key(&self) -> Vec { + self.0.get_snapshot_key().as_bytes().to_vec() + } + + /// Return the CID of what this access key decrypts. + /// This is mainly used for disambiguation, in case the + /// label the AccessKey links to has multiple conflicting writes. #[wasm_bindgen(js_name = "getContentCid")] pub fn get_content_cid(&self) -> Vec { self.0.get_content_cid().to_bytes() } + /// Serialize this AccessKey into bytes. + /// This will contain secret key material! + /// Make sure to keep safe or encrypt + /// (e.g. using the WebCrypto and asymmetrically encrypting these bytes). #[wasm_bindgen(js_name = "toBytes")] pub fn into_bytes(&self) -> Vec { Vec::::from(&self.0) } + /// Deserialize an AccessKey previously generated from `into_bytes`. #[wasm_bindgen(js_name = "fromBytes")] pub fn from_bytes(bytes: &[u8]) -> Self { Self(WnfsAccessKey::from(bytes).into()) diff --git a/wnfs/Cargo.toml b/wnfs/Cargo.toml index 75942256..0c60d04c 100644 --- a/wnfs/Cargo.toml +++ b/wnfs/Cargo.toml @@ -23,6 +23,7 @@ async-once-cell = "0.4" async-recursion = "1.0" async-stream = "0.3" async-trait = "0.1" +blake3 = { version = "1.4", features = ["traits-preview"] } bytes = "1.4.0" chacha20poly1305 = "0.10" chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } @@ -35,8 +36,7 @@ rand_core = "0.6" semver = { version = "1.0", features = ["serde"] } serde = { version = "1.0", features = ["rc"] } serde_ipld_dagcbor = "0.3.0" -sha3 = "0.10" -skip_ratchet = { version = "0.2.1", features = ["serde"] } +skip_ratchet = { version = "0.3.0", features = ["serde"] } thiserror = "1.0" wnfs-common = { path = "../wnfs-common", version = "0.1.22" } wnfs-hamt = { path = "../wnfs-hamt", version = "0.1.22" } diff --git a/wnfs/examples/mnemonic_based.rs b/wnfs/examples/mnemonic_based.rs index ef32a3a7..0429fe82 100644 --- a/wnfs/examples/mnemonic_based.rs +++ b/wnfs/examples/mnemonic_based.rs @@ -14,7 +14,7 @@ use wnfs::{ share::{recipient, sharer}, AccessKey, ExchangeKey, PrivateDirectory, PrivateKey, PrivateNode, PUBLIC_KEY_EXPONENT, }, - public::{PublicDirectory, PublicLink, PublicNode}, + public::{PublicDirectory, PublicLink}, }; use wnfs_common::{BlockStore, MemoryBlockStore, CODEC_RAW}; @@ -106,7 +106,7 @@ async fn setup_seeded_keypair_access( // The user identity's root DID. In practice this would be e.g. an ed25519 key used // for e.g. UCANs or key usually used for authenticating writes. - let root_did = "did:key:zExample".into(); + let root_did = "did:key:zExample"; let counter = recipient::find_latest_share_counter( 0, @@ -142,7 +142,7 @@ async fn regain_access_from_mnemonic( // Re-derive the same private key from the seed phrase let seed = Seed::new(&mnemonic, /* optional password */ ""); let exchange_keypair = SeededExchangeKey::from_bip39_seed(seed)?; - let root_did = "did:key:zExample".into(); + let root_did = "did:key:zExample"; // Re-load private node from forest let counter = recipient::find_latest_share_counter( diff --git a/wnfs/src/private/file.rs b/wnfs/src/private/file.rs index f4d4f0f7..d805b2dd 100644 --- a/wnfs/src/private/file.rs +++ b/wnfs/src/private/file.rs @@ -1,7 +1,7 @@ use super::{ encrypted::Encrypted, forest::traits::PrivateForest, PrivateFileContentSerializable, PrivateNode, PrivateNodeContentSerializable, PrivateNodeHeader, PrivateRef, SnapshotKey, - TemporalKey, AUTHENTICATION_TAG_SIZE, NONCE_SIZE, + TemporalKey, AUTHENTICATION_TAG_SIZE, BLOCK_SEGMENT_DSI, HIDING_SEGMENT_DSI, NONCE_SIZE, }; use crate::{error::FsError, traits::Id, WNFS_VERSION}; use anyhow::{bail, Result}; @@ -12,18 +12,17 @@ use futures::{future, AsyncRead, Stream, StreamExt, TryStreamExt}; use libipld_core::cid::Cid; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; -use sha3::{Digest, Sha3_256}; use std::{collections::BTreeSet, iter, rc::Rc}; use wnfs_common::{utils, BlockStore, Metadata, CODEC_RAW, MAX_BLOCK_SIZE}; -use wnfs_nameaccumulator::{AccumulatorSetup, Name, NameSegment}; +use wnfs_nameaccumulator::{AccumulatorSetup, Name, NameAccumulator, NameSegment}; //-------------------------------------------------------------------------------------------------- // Constants //-------------------------------------------------------------------------------------------------- -/// The maximum block size is 2 ^ 18 but the first 12 bytes are reserved for the cipher text's initialization vector. +/// The maximum block size is 2 ^ 18 but the first 24 bytes are reserved for the cipher text's initialization vector. /// The ciphertext then also contains a 16 byte authentication tag. -/// This leaves a maximum of (2 ^ 18) - 12 - 16 = 262,116 bytes for the actual data. +/// This leaves a maximum of (2 ^ 18) - 24 - 16 = 262,104 bytes for the actual data. /// /// More on that [here][priv-file]. /// @@ -87,12 +86,19 @@ pub(crate) struct PrivateFileContent { /// It is stored inline or stored in blocks. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub(crate) enum FileContent { - Inline { - data: Vec, - }, + #[serde(rename = "inline")] + Inline { data: Vec }, + #[serde(rename = "external")] External { key: SnapshotKey, + + #[serde(rename = "baseName")] + base_name: NameAccumulator, + + #[serde(rename = "blockCount")] block_count: usize, + + #[serde(rename = "blockContentSize")] block_content_size: usize, }, } @@ -177,7 +183,7 @@ impl PrivateFile { rng: &mut impl CryptoRngCore, ) -> Result { let header = PrivateNodeHeader::new(parent_name, rng); - let content = Self::prepare_content(&header.name, content, forest, store, rng).await?; + let content = Self::prepare_content(header.get_name(), content, forest, store, rng).await?; Ok(Self { header, @@ -241,7 +247,7 @@ impl PrivateFile { ) -> Result { let header = PrivateNodeHeader::new(parent_name, rng); let content = - Self::prepare_content_streaming(&header.name, content, forest, store, rng).await?; + Self::prepare_content_streaming(header.get_name(), content, forest, store, rng).await?; Ok(Self { header, @@ -317,10 +323,11 @@ impl PrivateFile { FileContent::External { key, block_count, + base_name, + // TODO(matheus23): take block_content_size into account .. } => { - let name = &self.header.name; - for name in Self::generate_shard_labels(key, index, *block_count, name) { + for name in Self::generate_shard_labels(key, index, *block_count, &Name::new(base_name.clone(), [])) { let bytes = Self::decrypt_block(key, &name, forest, store).await?; yield bytes } @@ -415,7 +422,11 @@ impl PrivateFile { forest: &impl PrivateForest, store: &impl BlockStore, ) -> Result> { - let mut content = Vec::with_capacity(self.get_content_size_upper_bound()); + // We're not using Vec::with_capacity here because + // a call to get_content instead of stream_content seems to + // indicate that the content is small enough to fit into + // memory. So let's keep allocations low. + let mut content = Vec::new(); self.stream_content(0, forest, store) .try_for_each(|chunk| { content.extend_from_slice(&chunk); @@ -436,7 +447,8 @@ impl PrivateFile { ) -> Result<()> { self.content.metadata = Metadata::new(time); self.content.content = - Self::prepare_content_streaming(&self.header.name, content, forest, store, rng).await?; + Self::prepare_content_streaming(self.header.get_name(), content, forest, store, rng) + .await?; Ok(()) } @@ -449,11 +461,11 @@ impl PrivateFile { rng: &mut impl CryptoRngCore, ) -> Result { // TODO(appcypher): Use a better heuristic to determine when to use external storage. - let key = SnapshotKey::from(utils::get_random_bytes(rng)); + let (key, base_name) = Self::prepare_key_and_base_name(file_name, rng); let block_count = (content.len() as f64 / MAX_BLOCK_CONTENT_SIZE as f64).ceil() as usize; for (index, name) in - Self::generate_shard_labels(&key, 0, block_count, file_name).enumerate() + Self::generate_shard_labels(&key, 0, block_count, &base_name).enumerate() { let start = index * MAX_BLOCK_CONTENT_SIZE; let end = content.len().min((index + 1) * MAX_BLOCK_CONTENT_SIZE); @@ -469,6 +481,9 @@ impl PrivateFile { Ok(FileContent::External { key, + base_name: base_name + .as_accumulator(forest.get_accumulator_setup()) + .clone(), block_count, block_content_size: MAX_BLOCK_CONTENT_SIZE, }) @@ -485,8 +500,7 @@ impl PrivateFile { store: &impl BlockStore, rng: &mut impl CryptoRngCore, ) -> Result { - let key = SnapshotKey::from(utils::get_random_bytes(rng)); - let file_revision_name = Self::create_revision_name(file_name, &key); + let (key, base_name) = Self::prepare_key_and_base_name(file_name, rng); let mut block_index = 0; @@ -510,7 +524,7 @@ impl PrivateFile { let content_cid = store.put_block(current_block, CODEC_RAW).await?; - let name = Self::create_block_label(&key, block_index, &file_revision_name); + let name = Self::create_block_name(&key, block_index, &base_name); forest .put_encrypted(&name, Some(content_cid), store) .await?; @@ -524,11 +538,25 @@ impl PrivateFile { Ok(FileContent::External { key, + base_name: base_name + .as_accumulator(forest.get_accumulator_setup()) + .clone(), block_count: block_index, block_content_size: MAX_BLOCK_CONTENT_SIZE, }) } + fn prepare_key_and_base_name( + file_name: &Name, + rng: &mut impl CryptoRngCore, + ) -> (SnapshotKey, Name) { + let key = SnapshotKey::new(rng); + let hiding_segment = NameSegment::new_hashed(HIDING_SEGMENT_DSI, key.as_bytes()); + let base_name = file_name.with_segments_added(Some(hiding_segment)); + + (key, base_name) + } + /// Gets the upper bound of a file content size. pub fn get_content_size_upper_bound(&self) -> usize { match &self.content.content { @@ -567,33 +595,27 @@ impl PrivateFile { key: &'a SnapshotKey, mut index: usize, block_count: usize, - file_block_name: &'a Name, + base_name: &'a Name, ) -> impl Iterator + 'a { - let file_revision_name = Self::create_revision_name(file_block_name, key); iter::from_fn(move || { if index >= block_count { return None; } - let label = Self::create_block_label(key, index, &file_revision_name); + let label = Self::create_block_name(key, index, base_name); index += 1; Some(label) }) } - fn create_revision_name(file_block_name: &Name, key: &SnapshotKey) -> Name { - let revision_segment = NameSegment::from_digest(Sha3_256::new().chain_update(key.0)); - file_block_name.with_segments_added(Some(revision_segment)) - } - /// Creates the label for a block of a file. - fn create_block_label(key: &SnapshotKey, index: usize, file_revision_name: &Name) -> Name { - let key_hash = Sha3_256::new() - .chain_update(key.0) - .chain_update(index.to_le_bytes()); - let elem = NameSegment::from_digest(key_hash); + fn create_block_name(key: &SnapshotKey, index: usize, base_name: &Name) -> Name { + let mut vec = Vec::with_capacity(40); + vec.extend(key.0); // 32 bytes + vec.extend((index as u64).to_le_bytes()); // 8 bytes + let block_segment = NameSegment::new_hashed(BLOCK_SEGMENT_DSI, vec); - file_revision_name.with_segments_added(Some(elem)) + base_name.with_segments_added(Some(block_segment)) } /// This should be called to prepare a node for modifications, diff --git a/wnfs/src/private/forest/hamt.rs b/wnfs/src/private/forest/hamt.rs index 946c8c29..73674c46 100644 --- a/wnfs/src/private/forest/hamt.rs +++ b/wnfs/src/private/forest/hamt.rs @@ -7,7 +7,6 @@ use rand_core::CryptoRngCore; use serde::{ de::Error as DeError, ser::Error as SerError, Deserialize, Deserializer, Serialize, Serializer, }; -use sha3::Sha3_256; use std::{collections::BTreeSet, rc::Rc}; use wnfs_common::{AsyncSerialize, BlockStore, HashOutput, Link}; use wnfs_hamt::{merge, Hamt, Hasher, KeyValueChange, Pair}; @@ -35,7 +34,7 @@ use wnfs_nameaccumulator::{AccumulatorSetup, Name, NameAccumulator}; /// ``` #[derive(Debug, Clone)] pub struct HamtForest { - hamt: Hamt, Sha3_256>, + hamt: Hamt, blake3::Hasher>, accumulator: AccumulatorSetup, } @@ -125,7 +124,7 @@ impl PrivateForest for HamtForest { async fn has(&self, name: &Name, store: &impl BlockStore) -> Result { self.has_by_hash( - &Sha3_256::hash(name.as_accumulator(&self.accumulator)), + &blake3::Hasher::hash(name.as_accumulator(&self.accumulator)), store, ) .await @@ -166,7 +165,7 @@ impl PrivateForest for HamtForest { name: &Name, store: &impl BlockStore, ) -> Result>> { - let name_hash = &Sha3_256::hash(name.as_accumulator(&self.accumulator)); + let name_hash = &blake3::Hasher::hash(name.as_accumulator(&self.accumulator)); self.get_encrypted_by_hash(name_hash, store).await } @@ -175,7 +174,7 @@ impl PrivateForest for HamtForest { name: &Name, store: &impl BlockStore, ) -> Result>>> { - let name_hash = &Sha3_256::hash(name.as_accumulator(&self.accumulator)); + let name_hash = &blake3::Hasher::hash(name.as_accumulator(&self.accumulator)); self.hamt.root.remove_by_hash(name_hash, store).await } } @@ -395,8 +394,8 @@ mod tests { let cid = Cid::default(); let name = forest.empty_name().with_segments_added([ - NameSegment::from_seed(b"one"), - NameSegment::from_seed(b"two"), + NameSegment::new_hashed("Testing", b"one"), + NameSegment::new_hashed("Testing", b"two"), ]); forest.put_encrypted(&name, [cid], store).await.unwrap(); diff --git a/wnfs/src/private/forest/traits.rs b/wnfs/src/private/forest/traits.rs index fc2c1f9d..0e8254c4 100644 --- a/wnfs/src/private/forest/traits.rs +++ b/wnfs/src/private/forest/traits.rs @@ -49,14 +49,12 @@ pub trait PrivateForest { /// use std::rc::Rc; /// use chrono::Utc; /// use rand::thread_rng; - /// use sha3::Sha3_256; /// use wnfs::{ /// private::{ - /// AccessKey, PrivateDirectory, PrivateNode, + /// PrivateDirectory, PrivateNode, /// forest::{hamt::HamtForest, traits::PrivateForest}, /// }, /// common::MemoryBlockStore, - /// hamt::Hasher, /// }; /// /// #[async_std::main] diff --git a/wnfs/src/private/keys/access.rs b/wnfs/src/private/keys/access.rs index 8b14a139..94b84d0f 100644 --- a/wnfs/src/private/keys/access.rs +++ b/wnfs/src/private/keys/access.rs @@ -61,6 +61,13 @@ impl AccessKey { Ok(&key.temporal_key) } + pub fn get_snapshot_key(&self) -> SnapshotKey { + match self { + Self::Temporal(t) => t.temporal_key.derive_snapshot_key(), + Self::Snapshot(s) => s.snapshot_key.clone(), + } + } + pub fn get_content_cid(&self) -> &Cid { match self { Self::Temporal(key) => &key.content_cid, diff --git a/wnfs/src/private/keys/privateref.rs b/wnfs/src/private/keys/privateref.rs index 461df742..af5f607c 100644 --- a/wnfs/src/private/keys/privateref.rs +++ b/wnfs/src/private/keys/privateref.rs @@ -17,7 +17,7 @@ use wnfs_common::HashOutput; /// It also includes required key material to decrypt/encrypt any future revisions of the node it points to. #[derive(Clone, PartialEq, Eq)] pub struct PrivateRef { - /// Sha3-256 hash of the revision name. Used as the label for identifying revisions of PrivateNodes in the PrivateForest. + /// Blake3 hash of the revision name. Used as the label for identifying revisions of PrivateNodes in the PrivateForest. pub revision_name_hash: HashOutput, /// Skip-ratchet-derived key. Gives read access to the revision pointed to and any newer revisions. pub temporal_key: TemporalKey, @@ -31,7 +31,7 @@ pub struct PrivateRef { /// revisions. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub(crate) struct RevisionRef { - /// Sha3-256 hash of the revision name. Used as the label for private nodes in the private forest. + /// Blake3 hash of the revision name. Used as the label for private nodes in the private forest. pub revision_name_hash: HashOutput, /// Skip-ratchet-derived key. Gives read access to the revision pointed to and any newer revisions. pub temporal_key: TemporalKey, @@ -89,7 +89,7 @@ impl PrivateRef { Ok(Self { revision_name_hash: private_ref.revision_name_hash, - temporal_key: temporal_key_raw.into(), + temporal_key: TemporalKey(temporal_key_raw), content_cid: private_ref.content_cid, }) } diff --git a/wnfs/src/private/node/header.rs b/wnfs/src/private/node/header.rs index 1e76415b..f17a6f97 100644 --- a/wnfs/src/private/node/header.rs +++ b/wnfs/src/private/node/header.rs @@ -1,9 +1,8 @@ -use super::{PrivateNodeHeaderSerializable, TemporalKey, REVISION_SEGMENT_DSS}; +use super::{PrivateNodeHeaderSerializable, TemporalKey, REVISION_SEGMENT_DSI}; use crate::{error::FsError, private::RevisionRef}; use anyhow::{bail, Result}; use libipld_core::cid::Cid; use rand_core::CryptoRngCore; -use sha3::Sha3_256; use skip_ratchet::Ratchet; use std::fmt::Debug; use wnfs_common::{BlockStore, CODEC_RAW}; @@ -80,7 +79,8 @@ impl PrivateNodeHeader { /// Derives the revision ref of the current header. pub(crate) fn derive_revision_ref(&self, setup: &AccumulatorSetup) -> RevisionRef { let temporal_key = self.derive_temporal_key(); - let revision_name_hash = Sha3_256::hash(self.get_revision_name().as_accumulator(setup)); + let revision_name_hash = + blake3::Hasher::hash(self.get_revision_name().as_accumulator(setup)); RevisionRef { revision_name_hash, @@ -112,12 +112,11 @@ impl PrivateNodeHeader { /// ``` #[inline] pub fn derive_temporal_key(&self) -> TemporalKey { - TemporalKey::from(&self.ratchet) + TemporalKey::new(&self.ratchet) } pub(crate) fn derive_revision_segment(&self) -> NameSegment { - let hasher = self.ratchet.derive_key(REVISION_SEGMENT_DSS); - NameSegment::from_digest(hasher) + NameSegment::new_hashed(REVISION_SEGMENT_DSI, self.ratchet.key_derivation_data()) } /// Gets the revision name for this node. diff --git a/wnfs/src/private/node/keys.rs b/wnfs/src/private/node/keys.rs index e4027b56..a92f97e8 100644 --- a/wnfs/src/private/node/keys.rs +++ b/wnfs/src/private/node/keys.rs @@ -1,25 +1,53 @@ use crate::error::CryptError; use aes_kw::KekAes256; use anyhow::{anyhow, Result}; +use blake3::traits::digest::Digest; use chacha20poly1305::{ aead::{Aead, AeadCore, KeyInit}, AeadInPlace, Tag, XChaCha20Poly1305, XNonce, }; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; -use sha3::{Digest, Sha3_256}; use skip_ratchet::Ratchet; use std::fmt::Debug; -use wnfs_hamt::Hasher; +use wnfs_common::utils; //-------------------------------------------------------------------------------------------------- // Constants //-------------------------------------------------------------------------------------------------- +/// The size of the nonce used when encrypting using snapshot keys. +/// The algorithm used is XChaCha20-Poly1305, i.e. the extended nonce variant, +/// so it's 196 bit. pub(crate) const NONCE_SIZE: usize = 24; +/// The size of the authentication tag used when encrypting using snapshot keys. +/// The algorithm used is XChaCha20-Poly1305, so it's 128 bit. pub(crate) const AUTHENTICATION_TAG_SIZE: usize = 16; +/// The general key size used in WNFS: 256-bit pub const KEY_BYTE_SIZE: usize = 32; +/// The revision segment derivation domain separation info +/// used for salting the hashing function when turning +/// node names into revisioned node names. +pub(crate) const REVISION_SEGMENT_DSI: &str = "wnfs/revision segment deriv from ratchet"; +/// The hiding segment derivation domain separation info +/// used for salting the hashing function when generating +/// the hiding segment from the external file content key, +/// which is added to a file's name to generate the external content base name. +pub(crate) const HIDING_SEGMENT_DSI: &str = "wnfs/hiding segment deriv from content key"; +/// The block segment derivation domain separation info +/// used for salting the hashing function when generating +/// the segments for each file's external content blocks. +pub(crate) const BLOCK_SEGMENT_DSI: &str = "wnfs/segment deriv for file block"; +/// The temporal key derivation domain seperation info +/// used for salting the hashing function when deriving +/// symmetric keys from ratchets. +pub(crate) const TEMPORAL_KEY_DSI: &str = "wnfs/temporal deriv from ratchet"; +/// The snapshot key derivation domain separation info +/// used for salting the hashing function when deriving +/// the snapshot key from the temporal key. +pub(crate) const SNAPSHOT_KEY_DSI: &str = "wnfs/snapshot key deriv from temporal"; + //-------------------------------------------------------------------------------------------------- // Type Definitions //-------------------------------------------------------------------------------------------------- @@ -29,7 +57,7 @@ pub const KEY_BYTE_SIZE: usize = 32; pub struct SnapshotKey( #[serde(serialize_with = "crate::utils::serialize_byte_slice32")] #[serde(deserialize_with = "crate::utils::deserialize_byte_slice32")] - pub [u8; KEY_BYTE_SIZE], + pub(crate) [u8; KEY_BYTE_SIZE], ); /// The key used to encrypt the header section of a node. @@ -37,31 +65,23 @@ pub struct SnapshotKey( pub struct TemporalKey( #[serde(serialize_with = "crate::utils::serialize_byte_slice32")] #[serde(deserialize_with = "crate::utils::deserialize_byte_slice32")] - pub [u8; KEY_BYTE_SIZE], + pub(crate) [u8; KEY_BYTE_SIZE], ); -//-------------------------------------------------------------------------------------------------- -// Constants -//-------------------------------------------------------------------------------------------------- - -/// The revision segment derivation domain separation string -/// used for salting the hashing function when turning -/// node names into revisioned node names. -pub(crate) const REVISION_SEGMENT_DSS: &str = "wnfs/segment deriv from temporal"; -/// The temporal key derivation domain seperation string -/// used for salting the hashing function when deriving -/// symmetric keys from ratchets. -pub(crate) const TEMPORAL_KEY_DSS: &str = "wnfs/temporal deriv from ratchet"; - //-------------------------------------------------------------------------------------------------- // Implementations //-------------------------------------------------------------------------------------------------- impl TemporalKey { + /// Derive a temporal key from the ratchet for use with WNFS. + pub fn new(ratchet: &Ratchet) -> Self { + Self(ratchet.derive_key(TEMPORAL_KEY_DSI).finalize().into()) + } + /// Turn this TemporalKey, which gives read access to the current revision and any future /// revisions into a SnapshotKey, which only gives read access to the current revision. pub fn derive_snapshot_key(&self) -> SnapshotKey { - SnapshotKey::from(Sha3_256::hash(&self.0)) + SnapshotKey(blake3::derive_key(SNAPSHOT_KEY_DSI, &self.0)) } /// Encrypt a cleartext with this temporal key. @@ -87,9 +107,19 @@ impl TemporalKey { .unwrap_with_padding_vec(ciphertext) .map_err(|e| CryptError::UnableToEncrypt(anyhow!(e)))?) } + + /// Return the temporal key's key material. + pub fn as_bytes(&self) -> &[u8; KEY_BYTE_SIZE] { + &self.0 + } } impl SnapshotKey { + /// Generate a random snapshot key from given randomness. + pub fn new(rng: &mut impl CryptoRngCore) -> Self { + Self(utils::get_random_bytes(rng)) + } + /// Encrypts the given plaintext using the key. /// /// # Examples @@ -100,7 +130,7 @@ impl SnapshotKey { /// use rand::thread_rng; /// /// let rng = &mut thread_rng(); - /// let key = SnapshotKey::from(utils::get_random_bytes(rng)); + /// let key = SnapshotKey::new(rng); /// /// let plaintext = b"Hello World!"; /// let ciphertext = key.encrypt(plaintext, rng).unwrap(); @@ -147,7 +177,7 @@ impl SnapshotKey { /// use rand::thread_rng; /// /// let rng = &mut thread_rng(); - /// let key = SnapshotKey::from(utils::get_random_bytes(rng)); + /// let key = SnapshotKey::new(rng); /// /// let plaintext = b"Hello World!"; /// let ciphertext = key.encrypt(plaintext, rng).unwrap(); @@ -182,24 +212,10 @@ impl SnapshotKey { .map_err(|e| CryptError::UnableToDecrypt(anyhow!(e)))?; Ok(()) } -} - -impl From<[u8; KEY_BYTE_SIZE]> for TemporalKey { - fn from(key: [u8; KEY_BYTE_SIZE]) -> Self { - Self(key) - } -} - -impl From<&Ratchet> for TemporalKey { - fn from(ratchet: &Ratchet) -> Self { - let key: [u8; KEY_BYTE_SIZE] = ratchet.derive_key(TEMPORAL_KEY_DSS).finalize().into(); - Self::from(key) - } -} -impl From<[u8; KEY_BYTE_SIZE]> for SnapshotKey { - fn from(key: [u8; KEY_BYTE_SIZE]) -> Self { - Self(key) + /// Return the snapshot key's key material. + pub fn as_bytes(&self) -> &[u8; KEY_BYTE_SIZE] { + &self.0 } } @@ -222,7 +238,7 @@ mod proptests { #[strategy(any::<[u8; KEY_BYTE_SIZE]>())] rng_seed: [u8; KEY_BYTE_SIZE], key_bytes: [u8; KEY_BYTE_SIZE], ) { - let key = SnapshotKey::from(key_bytes); + let key = SnapshotKey(key_bytes); let rng = &mut ChaCha12Rng::from_seed(rng_seed); let encrypted = key.encrypt(&data, rng).unwrap(); @@ -244,7 +260,7 @@ mod proptests { ) { let mut buffer = data.clone(); let nonce = XNonce::from_slice(&nonce); - let key = SnapshotKey::from(key_bytes); + let key = SnapshotKey(key_bytes); let tag = key.encrypt_in_place(nonce, &mut buffer).unwrap(); diff --git a/wnfs/src/private/node/node.rs b/wnfs/src/private/node/node.rs index 5e925154..ae82234a 100644 --- a/wnfs/src/private/node/node.rs +++ b/wnfs/src/private/node/node.rs @@ -14,7 +14,6 @@ use chrono::{DateTime, Utc}; use futures::StreamExt; use libipld_core::cid::Cid; use rand_core::CryptoRngCore; -use sha3::Sha3_256; use skip_ratchet::{JumpSize, RatchetSeeker}; use std::{cmp::Ordering, collections::BTreeSet, fmt::Debug, rc::Rc}; use wnfs_common::BlockStore; @@ -452,7 +451,8 @@ impl PrivateNode { current_header.ratchet = search.current().clone(); - let name_hash = Sha3_256::hash(¤t_header.get_revision_name().as_accumulator(setup)); + let name_hash = + blake3::Hasher::hash(¤t_header.get_revision_name().as_accumulator(setup)); Ok(forest .get_multivalue_by_hash( diff --git a/wnfs/src/private/previous.rs b/wnfs/src/private/previous.rs index 461ded86..88dfc657 100644 --- a/wnfs/src/private/previous.rs +++ b/wnfs/src/private/previous.rs @@ -140,7 +140,7 @@ impl PrivateNodeHistory { // That would need an additional API that allows 'selecting' one of the forks before moving on. // Then this function would derive the nth-previous ratchet by "peeking" ahead the current // self.ratchets iterator for n (the "# of revisions back" usize attached to the previous pointer) - let temporal_key = TemporalKey::from(previous_ratchet); + let temporal_key = TemporalKey::new(previous_ratchet); let Some((_, first_backpointer)) = self .previous .iter() diff --git a/wnfs/src/private/share.rs b/wnfs/src/private/share.rs index 94748d95..6309f88c 100644 --- a/wnfs/src/private/share.rs +++ b/wnfs/src/private/share.rs @@ -192,9 +192,9 @@ pub mod sharer { sharer_forest: &impl PrivateForest, ) -> Name { sharer_forest.empty_name().with_segments_added([ - NameSegment::from_seed(sharer_root_did.as_bytes()), - NameSegment::from_seed(recipient_exchange_key), - NameSegment::from_seed(share_count.to_le_bytes()), + NameSegment::new_hashed("Testing", sharer_root_did.as_bytes()), + NameSegment::new_hashed("Testing", recipient_exchange_key), + NameSegment::new_hashed("Testing", share_count.to_le_bytes()), ]) } } @@ -206,7 +206,6 @@ pub mod recipient { private::{forest::traits::PrivateForest, AccessKey, PrivateKey, PrivateNode}, }; use anyhow::Result; - use sha3::Sha3_256; use wnfs_common::BlockStore; use wnfs_hamt::Hasher; use wnfs_nameaccumulator::Name; @@ -255,7 +254,10 @@ pub mod recipient { let setup = sharer_forest.get_accumulator_setup(); // Get cid to encrypted payload from sharer's forest using share_label let access_key_cid = sharer_forest - .get_encrypted_by_hash(&Sha3_256::hash(&share_label.as_accumulator(setup)), store) + .get_encrypted_by_hash( + &blake3::Hasher::hash(&share_label.as_accumulator(setup)), + store, + ) .await? .ok_or(ShareError::AccessKeyNotFound)? .first()