Here a direct proof on puzzle 17:
def get_filter_candidate(x: int, c: int) -> int:
h2 = hash160_pubkey(x)
val = int.from_bytes(h2, 'big')
return val >> (160 - c)
def prefilter_scan(start: int, end: int, target: str, c: int):
t1 = get_filter_target(target, c)
ops = 0
for x in range(start, end+1):
if get_filter_candidate(x, c) != t1:
continue
ops += 1
if derive_address(x) == target:
return x, ops
return None, ops
You have an error in your script. You do H160 and continue before you increment ops. So the heavy op is not accounted always.
Also - if the key isn't found, the method simply fails.
I also believe that the actual HEAVY operation that actually ever matters is having a public key of the private key - because that is the actual step where you go from the scalar domain into the discrete log injective-only domain.