aere-contracts/contracts/interop/lib/SSZ.sol
Aere Network a13a649b77
Some checks are pending
contracts-ci / Install (lockfile) → compile → full test suite (push) Waiting to run
contracts-ci / PQC known-answer tests (NIST vectors) (push) Waiting to run
contracts-ci / Coverage (scoped, with artifacts) (push) Waiting to run
Initial public release
Aere Network public source. Everything here can be checked against the live
chain (chain id 2800, https://rpc.aere.network).

Scope note, stated up front rather than buried: consensus on chain 2800 is
classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at
the signature, precompile, account and transport layers. Nothing here makes the
consensus post-quantum, and no document in it should be read as claiming so.
2026-07-20 01:02:37 +03:00

98 lines
3.5 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
/**
* @title SSZ — the minimal Simple-Serialize merkleization primitives the Ethereum
* light client needs: SHA-256 chunk hashing, fixed-size merkleization,
* Merkle branch verification, and BeaconBlockHeader hash_tree_root.
*
* @dev All hashing is SHA-256 (the beacon-chain hash), via the 0x02 precompile —
* NOT keccak. Little-endian integer serialization matches the consensus spec.
*/
library SSZ {
/// @notice sha256(a || b) via the precompile.
function hashPair(bytes32 a, bytes32 b) internal view returns (bytes32 r) {
bytes memory input = new bytes(64);
assembly {
mstore(add(input, 32), a)
mstore(add(input, 64), b)
}
r = _sha(input);
}
function _sha(bytes memory input) private view returns (bytes32 r) {
bool ok;
assembly {
let out := mload(0x40)
ok := staticcall(gas(), 0x02, add(input, 32), mload(input), out, 32)
r := mload(out)
}
require(ok, "SSZ:sha256");
}
/// @notice uint64 -> a 32-byte SSZ chunk (little-endian, right-zero-padded).
function uint64Chunk(uint64 v) internal pure returns (bytes32 out) {
// little-endian bytes of v in the LOW 8 bytes of the (big-endian) word,
// laid out at the start of the 32-byte chunk.
uint256 le;
uint256 vv = uint256(v);
for (uint256 i = 0; i < 8; i++) {
le |= ((vv >> (8 * i)) & 0xff) << (8 * (31 - i));
}
out = bytes32(le);
}
/// @notice hash_tree_root of a BeaconBlockHeader (5 fields, merkleized as 8 leaves).
function beaconBlockHeaderRoot(
uint64 slot,
uint64 proposerIndex,
bytes32 parentRoot,
bytes32 stateRoot,
bytes32 bodyRoot
) internal view returns (bytes32) {
bytes32 zero = bytes32(0);
bytes32 l01 = hashPair(uint64Chunk(slot), uint64Chunk(proposerIndex));
bytes32 l23 = hashPair(parentRoot, stateRoot);
bytes32 l45 = hashPair(bodyRoot, zero);
bytes32 l67 = hashPair(zero, zero);
bytes32 a = hashPair(l01, l23);
bytes32 b = hashPair(l45, l67);
return hashPair(a, b);
}
/// @notice is_valid_merkle_branch (consensus-spec). Folds `leaf` up `depth`
/// levels using `branch`, choosing sibling order by the bits of `index`,
/// and checks the result equals `root`.
function isValidMerkleBranch(
bytes32 leaf,
bytes32[] memory branch,
uint256 depth,
uint256 index,
bytes32 root
) internal view returns (bool) {
require(branch.length >= depth, "SSZ:branch len");
bytes32 value = leaf;
for (uint256 i = 0; i < depth; i++) {
if ((index >> i) & 1 == 1) {
value = hashPair(branch[i], value);
} else {
value = hashPair(value, branch[i]);
}
}
return value == root;
}
/// @notice Merkleize `leaves` (length must be a power of two) into a single root.
function merkleize(bytes32[] memory leaves) internal view returns (bytes32) {
uint256 n = leaves.length;
require(n != 0 && (n & (n - 1)) == 0, "SSZ:not pow2");
while (n > 1) {
for (uint256 i = 0; i < n; i += 2) {
leaves[i / 2] = hashPair(leaves[i], leaves[i + 1]);
}
n /= 2;
}
return leaves[0];
}
}