diff --git a/eth.nimble b/eth.nimble index d962cdd..b54b4f7 100644 --- a/eth.nimble +++ b/eth.nimble @@ -66,6 +66,9 @@ task test_trie, "Run trie tests": task test_db, "Run db tests": runTest("tests/db/all_tests") +task test_ssz, "Run ssz tests": + runTest("tests/ssz/test_merkleization") + task test, "Run all tests": for filename in [ "test_bloom", @@ -78,6 +81,7 @@ task test, "Run all tests": test_p2p_task() test_trie_task() test_db_task() + test_ssz_task() task test_discv5_full, "Run discovery v5 and its dependencies tests": test_keys_task() diff --git a/eth/ssz/merkleization.nim b/eth/ssz/merkleization.nim index 84f365d..8eaff32 100644 --- a/eth/ssz/merkleization.nim +++ b/eth/ssz/merkleization.nim @@ -362,10 +362,12 @@ template createMerkleizer*(totalElements: static Limit): SszMerkleizerImpl = const treeHeight = binaryTreeHeight totalElements var combinedChunks {.noInit.}: array[treeHeight, Digest] - + + let topIndex = treeHeight - 1 + SszMerkleizerImpl( combinedChunks: cast[ptr UncheckedArray[Digest]](addr combinedChunks), - topIndex: treeHeight - 1, + topIndex: if (topIndex < 0): 0 else: topIndex, totalChunks: 0) func getFinalHash*(merkleizer: SszMerkleizerImpl): Digest = diff --git a/tests/ssz/test_merkleization.nim b/tests/ssz/test_merkleization.nim new file mode 100644 index 0000000..4fc6433 --- /dev/null +++ b/tests/ssz/test_merkleization.nim @@ -0,0 +1,75 @@ +import + sequtils, unittest, + nimcrypto/[hash, sha2], stew/endians2, + ../eth/ssz/merkleization + +proc h(a, b: seq[byte]): seq[byte] = + var h: sha256 + h.init() + h.update(a & b) + h.finish().data.toSeq() + +proc e(v: uint32): seq[byte] = + let elem: uint8 = 255 + newSeqWith(28, elem) & v.toBytesLe().toSeq() + +proc z(i: int): seq[byte] = + zerohashes[i].data.toSeq() + +type TestData[limit: static int64] = object + count: uint32 + expectedRoot: seq[byte] + +# only happy cases as our merkleizer will happy accept more chunks than limit +# cases from - https://github.com/ethereum/eth2.0-specs/blob/dev/tests/core/pyspec/eth2spec/utils/test_merkle_minimal.py +let cases = ( + TestData[0](count: 0, expectedRoot: z(0)), + TestData[1](count: 0, expectedRoot: z(0)), + TestData[1](count: 1, expectedRoot: e(0)), + + TestData[2](count: 0, expectedRoot: h(z(0), z(0))), + TestData[2](count: 1, expectedRoot: h(e(0), z(0))), + TestData[2](count: 2, expectedRoot: h(e(0), e(1))), + + TestData[4](count: 0, expectedRoot: h(h(z(0), z(0)), z(1))), + TestData[4](count: 1, expectedRoot: h(h(e(0), z(0)), z(1))), + TestData[4](count: 2, expectedRoot: h(h(e(0), e(1)), z(1))), + TestData[4](count: 3, expectedRoot: h(h(e(0), e(1)), h(e(2), z(0)))), + TestData[4](count: 4, expectedRoot: h(h(e(0), e(1)), h(e(2), e(3)))), + + TestData[8](count: 0, expectedRoot: h(h(h(z(0), z(0)), z(1)), z(2))), + TestData[8](count: 1, expectedRoot: h(h(h(e(0), z(0)), z(1)), z(2))), + TestData[8](count: 2, expectedRoot: h(h(h(e(0), e(1)), z(1)), z(2))), + TestData[8](count: 3, expectedRoot: h(h(h(e(0), e(1)), h(e(2), z(0))), z(2))), + TestData[8](count: 4, expectedRoot: h(h(h(e(0), e(1)), h(e(2), e(3))), z(2))), + TestData[8](count: 5, expectedRoot: h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), z(0)), z(1)))), + TestData[8](count: 6, expectedRoot: h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), e(5)), h(z(0), z(0))))), + TestData[8](count: 7, expectedRoot: h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), e(5)), h(e(6), z(0))))), + TestData[8](count: 8, expectedRoot: h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), e(5)), h(e(6), e(7))))), + + TestData[16](count: 0, expectedRoot: h(h(h(h(z(0), z(0)), z(1)), z(2)), z(3))), + TestData[16](count: 1, expectedRoot: h(h(h(h(e(0), z(0)), z(1)), z(2)), z(3))), + TestData[16](count: 2, expectedRoot: h(h(h(h(e(0), e(1)), z(1)), z(2)), z(3))), + TestData[16](count: 3, expectedRoot: h(h(h(h(e(0), e(1)), h(e(2), z(0))), z(2)), z(3))), + TestData[16](count: 4, expectedRoot: h(h(h(h(e(0), e(1)), h(e(2), e(3))), z(2)), z(3))), + TestData[16](count: 5, expectedRoot: h(h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), z(0)), z(1))), z(3))), + TestData[16](count: 6, expectedRoot: h(h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), e(5)), h(z(0), z(0)))), z(3))), + TestData[16](count: 7, expectedRoot: h(h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), e(5)), h(e(6), z(0)))), z(3))), + TestData[16](count: 8, expectedRoot: h(h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), e(5)), h(e(6), e(7)))), z(3))), + TestData[16](count: 9, expectedRoot: h(h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), e(5)), h(e(6), e(7)))), h(h(h(e(8), z(0)), z(1)), z(2)))) +) + +suite "Merkleization": + test "calculate correct root from provided chunks": + for testCase in cases.fields: + var merk = createMerkleizer(testCase.limit) + var i: uint32 = 0 + + while i < testCase.count: + let elem = e(i) + merk.addChunk(elem) + i = i + 1 + + let calculatedRoot = merk.getFinalhash() + + check calculatedRoot.data.toSeq() == testCase.expectedRoot