Coders for merkle trees
This commit is contained in:
parent
61f059e04f
commit
f9ee4ea7af
|
@ -0,0 +1,4 @@
|
||||||
|
import ./merkletree/merkletree
|
||||||
|
import ./merkletree/coders
|
||||||
|
|
||||||
|
export merkletree, coders
|
|
@ -0,0 +1,46 @@
|
||||||
|
## 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/libp2p
|
||||||
|
import pkg/questionable
|
||||||
|
import pkg/questionable/results
|
||||||
|
|
||||||
|
import ./merkletree
|
||||||
|
import ../units
|
||||||
|
import ../errors
|
||||||
|
|
||||||
|
const MaxMerkleTreeSize = 100.MiBs.uint
|
||||||
|
|
||||||
|
proc encode*(self: MerkleTree): seq[byte] =
|
||||||
|
var pb = initProtoBuffer(maxSize = MaxMerkleTreeSize)
|
||||||
|
pb.write(1, self.mcodec.uint64)
|
||||||
|
pb.write(2, self.digestSize.uint64)
|
||||||
|
pb.write(3, self.leavesCount.uint64)
|
||||||
|
pb.write(4, self.nodesBuffer)
|
||||||
|
pb.finish
|
||||||
|
pb.buffer
|
||||||
|
|
||||||
|
proc decode*(_: type MerkleTree, data: seq[byte]): ?!MerkleTree =
|
||||||
|
var pb = initProtoBuffer(data, maxSize = MaxMerkleTreeSize)
|
||||||
|
var mcodecCode: uint64
|
||||||
|
var digestSize: uint64
|
||||||
|
var leavesCount: uint64
|
||||||
|
discard ? pb.getField(1, mcodecCode).mapFailure
|
||||||
|
discard ? pb.getField(2, digestSize).mapFailure
|
||||||
|
discard ? pb.getField(3, leavesCount).mapFailure
|
||||||
|
|
||||||
|
let mcodec = MultiCodec.codec(cast[int](mcodecCode))
|
||||||
|
if mcodec == InvalidMultiCodec:
|
||||||
|
return failure("Invalid MultiCodec code " & $cast[int](mcodec))
|
||||||
|
|
||||||
|
var nodesBuffer = newSeq[byte]()
|
||||||
|
discard ? pb.getField(4, nodesBuffer).mapFailure
|
||||||
|
|
||||||
|
let tree = ? MerkleTree.init(mcodec, digestSize, leavesCount, nodesBuffer)
|
||||||
|
success(tree)
|
|
@ -15,6 +15,7 @@ import std/sugar
|
||||||
import pkg/questionable/results
|
import pkg/questionable/results
|
||||||
import pkg/nimcrypto/sha2
|
import pkg/nimcrypto/sha2
|
||||||
import pkg/libp2p/[multicodec, multihash, vbuffer]
|
import pkg/libp2p/[multicodec, multihash, vbuffer]
|
||||||
|
import pkg/stew/base58
|
||||||
|
|
||||||
import ../errors
|
import ../errors
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ type
|
||||||
mcodec: MultiCodec
|
mcodec: MultiCodec
|
||||||
digestSize: Natural
|
digestSize: Natural
|
||||||
leavesCount: Natural
|
leavesCount: Natural
|
||||||
nodesBuffer: seq[byte]
|
nodesBuffer*: seq[byte]
|
||||||
MerkleProof* = object
|
MerkleProof* = object
|
||||||
mcodec: MultiCodec
|
mcodec: MultiCodec
|
||||||
digestSize: Natural
|
digestSize: Natural
|
||||||
|
@ -111,8 +112,8 @@ proc build*(self: MerkleTreeBuilder): ?!MerkleTree =
|
||||||
copyMem(addr tree.nodesBuffer[0], unsafeAddr self.buffer[0], leavesCount * digestSize)
|
copyMem(addr tree.nodesBuffer[0], unsafeAddr self.buffer[0], leavesCount * digestSize)
|
||||||
|
|
||||||
# calculate intermediate nodes
|
# calculate intermediate nodes
|
||||||
var zero = newSeq[byte](self.digestSize)
|
var zero = newSeq[byte](digestSize)
|
||||||
var one = newSeq[byte](self.digestSize)
|
var one = newSeq[byte](digestSize)
|
||||||
one[^1] = 0x01
|
one[^1] = 0x01
|
||||||
|
|
||||||
var concatBuf = newSeq[byte](2 * digestSize)
|
var concatBuf = newSeq[byte](2 * digestSize)
|
||||||
|
@ -154,6 +155,12 @@ proc len*(self: (MerkleTree | MerkleProof)): Natural =
|
||||||
proc nodes*(self: (MerkleTree | MerkleProof)): seq[MultiHash] =
|
proc nodes*(self: (MerkleTree | MerkleProof)): seq[MultiHash] =
|
||||||
toSeq(0..<self.len).map(i => self.nodeBufferToMultiHash(i))
|
toSeq(0..<self.len).map(i => self.nodeBufferToMultiHash(i))
|
||||||
|
|
||||||
|
proc mcodec*(self: (MerkleTree | MerkleProof)): MultiCodec =
|
||||||
|
self.mcodec
|
||||||
|
|
||||||
|
proc digestSize*(self: (MerkleTree | MerkleProof)): Natural =
|
||||||
|
self.digestSize
|
||||||
|
|
||||||
proc root*(self: MerkleTree): MultiHash =
|
proc root*(self: MerkleTree): MultiHash =
|
||||||
let rootIndex = self.len - 1
|
let rootIndex = self.len - 1
|
||||||
self.nodeBufferToMultiHash(rootIndex)
|
self.nodeBufferToMultiHash(rootIndex)
|
||||||
|
@ -161,6 +168,9 @@ proc root*(self: MerkleTree): MultiHash =
|
||||||
proc leaves*(self: MerkleTree): seq[MultiHash] =
|
proc leaves*(self: MerkleTree): seq[MultiHash] =
|
||||||
toSeq(0..<self.leavesCount).map(i => self.nodeBufferToMultiHash(i))
|
toSeq(0..<self.leavesCount).map(i => self.nodeBufferToMultiHash(i))
|
||||||
|
|
||||||
|
proc leavesCount*(self: MerkleTree): Natural =
|
||||||
|
self.leavesCount
|
||||||
|
|
||||||
proc height*(self: MerkleTree): Natural =
|
proc height*(self: MerkleTree): Natural =
|
||||||
computeTreeHeight(self.leavesCount)
|
computeTreeHeight(self.leavesCount)
|
||||||
|
|
||||||
|
@ -213,6 +223,33 @@ proc `$`*(self: MerkleTree): string =
|
||||||
"\nleavesCount: " & $self.leavesCount &
|
"\nleavesCount: " & $self.leavesCount &
|
||||||
"\nnodes: " & $self.nodes
|
"\nnodes: " & $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)
|
||||||
|
|
||||||
|
func 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)
|
||||||
|
|
||||||
###########################################################
|
###########################################################
|
||||||
# MerkleProof
|
# MerkleProof
|
||||||
###########################################################
|
###########################################################
|
||||||
|
@ -226,7 +263,10 @@ proc `$`*(self: MerkleProof): string =
|
||||||
"\nnodes: " & $self.nodes
|
"\nnodes: " & $self.nodes
|
||||||
|
|
||||||
func `==`*(a, b: MerkleProof): bool =
|
func `==`*(a, b: MerkleProof): bool =
|
||||||
(a.index == b.index) and (a.mcodec == b.mcodec) and (a.digestSize == b.digestSize) == (a.nodesBuffer == b.nodesBuffer)
|
(a.index == b.index) and
|
||||||
|
(a.mcodec == b.mcodec) and
|
||||||
|
(a.digestSize == b.digestSize) and
|
||||||
|
(a.nodesBuffer == b.nodesBuffer)
|
||||||
|
|
||||||
proc init*(
|
proc init*(
|
||||||
T: type MerkleProof,
|
T: type MerkleProof,
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
import std/unittest
|
||||||
|
|
||||||
|
import pkg/questionable/results
|
||||||
|
import pkg/stew/byteutils
|
||||||
|
|
||||||
|
import pkg/codex/merkletree
|
||||||
|
import ../helpers
|
||||||
|
|
||||||
|
checksuite "merkletree - coders":
|
||||||
|
const data =
|
||||||
|
[
|
||||||
|
"0123456789012345678901234567890123456789".toBytes,
|
||||||
|
"1234567890123456789012345678901234567890".toBytes,
|
||||||
|
"2345678901234567890123456789012345678901".toBytes,
|
||||||
|
"3456789012345678901234567890123456789012".toBytes,
|
||||||
|
"4567890123456789012345678901234567890123".toBytes,
|
||||||
|
"5678901234567890123456789012345678901234".toBytes,
|
||||||
|
"6789012345678901234567890123456789012345".toBytes,
|
||||||
|
"7890123456789012345678901234567890123456".toBytes,
|
||||||
|
"8901234567890123456789012345678901234567".toBytes,
|
||||||
|
"9012345678901234567890123456789012345678".toBytes,
|
||||||
|
]
|
||||||
|
|
||||||
|
test "encoding and decoding a tree yields the same tree":
|
||||||
|
var builder = MerkleTreeBuilder.init(multiCodec("sha2-256")).tryGet()
|
||||||
|
builder.addDataBlock(data[0]).tryGet()
|
||||||
|
builder.addDataBlock(data[1]).tryGet()
|
||||||
|
builder.addDataBlock(data[2]).tryGet()
|
||||||
|
builder.addDataBlock(data[3]).tryGet()
|
||||||
|
builder.addDataBlock(data[4]).tryGet()
|
||||||
|
builder.addDataBlock(data[5]).tryGet()
|
||||||
|
builder.addDataBlock(data[6]).tryGet()
|
||||||
|
builder.addDataBlock(data[7]).tryGet()
|
||||||
|
builder.addDataBlock(data[8]).tryGet()
|
||||||
|
builder.addDataBlock(data[9]).tryGet()
|
||||||
|
|
||||||
|
let tree = builder.build().tryGet()
|
||||||
|
let encodedBytes = tree.encode()
|
||||||
|
echo "encode success, size " & $encodedBytes.len
|
||||||
|
let decodedTree = MerkleTree.decode(encodedBytes).tryGet()
|
||||||
|
|
||||||
|
check:
|
||||||
|
tree == decodedTree
|
|
@ -6,7 +6,7 @@ import pkg/questionable/results
|
||||||
import pkg/stew/byteutils
|
import pkg/stew/byteutils
|
||||||
import pkg/nimcrypto/sha2
|
import pkg/nimcrypto/sha2
|
||||||
|
|
||||||
import codex/merkletree/merkletree
|
import pkg/codex/merkletree
|
||||||
import ../helpers
|
import ../helpers
|
||||||
|
|
||||||
checksuite "merkletree":
|
checksuite "merkletree":
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
import ./merkletree/testmerkletree
|
import ./merkletree/testmerkletree
|
||||||
|
import ./merkletree/testcoders
|
||||||
|
|
||||||
{.warning[UnusedImport]: off.}
|
{.warning[UnusedImport]: off.}
|
||||||
|
|
Loading…
Reference in New Issue