// Copyright 2026 RISC Zero, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

pragma solidity ^0.8.9;

import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";

import {reverseByteOrderUint16} from "./Util.sol";

/// @notice Structural hashing routines used for RISC Zero data structures.
/// @dev
/// StructHash implements hashing for structs, incorporating type tags for domain separation.
/// The goals of this library are:
/// * Collision resistance: it should not be possible to find two semantically distinct values that
///   produce the same digest.
/// * Simplicity: implementations should be simple to understand and write, as these methods must
///   be implemented in multiple languages and environments, including zkSNARK circuits.
/// * Incremental openings: it should be possible to incrementally open a nested struct without
///   needing to open (very many) extra fields (i.e. the struct should be "Merkle-ized").
library StructHash {
    using SafeCast for uint256;

    // @notice Compute the struct digest with the given tag digest and digest fields down.
    function taggedStruct(bytes32 tagDigest, bytes32[] memory down) internal pure returns (bytes32) {
        bytes memory data = new bytes(0);
        return taggedStruct(tagDigest, down, data);
    }

    // @notice Compute the struct digest with the given tag digest, digest fields down, and data.
    function taggedStruct(bytes32 tagDigest, bytes32[] memory down, bytes memory data) internal pure returns (bytes32) {
        uint16 downLen = down.length.toUint16();
        // swap the byte order to encode as little-endian.
        bytes2 downLenLE = bytes2((downLen << 8) | (downLen >> 8));
        return sha256(abi.encodePacked(tagDigest, down, data, downLenLE));
    }

    // @notice Add an element (head) to the incremental hash of a list (tail).
    function taggedListCons(bytes32 tagDigest, bytes32 head, bytes32 tail) internal pure returns (bytes32) {
        bytes32[] memory down = new bytes32[](2);
        down[0] = head;
        down[1] = tail;
        return taggedStruct(tagDigest, down);
    }

    // @notice Hash the list by using taggedListCons to repeatedly add to the head of the list.
    function taggedList(bytes32 tagDigest, bytes32[] memory list) internal pure returns (bytes32) {
        bytes32 curr = bytes32(0x0000000000000000000000000000000000000000000000000000000000000000);
        for (uint256 i = 0; i < list.length; i++) {
            curr = taggedListCons(tagDigest, list[list.length - 1 - i], curr);
        }
        return curr;
    }
}
