From d4d4e8c28fec8aab4a4c8e919ff31a6a4970c580 Mon Sep 17 00:00:00 2001 From: Kim De Mey Date: Thu, 23 Jun 2022 21:00:59 +0200 Subject: [PATCH] Add accumulator buildProof, verifyProof and verifyHeader (#1132) --- fluffy/network/history/accumulator.nim | 100 +++++++++++++++++++++++++ fluffy/tests/test_accumulator.nim | 58 ++++++++++++++ vendor/nim-ssz-serialization | 2 +- 3 files changed, 159 insertions(+), 1 deletion(-) diff --git a/fluffy/network/history/accumulator.nim b/fluffy/network/history/accumulator.nim index 1294388c3..c33337293 100644 --- a/fluffy/network/history/accumulator.nim +++ b/fluffy/network/history/accumulator.nim @@ -217,3 +217,103 @@ proc buildAccumulator*(dataFile: string): Result[Accumulator, string] = headers[header.blockNumber.truncate(int)] = header ok(buildAccumulator(headers)) + +## Calls and helper calls for building header proofs and verifying headers +## against the Accumulator and the header proofs. + +func inCurrentEpoch*(header: BlockHeader, a: Accumulator): bool = + let blockNumber = header.blockNumber.truncate(uint64) + + blockNumber > uint64(a.historicalEpochs.len() * epochSize) - 1 + +func getEpochIndex(header: BlockHeader): uint64 = + ## Get the index for the historical epochs + header.blockNumber.truncate(uint64) div epochSize + +func getHeaderRecordIndex(header: BlockHeader, epochIndex: uint64): uint64 = + ## Get the relative header index for the epoch accumulator + uint64(header.blockNumber.truncate(uint64) - epochIndex * epochSize) + +proc buildProof*(db: AccumulatorDB, header: BlockHeader): + Result[seq[Digest], string] = + let accumulatorOpt = db.getAccumulator() + if accumulatorOpt.isNone(): + return err("Master accumulator not found in database") + + let + accumulator = accumulatorOpt.get() + epochIndex = getEpochIndex(header) + epochHash = Digest(data: accumulator.historicalEpochs[epochIndex]) + + key = ContentKey( + contentType: epochAccumulator, + epochAccumulatorKey: EpochAccumulatorKey( + epochHash: epochHash)) + + epochAccumulatorOpt = db.getEpochAccumulator(key.toContentId()) + + if epochAccumulatorOpt.isNone(): + return err("Epoch accumulator not found in database") + + let + epochAccumulator = epochAccumulatorOpt.get() + headerRecordIndex = getHeaderRecordIndex(header, epochIndex) + # TODO: Implement more generalized `get_generalized_index` + gIndex = GeneralizedIndex(epochSize*2*2 + (headerRecordIndex*2)) + + epochAccumulator.build_proof(gIndex) + +func verifyProof*( + a: Accumulator, proof: openArray[Digest], header: BlockHeader): bool = + let + epochIndex = getEpochIndex(header) + epochAccumulatorHash = Digest(data: a.historicalEpochs[epochIndex]) + + leave = hash_tree_root(header.blockHash()) + headerRecordIndex = getHeaderRecordIndex(header, epochIndex) + + # TODO: Implement more generalized `get_generalized_index` + gIndex = GeneralizedIndex(epochSize*2*2 + (headerRecordIndex*2)) + + verify_merkle_multiproof(@[leave], proof, @[gIndex], epochAccumulatorHash) + +proc verifyProof*( + db: AccumulatorDB, proof: openArray[Digest], header: BlockHeader): + Result[void, string] = + let accumulatorOpt = db.getAccumulator() + if accumulatorOpt.isNone(): + return err("Master accumulator not found in database") + + if accumulatorOpt.get().verifyProof(proof, header): + ok() + else: + err("Proof verification failed") + +proc verifyHeader*( + db: AccumulatorDB, header: BlockHeader, proof: Option[seq[Digest]]): + Result[void, string] = + let accumulatorOpt = db.getAccumulator() + if accumulatorOpt.isNone(): + return err("Master accumulator not found in database") + + let accumulator = accumulatorOpt.get() + + if header.inCurrentEpoch(accumulator): + let blockNumber = header.blockNumber.truncate(uint64) + let relIndex = blockNumber - uint64(accumulator.historicalEpochs.len()) * epochSize + + if relIndex > uint64(accumulator.currentEpoch.len() - 1): + return err("Blocknumber ahead of accumulator") + + if accumulator.currentEpoch[relIndex].blockHash == header.blockHash(): + ok() + else: + err("Header not part of canonical chain") + else: + if proof.isSome(): + if accumulator.verifyProof(proof.get, header): + ok() + else: + err("Proof verification failed") + else: + err("Need proof to verify header") diff --git a/fluffy/tests/test_accumulator.nim b/fluffy/tests/test_accumulator.nim index ec93e41b9..40fe18ca3 100644 --- a/fluffy/tests/test_accumulator.nim +++ b/fluffy/tests/test_accumulator.nim @@ -48,3 +48,61 @@ suite "Header Accumulator": updateAccumulator(accumulator, headers[i]) check accumulator.hash_tree_root().data.toHex() == hashTreeRoots[i] + + test "Header Accumulator Proofs": + const + # Amount of headers to be created and added to the accumulator + amount = 25000 + # Headers to test verification for + headersToTest = [ + 0, + epochSize - 1, + epochSize, + epochSize*2 - 1, + epochSize*2, + epochSize*3 - 1, + epochSize*3, + epochSize*3 + 1, + amount - 1] + + var headers: seq[BlockHeader] + for i in 0.. than latest in accumulator + let header = BlockHeader(blockNumber: 25000.stuint(256)) + check db.verifyHeader(header, none(seq[Digest])).isErr() + + # Test different block headers by altering the difficulty + for i in headersToTest: + let header = BlockHeader( + blockNumber: i.stuint(256), difficulty: 2.stuint(256)) + + check db.verifyHeader(header, none(seq[Digest])).isErr() diff --git a/vendor/nim-ssz-serialization b/vendor/nim-ssz-serialization index cd500484e..da3c08c16 160000 --- a/vendor/nim-ssz-serialization +++ b/vendor/nim-ssz-serialization @@ -1 +1 @@ -Subproject commit cd500484e054ead951f2d07aeb81c1c8c695db26 +Subproject commit da3c08c16da2e4d2e9f48556fbdbf90bfff22172