diff --git a/wnfs-nameaccumulator/src/name.rs b/wnfs-nameaccumulator/src/name.rs index c8aa820e..f43e74e5 100644 --- a/wnfs-nameaccumulator/src/name.rs +++ b/wnfs-nameaccumulator/src/name.rs @@ -25,11 +25,10 @@ const L_HASH_DSI: &str = "wnfs/PoKE*/l 128-bit hash derivation"; /// However, these names are based on RSA accumulators to make it possible /// to prove a relationship between two names, e.g a file being contained in /// a sub-directory of a directory while leaking as little information as possible. -#[derive(Clone, Debug, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Name { relative_to: NameAccumulator, segments: Vec, - accumulated: OnceCell<(NameAccumulator, ElementsProof)>, } /// Represents a setup needed for RSA accumulator operation. @@ -52,7 +51,7 @@ pub struct NameAccumulator { /// A name accumluator segment. A name accumulator commits to a set of these. /// They are represented as 256-bit prime numbers. -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct NameSegment( /// Invariant: Must be a 256-bit prime BigUint, @@ -116,7 +115,6 @@ impl Name { Self { relative_to, segments: segments.into_iter().collect(), - accumulated: OnceCell::new(), } } @@ -129,7 +127,6 @@ impl Name { /// Remove the last name segment, if possible. pub fn up(&mut self) { self.segments.pop(); - self.accumulated = OnceCell::new(); } /// Return the parent name, if possible. @@ -149,7 +146,6 @@ impl Name { pub fn add_segments(&mut self, segments: impl IntoIterator) { self.segments.extend(segments); - self.accumulated = OnceCell::new(); } pub fn with_segments_added(&self, segments: impl IntoIterator) -> Self { @@ -163,20 +159,18 @@ impl Name { /// this name is relative to. /// /// This proof process is memoized. Running it twice won't duplicate work. - pub fn as_proven_accumulator( + pub fn into_proven_accumulator( &self, setup: &AccumulatorSetup, - ) -> &(NameAccumulator, ElementsProof) { - self.accumulated.get_or_init(|| { - let mut name = self.relative_to.clone(); - let proof = name.add(self.segments.iter(), setup); - (name, proof) - }) + ) -> (NameAccumulator, ElementsProof) { + let mut name = self.relative_to.clone(); + let proof = name.add(self.segments.iter(), setup); + (name, proof) } /// Return what name accumulator this name commits to. - pub fn as_accumulator(&self, setup: &AccumulatorSetup) -> &NameAccumulator { - &self.as_proven_accumulator(setup).0 + pub fn into_accumulator(&self, setup: &AccumulatorSetup) -> NameAccumulator { + self.into_proven_accumulator(setup).0 } } @@ -498,27 +492,6 @@ impl Serialize for UnbatchableProofPart { } } -impl PartialEq for Name { - fn eq(&self, other: &Self) -> bool { - let left = self - .accumulated - .get() - .map(|x| &x.0) - .or(self.segments.is_empty().then_some(&self.relative_to)); - let right = other - .accumulated - .get() - .map(|x| &x.0) - .or(other.segments.is_empty().then_some(&other.relative_to)); - - if let (Some(left), Some(right)) = (left, right) { - return left == right; - } - - self.relative_to == other.relative_to && self.segments == other.segments - } -} - impl PartialEq for NameAccumulator { fn eq(&self, other: &Self) -> bool { self.state == other.state @@ -690,17 +663,17 @@ mod tests { ]); name_image.add_segments([root_dir_segment, pics_dir_segment, image_file_segment]); - let (accum_note, proof_note) = name_note.as_proven_accumulator(setup); - let (accum_image, proof_image) = name_image.as_proven_accumulator(setup); + let (accum_note, proof_note) = name_note.into_proven_accumulator(setup); + let (accum_image, proof_image) = name_image.into_proven_accumulator(setup); let mut batched_proof = BatchedProofPart::new(); - batched_proof.add(proof_note, setup); - batched_proof.add(proof_image, setup); + batched_proof.add(&proof_note, setup); + batched_proof.add(&proof_image, setup); - let name_base = Name::empty(setup).as_accumulator(setup).clone(); + let name_base = Name::empty(setup).into_accumulator(setup); let mut verification = BatchedProofVerification::new(setup); - verification.add(&name_base, accum_note, &proof_note.part)?; - verification.add(&name_base, accum_image, &proof_image.part)?; + verification.add(&name_base, &accum_note, &proof_note.part)?; + verification.add(&name_base, &accum_image, &proof_image.part)?; verification.verify(&batched_proof)?; Ok(()) diff --git a/wnfs/Cargo.toml b/wnfs/Cargo.toml index ada55c73..7715e1e5 100644 --- a/wnfs/Cargo.toml +++ b/wnfs/Cargo.toml @@ -32,6 +32,7 @@ libipld-core = { version = "0.16" } multihash = "0.19" once_cell = "1.16" proptest = { version = "1.1", optional = true } +quick_cache = "0.3.0" rand_core = "0.6" semver = { version = "1.0", features = ["serde"] } serde = { version = "1.0", features = ["rc"] } diff --git a/wnfs/examples/write_proofs.rs b/wnfs/examples/write_proofs.rs index 45e203fe..b314a9ac 100644 --- a/wnfs/examples/write_proofs.rs +++ b/wnfs/examples/write_proofs.rs @@ -57,8 +57,7 @@ async fn alice_actions(store: &impl BlockStore) -> Result<(Cid, AccessKey, NameA let access_key = root_dir.as_node().store(forest, store, rng).await?; let cid = forest.store(store).await?; - let setup = forest.get_accumulator_setup(); - let allowed_name = root_dir.header.get_name().as_accumulator(setup).clone(); + let allowed_name = forest.get_accumulated_name(&root_dir.header.get_name()); Ok((cid, access_key, allowed_name)) } diff --git a/wnfs/src/private/directory.rs b/wnfs/src/private/directory.rs index eb77c261..81827a28 100644 --- a/wnfs/src/private/directory.rs +++ b/wnfs/src/private/directory.rs @@ -15,7 +15,7 @@ use std::{ rc::Rc, }; use wnfs_common::{utils::error, BlockStore, Metadata, PathNodes, PathNodesResult, CODEC_RAW}; -use wnfs_nameaccumulator::{AccumulatorSetup, Name, NameSegment}; +use wnfs_nameaccumulator::{Name, NameSegment}; //-------------------------------------------------------------------------------------------------- // Type Definitions @@ -394,15 +394,6 @@ impl PrivateDirectory { Ok(cloned) } - /// Returns the private ref, if this directory has been `.store()`ed before. - pub(crate) fn derive_private_ref(&self, setup: &AccumulatorSetup) -> Option { - self.content.persisted_as.get().map(|content_cid| { - self.header - .derive_revision_ref(setup) - .into_private_ref(*content_cid) - }) - } - /// This prepares this directory for key rotation, usually for moving or /// copying the directory to some other place. /// @@ -1259,8 +1250,7 @@ impl PrivateDirectory { store: &impl BlockStore, rng: &mut impl CryptoRngCore, ) -> Result { - let setup = &forest.get_accumulator_setup().clone(); - let header_cid = self.header.store(store, setup).await?; + let header_cid = self.header.store(store, forest).await?; let temporal_key = self.header.derive_temporal_key(); let name_with_revision = self.header.get_revision_name(); @@ -1270,12 +1260,12 @@ impl PrivateDirectory { .await?; forest - .put_encrypted(name_with_revision, [header_cid, content_cid], store) + .put_encrypted(&name_with_revision, [header_cid, content_cid], store) .await?; Ok(self .header - .derive_revision_ref(setup) + .derive_revision_ref(forest) .into_private_ref(content_cid)) } @@ -1284,9 +1274,9 @@ impl PrivateDirectory { serializable: PrivateDirectoryContentSerializable, temporal_key: &TemporalKey, cid: Cid, + forest: &impl PrivateForest, store: &impl BlockStore, parent_name: Option, - setup: &AccumulatorSetup, ) -> Result { if serializable.version.major != 0 || serializable.version.minor != 2 { bail!(FsError::UnexpectedVersion(serializable.version)); @@ -1309,9 +1299,9 @@ impl PrivateDirectory { let header = PrivateNodeHeader::load( &serializable.header_cid, temporal_key, + forest, store, parent_name, - setup, ) .await?; Ok(Self { header, content }) diff --git a/wnfs/src/private/file.rs b/wnfs/src/private/file.rs index 9f621233..4d8a2432 100644 --- a/wnfs/src/private/file.rs +++ b/wnfs/src/private/file.rs @@ -14,7 +14,7 @@ use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use std::{collections::BTreeSet, iter, rc::Rc}; use wnfs_common::{utils, BlockStore, Metadata, CODEC_RAW, MAX_BLOCK_SIZE}; -use wnfs_nameaccumulator::{AccumulatorSetup, Name, NameAccumulator, NameSegment}; +use wnfs_nameaccumulator::{Name, NameAccumulator, NameSegment}; //-------------------------------------------------------------------------------------------------- // Constants @@ -531,9 +531,7 @@ impl PrivateFile { Ok(FileContent::External { key, - base_name: base_name - .as_accumulator(forest.get_accumulator_setup()) - .clone(), + base_name: forest.get_accumulated_name(&base_name), block_count, block_content_size: MAX_BLOCK_CONTENT_SIZE, }) @@ -588,9 +586,7 @@ impl PrivateFile { Ok(FileContent::External { key, - base_name: base_name - .as_accumulator(forest.get_accumulator_setup()) - .clone(), + base_name: forest.get_accumulated_name(&base_name), block_count: block_index, block_content_size: MAX_BLOCK_CONTENT_SIZE, }) @@ -696,15 +692,6 @@ impl PrivateFile { Ok(cloned) } - /// Returns the private ref, if this file has been `.store()`ed before. - pub(crate) fn derive_private_ref(&self, setup: &AccumulatorSetup) -> Option { - self.content.persisted_as.get().map(|content_cid| { - self.header - .derive_revision_ref(setup) - .into_private_ref(*content_cid) - }) - } - /// This prepares this file for key rotation, usually for moving or /// copying the file to some other place. /// @@ -733,8 +720,7 @@ impl PrivateFile { store: &impl BlockStore, rng: &mut impl CryptoRngCore, ) -> Result { - let setup = &forest.get_accumulator_setup().clone(); - let header_cid = self.header.store(store, setup).await?; + let header_cid = self.header.store(store, forest).await?; let temporal_key = self.header.derive_temporal_key(); let snapshot_key = temporal_key.derive_snapshot_key(); let name_with_revision = self.header.get_revision_name(); @@ -745,12 +731,12 @@ impl PrivateFile { .await?; forest - .put_encrypted(name_with_revision, [header_cid, content_cid], store) + .put_encrypted(&name_with_revision, [header_cid, content_cid], store) .await?; Ok(self .header - .derive_revision_ref(setup) + .derive_revision_ref(forest) .into_private_ref(content_cid)) } @@ -759,9 +745,9 @@ impl PrivateFile { serializable: PrivateFileContentSerializable, temporal_key: &TemporalKey, cid: Cid, + forest: &impl PrivateForest, store: &impl BlockStore, parent_name: Option, - setup: &AccumulatorSetup, ) -> Result { if serializable.version.major != 0 || serializable.version.minor != 2 { bail!(FsError::UnexpectedVersion(serializable.version)); @@ -777,9 +763,9 @@ impl PrivateFile { let header = PrivateNodeHeader::load( &serializable.header_cid, temporal_key, + forest, store, parent_name, - setup, ) .await?; Ok(Self { header, content }) diff --git a/wnfs/src/private/forest/hamt.rs b/wnfs/src/private/forest/hamt.rs index 73674c46..838c1ebf 100644 --- a/wnfs/src/private/forest/hamt.rs +++ b/wnfs/src/private/forest/hamt.rs @@ -3,6 +3,7 @@ use crate::error::FsError; use anyhow::Result; use async_trait::async_trait; use libipld_core::{cid::Cid, ipld::Ipld}; +use quick_cache::sync::Cache; use rand_core::CryptoRngCore; use serde::{ de::Error as DeError, ser::Error as SerError, Deserialize, Deserializer, Serialize, Serializer, @@ -10,7 +11,13 @@ use serde::{ use std::{collections::BTreeSet, rc::Rc}; use wnfs_common::{AsyncSerialize, BlockStore, HashOutput, Link}; use wnfs_hamt::{merge, Hamt, Hasher, KeyValueChange, Pair}; -use wnfs_nameaccumulator::{AccumulatorSetup, Name, NameAccumulator}; +use wnfs_nameaccumulator::{AccumulatorSetup, ElementsProof, Name, NameAccumulator}; + +const APPROX_CACHE_ENTRY_SIZE: usize = + std::mem::size_of::<(Name, NameAccumulator, ElementsProof)>(); +/// This gives us a *very rough* 2 MB limit on the cache. +/// It's sligthly more, since the `NameSegment`s inside the `Name`s aren't accounted for. +const NAME_CACHE_CAPACITY: usize = 2_000_000 / APPROX_CACHE_ENTRY_SIZE; //-------------------------------------------------------------------------------------------------- // Type Definitions @@ -36,6 +43,7 @@ use wnfs_nameaccumulator::{AccumulatorSetup, Name, NameAccumulator}; pub struct HamtForest { hamt: Hamt, blake3::Hasher>, accumulator: AccumulatorSetup, + name_cache: Rc>, } //-------------------------------------------------------------------------------------------------- @@ -48,6 +56,7 @@ impl HamtForest { Self { hamt: Hamt::new(), accumulator: setup, + name_cache: Rc::new(Cache::new(NAME_CACHE_CAPACITY)), } } @@ -113,6 +122,17 @@ impl PrivateForest for HamtForest { &self.accumulator } + fn get_proven_name(&self, name: &Name) -> (NameAccumulator, ElementsProof) { + match self + .name_cache + .get_or_insert_with(name, || Ok(name.into_proven_accumulator(&self.accumulator))) + { + // Neat trick to avoid .unwrap(): + Ok(r) => r, + Err(r) => r, + } + } + async fn has_by_hash(&self, name_hash: &HashOutput, store: &impl BlockStore) -> Result { Ok(self .hamt @@ -124,31 +144,31 @@ impl PrivateForest for HamtForest { async fn has(&self, name: &Name, store: &impl BlockStore) -> Result { self.has_by_hash( - &blake3::Hasher::hash(name.as_accumulator(&self.accumulator)), + &blake3::Hasher::hash(&self.get_accumulated_name(name)), store, ) .await } - async fn put_encrypted<'a>( + async fn put_encrypted( &mut self, - name: &'a Name, + name: &Name, values: impl IntoIterator, store: &impl BlockStore, - ) -> Result<&'a NameAccumulator> { - let name = name.as_accumulator(&self.accumulator); + ) -> Result { + let accumulator = self.get_accumulated_name(name); - match self.hamt.root.get_mut(name, store).await? { + match self.hamt.root.get_mut(&accumulator, store).await? { Some(cids) => cids.extend(values), None => { self.hamt .root - .set(name.clone(), values.into_iter().collect(), store) + .set(accumulator.clone(), values.into_iter().collect(), store) .await?; } } - Ok(name) + Ok(accumulator) } #[inline] @@ -165,7 +185,7 @@ impl PrivateForest for HamtForest { name: &Name, store: &impl BlockStore, ) -> Result>> { - let name_hash = &blake3::Hasher::hash(name.as_accumulator(&self.accumulator)); + let name_hash = &blake3::Hasher::hash(&self.get_accumulated_name(name)); self.get_encrypted_by_hash(name_hash, store).await } @@ -174,7 +194,7 @@ impl PrivateForest for HamtForest { name: &Name, store: &impl BlockStore, ) -> Result>>> { - let name_hash = &blake3::Hasher::hash(name.as_accumulator(&self.accumulator)); + let name_hash = &blake3::Hasher::hash(&self.get_accumulated_name(name)); self.hamt.root.remove_by_hash(name_hash, store).await } } @@ -189,6 +209,10 @@ impl PrivateForest for Rc { (**self).get_accumulator_setup() } + fn get_proven_name(&self, name: &Name) -> (NameAccumulator, ElementsProof) { + (**self).get_proven_name(name) + } + async fn has_by_hash(&self, name_hash: &HashOutput, store: &impl BlockStore) -> Result { (**self).has_by_hash(name_hash, store).await } @@ -197,12 +221,12 @@ impl PrivateForest for Rc { (**self).has(name, store).await } - async fn put_encrypted<'a>( + async fn put_encrypted( &mut self, - name: &'a Name, + name: &Name, values: impl IntoIterator, store: &impl BlockStore, - ) -> Result<&'a NameAccumulator> { + ) -> Result { Rc::make_mut(self).put_encrypted(name, values, store).await } @@ -309,12 +333,16 @@ impl HamtForest { ) .await?; + // TODO(matheus23) Should we find some way to sensibly merge caches? + let name_cache = self.name_cache.clone(); + Ok(Self { hamt: Hamt { version: self.hamt.version.clone(), root: merged_root, }, accumulator: self.accumulator.clone(), + name_cache, }) } } @@ -367,7 +395,11 @@ impl<'de> Deserialize<'de> for HamtForest { let accumulator = AccumulatorSetup::deserialize(accumulator_ipld).map_err(serde::de::Error::custom)?; - Ok(Self { hamt, accumulator }) + Ok(Self { + hamt, + accumulator, + name_cache: Rc::new(Cache::new(NAME_CACHE_CAPACITY)), + }) } } diff --git a/wnfs/src/private/forest/proofs.rs b/wnfs/src/private/forest/proofs.rs index d250ad6d..28f972f4 100644 --- a/wnfs/src/private/forest/proofs.rs +++ b/wnfs/src/private/forest/proofs.rs @@ -10,8 +10,8 @@ use std::{ use wnfs_common::{BlockStore, HashOutput}; use wnfs_hamt::Pair; use wnfs_nameaccumulator::{ - AccumulatorSetup, BatchedProofPart, BatchedProofVerification, Name, NameAccumulator, - UnbatchableProofPart, + AccumulatorSetup, BatchedProofPart, BatchedProofVerification, ElementsProof, Name, + NameAccumulator, UnbatchableProofPart, }; /// This holds proofs that added, removed or changed labels in the private forest correspond @@ -51,16 +51,16 @@ impl ForestProofs { } /// Prove given name, add its proof to the struct and return the accumulated name - pub fn add_and_prove_name<'a>( + pub fn add_and_prove_name( &mut self, - name: &'a Name, + name: &Name, setup: &AccumulatorSetup, - ) -> Result<&'a NameAccumulator> { - let (accumulated, proof) = name.as_proven_accumulator(setup); + ) -> Result { + let (accumulated, proof) = name.into_proven_accumulator(setup); let base = NameAccumulator::from_state(proof.base.clone()); let commitment = accumulated.clone(); - self.batched_proof_part.add(proof, setup); + self.batched_proof_part.add(&proof, setup); self.proofs_by_commitment .insert(commitment, (base, proof.part.clone())); @@ -160,6 +160,10 @@ impl PrivateForest for ProvingHamtForest { self.forest.get_accumulator_setup() } + fn get_proven_name(&self, name: &Name) -> (NameAccumulator, ElementsProof) { + self.forest.get_proven_name(name) + } + async fn has_by_hash(&self, name_hash: &HashOutput, store: &impl BlockStore) -> Result { self.forest.has_by_hash(name_hash, store).await } @@ -168,12 +172,12 @@ impl PrivateForest for ProvingHamtForest { self.forest.has(name, store).await } - async fn put_encrypted<'a>( + async fn put_encrypted( &mut self, - name: &'a Name, + name: &Name, values: impl IntoIterator, store: &impl BlockStore, - ) -> Result<&'a NameAccumulator> { + ) -> Result { let ProvingHamtForest { forest, proofs } = self; proofs.add_and_prove_name(name, forest.get_accumulator_setup())?; diff --git a/wnfs/src/private/forest/traits.rs b/wnfs/src/private/forest/traits.rs index 0e8254c4..787a31c0 100644 --- a/wnfs/src/private/forest/traits.rs +++ b/wnfs/src/private/forest/traits.rs @@ -10,7 +10,7 @@ use libipld_core::cid::Cid; use std::collections::BTreeSet; use wnfs_common::{BlockStore, HashOutput}; use wnfs_hamt::Pair; -use wnfs_nameaccumulator::{AccumulatorSetup, Name, NameAccumulator}; +use wnfs_nameaccumulator::{AccumulatorSetup, ElementsProof, Name, NameAccumulator}; /// A trait representing a (usually serializable) mapping from /// WNFS names to a set of encrypted ciphertext blocks. @@ -41,6 +41,20 @@ pub trait PrivateForest { /// the private forest name accumulators. fn get_accumulator_setup(&self) -> &AccumulatorSetup; + /// Accumulate all segments inside a name into a NameAccumulator and + /// also return an ElementsProof witnessing the Name being accumulated correctly. + /// + /// This is a function on `PrivateForest` so it can implement a cache on this + /// somewhat expensive operation. + fn get_proven_name(&self, name: &Name) -> (NameAccumulator, ElementsProof); + + /// Accumulate all segments inside a name into a NameAccumulator. + /// + /// The default implementation simply returns `self.get_proven_name(name).0`. + fn get_accumulated_name(&self, name: &Name) -> NameAccumulator { + self.get_proven_name(name).0 + } + /// Checks that a value with the given saturated name hash key exists. /// /// # Examples @@ -80,12 +94,12 @@ pub trait PrivateForest { async fn has(&self, name: &Name, store: &impl BlockStore) -> Result; /// Adds new encrypted values at the given key. - async fn put_encrypted<'a>( + async fn put_encrypted( &mut self, - name: &'a Name, + name: &Name, values: impl IntoIterator, store: &impl BlockStore, - ) -> Result<&'a NameAccumulator>; + ) -> Result; /// Gets the CIDs to blocks of ciphertext by hash of name. async fn get_encrypted_by_hash<'b>( @@ -119,17 +133,18 @@ pub trait PrivateForest { temporal_key: &'a TemporalKey, store: &'a impl BlockStore, parent_name: Option, - ) -> LocalBoxStream<'a, Result> { + ) -> LocalBoxStream<'a, Result> + where + Self: Sized, + { Box::pin(stream! { match self .get_encrypted_by_hash(label, store) .await { Ok(Some(cids)) => { - let setup = self.get_accumulator_setup(); - for cid in cids { - match PrivateNode::from_cid(*cid, temporal_key, store, parent_name.clone(), setup).await { + match PrivateNode::from_cid(*cid, temporal_key, self, store, parent_name.clone()).await { Ok(node) => yield Ok(node), Err(e) if e.downcast_ref::().is_some() => { // we likely matched a PrivateNodeHeader instead of a PrivateNode. diff --git a/wnfs/src/private/link.rs b/wnfs/src/private/link.rs index 8e2f1825..57107f27 100644 --- a/wnfs/src/private/link.rs +++ b/wnfs/src/private/link.rs @@ -7,7 +7,7 @@ use async_recursion::async_recursion; use rand_core::CryptoRngCore; use std::rc::Rc; use wnfs_common::BlockStore; -use wnfs_nameaccumulator::{AccumulatorSetup, Name}; +use wnfs_nameaccumulator::Name; #[derive(Debug)] pub(crate) enum PrivateLink { @@ -134,14 +134,6 @@ impl PrivateLink { pub(crate) fn with_file(file: PrivateFile) -> Self { Self::from(PrivateNode::File(Rc::new(file))) } - - #[allow(dead_code)] - pub(crate) fn get_ref(&self, setup: &AccumulatorSetup) -> Option { - match self { - Self::Encrypted { private_ref, .. } => Some(private_ref.clone()), - Self::Decrypted { node } => node.derive_private_ref(setup), - } - } } impl PartialEq for PrivateLink { diff --git a/wnfs/src/private/node/header.rs b/wnfs/src/private/node/header.rs index ce7a399c..a2f2f61c 100644 --- a/wnfs/src/private/node/header.rs +++ b/wnfs/src/private/node/header.rs @@ -1,14 +1,16 @@ use super::{PrivateNodeHeaderSerializable, TemporalKey, REVISION_SEGMENT_DSI}; -use crate::{error::FsError, private::RevisionRef}; +use crate::{ + error::FsError, + private::{forest::traits::PrivateForest, RevisionRef}, +}; use anyhow::{bail, Result}; use libipld_core::cid::Cid; -use once_cell::sync::OnceCell; use rand_core::CryptoRngCore; use skip_ratchet::Ratchet; use std::fmt::Debug; use wnfs_common::{BlockStore, CODEC_RAW}; use wnfs_hamt::Hasher; -use wnfs_nameaccumulator::{AccumulatorSetup, Name, NameSegment}; +use wnfs_nameaccumulator::{Name, NameSegment}; //-------------------------------------------------------------------------------------------------- // Type Definitions @@ -43,8 +45,6 @@ pub struct PrivateNodeHeader { pub(crate) ratchet: Ratchet, /// Stores the name of this node for easier lookup. pub(crate) name: Name, - /// Stores a cache of the name with the revision segment added - pub(crate) revision_name: OnceCell, } //-------------------------------------------------------------------------------------------------- @@ -60,26 +60,22 @@ impl PrivateNodeHeader { name: parent_name.with_segments_added(Some(inumber.clone())), ratchet: Ratchet::from_rng(rng), inumber, - revision_name: OnceCell::new(), } } /// Advances the ratchet. pub(crate) fn advance_ratchet(&mut self) { self.ratchet.inc(); - self.revision_name = OnceCell::new(); } /// Updates the name to the child of given parent name. pub(crate) fn update_name(&mut self, parent_name: &Name) { self.name = parent_name.with_segments_added(Some(self.inumber.clone())); - self.revision_name = OnceCell::new(); } /// Sets the ratchet and makes sure any caches are cleared. pub(crate) fn update_ratchet(&mut self, ratchet: Ratchet) { self.ratchet = ratchet; - self.revision_name = OnceCell::new(); } /// Resets the ratchet. @@ -88,10 +84,10 @@ impl PrivateNodeHeader { } /// Derives the revision ref of the current header. - pub(crate) fn derive_revision_ref(&self, setup: &AccumulatorSetup) -> RevisionRef { + pub(crate) fn derive_revision_ref(&self, forest: &impl PrivateForest) -> RevisionRef { let temporal_key = self.derive_temporal_key(); let revision_name_hash = - blake3::Hasher::hash(self.get_revision_name().as_accumulator(setup)); + blake3::Hasher::hash(&forest.get_accumulated_name(&self.get_revision_name())); RevisionRef { revision_name_hash, @@ -157,11 +153,9 @@ impl PrivateNodeHeader { /// /// println!("Revision name: {:?}", revision_name); /// ``` - pub fn get_revision_name(&self) -> &Name { - self.revision_name.get_or_init(|| { - self.name - .with_segments_added(Some(self.derive_revision_segment())) - }) + pub fn get_revision_name(&self) -> Name { + self.name + .with_segments_added(Some(self.derive_revision_segment())) } /// Gets the name for this node. @@ -173,21 +167,23 @@ impl PrivateNodeHeader { /// Encrypts this private node header in an block, then stores that in the given /// BlockStore and returns its CID. - pub async fn store(&self, store: &impl BlockStore, setup: &AccumulatorSetup) -> Result { + /// + /// This *does not* store the block itself in the forest, only in the given block store. + pub async fn store(&self, store: &impl BlockStore, forest: &impl PrivateForest) -> Result { let temporal_key = self.derive_temporal_key(); - let cbor_bytes = serde_ipld_dagcbor::to_vec(&self.to_serializable(setup))?; + let cbor_bytes = serde_ipld_dagcbor::to_vec(&self.to_serializable(forest))?; let ciphertext = temporal_key.key_wrap_encrypt(&cbor_bytes)?; store.put_block(ciphertext, CODEC_RAW).await } pub(crate) fn to_serializable( &self, - setup: &AccumulatorSetup, + forest: &impl PrivateForest, ) -> PrivateNodeHeaderSerializable { PrivateNodeHeaderSerializable { inumber: self.inumber.clone(), ratchet: self.ratchet.clone(), - name: self.name.as_accumulator(setup).clone(), + name: forest.get_accumulated_name(&self.name), } } @@ -196,7 +192,6 @@ impl PrivateNodeHeader { inumber: serializable.inumber, ratchet: serializable.ratchet, name: Name::new(serializable.name, []), - revision_name: OnceCell::new(), } } @@ -205,9 +200,9 @@ impl PrivateNodeHeader { pub(crate) async fn load( cid: &Cid, temporal_key: &TemporalKey, + forest: &impl PrivateForest, store: &impl BlockStore, parent_name: Option, - setup: &AccumulatorSetup, ) -> Result { let ciphertext = store.get_block(cid).await?; let cbor_bytes = temporal_key.key_wrap_decrypt(&ciphertext)?; @@ -216,8 +211,8 @@ impl PrivateNodeHeader { let mut header = Self::from_serializable(decoded); if let Some(parent_name) = parent_name { let name = parent_name.with_segments_added([header.inumber.clone()]); - let mounted_acc = name.as_accumulator(setup); - if mounted_acc != &serialized_name { + let mounted_acc = forest.get_accumulated_name(&name); + if mounted_acc != serialized_name { bail!(FsError::MountPointAndDeserializedNameMismatch( format!("{mounted_acc:?}"), format!("{serialized_name:?}"), @@ -231,6 +226,8 @@ impl PrivateNodeHeader { impl PartialEq for PrivateNodeHeader { fn eq(&self, other: &Self) -> bool { - self.inumber == other.inumber && self.ratchet == other.ratchet && self.name == other.name + // We skip equality-checking the name, since it depends on where the node header was mounted. + // The inumber should suffice as identifier, the ratchet covers the revision part. + self.inumber == other.inumber && self.ratchet == other.ratchet } } diff --git a/wnfs/src/private/node/node.rs b/wnfs/src/private/node/node.rs index 2b119c1b..b5547055 100644 --- a/wnfs/src/private/node/node.rs +++ b/wnfs/src/private/node/node.rs @@ -18,7 +18,7 @@ use skip_ratchet::{JumpSize, RatchetSeeker}; use std::{cmp::Ordering, collections::BTreeSet, fmt::Debug, rc::Rc}; use wnfs_common::BlockStore; use wnfs_hamt::Hasher; -use wnfs_nameaccumulator::{AccumulatorSetup, Name}; +use wnfs_nameaccumulator::Name; //-------------------------------------------------------------------------------------------------- // Type Definitions @@ -413,7 +413,6 @@ impl PrivateNode { store: &impl BlockStore, ) -> Result> { let header = self.get_header(); - let setup = forest.get_accumulator_setup(); let mountpoint = header.name.parent().unwrap_or_else(|| forest.empty_name()); let current_name = &header.get_revision_name(); @@ -433,7 +432,7 @@ impl PrivateNode { current_header.update_ratchet(current.clone()); let has_curr = forest - .has(current_header.get_revision_name(), store) + .has(¤t_header.get_revision_name(), store) .await?; let ord = if has_curr { @@ -450,7 +449,7 @@ impl PrivateNode { current_header.update_ratchet(search.current().clone()); let name_hash = - blake3::Hasher::hash(¤t_header.get_revision_name().as_accumulator(setup)); + blake3::Hasher::hash(&forest.get_accumulated_name(¤t_header.get_revision_name())); Ok(forest .get_multivalue_by_hash( @@ -482,17 +481,15 @@ impl PrivateNode { _ => bail!(FsError::NotFound), }; - let setup = forest.get_accumulator_setup(); - - Self::from_cid(cid, &private_ref.temporal_key, store, parent_name, setup).await + Self::from_cid(cid, &private_ref.temporal_key, forest, store, parent_name).await } pub(crate) async fn from_cid( cid: Cid, temporal_key: &TemporalKey, + forest: &impl PrivateForest, store: &impl BlockStore, parent_name: Option, - setup: &AccumulatorSetup, ) -> Result { let encrypted_bytes = store.get_block(&cid).await?; let snapshot_key = temporal_key.derive_snapshot_key(); @@ -504,9 +501,9 @@ impl PrivateNode { file, temporal_key, cid, + forest, store, parent_name, - setup, ) .await?; PrivateNode::File(Rc::new(file)) @@ -516,9 +513,9 @@ impl PrivateNode { dir, temporal_key, cid, + forest, store, parent_name, - setup, ) .await?; PrivateNode::Dir(Rc::new(dir)) @@ -526,14 +523,6 @@ impl PrivateNode { }) } - /// Returns the private ref, if this node has been `.store()`ed before. - pub(crate) fn derive_private_ref(&self, setup: &AccumulatorSetup) -> Option { - match self { - Self::File(file) => file.derive_private_ref(setup), - Self::Dir(dir) => dir.derive_private_ref(setup), - } - } - pub(crate) fn get_persisted_as(&self) -> &OnceCell { match self { Self::Dir(dir) => &dir.content.persisted_as, @@ -689,13 +678,15 @@ mod tests { let file_private_ref = file_node.store(forest, store, rng).await.unwrap(); let dir_private_ref = dir_node.store(forest, store, rng).await.unwrap(); - let deserialized_file_node = PrivateNode::load(&file_private_ref, forest, store, None) - .await - .unwrap(); + let deserialized_file_node = + PrivateNode::load(&file_private_ref, forest, store, Some(forest.empty_name())) + .await + .unwrap(); - let deserialized_dir_node = PrivateNode::load(&dir_private_ref, forest, store, None) - .await - .unwrap(); + let deserialized_dir_node = + PrivateNode::load(&dir_private_ref, forest, store, Some(forest.empty_name())) + .await + .unwrap(); assert_eq!(file_node, deserialized_file_node); assert_eq!(dir_node, deserialized_dir_node); diff --git a/wnfs/src/private/previous.rs b/wnfs/src/private/previous.rs index 1c5ab696..c057ab44 100644 --- a/wnfs/src/private/previous.rs +++ b/wnfs/src/private/previous.rs @@ -119,11 +119,10 @@ impl PrivateNodeHistory { self.header.update_ratchet(previous_ratchet); - let setup = self.forest.get_accumulator_setup(); let previous_node = PrivateNode::from_private_ref( &self .header - .derive_revision_ref(setup) + .derive_revision_ref(&self.forest) .into_private_ref(previous_cid), &self.forest, store, diff --git a/wnfs/src/private/share.rs b/wnfs/src/private/share.rs index 6309f88c..5518133d 100644 --- a/wnfs/src/private/share.rs +++ b/wnfs/src/private/share.rs @@ -251,11 +251,10 @@ pub mod recipient { sharer_forest: &impl PrivateForest, store: &impl BlockStore, ) -> Result { - 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( - &blake3::Hasher::hash(&share_label.as_accumulator(setup)), + &blake3::Hasher::hash(&sharer_forest.get_accumulated_name(share_label)), store, ) .await?