diff --git a/eth.nimble b/eth.nimble index b54b4f7..50e1f42 100644 --- a/eth.nimble +++ b/eth.nimble @@ -67,7 +67,7 @@ task test_db, "Run db tests": runTest("tests/db/all_tests") task test_ssz, "Run ssz tests": - runTest("tests/ssz/test_merkleization") + runTest("tests/ssz/all_tests") task test, "Run all tests": for filename in [ diff --git a/eth/ssz/merkleization.nim b/eth/ssz/merkleization.nim index 8eaff32..172d4d7 100644 --- a/eth/ssz/merkleization.nim +++ b/eth/ssz/merkleization.nim @@ -12,6 +12,7 @@ {.push raises: [Defect].} import + math, stew/[bitops2, endians2, ptrops], stew/ranges/ptr_arith, nimcrypto/[hash, sha2], serialization/testing/tracing, @@ -609,3 +610,29 @@ func hash_tree_root*(x: auto): Digest {.raises: [Defect].} = trs "HASH TREE ROOT FOR ", name(type x), " = ", "0x", $result +# https://github.com/ethereum/eth2.0-specs/blob/dev/ssz/merkle-proofs.md#get_generalized_index_length +func getGeneralizedIndexLength(x: uint64): int = + log2trunc(x) + +# https://github.com/ethereum/eth2.0-specs/blob/dev/ssz/merkle-proofs.md#get_generalized_index_bit +func getGeneralizedIndexBit(index: uint64, position: uint64): bool = + (index and (1'u64 shl position)) > 0 + +# validates merkle proof. Provided index should be a generalized index of leaf node +# as defined in: https://github.com/ethereum/eth2.0-specs/blob/dev/ssz/merkle-proofs.md#generalized-merkle-tree-index +func isValidProof*(leaf: Digest, proof: openArray[Digest], + index: uint64, root: Digest): bool = + if len(proof) == getGeneralizedIndexLength(index): + var + value = leaf + + for i, digest in proof: + value = + if getGeneralizedIndexBit(index, uint64 i): + mergeBranches(digest, value) + else: + mergeBranches(value, digest) + + value == root + else: + false diff --git a/tests/ssz/all_tests.nim b/tests/ssz/all_tests.nim new file mode 100644 index 0000000..c6d8857 --- /dev/null +++ b/tests/ssz/all_tests.nim @@ -0,0 +1,3 @@ +import + ./test_merkleization, + ./test_verification diff --git a/tests/ssz/test_merkleization.nim b/tests/ssz/test_merkleization.nim index 4fc6433..d004676 100644 --- a/tests/ssz/test_merkleization.nim +++ b/tests/ssz/test_merkleization.nim @@ -1,3 +1,5 @@ +{.used.} + import sequtils, unittest, nimcrypto/[hash, sha2], stew/endians2, diff --git a/tests/ssz/test_verification.nim b/tests/ssz/test_verification.nim new file mode 100644 index 0000000..00c299b --- /dev/null +++ b/tests/ssz/test_verification.nim @@ -0,0 +1,82 @@ +{.used.} + +import + sequtils, unittest, + nimcrypto/[hash, sha2], + ../eth/ssz/merkleization + +type TestCase = object + root: string + proof: seq[string] + leaf: string + index: uint64 + valid: bool + +let testCases = @[ + TestCase( + root: "2a23ef2b7a7221eaac2ffb3842a506a981c009ca6c2fcbf20adbc595e56f1a93", + proof: @[ + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b" + ], + leaf: "0100000000000000000000000000000000000000000000000000000000000000", + index: 4, + valid: true + ), + TestCase( + root: "2a23ef2b7a7221eaac2ffb3842a506a981c009ca6c2fcbf20adbc595e56f1a93", + proof: @[ + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b" + ], + leaf: "0100000000000000000000000000000000000000000000000000000000000000", + index: 6, + valid: false + ), + TestCase( + root: "2a23ef2b7a7221eaac2ffb3842a506a981c009ca6c2fcbf20adbc595e56f1a93", + proof: @[ + "0100000000000000000000000000000000000000000000000000000000000000", + "f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b" + ], + leaf: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + index: 5, + valid: true + ), + TestCase( + root: "f1824b0084956084591ff4c91c11bcc94a40be82da280e5171932b967dd146e9", + proof: @[ + "35210d64853aee79d03f30cf0f29c1398706cbbcacaf05ab9524f00070aec91e", + "f38a181470ef1eee90a29f0af0a9dba6b7e5d48af3c93c29b4f91fa11b777582" + ], + leaf: "0100000000000000000000000000000000000000000000000000000000000000", + index: 7, + valid: true + ), + TestCase( + root: "f1824b0084956084591ff4c91c11bcc94a40be82da280e5171932b967dd146e9", + proof: @[ + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", + "0100000000000000000000000000000000000000000000000000000000000000", + "f38a181470ef1eee90a29f0af0a9dba6b7e5d48af3c93c29b4f91fa11b777582" + ], + leaf: "6001000000000000000000000000000000000000000000000000000000000000", + index: 49, + valid: true + ) +] + +suite "Merkle Proof verification": + test "correctly verify proof": + for testCase in testCases: + let root = MDigest[256].fromHex(testCase.root) + let proof = map(testCase.proof, proc(x: string): Digest = MDigest[256].fromHex(x)) + let leaf = MDigest[256].fromHex(testCase.leaf) + let valid = isValidProof(leaf, proof, testCase.index, root) + + if (testCase.valid): + check valid + else: + check (not valid)