// Copyright (c) 2017-2026 Lethean (https://lt.hn)
//
// Licensed under the European Union Public Licence (EUPL) version 1.2.
// SPDX-License-Identifier: EUPL-1.2

package wire

import "golang.org/x/crypto/sha3"

// Keccak256 computes the Keccak-256 hash (pre-NIST, no domain separation)
// used as cn_fast_hash throughout the CryptoNote protocol.
func Keccak256(data []byte) [32]byte {
	h := sha3.NewLegacyKeccak256()
	h.Write(data)
	var out [32]byte
	h.Sum(out[:0])
	return out
}

// TreeHash computes the CryptoNote Merkle tree hash over a set of 32-byte
// hashes. This is a direct port of crypto/tree-hash.c from the C++ daemon.
//
// Algorithm:
//   - 0 hashes: returns zero hash
//   - 1 hash:   returns the hash itself (identity)
//   - 2 hashes: returns Keccak256(h0 || h1)
//   - N hashes: pad to power-of-2 leaves, pairwise Keccak up the tree
func TreeHash(hashes [][32]byte) [32]byte {
	count := len(hashes)
	if count == 0 {
		return [32]byte{}
	}
	if count == 1 {
		return hashes[0]
	}
	if count == 2 {
		var buf [64]byte
		copy(buf[:32], hashes[0][:])
		copy(buf[32:], hashes[1][:])
		return Keccak256(buf[:])
	}

	// Find largest power of 2 that is <= count. This mirrors the C++ bit trick:
	//   cnt = count - 1; for (i = 1; i < bits; i <<= 1) cnt |= cnt >> i;
	//   cnt &= ~(cnt >> 1);
	cnt := count - 1
	for i := 1; i < 64; i <<= 1 {
		cnt |= cnt >> uint(i)
	}
	cnt &= ^(cnt >> 1)

	// Allocate intermediate hash buffer.
	ints := make([][32]byte, cnt)

	// Copy the first (2*cnt - count) hashes directly into ints.
	direct := 2*cnt - count
	copy(ints[:direct], hashes[:direct])

	// Pair-hash the remaining hashes into ints.
	i := direct
	for j := direct; j < cnt; j++ {
		var buf [64]byte
		copy(buf[:32], hashes[i][:])
		copy(buf[32:], hashes[i+1][:])
		ints[j] = Keccak256(buf[:])
		i += 2
	}

	// Iteratively pair-hash until we have 2 hashes left.
	for cnt > 2 {
		cnt >>= 1
		for i, j := 0, 0; j < cnt; j++ {
			var buf [64]byte
			copy(buf[:32], ints[i][:])
			copy(buf[32:], ints[i+1][:])
			ints[j] = Keccak256(buf[:])
			i += 2
		}
	}

	// Final hash of the remaining pair.
	var buf [64]byte
	copy(buf[:32], ints[0][:])
	copy(buf[32:], ints[1][:])
	return Keccak256(buf[:])
}
