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.
98 lines
3.5 KiB
Solidity
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];
|
|
}
|
|
}
|