Skip to content

Commit

Permalink
fix: compress contract
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeday committed Feb 28, 2025
1 parent ddcd708 commit 74664a2
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 104 deletions.
187 changes: 113 additions & 74 deletions contracts/0.8.25/lib/BLS.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,14 @@ library BLS {
uint256 constant SUBGROUP_ORDER = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001;

bytes constant DST = bytes("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_");
uint16 constant MSG_LENGTH = 256;

bytes1 constant BLS_BYTE_WITHOUT_FLAGS_MASK = bytes1(0x1f);
uint256 constant BLS_UINT256_WITHOUT_FLAGS_MASK =
uint256(0x000000000000000000000000000000001f000000000000000000000000000000);
uint256 constant FP_NO_SIGN_BIT_MASK = uint256(0x01fffffffffffffffffffffffffffffff);

/// @notice PRECOMPILED CONTRACT ADDRESSES
address constant MOD_EXP = address(0x05);

/// forge
// MUL is deprecated in actual EIP in favor of MSM trivial case
// We are supposed to use MSM address but change it to MUL because of forge
Expand All @@ -71,30 +72,49 @@ library BLS {
address constant BLS12_PAIRING_CHECK = 0x0000000000000000000000000000000000000011;
address constant BLS12_MAP_FP2_TO_G2 = 0x0000000000000000000000000000000000000013;

// revm
// address constant BLS12_G2ADD = 0x000000000000000000000000000000000000000B;
// address constant BLS12_PAIRING_CHECK = 0x000000000000000000000000000000000000000F;
// address constant BLS12_MAP_FP2_TO_G2 = 0x0000000000000000000000000000000000000011;
/** Correct Pectra addresses & gas values for precompile calls
address constant BLS12_G2ADD = 0x000000000000000000000000000000000000000b;
uint256 constant BLS12_G2ADD_GAS = 600;
// TODO make constant
address constant BLS12_G1MSM = 0x000000000000000000000000000000000000000C;
uint256 constant BLS12_G1MSM_GAS = 12000;
address constant BLS12_G2MSM = 0x000000000000000000000000000000000000000E;
uint256 constant BLS12_G2MSM_GAS = 22500;
address constant BLS12_PAIRING_CHECK = 0x000000000000000000000000000000000000000F;
uint256 constant BLS12_PAIRING_CHECK_GAS = 102900;
address constant BLS12_MAP_FP2_TO_G2 = 0x0000000000000000000000000000000000000011;
uint256 constant BLS12_MAP_FP2_TO_G2_GAS = 23800;
*/

// Negated G1 generator compressed as raw bytes to save gas
// per https://eips.ethereum.org/EIPS/eip-2537#curve-parameters
// G1Point(
// Fp(
// 31827880280837800241567138048534752271,
// 88385725958748408079899006800036250932223001591707578097800747617502997169851
// ),
// Fp(
// 22997279242622214937712647648895181298,
// 46816884707101390882112958134453447585552332943769894357249934112654335001290
// )
// );
function NEGATED_G1_GENERATOR() internal pure returns (G1Point memory) {
return
G1Point(
Fp(
31827880280837800241567138048534752271,
88385725958748408079899006800036250932223001591707578097800747617502997169851
),
Fp(
22997279242622214937712647648895181298,
46816884707101390882112958134453447585552332943769894357249934112654335001290
)
abi.decode(
hex"0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00000000000000000000000000000000114d1d6855d545a8aa7d76c8cf2e21f267816aef1db507c96655b9d5caac42364e6f38ba0ecb751bad54dcd6b939c2ca",
(G1Point)
);
}

/// @notice Slices a byte array to a uint256
function sliceToUint(bytes memory data, uint256 start, uint256 end) internal pure returns (uint256 result) {
require(end >= start, "Invalid slice");
uint256 len = end - start;
require(len <= 32, "Slice length exceeds 32 bytes");
// Slice length exceeds 32 bytes"
assert(len <= 32);

/// @solidity memory-safe-assembly
assembly {
Expand All @@ -108,10 +128,14 @@ library BLS {
}
}

/// @notice Checks if provided point is at Infinity on the BLS curve
/// @param point G1Point to check
function isG1Infinity(G1Point memory point) internal pure returns (bool) {
return point.x.a == 0 && point.x.b == 0 && point.y.a == 0 && point.y.b == 0;
}

/// @notice Checks if provided point is at Infinity on the BLS curve
/// @param point G2Point to check
function isG2Infinity(G2Point memory point) internal pure returns (bool) {
return
point.x.c0.a == 0 &&
Expand All @@ -125,22 +149,33 @@ library BLS {
}

function validateG1Point(G1Point memory point) internal view {
require(!isG1Infinity(point), "G1 point at infinity");
if (isG1Infinity(point)) {
revert InputHasInfinityPoints();
}
G1Point memory check = G1Mul(point, SUBGROUP_ORDER);
require(isG1Infinity(check), "G1 point is not in main subgroup");

if (!isG1Infinity(check)) {
revert InputNotOnSubgroup();
}
}

function validateG2Point(G2Point memory point) internal view {
require(!isG2Infinity(point), "G2 point at infinity");
if (isG2Infinity(point)) {
revert InputHasInfinityPoints();
}
G2Point memory check = G2Mul(point, SUBGROUP_ORDER);
require(isG2Infinity(check), "G1 point is not in main subgroup");
if (!isG2Infinity(check)) {
revert InputNotOnSubgroup();
}
}

function G1Mul(G1Point memory point, uint256 scalar) internal view returns (G1Point memory result) {
bytes memory input = bytes.concat(abi.encode(point, scalar));

(bool success, bytes memory output) = address(BLS12_G1MSM).staticcall(input);
require(success, "G1MSM failed");
if (!success) {
revert BLSG1MsmFailed();
}
return abi.decode(output, (G1Point));
}

Expand All @@ -149,48 +184,47 @@ library BLS {

// we have to use deprecated G2MUL because in forge
(bool success, bytes memory output) = address(BLS12_G2MSM).staticcall(input);
require(success, "G2MSM failed");
if (!success) {
revert BLSG2MsmFailed();
}
return abi.decode(output, (G2Point));
}

// TODO change encodeX to calldata to optimize gas
// need to play around with FLAG so it can be correctly applied to 16 bytes zero padded uint256 of Fp.a
function decodeG1Point(bytes memory encodedX, Fp memory Y) internal pure returns (G1Point memory) {
encodedX[0] = encodedX[0] & BLS_BYTE_WITHOUT_FLAGS_MASK;
uint256 a = sliceToUint(encodedX, 0, 16);
function decodeG1Point(bytes calldata encodedX, Fp calldata Y) internal pure returns (G1Point memory) {
uint256 a = sliceToUint(encodedX, 0, 16) & FP_NO_SIGN_BIT_MASK;
uint256 b = sliceToUint(encodedX, 16, 48);
Fp memory X = Fp(a, b);
return G1Point(X, Y);
}

// TODO memory -> calldata
function decodeG2Point(bytes memory encodedX, Fp2 memory Y) internal pure returns (G2Point memory) {
encodedX[0] = encodedX[0] & BLS_BYTE_WITHOUT_FLAGS_MASK;
// NOTE: the "flag bits" of the second half of `encodedX` are always == 0x0

// Signature encoding has first Fp compnenet in the second half of the byte array
// With Fp components packed as 48 bytes
uint256 c0_a = sliceToUint(encodedX, 48, 64); // first Fp.a is 16 bytes
uint256 c0_b = sliceToUint(encodedX, 64, 96); // second Fp.b uint256 is 32 bytes
// and the second Fp component in the first half
uint256 c1_a = sliceToUint(encodedX, 0, 16);
function decodeG2Point(bytes calldata encodedX, Fp2 calldata Y) internal pure returns (G2Point memory) {
// Signature compressed encoding has are X Fp components packed in reverse with Z sign bit at the start

uint256 c0_a = sliceToUint(encodedX, 48, 64); // Fp.a is 16 bytes
uint256 c0_b = sliceToUint(encodedX, 64, 96); // Fp.b is 32 bytes

uint256 c1_a = sliceToUint(encodedX, 0, 16) & FP_NO_SIGN_BIT_MASK;
uint256 c2_b = sliceToUint(encodedX, 16, 48);
Fp2 memory X = Fp2(Fp(c0_a, c0_b), Fp(c1_a, c2_b));
return G2Point(X, Y);
}

function mapFp2ToG2(Fp2 memory element) public view returns (G2Point memory result) {
function mapFp2ToG2(Fp2 memory element) internal view returns (G2Point memory result) {
// exactly 23800 gas per https://eips.ethereum.org/EIPS/eip-2537#gas-schedule
(bool success, bytes memory output) = BLS12_MAP_FP2_TO_G2.staticcall(abi.encode(element));
require(success, "MAP_FP2_TO_G2 failed");

if (!success) {
revert BLSMapFp2ToG2Failed();
}

return abi.decode(output, (G2Point));
}

function hashToFieldFp2(bytes32 message, bytes memory dst) internal view returns (Fp2[2] memory) {
function hashToFieldFp2(bytes32 message) internal view returns (Fp2[2] memory) {
// 1. len_in_bytes = count * m * L
// so always 2 * 2 * 64 = 256
uint16 lenInBytes = 256;
// 2. uniform_bytes = expand_message(msg, DST, len_in_bytes)
bytes32[] memory pseudoRandomBytes = expandMsgXmd(message, dst, lenInBytes);
bytes32[] memory pseudoRandomBytes = expandMsgXmd(message);
Fp2[2] memory u;
// No loop here saves 800 gas hardcoding offset an additional 300
// 3. for i in (0, ..., count - 1):
Expand All @@ -202,17 +236,17 @@ library BLS {
// 7. e_j = OS2IP(tv) mod p
// 8. u_i = (e_0, ..., e_(m - 1))
// tv = bytes.concat(pseudo_random_bytes[0], pseudo_random_bytes[1]);
u[0].c0 = _modfield(pseudoRandomBytes[0], pseudoRandomBytes[1]);
u[0].c1 = _modfield(pseudoRandomBytes[2], pseudoRandomBytes[3]);
u[1].c0 = _modfield(pseudoRandomBytes[4], pseudoRandomBytes[5]);
u[1].c1 = _modfield(pseudoRandomBytes[6], pseudoRandomBytes[7]);
u[0].c0 = modfield(pseudoRandomBytes[0], pseudoRandomBytes[1]);
u[0].c1 = modfield(pseudoRandomBytes[2], pseudoRandomBytes[3]);
u[1].c0 = modfield(pseudoRandomBytes[4], pseudoRandomBytes[5]);
u[1].c1 = modfield(pseudoRandomBytes[6], pseudoRandomBytes[7]);
// 9. return (u_0, ..., u_(count - 1))
return u;
}

// passing two bytes32 instead of bytes memory saves approx 700 gas per call
// Computes the mod against the bls12-381 field modulus
function _modfield(bytes32 _b1, bytes32 _b2) private view returns (Fp memory r) {
function modfield(bytes32 _b1, bytes32 _b2) internal view returns (Fp memory r) {
(bool success, bytes memory output) = MOD_EXP.staticcall(
abi.encode(
// arg[0] = base.length
Expand All @@ -237,13 +271,15 @@ library BLS {
0x64774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab
)
);
require(success, "MODEXP failed");
if (!success) {
revert ModExpFailed();
}
return abi.decode(output, (Fp));
}

function hashToCurveG2(bytes32 message) internal view returns (G2Point memory) {
// 1. u = hash_to_field(msg, 2)
Fp2[2] memory u = hashToFieldFp2(message, DST);
Fp2[2] memory u = hashToFieldFp2(message);
// 2. Q0 = map_to_curve(u[0])
G2Point memory q0 = mapFp2ToG2(u[0]);
// 3. Q1 = map_to_curve(u[1])
Expand All @@ -252,43 +288,32 @@ library BLS {

// G2ADD address is 0x0e
(bool success, bytes memory output) = BLS12_G2ADD.staticcall(abi.encode(q0, q1));
require(success, "G2ADD failed");
if (!success) {
revert BLSG2AddFailed();
}
return abi.decode(output, (G2Point));
}

/// @notice Computes a field point from a message
/// @dev Follows https://datatracker.ietf.org/doc/html/rfc9380#section-5.3
/// @dev bytes32[] because len_in_bytes is always a multiple of 32 in our case even 128
/// @param message Arbitrarylength byte string to be hashed
/// @param dst The domain separation tag of at most 255 bytes
/// @param lenInBytes The length of the requested output in bytes
/// @param message byte32 to be hashed
/// @return A field point
function expandMsgXmd(
bytes32 message,
bytes memory dst,
uint16 lenInBytes
) internal pure returns (bytes32[] memory) {
function expandMsgXmd(bytes32 message) internal pure returns (bytes32[] memory) {
// 1. ell = ceil(len_in_bytes / b_in_bytes)
// b_in_bytes seems to be 32 for sha256
// ceil the division
uint256 ell = (lenInBytes - 1) / 32 + 1;

// 2. ABORT if ell > 255 or len_in_bytes > 65535 or len(DST) > 255
require(ell <= 255, "len_in_bytes too large for sha256");
// Not really needed because of parameter type
// require(lenInBytes <= 65535, "len_in_bytes too large");
// no length normalizing via hashing
require(dst.length <= 255, "dst too long");
uint256 ell = (MSG_LENGTH - 1) / 32 + 1;

bytes memory dstPrime = bytes.concat(dst, bytes1(uint8(dst.length)));
bytes memory dstPrime = bytes.concat(DST, bytes1(uint8(DST.length)));

// 4. Z_pad = I2OSP(0, s_in_bytes)
// this should be sha256 blocksize so 64 bytes
bytes memory zPad = new bytes(64);

// 5. l_i_b_str = I2OSP(len_in_bytes, 2)
// length in byte string?
bytes2 libStr = bytes2(lenInBytes);
bytes2 libStr = bytes2(MSG_LENGTH);

// 6. msg_prime = Z_pad || msg || l_i_b_str || I2OSP(0, 1) || DST_prime
bytes memory msgPrime = bytes.concat(zPad, message, libStr, hex"00", dstPrime);
Expand Down Expand Up @@ -318,9 +343,9 @@ library BLS {
DepositYComponents calldata depositY,
bytes32 withdrawalCredentials
) internal view {
// can we anything wut???
bytes32 message = SSZ.depositMessageSigningRoot(deposit, withdrawalCredentials);
G2Point memory msgG2 = hashToCurveG2(message);
// might be exsessive, need to check
validateG2Point(msgG2);

G1Point memory pubkeyG1 = decodeG1Point(deposit.pubkey, depositY.pubkeyY);
Expand All @@ -333,13 +358,27 @@ library BLS {

(bool success, bytes memory output) = BLS12_PAIRING_CHECK.staticcall(input);

if (!success) {
revert BLSPairingFailed();
}

bool result = abi.decode(output, (bool));

if (!success && !result) {
if (!result) {
revert InvalidSignature();
}
}

// Precompile errors
error BLSG1MsmFailed();
error BLSG2MsmFailed();
error ModExpFailed();
error BLSG2AddFailed();
error BLSPairingFailed();
error BLSMapFp2ToG2Failed();

// Signature errors
error InvalidSignature();
error SignatureContainsInfinityPoints();
error InputHasInfinityPoints();
error InputNotOnSubgroup();
}
30 changes: 0 additions & 30 deletions test/0.8.25/vaults/contracts/BLS.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,6 @@ contract BLSHarness is StdUtils {
function depositMessageSigningRoot(PrecomputedDepositMessage calldata message) public view returns (bytes32) {
return SSZ.depositMessageSigningRoot(message.deposit, message.withdrawalCredentials);
}

function hashToCurve(PrecomputedDepositMessage calldata message) public view {
console.log("msgG2");
BLS.G2Point memory msgG2 = BLS.hashToCurveG2(depositMessageSigningRoot(message));
console.logBytes(abi.encode(msgG2));

console.log("pubkeyG1");
BLS.G1Point memory pubkeyG1 = BLS.decodeG1Point(message.deposit.pubkey, message.depositYComponents.pubkeyY);
console.logBytes(abi.encode(pubkeyG1));

console.log("signatureG2");
BLS.G2Point memory signatureG2 = BLS.decodeG2Point(
message.deposit.signature,
message.depositYComponents.signatureY
);
console.logBytes(abi.encode(signatureG2));

console.log("NEGATED_G1_GENERATOR");
console.logBytes(abi.encode(BLS.NEGATED_G1_GENERATOR()));
}
}

contract BLSVerifyingKeyTest is Test {
Expand Down Expand Up @@ -80,16 +60,6 @@ contract BLSVerifyingKeyTest is Test {
harness.verifyDepositMessage(deposit);
}

function test_hashToCurve() external view {
PrecomputedDepositMessage memory message = STATIC_DEPOSIT_MESSAGE();
harness.hashToCurve(message);

console.log("Mainnet message");

PrecomputedDepositMessage memory _message = STATIC_MAINNET_MESSAGE();
harness.hashToCurve(_message);
}

function STATIC_DEPOSIT_MESSAGE() internal pure returns (PrecomputedDepositMessage memory) {
return
PrecomputedDepositMessage(
Expand Down

0 comments on commit 74664a2

Please sign in to comment.