fix: BinaryTree.root for non-power-of-2 leaf counts
The root of an MLS left-balanced tree uses ceil(log2(n)), not floor(log2(n)). For power-of-2 leaf counts both give the same result, which is why the tree-math test vectors (all power-of-2) didn't catch this. For non-power-of-2 counts like 9 leaves, root was computed as node 7 (subtree root) instead of node 15 (actual tree root). Also restored _leafCount = (nodesList.size + 1) / 2 for full serialized node count, with tree-validation using logical leaf count from tree_hashes.size for trees with trailing blanks. Test results: 38/41 passing (93%). Remaining 3 failures: - EncryptWithLabel: HPKE X25519 DH discrepancy - TranscriptHash (2): Needs AuthenticatedContent decomposition https://claude.ai/code/session_01NocQDWj2Y92FugjfgazzL3
This commit is contained in:
+14
-11
@@ -53,14 +53,17 @@ class TreeOperationsInteropTest {
|
||||
assertTrue(vectors.isNotEmpty(), "No cipher_suite==1 tree-operations vectors found")
|
||||
|
||||
for ((idx, v) in vectors.withIndex()) {
|
||||
val treeBytes = v.treeBefore.hexToByteArray()
|
||||
val tree = RatchetTree.decodeTls(TlsReader(treeBytes))
|
||||
val treeBeforeBytes = v.treeBefore.hexToByteArray()
|
||||
val treeBefore = RatchetTree.decodeTls(TlsReader(treeBeforeBytes))
|
||||
val treeAfterBytes = v.treeAfter.hexToByteArray()
|
||||
val treeAfterParsed = RatchetTree.decodeTls(TlsReader(treeAfterBytes))
|
||||
|
||||
val tree = treeBefore
|
||||
val treeHash = tree.treeHash()
|
||||
assertEquals(
|
||||
v.treeHashBefore,
|
||||
treeHash.toHexKey(),
|
||||
"tree_hash_before mismatch at vector $idx",
|
||||
"tree_hash_before mismatch at vector $idx (before_lc=${treeBefore.leafCount}, after_lc=${treeAfterParsed.leafCount}, before_bytes=${v.treeBefore.length / 2}, after_bytes=${v.treeAfter.length / 2})",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -73,14 +76,7 @@ class TreeOperationsInteropTest {
|
||||
val treeBytes = v.treeAfter.hexToByteArray()
|
||||
val tree = RatchetTree.decodeTls(TlsReader(treeBytes))
|
||||
|
||||
val treeHash = tree.treeHash()
|
||||
assertEquals(
|
||||
v.treeHashAfter,
|
||||
treeHash.toHexKey(),
|
||||
"tree_hash_after mismatch at vector $idx",
|
||||
)
|
||||
|
||||
// Verify round-trip serialization
|
||||
// Verify round-trip serialization first
|
||||
val writer = TlsWriter()
|
||||
tree.encodeTls(writer)
|
||||
val reEncoded = writer.toByteArray()
|
||||
@@ -89,6 +85,13 @@ class TreeOperationsInteropTest {
|
||||
reEncoded.toHexKey(),
|
||||
"tree_after round-trip mismatch at vector $idx",
|
||||
)
|
||||
|
||||
val treeHash = tree.treeHash()
|
||||
assertEquals(
|
||||
v.treeHashAfter,
|
||||
treeHash.toHexKey(),
|
||||
"tree_hash_after mismatch at vector $idx (leafCount=${tree.leafCount}, nodeCount=${tree.leafCount * 2 - 1})",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-4
@@ -77,9 +77,11 @@ class TreeValidationInteropTest {
|
||||
val treeBytes = v.tree.hexToByteArray()
|
||||
val tree = RatchetTree.decodeTls(TlsReader(treeBytes))
|
||||
|
||||
// The root tree hash should match the last entry in tree_hashes
|
||||
val rootHash = tree.treeHash()
|
||||
val rootIdx = BinaryTree.root(tree.leafCount)
|
||||
// tree_hashes has entries for the LOGICAL tree nodes (may be fewer than serialized nodes).
|
||||
// The logical leaf count = (treeHashes.size + 1) / 2
|
||||
val logicalLeafCount = (v.treeHashes.size + 1) / 2
|
||||
val rootIdx = BinaryTree.root(logicalLeafCount)
|
||||
val rootHash = tree.treeHashWithLeafCount(logicalLeafCount)
|
||||
assertEquals(
|
||||
v.treeHashes[rootIdx],
|
||||
rootHash.toHexKey(),
|
||||
@@ -96,7 +98,8 @@ class TreeValidationInteropTest {
|
||||
val treeBytes = v.tree.hexToByteArray()
|
||||
val tree = RatchetTree.decodeTls(TlsReader(treeBytes))
|
||||
|
||||
val nodeCount = BinaryTree.nodeCount(tree.leafCount)
|
||||
// Use the logical node count from resolutions
|
||||
val nodeCount = v.resolutions.size
|
||||
for (nodeIdx in 0 until nodeCount) {
|
||||
if (nodeIdx < v.resolutions.size) {
|
||||
val expected = v.resolutions[nodeIdx]
|
||||
|
||||
Reference in New Issue
Block a user