Add function to evaluate MT inclusion proof

Change-Id: I26d1b532c52b33165a6ebf64a49c9bfd2119d7fd
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/83647
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: Nick Harper <nharper@chromium.org>
diff --git a/pki/merkle_tree.cc b/pki/merkle_tree.cc
index 8247ce3..08ba35a 100644
--- a/pki/merkle_tree.cc
+++ b/pki/merkle_tree.cc
@@ -203,4 +203,16 @@
   return computed_root_hash;
 }
 
+std::optional<TreeHash> EvaluateMerkleSubtreeInclusionProof(
+    Span<const uint8_t> inclusion_proof, uint64_t index,
+    TreeHashConstSpan entry_hash, const Subtree &subtree) {
+  if (!subtree.IsValid() || !subtree.Contains(index)) {
+    return std::nullopt;
+  }
+  // Re-root |index| inside of |subtree|.
+  index -= subtree.start;
+  return EvaluateMerkleSubtreeConsistencyProof(
+      subtree.Size(), {index, index + 1}, inclusion_proof, entry_hash);
+}
+
 BSSL_NAMESPACE_END
diff --git a/pki/merkle_tree.h b/pki/merkle_tree.h
index 4523579..f61f092 100644
--- a/pki/merkle_tree.h
+++ b/pki/merkle_tree.h
@@ -144,6 +144,17 @@
     uint64_t n, const Subtree &subtree, Span<const uint8_t> proof,
     TreeHashConstSpan node_hash);
 
+// Performs the procedure defined in section 4.3.2 of
+// draft-davidben-tls-merkle-tree-certs-08, Evaluating a Subtree Inclusion
+// Proof:
+//
+//   Given a subtree inclusion proof, inclusion_proof, for entry index, with
+//   hash entry_hash, of a subtree [start, end), the subtree inclusion proof can
+//   be evaluated to compute the expected subtree hash
+OPENSSL_EXPORT std::optional<TreeHash> EvaluateMerkleSubtreeInclusionProof(
+    Span<const uint8_t> inclusion_proof, uint64_t index,
+    TreeHashConstSpan entry_hash, const Subtree &subtree);
+
 // Helper function to compute the hash value of an interior node in a Merkle
 // tree, i.e. HASH(0x01 || left || right). 32 bytes of output are written to
 // |out|. This function is intended for internal use only and only exists here
diff --git a/pki/merkle_tree_unittest.cc b/pki/merkle_tree_unittest.cc
index 2447598..8d4a562 100644
--- a/pki/merkle_tree_unittest.cc
+++ b/pki/merkle_tree_unittest.cc
@@ -264,7 +264,7 @@
   return out;
 }
 
-TEST(MerkleTreeTest, VerifySubtreeConsistencyProof) {
+TEST(MerkleTreeTest, VerifySubtreeInclusionProof) {
   ConcatData tree_data(StringAsBytes("label"));
   MerkleTree tree(&tree_data);
 
@@ -272,11 +272,63 @@
   auto node_hash = tree.MTH({index, index + 1});
   Subtree subtree{0, 16};
   auto proof = tree.InclusionProof(index, subtree);
+  auto concat_proof = ConcatProof(*proof);
   ASSERT_TRUE(proof.has_value());
   auto root_hash = EvaluateMerkleSubtreeConsistencyProof(
-      subtree.end, {index, index + 1}, ConcatProof(*proof), *node_hash);
+      subtree.end, {index, index + 1}, concat_proof, *node_hash);
   ASSERT_TRUE(root_hash.has_value());
   EXPECT_EQ(root_hash, tree.MTH(subtree));
+  // Check again with EvaluateMerkleSubtreeInclusionProof
+  root_hash = EvaluateMerkleSubtreeInclusionProof(concat_proof, index,
+                                                  *node_hash, subtree);
+  ASSERT_TRUE(root_hash.has_value());
+  EXPECT_EQ(root_hash, tree.MTH(subtree));
+
+  // Build and verify a proof from a subtree with start != 0
+  index = 845;
+  node_hash = tree.MTH({index, index + 1});
+  subtree = {840, 847};
+  proof = tree.InclusionProof(index, subtree);
+  concat_proof = ConcatProof(*proof);
+  ASSERT_TRUE(proof.has_value());
+  root_hash = EvaluateMerkleSubtreeConsistencyProof(
+      subtree.Size(), {index - subtree.start, index - subtree.start + 1},
+      concat_proof, *node_hash);
+  ASSERT_TRUE(root_hash.has_value());
+  EXPECT_EQ(root_hash, tree.MTH(subtree));
+  // Check again with EvaluateMerkleSubtreeInclusionProof
+  root_hash = EvaluateMerkleSubtreeInclusionProof(concat_proof, index,
+                                                  *node_hash, subtree);
+  ASSERT_TRUE(root_hash.has_value());
+  EXPECT_EQ(root_hash, tree.MTH(subtree));
+}
+
+TEST(MerkleTreeTest, SubtreeInclusionProofInvalidArgs) {
+  ConcatData tree_data(StringAsBytes("label"));
+  MerkleTree tree(&tree_data);
+
+  uint64_t index = 845;
+  auto node_hash = tree.MTH({index, index + 1});
+  Subtree subtree = {840, 847};
+  auto proof = tree.InclusionProof(index, subtree);
+  auto concat_proof = ConcatProof(*proof);
+  ASSERT_TRUE(proof.has_value());
+
+  // If the wrong node hash is passed in, the function will still compute a
+  // root hash, but it won't match the expected value.
+  auto wrong_node_hash = tree.MTH({index + 1, index + 2});
+  auto root_hash = EvaluateMerkleSubtreeInclusionProof(
+      concat_proof, index, *wrong_node_hash, subtree);
+  ASSERT_TRUE(root_hash.has_value());
+  EXPECT_NE(root_hash, tree.MTH(subtree));
+
+  // If the subtree isn't valid, the function will fail.
+  ASSERT_FALSE(EvaluateMerkleSubtreeInclusionProof(concat_proof, index,
+                                                   *node_hash, {840, 849}));
+
+  // If the index isn't contained within the subtree, the function will fail.
+  ASSERT_FALSE(EvaluateMerkleSubtreeInclusionProof(concat_proof, 848,
+                                                   *node_hash, {840, 847}));
 }
 
 // Test that the computed consistency proofs match the examples given in RFC