Before cleaning code for the Merkle tree verification

This commit is contained in:
Benjamin Loison 2023-04-13 00:29:48 +02:00
parent 126bd7c16a
commit e58af4e3d0
Signed by: Benjamin_Loison
SSH Key Fingerprint: SHA256:BtnEgYTlHdOg1u+RmYcDE0mnfz1rhv5dSbQ2gyxW8B8
3 changed files with 81 additions and 27 deletions

View File

@ -7,11 +7,15 @@ MAXIMUM_HASH_DIFFICULTY = 1 / 16
# The following space is a minimum.
SPACE_TO_PROVE_IN_BITS = 1_000
ENTRIES_NUMBER = math.ceil(SPACE_TO_PROVE_IN_BITS // int(math.log2(1 / MAXIMUM_HASH_DIFFICULTY)))
# To ease Merkle tree implementation.
ENTRIES_NUMBER = 2 ** math.ceil(math.log2(ENTRIES_NUMBER))
def hash(x):
return sha256(x.encode('ascii')).hexdigest()
# Storage could be done by using a separator but is it optimal? In fact how to do a separator optimally in binary? Could for instance encode on two bits, for normal bits the first one should be 0 otherwise if the first is 1 then whatever the second bit, it's a separator.
# TODO: sure that this function is meaningly correct? cf below comment, should also consider the serialization of the actual array
# TODO: sure that this function is meaningly correct? cf below comment, should also consider the serialization of the actual array - should in a similar way verify `ENTRIES_NUMBER` computation
# Have to pay attention that `0` isn't interesting us.
def numberOfBitsOf(n):
return math.floor(math.log2(n)) + 1

View File

@ -15,14 +15,13 @@ protocolInitializationPhaseId = input('protocolInitializationPhaseId: ')
# Could rename `STORED_DATA` to what it is actually storing.
STORED_DATA = []
STORED_DATA_IN_BITS = 0
counter = 0
lastCounter = 0
LEAST_DIFFICULT_HASH = int('F' * (common.SECURITY_PARAMETER // 4), 16)
# We enforce `STORED_DATA` to have its length being a power of 2, to ease Merkle tree implementation.
while STORED_DATA_IN_BITS < SPACE_TO_PROVE_IN_BITS or not math.log2(len(STORED_DATA)).is_integer():
while len(STORED_DATA) < common.ENTRIES_NUMBER:
hashed = common.hash(protocolInitializationPhaseId + str(counter))
hashedInteger = int(hashed, 16)
hashedDifficulty = hashedInteger / LEAST_DIFFICULT_HASH
@ -30,16 +29,10 @@ while STORED_DATA_IN_BITS < SPACE_TO_PROVE_IN_BITS or not math.log2(len(STORED_D
deltaCounter = counter - lastCounter
deltaCounterBits = common.numberOfBitsOf(deltaCounter)
STORED_DATA += [deltaCounter]
STORED_DATA_IN_BITS += deltaCounterBits
lastCounter = counter
print(STORED_DATA_IN_BITS, len(STORED_DATA))
print(len(STORED_DATA), common.ENTRIES_NUMBER)
counter += 1
# TODO: update `SPACE_TO_PROVE_IN_BITS` to prove more than what initially intended to.
entriesNumber = len(STORED_DATA)
print(f'{entriesNumber=}')
## Execution phase: 1. Receive a random bitstring from the verifier.
protocolExecutionPhaseId = input('protocolExecutionPhaseId: ')
@ -52,13 +45,13 @@ def pairwise(iterable):
return zip(a, a)
def getNextMerkleTreeLevel(nodes):
return [hash(leftNode + rightNode) for leftNode, rightNode in pairwise(nodes)]
return [common.hash(leftNode + rightNode) for leftNode, rightNode in pairwise(nodes)]
merkleTreeLevel = []
counter = 0
for deltaCounter in STORED_DATA:
counter += deltaCounter
hashed = hash(protocolInitializationPhaseId + protocolExecutionPhaseId + str(counter))
hashed = common.hash(protocolInitializationPhaseId + protocolExecutionPhaseId + str(counter))
merkleTreeLevel += [hashed]
merkleTreeLevels = [merkleTreeLevel]
@ -67,16 +60,77 @@ while len(merkleTreeLevels[-1]) > 1:
nextMerkleTreeLevel = getNextMerkleTreeLevel(merkleTreeLevels[-1])
merkleTreeLevels += [nextMerkleTreeLevel]
merkleTreeRoot = merkleTreeLevels[-1]
merkleTreeRoot = merkleTreeLevels[-1][0]
print(f'{merkleTreeRoot=}')
## Execution phase: 3. Receive a random set of indices from the prover.
## Execution phase: 3. Receive a random set of indexes from the verifier.
indicesRequest = input('indicesRequest: ')
indexesRequest = set([int(n) for n in input('indexesRequest: ')[1:-1].split(',')])
## Execution phase: 4. Respond with the corresponding table entries and commitment openings.
# First computing indexes of fields required in the different levels.
# Then just transmit the hashes of these indexes, as the verifier can compute these indexes on its own.
# Then proceed to the Merkle tree root validation. Designing this first to make sure transmit in the easiest way make sense.
# Note that should avoid repeating already needed index, for instance if request 0 and 2 then it requires also 1 and 3 on the first level but on the second level it would be bad to mention twice on the above level 0 and 1. Let's don't care about this detail for this prototype.
# Using an array of dict to keep array-lookup by indexes without wasting empty memory seems to make sense.
# For verifying treat level by level
# Can't just send the `dict`, as could fill penultimate both entries and not the previous ones, that way would end up with same Merkle tree root without providing it and without providing the given entries requested.
# Just sending a list of hashes that the verifier fills its data structure with, as the prover generated this list.
# So have to just verify that there's no such attack when filling the first level.
indexes = []
"""
for indexRequest in indexesRequest:
otherIndex = indexRequest + (1 if indexRequest % 2 == 0 else -1)
#indexes += []
leftIndex, rightIndex = sorted([indexRequest, otherIndex])
#leftHash, rightHash = merkleTreeLevels[0][leftIndex], merkleTreeLevels[0][rightIndex]
"""
levels = int(math.log2(common.ENTRIES_NUMBER)) + 1
verificationMerkleTreeLevels = [{} for _ in range(levels)]
for indexRequest in indexesRequest:
otherIndex = indexRequest + (1 if indexRequest % 2 == 0 else -1)
for index in [indexRequest, otherIndex]:
verificationMerkleTreeLevels[0][index] = merkleTreeLevels[0][index]
#import copy
#verificationMerkleTreeLevels = copy.deepcopy(merkleTreeLevels)
for merkleTreeLevelsIndex, merkleTreeLevel in enumerate(merkleTreeLevels):
for i, hash in enumerate(merkleTreeLevel):
verificationMerkleTreeLevels[merkleTreeLevelsIndex][i] = hash
entries = []
for verificationMerkleTreeLevelsIndex, verificationMerkleTreeLevel in enumerate(verificationMerkleTreeLevels[:-1]):
#print(verificationMerkleTreeLevel)
for index in verificationMerkleTreeLevel:
otherIndex = index + (1 if index % 2 == 0 else -1)
leftIndex, rightIndex = sorted([index, otherIndex])
#leftHash, rightHash = verificationMerkleTreeLevel[leftIndex], verificationMerkleTreeLevel[rightIndex]
leftHash, rightHash = merkleTreeLevels[verificationMerkleTreeLevelsIndex][leftIndex], merkleTreeLevels[verificationMerkleTreeLevelsIndex][rightIndex]
otherHash = merkleTreeLevels[verificationMerkleTreeLevelsIndex][otherIndex]
verificationMerkleTreeLevels[verificationMerkleTreeLevelsIndex + 1][leftIndex // 2] = common.hash(leftHash + rightHash)
if verificationMerkleTreeLevelsIndex > 0 or leftIndex == index:
if verificationMerkleTreeLevelsIndex == 0:
#print(leftIndex, rightIndex)
entries += [leftHash, rightHash]
else:
#print(otherIndex)
entries += [otherHash]
print(f'{entries=}')
# Assuming first level filled.
#for level in range(levels):

View File

@ -5,16 +5,12 @@ import secrets, common
# Should instead propose a probability of confidence setting.
FRACTION_OF_SPACE_TO_VERIFY = 10 ** -2
## Initialization phase: 1. Generate a random bitstring to send to the prover.
## Initialization phase: Generate a random bitstring to send to the prover.
# To make sure that the following protocol execution can't be used partially or totally by the prover to reduce the cost of another protocol execution.
protocolInitializationPhaseId = secrets.token_hex(common.SECURITY_PARAMETER // 8)
print(f'{protocolInitializationPhaseId=}')
## Initialization phase: 2. Receive the table size of the prover.
entriesNumber = input('entriesNumber: ')
## Execution phase: 1. Generate a random bitstring to send to the prover.
# TODO: improve step numbering
# Wait the initialization phase termination from the prover side before starting the execution phase.
@ -26,20 +22,20 @@ print(f'{protocolExecutionPhaseId=}')
merkleTreeRoot = input('merkleTreeRoot: ')
## Execution phase: 3. Generate and send a random set of indices to the prover.
## Execution phase: 3. Generate and send a random set of indexes to the prover.
# Need to know the actual table size to choose a random set of indices. This point isn't discussed to the best of my knowledge in Simple Proofs of Space-Time and Rational Proofs of Storage, Tal Moran and Ilan Orlov, 2016. In a more important manner, it's unclear how the prover proves to the verifier precisely with high probability or *for sure* that he is actually storing a given number of bits.
# Need to know the actual table size to choose a random set of indexes. This point isn't discussed to the best of my knowledge in Simple Proofs of Space-Time and Rational Proofs of Storage, Tal Moran and Ilan Orlov, 2016. In a more important manner, it's unclear how the prover proves to the verifier precisely with high probability or *for sure* that he is actually storing a given number of bits.
# For this prototype the prover claims a given table size and the verifier estimate the total table number of bits thanks to the requested set of its entries.
# Note that there's no advantage to request the last table entry by hoping that the prover is storing the nonces with delta in such order, for instance he can have stored the delta the other way around which screw up this additional request aim.
indicesRequest = set()
indexesRequest = set()
while len(indicesRequest) < entriesNumber * FRACTION_OF_SPACE_TO_VERIFY:
index = secrets.randbelow(entriesNumber)
indicesRequest.add(index)
while len(indexesRequest) < common.ENTRIES_NUMBER * FRACTION_OF_SPACE_TO_VERIFY:
index = secrets.randbelow(common.ENTRIES_NUMBER)
indexesRequest.add(index)
print(f'{indicesRequest=}')
print(f'{indexesRequest=}')
## Execution phase: 4. Receive and verify corresponding table entries and commitment openings.