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.
206 lines
10 KiB
Solidity
206 lines
10 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.23;
|
|
|
|
/**
|
|
* @title BLS12381 — thin, spec-correct wrappers over AERE's LIVE EIP-2537 precompiles.
|
|
*
|
|
* @notice AERE Network (chain 2800, Prague) exposes the EIP-2537 BLS12-381 precompiles
|
|
* natively. This library builds the exact EIP-2537 wire encodings and delegates
|
|
* all field/curve math to those precompiles. It also implements RFC-9380
|
|
* hash-to-curve for G2 (expand_message_xmd over SHA-256, hash_to_field via the
|
|
* modexp precompile, SSWU + isogeny + cofactor clear via MAP_FP2_TO_G2), so a
|
|
* message can be mapped to a G2 point ON CHAIN with no trusted hint.
|
|
*
|
|
* Point encodings follow EIP-2537 exactly:
|
|
* - Fp element = 64 bytes (16 zero bytes || 48-byte big-endian value)
|
|
* - G1 point = 128 bytes (x || y), infinity = 128 zero bytes
|
|
* - Fp2 element = 128 bytes (c0 || c1)
|
|
* - G2 point = 256 bytes (x.c0 || x.c1 || y.c0 || y.c1), infinity = 256 zeros
|
|
*
|
|
* @dev SECURITY / HONESTY. BLS12-381 is a CLASSICAL, pairing-friendly curve and is
|
|
* NOT post-quantum. This library is the INBOUND (Ethereum -> AERE) crypto: it
|
|
* inherits Ethereum's sync-committee security model, which is classical and
|
|
* quantum-vulnerable. It says nothing about AERE's own consensus (QBFT / ECDSA)
|
|
* or AERE's PQC precompiles. See AereEthLightClient for the full model.
|
|
*/
|
|
library BLS12381 {
|
|
// ---- EIP-2537 precompile addresses (Prague canonical) --------------------
|
|
address internal constant G1ADD = address(uint160(0x0b));
|
|
address internal constant G2ADD = address(uint160(0x0d));
|
|
address internal constant PAIRING = address(uint160(0x0f));
|
|
address internal constant MAP_FP2_TO_G2 = address(uint160(0x11));
|
|
// ---- Legacy precompiles used by hash-to-curve ---------------------------
|
|
address internal constant SHA256 = address(uint160(0x02));
|
|
address internal constant MODEXP = address(uint160(0x05));
|
|
|
|
// Field modulus p of BLS12-381, big-endian 48 bytes.
|
|
bytes internal constant P =
|
|
hex"1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab";
|
|
|
|
uint256 internal constant G1_LEN = 128;
|
|
uint256 internal constant G2_LEN = 256;
|
|
|
|
// -------------------------------------------------------------------------
|
|
// curve operations
|
|
// -------------------------------------------------------------------------
|
|
|
|
/// @notice G1 point addition via precompile 0x0b. `a`,`b` are 128-byte G1 encodings.
|
|
function g1Add(bytes memory a, bytes memory b) internal view returns (bytes memory out) {
|
|
require(a.length == G1_LEN && b.length == G1_LEN, "BLS:g1add len");
|
|
out = _call(G1ADD, bytes.concat(a, b), G1_LEN);
|
|
}
|
|
|
|
/// @notice G2 point addition via precompile 0x0d. `a`,`b` are 256-byte G2 encodings.
|
|
function g2Add(bytes memory a, bytes memory b) internal view returns (bytes memory out) {
|
|
require(a.length == G2_LEN && b.length == G2_LEN, "BLS:g2add len");
|
|
out = _call(G2ADD, bytes.concat(a, b), G2_LEN);
|
|
}
|
|
|
|
/// @notice Map an Fp2 element (two 64-byte Fp coords c0,c1) to G2 via precompile 0x11.
|
|
/// The precompile applies SSWU + the 3-isogeny + cofactor clearing, so the
|
|
/// result is a valid member of the G2 prime-order subgroup.
|
|
function mapFp2ToG2(bytes memory fp2) internal view returns (bytes memory out) {
|
|
require(fp2.length == 128, "BLS:map len");
|
|
out = _call(MAP_FP2_TO_G2, fp2, G2_LEN);
|
|
}
|
|
|
|
/// @notice EIP-2537 pairing check. `input` is k concatenated (G1 || G2) pairs
|
|
/// (k * 384 bytes). Returns true iff the product of pairings equals one.
|
|
function pairing(bytes memory input) internal view returns (bool) {
|
|
require(input.length % 384 == 0 && input.length != 0, "BLS:pair len");
|
|
bytes memory ret = _call(PAIRING, input, 32);
|
|
return ret[31] == 0x01;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// signature verification (min-pubkey-size)
|
|
// -------------------------------------------------------------------------
|
|
|
|
/// @notice Verify a BLS signature under the Ethereum "min-pubkey-size" scheme:
|
|
/// public key in G1 (128B), signature in G2 (256B). Checks
|
|
/// e(-G1_generator, signature) * e(pubkey, H(message)) == 1
|
|
/// where H = hashToG2(message, dst). All heavy math runs on the LIVE
|
|
/// precompiles; the message is hashed to the curve on chain (no hint).
|
|
/// @param pubkeyG1 aggregate (or single) public key, 128-byte G1 encoding
|
|
/// @param message the 32-byte signing root (or any message) that was signed
|
|
/// @param signatureG2 the signature, 256-byte G2 encoding
|
|
/// @param dst the RFC-9380 domain separation tag used when signing
|
|
function verify(
|
|
bytes memory pubkeyG1,
|
|
bytes memory message,
|
|
bytes memory signatureG2,
|
|
bytes memory dst
|
|
) internal view returns (bool) {
|
|
require(pubkeyG1.length == G1_LEN, "BLS:pk len");
|
|
require(signatureG2.length == G2_LEN, "BLS:sig len");
|
|
bytes memory hm = hashToG2(message, dst);
|
|
// pairing input = (-G1) || sig ++ pk || H(m)
|
|
bytes memory input = bytes.concat(NEG_G1_GENERATOR(), signatureG2, pubkeyG1, hm);
|
|
return pairing(input);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// RFC-9380 hash-to-curve (G2)
|
|
// -------------------------------------------------------------------------
|
|
|
|
/// @notice Hash an arbitrary message to a point in G2, per RFC-9380
|
|
/// (expand_message_xmd:SHA-256, SSWU_RO). Reproduces the reference
|
|
/// behaviour of noble-curves bls12_381.G2.hashToCurve (validated).
|
|
function hashToG2(bytes memory message, bytes memory dst) internal view returns (bytes memory) {
|
|
// 256 bytes = count(2) * m(2) * L(64)
|
|
bytes memory uniform = _expandMessageXmd(message, dst, 256);
|
|
bytes memory fp2a = bytes.concat(_reduceModP(uniform, 0), _reduceModP(uniform, 64));
|
|
bytes memory fp2b = bytes.concat(_reduceModP(uniform, 128), _reduceModP(uniform, 192));
|
|
bytes memory q0 = mapFp2ToG2(fp2a);
|
|
bytes memory q1 = mapFp2ToG2(fp2b);
|
|
return g2Add(q0, q1);
|
|
}
|
|
|
|
/// @dev expand_message_xmd (RFC-9380 §5.4.1) with SHA-256, b_in_bytes=32.
|
|
/// len_in_bytes must be a multiple of 32 and <= 255*32.
|
|
function _expandMessageXmd(bytes memory message, bytes memory dst, uint256 lenInBytes)
|
|
private
|
|
view
|
|
returns (bytes memory)
|
|
{
|
|
require(dst.length <= 255, "BLS:dst too long");
|
|
uint256 ell = lenInBytes / 32; // b_in_bytes = 32
|
|
require(ell * 32 == lenInBytes && ell <= 255, "BLS:xmd len");
|
|
bytes memory dstPrime = bytes.concat(dst, bytes1(uint8(dst.length)));
|
|
bytes memory lBytes = bytes.concat(bytes1(uint8(lenInBytes >> 8)), bytes1(uint8(lenInBytes)));
|
|
// b0 = H( Z_pad(64) || msg || l_i_b(2) || 0x00 || dst_prime )
|
|
bytes memory zPad = new bytes(64);
|
|
bytes32 b0 = _sha256(bytes.concat(zPad, message, lBytes, bytes1(0x00), dstPrime));
|
|
// b1 = H( b0 || 0x01 || dst_prime )
|
|
bytes32 bPrev = _sha256(bytes.concat(b0, bytes1(0x01), dstPrime));
|
|
bytes memory out = new bytes(lenInBytes);
|
|
_writeWord(out, 0, bPrev);
|
|
for (uint256 i = 2; i <= ell; i++) {
|
|
bytes32 xored = b0 ^ bPrev;
|
|
bPrev = _sha256(bytes.concat(xored, bytes1(uint8(i)), dstPrime));
|
|
_writeWord(out, (i - 1) * 32, bPrev);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/// @dev Reduce the 64-byte big-endian value at `uniform[off:off+64]` modulo p,
|
|
/// returning it as a 64-byte EIP-2537 Fp element (16 zero bytes || 48 bytes).
|
|
/// Uses the modexp precompile: x mod p = x^1 mod p.
|
|
function _reduceModP(bytes memory uniform, uint256 off) private view returns (bytes memory) {
|
|
bytes memory base = new bytes(64);
|
|
for (uint256 i = 0; i < 64; i++) base[i] = uniform[off + i];
|
|
// modexp input: <baseLen=64><expLen=1><modLen=48> base exp(0x01) mod(p)
|
|
bytes memory input = bytes.concat(
|
|
bytes32(uint256(64)),
|
|
bytes32(uint256(1)),
|
|
bytes32(uint256(48)),
|
|
base,
|
|
bytes1(0x01),
|
|
P
|
|
);
|
|
bytes memory r48 = _call(MODEXP, input, 48); // 48-byte reduced value
|
|
bytes memory fp = new bytes(64); // 16 zero pad + 48
|
|
for (uint256 i = 0; i < 48; i++) fp[16 + i] = r48[i];
|
|
return fp;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// helpers
|
|
// -------------------------------------------------------------------------
|
|
|
|
function _sha256(bytes memory data) private view returns (bytes32 h) {
|
|
bytes memory r = _call(SHA256, data, 32);
|
|
assembly {
|
|
h := mload(add(r, 32))
|
|
}
|
|
}
|
|
|
|
function _writeWord(bytes memory out, uint256 pos, bytes32 word) private pure {
|
|
assembly {
|
|
mstore(add(add(out, 32), pos), word)
|
|
}
|
|
}
|
|
|
|
/// @dev staticcall a precompile, require success and an exact-length return.
|
|
function _call(address to, bytes memory input, uint256 outLen) private view returns (bytes memory out) {
|
|
bool ok;
|
|
out = new bytes(outLen);
|
|
assembly {
|
|
ok := staticcall(gas(), to, add(input, 32), mload(input), add(out, 32), outLen)
|
|
}
|
|
require(ok, "BLS:precompile");
|
|
// guard against a short return (e.g. precompile missing on a non-Prague chain)
|
|
require(out.length == outLen, "BLS:retlen");
|
|
}
|
|
|
|
/// @dev The negation of the G1 generator, EIP-2537 encoded (128 bytes).
|
|
/// x = generator.x ; y = p - generator.y. Precomputed constant.
|
|
function NEG_G1_GENERATOR() internal pure returns (bytes memory) {
|
|
return
|
|
hex"0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f"
|
|
hex"c3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb"
|
|
hex"00000000000000000000000000000000114d1d6855d545a8aa7d76c8cf2e21f2"
|
|
hex"67816aef1db507c96655b9d5caac42364e6f38ba0ecb751bad54dcd6b939c2ca";
|
|
}
|
|
}
|