## 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 pkg/upraises push: {.upraises: [].} import std/math import std/bitops import std/sequtils import std/sugar import std/algorithm import std/tables import pkg/chronicles import pkg/questionable import pkg/questionable/results import pkg/libp2p/[cid, multicodec, multihash] import pkg/stew/byteutils import ../../errors import ../../blocktype import ../merkletree export merkletree logScope: topics = "codex merkletree" type ByteTreeKey* {.pure.} = enum KeyNone = 0x0.byte KeyBottomLayer = 0x1.byte KeyOdd = 0x2.byte KeyOddAndBottomLayer = 0x3.byte ByteHash* = seq[byte] ByteTree* = MerkleTree[ByteHash, ByteTreeKey] ByteTreeProof* = MerkleProof[ByteHash, ByteTreeKey] CodexMerkleTree* = object of ByteTree mhash: MHash CodexMerkleProof* = object of ByteTreeProof mhash: MHash func getMhash*(mcodec: MultiCodec): ?!MHash = let mhash = CodeHashes.getOrDefault(mcodec) if isNil(mhash.coder): return failure "Invalid multihash codec" success mhash func digestSize*(self: (CodexMerkleTree or CodexMerkleProof)): int = ## Number of leaves ## self.mhash.size func mcodec*(self: (CodexMerkleTree or CodexMerkleProof)): MultiCodec = ## Multicodec ## self.mhash.mcodec func bytes*(mhash: MultiHash): seq[byte] = ## Extract hash bytes ## mhash.data.buffer[mhash.dpos..= self.leavesCount: return failure "Invalid leaf index " & $i let leaf = self.leaves[i] Cid.init( CidVersion.CIDv1, dataCodec, ? MultiHash.init(self.mcodec, self.root).mapFailure).mapFailure func compress*( x, y: openArray[byte], key: ByteTreeKey, mhash: MHash): ?!ByteHash = ## Compress two hashes ## var digest = newSeq[byte](mhash.size) mhash.coder(@x & @y & @[ key.byte ], digest) success digest func init*( _: type CodexMerkleTree, mcodec: MultiCodec, leaves: openArray[ByteHash]): ?!CodexMerkleTree = if leaves.len == 0: return failure "Empty leaves" let mhash = ? mcodec.getMhash() compressor = proc(x, y: seq[byte], key: ByteTreeKey): ?!ByteHash {.noSideEffect.} = compress(x, y, key, mhash) Zero: ByteHash = newSeq[byte](mhash.size) if mhash.size != leaves[0].len: return failure "Invalid hash length" var self = CodexMerkleTree(mhash: mhash, compress: compressor, zero: Zero) self.layers = ? merkleTreeWorker(self, leaves, isBottomLayer = true) success self func init*( _: type CodexMerkleTree, leaves: openArray[MultiHash]): ?!CodexMerkleTree = if leaves.len == 0: return failure "Empty leaves" let mcodec = leaves[0].mcodec leaves = leaves.mapIt( it.bytes ) CodexMerkleTree.init(mcodec, leaves) func init*( _: type CodexMerkleTree, leaves: openArray[Cid]): ?!CodexMerkleTree = if leaves.len == 0: return failure "Empty leaves" let mcodec = (? leaves[0].mhash.mapFailure).mcodec leaves = leaves.mapIt( (? it.mhash.mapFailure).bytes ) CodexMerkleTree.init(mcodec, leaves) func fromNodes*( _: type CodexMerkleTree, mcodec: MultiCodec, nodes: openArray[seq[ByteHash]], nleaves: int): ?!CodexMerkleTree = if nodes.len == 0: return failure "Empty nodes" let mhash = ? mcodec.getMhash() Zero = newSeq[ByteHash](mhash.size) compressor = proc(x, y: openArray[byte], key: ByteTreeKey): ?!ByteHash {.noSideEffect.} = compress(x, y, key, mhash) if mhash.size != nodes[0].len: return failure "Invalid hash length" let self = CodexMerkleTree(compress: compressor, zero: Zero, mhash: mhash) var layer = nleaves pos = 0 while layer > 0: self.layers.add( nodes[pos..