Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Misc fixes #84

Merged
merged 10 commits into from
Nov 16, 2024
2 changes: 1 addition & 1 deletion docs/algorithms.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ node_hash = H(kv_hash, left_child_hash, right_child_hash)

Note that the `left_child_hash` and/or `right_child_hash` values may be null since it is possible for the node to have no children or only one child.

In our implementation, the hash function used is Blake2b (chosen for its performance characteristics) but this choice is trivially swappable.
In our implementation, the hash function used is SHA512/256 (SHA512 with output truncated to 256 bits) but this choice is trivially swappable.

#### Database Representation

Expand Down
7 changes: 5 additions & 2 deletions src/merk/restore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,11 @@
0
};

// FIXME: this one shouldn't be an assert because it comes from a peer
assert_eq!(self.stated_length, chunks_remaining + 1);
if self.stated_length != chunks_remaining + 1 {
return Err(Error::ChunkProcessing(
"Stated length does not match calculated number of chunks".into(),
));

Check warning on line 179 in src/merk/restore.rs

View check run for this annotation

Codecov / codecov/patch

src/merk/restore.rs#L177-L179

Added lines #L177 - L179 were not covered by tests
}

// note that these writes don't happen atomically, which is fine here
// because if anything fails during the restore process we will just
Expand Down
48 changes: 36 additions & 12 deletions src/proofs/chunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,17 +197,20 @@
#[cfg(feature = "full")]
pub(crate) fn verify_trunk<I: Iterator<Item = Result<Op>>>(ops: I) -> Result<(ProofTree, usize)> {
fn verify_height_proof(tree: &ProofTree) -> Result<usize> {
Ok(match tree.child(true) {
Some(child) => {
if let Node::Hash(_) = child.tree.node {
return Err(Error::UnexpectedNode(
"Expected height proof to only contain KV and KVHash nodes".into(),
));
}
verify_height_proof(&child.tree)? + 1
let mut height = 1;
let mut cursor = tree;
while let Some(child) = cursor.child(true) {
if let Node::Hash(_) = child.tree.node {
return Err(Error::UnexpectedNode(
"Expected height proof to only contain KV and KVHash
nodes"
.into(),
));

Check warning on line 208 in src/proofs/chunk.rs

View check run for this annotation

Codecov / codecov/patch

src/proofs/chunk.rs#L205-L208

Added lines #L205 - L208 were not covered by tests
}
None => 1,
})
height += 1;
cursor = &child.tree;
}
Ok(height)
}

fn verify_completeness(tree: &ProofTree, remaining_depth: usize, leftmost: bool) -> Result<()> {
Expand Down Expand Up @@ -253,6 +256,11 @@
})?;

let height = verify_height_proof(&tree)?;
if height > 64 {
// This is a sanity check to prevent stack overflows in `verify_completeness`,
// but any tree above 64 is probably an error (~3.7e19 nodes).
return Err(Error::Tree("Tree is too large".into()));
}
let trunk_height = height / 2;

if trunk_height < MIN_TRUNK_HEIGHT {
Expand All @@ -268,12 +276,11 @@

#[cfg(test)]
mod tests {
use std::usize;

use super::super::tree::Tree;
use super::*;
use crate::test_utils::*;
use crate::tree::{NoopCommit, PanicSource, Tree as BaseTree};
use ed::Encode;

#[derive(Default)]
struct NodeCounts {
Expand Down Expand Up @@ -467,4 +474,21 @@
assert_eq!(counts.hash, 0);
assert_eq!(counts.kvhash, 0);
}

#[test]
#[should_panic(expected = "Tree is too large")]
fn test_verify_height_stack_overflow() {
let height = 5_000u32;
let push_op = |i: u32| Op::Push(Node::KV(i.to_be_bytes().to_vec(), vec![]));
let mut ops = Vec::with_capacity((height * 2) as usize);
ops.push(push_op(0));
for i in 1..height {
ops.push(push_op(i));
ops.push(Op::Parent)
}
assert!(ops.encoding_length().unwrap() < 50_000);
println!("Len: {}", ops.encoding_length().unwrap());
let (_, result_height) = verify_trunk(ops.into_iter().map(Ok)).unwrap();
assert_eq!(height, result_height as u32);
}

Check warning on line 493 in src/proofs/chunk.rs

View check run for this annotation

Codecov / codecov/patch

src/proofs/chunk.rs#L493

Added line #L493 was not covered by tests
}
15 changes: 15 additions & 0 deletions src/proofs/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@
use super::*;
use crate::test_utils::make_tree_seq;
use crate::tree::{NoopCommit, PanicSource, RefWalker, Tree};
use ed::Encode;

fn make_3_node_tree() -> Result<Tree> {
let mut tree = Tree::new(vec![5], vec![5])?
Expand Down Expand Up @@ -1477,4 +1478,18 @@

let _result = verify_query(bytes.as_slice(), &query, [42; 32]).expect("verify failed");
}

#[test]
#[should_panic(expected = "Tried to attach to Hash node")]
fn hash_attach() {
let mut target = make_3_node_tree().expect("tree construction failed");

let mut proof = Vec::new();
proof.push(Op::Push(Node::KV(vec![42], vec![42])));
proof.push(Op::Push(Node::Hash(target.hash())));
proof.push(Op::Parent);

let map = verify(&proof.encode().unwrap(), target.hash()).unwrap();
assert_eq!(map.get(&[42]).unwrap().unwrap(), &[42])
}

Check warning on line 1494 in src/proofs/query/mod.rs

View check run for this annotation

Codecov / codecov/patch

src/proofs/query/mod.rs#L1494

Added line #L1494 was not covered by tests
}
8 changes: 6 additions & 2 deletions src/proofs/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,19 @@ impl Tree {
}
}

/// Attaches the child to the `Tree`'s given side. Panics if there is
/// already a child attached to this side.
/// Attaches the child to the `Tree`'s given side. Returns an error if
/// there is already a child attached to this side.
pub(crate) fn attach(&mut self, left: bool, child: Tree) -> Result<()> {
if self.child(left).is_some() {
return Err(Error::Attach(
"Tried to attach to left child, but it is already Some".into(),
));
}

if let Node::Hash(_) = self.node {
return Err(Error::Attach("Tried to attach to Hash node".into()));
}

self.height = self.height.max(child.height + 1);

let hash = child.hash()?;
Expand Down
3 changes: 0 additions & 3 deletions src/tree/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ pub const NULL_HASH: Hash = [0; HASH_LENGTH];
pub type Hash = [u8; HASH_LENGTH];

/// Hashes a key/value pair.
///
/// **NOTE:** This will fail if the key is longer than 255 bytes, or the value
/// is longer than 65,535 bytes.
pub fn kv_hash<D: Digest>(key: &[u8], value: &[u8]) -> Result<Hash, TryFromIntError> {
let mut hasher = D::new();
hasher.update([0]);
Expand Down
Loading