def merkle_compress_octopus(nodes: List[bytes], height: int,
                            octopus: Tuple[bytes, ...], octolen: int,
                            indices: List[int], count: int):
    offset = 0
    for l in range(height):
        i, j = 0, 0
        while i < count:
            index = indices[i]
            if index % 2 == 0:
                if i + 1 < count and indices[i + 1] == index + 1:
                    buf = nodes[i] + nodes[i + 1]
                    i += 1
                else:
                    assert offset < octolen
                    buf = nodes[i] + octopus[offset]
                    offset += 1

            else:
                assert offset < octolen
                buf = octopus[offset] + nodes[i]
                offset += 1

            nodes[j] = haraka512(buf)
            indices[j] = indices[i] >> 1
            j += 1
            i += 1
        count = j
    assert offset == octolen
def pors_randsubset(rand: bytes, msg: bytes) -> Tuple[int, Set[int]]:
    seed = haraka512(rand + msg)

    stream_len = 8 * PORS_k + HASH_SIZE
    cipher = Cipher(algorithms.AES(seed),
                    modes.CTR(b'\x00' * 16),
                    backend=default_backend())
    encryptor = cipher.encryptor()
    random_stream = encryptor.update(
        b'\x00' * stream_len) + encryptor.finalize()

    # compute address. can be simplified
    addr = 0
    for i in range(HASH_SIZE):
        addr = (addr << 8) | random_stream[i]
        addr &= GRAVITY_mask

    offset = HASH_SIZE
    subset = set()
    while len(subset) < PORS_k:
        index = int.from_bytes(random_stream[offset:offset + 4],
                               'big') % PORS_t
        offset += 4
        subset.add(index)
    return addr, subset
def merkle_compress_auth(node: bytes, index: int, auth: Tuple[bytes,
                                                              ...]) -> bytes:
    for i in range(len(auth)):
        if index % 2 == 0:
            buf = node + auth[i]
        else:
            buf = auth[i] + node
        node = haraka512(buf)
        index //= 2
    return node
def ltree(buf, count) -> bytes:
    while count > 1:
        buf2 = []
        new_count = count >> 1
        for i in range(new_count):
            buf2.append(haraka512(buf[2 * i] + buf[2 * i + 1]))
        if count & 1:
            buf2.append(buf[-1])
            new_count += 1
        buf = buf2
        count = new_count
    return buf[0]
def merkle_gen_auth(buf: List[bytes], height: int,
                    index: int) -> Tuple[bytes, Tuple[bytes, ...]]:
    auth = []

    for i in range(height):
        sibling = index ^ 1
        auth.append(buf[sibling])
        index >>= 1

        # compress pairs
        buf = [haraka512(buf[i] + buf[i + 1]) for i in range(0, len(buf), 2)]

    public_key = buf[0]
    return public_key, tuple(auth)
def gravity_gen_secret_key(seed: bytes, salt: bytes) -> GravityPrivateKey:
    address = Address()
    n = GRAVITY_ccc

    # create sub merkle trees
    cache = []
    for i in range(n):
        address.index = i * MERKLE_hhh
        merkle_public_key = merkle_genpk(seed, address)
        cache.append(merkle_public_key)

    # cache layers of merkle trees
    for i in range(GRAVITY_c):
        cache = [
            haraka512(cache[i] + cache[i + 1])
            for i in range(0, len(cache), 2)
        ]
        n >>= 1
    return GravityPrivateKey(seed, salt, tuple(cache))
def gravity_sign(private_key: GravityPrivateKey,
                 msg: bytes) -> GravitySignature:
    rand = haraka512(private_key.salt + msg)

    index, subset = pors_randsubset(rand, msg)
    address = Address(GRAVITY_d, index)

    pors_private_key = pors_gensk(private_key.seed, address)
    op_sign, public_key = octoporst_sign(pors_private_key, subset)

    h = public_key

    # hyper tree
    merkle_signature = []
    for layer in range(GRAVITY_d):
        address.layer -= 1
        h, sig = merkle_sign(private_key.seed, address, h)
        merkle_signature.append(sig)

        address.index >>= MERKLE_h
    return GravitySignature(rand, op_sign, tuple(merkle_signature))
def merkle_gen_octopus(buf: List[bytes], height: int, indices: List[int], count: int) \
        -> Tuple[Tuple[bytes, ...], bytes]:
    octopus = []
    for l in range(height):
        i, j = 0, 0
        while i < count:
            index = indices[i]
            sibling = index ^ 1

            if i + 1 < count and indices[i + 1] == sibling:
                i += 1
            else:
                octopus.append(buf[sibling])
            indices[j] = indices[i] >> 1
            i += 1
            j += 1

        count = j
        buf = [haraka512(buf[i] + buf[i + 1]) for i in range(0, len(buf), 2)]
    root = buf[0]
    return tuple(octopus), root
def merkle_compress_all(buf, height) -> bytes:
    for l in range(height):
        buf = [haraka512(buf[i] + buf[i + 1]) for i in range(0, len(buf), 2)]
    return buf[0]