From 61f059e04f8655bacfe272e57dca5f03ef399f88 Mon Sep 17 00:00:00 2001 From: Tomasz Bekas Date: Wed, 23 Aug 2023 18:38:58 +0200 Subject: [PATCH] Merkle trees can use codecs different than sha256 --- codex/merkletree/merkletree.nim | 184 +++++++++++++--------- tests/codex/merkletree/testmerkletree.nim | 153 +++++++++--------- 2 files changed, 190 insertions(+), 147 deletions(-) diff --git a/codex/merkletree/merkletree.nim b/codex/merkletree/merkletree.nim index 16fb236a..7c3442df 100644 --- a/codex/merkletree/merkletree.nim +++ b/codex/merkletree/merkletree.nim @@ -9,23 +9,30 @@ import std/math import std/bitops -import std/strutils +import std/sequtils +import std/sugar import pkg/questionable/results import pkg/nimcrypto/sha2 +import pkg/libp2p/[multicodec, multihash, vbuffer] -const digestSize = sha256.sizeDigest +import ../errors type - MerkleHash* = array[digestSize, byte] MerkleTree* = object + mcodec: MultiCodec + digestSize: Natural leavesCount: Natural - nodes: seq[MerkleHash] + nodesBuffer: seq[byte] MerkleProof* = object + mcodec: MultiCodec + digestSize: Natural index: Natural - path: seq[MerkleHash] + nodesBuffer: seq[byte] MerkleTreeBuilder* = object - buffer: seq[MerkleHash] + mcodec: MultiCodec + digestSize: Natural + buffer: seq[byte] ########################################################### # Helper functions @@ -37,41 +44,41 @@ func computeTreeHeight(leavesCount: int): int = else: fastLog2(leavesCount) + 2 -func computeLevels(leavesCount: int): seq[tuple[offset: int, width: int]] = +func computeLevels(leavesCount: int): seq[tuple[offset: int, width: int, index: int]] = let height = computeTreeHeight(leavesCount) - result = newSeq[tuple[offset: int, width: int]](height) + var levels = newSeq[tuple[offset: int, width: int, index: int]](height) - result[0].offset = 0 - result[0].width = leavesCount + levels[0].offset = 0 + levels[0].width = leavesCount + levels[0].index = 0 for i in 1.. self.nodeBufferToMultiHash(i)) -proc nodes*(self: MerkleTree): seq[MerkleHash] = - self.nodes +proc root*(self: MerkleTree): MultiHash = + let rootIndex = self.len - 1 + self.nodeBufferToMultiHash(rootIndex) + +proc leaves*(self: MerkleTree): seq[MultiHash] = + toSeq(0.. self.nodeBufferToMultiHash(i)) proc height*(self: MerkleTree): Natural = computeTreeHeight(self.leavesCount) @@ -157,32 +179,39 @@ proc getProof*(self: MerkleTree, index: Natural): ?!MerkleProof = ## Proofs of inclusion (index and path) are ## - 0,[H1, H4] for data block A ## - 1,[H0, H4] for data block B - ## - 2,[HZ, H3] for data block C - ## - ## where HZ=0x0b + ## - 2,[0x00, H3] for data block C ## if index >= self.leavesCount: - return failure("Index " & $index & " out of range [0.." & $self.leaves.high & "]" ) + return failure("Index " & $index & " out of range [0.." & $(self.leavesCount - 1) & "]" ) + + var zero = newSeq[byte](self.digestSize) + var one = newSeq[byte](self.digestSize) + one[^1] = 0x01 let levels = computeLevels(self.leavesCount) - var path = newSeq[MerkleHash](levels.len - 1) - for levelIndex, level in levels[0..^2]: - let i = index div (1 shl levelIndex) + var proofNodesBuffer = newSeq[byte]((levels.len - 1) * self.digestSize) + for level in levels[0..^2]: + let i = index div (1 shl level.index) let siblingIndex = if i mod 2 == 0: level.offset + i + 1 else: level.offset + i - 1 - - if siblingIndex < level.offset + level.width: - path[levelIndex] = self.nodes[siblingIndex] - else: - path[levelIndex] = zeroHash - success(MerkleProof(index: index, path: path)) + var dummyValue = if level.index == 0: zero else: one + + if siblingIndex < level.offset + level.width: + copyMem(addr proofNodesBuffer[level.index * self.digestSize], unsafeAddr self.nodesBuffer[siblingIndex * self.digestSize], self.digestSize) + else: + copyMem(addr proofNodesBuffer[level.index * self.digestSize], addr dummyValue[0], self.digestSize) + + # path[levelIndex] = zeroHash + + success(MerkleProof(mcodec: self.mcodec, digestSize: self.digestSize, index: index, nodesBuffer: proofNodesBuffer)) proc `$`*(self: MerkleTree): string = - result &= "leavesCount: " & $self.leavesCount - result &= "\nnodes: " & $self.nodes + "mcodec:" & $self.mcodec & + "\nleavesCount: " & $self.leavesCount & + "\nnodes: " & $self.nodes ########################################################### # MerkleProof @@ -191,19 +220,28 @@ proc `$`*(self: MerkleTree): string = proc index*(self: MerkleProof): Natural = self.index -proc path*(self: MerkleProof): seq[MerkleHash] = - self.path - proc `$`*(self: MerkleProof): string = - result &= "index: " & $self.index - result &= "\npath: " & $self.path + "mcodec:" & $self.mcodec & + "\nindex: " & $self.index & + "\nnodes: " & $self.nodes func `==`*(a, b: MerkleProof): bool = - (a.index == b.index) and (a.path == b.path) + (a.index == b.index) and (a.mcodec == b.mcodec) and (a.digestSize == b.digestSize) == (a.nodesBuffer == b.nodesBuffer) proc init*( T: type MerkleProof, index: Natural, - path: seq[MerkleHash] -): MerkleProof = - MerkleProof(index: index, path: path) + nodes: seq[MultiHash] +): ?!MerkleProof = + if nodes.len == 0: + return failure("At least one node is required") + + let + mcodec = nodes[0].mcodec + digestSize = nodes[0].size + + var nodesBuffer = newSeq[byte](nodes.len * digestSize) + for nodeIndex, node in nodes: + copyMem(addr nodesBuffer[nodeIndex * digestSize], unsafeAddr node.data.buffer[node.dpos], digestSize) + + success(MerkleProof(mcodec: mcodec, digestSize: digestSize, index: index, nodesBuffer: nodesBuffer)) diff --git a/tests/codex/merkletree/testmerkletree.nim b/tests/codex/merkletree/testmerkletree.nim index 132b601e..ed0ed9fd 100644 --- a/tests/codex/merkletree/testmerkletree.nim +++ b/tests/codex/merkletree/testmerkletree.nim @@ -23,28 +23,35 @@ checksuite "merkletree": "8901234567890123456789012345678901234567".toBytes, "9012345678901234567890123456789012345678".toBytes, ] - var zeroHash: MerkleHash - var expectedLeaves: array[data.len, MerkleHash] - var builder: MerkleTreeBuilder - proc combine(a, b: MerkleHash): MerkleHash = - var buf = newSeq[byte](a.len + b.len) - for i in 0..