## Nim-Codex ## Copyright (c) 2023 Status Research & Development GmbH ## Licensed under either of ## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) ## * MIT license ([LICENSE-MIT](LICENSE-MIT)) ## at your option. ## This file may not be copied, modified, or distributed except according to ## those terms. import std/math import std/bitops import std/sequtils import std/sugar import std/algorithm import pkg/chronicles import pkg/questionable import pkg/questionable/results import pkg/nimcrypto/sha2 import pkg/libp2p/[cid, multicodec, multihash, vbuffer] import pkg/stew/byteutils import ../errors logScope: topics = "codex merkletree" type MerkleTree* = object mcodec: MultiCodec digestSize: Natural leavesCount: Natural nodesBuffer*: seq[byte] MerkleProof* = object mcodec: MultiCodec digestSize: Natural index: Natural nodesBuffer*: seq[byte] MerkleTreeBuilder* = object mcodec: MultiCodec digestSize: Natural buffer: seq[byte] ########################################################### # Helper functions ########################################################### func computeTreeHeight(leavesCount: int): int = if isPowerOfTwo(leavesCount): fastLog2(leavesCount) + 1 else: fastLog2(leavesCount) + 2 func computeLevels(leavesCount: int): seq[tuple[offset: int, width: int, index: int]] = let height = computeTreeHeight(leavesCount) var levels = newSeq[tuple[offset: int, width: int, index: int]](height) levels[0].offset = 0 levels[0].width = leavesCount levels[0].index = 0 for i in 1.. dst.len: return failure("Not enough space in a destination buffer") dst[dstPos.. self.nodeBufferToMultiHash(i)) proc mcodec*(self: (MerkleTree | MerkleProof)): MultiCodec = self.mcodec proc digestSize*(self: (MerkleTree | MerkleProof)): Natural = self.digestSize proc root*(self: MerkleTree): MultiHash = let rootIndex = self.len - 1 self.nodeBufferToMultiHash(rootIndex) proc rootCid*(self: MerkleTree, version = CIDv1, dataCodec = multiCodec("raw")): ?!Cid = Cid.init(version, dataCodec, self.root).mapFailure iterator leaves*(self: MerkleTree): MultiHash = for i in 0..= self.leavesCount: return failure("Index " & $index & " out of range [0.." & $(self.leavesCount - 1) & "]" ) success(self.nodeBufferToMultiHash(index)) proc getLeafCid*(self: MerkleTree, index: Natural, version = CIDv1, dataCodec = multiCodec("raw")): ?!Cid = let leaf = ? self.getLeaf(index) Cid.init(version, dataCodec, leaf).mapFailure proc height*(self: MerkleTree): Natural = computeTreeHeight(self.leavesCount) proc getProof*(self: MerkleTree, index: Natural): ?!MerkleProof = ## Extracts proof from a tree for a given index ## ## Given a tree built from data blocks A, B and C ## H5 ## / \ ## H3 H4 ## / \ / ## H0 H1 H2 ## | | | ## A B C ## ## Proofs of inclusion (index and path) are ## - 0,[H1, H4] for data block A ## - 1,[H0, H4] for data block B ## - 2,[0x00, H3] for data block C ## if index >= self.leavesCount: 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 proofNodesBuffer = newSeq[byte]((levels.len - 1) * self.digestSize) for level in levels[0..^2]: let lr = index shr level.index let siblingIndex = if lr mod 2 == 0: level.offset + lr + 1 else: level.offset + lr - 1 var dummyValue = if level.index == 0: zero else: one if siblingIndex < level.offset + level.width: proofNodesBuffer[level.index * self.digestSize..<(level.index + 1) * self.digestSize] = self.nodesBuffer[siblingIndex * self.digestSize..<(siblingIndex + 1) * self.digestSize] else: proofNodesBuffer[level.index * self.digestSize..<(level.index + 1) * self.digestSize] = dummyValue success(MerkleProof(mcodec: self.mcodec, digestSize: self.digestSize, index: index, nodesBuffer: proofNodesBuffer)) proc `$`*(self: MerkleTree): string {.noSideEffect.} = "mcodec:" & $self.mcodec & ", digestSize: " & $self.digestSize & ", leavesCount: " & $self.leavesCount & ", nodes: " & $self.nodes proc `==`*(a, b: MerkleTree): bool = (a.mcodec == b.mcodec) and (a.digestSize == b.digestSize) and (a.leavesCount == b.leavesCount) and (a.nodesBuffer == b.nodesBuffer) proc init*( T: type MerkleTree, mcodec: MultiCodec, digestSize: Natural, leavesCount: Natural, nodesBuffer: seq[byte] ): ?!MerkleTree = let levels = computeLevels(leavesCount) let totalNodes = levels[^1].offset + 1 if totalNodes * digestSize == nodesBuffer.len: success( MerkleTree( mcodec: mcodec, digestSize: digestSize, leavesCount: leavesCount, nodesBuffer: nodesBuffer ) ) else: failure("Expected nodesBuffer len to be " & $(totalNodes * digestSize) & " but was " & $nodesBuffer.len) proc init*( T: type MerkleTree, leaves: openArray[MultiHash] ): ?!MerkleTree = without leaf =? leaves.?[0]: return failure("At least one leaf is required") var builder = ? MerkleTreeBuilder.init(mcodec = leaf.mcodec) for l in leaves: let res = builder.addLeaf(l) if res.isErr: return failure(res.error) builder.build() proc init*( T: type MerkleTree, cids: openArray[Cid] ): ?!MerkleTree = var leaves = newSeq[MultiHash]() for cid in cids: let res = cid.mhash.mapFailure if res.isErr: return failure(res.error) else: leaves.add(res.value) MerkleTree.init(leaves) ########################################################### # MerkleProof ########################################################### proc verifyLeaf*(self: MerkleProof, leaf: MultiHash, treeRoot: MultiHash): ?!bool = if leaf.mcodec != self.mcodec: return failure("Leaf mcodec was " & $leaf.mcodec & ", but " & $self.mcodec & " expected") if leaf.mcodec != self.mcodec: return failure("Tree root mcodec was " & $treeRoot.mcodec & ", but " & $treeRoot.mcodec & " expected") var digestBuf = newSeq[byte](self.digestSize) digestBuf[0..^1] = leaf.data.buffer[leaf.dpos..<(leaf.dpos + self.digestSize)] let proofLen = self.nodesBuffer.len div self.digestSize var concatBuf = newSeq[byte](2 * self.digestSize) for i in 0..